Token导航 LogoToken导航TokenDH.com
extrait (Tterrasson) logo
运维云端未说明官方级别未说明来源级核验

extrait (Tterrasson)

MCP Server

提供高级LLM文本生成和结构化JSON提取功能,支持验证、修复和流式处理。

工具数

0

提示词数

0

GitHub Stars

1

资源数

0
TypeScriptClaude结构化数据Claude

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

作者 / 组织

tterrasson

提供方

tterrasson

最后核验

2026/5/17 20:19

快速接入

先看主来源和安装命令,再打开仓库或文档;下面只保留这个条目的关键接入事实。

详细介绍

摘录

高级LLM文本生成和结构化JSON提取,包括验证、修复和流式传输。

特性

  • 从LLM响应中提取多候选JSON
  • 使用jsonrepair自动修复
  • Zod模式验证和强制
  • 验证失败的可选自我修复
  • 流媒体支持
  • MCP工具
  • 矢量嵌入(兼容OpenAI+Voyage AI)

安装

安装 extrait 使用您首选的包管理器。

bun add extrait
# or
npm install extrait
# or
deno add npm:extrait

快速开始

使用自定义的OpenAI兼容传输来指向 extrait 在本地端点。

import { createLLM, prompt, s } from "extrait";
import { z } from "zod";

const llm = createLLM({
  provider: "openai-compatible",
  model: "mistralai/ministral-3-3b",
  transport: {
    baseURL: "http://localhost:1234/v1",
    apiKey: process.env.LLM_API_KEY ?? "local-demo-key",
  },
});

const RecipeSchema = s.schema(
  "Recipe",
  z.object({
    title: s.string().min(1).describe("Short recipe title"),
    ingredients: s.array(s.string()).min(1).describe("Ingredient list"),
  })
);

const result = await llm.structured(
  RecipeSchema,
  prompt`Extract a simple recipe from this text: """${text}"""`
);

console.log(result.data);

示例一览

这些示例涵盖了存储库中最常见的使用模式。

bun run dev simple "Bun.js runtime"
bun run dev generate "Bun.js runtime"
bun run dev streaming
bun run dev calculator-tool

API 参考

以下部分介绍了图书馆的主要组成部分。

创建LLM客户端

使用 createLLM() 配置提供者、模型、传输和客户端默认值。

const llm = createLLM({
  provider: "openai-compatible" | "anthropic-compatible",
  model: "gpt-5-nano",
  baseURL: "https://api.openai.com",       // optional alias for transport.baseURL
  apiKey: process.env.LLM_API_KEY,         // optional alias for transport.apiKey
  transport: {
    baseURL: "https://api.openai.com",     // optional
    apiKey: process.env.LLM_API_KEY,       // optional
    path: "/v1/chat/completions",          // optional; anthropic-compatible usually uses /v1/messages
    headers: { "x-trace-id": "docs-demo" }, // optional extra headers
    defaultBody: { user: "docs-demo" },    // optional provider body defaults
    version: "2023-06-01",                 // anthropic-compatible only
    fetcher: fetch,                        // optional custom fetch implementation
  },
  defaults: {
    mode: "loose" | "strict",             // loose allows repair
    selfHeal: 1,                          // optional retry attempts
    debug: false,                         // optional structured debug output
    // or:
    // debug: { enabled: true, verbose: true },
    systemPrompt: "You are a helpful assistant.",
    timeout: {
      request: 30_000,
      tool: 10_000,
    },
  },
});

baseURLapiKey 在最高层,有缩写别名 transport.baseURLtransport.apiKey对于特定请求的选项,例如 stream, request, schemaInstruction,以及解析调优,请参阅下面的部分。

常见的设置模式:

// OpenAI-compatible gateway or local endpoint with top-level aliases
const llm = createLLM({
  provider: "openai-compatible",
  model: "gpt-4o-mini",
  baseURL: process.env.LLM_BASE_URL ?? "http://localhost:1234/v1",
  apiKey: process.env.LLM_API_KEY ?? "local-demo-key",
});

// Anthropic-compatible endpoint with explicit API version
const anthropic = createLLM({
  provider: "anthropic-compatible",
  model: "claude-3-5-sonnet-latest",
  transport: {
    baseURL: "https://api.anthropic.com",
    apiKey: process.env.LLM_API_KEY,
    version: "2023-06-01",
  },
});

