Day 51:完善迷你软件公司 —— 代码评审、迭代修复与工具共享
欢迎来到第五十一天!昨天我们搭建了迷你软件公司的骨架,完成了"需求 → 设计 → 编码 → 测试"的单向流程。但真实的软件开发不是一次性的——测试发现问题后,程序员需要修复 Bug,架构师需要评审代码质量,然后再次测试,直到所有验收标准通过。今天我们将为这个项目增加代码评审和迭代修复环节,形成闭环,并让 Agent 共享代码执行工具,让测试从"纸上审查"升级为"实际运行验证"。
一、今日学习目标
- 理解当前单向流程的局限性:测试发现问题后无法自动修复。
- 学会设计"编码 → 评审 → 测试 → 修复"的迭代闭环流程。
- 掌握在 CrewAI 中实现迭代逻辑:用 Python 循环包裹 Crew 执行,根据测试结果决定是否继续迭代。
- 实现代码执行工具,让测试员能够实际运行生成的代码并验证功能。
- 完成迷你软件公司的闭环版本,实现自动修复和重新测试。

二、详细实现步骤
步骤 1:分析当前流程的局限性
昨天的流程是:
需求 → 设计 → 编码 → 测试(结束)
问题在于:
- 测试员发现 Bug 后,流程就结束了,Bug 没有被修复。
- 代码质量没有被评审,可能不符合架构设计。
- 没有形成"发现问题 → 修复 → 重新验证"的闭环。
改进后的流程:
需求 → 设计 → 编码 → 代码评审 → 测试 → 是否通过?
│
┌─────────┴─────────┐
▼ ▼
通过 不通过
│ │
▼ ▼
结束 程序员修复 → 重新评审 → 重新测试
(最多迭代 N 次)
步骤 2:设计迭代控制策略
在 CrewAI 中,Crew 本身是一次性执行的,不支持内建循环。我们需要在 Python 层面用 while 循环包裹 Crew 的执行:
max_iterations = 3 # 最多迭代 3 次
for iteration in range(max_iterations):
# 执行 Crew(编码 → 评审 → 测试)
result = crew.kickoff()
# 检查测试是否通过
if test_passed(result):
break # 通过则退出
else:
# 不通过则进入下一轮修复
continue
关键问题:如何判断测试是否通过?
方案一:让测试员在报告中输出明确的结论(如 "测试结论:通过" 或 "测试结论:不通过"),然后用字符串匹配判断。
方案二:让测试员输出结构化的 Pydantic 对象,包含 passed: bool 字段。
我们采用方案二,更可靠。
步骤 3:定义测试报告的结构化模型
class TestReport(BaseModel):
passed: bool = Field(description="测试是否全部通过")
total_cases: int = Field(description="测试用例总数")
passed_cases: int = Field(description="通过的用例数")
failed_cases: List[str] = Field(description="失败的用例列表及原因")
bugs_found: List[str] = Field(description="发现的 Bug 列表")
suggestions: List[str] = Field(description="改进建议")
步骤 4:实现代码执行工具
为了让测试员能够实际运行代码,我们定义一个代码执行工具。这个工具接受代码文件路径,执行它,返回输出或错误信息。
import subprocess
import sys
from langchain_core.tools import tool
from typing import Annotated
@tool
def execute_python_file(
file_path: Annotated[str, "要执行的 Python 文件路径,例如 output/todo.py"]
) -> str:
"""执行指定的 Python 文件,返回标准输出和标准错误。用于验证代码是否能正常运行。"""
try:
# 使用 subprocess 执行,设置超时防止死循环
result = subprocess.run(
[sys.executable, file_path],
capture_output=True,
text=True,
timeout=10, # 最多执行 10 秒
cwd="." # 在当前目录执行
)
output = result.stdout.strip()
error = result.stderr.strip()
if result.returncode == 0:
return f"执行成功。\n输出:{output if output else '(无输出)'}"
else:
return f"执行失败(返回码 {result.returncode})。\n错误:{error}"
except subprocess.TimeoutExpired:
return "执行超时(超过 10 秒),可能存在死循环。"
except Exception as e:
return f"执行异常:{e}"
注意:这个工具只适合执行不会阻塞等待用户输入的程序。如果生成的代码需要 input() 交互,执行会卡住。我们可以在测试员的任务描述中说明"请只测试不需要交互的功能",或者让程序员生成支持命令行参数调用的代码。
步骤 5:定义代码评审员 Agent
增加一个新角色:代码评审员(可以由架构师兼任,也可以单独定义)。我们单独定义,让职责更清晰。
code_reviewer = Agent(
role="代码评审专家",
goal="审查程序员提交的代码,检查是否符合架构设计、代码规范和质量要求",
backstory="""你是一位严格的代码评审专家,有 10 年代码审查经验。
你检查代码时关注:
1. 是否实现了架构设计中定义的所有模块和接口
2. 代码是否符合 PEP8 规范(命名、缩进、注释)
3. 是否有明显的逻辑错误或边界问题
4. 是否有安全风险(如 eval、硬编码密码)
5. 是否有充分的错误处理
你的评审意见要具体,指出问题所在行或函数,并给出修改建议。
如果代码质量合格,输出"评审通过";否则输出"评审不通过"并列出问题。""",
llm=llm,
verbose=True,
allow_delegation=False
)
步骤 6:定义评审任务和修复任务
6.1 代码评审任务
task_review = Task(
description="""请审查程序员生成的代码文件 output/todo.py。
审查要点:
1. 对照架构设计文档,检查是否实现了所有模块和接口
2. 检查代码规范(命名、缩进、docstring)
3. 检查逻辑错误和边界问题
4. 检查是否有安全风险
请在报告中明确输出"评审通过"或"评审不通过",如果不通过,列出具体问题。""",
expected_output="代码评审报告,包含评审结论和具体问题列表",
agent=code_reviewer,
context=[task_coding, task_architecture],
output_file="output/code_review.md"
)
6.2 测试任务(使用代码执行工具)
task_testing = Task(
description="""请测试 output/todo.py 的代码。
步骤:
1. 先使用 execute_python_file 工具运行代码,检查是否能正常启动
2. 对照需求文档的验收标准,设计测试用例
3. 对于需要交互的功能,可以检查代码逻辑是否正确
4. 输出结构化的测试报告
注意:如果代码需要 input() 交互,请只做静态逻辑审查,不要尝试执行。""",
expected_output="结构化的测试报告,包含是否通过、测试用例、发现的 Bug 和改进建议",
agent=tester,
context=[task_coding, task_requirement],
tools=[execute_python_file], # 给测试员配备代码执行工具
output_pydantic=TestReport,
output_file="output/test_report.md"
)
6.3 修复任务(仅在测试不通过时执行)
修复任务不是每次都需要,而是在测试不通过时动态创建。我们不在 Crew 的固定任务列表中包含它,而是在循环中根据情况创建新的 Crew。
步骤 7:实现迭代闭环
def run_iteration(iteration: int, previous_test_report: TestReport = None):
"""执行一轮迭代:编码 → 评审 → 测试"""
# 如果是修复轮次,修改编码任务的描述,加入上一轮的测试报告
if previous_test_report:
coding_description = f"""根据上一轮的测试报告,修复代码中的问题。
上一轮测试报告:
- 通过的用例:{previous_test_report.passed_cases}/{previous_test_report.total_cases}
- 失败的用例:{previous_test_report.failed_cases}
- 发现的 Bug:{previous_test_report.bugs_found}
- 改进建议:{previous_test_report.suggestions}
请修改 output/todo.py,修复上述问题。输出完整的修复后代码。"""
else:
coding_description = """根据架构设计文档,编写完整的 Python 代码。
要求:
1. 代码必须可直接运行(`python todo.py` 即可启动)
2. 只使用 Python 标准库
3. 每个函数有 docstring
4. 代码符合 PEP8 规范
5. 将代码保存到 output/todo.py
请输出完整的代码,不要省略任何部分。"""
# 动态创建任务
task_coding = Task(
description=coding_description,
expected_output="完整的、可运行的 Python 代码文件",
agent=programmer,
context=[task_architecture] if not previous_test_report else [task_architecture, task_review],
output_file="output/todo.py"
)
task_review = Task(
description="请审查 output/todo.py 的代码,检查是否符合架构设计和代码规范。",
expected_output="代码评审报告",
agent=code_reviewer,
context=[task_coding, task_architecture],
output_file="output/code_review.md"
)
task_testing = Task(
description="请测试 output/todo.py,输出结构化测试报告。",
expected_output="结构化的测试报告",
agent=tester,
context=[task_coding, task_requirement],
tools=[execute_python_file],
output_pydantic=TestReport
)
crew = Crew(
agents=[programmer, code_reviewer, tester],
tasks=[task_coding, task_review, task_testing],
process=Process.sequential,
verbose=True
)
result = crew.kickoff()
# 从最后一个任务中获取结构化输出
test_report = task_testing.output.pydantic
return test_report
# 主循环
max_iterations = 3
test_report = None
for i in range(max_iterations):
print(f"\n{'='*60}")
print(f"第 {i+1} 轮迭代")
print(f"{'='*60}")
test_report = run_iteration(i, test_report)
print(f"\n测试结果:{'通过' if test_report.passed else '不通过'}")
print(f"通过用例:{test_report.passed_cases}/{test_report.total_cases}")
if test_report.passed:
print("所有测试通过,项目完成!")
break
else:
print(f"发现 {len(test_report.bugs_found)} 个 Bug,进入下一轮修复...")
else:
print(f"\n已达到最大迭代次数 {max_iterations},仍有未解决的问题。")
步骤 8:优化测试员的 Prompt
为了让测试员更有效地使用代码执行工具,在 System Prompt 中增加指导:
tester = Agent(
role="质量保障工程师",
goal="编写全面的测试用例,使用代码执行工具验证代码功能,发现潜在 Bug",
backstory="""你是一位严谨的测试工程师,擅长设计边界测试和异常测试用例。
你手头有一个工具 execute_python_file,可以执行 Python 文件并返回输出。
在执行代码前,先检查代码是否有需要 input() 的地方——如果有,不要执行,只做静态审查。
对于不需要交互的功能,尽量实际运行验证。
你的测试报告必须包含明确的"通过/不通过"结论。""",
llm=llm,
verbose=True,
allow_delegation=False
)
步骤 9:完整流程测试
运行完整流程,观察:
- 第一轮:程序员编码 → 评审员评审 → 测试员测试(可能发现 Bug)。
- 如果测试不通过,第二轮:程序员根据测试报告修复 → 评审 → 测试。
- 重复直到通过或达到最大迭代次数。
预期输出示例:
第 1 轮迭代
程序员:生成 todo.py ...
评审员:评审通过(或列出问题)
测试员:测试不通过,发现 2 个 Bug:删除功能未处理不存在的 ID;JSON 文件不存在时未初始化
第 2 轮迭代
程序员:修复了删除功能的边界问题,增加了文件初始化逻辑...
评审员:评审通过
测试员:测试通过,5/5 用例通过
项目完成!
三、常见问题与调试
Q1:代码执行工具返回超时,因为生成的代码需要 input() 交互。
→ 在测试任务描述中明确"如果代码需要交互,不要执行,只做静态审查"。也可以让程序员生成支持命令行参数调用的代码(如 python todo.py add "买牛奶"),这样执行工具可以带参数调用。
Q2:task_testing.output.pydantic 报错,提示没有 pydantic 属性。
→ 确保 output_pydantic=TestReport 已正确设置,且 CrewAI 版本支持。如果版本较旧,可以改用 output_json,然后手动解析。
Q3:迭代次数过多,Token 消耗大。
→ 设置合理的 max_iterations(如 2-3 次)。在修复任务中要求程序员"一次修复所有已知问题",避免逐个小问题反复迭代。
Q4:评审员和测试员的意见冲突(评审通过但测试不通过)。
→ 这是正常的——评审关注代码质量,测试关注功能正确性。以测试结果为准决定是否继续迭代。可以在评审任务中增加"功能正确性"的检查项,减少冲突。
Q5:修复后的代码引入了新 Bug。
→ 这正是迭代闭环的价值——下一轮测试会发现新 Bug。如果连续多轮无法收敛,可以增加"回滚"机制:保留上一版代码,如果修复后更差则回滚。
Q6:代码执行工具的安全问题。
→ 我们使用 subprocess 执行代码,存在安全风险(如代码中写 os.system("rm -rf /"))。生产环境应使用 Docker 沙箱或 RestrictedPython。当前演示环境下,可以让程序员 Agent 在 Prompt 中承诺"不包含危险操作"。
四、今日总结与作业
今天你完成了:
- ✅ 理解了单向流程的局限性,设计了迭代闭环流程。
- ✅ 增加了代码评审员角色,实现了代码质量审查。
- ✅ 实现了代码执行工具,让测试员能够实际运行代码。
- ✅ 使用 Python 循环包裹 Crew 执行,实现了"测试不通过则自动修复"的闭环。
- ✅ 定义了结构化的测试报告,便于程序判断测试是否通过。
今日作业(必做):
- 运行完整流程,测试"命令行待办事项工具"的生成。记录迭代次数、每轮测试结果和最终代码质量。
- 修改测试任务,让测试员在执行代码时传入命令行参数(如
python todo.py --help),验证程序是否支持帮助信息。提示:需要修改execute_python_file工具,增加args参数。 - 为代码评审员增加一个"安全检查"项:检查代码中是否包含
eval、exec、os.system等危险函数。如果发现,评审不通过。测试效果。 - (思考题)当前迭代闭环是"测试驱动"的——测试不通过就修复。如果评审员发现严重的架构问题(如模块划分不合理),需要回到架构设计阶段重新设计,你会如何扩展这个流程?请描述你的方案。
明日预告: 我们将学习 AutoGen 的嵌套对话和工具共享机制,探索如何将 CrewAI 和 AutoGen 结合使用,取长补短。同时,我们将对迷你软件公司项目进行最终复盘,整理代码和文档,完成第四阶段的第三个项目。
有任何问题欢迎随时评论交流!
本文介绍如何在 CrewAI 框架中构建 AI Agent 的迭代闭环流程,涵盖代码评审员角色定义、结构化测试报告建模、安全可控的代码执行工具实现,以及基于 Python 循环驱动的‘编码→评审→测试→修复’自动化流程。重点解决单向流程无法自愈的问题,提升生成代码的功能正确性与质量可靠性,为迷你软件公司项目提供可落地的 AI 软件工程实践范式。

558

被折叠的 条评论
为什么被折叠?



