@lunora/ai 1.0.0-alpha.16 → 1.0.0-alpha.17

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.
@@ -1,173 +1,560 @@
1
1
  import { Tool } from 'ai';
2
- import { E as EmbeddingModelInput, a as LunoraAi } from "../packem_shared/types.d-C6dA8LCy.mjs";
2
+ import { E as EmbeddingModelInput, a as LunoraAi } from "../packem_shared/types.d-BXCiRv1x.mjs";
3
+ /**
4
+ * Built-in fixed-window chunker: split into `size`-char windows overlapping by
5
+ * `overlap` chars. Deliberately simple and deterministic — the zero-config
6
+ * default. Token-aware / sentence / semantic strategies plug in via
7
+ * `RagConfig.chunk`.
8
+ * @experimental
9
+ */
3
10
  declare const fixedWindowChunks: (text: string, size: number, overlap: number) => ReadonlyArray<string>;
11
+ /**
12
+ * `(text) => vector` — the embedder shape `ctx.vectors` accepts on both its
13
+ * write (`upsert`) and read (`query`) inputs. Matches `@lunora/server`'s
14
+ * `VectorEmbedder` and `@lunora/bindings/vectors`' `EmbedFunction&lt;string>`.
15
+ * @experimental
16
+ */
4
17
  type RagEmbedder = (input: string) => Promise<ReadonlyArray<number>> | ReadonlyArray<number>;
18
+ /**
19
+ * `RagVectorMatch` is part of the experimental `@lunora/ai` API and may change without a major version bump.
20
+ * @experimental
21
+ */
5
22
  interface RagVectorMatch {
6
23
  id: string;
7
24
  metadata?: Record<string, unknown>;
8
25
  score: number;
9
26
  }
27
+ /**
28
+ * `RagVectorMatches` is part of the experimental `@lunora/ai` API and may change without a major version bump.
29
+ * @experimental
30
+ */
10
31
  interface RagVectorMatches {
11
32
  count: number;
12
33
  matches: ReadonlyArray<RagVectorMatch>;
13
34
  }
35
+ /**
36
+ * `RagVectorQueryInput` is part of the experimental `@lunora/ai` API and may change without a major version bump.
37
+ * @experimental
38
+ */
14
39
  interface RagVectorQueryInput {
40
+ /** Embedder used to vectorize `input`. */
15
41
  embed?: RagEmbedder;
16
42
  filter?: Record<string, unknown>;
43
+ /** Natural-language query text, embedded via `embed`. */
17
44
  input?: string;
18
45
  namespace?: string;
46
+ /**
47
+ * How much stored metadata to return on matches. The runtime honours it even
48
+ * though `@lunora/server`'s ctx type does not declare it — the helper relies
49
+ * on it to read chunk text back in metadata mode.
50
+ */
19
51
  returnMetadata?: "all" | "indexed" | "none";
20
52
  topK?: number;
21
53
  }
54
+ /**
55
+ * `RagVectorRecord` is part of the experimental `@lunora/ai` API and may change without a major version bump.
56
+ * @experimental
57
+ */
22
58
  interface RagVectorRecord {
23
59
  id: string;
24
60
  metadata?: Record<string, unknown>;
25
61
  }
62
+ /**
63
+ * `RagVectorUpsertInput` is part of the experimental `@lunora/ai` API and may change without a major version bump.
64
+ * @experimental
65
+ */
26
66
  interface RagVectorUpsertInput {
67
+ /** Embedder used to vectorize `input`. Optional — omitted for text-search indexes. */
27
68
  embed?: RagEmbedder;
28
69
  id: string;
29
70
  input: string;
30
71
  metadata?: Record<string, unknown>;
31
72
  namespace?: string;
32
73
  }
74
+ /**
75
+ * Structural subset of the vector surface the RAG helper needs. Both the
76
+ * `ctx.vectors` facade on Mutation/Action ctx (`@lunora/server`'s
77
+ * `VectorSearch`) and the raw `@lunora/bindings/vectors` `LunoraVectors`
78
+ * satisfy it — declared here so `@lunora/ai` depends on neither package.
79
+ * @experimental
80
+ */
33
81
  interface RagVectors {
34
82
  deleteByIds: (indexName: string, ids: ReadonlyArray<string>, namespace?: string) => Promise<unknown>;
35
83
  getByIds: (indexName: string, ids: ReadonlyArray<string>, namespace?: string) => Promise<ReadonlyArray<RagVectorRecord>>;
36
84
  query: (indexName: string, input: RagVectorQueryInput) => Promise<RagVectorMatches>;
37
85
  upsert: (indexName: string, input: RagVectorUpsertInput) => Promise<unknown>;
38
86
  }
