fix(bridge): 商机拦截话术不使用购物车

This commit is contained in:
wangliang
2026-10-23 14:25:03 +08:00
parent 9ce3120648
commit 22de20e718
2 changed files with 24 additions and 1 deletions
+10 -1
View File
@@ -98,6 +98,13 @@ function actionClarification(lang, hasOpportunity) {
return `${hasOpportunity ? 'Your request has been noted; a relevant colleague will follow up.\n\n' : ''}Note: I can't place orders, create order numbers, or send emails/PDFs from this chat; please complete the order in your account cart: https://www.yehwang.com/cart.`; return `${hasOpportunity ? 'Your request has been noted; a relevant colleague will follow up.\n\n' : ''}Note: I can't place orders, create order numbers, or send emails/PDFs from this chat; please complete the order in your account cart: https://www.yehwang.com/cart.`;
} }
// CRM/商机类措辞被拦时:只把真实发生了的事(需求已记录、同事跟进)告诉客户,
// 不要拼接“无法下单/购物车付款”这类误导性的订单话术。
function crmClarification(lang, hasOpportunity) {
if (lang === 'zh') return hasOpportunity ? '您的需求已记录,相关同事会与您联系。' : '相关同事会与您联系。';
return hasOpportunity ? 'Your request has been noted; a relevant colleague will follow up.' : 'A relevant colleague will follow up.';
}
function replaceUnsupported(content, amounts, stockNumbers, lang) { function replaceUnsupported(content, amounts, stockNumbers, lang) {
let out = String(content || ''); let out = String(content || '');
const priceText = lang === 'zh' ? '价格以商品页为准' : 'price shown on the product page'; const priceText = lang === 'zh' ? '价格以商品页为准' : 'price shown on the product page';
@@ -155,6 +162,7 @@ function guardReply({ content, toolResults, toolCalls, lang = 'en' }) {
blocked = true; blocked = true;
rewritten = true; rewritten = true;
reasons.push(...violations.map((v) => v.type)); reasons.push(...violations.map((v) => v.type));
const crmOnly = violations.every((v) => v.type === 'internal_crm' || v.type === 'opportunity_create');
// HTML 回复不做句子切割,避免破坏标签,直接安全话术 // HTML 回复不做句子切割,避免破坏标签,直接安全话术
if (/<[a-z][^>]*>/i.test(raw)) { if (/<[a-z][^>]*>/i.test(raw)) {
out = safeReply(lang, { hasOpportunity }); out = safeReply(lang, { hasOpportunity });
@@ -163,7 +171,8 @@ function guardReply({ content, toolResults, toolCalls, lang = 'en' }) {
.filter((sentence) => !detectActionClaims(sentence).some((h) => !h.requiresTool || !hasTool(toolCalls, h.requiresTool))) .filter((sentence) => !detectActionClaims(sentence).some((h) => !h.requiresTool || !hasTool(toolCalls, h.requiresTool)))
.join('') .join('')
.trim(); .trim();
out = isMeaningful(kept) ? `${kept}\n\n${actionClarification(lang, hasOpportunity)}` : safeReply(lang, { hasOpportunity }); const clarification = crmOnly ? crmClarification(lang, hasOpportunity) : actionClarification(lang, hasOpportunity);
out = isMeaningful(kept) ? `${kept}\n\n${clarification}` : (crmOnly ? crmClarification(lang, hasOpportunity) : safeReply(lang, { hasOpportunity }));
} }
} }
+14
View File
@@ -180,3 +180,17 @@ test('guard: keeps meaningful anchor text when the URL is removed', () => {
assert.doesNotMatch(r.content, /fake-guide/); assert.doesNotMatch(r.content, /fake-guide/);
assert.match(r.content, /the product care guide/); assert.match(r.content, /the product care guide/);
}); });
test('guard: CRM wording is replaced with a colleague follow-up, not a cart note', () => {
const r = guardReply({
content: 'Great — 500 stainless steel rings for your shops.\nI have created an opportunity for your request.',
toolResults: [{ created: true, opportunity_id: 'opp-1' }],
toolCalls: [{ name: 'create_opportunity', ok: true }],
lang: 'en',
});
assert.equal(r.blocked, true);
assert.ok(r.reasons.includes('internal_crm'));
assert.match(r.content, /stainless steel rings/i);
assert.match(r.content, /request has been noted/i);
assert.doesNotMatch(r.content, /account cart/i);
});