fix(bridge): 漏调 collect_info 时补抽线索

This commit is contained in:
wangliang
2026-10-23 14:36:47 +08:00
parent 4902cc632f
commit 751d9d63cd
4 changed files with 62 additions and 4 deletions
+36
View File
@@ -321,3 +321,39 @@ test('agent: does not auto-create when the LLM already created the opportunity',
await agent.run(conv, { uuid: 'msg-once', text_content: 'We are Acme GmbH, contact John Doe, we want 500 pieces.', content: '...' });
assert.equal(twenty.calls.filter((c) => c[0] === 'createOpportunity').length, 1);
});
test('agent: extracts lead fields when the LLM skips collect_info (company mentioned, unit-less quantity)', async () => {
const twenty = leadTwenty();
const tools = leadTools(twenty);
let sent = '';
let llmCalls = 0;
const deps = {
llm: {
enabled: true,
model: 'fake',
chat: async () => {
llmCalls += 1;
if (llmCalls === 1) {
return { content: 'Could you share your contact details so we can follow up?', toolCalls: [], usage: null };
}
// extractLeadFields 的调用:返回 JSON
return { content: JSON.stringify({ company: 'Leadfix GmbH', contactName: 'Anna Klein', productNeed: '500 stainless steel rings', quantity: '500' }), toolCalls: [], usage: null };
},
},
client: {
getMessages: async () => [{ id: 1, text_content: 'We are Leadfix GmbH, contact Anna Klein. We need 500 stainless steel rings.', sender_type: 'contact', private: false }],
sendMessage: async (_uuid, content) => { sent = content; },
sendPrivateNote: async () => {},
},
tools,
verification: { getVerifiedEmail: async () => null, sendCode: async () => ({}), verifyCode: async () => ({}) },
metrics: { record: async () => {}, enabled: false },
contextManager: { compressHistory: async (_c, h) => h },
};
const conv = { uuid: 'conv-lead-extract', inbox_id: '1', channel: 'livechat', contact: { type: 'visitor' } };
const agent = createAgent({ sites: [], defaultSite: 'www' }, deps);
await agent.run(conv, { uuid: 'msg-lead-extract', text_content: 'We are Leadfix GmbH, contact Anna Klein. We need 500 stainless steel rings.', content: '...' });
assert.equal(llmCalls >= 2, true);
assert.equal(twenty.calls.filter((c) => c[0] === 'createOpportunity').length, 1);
assert.ok(twenty.calls.some((c) => c[0] === 'createCompany' && c[1] === 'Leadfix GmbH'));
});
+9 -1
View File
@@ -2,7 +2,7 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const { hasBuyingIntent, missingLeadContacts, detectLanguage, appendLeadFollowup, alreadyAsksForLead } = require('../src/lead-intent');
const { hasBuyingIntent, looksLikeLead, missingLeadContacts, detectLanguage, appendLeadFollowup, alreadyAsksForLead } = require('../src/lead-intent');
test('lead-intent: product + quantity is buying intent', () => {
assert.equal(hasBuyingIntent('I want 500 pieces of stainless steel rings'), true);
@@ -26,3 +26,11 @@ test('lead-intent: detects existing ask in reply', () => {
assert.equal(alreadyAsksForLead('Could you share your company name and email?'), true);
assert.equal(alreadyAsksForLead('Here are some rings.'), false);
});
test('lead-intent: company/contact signal counts as a lead even without unit-quantity', () => {
// 单位不规整(500 stainless steel rings)时 hasBuyingIntent 为 false,但已是明确线索
assert.equal(hasBuyingIntent('We are Acme GmbH, contact Anna Klein. We need 500 stainless steel rings.'), false);
assert.equal(looksLikeLead('We are Acme GmbH, contact Anna Klein. We need 500 stainless steel rings.'), true);
assert.equal(looksLikeLead('Our company is Test Buyer BV, contact name Jan Jansen.'), false); // 没有商品词
assert.equal(looksLikeLead('Do you have rings?'), false); // 没有公司/联系人信号
});