@chatman-media/kb 1.3.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/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/ab-router.d.ts +66 -0
- package/dist/ab-router.d.ts.map +1 -0
- package/dist/answer-types.d.ts +194 -0
- package/dist/answer-types.d.ts.map +1 -0
- package/dist/answer.d.ts +59 -0
- package/dist/answer.d.ts.map +1 -0
- package/dist/built-in-tools/calendly.d.ts +19 -0
- package/dist/built-in-tools/calendly.d.ts.map +1 -0
- package/dist/chunk.d.ts +48 -0
- package/dist/chunk.d.ts.map +1 -0
- package/dist/conversation-store.d.ts +76 -0
- package/dist/conversation-store.d.ts.map +1 -0
- package/dist/eval.d.ts +64 -0
- package/dist/eval.d.ts.map +1 -0
- package/dist/extract-user-facts.d.ts +27 -0
- package/dist/extract-user-facts.d.ts.map +1 -0
- package/dist/fact-checker.d.ts +46 -0
- package/dist/fact-checker.d.ts.map +1 -0
- package/dist/grade-skills.d.ts +29 -0
- package/dist/grade-skills.d.ts.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62655 -0
- package/dist/ingest.d.ts +49 -0
- package/dist/ingest.d.ts.map +1 -0
- package/dist/multi-query.d.ts +29 -0
- package/dist/multi-query.d.ts.map +1 -0
- package/dist/parse-pdf.d.ts +14 -0
- package/dist/parse-pdf.d.ts.map +1 -0
- package/dist/persona-shortcuts.d.ts +51 -0
- package/dist/persona-shortcuts.d.ts.map +1 -0
- package/dist/prompt.d.ts +9 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/reflect.d.ts +29 -0
- package/dist/reflect.d.ts.map +1 -0
- package/dist/reranker.d.ts +71 -0
- package/dist/reranker.d.ts.map +1 -0
- package/dist/retrieval-utils.d.ts +94 -0
- package/dist/retrieval-utils.d.ts.map +1 -0
- package/dist/retry.d.ts +53 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/rewrite-query.d.ts +30 -0
- package/dist/rewrite-query.d.ts.map +1 -0
- package/dist/sanitize.d.ts +21 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/semantic-cache.d.ts +70 -0
- package/dist/semantic-cache.d.ts.map +1 -0
- package/dist/server.d.ts +77 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/stores/memory-store.d.ts +72 -0
- package/dist/stores/memory-store.d.ts.map +1 -0
- package/dist/structured-output.d.ts +21 -0
- package/dist/structured-output.d.ts.map +1 -0
- package/dist/styles.d.ts +186 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/summarize-conversation.d.ts +31 -0
- package/dist/summarize-conversation.d.ts.map +1 -0
- package/dist/system-prompt.d.ts +11 -0
- package/dist/system-prompt.d.ts.map +1 -0
- package/dist/text-style-rules.d.ts +133 -0
- package/dist/text-style-rules.d.ts.map +1 -0
- package/dist/tool-loop.d.ts +44 -0
- package/dist/tool-loop.d.ts.map +1 -0
- package/dist/tools.d.ts +64 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/topic-classifier.d.ts +11 -0
- package/dist/topic-classifier.d.ts.map +1 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/vision.d.ts +72 -0
- package/dist/vision.d.ts.map +1 -0
- package/package.json +76 -0
- package/src/ab-router.ts +118 -0
- package/src/answer-types.ts +191 -0
- package/src/answer.ts +696 -0
- package/src/built-in-tools/calendly.ts +32 -0
- package/src/chunk.ts +198 -0
- package/src/conversation-store.ts +138 -0
- package/src/eval.ts +127 -0
- package/src/extract-user-facts.ts +120 -0
- package/src/fact-checker.ts +171 -0
- package/src/grade-skills.ts +79 -0
- package/src/index.ts +191 -0
- package/src/ingest.ts +193 -0
- package/src/multi-query.ts +89 -0
- package/src/parse-pdf.ts +24 -0
- package/src/persona-shortcuts.ts +255 -0
- package/src/prompt.ts +190 -0
- package/src/reflect.ts +99 -0
- package/src/reranker.ts +166 -0
- package/src/retrieval-utils.ts +209 -0
- package/src/retry.ts +139 -0
- package/src/rewrite-query.ts +124 -0
- package/src/sanitize.ts +44 -0
- package/src/semantic-cache.ts +154 -0
- package/src/server.ts +164 -0
- package/src/stores/memory-store.ts +249 -0
- package/src/structured-output.ts +47 -0
- package/src/styles.ts +138 -0
- package/src/summarize-conversation.ts +88 -0
- package/src/system-prompt.ts +118 -0
- package/src/text-style-rules.ts +244 -0
- package/src/tool-loop.ts +110 -0
- package/src/tools.ts +79 -0
- package/src/topic-classifier.ts +112 -0
- package/src/types.ts +91 -0
- package/src/utils.ts +81 -0
- package/src/vision.ts +265 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AnswerInput, AnswerTelemetry } from "./answer-types.ts";
|
|
2
|
+
import type { ChatClient } from "@chatman-media/llm-router";
|
|
3
|
+
import type { EmbeddingClient } from "@chatman-media/llm-router";
|
|
4
|
+
import type { IKbStore } from "./types.ts";
|
|
5
|
+
export interface RagServerOptions {
|
|
6
|
+
/** Knowledge base store. */
|
|
7
|
+
kb: IKbStore;
|
|
8
|
+
/** LLM chat client. */
|
|
9
|
+
chat: ChatClient;
|
|
10
|
+
/** Embedding client. */
|
|
11
|
+
embedder: EmbeddingClient;
|
|
12
|
+
/** Port to listen on. Default: 3000. */
|
|
13
|
+
port?: number;
|
|
14
|
+
/** Hostname to bind to. Default: "0.0.0.0". */
|
|
15
|
+
hostname?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Path that the server listens on. Default: "/chat".
|
|
18
|
+
* POST JSON `{ question, userId?, conversationId?, ... }` → SSE stream of tokens.
|
|
19
|
+
*/
|
|
20
|
+
path?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Extra `AnswerInput` defaults merged into every request.
|
|
23
|
+
* Request body fields take precedence over these defaults.
|
|
24
|
+
*/
|
|
25
|
+
defaults?: Partial<Omit<AnswerInput, "question" | "kb" | "chat" | "embedder">>;
|
|
26
|
+
/**
|
|
27
|
+
* Called after every answered request with the final telemetry.
|
|
28
|
+
* Use this to log to your analytics backend.
|
|
29
|
+
*/
|
|
30
|
+
onTelemetry?: (telemetry: AnswerTelemetry) => void;
|
|
31
|
+
/**
|
|
32
|
+
* CORS origin header value. Set to `"*"` to allow all origins.
|
|
33
|
+
* Default: `undefined` (no CORS headers added).
|
|
34
|
+
*/
|
|
35
|
+
cors?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Shape of the JSON request body accepted by the RAG server. */
|
|
38
|
+
export interface RagRequestBody {
|
|
39
|
+
question: string;
|
|
40
|
+
[key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Lightweight Bun HTTP server that exposes `answerWithRagStream` as an SSE endpoint.
|
|
44
|
+
*
|
|
45
|
+
* **Request** — `POST {path}` with `Content-Type: application/json`:
|
|
46
|
+
* ```json
|
|
47
|
+
* { "question": "What is the onboarding process?" }
|
|
48
|
+
* ```
|
|
49
|
+
* Any additional fields in the body are merged into `AnswerInput` (e.g. `style`,
|
|
50
|
+
* `history`, `conversationSummary`, `userFacts`).
|
|
51
|
+
*
|
|
52
|
+
* **Response** — `text/event-stream`:
|
|
53
|
+
* ```
|
|
54
|
+
* data: Hello
|
|
55
|
+
* data: world
|
|
56
|
+
* data: [DONE]
|
|
57
|
+
* ```
|
|
58
|
+
* Each `data:` line carries one streamed token. The stream ends with `data: [DONE]`.
|
|
59
|
+
* On error the server emits `data: [ERROR] <message>` and closes the stream.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* import { createRagServer } from "@chatman-media/kb";
|
|
64
|
+
*
|
|
65
|
+
* const server = createRagServer({
|
|
66
|
+
* kb, chat, embedder,
|
|
67
|
+
* port: 3000,
|
|
68
|
+
* cors: "*",
|
|
69
|
+
* onTelemetry: (t) => console.log("path:", t.path, "ms:", t.latencyMs),
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* console.log(`Listening on http://localhost:${server.port}`);
|
|
73
|
+
* // server.stop() to shut down
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function createRagServer(opts: RagServerOptions): ReturnType<typeof Bun.serve>;
|
|
77
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,uBAAuB;IACvB,IAAI,EAAE,UAAU,CAAC;IACjB,wBAAwB;IACxB,QAAQ,EAAE,eAAe,CAAC;IAC1B,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC;IAC/E;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;IACnD;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAoFpF"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { IKbStore, KbSearchHit } from "../types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Zero-dependency in-memory `IKbStore` implementation.
|
|
4
|
+
*
|
|
5
|
+
* Suitable for:
|
|
6
|
+
* - Unit / integration tests (no database required)
|
|
7
|
+
* - Quick prototyping and examples
|
|
8
|
+
* - Demos and tutorials
|
|
9
|
+
*
|
|
10
|
+
* Limitations vs a real pgvector store:
|
|
11
|
+
* - O(n) linear scan for every search (no index) — fine up to ~50k chunks
|
|
12
|
+
* - BM25 is a minimal TF-IDF approximation, not a full BM25 implementation
|
|
13
|
+
* - Data is not persisted across process restarts
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { InMemoryKbStore, ingestText, OllamaEmbeddingClient } from "@chatman-media/kb";
|
|
18
|
+
*
|
|
19
|
+
* const kb = new InMemoryKbStore();
|
|
20
|
+
* const embedder = new OllamaEmbeddingClient({ host: "http://localhost:11434", model: "nomic-embed-text", dim: 768 });
|
|
21
|
+
*
|
|
22
|
+
* await ingestText({ title: "FAQ", body: "..." }, { kb, embedder });
|
|
23
|
+
* const hits = await kb.search(await embedder.embed(["query"])[0], 5);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare class InMemoryKbStore implements IKbStore {
|
|
27
|
+
private docs;
|
|
28
|
+
private chunks;
|
|
29
|
+
private nextDocId;
|
|
30
|
+
private nextChunkId;
|
|
31
|
+
search(embedding: number[], k: number, topic?: string | null): Promise<KbSearchHit[]>;
|
|
32
|
+
hybridSearch(input: {
|
|
33
|
+
embedding: number[];
|
|
34
|
+
query: string;
|
|
35
|
+
k?: number;
|
|
36
|
+
topic?: string | null;
|
|
37
|
+
}): Promise<KbSearchHit[]>;
|
|
38
|
+
prioritySearch(input: {
|
|
39
|
+
embedding: number[];
|
|
40
|
+
query: string;
|
|
41
|
+
k?: number;
|
|
42
|
+
vectorOnly?: boolean;
|
|
43
|
+
}): Promise<KbSearchHit[]>;
|
|
44
|
+
getDocumentBySource(source: string): Promise<{
|
|
45
|
+
id: number;
|
|
46
|
+
content_hash: string;
|
|
47
|
+
} | null>;
|
|
48
|
+
countChunksForDocument(documentId: number): Promise<number>;
|
|
49
|
+
deleteDocument(id: number): Promise<boolean>;
|
|
50
|
+
upsertDocument(input: {
|
|
51
|
+
source: string;
|
|
52
|
+
title: string;
|
|
53
|
+
contentHash: string;
|
|
54
|
+
topic?: string | null;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
id: number;
|
|
57
|
+
}>;
|
|
58
|
+
insertChunkWithEmbedding(input: {
|
|
59
|
+
documentId: number;
|
|
60
|
+
chunkIndex: number;
|
|
61
|
+
text: string;
|
|
62
|
+
tokenCount: number;
|
|
63
|
+
embedding: number[];
|
|
64
|
+
}): Promise<void>;
|
|
65
|
+
/** Total number of indexed chunks. Useful in tests. */
|
|
66
|
+
get chunkCount(): number;
|
|
67
|
+
/** Total number of indexed documents. Useful in tests. */
|
|
68
|
+
get documentCount(): number;
|
|
69
|
+
private topicOf;
|
|
70
|
+
private toHit;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=memory-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../src/stores/memory-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAoBzD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAC9C,OAAO,CAAC,IAAI,CAAwB;IACpC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,WAAW,CAAK;IAIlB,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAWrF,YAAY,CAAC,KAAK,EAAE;QACxB,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,CAAC,CAAC,EAAE,MAAM,CAAC;QACX,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAoBpB,cAAc,CAAC,KAAK,EAAE;QAC1B,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,CAAC,CAAC,EAAE,MAAM,CAAC;QACX,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAWpB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAKzF,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI3D,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO5C,cAAc,CAAC,KAAK,EAAE;QAC1B,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAmBrB,wBAAwB,CAAC,KAAK,EAAE;QACpC,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,EAAE,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjB,uDAAuD;IACvD,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,0DAA0D;IAC1D,IAAI,aAAa,IAAI,MAAM,CAE1B;IAID,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,KAAK;CAWd"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Injects a JSON output instruction into the system prompt when native
|
|
4
|
+
* structured output is not available (Ollama, OpenRouter, etc.).
|
|
5
|
+
*/
|
|
6
|
+
export declare function injectJsonInstruction(systemPrompt: string, jsonSchema: Record<string, unknown>): string;
|
|
7
|
+
/**
|
|
8
|
+
* Parses and validates an LLM response against a Zod schema.
|
|
9
|
+
* Strips markdown code fences if present before parsing.
|
|
10
|
+
* Returns `{ success: true, data }` or `{ success: false, error }`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseStructuredOutput<T extends z.ZodTypeAny>(raw: string, schema: T): {
|
|
13
|
+
success: true;
|
|
14
|
+
data: z.infer<T>;
|
|
15
|
+
} | {
|
|
16
|
+
success: false;
|
|
17
|
+
error: string;
|
|
18
|
+
};
|
|
19
|
+
/** Converts a Zod schema to a JSON Schema object for OpenAI's response_format. */
|
|
20
|
+
export declare function zodToJsonSchema(schema: z.ZodTypeAny): Record<string, unknown>;
|
|
21
|
+
//# sourceMappingURL=structured-output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structured-output.d.ts","sourceRoot":"","sources":["../src/structured-output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EAC1D,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,GACR;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAmBzE;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE7E"}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sales-style engine — typed schema for conversational personas.
|
|
3
|
+
*
|
|
4
|
+
* A `Style` bundles persona + voice + sales framework + Cialdini hooks +
|
|
5
|
+
* per-stage instructions + few-shot examples + guardrails + model pin.
|
|
6
|
+
* Styles are the unit of A/B testing in the sales engine: hold three of the
|
|
7
|
+
* four orthogonal concerns constant (persona, framework, hooks, stage) and
|
|
8
|
+
* rotate one to compare conversion outcomes.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
export declare const FUNNEL_STAGES: readonly ["opener", "qualify", "pitch", "objection", "close"];
|
|
12
|
+
export type FunnelStage = (typeof FUNNEL_STAGES)[number];
|
|
13
|
+
export declare const SALES_FRAMEWORKS: readonly ["AIDA", "PAS", "SPIN", "NEPQ", "straight_line"];
|
|
14
|
+
export type SalesFramework = (typeof SALES_FRAMEWORKS)[number];
|
|
15
|
+
export declare const HOOK_KINDS: readonly ["social_proof", "scarcity", "authority", "liking", "reciprocity", "commitment"];
|
|
16
|
+
export type HookKind = (typeof HOOK_KINDS)[number];
|
|
17
|
+
export declare const HookSchema: z.ZodObject<{
|
|
18
|
+
kind: z.ZodEnum<{
|
|
19
|
+
social_proof: "social_proof";
|
|
20
|
+
scarcity: "scarcity";
|
|
21
|
+
authority: "authority";
|
|
22
|
+
liking: "liking";
|
|
23
|
+
reciprocity: "reciprocity";
|
|
24
|
+
commitment: "commitment";
|
|
25
|
+
}>;
|
|
26
|
+
text: z.ZodString;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
export type Hook = z.infer<typeof HookSchema>;
|
|
29
|
+
export declare const StageConfigSchema: z.ZodObject<{
|
|
30
|
+
goal: z.ZodString;
|
|
31
|
+
guidance: z.ZodOptional<z.ZodString>;
|
|
32
|
+
groundingRequired: z.ZodDefault<z.ZodBoolean>;
|
|
33
|
+
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
}, z.core.$strip>;
|
|
35
|
+
export type StageConfig = z.infer<typeof StageConfigSchema>;
|
|
36
|
+
export declare const PersonaSchema: z.ZodObject<{
|
|
37
|
+
name: z.ZodString;
|
|
38
|
+
role: z.ZodEnum<{
|
|
39
|
+
human: "human";
|
|
40
|
+
assistant: "assistant";
|
|
41
|
+
}>;
|
|
42
|
+
company: z.ZodOptional<z.ZodString>;
|
|
43
|
+
facts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
export type StylePersona = z.infer<typeof PersonaSchema>;
|
|
46
|
+
export declare const StyleSchema: z.ZodObject<{
|
|
47
|
+
slug: z.ZodString;
|
|
48
|
+
displayName: z.ZodString;
|
|
49
|
+
persona: z.ZodObject<{
|
|
50
|
+
name: z.ZodString;
|
|
51
|
+
role: z.ZodEnum<{
|
|
52
|
+
human: "human";
|
|
53
|
+
assistant: "assistant";
|
|
54
|
+
}>;
|
|
55
|
+
company: z.ZodOptional<z.ZodString>;
|
|
56
|
+
facts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
voice: z.ZodObject<{
|
|
59
|
+
tone: z.ZodString;
|
|
60
|
+
language: z.ZodDefault<z.ZodEnum<{
|
|
61
|
+
ru: "ru";
|
|
62
|
+
en: "en";
|
|
63
|
+
}>>;
|
|
64
|
+
forbid: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
65
|
+
stallCtaReply: z.ZodOptional<z.ZodString>;
|
|
66
|
+
}, z.core.$strip>;
|
|
67
|
+
framework: z.ZodEnum<{
|
|
68
|
+
AIDA: "AIDA";
|
|
69
|
+
PAS: "PAS";
|
|
70
|
+
SPIN: "SPIN";
|
|
71
|
+
NEPQ: "NEPQ";
|
|
72
|
+
straight_line: "straight_line";
|
|
73
|
+
}>;
|
|
74
|
+
hooks: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
75
|
+
kind: z.ZodEnum<{
|
|
76
|
+
social_proof: "social_proof";
|
|
77
|
+
scarcity: "scarcity";
|
|
78
|
+
authority: "authority";
|
|
79
|
+
liking: "liking";
|
|
80
|
+
reciprocity: "reciprocity";
|
|
81
|
+
commitment: "commitment";
|
|
82
|
+
}>;
|
|
83
|
+
text: z.ZodString;
|
|
84
|
+
}, z.core.$strip>>>;
|
|
85
|
+
stages: z.ZodObject<{
|
|
86
|
+
opener: z.ZodOptional<z.ZodObject<{
|
|
87
|
+
goal: z.ZodString;
|
|
88
|
+
guidance: z.ZodOptional<z.ZodString>;
|
|
89
|
+
groundingRequired: z.ZodDefault<z.ZodBoolean>;
|
|
90
|
+
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
91
|
+
}, z.core.$strip>>;
|
|
92
|
+
qualify: z.ZodOptional<z.ZodObject<{
|
|
93
|
+
goal: z.ZodString;
|
|
94
|
+
guidance: z.ZodOptional<z.ZodString>;
|
|
95
|
+
groundingRequired: z.ZodDefault<z.ZodBoolean>;
|
|
96
|
+
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
97
|
+
}, z.core.$strip>>;
|
|
98
|
+
pitch: z.ZodOptional<z.ZodObject<{
|
|
99
|
+
goal: z.ZodString;
|
|
100
|
+
guidance: z.ZodOptional<z.ZodString>;
|
|
101
|
+
groundingRequired: z.ZodDefault<z.ZodBoolean>;
|
|
102
|
+
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
103
|
+
}, z.core.$strip>>;
|
|
104
|
+
objection: z.ZodOptional<z.ZodObject<{
|
|
105
|
+
goal: z.ZodString;
|
|
106
|
+
guidance: z.ZodOptional<z.ZodString>;
|
|
107
|
+
groundingRequired: z.ZodDefault<z.ZodBoolean>;
|
|
108
|
+
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
109
|
+
}, z.core.$strip>>;
|
|
110
|
+
close: z.ZodOptional<z.ZodObject<{
|
|
111
|
+
goal: z.ZodString;
|
|
112
|
+
guidance: z.ZodOptional<z.ZodString>;
|
|
113
|
+
groundingRequired: z.ZodDefault<z.ZodBoolean>;
|
|
114
|
+
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
115
|
+
}, z.core.$strip>>;
|
|
116
|
+
}, z.core.$strip>;
|
|
117
|
+
fewShot: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
118
|
+
user: z.ZodString;
|
|
119
|
+
assistant: z.ZodString;
|
|
120
|
+
stage: z.ZodOptional<z.ZodEnum<{
|
|
121
|
+
opener: "opener";
|
|
122
|
+
qualify: "qualify";
|
|
123
|
+
pitch: "pitch";
|
|
124
|
+
objection: "objection";
|
|
125
|
+
close: "close";
|
|
126
|
+
}>>;
|
|
127
|
+
}, z.core.$strip>>>;
|
|
128
|
+
guardrails: z.ZodObject<{
|
|
129
|
+
noMinors: z.ZodDefault<z.ZodBoolean>;
|
|
130
|
+
botDisclosureOnDirectQuestion: z.ZodDefault<z.ZodBoolean>;
|
|
131
|
+
forbiddenTopics: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
132
|
+
}, z.core.$strip>;
|
|
133
|
+
model: z.ZodObject<{
|
|
134
|
+
id: z.ZodDefault<z.ZodString>;
|
|
135
|
+
temperature: z.ZodDefault<z.ZodNumber>;
|
|
136
|
+
maxTokens: z.ZodDefault<z.ZodNumber>;
|
|
137
|
+
}, z.core.$strip>;
|
|
138
|
+
}, z.core.$strip>;
|
|
139
|
+
export type Style = z.infer<typeof StyleSchema>;
|
|
140
|
+
/**
|
|
141
|
+
* A persuasion skill in the shape `composeSystemPrompt` consumes.
|
|
142
|
+
* Decoupled from DB row shape so the prompt module stays pure.
|
|
143
|
+
*/
|
|
144
|
+
export interface SkillForPrompt {
|
|
145
|
+
slug: string;
|
|
146
|
+
displayName: string;
|
|
147
|
+
promptFragment: string;
|
|
148
|
+
/**
|
|
149
|
+
* Stages where this skill applies. Empty array = always applicable.
|
|
150
|
+
* May contain FunnelStage names ("qualify", "pitch"…) or stage-kind
|
|
151
|
+
* strings ("intake", "active") — `composeSystemPrompt` does string
|
|
152
|
+
* comparison against the current stage/kind value so both work.
|
|
153
|
+
*/
|
|
154
|
+
applicableStages: readonly string[];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* A director-level persuasion hook — tenant-specific scripted mini-technique.
|
|
158
|
+
* Unlike universal skills (from the catalogue), hooks are always injected for
|
|
159
|
+
* this tenant regardless of which style or stage is active.
|
|
160
|
+
*/
|
|
161
|
+
export interface DirectorHookForPrompt {
|
|
162
|
+
name: string;
|
|
163
|
+
body: string;
|
|
164
|
+
/** Optional natural-language hint for when to apply this hook. */
|
|
165
|
+
triggerHint?: string | null;
|
|
166
|
+
}
|
|
167
|
+
export interface ComposeOptions {
|
|
168
|
+
includeFewShot?: boolean;
|
|
169
|
+
userFacts?: Record<string, string>;
|
|
170
|
+
conversationSummary?: string;
|
|
171
|
+
skills?: readonly SkillForPrompt[];
|
|
172
|
+
/**
|
|
173
|
+
* Tenant-specific persuasion scripts added by the director. Injected as a
|
|
174
|
+
* "ХУКИ УБЕЖДЕНИЯ" block BEFORE the universal skills block. Always active —
|
|
175
|
+
* not filtered by stage or style.
|
|
176
|
+
*/
|
|
177
|
+
directorHooks?: readonly DirectorHookForPrompt[];
|
|
178
|
+
/**
|
|
179
|
+
* Support mode — set when the lead is past the sales stage and waiting on a
|
|
180
|
+
* downstream process. When set, the prompt drops the sales framework / hooks
|
|
181
|
+
* / skills / few-shot / funnel-stage guidance and uses a calm FAQ-support
|
|
182
|
+
* block instead. `docs` = collecting their documents, `submitted` = filed.
|
|
183
|
+
*/
|
|
184
|
+
supportPhase?: "docs" | "submitted";
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +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;CACrC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ChatClient, ChatMessage } from "@chatman-media/llm-router";
|
|
2
|
+
/**
|
|
3
|
+
* Compresses old turns of a long conversation into one short paragraph that
|
|
4
|
+
* preserves what nuance the user-facts memory layer can't — what the bot
|
|
5
|
+
* already promised, what the candidate hesitated about, what was already
|
|
6
|
+
* explained vs. left open. Injected into the next system prompt as
|
|
7
|
+
* "ИЗ РАННЕЙ ПЕРЕПИСКИ:" so the LLM has continuity past the 12-message
|
|
8
|
+
* recent-history window.
|
|
9
|
+
*
|
|
10
|
+
* Only the OLD tail is summarized — the recent ~12 messages are still
|
|
11
|
+
* passed to the LLM raw (they fit in the window). Pass them via
|
|
12
|
+
* `messagesToSummarize` (oldest first).
|
|
13
|
+
*/
|
|
14
|
+
export interface SummarizeInput {
|
|
15
|
+
/** Old messages to compress, oldest first. Caller must already have
|
|
16
|
+
* trimmed off the recent window — those go into LLM context as-is. */
|
|
17
|
+
messagesToSummarize: ChatMessage[];
|
|
18
|
+
chat: ChatClient;
|
|
19
|
+
/** Existing summary to refine (when refreshing an older summary).
|
|
20
|
+
* Helps the model preserve what was already known without re-reading
|
|
21
|
+
* the whole stretch of dialogue. */
|
|
22
|
+
previousSummary?: string;
|
|
23
|
+
/** Hard cap on summary length (chars). Default 600. Past this the
|
|
24
|
+
* summary is truncated at the last sentence boundary. */
|
|
25
|
+
maxLength?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function summarizeConversation(input: SummarizeInput): Promise<string>;
|
|
28
|
+
/** Strip think-tags / markdown / leading prefixes; cap length at the last
|
|
29
|
+
* sentence boundary inside the cap. Exported for unit tests. */
|
|
30
|
+
export declare function cleanSummary(raw: string, maxLength: number): string;
|
|
31
|
+
//# sourceMappingURL=summarize-conversation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize-conversation.d.ts","sourceRoot":"","sources":["../src/summarize-conversation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAGzE;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC7B;2EACuE;IACvE,mBAAmB,EAAE,WAAW,EAAE,CAAC;IACnC,IAAI,EAAE,UAAU,CAAC;IACjB;;yCAEqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;8DAC0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgBD,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBlF;AAED;iEACiE;AACjE,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAenE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Persona } from "./answer-types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Legacy RAG sampling temperature when a sales `style` is not used.
|
|
4
|
+
* Pass `tempOverride` to apply a custom value (e.g. from an env var).
|
|
5
|
+
*/
|
|
6
|
+
export declare function legacyRagSamplingTemperature(persona: Persona, tempOverride?: number): number;
|
|
7
|
+
export declare const DEFAULT_PERSONA: Persona;
|
|
8
|
+
export declare function renderSummaryBlock(summary?: string): string;
|
|
9
|
+
export declare function renderUserFactsBlock(userFacts?: Record<string, string>): string;
|
|
10
|
+
export declare function buildSystemPrompt(persona: Persona, context: string, userFacts?: Record<string, string>, conversationSummary?: string): string;
|
|
11
|
+
//# sourceMappingURL=system-prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../src/system-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEpE;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAG5F;AAED,eAAO,MAAM,eAAe,EAAE,OAI7B,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAK3D;AAED,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAQ/E;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,mBAAmB,CAAC,EAAE,MAAM,GAC3B,MAAM,CA8ER"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-processing rules for LLM output — the legacy-codebase analog of "skills":
|
|
3
|
+
* small, named, composable text transforms that run after the model returns.
|
|
4
|
+
* Each one targets a specific "AI tell" that breaks the human-manager
|
|
5
|
+
* illusion (the candidate must believe they're talking to a real recruiter,
|
|
6
|
+
* not a chatbot — see persona role="human" in `buildSystemPrompt`).
|
|
7
|
+
*
|
|
8
|
+
* Adding a new rule:
|
|
9
|
+
* 1. Define it as `TextStyleRule` (name + description + apply function).
|
|
10
|
+
* 2. Append it to `DEFAULT_STYLE_RULES` (or a style-specific bundle).
|
|
11
|
+
* 3. Add a unit test in `tests/unit/text-style-rules.test.ts`.
|
|
12
|
+
*
|
|
13
|
+
* Rules MUST be:
|
|
14
|
+
* - idempotent (`rule(rule(x)) === rule(x)`) so re-application is safe;
|
|
15
|
+
* - pure (same input → same output, no I/O, no global state);
|
|
16
|
+
* - cheap (sub-ms on a 1 KB string; we run the whole stack on every reply).
|
|
17
|
+
*
|
|
18
|
+
* Negative-instruction-in-prompt approach was tried and is unreliable —
|
|
19
|
+
* LLMs ignore "не используй длинное тире" in 30-50% of replies. Doing it
|
|
20
|
+
* deterministically as post-processing is bullet-proof.
|
|
21
|
+
*/
|
|
22
|
+
export interface TextStyleRule {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
apply: (text: string) => string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Em-dash (`—`, U+2014) — formally correct Russian typography but a dead
|
|
29
|
+
* giveaway in messenger chat. Real candidates type a plain hyphen `-` or
|
|
30
|
+
* skip the dash entirely. Replace with regular hyphen, normalising
|
|
31
|
+
* surrounding whitespace so we don't end up with double-spaces.
|
|
32
|
+
*/
|
|
33
|
+
export declare const replaceEmDash: TextStyleRule;
|
|
34
|
+
/**
|
|
35
|
+
* En-dash (`–`, U+2013). Less common but appears in date ranges
|
|
36
|
+
* («10:00–18:00») and is also AI-flavoured in casual chat.
|
|
37
|
+
*/
|
|
38
|
+
export declare const replaceEnDash: TextStyleRule;
|
|
39
|
+
/**
|
|
40
|
+
* Horizontal bar (`―`, U+2015) and figure dash (`‒`, U+2012) — the rest of
|
|
41
|
+
* the dash family. Same rule, same reason.
|
|
42
|
+
*/
|
|
43
|
+
export declare const replaceOtherDashes: TextStyleRule;
|
|
44
|
+
/**
|
|
45
|
+
* Unicode ellipsis (`…`, U+2026) → three ASCII dots. Native typists hit
|
|
46
|
+
* `...` on a regular keyboard; the single-codepoint ellipsis arrives only
|
|
47
|
+
* via autocomplete or model output.
|
|
48
|
+
*/
|
|
49
|
+
export declare const replaceEllipsis: TextStyleRule;
|
|
50
|
+
/**
|
|
51
|
+
* Strip AI-flavoured lead-ins at the start of the reply.
|
|
52
|
+
*
|
|
53
|
+
* "Конечно!" / "Безусловно!" / "Разумеется!" / "Хорошо!" alone, followed by
|
|
54
|
+
* a sentence boundary or comma, are textbook ChatGPT openers. A real
|
|
55
|
+
* recruiter just answers. We trim the preamble; if the rest of the line is
|
|
56
|
+
* empty we leave the original untouched (better to keep something than
|
|
57
|
+
* nothing).
|
|
58
|
+
*/
|
|
59
|
+
export declare const stripAILeadIns: TextStyleRule;
|
|
60
|
+
/**
|
|
61
|
+
* Capitalise the first alphabetic character of the reply.
|
|
62
|
+
*
|
|
63
|
+
* Why: qwen3 (and other models) tend to mirror the candidate's casing —
|
|
64
|
+
* if the user types «привет», the model replies «привет, ...» in lowercase.
|
|
65
|
+
* Real recruiters in our corpus (`kb/extracted/dialogs/*`) consistently
|
|
66
|
+
* start replies with a capital letter («Здравствуйте», «Хорошо»),
|
|
67
|
+
* regardless of how the candidate wrote.
|
|
68
|
+
*
|
|
69
|
+
* Implementation finds the FIRST alphabetic codepoint (skipping leading
|
|
70
|
+
* whitespace, emoji, punctuation) and uppercases it. Idempotent.
|
|
71
|
+
*/
|
|
72
|
+
export declare const capitalizeFirstLetter: TextStyleRule;
|
|
73
|
+
/**
|
|
74
|
+
* Strip Markdown bold (`**text**` and `__text__`). Telegram doesn't
|
|
75
|
+
* render Markdown unless `parse_mode` is set — and the bot uses plain
|
|
76
|
+
* text — so the asterisks/underscores leak through to the candidate
|
|
77
|
+
* verbatim ("Зарплата от **₩110 000**" → user sees the stars).
|
|
78
|
+
*
|
|
79
|
+
* We strip the markers and keep the inner text. Conservative: requires
|
|
80
|
+
* non-whitespace content inside, so a literal `**` separator wrapping
|
|
81
|
+
* spaces stays intact (rare but happens).
|
|
82
|
+
*/
|
|
83
|
+
export declare const stripMarkdownBold: TextStyleRule;
|
|
84
|
+
/**
|
|
85
|
+
* Strip Markdown italics (`*text*` and `_text_`). Same reason as bold.
|
|
86
|
+
*
|
|
87
|
+
* Tricky: bare `*` and `_` appear naturally in URLs, file names, math
|
|
88
|
+
* expressions, etc. We require:
|
|
89
|
+
* - the OPENING marker to be at start-of-string OR preceded by a
|
|
90
|
+
* non-letter/non-digit (so `foo_bar` and `https://example_com` stay);
|
|
91
|
+
* - the CLOSING marker to be at end-of-string OR followed by the same;
|
|
92
|
+
* - inner content to be non-empty + not start/end with whitespace.
|
|
93
|
+
*
|
|
94
|
+
* Run AFTER `stripMarkdownBold` so `**bold**` is unwrapped before the
|
|
95
|
+
* italic regex sees the standalone `*` pair.
|
|
96
|
+
*/
|
|
97
|
+
export declare const stripMarkdownItalic: TextStyleRule;
|
|
98
|
+
/**
|
|
99
|
+
* Strip Markdown inline / fenced code (`` `code` ``, ``` ```block``` ```).
|
|
100
|
+
* In a candidate-facing sales chat these are never helpful; the bot
|
|
101
|
+
* sometimes wraps numbers like `` `₩110 000` `` for emphasis and the
|
|
102
|
+
* candidate sees the backticks.
|
|
103
|
+
*
|
|
104
|
+
* Fenced (triple-backtick) blocks come first so their contents aren't
|
|
105
|
+
* partially eaten by the inline rule.
|
|
106
|
+
*/
|
|
107
|
+
export declare const stripMarkdownCode: TextStyleRule;
|
|
108
|
+
/**
|
|
109
|
+
* Strip Markdown headers (`# Heading` at the start of a line). LLMs
|
|
110
|
+
* sometimes emit `## Условия:` when listing facts; in chat that just
|
|
111
|
+
* dumps the hashes. We keep the trailing text.
|
|
112
|
+
*/
|
|
113
|
+
export declare const stripMarkdownHeaders: TextStyleRule;
|
|
114
|
+
/**
|
|
115
|
+
* Strip Markdown links (`[text](url)`) into `text (url)` — keeps the
|
|
116
|
+
* URL visible, drops the bracket syntax. Telegram autolinks plain
|
|
117
|
+
* URLs, so the candidate still gets a clickable link.
|
|
118
|
+
*/
|
|
119
|
+
export declare const stripMarkdownLinks: TextStyleRule;
|
|
120
|
+
/**
|
|
121
|
+
* The standard rule set applied by `sanitizeLlmOutput`.
|
|
122
|
+
*
|
|
123
|
+
* Order matters when one rule's output feeds another. Critical:
|
|
124
|
+
* `stripAILeadIns` MUST run before `capitalizeFirstLetter`, otherwise we
|
|
125
|
+
* just re-uppercase the «К» in «Конечно!» and the lead-in stays.
|
|
126
|
+
*/
|
|
127
|
+
export declare const DEFAULT_STYLE_RULES: readonly TextStyleRule[];
|
|
128
|
+
/**
|
|
129
|
+
* Apply a sequence of style rules in order. Returns the input unchanged
|
|
130
|
+
* when `rules` is empty.
|
|
131
|
+
*/
|
|
132
|
+
export declare function applyStyleRules(text: string, rules?: readonly TextStyleRule[]): string;
|
|
133
|
+
//# sourceMappingURL=text-style-rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-style-rules.d.ts","sourceRoot":"","sources":["../src/text-style-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACjC;AAID;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,aAI3B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,aAI3B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAIhC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,aAI7B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,EAAE,aAe5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAYnC,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAO/B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAOjC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAK/B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAIlC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAIhC,CAAC;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,aAAa,EAmBvD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,SAAS,aAAa,EAAwB,GACpD,MAAM,CAER"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { AnswerTelemetry } from "./answer-types.ts";
|
|
2
|
+
import type { ChatClient, ChatCompletionOpts, ChatMessage } from "@chatman-media/llm-router";
|
|
3
|
+
import type { AnyRagTool } from "./tools.ts";
|
|
4
|
+
/** Default maximum number of agentic tool-calling cycles. */
|
|
5
|
+
export declare const DEFAULT_MAX_TOOL_CYCLES = 4;
|
|
6
|
+
/** A single tool execution recorded during an agentic tool loop. */
|
|
7
|
+
export interface ToolCallRecord {
|
|
8
|
+
name: string;
|
|
9
|
+
args: Record<string, unknown>;
|
|
10
|
+
result: unknown;
|
|
11
|
+
/** True when the tool was unknown or `execute()` threw — `result` holds `{ error }`. */
|
|
12
|
+
error?: boolean;
|
|
13
|
+
/** Zero-based index of the loop cycle this call belongs to. */
|
|
14
|
+
cycle: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ToolLoopResult {
|
|
17
|
+
/** Final assistant text when the model stopped calling tools, else null. */
|
|
18
|
+
content: string | null;
|
|
19
|
+
/** Every tool call executed across all cycles, in order. */
|
|
20
|
+
toolCalls: ToolCallRecord[];
|
|
21
|
+
/** True when the loop stopped because `maxCycles` was hit while the model still wanted tools. */
|
|
22
|
+
exhausted: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Runs an agentic tool-calling loop. Mutates `messages` in place, appending an
|
|
26
|
+
* assistant message (carrying all tool calls) and one `tool` message per call
|
|
27
|
+
* for every cycle. Returns the final model text (when produced) and the full
|
|
28
|
+
* tool-call trace.
|
|
29
|
+
*
|
|
30
|
+
* Unknown tools and thrown `execute()` errors are fed back to the model as the
|
|
31
|
+
* tool result so it can recover — the loop never throws on tool failure.
|
|
32
|
+
*
|
|
33
|
+
* The caller must guarantee `chat.completeWithTools` exists and `tools` is non-empty.
|
|
34
|
+
*/
|
|
35
|
+
export declare function runToolLoop(opts: {
|
|
36
|
+
chat: ChatClient;
|
|
37
|
+
messages: ChatMessage[];
|
|
38
|
+
tools: AnyRagTool[];
|
|
39
|
+
llmOpts: ChatCompletionOpts;
|
|
40
|
+
maxCycles: number;
|
|
41
|
+
}): Promise<ToolLoopResult>;
|
|
42
|
+
/** Builds the tool-related telemetry fields from a tool-call trace. */
|
|
43
|
+
export declare function buildToolTelemetry(records: ToolCallRecord[]): Pick<AnswerTelemetry, "toolCall" | "toolCalls">;
|
|
44
|
+
//# sourceMappingURL=tool-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-loop.d.ts","sourceRoot":"","sources":["../src/tool-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,wFAAwF;IACxF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,4DAA4D;IAC5D,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,iGAAiG;IACjG,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,cAAc,CAAC,CAoD1B;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,cAAc,EAAE,GACxB,IAAI,CAAC,eAAe,EAAE,UAAU,GAAG,WAAW,CAAC,CAOjD"}
|