extrait 0.5.2 → 0.5.4
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 +105 -3
- package/dist/conversation.d.ts +21 -0
- package/dist/index.cjs +97 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +97 -4
- package/dist/llm.d.ts +2 -1
- package/dist/prompt.d.ts +3 -1
- package/dist/providers/openai-compatible.d.ts +1 -0
- package/dist/types.d.ts +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Structured JSON extraction from LLMs with validation, repair, and streaming.
|
|
|
15
15
|
- Optional self-healing for validation failures
|
|
16
16
|
- Streaming support
|
|
17
17
|
- MCP tools
|
|
18
|
+
- Vector embeddings (OpenAI-compatible + Voyage AI)
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
20
21
|
|
|
@@ -175,19 +176,27 @@ const result = await llm.structured(
|
|
|
175
176
|
Use `images()` to build base64 image content blocks for vision-capable models.
|
|
176
177
|
|
|
177
178
|
```typescript
|
|
178
|
-
import { images } from "extrait";
|
|
179
|
+
import { images, prompt } from "extrait";
|
|
179
180
|
import { readFileSync } from "fs";
|
|
180
181
|
|
|
181
182
|
const base64 = readFileSync("photo.png").toString("base64");
|
|
183
|
+
const img = { base64, mimeType: "image/png" };
|
|
182
184
|
|
|
183
|
-
//
|
|
185
|
+
// With prompt() builder — pass LLMMessageContent array to .user() or .assistant()
|
|
186
|
+
const result = await llm.structured(Schema,
|
|
187
|
+
prompt()
|
|
188
|
+
.system`You are a vision assistant.`
|
|
189
|
+
.user([{ type: "text", text: "Describe this image." }, ...images(img)])
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// With raw messages array
|
|
184
193
|
const result = await llm.structured(Schema, {
|
|
185
194
|
messages: [
|
|
186
195
|
{
|
|
187
196
|
role: "user",
|
|
188
197
|
content: [
|
|
189
198
|
{ type: "text", text: "Describe this image." },
|
|
190
|
-
...images(
|
|
199
|
+
...images(img),
|
|
191
200
|
],
|
|
192
201
|
},
|
|
193
202
|
],
|
|
@@ -205,6 +214,38 @@ const content = [
|
|
|
205
214
|
|
|
206
215
|
`images()` accepts a single `{ base64, mimeType }` object or an array, and always returns an `LLMImageContent[]` that spreads directly into a content array.
|
|
207
216
|
|
|
217
|
+
### Conversations (multi-turn history)
|
|
218
|
+
|
|
219
|
+
Use `conversation()` to build a `LLMMessage[]` from an existing conversation history. This is the idiomatic way to pass prior turns to the LLM.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { conversation } from "extrait";
|
|
223
|
+
|
|
224
|
+
const messages = conversation("You are a helpful assistant.", [
|
|
225
|
+
{ role: "user", text: "What is the speed of light?" },
|
|
226
|
+
{ role: "assistant", text: "Approximately 299,792 km/s in a vacuum." },
|
|
227
|
+
{ role: "user", text: "How long does light take to reach Earth from the Sun?" },
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
// Pass to adapter directly
|
|
231
|
+
const response = await llm.adapter.complete({ messages });
|
|
232
|
+
|
|
233
|
+
// Or to structured extraction
|
|
234
|
+
const result = await llm.structured(Schema, { messages });
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Entries with `images` produce multimodal content automatically:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
const messages = conversation("You are a vision assistant.", [
|
|
241
|
+
{
|
|
242
|
+
role: "user",
|
|
243
|
+
text: "What is in this image?",
|
|
244
|
+
images: [{ base64, mimeType: "image/png" }],
|
|
245
|
+
},
|
|
246
|
+
]);
|
|
247
|
+
```
|
|
248
|
+
|
|
208
249
|
### Result Object
|
|
209
250
|
|
|
210
251
|
```typescript
|
|
@@ -242,6 +283,64 @@ try {
|
|
|
242
283
|
}
|
|
243
284
|
```
|
|
244
285
|
|
|
286
|
+
### Embeddings
|
|
287
|
+
|
|
288
|
+
Generate vector embeddings using `llm.embed()`. It always returns `number[][]` — one vector per input string.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// Create a dedicated embedder client (recommended)
|
|
292
|
+
const embedder = createLLM({
|
|
293
|
+
provider: "openai-compatible",
|
|
294
|
+
model: "text-embedding-3-small",
|
|
295
|
+
transport: { apiKey: process.env.OPENAI_API_KEY },
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Single string
|
|
299
|
+
const { embeddings, model, usage } = await embedder.embed("Hello world");
|
|
300
|
+
const vector: number[] = embeddings[0];
|
|
301
|
+
|
|
302
|
+
// Multiple strings in one request
|
|
303
|
+
const { embeddings } = await embedder.embed(["text one", "text two", "text three"]);
|
|
304
|
+
// embeddings[0], embeddings[1], embeddings[2] — one vector each
|
|
305
|
+
|
|
306
|
+
// Optional: override model or request extra options per call
|
|
307
|
+
const { embeddings } = await embedder.embed("Hello", {
|
|
308
|
+
model: "text-embedding-ada-002",
|
|
309
|
+
dimensions: 512, // supported by text-embedding-3-* models
|
|
310
|
+
body: { user: "user-id" }, // pass-through to provider
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Result shape:**
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
{
|
|
318
|
+
embeddings: number[][]; // one vector per input
|
|
319
|
+
model: string;
|
|
320
|
+
usage?: { inputTokens?: number; totalTokens?: number };
|
|
321
|
+
raw?: unknown; // full provider response
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Anthropic / Voyage AI**
|
|
326
|
+
|
|
327
|
+
Anthropic does not provide a native embedding API. Their recommended solution is [Voyage AI](https://api.voyageai.com), which uses the same OpenAI-compatible format:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
const embedder = createLLM({
|
|
331
|
+
provider: "openai-compatible",
|
|
332
|
+
model: "voyage-3",
|
|
333
|
+
transport: {
|
|
334
|
+
baseURL: "https://api.voyageai.com",
|
|
335
|
+
apiKey: process.env.VOYAGE_API_KEY,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const { embeddings } = await embedder.embed(["query", "document"]);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Calling `llm.embed()` on an `anthropic-compatible` adapter throws a descriptive error pointing to Voyage AI.
|
|
343
|
+
|
|
245
344
|
### MCP Tools
|
|
246
345
|
|
|
247
346
|
```typescript
|
|
@@ -329,6 +428,8 @@ Available examples:
|
|
|
329
428
|
- `multi-step-reasoning` - Chained structured calls ([multi-step-reasoning.ts](examples/multi-step-reasoning.ts))
|
|
330
429
|
- `calculator-tool` - MCP tool integration ([calculator-tool.ts](examples/calculator-tool.ts))
|
|
331
430
|
- `image-analysis` - Multimodal structured extraction from an image file ([image-analysis.ts](examples/image-analysis.ts))
|
|
431
|
+
- `conversation` - Multi-turn conversation history and inline image messages ([conversation.ts](examples/conversation.ts))
|
|
432
|
+
- `embeddings` - Vector embeddings, cosine similarity, and semantic comparison ([embeddings.ts](examples/embeddings.ts))
|
|
332
433
|
|
|
333
434
|
Pass arguments after the example name:
|
|
334
435
|
```bash
|
|
@@ -339,6 +440,7 @@ bun run dev timeout 5000
|
|
|
339
440
|
bun run dev simple "Bun.js runtime"
|
|
340
441
|
bun run dev sentiment-analysis "I love this product."
|
|
341
442
|
bun run dev multi-step-reasoning "Why is the sky blue?"
|
|
443
|
+
bun run dev embeddings "the cat sat on the mat" "a feline rested on the rug"
|
|
342
444
|
```
|
|
343
445
|
|
|
344
446
|
## Environment Variables
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ImageInput } from "./image";
|
|
2
|
+
import type { LLMMessage } from "./types";
|
|
3
|
+
export type ConversationEntry = {
|
|
4
|
+
role: "user";
|
|
5
|
+
text: string;
|
|
6
|
+
images?: ImageInput[];
|
|
7
|
+
} | {
|
|
8
|
+
role: "assistant";
|
|
9
|
+
text: string;
|
|
10
|
+
images?: ImageInput[];
|
|
11
|
+
} | {
|
|
12
|
+
role: "tool_call";
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
arguments?: Record<string, unknown>;
|
|
16
|
+
} | {
|
|
17
|
+
role: "tool_result";
|
|
18
|
+
id: string;
|
|
19
|
+
output: unknown;
|
|
20
|
+
};
|
|
21
|
+
export declare function conversation(systemPrompt: string, entries: ConversationEntry[]): LLMMessage[];
|
package/dist/index.cjs
CHANGED
|
@@ -64,6 +64,7 @@ __export(exports_src, {
|
|
|
64
64
|
createLLM: () => createLLM,
|
|
65
65
|
createDefaultProviderRegistry: () => createDefaultProviderRegistry,
|
|
66
66
|
createAnthropicCompatibleAdapter: () => createAnthropicCompatibleAdapter,
|
|
67
|
+
conversation: () => conversation,
|
|
67
68
|
buildSelfHealPrompt: () => buildSelfHealPrompt,
|
|
68
69
|
buildDefaultStructuredPrompt: () => buildDefaultStructuredPrompt,
|
|
69
70
|
StructuredParseError: () => StructuredParseError,
|
|
@@ -1605,6 +1606,7 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1605
1606
|
const fetcher = options.fetcher ?? fetch;
|
|
1606
1607
|
const path = options.path ?? "/v1/chat/completions";
|
|
1607
1608
|
const responsesPath = options.responsesPath ?? "/v1/responses";
|
|
1609
|
+
const embeddingPath = options.embeddingPath ?? "/v1/embeddings";
|
|
1608
1610
|
return {
|
|
1609
1611
|
provider: "openai-compatible",
|
|
1610
1612
|
model: options.model,
|
|
@@ -1677,6 +1679,36 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1677
1679
|
const out = { text, usage, finishReason };
|
|
1678
1680
|
callbacks.onComplete?.(out);
|
|
1679
1681
|
return out;
|
|
1682
|
+
},
|
|
1683
|
+
async embed(request) {
|
|
1684
|
+
const body = cleanUndefined({
|
|
1685
|
+
...options.defaultBody,
|
|
1686
|
+
...request.body,
|
|
1687
|
+
model: request.model ?? options.model,
|
|
1688
|
+
input: request.input,
|
|
1689
|
+
dimensions: request.dimensions,
|
|
1690
|
+
encoding_format: "float"
|
|
1691
|
+
});
|
|
1692
|
+
const response = await fetcher(buildURL(options.baseURL, embeddingPath), {
|
|
1693
|
+
method: "POST",
|
|
1694
|
+
headers: buildHeaders(options),
|
|
1695
|
+
body: JSON.stringify(body)
|
|
1696
|
+
});
|
|
1697
|
+
if (!response.ok) {
|
|
1698
|
+
const message = await response.text();
|
|
1699
|
+
throw new Error(`HTTP ${response.status}: ${message}`);
|
|
1700
|
+
}
|
|
1701
|
+
const json = await response.json();
|
|
1702
|
+
const data = json.data;
|
|
1703
|
+
if (!Array.isArray(data)) {
|
|
1704
|
+
throw new Error("Unexpected embedding response: missing data array");
|
|
1705
|
+
}
|
|
1706
|
+
return {
|
|
1707
|
+
embeddings: data.map((d) => isRecord2(d) && Array.isArray(d.embedding) ? d.embedding : []),
|
|
1708
|
+
model: pickString(json.model) ?? body.model,
|
|
1709
|
+
usage: pickUsage(json),
|
|
1710
|
+
raw: json
|
|
1711
|
+
};
|
|
1680
1712
|
}
|
|
1681
1713
|
};
|
|
1682
1714
|
}
|
|
@@ -2265,10 +2297,7 @@ function buildResponsesInput(request) {
|
|
|
2265
2297
|
return buildMessages(request);
|
|
2266
2298
|
}
|
|
2267
2299
|
function toOpenAIMessage(message) {
|
|
2268
|
-
return {
|
|
2269
|
-
role: message.role,
|
|
2270
|
-
content: message.content
|
|
2271
|
-
};
|
|
2300
|
+
return { ...message };
|
|
2272
2301
|
}
|
|
2273
2302
|
function toResponsesTools(tools) {
|
|
2274
2303
|
if (!Array.isArray(tools) || tools.length === 0) {
|
|
@@ -2736,6 +2765,9 @@ function createAnthropicCompatibleAdapter(options) {
|
|
|
2736
2765
|
const out = { text, usage, finishReason };
|
|
2737
2766
|
callbacks.onComplete?.(out);
|
|
2738
2767
|
return out;
|
|
2768
|
+
},
|
|
2769
|
+
async embed() {
|
|
2770
|
+
throw new Error("Anthropic does not provide a native embedding API. " + "Use the openai-compatible provider with Voyage AI (https://api.voyageai.com) — " + "Anthropic's recommended embedding solution, which uses the same request format.");
|
|
2739
2771
|
}
|
|
2740
2772
|
};
|
|
2741
2773
|
}
|
|
@@ -3014,6 +3046,23 @@ function toAnthropicInput(messages) {
|
|
|
3014
3046
|
continue;
|
|
3015
3047
|
}
|
|
3016
3048
|
sawNonSystem = true;
|
|
3049
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
3050
|
+
const parts = [];
|
|
3051
|
+
if (message.content)
|
|
3052
|
+
parts.push({ type: "text", text: message.content });
|
|
3053
|
+
for (const tc of message.tool_calls) {
|
|
3054
|
+
parts.push({ type: "tool_use", id: tc.id, name: tc.function.name, input: JSON.parse(tc.function.arguments) });
|
|
3055
|
+
}
|
|
3056
|
+
normalizedMessages.push({ role: "assistant", content: parts });
|
|
3057
|
+
continue;
|
|
3058
|
+
}
|
|
3059
|
+
if (message.role === "tool") {
|
|
3060
|
+
normalizedMessages.push({
|
|
3061
|
+
role: "user",
|
|
3062
|
+
content: [{ type: "tool_result", tool_use_id: message.tool_call_id, content: message.content }]
|
|
3063
|
+
});
|
|
3064
|
+
continue;
|
|
3065
|
+
}
|
|
3017
3066
|
normalizedMessages.push({
|
|
3018
3067
|
role: message.role,
|
|
3019
3068
|
content: message.content
|
|
@@ -4793,6 +4842,12 @@ function createLLM(config, registry = createDefaultProviderRegistry()) {
|
|
|
4793
4842
|
async structured(schema, prompt, options) {
|
|
4794
4843
|
const merged = mergeStructuredOptions(defaults, options);
|
|
4795
4844
|
return structured(adapter, schema, prompt, merged);
|
|
4845
|
+
},
|
|
4846
|
+
async embed(input, options = {}) {
|
|
4847
|
+
if (!adapter.embed) {
|
|
4848
|
+
throw new Error(`Provider "${adapter.provider ?? "unknown"}" does not support embeddings.`);
|
|
4849
|
+
}
|
|
4850
|
+
return adapter.embed({ ...options, input });
|
|
4796
4851
|
}
|
|
4797
4852
|
};
|
|
4798
4853
|
}
|
|
@@ -4950,6 +5005,38 @@ async function resizeImage(source, size, mimeType) {
|
|
|
4950
5005
|
const buf = await img.toFormat(sharpFormat).toBuffer();
|
|
4951
5006
|
return { base64: buf.toString("base64"), mimeType: outputMime };
|
|
4952
5007
|
}
|
|
5008
|
+
// src/conversation.ts
|
|
5009
|
+
function conversation(systemPrompt, entries) {
|
|
5010
|
+
return [
|
|
5011
|
+
{ role: "system", content: systemPrompt },
|
|
5012
|
+
...entries.map((entry) => {
|
|
5013
|
+
if (entry.role === "tool_call") {
|
|
5014
|
+
return {
|
|
5015
|
+
role: "assistant",
|
|
5016
|
+
content: "",
|
|
5017
|
+
tool_calls: [
|
|
5018
|
+
{
|
|
5019
|
+
id: entry.id,
|
|
5020
|
+
type: "function",
|
|
5021
|
+
function: { name: entry.name, arguments: JSON.stringify(entry.arguments ?? {}) }
|
|
5022
|
+
}
|
|
5023
|
+
]
|
|
5024
|
+
};
|
|
5025
|
+
}
|
|
5026
|
+
if (entry.role === "tool_result") {
|
|
5027
|
+
return {
|
|
5028
|
+
role: "tool",
|
|
5029
|
+
content: typeof entry.output === "string" ? entry.output : JSON.stringify(entry.output),
|
|
5030
|
+
tool_call_id: entry.id
|
|
5031
|
+
};
|
|
5032
|
+
}
|
|
5033
|
+
return {
|
|
5034
|
+
role: entry.role,
|
|
5035
|
+
content: entry.images && entry.images.length > 0 ? [{ type: "text", text: entry.text }, ...images(entry.images)] : entry.text
|
|
5036
|
+
};
|
|
5037
|
+
})
|
|
5038
|
+
];
|
|
5039
|
+
}
|
|
4953
5040
|
// src/prompt.ts
|
|
4954
5041
|
function toPromptString(value) {
|
|
4955
5042
|
if (value === null || value === undefined) {
|
|
@@ -5024,6 +5111,12 @@ class PromptMessageBuilderImpl {
|
|
|
5024
5111
|
return this.pushMessage("assistant", input, values);
|
|
5025
5112
|
}
|
|
5026
5113
|
pushMessage(role, input, values) {
|
|
5114
|
+
if (Array.isArray(input) && !isTemplateStringsArray(input)) {
|
|
5115
|
+
if (input.length > 0) {
|
|
5116
|
+
this.messages.push({ role, content: input });
|
|
5117
|
+
}
|
|
5118
|
+
return this;
|
|
5119
|
+
}
|
|
5027
5120
|
const message = toPromptMessage(input, values);
|
|
5028
5121
|
if (message.length > 0) {
|
|
5029
5122
|
this.messages.push({ role, content: message });
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { createLLM, type CreateLLMOptions, type LLMClient } from "./llm";
|
|
|
6
6
|
export { formatZodIssues, parseLLMOutput } from "./parse";
|
|
7
7
|
export { createMCPClient, wrapMCPClient, type CreateMCPClientOptions, type MCPClientInfo, type MCPInMemoryTransportConfig, type MCPStdioTransportConfig, type MCPStreamableHTTPTransportConfig, type MCPTransportConfig, type ManagedMCPToolClient, } from "./mcp";
|
|
8
8
|
export { images, resizeImage, type ImageInput, type ImageSize } from "./image";
|
|
9
|
+
export { conversation, type ConversationEntry } from "./conversation";
|
|
9
10
|
export { prompt, type PromptMessageBuilder } from "./prompt";
|
|
10
11
|
export { s, inspectSchemaMetadata, inferSchemaExample } from "./schema-builder";
|
|
11
12
|
export { buildDefaultStructuredPrompt, DEFAULT_LOOSE_PARSE_OPTIONS, DEFAULT_SELF_HEAL_BY_MODE, DEFAULT_SELF_HEAL_CONTEXT_LABEL, DEFAULT_SELF_HEAL_FIX_INSTRUCTION, DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS, DEFAULT_SELF_HEAL_NO_ISSUES_MESSAGE, DEFAULT_SELF_HEAL_PROTOCOL, DEFAULT_SELF_HEAL_RAW_OUTPUT_LABEL, DEFAULT_SELF_HEAL_RETURN_INSTRUCTION, DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS, DEFAULT_SELF_HEAL_VALIDATION_LABEL, DEFAULT_STRICT_PARSE_OPTIONS, DEFAULT_STRUCTURED_OBJECT_INSTRUCTION, DEFAULT_STRUCTURED_STYLE_INSTRUCTION, buildSelfHealPrompt, structured, StructuredParseError, type BuildDefaultStructuredPromptOptions, type SelfHealPromptTextOptions, } from "./structured";
|
|
@@ -13,4 +14,4 @@ export { createOpenAICompatibleAdapter, type OpenAICompatibleAdapterOptions, } f
|
|
|
13
14
|
export { createAnthropicCompatibleAdapter, DEFAULT_ANTHROPIC_MAX_TOKENS, DEFAULT_ANTHROPIC_VERSION, type AnthropicCompatibleAdapterOptions, } from "./providers/anthropic-compatible";
|
|
14
15
|
export { DEFAULT_MAX_TOOL_ROUNDS } from "./providers/mcp-runtime";
|
|
15
16
|
export { createDefaultProviderRegistry, createModelAdapter, createProviderRegistry, registerBuiltinProviders, type BuiltinProviderKind, type ModelAdapterConfig, type ProviderFactory, type ProviderRegistry, type ProviderTransportConfig, } from "./providers/registry";
|
|
16
|
-
export type { CandidateDiagnostics, LLMImageContent, LLMMessageContent, LLMTextContent, ExtractJsonCandidatesOptions, ExtractionCandidate, ExtractionHeuristicsOptions, ExtractionParseHint, HTTPHeaders, LLMAdapter, LLMMessage, LLMRequest, LLMResponse, LLMStreamCallbacks, LLMStreamChunk, LLMToolCall, LLMToolDebugOptions, LLMToolExecution, LLMToolOutputTransformer, LLMToolArgumentsTransformer, LLMToolChoice, MCPCallToolParams, MCPListToolsResult, MCPToolClient, MCPToolDescriptor, MCPToolSchema, LLMUsage, MarkdownCodeBlock, MarkdownCodeOptions, ParseLLMOutputOptions, ParseLLMOutputResult, ParseTraceEvent, PipelineError, StructuredAttempt, StructuredCallOptions, StructuredDebugOptions, StructuredError, StructuredMode, StructuredOptions, StructuredPromptBuilder, StructuredPromptContext, StructuredPromptPayload, StructuredPromptResolver, StructuredPromptValue, StructuredResult, StructuredStreamData, StructuredStreamEvent, StructuredStreamInput, StructuredStreamOptions, StructuredSelfHealInput, StructuredTimeoutOptions, ThinkDiagnostics, ThinkBlock, StructuredTraceEvent, } from "./types";
|
|
17
|
+
export type { CandidateDiagnostics, EmbeddingRequest, EmbeddingResult, LLMImageContent, LLMMessageContent, LLMTextContent, ExtractJsonCandidatesOptions, ExtractionCandidate, ExtractionHeuristicsOptions, ExtractionParseHint, HTTPHeaders, LLMAdapter, LLMMessage, LLMRequest, LLMResponse, LLMStreamCallbacks, LLMStreamChunk, LLMToolCall, LLMToolCallRef, LLMToolDebugOptions, LLMToolExecution, LLMToolOutputTransformer, LLMToolArgumentsTransformer, LLMToolChoice, MCPCallToolParams, MCPListToolsResult, MCPToolClient, MCPToolDescriptor, MCPToolSchema, LLMUsage, MarkdownCodeBlock, MarkdownCodeOptions, ParseLLMOutputOptions, ParseLLMOutputResult, ParseTraceEvent, PipelineError, StructuredAttempt, StructuredCallOptions, StructuredDebugOptions, StructuredError, StructuredMode, StructuredOptions, StructuredPromptBuilder, StructuredPromptContext, StructuredPromptPayload, StructuredPromptResolver, StructuredPromptValue, StructuredResult, StructuredStreamData, StructuredStreamEvent, StructuredStreamInput, StructuredStreamOptions, StructuredSelfHealInput, StructuredTimeoutOptions, ThinkDiagnostics, ThinkBlock, StructuredTraceEvent, } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -1517,6 +1517,7 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1517
1517
|
const fetcher = options.fetcher ?? fetch;
|
|
1518
1518
|
const path = options.path ?? "/v1/chat/completions";
|
|
1519
1519
|
const responsesPath = options.responsesPath ?? "/v1/responses";
|
|
1520
|
+
const embeddingPath = options.embeddingPath ?? "/v1/embeddings";
|
|
1520
1521
|
return {
|
|
1521
1522
|
provider: "openai-compatible",
|
|
1522
1523
|
model: options.model,
|
|
@@ -1589,6 +1590,36 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1589
1590
|
const out = { text, usage, finishReason };
|
|
1590
1591
|
callbacks.onComplete?.(out);
|
|
1591
1592
|
return out;
|
|
1593
|
+
},
|
|
1594
|
+
async embed(request) {
|
|
1595
|
+
const body = cleanUndefined({
|
|
1596
|
+
...options.defaultBody,
|
|
1597
|
+
...request.body,
|
|
1598
|
+
model: request.model ?? options.model,
|
|
1599
|
+
input: request.input,
|
|
1600
|
+
dimensions: request.dimensions,
|
|
1601
|
+
encoding_format: "float"
|
|
1602
|
+
});
|
|
1603
|
+
const response = await fetcher(buildURL(options.baseURL, embeddingPath), {
|
|
1604
|
+
method: "POST",
|
|
1605
|
+
headers: buildHeaders(options),
|
|
1606
|
+
body: JSON.stringify(body)
|
|
1607
|
+
});
|
|
1608
|
+
if (!response.ok) {
|
|
1609
|
+
const message = await response.text();
|
|
1610
|
+
throw new Error(`HTTP ${response.status}: ${message}`);
|
|
1611
|
+
}
|
|
1612
|
+
const json = await response.json();
|
|
1613
|
+
const data = json.data;
|
|
1614
|
+
if (!Array.isArray(data)) {
|
|
1615
|
+
throw new Error("Unexpected embedding response: missing data array");
|
|
1616
|
+
}
|
|
1617
|
+
return {
|
|
1618
|
+
embeddings: data.map((d) => isRecord2(d) && Array.isArray(d.embedding) ? d.embedding : []),
|
|
1619
|
+
model: pickString(json.model) ?? body.model,
|
|
1620
|
+
usage: pickUsage(json),
|
|
1621
|
+
raw: json
|
|
1622
|
+
};
|
|
1592
1623
|
}
|
|
1593
1624
|
};
|
|
1594
1625
|
}
|
|
@@ -2177,10 +2208,7 @@ function buildResponsesInput(request) {
|
|
|
2177
2208
|
return buildMessages(request);
|
|
2178
2209
|
}
|
|
2179
2210
|
function toOpenAIMessage(message) {
|
|
2180
|
-
return {
|
|
2181
|
-
role: message.role,
|
|
2182
|
-
content: message.content
|
|
2183
|
-
};
|
|
2211
|
+
return { ...message };
|
|
2184
2212
|
}
|
|
2185
2213
|
function toResponsesTools(tools) {
|
|
2186
2214
|
if (!Array.isArray(tools) || tools.length === 0) {
|
|
@@ -2648,6 +2676,9 @@ function createAnthropicCompatibleAdapter(options) {
|
|
|
2648
2676
|
const out = { text, usage, finishReason };
|
|
2649
2677
|
callbacks.onComplete?.(out);
|
|
2650
2678
|
return out;
|
|
2679
|
+
},
|
|
2680
|
+
async embed() {
|
|
2681
|
+
throw new Error("Anthropic does not provide a native embedding API. " + "Use the openai-compatible provider with Voyage AI (https://api.voyageai.com) — " + "Anthropic's recommended embedding solution, which uses the same request format.");
|
|
2651
2682
|
}
|
|
2652
2683
|
};
|
|
2653
2684
|
}
|
|
@@ -2926,6 +2957,23 @@ function toAnthropicInput(messages) {
|
|
|
2926
2957
|
continue;
|
|
2927
2958
|
}
|
|
2928
2959
|
sawNonSystem = true;
|
|
2960
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
2961
|
+
const parts = [];
|
|
2962
|
+
if (message.content)
|
|
2963
|
+
parts.push({ type: "text", text: message.content });
|
|
2964
|
+
for (const tc of message.tool_calls) {
|
|
2965
|
+
parts.push({ type: "tool_use", id: tc.id, name: tc.function.name, input: JSON.parse(tc.function.arguments) });
|
|
2966
|
+
}
|
|
2967
|
+
normalizedMessages.push({ role: "assistant", content: parts });
|
|
2968
|
+
continue;
|
|
2969
|
+
}
|
|
2970
|
+
if (message.role === "tool") {
|
|
2971
|
+
normalizedMessages.push({
|
|
2972
|
+
role: "user",
|
|
2973
|
+
content: [{ type: "tool_result", tool_use_id: message.tool_call_id, content: message.content }]
|
|
2974
|
+
});
|
|
2975
|
+
continue;
|
|
2976
|
+
}
|
|
2929
2977
|
normalizedMessages.push({
|
|
2930
2978
|
role: message.role,
|
|
2931
2979
|
content: message.content
|
|
@@ -4705,6 +4753,12 @@ function createLLM(config, registry = createDefaultProviderRegistry()) {
|
|
|
4705
4753
|
async structured(schema, prompt, options) {
|
|
4706
4754
|
const merged = mergeStructuredOptions(defaults, options);
|
|
4707
4755
|
return structured(adapter, schema, prompt, merged);
|
|
4756
|
+
},
|
|
4757
|
+
async embed(input, options = {}) {
|
|
4758
|
+
if (!adapter.embed) {
|
|
4759
|
+
throw new Error(`Provider "${adapter.provider ?? "unknown"}" does not support embeddings.`);
|
|
4760
|
+
}
|
|
4761
|
+
return adapter.embed({ ...options, input });
|
|
4708
4762
|
}
|
|
4709
4763
|
};
|
|
4710
4764
|
}
|
|
@@ -4866,6 +4920,38 @@ async function resizeImage(source, size, mimeType) {
|
|
|
4866
4920
|
const buf = await img.toFormat(sharpFormat).toBuffer();
|
|
4867
4921
|
return { base64: buf.toString("base64"), mimeType: outputMime };
|
|
4868
4922
|
}
|
|
4923
|
+
// src/conversation.ts
|
|
4924
|
+
function conversation(systemPrompt, entries) {
|
|
4925
|
+
return [
|
|
4926
|
+
{ role: "system", content: systemPrompt },
|
|
4927
|
+
...entries.map((entry) => {
|
|
4928
|
+
if (entry.role === "tool_call") {
|
|
4929
|
+
return {
|
|
4930
|
+
role: "assistant",
|
|
4931
|
+
content: "",
|
|
4932
|
+
tool_calls: [
|
|
4933
|
+
{
|
|
4934
|
+
id: entry.id,
|
|
4935
|
+
type: "function",
|
|
4936
|
+
function: { name: entry.name, arguments: JSON.stringify(entry.arguments ?? {}) }
|
|
4937
|
+
}
|
|
4938
|
+
]
|
|
4939
|
+
};
|
|
4940
|
+
}
|
|
4941
|
+
if (entry.role === "tool_result") {
|
|
4942
|
+
return {
|
|
4943
|
+
role: "tool",
|
|
4944
|
+
content: typeof entry.output === "string" ? entry.output : JSON.stringify(entry.output),
|
|
4945
|
+
tool_call_id: entry.id
|
|
4946
|
+
};
|
|
4947
|
+
}
|
|
4948
|
+
return {
|
|
4949
|
+
role: entry.role,
|
|
4950
|
+
content: entry.images && entry.images.length > 0 ? [{ type: "text", text: entry.text }, ...images(entry.images)] : entry.text
|
|
4951
|
+
};
|
|
4952
|
+
})
|
|
4953
|
+
];
|
|
4954
|
+
}
|
|
4869
4955
|
// src/prompt.ts
|
|
4870
4956
|
function toPromptString(value) {
|
|
4871
4957
|
if (value === null || value === undefined) {
|
|
@@ -4940,6 +5026,12 @@ class PromptMessageBuilderImpl {
|
|
|
4940
5026
|
return this.pushMessage("assistant", input, values);
|
|
4941
5027
|
}
|
|
4942
5028
|
pushMessage(role, input, values) {
|
|
5029
|
+
if (Array.isArray(input) && !isTemplateStringsArray(input)) {
|
|
5030
|
+
if (input.length > 0) {
|
|
5031
|
+
this.messages.push({ role, content: input });
|
|
5032
|
+
}
|
|
5033
|
+
return this;
|
|
5034
|
+
}
|
|
4943
5035
|
const message = toPromptMessage(input, values);
|
|
4944
5036
|
if (message.length > 0) {
|
|
4945
5037
|
this.messages.push({ role, content: message });
|
|
@@ -5170,6 +5262,7 @@ export {
|
|
|
5170
5262
|
createLLM,
|
|
5171
5263
|
createDefaultProviderRegistry,
|
|
5172
5264
|
createAnthropicCompatibleAdapter,
|
|
5265
|
+
conversation,
|
|
5173
5266
|
buildSelfHealPrompt,
|
|
5174
5267
|
buildDefaultStructuredPrompt,
|
|
5175
5268
|
StructuredParseError,
|
package/dist/llm.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
2
|
import { type ModelAdapterConfig, type ProviderRegistry } from "./providers/registry";
|
|
3
|
-
import type { LLMAdapter, StructuredCallOptions, StructuredPromptBuilder, StructuredResult } from "./types";
|
|
3
|
+
import type { EmbeddingRequest, EmbeddingResult, LLMAdapter, StructuredCallOptions, StructuredPromptBuilder, StructuredResult } from "./types";
|
|
4
4
|
export interface CreateLLMOptions extends ModelAdapterConfig {
|
|
5
5
|
defaults?: StructuredCallOptions<z.ZodTypeAny>;
|
|
6
6
|
}
|
|
@@ -9,5 +9,6 @@ export interface LLMClient {
|
|
|
9
9
|
provider?: string;
|
|
10
10
|
model?: string;
|
|
11
11
|
structured<TSchema extends z.ZodTypeAny>(schema: TSchema, prompt: StructuredPromptBuilder, options?: StructuredCallOptions<TSchema>): Promise<StructuredResult<z.infer<TSchema>>>;
|
|
12
|
+
embed(input: string | string[], options?: Omit<EmbeddingRequest, "input">): Promise<EmbeddingResult>;
|
|
12
13
|
}
|
|
13
14
|
export declare function createLLM(config: CreateLLMOptions, registry?: ProviderRegistry): LLMClient;
|
package/dist/prompt.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type { StructuredPromptPayload, StructuredPromptResolver } from "./types";
|
|
1
|
+
import type { LLMMessageContent, StructuredPromptPayload, StructuredPromptResolver } from "./types";
|
|
2
2
|
export interface PromptMessageBuilder extends StructuredPromptResolver {
|
|
3
3
|
system(input: string): PromptMessageBuilder;
|
|
4
4
|
system(strings: TemplateStringsArray, ...values: unknown[]): PromptMessageBuilder;
|
|
5
5
|
user(input: string): PromptMessageBuilder;
|
|
6
6
|
user(strings: TemplateStringsArray, ...values: unknown[]): PromptMessageBuilder;
|
|
7
|
+
user(content: LLMMessageContent): PromptMessageBuilder;
|
|
7
8
|
assistant(input: string): PromptMessageBuilder;
|
|
8
9
|
assistant(strings: TemplateStringsArray, ...values: unknown[]): PromptMessageBuilder;
|
|
10
|
+
assistant(content: LLMMessageContent): PromptMessageBuilder;
|
|
9
11
|
build(): StructuredPromptPayload;
|
|
10
12
|
}
|
|
11
13
|
export declare function prompt(strings: TemplateStringsArray, ...values: unknown[]): string;
|
package/dist/types.d.ts
CHANGED
|
@@ -130,9 +130,18 @@ export interface LLMImageContent {
|
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
export type LLMMessageContent = string | (LLMTextContent | LLMImageContent)[];
|
|
133
|
+
export interface LLMToolCallRef {
|
|
134
|
+
id: string;
|
|
135
|
+
type: "function";
|
|
136
|
+
function: {
|
|
137
|
+
name: string;
|
|
138
|
+
arguments: string;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
133
141
|
export interface LLMMessage {
|
|
134
142
|
role: "system" | "user" | "assistant" | "tool";
|
|
135
143
|
content: LLMMessageContent;
|
|
144
|
+
[key: string]: unknown;
|
|
136
145
|
}
|
|
137
146
|
export interface LLMRequest {
|
|
138
147
|
prompt?: string;
|
|
@@ -179,11 +188,24 @@ export interface LLMStreamCallbacks {
|
|
|
179
188
|
onChunk?: (chunk: LLMStreamChunk) => void;
|
|
180
189
|
onComplete?: (response: LLMResponse) => void;
|
|
181
190
|
}
|
|
191
|
+
export interface EmbeddingRequest {
|
|
192
|
+
input: string | string[];
|
|
193
|
+
model?: string;
|
|
194
|
+
dimensions?: number;
|
|
195
|
+
body?: Record<string, unknown>;
|
|
196
|
+
}
|
|
197
|
+
export interface EmbeddingResult {
|
|
198
|
+
embeddings: number[][];
|
|
199
|
+
model: string;
|
|
200
|
+
usage?: LLMUsage;
|
|
201
|
+
raw?: unknown;
|
|
202
|
+
}
|
|
182
203
|
export interface LLMAdapter {
|
|
183
204
|
provider?: string;
|
|
184
205
|
model?: string;
|
|
185
206
|
complete(request: LLMRequest): Promise<LLMResponse>;
|
|
186
207
|
stream?(request: LLMRequest, callbacks?: LLMStreamCallbacks): Promise<LLMResponse>;
|
|
208
|
+
embed?(request: EmbeddingRequest): Promise<EmbeddingResult>;
|
|
187
209
|
}
|
|
188
210
|
export interface LLMToolCall {
|
|
189
211
|
id: string;
|