做 Agent 开发的人,很多都有过这种经历:模型选的是当下最强的,框架用的是社区最火的,工具接了一大堆,结果一跑起来,Agent 不是东答西问,就是明明连着十个工具却只用一个。这时候大多数人的第一反应是换模型、调温度、重写 Prompt,折腾一圈没用,最后才发现问题出在那些根本没人愿意多看一眼的服务描述上。
服务描述,通俗点说,就是你给 Agent 看的"能力说明书":这个工具是干什么的、什么场景该用、参数怎么填、结果返回什么。模型不读你的代码,也看不到你的接口文档,它只能靠这段文字来判断"这个请求该不该调用这个服务、参数怎么传"。描述写得好,Agent 就是个靠谱的执行者;写得不好,它就是个对着工具瞎转悠的实习生,实在不行就自己编个答案糊弄你。
我把公开能接触到的 Agent 项目、工具市场、开源仓库里的服务描述翻了个底朝天,累计整理了 3.2 万条有效样本。这篇文章就是这次调研的完整复盘:3.2 万条描述里到底有哪些通病,真正能用的描述长什么样,以及一套可以直接照抄的六要素框架。如果你是做 Agent 应用开发、要给工具写接入说明,或者正在被"模型就是不调用我的服务"逼到怀疑人生,这篇应该能帮你少走好几个星期的弯路。
1. 先搞明白:服务描述在 Agent 里到底是干嘛的
1.1 Agent 不是靠代码读懂工具,而是靠描述
举一个我调试过的真实案例。有个项目接了一个天气接口,代码链路完全正常,工具也成功注册到了 Agent 引擎里。但用户问"昆明明天适合穿什么",Agent 就是不调天气服务,反而一本正经地编了一句"昆明明天多云,建议穿长袖外套"。如果把这类日志翻出来看,你会看到一个扎心的事实:模型不是不会调,而是根本不知道"用户问穿衣建议时该用天气工具"。
原因很简单,那个工具的服务描述只有五个字:"获取天气信息"。模型拿到用户问题之后,要在几十个工具描述里做匹配,"获取天气信息"和"用户想了解穿什么衣服"之间存在着巨大的语义鸿沟。模型拿不准这个工具能不能回答"穿什么"的问题,于是选择了最保守的策略——自己编一段看起来合理的回答。
原理其实很好理解。Agent 和传统程序不一样,它没有所谓"导过来的接口文档",模型感知外部能力的唯一通道就是服务描述。代码里那些精心实现的参数校验、异常处理、数据覆盖范围,模型一概不知。服务描述就是模型和外部世界之间的独木桥,桥窄了,什么东西都过不去。
打个生活化的比方。你给新来的实习生分配工作,如果只丢一句"你负责处理用户问题",他大概率会愣住:处理什么问题、用什么系统、边界在哪、卡住了找谁?而如果你写清楚岗位职责、处理流程、权限边界、例外情况,他第二天就能直接进入状态。模型其实比实习生更需要这份"岗位说明书",因为它连主动追问都不敢——描述里没写的,它就默认不存在,然后开始自由发挥。
1.2 描述质量直接影响 Agent 的整体表现
再往深一层说,服务描述不是锦上添花,而是直接决定 Agent 成败的基础设施。我见过很多项目,前期调试时模型死活不调工具,开发者怀疑是模型不行,把温度从 0.2 调到 0,换了更新的模型,重写了主提示词,问题依然在。最后回头把服务描述逐字逐句重写一遍,场景立刻通了。
这不是玄学,而是 Agent 的执行链路决定的。通常一次任务要经过这样的路径:模型读用户请求,扫描全部服务描述,判断哪个服务匹配,根据描述里的参数说明生成调用参数,工具执行并返回结果,模型再把结果整理成回答。纵观整条链路,服务描述在第一、二、三步都有参与。工具本身再快再稳,前面这几步走错了,后面全是白搭。
如果给 Agent 的行为做量化统计,服务描述质量差通常会暴露成三类指标异常:一是工具调用率偏低,模型宁可自由发挥也不用现有能力;二是参数错误率高,虽然调了工具,但传进去的日期格式、城市名、单位总是有偏差;三是任务完成率波动大,同一类问题时好时坏。这轮调研里我反复看到这三类问题的影子,几乎每一个都能回溯到描述本身的缺陷上。
1.3 服务描述不止工具描述一种形态
这里需要先做个概念清点。服务描述并不只在"工具"这一层出现,Agent 生态里至少存在四种常见形态:
- 工具型服务描述:描述一个 API、函数或 Skill 的用途和参数,最常见,也是后面六要素框架的主要应用对象。
- 子 Agent 描述:多 Agent 架构里,每个子 Agent 也有一份"身份说明",主 Agent 靠它判断"这个子任务该交给谁"。
- 插件描述:类似工具型,但往往包含更完整的触发条件、执行流程、权限声明,不少插件还要说明自己会读取哪些文件、访问哪些网页。
- 系统级 Agent 描述:定义整个 Agent 的定位、边界和回答风格,市面上多数平台里叫"人设"或"系统提示词"。
这四种形态的载体和颗粒度不同,但底层逻辑完全一致:用一段模型能高效解析的自然语言,把"能力边界"和"使用方式"说清楚。后面要讲的写作框架对这四种形态都适用,只是不同形态侧重点略有差异,这一部分我会在第三章节单独展开。
2. 翻了 3.2 万条之后,最常见的五种通病
2.1 先把研究样本和方法说清楚
在讲结论之前,先交代一下这 3.2 万条是怎么来的。样本主要来自四个渠道:公开的 Agent 项目仓库,重点看工具配置、Skill 定义、能力清单;国内外主流 Agent 平台和工具市场,看开发者在发布工具时填写的说明文本;几个主流框架自带的示例和官方模板;以及社区里流传的提示词库、工具合集中的描述片段。去重、筛掉明显无效的空文本之后,累计保留了 3.2 万条有效描述。
分析方法不复杂。先把样本通读一遍建立整体感觉,再按质量粗分三档:明显不合格、中规中矩、值得借鉴。之后对明显不合格和中规中矩的样本做模式归纳,最后把"值得借鉴"的样本逐条拆解,提取它们的共性。这里先放一个最直接的结论:真正能达到"看到一个描述,就知道怎么调、什么时候调、调完怎么用"这一标准的样本,占比非常低。大量描述连最基本的功能边界都没讲清楚。
2.2 通病一:只有名字,没有上下文
最常见的低质量描述,就是一句话版。比如"获取天气""发送邮件""查询订单"。这类描述说起来言简意赅,但对模型来讲,信息量几乎为零。
我拿一个只有"获取天气信息"五个字的工具做过一次对照实验。用户问"昆明明天适合穿短袖吗",模型完全不调用工具,直接凭训练数据里的常识编了一段天气,还煞有介事地给出了穿衣建议。整个回复流畅、自信,但没有任何真实数据支撑。这就是典型的有工具不会用——描述里没有给模型任何"这件事该用我"的提示,模型宁可自己编答案,也不敢把一个它理解不了的请求托付给工具。
这类问题很有迷惑性,因为代码里"getWeather"这个函数名本身是自解释的,开发者看代码觉得没问题,但模型看到的不是函数名,而是你在工具配置里写的那段描述。函数名再清晰,描述里没写出来,模型也感知不到。
2.3 通病二:参数说明随意,模型瞎填
只写功能、不写参数,是第二类高频毛病。比如描述里写"输入:城市",但没说清楚要的是城市中文名、城市代码还是拼音,更别提格式、单位、取值范围和必填项。
日期参数是最容易翻车的重灾区。用户说"帮我定下周三早上 8 点的闹钟",如果描述里没有写明"日期请按 YYYY-MM-DD 格式输出,并基于今天日期自动换算",模型很可能把"下周三"这三个字原封不动地当成参数传进去,工具拿到之后自然是解析失败。还有一类是单位问题:有的接口温度单位是摄氏度,有的在内部换算成华氏度,描述里不写,模型传出来的值可能直接差出几十度。模型对参数的生成完全依赖描述里的约束,约束越清晰,参数就越准确。
2.4 通病三:只写做什么,不写什么时候不该做
很多开发者以为服务描述就是把"做什么"写清楚就够了,但模型的匹配机制决定了,你还得把"不做什么"一起写明白。
举个例子。某个天气工具只支持国内城市,描述里没写这个限制。用户问"东京明天天气如何",模型检索能力清单时发现这个工具名字里带"天气"两个字,触发了匹配,于是一通调用,结果返回查询失败。这就是典型缺乏边界条件的后果。模型不会自动知道你的数据覆盖范围、权限边界、支持的语言种类、时间粒度,你不主动声明,它就会默认"什么都能干"。
更微妙的是多工具场景。假设系统里同时有"天气查询"和"空气质量查询"两个工具,前者描述里没写"本服务不含空气污染数据",用户问"今天空气好不好",模型可能随机选一个,甚至两个都调,返回结果又互相矛盾。边界声明就是在帮模型做排除法,排除得干净,模型才能选得果断。
2.5 通病四:不给示例,全靠模型猜
文本描述写得再完整,终归是抽象规则,而模型对具体示例的模仿能力远强于对抽象规则的解析能力。这个特性在服务描述里可以发挥到极致:写一个"用户问题 → 调用参数 → 返回结果"的完整示例,模型遇到类似场景时,会直接模仿示例来生成调用。
我改过一个订单查询工具。原始描述只有一句话"查询用户订单",加上一条示例之后,模型调用参数的正确率肉眼可见地高了。那条示例是这样写的:当用户说"我上个月买的东西发货了吗"时,应传 userId 等于用户 ID、orderTimeRange 设为最近 30 天、status 置空,返回结果里优先提取物流状态字段。模型看完示例,基本能依葫芦画瓢:遇到"我买的东西到哪了",它知道要查物流状态;遇到"我买过哪些东西",它知道要罗列订单列表。示例把抽象规则翻译成了可模仿的行为模式,而这正是模型最擅长做的事情。
2.6 通病五:描述写成散文,有效信息被稀释
与"太短"相对的另一个极端,是"太长太散"。有些开发者生怕模型理解不了,把服务描述写成了一篇小作文,前面还铺一层营销话术:"本工具采用先进技术,功能强大、性能卓越、智能高效,是你最值得信赖的选择。"
这类文字对模型来说就是信息噪声。模型处理文本时,注意力资源是有限的,一长串形容词和宣传语会稀释真正重要的指令信息。我在 3.2 万条样本里见过不少这样的"散文式描述",读的时候感觉业务方很用心,洋洋洒洒写了一大段,但套到实际任务里,模型反而抓不住重点,该触发的没触发,该传对的参数传错。
3. 高质量服务描述怎么写:六要素框架直接套
3.1 六要素总览:一段描述该包含什么
先亮框架。在拆解了大量优质样本之后,我把服务描述的核心信息归纳成六个要素,这六个要素基本能回答模型做决策时的所有疑问:
| 要素 | 要解决的问题 | 写作要点 |
|---|---|---|
| 一句话职责声明 | 模型快速判断"这个服务是不是干这个的" | 动词开头,写明对象与动作 |
| 触发条件 | 模型决定"现在该不该用它" | 写明适合场景,也要写排除场景 |
| 输入参数说明 | 模型生成调用参数不乱填 | 写清类型、格式、单位、必填项 |
| 输出格式说明 | 模型解析返回结果不困惑 | 写清结构、字段含义、示例值 |
| 边界与禁忌 | 模型不会把不合适的任务硬塞进来 | 写明能力范围、数据限制、权限前提 |
| 典型示例 | 模型有可模仿的完整流程 | 给一段用户问法加对应调用参数 |
要特别说明的是,六要素并不需要每一条都在所有场景下大篇幅展开。工具很简单时,触发条件和边界可以直接合并成一句话;服务逻辑复杂时,示例可以写两到三个覆盖不同分支。但"职责、参数、边界、示例"这四个关键要素,任何情况下都不建议省,省掉哪一个,模型都会在对应环节多一次猜错的机会。
3.2 可以直接抄的通用模板
下面这个模板可以直接复制去改,结构上是六要素的展开版:
服务名称:... 一句话职责:这个服务负责... 触发场景: - 适合:用户询问...,或表达...意图时 - 不适合:用户询问...,或数据范围超出...时 输入参数: - param1(类型,必填):说明。示例:... - param2(类型,选填):说明。示例:... 输出格式: - 返回结构:{字段:含义} - 失败时返回:{error:原因} - 回答用户问题时,优先使用...字段 边界与禁忌: - 仅支持... - 不支持... - 查不到结果时,请明确回复无法查询,不要编造数据 典型示例: 用户:... 调用参数:... 返回结果:...模板里"一句话职责"我建议用动词短语开头,比如"查询并返回指定城市的实时天气",而不是"天气工具"。原因是模型做能力匹配时,动词短语更接近自然指令,语义命中率更高。参数部分要区分必填和选填,还要写明什么情况下可以省略;输出格式不能只列字段名,最好说明模型在组织最终回答时应该优先使用哪个字段,这能直接提升回答质量。
3.3 触发条件和边界词怎么写才有用
触发条件是最容易被低估的要素。很多开发者只会写正向触发:"用户询问天气时调用",但模型在实际匹配中更需要反向排除。以天气和空气质量工具混用的例子来说,如果天气工具描述里的触发条件写成"用户询问天气、气温、降水、穿衣建议时触发;本服务不包含空气质量与污染指数信息,该类问题请不要调用",模型的选择就会清晰很多。
写触发条件时,用词越具体越好。与其写"与天气相关的问题",不如枚举几种典型问法:"用户问明天出门要不要带伞""用户问某地温度冷不冷""用户问周末适不适合露营",让模型的匹配有据可依。边界部分则要说得绝对一些,模型是概率推理系统,你不写"不要编造",它在查询失败之后真的会编一个看起来合理的答案。
3.4 不同 Agent 形态的侧重点差异
六要素是通用框架,但不同形态的描述,核心侧重点要跟着变。工具型服务描述要重点打磨参数与输出格式,因为工具对接的是真实 API,字段错一个就可能整个报错;子 Agent 描述则要把重心放在职责边界和协作说明上,写清楚"我擅长什么、不擅长什么、什么情况应该把任务转交给其他子 Agent",否则多 Agent 编排时容易出现职责重叠和互相推诿。
插件和 Skill 描述与工具型类似,但额外要说明执行步骤和权限范围。尤其像读取本地文件、访问外部网页这类带副作用的操作,务必要写明"需要用户授权后才可执行",这也是一道安全边界。系统级 Agent 描述则重在整体风格与通用边界,服务级别的细节交给各层级描述去展开,不必全部堆在最顶层,否则主题太长,反而稀释核心人设。
4. 实战复盘:把一条天气服务描述从 17 个字改到 200 字
4.1 原始版本为什么不行
为了把六要素框架落到实地,我用一个常见的例子完整复盘一遍。假设你有一个查询实时天气的工具,原始描述是这样的:
获取天气信息。输入:城市。返回:天气。这条描述刚好踩中了前面提到的至少三类通病。第一,没有触发条件,模型不知道"穿衣建议"这类问题也归它管;第二,参数只说"城市",没写类型和格式,模型不知道传中文名还是拼音,更不知道用户提到地标时需要先换算;第三,输出只写了"天气"两个字,模型既不知道返回结构,也不知道该怎么把结果转述给用户。没有边界,模型默认它能查全世界;没有示例,参数怎么写全靠猜。
用这条描述上线的结果是:模型偶尔能调对,但只限于用户问法非常直白的情况,比如"北京天气怎么样"这种描述里直接出现过词的问法。一旦问题绕个弯,比如"明天去上海,需要带伞吗",模型就容易犹豫,甚至干脆放弃调用工具直接编答案,因为"带伞"这个意图和"获取天气信息"之间的语义关联太弱了,描述里没有任何提示告诉模型"可以用降水概率来回答带伞问题"。
4.2 四轮迭代,逐层加东西
第一轮,先补上职责声明和触发条件:
服务名称:实时天气查询 职责:根据用户提供的城市和日期,返回该城市的实时天气与近期预报。 触发条件:用户询问天气、气温、降水、风力、穿衣建议等相关问题时使用。这一轮改完,模型至少不会被"穿衣"这类问题绕晕。但参数说明还是模糊,继续第二轮。
第二轮,把参数说清楚:
输入参数: - city(字符串,必填):城市中文名,如"北京"。 若用户只说了地标、景区或区域名,请先换算为所属城市,如"上海迪士尼"→"上海"。 - date(字符串,选填):日期,格式 YYYY-MM-DD。缺省表示今天。 - unit(字符串,选填):温度单位,celsius 或 fahrenheit,缺省 celsius。这一轮的亮点是给了一个换算规则。模型遇到"明天去上海迪士尼穿什么"这种问题,就能把地标换算成上海,再触发天气查询,而不是卡在"这是哪个城市"上。
第三轮,补输出说明和边界条件:
输出格式: - 返回 JSON,主要包含 current(当前天气)、forecast(逐日预报)、tips(穿衣建议)字段。 - 回答用户时,优先使用 tips 字段组织成自然语句。 边界与禁忌: - 仅支持中国大陆城市,不支持港澳台及海外城市。 - 不包含空气质量/污染指数信息,此类问题请不要调用本服务。 - 查询失败或城市不支持时,请明确回复无法查询,不要编造数据。到了这一轮,描述已经覆盖了绝大多数真实情况。第四轮,加上典型示例收尾:
典型示例: 用户:"北京明天需要穿羽绒服吗?" 调用:city=北京, date=2025-01-10, unit=celsius 返回:{"current": {...}, "forecast": [...], "tips": "北京明天最低气温零下6℃,建议穿羽绒服、毛衣和厚裤子。"}一个完整链路示例放进去,模型对"穿衣相关提问"的调用行为马上就稳了。因为它能看到用户怎么问、参数怎么填、返回结果怎么用这三者之间的对应关系。
4.3 改完之后的效果对比
这条描述从最初的 17 个字符改到 200 字左右,全程没有换模型、没有改框架、没有调任何推理参数,只动了服务描述本身。
在我自己搭的一组测试问题集上,效果变化非常直观。首先是工具调用率明显上升,原先部分"擦边"问题模型会直接编答案,改完之后绝大多数问题都会先尝试调用工具;其次是参数正确率大幅提升,最突出的是日期格式和地标换算基本不再出错;最后是回答可控性变好,有了输出字段的指示,模型不再自由发挥,而是按 tips 字段里的信息组织语言。
说实话,描述不是魔法,它不能让一个本身就残缺的工具变得无所不能,但它能把工具已经实现的能力真正暴露给模型。你每多写一个要素,都是在帮模型降低一次猜错的概率。很多团队花大精力选模型、调框架,却连这种几乎零成本的优化都不做,属实可惜。
5. 高频问题排查与调试实录
5.1 模型不调用服务,先查这四件事
遇到"模型就是不调用我的服务",别急着怀疑模型,按顺序排查四件事。
第一,服务描述里有没有明确的触发场景。如果描述只有"获取信息"这种没头没尾的话,模型压根没有触发依据。改法很直接:把用户可能会说的三四句典型问法直接写进描述里,让模型看到匹配点。第二,描述里的用词是否贴近用户的自然表达。模型做能力匹配时依赖描述文本与用户问题之间的语义相似度,你写的是"获取气象数据",用户说的是"明天穿什么",这中间的语义距离就需要靠描述里的触发条件去拉近。第三,Agent 引擎本身的配置是否正确。有些框架要求注册时的 name 字段与代码中的函数名保持一致,还有的要显式声明启用状态,配置错位的话,描述写得再好也白搭,这时候要翻运行时日志确认工具确实被加载了。第四,是否存在描述过于相似的其他服务。两个服务描述高度雷同,模型会因分不清而随机挑一个,导致另一个始终不被调用,把各自的边界写明确就能解决。
5.2 模型总传错参数,可能是描述挖的坑
参数传错,先别急着怪模型,回头看看描述里有没有埋坑。
最常见的几类坑包括:没写类型,模型把布尔值传成了字符串;没写格式,日期传成"明天"而不是 YYYY-MM-DD;没写单位,温度值直接按华氏度传;没写取值范围,数字参数传了负数。请在描述里为每个参数写清五样东西:类型、必填还是选填、格式、示例值、约束条件。
还有一类更隐蔽的问题:参数与参数之间存在依赖关系。比如"当 startTime 为空时,endTime 也必须为空",这种约束模型很难自己推出来,建议直接写成明确指令"若 startTime 为空,则忽略 endTime"。模型对自然语言指令的遵循能力,比你想象中强很多,前提是你真的把它写出来。
5.3 多个服务互相抢调用,怎么划清边界
多个服务并存时,最怕的是边界不清。我的经验是:先给每个服务安排一个"专属场景",确保至少有一种用户问法会让模型稳定选到它,然后再用排除法处理重叠区。
举个例子,系统里同时有"天气查询"和"穿衣建议"两个服务,前者提供原始气象数据,后者基于气象数据生成穿搭建议。如果不加边界,模型面对"明天北京穿什么"这个问题,可能两个都调,给用户返回两段冗余信息。更好的做法是让天气查询描述里写明"穿衣建议请交给穿衣建议服务,本服务只提供原始气象数据",同时让穿衣建议服务描述写明"本服务依赖天气查询获取温度和降水数据,若用户未提供城市信息,请先向用户确认"。边界一旦写明白,模型就不会在服务之间左右横跳。
5.4 一个反向问题:服务被过度调用
有些服务不是没人调,而是什么都被往它身上塞。我在调研里也见过不少这类案例,根源往往是触发条件写得太宽泛,比如"天气相关问题时调用",结果用户问"明天空气质量怎么样",模型也把空气质量问题甩给了天气服务。
解决思路是给描述加一个"不适用场景"清单,而且这种清单应该越具体越好。数据类服务尤其适合这样做,比如气象工具可以明确写"不包含空气污染、紫外线指数、日出日落数据"。对于执行成本较高的操作,还要写明触发门槛,比如"只有在用户明确要求执行时才能调用,禁止仅凭猜测触发"。这个写法不仅是功能需要,也是一种安全约束,能防止 Agent 在不合适的时机做出有副作用的动作。
6. 两个让我省下大量返工的习惯
6.1 给服务描述建一个专属测试集
描述写得好不好,不能靠感觉,一定要有可回归的验证手段。我强烈建议每个 Agent 项目维护一份测试集:20 到 30 条覆盖典型用户提问的小样本,每条标注期望行为,包括期望调用哪个服务、期望参数是什么、期望回答用什么字段。
每次改完描述,就把这套测试集完整跑一遍,记录"调用是否命中、参数是否准确、回答是否符合预期"。它的价值和代码单元测试一样大,因为服务描述之间会互相影响:你改了 A 服务的描述,可能意外让 B 服务的匹配率下降,没有测试集你根本发现不了。我用 JSON 文件维护测试集,按场景分组管理,每隔两周补几条新出现的边界提问,随着 Agent 能力增加,这套测试集会慢慢变成一个覆盖核心场景的验收清单。
6.2 服务描述要像代码一样进版本管理
服务描述是运行时要被模型读取的"代码"级产物,必须放进版本管理,不能只躺在平台后台里。它要跟代码一起提交、一起评审、一起回滚。
比较推荐的做法是为每个服务维护一份 Markdown 或 YAML 格式的描述文件,放在 services 目录下,由主程序加载。这样描述既能被运行时读取,也能被人读、被评审、被 diff。每次改动都留下 commit 记录,等 Agent 行为突然异常时,可以直接对照描述变更记录定位原因。
我在实际项目中遇到过"前一天还好好的,第二天突然不调工具"的情况,最后排查发现不是模型变了,而是有人手滑把描述里的触发条件改窄了。如果当时没有版本记录,这类问题查起来基本只能靠肉眼翻后台,效率和体验都非常差。
6.3 最后说一个关于写作心态的建议
从这 3.2 万条样本里,我总结出一个比较实用的心态:不要试图一次把描述写到完美。服务描述和代码一样,需要小步迭代、测试驱动、持续重构。第一次能覆盖八成常见场景,就已经超过大多数项目了,剩下的问题会在真实使用里逐渐暴露,只要你留了测试集和版本记录,修补成本就会非常低。
我自己的习惯是,每接入一个新工具,先写一版只覆盖核心场景的粗略描述,然后边测边改,迭代个三五轮之后才会稳定下来。比起闷头写一上午"完美描述",这种先跑起来再打磨的方式,效率高得多,也更能真实地反映出模型到底能理解到哪一层。写描述这事没什么高深诀窍,无非是不断把"模型猜不到的信息"提前写进去,让它的每一步选择都有据可依。