构建一个能调用工具的多步 Agent:从原理到实战
1. 工具定义与环境搭建
构建多步 Agent 的第一步是明确它能调用哪些工具。通常我们会定义一组函数,Agent 通过这些函数与外部环境交互。以 Python 为例,我们可以使用函数式接口来描述工具。每个工具需要有明确的名称、描述以及参数 schema,这使得 Agent 能够理解何时使用哪个工具以及传入什么参数。在实际代码实现中,我们通常使用装饰器或工具注册表来管理这些工具。例如,一个简单的计算器工具可以定义为:
name: "calculator"description: "接收数学表达式并返回计算结果"parameters: {"type": "object", "properties": {"expression": {"type": "string"}}, "required": ["expression"]}
读者可以在本地创建一个名为 tools.py 的文件,并在其中编写上述结构。确保工具函数的健壮性很重要,例如在处理用户输入的表达式时,应当考虑安全过滤,防止注入恶意代码。准备好这些定义后,Agent 才能感知到有哪些可用的操作入口。
2. 规划与执行循环
有了工具定义,核心逻辑在于 Agent 的“思考”与“行动”循环。这个循环通常遵循:Agent 接收用户目标 -> 生成思考链 -> 判断是否需要调用工具 -> 如果需要则调用工具并观察结果 -> 否则直接回答用户。我们可以使用一个简单的 while 循环来模拟这个过程。在每一次迭代中,Agent 会根据当前的上下文(包括之前的工具返回结果)来决定下一步行动。代码结构大致如下:
while not task_complete:
# Agent 生成思考
thought = generate_thought(context)
# 判断是否调用工具
if needs_tool_call(thought):
tool_name, args = parse_tool_call(thought)
result = call_tool(tool_name, args)
context.append(result)
else:
response = generate_response(thought)
break
读者可以尝试运行这段伪代码,观察 Agent 如何根据上下文不断迭代。关键点在于 generate_thought 和 generate_response 这两个模块,它们通常由大型语言模型(LLM)驱动。通过不断地循环,Agent 能够处理需要多个步骤的复杂问题,例如“查询今天的天气并计算与昨天的温差”。
3. 失败重试与错误处理
在实际操作中,工具调用必然会遇到各种失败情况,例如网络超时、参数格式错误或工具返回的意外结果。构建健壮的 Agent 必须包含失败重试机制。当工具调用返回错误时,Agent 不应直接终止,而应捕获异常,将错误信息加入上下文,并重新进行规划。我们可以在 call_tool 函数外层包裹 try...except 结构:
try:
result = call_tool(tool_name, args)
except Exception as e:
# 将错误信息记录到上下文,最多重试3次
if retry_count < 3:
context.append(f"Error: {e}. 正在重试...")
continue
else:
return "工具执行失败,请稍后再试。"
此外,还需要设定合理的重试次数和超时时间,避免陷入无限循环。读者在实践中应关注错误日志的输出,根据错误类型决定是重试还是直接报告给用户。这种容错设计是保证 Agent 在不确定的环境中稳定运行的关键。
4. 边界与安全约束
多步 Agent 的强大能力也带来了安全风险,因此必须设定明确的边界与约束。首先,工具的权限应该最小化,即 Agent 只能调用完成特定任务所必需的工具,禁止访问系统敏感接口。其次,对于涉及金钱、个人数据的操作,应增加确认步骤或人工审计机制。在代码层面,可以通过白名单的方式限制可用工具列表,确保 Agent 无法随意调用未授权的函数。
另外,对工具返回的数据进行严格的格式验证和清理,防止跨站脚本(XSS)或数据泄露。读者在部署 Agent 时,建议在隔离的沙箱环境中测试,避免在生产环境直接暴露敏感配置。通过这些边界约束,我们可以在享受自动化带来便利的同时,有效规避潜在的安全隐患,确保 Agent 的行为可控可追踪。