共计 2671 个字符,预计需要花费 7 分钟才能阅读完成。
为什么工具调用的描述输出很重要
在复杂 Agent 系统中,工具调用的描述输出不仅仅是简单的日志记录,它涉及到多个关键方面:

- 调试效率 :当 Agent 执行链路过长时,清晰的描述能帮助快速定位问题节点
- 系统监控 :结构化描述便于采集关键指标(如工具调用耗时、成功率)
- 用户体验 :对终端用户展示友好的执行进度反馈
- 审计追踪 :合规场景下需要完整记录工具调用的上下文
实际案例:某电商客服 Agent 因未正确处理库存查询工具的错误描述,导致用户看到 ” 调用 WS-238 失败 ” 这类无意义报错,引发大量投诉。
技术方案对比
1. 直接返回字符串
# 简单但不可扩展
def get_weather():
return "查询北京天气:晴,25℃"
优点:
– 实现简单
– 人类可读性强
缺点:
– 难以程序化解析
– 无法附加元数据(如置信度、数据来源)
2. 结构化 JSON
{
"description": "查询北京天气",
"content": {
"weather": "晴",
"temp": 25,
"unit": "℃",
"source": "中国气象局"
},
"timestamp": "2023-07-15T14:30:00Z"
}
优点:
– 机器可读性强
– 支持嵌套数据结构
缺点:
– 需要额外序列化 / 反序列化
– 原始可读性较差
3. 流式输出
适用场景:
– 长时间运行的工具(如爬虫)
– 需要实时进度反馈
实现模式:
- 工具注册进度回调函数
- 分批次推送描述片段
- Agent 聚合最终结果
核心实现:Python Tool 基类
from typing import Dict, Any, Optional
from pydantic import BaseModel, Field, validator
class ToolDescription(BaseModel):
"""描述数据的结构化模型"""
summary: str = Field(..., max_length=200) # 简短摘要
details: Optional[Dict[str, Any]] = None # 详细数据
@validator('summary')
def validate_summary(cls, v):
if "密码" in v or "密钥" in v:
raise ValueError("描述中包含敏感词汇")
return v
class BaseTool:
def __init__(self, name: str):
self.name = name
def run(self, **kwargs) -> Any:
"""
工具执行入口
返回:(执行结果, 描述对象)
"""
result = self._execute(**kwargs)
description = self.generate_description(result, **kwargs)
return result, description
def _execute(self, **kwargs) -> Any:
"""子类实现具体工具逻辑"""
raise NotImplementedError
def generate_description(self, result: Any, **kwargs) -> ToolDescription:
"""
默认描述生成逻辑
可被子类覆盖实现定制化描述
"""
return ToolDescription(summary=f"{self.name} 执行完成",
details={"result": result}
)
关键设计点:
- 使用 Pydantic 进行数据验证
- 分离执行逻辑与描述生成
- 内置敏感词检测
- 支持子类自定义描述
完整代码示例
# 天气预报工具实现
class WeatherTool(BaseTool):
def __init__(self):
super().__init__("天气预报查询")
def _execute(self, city: str) -> dict:
# 模拟 API 调用
return {
"city": city,
"weather": "晴",
"temperature": 25,
"humidity": 0.6
}
def generate_description(self, result: dict, **kwargs) -> ToolDescription:
city = kwargs.get('city', '未知城市')
return ToolDescription(summary=f"{city} 天气:{result['weather']} {result['temperature']}℃",
details={
"source": "模拟数据",
"raw_data": result
}
)
# 使用示例
weather_tool = WeatherTool()
result, desc = weather_tool.run(city="北京")
print(desc.json(indent=2))
"""
输出示例:{
"summary": "北京天气:晴 25℃",
"details": {
"source": "模拟数据",
"raw_data": {
"city": "北京",
"weather": "晴",
"temperature": 25,
"humidity": 0.6
}
}
}
"""
性能优化建议
- 内存控制
- 对大型数据集,描述中只保留摘要和元数据
-
使用生成器逐步构建大文本描述
-
延迟优化
- 异步生成描述(计算密集型描述可放在后台线程)
-
对高频工具缓存常见描述模板
-
流量节省
- 在链式调用中传递描述上下文而非重复数据
- 使用二进制编码(如 MessagePack)替代 JSON
实测数据对比(处理 1000 次工具调用):
| 方案 | 内存峰值 (MB) | 平均延迟 (ms) |
|---|---|---|
| 完整 JSON 描述 | 145 | 12.3 |
| 仅摘要文本 | 78 | 4.1 |
| 流式分块输出 | 52 | 8.7(首块 2.1) |
生产环境避坑指南
- 描述过长导致截断
- 解决方案:强制 summary 字段长度限制
-
使用折叠式 UI 展示详细描述
-
敏感信息泄露
- 自动过滤 API 密钥、手机号等模式(正则表达式检测)
-
区分内部详细日志和用户可见描述
-
动态内容不一致
- 问题:描述生成时数据状态已变化
-
解决方案:在工具执行时同步捕获描述所需数据
-
多语言支持缺失
- 设计时预留 i18n 字段
-
根据用户语言偏好选择描述模板
-
描述生成成为性能瓶颈
- 复杂描述采用懒加载
- 统计分析高频查询模式进行预生成
流程示意图
Agent
│
├─ 调用工具 A
│ ├─ 执行核心逻辑
│ └─ 生成描述(包含执行上下文)│
├─ 调用工具 B
│ ├─ 执行核心逻辑
│ └─ 生成描述(引用工具 A 的结果)│
└─ 聚合所有工具描述
├─ 统一格式化
└─ 输出最终响应
思考题
在你的 Agent 系统中,工具描述是否应该包含执行耗时等性能指标?这些数据应该:
- 始终包含在描述中
- 仅调试模式输出
- 通过独立监控通道收集
不同的选择会对系统设计产生哪些影响?
正文完
