From 22de20e7180037e353f0e718356d9d874b005d5f Mon Sep 17 00:00:00 2001 From: wangliang Date: Fri, 23 Oct 2026 14:25:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(bridge):=20=E5=95=86=E6=9C=BA=E6=8B=A6?= =?UTF-8?q?=E6=88=AA=E8=AF=9D=E6=9C=AF=E4=B8=8D=E4=BD=BF=E7=94=A8=E8=B4=AD?= =?UTF-8?q?=E7=89=A9=E8=BD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bridge/src/output-guard.js | 11 ++++++++++- bridge/test/output-guard.test.js | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/bridge/src/output-guard.js b/bridge/src/output-guard.js index 3a663dd..9e69690 100644 --- a/bridge/src/output-guard.js +++ b/bridge/src/output-guard.js @@ -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.`; } +// 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) { let out = String(content || ''); const priceText = lang === 'zh' ? '价格以商品页为准' : 'price shown on the product page'; @@ -155,6 +162,7 @@ function guardReply({ content, toolResults, toolCalls, lang = 'en' }) { blocked = true; rewritten = true; reasons.push(...violations.map((v) => v.type)); + const crmOnly = violations.every((v) => v.type === 'internal_crm' || v.type === 'opportunity_create'); // HTML 回复不做句子切割,避免破坏标签,直接安全话术 if (/<[a-z][^>]*>/i.test(raw)) { 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))) .join('') .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 })); } } diff --git a/bridge/test/output-guard.test.js b/bridge/test/output-guard.test.js index dc91976..d1f754d 100644 --- a/bridge/test/output-guard.test.js +++ b/bridge/test/output-guard.test.js @@ -180,3 +180,17 @@ test('guard: keeps meaningful anchor text when the URL is removed', () => { assert.doesNotMatch(r.content, /fake-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); +});