@lunora/ai 1.0.0-alpha.15 → 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.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,28 @@
1
- import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-C6dA8LCy.mjs";
2
- export type { A as AiBindingLike, b as AiGatewayOptions, E as EmbeddingModelInput, M as ModelInput, W as WorkersAiProviderLike } from "./packem_shared/types.d-C6dA8LCy.mjs";
1
+ import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-BXCiRv1x.mjs";
2
+ export type { A as AiBindingLike, b as AiGatewayOptions, E as EmbeddingModelInput, M as ModelInput, W as WorkersAiProviderLike } from "./packem_shared/types.d-BXCiRv1x.mjs";
3
3
  export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, hasToolCall, jsonSchema, streamObject, streamText, tool } from 'ai';
4
4
  export { createWorkersAI } from 'workers-ai-provider';
5
+ /**
6
+ * Create the `ctx.ai` helper over a Workers `AI` binding.
7
+ *
8
+ * Workers AI is the zero-config default, but `@lunora/ai` is provider-agnostic:
9
+ * every helper takes either a model id string (resolved against the Workers AI
10
+ * provider) or any AI SDK {@link LanguageModel}/{@link EmbeddingModel} object
11
+ * (`@ai-sdk/openai`, `@ai-sdk/anthropic`, OpenRouter, …), so apps are never
12
+ * locked to Workers AI. Pair `embed` with `@lunora/bindings/vectors` for RAG.
13
+ *
14
+ * Combine with the re-exported `generateText`/`streamText`/`generateObject`/
15
+ * `embed`/`tool` from this package:
16
+ *
17
+ * ```ts
18
+ * import { streamText } from "@lunora/ai";
19
+ *
20
+ * const result = streamText({
21
+ * model: ctx.ai.model("@cf/meta/llama-3.3-70b-instruct-fp8-fast"),
22
+ * messages,
23
+ * });
24
+ * ```
25
+ * @experimental
26
+ */
5
27
  declare const createAi: (options: LunoraAiOptions) => LunoraAi;
6
28
  export { type LunoraAi, type LunoraAiOptions, createAi };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,28 @@
1
- import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-C6dA8LCy.js";
2
- export type { A as AiBindingLike, b as AiGatewayOptions, E as EmbeddingModelInput, M as ModelInput, W as WorkersAiProviderLike } from "./packem_shared/types.d-C6dA8LCy.js";
1
+ import { L as LunoraAiOptions, a as LunoraAi } from "./packem_shared/types.d-BXCiRv1x.js";
2
+ export type { A as AiBindingLike, b as AiGatewayOptions, E as EmbeddingModelInput, M as ModelInput, W as WorkersAiProviderLike } from "./packem_shared/types.d-BXCiRv1x.js";
3
3
  export { type EmbeddingModel, type LanguageModel, embed, embedMany, generateObject, generateText, hasToolCall, jsonSchema, streamObject, streamText, tool } from 'ai';
4
4
  export { createWorkersAI } from 'workers-ai-provider';
5
+ /**
6
+ * Create the `ctx.ai` helper over a Workers `AI` binding.
7
+ *
8
+ * Workers AI is the zero-config default, but `@lunora/ai` is provider-agnostic:
9
+ * every helper takes either a model id string (resolved against the Workers AI
10
+ * provider) or any AI SDK {@link LanguageModel}/{@link EmbeddingModel} object
11
+ * (`@ai-sdk/openai`, `@ai-sdk/anthropic`, OpenRouter, …), so apps are never
12
+ * locked to Workers AI. Pair `embed` with `@lunora/bindings/vectors` for RAG.
13
+ *
14
+ * Combine with the re-exported `generateText`/`streamText`/`generateObject`/
15
+ * `embed`/`tool` from this package:
16
+ *
17
+ * ```ts
18
+ * import { streamText } from "@lunora/ai";
19
+ *
20
+ * const result = streamText({
21
+ * model: ctx.ai.model("@cf/meta/llama-3.3-70b-instruct-fp8-fast"),
22
+ * messages,
23
+ * });
24
+ * ```
25
+ * @experimental
26
+ */
5
27
  declare const createAi: (options: LunoraAiOptions) => LunoraAi;
6
28
  export { type LunoraAi, type LunoraAiOptions, createAi };
@@ -2,7 +2,6 @@ const BM25_K1 = 1.5;
2
2
  const BM25_B = 0.75;
