从零构建Coding Agent:基于ReAct与LangChain的AI编程助手实现

发布时间:2026/8/14 2:24:51
从零构建Coding Agent:基于ReAct与LangChain的AI编程助手实现 1. 项目概述为什么我们要亲手造一个Coding Agent最近和几个做AI应用开发的朋友聊天大家不约而同地提到了一个词Coding Agent。无论是GitHub上满天飞的开源项目还是各种AI编程助手的宣传似乎一夜之间让大模型自己写代码、自己调试、自己完成一个完整任务的“智能体”成了新的风口。但说实话很多现成的框架要么太重要么黑盒你只知道它能跑却不知道它为什么能跑更别提根据自己业务去定制了。所以我决定自己动手从零开始用最直接的方式实现一个简易版的Coding Agent。这个项目的目标不是要造一个能替代程序员的超级AI而是想彻底搞明白一个能“思考”并“执行”代码任务的智能体它的核心骨架到底是什么。我们会用到当下最热门的几个技术栈大语言模型LLM、ReAct推理框架、以及LangChain这样的工具链但我们会抛开那些复杂的封装从最基础的原理开始搭建。这个项目非常适合两类朋友一是对AI应用开发感兴趣想深入理解Agent工作原理的开发者二是已经用过一些现成工具比如AutoGPT、Cursor的Agent模式但总觉得隔靴搔痒想自己掌控更多细节的技术爱好者。通过这个项目你不仅能得到一个可以运行的小型Coding Agent更重要的是你能掌握一套构建更复杂、更定制化AI工作流的方法论。2. 核心架构设计拆解一个Coding Agent的“大脑”与“手脚”要构建一个能自主编码的Agent我们首先要把它拆解成几个核心模块。一个典型的Coding Agent其工作流程可以抽象为“感知-思考-行动-观察”的循环。在我们的简易版实现中这个循环主要由三个部分构成一个负责规划和推理的“大脑”基于ReAct的LLM一套可供调用的“工具”Tools以及一个协调整个流程的“调度中心”Agent Executor。2.1 大脑基于ReAct框架的LLM推理引擎ReActReasoning Acting是目前让LLM具备规划能力最流行的范式之一。它的核心思想是让模型交替进行“推理”和“行动”。在代码生成的场景下推理就是分析任务、制定步骤比如“我需要先创建一个文件然后写入函数头”行动就是调用具体的工具去执行比如调用“写文件”工具。为什么选择ReAct而不是其他方式因为纯链式调用Chain缺乏灵活性而纯工具调用Tool Calling又缺乏规划性。ReAct巧妙地将两者结合让LLM能根据当前环境和历史结果动态调整计划这非常接近人类程序员解决问题的方式先想个大概写点代码运行看看报错再根据报错信息调整思路。在我们的实现中这个“大脑”就是一个被特殊提示词Prompt武装起来的LLM。这个提示词会明确告诉模型你现在是一个Coding Agent你可以使用一系列工具你的输出必须严格遵循“Thought: ... Action: ... Action Input: ... Observation: ...”的格式。其中Thought是模型的内部推理Action是要调用的工具名Action Input是工具的输入参数Observation是工具执行后返回的结果。模型根据Observation再进行下一轮的Thought如此循环直到任务完成或达到步数限制。2.2 手脚为Agent配备的关键工具集一个只会“想”的Agent是没用的它必须能“做”。对于Coding Agent来说它的“手脚”就是一系列可以操作代码环境的工具。这些工具的设计至关重要直接决定了Agent的能力边界。在我们的简易版中我们会实现几个最基础但必不可少的工具文件读写工具包括创建文件、读取文件内容、向文件追加内容。这是代码生成的基石。代码执行工具能够在一个安全的、隔离的环境中执行一段Python代码并捕获输出和错误。这是实现“运行-调试”循环的关键。代码分析工具例如一个简单的代码语法检查可以用ast模块实现或者获取当前目录文件列表。这为Agent提供了环境感知能力。每个工具都需要被封装成一个标准的函数具有清晰的输入输出描述。这些描述会被写入给LLM的提示词中让LLM知道它能用什么、怎么用。例如execute_python_code工具的描述可能是“执行一段Python代码字符串返回标准输出和标准错误。如果执行成功输出结果如果失败返回错误信息。”注意工具的设计原则是“单一职责”和“描述清晰”。一个工具只做一件事并且它的功能描述要让LLM能准确理解。过于复杂的工具会让LLM困惑导致错误调用。2.3 调度中心LangChain的AgentExecutor虽然我们可以完全手动实现ReAct循环用一个while循环不断解析LLM输出、调用工具、拼接历史但这会涉及大量繁琐的字符串解析和状态管理。为了更高效、更可靠我们引入LangChain的AgentExecutor。AgentExecutor就像一个老练的项目经理它负责驱动循环自动管理ReAct的“思考-行动-观察”循环。解析输出使用OutputParser来解析LLM生成的文本提取出ThoughtActionAction Input。调用工具根据解析出的Action找到对应的工具函数并执行。处理错误当LLM输出格式错误或工具调用失败时能进行优雅的处理比如将错误信息作为Observation反馈给LLM让它重试。限制步骤防止Agent陷入死循环通过设置max_iterations参数来控制最大执行步数。使用AgentExecutor我们就能将主要精力集中在核心的Agent逻辑Prompt设计和工具实现上而不用重复造轮子处理执行引擎的细节。它提供了生产级应用所需的鲁棒性。3. 环境准备与核心组件实现纸上谈兵终觉浅我们现在就来动手搭建。整个项目基于Python你需要准备Python 3.8或以上的环境。我们将使用openai库或兼容OpenAI API的库来调用LLM使用langchain框架来构建Agent。3.1 安装依赖与初始化首先创建一个新的项目目录并初始化虚拟环境。然后安装核心依赖pip install langchain langchain-openai python-dotenv这里我们使用langchain-openai这是LangChain官方维护的OpenAI集成包。同时安装python-dotenv来管理环境变量比如你的OpenAI API密钥。接下来在项目根目录创建一个.env文件写入你的API密钥OPENAI_API_KEY你的密钥然后在代码中初始化LLM。我们选择gpt-3.5-turbo作为起点它成本低、速度快足够完成我们的演示。对于更复杂的任务可以升级到gpt-4。import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI load_dotenv() llm ChatOpenAI( modelgpt-3.5-turbo, temperature0, # 温度设为0使输出更确定、更可靠 api_keyos.getenv(OPENAI_API_KEY) )将temperature设置为0非常重要。对于需要精确执行指令的Agent任务我们希望LLM的输出尽可能稳定、可预测减少随机性带来的不确定性。3.2 实现核心工具函数工具是Agent能力的延伸。我们来实现前面提到的三个核心工具。我们将使用LangChain的tool装饰器来封装它们这能让AgentExecutor自动识别和管理这些工具。1. 文件读写工具from langchain.tools import tool import os tool def write_file(file_path: str, content: str) - str: 将内容写入指定文件。如果文件已存在会被覆盖。 try: os.makedirs(os.path.dirname(file_path), exist_okTrue) with open(file_path, w, encodingutf-8) as f: f.write(content) return f成功将内容写入文件{file_path} except Exception as e: return f写入文件时出错{str(e)} tool def read_file(file_path: str) - str: 读取指定文件的全部内容。 try: if not os.path.exists(file_path): return f错误文件 {file_path} 不存在。 with open(file_path, r, encodingutf-8) as f: content f.read() return content except Exception as e: return f读取文件时出错{str(e)} tool def list_files(directory: str .) - str: 列出指定目录下的所有文件和文件夹。 try: items os.listdir(directory) return f目录 {directory} 下的内容\n \n.join(items) except Exception as e: return f列出目录时出错{str(e)}2. 代码执行工具这是一个关键且需要谨慎处理的工具。为了安全我们不应该允许Agent执行任意代码。这里我们实现一个非常基础的、在子进程中执行代码的版本并强烈建议在实际生产环境中使用沙箱如Docker容器进行隔离。import subprocess import sys from io import StringIO import contextlib tool def execute_python_code(code: str) - str: 在独立进程中执行一段Python代码并返回其输出或错误信息。 # 这是一个简易实现生产环境需沙箱隔离 try: # 使用subprocess在独立进程中运行避免影响主进程 result subprocess.run( [sys.executable, -c, code], capture_outputTrue, textTrue, timeout30 # 设置超时防止无限循环 ) if result.returncode 0: output result.stdout.strip() return f代码执行成功。输出\n{output} if output else 代码执行成功无输出。 else: error result.stderr.strip() return f代码执行失败。错误\n{error} except subprocess.TimeoutExpired: return 错误代码执行超时超过30秒。 except Exception as e: return f执行过程中发生未知错误{str(e)}重要安全警告execute_python_code工具是最大的安全风险点。上述实现仅用于学习和演示它仍然可能执行危险操作如import os; os.system(rm -rf /)。在真实项目中你必须使用严格的沙箱技术例如使用docker运行代码在一个无网络、只读文件系统的容器中。使用像pysandbox这样的专门库但需注意其维护状态。在云函数或专门的安全环境中执行。 永远不要在生产服务器上直接运行来自不可信LLM生成的代码。3. 代码分析工具示例简单语法检查import ast tool def check_python_syntax(code: str) - str: 检查一段Python代码的语法是否正确。 try: ast.parse(code) return 语法检查通过代码语法正确。 except SyntaxError as e: return f语法错误{e.msg} 在 第{e.lineno}行第{e.offset}列。将所有工具收集到一个列表中tools [write_file, read_file, list_files, execute_python_code, check_python_syntax]3.3 构建ReAct提示词与Agent有了LLM和工具我们需要用提示词将它们“粘合”起来告诉LLM它扮演的角色、可用的工具以及必须遵守的输出格式。LangChain为ReAct模式提供了内置的提示词模板我们可以基于它进行定制。from langchain.agents import create_react_agent from langchain.agents import AgentExecutor from langchain import hub # 从LangChain Hub拉取一个ReAct提示词模板并做自定义 prompt hub.pull(hwchase17/react) # 你也可以直接使用字符串定义更复杂的提示词 custom_prompt 你是一个专业的Python编程助手。你的任务是理解用户的需求并通过使用工具一步步地完成代码编写、执行和调试。 你必须严格按以下格式回应 Thought: 你需要思考当前情况分析下一步该做什么。 Action: 你要执行的动作必须是以下工具之一[{tool_names}] Action Input: 所选动作的输入必须是一个合法的JSON字符串。 Observation: 动作执行后的结果。 当你认为任务已经完成或者无法继续进行时你必须输出 Thought: 我已经完成了任务。 Final Answer: [你的最终回答例如生成的代码位置、执行结果等] 开始 历史对话 {chat_history} 工具描述 {tools} 任务{input} {agent_scratchpad} # 在实际使用中我们可以用custom_prompt替换从hub拉取的prompt # 创建ReAct Agent agent create_react_agent(llm, tools, prompt) # 创建执行器 agent_executor AgentExecutor.from_agent_and_tools( agentagent, toolstools, verboseTrue, # 设置为True可以看到Agent详细的思考过程便于调试 handle_parsing_errorsTrue, # 优雅处理解析错误 max_iterations10, # 防止无限循环 early_stopping_methodgenerate # 当LLM输出Final Answer时停止 )关键点在于verboseTrue这会在控制台打印出Agent完整的“思考-行动-观察”链对于我们调试和理解Agent的工作流程至关重要。max_iterations是安全阀必须设置。4. 实战演练让Agent完成一个具体任务现在我们的简易Coding Agent已经组装完毕。让我们用一个具体的任务来测试它“创建一个名为calculator.py的文件在其中实现一个能进行加、减、乘、除运算的计算器类Calculator并写一个简单的测试用例来验证它。”我们将任务输入给agent_executortask 请完成以下任务 1. 创建一个名为 calculator.py 的文件。 2. 在该文件中定义一个名为 Calculator 的类。 3. 该类应包含 add, subtract, multiply, divide 四个方法分别实现加、减、乘、除运算。 4. 除法方法应处理除零错误。 5. 在同一个文件的 if __name__ __main__: 块中编写测试代码实例化Calculator并测试每个方法。 请一步步完成。 result agent_executor.invoke({input: task}) print(\n 任务完成 ) print(result[output])当verboseTrue时你会在控制台看到类似下面的输出已简化 进入新的AgentExecutor链... Thought: 用户要求我创建一个计算器类并测试。我需要先创建文件然后写入代码。 Action: write_file Action Input: {file_path: calculator.py, content: } Observation: 成功将内容写入文件calculator.py Thought: 文件已创建现在需要写入Calculator类的代码。我应该先定义类和方法。 Action: write_file Action Input: {file_path: calculator.py, content: class Calculator:\n def add(self, a, b):\n return a b\n\n def subtract(self, a, b):\n return a - b\n\n def multiply(self, a, b):\n return a * b\n\n def divide(self, a, b):\n if b 0:\n raise ValueError(\除数不能为零\)\n return a / b\n\nif __name__ __main__:\n calc Calculator()\n print(calc.add(5, 3))\n print(calc.subtract(5, 3))\n print(calc.multiply(5, 3))\n print(calc.divide(6, 3))} Observation: 成功将内容写入文件calculator.py Thought: 代码已经写入。现在我应该运行这个文件来测试它是否工作正常。 Action: execute_python_code Action Input: {code: import sys\nsys.path.insert(0, .)\nfrom calculator import Calculator\n\ncalc Calculator()\nprint(加法测试:, calc.add(5, 3))\nprint(减法测试:, calc.subtract(5, 3))\nprint(乘法测试:, calc.multiply(5, 3))\nprint(除法测试:, calc.divide(6, 3))\ntry:\n calc.divide(5, 0)\nexcept ValueError as e:\n print(除零错误测试:, e)} Observation: 代码执行成功。输出 加法测试: 8 减法测试: 2 乘法测试: 15 除法测试: 2.0 除零错误测试: 除数不能为零 Thought: 所有测试都通过了。任务完成。 Final Answer: 已成功创建 calculator.py 文件其中包含了 Calculator 类和测试代码。所有功能测试均已通过包括除零错误处理。通过这个流程我们可以看到Agent是如何一步步思考并行动的它先创建文件然后写入初步代码接着执行代码进行测试最后确认任务完成。整个过程完全自动化无需人工干预编码细节。5. 深入优化与高级技巧一个能跑起来的Demo只是起点。要让这个Coding Agent真正实用我们还需要在稳定性、安全性和能力上进行深度优化。5.1 提升规划与推理的可靠性LLM的“思考”并不总是靠谱的。你可能会遇到以下问题规划混乱Agent可能跳过关键步骤比如忘记导入模块。工具误用错误地调用工具或传入错误格式的参数。陷入循环在两个步骤间来回切换无法推进。优化策略1增强提示词工程在基础ReAct提示词中加入更多约束和示例Few-shot Learning。例如在提示词开头提供一两个完整的成功任务示例让LLM模仿正确的推理路径。还可以加入更严格的格式要求比如“Action Input必须是纯字符串不要带引号外的JSON标记”。优化策略2引入验证步骤在Agent执行关键操作如覆盖重要文件、执行复杂代码前可以插入一个“验证”工具或步骤。例如在执行write_file覆盖现有文件前先调用read_file确认内容或者设计一个confirm_action工具让LLM生成一个需要用户或系统确认的摘要。优化策略3使用更强大的模型对于复杂任务gpt-3.5-turbo可能力不从心。切换到gpt-4或claude-3系列模型能显著提升规划能力和代码生成质量。虽然成本增加但对于关键任务来说是值得的。5.2 扩展工具集以增强能力基础工具集只能完成简单任务。一个强大的Coding Agent应该能处理更复杂的软件开发场景。版本控制工具集成git命令让Agent能够git addgit commit甚至基于错误信息进行git revert。tool def git_commit(message: str) - str: 执行 git add . 和 git commit -m \...\ # ... 实现代码依赖管理工具检查requirements.txt使用pip安装缺失的包。tool def install_python_package(package_name: str) - str: 使用pip安装指定的Python包。 # ... 实现代码需注意安全网络搜索工具当遇到未知错误时让Agent能“谷歌一下”。这可以通过集成Serper API或DuckDuckGo Search来实现让LLM根据错误信息生成搜索查询获取解决方案。tool def search_web(query: str) - str: 在互联网上搜索信息。返回搜索结果的摘要。 # ... 调用搜索API单元测试工具不仅运行代码还能运行特定的测试框架如pytest并解析测试结果反馈给Agent。tool def run_pytest(test_file: str) - str: 运行pytest测试文件并返回测试结果摘要。 # ... 实现代码5.3 实现记忆与状态管理目前的Agent是“无状态”的每轮对话都是独立的。但对于一个编码任务记住之前的上下文比如已经创建了哪些文件修改了哪些函数至关重要。短期记忆ConversationBufferMemory使用LangChain的ConversationBufferMemory可以将整个对话历史包括Thought Action Observation保存下来并作为上下文传递给下一轮LLM调用。这能有效解决“遗忘”问题。from langchain.memory import ConversationBufferMemory memory ConversationBufferMemory(memory_keychat_history, return_messagesTrue) # 在创建agent时将memory传入prompt模板长期记忆向量数据库对于更复杂的项目我们可以将每次任务执行的关键节点如生成的代码片段、遇到的错误及解决方案存储到向量数据库如Chroma Pinecone中。当遇到类似问题时Agent可以先在记忆库中检索相关解决方案实现“经验复用”。5.4 安全与权限管控的终极考量这是将Coding Agent投入实际使用的最大门槛。我们必须建立一个“最小权限原则”的执行环境。文件系统沙箱使用chroot或容器技术将Agent限制在一个特定的工作目录内禁止其访问系统其他部分。网络隔离默认情况下代码执行环境应无网络访问权限。如果某些工具如search_webinstall_package需要网络应通过严格审计的代理网关进行。资源限制对代码执行工具设置严格的CPU时间、内存使用和运行时间限制。工具白名单只暴露必要的工具。像os.systemsubprocess.call这类高风险函数绝对不能在工具中直接暴露。人工审核环节对于生产环境可以设计“关键操作需人工批准”的流程。例如Agent生成的代码在合并到主分支前必须经过一个Pull Request流程由人类开发者审核。6. 常见问题排查与实战心得在开发和测试这个Coding Agent的过程中我踩过不少坑。这里把一些典型问题和解决方案记录下来希望能帮你节省时间。6.1 Agent陷入循环或输出无关内容现象Agent的Thought开始重复或者开始讨论与任务无关的哲学问题。原因通常是提示词不够清晰或者max_iterations设置过高让LLM有了“瞎想”的空间。也可能是工具返回的Observation过于冗长干扰了LLM。解决检查并强化提示词中的指令例如明确写上“请专注于当前任务不要讨论任务之外的内容”。适当降低temperature设为0。在工具函数中确保返回的Observation信息简洁、结构化。例如执行代码成功时不要返回整个庞大的输出日志只返回“执行成功”和关键结果摘要。将max_iterations设为一个合理的值如15。6.2 工具调用格式错误现象LLM输出的Action Input不是合法的JSON或者参数名与工具函数定义不匹配。原因LLM没有严格遵守格式或者工具的函数签名参数名、类型提示不够清晰。解决利用LangChain的StructuredTool来定义工具它可以为LLM生成更精确的JSON Schema描述。在提示词中提供更具体的工具调用示例。在AgentExecutor中确保handle_parsing_errorsTrue这样当解析失败时错误信息会作为Observation反馈给LLM让它有机会纠正自己。6.3 代码生成质量不高现象生成的代码有语法错误、逻辑错误或者不符合最佳实践比如没有错误处理。原因LLM本身的能力限制或者任务描述不够具体。解决任务拆解不要给一个庞大模糊的任务如“构建一个Web应用”。而是将其拆解成原子性的小任务如“创建app.py文件”、“定义User模型类”一步步引导Agent。迭代优化采用“生成-审查-修正”的循环。先让Agent生成代码然后用check_python_syntax或execute_python_code工具进行测试将错误信息反馈给它让它自行修正。这模拟了人类的编程调试过程。提供上下文在任务描述中提供更多的上下文信息比如“请使用FastAPI框架”、“请遵循PEP8编码规范”。6.4 执行速度慢或成本高现象完成一个简单任务需要很多步消耗大量Token导致速度慢、API调用成本高。原因ReAct的每一步都需要调用一次LLM思考过程Thought也会消耗Token。优化缓存对相同的工具调用如读取同一个文件多次结果进行缓存。简化思考尝试让LLM生成更简短的Thought。可以通过在提示词中要求“Thought应简洁明了”来实现。使用更快的模型对于简单、模式化的任务步骤可以尝试使用更小、更快的模型如gpt-3.5-turbo-instruct来驱动Agent或者将部分决策逻辑固化到程序里。6.5 个人实战心得从小任务开始不要一开始就让Agent去写一个完整的项目。从“创建一个Hello World文件”开始逐步增加复杂度。这有助于你调试整个流程建立信心。Verbose模式是你的朋友在开发阶段务必开启verboseTrue。仔细观察Agent的每一步Thought和Action这是理解其“思维过程”、定位问题根源的唯一途径。工具设计比模型调优更重要很多时候Agent表现不佳不是因为模型不够聪明而是因为工具设计得不好。工具应该像乐高积木一样功能单一、接口清晰、组合性强。花时间打磨你的工具集收益远大于盲目升级到更贵的模型。接受不完美当前的AI Agent远未达到完美。它会犯傻会绕弯路。我们的目标不是创造一个全能的AI程序员而是创造一个能处理特定类型、可重复编码任务的可靠助手。明确它的能力边界在边界内使用它你会获得巨大的效率提升。通过这个从零搭建简易Coding Agent的过程我们不仅实现了一个有趣的AI应用更深入理解了智能体Agent技术的核心组件和工作原理。这套由规划器LLMPrompt、工具集Tools和执行器AgentExecutor构成的架构是构建各类AI Agent的通用蓝图。你可以在此基础上更换不同的LLM添加更强大的工具如集成IDE接口、连接数据库甚至引入多智能体协作创造出更自动化、更智能的开发体验。