feat: complete quality/observability/KB port batch
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user