87
+ /**
88
+ * The two facades `defineRag` binds. An `ActionCtx` satisfies this directly
89
+ * (`ctx.ai` is action-only, so RAG methods run inside actions); any object
90
+ * carrying the two facades works in tests.
91
+ * @experimental
92
+ */
39
93
  interface RagContext {
94
+ /**
95
+ * Resolves a Workers AI embedding-model id (or the omitted default) — an
96
+ * `ActionCtx`'s `ctx.ai` satisfies it. OPTIONAL: when
97
+ * {@link RagConfig.embeddingModel} is a direct AI SDK `EmbeddingModel` object
98
+ * (bring-your-own embeddings, e.g. `@ai-sdk/openai`), the helper uses that
99
+ * object as-is and never reads `ai`, so a hand-built context may omit it and
100
+ * no `env.AI` binding is needed. A model-id string (or an omitted model) with
101
+ * no `ai` present throws a directed error.
102
+ */
40
103
  ai?: Pick<LunoraAi, "embeddingModel">;
104
+ /**
105
+ * The verified retrieval identity, read by {@link RagConfig.rlsFilter} to
106
+ * derive a per-request row filter. An `ActionCtx` carrying `ctx.auth`
107
+ * satisfies this structurally, so `docs(ctx)` picks the identity up
108
+ * automatically; tests pass any value. `unknown` on purpose — `@lunora/ai`
109
+ * stays decoupled from `@lunora/server`'s identity type; `rlsFilter` narrows.
110
+ */
41
111
  auth?: unknown;
42
112
  vectors: RagVectors;
43
113
  }
114
+ /**
115
+ * Pluggable chunk-text storage. By default chunk text is stored in vector
116
+ * metadata (`__ragText`), which forces `returnMetadata: "all"` on retrieval and
117
+ * caps `topK` at 20 (the Vectorize full-metadata ceiling) — and each vector's
118
+ * metadata must stay under the ~10 KiB Vectorize cap. Supplying a text store
119
+ * (a DO table, KV, …) moves the text out of metadata: retrieval queries with
120
+ * `returnMetadata: "indexed"` (topK up to 100) and hydrates text by chunk id.
121
+ * @experimental
122
+ */
44
123
  interface RagTextStore {
124
+ /** Fetch chunk texts by id, aligned with the input order; `undefined` for misses. */
45
125
  getMany: (ids: ReadonlyArray<string>, options: {
46
126
  namespace?: string;
47
127
  }) => Promise<ReadonlyArray<string | undefined>>;
128
+ /** Persist chunk texts. Must be idempotent by chunk `id` (re-index re-puts). */
48
129
  put: (chunks: ReadonlyArray<StoredRagChunk>, options: {
49
130
  namespace?: string;
50
131
  }) => Promise<void>;
132
+ /** Optional cleanup hook, invoked when a source's chunks are deleted. */
51
133
  remove?: (ids: ReadonlyArray<string>, options: {
52
134
  namespace?: string;
53
135
  }) => Promise<void>;
54
136
  }
137
+ /**
138
+ * A chunk handed to {@link RagTextStore.put} / {@link RagLexicalStore.index}.
139
+ * @experimental
140
+ */
55
141
  interface StoredRagChunk {
56
142
  chunkIndex: number;
57
143
  id: string;
58
144
  sourceId: string;
59
145
  text: string;
60
146
  }
147
+ /**
148
+ * One lexical (BM25) hit returned by {@link RagLexicalStore.search}.
149
+ * @experimental
150
+ */
61
151
  interface LexicalMatch {
152
+ /** The chunk vector id — the same id scheme the vector leg uses, so RRF can fuse the two. */
62
153
  id: string;
154
+ /** BM25 relevance score (higher = better). Used only for the leg's internal ranking; RRF fuses by rank. */
63
155
  score: number;
156
+ /** The chunk text, returned so a fused lexical-only hit needs no extra hydration round-trip. */
64
157
  text: string;
65
158
  }
