如何用 AI SDK 的 wrapLanguageModel 和 Language Model Middleware 拦截修改模型调用 如何用 AI SDK 的 wrapLanguageModel 和 Language Model Middleware 拦截修改模型调用【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai在用 AI SDK 调用语言模型时generateText和streamText的参数在每次调用处都要手动处理想加日志、缓存、RAG 注入或结果过滤时只能散落在各个调用点。AI SDK 提供的 Language Model Middleware 机制可以把这类逻辑集中在模型层用wrapLanguageModel函数把一个模型和一个或多个middleware 组合成一个新的语言模型所有后续调用都会经过 middleware 的拦截。本文按「实现 middleware → 应用到模型 → 验证拦截生效」的顺序演示这条路径内容基于 AI SDK 的 Language Model Middleware 文档。准备条件一个 TypeScript 项目代码从ai包导入wrapLanguageModel等函数一个 provider 包用于创建模型实例下文示例使用ai-sdk/openai请替换为你实际使用的 provider 和模型对应 provider 的 API key仓库示例通过dotenv/config加载环境变量可参考 Local Caching Middleware 示例。middleware 的三个拦截点自定义 middleware 是一个LanguageModelV4Middleware类型的对象从ai-sdk/provider导入可以按需实现以下三个函数中的任意一个类型定义见 language-model-v4-middleware.ts函数作用transformParams在参数传给模型前转换它们对doGenerate和doStream都生效接收{ type: generate \| stream, params, model }返回转换后的参数wrapGenerate包装doGenerate可以修改参数、调用模型、再修改结果wrapStream包装doStream与wrapGenerate对应处理流式调用的参数、调用和返回的 streammiddleware 对象按当前规范声明时带有specificationVersion: v4字段仓库中的 本地缓存 middleware 示例展示了带该字段的写法。文档提醒实现自定义 middleware 属于进阶功能需要熟悉 LanguageModelV4 规范。用 wrapLanguageModel 把 middleware 应用到模型wrapLanguageModel接收一个模型和 middleware返回一个集成了 middleware 的新语言模型用法和普通模型完全一致见 API 参考import { openai } from ai-sdk/openai; import { wrapLanguageModel, streamText } from ai; const wrappedLanguageModel wrapLanguageModel({ model: openai(gpt-4o), // 文档中写作 yourModel替换为你的 provider 模型实例 middleware: yourLanguageModelMiddleware, }); const result streamText({ model: wrappedLanguageModel, prompt: What cities are in the United States?, });middleware参数也可以是数组。多个 middleware 按提供顺序应用第一个先转换输入最后一个直接包裹在模型外层const wrappedLanguageModel wrapLanguageModel({ model: yourModel, middleware: [firstMiddleware, secondMiddleware], }); // applied as: firstMiddleware(secondMiddleware(yourModel))此外还有两个可选参数modelId和providerId用于覆盖原模型的 ID 和 provider 标识。验证拦截一个完整的日志 middleware文档给出的 Logging 示例同时实现了wrapGenerate和wrapStream是最直接的验证方式如果 middleware 生效控制台会按调用过程输出参数和生成文本。import type { LanguageModelV4Middleware, LanguageModelV4StreamPart, } from ai-sdk/provider; export const yourLogMiddleware: LanguageModelV4Middleware { wrapGenerate: async ({ doGenerate, params }) { console.log(doGenerate called); console.log(params: ${JSON.stringify(params, null, 2)}); const result await doGenerate(); const generatedText result.content .filter(part part.type text) .map(part part.text) .join(); console.log(doGenerate finished); console.log(generated text: ${generatedText}); return result; }, wrapStream: async ({ doStream, params }) { console.log(doStream called); console.log(params: ${JSON.stringify(params, null, 2)}); const { stream, ...rest } await doStream(); let generatedText ; const textBlocks new Mapstring, string(); const transformStream new TransformStream LanguageModelV4StreamPart, LanguageModelV4StreamPart ({ transform(chunk, controller) { switch (chunk.type) { case text-start: { textBlocks.set(chunk.id, ); break; } case text-delta: { const existing textBlocks.get(chunk.id) || ; textBlocks.set(chunk.id, existing chunk.delta); generatedText chunk.delta; break; } case text-end: { console.log( Text block ${chunk.id} completed:, textBlocks.get(chunk.id), ); break; } } controller.enqueue(chunk); }, flush() { console.log(doStream finished); console.log(generated text: ${generatedText}); }, }); return { stream: stream.pipeThrough(transformStream), ...rest, }; }, };把它应用到模型上并发起调用import { openai } from ai-sdk/openai; import { generateText, wrapLanguageModel } from ai; import { yourLogMiddleware } from ./your-log-middleware; const result await generateText({ model: wrapLanguageModel({ model: openai(gpt-4o), middleware: yourLogMiddleware, }), prompt: What cities are in the United States?, });如何判断生效generateText走wrapGenerate分支日志依次出现doGenerate called、格式化的请求参数、doGenerate finished和完整生成文本改用streamText则走wrapStream分支每完成一个文本块打印一条Text block ... completed:流结束时在flush中打印doStream finished和汇总文本。注意wrapStream里必须用controller.enqueue(chunk)把 chunk 原样转发否则流会被中断。拦截修改请求参数transformParams 与按请求传元数据transformParams适合在参数到达模型前做注入或改写。一个实用场景是通过providerOptions为每次调用附带自定义元数据如用户 ID、时间戳供日志类 middleware 读取import { openai } from ai-sdk/openai; import { generateText, wrapLanguageModel } from ai; import type { LanguageModelV4Middleware } from ai-sdk/provider; export const yourLogMiddleware: LanguageModelV4Middleware { wrapGenerate: async ({ doGenerate, params }) { console.log(METADATA, params?.providerMetadata?.yourLogMiddleware); const result await doGenerate(); return result; }, }; const { text } await generateText({ model: wrapLanguageModel({ model: openai(gpt-4o), middleware: yourLogMiddleware, }), prompt: Invent a new holiday and describe its traditions., providerOptions: { yourLogMiddleware: { hello: world, }, }, }); console.log(text);文档示例中的 provider 导入和模型占位符在这里以openai(gpt-4o)展开替换成你自己的模型即可。验证方式控制台会打印METADATA后跟该次调用传入的元数据对象text照常返回生成结果。拦截修改响应guardrails 示例反过来middleware 也可以在模型返回后修改结果。文档给出的 guardrails 示例在wrapGenerate中对文本内容做过滤此处为示例逻辑把敏感词替换为REDACTEDimport type { LanguageModelV4Middleware } from ai-sdk/provider; export const yourGuardrailMiddleware: LanguageModelV4Middleware { wrapGenerate: async ({ doGenerate }) { const result await doGenerate(); // filtering approach, e.g. for PII or other sensitive information: const content result.content.map(part part.type text ? { ...part, text: part.text.replace(/badword/g, REDACTED) } : part, ); return { ...result, content }; }, };缓存是文档给出的另一个完整示例在wrapGenerate中用JSON.stringify(params)作为缓存键命中则直接返回缓存的LanguageModelV4GenerateResult未命中则调用doGenerate()并把结果写入缓存。验证方式是同一组参数第二次调用直接命中缓存、不再触发模型请求文档示例只实现了wrapGenerate流式缓存需要自行实现。如果需要基于文件、且同时支持流式的本地缓存仓库提供了完整的 Local Caching Middleware cookbook它通过wrapStream记录每个 chunk命中缓存时用simulateReadableStream以每 10ms 一个 chunk 的速度重放流。内置 middleware无需自行实现的拦截能力如果目标行为已有内置实现可以直接传入wrapLanguageModel不需要自己写函数。内置 middleware 包括extractReasoningMiddleware从生成文本中提取推理信息并暴露为结果的reasoning属性选项tagName指定标签名startWithReasoning: true时会在生成文本前补上推理标签适用于回复开头不含推理标签的模型更多细节见 DeepSeek R1 指南extractJsonMiddleware剥离模型把 JSON 包在 markdown 代码围栏里的输出使其兼容Output.object()可用transform传入自定义转换函数处理其他格式simulateStreamingMiddleware对只返回完整响应的模型模拟流式行为defaultSettingsMiddleware为模型应用默认设置如temperature: 0.5、maxOutputTokens: 800、providerOptionsdefaultInstructionsMiddleware为不含 system message 的调用应用默认指令调用上直接提供的指令优先于默认值instructions也可传SystemModelMessage或数组addToolInputExamplesMiddleware把工具的inputExamples序列化进工具描述供不支持inputExamples属性的 provider 使用选项有prefix默认Input Examples:、format默认JSON.stringify(example.input)、remove默认true添加后移除原属性。限制与注意事项文档明确说明 Logging、Caching、RAG、Guardrails 等自定义示例仅用于展示写法不是生产实现其中 RAG 示例用到的getLastUserMessageText、findSources等辅助函数不属于 AI SDK。流式 guardrails 难以实现在流结束前无法知道完整内容文档示例因此只实现了wrapGenerate侧。defaultInstructionsMiddleware把归一化 prompt 中已有的任意 system message 视为调用级指令不会追加默认值allowSystemInMessages只对受信任的消息历史开启因为历史中的 system message 会覆盖这些默认值。文件缓存方案cookbook只面向本地开发而非生产环境想要新响应时删除缓存文件即可失效缓存且缓存路径应加入.gitignore。使用stopWhen的多步流程中缓存发生在单个模型响应级别工具调用不会被缓存每次都会执行。下一步wrapLanguageModel完整参数API 参考各内置 middleware 的参数细节simulateStreamingMiddleware、defaultInstructionsMiddleware、addToolInputExamplesMiddleware、extractJsonMiddlewaremiddleware 机制总览Language Model Middleware。【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考