02. 让 Agent 有手有脚:工具系统的设计与演化
02. 让 Agent 有手有脚工具系统的设计与演化从零到一实现一个 AI Agent 框架 · 第二篇1. 为什么需要工具系统上一篇我们实现了 Agent Loop——LLM 能自己决定下一步做什么了。但注意那个循环里最关键的一步我们跳过了LLM我想查 AAPL 的股价 循环好那你去吧 → 怎么查谁来执行Agent Loop 负责决定做什么工具系统负责真正去做。没有工具系统Agent 就是个空转的大脑——想了很多但什么都做不了。最早期的 Agent 实现里工具就是一段 if-elseifnameget_weather:returnget_weather(args)elifnamesearch_web:returnsearch_web(args)# ... 每加一个工具就加一个 elif但随着工具变多问题就来了每个工具的参数怎么校验谁来判断这个工具是只读的还是破坏性的工具输出太大怎么办直接塞回上下文那么多工具模型每次都得看全部 schema浪费 token这些问题就是工具系统要解决的。2. 从零开始最小工具系统先把问题简化到极致——一个工具系统最少需要什么# 最小工具系统tools{}defregister_tool(name,fn,description,parameters):注册一个工具tools[name]{fn:fn,schema:{name:name,description:description,parameters:parameters}}defdispatch(name,args):调用一个工具ifnamenotintools:returnfError: unknown tool {name}returntools[name][fn](**args)defget_schemas():获取所有工具的 schema传给 LLMreturn[t[schema]fortintools.values()]就这么简单注册 → 生成 Schema → 分发调用。来注册两个工具试试defget_weather(city):returnf{city}的天气晴22°Cdefsend_email(to,subject,body):returnf邮件已发送到{to}register_tool(get_weather,get_weather,description获取城市天气,parameters{type:object,properties:{city:{type:string,description:城市名}},required:[city]})register_tool(send_email,send_email,description发送邮件,parameters{type:object,properties:{to:{type:string},subject:{type:string},body:{type:string}},required:[to,subject,body]})然后和上一篇的 Agent Loop 接起来# Agent Loop 里调用工具的代码fortool_callinmsg.tool_calls:resultdispatch(tool_call.function.name,json.loads(tool_call.function.arguments))messages.append({role:tool,content:result})这就是最小可用方案了。但和第一篇一样这版本也有很多工程问题等着解决。3. 工程演进工具系统需要解决什么3.1 工具 Schema 怎么生成上面的代码里parameters 是手写的 JSON。手写的问题容易出错类型写错、漏了字段难以维护函数改参数了但 JSON 没同步更新不够精确JSON Schema 表达能力有限更好的做法是从类型定义自动生成Schema。在 TypeScript 里可以用zod或json-schema这类库从函数签名推导出 OpenAI Function Calling 兼容的 Schema。// 理想方案从类型定义自动生成constweatherToolcreateTool({name:get_weather,description:获取城市天气,input:z.object({// 用 zod 定义参数city:z.string().describe(城市名)}),handler:async({city}){return${city}的天气晴22°C;}});// → 自动生成 OpenAI 兼容的 schema3.2 工具多了怎么办假设你有 20 个工具每次调用 LLM 都要把 20 个完整 Schema 传过去。这会浪费 token每个 Schema 几百到上千 token20 个就是上万干扰决策模型要从 20 个里选容易选错解决方案按需加载Deferred Tools。常用工具常驻低频工具默认隐藏。模型先调tool_search搜索再把匹配的工具激活到下一轮。# 不是所有工具都在 Schema 里active_tools[read_file,write_file,bash,search_files]# 需要记忆工具先搜一下# LLMtool_search(querymemory)# 系统找到 memory_save / memory_read激活下一轮可用3.3 工具输出太大怎么办工具可能返回巨大结果npm test→ 几百行测试日志search_files(TODO)→ 命中 50 个文件read_file(large.json)→ 几万行的 JSON把这些原样塞回上下文后果很严重上下文窗口爆炸LLM 调用变贵关键信息被淹没模型找不到重点可能触发上下文超限整个请求失败常见的治理策略策略做法适合场景截断保留头尾 N 字符日志、测试输出摘要LLM 压缩成一句话搜索结果落盘保存到文件给模型路径超大输出过滤只返回关键行错误信息注意截断时优先保留尾部——很多工具的重要信息在末尾测试失败数、构建状态码、命令退出码。3.4 工具的安全性怎么保障不是所有工具都能随便调。考虑这几个场景LLM让我看看用户的 ~/.ssh/id_rsa 文件 → 危险 LLMrm -rf / → 非常危险 LLM帮我把这篇文章发布到生产环境 → 需要确认Axon 的做法是给每个工具打标签让系统层做决策isReadOnly: 只读工具可以并发、不需要确认isDestructive: 破坏性操作必须要用户确认isConcurrencySafe: 是否可以和其他工具并行执行而且这些标签可以是输入相关的bash(ls)是只读的bash(rm -rf /)不是。4. 代码解剖Axon 的工具系统Axon 的工具系统核心在src/tools/index.ts。几个关键概念4.1 ToolSpec工具的完整契约每个工具不是一个简单的name→function映射而是一个ToolSpecinterfaceToolSpec{name:string;// 工具名definition:object;// OpenAI Function Calling Schemahandler?:ToolHandler;// 实际执行函数// 行为元数据输入相关isReadOnly?:(input)boolean;isConcurrencySafe?:(input)boolean;isDestructive?:(input)boolean;// 结果治理maxResultSizeChars?:number;// 默认 50_000// 延迟加载deferred?:boolean;}关键设计isReadOnly等元数据接收 input 参数。同一个工具在不同输入下有不同的行为特征。4.2 工具注册所有工具汇总到一个注册中心// 核心工具 扩展工具 MCP 工具 → 合并 → 按需激活functiongetActiveToolSpecs():ToolSpec[]{return[...coreTools,// read_file, bash 等常驻...extensionTools,// task, memory 等...activatedDeferred,// 通过 tool_search 激活的...mcpTools// 动态注入的 MCP 工具];}4.3 工具执行链路一次工具调用的完整链路结果治理工具处理函数权限检查dispatchAgent LoopLLM结果治理工具处理函数权限检查dispatchAgent LoopLLMalt[allow][deny]tool_call(name, args)dispatch(name, input)查找 ToolSpeccheckPermission(name, input)allow / deny / confirmhandler(input)原始输出脱敏mask secrets截断 / 落盘处理后的结果Permission deniedtool role message几个要点工具找不到返回Error: unknown tool不是抛异常——让 LLM 自己修正权限拒绝也作为工具结果返回不中断 Agent Loop所有结果都经过脱敏和审计4.4 并发执行当 LLM 一次请求多个工具调用时Agent Loop 会判断是否可以并行constcanRunConcurrentlycalls.length1calls.every(cisConcurrencySafe(c.name,c.input));if(canRunConcurrently){awaitPromise.all(calls.map(cdispatch(c)));}else{for(constcofcalls){awaitdispatch(c);}}Axon 的做法是整批判断只要有一个工具不能并发整批串行。更激进的策略可以分组并发先读后写但会让执行顺序和审计变得复杂。4.5 文件编辑安全edit_file是最容易出问题的工具Axon 做了三层保护第一层Read-before-edit编辑前必须调用过read_file否则拒绝。第二层mtime 检查读取时记录文件的修改时间戳写入时检查是否被外部修改过。第三层唯一匹配替换使用 search-and-replace 而不是行号编辑// 要求old_string 在文件中出现且只出现一次 // 出现 0 次 → 模型幻觉拒绝 // 出现 1 次 → 正常替换 // 出现多次 → 要求提供更多上下文这样就把模型的幻觉修改变成了显式失败。5. 动手实验搭建自己的工具系统基于上一篇的 Agent Loop加上工具系统。importjsonfromopenaiimportOpenAI clientOpenAI(api_keyyour-api-key)# 工具注册中心 tools_registry{}defregister(name,handler,definition):tools_registry[name]{handler:handler,definition:definition}defdispatch(name,args):ifnamenotintools_registry:returnfError: unknown tool {name}returntools_registry[name][handler](**args)defget_schemas():return[t[definition]fortintools_registry.values()]# 注册工具 defget_weather(city):returnjson.dumps({city:city,temp:22,condition:晴})defcalculate(expression):try:returnstr(eval(expression))exceptExceptionase:returnfError:{e}register(get_weather,get_weather,{type:function,function:{name:get_weather,description:获取城市天气,parameters:{type:object,properties:{city:{type:string}},required:[city]}}})register(calculate,calculate,{type:function,function:{name:calculate,description:执行数学计算,parameters:{type:object,properties:{expression:{type:string,description:数学表达式}},required:[expression]}}})# Agent Loop接上第一篇 defagent_loop(prompt):messages[{role:user,content:prompt}]max_turns10forturninrange(max_turns):print(f\n--- Turn{turn1}---)responseclient.chat.completions.create(modelgpt-4o,messagesmessages,toolsget_schemas(),tool_choiceauto)msgresponse.choices[0].messageifnotmsg.tool_calls:returnmsg.content messages.append(msg)fortool_callinmsg.tool_calls:nametool_call.function.name argsjson.loads(tool_call.function.arguments)print(f→ 调用:{name}({args}))resultdispatch(name,args)print(f← 结果:{result[:100]}...)messages.append({role:tool,tool_call_id:tool_call.id,content:result})return已达最大轮数。# 试试看resultagent_loop(北京天气怎么样再帮我算一下 2^10 等于多少)print(f\n最终回复{result})实验一下加一个新工具比如search_web(query)返回模拟结果让工具返回错误get_weather返回error: 服务不可用看 LLM 怎么应对尝试危险操作加一个delete_file(path)工具观察 LLM 是否会在没有确认的情况下调用下一篇预告别让 Agent 乱跑——权限与安全治理工具越多风险越大。怎么让 Agent 只能读不能删怎么保护 API Key 不被泄露怎么让危险操作需要用户确认下一篇讲权限和安全设计。

