extrait 0.5.2 → 0.5.3

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 CHANGED
@@ -175,19 +175,27 @@ const result = await llm.structured(
175
175
  Use `images()` to build base64 image content blocks for vision-capable models.
176
176
 
177
177
  ```typescript
178
- import { images } from "extrait";
178
+ import { images, prompt } from "extrait";
179
179
  import { readFileSync } from "fs";
180
180
 
181
181
  const base64 = readFileSync("photo.png").toString("base64");
182
+ const img = { base64, mimeType: "image/png" };
182
183
 
183
- // Single image
184
+ // With prompt() builder — pass LLMMessageContent array to .user() or .assistant()
185
+ const result = await llm.structured(Schema,
186
+ prompt()
187
+ .system`You are a vision assistant.`
188
+ .user([{ type: "text", text: "Describe this image." }, ...images(img)])
189
+ );
190
+
191
+ // With raw messages array
184
192
  const result = await llm.structured(Schema, {
185
193
  messages: [
186
194
  {
187
195
  role: "user",
188
196
  content: [
189
197
  { type: "text", text: "Describe this image." },
190
- ...images({ base64, mimeType: "image/png" }),
198
+ ...images(img),
191
199
  ],
192
200
  },
193
201
  ],
@@ -205,6 +213,38 @@ const content = [
205
213
 
206
214
  `images()` accepts a single `{ base64, mimeType }` object or an array, and always returns an `LLMImageContent[]` that spreads directly into a content array.
207
215
 
216
+ ### Conversations (multi-turn history)
217
+
218
+ Use `conversation()` to build a `LLMMessage[]` from an existing conversation history. This is the idiomatic way to pass prior turns to the LLM.
219
+
220
+ ```typescript
221
+ import { conversation } from "extrait";
222
+
223
+ const messages = conversation("You are a helpful assistant.", [
224
+ { role: "user", text: "What is the speed of light?" },
225
+ { role: "assistant", text: "Approximately 299,792 km/s in a vacuum." },
226
+ { role: "user", text: "How long does light take to reach Earth from the Sun?" },
227
+ ]);
228
+
229
+ // Pass to adapter directly
230
+ const response = await llm.adapter.complete({ messages });
231
+
232
+ // Or to structured extraction
233
+ const result = await llm.structured(Schema, { messages });
234
+ ```
235
+
236
+ Entries with `images` produce multimodal content automatically:
237
+
238
+ ```typescript
239
+ const messages = conversation("You are a vision assistant.", [
240
+ {
241
+ role: "user",
242
+ text: "What is in this image?",
243
+ images: [{ base64, mimeType: "image/png" }],
244
+ },
245
+ ]);
246
+ ```
247
+
208
248
  ### Result Object
209
249
 
210
250
  ```typescript
@@ -329,6 +369,7 @@ Available examples:
329
369
  - `multi-step-reasoning` - Chained structured calls ([multi-step-reasoning.ts](examples/multi-step-reasoning.ts))
330
370
  - `calculator-tool` - MCP tool integration ([calculator-tool.ts](examples/calculator-tool.ts))
331
371
  - `image-analysis` - Multimodal structured extraction from an image file ([image-analysis.ts](examples/image-analysis.ts))
372
+ - `conversation` - Multi-turn conversation history and inline image messages ([conversation.ts](examples/conversation.ts))
332
373
 
333
374
  Pass arguments after the example name:
334
375
  ```bash
@@ -0,0 +1,8 @@
1
+ import { type ImageInput } from "./image";
2
+ import type { LLMMessage } from "./types";
3
+ export interface ConversationEntry {
4
+ role: "user" | "assistant";
5
+ text: string;
6
+ images?: ImageInput[];
7
+ }
8
+ 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,
@@ -4950,6 +4951,16 @@ async function resizeImage(source, size, mimeType) {
4950
4951
  const buf = await img.toFormat(sharpFormat).toBuffer();
4951
4952
  return { base64: buf.toString("base64"), mimeType: outputMime };
4952
4953
  }
4954
+ // src/conversation.ts
4955
+ function conversation(systemPrompt, entries) {
4956
+ return [
4957
+ { role: "system", content: systemPrompt },
4958
+ ...entries.map((entry) => ({
4959
+ role: entry.role,
4960
+ content: entry.images && entry.images.length > 0 ? [{ type: "text", text: entry.text }, ...images(entry.images)] : entry.text
4961
+ }))
4962
+ ];
4963
+ }
4953
4964
  // src/prompt.ts
4954
4965
  function toPromptString(value) {
4955
4966
  if (value === null || value === undefined) {
@@ -5024,6 +5035,12 @@ class PromptMessageBuilderImpl {
5024
5035
  return this.pushMessage("assistant", input, values);
5025
5036
  }
5026
5037
  pushMessage(role, input, values) {
5038
+ if (Array.isArray(input) && !isTemplateStringsArray(input)) {
5039
+ if (input.length > 0) {
5040
+ this.messages.push({ role, content: input });
5041
+ }
5042
+ return this;
5043
+ }
5027
5044
  const message = toPromptMessage(input, values);
5028
5045
  if (message.length > 0) {
5029
5046
  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";
package/dist/index.js CHANGED
@@ -4866,6 +4866,16 @@ async function resizeImage(source, size, mimeType) {
4866
4866
  const buf = await img.toFormat(sharpFormat).toBuffer();
4867
4867
  return { base64: buf.toString("base64"), mimeType: outputMime };
4868
4868
  }
4869
+ // src/conversation.ts
4870
+ function conversation(systemPrompt, entries) {
4871
+ return [
4872
+ { role: "system", content: systemPrompt },
4873
+ ...entries.map((entry) => ({
4874
+ role: entry.role,
4875
+ content: entry.images && entry.images.length > 0 ? [{ type: "text", text: entry.text }, ...images(entry.images)] : entry.text
4876
+ }))
4877
+ ];
4878
+ }
4869
4879
  // src/prompt.ts
4870
4880
  function toPromptString(value) {
4871
4881
  if (value === null || value === undefined) {
@@ -4940,6 +4950,12 @@ class PromptMessageBuilderImpl {
4940
4950
  return this.pushMessage("assistant", input, values);
4941
4951
  }
4942
4952
  pushMessage(role, input, values) {
4953
+ if (Array.isArray(input) && !isTemplateStringsArray(input)) {
4954
+ if (input.length > 0) {
4955
+ this.messages.push({ role, content: input });
4956
+ }
4957
+ return this;
4958
+ }
4943
4959
  const message = toPromptMessage(input, values);
4944
4960
  if (message.length > 0) {
4945
4961
  this.messages.push({ role, content: message });
@@ -5170,6 +5186,7 @@ export {
5170
5186
  createLLM,
5171
5187
  createDefaultProviderRegistry,
5172
5188
  createAnthropicCompatibleAdapter,
5189
+ conversation,
5173
5190
  buildSelfHealPrompt,
5174
5191
  buildDefaultStructuredPrompt,
5175
5192
  StructuredParseError,
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "extrait",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/tterrasson/extrait.git"