159
+ /**
160
+ * Pluggable lexical (BM25 / keyword) store — the production seam for hybrid
161
+ * retrieval. When {@link RagConfig.lexicalStore} is set, `index()` mirrors each
162
+ * chunk's text here and `retrieve()` fuses this store's keyword ranking with the
163
+ * vector store's semantic ranking via Reciprocal Rank Fusion. Mirrors the
164
+ * {@link RagTextStore} shape (idempotent by chunk `id`, namespace-partitioned).
165
+ *
166
+ * `@lunora/ai/rag` ships `bm25LexicalStore()`, an in-memory reference adapter;
167
+ * production deployments plug a durable one (DO SQLite inverted index, D1,
168
+ * Vectorize-adjacent search service, …) behind this same interface.
169
+ * @experimental
170
+ */
66
171
  interface RagLexicalStore {
172
+ /** Index chunk texts for keyword search. Must be idempotent by chunk `id` (re-index re-puts). */
67
173
  index: (chunks: ReadonlyArray<StoredRagChunk>, options: {
68
174
  namespace?: string;
69
175
  }) => Promise<void>;
176
+ /** Optional cleanup hook, invoked when a source's chunks are deleted or a re-index shrinks it. */
70
177
  remove?: (ids: ReadonlyArray<string>, options: {
71
178
  namespace?: string;
72
179
  }) => Promise<void>;
180
+ /**
181
+ * Rank chunks by lexical relevance to `query`. `filter` carries the same
182
+ * (RLS-merged) metadata predicate handed to the vector leg — a store that
183
+ * indexes metadata MUST honour it so hybrid retrieval can't surface a row
184
+ * the RLS filter would exclude; a namespace-only store (the reference
185
+ * adapter) isolates by `namespace` and documents that it ignores `filter`.
186
+ */
73
187
  search: (query: string, options: {
74
188
  filter?: Record<string, unknown>;
75
189
  namespace?: string;
76
190
  topK: number;
77
191
  }) => Promise<ReadonlyArray<LexicalMatch>>;
78
192
  }
193
+ /**
194
+ * A pre-defined, reusable filter expression. Declared on `RagConfig.filters`
195
+ * (keyed by name) and referenced by name from `RetrieveOptions.filter` — avoids
196
+ * repeating the same tenant/RBAC filter shape across every retrieval site.
197
+ * @example
198
+ * ```ts
199
+ * const docs = defineRag({
200
+ * index: "docs",
201
+ * filters: {
202
+ * published: { filter: { status: "published", deleted: false }, description: "Only published content" },
203
+ * },
204
+ * });
205
+ * // Later — reference by name:
206
+ * docs(ctx).retrieve("query", { filter: "published" });
207
+ * ```
208
+ * @experimental
209
+ */
79
210
  interface RagNamedFilter {
211
+ /** Optional human-readable description for observability / Studio display. */
80
212
  description?: string;
213
+ /** The filter expression passed verbatim to Vectorize's `filter` parameter. */
81
214
  filter: Record<string, unknown>;
82
215
  }
