1. 从单体接口到智能体协作:为什么传统API撑不住AI应用了
过去两年我经手过不少AI应用的后端改造项目,从智能客服到自动化数据分析流水线,几乎每一个都踩过同一个坑:用传统RESTful接口去对接大模型和智能体,前期跑得挺顺,一旦并发上来、任务链路变长,整个系统就开始各种超时、状态错乱、成本失控。这个项目标题“为AI应用构建可扩展、智能体友好的API”,说的正是这件事——它不是让你把现有接口加个/v1/chat路径就完事,而是要从设计范式上重新思考:当调用方从“人写的代码”变成“会自主决策的智能体”时,API该长什么样。
先把这个概念拆开讲清楚。可扩展在这里有两层含义:一是横向扩容能力,请求量涨十倍时不用重写架构;二是语义可扩展,新增一个工具或能力时,不需要改动调用方的核心逻辑。智能体友好则更微妙,它指的是API的设计要符合智能体的“认知习惯”——智能体不像人类开发者那样会读文档、会试错,它依赖结构化的描述、明确的错误语义、可预测的返回格式来规划自己的下一步动作。一个对智能体友好的API,本质上是在用机器能理解的方式“自我介绍”。
这套东西适合谁来参考?如果你正在做这几类事情,那这篇内容基本就是为你写的:正在给大模型接工具调用能力的后端工程师、在设计多智能体协作平台的架构师、把内部系统暴露给AI助手的产品技术团队,以及任何发现“接口能跑但一上量就崩”的开发者。哪怕你只是刚接触function calling,看完也能明白为什么有些接口设计让模型“用一次错一次”。
我个人的判断是,未来两年内,API的消费方会从“人类工程师为主”逐渐转向“智能体为主、人类为辅”。这个转变不是渐进的,而是会在某个临界点突然加速。现在把API的智能体友好度做扎实,等于提前把地基打好了。下面我会从设计思路、核心细节、实操落地、问题排查四个层面,把这套方法论完整拆一遍,中间会穿插大量我在实际项目里踩过的坑和验证过的参数。
2. 整体设计思路:把API当成“给智能体看的说明书”来设计
2.1 核心矛盾:智能体不会读你的Swagger文档
传统API设计的隐含假设是:调用方是人类开发者,他会读文档、理解业务语义、处理边界情况。所以我们可以写“参数type传1表示A,传2表示B”这种只有看了文档才懂的约定。但智能体不这么工作。它拿到的是一个工具描述(tool description),然后基于这个描述和当前任务上下文,决定要不要调用、怎么传参。如果描述模糊、参数含义不直观、错误返回没有指导性,智能体就会反复试错,token成本飙升,任务成功率暴跌。
我做过一个对比实验:同一个“查询订单状态”的能力,用两种方式暴露给智能体。第一种是传统风格,接口叫/api/order/query,参数是orderId和statusCode(0全部/1待付款/2已发货)。第二种是智能体友好风格,工具名get_order_status,描述里明确写“根据订单号查询当前物流和支付状态,返回人类可读的状态文本”,参数只有order_id一个必填项。结果第二种的任务一次成功率比第一种高出将近40%,平均调用轮次少了1.8轮。这个差距不是模型能力问题,纯粹是接口设计问题。
所以整体设计的第一原则是:API的每一个对外暴露的单元,都要自带完整的、自解释的语义。工具名要动词开头、见名知意;描述要写清楚“什么时候该用、什么时候不该用、返回什么”;参数要尽量扁平、避免嵌套过深;错误返回要告诉智能体“你哪里错了、下一步可以怎么改”。
2.2 方案选型:为什么我最终选了“工具网关+能力注册中心”这套组合
在架构层面,我试过三种方案。第一种是直接在业务服务上加一层薄薄的适配层,把现有接口包装成工具。这种做法上手快,但很快会遇到问题:工具描述散落在各个服务里,版本不一致,新增能力要改多处,扩展性极差。第二种是引入一个独立的“工具服务”,所有能力都注册到这里,业务服务只负责执行。这个方案解决了统一管理的问题,但工具服务和业务服务之间的耦合又成了新麻烦,每次业务变更都要同步改工具定义。
最终我采用的是第三种:工具网关 + 能力注册中心 + 执行器分离。工具网关负责对外暴露统一的工具列表和调用入口,能力注册中心维护所有工具的描述、参数schema、版本信息,执行器则负责把调用请求路由到真正的业务逻辑。这三者通过一份标准化的“能力描述文件”解耦。新增一个能力时,只需要在注册中心登记一份描述,执行器实现对应逻辑,网关自动就能暴露出去。这套结构的好处是,智能体看到的是稳定的工具契约,而底层业务怎么变都不影响它。
这里有个关键决策点:能力描述文件用什么格式。我对比过OpenAPI、JSON Schema和自定义DSL。OpenAPI太啰嗦,一个简单工具的描述能写上百行,智能体解析起来负担重;自定义DSL灵活但生态不成熟,团队学习成本高。最后我选了JSON Schema作为参数描述的基础,外面包一层轻量的工具元数据。这样既复用了成熟的schema校验能力,又能把描述控制得足够精简。实测下来,一个典型工具的完整描述能控制在30行以内,智能体解析又快又准。
2.3 可扩展性的三个维度:别等到崩了才想扩容
很多人理解的“可扩展”就是加机器。但在AI应用场景里,可扩展性至少有三个维度,缺一不可。
第一个维度是连接可扩展。智能体的调用模式跟人类用户完全不同,它可能在几秒内发起几十个并发调用,也可能长时间挂起等待一个异步结果。传统的短连接、同步阻塞模型根本扛不住。我的做法是全面转向异步优先:所有工具调用默认返回一个任务ID,智能体通过轮询或回调获取结果。这样连接数不再受限于并发请求数,而是受限于任务队列长度,扩容时只需要加消费者即可。
第二个维度是语义可扩展。当业务新增一个能力时,理想情况是智能体不需要重新学习就能用上。这要求工具描述遵循统一的命名和结构约定。比如所有查询类工具都以get_或list_开头,所有操作类工具都以create_、update_、delete_开头,参数命名统一用下划线风格,返回结构统一包含status、data、error三个顶层字段。这些约定看起来是小事,但能让智能体在遇到新工具时快速迁移已有认知。
第三个维度是成本可扩展。AI应用的调用成本跟token消耗直接挂钩,而token消耗又跟工具描述长度、返回内容长度、错误重试次数强相关。如果工具描述写得太长,每次调用都要把这段描述塞进上下文,成本会线性上升。我的经验是,单个工具的描述控制在200个token以内,返回内容默认只给必要字段,详细数据通过分页或按需获取。这样即使工具数量从10个涨到100个,上下文成本也不会失控。
3. 核心细节解析:智能体友好API的五个关键设计点
3.1 工具描述怎么写,智能体才不犯迷糊
工具描述是智能体理解能力的唯一入口,写得好不好直接决定调用成功率。我总结了一个“四要素”模板,每个工具描述都必须包含:做什么、什么时候用、参数含义、返回什么。
举个例子,假设有一个“发送通知”的能力。差的描述是:“发送通知,参数:userId, content”。好的描述是:“向指定用户发送一条站内通知。当需要告知用户某个事件结果、提醒待办事项或推送系统消息时使用。参数user_id为目标用户的唯一标识,content为通知正文,不超过500字。返回通知ID和发送状态。”
差别在哪?好的描述里,“什么时候用”帮智能体做了决策判断,“参数含义”消除了歧义,“返回什么”让它知道调用后能拿到什么。我实测过,加上“什么时候用”这一句,智能体误调用率能降低一半以上。因为它不再需要猜测这个工具是否适用于当前场景。
还有一个细节:避免在描述里使用否定句。比如“不要用这个工具查询历史数据”,智能体对否定句的处理能力很弱,很容易忽略“不要”两个字。正确的做法是正面引导:“此工具仅用于查询实时数据,历史数据请使用query_history工具”。把否定转成正面指引,效果天差地别。
3.2 参数设计:扁平化、强类型、给默认值
参数设计是另一个重灾区。我见过太多接口把参数设计成嵌套对象,比如{"filter": {"timeRange": {"start": ..., "end": ...}, "status": [...]}}。人类开发者看着还行,智能体解析起来就容易出错,尤其是当嵌套层级超过两层时,它经常漏传或多传字段。
我的原则是参数尽量扁平,最多一层嵌套。上面那个例子可以改成start_time、end_time、status_list三个平级参数。虽然看起来没那么“优雅”,但智能体调用准确率明显更高。
强类型也很关键。参数类型要明确是string、integer、boolean还是array,并且给出取值范围或枚举值。比如状态参数不要写“传对应数字”,而是直接列出["pending", "processing", "completed"]。智能体看到枚举值,基本不会传错。对于可选参数,一定要给默认值,并在描述里写清楚默认行为。这样智能体在信息不全时也能做出合理决策,而不是卡住不动。
注意:参数名统一用下划线风格(snake_case),不要混用驼峰。智能体在生成参数时对命名风格的敏感度比人类高,混用风格会导致它频繁生成错误字段名。
3.3 返回结构:让智能体一眼看懂“成功还是失败”
返回结构的设计目标只有一个:智能体不需要额外推理就能判断调用结果。我见过一些接口,成功时返回业务数据,失败时返回HTTP 200加一个{"code": 500, "msg": "error"}。这种设计对人类开发者都要看文档才懂,对智能体简直是灾难。
我的标准返回结构是三个顶层字段:status、data、error。status只有两个值:success或failed。data在成功时包含业务数据,失败时为null。error在失败时包含code、message、suggestion三个子字段,其中suggestion是给智能体的修复建议。比如参数缺失时,suggestion写“请补充user_id参数后重试”。智能体拿到这个建议,下一轮就能自动修正。
对于列表类返回,一定要包含分页信息:total、page、page_size、has_more。智能体需要知道“还有没有更多数据”来决定是否继续拉取。如果没有has_more,它要么反复拉取直到空结果(浪费调用),要么只拉一页就停(数据不全)。加上这个字段,行为就明确了。
3.4 错误语义:把错误当成“教学信号”而不是“终止信号”
传统API里,错误就是错误,调用方自己处理。但在智能体场景里,错误是智能体学习如何正确调用的重要信号。如果错误信息只是“参数错误”,智能体不知道错在哪,只能盲目重试。如果错误信息是“参数start_time格式错误,应为ISO 8601格式,例如2024-01-01T00:00:00Z”,智能体下一轮就能改对。
我把错误分成三类,每类给不同的处理策略。第一类是可自修复错误,比如参数格式错、必填项缺失、枚举值不在范围内。这类错误返回详细的修正建议,智能体通常一轮就能改对。第二类是需人工介入错误,比如权限不足、资源不存在。这类错误返回明确的终止信号,避免智能体无限重试。第三类是临时性错误,比如限流、下游超时。这类错误返回retry_after字段,告诉智能体等多久再试。
这里有个坑我踩过:早期我把所有错误都返回HTTP 400,结果智能体分不清哪些能重试哪些不能,经常在权限错误上死循环。后来改成用不同的error.code区分,并在描述里写明哪些code可重试,问题才解决。错误码不要用纯数字,用有语义的字符串,比如INVALID_PARAM、PERMISSION_DENIED、RATE_LIMITED,智能体对字符串语义的理解能力远强于数字。
3.5 幂等与状态管理:智能体最怕“重复执行”
智能体的一个典型行为是:调用一个工具后没收到明确结果,它会重试。如果这个工具不是幂等的,重试就会产生副作用。比如“创建订单”被调用两次,就生成了两个订单。这在AI应用里是高频问题。
我的做法是所有写操作强制要求传入幂等键(idempotency_key)。智能体在第一次调用时生成一个唯一键,重试时带上同一个键,服务端根据键去重。这个键可以由智能体自己生成(比如用任务ID加时间戳),也可以由网关在首次调用时返回、后续重试时复用。实测下来,加上幂等机制后,重复创建类问题基本归零。
状态管理方面,对于长耗时任务,我采用“提交-轮询”模式。调用工具时立即返回task_id和status: pending,智能体通过get_task_result工具轮询。轮询间隔建议在描述里写明,比如“建议每2秒轮询一次,最多轮询30次”。这样智能体不会疯狂轮询把服务打挂,也不会等太久导致任务超时。对于超过30次还没完成的任务,返回status: timeout并建议智能体转为异步通知模式。
4. 实操落地:从零搭建一套智能体友好的API网关
4.1 能力注册中心的表结构设计与字段说明
落地第一步是把能力注册中心建起来。我用的是关系型数据库存元数据,因为工具数量通常不会太大(几百到几千),关系型足够用,而且事务支持好。核心表叫tool_registry,字段设计如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| tool_id | varchar(64) | 工具唯一标识,如get_order_status |
| display_name | varchar(128) | 人类可读名称 |
| description | text | 工具描述,遵循四要素模板 |
| parameters_schema | json | JSON Schema格式的参数定义 |
| returns_schema | json | 返回结构定义 |
| version | varchar(16) | 语义化版本号 |
| status | varchar(16) | active/deprecated/disabled |
| executor_endpoint | varchar(256) | 执行器路由地址 |
| created_at | timestamp | 创建时间 |
| updated_at | timestamp | 更新时间 |
这里有几个设计决策值得说明。tool_id用动词开头的下划线命名,是为了让智能体在只看名字时就能大致判断用途。parameters_schema用标准JSON Schema,是为了复用现有的校验库,同时让智能体解析有统一格式。status字段支持工具下线但不删除,因为智能体可能还在用旧版本,直接删除会导致调用失败。version字段配合status实现灰度下线:先把旧版本标记为deprecated,观察一段时间没有调用后再disabled。
提示:
description字段建议限制在500字符以内。超过这个长度,智能体解析负担加重,而且很多模型对超长描述的处理会截断,导致关键信息丢失。
4.2 工具网关的核心路由逻辑与代码骨架
工具网关是整个系统的入口,它要做三件事:接收调用请求、校验参数、路由到执行器。我用Python写了一个轻量网关,核心逻辑大概长这样:
async def handle_tool_call(tool_id: str, params: dict, idempotency_key: str): # 1. 查注册中心,获取工具元数据 tool = await registry.get_tool(tool_id) if not tool or tool.status != "active": return error_response("TOOL_NOT_FOUND", "工具不存在或已下线") # 2. 幂等检查 if idempotency_key: cached = await idempotency_store.get(idempotency_key) if cached: return cached # 直接返回上次结果 # 3. 参数校验 validation = validate_schema(params, tool.parameters_schema) if not validation.ok: return error_response( "INVALID_PARAM", validation.message, suggestion=validation.suggestion ) # 4. 路由到执行器 executor = get_executor(tool.executor_endpoint) result = await executor.execute(params) # 5. 缓存幂等结果 if idempotency_key: await idempotency_store.set(idempotency_key, result, ttl=3600) return result这段代码里,幂等检查放在参数校验之前是关键。因为重试请求可能带着已经修正过的参数,如果先校验参数,可能因为参数格式问题直接返回错误,而实际上上次调用已经成功了。先查幂等缓存,能保证重试请求直接拿到上次结果,避免重复执行。
参数校验部分,我用了JSON Schema的jsonschema库,但做了一层封装,把校验失败信息转成智能体友好的格式。比如原始错误是'start_time' is not of type 'string',我转成参数start_time类型错误,应为字符串格式的ISO 8601时间,例如2024-01-01T00:00:00Z。这个转换看起来简单,但对智能体修正参数帮助极大。
4.3 执行器的异步化改造与任务队列配置
执行器是真正干活的模块。传统同步执行器在AI场景下问题很大:一个耗时10秒的任务会占住一个连接10秒,并发100就是100个连接被占满。我把它改成了异步任务模式:执行器收到请求后,立即把任务丢进消息队列,返回task_id,然后由后台worker消费队列执行。
消息队列我选的是Redis Stream,原因是轻量、部署简单、支持消费者组。配置参数如下:
| 参数 | 值 | 说明 |
|---|---|---|
| stream_key | tool:tasks | 任务流名称 |
| consumer_group | tool_workers | 消费者组 |
| max_len | 10000 | 流最大长度,防止内存溢出 |
| claim_timeout | 300s | 未确认消息超时重认领 |
| batch_size | 10 | 单次拉取任务数 |
max_len设10000是因为任务通常很快被消费,积压超过这个数说明worker不够或下游有问题,需要告警。claim_timeout设300秒是为了处理worker崩溃的情况:如果一个任务被某个worker领取后300秒还没确认,就重新分配给其他worker。batch_size设10是平衡吞吐和延迟,太小吞吐上不去,太大单次处理时间长。
worker的执行逻辑是:从队列拉任务,调用真正的业务逻辑,把结果写入结果存储(我用的是Redis,key是task_result:{task_id},TTL设1小时),然后确认消息。智能体通过get_task_result工具查询结果,查到后结果保留一段时间供重复查询,过期后返回TASK_EXPIRED。
4.4 智能体侧的调用示例与提示词配合
网关搭好后,智能体侧怎么用?这里给一个完整的调用示例。假设智能体要完成“查询用户最近订单并发送通知”这个任务,它的工具列表里会有list_user_orders和send_notification两个工具。调用流程如下:
第一步,智能体调用list_user_orders,参数{"user_id": "u123", "limit": 5}。网关返回{"status": "success", "data": {"orders": [...], "has_more": false}}。
第二步,智能体从返回中提取最新订单,调用send_notification,参数{"user_id": "u123", "content": "您的订单已发货"}。网关返回{"status": "success", "data": {"notification_id": "n456"}}。
第三步,任务完成。
这个流程看起来简单,但前提是工具描述写得好。list_user_orders的描述里要写明“返回按时间倒序排列的订单列表,limit默认10,最大50”。send_notification的描述里要写明“content不超过500字,发送后不可撤回”。这些约束写清楚,智能体就不会传limit: 1000或者超长content。
提示词侧也要配合。我在系统提示词里加了一段:“调用工具前先确认参数完整,如果缺少必填参数,先向用户询问。工具返回错误时,根据error.suggestion修正后重试,同一错误最多重试2次。”这段提示词配合网关的错误建议,能把智能体的自主修复能力拉满。
5. 常见问题与排查技巧实录
5.1 智能体反复调用同一个工具怎么办
这是最常见的问题。表现是智能体在同一个工具上连续调用多次,每次参数略有不同,但都不成功。排查思路分三步。
第一步,看工具描述是否有歧义。如果描述里写了“查询订单”,但没写“根据什么查询”,智能体可能尝试用不同参数组合去试。解决办法是在描述里明确必填参数和查询维度。
第二步,看错误返回是否有指导性。如果错误只返回“失败”,智能体不知道下一步怎么改,只能瞎试。解决办法是补全error.suggestion字段。
第三步,看是否有循环触发条件。比如智能体调用A工具,A返回的结果又触发了它对A的再次调用。这种情况通常是返回数据里包含了误导性字段。解决办法是检查返回结构,移除可能被智能体误读为“需要再次调用”的字段。
我遇到过一个典型案例:一个“搜索文档”工具返回了{"results": [...], "next_query": "..."},本意是给人类开发者提示下一个搜索词,结果智能体看到next_query就自动又调了一次。后来把next_query改名为_internal_hint并加了下划线前缀,智能体就不再误读了。返回字段命名要避免使用可能被理解为“行动指令”的词。
5.2 参数校验总是不通过怎么排查
参数校验失败通常有三个原因。第一是schema定义太严格,比如要求integer但智能体传了字符串"5"。解决办法是在schema里加类型转换,或者在描述里明确“请传数字类型”。第二是枚举值不匹配,智能体传了"pending"但枚举里是"PENDING"。解决办法是枚举值统一小写,并在描述里强调大小写敏感。第三是嵌套结构解析错误,智能体把嵌套对象拍平了。解决办法是尽量扁平化参数,必须嵌套时在描述里给出完整示例。
我整理了一个排查速查表:
| 现象 | 可能原因 | 解决动作 |
|---|---|---|
| 提示类型错误 | schema类型与传入不符 | 检查schema,增加类型容错 |
| 提示枚举不匹配 | 大小写或拼写差异 | 统一枚举值格式,描述中强调 |
| 提示缺少必填项 | 智能体漏传 | 检查描述是否明确标注必填 |
| 提示多余字段 | 智能体多传 | schema设additionalProperties为false |
| 嵌套解析失败 | 结构过深 | 扁平化参数设计 |
注意:
additionalProperties设为false能防止智能体传多余字段,但要在描述里写明“仅接受以下参数”,否则智能体会困惑为什么传了却被拒绝。
5.3 高并发下任务积压和超时怎么处理
高并发场景下,任务队列积压是常态。我的处理策略分三层。第一层是限流,在网关层对每个智能体或每个工具设置QPS上限,超过就返回RATE_LIMITED并附带retry_after。第二层是优先级队列,把任务按优先级分到不同stream,高优先级任务用独立worker池消费。第三层是降级,当队列长度超过阈值时,非核心工具直接返回SERVICE_BUSY,把资源留给核心工具。
限流参数怎么定?我的经验值是:单个工具QPS上限设为worker数量的5倍,因为单个任务平均执行200毫秒,一个worker每秒能处理5个任务。如果worker有10个,QPS上限设50比较合理。超过这个值,任务开始排队,延迟上升。这个值不是固定的,要根据实际任务耗时动态调整。
超时处理方面,我给每个任务设了最大执行时间。超过时间还没完成,worker主动终止任务并返回TASK_TIMEOUT。同时,结果存储里写入超时状态,智能体查询时能看到明确结果,而不是一直等。超时时间设多少?看业务,查询类任务设10秒,写操作类设30秒,批量处理类设300秒。关键是在工具描述里写明超时时间,让智能体有预期。
5.4 工具版本升级如何不影响在线智能体
工具升级是必然的,但直接改描述或参数会导致正在运行的智能体调用失败。我的做法是版本并行。新版本工具用新的tool_id(比如get_order_status_v2),旧版本保持active但标记deprecated。智能体默认调用旧版本,新智能体或主动升级的智能体调用新版本。等旧版本调用量降到阈值以下,再标记disabled。
这个过程中,注册中心要维护版本映射关系,网关根据智能体传入的版本偏好路由。如果智能体没指定版本,默认走最新稳定版。同时,在旧版本的描述里加一句“此版本即将下线,建议迁移到get_order_status_v2”,给智能体(以及背后的人类开发者)迁移提示。
我踩过的坑是:有一次直接改了工具描述里的参数名,从user_id改成uid,结果所有在线智能体全部调用失败。因为智能体缓存的工具描述还是旧的,它按旧描述传user_id,网关按新schema校验就报错。任何破坏性变更都必须走新版本,不能原地修改。这个教训值好几天的故障排查时间。
5.5 成本失控:token消耗为什么比预期高
AI应用的成本大头在token。工具调用场景下,token消耗主要来自三块:工具描述注入上下文、调用参数生成、返回结果解析。如果发现成本比预期高,先查这三个地方。
工具描述注入是最大头。如果工具列表有50个工具,每个描述200token,那每次对话光工具描述就占10000token。解决办法是按需注入:根据当前任务上下文,只注入相关工具的描述。比如用户问订单问题,就只注入订单相关工具,其他工具不注入。这需要网关支持按标签或分类过滤工具列表。
返回结果解析是第二大头。如果返回数据包含大量冗余字段,智能体解析时消耗的token也会增加。解决办法是默认精简返回,只给必要字段,详细数据通过get_detail类工具按需获取。比如列表接口只返回id和name,详情接口才返回完整字段。
参数生成是第三块,通常占比不大,但如果参数schema太复杂,智能体生成参数时也会多消耗token。扁平化参数设计在这里也有收益。
我实测过一个优化案例:一个包含30个工具的系统,优化前每次对话平均消耗15000token,优化后降到6000token,降幅60%。主要动作就是按需注入工具描述和精简返回字段。这个优化不需要改模型,纯粹是API设计层面的调整。
6. 我在实际项目里沉淀的几条硬经验
先说一条最反直觉的:工具不是越多越好。我见过团队把内部所有接口都包装成工具暴露给智能体,结果智能体选择困难,调用准确率反而下降。后来砍到只保留高频、核心的20个工具,准确率立刻回升。智能体的“注意力”是有限资源,工具列表越长,它越容易选错。我的建议是,单个智能体可见的工具数量控制在15到25个之间,超过就分组,让智能体先选组再选工具。
第二条是关于描述的:描述里的每一个字都要有信息量。我审过很多工具描述,充斥着“此工具用于”“该接口可以”这类废话。智能体不需要客套,它需要的是决策依据。把“此工具用于查询订单状态”改成“查询订单当前物流和支付状态”,去掉废话,信息密度立刻提升。描述写完后,自己读一遍,问自己:如果我只看到这句话,能不能判断什么时候该调用它?如果不能,就继续改。
第三条是关于测试的:用真实智能体做回归测试,不要只用人写的测试用例。人写的用例覆盖的是预期路径,智能体走的是非预期路径。我每次上线新工具或改描述,都会用几个典型智能体跑一轮真实任务,观察调用成功率、平均轮次、错误分布。这些数据比单元测试更能反映API的智能体友好度。有一次单元测试全过,但智能体实测成功率只有60%,排查发现是描述里一个歧义词导致智能体理解偏差。改掉那个词,成功率回到95%。
最后分享一个关于错误信息的小技巧:在错误信息里带上正确的调用示例。比如参数错误时,除了说明哪里错,再附上一句“正确调用示例:get_order_status(order_id='123')”。智能体看到示例,修正速度比只看文字说明快得多。这个技巧看起来简单,但效果立竿见影,我在多个项目里验证过,平均减少一轮重试。
这套方法论不是一成不变的。随着模型能力提升,智能体对模糊描述的容忍度会变高,但对返回结构和错误语义的要求会更高。我的建议是每季度回顾一次工具调用数据,看看哪些工具调用成功率低、哪些错误高频出现,然后针对性优化。API的智能体友好度是一个持续打磨的过程,没有一劳永逸的终点。