【从0开发一个 Agent】第六章:实现 Tool Calling
在第五章中我们将聊天体验打磨到了生产级。然而无论 UI 多么丝滑目前的 AI 依然是一个“只会说不会做”的顾问。当你问它“帮我查一下杭州明天的天气”或“统计一下数据库里昨天的订单量”时它只能基于训练数据“编造”一个答案或者无奈地告诉你“我无法访问实时信息”。本章是 AI Agent 真正的分水岭。我们将赋予 AI “手脚”让它能够自主决定并调用外部工具Tool Calling。通过这一章你的应用将从一个“问答机器人”正式进化为“任务执行系统”。1. 为什么需要 Tool Calling大语言模型LLM本质上是一个概率预测引擎它的世界观是由 Token 构成的它无法直接访问互联网、读取本地文件或连接你的 PostgreSQL 数据库。Tool Calling 的本质不是让模型去“执行”代码而是让模型去“提议”执行代码。它的工作机制是一个完美的闭环意图识别模型判断当前问题需要外部数据。生成指令模型不直接回答而是输出一段结构化的 JSON包含工具名和参数。外部执行你的后端代码Agent Runtime拦截这个 JSON调用真实的 API 或数据库。结果整合将真实数据返回给模型模型再将其转化为自然语言回复给用户。2. Tool Calling 架构与生命周期在 Vercel AI SDK 中工具调用的生命周期被高度抽象开发者只需关注业务逻辑。核心设计思想模型负责“思考与决策”你的代码负责“执行与兜底”。这种解耦设计既保证了 AI 的灵活性又确保了后端的安全性。3. 核心代码实现构建工具集我们将使用 AI SDK 的 tool 函数和 zod 库来定义工具的参数 Schema。在src/lib/ai/tools.ts中集中管理所有工具// src/lib/ai/tools.tsimport{tool}fromai;import{z}fromzod;// 1. 时间工具exportconstgetCurrentTimetool({description:获取当前的日期和时间当用户询问现在几点或今天日期时调用。,parameters:z.object({}),execute:async(){returnnewDate().toLocaleString(zh-CN,{timeZone:Asia/Shanghai});},});// 2. 天气工具exportconstgetWeathertool({description:查询指定城市的实时天气信息。,parameters:z.object({city:z.string().describe(城市名称例如杭州北京齐齐哈尔),}),execute:async({city}){// 这里可以调用真实的天气 API这里用模拟数据演示return{city,temperature:25°C,condition:晴天,wind:东南风 3级};},});// 3. 数据库查询工具exportconstqueryDatabasetool({description:查询企业订单数据库。,parameters:z.object({sql:z.string().describe(要执行的只读 SQL 查询语句),}),execute:async({sql}){// 生产环境建议严格限制为只读操作防止 SQL 注入constresultawaitprisma.$queryRawUnsafe(sql);returnresult;},});设计思考Zod SchemaAI SDK 使用 Zod 来验证参数。如果模型传错了参数类型Zod 会拦截并让模型重新生成极大降低了运行时错误。Description 的重要性模型完全依赖 description 来决定是否调用工具。描述必须清晰、准确说明“什么时候该用”以及“需要什么参数”。4. 接入流式对话接口在上一章的/api/chat/route.ts中将工具集注入到streamText中// src/app/api/chat/route.tsimport{streamText}fromai;import{openai}from/lib/ai/config;import{getCurrentTime,getWeather,queryDatabase}from/lib/ai/tools;exportasyncfunctionPOST(req:Request){const{messages}awaitreq.json();constresultstreamText({model:openai(gpt-4o),system:你是一个全能 AI 助手。如果用户的问题需要实时信息或数据请务必使用提供的工具。,messages,tools:{getCurrentTime,getWeather,queryDatabase,},// 允许模型在一次回复中连续调用多个工具maxSteps:5,});returnresult.toDataStreamResponse();}关键配置 maxSteps默认情况下AI SDK 只执行一步。设置maxSteps: 5允许 Agent 在遇到复杂任务时例如先查天气再根据天气决定是否发邮件进行多轮工具链式调用。5. 前端 UI 状态反馈进阶体验当 AI 在后台调用工具时用户不能只看到一片空白。我们需要在前端展示工具调用的状态。AI SDK 的useChat会在messages中自动注入toolInvocation状态。我们可以这样优化MessageBubble// 在 MessageBubble.tsx 中增加工具状态渲染exportfunctionMessageBubble({message}:{message:Message}){// 处理工具调用状态if(message.roleassistantmessage.toolInvocations){return(div classNameflex justify-start mb-4div classNamemax-w-[80%] p-3 rounded-lg bg-gray-100 text-gray-600 text-sm{message.toolInvocations.map((invocation)(div key{invocation.toolCallId}classNameflex items-center gap-2span classNameanimate-spin️/spanspan正在调用工具:{invocation.toolName}/span{invocation.stateresultspan classNametext-green-500/span}/div))}/div/div);}// 正常的文本渲染逻辑...}6. 测试验证输入“现在几点了” AI 应调用getCurrentTime并准确回答。输入“杭州今天天气怎么样” AI 应调用getWeather展示加载状态并返回真实/模拟数据。输入“帮我查一下数据库里有没有叫张三的用户” AI 应生成安全的 SQL 并调用queryDatabase。观察前端 UI在工具执行期间是否有齿轮转动和工具名称提示。7. 常见问题与踩坑分析问题 1模型疯狂调用工具陷入死循环原因工具描述不够清晰或者模型没有正确理解工具返回的结果。解决优化description明确告知模型“如果工具返回了错误请向用户解释不要重试”。同时合理设置maxSteps上限。问题 2工具执行报错AI 直接回复“我遇到了错误”原因execute函数中没有做好异常捕获。解决在execute内部使用try-catch将错误转化为结构化的 JSON 返回给模型让模型自己决定如何向用户解释错误而不是直接中断流式响应。本章总结我们深入理解了 Tool Calling 的核心原理模型负责决策代码负责执行。使用 Zod AI SDK 构建了类型安全、易于扩展的工具集。实现了时间、天气、数据库查询三个典型工具。在前端实现了工具调用状态的实时可视化提升了交互体验。至此你的 AI 已经长出了“手脚”能够与真实世界交互。但目前的工具都是硬编码在后端的。如果我们要接入 Notion、GitHub 等成百上千个外部服务难道要为每个服务都写一套对接代码吗从下一章开始我们将引入MCPModel Context Protocol让你的 Agent 具备无限扩展的标准化能力。