"会说话的只是ChatBot,会调工具做事的才叫Agent"。这句话精准概括了大语言模型(LLM)与智能体(Agent)的本质区别。大模型本质上是一个文本生成器,它不能直接操作系统、调用API、访问数据库。所有这些能力都需要额外的工程实现。
工具使用模式是突破大语言模型固有局限、实现Agent与现实世界交互的核心架构范式。其本质是让LLM从单纯的文本生成器转变为具备感知、推理和行动能力的智能体,核心依托ReAct循环中模型对工具调用时机的自主决策能力。
LLM存在三个根本性局限:
工具调用模式通过搭建LLM与外部系统的桥梁,完美解决了这些问题。该模式的核心逻辑是:
工具调用的核心在于:LLM需要把用户的非结构化需求(一段自然语言文本)转换为结构化的函数调用(函数名和参数),然后与其他应用程序交互,再将结构化结果返回给模型。
这个过程的本质是信息形式的转换。历史上其他系统(数据库、API、文件系统等)只能处理结构化信息,而LLM擅长处理非结构化信息(文本)。因此,LLM必须成为两种信息形式之间的桥梁:
code复制用户自然语言请求 → LLM解析 → 结构化函数调用 → 外部系统执行 → 结构化结果 → LLM整合 → 自然语言响应
这种"非结构化→结构化→非结构化"的闭环,正是AI Agent工具能力的基础。
Agent工具使用模式的核心设计原则围绕"解耦、智能决策、扩展性、实用性"四大核心展开:
无论底层是函数、API、数据库查询还是其他Agent,都应封装为标准化工具对象,包含:
这种标准化让LLM能以一致的逻辑理解和调用不同类型的工具。
实践建议:使用Pydantic BaseModel定义工具schema,自动处理数据验证和文档生成。
通过工具注册表(ToolRegistry)实现解耦:
这种设计支持动态扩展工具集。例如新增"邮件发送工具"时,仅需在注册表中完成注册,LLM即可感知并使用该工具。
将工具组合与调用的决策权完全交予LLM:
例如用户要求"分析近一周股票数据并生成可视化报告",LLM可自主决策调用顺序:
LLM与框架间的交互必须使用结构化数据(如JSON),避免自然语言歧义。例如:
json复制{
"tool_name": "weather_query",
"params": {
"city": "北京",
"date": "2025-12-01"
}
}
形成"请求→决策→调用→反馈→再决策"的闭环:
一个完整的工具系统通常包含以下组件:
| 组件 | 职责 | 关键技术点 |
|---|---|---|
| 工具注册表 | 管理工具元数据 | 支持动态注册、版本控制 |
| 调度引擎 | 执行工具调用 | 异步执行、超时控制 |
| 适配层 | 统一工具接口 | 封装不同协议(HTTP/SQL等) |
| 安全层 | 权限控制 | 沙箱执行、输入校验 |
| 监控 | 记录调用指标 | 成功率、耗时统计 |
典型调用流程:
OpenHands是一个开源的Agent框架,其工具系统设计具有典型参考价值。
采用"动作→执行→观察"三层抽象:
python复制class Action:
tool_name: str
params: dict
class ToolExecutor:
def execute(self, action: Action) -> Observation:
...
class Observation:
success: bool
data: dict
error: Optional[str]
以IPython执行工具为例:
python复制_IPYTHON_DESCRIPTION = """Run a cell of Python code in an IPython environment.
* 需先定义变量和导入包
* 变量仅在IPython环境中有效
"""
IPythonTool = {
'type': 'function',
'function': {
'name': 'execute_ipython_cell',
'description': _IPYTHON_DESCRIPTION,
'parameters': {
'type': 'object',
'properties': {
'code': {'type': 'string', 'description': '要执行的Python代码'},
'security_risk': {'type': 'string', 'enum': ['low', 'medium', 'high']}
},
'required': ['code', 'security_risk']
}
}
}
response_to_actions函数将LLM响应转换为系统动作:
python复制def response_to_actions(response: ModelResponse) -> List[Action]:
actions = []
for tool_call in response.tool_calls:
# 解析参数
args = json.loads(tool_call.function.arguments)
# 根据工具名创建对应动作
if tool_call.function.name == 'execute_ipython_cell':
action = IPythonRunCellAction(code=args['code'])
elif tool_call.function.name == 'cmd_run':
action = CmdRunAction(command=args['command'])
# ...其他工具处理
# 添加元数据
action.tool_call_id = tool_call.id
actions.append(action)
return actions
OpenHands采用三层架构避免上下文混淆:
经验值:单次提示中工具数量不宜超过20个,否则易导致模型混淆。
单一职责:每个工具只做一件事
analyze_and_plot(data)analyze(data) + generate_plot(results)自然语言优先:
强类型约束:
问题1:LLM频繁调用错误工具
问题2:工具执行超时
问题3:结果格式不一致
异步调用:并行执行无依赖的工具
python复制async def execute_parallel(tools):
tasks = [asyncio.create_task(run(tool)) for tool in tools]
return await asyncio.gather(*tasks)
结果精简:只返回必要字段,避免上下文溢出
python复制def query_database(query):
# 原始返回100条记录
return {'data': rows[:10]} # 只返回前10条
缓存机制:对相同参数的工具调用缓存结果
让我们用上述原则构建一个股票分析Agent:
python复制tools = [
{
"name": "get_stock_data",
"description": "获取指定股票的历史数据。示例:get_stock_data(symbol='AAPL', days=7)",
"parameters": {
"symbol": {"type": "string", "description": "股票代码"},
"days": {"type": "integer", "description": "查询天数"}
}
},
{
"name": "analyze_trend",
"description": "分析数据趋势。输入应为get_stock_data的原始输出",
"parameters": {
"data": {"type": "object", "description": "股票数据"}
}
},
{
"name": "generate_report",
"description": "生成可视化报告",
"parameters": {
"analysis": {"type": "object", "description": "分析结果"},
"format": {"type": "string", "enum": ["png", "pdf"], "default": "png"}
}
}
]
json复制[
{
"tool_name": "get_stock_data",
"params": {"symbol": "AAPL", "days": 7}
},
{
"tool_name": "analyze_trend",
"params": {"data": "<上一步结果>"}
},
{
"tool_name": "generate_report",
"params": {"analysis": "<上一步结果>", "format": "pdf"}
}
]
为每个工具添加错误码和修复建议:
python复制{
"error": "INVALID_SYMBOL",
"message": "无效股票代码",
"suggestion": "请检查代码是否正确,参考:AAPL(苹果), MSFT(微软)"
}
这样当LLM收到错误时,可以自动调整参数重试或向用户请求澄清。
工具调用模式正在使LLM从"知道分子"变为"行动分子"。随着工具生态的丰富,Agent的能力边界将不断扩展,最终成为连接数字世界与物理世界的智能枢纽。