【三个月 AI Agent 实战学习】Day 51:完善迷你软件公司 —— 代码评审、迭代修复与工具共享

本文介绍如何在 CrewAI 框架中构建 AI Agent 的迭代闭环流程,涵盖代码评审员角色定义、结构化测试报告建模、安全可控的代码执行工具实现,以及基于 Python 循环驱动的‘编码→评审→测试→修复’自动化流程。重点解决单向流程无法自愈的问题,提升生成代码的功能正确性与质量可靠性,为迷你软件公司项目提供可落地的 AI 软件工程实践范式。

Day 51:完善迷你软件公司 —— 代码评审、迭代修复与工具共享

欢迎来到第五十一天!昨天我们搭建了迷你软件公司的骨架,完成了"需求 → 设计 → 编码 → 测试"的单向流程。但真实的软件开发不是一次性的——测试发现问题后,程序员需要修复 Bug,架构师需要评审代码质量,然后再次测试,直到所有验收标准通过。今天我们将为这个项目增加代码评审和迭代修复环节,形成闭环,并让 Agent 共享代码执行工具,让测试从"纸上审查"升级为"实际运行验证"。


一、今日学习目标

  1. 理解当前单向流程的局限性:测试发现问题后无法自动修复。
  2. 学会设计"编码 → 评审 → 测试 → 修复"的迭代闭环流程。
  3. 掌握在 CrewAI 中实现迭代逻辑:用 Python 循环包裹 Crew 执行,根据测试结果决定是否继续迭代。
  4. 实现代码执行工具,让测试员能够实际运行生成的代码并验证功能。
  5. 完成迷你软件公司的闭环版本,实现自动修复和重新测试。

在这里插入图片描述

二、详细实现步骤

步骤 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:完整流程测试

运行完整流程,观察:

  1. 第一轮:程序员编码 → 评审员评审 → 测试员测试(可能发现 Bug)。
  2. 如果测试不通过,第二轮:程序员根据测试报告修复 → 评审 → 测试。
  3. 重复直到通过或达到最大迭代次数。

预期输出示例:

第 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 执行,实现了"测试不通过则自动修复"的闭环。
  • ✅ 定义了结构化的测试报告,便于程序判断测试是否通过。

今日作业(必做):

  1. 运行完整流程,测试"命令行待办事项工具"的生成。记录迭代次数、每轮测试结果和最终代码质量。
  2. 修改测试任务,让测试员在执行代码时传入命令行参数(如 python todo.py --help),验证程序是否支持帮助信息。提示:需要修改 execute_python_file 工具,增加 args 参数。
  3. 为代码评审员增加一个"安全检查"项:检查代码中是否包含 eval、exec、os.system 等危险函数。如果发现,评审不通过。测试效果。
  4. (思考题)当前迭代闭环是"测试驱动"的——测试不通过就修复。如果评审员发现严重的架构问题(如模块划分不合理),需要回到架构设计阶段重新设计,你会如何扩展这个流程?请描述你的方案。

明日预告: 我们将学习 AutoGen 的嵌套对话和工具共享机制,探索如何将 CrewAI 和 AutoGen 结合使用,取长补短。同时,我们将对迷你软件公司项目进行最终复盘,整理代码和文档,完成第四阶段的第三个项目。

有任何问题欢迎随时评论交流!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值