20261028 #16

Merged
zzh merged 3 commits from test into master 2026-10-28 11:26:50 +08:00
3 changed files with 63 additions and 15 deletions
Showing only changes of commit ab37f59b1a - Show all commits

View File

@ -40,20 +40,16 @@ public static class McpToolCatalog
new( new(
ChannelGuide, ChannelGuide,
"查渠道对客说明", "查渠道对客说明",
"按销售产品代码或名称查询对客说明,判断客户品名是否可承运(禁运/可寄)。", "客户问某品名能不能寄、走什么渠道时调用。必填 cargoName;productCode/productName 可选(已知渠道时传入)。仅品名时按对客说明检索可走渠道。",
""" """
{ {
"type": "object", "type": "object",
"properties": { "properties": {
"productCode": { "type": "string", "description": "销售产品代码,优先使用" }, "productCode": { "type": "string", "description": "销售产品代码,可选;客户已指定渠道时优先" },
"productName": { "type": "string", "description": "销售产品名称关键词(无代码时用)" }, "productName": { "type": "string", "description": "销售产品/渠道名称关键词,可选" },
"cargoName": { "type": "string", "description": "客户品名,用于对照说明判断可否承运" } "cargoName": { "type": "string", "description": "客户品名,如 lipstick、化妆品;必填" }
}, },
"required": ["cargoName"], "required": ["cargoName"]
"anyOf": [
{ "required": ["productCode"] },
{ "required": ["productName"] }
]
} }
""", """,
20), 20),

View File

@ -153,6 +153,13 @@ public class McpToolExecutor(
var productName = GetString(root, "productName", "ProductName"); var productName = GetString(root, "productName", "ProductName");
var cargoName = GetString(root, "cargoName", "CargoName"); var cargoName = GetString(root, "cargoName", "CargoName");
if (string.IsNullOrWhiteSpace(cargoName)
&& string.IsNullOrWhiteSpace(productCode)
&& string.IsNullOrWhiteSpace(productName))
{
return (false, "请至少提供 cargoName(客户品名),或 productCode / productName");
}
var products = new List<Oms.Domain.ChannelsAggregate.Entities.SalesProduct>(); var products = new List<Oms.Domain.ChannelsAggregate.Entities.SalesProduct>();
if (!string.IsNullOrWhiteSpace(productCode)) if (!string.IsNullOrWhiteSpace(productCode))
{ {
@ -166,14 +173,51 @@ public class McpToolExecutor(
var list = await salesProductRepository.GetListAsync( var list = await salesProductRepository.GetListAsync(
x => x.IsActive == EnableStatus.Enabled x => x.IsActive == EnableStatus.Enabled
&& (x.ProductName.Contains(keyword) || x.ProductCode.Contains(keyword))); && (x.ProductName.Contains(keyword) || x.ProductCode.Contains(keyword)));
products.AddRange(list.Take(5)); products.AddRange(list.Take(8));
}
// 仅品名:在已启用且配置了对客说明的产品中检索可走渠道
if (products.Count == 0 && !string.IsNullOrWhiteSpace(cargoName))
{
var keyword = cargoName.Trim();
var withGuide = await salesProductRepository.GetListAsync(
x => x.IsActive == EnableStatus.Enabled
&& x.CustomerGuideMarkdown != null
&& x.CustomerGuideMarkdown != "");
static bool ContainsIgnoreCase(string? haystack, string needle) =>
!string.IsNullOrEmpty(haystack)
&& haystack.Contains(needle, StringComparison.OrdinalIgnoreCase);
var matched = withGuide
.Where(p =>
ContainsIgnoreCase(p.CustomerGuideMarkdown, keyword)
|| ContainsIgnoreCase(p.ProductName, keyword)
|| ContainsIgnoreCase(p.ProductCode, keyword))
.Take(10)
.ToList();
if (matched.Count > 0)
{
products.AddRange(matched);
}
else
{
return (true, JsonSerializer.Serialize(new
{
items = Array.Empty<object>(),
cargoName = keyword,
hint = "未在已配置对客说明的渠道中命中该品名。请换关键词,或补充销售产品代码/名称后再查;不要擅自判定可寄。"
}, JsonOptions));
}
} }
if (products.Count == 0) if (products.Count == 0)
{ {
return (false, "未找到销售产品,请提供 productCode 或 productName"); return (false, "未找到销售产品,请提供 productCode、productName 或 cargoName");
} }
var cargo = string.IsNullOrWhiteSpace(cargoName) ? null : cargoName.Trim();
var items = products.Select(p => var items = products.Select(p =>
{ {
var guide = p.CustomerGuideMarkdown?.Trim() ?? string.Empty; var guide = p.CustomerGuideMarkdown?.Trim() ?? string.Empty;
@ -184,14 +228,19 @@ public class McpToolExecutor(
productName = p.ProductName, productName = p.ProductName,
hasGuide = !string.IsNullOrWhiteSpace(guide), hasGuide = !string.IsNullOrWhiteSpace(guide),
customerGuideMarkdown = string.IsNullOrWhiteSpace(guide) ? null : guide, customerGuideMarkdown = string.IsNullOrWhiteSpace(guide) ? null : guide,
cargoNameToCheck = string.IsNullOrWhiteSpace(cargoName) ? null : cargoName.Trim(), cargoNameToCheck = cargo,
hint = string.IsNullOrWhiteSpace(guide) hint = string.IsNullOrWhiteSpace(guide)
? "该产品尚未配置对客说明,无法据此判断品名可否承运。" ? "该产品尚未配置对客说明,无法据此判断品名可否承运。"
: "请根据对客说明判断 cargoName 是否可寄;说明未提及的品名不要擅自判定可寄。" : "请根据对客说明判断 cargoName 是否可寄、是否适合该渠道;说明未提及的品名不要擅自判定可寄。"
}; };
}).ToList(); }).ToList();
return (true, JsonSerializer.Serialize(new { items }, JsonOptions)); return (true, JsonSerializer.Serialize(new
{
items,
cargoName = cargo,
hint = "用一两句中文告诉客户:哪些渠道对客说明支持该品名、哪些不支持;可列出产品代码/名称。"
}, JsonOptions));
} }
private async Task<(bool, string)> TrackQueryAsync(string argumentsJson, CancellationToken cancellationToken) private async Task<(bool, string)> TrackQueryAsync(string argumentsJson, CancellationToken cancellationToken)

