1. 从“一次修复”到“持续改进”为什么反馈循环是LLM Agent的命脉最近在折腾一个基于大语言模型的代码修复Agent遇到了一个典型问题模型第一次生成的修复方案十次里有七八次跑不通单元测试。这让我开始重新审视一个被很多快速上马的Agent项目忽略的核心环节——结构化反馈。我们往往把LLM Agent想象成一个“魔法黑盒”输入问题期待它吐出完美答案。但在复杂的、需要多步推理的任务中比如修复一个包含逻辑错误的函数单次生成的成功率远低于预期。这时一个设计良好的反馈循环就成了区分“玩具”和“工具”的关键。简单来说结构化反馈循环就是让Agent具备“自我检查”和“基于错误学习”的能力。它不是简单地把错误信息扔回给模型而是将执行结果如测试失败日志、编译错误、代码风格检查报告进行解析、提炼转换成一种模型更容易理解和处理的格式再作为新的输入引导模型进行下一轮、更精准的修复尝试。这个过程的核心价值在于它将一次性的、碰运气式的生成转变为一个可迭代、可收敛的优化过程。对于开发者而言这意味着你不再需要手动反复提示、调整Agent能自己沿着“尝试-反馈-修正”的路径前进直到问题解决。无论是修复代码漏洞、优化SQL查询还是调整配置参数只要任务存在明确的成功标准如测试用例、格式规范、性能指标引入结构化反馈循环都能显著提升Agent的最终效果和可靠性。接下来我将结合实践拆解如何构建这样一个循环并分享其中几个决定成败的细节。2. 反馈循环的骨架拆解“执行-评估-再生成”的核心流程一个有效的反馈循环远不止是“如果出错就重试”。它需要一套清晰的流程确保每次迭代都能从错误中提取有效信息推动解决方案向正确方向演进。这个流程通常包含四个核心阶段构成了一个完整的闭环。2.1 阶段一初始指令与任务执行一切始于一个明确的任务描述。对于代码修复这个描述必须包含待修复的代码片段和验证其正确性的方法。后者通常是一组单元测试。你的初始指令需要清晰地封装这两者。例如不要只说“修复这个函数的bug”而应该说“请修复以下Python函数中的错误修复后的函数必须能通过附带的三个单元测试。” 同时将代码和测试用例一并提供给Agent。Agent接收到指令后会生成第一版的修复代码。这是它的“初稿”。在这个阶段模型主要依赖其预训练知识和对问题描述的浅层理解进行生成尚未接触到任何关于当前具体上下文如测试失败细节的反馈。2.2 阶段二结构化验证与错误信息提取这是整个循环的“裁判”环节。Agent或你搭建的自动化流程需要执行验证步骤。对于代码就是运行测试。关键点在于捕获所有输出而不仅仅是“通过/失败”的布尔结果。当测试失败时控制台会输出错误信息Traceback、断言失败的具体值、标准输出等。这些原始日志对人类开发者是线索但对LLM来说可能过于冗长和嘈杂。因此必须进行“结构化提取”。例如一个断言错误AssertionError: Expected output 10, but got 9可以被提取为错误类型AssertionError预期值10实际值9相关代码行 引发断言的那一行。这个过程就是将非结构化的自然语言日志转化为键值对、JSON或特定格式的文本块。工具如pytest的-v详细输出模式或专门的结果解析脚本在这里至关重要。提取出的结构化信息构成了反馈的“原材料”。2.3 阶段三反馈信息的构建与整合有了结构化的错误信息下一步是将其整合进给模型的下一轮提示Prompt中。这是体现“结构化”精髓的地方。你不能简单地把整段错误日志贴回去说“你看出错了”。一个高效的反馈整合格式通常遵循以下结构重申任务目标 “我们仍在尝试修复函数X使其通过测试Y。”呈现上一轮尝试 “上一轮你提供的修复代码如下[代码块]”清晰陈述结果 “执行测试后发生了以下错误”提供结构化反馈 以易于消化的方式列出关键错误点。例如测试test_calculate_total失败。错误类型AssertionError位置 在test.py第15行断言result 25失败。详情 函数返回了20但期望值是25。这表明计算逻辑可能漏加了某个项。给出明确的后续指令 “请分析上述错误修正你的代码并输出新的、完整的修复后函数。”这种结构化的反馈相当于为模型提供了一个清晰的“错题本”它指出了具体哪里不对、期望是什么甚至暗示了可能的方向如“漏加了某个项”极大地缩小了模型的搜索空间。2.4 阶段四迭代生成与终止条件模型接收到包含反馈的新提示后会生成新一轮的修复方案。然后流程跳回阶段二再次验证。这个循环会持续进行。这里必须设置合理的终止条件否则可能陷入死循环。常见的条件包括成功 所有测试通过。最大迭代次数 例如循环5次后仍未成功则终止并报告失败。反馈重复或退化 如果连续两轮的反馈信息完全相同或错误变得更糟可能意味着模型无法解决此问题应提前终止。超时 设定总执行时间上限。一个健壮的循环设计必须在追求成功的同时预留优雅失败的出口。3. 实战构建以Python函数修复为例的完整Agent实现理论说再多不如一行代码。让我们用一个具体的例子看看如何用Python搭建一个具备结构化反馈循环的简易代码修复Agent。这里我们会用到OpenAI的API或任何兼容接口的LLM服务和pytest来运行测试。3.1 环境准备与核心工具选择首先你需要一个LLM的API访问权限。OpenAI GPT-4或Claude 3系列是当前效果较好的选择。对于本地或对成本敏感的场景开源的DeepSeek-Coder、CodeLlama等模型也是备选但可能需要更精细的提示工程。验证工具方面pytest是Python生态的事实标准因为它能提供丰富的、可解析的输出。我们将用它来运行测试并捕获结果。我们的项目结构会很简单code_repair_agent/ ├── agent_loop.py # 主循环逻辑 ├── problem.py # 存放待修复的buggy_code和测试用例 └── requirements.txt # 依赖requirements.txt内容openai1.0.0 pytest3.2 定义问题一个有缺陷的函数及其测试我们在problem.py中定义一个简单但有代表性的问题。假设有一个计算商品总价的函数它错误地忽略了税费。# problem.py buggy_code def calculate_total(price, quantity, tax_rate0.1): \\\计算商品总价含税\\\ subtotal price * quantity # 错误忘记了加上税费 total subtotal # 这里应该是 subtotal * (1 tax_rate) return total test_code import pytest def test_calculate_total_basic(): assert calculate_total(10, 2) 22.0 # 10*2*(10.1) 22 def test_calculate_total_with_tax(): assert calculate_total(5, 3, 0.2) 18.0 # 5*3*(10.2) 18 def test_calculate_total_zero_quantity(): assert calculate_total(100, 0) 0.0 3.3 构建核心Agent循环现在我们编写agent_loop.py。这个文件包含了反馈循环的所有逻辑。# agent_loop.py import subprocess import json import sys from openai import OpenAI from problem import buggy_code, test_code # 初始化LLM客户端请替换为你的API密钥和Base URL client OpenAI( api_keyyour-api-key-here, base_urlhttps://api.openai.com/v1 # 或你的自定义端点 ) def run_tests(code_to_test): 执行测试返回结构化结果。 # 1. 将待测代码和测试代码写入临时文件 with open(temp_module.py, w) as f: f.write(code_to_test) f.write(\n\n) f.write(test_code) # 2. 使用pytest运行测试捕获输出 try: result subprocess.run( [sys.executable, -m, pytest, temp_module.py, -v, --tbshort], capture_outputTrue, textTrue, timeout30 ) except subprocess.TimeoutExpired: return {status: timeout, details: 测试执行超时} # 3. 解析pytest输出提取结构化信息 feedback {status: unknown, details: , failures: []} if result.returncode 0: feedback[status] success feedback[details] 所有测试通过 else: feedback[status] failure output_lines result.stdout.split(\n) # 简化解析寻找FAILURES部分或错误摘要 for line in output_lines: if FAILED in line: # 提取测试名和错误信息简化版 parts line.split( - ) if len(parts) 2: test_name parts[0].strip() error_msg parts[1].strip() feedback[failures].append({ test: test_name, message: error_msg }) feedback[details] result.stdout[-1000:] # 截取最后一部分详细日志 return feedback def build_prompt_with_feedback(previous_code, test_feedback, iteration): 构建包含结构化反馈的提示词。 system_message 你是一个专业的代码修复助手。你将收到一个有缺陷的代码片段、一组测试用例以及之前尝试修复后测试失败的信息。你的任务是分析失败原因并生成修正后的完整代码。只输出最终的、完整的Python函数代码不要包含任何解释或额外文本。 user_message f 迭代轮次{iteration} 原始问题修复以下函数使其能通过所有测试。 待修复的函数 python {buggy_code} 测试用例已定义用于验证 python {test_code} if previous_code: user_message f 上一轮你提供的修复代码 python {previous_code} 执行测试后的反馈 **状态**{test_feedback[status]} **详情** {test_feedback[details]} if test_feedback[failures]: user_message \n**具体的测试失败点**\n for fail in test_feedback[failures]: user_message f- 测试 {fail[test]}: {fail[message]}\n user_message \n请仔细分析上述测试失败信息修正代码中的错误并输出新的、完整的 calculate_total 函数定义。 else: user_message \n请直接输出你修复后的 calculate_total 函数定义。 return [ {role: system, content: system_message}, {role: user, content: user_message} ] def code_repair_agent(max_iterations5): 主修复循环。 current_code None for i in range(1, max_iterations 1): print(f\n 迭代第 {i} 轮 ) # 构建提示词 if i 1: # 第一轮没有上一轮代码和反馈 messages build_prompt_with_feedback(None, {}, i) else: # 后续轮次基于上一轮结果和反馈 test_result run_tests(current_code) print(f测试结果{test_result[status]}) if test_result[status] success: print(修复成功) return current_code, True messages build_prompt_with_feedback(current_code, test_result, i) # 调用LLM生成修复代码 try: response client.chat.completions.create( modelgpt-4-turbo-preview, # 可根据需要更换模型 messagesmessages, temperature0.2, # 低温度确保输出稳定 max_tokens1000 ) new_code response.choices[0].message.content.strip() # 清理代码块标记 if new_code.startswith(python): new_code new_code[9:] if new_code.endswith(): new_code new_code[:-3] new_code new_code.strip() print(f生成的新代码\n{new_code[:200]}...) # 打印前200字符预览 current_code new_code except Exception as e: print(f调用LLM API失败{e}) return None, False # 达到最大迭代次数仍未成功 print(f\n已达到最大迭代次数{max_iterations}修复失败。) final_result run_tests(current_code) print(f最终测试结果{final_result}) return current_code, False if __name__ __main__: final_code, success code_repair_agent() if success: print(\n 最终修复成功的代码) print(final_code) else: print(\n❌ 修复未成功。最后生成的代码) print(final_code)3.4 循环过程解析与关键点运行这个脚本你会观察到类似以下的输出具体内容因模型输出而异 迭代第 1 轮 生成的新代码 def calculate_total(price, quantity, tax_rate0.1): subtotal price * quantity total subtotal (subtotal * tax_rate) return total ... 迭代第 2 轮 测试结果failure 生成的新代码 def calculate_total(price, quantity, tax_rate0.1): subtotal price * quantity total subtotal * (1 tax_rate) return total ... 迭代第 3 轮 测试结果success 修复成功关键点分析第一轮模型基于初始描述生成了代码。它可能直接发现了“忘记加税”这个明显错误给出了subtotal (subtotal * tax_rate)。但我们的测试期望值是subtotal * (1 tax_rate)在浮点数计算中这两者结果可能因精度问题导致断言失败如22.0 vs 21.999999...或者模型在格式化时出了细微差错。第二轮run_tests函数执行第一轮的代码pytest会失败并返回断言错误信息例如AssertionError: assert 21.999999999999996 22.0。我们的解析逻辑虽然简单会捕获到这个失败状态和部分错误信息。构建第二轮提示时这个结构化的失败反馈“测试失败实际值21.99期望22.0”被提供给模型。第三轮模型接收到“数值接近但不完全相等”的反馈很可能推断出是计算表达式或浮点数精度问题于是将公式修正为更标准的subtotal * (1 tax_rate)从而通过了测试。这个简单的例子揭示了反馈循环的核心价值它将模型从“猜测”引导至“精确修正”。没有反馈模型可能停留在第一个近似解上有了反馈它就能进行针对性调整。4. 超越基础高级反馈策略与工程化考量上面的例子展示了基本循环但在真实、复杂的场景中我们需要更精细的策略来处理反馈的“质”与“量”。4.1 反馈信息的提炼与摘要原始的错误日志如完整的pytest -v输出可能非常冗长包含大量无关的路径信息、框架内部日志等。直接塞给模型会浪费令牌Token并可能引入噪音。策略在解析阶段进行智能摘要。例如提取堆栈跟踪中的关键行只保留与用户代码相关的文件路径和行号。归一化错误信息将AssertionError: assert 21.999999999999996 22.0摘要为“浮点数精度未通过实际值约21.999期望22.0”。关联测试意图如果测试函数名是test_calculate_total_basic可以附加说明“此测试检查基础计算逻辑”。这需要编写更健壮的日志解析器或者利用LLM本身对原始日志进行一次摘要但这会增加成本。目标是提供高信息密度、低噪音的反馈。4.2 多维度验证与复合反馈代码质量不止于通过测试。我们可能还关心代码风格是否符合PEP 8可以使用black、flake8工具。类型安全对于静态类型语言可以使用mypy。潜在漏洞可以使用bandit等安全扫描工具。性能表现是否引入了低效循环或算法。一个成熟的修复Agent应该集成这些验证工具并将它们的输出融合成一份综合的、结构化的反馈报告。例如{ “unit_tests”: {“status”: “failure”, “details”: “...”}, “code_style”: {“status”: “warning”, “details”: “第5行行超长”}, “type_check”: {“status”: “success”, “details”: “”} }然后在提示词中告诉模型“你的代码通过了类型检查但单元测试失败且存在代码风格问题。请优先修复导致测试失败的逻辑错误其次调整代码风格。”4.3 历史记忆与避免循环模型有时会陷入“死循环”在几个错误的解决方案之间来回切换。为了避免这种情况需要在提示词中引入尝试历史。策略在每次迭代的提示词中不仅包含上一轮的代码和反馈还可以简要提及之前几轮失败的根本原因由你或另一个LLM总结。例如“在第一轮中你尝试了加法公式但存在精度问题在第二轮中你调整了公式但忽略了边界条件。” 这能帮助模型避免重复过去的错误。更高级的做法是维护一个向量数据库存储每次尝试的代码嵌入和反馈在生成新方案前进行相似性检索主动提示模型避开已知的无效路径。4.4 模型指令与温度参数的调优在反馈循环中给模型的指令System Prompt至关重要。它需要明确界定Agent的角色、任务边界和输出格式。例如强调“只输出代码”、“基于给定的具体错误进行修正”、“不要引入未要求的功能”。temperature参数控制生成的随机性。在修复任务中通常建议设置为较低的值如0.1-0.3以确保模型输出稳定、确定性的修复方案而不是天马行空地尝试。但在模型陷入局部最优解时可以尝试适度调高temperature如到0.7以激发不同的解决思路。4.5 评估与回退机制不是所有问题都能在有限步骤内被解决。系统需要有一个评估机制判断当前迭代是否在进步。如果连续几轮反馈显示错误类型没有变化或者代码质量在下降如引入了语法错误就应该触发回退机制。例如可以保存历史上测试通过率最高的那个代码版本。当连续失败N次后回退到那个版本并尝试一个完全不同的提示策略或者直接向用户请求人工干预。这能防止系统在错误的方向上浪费资源。5. 避坑指南构建反馈循环时常见的五个陷阱在实际搭建和运行这类系统的过程中我踩过不少坑。这里总结五个最常见的问题希望能帮你绕过去。5.1 陷阱一反馈信息过于模糊或冗长问题直接把长达几百行的编译错误或测试日志扔给模型。模型要么无法抓住重点要么因为上下文长度限制被截断关键信息。解决必须做信息提炼。编写一个“反馈压缩器”模块其任务就是从原始输出中提取出对诊断问题有直接帮助的错误类型、位置、预期与实际值对比。对于复杂错误可以尝试用一个小型LLM如GPT-3.5-turbo先对错误进行一句话总结再将总结放入主模型的提示中。5.2 陷阱二验证环境的不一致性问题Agent在循环中生成的代码可能依赖于临时创建的文件、特定的环境变量或外部服务状态。如果每次验证的环境有细微差别如文件路径不同、依赖版本不同可能导致结果不稳定让反馈失去意义。解决使用容器化如Docker或严格的虚拟环境来确保验证环境的一致性。每次验证都在一个全新的、干净的环境中执行确保结果只与代码本身有关。这对于保证反馈循环的可靠性至关重要。5.3 陷阱三无限循环与资源消耗问题如果没有设置最大迭代次数或超时一个有缺陷的Agent可能永远运行下去消耗大量API调用和计算资源。解决如前所述必须设置硬性限制最大迭代次数如10次、总时间预算、单次验证时间限制。同时实现监控和告警当循环异常长时间运行或API调用频率异常高时能及时中断。5.4 陷阱四忽略“成功”的假阳性问题测试通过了但代码可能引入了更隐蔽的问题比如性能退化、安全漏洞或者只是凑巧通过了测试用例测试用例不完备。解决不要仅仅依赖单元测试作为唯一成功标准。在最终“成功”后可以加入一轮额外的检查代码风格检查、静态安全扫描、甚至用一组更全面的“隐藏测试”进行验证。这能有效降低假阳性率。5.5 陷阱五提示词设计缺乏演进问题从头到尾使用完全相同的提示词模板。当问题复杂时后期迭代可能需要更具体的指导而初始的通用提示词可能不够用。解决设计动态提示词。可以根据迭代轮次、错误类型来调整提示词的严厉程度或侧重点。例如如果连续失败是因为语法错误可以在提示词中强调“请特别注意Python语法规则”如果失败是因为逻辑错误则可以强调“请逐步推理计算过程并与预期结果对比”。构建一个高效的LLM Agent修复循环本质上是在设计一个能够“自我教育”的系统。结构化反馈是它的眼睛和耳朵让它能看清自己的错误并据此调整行动。从简单的代码修复到复杂的系统配置这个模式都具有强大的通用性。核心在于你必须将模糊的“出错啦”转变为清晰的“你在A点犯了B错误导致C结果而期望是D”。当你把这个问题解决好你的Agent就从只能执行简单命令的“实习生”成长为能够处理复杂任务、并在实践中学习的“熟练工”了。