使用 Hono 构建 AI SDK 流式 API 服务器:从文本流到 Agent 集成的完整实战
【免费下载链接】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
导读
本文基于 examples/hono 示例,讲解如何在 Hono 服务器中接入 AI SDK,实现文本生成、UI 消息流式传输、自定义数据流注入以及基于 Agent 的联网搜索对话。读完本文,你将掌握 AI SDK 各流式响应工具(createTextStreamResponse、createUIMessageStreamResponse、toUIMessageStream、createUIMessageStream、createAgentUIStreamResponse)的用途与底层原理,并能独立搭建一个可供useChat等前端 UI 直接消费的流式 API 服务。
示例概览:一个 Hono 驱动的 AI 流式服务器
AI SDK 本身是框架无关的 TypeScript 库,可以运行在任意 Web 框架之上。本示例选择 Hono——一个轻量、快速、跨平台的 TypeScript Web 框架——配合@hono/node-server在 Node.js 中启动服务,展示了两种典型的流式输出模式:
- 纯文本流:直接返回
text/plain的流式文本,适合命令行或简单消费端; - UI 消息流:返回结构化消息块(start、text-delta、data-custom、finish 等),与 AI SDK 的前端
useChat等 hook 天然对接,适合浏览器交互应用。
示例的完整目录结构如下:
examples/hono/ ├── src/ │ ├── server.ts # Hono 应用入口,5 个路由端点 │ └── openai-web-search-agent.ts # 基于 ToolLoopAgent 的联网搜索 Agent ├── package.json ├── tsconfig.json └── README.md其中 server.ts 是服务端核心,openai-web-search-agent.ts 定义了/chat端点使用的 Agent。
环境准备与启动步骤
1. 配置环境变量
在示例目录(或仓库根目录)创建.env文件,至少写入你所用 Provider 的密钥。若使用 OpenAI,内容如下:
OPENAI_API_KEY="YOUR_OPENAI_API_KEY"server.ts 中通过import 'dotenv/config'加载环境变量,因此除了 OpenAI,你也可以按需增加其他 Provider 的密钥配置。
2. 安装依赖并构建
示例通过 pnpm workspace 引用仓库内的ai与@ai-sdk/openai源码包(见 tsconfig.json 中的references配置),因此需要在 AI SDK 仓库根目录依次执行:
pnpm install pnpm build3. 启动开发服务器
在仓库根目录运行:
pnpm dev该命令对应 package.json 中的脚本tsx watch src/server.ts,借助tsx直接运行 TypeScript 源码并开启文件监听(热重载)。启动后服务监听在http://localhost:8080。
4. 用 Curl 验证端点
curl -i -X POST http://localhost:8080/text该命令返回 HTTP 状态行、响应头以及流式输出的文本内容。你也可以直接运行pnpm curl(package.json 中已预置该脚本)达到同样效果。
核心端点逐个拆解
server.ts 共定义了 5 个路由,覆盖了 AI SDK 在服务端的主要流式用法。
根路径/:UI 消息流的基本形态
app.post('/', async c => { const result = streamText({ model: openai('gpt-4o'), prompt: 'Invent a new holiday and describe its traditions.', }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); });这里streamText立即返回一个结果对象(不会阻塞等待完整生成),其.stream属性是TextStreamPart类型的流;toUIMessageStream将其转换为UIMessageChunk流,createUIMessageStreamResponse再包装为 HTTPResponse。这种"立即返回、边生成边推送"的模式是 AI SDK 流式编程的核心思想。
/text:纯文本流
app.post('/text', async c => { const result = streamText({ model: openai('gpt-4o'), prompt: 'Write a short poem about coding.', }); return createTextStreamResponse({ stream: toTextStream({ stream: result.stream }), }); });toTextStream从完整流中提取纯文本增量,createTextStreamResponse将其编码为 UTF-8 分块发送,并设置Content-Type: text/plain; charset=utf-8。查看源码 create-text-stream-response.ts 可以看到其实现非常简洁:通过stream.pipeThrough(new TextEncoderStream())作为 Response body,同时支持自定义status、statusText与headers参数。这是最简单、与任何 HTTP 客户端都兼容的流式输出方式。
/stream-data:手动编排 UI 消息流
const stream = createUIMessageStream({ execute: ({ writer }) => { writer.write({ type: 'start' }); writer.write({ type: 'data-custom', data: { custom: 'Hello, world!' }, }); const result = streamText({ model: openai('gpt-4o'), prompt: 'Invent a new holiday and describe its traditions.', }); writer.merge( toUIMessageStream({ stream: result.stream, sendStart: false, onError: error => { // Error messages are masked by default for security reasons. // 若需向客户端暴露具体错误信息,可在此返回 error.message return error instanceof Error ? error.message : String(error); }, }), ); }, }); return createUIMessageStreamResponse({ stream });createUIMessageStream是比直接转换更底层的工具:它接受一个execute({ writer })回调,由你决定何时写入什么消息块。writer.write用于手动推送自定义块(如type: 'start'、type: 'data-custom'),writer.merge则可将另一条流(如streamText转换后的 UI 消息流)合并进来。
从源码 create-ui-message-stream.ts 可以确认几个关键设计:
- 返回的是一个
ReadableStream<InferUIMessageChunk>,并在内部维护所有ongoingStreamPromises,即使execute已返回,只要还有合并中的流未结束,就不会提前关闭输出流; onError的默认值是() => 'An error occurred.',刻意避免把服务端错误细节泄露给客户端;示例中通过自定义onError展示了如何按需暴露error.message;- 支持
originalMessages(传入后进入持久化模式并为响应消息提供 ID)、onStepEnd(多步 Agent 运行中每步结束时的回调,可用于持久化中间消息)以及generateId等参数。
值得留意的是sendStart: false的用法:因为外层已经手动写过type: 'start'块,转换流就不再重复发送 start 块,避免重复消息。
/chat:Agent 驱动的对话流
app.post('/chat', async c => { const { messages } = await c.req.json(); return createAgentUIStreamResponse({ agent: openaiWebSearchAgent, uiMessages: messages, }); });该端点接收前端传来的messages(即useChat维护的 UI 消息数组),交给openaiWebSearchAgent执行多步工具循环,并将结果以 UI 消息流形式返回。这正是"服务端 Agent + 前端useChat"的端到端链路:前端无需关心 Agent 内部调用了多少次模型、多少次工具。
Agent 定义:基于 ToolLoopAgent 的联网搜索
openai-web-search-agent.ts 展示了如何声明一个带工具循环能力的 Agent:
import { openai, type OpenAILanguageModelResponsesOptions } from '@ai-sdk/openai'; import { ToolLoopAgent } from 'ai'; export const openaiWebSearchAgent = new ToolLoopAgent({ model: openai('gpt-5-mini'), tools: { web_search: openai.tools.webSearch({ searchContextSize: 'low', userLocation: { type: 'approximate', city: 'San Francisco', region: 'California', country: 'US', }, }), }, providerOptions: { openai: { reasoningEffort: 'medium', reasoningSummary: 'detailed', } satisfies OpenAILanguageModelResponsesOptions, }, });要点说明:
ToolLoopAgent是 AI SDK 提供的内置 Agent 类(源码位于 packages/ai/src/agent/tool-loop-agent.ts),负责"模型推理 → 调用工具 → 将结果回传模型 → 继续推理"的循环,直到模型不再请求工具为止;web_search工具直接取自openai.tools.webSearch,可配置searchContextSize(如low)以及近似用户位置(userLocation),让搜索结果更贴近地域语境;providerOptions.openai透传给 OpenAI 的底层请求参数,示例设置了reasoningEffort: 'medium'与reasoningSummary: 'detailed',并通过satisfies OpenAILanguageModelResponsesOptions获得类型检查保障。
CORS 配置:对接前端useChat
为了允许运行在localhost:3000的前端页面(如 Next.js 开发服务器)调用/chat/*接口,示例为聊天路径配置了 CORS:
app.use( '/chat/*', cors({ origin: 'http://localhost:3000', allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowHeaders: ['Content-Type', 'Authorization'], maxAge: 86400, }), );origin限定了允许的前端来源,生产环境应替换为你的实际域名;allowHeaders覆盖了Content-Type(JSON 请求必需)与Authorization(如需携带鉴权令牌);maxAge: 86400让浏览器缓存预检请求结果一天,减少 OPTIONS 请求次数;- 其余端点(如
/text)若同样需要跨域调用,可参照此配置扩大app.use的匹配路径。
此外GET /health提供了一个简单的存活探测:c.text('Hono AI SDK example server is running!'),便于部署后验证服务状态。
流式响应的底层原理速览
结合源码可以总结出本示例背后两条核心链路:
- 文本链路:
streamText().stream(TextStreamPart流)→toTextStream()提取纯文本 →createTextStreamResponse()以text/plain逐块下发。适用于日志、终端、简单渲染场景。 - UI 消息链路:
streamText().stream→toUIMessageStream()将文本增量、工具调用、完成事件等转换为UIMessageChunk块 →createUIMessageStreamResponse()包装为 HTTP 响应。toUIMessageStream内部通过TransformStream逐块转换(见 to-ui-message-stream.ts),并依据part.type维护流的最终状态(completed / aborted / failed)。
两条链路的共同点:都是"创建后立即返回 Response、生成过程异步推进"的流式模型,因此模型首字延迟(TTFT)之后的内容可以持续、增量地到达客户端,这也是对话体验流畅的关键。
小结
通过 examples/hono 这个示例,你可以在一套 Hono 服务中同时获得:
- 最简文本流(
/text),适合快速验证与通用消费端; - 结构化 UI 消息流(
/、/chat),可直接对接 AI SDK 前端useChat; - 手动控制消息块(
/stream-data),便于注入自定义数据或编排多条流; - 基于
ToolLoopAgent的联网搜索 Agent(/chat),演示了工具循环与 Provider 选项透传。
如需在此基础上扩展,可以参照 next-agent(Next.js + Agent 组合)或 express、fastify(其他 Node 框架的 AI SDK 接入方式)对比学习;AI SDK 各流式工具的完整参数定义可进一步查阅 packages/ai/src/ui-message-stream 与 packages/ai/src/text-stream 目录下的源码与类型注释。
【免费下载链接】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),仅供参考