相关新闻

PPTist:8个专业模板+完整功能,打造浏览器中的PowerPoint替代方案

PPTist:8个专业模板+完整功能,打造浏览器中的PowerPoint替代方案

PPTist:8个专业模板完整功能,打造浏览器中的PowerPoint替代方案 【免费下载链接】PPTist PowerPoint-ist(/pauəpɔintist/), An online presentation application that replicates most of the commonly used features of MS Pow…

2026/7/2 1:13:27阅读更多 →
AI 辅助:Redis 高可用设计:缓存不是数据库的廉价替身

AI 辅助:Redis 高可用设计:缓存不是数据库的廉价替身

AI 辅助:Redis 高可用设计:缓存不是数据库的廉价替身 一、先确认 Redis 保存的是什么数据 Redis 常被用于缓存、计数、分布式锁和会话存储,但它不是数据库的廉价替身。高可用设计中,需要明确 Redis 保存的是可丢失缓存&#xff0c…

2026/7/2 1:13:27阅读更多 →
Windows 10/11用户如何高效解决苹果设备连接问题:PowerShell驱动的驱动程序安装方案

Windows 10/11用户如何高效解决苹果设备连接问题:PowerShell驱动的驱动程序安装方案

Windows 10/11用户如何高效解决苹果设备连接问题:PowerShell驱动的驱动程序安装方案 【免费下载链接】Apple-Mobile-Drivers-Installer Powershell script to easily install Apple USB and Mobile Device Ethernet (USB Tethering) drivers on Windows! 项目地址…

