@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Alexander Kireev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# @chatman-media/kb
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@chatman-media/kb)
|
|
4
|
+
[](https://github.com/chatman-media/lead-engine/actions/workflows/ci.yml)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://bun.sh/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
Tenant-scoped Knowledge Base for the Lead Engine platform. Provides a full
|
|
10
|
+
retrieval-augmented generation (RAG) pipeline: hybrid search (pgvector cosine +
|
|
11
|
+
BM25 with RRF fusion), multi-query expansion, cross-encoder reranking, MMR
|
|
12
|
+
diversification, dynamic distance threshold, semantic caching, persona/skill
|
|
13
|
+
composition, photo classification and passport OCR.
|
|
14
|
+
|
|
15
|
+
LLM I/O lives in `@chatman-media/llm-router`.
|
|
16
|
+
|
|
17
|
+
## Key modules
|
|
18
|
+
|
|
19
|
+
| Module | What it does |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `answer.ts` | Full RAG answer pipeline: retrieve → filter → diversify → rerank → generate |
|
|
22
|
+
| `ingest.ts` | Document ingest: parse → chunk → embed → upsert |
|
|
23
|
+
| `hybrid-search.ts` | pgvector cosine + BM25 keyword fusion via RRF |
|
|
24
|
+
| `retrieval-utils.ts` | `rrfMerge`, `applyDynamicThreshold`, `mmrDiversify` — post-retrieval transforms |
|
|
25
|
+
| `multi-query.ts` | `expandQueries` — LLM-generated query variants for parallel search |
|
|
26
|
+
| `reranker.ts` | `JinaReranker`, `CohereReranker` — cross-encoder second-pass reranking |
|
|
27
|
+
| `rewrite-query.ts` | Context-aware query rewriting (resolves pronouns / ellipsis via history) |
|
|
28
|
+
| `semantic-cache.ts` | Vector-similarity cache for identical/near-identical questions |
|
|
29
|
+
| `vision.ts` | `classifyPhoto()` + `extractPassportIdentity()` — passport OCR via vision LLM |
|
|
30
|
+
| `ab-router.ts` | A/B experiment allocation for styles/personas |
|
|
31
|
+
| `grade-skills.ts` | ELO-based skill grading via judge LLM |
|
|
32
|
+
| `prompt.ts` | `composeSystemPrompt()` — assemble sales persona + KB context + style |
|
|
33
|
+
|
|
34
|
+
## RAG pipeline
|
|
35
|
+
|
|
36
|
+
Each call to `answerWithRag` / `answerWithRagStream` goes through these stages:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
1. [opt] Query rewrite — resolves pronouns, expands ellipsis (rewriteQueryBeforeRetrieval)
|
|
40
|
+
2. [opt] Multi-query — generate N variants → embed all in parallel (multiQuery)
|
|
41
|
+
3. Vector / hybrid search — pgvector cosine or RRF(vector+BM25)
|
|
42
|
+
4. [opt] RRF merge — fuse N result lists if multi-query was used (rrfMerge)
|
|
43
|
+
5. [opt] Distance filter — drop hits > threshold (autoTrimDistance)
|
|
44
|
+
6. [opt] MMR diversify — reduce duplicate chunks (mmr)
|
|
45
|
+
7. [opt] Cross-encoder — reranker.rerank(query, candidates, topK) (reranker)
|
|
46
|
+
8. Prompt composition — style + persona + context + skills + hooks
|
|
47
|
+
9. LLM generation — stream or single response
|
|
48
|
+
10.[opt] Fact-checker — hallucination guard (reflect)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
All stages are optional and controlled per-request via `AnswerInput` fields.
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
### `answerWithRag(input: AnswerInput): Promise<AnswerResult>`
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { answerWithRag, JinaReranker } from "@chatman-media/kb";
|
|
59
|
+
|
|
60
|
+
const result = await answerWithRag({
|
|
61
|
+
question: "сколько стоит квартира в ЖК Марина?",
|
|
62
|
+
kb, // IKbStore implementation
|
|
63
|
+
embedder, // EmbeddingClient
|
|
64
|
+
chat, // ChatClient
|
|
65
|
+
|
|
66
|
+
// ── Retrieval tuning ──────────────────────────────
|
|
67
|
+
topK: 5, // final chunks to pass to the LLM
|
|
68
|
+
hybridSearch: true, // vector + BM25 fusion
|
|
69
|
+
rewriteQueryBeforeRetrieval: true, // resolve "там" / "он" via history
|
|
70
|
+
|
|
71
|
+
// Multi-query expansion (generate 2 rephrases, search 3 in parallel)
|
|
72
|
+
multiQuery: true,
|
|
73
|
+
multiQueryCount: 2,
|
|
74
|
+
|
|
75
|
+
// Drop chunks with cosine distance > 0.45 (reduce hallucinations)
|
|
76
|
+
autoTrimDistance: true,
|
|
77
|
+
autoTrimThreshold: 0.45,
|
|
78
|
+
|
|
79
|
+
// Maximal Marginal Relevance — diversify results
|
|
80
|
+
mmr: true,
|
|
81
|
+
mmrLambda: 0.6, // 1.0 = pure relevance, 0.0 = pure diversity
|
|
82
|
+
|
|
83
|
+
// Cross-encoder reranker (retrieves topK×3 candidates, returns topK)
|
|
84
|
+
reranker: new JinaReranker({ apiKey: process.env.JINA_API_KEY! }),
|
|
85
|
+
|
|
86
|
+
// ── Generation ────────────────────────────────────
|
|
87
|
+
history, // ChatMessage[] — conversation context
|
|
88
|
+
persona, // Persona — bot identity
|
|
89
|
+
style, // Style — sales methodology (SPIN / NEPQ / AIDA)
|
|
90
|
+
stage, // FunnelStage — current funnel stage
|
|
91
|
+
skills, // SkillForPrompt[] — active persuasion skills
|
|
92
|
+
reflect: true, // hallucination guard (LLM judge)
|
|
93
|
+
|
|
94
|
+
onTelemetry: (t) => console.log(t), // retrieval_ms, top_distances, path, ...
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
console.log(result.text); // generated reply
|
|
98
|
+
console.log(result.hits); // KbSearchHit[] — chunks used
|
|
99
|
+
console.log(result.usedChunkIds); // chunk IDs referenced in the reply
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Rerankers
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { JinaReranker, CohereReranker } from "@chatman-media/kb";
|
|
106
|
+
|
|
107
|
+
// Jina — multilingual, good for Russian (jina-reranker-v2-base-multilingual)
|
|
108
|
+
const reranker = new JinaReranker({
|
|
109
|
+
apiKey: process.env.JINA_API_KEY!,
|
|
110
|
+
model: "jina-reranker-v2-base-multilingual", // default
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Cohere — also multilingual (rerank-v3.5)
|
|
114
|
+
const reranker = new CohereReranker({
|
|
115
|
+
apiKey: process.env.COHERE_API_KEY!,
|
|
116
|
+
model: "rerank-v3.5", // default
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Post-retrieval utilities
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { rrfMerge, applyDynamicThreshold, mmrDiversify } from "@chatman-media/kb";
|
|
124
|
+
|
|
125
|
+
// Merge results from multiple queries via Reciprocal Rank Fusion
|
|
126
|
+
const merged = rrfMerge([hitsFromQuery1, hitsFromQuery2, hitsFromQuery3], { topN: 15 });
|
|
127
|
+
|
|
128
|
+
// Drop hits with cosine distance > 0.4 (keep at least 1)
|
|
129
|
+
const trimmed = applyDynamicThreshold(hits, { threshold: 0.4, minHits: 1 });
|
|
130
|
+
|
|
131
|
+
// Maximal Marginal Relevance — diversify, reduce duplicates
|
|
132
|
+
const diverse = mmrDiversify(hits, { lambda: 0.6, topK: 5 });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Vision
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { classifyPhoto, extractPassportIdentity } from "@chatman-media/kb";
|
|
139
|
+
|
|
140
|
+
const cls = await classifyPhoto({
|
|
141
|
+
bytes: await res.arrayBuffer(),
|
|
142
|
+
model: "gpt-4o",
|
|
143
|
+
apiKey: "sk-...",
|
|
144
|
+
provider: "openai",
|
|
145
|
+
});
|
|
146
|
+
// cls → "passport" | "full_body" | "portrait" | "other"
|
|
147
|
+
|
|
148
|
+
if (cls === "passport") {
|
|
149
|
+
const identity = await extractPassportIdentity({ bytes, model, apiKey, provider });
|
|
150
|
+
// identity → { family_name?, given_name?, passport_number?, passport_expiry? }
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
In `apps/api`, photo classification is wired automatically via `photo-processor.ts`:
|
|
155
|
+
when a tenant has a `vision` LLM config, every incoming photo is classified
|
|
156
|
+
and passport data is merged into `contact.attributes_json`.
|
|
157
|
+
|
|
158
|
+
## Install
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bun add @chatman-media/kb # Bun
|
|
162
|
+
npm install @chatman-media/kb # npm / pnpm / yarn
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Part of the [**lead-engine**](https://github.com/chatman-media/lead-engine) monorepo — a multi-tenant SaaS platform for AI sales bots on Telegram / WhatsApp.
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
[MIT](LICENSE) — Alexander Kireev / [chatman-media](https://github.com/chatman-media)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { AnswerTelemetry } from "./answer-types.ts";
|
|
2
|
+
import type { Style } from "./styles.ts";
|
|
3
|
+
export interface ABVariant {
|
|
4
|
+
/** Weight relative to other variants. Default 1. */
|
|
5
|
+
weight?: number;
|
|
6
|
+
style: Style;
|
|
7
|
+
}
|
|
8
|
+
export interface ABRouterOptions {
|
|
9
|
+
variants: ABVariant[];
|
|
10
|
+
/**
|
|
11
|
+
* Called after every answer with the assigned variant slug and telemetry.
|
|
12
|
+
* Use this to log impressions, latency, and conversion signals to your DB.
|
|
13
|
+
*/
|
|
14
|
+
onResult?: (variantSlug: string, telemetry: AnswerTelemetry) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Salt added to the userId before hashing. Change it to re-randomise
|
|
17
|
+
* assignments without changing user ids.
|
|
18
|
+
*/
|
|
19
|
+
salt?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Deterministic A/B style router.
|
|
23
|
+
*
|
|
24
|
+
* Assigns each user to a variant by hashing `userId + salt` — the same user
|
|
25
|
+
* always gets the same variant within an experiment. Weights are respected:
|
|
26
|
+
* a variant with weight 2 gets twice as many users as one with weight 1.
|
|
27
|
+
*
|
|
28
|
+
* Pass the returned `style` directly to `answerWithRag`. Log results via
|
|
29
|
+
* `onResult` to measure conversion by variant.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { ABRouter, answerWithRag } from "@chatman-media/kb";
|
|
34
|
+
*
|
|
35
|
+
* const router = new ABRouter({
|
|
36
|
+
* variants: [
|
|
37
|
+
* { style: nepqStyle, weight: 1 },
|
|
38
|
+
* { style: straightLineStyle, weight: 1 },
|
|
39
|
+
* ],
|
|
40
|
+
* onResult: (slug, telemetry) => db.logImpression(slug, telemetry),
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* const { style, onTelemetry } = router.assign(userId);
|
|
44
|
+
* const result = await answerWithRag({ question, kb, chat, embedder, style, onTelemetry });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare class ABRouter {
|
|
48
|
+
private readonly variants;
|
|
49
|
+
private readonly totalWeight;
|
|
50
|
+
private readonly onResult;
|
|
51
|
+
private readonly salt;
|
|
52
|
+
constructor(opts: ABRouterOptions);
|
|
53
|
+
/**
|
|
54
|
+
* Assign a user to a variant. Returns the `style` to pass to `answerWithRag`
|
|
55
|
+
* and an `onTelemetry` callback that forwards results to `opts.onResult`.
|
|
56
|
+
*/
|
|
57
|
+
assign(userId: string): {
|
|
58
|
+
style: Style;
|
|
59
|
+
variantSlug: string;
|
|
60
|
+
onTelemetry: (t: AnswerTelemetry) => void;
|
|
61
|
+
};
|
|
62
|
+
/** Distribution map — slug → fraction of users assigned. */
|
|
63
|
+
get distribution(): Record<string, number>;
|
|
64
|
+
private pickVariant;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=ab-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ab-router.d.ts","sourceRoot":"","sources":["../src/ab-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,WAAW,SAAS;IACxB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;IACrE;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IACvD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;gBAElB,IAAI,EAAE,eAAe;IAQjC;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG;QACtB,KAAK,EAAE,KAAK,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;KAC3C;IAcD,4DAA4D;IAC5D,IAAI,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMzC;IAID,OAAO,CAAC,WAAW;CAWpB"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { ChatClient, ChatMessage } from "@chatman-media/llm-router";
|
|
3
|
+
import type { EmbeddingClient } from "@chatman-media/llm-router";
|
|
4
|
+
import type { DirectorHookForPrompt, FunnelStage, SkillForPrompt, Style } from "./styles.ts";
|
|
5
|
+
import type { Reranker } from "./reranker.ts";
|
|
6
|
+
import type { AnyRagTool } from "./tools.ts";
|
|
7
|
+
import type { IKbStore, KbSearchHit } from "./types.ts";
|
|
8
|
+
export declare const NO_CONTEXT_MARKER = "__NO_CONTEXT__";
|
|
9
|
+
export interface Persona {
|
|
10
|
+
name: string;
|
|
11
|
+
role: "human" | "assistant";
|
|
12
|
+
company?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Fixed personal facts about the persona — bypasses RAG for direct personal
|
|
15
|
+
* questions and injects into the system prompt as inviolable grounding.
|
|
16
|
+
* Known keys: "city", "age", "status", "experience", "phone".
|
|
17
|
+
*/
|
|
18
|
+
facts?: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
export interface AnswerInput {
|
|
21
|
+
question: string;
|
|
22
|
+
kb: IKbStore;
|
|
23
|
+
embedder: EmbeddingClient;
|
|
24
|
+
chat: ChatClient;
|
|
25
|
+
history?: ChatMessage[];
|
|
26
|
+
topK?: number;
|
|
27
|
+
maxDistance?: number;
|
|
28
|
+
persona?: Persona;
|
|
29
|
+
style?: Style;
|
|
30
|
+
stage?: FunnelStage;
|
|
31
|
+
includeFewShot?: boolean;
|
|
32
|
+
numPredict?: number;
|
|
33
|
+
userFacts?: Record<string, string>;
|
|
34
|
+
rewriteQueryBeforeRetrieval?: boolean;
|
|
35
|
+
reflect?: boolean;
|
|
36
|
+
hybridSearch?: boolean;
|
|
37
|
+
conversationSummary?: string;
|
|
38
|
+
topicRouting?: boolean;
|
|
39
|
+
vacanciesBlock?: string;
|
|
40
|
+
vacancyGuard?: boolean;
|
|
41
|
+
skills?: readonly SkillForPrompt[];
|
|
42
|
+
/**
|
|
43
|
+
* Tenant-specific director hooks to inject into the system prompt.
|
|
44
|
+
* Loaded from `director_hooks` table filtered by `is_active = true`.
|
|
45
|
+
* Always injected (not stage-filtered) — the LLM decides when to apply.
|
|
46
|
+
*/
|
|
47
|
+
directorHooks?: readonly DirectorHookForPrompt[];
|
|
48
|
+
booksPriority?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Support mode — set when the lead is past the sales stage and waiting on a
|
|
51
|
+
* downstream process (`docs` = collecting their documents, `submitted` =
|
|
52
|
+
* filed). When set AND a `style` is active, the composed system prompt drops
|
|
53
|
+
* the sales framework / hooks / skills / few-shot / funnel-stage guidance and
|
|
54
|
+
* uses a calm FAQ-support block instead — answering questions without selling.
|
|
55
|
+
*/
|
|
56
|
+
supportPhase?: "docs" | "submitted";
|
|
57
|
+
/**
|
|
58
|
+
* Called after every `answerWithRag` or `answerWithRagStream` call with the
|
|
59
|
+
* final telemetry. Useful for logging, metrics, or A/B experiment recording
|
|
60
|
+
* without having to unwrap the return value.
|
|
61
|
+
*/
|
|
62
|
+
onTelemetry?: (telemetry: AnswerTelemetry) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Optional tools the LLM can call during answer generation (single-cycle).
|
|
65
|
+
* When the model decides to call a tool, `execute()` is called automatically
|
|
66
|
+
* and the result is fed back for a final answer.
|
|
67
|
+
* Requires `chat` to implement `completeWithTools` (e.g. `OpenAIChatClient`),
|
|
68
|
+
* otherwise falls back to prompt-based tool injection.
|
|
69
|
+
*/
|
|
70
|
+
tools?: AnyRagTool[];
|
|
71
|
+
/**
|
|
72
|
+
* Maximum number of agentic tool-calling cycles. Each cycle is one LLM call
|
|
73
|
+
* that may request tools, followed by execution of those tools. When the
|
|
74
|
+
* limit is reached, a final answer is forced WITHOUT tools. Only relevant
|
|
75
|
+
* when `tools` is set. Default: 4 (caps at 5 LLM calls including the final
|
|
76
|
+
* answer — note the latency and cost of a long tool chain).
|
|
77
|
+
*/
|
|
78
|
+
maxToolCycles?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Enable Maximal Marginal Relevance re-ranking after retrieval.
|
|
81
|
+
* Selects a diverse set of chunks so that repeated near-duplicate passages
|
|
82
|
+
* do not crowd out different sub-topics. Default: false.
|
|
83
|
+
*/
|
|
84
|
+
mmr?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* λ (lambda) for MMR: trade-off between relevance and diversity.
|
|
87
|
+
* 1.0 = pure relevance, 0.0 = pure diversity. Default: 0.6.
|
|
88
|
+
* Only has effect when `mmr` is true.
|
|
89
|
+
*/
|
|
90
|
+
mmrLambda?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Trim retrieved hits that exceed a cosine-distance threshold before passing
|
|
93
|
+
* them to the LLM. Reduces hallucinations caused by weak/unrelated matches.
|
|
94
|
+
* Default: false (no trimming).
|
|
95
|
+
*/
|
|
96
|
+
autoTrimDistance?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Distance threshold used when `autoTrimDistance` is true.
|
|
99
|
+
* Cosine distance in [0, 2]; typical useful range is ≤ 0.4.
|
|
100
|
+
* Default: 0.45.
|
|
101
|
+
*/
|
|
102
|
+
autoTrimThreshold?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Enable multi-query expansion: generate `multiQueryCount` rephrased variants
|
|
105
|
+
* of the question with a fast LLM call, search with each in parallel, and
|
|
106
|
+
* merge all result lists via Reciprocal Rank Fusion (RRF). Improves recall
|
|
107
|
+
* for synonym gaps and differently-phrased concepts.
|
|
108
|
+
* Default: false.
|
|
109
|
+
*/
|
|
110
|
+
multiQuery?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Number of ADDITIONAL query variants to generate (not counting the original).
|
|
113
|
+
* Only has effect when `multiQuery` is true. Default: 2.
|
|
114
|
+
*/
|
|
115
|
+
multiQueryCount?: number;
|
|
116
|
+
/**
|
|
117
|
+
* Optional cross-encoder reranker applied after vector/hybrid retrieval.
|
|
118
|
+
* Retrieves `topK * 3` candidates, passes them to the reranker, then keeps
|
|
119
|
+
* the top `topK`. Use `JinaReranker` or `CohereReranker` from this package.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* import { JinaReranker } from "@chatman-media/kb";
|
|
124
|
+
* await answerWithRag({ ..., reranker: new JinaReranker({ apiKey: process.env.JINA_API_KEY! }) });
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
reranker?: Reranker;
|
|
128
|
+
/**
|
|
129
|
+
* When provided, the LLM is instructed to return a JSON object matching this
|
|
130
|
+
* Zod schema. The parsed and validated value is available as `result.output`.
|
|
131
|
+
* Uses OpenAI's native `response_format` when available; falls back to prompt
|
|
132
|
+
* injection for other providers (Ollama, OpenRouter, etc.).
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* const result = await answerWithRag({
|
|
137
|
+
* question: "Classify this lead",
|
|
138
|
+
* kb, chat, embedder,
|
|
139
|
+
* outputSchema: z.object({
|
|
140
|
+
* intent: z.enum(["buy", "info", "not_interested"]),
|
|
141
|
+
* budget: z.number().optional(),
|
|
142
|
+
* nextAction: z.string(),
|
|
143
|
+
* }),
|
|
144
|
+
* });
|
|
145
|
+
* console.log(result.output.intent); // "buy" | "info" | "not_interested"
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
outputSchema?: z.ZodTypeAny;
|
|
149
|
+
}
|
|
150
|
+
export interface AnswerTelemetry {
|
|
151
|
+
path: "smalltalk" | "persona_fact" | "no_context" | "ungrounded" | "ok" | "cache_hit";
|
|
152
|
+
total_ms?: number;
|
|
153
|
+
retrieval_ms?: number;
|
|
154
|
+
generation_ms?: number;
|
|
155
|
+
top_distances?: number[];
|
|
156
|
+
hybrid?: boolean;
|
|
157
|
+
topic?: string | null;
|
|
158
|
+
original_query?: string;
|
|
159
|
+
rewritten_query?: string;
|
|
160
|
+
factCheck?: {
|
|
161
|
+
grounded: boolean;
|
|
162
|
+
vacancyOk: boolean;
|
|
163
|
+
reason?: string;
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* @deprecated Use `toolCalls`. Retained for backward compatibility — set to
|
|
167
|
+
* the first tool call when any tools ran during answer generation.
|
|
168
|
+
*/
|
|
169
|
+
toolCall?: {
|
|
170
|
+
name: string;
|
|
171
|
+
result: unknown;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Every tool call executed across all agentic cycles, in order. Note that
|
|
175
|
+
* `args` may contain user-derived input — treat as sensitive if your tools
|
|
176
|
+
* receive PII.
|
|
177
|
+
*/
|
|
178
|
+
toolCalls?: Array<{
|
|
179
|
+
name: string;
|
|
180
|
+
args: Record<string, unknown>;
|
|
181
|
+
result: unknown;
|
|
182
|
+
error?: boolean;
|
|
183
|
+
cycle: number;
|
|
184
|
+
}>;
|
|
185
|
+
}
|
|
186
|
+
export interface AnswerResult<TOutput = unknown> {
|
|
187
|
+
text: string;
|
|
188
|
+
usedChunkIds: number[];
|
|
189
|
+
hits: KbSearchHit[];
|
|
190
|
+
telemetry: AnswerTelemetry;
|
|
191
|
+
/** Parsed and validated output when `outputSchema` was passed to `answerWithRag`. */
|
|
192
|
+
output?: TOutput;
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=answer-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import { type AnswerInput, type AnswerResult, type Persona } from "./answer-types.ts";
|
|
3
|
+
import type { ChatClient, ChatMessage } from "@chatman-media/llm-router";
|
|
4
|
+
import type { KbSearchHit } from "./types.ts";
|
|
5
|
+
export { type AnswerInput, type AnswerResult, type AnswerTelemetry, NO_CONTEXT_MARKER, type Persona, } from "./answer-types.ts";
|
|
6
|
+
export { botPresenceReply, isBotPresenceQuestion, isPersonalFactQuestion, isPersonaSmalltalkQuestion, personaFactReply, personaSmalltalkReply, } from "./persona-shortcuts.ts";
|
|
7
|
+
export { sanitizeLlmOutput } from "./sanitize.ts";
|
|
8
|
+
export { buildSystemPrompt, legacyRagSamplingTemperature, renderSummaryBlock, renderUserFactsBlock, } from "./system-prompt.ts";
|
|
9
|
+
export interface RetrievalResult {
|
|
10
|
+
hits: KbSearchHit[];
|
|
11
|
+
retrievalMs: number;
|
|
12
|
+
searchQuery: string;
|
|
13
|
+
queries: string[];
|
|
14
|
+
/** null when topicRouting is off or booksPriority path was used. */
|
|
15
|
+
usedTopic: string | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Shared retrieval logic for both `answerWithRag` and `answerWithRagStream`.
|
|
19
|
+
*
|
|
20
|
+
* Steps:
|
|
21
|
+
* 1. Optional query rewrite (LLM resolves pronouns/ellipsis via history).
|
|
22
|
+
* 2. Optional multi-query expansion (LLM generates N variants).
|
|
23
|
+
* 3. Embed all queries in one batch.
|
|
24
|
+
* 4. booksPriority path OR normal path (multi-query → RRF | single → topic fallback).
|
|
25
|
+
* 5. maxDistance filter → applyDynamicThreshold → mmrDiversify → reranker.
|
|
26
|
+
* 6. Slice to topK.
|
|
27
|
+
*/
|
|
28
|
+
export declare function retrieveHits(input: AnswerInput): Promise<RetrievalResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Streaming variant of `answerWithRag`. Yields raw text tokens as they arrive
|
|
31
|
+
* from the LLM. The final telemetry is delivered via `input.onTelemetry` (if
|
|
32
|
+
* set). Falls back to `complete()` when the chat client has no `stream()`.
|
|
33
|
+
*
|
|
34
|
+
* Note: hallucination guard (`reflect`, `vacancyGuard`) is not applied during
|
|
35
|
+
* streaming — fact-checking requires the full answer. Use `answerWithRag()` when
|
|
36
|
+
* fact-checking is required.
|
|
37
|
+
*/
|
|
38
|
+
export declare function answerWithRagStream(input: AnswerInput): AsyncIterable<string>;
|
|
39
|
+
export declare function answerWithRag<T extends z.ZodTypeAny>(input: AnswerInput & {
|
|
40
|
+
outputSchema: T;
|
|
41
|
+
}): Promise<AnswerResult<z.infer<T>>>;
|
|
42
|
+
export declare function answerWithRag(input: AnswerInput): Promise<AnswerResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Soft fallback reply for turns where RAG produced nothing groundable — no KB
|
|
45
|
+
* hit, or the fact-checker dropped the draft as ungrounded. Instead of going
|
|
46
|
+
* silent, the bot answers in its own persona voice, but is hard-constrained
|
|
47
|
+
* NOT to invent any specifics (salaries, dates, visa terms, cities, prices).
|
|
48
|
+
*
|
|
49
|
+
* Concrete questions get an honest "I'll clarify and come back"; general
|
|
50
|
+
* questions get a normal conversational answer. The caller is still expected
|
|
51
|
+
* to log the unanswered question (kb_suggestions) for a later precise reply.
|
|
52
|
+
*/
|
|
53
|
+
export declare function generateSoftFallback(input: {
|
|
54
|
+
question: string;
|
|
55
|
+
chat: ChatClient;
|
|
56
|
+
persona: Persona;
|
|
57
|
+
history?: ChatMessage[];
|
|
58
|
+
}): Promise<string>;
|
|
59
|
+
//# sourceMappingURL=answer.d.ts.map
|
|
@@ -0,0 +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;AA8BzE,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;AAI5B,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;AA2OD;;;;;;;;GAQG;AACH,wBAAuB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CA0HpF;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,19 @@
|
|
|
1
|
+
import type { AnyRagTool } from "../tools.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Встроенный tool: "offer_booking_link".
|
|
4
|
+
*
|
|
5
|
+
* Когда лид/кандидат хочет записаться на звонок, демо или встречу — LLM
|
|
6
|
+
* вызывает этот tool, получает ссылку и вставляет её в ответ.
|
|
7
|
+
* Работает с любой платформой бронирования: Calendly, Cal.com, Tidycal, и т.д.
|
|
8
|
+
*
|
|
9
|
+
* @param schedulingUrl — публичная ссылка на страницу бронирования тенанта
|
|
10
|
+
* Пример: "https://calendly.com/your-agency/30min"
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const tool = makeBookingLinkTool("https://calendly.com/my-agency/demo");
|
|
15
|
+
* const result = await answerWithRag({ ..., tools: [tool] });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function makeBookingLinkTool(schedulingUrl: string): AnyRagTool;
|
|
19
|
+
//# sourceMappingURL=calendly.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendly.d.ts","sourceRoot":"","sources":["../../src/built-in-tools/calendly.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,UAAU,CAYrE"}
|
package/dist/chunk.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface ChunkOptions {
|
|
2
|
+
maxChars: number;
|
|
3
|
+
overlapChars: number;
|
|
4
|
+
}
|
|
5
|
+
export interface Chunk {
|
|
6
|
+
index: number;
|
|
7
|
+
text: string;
|
|
8
|
+
/** Rough token estimate: ~4 chars per token. */
|
|
9
|
+
tokenCount: number;
|
|
10
|
+
}
|
|
11
|
+
/** Approximate token count for English/mixed text: ~4 chars per token. */
|
|
12
|
+
export declare function estimateTokens(text: string): number;
|
|
13
|
+
/**
|
|
14
|
+
* Splits `text` into overlapping chunks bounded by `maxChars`. Tries to break
|
|
15
|
+
* on paragraph boundaries (\n\n) first, then on whitespace, before slicing
|
|
16
|
+
* mid-word. Empty/whitespace-only input yields an empty array.
|
|
17
|
+
*/
|
|
18
|
+
export declare function chunkText(text: string, opts?: Partial<ChunkOptions>): Chunk[];
|
|
19
|
+
export interface SectionChunk extends Chunk {
|
|
20
|
+
/** Heading that introduces this section, or null for the document preamble. */
|
|
21
|
+
heading: string | null;
|
|
22
|
+
/** Heading level (1–6) derived from the number of `#` characters, or null. */
|
|
23
|
+
headingLevel: number | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Semantic chunker that splits Markdown/plain-text documents by headings
|
|
27
|
+
* (`#`, `##`, …) and paragraph breaks, keeping each heading with its content.
|
|
28
|
+
*
|
|
29
|
+
* Compared to `chunkText()`:
|
|
30
|
+
* - Never breaks a heading away from its first paragraph
|
|
31
|
+
* - Each chunk carries the heading context, so retrieval results are
|
|
32
|
+
* self-contained even without surrounding text
|
|
33
|
+
* - Falls back to `chunkText()` for sections that exceed `maxChars`
|
|
34
|
+
*
|
|
35
|
+
* Use this for structured knowledge-base documents (FAQs, wikis, product
|
|
36
|
+
* pages). Use `chunkText()` for unstructured long-form prose.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { chunkBySections } from "@chatman-media/kb";
|
|
41
|
+
*
|
|
42
|
+
* const chunks = chunkBySections(markdownString, { maxChars: 1200 });
|
|
43
|
+
* // chunks[0].heading → "Installation"
|
|
44
|
+
* // chunks[0].text → "## Installation\n\nRun `bun add …`"
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function chunkBySections(text: string, opts?: Partial<ChunkOptions>): SectionChunk[];
|
|
48
|
+
//# sourceMappingURL=chunk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk.d.ts","sourceRoot":"","sources":["../src/chunk.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;CACpB;AAOD,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,KAAK,EAAE,CAqDjF;AAED,MAAM,WAAW,YAAa,SAAQ,KAAK;IACzC,+EAA+E;IAC/E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,8EAA8E;IAC9E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,YAAY,EAAE,CA2E9F"}
|