216
+ /**
217
+ * `RagConfig` is part of the experimental `@lunora/ai` API and may change without a major version bump.
218
+ * @experimental
219
+ */
83
220
  interface RagConfig {
221
+ /**
222
+ * Suppress the one-time dev warning emitted when `index`/`retrieve` run
223
+ * without a `namespace`. Only appropriate for genuinely single-tenant apps —
224
+ * Vectorize indexes are account-global, so a namespace-less index shares
225
+ * vectors across every tenant.
226
+ */
84
227
  allowSharedNamespace?: boolean;
228
+ /** Custom chunker; overrides the built-in fixed-window splitter. */
85
229
  chunk?: (text: string) => ReadonlyArray<string>;
230
+ /** Overlap (chars) between adjacent chunks. Default 200. Must be < `chunkSize`. */
86
231
  chunkOverlap?: number;
232
+ /** Target chunk size (chars). Default 1000. */
87
233
  chunkSize?: number;
234
+ /**
235
+ * Embedding model, declared once so index + retrieve embed identically: a
236
+ * Workers AI id (e.g. `@cf/baai/bge-base-en-v1.5`) or any AI SDK
237
+ * `EmbeddingModel`. Falls back to `createAi`'s `defaultModel` when omitted.
238
+ */
88
239
  embeddingModel?: EmbeddingModelInput;
240
+ /**
241
+ * Embedding-model version tag — an opt-in discriminator that partitions the
242
+ * vector space so a model swap can never silently return garbage. Vectors
243
+ * embedded by one model live in a different space from another's, and
244
+ * querying across the two returns meaningless neighbours. When set, the tag
245
+ * is folded into the effective Vectorize namespace (and chunk-id prefix) of
246
+ * every index/retrieve/remove, so bumping it re-partitions cleanly: old
247
+ * vectors become unreachable to new queries (empty ≫ wrong) until sources
248
+ * are re-indexed under the new tag.
249
+ *
250
+ * Set + bump this whenever you change {@link RagConfig.embeddingModel} (or
251
+ * its dimensions). Opt-in and non-breaking — omitting it keeps the exact
252
+ * chunk-id/namespace scheme of un-versioned indexes. Must match
253
+ * `^[A-Za-z0-9._-]{1,40}$` (e.g. `"bge-v1.5"`, `"v2"`).
254
+ */
89
255
  embeddingModelVersion?: string;
256
+ /**
257
+ * Pre-defined named filter expressions. Each key is a filter name users
258
+ * pass through `RetrieveOptions.filter`. Throws at retrieve-time if the
259
+ * name is not found here — catches spelling mistakes early.
260
+ */
90
261
  filters?: Record<string, RagNamedFilter>;
262
+ /** The Vectorize index name (a `ctx.vectors` index binding key). */
91
263
  index: string;
264
+ /**
265
+ * Pluggable lexical (BM25) store for hybrid retrieval. When set, `index()`
266
+ * mirrors chunk text into it and `retrieve()` fuses the vector (semantic)
267
+ * and lexical (keyword) rankings via Reciprocal Rank Fusion — recovering the
268
+ * exact-term / rare-token matches a pure-embedding search misses. Use the
269
+ * shipped `bm25LexicalStore()` reference adapter or plug your own durable
270
+ * one. See {@link RagLexicalStore}.
271
+ */
92
272
  lexicalStore?: RagLexicalStore;
273
+ /** Retrieval depth for the lexical leg of hybrid search. Defaults to the effective `topK`. */
93
274
  lexicalTopK?: number;
275
+ /**
276
+ * Enforce tenant isolation: throw (instead of the one-time dev warning)
277
+ * when `index`/`retrieve`/`remove` run without a `namespace`. Recommended
278
+ * for every multi-tenant app — Vectorize indexes are account-global, and
279
+ * in metadata mode the leaked payload includes raw chunk text.
280
+ */
94
281
  requireNamespace?: boolean;
282
+ /**
283
+ * Row-level-security filter derived from the retrieval identity. Called once
284
+ * per `retrieve()` with {@link RagContext.auth} (the bound ctx's `auth`); the
285
+ * returned Vectorize metadata filter is merged over the caller's `filter`
286
+ * with **RLS keys winning** (a caller can never widen past what RLS allows),
287
+ * then applied to both the vector and the lexical legs. Return `undefined` to
288
+ * add no constraint (e.g. an admin identity). Runs on retrieval only —
289
+ * indexing is a trusted server path.
290
+ * @example
291
+ * ```ts
292
+ * const docs = defineRag({
293
+ * index: "docs",
294
+ * // only ever return the caller's own org, whatever else they ask for:
295
+ * rlsFilter: (auth) => ({ orgId: (auth as { orgId: string }).orgId }),
296
+ * });
297
+ * ```
298
+ */
95
299
  rlsFilter?: (auth: unknown) => Promise<Record<string, unknown> | undefined> | Record<string, unknown> | undefined;
300
+ /** Chunk-text storage override — see {@link RagTextStore}. */
96
301
  textStore?: RagTextStore;
302
+ /** Default retrieval depth. Default 5. Capped at 20 (metadata mode) / 100 (text-store mode). */
97
303
  topK?: number;
98
304
  }