定义模式

使用 s Zod的包装器,用于模式名称、描述和更符合人体工程学的创作流程。

import { s } from "extrait";
import { z } from "zod";

const Schema = s.schema(
  "SchemaName",
  z.object({
    // String fields
    text: s.string().min(1).describe("Field description"),
    optional: s.string().optional(),
    withDefault: s.string().default("value"),

    // Numbers
    count: s.number().int().min(0).max(100),
    score: s.number().min(0).max(1),

    // Arrays
    items: s.array(s.string()).min(1).max(10),

    // Nested objects
    nested: z.object({
      field: s.string(),
    }),

    // Enums (use native Zod)
    category: z.enum(["a", "b", "c"]),

    // Booleans
    flag: s.boolean(),
  })
);

进行结构化通话

structured() 接受模式加上标记的提示、流畅的提示构建器或原始消息有效负载。

// Simple prompt
const result = await llm.structured(
  Schema,
  prompt`Your prompt with ${variables}`
);

// Multi-part prompt
const result = await llm.structured(
  Schema,
  prompt()
    .system`You are an expert assistant.`
    .user`Analyze: """${input}"""`
);

// Multi-turn conversation
const conversationResult = await llm.structured(
  Schema,
  prompt()
    .system`You are an expert assistant.`
    .user`Hello`
    .assistant`Hi, how can I help?`
    .user`Analyze: """${input}"""`
);

// With options
const result = await llm.structured(
  Schema,
  prompt`Your prompt`,
  {
    mode: "loose",
    selfHeal: 1,
    debug: true,
    systemPrompt: "You are a helpful assistant.",
    stream: {
      to: "stdout",
      onData: (event) => {
        if (event.delta.text) {
          console.log("New visible text:", event.delta.text);
        }
        if (event.delta.reasoning) {
          console.log("New reasoning text:", event.delta.reasoning);
        }

        console.log("Current visible text:", event.snapshot.text);
        console.log("Current reasoning:", event.snapshot.reasoning);
        console.log("Current structured snapshot:", event.snapshot.data);

        if (event.done) {
          console.log("Streaming done.");
        }
      },
    },
    request: {
      signal: AbortSignal.timeout(30_000),  // optional AbortSignal
      reasoningEffort: "medium",            // optional reasoning effort hint
    },
    timeout: {
      request: 30_000,  // ms per LLM HTTP request
      tool: 10_000,     // ms per MCP tool call
    },
  }
);

prompt() 构建有序 messages 有效载荷。使用 ` prompt... 用于单个字符串提示,或用于多回合对话的流畅构建器。这 LLMMessage` 如果需要键入自己的消息数组,则导出类型。

stream.onData,事件分为两层:

  • event.delta.text 仅是自上次事件以来新接收到的可见文本。
  • event.delta.reasoning 只是自上次事件以来新收到的推理文本。
  • event.snapshot.text 是迄今为止积累的完整可见文本。
  • event.snapshot.reasoning 是迄今为止积累的完全规范化推理。
  • event.snapshot.data 是迄今为止可以从流中解析的结构最好的JSON快照。它可能保持不变,同时 event.delta.text 继续增长。

典型用法是:

  • 渲染 event.delta.text 直接连接到终端或聊天UI
  • 可选渲染 event.delta.reasoning 在单独的推理小组中
  • 使用 event.snapshot.data 驱动部分结构化UI状态
  • 使用 event.snapshot.text / event.snapshot.reasoning 当您需要完整的累积状态,而不仅仅是最新的增量时

您还可以通过以下方式传递提供者请求选项 request:

const result = await llm.structured(
  Schema,
  prompt`Summarize this document: """${text}"""`,
  {
    request: {
      temperature: 0,
      maxTokens: 800,
      body: { user: "demo-user" },
    },
  }
);

拨打短信

generate() 是非结构化生成的高级API。它接受与相同的提示形状 structured(),但不注入任何模式或解析输出。

// Simple prompt
const result = await llm.generate(
  prompt`Write a short summary of ${topic}.`
);

// Multi-message prompt
const result = await llm.generate(
  prompt()
    .system`You are a concise assistant.`
    .user`Summarize: """${text}"""`
);

// Raw messages payload
const result = await llm.generate({
  prompt: {
    messages: [
      { role: "user", content: "Say hello in one sentence." },
    ],
  },
});

