
1. 为什么AI需要一个专属工具箱文件第一次看到TOOLS.md这个文件名的时候我脑子里冒出的第一个念头是又多了一个要维护的文档。毕竟手头已经有README.md讲项目概况有SKILL.md记录能力清单还有SOUL.md承载人格设定再塞一个TOOLS.md进去怎么看都像是给自己找活干。但真正把一套 AI Agent 跑起来、并且让它稳定干活之后我才意识到这个文件不是可选项而是整个体系里最容易被低估的那块拼图。先说清楚它是什么。TOOLS.md本质上是一份面向 AI 的工具能力声明文件它用结构化的方式告诉模型你现在手上有哪些工具可以用、每个工具叫什么名字、接受什么参数、返回什么结果、什么场景下该调用它、什么情况下绝对不能碰。你可以把它理解成给 AI 写的一份设备操作手册只不过这份手册不是给人看的是给模型在推理过程中实时查阅的。它解决的问题非常具体。在没有TOOLS.md的时候我遇到过太多这样的情况模型明明有能力调用某个接口却因为不知道这个接口存在而选择用自然语言瞎编或者知道有工具但参数传错、格式不对调用直接失败再或者更糟模型在不该调用工具的时候乱调把只读操作变成了写操作。这些问题的根源都不是模型不够聪明而是工具边界没有被清晰定义。适合读这份内容的人我大致分三类。第一类是正在做 AI Agent 应用开发的工程师尤其是用 OpenClaw 这类框架做本地部署或者集成到聊天平台的第二类是产品经理或者技术负责人需要理解为什么一个 AI 项目里要维护这么多 Markdown 文件各自分工是什么第三类是自己折腾本地大模型、想让模型真正动手干活而不是只聊天的爱好者。不管你是哪一类只要你的 AI 需要调用外部能力TOOLS.md就绕不开。我后面会把这套东西拆开讲它和SKILL.md、SOUL.md到底怎么分工文件内部该怎么写参数怎么设计实际部署时会踩哪些坑以及怎么排查那些工具明明注册了却调不动的玄学问题。这些都是我在实际项目里一行一行试出来的不是从文档里抄的。2. 四个核心文件的职责边界与协作逻辑2.1 TOOLS.md、SKILL.md、SOUL.md 到底谁管什么很多人第一次接触这套文件体系时会懵因为名字看起来都差不多都是大写加.md感觉像是同一类东西拆成了好几份。实际上它们的分工非常明确我用一个生活化的类比来说明把 AI 想象成一个刚入职的员工。SOUL.md是这个员工的性格和价值观。它决定了这个员工说话是什么调性、遇到模糊指令时倾向于保守还是激进、面对用户情绪时怎么回应。这是最底层的东西改一次影响全局。SKILL.md是这个员工的技能清单。它记录的是我会做什么比如会写代码、会做数据分析、会翻译、会总结长文。这是能力层面的描述偏向于知识和方法论。TOOLS.md是这个员工的工具箱和操作规范。它记录的是我手边有哪些具体设备、每个设备怎么开、什么情况下该用哪个。技能是抽象的工具是具体的。会写代码是技能但具体调用哪个编译命令、传什么参数、输出到哪里这是工具层面的事。我见过不少人把这三者混在一起写结果就是文件越来越臃肿模型读起来抓不住重点。分开写之后最大的好处是修改隔离。你想调整 AI 的性格只动SOUL.md想加一个新能力动SKILL.md想接入一个新接口动TOOLS.md。互不干扰回归测试的范围也小。2.2 为什么工具声明必须独立成文件有人会问既然SKILL.md已经在描述能力了为什么不把工具信息直接塞进去我的实践经验是技能是稳定的工具是易变的。一个 AI 的会总结这个技能可能半年都不会变。但它背后调用的总结工具可能这周用的是本地模型下周换成了云端接口参数格式完全不一样。如果两者混在一个文件里每次换工具都要动技能描述很容易改出问题。更关键的是上下文窗口的消耗。模型每次推理能读的内容是有限的。SKILL.md通常比较长因为技能描述需要展开讲。而TOOLS.md需要的是高密度、结构化、可快速检索。把工具信息独立出来可以针对性地做精简和格式化让模型在需要调用工具时能快速定位而不是在一大段技能描述里翻找。还有一个很实际的原因权限和审计。工具调用往往涉及实际操作比如发消息、写文件、访问网络。把这些单独放在一个文件里方便做权限控制和安全审查。你可以在TOOLS.md里明确标注哪些工具是只读的、哪些是写入的、哪些需要二次确认。这种信息混在技能描述里根本没法管。2.3 文件之间的引用关系怎么设计实际项目里这三个文件不是孤立的它们之间有引用关系。我的做法是在SKILL.md里描述技能时如果某个技能依赖具体工具就明确指向TOOLS.md里的对应条目。比如写具备发送消息的能力具体工具定义见 TOOLS.md 中的 message_send 条目。这样做的好处是单一事实来源。工具的具体参数只在TOOLS.md里定义一次其他地方只引用不重复。避免了改了一处忘了另一处导致的不一致。SOUL.md一般不直接引用工具但它会影响工具的使用策略。比如SOUL.md里如果设定这个 AI 倾向于谨慎涉及写操作前要确认那么TOOLS.md里对应的写工具就应该标注需要确认。这是一种隐式的协作。我整理了一个简单的对照表方便快速理解三者的差异维度SOUL.mdSKILL.mdTOOLS.md核心内容性格、价值观、语气能力清单、方法论工具定义、参数、调用规范变更频率极低低高面向对象模型的行为倾向模型的知识范围模型的执行动作典型条目回答要简洁会做数据清洗调用 clean_data 函数安全敏感度低中高这张表我在团队内部培训新人时一直在用基本上看一眼就能明白各自定位。3. TOOLS.md 内部结构该怎么设计3.1 单个工具条目的标准字段写TOOLS.md最核心的工作就是定义每一个工具条目。我试过很多种写法最后稳定下来的字段结构是这样的名称、描述、参数、返回值、调用时机、限制条件、示例。这七个字段缺一不可少一个都会在实际运行中出问题。名称必须是唯一的、机器可读的标识符用下划线或者驼峰都行但要全项目统一。我踩过的坑是早期混用了两种命名风格结果模型有时候会猜错名字。描述是给模型看的自然语言说明要写清楚这个工具做什么而不是怎么做。描述里不要塞实现细节那些放在别的地方。参数是最容易出问题的部分。每个参数要标明名称、类型、是否必填、取值范围、默认值。类型一定要明确是字符串还是数字还是布尔模型对类型很敏感。返回值要说明返回的数据结构尤其是当返回值会被后续步骤使用时。如果返回的是 JSON最好把字段结构写出来。调用时机这个字段很多人会忽略但它极其重要。它告诉模型什么情况下应该调用这个工具。写得好能大幅减少误调用。限制条件包括频率限制、权限要求、前置条件等。比如每分钟最多调用 10 次或者需要先完成认证。示例给出一到两个具体的调用例子包括输入和预期输出。模型通过示例学习的效果远好于纯文字描述。3.2 参数描述为什么最容易翻车我统计过自己项目里工具调用失败的原因超过六成是参数问题。不是模型不会调是参数没描述清楚。最常见的错误是类型模糊。比如写参数 count 表示数量模型可能传字符串 5 也可能传数字 5如果后端严格校验类型就会失败。正确写法是参数 count整数类型表示要处理的数量取值范围 1 到 100。第二个坑是枚举值没列全。比如一个工具支持多种模式描述里只写了mode 表示模式模型就会瞎猜。必须把所有合法值列出来比如mode字符串可选值为 fast、balanced、accurate默认 balanced。第三个坑是嵌套结构没说明。有些工具的参数是对象或者数组如果不把内部结构写清楚模型生成的 JSON 结构经常对不上。我的做法是直接给一个完整的参数示例让模型照着套。提示参数描述里尽量避免等等、之类的这种模糊词。模型会把这些当成真的还有别的选项然后开始编。3.3 调用时机描述怎么写才有效调用时机这个字段我一开始觉得可有可无后来发现它直接决定了工具调用的准确率。写得好模型知道什么时候该动手写得差模型要么该调不调要么不该调乱调。有效的调用时机描述应该包含触发条件和排除条件。触发条件是当用户请求 X 时调用排除条件是当 Y 情况下不要调用。举个例子一个发送消息的工具触发条件写当用户明确要求向某个联系人发送消息且已经提供了消息内容时调用。排除条件写当用户只是在讨论消息功能、或者询问如何发消息时不要调用。这种正反两面的描述能大幅降低误触发。我实测下来加了排除条件之后误调用率能降一半以上。还有一个技巧是优先级说明。当多个工具都能完成类似任务时要告诉模型优先用哪个。比如如果有本地工具和远程工具都能完成优先用本地工具因为更快。3.4 一个完整的工具条目示例光说理论不够我直接给一个实际项目里用过的条目你可以照着改### tool: file_read **描述**读取指定路径的文件内容返回文本。仅支持读取文本文件不支持二进制文件。 **参数** - path字符串必填文件的绝对路径或相对于工作目录的路径 - encoding字符串可选文件编码可选值为 utf-8、gbk默认 utf-8 - max_lines整数可选最多读取的行数默认读取全部取值范围 1 到 10000 **返回值** - 成功时返回对象{ success: true, content: 文件内容, lines: 行数 } - 失败时返回对象{ success: false, error: 错误原因 } **调用时机** - 当用户要求查看、读取、打开某个文件内容时调用 - 当需要获取文件内容作为后续处理输入时调用 - 当用户只是询问文件是否存在、或讨论文件相关话题时不要调用 **限制条件** - 单次读取不超过 10000 行 - 不支持读取二进制文件 - 路径必须在允许的工作目录范围内 **示例** 输入{ path: ./data/config.json, encoding: utf-8 } 输出{ success: true, content: {...}, lines: 42 }这个结构看起来有点啰嗦但正是这种啰嗦让模型调用时的准确率上了一个台阶。我对比过精简版和完整版完整版的首次调用成功率明显更高。4. 实操从零搭建一份可用的 TOOLS.md4.1 先盘点你手头到底有哪些工具动手写之前先做一件事把所有可调用的能力列出来。这一步很多人跳过直接开始写文件结果写到一半发现漏了工具又回头补结构就乱了。盘点的时候按来源分类。一类是系统内置工具比如文件读写、命令执行、网络请求这些框架自带的能力。另一类是自定义工具你自己封装的接口或者函数。第三类是外部集成工具比如对接的第三方服务。每一类下面再按功能分组。文件操作一组、网络操作一组、数据处理一组、消息通信一组。分组的好处是后面写文件时结构清晰模型检索也快。我一般会用一个简单的表格先做盘点确认没有遗漏再开始写正式文件工具名来源功能分组是否写操作优先级file_read内置文件操作否高file_write内置文件操作是高http_get内置网络操作否中message_send自定义消息通信是高这张表填完TOOLS.md的骨架基本就有了。4.2 按功能分组组织文件结构盘点完之后正式文件按功能分组来组织。我的习惯是用二级标题分大类三级标题放具体工具。这样模型在检索时可以先定位大类再找具体工具效率更高。大类的划分不要太细一般五到八个大类就够了。太细会导致模型在检索时来回跳太粗又起不到分类的作用。常见的分类有文件与目录操作、网络与请求、数据处理与转换、消息与通知、系统与命令、外部服务集成。每个大类开头写一段简短的说明告诉模型这个大类下的工具大概是什么用途。这段说明不用长两三句话就行但能帮模型快速判断该不该在这个大类里找工具。4.3 参数校验规则要写进文件这一点是我踩了大坑之后才补上的。早期我只写参数类型不写校验规则结果模型经常传一些边界值导致工具报错。后来我在参数描述里直接加上校验规则情况就好多了。校验规则包括数值范围、字符串长度限制、格式要求比如必须是邮箱格式、必须是 URL、枚举值列表。把这些写清楚模型在生成参数时就会自我约束。比如一个发送消息的工具接收者参数我会写recipient字符串必填必须是系统中已存在的联系人标识长度 1 到 64 个字符不能包含空格。这种详细的约束能挡掉大量无效调用。4.4 给每个工具标注安全等级安全等级这个字段是我强烈建议加的。把工具分成三个等级只读、写入、危险。只读工具随便调不会造成副作用。写入工具会改变状态调用前要谨慎。危险工具可能造成不可逆的影响比如删除文件、发送对外消息这类工具要标注需要二次确认。标注方式很简单在工具条目里加一行安全等级写入。然后在SOUL.md里约定调用写入及以上等级的工具前先向用户确认。这样两层配合安全性就有保障了。我实际项目里加了安全等级标注之后误操作导致的问题几乎归零。这个投入产出比非常高。5. 部署集成时的真实踩坑记录5.1 工具注册了但模型调不动这是最常见的问题没有之一。表现是TOOLS.md里明明写了工具模型也知道有这个工具但就是不调用或者调用时报工具不存在。排查思路我总结了一个顺序。第一步查文件是否被正确加载。很多框架需要显式指定要加载哪些 Markdown 文件如果TOOLS.md没在加载列表里写了等于没写。第二步查格式是否符合解析要求。不同框架对 Markdown 的解析规则不一样有的要求特定标题层级有的要求特定字段名。第三步查工具名是否和实际注册的一致。文件里写的是file_read代码里注册的是readFile对不上就调不动。我遇到过一次特别隐蔽的文件里工具名用了中文全角字符看起来和半角一模一样但解析时就是匹配不上。这种问题只能靠仔细检查字符编码来发现。5.2 参数传递总是格式错误参数格式错误的表现是工具被调用了但执行失败报参数不合法。原因通常是模型生成的参数结构和工具期望的不一致。解决办法有两个方向。一是在TOOLS.md里把参数结构写得更死直接给完整的 JSON 示例让模型照着套。二是在工具封装层做兼容处理对常见的不一致做自动转换比如字符串数字自动转数字。我倾向于两个都做。文件里写清楚是治本封装层做兼容是兜底。双保险下来参数问题基本能压到很低。5.3 工具调用陷入死循环这个坑比较隐蔽。表现是模型反复调用同一个工具每次都得到相似结果然后继续调停不下来。根本原因通常是工具的返回值没有给模型足够的终止信号。比如一个查询工具每次都返回没有找到结果但描述里没说没有结果时应该停止查询模型就会一直试。解决办法是在工具的返回值说明里明确写如果没有结果返回 success: false此时不应重试。同时在调用时机里加一条同一工具连续调用超过 3 次无有效结果时停止调用并告知用户。5.4 常见问题速查表我把实际项目中遇到的问题整理成了一张表方便快速对照排查现象可能原因排查方向解决方式工具完全不被调用文件未加载检查加载配置把 TOOLS.md 加入加载列表调用报工具不存在名称不匹配对比文件与代码统一命名参数格式错误结构描述不清检查参数定义补充完整示例反复调用不停止缺终止条件检查返回值说明加停止规则该调不调调用时机模糊检查触发条件补充正反描述不该调乱调缺排除条件检查排除描述加排除条件这张表我贴在工位上遇到问题先扫一眼大部分情况能直接定位。6. 让工具调用更稳的几个进阶技巧6.1 用示例驱动代替规则堆砌我早期写TOOLS.md喜欢堆规则一条接一条写得像法律条文。后来发现模型对规则的遵循度其实一般但对示例的模仿能力极强。所以现在我更倾向于多给示例少写抽象规则。一个工具给两到三个典型调用示例覆盖正常情况、边界情况、错误情况。模型看示例就能学会怎么调比读一堆规则有效得多。示例的写法也有讲究。不要只给输入要给完整的输入输出对。最好再配一句简短的说明讲清楚这个示例演示的是什么场景。6.2 给工具加使用场景标签除了调用时机我还会给每个工具加一组场景标签比如日常查询、批量处理、紧急操作。这些标签不直接给模型看而是用于我自己的管理和调试。当发现某类场景下工具调用频繁出错时我可以快速定位到相关工具集中优化。这种标签化管理在工具数量多的时候特别有用。6.3 定期做工具调用的回归测试TOOLS.md不是写完就完事的它需要维护。我给自己定的规矩是每次修改文件后跑一遍核心工具的调用测试。测试不用很复杂准备一组典型的用户请求看模型是否能正确选择工具、正确传参、正确处理返回。这组测试用例我维护在一个单独的列表里每次改完文件就跑一遍。这个习惯帮我挡掉了很多改了一处坏了另一处的问题。尤其是当工具之间有依赖关系时回归测试几乎是必须的。6.4 版本管理和变更记录TOOLS.md一定要纳入版本管理。每次修改都要有记录写清楚改了什么、为什么改。因为工具定义的变更会直接影响模型行为出问题时需要能回溯到具体是哪次改动导致的。我的做法是在文件末尾加一个变更记录区按时间倒序记录每次修改。格式很简单日期、修改人、修改内容、修改原因。这个记录在排查回归问题时价值极高。7. 关于这套文件体系的一些个人体会折腾这套东西大半年我最大的感受是AI Agent 的稳定性很大程度上不取决于模型多强而取决于你给它的边界多清晰。TOOLS.md就是画边界的那支笔。我见过太多项目模型选得很先进框架搭得很花哨但实际跑起来各种问题根源都在于工具定义含糊。反过来有些项目用的模型不算顶尖但TOOLS.md写得极其扎实运行起来反而很稳。还有一个体会是这套文件体系的价值会随着项目复杂度上升而放大。工具少的时候怎么写都行。工具一多没有清晰的结构和规范维护成本会指数级上升。所以我的建议是从一开始就按规范来写哪怕现在只有三五个工具。养成习惯之后后面扩展会轻松很多。最后分享一个小技巧把TOOLS.md当成一份要交给别人的文档来写。想象你明天要休假同事需要接手你的 AI 项目他能不能只看这份文件就搞清楚所有工具怎么用。如果能说明你写到位了如果不能说明还有模糊的地方需要补。这个标准比任何规范都管用。