View File

@ -149,7 +149,10 @@ public class McpToolService : ApplicationService
"不要索要起运地、发货城市、仓库(用户提到深圳等可忽略或作备注)。" + "不要索要起运地、发货城市、仓库(用户提到深圳等可忽略或作备注)。" +
"国家可用中文,调工具时转二字码(美国→US、英国→GB)。" + "国家可用中文,调工具时转二字码(美国→US、英国→GB)。" +
"用户已给目的国+重量时,必须立刻调用 oms.price_check,用一两句中文总结结果。" + "用户已给目的国+重量时,必须立刻调用 oms.price_check,用一两句中文总结结果。" +
"\n2) 对客说明/可承运:必填销售产品代码或名称 + 客户品名;缺一再追问。" + "\n2) 对客说明/可承运/走什么渠道:用户问某品名能不能寄、能不能走、走什么渠道时,必须立刻调用 oms.channel_guide。" +
"参数示例:{\"productCode\":\"\",\"productName\":\"\",\"cargoName\":\"lipstick\"}。" +
"cargoName 必填(从用户话里提取品名);productCode/productName 仅在客户已点名渠道时填写,否则留空由工具按品名检索。" +
"有结果后用一两句中文说明可走/不可走的渠道;未命中时如实说,不要编造。" +
"\n3) 查轨迹:消息里已有运单号/跟踪号/客户单号(如 IDE 开头)时,立刻调用 oms.track_query,禁止再向用户要单号。" + "\n3) 查轨迹:消息里已有运单号/跟踪号/客户单号(如 IDE 开头)时,立刻调用 oms.track_query,禁止再向用户要单号。" +
"\n4) 禁止空回复;信息不足时用一两句中文追问缺项;不要复述 JSON 或大段工具原文。" + "\n4) 禁止空回复;信息不足时用一两句中文追问缺项;不要复述 JSON 或大段工具原文。" +
"\n【省 token】回复尽量短;一次只调必需工具。" "\n【省 token】回复尽量短;一次只调必需工具。"