流媒体镜像 structured(),但快照仅包含 textreasoning:

const result = await llm.generate(
  prompt`Explain ${topic} in one short paragraph.`,
  {
    stream: {
      enabled: true,
      onData: (event) => {
        process.stdout.write(event.delta.text);

        console.log("Full text so far:", event.snapshot.text);
        console.log("Full reasoning so far:", event.snapshot.reasoning);

        if (event.done) {
          console.log("Streaming done.");
        }
      },
    },
  }
);

提供商请求选项和MCP工具仍在进行中 request:

const result = await llm.generate(
  prompt`Use tools if needed and answer the user clearly.`,
  {
    request: {
      temperature: 0,
      maxTokens: 800,
      reasoningEffort: "medium",
      mcpClients: [calculatorMCP],
      maxToolRounds: 10,
    },
  }
);

openai-compatible,此内容以如下方式发送 reasoning_effort,与 max 映射到 xhigh.On anthropic-compatible,此内容以如下方式发送 output_config.effort 并自动启用 thinking: { type: "adaptive" }.

对于现有的历史记录或多回合对话,请通过 messages 直接:

const messages = conversation("You are a helpful assistant.", [
  { role: "user", text: "What is the speed of light?" },
  { role: "assistant", text: "Approximately 299,792 km/s in a vacuum." },
  { role: "user", text: "How long does light take to reach Earth from the Sun?" },
]);

const result = await llm.generate({ prompt: { messages } });

使用 llm.adapter.complete(...)llm.adapter.stream(...) 仅当您需要原始低级提供者接口时。

图像(多模式)

使用 images() 为支持视觉的模型构建base64图像内容块。

import { images, prompt } from "extrait";
import { readFileSync } from "fs";

const base64 = readFileSync("photo.png").toString("base64");
const img = { base64, mimeType: "image/png" };

// With prompt() builder — pass LLMMessageContent array to .user() or .assistant()
const result = await llm.structured(Schema,
  prompt()
    .system`You are a vision assistant.`
    .user([{ type: "text", text: "Describe this image." }, ...images(img)])
);

// With raw messages array
const result = await llm.structured(Schema, {
  messages: [
    {
      role: "user",
      content: [
        { type: "text", text: "Describe this image." },
        ...images(img),
      ],
    },
  ],
});

// Multiple images
const content = [
  { type: "text", text: "Compare these two images." },
  ...images([
    { base64: base64A, mimeType: "image/png" },
    { base64: base64B, mimeType: "image/jpeg" },
  ]),
];

images() 接受单个 { base64, mimeType } 对象或数组,并始终返回 LLMImageContent[] 它直接传播到内容数组中。

对话(多回合历史)

使用 conversation() 建造a LLMMessage[] 从现有的对话历史中。这是将之前的轮次转到法学硕士的惯用方式。

import { conversation } from "extrait";

const messages = conversation("You are a helpful assistant.", [
  { role: "user",      text: "What is the speed of light?" },
  { role: "assistant", text: "Approximately 299,792 km/s in a vacuum." },
  { role: "user",      text: "How long does light take to reach Earth from the Sun?" },
]);

// High-level text generation
const response = await llm.generate({ prompt: { messages } });

// Or to structured extraction
const result = await llm.structured(Schema, { messages });

条目与 images 自动生成多模式内容:

const messages = conversation("You are a vision assistant.", [
  {
    role: "user",
    text: "What is in this image?",
    images: [{ base64, mimeType: "image/png" }],
  },
]);

结果对象

成功的 generate() 调用返回规范化的文本/推理以及请求元数据:

{
  text: string,
  reasoning: string,
  attempts: GenerateAttempt[],
  usage?: {
    inputTokens?: number,
    outputTokens?: number,
    totalTokens?: number,
    cost?: number,
  },
  finishReason?: string,
}

attempts 条目包括:

{
  attempt: number,
  via: "complete" | "stream",
  text: string,
  reasoning: string,
  usage?: LLMUsage,
  finishReason?: string,
}

成功的 structured() 调用返回经过验证的数据以及规范化的文本/推理和跟踪元数据。

{
  data: T,                      // Validated data matching schema
  text: string,                 // Visible model text, without inline  blocks
  reasoning: string,            // Normalized reasoning across dedicated fields and inline 
  json: unknown | null,         // Parsed JSON before validation
  attempts: StructuredAttempt[], // One entry per parse / self-heal attempt
  usage?: {
    inputTokens?: number,
    outputTokens?: number,
    totalTokens?: number,
    cost?: number,
  },
  finishReason?: string,        // e.g., "stop"
}

