基于Claude Agent SDK构建AI自动修Bug Agent:20行Python实现代码智能修复

📅 2026/8/27 4:09:33
基于Claude Agent SDK构建AI自动修Bug Agent:20行Python实现代码智能修复
1. 项目缘起从“人肉修Bug”到“AI自动工兵”的转变最近在维护一个老项目每次看到测试同学提过来的Bug单心里就有点发怵。倒不是问题有多难而是很多Bug都属于那种“一眼就能看出问题但改起来又得小心翼翼”的类型——比如某个API的响应里多返回了一个没用的字段或者某个条件判断的逻辑边界没处理好。这些Bug修复本身不复杂但需要反复在本地复现、写测试用例、跑CI一套流程下来半小时就没了。一天处理五六个这样的Bug大半天时间就搭进去了真正该做的架构优化和新功能开发反而没时间。就在上个月团队里一个实习生写了个简单的脚本用大语言模型的API去自动分析日志里的错误信息然后给出修复建议。虽然那个脚本还很粗糙经常“胡说八道”但这个思路让我眼前一亮。如果能让AI直接理解代码上下文、自动定位问题、甚至生成修复代码那该多省事这不就是我一直想要的“自动修Bug工兵”吗正好Anthropic发布了Claude Agent SDK。看官方介绍它不像普通的Chat API那样只是“一问一答”而是能真正拥有“工具使用Tool Use”能力可以按计划执行多步操作比如读取文件、运行命令、编辑代码。这简直就是为“自动修Bug”这个场景量身定做的。于是我决定用这个SDK亲手搭一个能自动分析并修复简单Python代码Bug的AI Agent。我的目标很明确不要复杂的界面不要庞大的工程就用最少的代码实现一个能实际跑起来的原型。最终核心逻辑真的只用了20行左右的Python。当然从“跑起来”到“真正有用”中间踩的坑可不少。这篇文章我就把这个项目的实现思路、核心代码、以及那些让你少走弯路的“坑”全部分享出来。2. Claude Agent SDK 核心能力解读它为何是构建“修Bug Agent”的利器在决定用Claude Agent SDK之前我也调研过其他方案。比如直接用OpenAI的Assistants API或者基于LangChain来组装。但最终选择Claude Agent SDK主要是看中了它在“让AI执行具体操作”这件事上的设计哲学和易用性。2.1 与普通Chat API的本质区别普通的Chat Completion API无论是GPT还是Claude其交互模式本质上是“单次问答”。你给它一段包含问题和上下文的Prompt它给你一段回答。如果你想让它基于回答再去执行一个操作比如“好的现在请把刚刚分析出的错误代码的第30行修改一下”你需要手动解析它的回答提取出操作意图再用你的代码去执行然后把结果再塞回下一轮对话。这个过程非常脆弱AI的回答格式稍有变动你的解析逻辑就可能崩溃。Claude Agent SDK引入的“Agent”概念核心是“规划-执行”循环。你为AI定义好一系列它可以使用的“工具”Tools比如read_file,execute_shell,edit_file。然后你给它一个目标比如“修复这个Python文件中的Bug”。Agent会自己“思考”规划要达到这个目标我需要先做什么再做什么。例如它可能规划为调用read_file工具读取目标文件。分析代码定位问题。调用edit_file工具修改有问题的行。调用execute_shell工具运行测试验证修复是否成功。如果测试失败重新分析进入下一个循环。这个“思考”和调用工具的过程对开发者是透明的。你不需要写复杂的逻辑去解析AI的“想法”只需要定义好工具然后启动Agent它就会自动运行下去直到任务完成或达到步骤限制。2.2 核心概念工具Tools、运行器Runner与状态StateSDK的设计非常简洁主要围绕三个核心概念工具Tools这是Agent的“手”和“眼睛”。一个工具就是一个Python函数加上相应的描述。例如一个读取文件的工具from anthropic import Agent import os Agent.tool def read_file(file_path: str) - str: 读取指定路径文件的内容。 if not os.path.exists(file_path): return f错误文件 {file_path} 不存在。 with open(file_path, r, encodingutf-8) as f: return f.read()用Agent.tool装饰器标记后这个函数就对Agent可见了。关键是函数文档字符串读取指定路径文件的内容。Claude会仔细阅读这个描述来理解工具的用途和参数。运行器Runner这是驱动Agent执行的中枢。你创建运行器把定义好的工具注册给它然后给它一个初始任务比如“请修复bug.py中的错误”它就会开始工作。agent Agent(tools[read_file, execute_shell, edit_file], modelclaude-3-5-sonnet-20241022) runner agent.create_run(请修复 bug.py 中的错误。)状态StateRunner在运行过程中会产生一个状态流Stream。通过监听这个流你可以实时看到Agent的“思考过程”它决定下一步做什么、工具调用的输入输出、以及最终的结果。这对于调试和了解Agent的工作逻辑至关重要。2.3 为什么适合“修Bug”场景修Bug是一个典型的多步骤、需要上下文感知和实际操作的任务。Claude Agent SDK的“规划-执行”模式完美匹配感知Agent可以通过read_file工具获取完整的代码上下文而不是靠我们手动拼接的代码片段。分析与规划Claude模型强大的代码理解能力可以分析错误并规划出“先读文件-定位错误-修改代码-运行测试”的路径。执行与验证通过execute_shell运行测试或脚本验证修复是否有效如果无效它可以基于错误输出开始新的规划循环。这种将复杂任务分解、自主调用工具执行的能力使得用极少的“胶水代码”构建一个功能强大的自动化Agent成为可能。接下来我们就看看如何用这20行核心代码把它搭起来。3. 20行核心代码搭建“自动修Bug Agent”我们的目标是构建一个Agent它能接受一个Python文件路径和一个错误描述或测试失败信息然后自动尝试修复。以下是完整的、可运行的代码示例包含了最核心的逻辑。import asyncio from anthropic import Agent, AsyncAnthropic import os import subprocess # 1. 定义Agent可以使用的工具 Agent.tool async def read_file(file_path: str) - str: 读取指定路径的文本文件内容。 try: with open(file_path, r, encodingutf-8) as f: return f.read() except Exception as e: return f读取文件失败: {str(e)} Agent.tool async def edit_file(file_path: str, new_content: str) - str: 用新的内容覆盖写入指定文件。 try: with open(file_path, w, encodingutf-8) as f: f.write(new_content) return f文件 {file_path} 已成功更新。 except Exception as e: return f写入文件失败: {str(e)} Agent.tool async def run_pytest(test_path: str) - str: 运行pytest测试并返回输出结果。 try: # 注意这里假设pytest已安装。更安全的做法是使用项目的虚拟环境路径。 result subprocess.run([pytest, test_path, -v], capture_outputTrue, textTrue, timeout30) output fSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}\nReturn Code: {result.returncode} return output except subprocess.TimeoutExpired: return 测试执行超时30秒。 except Exception as e: return f执行测试命令失败: {str(e)} # 2. 主函数创建并运行Agent async def main(): # 替换成你的Claude API密钥 client AsyncAnthropic(api_keyos.environ.get(ANTHROPIC_API_KEY)) # 创建Agent实例并注册工具 agent Agent( clientclient, modelclaude-3-5-sonnet-20241022, # 推荐使用最新版Sonnet模型代码能力强 tools[read_file, edit_file, run_pytest], system_prompt你是一个资深的Python开发专家擅长分析和修复代码中的Bug。你的任务是理解用户提供的错误描述阅读相关代码文件分析问题根源并给出正确的修复方案。在修改代码前务必仔细推理。修改后应运行测试验证修复是否成功。 ) # 用户任务修复指定文件的Bug user_task 请修复文件 buggy_code.py 中的错误。 错误现象当调用函数 calculate_discount(price, is_member) 时如果 is_member 为 True折扣计算有误。非会员应无折扣会员应享受9折优惠。但当前代码逻辑反了。 请先分析代码然后进行修复最后运行 pytest test_discount.py 来验证修复是否正确。 # 创建并启动一个运行会话 run agent.create_run(user_task) # 实时流式输出Agent的思考过程和工具调用结果 async for event in run.stream_events(): if event.type text_delta: # 输出Agent的“思考”内容 print(event.delta, end, flushTrue) elif event.type tool_call_started: print(f\n[Agent 开始调用工具: {event.tool_name}]) elif event.type tool_call_succeeded: print(f[工具调用成功输出: {event.output[:200]}...]) # 只打印前200字符 # 3. 程序入口 if __name__ __main__: asyncio.run(main())代码逐行解读与设计逻辑工具定义第7-34行这是Agent能力的边界。我们定义了三个最基础但最关键的工具。read_file让Agent能“看到”代码。这是所有分析的起点。edit_file让Agent能“动手”修改代码。这是修复动作的核心。注意这里采用了直接覆盖写入的方式在实际生产中你可能需要更复杂的版本比如生成Patch文件或者先备份原文件。run_pytest让Agent能“验证”修复结果。通过运行测试获得客观的反馈驱动下一步的规划。这里使用subprocess调用系统命令并设置了超时防止某些测试用例卡死Agent。Agent配置与系统提示第41-50行这是Agent的“大脑”和“人格”设定。model选择了claude-3-5-sonnet-20241022。经过测试这个版本在代码推理和工具调用上表现非常稳定和精准比Haiku版本更适合此类任务。system_prompt这是灵魂所在。它定义了Agent的角色、目标和行为规范。“资深Python开发专家”设定了能力基线“擅长分析和修复Bug”明确了任务“仔细推理”和“运行测试验证”是强制性的工作流程。一个好的System Prompt能极大提升Agent的任务完成质量。任务描述第53-59行用户给Agent的指令。指令必须清晰、具体、包含所有必要上下文。这里明确指出了目标文件、函数、错误现象、预期正确行为、以及验证方式。模糊的指令会导致Agent行为不可控。事件流处理第64-73行通过run.stream_events()我们实时监听Agent的运行状态。text_delta是Agent的“内心独白”推理过程tool_call_*事件展示了工具调用的生命周期。这个流式输出对于调试和理解Agent为什么这么做至关重要。这不到80行的代码包含注释和空行核心的Agent逻辑其实就20行左右工具定义装饰器Agent初始化运行流就构建了一个具备“感知-分析-执行-验证”完整闭环的自动修Bug机器人。你可以把buggy_code.py和test_discount.py准备好然后运行这个脚本亲眼看看它是如何工作的。4. 实战踩坑全记录从“跑不通”到“修得准”代码写出来能跑只是第一步。要让这个Agent真正可靠地工作我遇到了好几个意料之外的问题。下面就把这些坑和解决方案详细记录下来希望能帮你省下几个小时甚至几天的调试时间。4.1 坑一工具函数必须异步Async这是我遇到的第一个拦路虎。最初我像写普通函数一样定义了工具没有加async关键字。# 错误示例 Agent.tool def read_file(file_path: str) - str: # 缺少 async with open(file_path, r) as f: return f.read()运行Agent时它规划了要调用read_file但随后就卡住了事件流没有任何输出最终超时。查看日志也没有明确的错误信息。根因与解决方案Claude Agent SDK 在设计上默认支持异步操作以提高效率。它期望工具函数是async的即使函数内部没有实际的await操作。这是SDK的默认约定。将所有的工具函数都加上async关键字问题立刻解决。这也提醒我们在使用任何新的SDK时仔细阅读官方文档中关于函数签名的要求至关重要哪怕是一个关键字。4.2 坑二文件路径与工作目录的“相对”陷阱Agent在调用read_file(‘buggy_code.py’)时返回了“文件不存在”。但我明明在同一个目录下创建了这个文件。根因与解决方案Agent进程运行时其“当前工作目录”可能不是你启动脚本的目录。当你在IDE中运行或通过某些进程管理器启动时工作目录可能会改变。工具函数中使用的相对路径‘buggy_code.py’是相对于Agent进程的当前工作目录而非你的脚本文件所在目录。解决方案有两种使用绝对路径在任务描述或工具调用中传入文件的绝对路径。在工具函数内部处理更健壮的做法是在工具函数内将相对路径基于一个已知的基准目录如项目根目录转换为绝对路径。例如import os PROJECT_ROOT os.path.dirname(os.path.abspath(__file__)) Agent.tool async def read_file(relative_path: str) - str: abs_path os.path.join(PROJECT_ROOT, relative_path) if not os.path.exists(abs_path): return f“错误文件 {abs_path} 不存在。” # ... 读取文件我采用了第二种方法一劳永逸地解决了路径问题。4.3 坑三Agent的“过度自信”与错误回滚这是一个非常有趣且关键的坑。Agent成功地分析出了一个Bug并调用edit_file进行了修改。然后它调用run_pytest验证但测试失败了可能是因为它引入了一个新错误或者测试本身有依赖问题。这时我观察到Agent的“思考”过程它没有去分析测试失败的原因而是试图再次修改文件但方向可能是错的甚至可能把代码改得更乱。根因与解决方案Agent在单次运行Run中会持续尝试完成任务。如果一次修改导致测试失败它会认为“任务未完成”从而启动新一轮的“规划-执行”。然而它的“记忆”可能不足以完美理解上一轮修改带来的全部影响尤其是在代码状态已经改变的情况下。这可能导致它在一个错误的方向上越走越远。我的解决方案是引入“操作日志”和“安全回滚”机制增强edit_file工具在覆盖写入前先备份原文件内容。可以在内存中保存一个{file_path: original_content}的字典或者直接备份到一个.backup文件。file_backups {} Agent.tool async def edit_file(file_path: str, new_content: str) - str: # 首次编辑时备份 if file_path not in file_backups: with open(file_path, ‘r’) as f: file_backups[file_path] f.read() # 执行写入... return “文件已更新。”新增一个revert_file工具当测试连续失败多次比如3次后可以通过在System Prompt中指示或者由监控脚本主动调用这个工具将文件回滚到最后一次已知的“稳定”状态。Agent.tool async def revert_file(file_path: str) - str: if file_path in file_backups: with open(file_path, ‘w’) as f: f.write(file_backups[file_path]) return f“文件 {file_path} 已回滚到备份版本。” else: return “找不到该文件的备份。”在System Prompt中明确规则在给Agent的指令中加入“如果你进行的修改导致测试失败请首先仔细阅读测试输出分析失败原因。如果连续尝试修复3次后测试仍然失败请考虑调用revert_file工具回滚更改并重新评估问题。”这个机制极大地提高了Agent的容错能力防止它把代码库搞得一团糟。4.4 坑四工具描述Docstring的质量决定Agent的理解深度最初我的run_pytest工具描述很简单“运行pytest测试”。结果Agent在需要验证特定测试文件时偶尔会调用成run_pytest(‘.’)运行所有测试这不仅慢而且无关测试的输出可能会干扰它的判断。根因与解决方案Claude模型极度依赖工具函数的文档字符串Docstring来理解该工具的用途、输入参数的含义和输出格式。模糊的描述会导致模糊的调用。优化后的描述Agent.tool async def run_pytest(test_path: str) - str: “““ 运行pytest测试套件。 参数: test_path (str): pytest的测试目标。可以是一个具体的测试文件如 ‘test_module.py‘ 一个测试类如 ‘test_module.py::TestClass‘ 一个测试函数如 ‘test_module.py::TestClass::test_method‘ 或者一个目录如 ‘tests/‘。 返回: str: 包含测试执行标准输出、标准错误和返回码的完整文本。返回码0通常表示所有测试通过。 ”““经过这样详细的描述后Agent在调用时几乎总能精确地传入我期望的测试路径如‘test_discount.py::test_member_discount’。经验像写API文档一样认真对待每个工具的Docstring参数类型、含义、返回值格式都要清晰。4.5 坑五长上下文下的“迷失”与关键信息提取当需要修复的代码文件很大比如几百行或者错误涉及多个文件时Agent在分析了大量代码后有时会在其“思考”中陷入细节忘记核心任务或者做出不符合最初指令的修改。根因与解决方案这本质上是大模型处理长上下文时的注意力分散问题。虽然Claude支持200K上下文但信息过多时关键指令的权重可能被稀释。我的应对策略是“分而治之”和“主动聚焦”任务拆解不一开始就给Agent一个庞大的任务“修复整个项目的XX问题”。而是先手动或用一个预处理脚本将问题定位到具体的文件、函数甚至代码行。然后给Agent更精确的任务“请分析utils/helper.py中第45-60行的validate_input函数为什么当输入为空字符串时会抛出TypeError”在System Prompt中强化指令在Prompt的开头或结尾用醒目的方式如### 核心指令 ###重复最重要的任务目标和约束条件。例如“### 核心指令 ### 你的首要目标是修复calculate_discount函数的逻辑错误确保会员享受9折。不要修改其他无关的函数或代码风格。”利用中间结果不要让Agent一次性读太多文件。可以设计工作流先让Agent调用一个find_relevant_code工具基于grep或静态分析找到可能出错的代码块然后再针对性地读取和修改那些块。踩过这些坑之后我的“自动修Bug工兵”才从一个经常闯祸的“实习生”变成了一个值得信赖的“初级工程师”。它仍然不会处理非常复杂的架构性问题但对于那些定义清晰、范围有限的典型Bug已经能提供令人惊喜的效率和准确度。5. 超越原型让“修Bug Agent”融入真实工作流一个能在命令行里跑起来的原型很有成就感但它的价值只有在融入日常开发工作流时才能最大化。下面分享我是如何将这个“玩具”升级为一个团队可用的实用工具的。5.1 与CI/CD管道集成自动处理回归Bug我们的CI pipeline使用GitLab CI会在每次合并请求MR时运行完整的测试套件。如果测试失败通常会通知提交者。我们可以将Agent集成到这一步。触发条件当CI检测到Python测试用例失败时自动触发一个修复Job。工作流程CI Job将失败的测试用例名称、错误日志、以及相关的代码文件路径收集起来。调用我们的Agent脚本并将这些信息作为初始任务输入。例如“测试test_discount.py::test_member_discount失败错误信息是AssertionError: Expected 90.0, got 100.0。相关代码文件是src/calculator.py。请分析并修复。”Agent运行并尝试修复。如果修复后测试通过CI Job可以自动提交一个包含修复代码的新Commit到当前分支或者生成一个包含Diff的评论供开发者审查合并。如果修复失败如连续尝试后仍未通过则记录日志并通知人工处理。注意这一步需要极高的谨慎。我们团队的做法是Agent生成的修复永远不会自动合并而是自动创建一个“Draft MR”或评论必须至少有一名核心成员进行代码审查Code Review后才能合并。AI是助手不是决策者。5.2 增强工具集赋予Agent更多“超能力”基础的读、写、运行测试工具只能处理简单问题。要应对更复杂的场景需要扩展它的工具箱search_codebase基于语义或关键词搜索整个代码库找到相关函数、类或引用。这可以帮助Agent理解跨文件的依赖。run_linter运行flake8、black、isort等工具。在Agent修改代码后自动调用linter进行格式化保证代码风格一致同时linter的警告有时也能提示潜在逻辑错误。analyze_logs读取应用运行时日志帮助诊断那些无法由单元测试复现的、与环境或数据相关的Bug。query_documentation连接内部Wiki或API文档让Agent能参考最新的开发规范或接口定义。5.3 构建“经验”记忆库让Agent越用越聪明每次Agent成功修复一个Bug都是一个宝贵的学习案例。我们可以建立一个简单的记忆库案例记录将成功修复的任务描述Bug现象、涉及的代码文件修复前、Agent给出的修复方案Diff、以及验证通过的测试用例结构化地存储下来例如存到JSON文件或向量数据库。相似问题匹配当新的Bug出现时可以先在记忆库中搜索相似的案例。如果找到高度相似的可以直接将历史修复方案作为参考甚至直接建议给开发者大大缩短处理时间。Prompt优化通过分析成功和失败的案例可以不断优化System Prompt和工具描述让Agent的表现持续提升。例如我们发现Agent在处理“空指针异常NoneType”这类Bug时成功率很高但在处理“并发竞争条件”时总是失败。那么我们就可以在Prompt中加强“你擅长修复数据验证和条件判断错误。如果问题涉及多线程或异步操作请格外谨慎并建议在修复方案中明确标注需要人工复核。”5.4 设定安全边界与人工审核流程让AI直接修改生产代码是危险的。必须设立安全边界沙盒环境Agent永远在一个独立的代码仓库副本或容器内运行。它的所有文件修改都只作用于这个沙盒。变更集Diff审查Agent不直接提交代码而是输出一个标准的Git Diff格式的补丁文件。开发者必须肉眼审查这个Diff确认修改正确、无副作用后再手动应用。作用域限制通过工具函数的逻辑限制Agent只能读取和修改指定的目录如src/而不能触及配置文件、密钥文件或构建脚本。操作确认对于某些高风险操作如删除文件、运行rm -rf等可以在工具函数中实现二次确认逻辑或者直接禁止提供这类工具。6. 效果评估与局限性它现在能做什么还不能做什么经过一段时间的试用和迭代我对这个“自动修Bug Agent”的能力边界有了更清晰的认识。6.1 效果评估哪些Bug修得又快又好语法错误和简单的逻辑错误这是它的强项。比如拼写错误、错误的操作符和混淆、条件判断边界错误、简单的计算逻辑错误。Agent能精准定位并修复。API响应格式错误比如JSON中多了或少了一个字段字段名拼写错误。Agent通过阅读测试失败信息期望的JSON vs 实际的JSON能很好地理解问题并修正。基于错误信息的修复当错误信息非常明确时例如“AttributeError: ‘NoneType‘ object has no attribute ‘split‘”Agent能准确地找到可能为None的变量并添加空值检查。重复性代码风格问题配合black、isort工具可以自动统一代码格式。在以上场景中Agent的处理速度是人类的数倍准确率在我测试的简单案例中能达到90%以上极大地释放了开发者的精力。6.2 当前局限性哪些地方还需要人类出手需要深度领域知识的Bug如果Bug的修复需要理解复杂的业务规则、特定的算法逻辑或领域概念而代码和注释中又没有明确体现Agent通常会失败或给出似是而非的修复。涉及架构设计的修改比如发现某个函数过于冗长需要拆解或者某个模块设计不合理需要重构。这类问题需要高层次的抽象思维和系统设计能力目前的Agent还无法胜任。多文件、状态复杂的并发Bug竞态条件、死锁等问题其复现依赖于特定的时序难以通过静态代码分析和单次测试运行来诊断。Agent给出的修复往往治标不治本。与外部系统集成相关的Bug比如数据库查询优化、第三方API调用失败处理等需要了解外部系统的特性和约束这超出了代码本身的范畴。“修复”引入新Bug尽管有测试验证但测试用例的覆盖率并非100%。Agent有可能为了通过当前失败的测试而破坏了其他未覆盖到的功能逻辑。这就是为什么人工代码审查环节不可省略。6.3 成本考量Token消耗与响应时间使用Claude API是需要成本的。一次复杂的Agent运行多次工具调用、长上下文可能会消耗数万甚至数十万Tokens。对于频繁运行或处理大型代码库的场景需要监控API使用成本。响应时间也是一个因素。一次完整的“分析-修复-验证”循环可能需要几十秒到几分钟。这对于CI中的快速反馈来说可能有点慢更适合作为异步处理任务如夜间批量处理失败的测试。总的来说这个20行Python起步的AI Agent已经从一个有趣的想法变成了我日常开发工具箱中一个实实在在的“效率倍增器”。它不是为了取代开发者而是作为一个不知疲倦的初级助手帮我们处理那些繁琐、重复但又有规律可循的“脏活累活”。