@chatman-media/conversation-engine 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +134 -7
- package/dist/lead-advance.d.ts +26 -0
- package/dist/lead-advance.d.ts.map +1 -0
- package/dist/notifications.d.ts.map +1 -1
- package/dist/process-inbound.d.ts +8 -1
- package/dist/process-inbound.d.ts.map +1 -1
- package/dist/reply-strategy/llm-reply.d.ts +11 -0
- package/dist/reply-strategy/llm-reply.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1020,7 +1020,8 @@ class NotificationService {
|
|
|
1020
1020
|
human_takeover: "\uD83C\uDD98",
|
|
1021
1021
|
document_uploaded: "\uD83D\uDCF8",
|
|
1022
1022
|
high_value_deal: "\uD83D\uDC8E",
|
|
1023
|
-
lead_stale: "⏰"
|
|
1023
|
+
lead_stale: "⏰",
|
|
1024
|
+
operator_confirm_needed: "✋"
|
|
1024
1025
|
};
|
|
1025
1026
|
return map[type] ?? "\uD83D\uDD14";
|
|
1026
1027
|
}
|
|
@@ -1031,7 +1032,8 @@ class NotificationService {
|
|
|
1031
1032
|
human_takeover: "Нужна помощь оператора",
|
|
1032
1033
|
document_uploaded: "Загружен документ",
|
|
1033
1034
|
high_value_deal: "Крупная сделка",
|
|
1034
|
-
lead_stale: "Лид завис"
|
|
1035
|
+
lead_stale: "Лид завис",
|
|
1036
|
+
operator_confirm_needed: "Нужно подтверждение оператора"
|
|
1035
1037
|
};
|
|
1036
1038
|
return map[type] ?? "Уведомление";
|
|
1037
1039
|
}
|
|
@@ -2333,6 +2335,70 @@ async function generateReplyAndEnqueue(deps) {
|
|
|
2333
2335
|
});
|
|
2334
2336
|
return { outboundEnqueued: count };
|
|
2335
2337
|
}
|
|
2338
|
+
// src/lead-advance.ts
|
|
2339
|
+
import { funnels, leads as leadsTable2, stageDefinitions } from "@chatman-media/storage";
|
|
2340
|
+
import { and as and14, asc, eq as eq14 } from "drizzle-orm";
|
|
2341
|
+
var SALES_STAGE_TO_PHASE = {
|
|
2342
|
+
qualify: "qualify",
|
|
2343
|
+
pitch: "offer",
|
|
2344
|
+
objection: "clear",
|
|
2345
|
+
close: "fulfill"
|
|
2346
|
+
};
|
|
2347
|
+
async function ensureAndAdvanceLeadByPhase(opts) {
|
|
2348
|
+
const { db, tenantId, contactId, salesStage, nowEpoch } = opts;
|
|
2349
|
+
const stages = await db.select({
|
|
2350
|
+
id: stageDefinitions.id,
|
|
2351
|
+
slug: stageDefinitions.slug,
|
|
2352
|
+
phase: stageDefinitions.phase,
|
|
2353
|
+
kind: stageDefinitions.kind,
|
|
2354
|
+
position: stageDefinitions.position
|
|
2355
|
+
}).from(stageDefinitions).innerJoin(funnels, eq14(stageDefinitions.funnelId, funnels.id)).where(and14(eq14(funnels.tenantId, tenantId), eq14(funnels.isActive, true))).orderBy(asc(stageDefinitions.position));
|
|
2356
|
+
if (stages.length === 0)
|
|
2357
|
+
return null;
|
|
2358
|
+
const initial = stages.find((s) => s.kind === "intake") ?? stages[0];
|
|
2359
|
+
const [existing] = await db.select({ id: leadsTable2.id, state: leadsTable2.state, stageDefinitionId: leadsTable2.stageDefinitionId }).from(leadsTable2).where(and14(eq14(leadsTable2.tenantId, tenantId), eq14(leadsTable2.userId, contactId))).limit(1);
|
|
2360
|
+
let leadId;
|
|
2361
|
+
let curStageId;
|
|
2362
|
+
let curSlug;
|
|
2363
|
+
let created = false;
|
|
2364
|
+
if (existing) {
|
|
2365
|
+
leadId = existing.id;
|
|
2366
|
+
curStageId = existing.stageDefinitionId ?? null;
|
|
2367
|
+
curSlug = existing.state;
|
|
2368
|
+
} else {
|
|
2369
|
+
const [row] = await db.insert(leadsTable2).values({
|
|
2370
|
+
tenantId,
|
|
2371
|
+
userId: contactId,
|
|
2372
|
+
state: initial.slug,
|
|
2373
|
+
stageDefinitionId: initial.id,
|
|
2374
|
+
createdAt: nowEpoch,
|
|
2375
|
+
updatedAt: nowEpoch
|
|
2376
|
+
}).returning({ id: leadsTable2.id });
|
|
2377
|
+
if (!row)
|
|
2378
|
+
return null;
|
|
2379
|
+
leadId = row.id;
|
|
2380
|
+
curStageId = initial.id;
|
|
2381
|
+
curSlug = initial.slug;
|
|
2382
|
+
created = true;
|
|
2383
|
+
}
|
|
2384
|
+
const targetPhase = SALES_STAGE_TO_PHASE[salesStage];
|
|
2385
|
+
if (!targetPhase)
|
|
2386
|
+
return { leadId, stageSlug: curSlug, created, advanced: false };
|
|
2387
|
+
const curStage = curStageId != null ? stages.find((s) => s.id === curStageId) : undefined;
|
|
2388
|
+
const curPos = curStage?.position ?? -1;
|
|
2389
|
+
let target = salesStage === "close" && curStage?.phase === "fulfill" ? stages.find((s) => s.kind === "terminal_won") : undefined;
|
|
2390
|
+
if (!target) {
|
|
2391
|
+
target = stages.find((s) => s.phase === targetPhase && s.kind !== "terminal_won" && s.kind !== "terminal_lost");
|
|
2392
|
+
}
|
|
2393
|
+
if (!target)
|
|
2394
|
+
return { leadId, stageSlug: curSlug, created, advanced: false };
|
|
2395
|
+
if (target.position > curPos) {
|
|
2396
|
+
await db.update(leadsTable2).set({ stageDefinitionId: target.id, state: target.slug, updatedAt: nowEpoch }).where(and14(eq14(leadsTable2.id, leadId), eq14(leadsTable2.tenantId, tenantId)));
|
|
2397
|
+
return { leadId, stageSlug: target.slug, created, advanced: true };
|
|
2398
|
+
}
|
|
2399
|
+
return { leadId, stageSlug: curSlug, created, advanced: false };
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2336
2402
|
// src/process-inbound.ts
|
|
2337
2403
|
function inboundText(inbound) {
|
|
2338
2404
|
const textParts = [];
|
|
@@ -2518,6 +2584,33 @@ async function processInbound(inbound, deps) {
|
|
|
2518
2584
|
to: newStage
|
|
2519
2585
|
});
|
|
2520
2586
|
}
|
|
2587
|
+
if (newStage && newStage !== "opener" && deps.leads && deps.db) {
|
|
2588
|
+
try {
|
|
2589
|
+
const res = await ensureAndAdvanceLeadByPhase({
|
|
2590
|
+
db: deps.db,
|
|
2591
|
+
tenantId: deps.tenant.tenantId,
|
|
2592
|
+
contactId: contact.id,
|
|
2593
|
+
salesStage: newStage,
|
|
2594
|
+
nowEpoch: now
|
|
2595
|
+
});
|
|
2596
|
+
if (res && (res.created || res.advanced)) {
|
|
2597
|
+
deps.sink?.log?.("info", res.created ? "lead auto-created" : "lead advanced", {
|
|
2598
|
+
tenantId: deps.tenant.tenantId,
|
|
2599
|
+
conversationId: conversation.id,
|
|
2600
|
+
contactId: contact.id,
|
|
2601
|
+
leadId: res.leadId,
|
|
2602
|
+
stage: res.stageSlug,
|
|
2603
|
+
salesStage: newStage
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
} catch (err) {
|
|
2607
|
+
deps.sink?.log?.("warn", "lead auto-advance failed", {
|
|
2608
|
+
tenantId: deps.tenant.tenantId,
|
|
2609
|
+
conversationId: conversation.id,
|
|
2610
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2521
2614
|
} catch (err) {
|
|
2522
2615
|
deps.sink?.log?.("warn", "stage classifier failed", {
|
|
2523
2616
|
tenantId: deps.tenant.tenantId,
|
|
@@ -2609,6 +2702,7 @@ async function processInbound(inbound, deps) {
|
|
|
2609
2702
|
};
|
|
2610
2703
|
}
|
|
2611
2704
|
// src/reply-strategy/llm-reply.ts
|
|
2705
|
+
import { DEFAULT_MAX_TOOL_CYCLES, runToolLoop } from "@chatman-media/kb";
|
|
2612
2706
|
var BASE_SYSTEM_PROMPT = "Ты — операционный бот платформы lead-engine. Отвечай кратко, " + "уважительно, по делу. Никогда не выдумывай факты которых нет в " + "контексте — лучше скажи «уточню у партнёра» и поставь сообщение в очередь оператора.";
|
|
2613
2707
|
function messagesToChatHistory2(history) {
|
|
2614
2708
|
const out = [];
|
|
@@ -2642,14 +2736,47 @@ class LlmReplyStrategy {
|
|
|
2642
2736
|
const messages = this.messagesRepoFor(input.tenant.tenantId);
|
|
2643
2737
|
const history = await messages.recent(input.conversationId, this.opts.historyLimit ?? 20);
|
|
2644
2738
|
const historyMessages = messagesToChatHistory2(history);
|
|
2645
|
-
const systemPrompt = [BASE_SYSTEM_PROMPT, this.opts.template.systemPromptFragment].filter(Boolean).join(`
|
|
2646
|
-
|
|
2647
|
-
`);
|
|
2648
2739
|
const chat = this.opts.resolveChat(input.tenant.tenantId);
|
|
2649
|
-
const
|
|
2740
|
+
const llmOpts = {
|
|
2650
2741
|
temperature: this.opts.temperature ?? 0.7,
|
|
2651
2742
|
numPredict: this.opts.maxOutputTokens ?? 600
|
|
2652
|
-
}
|
|
2743
|
+
};
|
|
2744
|
+
let tools = [];
|
|
2745
|
+
if (this.opts.resolveTools) {
|
|
2746
|
+
try {
|
|
2747
|
+
tools = await this.opts.resolveTools({
|
|
2748
|
+
tenantId: input.tenant.tenantId,
|
|
2749
|
+
conversationId: input.conversationId
|
|
2750
|
+
});
|
|
2751
|
+
} catch {
|
|
2752
|
+
tools = [];
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
const toolsActive = tools.length > 0 && typeof chat.completeWithTools === "function";
|
|
2756
|
+
const systemPrompt = [
|
|
2757
|
+
BASE_SYSTEM_PROMPT,
|
|
2758
|
+
toolsActive ? "Если для ответа есть подходящий инструмент (например, расчёт курса обмена) — " + "ОБЯЗАТЕЛЬНО вызови его и дай конкретные числа. Не отсылай к оператору, если можешь " + "ответить инструментом." : "",
|
|
2759
|
+
this.opts.template.systemPromptFragment
|
|
2760
|
+
].filter(Boolean).join(`
|
|
2761
|
+
|
|
2762
|
+
`);
|
|
2763
|
+
const msgs = [
|
|
2764
|
+
{ role: "system", content: systemPrompt },
|
|
2765
|
+
...historyMessages
|
|
2766
|
+
];
|
|
2767
|
+
let reply;
|
|
2768
|
+
if (toolsActive) {
|
|
2769
|
+
const loop = await runToolLoop({
|
|
2770
|
+
chat,
|
|
2771
|
+
messages: msgs,
|
|
2772
|
+
tools,
|
|
2773
|
+
llmOpts,
|
|
2774
|
+
maxCycles: DEFAULT_MAX_TOOL_CYCLES
|
|
2775
|
+
});
|
|
2776
|
+
reply = loop.content ?? await chat.complete(msgs, llmOpts);
|
|
2777
|
+
} else {
|
|
2778
|
+
reply = await chat.complete(msgs, llmOpts);
|
|
2779
|
+
}
|
|
2653
2780
|
if (reply.trim().length === 0)
|
|
2654
2781
|
return null;
|
|
2655
2782
|
return [
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Db } from "./dal/types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Find-or-create лид для (tenant, contact) на активной воронке тенанта и
|
|
4
|
+
* продвигает его вперёд до фазы, соответствующей текущей sales-стадии диалога.
|
|
5
|
+
* Двигает ТОЛЬКО вперёд (по position); назад не откатывает.
|
|
6
|
+
*
|
|
7
|
+
* Обновляет lead.state + lead.stageDefinitionId синхронно (как admin-leads /
|
|
8
|
+
* field-extractor). Работает поверх DB-воронки (stage_definitions), а не
|
|
9
|
+
* template.funnelStages — это «живая» позиция в кастомной воронке тенанта.
|
|
10
|
+
*
|
|
11
|
+
* Возвращает null если у тенанта нет активной воронки со стадиями.
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensureAndAdvanceLeadByPhase(opts: {
|
|
14
|
+
db: Db;
|
|
15
|
+
tenantId: number;
|
|
16
|
+
contactId: number;
|
|
17
|
+
/** sales-стадия из stage-classifier (opener|qualify|pitch|objection|close). */
|
|
18
|
+
salesStage: string;
|
|
19
|
+
nowEpoch: number;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
leadId: number;
|
|
22
|
+
stageSlug: string;
|
|
23
|
+
created: boolean;
|
|
24
|
+
advanced: boolean;
|
|
25
|
+
} | null>;
|
|
26
|
+
//# sourceMappingURL=lead-advance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lead-advance.d.ts","sourceRoot":"","sources":["../src/lead-advance.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAoBzC;;;;;;;;;;GAUG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE;IACtD,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,CAmF7F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAElF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,qBAAa,mBAAmB;IAI5B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAX5B,OAAO,CAAC,MAAM,CAA+B;gBAG1B,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;IAC/B;;;;OAIG;IACc,QAAQ,CAAC,EAAE,aAAa,YAAA;IAOrC,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmD/C,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;YAcjE,WAAW;IAazB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO;IAa3E,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM;IAc9D,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM;IAsB/C,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;
|
|
1
|
+
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAElF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,qBAAa,mBAAmB;IAI5B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAX5B,OAAO,CAAC,MAAM,CAA+B;gBAG1B,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;IAC/B;;;;OAIG;IACc,QAAQ,CAAC,EAAE,aAAa,YAAA;IAOrC,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmD/C,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;YAcjE,WAAW;IAazB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO;IAa3E,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM;IAc9D,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM;IAsB/C,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,SAAS;CAUlB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Inbound, OutboundEnvelope } from "@chatman-media/channel-core";
|
|
2
2
|
import type { VerticalTemplate } from "@chatman-media/verticals";
|
|
3
|
-
import type { ChannelIdentitiesRepo, ContactsRepo, ConversationsRepo, MessagesRepo, OutboundQueueRepo } from "./dal/index.ts";
|
|
3
|
+
import type { ChannelIdentitiesRepo, ContactsRepo, ConversationsRepo, LeadsRepo, MessagesRepo, OutboundQueueRepo } from "./dal/index.ts";
|
|
4
4
|
import { type MemoryExtractor } from "./memory-extractor.ts";
|
|
5
5
|
import { type StageClassifier } from "./stage-classifier.ts";
|
|
6
6
|
import type { ITranscriber } from "./transcriber.ts";
|
|
@@ -37,6 +37,13 @@ export interface ProcessInboundDeps {
|
|
|
37
37
|
conversations: ConversationsRepo;
|
|
38
38
|
messages: MessagesRepo;
|
|
39
39
|
outbound: OutboundQueueRepo;
|
|
40
|
+
/**
|
|
41
|
+
* Опциональный LeadsRepo. Если задан вместе с `template` и `stageClassifier`,
|
|
42
|
+
* pipeline авто-создаёт Lead, как только цель/интент клиента определён
|
|
43
|
+
* (stage classifier вернул стадию ≠ "opener"). Без него лиды не создаются
|
|
44
|
+
* из диалога (legacy-поведение).
|
|
45
|
+
*/
|
|
46
|
+
leads?: LeadsRepo;
|
|
40
47
|
/** Стратегия ответа. null = pipeline сохраняет inbound и не отвечает. */
|
|
41
48
|
reply?: ReplyStrategy | null;
|
|
42
49
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-inbound.d.ts","sourceRoot":"","sources":["../src/process-inbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,KAAK,EACV,qBAAqB,EACrB,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"process-inbound.d.ts","sourceRoot":"","sources":["../src/process-inbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,KAAK,EACV,qBAAqB,EACrB,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,KAAK,eAAe,EAAuB,MAAM,uBAAuB,CAAC;AAElF,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,cAAc,EACd,KAAK,KAAK,EACV,KAAK,YAAY,EACjB,KAAK,oBAAoB,EAEzB,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE;QACb,MAAM,EAAE,aAAa,CAAC;QACtB,OAAO,EAAE,cAAc,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,qBAAqB,CAAC;IAClC,aAAa,EAAE,iBAAiB,CAAC;IACjC,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,yEAAyE;IACzE,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,EAAE,CAAC,EAAE,OAAO,gBAAgB,EAAE,EAAE,CAAC;IACjC;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,yFAAyF;IACzF,aAAa,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,OAAO,6BAA6B,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC;CAClI;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,oBAAoB,CAAC,CA4V/B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OutboundEnvelope } from "@chatman-media/channel-core";
|
|
2
|
+
import { type AnyRagTool } from "@chatman-media/kb";
|
|
2
3
|
import type { ChatClient } from "@chatman-media/llm-router";
|
|
3
4
|
import type { VerticalTemplate } from "@chatman-media/verticals";
|
|
4
5
|
import type { MessagesRepo } from "../dal/messages.ts";
|
|
@@ -46,6 +47,16 @@ export interface LlmReplyStrategyOpts {
|
|
|
46
47
|
tenantId: number;
|
|
47
48
|
contactId: number;
|
|
48
49
|
}) => Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* Опциональный резолвер agentic-инструментов (напр. расчёт курса обмена).
|
|
52
|
+
* Если задан и вернул непустой список, а ChatClient умеет completeWithTools —
|
|
53
|
+
* strategy прогоняет tool-loop, чтобы бот мог дать конкретный ответ
|
|
54
|
+
* (курс/сумму) даже без RAG/эмбеддингов, а не уходить в «уточню у партнёра».
|
|
55
|
+
*/
|
|
56
|
+
resolveTools?: (input: {
|
|
57
|
+
tenantId: number;
|
|
58
|
+
conversationId: number;
|
|
59
|
+
}) => Promise<AnyRagTool[]> | AnyRagTool[];
|
|
49
60
|
}
|
|
50
61
|
export declare class LlmReplyStrategy implements ReplyStrategy {
|
|
51
62
|
private readonly opts;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm-reply.d.ts","sourceRoot":"","sources":["../../src/reply-strategy/llm-reply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,CAAC;IAC9C,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"llm-reply.d.ts","sourceRoot":"","sources":["../../src/reply-strategy/llm-reply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,KAAK,UAAU,EAAwC,MAAM,mBAAmB,CAAC;AAC1F,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,CAAC;IAC9C,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxF;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC;CAC5C;AAkBD,qBAAa,gBAAiB,YAAW,aAAa;IAElD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,eAAe;gBADf,IAAI,EAAE,oBAAoB,EAC1B,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,YAAY;IAGhE,QAAQ,CAAC,KAAK,EAAE;QACpB,MAAM,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QAC7B,OAAO,EAAE;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/B,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE;YAAE,cAAc,EAAE,MAAM,CAAA;SAAE,CAAC;QACpC,eAAe,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC;CA8EvC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatman-media/conversation-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Channel-agnostic pipeline обработки inbound сообщений: contact-resolve → conversation lookup → mode routing → AI-reply / queued / human → outbound. Сердце data plane.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|