attempts 条目包括:

{
  attempt: number,
  selfHeal: boolean,
  via: "complete" | "stream",
  text: string,
  reasoning: string,
  json: unknown | null,
  candidates: string[],
  repairLog: string[],
  zodIssues: z.ZodIssue[],
  success: boolean,
  usage?: LLMUsage,
  finishReason?: string,
  parsed: ParseLLMOutputResult,
}

传统内联 ... 块仍然受支持,但高级 structured() API现在将它们折叠为 reasoning 而不是在内部公开块元数据。

错误处理

抓住 StructuredParseError 当修复和验证仍然失败时。

import { StructuredParseError } from "extrait";

try {
  const result = await llm.structured(Schema, prompt`...`);
} catch (error) {
  if (error instanceof StructuredParseError) {
    console.error("Validation failed");
    console.error("Attempt:", error.attempt);
    console.error("Zod issues:", error.zodIssues);
    console.error("Repair log:", error.repairLog);
    console.error("Candidates:", error.candidates);
  }
}

嵌入

使用生成向量嵌入 llm.embed()它总是会回来的 number[][] --每个输入字符串一个向量。

// Create a dedicated embedder client (recommended)
const embedder = createLLM({
  provider: "openai-compatible",
  model: "text-embedding-3-small",
  transport: { apiKey: process.env.LLM_API_KEY },
});

// Single string
const { embeddings, model, usage } = await embedder.embed("Hello world");
const vector: number[] = embeddings[0];

// Multiple strings in one request
const { embeddings } = await embedder.embed(["text one", "text two", "text three"]);
// embeddings[0], embeddings[1], embeddings[2] — one vector each

// Optional: override model or request extra options per call
const { embeddings } = await embedder.embed("Hello", {
  model: "text-embedding-ada-002",
  dimensions: 512,              // supported by text-embedding-3-* models
  body: { user: "user-id" },    // pass-through to provider
});

结果形状:

{
  embeddings: number[][];  // one vector per input
  model: string;
  usage?: { inputTokens?: number; totalTokens?: number };
  raw?: unknown;           // full provider response
}

人类学/航海AI

Anthropic不提供原生嵌入API。他们推荐的解决方案是 Voyage AI,它使用相同的OpenAI兼容格式:

const embedder = createLLM({
  provider: "openai-compatible",
  model: "voyage-3",
  transport: {
    baseURL: "https://api.voyageai.com",
    apiKey: process.env.LLM_API_KEY,
  },
});

const { embeddings } = await embedder.embed(["query", "document"]);

召唤 llm.embed() 在一个 anthropic-compatible 适配器抛出一个指向Voyage AI的描述性错误。

MCP工具

在请求时连接MCP客户端,以便模型在结构化生成过程中调用工具。

import { createMCPClient } from "extrait";

const mcpClient = await createMCPClient({
  id: "calculator",
  transport: {
    type: "stdio",
    command: "bun",
    args: ["run", "examples/calculator-mcp-server.ts"],
  },
});

const result = await llm.structured(
  Schema,
  prompt`Calculate 14 + 8`,
  {
    request: {
      mcpClients: [mcpClient],
      maxToolRounds: 5,
      toolDebug: {
        enabled: true,
        includeRequest: true,
        includeResult: true,
      },
      onToolExecution: (execution) => {
        console.log(execution.name, execution.durationMs);
      },
      // Optional: transform tool output before it is sent back to the LLM
      transformToolOutput: (output, execution) => {
        return { ...output, source: execution.name };
      },
      // Optional: transform tool arguments before the tool is called
      transformToolArguments: (args, call) => args,
      // Optional: transform the full MCP call payload, including _meta
      transformToolCallParams: (params, call) => ({
        ...params,
        _meta: {
          source: "extrait-docs",
          clientId: call.clientId,
        },
      }),
      // Optional: custom error message when an unknown tool is called
      unknownToolError: (toolName) => `Tool "${toolName}" is not available.`,
    },
  }
);

await mcpClient.close?.();

transformToolArguments() 仅接收工具输入对象。 transformToolCallParams() 运行它并接收完整 MCPCallToolParams 将发送到MCP客户端的有效载荷:

