@chatman-media/kb 1.4.0 → 1.6.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/answer-types.d.ts +10 -0
- package/dist/answer-types.d.ts.map +1 -1
- package/dist/answer.d.ts.map +1 -1
- package/dist/answer.test.d.ts +2 -0
- package/dist/answer.test.d.ts.map +1 -0
- package/dist/index.js +26 -3
- package/dist/ingest.test.d.ts +2 -0
- package/dist/ingest.test.d.ts.map +1 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/styles.d.ts +11 -0
- package/dist/styles.d.ts.map +1 -1
- package/dist/system-prompt.test.d.ts +2 -0
- package/dist/system-prompt.test.d.ts.map +1 -0
- package/dist/vision.test.d.ts +2 -0
- package/dist/vision.test.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/answer-types.ts +10 -0
- package/src/answer.test.ts +380 -0
- package/src/answer.ts +22 -2
- package/src/ingest.test.ts +178 -0
- package/src/prompt.test.ts +175 -0
- package/src/prompt.ts +13 -0
- package/src/styles.ts +11 -0
- package/src/system-prompt.test.ts +81 -0
- package/src/vision.test.ts +199 -0
package/dist/answer-types.d.ts
CHANGED
|
@@ -63,6 +63,16 @@ export interface AnswerInput {
|
|
|
63
63
|
goal: string;
|
|
64
64
|
guidance?: string;
|
|
65
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* Dynamic per-request context (R4 / multi-request): the guest's current
|
|
68
|
+
* request_type + how many open requests. Injected as a separate prompt block.
|
|
69
|
+
*/
|
|
70
|
+
requestContext?: string;
|
|
71
|
+
/**
|
|
72
|
+
* The guest's current lead is on an `awaiting_operator` stage (R5): the bot
|
|
73
|
+
* holds and defers pricing/decisions to a human operator.
|
|
74
|
+
*/
|
|
75
|
+
awaitingOperator?: boolean;
|
|
66
76
|
/**
|
|
67
77
|
* Called after every `answerWithRag` or `answerWithRagStream` call with the
|
|
68
78
|
* final telemetry. Useful for logging, metrics, or A/B experiment recording
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"answer-types.d.ts","sourceRoot":"","sources":["../src/answer-types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"answer-types.d.ts","sourceRoot":"","sources":["../src/answer-types.ts"],"names":[],"mappings":"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"}
|
package/dist/answer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../src/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EAGjB,KAAK,OAAO,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../src/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EAGjB,KAAK,OAAO,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAmCzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,iBAAiB,EACjB,KAAK,OAAO,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAa5B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oEAAoE;IACpE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAgG/E;AAgPD;;;;;;;;GAQG;AACH,wBAAuB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CA6HpF;AAED,wBAAsB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACxD,KAAK,EAAE,WAAW,GAAG;IAAE,YAAY,EAAE,CAAC,CAAA;CAAE,GACvC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;AAiG/E;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answer.test.d.ts","sourceRoot":"","sources":["../src/answer.test.ts"],"names":[],"mappings":""}
|
package/dist/index.js
CHANGED
|
@@ -45106,6 +45106,8 @@ ${triggerLine}${h.body}`;
|
|
|
45106
45106
|
` + `\u0426\u0415\u041B\u042C \u042D\u0422\u0410\u041F\u0410: ${effGoal}.` + (effGuidance ? `
|
|
45107
45107
|
\u041A\u0410\u041A: ${effGuidance}` : "") + (stageCfg?.groundingRequired ? `
|
|
45108
45108
|
GROUNDING: \u043D\u0430 \u044D\u0442\u043E\u043C \u044D\u0442\u0430\u043F\u0435 \u0432\u0441\u0435 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0435 \u0444\u0430\u043A\u0442\u044B (\u0446\u0438\u0444\u0440\u044B, \u0441\u0443\u043C\u043C\u044B, \u0441\u0440\u043E\u043A\u0438) \u0431\u0435\u0440\u0438 \u0422\u041E\u041B\u042C\u041A\u041E \u0438\u0437 \u0441\u0435\u043A\u0446\u0438\u0438 KB CONTEXT \u043D\u0438\u0436\u0435. \u0415\u0441\u043B\u0438 \u0435\u0451 \u043D\u0435\u0442 \u0438\u043B\u0438 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u0444\u0430\u043A\u0442\u0430 \u0432 \u043D\u0435\u0439 \u043D\u0435\u0442 \u2014 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439, \u0441\u043A\u0430\u0436\u0438 \u0447\u0442\u043E \u0443\u0442\u043E\u0447\u043D\u0438\u0448\u044C.` : "") : `\u0422\u0415\u041A\u0423\u0429\u0418\u0419 \u042D\u0422\u0410\u041F: ${stage}. (\u0421\u043F\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u0441\u043A\u0438\u0445 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043B\u044F \u044D\u0442\u0430\u043F\u0430 \u043D\u0435\u0442 \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u043E\u0431\u0449\u0438\u0439 \u0441\u0442\u0438\u043B\u044C.)`;
|
|
45109
|
+
const requestBlock = options.requestContext ? `\u0417\u0410\u041F\u0420\u041E\u0421 \u0413\u041E\u0421\u0422\u042F: ${options.requestContext}` : "";
|
|
45110
|
+
const operatorBlock = options.awaitingOperator ? `\u041E\u0416\u0418\u0414\u0410\u041D\u0418\u0415 \u041E\u041F\u0415\u0420\u0410\u0422\u041E\u0420\u0410: \u043D\u0430 \u044D\u0442\u043E\u0439 \u0441\u0442\u0430\u0434\u0438\u0438 \u0443\u0441\u043B\u043E\u0432\u0438\u044F/\u0446\u0435\u043D\u0443/\u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0433\u043E\u0442\u043E\u0432\u0438\u0442 \u043A\u043E\u043B\u043B\u0435\u0433\u0430-\u0447\u0435\u043B\u043E\u0432\u0435\u043A. \u041D\u0415 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u0447\u0438\u0441\u043B\u0430 \u0438 \u0434\u0435\u0442\u0430\u043B\u0438. \u0421\u043A\u0430\u0436\u0438 \u0433\u043E\u0441\u0442\u044E \u043A\u043E\u0440\u043E\u0442\u043A\u043E, \u0447\u0442\u043E \u0443\u0442\u043E\u0447\u043D\u044F\u0435\u0448\u044C \u0438 \u0432\u0435\u0440\u043D\u0451\u0448\u044C\u0441\u044F \u0441 \u043E\u0442\u0432\u0435\u0442\u043E\u043C \u2014 \u0438 \u0436\u0434\u0438, \u043D\u0435 \u0434\u0430\u0432\u0438 \u0438 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u0439 \u0441\u0434\u0435\u043B\u043A\u0443.` : "";
|
|
45109
45111
|
const minorRule = guardrails.noMinors ? "- \u0415\u0441\u043B\u0438 prospect <18 \u043B\u0435\u0442 \u2014 \u0432\u0435\u0436\u043B\u0438\u0432\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0434\u0438\u0430\u043B\u043E\u0433." : "";
|
|
45110
45112
|
const topicsRule = guardrails.forbiddenTopics.length ? `- \u0417\u0430\u043F\u0440\u0435\u0449\u0451\u043D\u043D\u044B\u0435 \u0442\u0435\u043C\u044B: ${guardrails.forbiddenTopics.join(", ")}.` : "";
|
|
45111
45113
|
const brevityRule = persona.role === "human" ? `- \u041F\u0438\u0448\u0438 \u043A\u0430\u043A \u0432 \u0436\u0438\u0432\u043E\u043C \u0447\u0430\u0442\u0435: 2\u20136 \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0445 \u0444\u0440\u0430\u0437 \u043C\u043E\u0436\u043D\u043E, \u0435\u0441\u043B\u0438 \u043D\u0443\u0436\u043D\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u0442\u044C \u0443\u0441\u043B\u043E\u0432\u0438\u044F. \u0411\u0435\u0437 markdown-\u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u043E\u0432. ` + `\u0421\u043F\u0438\u0441\u043A\u043E\u043C \u0441 \u043D\u043E\u043C\u0435\u0440\u0430\u043C\u0438 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0441\u0430\u043C \u043F\u0440\u043E\u0441\u0438\u0442 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443.` : `- \u041F\u0438\u0448\u0438 \u043A\u043E\u0440\u043E\u0442\u043A\u043E: 1-3 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u044F. \u0411\u0435\u0437 markdown-\u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u043E\u0432 \u0438 \u043D\u0443\u043C\u0435\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u0441\u043F\u0438\u0441\u043A\u043E\u0432.`;
|
|
@@ -45132,6 +45134,8 @@ ${preFetchedKbContext}` : "";
|
|
|
45132
45134
|
support ? "" : directorHooksBlock,
|
|
45133
45135
|
support ? "" : skillsBlock,
|
|
45134
45136
|
support || stageBlock,
|
|
45137
|
+
operatorBlock,
|
|
45138
|
+
requestBlock,
|
|
45135
45139
|
summaryBlock,
|
|
45136
45140
|
userFactsBlock,
|
|
45137
45141
|
needsGroundingReminder ? kbGroundingReminder(persona.role) : "",
|
|
@@ -59799,6 +59803,17 @@ function classifyTopicAll(question) {
|
|
|
59799
59803
|
var KNOWN_TOPICS = TOPIC_PATTERNS.map((p) => p.topic);
|
|
59800
59804
|
|
|
59801
59805
|
// src/answer.ts
|
|
59806
|
+
function toolCallsToGroundingContext(records) {
|
|
59807
|
+
if (!records || records.length === 0)
|
|
59808
|
+
return "";
|
|
59809
|
+
const lines = records.map((record2, index) => {
|
|
59810
|
+
const result = typeof record2.result === "string" ? record2.result : JSON.stringify(record2.result);
|
|
59811
|
+
return `[#tool-${index + 1}] ${record2.name}: ${result ?? ""}`;
|
|
59812
|
+
});
|
|
59813
|
+
return `TOOL RESULTS:
|
|
59814
|
+
${lines.join(`
|
|
59815
|
+
`)}`;
|
|
59816
|
+
}
|
|
59802
59817
|
async function retrieveHits(input) {
|
|
59803
59818
|
const topK = input.topK ?? 5;
|
|
59804
59819
|
const candidateK = input.reranker ? topK * 3 : topK;
|
|
@@ -59908,7 +59923,9 @@ ${kbContextStr}` : vacBlock : kbContextStr;
|
|
|
59908
59923
|
...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
|
|
59909
59924
|
...input.directorHooks && input.directorHooks.length > 0 ? { directorHooks: input.directorHooks } : {},
|
|
59910
59925
|
...input.supportPhase ? { supportPhase: input.supportPhase } : {},
|
|
59911
|
-
...input.stageOverride ? { stageOverride: input.stageOverride } : {}
|
|
59926
|
+
...input.stageOverride ? { stageOverride: input.stageOverride } : {},
|
|
59927
|
+
...input.requestContext ? { requestContext: input.requestContext } : {},
|
|
59928
|
+
...input.awaitingOperator ? { awaitingOperator: true } : {}
|
|
59912
59929
|
});
|
|
59913
59930
|
temperature = input.style.model.temperature;
|
|
59914
59931
|
} else {
|
|
@@ -59995,10 +60012,14 @@ ${kbContextStr}` : vacBlock : kbContextStr;
|
|
|
59995
60012
|
const groundingExempt = input.stage !== undefined && GROUNDING_EXEMPT_STAGES.has(input.stage);
|
|
59996
60013
|
const runFactCheck = (input.reflect || runVacancyCheck) && text !== NO_CONTEXT_MARKER && text.trim().length > 0 && !(groundingExempt && !runVacancyCheck);
|
|
59997
60014
|
if (runFactCheck) {
|
|
60015
|
+
const toolContext = toolCallsToGroundingContext(multiCycleToolCalls);
|
|
60016
|
+
const groundingContext = toolContext ? [context, toolContext].filter(Boolean).join(`
|
|
60017
|
+
|
|
60018
|
+
`) : context;
|
|
59998
60019
|
const verdict = await checkFacts({
|
|
59999
60020
|
question: input.question,
|
|
60000
60021
|
answer: text,
|
|
60001
|
-
context,
|
|
60022
|
+
context: groundingContext,
|
|
60002
60023
|
chat: input.chat,
|
|
60003
60024
|
...runVacancyCheck ? { vacanciesBlock: vacBlock } : {}
|
|
60004
60025
|
});
|
|
@@ -60096,7 +60117,9 @@ ${kbContextStr}` : vacBlock : kbContextStr;
|
|
|
60096
60117
|
...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
|
|
60097
60118
|
...input.directorHooks && input.directorHooks.length > 0 ? { directorHooks: input.directorHooks } : {},
|
|
60098
60119
|
...input.supportPhase ? { supportPhase: input.supportPhase } : {},
|
|
60099
|
-
...input.stageOverride ? { stageOverride: input.stageOverride } : {}
|
|
60120
|
+
...input.stageOverride ? { stageOverride: input.stageOverride } : {},
|
|
60121
|
+
...input.requestContext ? { requestContext: input.requestContext } : {},
|
|
60122
|
+
...input.awaitingOperator ? { awaitingOperator: true } : {}
|
|
60100
60123
|
});
|
|
60101
60124
|
temperature = input.style.model.temperature;
|
|
60102
60125
|
} else {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.test.d.ts","sourceRoot":"","sources":["../src/ingest.test.ts"],"names":[],"mappings":""}
|
package/dist/prompt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAQ,KAAK,EAAE,MAAM,aAAa,CAAC;AA6C5E;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,WAAW,EAClB,mBAAmB,GAAE,MAAM,GAAG,IAAW,EACzC,OAAO,GAAE,cAAmB,GAC3B,MAAM,
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAQ,KAAK,EAAE,MAAM,aAAa,CAAC;AA6C5E;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,WAAW,EAClB,mBAAmB,GAAE,MAAM,GAAG,IAAW,EACzC,OAAO,GAAE,cAAmB,GAC3B,MAAM,CAsJR"}
|
package/dist/styles.d.ts
CHANGED
|
@@ -191,5 +191,16 @@ export interface ComposeOptions {
|
|
|
191
191
|
goal: string;
|
|
192
192
|
guidance?: string;
|
|
193
193
|
};
|
|
194
|
+
/**
|
|
195
|
+
* Динамический контекст текущего запроса гостя (multi-request / concierge):
|
|
196
|
+
* какой тип запроса ведётся + сколько открыто. Инжектится блоком «ЗАПРОС
|
|
197
|
+
* ГОСТЯ». null/absent для линейных вертикалей.
|
|
198
|
+
*/
|
|
199
|
+
requestContext?: string;
|
|
200
|
+
/**
|
|
201
|
+
* Лид стоит на стадии awaiting_operator (R5): цену/условия даёт человек-оператор.
|
|
202
|
+
* Бот придерживает гостя и не выдумывает детали. Блок «ОЖИДАНИЕ ОПЕРАТОРА».
|
|
203
|
+
*/
|
|
204
|
+
awaitingOperator?: boolean;
|
|
194
205
|
}
|
|
195
206
|
//# sourceMappingURL=styles.d.ts.map
|
package/dist/styles.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,+DAAgE,CAAC;AAC3F,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAO,MAAM,gBAAgB,2DAA4D,CAAC;AAC1F,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,eAAO,MAAM,UAAU,2FAOb,CAAC;AACX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,UAAU;;;;;;;;;;iBAGrB,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,iBAAiB;;;;;iBAK5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,aAAa;;;;;;;;iBAKxB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEzD,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsCtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACjD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,+DAAgE,CAAC;AAC3F,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAO,MAAM,gBAAgB,2DAA4D,CAAC;AAC1F,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,eAAO,MAAM,UAAU,2FAOb,CAAC;AACX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,UAAU;;;;;;;;;;iBAGrB,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,iBAAiB;;;;;iBAK5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,aAAa;;;;;;;;iBAKxB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEzD,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsCtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACjD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-prompt.test.d.ts","sourceRoot":"","sources":["../src/system-prompt.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision.test.d.ts","sourceRoot":"","sources":["../src/vision.test.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatman-media/kb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Tenant-scoped Knowledge Base: hybrid retrieval (pgvector + BM25), ingest, answer pipeline, persona/skill composition. LLM I/O живёт в @chatman-media/llm-router.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
package/src/answer-types.ts
CHANGED
|
@@ -63,6 +63,16 @@ export interface AnswerInput {
|
|
|
63
63
|
* per-sales-stage config when composing the prompt.
|
|
64
64
|
*/
|
|
65
65
|
stageOverride?: { goal: string; guidance?: string };
|
|
66
|
+
/**
|
|
67
|
+
* Dynamic per-request context (R4 / multi-request): the guest's current
|
|
68
|
+
* request_type + how many open requests. Injected as a separate prompt block.
|
|
69
|
+
*/
|
|
70
|
+
requestContext?: string;
|
|
71
|
+
/**
|
|
72
|
+
* The guest's current lead is on an `awaiting_operator` stage (R5): the bot
|
|
73
|
+
* holds and defers pricing/decisions to a human operator.
|
|
74
|
+
*/
|
|
75
|
+
awaitingOperator?: boolean;
|
|
66
76
|
/**
|
|
67
77
|
* Called after every `answerWithRag` or `answerWithRagStream` call with the
|
|
68
78
|
* final telemetry. Useful for logging, metrics, or A/B experiment recording
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
// Unit tests for the RAG answer pipeline (answer.ts): retrieveHits,
|
|
2
|
+
// answerWithRag (+ its answerFromHits branches), answerWithRagStream and
|
|
3
|
+
// generateSoftFallback. The KB store, embedder and chat client are faked so
|
|
4
|
+
// every branch (shortcuts, no-context, structured output, tool loop, fact
|
|
5
|
+
// check drops, retrieval failure, streaming) runs without network or DB.
|
|
6
|
+
|
|
7
|
+
import { describe, expect, it } from "bun:test";
|
|
8
|
+
import type { ChatClient, ChatMessage, EmbeddingClient } from "@chatman-media/llm-router";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import {
|
|
11
|
+
answerWithRag,
|
|
12
|
+
answerWithRagStream,
|
|
13
|
+
generateSoftFallback,
|
|
14
|
+
NO_CONTEXT_MARKER,
|
|
15
|
+
retrieveHits,
|
|
16
|
+
} from "./answer.ts";
|
|
17
|
+
import type { AnswerInput } from "./answer-types.ts";
|
|
18
|
+
import type { KbSearchHit } from "./types.ts";
|
|
19
|
+
|
|
20
|
+
const hit = (id: number, over: Partial<KbSearchHit> = {}): KbSearchHit => ({
|
|
21
|
+
chunk_id: id,
|
|
22
|
+
distance: 0.1,
|
|
23
|
+
text: `chunk ${id}`,
|
|
24
|
+
document_id: 1,
|
|
25
|
+
source: "src",
|
|
26
|
+
title: `title ${id}`,
|
|
27
|
+
...over,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const embedder: EmbeddingClient = {
|
|
31
|
+
embed: async (inputs: string[]) => inputs.map(() => [1, 0, 0]),
|
|
32
|
+
dim: 3,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function fakeKb(
|
|
36
|
+
over: Partial<Record<keyof FakeKbCalls, unknown>> = {},
|
|
37
|
+
hits: KbSearchHit[] = [hit(1)],
|
|
38
|
+
) {
|
|
39
|
+
const calls: FakeKbCalls = { search: 0, hybrid: 0, priority: 0 };
|
|
40
|
+
const store = {
|
|
41
|
+
search: async (_e: number[], _k: number, topic?: string | null) => {
|
|
42
|
+
calls.search++;
|
|
43
|
+
calls.lastTopic = topic ?? null;
|
|
44
|
+
return (over.search as KbSearchHit[] | undefined) ?? hits;
|
|
45
|
+
},
|
|
46
|
+
hybridSearch: async () => {
|
|
47
|
+
calls.hybrid++;
|
|
48
|
+
return (over.hybrid as KbSearchHit[] | undefined) ?? hits;
|
|
49
|
+
},
|
|
50
|
+
prioritySearch: async () => {
|
|
51
|
+
calls.priority++;
|
|
52
|
+
return (over.priority as KbSearchHit[] | undefined) ?? hits;
|
|
53
|
+
},
|
|
54
|
+
} as unknown as AnswerInput["kb"];
|
|
55
|
+
return { store, calls };
|
|
56
|
+
}
|
|
57
|
+
interface FakeKbCalls {
|
|
58
|
+
search: number;
|
|
59
|
+
hybrid: number;
|
|
60
|
+
priority: number;
|
|
61
|
+
lastTopic?: string | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Chat client whose complete() returns a fixed string and records messages. */
|
|
65
|
+
function fakeChat(
|
|
66
|
+
reply: string | ((m: ChatMessage[]) => string),
|
|
67
|
+
extra: Partial<ChatClient> = {},
|
|
68
|
+
): ChatClient & { lastMessages?: ChatMessage[] } {
|
|
69
|
+
const c: ChatClient & { lastMessages?: ChatMessage[] } = {
|
|
70
|
+
complete: async (messages: ChatMessage[]) => {
|
|
71
|
+
c.lastMessages = messages;
|
|
72
|
+
return typeof reply === "function" ? reply(messages) : reply;
|
|
73
|
+
},
|
|
74
|
+
...extra,
|
|
75
|
+
};
|
|
76
|
+
return c;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const baseInput = (over: Partial<AnswerInput> = {}): AnswerInput => {
|
|
80
|
+
const { store } = fakeKb();
|
|
81
|
+
return {
|
|
82
|
+
question: "Расскажи про вакансию в Китае и условия", // job intent → not a shortcut
|
|
83
|
+
kb: store,
|
|
84
|
+
embedder,
|
|
85
|
+
chat: fakeChat("ответ модели"),
|
|
86
|
+
...over,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
describe("retrieveHits", () => {
|
|
91
|
+
it("single-query vector search, без topic", async () => {
|
|
92
|
+
const { store, calls } = fakeKb();
|
|
93
|
+
const r = await retrieveHits(baseInput({ kb: store }));
|
|
94
|
+
expect(r.hits).toHaveLength(1);
|
|
95
|
+
expect(calls.search).toBe(1);
|
|
96
|
+
expect(r.searchQuery).toContain("вакансию");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("hybridSearch path", async () => {
|
|
100
|
+
const { store, calls } = fakeKb();
|
|
101
|
+
await retrieveHits(baseInput({ kb: store, hybridSearch: true }));
|
|
102
|
+
expect(calls.hybrid).toBe(1);
|
|
103
|
+
expect(calls.search).toBe(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("booksPriority path", async () => {
|
|
107
|
+
const { store, calls } = fakeKb();
|
|
108
|
+
const r = await retrieveHits(baseInput({ kb: store, booksPriority: true }));
|
|
109
|
+
expect(calls.priority).toBe(1);
|
|
110
|
+
expect(r.usedTopic).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("topicRouting: пустой результат по topic → fallback на global (usedTopic=null)", async () => {
|
|
114
|
+
let first = true;
|
|
115
|
+
const store = {
|
|
116
|
+
search: async () => {
|
|
117
|
+
if (first) {
|
|
118
|
+
first = false;
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
return [hit(2)];
|
|
122
|
+
},
|
|
123
|
+
hybridSearch: async () => [],
|
|
124
|
+
prioritySearch: async () => [],
|
|
125
|
+
} as unknown as AnswerInput["kb"];
|
|
126
|
+
const r = await retrieveHits(
|
|
127
|
+
baseInput({ kb: store, topicRouting: true, question: "виза china" }),
|
|
128
|
+
);
|
|
129
|
+
// fell back to global → got the hit, usedTopic reset to null
|
|
130
|
+
expect(r.usedTopic).toBeNull();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("maxDistance фильтрует далёкие хиты (non-hybrid)", async () => {
|
|
134
|
+
const { store } = fakeKb({}, [hit(1, { distance: 0.1 }), hit(2, { distance: 0.9 })]);
|
|
135
|
+
const r = await retrieveHits(baseInput({ kb: store, maxDistance: 0.5 }));
|
|
136
|
+
expect(r.hits.map((h) => h.chunk_id)).toEqual([1]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("multiQuery → expandQueries + RRF merge", async () => {
|
|
140
|
+
const { store, calls } = fakeKb({}, [hit(1), hit(2)]);
|
|
141
|
+
// expandQueries uses chat to generate variants; a chat returning lines works,
|
|
142
|
+
// but multiQuery merges per-query result lists regardless of expansion output.
|
|
143
|
+
const r = await retrieveHits(
|
|
144
|
+
baseInput({ kb: store, multiQuery: true, chat: fakeChat("вариант1\nвариант2") }),
|
|
145
|
+
);
|
|
146
|
+
expect(r.queries.length).toBeGreaterThanOrEqual(1);
|
|
147
|
+
expect(calls.search).toBeGreaterThanOrEqual(1);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("embedder без вектора → throw", async () => {
|
|
151
|
+
const empty: EmbeddingClient = { embed: async () => [], dim: 3 };
|
|
152
|
+
await expect(retrieveHits(baseInput({ embedder: empty }))).rejects.toThrow(/no vector/);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("answerWithRag — persona shortcuts", () => {
|
|
157
|
+
it("smalltalk «кто ты?» → path=smalltalk, без LLM", async () => {
|
|
158
|
+
const chat = fakeChat(() => {
|
|
159
|
+
throw new Error("should not call LLM");
|
|
160
|
+
});
|
|
161
|
+
const r = await answerWithRag(baseInput({ question: "кто ты?", chat }));
|
|
162
|
+
expect(r.telemetry.path).toBe("smalltalk");
|
|
163
|
+
expect(r.hits).toHaveLength(0);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("bot-presence «ты бот?» → path=smalltalk", async () => {
|
|
167
|
+
const r = await answerWithRag(baseInput({ question: "ты бот?" }));
|
|
168
|
+
expect(r.telemetry.path).toBe("smalltalk");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("personal-fact «где ты живёшь?» с persona.facts → path=persona_fact", async () => {
|
|
172
|
+
const r = await answerWithRag(
|
|
173
|
+
baseInput({
|
|
174
|
+
question: "где ты живёшь?",
|
|
175
|
+
persona: { name: "Аня", role: "human", facts: { city: "Москва" } },
|
|
176
|
+
}),
|
|
177
|
+
);
|
|
178
|
+
expect(r.telemetry.path).toBe("persona_fact");
|
|
179
|
+
expect(r.text).toContain("Москва");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("answerWithRag — main paths", () => {
|
|
184
|
+
it("happy path: hits + LLM → path=ok, usedChunkIds", async () => {
|
|
185
|
+
const onTel: unknown[] = [];
|
|
186
|
+
const r = await answerWithRag(baseInput({ onTelemetry: (t) => onTel.push(t) }));
|
|
187
|
+
expect(r.telemetry.path).toBe("ok");
|
|
188
|
+
expect(r.text.toLowerCase()).toBe("ответ модели");
|
|
189
|
+
expect(r.usedChunkIds).toEqual([1]);
|
|
190
|
+
expect(onTel).toHaveLength(1);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("нет hits, нет style/vac/tools → NO_CONTEXT_MARKER", async () => {
|
|
194
|
+
const { store } = fakeKb({ search: [] });
|
|
195
|
+
const r = await answerWithRag(baseInput({ kb: store }));
|
|
196
|
+
expect(r.text).toBe(NO_CONTEXT_MARKER);
|
|
197
|
+
expect(r.telemetry.path).toBe("no_context");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("retrieval падает → отвечаем без базы (graceful)", async () => {
|
|
201
|
+
const store = {
|
|
202
|
+
search: async () => {
|
|
203
|
+
throw new Error("vector down");
|
|
204
|
+
},
|
|
205
|
+
hybridSearch: async () => [],
|
|
206
|
+
prioritySearch: async () => [],
|
|
207
|
+
} as unknown as AnswerInput["kb"];
|
|
208
|
+
// no style → with zero hits answerFromHits returns no_context (no throw)
|
|
209
|
+
const r = await answerWithRag(baseInput({ kb: store }));
|
|
210
|
+
expect(r.text).toBe(NO_CONTEXT_MARKER);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("structured output через completeStructured", async () => {
|
|
214
|
+
const schema = z.object({ ok: z.boolean() });
|
|
215
|
+
const chat = fakeChat("unused", {
|
|
216
|
+
completeStructured: async () => '{"ok":true}',
|
|
217
|
+
});
|
|
218
|
+
const r = await answerWithRag({ ...baseInput({ chat }), outputSchema: schema });
|
|
219
|
+
expect(r.output).toEqual({ ok: true });
|
|
220
|
+
expect(r.telemetry.path).toBe("ok");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("structured output fallback (нет completeStructured) → inject + complete temp0", async () => {
|
|
224
|
+
const schema = z.object({ n: z.number() });
|
|
225
|
+
const chat = fakeChat('{"n":7}');
|
|
226
|
+
const r = await answerWithRag({ ...baseInput({ chat }), outputSchema: schema });
|
|
227
|
+
expect(r.output).toEqual({ n: 7 });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("fact-check (reflect): ungrounded → дроп в NO_CONTEXT_MARKER, path=ungrounded", async () => {
|
|
231
|
+
const chat = fakeChat((m) => {
|
|
232
|
+
// checkFacts uses the last system prompt asking for JSON verdict
|
|
233
|
+
const sys = m[0]?.content ?? "";
|
|
234
|
+
return sys.includes("grounded")
|
|
235
|
+
? '{"grounded":false,"vacancyOk":true,"reason":"выдумал"}'
|
|
236
|
+
: "сырой ответ";
|
|
237
|
+
});
|
|
238
|
+
const r = await answerWithRag(baseInput({ chat, reflect: true, stage: "pitch" }));
|
|
239
|
+
expect(r.telemetry.path).toBe("ungrounded");
|
|
240
|
+
expect(r.text).toBe(NO_CONTEXT_MARKER);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("vacancyGuard: mismatched vacancy → дроп", async () => {
|
|
244
|
+
const chat = fakeChat((m) => {
|
|
245
|
+
const sys = m[0]?.content ?? "";
|
|
246
|
+
return sys.includes("grounded")
|
|
247
|
+
? '{"grounded":true,"vacancyOk":false,"reason":"цифры"}'
|
|
248
|
+
: "ответ с вакансией";
|
|
249
|
+
});
|
|
250
|
+
const r = await answerWithRag(
|
|
251
|
+
baseInput({ chat, vacanciesBlock: "Вакансия 2000$", stage: "pitch" }),
|
|
252
|
+
);
|
|
253
|
+
expect(r.telemetry.path).toBe("ungrounded");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("grounding-exempt стадия (qualify) без vacancy → fact-check скипается", async () => {
|
|
257
|
+
let factCheckCalled = false;
|
|
258
|
+
const chat = fakeChat((m) => {
|
|
259
|
+
if ((m[0]?.content ?? "").includes("grounded")) {
|
|
260
|
+
factCheckCalled = true;
|
|
261
|
+
return '{"grounded":false,"vacancyOk":true}';
|
|
262
|
+
}
|
|
263
|
+
return "вопрос кандидату";
|
|
264
|
+
});
|
|
265
|
+
const r = await answerWithRag(baseInput({ chat, reflect: true, stage: "qualify" }));
|
|
266
|
+
expect(factCheckCalled).toBe(false);
|
|
267
|
+
expect(r.telemetry.path).toBe("ok");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("tool-loop: модель отвечает без вызова tools → early-return ok", async () => {
|
|
271
|
+
const chat = fakeChat("unused", {
|
|
272
|
+
completeWithTools: async () => ({ content: "финальный ответ", toolCalls: [] }),
|
|
273
|
+
}) as ChatClient;
|
|
274
|
+
const tool = {
|
|
275
|
+
name: "quote",
|
|
276
|
+
description: "d",
|
|
277
|
+
parameters: z.object({}),
|
|
278
|
+
execute: async () => "x",
|
|
279
|
+
};
|
|
280
|
+
const r = await answerWithRag(
|
|
281
|
+
baseInput({ chat, tools: [tool] as unknown as AnswerInput["tools"] }),
|
|
282
|
+
);
|
|
283
|
+
expect(r.text.toLowerCase()).toBe("финальный ответ");
|
|
284
|
+
expect(r.telemetry.path).toBe("ok");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("fact-check sees tool results as grounding context", async () => {
|
|
288
|
+
let calls = 0;
|
|
289
|
+
let checkerPrompt = "";
|
|
290
|
+
const chat = fakeChat("unused", {
|
|
291
|
+
completeWithTools: async () => {
|
|
292
|
+
calls++;
|
|
293
|
+
if (calls === 1) {
|
|
294
|
+
return {
|
|
295
|
+
content: null,
|
|
296
|
+
toolCalls: [{ id: "q1", name: "quote", args: { asset: "USDT", amount: 335 } }],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return { content: "Курс 31.5, получите 10553 THB.", toolCalls: [] };
|
|
300
|
+
},
|
|
301
|
+
complete: async (messages: ChatMessage[]) => {
|
|
302
|
+
if ((messages[0]?.content ?? "").includes("grounded")) {
|
|
303
|
+
checkerPrompt = messages[1]?.content ?? "";
|
|
304
|
+
return '{"grounded":true,"vacancyOk":true}';
|
|
305
|
+
}
|
|
306
|
+
return "Курс 31.5, получите 10553 THB.";
|
|
307
|
+
},
|
|
308
|
+
}) as ChatClient;
|
|
309
|
+
const tool = {
|
|
310
|
+
name: "quote",
|
|
311
|
+
description: "d",
|
|
312
|
+
parameters: z.object({ asset: z.string(), amount: z.number() }),
|
|
313
|
+
execute: async () => ({ rate: 31.5, amountToThb: 10553 }),
|
|
314
|
+
};
|
|
315
|
+
const r = await answerWithRag(
|
|
316
|
+
baseInput({ chat, reflect: true, tools: [tool] as unknown as AnswerInput["tools"] }),
|
|
317
|
+
);
|
|
318
|
+
expect(r.telemetry.path).toBe("ok");
|
|
319
|
+
expect(r.text).toContain("10553 THB");
|
|
320
|
+
expect(checkerPrompt).toContain("TOOL RESULTS");
|
|
321
|
+
expect(checkerPrompt).toContain("quote");
|
|
322
|
+
expect(checkerPrompt).toContain("10553");
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe("answerWithRagStream", () => {
|
|
327
|
+
async function collect(it: AsyncIterable<string>): Promise<string> {
|
|
328
|
+
let out = "";
|
|
329
|
+
for await (const t of it) out += t;
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
it("smalltalk shortcut → yield + telemetry", async () => {
|
|
334
|
+
const tel: { path?: string }[] = [];
|
|
335
|
+
const out = await collect(
|
|
336
|
+
answerWithRagStream(baseInput({ question: "кто ты?", onTelemetry: (t) => tel.push(t) })),
|
|
337
|
+
);
|
|
338
|
+
expect(out.length).toBeGreaterThan(0);
|
|
339
|
+
expect(tel[0]?.path).toBe("smalltalk");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("no-context → yield NO_CONTEXT_MARKER", async () => {
|
|
343
|
+
const { store } = fakeKb({ search: [] });
|
|
344
|
+
const out = await collect(answerWithRagStream(baseInput({ kb: store })));
|
|
345
|
+
expect(out).toBe(NO_CONTEXT_MARKER);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("stream() доступен → токены отдаются по одному", async () => {
|
|
349
|
+
const chat = fakeChat("unused", {
|
|
350
|
+
// eslint-disable-next-line require-yield
|
|
351
|
+
stream: async function* () {
|
|
352
|
+
yield "при";
|
|
353
|
+
yield "вет";
|
|
354
|
+
},
|
|
355
|
+
}) as ChatClient;
|
|
356
|
+
const out = await collect(answerWithRagStream(baseInput({ chat })));
|
|
357
|
+
expect(out).toBe("привет");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("нет stream() → fallback на complete()", async () => {
|
|
361
|
+
const out = await collect(answerWithRagStream(baseInput({ chat: fakeChat("полный ответ") })));
|
|
362
|
+
// complete() fallback runs sanitizeLlmOutput (capitalises the first letter)
|
|
363
|
+
expect(out.toLowerCase()).toBe("полный ответ");
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("generateSoftFallback", () => {
|
|
368
|
+
it("строит persona-промпт и санитизирует ответ", async () => {
|
|
369
|
+
const chat = fakeChat("Уточню и вернусь!");
|
|
370
|
+
const r = await generateSoftFallback({
|
|
371
|
+
question: "сколько платят?",
|
|
372
|
+
chat,
|
|
373
|
+
persona: { name: "Аня", role: "human", company: "Acme" },
|
|
374
|
+
});
|
|
375
|
+
expect(r).toBe("Уточню и вернусь!");
|
|
376
|
+
const sys = chat.lastMessages?.[0]?.content ?? "";
|
|
377
|
+
expect(sys).toContain("Аня");
|
|
378
|
+
expect(sys).toContain("Acme");
|
|
379
|
+
});
|
|
380
|
+
});
|
package/src/answer.ts
CHANGED
|
@@ -33,7 +33,12 @@ import {
|
|
|
33
33
|
legacyRagSamplingTemperature,
|
|
34
34
|
} from "./system-prompt.ts";
|
|
35
35
|
import { applyStyleRules } from "./text-style-rules.ts";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
buildToolTelemetry,
|
|
38
|
+
DEFAULT_MAX_TOOL_CYCLES,
|
|
39
|
+
runToolLoop,
|
|
40
|
+
type ToolCallRecord,
|
|
41
|
+
} from "./tool-loop.ts";
|
|
37
42
|
import type { AnyRagTool } from "./tools.ts";
|
|
38
43
|
import { classifyTopic } from "./topic-classifier.ts";
|
|
39
44
|
import type { KbSearchHit } from "./types.ts";
|
|
@@ -62,6 +67,15 @@ export {
|
|
|
62
67
|
renderUserFactsBlock,
|
|
63
68
|
} from "./system-prompt.ts";
|
|
64
69
|
|
|
70
|
+
function toolCallsToGroundingContext(records: ToolCallRecord[] | undefined): string {
|
|
71
|
+
if (!records || records.length === 0) return "";
|
|
72
|
+
const lines = records.map((record, index) => {
|
|
73
|
+
const result = typeof record.result === "string" ? record.result : JSON.stringify(record.result);
|
|
74
|
+
return `[#tool-${index + 1}] ${record.name}: ${result ?? ""}`;
|
|
75
|
+
});
|
|
76
|
+
return `TOOL RESULTS:\n${lines.join("\n")}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
65
79
|
// ── Shared retrieval ─────────────────────────────────────────────────────────
|
|
66
80
|
|
|
67
81
|
export interface RetrievalResult {
|
|
@@ -235,6 +249,8 @@ async function answerFromHits(opts: {
|
|
|
235
249
|
: {}),
|
|
236
250
|
...(input.supportPhase ? { supportPhase: input.supportPhase } : {}),
|
|
237
251
|
...(input.stageOverride ? { stageOverride: input.stageOverride } : {}),
|
|
252
|
+
...(input.requestContext ? { requestContext: input.requestContext } : {}),
|
|
253
|
+
...(input.awaitingOperator ? { awaitingOperator: true } : {}),
|
|
238
254
|
});
|
|
239
255
|
temperature = input.style.model.temperature;
|
|
240
256
|
} else {
|
|
@@ -366,10 +382,12 @@ async function answerFromHits(opts: {
|
|
|
366
382
|
!(groundingExempt && !runVacancyCheck);
|
|
367
383
|
|
|
368
384
|
if (runFactCheck) {
|
|
385
|
+
const toolContext = toolCallsToGroundingContext(multiCycleToolCalls);
|
|
386
|
+
const groundingContext = toolContext ? [context, toolContext].filter(Boolean).join("\n\n") : context;
|
|
369
387
|
const verdict = await checkFacts({
|
|
370
388
|
question: input.question,
|
|
371
389
|
answer: text,
|
|
372
|
-
context,
|
|
390
|
+
context: groundingContext,
|
|
373
391
|
chat: input.chat,
|
|
374
392
|
...(runVacancyCheck ? { vacanciesBlock: vacBlock } : {}),
|
|
375
393
|
});
|
|
@@ -505,6 +523,8 @@ export async function* answerWithRagStream(input: AnswerInput): AsyncIterable<st
|
|
|
505
523
|
: {}),
|
|
506
524
|
...(input.supportPhase ? { supportPhase: input.supportPhase } : {}),
|
|
507
525
|
...(input.stageOverride ? { stageOverride: input.stageOverride } : {}),
|
|
526
|
+
...(input.requestContext ? { requestContext: input.requestContext } : {}),
|
|
527
|
+
...(input.awaitingOperator ? { awaitingOperator: true } : {}),
|
|
508
528
|
});
|
|
509
529
|
temperature = input.style.model.temperature;
|
|
510
530
|
} else {
|