2026/7/2 1:13:27阅读更多 →
构建现代 Web3 后端:Go + Solidity 全栈技术指南

构建现代 Web3 后端:Go + Solidity 全栈技术指南

1. 引言:Web3 后端的技术革命 在传统 Web2 架构中,后端系统围绕中心化服务器、数据库和 API 构建。Web3 的到来彻底改变了这一范式,将核心逻辑转移到去中心化的区块链网络上。这种转变不仅要求开发者掌握新的编程语言和工具,更需…

2026/7/2 2:33:32阅读更多 →
不安装 setup.py中的依赖

不安装 setup.py中的依赖

不安装 setup.py 中 install_requires 或 pyproject.toml 中声明的运行依赖。pip install -e argoverse-api/ --no-deps

2026/7/2 2:33:32阅读更多 →
2026年算法工程师必备:大模型技术体系与实战指南

2026年算法工程师必备:大模型技术体系与实战指南

1. 为什么2026年算法工程师必须掌握大模型技术?最近三年面试了上百位算法岗位候选人,发现一个明显趋势:传统机器学习工程师的薪资溢价正在消失。去年某头部大厂NLP岗位的招聘数据表明,掌握大模型技术的候选人平均薪资比传统算法工…

2026/7/2 2:33:32阅读更多 →
科技创业者读什么在职硕士能拓展产业人脉圈-2027项目对比与交大MTT解读