305
+ /**
306
+ * `IndexInput` is part of the experimental `@lunora/ai` API and may change without a major version bump.
307
+ * @experimental
308
+ */
99
309
  interface IndexInput {
310
+ /**
311
+ * When `false`, throws if the source text produces zero chunks (e.g. empty
312
+ * or whitespace-only text). Default `true` (silently produces zero chunks).
313
+ */
100
314
  allowEmptySources?: boolean;
315
+ /** Source document id — chunk ids derive from it as `${id}#${chunkIndex}`. */
101
316
  id: string;
317
+ /**
318
+ * Relative weight in `[0, 1]` multiplied into this source's match scores at
319
+ * retrieval time (default 1). Lets canonical docs outrank incidental ones.
320
+ */
102
321
  importance?: number;
322
+ /** Source metadata copied onto every chunk vector (e.g. title, url). */
103
323
  metadata?: Record<string, unknown>;
324
+ /** Tenant/shard key. Required for multi-tenant apps — Vectorize is account-global. */
104
325
  namespace?: string;
326
+ /**
327
+ * Called after each chunk is successfully upserted. Useful for progress
328
+ * tracking during large indexing operations — e.g. updating a UI progress
329
+ * bar or logging per-chunk status.
330
+ */
105
331
  onChunk?: (info: {
106
332
  chunkIndex: number;
107
333
  id: string;
108
334
  text: string;
109
335
  total: number;
110
336
  }) => void;
337
+ /** The document body to chunk + embed + upsert. */
111
338
  text: string;
112
339
  }
340
+ /**
341
+ * `IndexResult` is part of the experimental `@lunora/ai` API and may change without a major version bump.
342
+ * @experimental
343
+ */
113
344
  interface IndexResult {
345
+ /** Number of chunks the source is indexed into. */
114
346
  chunks: number;
347
+ /** The deterministic chunk vector ids, in chunk order. */
115
348
  ids: ReadonlyArray<string>;
349
+ /**
350
+ * True when the source's content hash matched the previously indexed hash —
351
+ * chunking/embedding/upserts were skipped entirely (a no-op re-sync).
352
+ */
116
353
  unchanged: boolean;
117
354
  }
355
+ /**
356
+ * `RemoveInput` is part of the experimental `@lunora/ai` API and may change without a major version bump.
357
+ * @experimental
358
+ */
118
359
  interface RemoveInput {
360
+ /** The source document id whose chunks are removed. */
119
361
  id: string;
120
362
  namespace?: string;
121
363
  }
364
+ /**
365
+ * `RetrieveOptions` is part of the experimental `@lunora/ai` API and may change without a major version bump.
366
+ * @experimental
367
+ */
122
368
  interface RetrieveOptions {
369
+ /**
370
+ * Also return this many neighbouring chunks around each match (fetched by
371
+ * deterministic id, not re-queried) — "embed small, retrieve big". Neighbour
372
+ * text is stitched into the chunk's `text` in document order. Best combined
373
+ * with `chunkOverlap: 0`, since overlapping windows repeat boundary text.
374
+ */
123
375
  chunkContext?: {
124
376
  after?: number;
125
377
  before?: number;
126
378
  };
379
+ /**
380
+ * Vectorize filter expression — or the name of a pre-defined filter declared
381
+ * in `RagConfig.filters`. Passing a name that is not registered throws at
382
+ * call time, catching spelling mistakes early.
383
+ */
127
384
  filter?: Record<string, unknown> | string;
385
+ /** Drop matches whose (importance-adjusted) score falls below this threshold. */
128
386
  minScore?: number;
129
387
  namespace?: string;
388
+ /**
389
+ * Fires after retrieval completes, before chunk expansion. Useful for
390
+ * observability — logging query latency, hit counts, etc.
391
+ */
130
392
  onRetrieve?: (info: {
131
393
  matches: number;
132
394
  query: string;
133
395
  }) => void;
134
396
  topK?: number;
135
397
  }
398
+ /**
399
+ * `RetrievedChunk` is part of the experimental `@lunora/ai` API and may change without a major version bump.
400
+ * @experimental
401
+ */
136
402
  interface RetrievedChunk {
137
403
  chunkIndex: number;
138
404
  id: string;
405
+ /**
406
+ * The source-level importance weight that was multiplied into this chunk's
407
+ * score. `1` when no importance was set at index time.
408
+ */
139
409
  importance: number;
410
+ /** Caller metadata stored on the vector (internal `__rag*` keys stripped). */
140
411
  metadata?: Record<string, unknown>;
412
+ /** Cosine similarity, multiplied by the source's `importance` when one was set. */
141
413
  score: number;
142
414
  sourceId: string;
143
415
  text: string;
144
416
  }
