【CodeFlow AI 实战】给大模型装上“调试器”:基于 LangGraph 的自我反思型测试 Agent 实现
1. 为什么我们需要“自我反思”型 Agent?
在开发 CodeFlow AI 的过程中,我发现了一个 RAG 项目普遍面临的痛点:大模型生成的代码往往“看起来很对,一跑就废”。
即便是最强大的 Qwen2.5-Coder 或 GPT-4o,在生成单元测试(Unit Test)时,也经常会出现:
-
导入错误:引用了不存在的模块或路径。
-
断言失败:逻辑理解偏差导致测试用例无法通过。
-
语法幻觉:使用了错误的库函数。
如果只是简单地将代码扔给用户,这不叫“人工智能”,这叫“给用户添乱”。为了解决这个问题,我为 CodeFlow AI 引入了第二个核心智能体:Test Generation Agent。它的核心思想是:不只写代码,还要自己跑代码、看报错、改 Bug。
2. 核心架构:Self-Reflection(自我反思)模式
不同于传统的线性逻辑(Chain),该 Agent 采用了 LangGraph 构建了一个循环状态机:
-
Designer(设计者):分析源码,生成初步的 pytest 代码。
-
Executor(执行者):在真实 Python 环境中运行测试,捕获 stdout 和 stderr。
-
Reflector(反思者):如果测试失败,将 Traceback 报错信息反馈给设计者。
-
Loop(循环):设计者根据报错重新修正代码,直到测试通过或达到尝试上限。
3. 技术实现深度拆解
3.1 状态定义 (State)
使用 TypedDict 定义 Agent 的记忆,记录源码、生成的测试、报错信息及迭代次数。
from typing import TypedDict
class TestAgentState(TypedDict):
source_code: str # 源码
test_code: str # 生成的测试代码
error_message: str # 报错堆栈
iteration_count: int # 当前迭代次数
is_passed: bool # 是否通过
max_iterations: int # 最大尝试次数
3.2 环境反馈:物理执行层
这是 Agent 触达“物理世界”的桥梁。我们使用 subprocess 真实触发 pytest 命令。
import subprocess
def execute_pytest(test_code: str):
# 将代码写入临时文件并运行
with open("temp_test.py", "w") as f:
f.write(test_code)
result = subprocess.run(
["pytest", "temp_test.py"],
capture_output=True, text=True
)
if result.returncode == 0:
return "SUCCESS"
return f"STDOUT: {result.stdout}\nSTDERR: {result.stderr}"
3.3 逻辑流转:LangGraph 编排
利用 LangGraph 的 add_conditional_edges 实现“写-跑-改”的逻辑闭环。
from langgraph.graph import StateGraph, END
workflow = StateGraph(TestAgentState)
# 定义节点:生成与验证
workflow.add_node("designer", designer_node)
workflow.add_node("executor", executor_node)
# 定义连线
workflow.set_entry_point("designer")
workflow.add_edge("designer", "executor")
# 核心逻辑:失败了就回去重写,成功了就结束
workflow.add_conditional_edges(
"executor",
lambda x: END if x["is_passed"] or x["iteration_count"] >= x["max_iterations"] else "designer"
)
test_gen_agent = workflow.compile()
4. 实战案例演示
输入(源码):
一个简单的除法函数,但模型第一次生成测试时忘记处理 ZeroDivisionError。
第一轮循环:
-
Agent 输出:生成了基础测试。
-
执行结果:FAILED (division by zero)。
-
反思反馈:反馈 Traceback 给模型。
第二轮循环:
-
Agent 反思:“我发现之前的测试没有覆盖异常处理,我现在加上 pytest.raises。”
-
执行结果:SUCCESS。
最终结果:
用户拿到的不是一份“可能有用”的代码,而是一份已经在后台验证通过、逻辑完备的生产级代码。
详细演示版本是
为了让你深度理解这个 Agent 是如何“思考”和“修正”代码的,我们来模拟一个真实的研发场景。
这个案例将展示:Agent 如何从“写错代码”到“看到报错”,最后“自愈成功”的全过程。
场景设定:为“折扣计算函数”编写单测
1. 待测试的源码 (utils.py)
这段代码逻辑很简单,但有一个潜在风险:它没有处理折扣大于 1 的情况。
# utils.py
def calculate_price(price: float, discount: float) -> float:
"""计算折扣后的价格"""
if price < 0:
raise ValueError("价格不能为负数")
return price * (1 - discount)
第一轮迭代:Agent 的“盲目”尝试
Step 1: Designer 节点生成代码
Agent 此时的 Prompt 输入: “请为 calculate_price 函数生成 pytest 测试用例。”
Agent 输出的测试代码 (temp_test.py):
注:此时 Agent 犯了一个低级错误:它忘记导入 calculate_price 函数了。
import pytest
# 错误点:忘记从 utils 导入 calculate_price
def test_calculate_price_success():
# 逻辑错误:它以为 discount 传的是百分比(20),但函数定义要求的是小数(0.2)
assert calculate_price(100, 20) == 80
Step 2: Executor 节点运行并报错
系统在后台运行 pytest temp_test.py。
终端返回的报错信息(反馈给 Agent):
________________ ERROR collecting temp_test.py ________________
temp_test.py:6: in <module>
def test_calculate_price_success():
E NameError: name 'calculate_price' is not defined
第二轮迭代:Agent 的“自我反思”与修正
Step 3: Reflector 逻辑启动
此时系统给 Agent 的 Prompt:
“你生成的代码运行失败了。
报错信息: NameError: name 'calculate_price' is not defined
源码参考: (给出 utils.py 的内容)
请分析原因并修正。”
Agent 内部的“反思”过程(在面试中可以重点讲):
-
识别问题:看到 NameError,意识到是 import 语句缺失。
-
逻辑对齐:通过阅读源码发现,1 - discount 意味着 discount 应该是 0.2 而不是 20。
-
完善覆盖:顺便发现源码里有 ValueError 的逻辑,应该增加一个异常测试。
Step 4: Designer 节点输出修正后的代码
import pytest
from utils import calculate_price # 修复了导入问题
def test_calculate_price_success():
# 修复了数值逻辑错误:20% 折扣应传 0.2
assert calculate_price(100, 0.2) == 80.0
def test_calculate_price_negative_value():
# 额外增加了对异常路径的覆盖
with pytest.raises(ValueError, match="价格不能为负数"):
calculate_price(-10, 0.1)
第三轮迭代:验证通过
Step 5: Executor 节点再次运行
系统再次运行 pytest temp_test.py。
终端返回信息:
======================= test session starts =======================
collected 2 items
temp_test.py .. [100%]
======================== 2 passed in 0.01s ========================
最终结果: Agent 检测到 is_passed = True,停止循环,将这份已经跑通的代码交付给用户。
面试中你可以如何利用这个案例?
如果你被面试官问到:“你的 Agent 怎么保证生成的代码能用?”
你可以这样回答(结合上面的案例):
“我想举一个具体的例子。当我输入一个折扣计算函数时,Agent 第一次生成的代码往往会忽略一些细节,比如忘记写 import 语句,或者对参数含义理解有误(比如把折扣比例当成了百分比整数)。
传统的 LLM 到这一步就结束了,但我的 Agent 会把代码放到物理环境中去跑。当 pytest 返回一个 NameError 或者 AssertionError 时,我的 Self-Reflection 循环会捕获这个真实的 Traceback。
就像一个真实的程序员一样,Agent 会看到:‘哦,原来是我没导包’,或者‘原来断言的值对不上’。它会基于这个反馈重新改写代码。
在我的实验中,这种‘执行-报错-重写’的机制,让代码的一次性通过率(Pass@1)大幅度提升,真正实现了代码生成的自愈闭环。”
5. 项目总结与思考
在 CodeFlow AI 的 V2 版本中,通过引入 Self-Reflection 架构,我们将测试生成的可用率从原本的 60% 提升到了 90% 以上。
面试技术点总结:
-
从 Chain 到 Graph:解决了复杂业务中的循环逻辑问题。
-
环境驱动的 Agent:LLM 不再是闭门造车,而是通过外部工具反馈进行进化。
-
工程化安全:在实际生产中,这类执行环境建议放在 Docker 容器中实现沙箱隔离。
6. 后续计划
目前 CodeFlow AI 已经完成了 Code Review Agent 和 Test Gen Agent。下一步,我将引入 Ruflo 作为全局编排层,实现从“代码变更触发”到“自动审计+测试”的全自动化流水线。
如果你对本项目感兴趣,欢迎在 GitHub 上关注我的项目:CodeFlow AI。
更多推荐
所有评论(0)