3
3
  const TOKEN_PATTERN = /[a-z0-9]+/g;
4
4
  const tokenize = (text) => text.toLowerCase().match(TOKEN_PATTERN) ?? [];
5
- const isPrimitiveFilter = (filter) => Object.values(filter).every((value) => value === null || typeof value !== "object");
6
5
  const filterWarned = /* @__PURE__ */ new WeakSet();
7
6
  const bm25LexicalStore = () => {
8
7
  const namespaces = /* @__PURE__ */ new Map();
@@ -65,7 +64,7 @@ const bm25LexicalStore = () => {
65
64
  return Promise.resolve();
66
65
  },
67
66
  search: (query, options) => {
68
- if (options.filter && Object.keys(options.filter).length > 0 && !isPrimitiveFilter(options.filter)) {
67
+ if (options.filter && Object.keys(options.filter).length > 0) {
69
68
  if (!filterWarned.has(store)) {
70
69
  filterWarned.add(store);
71
70
  console.warn(
@@ -8,17 +8,40 @@ const concurrentMap = async (items, limit, function_) => {
8
8
  if (!Number.isInteger(limit) || limit < 1) {
9
9
  throw new RangeError("concurrentMap: `limit` must be a positive integer");
10
10
  }
11
+ if (items.length === 0) {
12
+ return [];
13
+ }
14
+ const effectiveLimit = Math.max(1, Math.min(limit, items.length));
11
15
  const results = Array.from({ length: items.length });
12
16
  let cursor = 0;
17
+ let failed = false;
18
+ let firstError;
13
19
  const worker = async () => {
14
- while (cursor < items.length) {
20
+ for (; ; ) {
21
+ if (failed) {
22
+ return;
23
+ }
15
24
  const index = cursor;
16
25
  cursor += 1;
17
- results[index] = await function_(items[index], index);
26
+ if (index >= items.length) {
27
+ return;
28
+ }
29
+ try {
30
+ results[index] = await function_(items[index], index);
31
+ } catch (error) {
32
+ if (!failed) {
33
+ failed = true;
34
+ firstError = error;
35
+ }
36
+ return;
37
+ }
18
38
  }
19
39
  };
20
- const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
40
+ const workers = Array.from({ length: effectiveLimit }, () => worker());
21
41
  await Promise.all(workers);
42
+ if (failed) {
43
+ throw firstError;
44
+ }
22
45
  return results;
23
46
  };
24
47
 
@@ -322,13 +345,19 @@ const defineRag = (config) => {
322
345
  if (!textStore) {
323
346
  return chunks;
324
347
  }
325
- const texts = await textsByIds(
326
- chunks.map((chunk) => chunk.id),
327
- namespace
328
- );
348
+ const ids = chunks.map((chunk) => chunk.id);
349
+ const [texts, records] = await Promise.all([textsByIds(ids, namespace), context.vectors.getByIds(config.index, ids, namespace)]);
350
+ const fullMetadataById = new Map(records.map((record) => [record.id, record.metadata]));
329
351
  return chunks.flatMap((chunk) => {
330
352
  const text = texts.get(chunk.id);
331
- return text === void 0 ? [] : [{ ...chunk, text }];
353
+ if (text === void 0) {
354
+ return [];
355
+ }
356
+ const fullMetadata = fullMetadataById.get(chunk.id);
357
+ const rawImportance = fullMetadata?.[IMPORTANCE_KEY];
358
+ const importance = typeof rawImportance === "number" && rawImportance >= 0 && rawImportance <= 1 ? rawImportance : chunk.importance;
359
+ const score = chunk.score / chunk.importance * importance;
360
+ return [{ ...chunk, importance, metadata: userMetadataOf(fullMetadata) ?? chunk.metadata, score, text }];
332
361
  });
333
362
  };
334
363
  const retrieve = async (query, options) => {
@@ -347,6 +376,10 @@ const defineRag = (config) => {
347
376
  topK
348
377
  });
349
378
  let chunks = await hydrateFromStore(parseMatches(vectorResult, effectiveNamespace), effectiveNamespace);
379
+ const minScore = options?.minScore;
380
+ if (minScore !== void 0) {
381
+ chunks = chunks.filter((chunk) => chunk.score >= minScore);
382
+ }
350
383
  if (config.lexicalStore) {
351
384
  const lexicalMatches = await config.lexicalStore.search(query, {
352
385
  filter: effectiveFilter,
@@ -368,10 +401,6 @@ const defineRag = (config) => {
368
401
  chunks = [...hybridRank(chunks, lexicalChunks)];
369
402
  }
370
403
  chunks.sort((a, b) => b.score - a.score);
371
- const minScore = options?.minScore;
372
- if (minScore !== void 0) {
373
- chunks = chunks.filter((chunk) => chunk.score >= minScore);
374
- }
375
404
  chunks = [...await expandChunks(chunks, options, effectiveNamespace)];
376
405
  const sources = [];
377
406
  const seen = /* @__PURE__ */ new Set();
@@ -0,0 +1,109 @@
1
+ import { EmbeddingModel, LanguageModel } from 'ai';
2
+ /**
3
+ * Structural projection of the Cloudflare Workers `AI` binding (`env.AI`).
4
+ * Declared locally so unit tests can pass a plain-object double and the real
5
+ * binding satisfies the same shape without importing `@cloudflare/workers-types`
6
+ * into the public surface. Mirrors the `run` method documented at
7
+ * https://developers.cloudflare.com/workers-ai/.
8
+ * @experimental
9
+ */
10
+ interface AiBindingLike {
11
+ run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
12
+ }
13
+ /**
14
+ * A Workers AI provider instance — the value returned by `createWorkersAI(...)`.
15
+ * Calling it with a model id yields an AI SDK {@link LanguageModel}; the
16
+ * optional `textEmbeddingModel` factory yields an {@link EmbeddingModel}.
17
+ * Typed structurally so `@lunora/ai` neither re-declares the provider's full
18
+ * surface nor hard-pins its exact type across minor releases.
19
+ * @experimental
20
+ */
21
+ interface WorkersAiProviderLike {
22
+ (modelId: string, settings?: Record<string, unknown>): LanguageModel;
23
+ textEmbeddingModel?: (modelId: string) => EmbeddingModel;
24
+ }
25
+ /**
26
+ * AI Gateway options forwarded to `createWorkersAI`. Lets inference route
27
+ * through a Cloudflare AI Gateway for caching, rate-limiting, and observability.
28
+ * @experimental
29
+ */
30
+ interface AiGatewayOptions {
31
+ [key: string]: unknown;
32
+ id: string;
33
+ }
34
+ /**
35
+ * `LunoraAiOptions` is part of the experimental `@lunora/ai` API and may change without a major version bump.
36
+ * @experimental
37
+ */
38
+ interface LunoraAiOptions {
39
+ /**
40
+ * The Workers `AI` binding (`env.AI`). Required for the zero-config Workers
41
+ * AI default and for the raw `ai.run(...)` passthrough. May be omitted when
42
+ * a pre-built `provider` is supplied (e.g. in tests or a custom setup).
43
+ */
44
+ binding?: AiBindingLike;
45
+ /**
46
+ * Default Workers AI **embedding** model id used by `embeddingModel()` when no
47
+ * explicit model is passed (e.g. `@cf/baai/bge-base-en-v1.5`). Kept separate
48
+ * from `defaultModel` because a language-model id and an embedding-model
49
+ * id belong to different Workers AI families and are never interchangeable —
50
+ * reusing the language-model default here would defer a wrong-family error to
51
+ * inference time. Has no effect on bring-your-own providers.
52
+ */
53
+ defaultEmbeddingModel?: string;
54
+ /**
55
+ * Default Workers AI **language** model id used by `model()` when no explicit
56
+ * model is passed. For embeddings, set `defaultEmbeddingModel` instead.
57
+ * Has no effect on bring-your-own providers.
58
+ */
59
+ defaultModel?: string;
60
+ /** Route Workers AI inference through a Cloudflare AI Gateway. */
61
+ gateway?: AiGatewayOptions;
62
+ /**
63
+ * Pre-built Workers AI provider. When omitted, one is constructed from
64
+ * `binding` via `createWorkersAI`. Supplying it directly is the seam used by
65
+ * tests and advanced setups; it also lets callers configure the provider
66
+ * (e.g. `safePrompt`) before handing it to `@lunora/ai`.
67
+ */
68
+ provider?: WorkersAiProviderLike;
69
+ }
70
+ /**
71
+ * A model to run against. The AI SDK's {@link LanguageModel} already admits a
72
+ * bare `string`, so this alias covers both arms of the provider-agnostic seam:
73
+ * a string id is the Workers AI convenience path (resolved by `ctx.ai.model`),
74
+ * a built model object is bring-your-own (`@ai-sdk/openai`, `@ai-sdk/anthropic`,
75
+ * `@ai-sdk/google`, OpenRouter, …).
76
+ * @experimental
77
+ */
78
+ type ModelInput = LanguageModel;
79
+ /**
80
+ * Likewise for embeddings: a Workers AI embedding model id (e.g.
81
+ * `@cf/baai/bge-base-en-v1.5`) or any AI SDK {@link EmbeddingModel}.
82
+ * @experimental
83
+ */
84
+ type EmbeddingModelInput = EmbeddingModel | string;
85
+ /**
86
+ * The `ctx.ai` surface. `model`/`embeddingModel` resolve a Workers AI model from
87
+ * a string (the default provider) and pass any non-string model straight through,
88
+ * so both accept Workers AI and bring-your-own providers. Feed the resolved model
89
+ * to the AI SDK functions re-exported from `@lunora/ai` (`generateText`,
90
+ * `streamText`, `generateObject`, `embed`, …); `run` is the raw binding escape
91
+ * hatch, and `workersai` is the underlying provider for direct model access.
92
+ * @experimental
93
+ */
94
+ interface LunoraAi {
95
+ /** Resolve an {@link EmbeddingModel}: a string → Workers AI, an object → passthrough. */
96
+ embeddingModel: (model?: EmbeddingModelInput) => EmbeddingModel;
97
+ /** Resolve a {@link LanguageModel}: a string → Workers AI, an object → passthrough. */
98
+ model: (model?: ModelInput) => LanguageModel;
99
+ /**
100
+ * Raw Workers AI binding passthrough (void-style `ai.run`). Bypasses the AI
101
+ * SDK entirely — useful for Workers-AI-only model families (image, ASR,
102
+ * translation) not surfaced through the provider. Throws if no binding was
103
+ * supplied.
104
+ */
105
+ run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
106
+ /** The underlying Workers AI provider — `ai.workersai("@cf/...")` for a raw model. */
107
+ workersai: WorkersAiProviderLike;
108
+ }
109
+ export { AiBindingLike as A, EmbeddingModelInput as E, LunoraAiOptions as L, ModelInput as M, WorkersAiProviderLike as W, LunoraAi as a, AiGatewayOptions as b };
@@ -0,0 +1,109 @@
1
+ import { EmbeddingModel, LanguageModel } from 'ai';
2
+ /**
3
+ * Structural projection of the Cloudflare Workers `AI` binding (`env.AI`).
4
+ * Declared locally so unit tests can pass a plain-object double and the real
5
+ * binding satisfies the same shape without importing `@cloudflare/workers-types`
6
+ * into the public surface. Mirrors the `run` method documented at
7
+ * https://developers.cloudflare.com/workers-ai/.
8
+ * @experimental
9
+ */
10
+ interface AiBindingLike {
11
+ run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
12
+ }
13
+ /**
14
+ * A Workers AI provider instance — the value returned by `createWorkersAI(...)`.
15
+ * Calling it with a model id yields an AI SDK {@link LanguageModel}; the
16
+ * optional `textEmbeddingModel` factory yields an {@link EmbeddingModel}.
17
+ * Typed structurally so `@lunora/ai` neither re-declares the provider's full
18
+ * surface nor hard-pins its exact type across minor releases.
19
+ * @experimental
20
+ */
21
+ interface WorkersAiProviderLike {
22
+ (modelId: string, settings?: Record<string, unknown>): LanguageModel;
23
+ textEmbeddingModel?: (modelId: string) => EmbeddingModel;
24
+ }
25
+ /**
26
+ * AI Gateway options forwarded to `createWorkersAI`. Lets inference route
27
+ * through a Cloudflare AI Gateway for caching, rate-limiting, and observability.
28
+ * @experimental
29
+ */
30
+ interface AiGatewayOptions {
31
+ [key: string]: unknown;
32
+ id: string;
33
+ }
34
+ /**
35
+ * `LunoraAiOptions` is part of the experimental `@lunora/ai` API and may change without a major version bump.
36
+ * @experimental
37
+ */
38
+ interface LunoraAiOptions {
39
+ /**
40
+ * The Workers `AI` binding (`env.AI`). Required for the zero-config Workers
41
+ * AI default and for the raw `ai.run(...)` passthrough. May be omitted when
42
+ * a pre-built `provider` is supplied (e.g. in tests or a custom setup).
43
+ */
44
+ binding?: AiBindingLike;
45
+ /**
46
+ * Default Workers AI **embedding** model id used by `embeddingModel()` when no
47
+ * explicit model is passed (e.g. `@cf/baai/bge-base-en-v1.5`). Kept separate
48
+ * from `defaultModel` because a language-model id and an embedding-model
49
+ * id belong to different Workers AI families and are never interchangeable —
50
+ * reusing the language-model default here would defer a wrong-family error to
51
+ * inference time. Has no effect on bring-your-own providers.
52
+ */
53
+ defaultEmbeddingModel?: string;
54
+ /**
55
+ * Default Workers AI **language** model id used by `model()` when no explicit
56
+ * model is passed. For embeddings, set `defaultEmbeddingModel` instead.
57
+ * Has no effect on bring-your-own providers.
58
+ */
59
+ defaultModel?: string;
60
+ /** Route Workers AI inference through a Cloudflare AI Gateway. */
61
+ gateway?: AiGatewayOptions;
62
+ /**
63
+ * Pre-built Workers AI provider. When omitted, one is constructed from
64
+ * `binding` via `createWorkersAI`. Supplying it directly is the seam used by
65
+ * tests and advanced setups; it also lets callers configure the provider
66
+ * (e.g. `safePrompt`) before handing it to `@lunora/ai`.
67
+ */
68
+ provider?: WorkersAiProviderLike;
69
+ }
70
+ /**
71
+ * A model to run against. The AI SDK's {@link LanguageModel} already admits a
72
+ * bare `string`, so this alias covers both arms of the provider-agnostic seam:
73
+ * a string id is the Workers AI convenience path (resolved by `ctx.ai.model`),
74
+ * a built model object is bring-your-own (`@ai-sdk/openai`, `@ai-sdk/anthropic`,
75
+ * `@ai-sdk/google`, OpenRouter, …).
76
+ * @experimental
77
+ */
78
+ type ModelInput = LanguageModel;
79
+ /**
80
+ * Likewise for embeddings: a Workers AI embedding model id (e.g.
81
+ * `@cf/baai/bge-base-en-v1.5`) or any AI SDK {@link EmbeddingModel}.
82
+ * @experimental
83
+ */
84
+ type EmbeddingModelInput = EmbeddingModel | string;
85
+ /**
86
+ * The `ctx.ai` surface. `model`/`embeddingModel` resolve a Workers AI model from
87
+ * a string (the default provider) and pass any non-string model straight through,
88
+ * so both accept Workers AI and bring-your-own providers. Feed the resolved model
89
+ * to the AI SDK functions re-exported from `@lunora/ai` (`generateText`,
90
+ * `streamText`, `generateObject`, `embed`, …); `run` is the raw binding escape
91
+ * hatch, and `workersai` is the underlying provider for direct model access.
92
+ * @experimental
93
+ */
94
+ interface LunoraAi {
95
+ /** Resolve an {@link EmbeddingModel}: a string → Workers AI, an object → passthrough. */
96
+ embeddingModel: (model?: EmbeddingModelInput) => EmbeddingModel;
97
+ /** Resolve a {@link LanguageModel}: a string → Workers AI, an object → passthrough. */
98
+ model: (model?: ModelInput) => LanguageModel;
99
+ /**
100
+ * Raw Workers AI binding passthrough (void-style `ai.run`). Bypasses the AI
101
+ * SDK entirely — useful for Workers-AI-only model families (image, ASR,
102
+ * translation) not surfaced through the provider. Throws if no binding was
103
+ * supplied.
104
+ */
105
+ run: (model: string, inputs: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>;
106
+ /** The underlying Workers AI provider — `ai.workersai("@cf/...")` for a raw model. */
107
+ workersai: WorkersAiProviderLike;
108
+ }
109
+ export { AiBindingLike as A, EmbeddingModelInput as E, LunoraAiOptions as L, ModelInput as M, WorkersAiProviderLike as W, LunoraAi as a, AiGatewayOptions as b };