2026/10/16 6:03:35

AIRI 的 xsAI 实战:generateText、streamText 与 Tool Loop 实践指南

AIRI 的 xsAI 实战:generateText、streamText 与 Tool Loop 实践指南 人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手【免费下载链接】airi 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器目标是接近 Neuro-sama 的高度支持实时语音聊天、Minecraft 和 Factorio 游玩支持 Web / macOS / Windows。项目地址https://gitcode.com/moeru-ai/airi点击查看免费下载本文围绕 AIRI 仓库中的 xsAI 参考文档 text-stream-tools.md 展开系统讲解 xsAI 文本 API 的共用心智模型、generateText/streamText的返回结构与事件体系以及基于stopWhen的工具循环Tool Loop。读完本文你将掌握如何在 AIRI 这类项目中选型 xsAI 文本 API、消费流式事件并落地一个轻量 Agent 循环同时可以从 core-agent 的源码中看到这些概念在真实产品中的完整调用链。一、共用心智模型每次调用只需要四样东西xsAI 的绝大多数文本 API 共享同一套调用骨架参考文档将其总结为apiKeybaseURLmodelmessages可选的fetch、headers、abortSignal对于聊天类 API还可以预期使用这些通用选项选项作用temperature控制输出的随机性越低越确定topP核采样nucleus sampling参数stop停止序列seed随机种子toolChoice工具选择指令如requiredtools工具定义列表stopWhen控制多轮工具循环何时停止其中temperature与topP的语义在 AIRI 源码中有明确注释temperature越低输出越确定、聚焦越高越有创造性top_p为 0.1 时表示只考虑累积概率质量前 10% 的 token见 StreamOptions 定义。几个在 AIRI 中被反复强调的实践约束来自 xsAI 技能文档 SKILL.md 的 Key constraints 一节baseURL和model在实际调用中几乎都是必需的示例中应显式给出apiKey是否必需取决于提供方托管服务通常需要本地或代理端点可能不需要。Node.js 中优先用process.env浏览器中优先读localStorage不要硬编码密钥streamText()立即返回调用方需要异步消费textStream、fullStream与结果 Promise。从依赖锁定看AIRI 通过 pnpm catalog 将 xsAI 生态统一钉在 0.5.1 版本包括xsai/generate-text、xsai/stream-text、xsai/tool、xsai/shared-chat、xsai-ext/responses等见 pnpm-workspace.yaml。核心 Agent 运行时 core-agent 的依赖只引入了所需的细粒度包xsai/generate-text、xsai/stream-text、xsai/tool等而不是伞形xsai包——这正是 xsAI “保持小体积”设计取向的直接体现。二、generateText一次调用拿到最终结果当你只需要“一个最终结果”时使用generateText。参考文档给出的结果形状包含text—— 最终文本finishReason—— 结束原因usage—— token 用量messages—— 会话消息steps—— 多步循环中的步骤toolCalls/toolResults—— 工具调用与结果reasoningText—— 推理文本最简的调用形态来自仓库内置的规范示例 recipes.mdimport { env } from node:process import { generateText } from xsai/generate-text const { text } await generateText({ apiKey: env.OPENAI_API_KEY!, baseURL: https://api.openai.com/v1/, messages: [ { content: You are a helpful assistant., role: system, }, { content: Write one sentence about the moon., role: user, }, ], model: gpt-4o, })该示例被明确定位为“简单脚本、测试、一次性辅助功能”的默认起点。仓库中的真实用例Provider 连通性探测在 AIRI 里generateText并不只用于生成对话内容。provider-inference包的 OpenAI 兼容校验器把它当作“最小真实请求”来探测用户配置的baseURLapiKeymodel是否可用见 createOpenAICompatibleValidatorsawait generateText({ apiKey: config.apiKey, baseURL: config.baseUrl!, headers: additionalHeaders, model: normalizedModel, messages: message.messages(message.user(ping)), // 部分 OpenAI 兼容服务方拒绝小于 16 的输出限制 // 因此探测统一使用 16 作为上限 ...(options?.chatCompletionTokenParameter max_completion_tokens ? { max_completion_tokens: 16 } : { max_tokens: 16 }), })这里有两个值得注意的细节探测请求刻意压小输出max_tokens: 16注释说明部分 OpenAI 兼容服务方拒绝低于 16 的输出上限16 是兼顾成本与兼容性的安全值错误分类捕获到400状态码时校验器认为“连通成功但聊天失败”可能是模型名错误网络错误则判定连通失败见 错误处理分支。这正是文档推荐规则“优先用generateText写简单示例、脚本与测试”在工程上的延伸——测试与校验路径用同步等待的 API主交互路径才走流式。三、streamText立即返回事件驱动streamText面向需要增量输出或工具事件的场景。它的关键行为是立即返回并把结果拆成两类流异步迭代器textStream—— 纯文本增量fullStream—— 全量事件流reasoningTextStream—— 推理增量结果 Promisemessages、steps、usage、totalUsagefullStream中可能出现的事件类型包括text-deltareasoning-deltatool-call-streaming-starttool-call-deltatool-calltool-resultfinisherror消费策略上文档给出的建议是只需要实时文本就用textStream需要工具事件、推理增量或 finish 元数据时用fullStream。带工具的规范示例如下recipes.mdimport { env } from node:process import { streamText } from xsai/stream-text import { stepCountAtLeast } from xsai/stream-text/shared-chat import { tool } from xsai/tool import * as v from valibot const add await tool({ description: Adds two numbers, execute: ({ a, b }) (Number.parseInt(a) Number.parseInt(b)).toString(), name: add, parameters: v.object({ a: v.pipe(v.string(), v.description(First number)), b: v.pipe(v.string(), v.description(Second number)), }), }) const { fullStream } streamText({ apiKey: env.OPENAI_API_KEY!, baseURL: https://api.openai.com/v1/, messages: [ { content: You are a helpful assistant., role: system, }, { content: What is 12 plus 30? Use the add tool., role: user, }, ], model: gpt-4o, stopWhen: stepCountAtLeast(2), toolChoice: required, tools: [add], }) const text: string[] [] for await (const event of fullStream) { if (event.type text-delta) { text.push(event.text) } if (event.type tool-call || event.type tool-result) { console.log(event) } } console.log(text.join())AIRI 如何消费 streamTextstreamChatCompletionsAIRI 的主聊天链路并没有直接for await迭代fullStream而是通过onEvent回调消费 xsAI 的内部事件再映射到自己的事件契约。核心实现在 streamChatCompletionsconst requestOptions: Parameterstypeof streamText[0] { ...input.config, prepareStep: ({ input: current, steps }) { /* 见下一节 */ }, abortSignal: input.options?.abortSignal, temperature: input.options?.temperature, topP: input.options?.topP, messages, headers: mergeRequestHeaders(input.config.headers, input.options?.headers), streamOptions: { includeUsage: true }, stopWhen: stepCountAtLeast(10), tools: input.tools, toolChoice: input.options?.resolveStep ? undefined : input.options?.toolChoice, onEvent: async (event) { const mapped toAiriStreamEvent(event) if (mapped) await input.onEvent(mapped) }, } const result streamText(requestOptions)几个与文档一一对应、但只有看源码才能看到的细节stopWhen: stepCountAtLeast(10)AIRI 把默认循环上限设为 10 步stepCountAtLeast直接从xsai/shared-chat导入见 导入语句。streamOptions: { includeUsage: true }显式要求提供方在流中回传用量供上层totalUsagePromise 汇总。事件映射层toAiriStreamEvent 把 xsAI 事件如text.delta、reasoning.delta、tool-call.done、tool-result.done、error映射为 AIRI 自己的StreamEventtext-delta、reasoning-delta、tool-call、tool-result、tool-error、error并把与 UI 无关的text.start、step.done、tool-call.delta等直接过滤为null。该文件的注释还指出 xsAI 0.5 版本用isError: true标记失败的tool-result.done而不中断流因此 AIRI 可以直接区分tool-error与tool-result。结果 Promise 的等待时机streamOnce 明确注释了steps在所有工具轮次结束后才 settle而提供方的 finish 事件可能更早到达因此统一以steps作为“整轮结束”的权威信号并对usage、totalUsage、generatedTurn分别挂 rejection 兜底避免 xsAI 产生未处理的 Promise rejection。可以推断这种“onEvent回调 显式等待steps”的组合是为了让上层语音播报、UI 渲染能拿到逐 delta 的低延迟输出同时仍有一个确定性的结算点。四、Tool LoopstopWhen、stepCountAtLeast 与 prepareStep参考文档对工具循环的要点stopWhen以显式谓词开启重复工具循环默认循环边界使用stepCountAtLeast(n)条件更复杂时可用and()、or()、not()组合xsAI 还提供hasToolCall()这类内置谓词见 SKILL.md每一步会把 assistant 输出和工具结果追加进消息再按需发起下一次 API 调用当必须让模型调用工具时使用toolChoice: required。上一节的add示例正是“toolChoice: requiredstepCountAtLeast(2)”的组合第一轮强制模型调用add第二轮拿到工具结果后生成答案。AIRI 源码中的循环控制prepareStepxsAI 0.5 的每步钩子prepareStep只返回input、model、toolChoice三个字段。AIRI 利用这一点实现了“每步动态解析提供方配置”——包括每步重新读取角色设定、切换模型、注入 system prompt、以及在不支持 content 数组的提供方上强制把消息压平为字符串见 prepareStep 实现。源码中的注释直接说明了这一约束的来源与解除条件xsAI 0.5 prepareStep returns only input, model, and toolChoice. It reads other options afterward but snapshots toolChoice beforehand.也就是说toolChoice在步骤切换前被快照AIRI 只能通过在prepareStep中变更外层requestOptions对象Object.assign替换apiKey、fetch、temperature、topP、tools等来让后续步骤生效并刻意把toolChoice置为undefined再按步回填。这是一个典型的“库能力边界 调用方适配”的工程案例。跨协议工具循环Responses 适配器对于走 OpenAI Responses 协议的提供方AIRI 使用xsai-ext/responses扩展包的responses()但循环控制语义与streamText完全一致同样是stopWhen: stepCountAtLeast(10)同样用prepareStep逐步解析配置见 streamResponses。两者的差异主要在事件面Responses 适配器额外通过onNativeEvent消费原生事件把web_search_call的状态变化转成 AIRI 的search事件、把output_text的 annotations 转成citations事件见 onNativeEvent 处理。值得注意的是onNativeEvent与web_search工具类型并非xsai-ext/responses上游默认提供的能力——AIRI 通过补丁 patches/xsai-ext__responses0.5.1.patch 给该包增加了WebSearchTool、WebSearchCall类型、onNativeEvent回调并放宽了 error 事件的构造逻辑。补丁在 pnpm-workspace.yaml 中注册这解释了为什么同一份“streamText tools 的心智模型”在两条协议路径上都能成立。循环之外的容错瞬态重试与协议切换工具循环真正跑在生产环境时还要处理两类问题AIRI 的实现位于 llm-service.ts瞬态失败重试408、429、5xx被视为可重试错误退避序列为[3_000, 6_000, 12_000]ms并尊重服务端的Retry-After上限 30 秒但仅在尚未向消费者发出任何流事件时才允许重试——因为一旦文本开始渲染/播报或工具已执行重复请求会造成副作用重复见 transientRetry 与 streamWithTransientRetry。协议/提供方中途切换prepareStep里如果发现下一步应走不同协议如从 chat-completions 切到 responses会抛出RequestSwitch携带已完成的“部分轮次”外层streamFrom把它接住把已完成轮次并入会话后以新提供方继续并设有 10 次切换、10 个轮次的硬上限防止死循环见 streamFrom。五、选型规则什么时候用哪个 API汇总参考文档的 Recommendation rules并结合 AIRI 的实际分工场景推荐 APIAIRI 中的落点简单示例、脚本、测试、一次性辅助generateTextProvider 连通性/能力探测openai-compatible.tsUI、实时输出、Agent 式流程、事件驱动集成streamText主聊天链路chat-completions.ts轻量 Agent 循环不引入完整 Agent 框架streamTexttoolsstopWhencore-agent 的streamFrom整条编排Responses 协议提供方xsai-ext/responses的responses()responses.ts其中“streamText 工具 轻量 Agent 循环”这一点在 AIRI 中体现得最为彻底整个core-agent没有依赖任何重型 Agent 框架一个助手轮次AssistantTurn由“N 次模型调用 工具执行 步骤投影”组成循环边界由stepCountAtLeast(10)控制事件面由toAiriStreamEvent统一收敛用量由totalUsage汇总。六、小结xsAI 的文本 API 体系可以用一句话概括同一个apiKey baseURL model messages心智模型下generateText负责“拿结果”streamText负责“拿过程”stopWhen负责“拿多少轮”。AIRI 仓库为这套模型提供了完整的工程注脚校验与测试路径用generateText发最小探测请求max_tokens: 16主聊天路径用streamText的onEvent回调做低延迟事件下发以stepsPromise 作为整轮结算点工具循环统一以stepCountAtLeast为默认边界配合toolChoice: required强制首轮调用prepareStep的字段限制催生了每步变更请求配置的适配模式并衍生出跨协议RequestSwitch与瞬态重试两类生产级容错。如果你在 OpenAI 兼容端点之上构建自己的伴侣/助手类产品可以直接复用仓库中的这套调用链作为参考实现chat-completions.ts、responses.ts、xsai-events.ts 与 llm-service.ts 共同覆盖了从事件映射、循环控制到错误恢复的完整面。赞分享人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手【免费下载链接】airi 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器目标是接近 Neuro-sama 的高度支持实时语音聊天、Minecraft 和 Factorio 游玩支持 Web / macOS / Windows。项目地址https://gitcode.com/moeru-ai/airi点击查看免费下载相关推荐xsAI 文本流与工具循环airi 仓库中 generateText、streamText 与 Tool Loop 实战参考xsAI 文本流与工具循环airi 仓库中 generateText、streamText 与 Tool Loop 实战参考 本篇基于 airi 仓库内 xs人工智能AI 应用大模型AI Agent交互助手语音数字人后端前端桌面应用移动开发GUI 自动化MCP 服务AIRI 的 xsAI 包选型指南细粒度 xsai/* 包与 xsai 伞包如何取舍AIRI 的 xsAI 包选型指南细粒度 xsai/ 包与 xsai 伞包如何取舍 AIRI 的 LLM、语音、工具调用等能力构建在 xsAI 这套 Ope人工智能AI 应用大模型AI Agent交互助手语音数字人后端前端桌面应用移动开发GUI 自动化MCP 服务AI SDK 入门实战使用 TypeScript 的 generateText 与 streamText 构建 AI 文本应用AI SDK 入门实战使用 TypeScript 的 generateText 与 streamText 构建 AI 文本应用 本文是一份基于 AI SDK人工智能大模型AI 应用AI Agent工具调用LLM 网关MCP Clients上一篇LinkSwift九大网盘直链下载助手告别限速实现高速下载自由下一篇你的手写笔记还在用纸笔Obsidian手写插件让你告别纸质束缚创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考