做了几年 Java 和 AI我发现一件挺反直觉的事身边不少同事 LangChain、AutoGPT、各种 Agent 框架玩得飞起让他当场从零写一个会自己调工具的智能体反而愣住了。不是他们不会写代码是框架把太多东西包掉了。你只管agent.run(帮我订明天的机票)至于它怎么决定调哪个工具、怎么把工具结果塞回去、怎么判断该停了全在黑盒里。用着是爽真出一个诡异的 bug你连日志往哪看都不知道。更尴尬的是现在Agent这个词被用得太滥了。加个 if 判断也敢叫智能体套个提示词模板也敢叫 Agent。听得多了反而没人说得清它到底比普通聊天机器人多了哪根筋。所以我开这个系列叫「Agent 实战」。不堆概念不抄论文一期一个能落地的主题从最底下那层往上拆。今天第 1 期我们不依赖任何框架只靠 Node 自带的 fetch用不到一百行 TypeScript手搓一个能自己决定该调哪个工具、调完了再想的最小智能体。读完后你能带走三样东西一句话讲清 Agent 到底是什么、一张完整的一次对话流转图、一份直接能跑的 TS 代码。后面几期再往上叠记忆、规划、多智能体协作。先把话说清楚Agent 到底多了哪根筋先讲个生活里的例子。你让普通聊天机器人明天北京下雨吗下的话提醒我带伞它大概率会给你一段关于北京天气的文字描述然后停住。它只是把话说顺了没真的去查、也没真的替你做决定。你让一个 Agent 做同样的事它会拆成几步先调天气工具查北京明天的降水概率看到概率超过六成再决定给你发一条带伞提醒。区别在于Agent 不只是吐字它会动手。它和普通脚本、和 RPA机器人流程自动化也不一样。RPA 是按你写死的步骤一步步点按钮流程变了就报错。Agent 是给模型一组工具和一个目标让它自己判断每一步怎么走。同样是查天气再提醒RPA 你得把判断逻辑亲手写好Agent 是把判断权交给了模型。为什么这东西这两年才火不是因为概念新ReAct 那篇论文 2022 年底就出了。真正的原因是 2023 年之后大模型推理能力过了某个坎能比较稳定地输出我接下来要调工具这种结构化决策了。模型不够聪明时你让它规划它胡说八道模型够聪明了这套边想边干的循环才转得起来。一句话总结 Agent 的骨架一个大模型当脑子一组工具当手一段记忆当笔记本外面套一个循环当节拍器。循环反复问模型同一件事下一步你打算干嘛要调工具就说拿到结果再想直到能直接回答你为止。这套边推理边行动的思路圈内叫 ReActReasoning 加 Acting名字挺学术本质就是边想边干。拆开看四个零件每个都有坑脑子LLM不用多说负责决策和表达。它会犯的错是自信地乱调工具比如你问它一个纯知识问题它偏要调计算器。靠 system 提示词把边界划清楚能缓解但没法根治后面踩坑章节会细讲。手Tools是 Agent 真正能产生外部影响的地方也是它和普通聊天机器人最本质的区别。工具可以是计算器、查数据库、调内部 API、发邮件、跑一段 SQL。模型本身碰不到这些它只能说我要调真正执行的是你写的函数。这就是 Agent 安全性的命门模型只有建议权执行权和边界在你手里。记忆Memory分两种短期是你传给模型的整段 messages长期是跨会话存下来的东西比如用户偏好。本期这个最小版先不接记忆每次对话都是一张白纸。别急下期专门讲怎么把记忆接上那才是 Agent 从玩具变助手的关键一步。循环Loop是节拍器负责反复问、反复收、反复判断停没停。它看起来最不起眼却最容易出事。循环不设上限模型一旦陷入调工具、看结果、觉得不对再调的死结token 蹭蹭涨你的账单也跟着跳。本期代码里我给循环套了maxSteps就是专门防这个。把四个零件串起来循环每转一圈其实就三步思考模型读上下文决定下一步、行动模型说要调工具你执行、观察把工具结果塞回上下文。再转下一圈直到模型觉得能答了就停。一整轮对话是怎么转起来的光讲概念还是虚我把一次真实对话的 message 流转完整贴出来。看懂这一段Agent 对你就没有秘密了。假设用户问北京今天天气怎么样顺便帮我算 123 乘 456 等于多少。第 1 轮你发请求。messages 里先放一条 system你是小关的智能体手里有计算和查天气两个工具再放一条 user上面那个问题。发给模型。第 1 轮模型回。模型读完决定先查天气。它不会直接吐文字而是返回一个tool_calls字段里面写着我要调get_weather参数是{city: 北京}。注意这一步模型没有回答问题它只是举手说我要干活。第 1 轮你执行。你看到tool_calls按名字找到本地函数get_weather(北京)跑出结果比如晴28 度。第 1 轮你回填。把模型那条带tool_calls的消息原样存回 messages再追加一条 role 为 tool 的消息内容是晴28 度并带上tool_call_id和刚才那次调用对上号。第 2 轮模型再读。现在上下文里多了北京晴 28 度这个观察结果。模型想了想天气回完了但用户还让算 123 乘 456于是它又回一个tool_calls调calculator参数{expr: 123*456}。第 2 轮你执行并回填。跑出 56088再追加一条 tool 消息。第 3 轮模型收尾。这次模型不再返回tool_calls而是直接给出自然语言回答北京今天晴28 度。另外 123 乘 456 等于 56088。循环结束。整个过程里模型从来没有直接算出 56088它只做了两次决定调工具和一次总结回答。真正的计算和查天气都是你写的函数干的。这就是 Agent 和聊天机器人那条分界线也是它能干实事的原因。动手完整可运行版多工具路由下面这个版本在最小版基础上加了一个工具变成计算 查天气两个刚好能演示 Agent 自己选工具的能力。只依赖 Node 18 自带的 fetch零第三方依赖保存成agent.ts用ts-node直接跑或先tsc编译再用node跑Key 填上就能动。// agent.ts —— Agent 实战第1期零框架手搓最小智能体 // 运行Node 18npm i -D typescript ts-node然后 npx ts-node agent.ts const API_KEY 你的KEY; const BASE https://api.deepseek.com; const MODEL deepseek-v4-flash; // 1) 工具清单模型只能从这里挑。挑错了先别怪模型多半是你的描述没写清 const tools [ { type: function, function: { name: calculator, description: 计算数学表达式比如 23*74。只接收合法算术式。, parameters: { type: object, properties: { expr: { type: string, description: 算术表达式 } }, required: [expr], }, }, }, { type: function, function: { name: get_weather, description: 查询指定城市今天的天气返回温度和天气状况。, parameters: { type: object, properties: { city: { type: string, description: 城市名如 北京 } }, required: [city], }, }, }, ]; // 2) 本地工具实现模型只说要调真正干活的是这些函数 function calculator(args: { expr: string }): string { // 演示用 eval线上务必换成安全解析器见踩坑第 3 条 // 注意Node 的 eval 同样能执行任意代码这里仅作演示 return String(eval(args.expr)); } function get_weather(args: { city: string }): string { // 这里用假数据真实场景换成和风天气、高德等开放 API const fake: Recordstring, string { 北京: 晴28度, 上海: 阴25度, 深圳: 雷阵雨30度, }; return fake[args.city] ?? ${args.city} 未知默认多云22度; } // 3) 工具分发表名字映射到函数比一长串 if-else 干净 const toolRegistry: Recordstring, (args: any) string { calculator, get_weather, }; async function callLlm(messages: any[]): Promiseany { const resp await fetch(${BASE}/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_KEY}, }, body: JSON.stringify({ model: ${MODEL}, messages, tools, tool_choice: auto, // 让模型自己决定调不调、调哪个 }), }); const data: any await resp.json(); return data.choices[0].message; } async function runAgent(question: string, maxSteps 6): Promisestring { const messages: any[] [ { role: system, content: 你是工具智能体手里有计算和查天气两个工具。需要时用工具能直接回答就别硬调。, }, { role: user, content: question }, ]; for (let step 1; step maxSteps; step) { console.log(\n 第 ${step} 步 ); const msg await callLlm(messages); if (msg.tool_calls) { // 模型举手说要干活我们照办 messages.push(msg); for (const tc of msg.tool_calls) { const name tc.function.name; const args JSON.parse(tc.function.arguments); console.log(模型决定调工具${name}参数${JSON.stringify(args)}); const result toolRegistry[name](args); console.log(工具返回${result}); messages.push({ role: tool, content: String(result), tool_call_id: tc.id, }); } } else { // 模型不再调工具说明它觉得能答了 console.log(模型给出最终回答); return msg.content; } } return 步数到上限强制收工避免无限循环烧钱。; } runAgent(北京今天天气怎么样顺便帮我算一下 123 乘 456 等于多少).then(console.log);跑起来控制台如图你看第 1 步它自己选了查天气第 2 步自己选了算乘法全程没有你写死任何先查后算的 if。这就是 Agent 的妙处路径是模型临场选的不是你预设的。我踩过的坑你直接拿去避这些问题我当时一个一个撞过按出现频率排循环不设上限。最早我没加maxSteps有次模型对一个含糊问题反复调工具十几次一条请求烧掉快一块钱。现在默认 6 步复杂任务最多放 10 步。工具返回直接整段塞回上下文。某个工具返回了几千字的 JSON结果前面的对话全被挤到 token 上限外模型失忆开始胡说。现在关键结果一律截断只留模型真要用的那几行。calculator 用了 eval。这是送上门的远程执行漏洞Node 的 eval 同样能执行任意代码。线上我改用独立表达式解析库如 expr-eval配合运算符白名单或者自己写个只认加减乘除的解析器绝不直接 eval 用户串。工具描述写得太省。模型是靠description判断什么时候该调这个工具的。你把描述写成计算它就只认字面计算两个字写成计算数学表达式如 23*74命中率明显高。描述是给模型看的说明书不是给你自己看的注释。一次让模型返回多个 tool_calls 时处理不全。有些问题模型会一口气甩出两三个tool_calls只处理第一个就会漏活。上面代码用 for 循环把msg.tool_calls全兜住了别偷懒只取[0]。tool 消息忘了带tool_call_id。模型回你的tool_calls里每个都有 id回填的 tool 消息必须带上同一个 id 对应上否则接口直接报错。这个错非常隐蔽第一次写基本都会踩。把模型当真理。模型会自信地乱调比如你问它一个纯知识问题它偏要调计算器。靠 system 提示词把边界划清能缓解但别指望它 100% 听话。关键动作发邮件、删数据一定要在你这层加二次确认别让模型一句话就把生产库清空了。选太重的模型做简单路由。早期我所有步骤都上 deepseek-v4-pro后来发现决定调不调工具这种活 4o-mini或者flash 就够换小模型后单请求成本降了七八成准确率几乎没掉。什么时候该上 Agent什么时候别碰这是我最想强调的一节因为太多人为了用而用。适合上 Agent 的场景任务路径不固定、需要跟外部系统交互、且中间要靠判断决定下一步。比如从用户问题里抽关键词查内部知识库拼成答案回复比如监控告警判断严重级别严重就拉群通知人。这类活规则写不死Agent 正好补上。不该上 Agent 的场景流程固定、输入输出可枚举。比如把 CSV 转成 Excel一个脚本够了别套 Agent纯属加延迟加成本。还有对准确率要求极高、错了代价很大的环节比如直接操作资金转账宁可多写几个 if也别把决策权交给模型。Agent 是给不确定性用的。确定性高的活老老实实写代码又快又稳又便宜。进阶路线图这个系列后面讲什么本期是地基。往上盖楼按顺序大概是这样第 2 期 记忆系统给 Agent 接上短期和长期记忆让它跨多轮对话不健忘能记住我上次说我喜欢用 Java 不用 Go。第 3 期 规划与反思引入先列步骤再执行以及执行失败后的自我纠错从单步反应进化成有策略的多步任务。第 4 期 多智能体协作一个负责拆解任务几个分别干检索、计算、校对的活最后汇总。讲清楚什么时候该拆、怎么避免它们互相甩锅。第 5 期 工具编排与评估怎么管理十几个工具不混乱怎么给 Agent 打分看它到底靠不靠谱。每一期都会延续今天这个零框架、能跑、有踩坑的写法代码同样会传 Gitee。下期预告与互动下期我们给这个健忘的智能体接上记忆让它第一次能接着上次的话聊这一步做完它才真正像个助手而不是一次性计算器。完整可运行代码已上传 GitHub评论区回复666即可获取。
