feat: complete quality/observability/KB port batch

This commit is contained in:
wangliang
2026-10-23 11:42:51 +08:00
parent b778570045
commit f343bf1832
12 changed files with 202 additions and 2 deletions
+28
View File
@@ -21,6 +21,8 @@ const { createFlywheelWriter } = require('./flywheel');
const { languageMandate } = require('./language');
const { createEscalation } = require('./escalation');
const { createExperimentManager } = require('./experiments');
const { createTraceWriter } = require('./trace-writer');
const { extractLeadFields } = require('./lead-extract');
const crypto = require('crypto');
const prom = require('./prom');
@@ -49,6 +51,7 @@ function createAgent(cfg, deps = {}) {
const llmCache = deps.llmCache || createLlmCache(cfg);
const escalation = deps.escalation || createEscalation({ client, handoffUserId: cfg.handoffUserId || 1 });
const experiments = deps.experiments || createExperimentManager(cfg);
const traceWriter = deps.traceWriter || createTraceWriter(metrics.pool, cfg.traceEnabled);
const flywheel = deps.flywheel || createFlywheelWriter({
pool: metrics.pool,
callLLM: async (messages, system, maxTokens) => {
@@ -104,6 +107,8 @@ function createAgent(cfg, deps = {}) {
if (!llm.enabled) throw new Error('LLM is not configured');
const startedAt = Date.now();
const stats = { llmCalls: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0, toolCalls: [], toolResults: [] };
const traceSteps = [];
const traceId = `${conv.uuid}:${webhookMessage.uuid || ''}`;
const profile = resolveSiteProfile(cfg, { inboxId: String(conv.inbox_id || '') });
const variant = experiments.getVariant(conv.uuid);
const identity = await identityFor(conv);
@@ -211,6 +216,7 @@ function createAgent(cfg, deps = {}) {
}
for (let step = 0; step < maxSteps; step += 1) {
prom.inc('bridge_ai_llm_calls_total');
const llmStarted = Date.now();
const response = await llm.chat({
messages,
tools: toolDefs,
@@ -218,6 +224,7 @@ function createAgent(cfg, deps = {}) {
maxTokens: (variant.overriddenParams && variant.overriddenParams.maxTokens) ?? 1200,
});
stats.llmCalls += 1;
traceSteps.push({ step_type: 'llm', turn: step, model: llm.model, prompt_tokens: response.usage?.prompt_tokens, completion_tokens: response.usage?.completion_tokens, duration_ms: Date.now() - llmStarted, finish_reason: response.finishReason || null });
if (response.usage) {
stats.promptTokens += Number(response.usage.prompt_tokens) || 0;
stats.completionTokens += Number(response.usage.completion_tokens) || 0;
@@ -242,6 +249,7 @@ function createAgent(cfg, deps = {}) {
result = { error: err.code || err.message || 'tool_failed', message: err.message || String(err) };
}
prom.inc('bridge_ai_tool_calls_total', { tool: name, ok: !(result && result.error) });
traceSteps.push({ step_type: 'tool', turn: step, tool_name: name, duration_ms: Date.now() - started });
stats.toolCalls.push({ name, ms: Date.now() - started, ok: !(result && result.error) });
stats.toolResults.push(result);
console.log(JSON.stringify({
@@ -264,6 +272,25 @@ function createAgent(cfg, deps = {}) {
if (!finalContent) {
finalContent = '抱歉,我这边暂时无法处理,请您稍后再试。';
}
// collect_info 兜底:消息含明显 lead 信息但 LLM 没调 collect_info 时,补抽槽位
if (buyingIntent && leadStore && !(stats.toolCalls || []).some((t) => t.name === 'collect_info')) {
try {
const latest = await leadStore.get(conv.uuid);
if (!latest || !latest.opportunityId) {
const fields = await extractLeadFields(llm, currentText);
if (fields) {
const state = latest ? { ...latest } : { slots: {}, createdFingerprints: [], opportunityId: null, stage: 'NEW' };
state.slots = { ...(state.slots || {}), ...fields };
if (leadStore.saveState) await leadStore.saveState(conv.uuid, state);
else await leadStore.save(conv.uuid, state.slots);
console.log(JSON.stringify({ ts: new Date().toISOString(), agent: 'lead_extract_fallback', conversation: conv.uuid, fields: Object.keys(fields) }));
}
}
} catch (err) {
console.error(JSON.stringify({ ts: new Date().toISOString(), warn: `lead extract fallback failed: ${err.message}` }));
}
}
// post-LLM 兜底:LLM 没问公司/联系方式时,代码强制追加多语言追问
let leadFollowup = false;
try {
@@ -420,6 +447,7 @@ function createAgent(cfg, deps = {}) {
replyLength: finalContent.length,
});
prom.inc('bridge_ai_runs_total');
await traceWriter.writeSteps(traceSteps, traceId, conv.uuid).catch(() => {});
console.log(JSON.stringify({ ts: new Date().toISOString(), agent: 'replied', conversation: conv.uuid, length: finalContent.length, audit }));
return finalContent;
}