示例代码摘取自开源项目:next-ai-draw-io
1. 总述
在 ai.js 中,工具(Tools)通过 streamText 配置的 tools 参数注入,分为两类:
- 服务端工具 :配置包含
execute异步函数,可在服务端自动执行并返回结果; - 客户端工具 :仅配置元信息(
description/inputSchema),无execute函数,需前端执行后手动回传结果。
2. 工具注入(streamText 核心配置)
javascript
// 服务端 streamText 调用(核心代码)
const result = await streamText({
model: openai('gpt-4o'),
messages: allMessages, // 对话历史
// 注入工具
tools: {
// 1. 服务端工具:带 execute 函数
get_shape_library: {
description: "获取draw.io形状库文档", // 模型识别工具的描述
inputSchema: z.object({ // 入参校验Schema(zod)
library: z.string().describe("形状库名称,如aws4")
}),
// 服务端自动执行的函数,返回值直接传给模型
execute: async ({ library }) => {
// 服务端业务逻辑(文件读取/API调用等)
const content = await fs.readFile(`./docs/${library}.md`, 'utf-8');
return content; // 结果自动返回给模型,无需手动处理
}
},
// 2. 客户端工具:仅元信息,无 execute
display_diagram: {
description: "在draw.io渲染图形(客户端执行)",
inputSchema: z.object({
xml: z.string().describe("draw.io的mxCell XML字符串")
})
// 无 execute 函数,需前端处理
}
},
experimental_repairToolCall: async ({ toolCall, error }) => {
// 修复模型返回的工具入参格式错误(如截断的JSON)
if (error.name === 'InvalidToolInputError') {
return { ...toolCall, input: jsonrepair(toolCall.input) };
}
return null;
},
onFinish: ({ totalUsage }) => {
// 对话结束回调(记录token/日志)
}
});
// 转换为前端可识别的流式响应
return result.toUIMessageStreamResponse({
sendReasoning: true, // 返回模型推理过程
});
注解:
tools对象中,每个键为工具名称,值为工具配置;inputSchema(zod 格式)用于校验模型传入的工具参数;toUIMessageStreamResponse是服务端与前端useChat通信的关键,格式化流式响应为前端可解析的结构(包含toolCall/text等字段)。
3. 前端工具交互(useChat 核心配置)
tsx
// 前端 useChat 调用
const { messages, sendMessage, addToolOutput } = useChat({
// 配置传输层,对接服务端 /api/chat 接口
transport: new DefaultChatTransport({
api: '/api/chat',
}),
// 模型触发工具调用时的回调
onToolCall: async ({ toolCall }) => {
const { id: toolCallId, toolName, input: toolInput } = toolCall;
try {
// 区分工具类型处理
switch (toolName) {
// 客户端工具:前端执行逻辑 + 手动回传结果
case 'display_diagram': {
const { xml } = toolInput as { xml: string };
// 前端业务逻辑(如调用draw.io API渲染图形)
await renderDiagram(xml);
// 手动回传结果给模型
addToolOutput({
toolCallId: toolCallId, // 必须匹配模型返回的ID(关键)
toolName: toolName,
output: JSON.stringify({ // 结果需序列化为字符串
success: true,
message: '图形渲染成功'
})
});
break;
}
// 服务端工具:前端无需处理,等待服务端自动执行
case 'get_shape_library': {
console.log('等待服务端返回形状库结果');
break;
}
default:
// 未知工具返回错误
addToolOutput({
toolCallId,
toolName,
output: '',
error: `未知工具:${toolName}`
});
}
} catch (error) {
// 工具执行失败,回传错误信息
addToolOutput({
toolCallId,
toolName,
output: '',
error: (error as Error).message
});
}
},
// 工具结果回传后自动触发模型继续回复
sendAutomaticallyWhen: ({ messages }) => true,
onError: (error) => console.error('聊天错误:', error)
});
注解:
DefaultChatTransport:封装前端与服务端的通信逻辑,自动解析服务端返回的流式响应;onToolCall:前端捕获模型工具调用指令的唯一入口;addToolOutput:客户端工具结果回传的核心方法,toolCallId必须与模型返回的 ID 一致(否则模型无法关联结果);sendAutomaticallyWhen: () => true:确保工具结果回传后,模型自动生成最终回复,完成闭环。
二、服务端工具 vs 客户端工具:交互核心区别
| 维度 | 服务端工具(带 execute) | 客户端工具(无 execute) |
|---|---|---|
| 执行主体 | 服务端(Node.js) | 前端(浏览器 / 客户端) |
| 核心触发逻辑 | AI.js 自动触发 execute 函数 |
前端 onToolCall 回调手动触发 |
| 结果返回方式 | execute 返回值由 AI.js 自动封装并传给模型 |
前端调用 addToolOutput 手动回传结果 |
| 关键依赖 | 服务端环境(文件 / 数据库 / 后端 API) | 前端环境(DOM / 客户端 SDK / 本地存储) |
| 通信成本 | 无额外网络请求(服务端内部完成) | 需 2 次网络请求(指令下发 + 结果回传) |
| 核心约束 | execute 需自行捕获异常,返回友好提示 |
toolCallId 必须与模型返回的 ID 严格匹配 |
| 典型场景 | 数据查询、文件读取、后端 API 调用 | 前端 UI 交互、本地操作、客户端 SDK 调用 |
三、核心流程梳理
1. 服务端工具流程
用户指令 → 服务端 streamText → 模型调用服务端工具 → AI.js 自动执行 execute → 结果自动传给模型 → 模型生成回复 → 前端展示。
2. 客户端工具流程
用户指令 → 服务端 streamText → 模型调用客户端工具 → 服务端通过流式响应下发 toolCall 指令 → 前端 onToolCall 触发 → 执行客户端逻辑 → addToolOutput 回传结果 → 服务端封装结果喂给模型 → 模型生成回复 → 前端展示。