417
+ /**
418
+ * `RagSource` is part of the experimental `@lunora/ai` API and may change without a major version bump.
419
+ * @experimental
420
+ */
145
421
  interface RagSource {
146
422
  id: string;
423
+ /** Caller metadata from the source's first-seen chunk (internal keys stripped). */
147
424
  metadata?: Record<string, unknown>;
425
+ /**
426
+ * The source's importance weight (the `importance` value passed at index
427
+ * time, default 1), propagated so downstream consumers can factor it into
428
+ * their own ranking or UI.
429
+ */
148
430
  weight?: number;
149
431
  }
432
+ /**
433
+ * The retrieve return shape — designed so an agent memory step consumes it directly.
434
+ * @experimental
435
+ */
150
436
  interface RetrieveResult {
437
+ /** Ranked chunks (best first). */
151
438
  chunks: ReadonlyArray<RetrievedChunk>;
439
+ /** Ready-to-inject prompt context: chunks joined under `[source:&lt;id>#&lt;n>]` headers. */
152
440
  context: string;
441
+ /** Deduped source references, in first-seen (best) order. */
153
442
  sources: ReadonlyArray<RagSource>;
154
443
  }
444
+ /**
445
+ * `RagToolOptions` is part of the experimental `@lunora/ai` API and may change without a major version bump.
446
+ * @experimental
447
+ */
155
448
  interface RagToolOptions {
449
+ /** Tool description shown to the model. Defaults to a search description naming the index. */
156
450
  description?: string;
451
+ /** Namespace applied to every tool-invoked retrieval (the tenant key). */
157
452
  namespace?: string;
453
+ /** Retrieval depth for tool-invoked retrievals. */
158
454
  topK?: number;
159
455
  }
456
+ /**
457
+ * The per-request RAG surface returned by binding a ctx: `docs(ctx)`.
458
+ * @experimental
459
+ */
160
460
  interface Rag {
461
+ /**
462
+ * Expose `retrieve` as an AI SDK tool (for `generateText`/`streamText`
463
+ * `tools:` maps), so a model can decide to search the index itself.
464
+ */
161
465
  asTool: (options?: RagToolOptions) => Tool<{
162
466
  query: string;
163
467
  }, RetrieveResult>;
468
+ /**
469
+ * Chunk + embed + upsert one source document. Re-indexing the same `id` is
470
+ * an atomic-enough replace: unchanged content short-circuits via content
471
+ * hash, and stale chunks beyond the new count are deleted automatically.
472
+ */
164
473
  index: (input: IndexInput) => Promise<IndexResult>;
474
+ /** Delete every chunk of a previously indexed source. */
165
475
  remove: (input: RemoveInput) => Promise<void>;
476
+ /** Embed the query and return ranked chunks + prompt-ready context. */
166
477
  retrieve: (query: string, options?: RetrieveOptions) => Promise<RetrieveResult>;
167
478
  }
479
+ /**
480
+ * Declare a RAG index over the two facades every Lunora action already has:
481
+ * `ctx.ai` (embeddings) + `ctx.vectors` (Vectorize). Returns a per-request
482
+ * factory — bind a ctx to get `{ index, retrieve, remove, asTool }`:
483
+ *
484
+ * ```ts
485
+ * // lunora/rag.ts
486
+ * export const docs = defineRag({ embeddingModel: "@cf/baai/bge-base-en-v1.5", index: "docs" });
487
+ *
488
+ * // inside an action:
489
+ * await docs(ctx).index({ id: doc._id, metadata: { title: doc.title }, namespace: ctx.shardKey, text: doc.body });
490
+ * const { chunks, context, sources } = await docs(ctx).retrieve(question, { namespace: ctx.shardKey });
491
+ * ```
492
+ *
493
+ * Pure composition — no new binding, no I/O until a method runs. Chunk text
494
+ * lives in vector metadata by default (`topK` ≤ 20); supply `textStore` to move
495
+ * it into your own storage and lift the ceiling to 100 — see `RagTextStore`.
496
+ *
497
+ * `ctx.ai` is only needed to resolve a Workers AI `embeddingModel` id. Pass a
498
+ * direct AI SDK `EmbeddingModel` object (`@ai-sdk/openai`, …) and the helper
499
+ * embeds through it without `ctx.ai` — so a bring-your-own-embeddings index
500
+ * needs no `env.AI` binding (bind any context carrying just `vectors`).
501
+ * @experimental
502
+ */
168
503
  declare const defineRag: (config: RagConfig) => ((context: RagContext) => Rag);
