expo-ai-kit 0.9.0 → 0.11.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/README.md +96 -2
- package/ai.d.ts +1 -0
- package/ai.js +5 -0
- package/android/src/main/java/expo/modules/aikit/ExpoAiKitModule.kt +14 -0
- package/build/ExpoAiKitModule.d.ts +4 -0
- package/build/ExpoAiKitModule.d.ts.map +1 -1
- package/build/ExpoAiKitModule.js.map +1 -1
- package/build/ai/convert.d.ts +55 -0
- package/build/ai/convert.d.ts.map +1 -0
- package/build/ai/convert.js +270 -0
- package/build/ai/convert.js.map +1 -0
- package/build/ai/index.d.ts +66 -0
- package/build/ai/index.d.ts.map +1 -0
- package/build/ai/index.js +273 -0
- package/build/ai/index.js.map +1 -0
- package/build/index.d.ts +43 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +59 -0
- package/build/index.js.map +1 -1
- package/build/rag.d.ts +116 -0
- package/build/rag.d.ts.map +1 -0
- package/build/rag.js +189 -0
- package/build/rag.js.map +1 -0
- package/build/types.d.ts +13 -0
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/ios/ExpoAiKitModule.swift +77 -0
- package/package.json +19 -3
- package/src/ExpoAiKitModule.ts +4 -0
- package/src/ai/convert.ts +309 -0
- package/src/ai/index.ts +338 -0
- package/src/index.ts +66 -0
- package/src/rag.ts +262 -0
- package/src/types.ts +18 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { embed, getActiveModel, sendMessage, setModel, streamMessage } from '../index';
|
|
2
|
+
import { ModelError } from '../types';
|
|
3
|
+
import { convertCallOptions, extractOutput, EMPTY_USAGE, newPartId } from './convert';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Vercel AI SDK provider for expo-ai-kit — `import { expoAiKit } from 'expo-ai-kit/ai'`.
|
|
6
|
+
//
|
|
7
|
+
// Implements the LanguageModelV3 spec (AI SDK 6; also accepted by AI SDK 7),
|
|
8
|
+
// wrapping the same core sendMessage/streamMessage/embed calls as the rest of
|
|
9
|
+
// the library — the single-flight INFERENCE_BUSY guard, stateless-model
|
|
10
|
+
// semantics, and typed ModelError contract all apply unchanged.
|
|
11
|
+
//
|
|
12
|
+
// Zero-dependency by construction: everything imported from '@ai-sdk/provider'
|
|
13
|
+
// is a *type* (erased at compile time). The package is only needed by consumers
|
|
14
|
+
// who use this entry point, as an optional peer for its typings.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
const PROVIDER = 'expo-ai-kit';
|
|
17
|
+
/**
|
|
18
|
+
* Sentinel model id meaning "whatever model is currently active" — the OS
|
|
19
|
+
* built-in by default, or whatever the app last activated via setModel().
|
|
20
|
+
* The provider never switches models for this id.
|
|
21
|
+
*/
|
|
22
|
+
export const AUTO_MODEL_ID = 'auto';
|
|
23
|
+
/** Model id for the built-in embedding model (Apple NLContextualEmbedding, iOS 17+). */
|
|
24
|
+
export const EMBEDDING_MODEL_ID = 'apple-nl-contextual';
|
|
25
|
+
/**
|
|
26
|
+
* Create an expo-ai-kit provider for the Vercel AI SDK.
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { generateText, streamText } from 'ai';
|
|
30
|
+
* import { expoAiKit } from 'expo-ai-kit/ai';
|
|
31
|
+
*
|
|
32
|
+
* const { text } = await generateText({
|
|
33
|
+
* model: expoAiKit(), // the active on-device model
|
|
34
|
+
* prompt: 'Capital of France?',
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* const result = streamText({
|
|
38
|
+
* model: expoAiKit('gemma-e2b'), // activates gemma-e2b first (must be downloaded)
|
|
39
|
+
* prompt: 'Write a short story',
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* On-device caveats (all documented in the guide):
|
|
44
|
+
* - One generation at a time — concurrent calls reject with INFERENCE_BUSY.
|
|
45
|
+
* - Per-call sampling (temperature, topK, …) is reported as an unsupported-
|
|
46
|
+
* setting warning; sampling is fixed when the model is activated.
|
|
47
|
+
* - Tool calling and JSON output ride the same prompt protocol as
|
|
48
|
+
* generateText()/generateObject() — single-shot here, since the AI SDK owns
|
|
49
|
+
* the loop. Streaming buffers when tools/JSON are requested (the envelope
|
|
50
|
+
* must be parsed whole, not surfaced as text deltas).
|
|
51
|
+
* - embeddingModel() is iOS-only for now; Android throws DEVICE_NOT_SUPPORTED.
|
|
52
|
+
*/
|
|
53
|
+
export function createExpoAiKit() {
|
|
54
|
+
const languageModel = (modelId = AUTO_MODEL_ID, settings) => createLanguageModel(modelId, settings);
|
|
55
|
+
const embeddingModel = (modelId = EMBEDDING_MODEL_ID) => {
|
|
56
|
+
if (modelId !== EMBEDDING_MODEL_ID) {
|
|
57
|
+
throw new ModelError('MODEL_NOT_FOUND', modelId, `expo-ai-kit has one embedding model: "${EMBEDDING_MODEL_ID}" (Apple NLContextualEmbedding).`);
|
|
58
|
+
}
|
|
59
|
+
return createEmbeddingModel();
|
|
60
|
+
};
|
|
61
|
+
const provider = languageModel;
|
|
62
|
+
provider.languageModel = languageModel;
|
|
63
|
+
provider.embeddingModel = embeddingModel;
|
|
64
|
+
provider.textEmbeddingModel = embeddingModel;
|
|
65
|
+
provider.imageModel = (modelId) => {
|
|
66
|
+
throw new ModelError('MODEL_NOT_FOUND', modelId, 'expo-ai-kit does not provide image models.');
|
|
67
|
+
};
|
|
68
|
+
return provider;
|
|
69
|
+
}
|
|
70
|
+
/** The default provider instance. */
|
|
71
|
+
export const expoAiKit = createExpoAiKit();
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Language model
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
function createLanguageModel(modelId, settings) {
|
|
76
|
+
/**
|
|
77
|
+
* Make sure this instance's model is the active one. 'auto' never switches.
|
|
78
|
+
* Deliberately skipped when already active: reloading a multi-GB model per
|
|
79
|
+
* call would be catastrophic, so settings apply on activation only.
|
|
80
|
+
*/
|
|
81
|
+
const ensureModel = async () => {
|
|
82
|
+
if (modelId === AUTO_MODEL_ID) {
|
|
83
|
+
try {
|
|
84
|
+
return getActiveModel();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return AUTO_MODEL_ID; // native module unavailable (e.g. web) — let the call itself fail
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let active;
|
|
91
|
+
try {
|
|
92
|
+
active = getActiveModel();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
active = undefined;
|
|
96
|
+
}
|
|
97
|
+
if (active !== modelId) {
|
|
98
|
+
await setModel(modelId, settings);
|
|
99
|
+
}
|
|
100
|
+
return modelId;
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
specificationVersion: 'v3',
|
|
104
|
+
provider: PROVIDER,
|
|
105
|
+
modelId,
|
|
106
|
+
supportedUrls: {}, // no URLs are consumed natively — text-only models
|
|
107
|
+
async doGenerate(options) {
|
|
108
|
+
const call = convertCallOptions(options);
|
|
109
|
+
const activeModelId = await ensureModel();
|
|
110
|
+
const { text } = await sendMessage(call.messages, { signal: options.abortSignal });
|
|
111
|
+
const output = extractOutput(text, call.toolNames, call.jsonMode);
|
|
112
|
+
const content = output.kind === 'tool-call'
|
|
113
|
+
? [
|
|
114
|
+
{
|
|
115
|
+
type: 'tool-call',
|
|
116
|
+
toolCallId: newPartId('call'),
|
|
117
|
+
toolName: output.toolName,
|
|
118
|
+
input: output.input,
|
|
119
|
+
},
|
|
120
|
+
]
|
|
121
|
+
: [{ type: 'text', text: output.text }];
|
|
122
|
+
return {
|
|
123
|
+
content,
|
|
124
|
+
finishReason: {
|
|
125
|
+
unified: output.kind === 'tool-call' ? 'tool-calls' : 'stop',
|
|
126
|
+
raw: undefined,
|
|
127
|
+
},
|
|
128
|
+
usage: EMPTY_USAGE,
|
|
129
|
+
warnings: call.warnings,
|
|
130
|
+
response: { modelId: activeModelId, timestamp: new Date() },
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
async doStream(options) {
|
|
134
|
+
if (typeof ReadableStream === 'undefined') {
|
|
135
|
+
throw new Error('expo-ai-kit/ai: streaming needs a ReadableStream polyfill in React Native ' +
|
|
136
|
+
'(e.g. web-streams-polyfill) — the AI SDK requires the same polyfills. See the docs.');
|
|
137
|
+
}
|
|
138
|
+
const call = convertCallOptions(options);
|
|
139
|
+
const activeModelId = await ensureModel();
|
|
140
|
+
const abortSignal = options.abortSignal;
|
|
141
|
+
// Tool calling / JSON mode: the envelope must be parsed as a whole — a
|
|
142
|
+
// half-streamed `{"tool": …` would surface as garbage text deltas — so
|
|
143
|
+
// those runs buffer and emit the parsed result in one go.
|
|
144
|
+
if (call.toolNames.length > 0 || call.jsonMode) {
|
|
145
|
+
const stream = new ReadableStream({
|
|
146
|
+
start: async (controller) => {
|
|
147
|
+
controller.enqueue({ type: 'stream-start', warnings: call.warnings });
|
|
148
|
+
controller.enqueue({
|
|
149
|
+
type: 'response-metadata',
|
|
150
|
+
modelId: activeModelId,
|
|
151
|
+
timestamp: new Date(),
|
|
152
|
+
});
|
|
153
|
+
try {
|
|
154
|
+
const { text } = await sendMessage(call.messages, { signal: abortSignal });
|
|
155
|
+
const output = extractOutput(text, call.toolNames, call.jsonMode);
|
|
156
|
+
if (output.kind === 'tool-call') {
|
|
157
|
+
const id = newPartId('call');
|
|
158
|
+
controller.enqueue({ type: 'tool-input-start', id, toolName: output.toolName });
|
|
159
|
+
controller.enqueue({ type: 'tool-input-delta', id, delta: output.input });
|
|
160
|
+
controller.enqueue({ type: 'tool-input-end', id });
|
|
161
|
+
controller.enqueue({
|
|
162
|
+
type: 'tool-call',
|
|
163
|
+
toolCallId: id,
|
|
164
|
+
toolName: output.toolName,
|
|
165
|
+
input: output.input,
|
|
166
|
+
});
|
|
167
|
+
controller.enqueue({
|
|
168
|
+
type: 'finish',
|
|
169
|
+
usage: EMPTY_USAGE,
|
|
170
|
+
finishReason: { unified: 'tool-calls', raw: undefined },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const id = newPartId('text');
|
|
175
|
+
controller.enqueue({ type: 'text-start', id });
|
|
176
|
+
controller.enqueue({ type: 'text-delta', id, delta: output.text });
|
|
177
|
+
controller.enqueue({ type: 'text-end', id });
|
|
178
|
+
controller.enqueue({
|
|
179
|
+
type: 'finish',
|
|
180
|
+
usage: EMPTY_USAGE,
|
|
181
|
+
finishReason: { unified: 'stop', raw: undefined },
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
controller.close();
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
controller.error(error);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
return { stream };
|
|
192
|
+
}
|
|
193
|
+
// Plain text: real token streaming over streamMessage().
|
|
194
|
+
let handle;
|
|
195
|
+
let settled = false;
|
|
196
|
+
const stream = new ReadableStream({
|
|
197
|
+
start: (controller) => {
|
|
198
|
+
controller.enqueue({ type: 'stream-start', warnings: call.warnings });
|
|
199
|
+
controller.enqueue({
|
|
200
|
+
type: 'response-metadata',
|
|
201
|
+
modelId: activeModelId,
|
|
202
|
+
timestamp: new Date(),
|
|
203
|
+
});
|
|
204
|
+
const textId = newPartId('text');
|
|
205
|
+
let textStarted = false;
|
|
206
|
+
const onAbort = () => {
|
|
207
|
+
if (settled)
|
|
208
|
+
return;
|
|
209
|
+
settled = true;
|
|
210
|
+
handle?.stop(); // stops native generation; promise resolves with partial text
|
|
211
|
+
controller.error(new ModelError('INFERENCE_CANCELLED', '', 'Aborted by caller'));
|
|
212
|
+
};
|
|
213
|
+
abortSignal?.addEventListener('abort', onAbort, { once: true });
|
|
214
|
+
handle = streamMessage(call.messages, (event) => {
|
|
215
|
+
if (settled)
|
|
216
|
+
return;
|
|
217
|
+
if (event.token !== '') {
|
|
218
|
+
if (!textStarted) {
|
|
219
|
+
textStarted = true;
|
|
220
|
+
controller.enqueue({ type: 'text-start', id: textId });
|
|
221
|
+
}
|
|
222
|
+
controller.enqueue({ type: 'text-delta', id: textId, delta: event.token });
|
|
223
|
+
}
|
|
224
|
+
if (event.isDone) {
|
|
225
|
+
settled = true;
|
|
226
|
+
abortSignal?.removeEventListener('abort', onAbort);
|
|
227
|
+
if (textStarted)
|
|
228
|
+
controller.enqueue({ type: 'text-end', id: textId });
|
|
229
|
+
controller.enqueue({
|
|
230
|
+
type: 'finish',
|
|
231
|
+
usage: EMPTY_USAGE,
|
|
232
|
+
finishReason: { unified: 'stop', raw: undefined },
|
|
233
|
+
});
|
|
234
|
+
controller.close();
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
handle.promise.catch((error) => {
|
|
238
|
+
if (settled)
|
|
239
|
+
return;
|
|
240
|
+
settled = true;
|
|
241
|
+
abortSignal?.removeEventListener('abort', onAbort);
|
|
242
|
+
controller.error(error);
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
cancel: () => {
|
|
246
|
+
settled = true;
|
|
247
|
+
handle?.stop();
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
return { stream };
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Embedding model
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
function createEmbeddingModel() {
|
|
258
|
+
return {
|
|
259
|
+
specificationVersion: 'v3',
|
|
260
|
+
provider: PROVIDER,
|
|
261
|
+
modelId: EMBEDDING_MODEL_ID,
|
|
262
|
+
maxEmbeddingsPerCall: Infinity, // embed() batches internally; no API limit
|
|
263
|
+
supportsParallelCalls: true, // embeddings bypass the single-flight inference guard
|
|
264
|
+
async doEmbed({ values, abortSignal }) {
|
|
265
|
+
if (abortSignal?.aborted) {
|
|
266
|
+
throw new ModelError('INFERENCE_CANCELLED', '', 'Aborted before start');
|
|
267
|
+
}
|
|
268
|
+
const { embeddings } = await embed(values);
|
|
269
|
+
return { embeddings, warnings: [] };
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n EmbeddingModelV3,\n LanguageModelV3,\n LanguageModelV3CallOptions,\n LanguageModelV3Content,\n LanguageModelV3GenerateResult,\n LanguageModelV3StreamPart,\n LanguageModelV3StreamResult,\n} from '@ai-sdk/provider';\nimport { embed, getActiveModel, sendMessage, setModel, streamMessage } from '../index';\nimport { ModelError, type SetModelOptions } from '../types';\nimport { convertCallOptions, extractOutput, EMPTY_USAGE, newPartId } from './convert';\n\n// ---------------------------------------------------------------------------\n// Vercel AI SDK provider for expo-ai-kit — `import { expoAiKit } from 'expo-ai-kit/ai'`.\n//\n// Implements the LanguageModelV3 spec (AI SDK 6; also accepted by AI SDK 7),\n// wrapping the same core sendMessage/streamMessage/embed calls as the rest of\n// the library — the single-flight INFERENCE_BUSY guard, stateless-model\n// semantics, and typed ModelError contract all apply unchanged.\n//\n// Zero-dependency by construction: everything imported from '@ai-sdk/provider'\n// is a *type* (erased at compile time). The package is only needed by consumers\n// who use this entry point, as an optional peer for its typings.\n// ---------------------------------------------------------------------------\n\nconst PROVIDER = 'expo-ai-kit';\n\n/**\n * Sentinel model id meaning \"whatever model is currently active\" — the OS\n * built-in by default, or whatever the app last activated via setModel().\n * The provider never switches models for this id.\n */\nexport const AUTO_MODEL_ID = 'auto';\n\n/** Model id for the built-in embedding model (Apple NLContextualEmbedding, iOS 17+). */\nexport const EMBEDDING_MODEL_ID = 'apple-nl-contextual';\n\n/**\n * Per-instance model settings, applied when this instance activates its model\n * (same shape as {@link setModel}'s options). If the model is already active,\n * it is NOT reloaded — on-device model loads are expensive, so sampling config\n * effectively belongs to whoever activated the model first.\n */\nexport type ExpoAiKitModelSettings = SetModelOptions;\n\nexport interface ExpoAiKitProvider {\n /** Shorthand for {@link ExpoAiKitProvider.languageModel}. */\n (modelId?: string, settings?: ExpoAiKitModelSettings): LanguageModelV3;\n /**\n * A LanguageModelV3 over the on-device model. Pass any model id accepted by\n * setModel() ('apple-fm', 'mlkit', 'gemma-e2b', a registerModel() id, …) to\n * have the provider activate it before generating, or omit / pass 'auto' to\n * use whatever model is already active.\n */\n languageModel(modelId?: string, settings?: ExpoAiKitModelSettings): LanguageModelV3;\n /** An EmbeddingModelV3 over embed() — iOS-only (Apple NLContextualEmbedding). */\n embeddingModel(modelId?: string): EmbeddingModelV3;\n /** @deprecated Spec alias for {@link ExpoAiKitProvider.embeddingModel}. */\n textEmbeddingModel(modelId?: string): EmbeddingModelV3;\n /** expo-ai-kit has no image models; always throws MODEL_NOT_FOUND. */\n imageModel(modelId: string): never;\n}\n\n/**\n * Create an expo-ai-kit provider for the Vercel AI SDK.\n *\n * ```ts\n * import { generateText, streamText } from 'ai';\n * import { expoAiKit } from 'expo-ai-kit/ai';\n *\n * const { text } = await generateText({\n * model: expoAiKit(), // the active on-device model\n * prompt: 'Capital of France?',\n * });\n *\n * const result = streamText({\n * model: expoAiKit('gemma-e2b'), // activates gemma-e2b first (must be downloaded)\n * prompt: 'Write a short story',\n * });\n * ```\n *\n * On-device caveats (all documented in the guide):\n * - One generation at a time — concurrent calls reject with INFERENCE_BUSY.\n * - Per-call sampling (temperature, topK, …) is reported as an unsupported-\n * setting warning; sampling is fixed when the model is activated.\n * - Tool calling and JSON output ride the same prompt protocol as\n * generateText()/generateObject() — single-shot here, since the AI SDK owns\n * the loop. Streaming buffers when tools/JSON are requested (the envelope\n * must be parsed whole, not surfaced as text deltas).\n * - embeddingModel() is iOS-only for now; Android throws DEVICE_NOT_SUPPORTED.\n */\nexport function createExpoAiKit(): ExpoAiKitProvider {\n const languageModel = (\n modelId: string = AUTO_MODEL_ID,\n settings?: ExpoAiKitModelSettings\n ): LanguageModelV3 => createLanguageModel(modelId, settings);\n\n const embeddingModel = (modelId: string = EMBEDDING_MODEL_ID): EmbeddingModelV3 => {\n if (modelId !== EMBEDDING_MODEL_ID) {\n throw new ModelError(\n 'MODEL_NOT_FOUND',\n modelId,\n `expo-ai-kit has one embedding model: \"${EMBEDDING_MODEL_ID}\" (Apple NLContextualEmbedding).`\n );\n }\n return createEmbeddingModel();\n };\n\n const provider = languageModel as ExpoAiKitProvider;\n provider.languageModel = languageModel;\n provider.embeddingModel = embeddingModel;\n provider.textEmbeddingModel = embeddingModel;\n provider.imageModel = (modelId: string): never => {\n throw new ModelError('MODEL_NOT_FOUND', modelId, 'expo-ai-kit does not provide image models.');\n };\n return provider;\n}\n\n/** The default provider instance. */\nexport const expoAiKit: ExpoAiKitProvider = createExpoAiKit();\n\n// ---------------------------------------------------------------------------\n// Language model\n// ---------------------------------------------------------------------------\n\nfunction createLanguageModel(\n modelId: string,\n settings?: ExpoAiKitModelSettings\n): LanguageModelV3 {\n /**\n * Make sure this instance's model is the active one. 'auto' never switches.\n * Deliberately skipped when already active: reloading a multi-GB model per\n * call would be catastrophic, so settings apply on activation only.\n */\n const ensureModel = async (): Promise<string> => {\n if (modelId === AUTO_MODEL_ID) {\n try {\n return getActiveModel();\n } catch {\n return AUTO_MODEL_ID; // native module unavailable (e.g. web) — let the call itself fail\n }\n }\n let active: string | undefined;\n try {\n active = getActiveModel();\n } catch {\n active = undefined;\n }\n if (active !== modelId) {\n await setModel(modelId, settings);\n }\n return modelId;\n };\n\n return {\n specificationVersion: 'v3',\n provider: PROVIDER,\n modelId,\n supportedUrls: {}, // no URLs are consumed natively — text-only models\n\n async doGenerate(options: LanguageModelV3CallOptions): Promise<LanguageModelV3GenerateResult> {\n const call = convertCallOptions(options);\n const activeModelId = await ensureModel();\n\n const { text } = await sendMessage(call.messages, { signal: options.abortSignal });\n const output = extractOutput(text, call.toolNames, call.jsonMode);\n\n const content: LanguageModelV3Content[] =\n output.kind === 'tool-call'\n ? [\n {\n type: 'tool-call',\n toolCallId: newPartId('call'),\n toolName: output.toolName,\n input: output.input,\n },\n ]\n : [{ type: 'text', text: output.text }];\n\n return {\n content,\n finishReason: {\n unified: output.kind === 'tool-call' ? 'tool-calls' : 'stop',\n raw: undefined,\n },\n usage: EMPTY_USAGE,\n warnings: call.warnings,\n response: { modelId: activeModelId, timestamp: new Date() },\n };\n },\n\n async doStream(options: LanguageModelV3CallOptions): Promise<LanguageModelV3StreamResult> {\n if (typeof ReadableStream === 'undefined') {\n throw new Error(\n 'expo-ai-kit/ai: streaming needs a ReadableStream polyfill in React Native ' +\n '(e.g. web-streams-polyfill) — the AI SDK requires the same polyfills. See the docs.'\n );\n }\n\n const call = convertCallOptions(options);\n const activeModelId = await ensureModel();\n const abortSignal = options.abortSignal;\n\n // Tool calling / JSON mode: the envelope must be parsed as a whole — a\n // half-streamed `{\"tool\": …` would surface as garbage text deltas — so\n // those runs buffer and emit the parsed result in one go.\n if (call.toolNames.length > 0 || call.jsonMode) {\n const stream = new ReadableStream<LanguageModelV3StreamPart>({\n start: async (controller) => {\n controller.enqueue({ type: 'stream-start', warnings: call.warnings });\n controller.enqueue({\n type: 'response-metadata',\n modelId: activeModelId,\n timestamp: new Date(),\n });\n try {\n const { text } = await sendMessage(call.messages, { signal: abortSignal });\n const output = extractOutput(text, call.toolNames, call.jsonMode);\n if (output.kind === 'tool-call') {\n const id = newPartId('call');\n controller.enqueue({ type: 'tool-input-start', id, toolName: output.toolName });\n controller.enqueue({ type: 'tool-input-delta', id, delta: output.input });\n controller.enqueue({ type: 'tool-input-end', id });\n controller.enqueue({\n type: 'tool-call',\n toolCallId: id,\n toolName: output.toolName,\n input: output.input,\n });\n controller.enqueue({\n type: 'finish',\n usage: EMPTY_USAGE,\n finishReason: { unified: 'tool-calls', raw: undefined },\n });\n } else {\n const id = newPartId('text');\n controller.enqueue({ type: 'text-start', id });\n controller.enqueue({ type: 'text-delta', id, delta: output.text });\n controller.enqueue({ type: 'text-end', id });\n controller.enqueue({\n type: 'finish',\n usage: EMPTY_USAGE,\n finishReason: { unified: 'stop', raw: undefined },\n });\n }\n controller.close();\n } catch (error) {\n controller.error(error);\n }\n },\n });\n return { stream };\n }\n\n // Plain text: real token streaming over streamMessage().\n let handle: ReturnType<typeof streamMessage> | undefined;\n let settled = false;\n const stream = new ReadableStream<LanguageModelV3StreamPart>({\n start: (controller) => {\n controller.enqueue({ type: 'stream-start', warnings: call.warnings });\n controller.enqueue({\n type: 'response-metadata',\n modelId: activeModelId,\n timestamp: new Date(),\n });\n\n const textId = newPartId('text');\n let textStarted = false;\n\n const onAbort = () => {\n if (settled) return;\n settled = true;\n handle?.stop(); // stops native generation; promise resolves with partial text\n controller.error(new ModelError('INFERENCE_CANCELLED', '', 'Aborted by caller'));\n };\n abortSignal?.addEventListener('abort', onAbort, { once: true });\n\n handle = streamMessage(call.messages, (event) => {\n if (settled) return;\n if (event.token !== '') {\n if (!textStarted) {\n textStarted = true;\n controller.enqueue({ type: 'text-start', id: textId });\n }\n controller.enqueue({ type: 'text-delta', id: textId, delta: event.token });\n }\n if (event.isDone) {\n settled = true;\n abortSignal?.removeEventListener('abort', onAbort);\n if (textStarted) controller.enqueue({ type: 'text-end', id: textId });\n controller.enqueue({\n type: 'finish',\n usage: EMPTY_USAGE,\n finishReason: { unified: 'stop', raw: undefined },\n });\n controller.close();\n }\n });\n\n handle.promise.catch((error) => {\n if (settled) return;\n settled = true;\n abortSignal?.removeEventListener('abort', onAbort);\n controller.error(error);\n });\n },\n cancel: () => {\n settled = true;\n handle?.stop();\n },\n });\n return { stream };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Embedding model\n// ---------------------------------------------------------------------------\n\nfunction createEmbeddingModel(): EmbeddingModelV3 {\n return {\n specificationVersion: 'v3',\n provider: PROVIDER,\n modelId: EMBEDDING_MODEL_ID,\n maxEmbeddingsPerCall: Infinity, // embed() batches internally; no API limit\n supportsParallelCalls: true, // embeddings bypass the single-flight inference guard\n\n async doEmbed({ values, abortSignal }) {\n if (abortSignal?.aborted) {\n throw new ModelError('INFERENCE_CANCELLED', '', 'Aborted before start');\n }\n const { embeddings } = await embed(values);\n return { embeddings, warnings: [] };\n },\n };\n}\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { LLMMessage, LLMSendOptions, LLMResponse, LLMStreamOptions, LLMStreamCallback, LLMStreamHandle, BuiltInModel, DownloadableModel, SetModelOptions, JSONSchema, GenerateObjectOptions, GenerateObjectResult, GenerateTextOptions, GenerateTextResult } from './types';
|
|
1
|
+
import { LLMMessage, LLMSendOptions, LLMResponse, LLMStreamOptions, LLMStreamCallback, LLMStreamHandle, BuiltInModel, DownloadableModel, SetModelOptions, JSONSchema, GenerateObjectOptions, GenerateObjectResult, GenerateTextOptions, GenerateTextResult, EmbedResult } from './types';
|
|
2
2
|
export * from './types';
|
|
3
3
|
export * from './models';
|
|
4
|
+
export * from './rag';
|
|
4
5
|
/**
|
|
5
6
|
* Check if on-device AI is available on the current device.
|
|
6
7
|
* Returns false on unsupported platforms (web, etc.).
|
|
@@ -174,6 +175,47 @@ export declare function generateObject<T = unknown>(messages: LLMMessage[], sche
|
|
|
174
175
|
* ```
|
|
175
176
|
*/
|
|
176
177
|
export declare function generateText(messages: LLMMessage[], options?: GenerateTextOptions): Promise<GenerateTextResult>;
|
|
178
|
+
/**
|
|
179
|
+
* Turn text into embedding vectors for semantic search / on-device RAG.
|
|
180
|
+
*
|
|
181
|
+
* Returns one vector per input string (in order), which you can compare with
|
|
182
|
+
* {@link cosineSimilarity} or store in a {@link createVectorStore} to retrieve
|
|
183
|
+
* the most relevant chunks before a {@link sendMessage} / {@link generateText}
|
|
184
|
+
* call. Pair with {@link chunkText} to split documents first.
|
|
185
|
+
*
|
|
186
|
+
* **iOS-only for now**, backed by Apple's `NLContextualEmbedding` — a zero-
|
|
187
|
+
* download, OS-maintained model (no app-size cost, works even where Apple
|
|
188
|
+
* Intelligence isn't enabled, iOS 17+). On Android/web it throws
|
|
189
|
+
* `DEVICE_NOT_SUPPORTED`; the RAG toolkit (`chunkText`, `cosineSimilarity`,
|
|
190
|
+
* `createVectorStore`) still works there with any vector source you bring.
|
|
191
|
+
*
|
|
192
|
+
* Embeddings don't use the generation KV-cache, so `embed()` is **not** subject
|
|
193
|
+
* to the single-flight `INFERENCE_BUSY` guard — it can run alongside other work.
|
|
194
|
+
*
|
|
195
|
+
* @param texts - Non-empty array of strings to embed.
|
|
196
|
+
* @returns `{ embeddings, dimensions }` — `embeddings[i]` is the vector for `texts[i]`.
|
|
197
|
+
* @throws {ModelError} DEVICE_NOT_SUPPORTED off iOS, or if no embedding model is
|
|
198
|
+
* available on the device.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* import { embed, chunkText, createVectorStore } from 'expo-ai-kit';
|
|
203
|
+
*
|
|
204
|
+
* const chunks = chunkText(document);
|
|
205
|
+
* const { embeddings } = await embed(chunks);
|
|
206
|
+
*
|
|
207
|
+
* const store = createVectorStore<{ text: string }>();
|
|
208
|
+
* store.addMany(chunks.map((text, i) => ({ id: `c${i}`, vector: embeddings[i], metadata: { text } })));
|
|
209
|
+
*
|
|
210
|
+
* const { embeddings: [q] } = await embed([question]);
|
|
211
|
+
* const context = store.search(q, { topK: 4 }).map((h) => h.metadata!.text).join('\n\n');
|
|
212
|
+
* const { text } = await sendMessage([
|
|
213
|
+
* { role: 'system', content: `Answer using only this context:\n${context}` },
|
|
214
|
+
* { role: 'user', content: question },
|
|
215
|
+
* ]);
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
export declare function embed(texts: string[]): Promise<EmbedResult>;
|
|
177
219
|
/**
|
|
178
220
|
* Get all built-in models available on the current platform.
|
|
179
221
|
*
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAEhB,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,iBAAiB,EAIjB,eAAe,EACf,UAAU,EACV,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,UAAU,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAEhB,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,iBAAiB,EAIjB,eAAe,EACf,UAAU,EACV,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAIlB,WAAW,EACZ,MAAM,SAAS,CAAC;AAiBjB,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AA0HtB;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAKpD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,UAAU,EAAE,EACtB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,WAAW,CAAC,CAmEtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,UAAU,EAAE,EACtB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,CAAC,EAAE,gBAAgB,GACzB,eAAe,CAwFjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAsB,cAAc,CAAC,CAAC,GAAG,OAAO,EAC9C,QAAQ,EAAE,UAAU,EAAE,EACtB,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAoElC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,UAAU,EAAE,EACtB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAmJ7B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAkBjE;AAMD;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAKhE;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAkC1E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAM7E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACpD,OAAO,CAAC,IAAI,CAAC,CA+Cf;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnE;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAQxF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAEjD"}
|
package/build/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { buildToolInstruction, parseToolCall, buildUnknownToolRepair, buildToolA
|
|
|
6
6
|
import { getAllModels, getRegistryEntry } from './models';
|
|
7
7
|
export * from './types';
|
|
8
8
|
export * from './models';
|
|
9
|
+
export * from './rag';
|
|
9
10
|
const DEFAULT_SYSTEM_PROMPT = 'You are a helpful, friendly assistant. Answer the user directly and concisely.';
|
|
10
11
|
const DEFAULT_OBJECT_SYSTEM_PROMPT = 'You output structured data as JSON. Follow the provided JSON Schema exactly.';
|
|
11
12
|
let streamIdCounter = 0;
|
|
@@ -598,6 +599,64 @@ export async function generateText(messages, options) {
|
|
|
598
599
|
};
|
|
599
600
|
}
|
|
600
601
|
// ============================================================================
|
|
602
|
+
// Embeddings API
|
|
603
|
+
// ============================================================================
|
|
604
|
+
/**
|
|
605
|
+
* Turn text into embedding vectors for semantic search / on-device RAG.
|
|
606
|
+
*
|
|
607
|
+
* Returns one vector per input string (in order), which you can compare with
|
|
608
|
+
* {@link cosineSimilarity} or store in a {@link createVectorStore} to retrieve
|
|
609
|
+
* the most relevant chunks before a {@link sendMessage} / {@link generateText}
|
|
610
|
+
* call. Pair with {@link chunkText} to split documents first.
|
|
611
|
+
*
|
|
612
|
+
* **iOS-only for now**, backed by Apple's `NLContextualEmbedding` — a zero-
|
|
613
|
+
* download, OS-maintained model (no app-size cost, works even where Apple
|
|
614
|
+
* Intelligence isn't enabled, iOS 17+). On Android/web it throws
|
|
615
|
+
* `DEVICE_NOT_SUPPORTED`; the RAG toolkit (`chunkText`, `cosineSimilarity`,
|
|
616
|
+
* `createVectorStore`) still works there with any vector source you bring.
|
|
617
|
+
*
|
|
618
|
+
* Embeddings don't use the generation KV-cache, so `embed()` is **not** subject
|
|
619
|
+
* to the single-flight `INFERENCE_BUSY` guard — it can run alongside other work.
|
|
620
|
+
*
|
|
621
|
+
* @param texts - Non-empty array of strings to embed.
|
|
622
|
+
* @returns `{ embeddings, dimensions }` — `embeddings[i]` is the vector for `texts[i]`.
|
|
623
|
+
* @throws {ModelError} DEVICE_NOT_SUPPORTED off iOS, or if no embedding model is
|
|
624
|
+
* available on the device.
|
|
625
|
+
*
|
|
626
|
+
* @example
|
|
627
|
+
* ```ts
|
|
628
|
+
* import { embed, chunkText, createVectorStore } from 'expo-ai-kit';
|
|
629
|
+
*
|
|
630
|
+
* const chunks = chunkText(document);
|
|
631
|
+
* const { embeddings } = await embed(chunks);
|
|
632
|
+
*
|
|
633
|
+
* const store = createVectorStore<{ text: string }>();
|
|
634
|
+
* store.addMany(chunks.map((text, i) => ({ id: `c${i}`, vector: embeddings[i], metadata: { text } })));
|
|
635
|
+
*
|
|
636
|
+
* const { embeddings: [q] } = await embed([question]);
|
|
637
|
+
* const context = store.search(q, { topK: 4 }).map((h) => h.metadata!.text).join('\n\n');
|
|
638
|
+
* const { text } = await sendMessage([
|
|
639
|
+
* { role: 'system', content: `Answer using only this context:\n${context}` },
|
|
640
|
+
* { role: 'user', content: question },
|
|
641
|
+
* ]);
|
|
642
|
+
* ```
|
|
643
|
+
*/
|
|
644
|
+
export async function embed(texts) {
|
|
645
|
+
if (Platform.OS !== 'ios') {
|
|
646
|
+
throw new ModelError('DEVICE_NOT_SUPPORTED', '', Platform.OS === 'android'
|
|
647
|
+
? 'embed() is iOS-only for now (Apple NLContextualEmbedding); Android support via MediaPipe is planned. ' +
|
|
648
|
+
'The RAG toolkit (chunkText, cosineSimilarity, createVectorStore) works on Android with any vector source.'
|
|
649
|
+
: 'embed() is only available on iOS');
|
|
650
|
+
}
|
|
651
|
+
if (!Array.isArray(texts) || texts.length === 0) {
|
|
652
|
+
throw new Error('texts array cannot be empty');
|
|
653
|
+
}
|
|
654
|
+
if (!texts.every((t) => typeof t === 'string')) {
|
|
655
|
+
throw new Error('texts must be an array of strings');
|
|
656
|
+
}
|
|
657
|
+
return wrapNative(() => ExpoAiKitModule.embed(texts));
|
|
658
|
+
}
|
|
659
|
+
// ============================================================================
|
|
601
660
|
// Model Management API
|
|
602
661
|
// ============================================================================
|
|
603
662
|
/**
|