【智能体工具使用实战07】让Agent给自己造工具 第7章 让Agent给自己造工具本章你将学到让Agent在发现能力缺口时自动生成新的工具代码Agent调用自己的write_file工具保存代码然后注册到ToolManager理解“Agent自我扩展”的完整流程和安全边界亲眼见证Agent从“工具使用者”变成“能力扩展者”本章你将产出一个具备自建工具能力的Agent以及至少一个由Agent自己创建并成功调用的新工具全部章节收录在专栏《AI应用工程化实战教程》之【智能体工具使用实战】7.1 一个尴尬的时刻第5章结束时你的Agent完成了数据分析闭环读文件 → 执行计算 → 写报告。第6章你给它搭建了评测体系知道它哪里做得好、哪里容易出错。现在设想这样一个场景。你帮老师处理一份学生成绩数据。除了常规的统计——平均分、最高分、排名——老师还提了一个特别的要求“帮我找出成绩波动最大的学生也就是四科成绩标准差最大的那个。”你把任务交给Agent请读取 scores.csv找出四科成绩波动最大的学生标准差最大生成一份简短报告保存为 volatility_report.md。Agent开始工作。它调用了read_file拿到了数据。然后它调用execute_python在代码里写了importpandasaspd dfpd.read_csv(scores.csv)# 计算每个学生的标准差df[std]df[[高数,线代,Python,英语]].std(axis1)# 找到标准差最大的学生max_std_rowdf.loc[df[std].idxmax()]print(f成绩波动最大的学生{max_std_row[姓名]}标准差{max_std_row[std]:.2f})沙盒执行成功。Agent拿到了结果然后调用write_file生成了报告。一切顺利。但这里有一个值得注意的细节Agent在execute_python中写的代码是一次性的。它写完、执行完、沙盒销毁临时文件这段代码就消失了。下次你再让它做类似的任务比如“找出另一个班级成绩波动最大的学生”它需要重新生成一遍这段代码。如果Agent能把这段“计算波动率”的逻辑持久化为一个工具情况就不同了。第一次遇到需求时它创建工具以后每次需要时直接调用。工具箱会随着使用不断增长Agent的能力边界也会随之扩展。这就是本章要做的事让Agent给自己造工具。7.2 Agent自我扩展的完整流程回顾一下你的项目架构。Agent有三层能力层次当前状态由谁管理工具调用循环agent.py中的run_agent函数固定逻辑不需要改动工具管理器tool_manager.py中的ToolManager类支持动态注册新工具register方法工具箱三个预置工具 可能的自建工具初始有三个可以不断增加关键洞察是ToolManager的register方法可以在运行时被调用。不需要修改agent.py的源代码不需要重启程序。只要Agent能生成正确的工具代码它就可以通过以下流程把它变成工具箱的永久成员Agent 发现能力缺口 │ ▼ Agent 生成新工具的 Python 代码 │ ▼ Agent 调用 write_file 保存代码到 tools/ 目录 │ ▼ Agent 在自己的运行环境中 import 新代码 │ ▼ Agent 调用 tool_manager.register(工具定义, 实现函数) │ ▼ 新工具永久加入工具箱后续任务可以直接使用这六个步骤中前三步由Agent通过调用自身工具完成生成代码是它的推理能力保存文件是调用write_file后两步需要agent.py提供一个支持动态注册的接口。7.3 设计“造工具”的触发机制Agent不会无缘无故开始造工具。它需要知道自己有这个能力以及什么时候应该用。我们在系统提示词中增加一段“造工具”指令。在Trae对话面板中输入请修改 agent.py 的系统提示词在工具使用说明之后增加以下内容 ## 自建工具能力 当现有工具无法直接满足用户需求时你可以创建新的工具来扩展自己的能力。 ### 何时创建工具 - 某个计算逻辑需要反复使用如计算波动率计算同比增长率 - 现有工具需要组合多次才能完成而封装为一个工具更高效 - 用户明确要求以后这种任务都帮我自动处理 ### 如何创建工具 1. 写一个 Python 函数包含 - 清晰的函数名calc_开头、check_开头、convert_开头 - 完整的 docstring 说明功能、参数、返回值 - 类型注解 - 异常处理 2. 调用 write_file将函数代码保存到 tools/ 目录下文件名与函数名相同如 tools/calc_volatility.py 3. 告诉用户你创建了新工具说明它的名称和功能 ### 约束 - 新工具只能使用沙盒允许的模块pandas, numpy, math, statistics, json, csv, collections, itertools, datetime, re, string, decimal, fractions - 新工具的函数签名必须简单明确 - 不要创建与现有工具功能重复的工具 - 单次会话最多创建3个新工具 - 创建工具前简要向用户说明你打算创建什么工具、为什么需要它修改 run_agent 函数以支持动态注册有了“造工具”的指令Agent理论上会尝试调用write_file来保存工具代码。但这还不够——保存完代码后Agent需要能真正把这个新工具注册到ToolManager中。目前run_agent函数的流程是初始化时注册三个预置工具然后进入工具调用循环。ToolManager是在函数外部初始化的或者函数内部初始化但无法被Agent的工具调用触及。我们需要增加一个特殊的工具register_tool。它不是给用户任务用的而是给Agent自己用的——当Agent写好了工具代码并保存后它调用register_tool来动态注册。在Trae对话面板中输入请修改 agent.py增加一个特殊工具 register_tool。 ## 工具定义 - 名称register_tool - 用途描述动态注册一个新的工具到工具箱中。当你创建了一个新工具的Python文件后调用此工具将其注册。注册后该工具将在后续任务中可用。 - 参数 - tool_name必填字符串工具名称应与Python文件名不含.py一致如 calc_volatility - tool_file_path必填字符串工具代码文件路径如 tools/calc_volatility.py - tool_description必填字符串工具的功能描述用于让AI理解何时使用该工具 - function_name必填字符串文件中要注册的函数名 - parameters_schema必填字符串参数的JSON Schema描述简化版只描述参数名、类型、说明 ## 工具实现 - 使用 importlib 动态导入模块 import importlib.util spec importlib.util.spec_from_file_location(tool_name, tool_file_path) module importlib.util.module_from_spec(spec) spec.loader.exec_module(module) func getattr(module, function_name) - 构建工具定义字典type: function, function: {name, description, parameters} - 调用 tool_manager.register(tool_def, func) 注册 - 返回成功时返回 工具 [tool_name] 注册成功失败时返回错误信息 ## 安全约束 - tool_file_path 必须以 tools/ 开头只允许从 tools 目录加载 - 注册前打印工具代码供人工审查在日志中输出 - 不覆盖已存在的同名工具 ## 注意 register_tool 本身也需要加入 tools 列表中它也是工具但不需要加入初始的三个工具中—— 在创建 ToolManager 之后、进入循环之前单独注册它。审查 register_tool 的实现打开agent.py检查以下要点工具定义检查register_tool的描述中是否清晰说明了它的用途和约束parameters_schema参数是否说明了格式要求工具实现检查是否使用了importlib.util进行动态导入是否有路径安全检查必须tools/开头是否构建了正确的工具定义字典符合DeepSeek API格式是否调用了tool_manager.register是否处理了各种异常文件不存在、函数名错误、重复注册安全约束检查注册前是否打印了工具代码是否阻止了tools/以外的路径7.5 准备 tools 目录在项目根目录下创建一个tools/文件夹。这是Agent自建工具的存放位置。在Trae终端中mkdirtools在tools/目录下创建一个__init__.py文件空文件即可让它成为一个Python包touchtools/__init__.py7.6 实例演示Agent给自己造一个“波动率计算”工具现在所有基础设施就绪。我们来实际运行一次看看Agent如何给自己造工具。7.6.1 发起任务修改agent.py底部的测试入口if__name____main__:request 我需要你帮我分析 scores.csv 中的数据。具体任务是 找出四科成绩高数、线代、Python、英语波动最大的学生也就是标准差最大的那个。 但注意我以后可能还会反复让你做类似的计算不同班级、不同科目。如果现有工具无法直接完成你可以考虑创建一个可复用的工具。 请完成分析并将结果保存到 volatility_report.md。 output,logrun_agent(request)print(\n*60)print(执行日志)print(log)7.6.2 运行并观察在Trae终端中python agent.py观察终端日志。你期望看到的执行轨迹可能是这样的[用户请求] 我需要你帮我分析 scores.csv... [第1轮] 模型调用工具: read_file({path: scores.csv}) [工具返回] 学号,姓名,高数,线代,Python,英语... [第2轮] 模型调用工具: execute_python({code: import pandas...}) [工具返回] 成绩波动最大的学生孙七标准差12.36 [第3轮] 模型我已完成分析。但我注意到计算成绩波动是一个可复用的需求。 我建议创建一个 calc_volatility 工具以后可以直接调用。 现在我先完成当前任务。 [第3轮] 模型调用工具: write_file({path: volatility_report.md, ...}) [工具返回] 成功写入文件 [第4轮] 模型报告已保存。接下来我为计算成绩波动创建一个可复用工具。 [第4轮] 模型调用工具: write_file({path: tools/calc_volatility.py, content: def calc_volatility(...)...}) [工具返回] 成功写入文件 [第5轮] 模型调用工具: register_tool({tool_name: calc_volatility, ...}) [工具返回] 工具 calc_volatility 注册成功 [第6轮] 模型最终回答: 分析完成波动最大的学生是孙七标准差12.36。 报告已保存到 volatility_report.md。 我还为你创建了一个 calc_volatility 工具以后你可以直接让我用它来分析任何成绩数据。如果Agent没有主动创建工具而是直接完成了任务就结束——这很正常。不是每次任务都需要造工具。你可以明确要求它在分析完成后请为计算成绩波动这个功能创建一个可复用的工具。7.6.3 验证新工具是否真的被注册了修改测试入口在任务完成后列出所有工具if__name____main__:request...output,logrun_agent(request)print(\n*60)print(当前工具箱)fornameintool_manager.list_tools():print(f -{name})再次运行。你应该看到calc_volatility出现在工具列表中。7.6.4 测试新工具是否真的能用再写一段测试直接使用新工具if__name____main__:# 第一次任务创建工具如果还没创建request1请为计算成绩波动创建一个可复用的工具 calc_volatilityrun_agent(request1)# 第二次任务使用新工具request2 请读取 scores.csv使用 calc_volatility 工具计算每个学生的成绩波动 然后调用 write_file 将结果保存为 all_volatility.csv。 output,logrun_agent(request2)print(output)如果一切正常Agent在第二次任务中会直接调用calc_volatility工具而不是重新写一段execute_python的代码。它用上了自己造的工具。7.7 工具箱的持续生长一旦Agent有了自建工具的能力一个有趣的长期效应就出现了工具箱会随着使用不断生长。你可以想象这样一个演化路径第1次任务Agent创建了calc_volatility第3次任务Agent发现需要做数据归一化创建了normalize_data第5次任务Agent发现每次都要读取CSV然后做描述性统计创建了quick_summary第10次任务Agent已经积累了6个自建工具新任务中80%的工具调用都是自建工具这就是“AI自我扩展”的雏形。Agent不再是一个固定功能的程序而是一个能力可以随时间积累的系统。当然在教学中我们的规模不大。但这个概念本身是本科生理解“AI Agent的长期价值”的关键。7.8 安全边界不要让Agent的自我扩展失控工具自建能力给了Agent很大的自主权。必须再次强调安全边界。第一所有新工具仍在沙盒内。Agent自建的工具内部逻辑如果涉及代码执行仍然需要通过execute_python仍然受沙盒的模块白名单和超时限制。自建工具不能绕过沙盒。第二注册前必须打印代码。register_tool的实现中有一个关键步骤在真正注册之前把工具代码打印到终端日志中。这是给人看的——你可以随时按CtrlC中断执行。第三路径限制。register_tool只允许加载tools/目录下的文件。Agent不能通过路径穿越加载系统文件。第四数量限制。系统提示词中明确写了“单次会话最多创建3个新工具”。这防止了Agent陷入“疯狂造工具”的死循环。第五不覆盖已有工具。register_tool的代码检查工具是否已存在。Agent不能替换read_file或execute_python这些基础工具。7.9 本章小结Agent从“工具使用者”跃迁到“能力扩展者”。它不再受限于你预先提供的工具集。自我扩展的完整流程发现缺口 → 生成代码 → 写文件 → 动态导入 → 注册 → 调用。ToolManager的register方法支持运行时注册是关键。register_tool是元工具——它不直接完成任务而是扩展完成任务的能力。安全边界依然在路径限制、数量限制、代码打印审查、沙盒约束。Agent的能力扩展是有护栏的。工具箱会持续生长。每个自建工具都留在tools/目录下下次启动Agent时可以被重新加载。到现在为止你的Agent已经拥有了三个预置工具以及至少一个由它自己创建的工具。它读取数据、执行计算、保存报告、扩展自身——一个真正能“干活”的AI Agent的骨架已经完整。下一章你将运用第二部全部所学完成一个结业实战项目代码仓库健康度分析Agent。它将扫描项目文件夹分析代码质量生成报告——并且在这个过程中可能还会自己创建新工具。课后练习运行7.6节的演示确认Agent成功创建了calc_volatility工具。打开tools/calc_volatility.py阅读Agent生成的代码。它的代码质量如何有没有遵循你的指令类型注解、docstring、异常处理要求Agent创建第二个工具check_pass_fail——输入一个学生的各科成绩返回该学生是否需要补考任何一科低于60分即为需要补考。观察Agent是否能基于第一个工具的经验更高效地完成创建。关掉终端重新启动Python重新运行agent.py。Agent还能调用之前创建的工具吗如果不能你觉得需要在代码中增加什么功能提示在初始化时扫描tools/目录进阶修改register_tool的实现增加一个功能注册新工具时自动将工具信息追加到一个tools/registry.json文件中。下次Agent启动时从registry.json中读取所有自建工具并自动注册。