科技创业者读什么在职硕士能拓展产业人脉圈-2027项目对比与交大MTT解读

科技创业者读什么在职硕士能拓展产业人脉圈?2027项目对比与交大 MTT 解读 科技创业者读在职硕士,最核心的目标通常不是「多学几门管理课」,而是拓展能真正帮项目往前走的产业人脉圈:科学家、技术专家、产业客户、投资人、园区资源…

2026/7/2 2:33:32阅读更多 →
打包带在高温环境下会变形吗?

打包带在高温环境下会变形吗?

打包带在高温环境下会变形吗? 在众多工业包装材料中,打包带是常见且实用的一种。然而,许多人都会有一个疑问,打包带在高温环境下会不会变形?今天就此展开深度探讨,希望能给有相关疑问的人带来帮助。此外&a…

2026/7/2 2:33:32阅读更多 →
Claude 桌面版(macOS / Windows)工具分享

Claude 桌面版(macOS / Windows)工具分享

【资源分享】Claude 桌面版(macOS / Windows)安装包 使用体验 最近一直在用 Claude 桌面版办公和写代码,Mac 和 Windows 两个平台都体验了一段时间,整体感觉比网页版舒服不少,整理了一下安装包和使用体验分享给大家。…

2026/7/2 2:28:31阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月,Boris Cherny 公开宣布自己卸载了 IDE。一时间,Vibe Coding 成了全行业最热的话题。6个月后,当我们回过头来拉一份真实账本,发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/7/1 4:42:14阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言:审计结束三个月了,审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间,内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中,审计…

2026/7/1 5:19:01阅读更多 →
塞尔达传说旷野之息存档修改器:3分钟掌握海拉鲁世界自由定制技巧

塞尔达传说旷野之息存档修改器:3分钟掌握海拉鲁世界自由定制技巧

塞尔达传说旷野之息存档修改器:3分钟掌握海拉鲁世界自由定制技巧 【免费下载链接】BOTW-Save-Editor-GUI A Work in Progress Save Editor for BOTW 项目地址: https://gitcode.com/gh_mirrors/bo/BOTW-Save-Editor-GUI 想在《塞尔达传说:旷野之息…

2026/7/2 0:03:01阅读更多 →
告别 AccessKey:多云平台 CLI OAuth 免密认证完全指南

告别 AccessKey:多云平台 CLI OAuth 免密认证完全指南

在本地开发环境使用云厂商 CLI 时,传统的 AccessKey(AK)方式需要手动创建、下载和保管密钥,不仅繁琐,还存在泄漏风险。其实,主流云平台都已提供基于 OAuth 2.0 的免密认证方案,让开发者可以通过浏览器登录一次性完成授权,CLI 自动管理临时凭证的刷新,兼顾了便利与安全…

2026/7/2 0:03:01阅读更多 →
基于13DOF传感器与PIC32MZ的高精度嵌入式导航系统设计

基于13DOF传感器与PIC32MZ的高精度嵌入式导航系统设计

1. 项目背景与核心价值在嵌入式系统开发领域,高精度定位与导航一直是极具挑战性的技术方向。传统方案往往面临成本、精度和实时性难以兼顾的困境。这个项目通过13DOF(13自由度)传感器组合与PIC32MZ2048EFH100高性能MCU的协同工作,…

2026/7/2 0:03:01阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/2 0:33:58阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/2 1:32:11阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/2 1:50:13阅读更多 →