type MCPCallToolParams = {
  name: string;
  arguments?: Record;
  _meta?: Record;
};

使用 transformToolCallParams() 当您需要附加MCP特定的元数据时,覆盖最终的远程工具名称,或以其他方式更改传递给的完整请求 client.callTool()。此挂钩出口时为 LLMToolCallParamsTransformer.

超时

使用 timeout 无需管理即可设置每个请求和每个工具调用的时间限制 AbortSignal 手动。

const result = await llm.structured(Schema, prompt`...`, {
  timeout: {
    request: 30_000,  // abort the LLM HTTP request after 30s
    tool: 5_000,      // abort each MCP tool call after 5s
  },
});

这两个字段都是可选的。 timeout.request 创建一个 AbortSignal.timeout 内部;如果你也通过,它将被忽略 request.signal (您的信号优先)。 timeout.tool 透明地封装每个MCP客户端。

您还可以在客户端设置默认值:

const llm = createLLM({
  provider: "openai-compatible",
  model: "gpt-5-nano",
  transport: { apiKey: process.env.LLM_API_KEY },
  defaults: {
    timeout: { request: 60_000 },
  },
});

示例

使用以下命令运行存储库示例 bun run dev .

可用示例:

  • generate -高级文本生成(generate.ts)
  • streaming -真正的LLM流媒体+快照自检(stream.ts)
  • streaming-with-tools -使用MCP工具进行实时文本流+自检(streaming-with-tools.ts)
  • streaming-turns-with-tools -流式MCP转换、转换和推理块(streaming-turns-with-tols.ts)
  • abort-signal -启动一代,然后快速取消 AbortSignal (abort-signal.ts)
  • timeout -通过设置每个请求和每个工具的超时 timeout 选项(超时.ts)
  • simple -带流媒体的基本结构化输出(simple.ts)
  • sentiment-analysis -枚举验证,严格模式(情感分析)
  • data-extraction -复杂的嵌套模式,自我修复(数据输出)
  • multi-step-reasoning -链式结构化呼叫(多步推理)
  • calculator-tool -MCP工具集成(计算器tool.ts)
  • image-analysis -从图像文件中提取多模态结构(图像分析)
  • conversation -多回合对话历史和内联图像消息(对话.ts)
  • simulated-tools -在没有实际执行的情况下,将虚假的工具调用/结果注入到对话上下文中(模拟tools.ts)
  • embeddings -向量嵌入、余弦相似度和语义比较(嵌入s.ts)

在示例名称后传递参数:

bun run dev generate "Why Bun is fast"
bun run dev streaming
bun run dev streaming-with-tools
bun run dev abort-signal 120 "JSON cancellation demo"
bun run dev timeout 5000
bun run dev simple "Bun.js runtime"
bun run dev sentiment-analysis "I love this product."
bun run dev multi-step-reasoning "Why is the sky blue?"
bun run dev embeddings "the cat sat on the mat" "a feline rested on the rug"

环境变量

这些环境变量在示例和常见客户端设置中使用。

  • LLM_PROVIDER - openai-compatibleanthropic-compatible
  • LLM_BASE_URL -API端点(可选)
  • LLM_MODEL -型号名称(默认值: gpt-5-nano)
  • LLM_API_KEY -提供程序的API密钥
  • STRUCTURED_DEBUG=1 -启用调试输出

默认情况下,结构化调试打印 text (公共可见输出)和 reasoning (标准化推理)。 parseSource (使用的内部来源 解析和自愈)仅在以下情况下打印 debug.verbose 已启用。

测试

与Bun一起运行测试套件。

bun run test

目录标签

目录标签

TypeScriptClaude结构化数据LLM文本生成本地部署JSON提取流式处理多模态

支持客户端

Claude

接入字段

传输方式(transport,传输协议)

未说明

鉴权方式(authType,认证方式)

none

工具数量(toolCount,工具数)

0

资源数量(resourceCount,资源数)

0

提示词数量(promptCount,提示词数)

0

权限和风险

未说明none部署方式未说明

接入前请确认传输方式、认证方式和部署位置,并根据实际工具能力限制访问范围。

安装前确认

不要直接授予不必要的文件、网络或账号权限;先核对安装命令和配置内容。

仍需确认:installCommand

来源信息

继续浏览同类 MCP