504
+ /**
505
+ * Guess a MIME type from a file extension. Lowercases and strips a leading `.`
506
+ * from `extension`; returns `"application/octet-stream"` for unknown extensions.
507
+ *
508
+ * Covers the broad set of extensions users are likely to encounter in a web /
509
+ * document-processing context — images, video, audio, office docs, PDF, text,
510
+ * archives, and source code. Follows the same approach as Convex's
511
+ * `guessMimeType` helper.
512
+ * @experimental
513
+ */
169
514
  declare const guessMimeTypeFromExtension: (extension: string) => string;
515
+ /**
516
+ * SHA-256 hex digest of binary data. Accepts a `BufferSource` (`ArrayBuffer` or
517
+ * `ArrayBufferView` such as `Uint8Array`). Useful for content-addressable
518
+ * storage — pair with `IndexInput.text` to detect duplicates across re-indexes.
519
+ * @experimental
520
+ */
170
521
  declare const contentHash: (data: BufferSource) => Promise<string>;
522
+ /**
523
+ * Reciprocal Rank Fusion (RRF): merge two ranked lists of chunks by their
524
+ * _rank position_ rather than their absolute scores, which are not comparable
525
+ * across different search methods (cosine vs BM25).
526
+ *
527
+ * Each result set contributes `1 / (k + rank)` to each chunk's fused score,
528
+ * where `rank` is 0-based position in the list. The constant `k` (default 60)
529
+ * dampens the influence of high ranks — the standard value from the RRF
530
+ * literature that works well across domains.
531
+ *
532
+ * The fused list is sorted descending by fused score. Ties are broken by
533
+ * preferring the chunk ranked higher in the vector search result (typically
534
+ * the more semantically accurate of the two methods).
535
+ *
536
+ * Callers MUST ensure every chunk in both lists carries a unique, comparable
537
+ * `id` — this is guaranteed by the chunk-id scheme `${sourceId}#${chunkIndex}`.
538
+ * @experimental
539
+ */
171
540
  declare const hybridRank: (vectorResults: ReadonlyArray<RetrievedChunk>, textResults: ReadonlyArray<RetrievedChunk>, k?: number) => ReadonlyArray<RetrievedChunk>;
541
+ /**
542
+ * An **in-memory** Okapi BM25 lexical store — the reference adapter behind
543
+ * `RagConfig.lexicalStore`, giving hybrid retrieval its keyword leg with zero
544
+ * infrastructure. State lives in the worker isolate: it is **not durable and not
545
+ * shared across isolates**, so it is intended for tests, local development, and
546
+ * single-isolate workloads. Production deployments plug a durable
547
+ * {@link RagLexicalStore} (a DO-SQLite inverted index, D1, or an external search
548
+ * service) behind the same seam.
549
+ *
550
+ * Tenant isolation is by `namespace` (each namespace keeps its own index). This
551
+ * store holds **no metadata**, so it cannot evaluate a metadata `filter`
552
+ * (including an `rlsFilter` result): when `search` is called with a non-empty
553
+ * filter it **fails closed** — returns no lexical hits and warns once — rather
554
+ * than risk surfacing a row the filter would exclude. If your RLS is
555
+ * metadata-based (not namespace-based) and you want a lexical leg, fold the RLS
556
+ * dimension into the `namespace` or plug a filter-aware store.
557
+ * @experimental
558
+ */
172
559
  declare const bm25LexicalStore: () => RagLexicalStore;
173
560
  export { type IndexInput, type IndexResult, type LexicalMatch, type Rag, type RagConfig, type RagContext, type RagEmbedder, type RagLexicalStore, type RagNamedFilter, type RagSource, type RagTextStore, type RagToolOptions, type RagVectorMatch, type RagVectorMatches, type RagVectorQueryInput, type RagVectorRecord, type RagVectorUpsertInput, type RagVectors, type RemoveInput, type RetrieveOptions, type RetrieveResult, type RetrievedChunk, type StoredRagChunk, bm25LexicalStore, contentHash, defineRag, fixedWindowChunks, guessMimeTypeFromExtension, hybridRank };