1. 项目概述:基于ReAct框架的代码生成Agent
在当今AI技术快速发展的背景下,大语言模型(LLM)的应用已经不再局限于简单的文本生成。作为一名长期从事AI应用开发的工程师,我发现将LLM与特定领域的工具链结合,构建能够自主思考、决策和执行的智能体(Agent),正在成为技术落地的关键方向。
这次我要分享的是一个基于ReAct框架的代码生成Agent实战项目。这个Agent能够像人类开发者一样,完成从代码生成、语法检查到测试验证的全流程工作。不同于普通的代码补全工具,它通过"思考-行动-观察"的循环机制,实现了真正意义上的自动化编程辅助。
技术亮点:本项目将Python开发中常见的代码生成、静态检查、单元测试等环节通过工具链封装,让LLM专注于决策过程,既发挥了模型的推理能力,又避免了直接生成代码可能带来的质量问题。
2. ReAct框架原理深度解析
2.1 ReAct的核心设计思想
ReAct(Reasoning + Acting)框架的核心在于模拟人类的认知过程。在传统的大模型应用中,我们通常期望模型一次性输出完美结果。但实际开发中,程序员也是通过"编写-测试-调试"的循环来完善代码的。
ReAct框架将这个过程抽象为四个关键阶段:
- 思考(Reason): Agent分析当前任务状态,决定下一步行动
- 行动(Act): 执行选定的操作,如生成代码、运行测试等
- 观察(Observe): 获取行动结果,更新环境状态
- 循环: 重复上述过程直至任务完成
这种设计有三大优势:
- 将复杂任务分解为可管理的步骤
- 通过工具调用确保执行结果的准确性
- 允许在过程中修正错误,提高最终输出质量
2.2 代码生成场景的ReAct实现
在我们的代码生成Agent中,ReAct流程被具体化为:
mermaid复制graph TD
A[初始状态] --> B[生成核心代码]
B --> C{语法检查通过?}
C -->|是| D[生成测试用例]
C -->|否| B
D --> E{测试通过?}
E -->|是| F[输出最终结果]
E -->|否| B
这个流程确保了:
- 生成的代码首先满足基本语法要求
- 通过测试验证代码逻辑正确性
- 任何环节出错都能及时反馈并重新尝试
3. 基础组件实现细节
3.1 代码工具链封装
工具链是Agent的"双手",负责具体的代码处理工作。我们实现了CodeToolManager类,包含两个核心功能:
3.1.1 代码语法检查
python复制@staticmethod
def generate_code(code_content: str) -> str:
"""清理并验证代码语法"""
try:
# 清理Markdown代码块标记
clean_code = re.sub(r'```python|```', '', code_content).strip()
if not clean_code:
return "错误:代码为空"
# 使用Python内置compile函数验证语法
compile(clean_code, '<string>', 'exec')
return f"✅ 代码生成成功\n{clean_code}"
except SyntaxError as e:
return f"❌ 语法错误:{str(e)}\n问题代码:\n{clean_code}"
关键点:
- 首先清理可能存在的Markdown格式标记
- 使用Python标准库的
compile函数进行语法验证 - 提供清晰的错误反馈,帮助后续调试
3.1.2 代码执行验证
python复制@staticmethod
def execute_code(code_content: str, context_code: str = "") -> str:
"""执行测试代码并捕获结果"""
temp_file = "temp_code_run.py"
try:
# 拼接核心代码和测试代码
full_code = f"{context_code}\n\n{code_content}"
# 写入临时文件避免注入风险
with open(temp_file, "w", encoding="utf-8") as f:
f.write(full_code)
# 使用subprocess运行确保隔离性
result = subprocess.run(
[sys.executable, temp_file],
capture_output=True,
text=False,
timeout=10
)
# 处理输出编码
stdout = result.stdout.decode("gbk", errors="ignore") if result.stdout else ""
stderr = result.stderr.decode("gbk", errors="ignore") if result.stderr else ""
return f"✅ 执行成功\n{stdout}" if result.returncode == 0 else f"❌ 执行失败\n{stderr}"
except Exception as e:
return f"❌ 执行异常:{str(e)}"
安全考虑:
- 使用临时文件而非直接执行字符串,避免代码注入
- 限制执行时间为10秒,防止无限循环
- 捕获所有异常,确保Agent不会因工具错误而崩溃
3.2 LLM交互模块设计
LLM是Agent的"大脑",负责决策过程。我们封装了LLM类来统一交互接口:
python复制class LLM:
@staticmethod
def chat(question: str, prompt: str = "") -> str:
"""与Ollama本地模型交互"""
try:
response = ollama.chat(
model='qwen3-coder:480b-cloud',
messages=[{'role': 'user', 'content': f"{question} {prompt}"}],
stream=False,
options={
"temperature": 0.1, # 低随机性保证代码稳定性
"num_predict": 1000 # 足够长的输出空间
}
)
return response.message.content.replace("\\n", "\n")
except Exception as e:
return f"Action: Error[LLM调用失败:{str(e)}]"
模型选择考量:
- 使用专门针对代码生成的qwen3-coder模型
- 设置temperature=0.1降低随机性,提高代码可靠性
- 预留足够的输出token空间(1000)容纳完整代码
4. ReAct Agent核心实现
4.1 Agent初始化与状态管理
CodeReActAgent类是整个系统的协调中心:
python复制class CodeReActAgent:
def __init__(self, goal: str):
self.goal = goal # 任务目标描述
self.current_state = "初始状态:待生成代码"
self.core_code = "" # 保存生成的核心代码
# 系统提示词设计
self.system_prompt = f"""
你是严格遵循ReAct流程的Python代码助手,必须按以下逻辑执行:
1. 初始状态:生成核心函数 → Action: GenerateCode[代码]
2. 代码生成成功:生成测试用例 → Action: ExecuteCode[测试代码]
3. 测试通过:结束任务 → Action: Finish[总结]
4. 任何错误:输出Action: Error[错误信息]
当前任务:{self.goal}
约束:
- 处理边界情况(空列表、负数等)
- 测试用例必须覆盖典型场景
- 测试代码直接打印结果
- 不要重复生成相同代码
"""
提示词设计要点:
- 明确ReAct流程的各个阶段
- 严格定义输出格式规范
- 包含具体的编码约束条件
- 动态插入任务目标保持上下文
4.2 动作解析与执行循环
4.2.1 动作解析器
python复制def _parse_action(self, thought: str) -> Tuple[str, str]:
"""解析LLM输出的Action指令"""
action_match = re.search(r'Action:\s*(\w+)\[(.*)\]', thought, re.DOTALL)
if action_match:
action_type = action_match.group(1).strip()
action_content = action_match.group(2).strip()
# 统一清理代码格式
action_content = re.sub(r'```python|```|\n{3,}', '\n', action_content)
action_content = action_content.strip('\n')
return action_type, action_content
return "Unknown", thought
解析逻辑:
- 使用正则表达式提取Action类型和内容
- 清理冗余的代码标记和空行
- 处理未知格式的容错机制
4.2.2 主循环实现
python复制MAX_STEPS = 10 # 防止无限循环
def run(self) -> str:
"""执行ReAct循环"""
for step in range(1, self.MAX_STEPS + 1):
# 1. 思考阶段
thought = self.llm.chat(
question=f"当前状态:{self.current_state}\n下一步行动?",
prompt=self.system_prompt
)
# 2. 行动阶段
action_type, action_content = self._parse_action(thought)
if action_type == "GenerateCode":
observation = self.code_tools.generate_code(action_content)
self.core_code = action_content
elif action_type == "ExecuteCode":
observation = self.code_tools.execute_code(action_content, self.core_code)
elif action_type == "Finish":
break
elif action_type == "Error":
observation = f"错误:{action_content}"
# 3. 观察阶段
self.current_state = observation
# 终止条件检查
if action_type in ["Finish", "Error"] or step == self.MAX_STEPS:
break
return self._generate_report()
循环控制:
- 限制最大步数防止无限循环
- 清晰的状态转移逻辑
- 全面的异常处理机制
- 自动生成最终报告
5. 实战演示与效果分析
5.1 完整运行示例
以"编写计算偶数平方和的函数"为例:
python复制if __name__ == "__main__":
task_goal = """
编写Python函数,输入数字列表,输出所有偶数的平方和。
要求:
1. 处理空列表、负数、零等边界情况
2. 生成测试用例验证函数正确性
3. 确保代码可直接运行
"""
agent = CodeReActAgent(goal=task_goal)
print(agent.run())
5.2 典型执行流程
-
第一轮循环
- LLM生成核心函数代码
- 工具链验证语法通过
- 状态更新为"代码生成成功"
-
第二轮循环
- LLM生成测试用例代码
- 工具链执行测试并验证逻辑
- 状态更新为"测试通过"
-
终止
- LLM输出完成指令
- Agent生成最终报告
5.3 生成代码示例
核心函数:
python复制def sum_of_squares_of_evens(numbers):
return sum(num ** 2 for num in numbers if num % 2 == 0)
测试代码:
python复制if __name__ == '__main__':
# 测试空列表
print(sum_of_squares_of_evens([])) # 应输出0
# 测试正常情况
print(sum_of_squares_of_evens([1, 2, 3, 4])) # 应输出20
# 测试负数情况
print(sum_of_squares_of_evens([-2, -4, 1, 3])) # 应输出20
6. 关键问题与优化方向
6.1 常见问题排查
-
LLM不遵循指定格式
- 强化系统提示词中的格式要求
- 在解析阶段添加更严格的校验
-
代码生成质量不稳定
- 调整temperature参数降低随机性
- 使用更专业的代码生成模型
-
工具执行失败
- 加强错误处理和状态回滚
- 添加执行环境检查机制
6.2 性能优化建议
-
缓存机制
- 缓存已验证通过的代码片段
- 避免重复生成相同逻辑的代码
-
并行验证
- 对多个测试用例并行执行
- 减少整体运行时间
-
增量生成
- 基于已有代码进行增量修改
- 而非每次都从头生成
6.3 扩展应用场景
-
多语言支持
- 扩展工具链支持JavaScript、Java等语言
- 根据文件后缀自动选择验证工具
-
复杂项目支持
- 添加多文件管理能力
- 支持跨文件引用和测试
-
集成开发环境
- 开发IDE插件
- 实现实时交互式编程辅助
7. 开发心得与经验分享
在实际开发这个ReAct Agent的过程中,我总结了以下几点重要经验:
-
工具链的可靠性至关重要
- 工具链的任何错误都会导致整个Agent失败
- 必须对工具输出进行严格验证
- 建议为每个工具编写单元测试
-
提示词工程需要迭代优化
- 初始版本的提示词往往不够完善
- 需要通过实际运行不断调整
- 建议保存历史提示词版本便于回滚
-
状态设计要简洁明确
- 过于复杂的状态机难以维护
- 状态信息应该包含足够但不过量的上下文
- 使用枚举类型定义有限状态集
-
日志记录是调试的关键
- 详细记录每个循环的状态变化
- 保存LLM的原始输入输出
- 建议实现日志分级控制
-
性能监控不可忽视
- 统计每个步骤的耗时
- 监控LLM调用的token使用量
- 设置合理的超时限制