@halo-sdk/rag 1.0.0 → 2.0.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/embedders.d.ts +37 -0
- package/dist/embedders.d.ts.map +1 -0
- package/dist/embedders.js +74 -0
- package/dist/embedders.js.map +1 -0
- package/dist/index.cjs +65 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Embedder } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Dependency-free, deterministic embedder using the hashing trick (signed
|
|
4
|
+
* feature hashing). It maps each word token to a bucket and accumulates a
|
|
5
|
+
* signed count, then L2-normalizes. No model, no network — good for demos,
|
|
6
|
+
* tests, and small corpora where lexical overlap is enough. For semantic
|
|
7
|
+
* retrieval, swap in {@link OpenAICompatibleEmbedder} or your own `Embedder`.
|
|
8
|
+
*/
|
|
9
|
+
export declare class HashEmbedder implements Embedder {
|
|
10
|
+
private readonly _dim;
|
|
11
|
+
constructor(opts?: {
|
|
12
|
+
dimensions?: number;
|
|
13
|
+
});
|
|
14
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
15
|
+
private _embedOne;
|
|
16
|
+
}
|
|
17
|
+
export interface OpenAICompatibleEmbedderOptions {
|
|
18
|
+
apiKey: string;
|
|
19
|
+
/** Embedding model id, e.g. `text-embedding-3-small`. */
|
|
20
|
+
model: string;
|
|
21
|
+
/** API base. Default OpenAI; point at any OpenAI-compatible `/embeddings`. */
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Calls an OpenAI-compatible `/embeddings` endpoint (OpenAI, or DeepSeek/others
|
|
26
|
+
* that expose the same shape). Real semantic vectors, at the cost of a network
|
|
27
|
+
* round-trip and an API key. Implements the same {@link Embedder} seam, so it
|
|
28
|
+
* drops into `VectorRetriever` unchanged.
|
|
29
|
+
*/
|
|
30
|
+
export declare class OpenAICompatibleEmbedder implements Embedder {
|
|
31
|
+
private readonly _apiKey;
|
|
32
|
+
private readonly _model;
|
|
33
|
+
private readonly _baseUrl;
|
|
34
|
+
constructor(opts: OpenAICompatibleEmbedderOptions);
|
|
35
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=embedders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedders.d.ts","sourceRoot":"","sources":["../src/embedders.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;GAMG;AACH,qBAAa,YAAa,YAAW,QAAQ;IAC3C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;gBAElB,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;IAI1C,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAI3C,OAAO,CAAC,SAAS;CAclB;AAYD,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD;;;;;GAKG;AACH,qBAAa,wBAAyB,YAAW,QAAQ;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,IAAI,EAAE,+BAA+B;IAM3C,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;CAgBlD"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-free, deterministic embedder using the hashing trick (signed
|
|
3
|
+
* feature hashing). It maps each word token to a bucket and accumulates a
|
|
4
|
+
* signed count, then L2-normalizes. No model, no network — good for demos,
|
|
5
|
+
* tests, and small corpora where lexical overlap is enough. For semantic
|
|
6
|
+
* retrieval, swap in {@link OpenAICompatibleEmbedder} or your own `Embedder`.
|
|
7
|
+
*/
|
|
8
|
+
export class HashEmbedder {
|
|
9
|
+
_dim;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
this._dim = Math.max(8, opts?.dimensions ?? 256);
|
|
12
|
+
}
|
|
13
|
+
embed(texts) {
|
|
14
|
+
return Promise.resolve(texts.map((t) => this._embedOne(t)));
|
|
15
|
+
}
|
|
16
|
+
_embedOne(text) {
|
|
17
|
+
const vec = Array.from({ length: this._dim }, () => 0);
|
|
18
|
+
const tokens = text.toLowerCase().match(/\w+/g) ?? [];
|
|
19
|
+
for (const tok of tokens) {
|
|
20
|
+
const h = fnv1a(tok);
|
|
21
|
+
const idx = h % this._dim;
|
|
22
|
+
const sign = (h & 1) === 0 ? 1 : -1;
|
|
23
|
+
vec[idx] = (vec[idx] ?? 0) + sign;
|
|
24
|
+
}
|
|
25
|
+
let norm = 0;
|
|
26
|
+
for (const v of vec)
|
|
27
|
+
norm += v * v;
|
|
28
|
+
norm = Math.sqrt(norm) || 1;
|
|
29
|
+
return vec.map((v) => v / norm);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** FNV-1a 32-bit hash → non-negative integer. */
|
|
33
|
+
function fnv1a(str) {
|
|
34
|
+
let h = 0x811c9dc5;
|
|
35
|
+
for (let i = 0; i < str.length; i++) {
|
|
36
|
+
h ^= str.charCodeAt(i);
|
|
37
|
+
h = Math.imul(h, 0x01000193);
|
|
38
|
+
}
|
|
39
|
+
return h >>> 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Calls an OpenAI-compatible `/embeddings` endpoint (OpenAI, or DeepSeek/others
|
|
43
|
+
* that expose the same shape). Real semantic vectors, at the cost of a network
|
|
44
|
+
* round-trip and an API key. Implements the same {@link Embedder} seam, so it
|
|
45
|
+
* drops into `VectorRetriever` unchanged.
|
|
46
|
+
*/
|
|
47
|
+
export class OpenAICompatibleEmbedder {
|
|
48
|
+
_apiKey;
|
|
49
|
+
_model;
|
|
50
|
+
_baseUrl;
|
|
51
|
+
constructor(opts) {
|
|
52
|
+
this._apiKey = opts.apiKey;
|
|
53
|
+
this._model = opts.model;
|
|
54
|
+
this._baseUrl = (opts.baseUrl ?? "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
55
|
+
}
|
|
56
|
+
async embed(texts) {
|
|
57
|
+
if (texts.length === 0)
|
|
58
|
+
return [];
|
|
59
|
+
const resp = await fetch(`${this._baseUrl}/embeddings`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: {
|
|
62
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({ model: this._model, input: texts }),
|
|
66
|
+
});
|
|
67
|
+
if (!resp.ok) {
|
|
68
|
+
throw new Error(`Embeddings ${resp.status}: ${await resp.text().catch(() => "")}`);
|
|
69
|
+
}
|
|
70
|
+
const json = (await resp.json());
|
|
71
|
+
return (json.data ?? []).map((d) => d.embedding);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=embedders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedders.js","sourceRoot":"","sources":["../src/embedders.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -21,13 +21,76 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
CacheAwareRag: () => CacheAwareRag,
|
|
24
|
+
HashEmbedder: () => HashEmbedder,
|
|
24
25
|
MemoryVectorStore: () => MemoryVectorStore,
|
|
26
|
+
OpenAICompatibleEmbedder: () => OpenAICompatibleEmbedder,
|
|
25
27
|
VectorRetriever: () => VectorRetriever,
|
|
26
28
|
cosineSimilarity: () => cosineSimilarity,
|
|
27
29
|
jaccardSimilarity: () => jaccardSimilarity
|
|
28
30
|
});
|
|
29
31
|
module.exports = __toCommonJS(index_exports);
|
|
30
32
|
var import_core = require("@halo-sdk/core");
|
|
33
|
+
|
|
34
|
+
// src/embedders.ts
|
|
35
|
+
var HashEmbedder = class {
|
|
36
|
+
_dim;
|
|
37
|
+
constructor(opts) {
|
|
38
|
+
this._dim = Math.max(8, opts?.dimensions ?? 256);
|
|
39
|
+
}
|
|
40
|
+
embed(texts) {
|
|
41
|
+
return Promise.resolve(texts.map((t) => this._embedOne(t)));
|
|
42
|
+
}
|
|
43
|
+
_embedOne(text) {
|
|
44
|
+
const vec = Array.from({ length: this._dim }, () => 0);
|
|
45
|
+
const tokens = text.toLowerCase().match(/\w+/g) ?? [];
|
|
46
|
+
for (const tok of tokens) {
|
|
47
|
+
const h = fnv1a(tok);
|
|
48
|
+
const idx = h % this._dim;
|
|
49
|
+
const sign = (h & 1) === 0 ? 1 : -1;
|
|
50
|
+
vec[idx] = (vec[idx] ?? 0) + sign;
|
|
51
|
+
}
|
|
52
|
+
let norm = 0;
|
|
53
|
+
for (const v of vec) norm += v * v;
|
|
54
|
+
norm = Math.sqrt(norm) || 1;
|
|
55
|
+
return vec.map((v) => v / norm);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function fnv1a(str) {
|
|
59
|
+
let h = 2166136261;
|
|
60
|
+
for (let i = 0; i < str.length; i++) {
|
|
61
|
+
h ^= str.charCodeAt(i);
|
|
62
|
+
h = Math.imul(h, 16777619);
|
|
63
|
+
}
|
|
64
|
+
return h >>> 0;
|
|
65
|
+
}
|
|
66
|
+
var OpenAICompatibleEmbedder = class {
|
|
67
|
+
_apiKey;
|
|
68
|
+
_model;
|
|
69
|
+
_baseUrl;
|
|
70
|
+
constructor(opts) {
|
|
71
|
+
this._apiKey = opts.apiKey;
|
|
72
|
+
this._model = opts.model;
|
|
73
|
+
this._baseUrl = (opts.baseUrl ?? "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
74
|
+
}
|
|
75
|
+
async embed(texts) {
|
|
76
|
+
if (texts.length === 0) return [];
|
|
77
|
+
const resp = await fetch(`${this._baseUrl}/embeddings`, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: {
|
|
80
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
81
|
+
"Content-Type": "application/json"
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({ model: this._model, input: texts })
|
|
84
|
+
});
|
|
85
|
+
if (!resp.ok) {
|
|
86
|
+
throw new Error(`Embeddings ${resp.status}: ${await resp.text().catch(() => "")}`);
|
|
87
|
+
}
|
|
88
|
+
const json = await resp.json();
|
|
89
|
+
return (json.data ?? []).map((d) => d.embedding);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/index.ts
|
|
31
94
|
function cosineSimilarity(a, b) {
|
|
32
95
|
let dot = 0;
|
|
33
96
|
let na = 0;
|
|
@@ -123,7 +186,9 @@ function jaccardSimilarity(a, b) {
|
|
|
123
186
|
// Annotate the CommonJS export names for ESM import in node:
|
|
124
187
|
0 && (module.exports = {
|
|
125
188
|
CacheAwareRag,
|
|
189
|
+
HashEmbedder,
|
|
126
190
|
MemoryVectorStore,
|
|
191
|
+
OpenAICompatibleEmbedder,
|
|
127
192
|
VectorRetriever,
|
|
128
193
|
cosineSimilarity,
|
|
129
194
|
jaccardSimilarity
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { StableContext, type CacheSegment, type ChatMessage } from \"@halo-sdk/core\";\n\n/** A retrievable document. */\nexport interface RagDocument {\n id: string;\n text: string;\n metadata?: Record<string, unknown>;\n}\n\n/** A document with a relevance score (higher = more relevant). */\nexport interface ScoredDocument extends RagDocument {\n score: number;\n}\n\n// ── Seams (bring your own implementation) ──\n\n/** Turns text into vectors. Seam — wrap any embedding model. */\nexport interface Embedder {\n embed(texts: string[]): Promise<number[][]>;\n}\n\n/** Stores + searches document vectors. Seam — wrap any vector DB. */\nexport interface VectorStore {\n add(docs: RagDocument[], embeddings: number[][]): Promise<void> | void;\n query(embedding: number[], k: number): Promise<ScoredDocument[]> | ScoredDocument[];\n}\n\n/** Produces relevant documents for a query. Seam — the top-level RAG interface. */\nexport interface Retriever {\n retrieve(query: string, k: number): Promise<RagDocument[]>;\n}\n\n// ── In-memory defaults (dependency-free) ──\n\n/** Cosine similarity of two equal-length vectors. */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let na = 0;\n let nb = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i]! * b[i]!;\n na += a[i]! * a[i]!;\n nb += b[i]! * b[i]!;\n }\n const denom = Math.sqrt(na) * Math.sqrt(nb);\n return denom === 0 ? 0 : dot / denom;\n}\n\n/** A simple in-memory cosine vector store. Good for tests, demos, small corpora. */\nexport class MemoryVectorStore implements VectorStore {\n private _items: { doc: RagDocument; embedding: number[] }[] = [];\n\n add(docs: RagDocument[], embeddings: number[][]): void {\n for (let i = 0; i < docs.length; i++) {\n this._items.push({ doc: docs[i]!, embedding: embeddings[i]! });\n }\n }\n\n query(embedding: number[], k: number): ScoredDocument[] {\n return this._items\n .map(({ doc, embedding: e }) => ({ ...doc, score: cosineSimilarity(embedding, e) }))\n .sort((a, b) => b.score - a.score)\n .slice(0, k);\n }\n}\n\n/** Build a {@link Retriever} from an {@link Embedder} + {@link VectorStore}. */\nexport class VectorRetriever implements Retriever {\n constructor(\n private readonly _embedder: Embedder,\n private readonly _store: VectorStore,\n ) {}\n\n async retrieve(query: string, k: number): Promise<RagDocument[]> {\n const [queryEmbedding] = await this._embedder.embed([query]);\n return [...(await this._store.query(queryEmbedding!, k))];\n }\n}\n\n// ── Cache-aware orchestration (what Halo owns) ──\n\nexport interface CacheAwareRagOptions {\n retriever: Retriever;\n /** Docs to retrieve per query. Default 4. */\n k?: number;\n /**\n * Sticky-retrieval threshold. If the new query's lexical similarity to the\n * last one is ≥ this, retrieval is skipped and the cached segment is reused.\n * Default 0.85. Set to 1 to always re-retrieve.\n */\n stickyThreshold?: number;\n /** Segment id (for cache miss attribution). Default \"rag\". */\n segmentId?: string;\n /** Format a document into a context message. Default: `[doc id] text`. */\n formatDoc?: (doc: RagDocument) => string;\n /** Override the lexical similarity measure (default token Jaccard). */\n similarity?: (a: string, b: string) => number;\n}\n\n/**\n * Cache-aware RAG: turns a {@link Retriever} into a reusable {@link CacheSegment}\n * with two cache-preserving policies:\n *\n * - **sticky retrieval** — when consecutive queries are near-duplicates, skip\n * re-retrieval entirely so the segment (and the prefix cache after it) is\n * untouched, and\n * - **append-only growth** — when a new result set extends the previous one,\n * append the new docs instead of rebuilding, keeping the already-cached prefix\n * valid; only a genuinely different result set rebuilds (and busts) the block.\n *\n * Attach `rag.segment` via `agent.setContextSegments([rag.segment])`.\n */\nexport class CacheAwareRag {\n readonly segment: StableContext;\n\n private readonly _retriever: Retriever;\n private readonly _k: number;\n private readonly _stickyThreshold: number;\n private readonly _formatDoc: (doc: RagDocument) => string;\n private readonly _similarity: (a: string, b: string) => number;\n\n private _lastQuery: string | null = null;\n private _docIds: string[] = [];\n\n constructor(opts: CacheAwareRagOptions) {\n this._retriever = opts.retriever;\n this._k = opts.k ?? 4;\n this._stickyThreshold = opts.stickyThreshold ?? 0.85;\n this._formatDoc = opts.formatDoc ?? ((d) => `[${d.id}] ${d.text}`);\n this._similarity = opts.similarity ?? jaccardSimilarity;\n this.segment = new StableContext({ id: opts.segmentId ?? \"rag\", kind: \"rag\", messages: [] });\n }\n\n /** Document ids currently in the segment, in order. */\n get documentIds(): readonly string[] {\n return this._docIds;\n }\n\n /**\n * Refresh the segment for `query`. Returns whether a retrieval actually ran\n * (`false` = sticky reuse) and whether the cache was preserved (append-only or\n * skip) vs. busted (rebuild).\n */\n async update(query: string): Promise<{ retrieved: boolean; cachePreserved: boolean }> {\n if (\n this._lastQuery !== null &&\n this._docIds.length > 0 &&\n this._similarity(query, this._lastQuery) >= this._stickyThreshold\n ) {\n return { retrieved: false, cachePreserved: true }; // sticky — segment untouched\n }\n\n this._lastQuery = query;\n const docs = await this._retriever.retrieve(query, this._k);\n const newIds = docs.map((d) => d.id);\n\n // Append-only when the previous ids are an ordered prefix of the new ids.\n const isPrefix =\n this._docIds.length > 0 &&\n this._docIds.length <= newIds.length &&\n this._docIds.every((id, i) => id === newIds[i]);\n\n if (isPrefix) {\n const extra = docs.slice(this._docIds.length);\n this.segment.append(extra.map((d) => this._toMessage(d)));\n this._docIds = newIds;\n return { retrieved: true, cachePreserved: true };\n }\n\n this.segment.setMessages(docs.map((d) => this._toMessage(d)));\n this._docIds = newIds;\n return { retrieved: true, cachePreserved: this._docIds.length === 0 };\n }\n\n private _toMessage(doc: RagDocument): ChatMessage {\n return { role: \"user\", content: this._formatDoc(doc), discardable: false };\n }\n}\n\n/** Token-set Jaccard similarity (lowercased word tokens). */\nexport function jaccardSimilarity(a: string, b: string): number {\n const ta = new Set(a.toLowerCase().match(/\\w+/g) ?? []);\n const tb = new Set(b.toLowerCase().match(/\\w+/g) ?? []);\n if (ta.size === 0 && tb.size === 0) return 1;\n let inter = 0;\n for (const t of ta) if (tb.has(t)) inter++;\n const union = ta.size + tb.size - inter;\n return union === 0 ? 0 : inter / union;\n}\n\nexport type { CacheSegment };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/embedders.ts"],"sourcesContent":["import { StableContext, type CacheSegment, type ChatMessage } from \"@halo-sdk/core\";\n\n/** A retrievable document. */\nexport interface RagDocument {\n id: string;\n text: string;\n metadata?: Record<string, unknown>;\n}\n\n/** A document with a relevance score (higher = more relevant). */\nexport interface ScoredDocument extends RagDocument {\n score: number;\n}\n\n// ── Seams (bring your own implementation) ──\n\n/** Turns text into vectors. Seam — wrap any embedding model. */\nexport interface Embedder {\n embed(texts: string[]): Promise<number[][]>;\n}\n\n/** Stores + searches document vectors. Seam — wrap any vector DB. */\nexport interface VectorStore {\n add(docs: RagDocument[], embeddings: number[][]): Promise<void> | void;\n query(embedding: number[], k: number): Promise<ScoredDocument[]> | ScoredDocument[];\n}\n\n/** Produces relevant documents for a query. Seam — the top-level RAG interface. */\nexport interface Retriever {\n retrieve(query: string, k: number): Promise<RagDocument[]>;\n}\n\n// ── In-memory defaults (dependency-free) ──\n\n/** Cosine similarity of two equal-length vectors. */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let na = 0;\n let nb = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i]! * b[i]!;\n na += a[i]! * a[i]!;\n nb += b[i]! * b[i]!;\n }\n const denom = Math.sqrt(na) * Math.sqrt(nb);\n return denom === 0 ? 0 : dot / denom;\n}\n\n/** A simple in-memory cosine vector store. Good for tests, demos, small corpora. */\nexport class MemoryVectorStore implements VectorStore {\n private _items: { doc: RagDocument; embedding: number[] }[] = [];\n\n add(docs: RagDocument[], embeddings: number[][]): void {\n for (let i = 0; i < docs.length; i++) {\n this._items.push({ doc: docs[i]!, embedding: embeddings[i]! });\n }\n }\n\n query(embedding: number[], k: number): ScoredDocument[] {\n return this._items\n .map(({ doc, embedding: e }) => ({ ...doc, score: cosineSimilarity(embedding, e) }))\n .sort((a, b) => b.score - a.score)\n .slice(0, k);\n }\n}\n\n/** Build a {@link Retriever} from an {@link Embedder} + {@link VectorStore}. */\nexport class VectorRetriever implements Retriever {\n constructor(\n private readonly _embedder: Embedder,\n private readonly _store: VectorStore,\n ) {}\n\n async retrieve(query: string, k: number): Promise<RagDocument[]> {\n const [queryEmbedding] = await this._embedder.embed([query]);\n return [...(await this._store.query(queryEmbedding!, k))];\n }\n}\n\n// ── Cache-aware orchestration (what Halo owns) ──\n\nexport interface CacheAwareRagOptions {\n retriever: Retriever;\n /** Docs to retrieve per query. Default 4. */\n k?: number;\n /**\n * Sticky-retrieval threshold. If the new query's lexical similarity to the\n * last one is ≥ this, retrieval is skipped and the cached segment is reused.\n * Default 0.85. Set to 1 to always re-retrieve.\n */\n stickyThreshold?: number;\n /** Segment id (for cache miss attribution). Default \"rag\". */\n segmentId?: string;\n /** Format a document into a context message. Default: `[doc id] text`. */\n formatDoc?: (doc: RagDocument) => string;\n /** Override the lexical similarity measure (default token Jaccard). */\n similarity?: (a: string, b: string) => number;\n}\n\n/**\n * Cache-aware RAG: turns a {@link Retriever} into a reusable {@link CacheSegment}\n * with two cache-preserving policies:\n *\n * - **sticky retrieval** — when consecutive queries are near-duplicates, skip\n * re-retrieval entirely so the segment (and the prefix cache after it) is\n * untouched, and\n * - **append-only growth** — when a new result set extends the previous one,\n * append the new docs instead of rebuilding, keeping the already-cached prefix\n * valid; only a genuinely different result set rebuilds (and busts) the block.\n *\n * Attach `rag.segment` via `agent.setContextSegments([rag.segment])`.\n */\nexport class CacheAwareRag {\n readonly segment: StableContext;\n\n private readonly _retriever: Retriever;\n private readonly _k: number;\n private readonly _stickyThreshold: number;\n private readonly _formatDoc: (doc: RagDocument) => string;\n private readonly _similarity: (a: string, b: string) => number;\n\n private _lastQuery: string | null = null;\n private _docIds: string[] = [];\n\n constructor(opts: CacheAwareRagOptions) {\n this._retriever = opts.retriever;\n this._k = opts.k ?? 4;\n this._stickyThreshold = opts.stickyThreshold ?? 0.85;\n this._formatDoc = opts.formatDoc ?? ((d) => `[${d.id}] ${d.text}`);\n this._similarity = opts.similarity ?? jaccardSimilarity;\n this.segment = new StableContext({ id: opts.segmentId ?? \"rag\", kind: \"rag\", messages: [] });\n }\n\n /** Document ids currently in the segment, in order. */\n get documentIds(): readonly string[] {\n return this._docIds;\n }\n\n /**\n * Refresh the segment for `query`. Returns whether a retrieval actually ran\n * (`false` = sticky reuse) and whether the cache was preserved (append-only or\n * skip) vs. busted (rebuild).\n */\n async update(query: string): Promise<{ retrieved: boolean; cachePreserved: boolean }> {\n if (\n this._lastQuery !== null &&\n this._docIds.length > 0 &&\n this._similarity(query, this._lastQuery) >= this._stickyThreshold\n ) {\n return { retrieved: false, cachePreserved: true }; // sticky — segment untouched\n }\n\n this._lastQuery = query;\n const docs = await this._retriever.retrieve(query, this._k);\n const newIds = docs.map((d) => d.id);\n\n // Append-only when the previous ids are an ordered prefix of the new ids.\n const isPrefix =\n this._docIds.length > 0 &&\n this._docIds.length <= newIds.length &&\n this._docIds.every((id, i) => id === newIds[i]);\n\n if (isPrefix) {\n const extra = docs.slice(this._docIds.length);\n this.segment.append(extra.map((d) => this._toMessage(d)));\n this._docIds = newIds;\n return { retrieved: true, cachePreserved: true };\n }\n\n this.segment.setMessages(docs.map((d) => this._toMessage(d)));\n this._docIds = newIds;\n return { retrieved: true, cachePreserved: this._docIds.length === 0 };\n }\n\n private _toMessage(doc: RagDocument): ChatMessage {\n return { role: \"user\", content: this._formatDoc(doc), discardable: false };\n }\n}\n\n/** Token-set Jaccard similarity (lowercased word tokens). */\nexport function jaccardSimilarity(a: string, b: string): number {\n const ta = new Set(a.toLowerCase().match(/\\w+/g) ?? []);\n const tb = new Set(b.toLowerCase().match(/\\w+/g) ?? []);\n if (ta.size === 0 && tb.size === 0) return 1;\n let inter = 0;\n for (const t of ta) if (tb.has(t)) inter++;\n const union = ta.size + tb.size - inter;\n return union === 0 ? 0 : inter / union;\n}\n\nexport { HashEmbedder, OpenAICompatibleEmbedder } from \"./embedders.js\";\nexport type { OpenAICompatibleEmbedderOptions } from \"./embedders.js\";\n\nexport type { CacheSegment };\n","import type { Embedder } from \"./index.js\";\n\n/**\n * Dependency-free, deterministic embedder using the hashing trick (signed\n * feature hashing). It maps each word token to a bucket and accumulates a\n * signed count, then L2-normalizes. No model, no network — good for demos,\n * tests, and small corpora where lexical overlap is enough. For semantic\n * retrieval, swap in {@link OpenAICompatibleEmbedder} or your own `Embedder`.\n */\nexport class HashEmbedder implements Embedder {\n private readonly _dim: number;\n\n constructor(opts?: { dimensions?: number }) {\n this._dim = Math.max(8, opts?.dimensions ?? 256);\n }\n\n embed(texts: string[]): Promise<number[][]> {\n return Promise.resolve(texts.map((t) => this._embedOne(t)));\n }\n\n private _embedOne(text: string): number[] {\n const vec = Array.from({ length: this._dim }, () => 0);\n const tokens = text.toLowerCase().match(/\\w+/g) ?? [];\n for (const tok of tokens) {\n const h = fnv1a(tok);\n const idx = h % this._dim;\n const sign = (h & 1) === 0 ? 1 : -1;\n vec[idx] = (vec[idx] ?? 0) + sign;\n }\n let norm = 0;\n for (const v of vec) norm += v * v;\n norm = Math.sqrt(norm) || 1;\n return vec.map((v) => v / norm);\n }\n}\n\n/** FNV-1a 32-bit hash → non-negative integer. */\nfunction fnv1a(str: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\nexport interface OpenAICompatibleEmbedderOptions {\n apiKey: string;\n /** Embedding model id, e.g. `text-embedding-3-small`. */\n model: string;\n /** API base. Default OpenAI; point at any OpenAI-compatible `/embeddings`. */\n baseUrl?: string;\n}\n\ninterface EmbeddingResponse {\n data?: { embedding: number[] }[];\n}\n\n/**\n * Calls an OpenAI-compatible `/embeddings` endpoint (OpenAI, or DeepSeek/others\n * that expose the same shape). Real semantic vectors, at the cost of a network\n * round-trip and an API key. Implements the same {@link Embedder} seam, so it\n * drops into `VectorRetriever` unchanged.\n */\nexport class OpenAICompatibleEmbedder implements Embedder {\n private readonly _apiKey: string;\n private readonly _model: string;\n private readonly _baseUrl: string;\n\n constructor(opts: OpenAICompatibleEmbedderOptions) {\n this._apiKey = opts.apiKey;\n this._model = opts.model;\n this._baseUrl = (opts.baseUrl ?? \"https://api.openai.com/v1\").replace(/\\/+$/, \"\");\n }\n\n async embed(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n const resp = await fetch(`${this._baseUrl}/embeddings`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this._apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ model: this._model, input: texts }),\n });\n if (!resp.ok) {\n throw new Error(`Embeddings ${resp.status}: ${await resp.text().catch(() => \"\")}`);\n }\n const json = (await resp.json()) as EmbeddingResponse;\n return (json.data ?? []).map((d) => d.embedding);\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -92,5 +92,7 @@ export declare class CacheAwareRag {
|
|
|
92
92
|
}
|
|
93
93
|
/** Token-set Jaccard similarity (lowercased word tokens). */
|
|
94
94
|
export declare function jaccardSimilarity(a: string, b: string): number;
|
|
95
|
+
export { HashEmbedder, OpenAICompatibleEmbedder } from "./embedders.js";
|
|
96
|
+
export type { OpenAICompatibleEmbedderOptions } from "./embedders.js";
|
|
95
97
|
export type { CacheSegment };
|
|
96
98
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAEpF,8BAA8B;AAC9B,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,kEAAkE;AAClE,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,KAAK,EAAE,MAAM,CAAC;CACf;AAID,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;CAC7C;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC;CACrF;AAED,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CAC5D;AAID,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAWjE;AAED,oFAAoF;AACpF,qBAAa,iBAAkB,YAAW,WAAW;IACnD,OAAO,CAAC,MAAM,CAAmD;IAEjE,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI;IAMtD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE;CAMxD;AAED,gFAAgF;AAChF,qBAAa,eAAgB,YAAW,SAAS;IAE7C,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,SAAS,EAAE,QAAQ,EACnB,MAAM,EAAE,WAAW;IAGhC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAIjE;AAID,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,CAAC;IACrB,6CAA6C;IAC7C,CAAC,CAAC,EAAE,MAAM,CAAC;IACX;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,MAAM,CAAC;IACzC,uEAAuE;IACvE,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/C;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAC1D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmC;IAE/D,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,OAAO,CAAgB;gBAEnB,IAAI,EAAE,oBAAoB;IAStC,uDAAuD;IACvD,IAAI,WAAW,IAAI,SAAS,MAAM,EAAE,CAEnC;IAED;;;;OAIG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC;IA+BrF,OAAO,CAAC,UAAU;CAGnB;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ9D;AAED,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AACxE,YAAY,EAAE,+BAA+B,EAAE,MAAM,gBAAgB,CAAC;AAEtE,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { StableContext } from "@halo-sdk/core";
|
|
3
|
+
|
|
4
|
+
// src/embedders.ts
|
|
5
|
+
var HashEmbedder = class {
|
|
6
|
+
_dim;
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
this._dim = Math.max(8, opts?.dimensions ?? 256);
|
|
9
|
+
}
|
|
10
|
+
embed(texts) {
|
|
11
|
+
return Promise.resolve(texts.map((t) => this._embedOne(t)));
|
|
12
|
+
}
|
|
13
|
+
_embedOne(text) {
|
|
14
|
+
const vec = Array.from({ length: this._dim }, () => 0);
|
|
15
|
+
const tokens = text.toLowerCase().match(/\w+/g) ?? [];
|
|
16
|
+
for (const tok of tokens) {
|
|
17
|
+
const h = fnv1a(tok);
|
|
18
|
+
const idx = h % this._dim;
|
|
19
|
+
const sign = (h & 1) === 0 ? 1 : -1;
|
|
20
|
+
vec[idx] = (vec[idx] ?? 0) + sign;
|
|
21
|
+
}
|
|
22
|
+
let norm = 0;
|
|
23
|
+
for (const v of vec) norm += v * v;
|
|
24
|
+
norm = Math.sqrt(norm) || 1;
|
|
25
|
+
return vec.map((v) => v / norm);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function fnv1a(str) {
|
|
29
|
+
let h = 2166136261;
|
|
30
|
+
for (let i = 0; i < str.length; i++) {
|
|
31
|
+
h ^= str.charCodeAt(i);
|
|
32
|
+
h = Math.imul(h, 16777619);
|
|
33
|
+
}
|
|
34
|
+
return h >>> 0;
|
|
35
|
+
}
|
|
36
|
+
var OpenAICompatibleEmbedder = class {
|
|
37
|
+
_apiKey;
|
|
38
|
+
_model;
|
|
39
|
+
_baseUrl;
|
|
40
|
+
constructor(opts) {
|
|
41
|
+
this._apiKey = opts.apiKey;
|
|
42
|
+
this._model = opts.model;
|
|
43
|
+
this._baseUrl = (opts.baseUrl ?? "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
44
|
+
}
|
|
45
|
+
async embed(texts) {
|
|
46
|
+
if (texts.length === 0) return [];
|
|
47
|
+
const resp = await fetch(`${this._baseUrl}/embeddings`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
51
|
+
"Content-Type": "application/json"
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify({ model: this._model, input: texts })
|
|
54
|
+
});
|
|
55
|
+
if (!resp.ok) {
|
|
56
|
+
throw new Error(`Embeddings ${resp.status}: ${await resp.text().catch(() => "")}`);
|
|
57
|
+
}
|
|
58
|
+
const json = await resp.json();
|
|
59
|
+
return (json.data ?? []).map((d) => d.embedding);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/index.ts
|
|
3
64
|
function cosineSimilarity(a, b) {
|
|
4
65
|
let dot = 0;
|
|
5
66
|
let na = 0;
|
|
@@ -94,7 +155,9 @@ function jaccardSimilarity(a, b) {
|
|
|
94
155
|
}
|
|
95
156
|
export {
|
|
96
157
|
CacheAwareRag,
|
|
158
|
+
HashEmbedder,
|
|
97
159
|
MemoryVectorStore,
|
|
160
|
+
OpenAICompatibleEmbedder,
|
|
98
161
|
VectorRetriever,
|
|
99
162
|
cosineSimilarity,
|
|
100
163
|
jaccardSimilarity
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { StableContext, type CacheSegment, type ChatMessage } from \"@halo-sdk/core\";\n\n/** A retrievable document. */\nexport interface RagDocument {\n id: string;\n text: string;\n metadata?: Record<string, unknown>;\n}\n\n/** A document with a relevance score (higher = more relevant). */\nexport interface ScoredDocument extends RagDocument {\n score: number;\n}\n\n// ── Seams (bring your own implementation) ──\n\n/** Turns text into vectors. Seam — wrap any embedding model. */\nexport interface Embedder {\n embed(texts: string[]): Promise<number[][]>;\n}\n\n/** Stores + searches document vectors. Seam — wrap any vector DB. */\nexport interface VectorStore {\n add(docs: RagDocument[], embeddings: number[][]): Promise<void> | void;\n query(embedding: number[], k: number): Promise<ScoredDocument[]> | ScoredDocument[];\n}\n\n/** Produces relevant documents for a query. Seam — the top-level RAG interface. */\nexport interface Retriever {\n retrieve(query: string, k: number): Promise<RagDocument[]>;\n}\n\n// ── In-memory defaults (dependency-free) ──\n\n/** Cosine similarity of two equal-length vectors. */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let na = 0;\n let nb = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i]! * b[i]!;\n na += a[i]! * a[i]!;\n nb += b[i]! * b[i]!;\n }\n const denom = Math.sqrt(na) * Math.sqrt(nb);\n return denom === 0 ? 0 : dot / denom;\n}\n\n/** A simple in-memory cosine vector store. Good for tests, demos, small corpora. */\nexport class MemoryVectorStore implements VectorStore {\n private _items: { doc: RagDocument; embedding: number[] }[] = [];\n\n add(docs: RagDocument[], embeddings: number[][]): void {\n for (let i = 0; i < docs.length; i++) {\n this._items.push({ doc: docs[i]!, embedding: embeddings[i]! });\n }\n }\n\n query(embedding: number[], k: number): ScoredDocument[] {\n return this._items\n .map(({ doc, embedding: e }) => ({ ...doc, score: cosineSimilarity(embedding, e) }))\n .sort((a, b) => b.score - a.score)\n .slice(0, k);\n }\n}\n\n/** Build a {@link Retriever} from an {@link Embedder} + {@link VectorStore}. */\nexport class VectorRetriever implements Retriever {\n constructor(\n private readonly _embedder: Embedder,\n private readonly _store: VectorStore,\n ) {}\n\n async retrieve(query: string, k: number): Promise<RagDocument[]> {\n const [queryEmbedding] = await this._embedder.embed([query]);\n return [...(await this._store.query(queryEmbedding!, k))];\n }\n}\n\n// ── Cache-aware orchestration (what Halo owns) ──\n\nexport interface CacheAwareRagOptions {\n retriever: Retriever;\n /** Docs to retrieve per query. Default 4. */\n k?: number;\n /**\n * Sticky-retrieval threshold. If the new query's lexical similarity to the\n * last one is ≥ this, retrieval is skipped and the cached segment is reused.\n * Default 0.85. Set to 1 to always re-retrieve.\n */\n stickyThreshold?: number;\n /** Segment id (for cache miss attribution). Default \"rag\". */\n segmentId?: string;\n /** Format a document into a context message. Default: `[doc id] text`. */\n formatDoc?: (doc: RagDocument) => string;\n /** Override the lexical similarity measure (default token Jaccard). */\n similarity?: (a: string, b: string) => number;\n}\n\n/**\n * Cache-aware RAG: turns a {@link Retriever} into a reusable {@link CacheSegment}\n * with two cache-preserving policies:\n *\n * - **sticky retrieval** — when consecutive queries are near-duplicates, skip\n * re-retrieval entirely so the segment (and the prefix cache after it) is\n * untouched, and\n * - **append-only growth** — when a new result set extends the previous one,\n * append the new docs instead of rebuilding, keeping the already-cached prefix\n * valid; only a genuinely different result set rebuilds (and busts) the block.\n *\n * Attach `rag.segment` via `agent.setContextSegments([rag.segment])`.\n */\nexport class CacheAwareRag {\n readonly segment: StableContext;\n\n private readonly _retriever: Retriever;\n private readonly _k: number;\n private readonly _stickyThreshold: number;\n private readonly _formatDoc: (doc: RagDocument) => string;\n private readonly _similarity: (a: string, b: string) => number;\n\n private _lastQuery: string | null = null;\n private _docIds: string[] = [];\n\n constructor(opts: CacheAwareRagOptions) {\n this._retriever = opts.retriever;\n this._k = opts.k ?? 4;\n this._stickyThreshold = opts.stickyThreshold ?? 0.85;\n this._formatDoc = opts.formatDoc ?? ((d) => `[${d.id}] ${d.text}`);\n this._similarity = opts.similarity ?? jaccardSimilarity;\n this.segment = new StableContext({ id: opts.segmentId ?? \"rag\", kind: \"rag\", messages: [] });\n }\n\n /** Document ids currently in the segment, in order. */\n get documentIds(): readonly string[] {\n return this._docIds;\n }\n\n /**\n * Refresh the segment for `query`. Returns whether a retrieval actually ran\n * (`false` = sticky reuse) and whether the cache was preserved (append-only or\n * skip) vs. busted (rebuild).\n */\n async update(query: string): Promise<{ retrieved: boolean; cachePreserved: boolean }> {\n if (\n this._lastQuery !== null &&\n this._docIds.length > 0 &&\n this._similarity(query, this._lastQuery) >= this._stickyThreshold\n ) {\n return { retrieved: false, cachePreserved: true }; // sticky — segment untouched\n }\n\n this._lastQuery = query;\n const docs = await this._retriever.retrieve(query, this._k);\n const newIds = docs.map((d) => d.id);\n\n // Append-only when the previous ids are an ordered prefix of the new ids.\n const isPrefix =\n this._docIds.length > 0 &&\n this._docIds.length <= newIds.length &&\n this._docIds.every((id, i) => id === newIds[i]);\n\n if (isPrefix) {\n const extra = docs.slice(this._docIds.length);\n this.segment.append(extra.map((d) => this._toMessage(d)));\n this._docIds = newIds;\n return { retrieved: true, cachePreserved: true };\n }\n\n this.segment.setMessages(docs.map((d) => this._toMessage(d)));\n this._docIds = newIds;\n return { retrieved: true, cachePreserved: this._docIds.length === 0 };\n }\n\n private _toMessage(doc: RagDocument): ChatMessage {\n return { role: \"user\", content: this._formatDoc(doc), discardable: false };\n }\n}\n\n/** Token-set Jaccard similarity (lowercased word tokens). */\nexport function jaccardSimilarity(a: string, b: string): number {\n const ta = new Set(a.toLowerCase().match(/\\w+/g) ?? []);\n const tb = new Set(b.toLowerCase().match(/\\w+/g) ?? []);\n if (ta.size === 0 && tb.size === 0) return 1;\n let inter = 0;\n for (const t of ta) if (tb.has(t)) inter++;\n const union = ta.size + tb.size - inter;\n return union === 0 ? 0 : inter / union;\n}\n\nexport type { CacheSegment };\n"],"mappings":";AAAA,SAAS,qBAA0D;AAmC5D,SAAS,iBAAiB,GAAa,GAAqB;AACjE,MAAI,MAAM;AACV,MAAI,KAAK;AACT,MAAI,KAAK;AACT,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,WAAO,EAAE,CAAC,IAAK,EAAE,CAAC;AAClB,UAAM,EAAE,CAAC,IAAK,EAAE,CAAC;AACjB,UAAM,EAAE,CAAC,IAAK,EAAE,CAAC;AAAA,EACnB;AACA,QAAM,QAAQ,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,EAAE;AAC1C,SAAO,UAAU,IAAI,IAAI,MAAM;AACjC;AAGO,IAAM,oBAAN,MAA+C;AAAA,EAC5C,SAAsD,CAAC;AAAA,EAE/D,IAAI,MAAqB,YAA8B;AACrD,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,WAAK,OAAO,KAAK,EAAE,KAAK,KAAK,CAAC,GAAI,WAAW,WAAW,CAAC,EAAG,CAAC;AAAA,IAC/D;AAAA,EACF;AAAA,EAEA,MAAM,WAAqB,GAA6B;AACtD,WAAO,KAAK,OACT,IAAI,CAAC,EAAE,KAAK,WAAW,EAAE,OAAO,EAAE,GAAG,KAAK,OAAO,iBAAiB,WAAW,CAAC,EAAE,EAAE,EAClF,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,MAAM,GAAG,CAAC;AAAA,EACf;AACF;AAGO,IAAM,kBAAN,MAA2C;AAAA,EAChD,YACmB,WACA,QACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA,EAGnB,MAAM,SAAS,OAAe,GAAmC;AAC/D,UAAM,CAAC,cAAc,IAAI,MAAM,KAAK,UAAU,MAAM,CAAC,KAAK,CAAC;AAC3D,WAAO,CAAC,GAAI,MAAM,KAAK,OAAO,MAAM,gBAAiB,CAAC,CAAE;AAAA,EAC1D;AACF;AAmCO,IAAM,gBAAN,MAAoB;AAAA,EAChB;AAAA,EAEQ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,aAA4B;AAAA,EAC5B,UAAoB,CAAC;AAAA,EAE7B,YAAY,MAA4B;AACtC,SAAK,aAAa,KAAK;AACvB,SAAK,KAAK,KAAK,KAAK;AACpB,SAAK,mBAAmB,KAAK,mBAAmB;AAChD,SAAK,aAAa,KAAK,cAAc,CAAC,MAAM,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI;AAC/D,SAAK,cAAc,KAAK,cAAc;AACtC,SAAK,UAAU,IAAI,cAAc,EAAE,IAAI,KAAK,aAAa,OAAO,MAAM,OAAO,UAAU,CAAC,EAAE,CAAC;AAAA,EAC7F;AAAA;AAAA,EAGA,IAAI,cAAiC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAO,OAAyE;AACpF,QACE,KAAK,eAAe,QACpB,KAAK,QAAQ,SAAS,KACtB,KAAK,YAAY,OAAO,KAAK,UAAU,KAAK,KAAK,kBACjD;AACA,aAAO,EAAE,WAAW,OAAO,gBAAgB,KAAK;AAAA,IAClD;AAEA,SAAK,aAAa;AAClB,UAAM,OAAO,MAAM,KAAK,WAAW,SAAS,OAAO,KAAK,EAAE;AAC1D,UAAM,SAAS,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;AAGnC,UAAM,WACJ,KAAK,QAAQ,SAAS,KACtB,KAAK,QAAQ,UAAU,OAAO,UAC9B,KAAK,QAAQ,MAAM,CAAC,IAAI,MAAM,OAAO,OAAO,CAAC,CAAC;AAEhD,QAAI,UAAU;AACZ,YAAM,QAAQ,KAAK,MAAM,KAAK,QAAQ,MAAM;AAC5C,WAAK,QAAQ,OAAO,MAAM,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC;AACxD,WAAK,UAAU;AACf,aAAO,EAAE,WAAW,MAAM,gBAAgB,KAAK;AAAA,IACjD;AAEA,SAAK,QAAQ,YAAY,KAAK,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC;AAC5D,SAAK,UAAU;AACf,WAAO,EAAE,WAAW,MAAM,gBAAgB,KAAK,QAAQ,WAAW,EAAE;AAAA,EACtE;AAAA,EAEQ,WAAW,KAA+B;AAChD,WAAO,EAAE,MAAM,QAAQ,SAAS,KAAK,WAAW,GAAG,GAAG,aAAa,MAAM;AAAA,EAC3E;AACF;AAGO,SAAS,kBAAkB,GAAW,GAAmB;AAC9D,QAAM,KAAK,IAAI,IAAI,EAAE,YAAY,EAAE,MAAM,MAAM,KAAK,CAAC,CAAC;AACtD,QAAM,KAAK,IAAI,IAAI,EAAE,YAAY,EAAE,MAAM,MAAM,KAAK,CAAC,CAAC;AACtD,MAAI,GAAG,SAAS,KAAK,GAAG,SAAS,EAAG,QAAO;AAC3C,MAAI,QAAQ;AACZ,aAAW,KAAK,GAAI,KAAI,GAAG,IAAI,CAAC,EAAG;AACnC,QAAM,QAAQ,GAAG,OAAO,GAAG,OAAO;AAClC,SAAO,UAAU,IAAI,IAAI,QAAQ;AACnC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/embedders.ts"],"sourcesContent":["import { StableContext, type CacheSegment, type ChatMessage } from \"@halo-sdk/core\";\n\n/** A retrievable document. */\nexport interface RagDocument {\n id: string;\n text: string;\n metadata?: Record<string, unknown>;\n}\n\n/** A document with a relevance score (higher = more relevant). */\nexport interface ScoredDocument extends RagDocument {\n score: number;\n}\n\n// ── Seams (bring your own implementation) ──\n\n/** Turns text into vectors. Seam — wrap any embedding model. */\nexport interface Embedder {\n embed(texts: string[]): Promise<number[][]>;\n}\n\n/** Stores + searches document vectors. Seam — wrap any vector DB. */\nexport interface VectorStore {\n add(docs: RagDocument[], embeddings: number[][]): Promise<void> | void;\n query(embedding: number[], k: number): Promise<ScoredDocument[]> | ScoredDocument[];\n}\n\n/** Produces relevant documents for a query. Seam — the top-level RAG interface. */\nexport interface Retriever {\n retrieve(query: string, k: number): Promise<RagDocument[]>;\n}\n\n// ── In-memory defaults (dependency-free) ──\n\n/** Cosine similarity of two equal-length vectors. */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let na = 0;\n let nb = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i]! * b[i]!;\n na += a[i]! * a[i]!;\n nb += b[i]! * b[i]!;\n }\n const denom = Math.sqrt(na) * Math.sqrt(nb);\n return denom === 0 ? 0 : dot / denom;\n}\n\n/** A simple in-memory cosine vector store. Good for tests, demos, small corpora. */\nexport class MemoryVectorStore implements VectorStore {\n private _items: { doc: RagDocument; embedding: number[] }[] = [];\n\n add(docs: RagDocument[], embeddings: number[][]): void {\n for (let i = 0; i < docs.length; i++) {\n this._items.push({ doc: docs[i]!, embedding: embeddings[i]! });\n }\n }\n\n query(embedding: number[], k: number): ScoredDocument[] {\n return this._items\n .map(({ doc, embedding: e }) => ({ ...doc, score: cosineSimilarity(embedding, e) }))\n .sort((a, b) => b.score - a.score)\n .slice(0, k);\n }\n}\n\n/** Build a {@link Retriever} from an {@link Embedder} + {@link VectorStore}. */\nexport class VectorRetriever implements Retriever {\n constructor(\n private readonly _embedder: Embedder,\n private readonly _store: VectorStore,\n ) {}\n\n async retrieve(query: string, k: number): Promise<RagDocument[]> {\n const [queryEmbedding] = await this._embedder.embed([query]);\n return [...(await this._store.query(queryEmbedding!, k))];\n }\n}\n\n// ── Cache-aware orchestration (what Halo owns) ──\n\nexport interface CacheAwareRagOptions {\n retriever: Retriever;\n /** Docs to retrieve per query. Default 4. */\n k?: number;\n /**\n * Sticky-retrieval threshold. If the new query's lexical similarity to the\n * last one is ≥ this, retrieval is skipped and the cached segment is reused.\n * Default 0.85. Set to 1 to always re-retrieve.\n */\n stickyThreshold?: number;\n /** Segment id (for cache miss attribution). Default \"rag\". */\n segmentId?: string;\n /** Format a document into a context message. Default: `[doc id] text`. */\n formatDoc?: (doc: RagDocument) => string;\n /** Override the lexical similarity measure (default token Jaccard). */\n similarity?: (a: string, b: string) => number;\n}\n\n/**\n * Cache-aware RAG: turns a {@link Retriever} into a reusable {@link CacheSegment}\n * with two cache-preserving policies:\n *\n * - **sticky retrieval** — when consecutive queries are near-duplicates, skip\n * re-retrieval entirely so the segment (and the prefix cache after it) is\n * untouched, and\n * - **append-only growth** — when a new result set extends the previous one,\n * append the new docs instead of rebuilding, keeping the already-cached prefix\n * valid; only a genuinely different result set rebuilds (and busts) the block.\n *\n * Attach `rag.segment` via `agent.setContextSegments([rag.segment])`.\n */\nexport class CacheAwareRag {\n readonly segment: StableContext;\n\n private readonly _retriever: Retriever;\n private readonly _k: number;\n private readonly _stickyThreshold: number;\n private readonly _formatDoc: (doc: RagDocument) => string;\n private readonly _similarity: (a: string, b: string) => number;\n\n private _lastQuery: string | null = null;\n private _docIds: string[] = [];\n\n constructor(opts: CacheAwareRagOptions) {\n this._retriever = opts.retriever;\n this._k = opts.k ?? 4;\n this._stickyThreshold = opts.stickyThreshold ?? 0.85;\n this._formatDoc = opts.formatDoc ?? ((d) => `[${d.id}] ${d.text}`);\n this._similarity = opts.similarity ?? jaccardSimilarity;\n this.segment = new StableContext({ id: opts.segmentId ?? \"rag\", kind: \"rag\", messages: [] });\n }\n\n /** Document ids currently in the segment, in order. */\n get documentIds(): readonly string[] {\n return this._docIds;\n }\n\n /**\n * Refresh the segment for `query`. Returns whether a retrieval actually ran\n * (`false` = sticky reuse) and whether the cache was preserved (append-only or\n * skip) vs. busted (rebuild).\n */\n async update(query: string): Promise<{ retrieved: boolean; cachePreserved: boolean }> {\n if (\n this._lastQuery !== null &&\n this._docIds.length > 0 &&\n this._similarity(query, this._lastQuery) >= this._stickyThreshold\n ) {\n return { retrieved: false, cachePreserved: true }; // sticky — segment untouched\n }\n\n this._lastQuery = query;\n const docs = await this._retriever.retrieve(query, this._k);\n const newIds = docs.map((d) => d.id);\n\n // Append-only when the previous ids are an ordered prefix of the new ids.\n const isPrefix =\n this._docIds.length > 0 &&\n this._docIds.length <= newIds.length &&\n this._docIds.every((id, i) => id === newIds[i]);\n\n if (isPrefix) {\n const extra = docs.slice(this._docIds.length);\n this.segment.append(extra.map((d) => this._toMessage(d)));\n this._docIds = newIds;\n return { retrieved: true, cachePreserved: true };\n }\n\n this.segment.setMessages(docs.map((d) => this._toMessage(d)));\n this._docIds = newIds;\n return { retrieved: true, cachePreserved: this._docIds.length === 0 };\n }\n\n private _toMessage(doc: RagDocument): ChatMessage {\n return { role: \"user\", content: this._formatDoc(doc), discardable: false };\n }\n}\n\n/** Token-set Jaccard similarity (lowercased word tokens). */\nexport function jaccardSimilarity(a: string, b: string): number {\n const ta = new Set(a.toLowerCase().match(/\\w+/g) ?? []);\n const tb = new Set(b.toLowerCase().match(/\\w+/g) ?? []);\n if (ta.size === 0 && tb.size === 0) return 1;\n let inter = 0;\n for (const t of ta) if (tb.has(t)) inter++;\n const union = ta.size + tb.size - inter;\n return union === 0 ? 0 : inter / union;\n}\n\nexport { HashEmbedder, OpenAICompatibleEmbedder } from \"./embedders.js\";\nexport type { OpenAICompatibleEmbedderOptions } from \"./embedders.js\";\n\nexport type { CacheSegment };\n","import type { Embedder } from \"./index.js\";\n\n/**\n * Dependency-free, deterministic embedder using the hashing trick (signed\n * feature hashing). It maps each word token to a bucket and accumulates a\n * signed count, then L2-normalizes. No model, no network — good for demos,\n * tests, and small corpora where lexical overlap is enough. For semantic\n * retrieval, swap in {@link OpenAICompatibleEmbedder} or your own `Embedder`.\n */\nexport class HashEmbedder implements Embedder {\n private readonly _dim: number;\n\n constructor(opts?: { dimensions?: number }) {\n this._dim = Math.max(8, opts?.dimensions ?? 256);\n }\n\n embed(texts: string[]): Promise<number[][]> {\n return Promise.resolve(texts.map((t) => this._embedOne(t)));\n }\n\n private _embedOne(text: string): number[] {\n const vec = Array.from({ length: this._dim }, () => 0);\n const tokens = text.toLowerCase().match(/\\w+/g) ?? [];\n for (const tok of tokens) {\n const h = fnv1a(tok);\n const idx = h % this._dim;\n const sign = (h & 1) === 0 ? 1 : -1;\n vec[idx] = (vec[idx] ?? 0) + sign;\n }\n let norm = 0;\n for (const v of vec) norm += v * v;\n norm = Math.sqrt(norm) || 1;\n return vec.map((v) => v / norm);\n }\n}\n\n/** FNV-1a 32-bit hash → non-negative integer. */\nfunction fnv1a(str: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\nexport interface OpenAICompatibleEmbedderOptions {\n apiKey: string;\n /** Embedding model id, e.g. `text-embedding-3-small`. */\n model: string;\n /** API base. Default OpenAI; point at any OpenAI-compatible `/embeddings`. */\n baseUrl?: string;\n}\n\ninterface EmbeddingResponse {\n data?: { embedding: number[] }[];\n}\n\n/**\n * Calls an OpenAI-compatible `/embeddings` endpoint (OpenAI, or DeepSeek/others\n * that expose the same shape). Real semantic vectors, at the cost of a network\n * round-trip and an API key. Implements the same {@link Embedder} seam, so it\n * drops into `VectorRetriever` unchanged.\n */\nexport class OpenAICompatibleEmbedder implements Embedder {\n private readonly _apiKey: string;\n private readonly _model: string;\n private readonly _baseUrl: string;\n\n constructor(opts: OpenAICompatibleEmbedderOptions) {\n this._apiKey = opts.apiKey;\n this._model = opts.model;\n this._baseUrl = (opts.baseUrl ?? \"https://api.openai.com/v1\").replace(/\\/+$/, \"\");\n }\n\n async embed(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n const resp = await fetch(`${this._baseUrl}/embeddings`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this._apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ model: this._model, input: texts }),\n });\n if (!resp.ok) {\n throw new Error(`Embeddings ${resp.status}: ${await resp.text().catch(() => \"\")}`);\n }\n const json = (await resp.json()) as EmbeddingResponse;\n return (json.data ?? []).map((d) => d.embedding);\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@halo-sdk/rag",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Cache-aware retrieval-augmented generation for Halo AI SDK — sticky retrieval + append-only growth that keeps the prefix cache warm",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"typescript": "^5.8.0",
|
|
37
37
|
"vitest": "^3.0.0",
|
|
38
|
-
"@halo-sdk/core": "1.
|
|
38
|
+
"@halo-sdk/core": "1.2.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@halo-sdk/core": ">=1.
|
|
41
|
+
"@halo-sdk/core": ">=1.2.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc --build --emitDeclarationOnly && tsup",
|