
1. 项目概述一个极简Agent的诞生最近在AI编程工具圈里一个概念被反复提及Agent。无论是Claude Code、GitHub Copilot背后的Codex模型还是新兴的Cursor编辑器它们都在强调自己具备“智能体”的能力能够理解上下文、规划步骤并执行任务。听起来很高大上对吧但作为一个常年折腾开发工具的老手我一直在想这些商业产品宣称的“智能”底层到底是不是被过度包装了我们能不能用最少的代码揭开这层神秘面纱自己搭一个能跑起来的“微型Agent”答案是肯定的。经过一段时间的摸索和实验我发现抛开那些复杂的框架和云服务一个具备基础感知、决策和执行循环的Agent核心真的可以用寥寥数行代码实现。这并非要替代那些成熟工具而是通过亲手构建帮助我们真正理解所谓“AI智能体”的底层运作逻辑。当你明白了这个简单的核心再去看Claude Code帮你写代码、Cursor自动重构文件就会有一种“哦原来如此”的通透感。本文将从一个实战开发者的角度带你从零开始用6行左右代码构建一个可运行的Agent内核并以此为基础探讨其与主流智能编程工具在底层思想上的共通之处。2. 智能体Agent核心逻辑拆解在开始写代码之前我们必须先统一思想一个Agent究竟是什么抛开学术界复杂的定义在工程实践层面尤其是编程辅助场景下一个Agent可以简化为一个持续运行的循环这个循环包含三个关键阶段感知Perception、决策Decision、执行Action。听起来很像机器人领域的“感知-规划-执行”范式没错其思想内核是相通的。感知对于代码Agent来说就是获取当前环境的“上下文”。这可能是你正在编辑的文件内容、终端里最新的错误信息、项目文件树的结构或者你刚刚输入的自然语言指令。决策是Agent的“大脑”它基于感知到的上下文决定接下来要做什么。这个大脑通常是一个语言模型LLM它分析信息并输出一个结构化的“动作”描述比如“在文件A的第10行插入一段代码”或“运行命令npm install”。执行就是将决策付诸实践真正地去修改文件、运行命令或调用某个API。而驱动这个循环运转的是一个核心机制循环与状态管理。Agent需要记住之前做过什么历史当前的目标是什么目标并根据执行结果决定是继续下一个动作还是任务已完成。这个“运行-观察-再运行”的循环是区分一个简单脚本和一个智能体的关键。我们即将构建的极简版本正是对这个核心循环的高度抽象。3. 六行代码构建最小可行Agent理论说再多不如动手。下面就是我们的“六行代码”Agent核心骨架。请注意这六行是逻辑核心为了使其运行我们需要一些基本的准备工作。首先你需要一个Python环境并安装openai库或其他你喜欢的LLM SDK。这里我们以OpenAI API为例因为它接口规范易于说明。实际你可以替换为任何提供类似Completion功能的模型API包括本地部署的模型。import openai class MiniAgent: def __init__(self, system_prompt): self.messages [{role: system, content: system_prompt}] def run(self, user_input): self.messages.append({role: user, content: user_input}) response openai.ChatCompletion.create(modelgpt-3.5-turbo, messagesself.messages) action response.choices[0].message.content print(f“Agent决策: {action}”) # 此处应解析action并执行为简化我们仅打印 # 实际执行后可将结果作为新一轮的“感知”追加到messages中 self.messages.append({role: “assistant”, “content”: action}) return action让我们拆解这六行核心逻辑集中在run方法里self.messages.append(...)感知。将用户的输入或任何环境信息作为新的上下文追加到对话历史中。response openai.ChatCompletion.create(...)决策。将完整的对话历史包含系统指令、历史、最新问题发送给LLM让它基于所有信息做出下一步的“思考”。action response.choices[0].message.content获取LLM输出的决策文本。print(f“Agent决策: {action}”)执行的展示。这里简化了只是打印出来。在完整版中这里会有一个解析器将action文本解析成具体的命令或函数调用。self.messages.append(...)状态更新。将Agent自己输出的动作追加到历史中这一步至关重要。它让LLM在下一轮循环中知道自己刚才“说过”什么实现了状态的延续。如果执行后有结果比如命令输出那个结果也应该作为user或system消息追加进去形成“观察”。return action返回动作以便外层循环处理。初始化的重要性__init__中的system_prompt是Agent的“人格”和“能力边界”设定。例如你可以设定“你是一个Python编程助手只能输出可执行的Python代码片段或Shell命令。每次只做一个动作。” 这个系统提示词的质量直接决定了Agent的行为模式。注意这六行代码是一个极度简化的“决策生成器”。它缺少了真正的动作解析与执行模块。一个完整的Agent还需要一个Executor来运行代码或命令并将执行结果作为新的“感知”输入从而形成闭环。但正是这个简化的核心揭示了所有Agent框架共有的工作流。4. 从极简核心到实用化改造上面的六行代码只是一个起点它只能“说”不能“做”。要让它变成一个能真正交互的实用工具我们需要在核心循环周围添砖加瓦。关键在于动作的标准化与安全执行。4.1 定义动作空间与解析决策LLM输出的action是一段自然语言比如“现在需要安装requests库请运行pip install requests”。我们需要引导LLM输出结构化的数据方便程序解析。通常有两种方式函数调用Function Calling这是目前主流API如OpenAI, Claude支持的方式。在请求中定义好工具函数的 schema名称、描述、参数LLM会返回一个包含具体函数名和参数的JSON对象。这是最推荐的方式因为它结构化、无歧义。文本约定在系统提示词中严格要求LLM以特定格式输出比如COMMAND: pip install requests。然后我们用正则表达式去解析。这种方式更灵活但不稳定依赖于LLM的遵循程度。让我们用函数调用的方式升级我们的MiniAgent。假设我们只允许两个动作运行Shell命令和写入文件。import openai import json import subprocess class PracticalMiniAgent: def __init__(self): self.messages [] self.tools [ # 定义工具动作空间 { “type”: “function”, “function”: { “name”: “execute_shell”, “description”: “执行一个Shell命令并返回输出”, “parameters”: { “type”: “object”, “properties”: { “command”: {“type”: “string”, “description”: “要执行的命令”} }, “required”: [“command”] } } }, { “type”: “function”, “function”: { “name”: “write_file”, “description”: “创建或覆盖一个文件”, “parameters”: { “type”: “object”, “properties”: { “path”: {“type”: “string”, “description”: “文件路径”}, “content”: {“type”: “string”, “description”: “文件内容”} }, “required”: [“path”, “content”] } } } ] # 系统提示词明确告知Agent可用的工具和行为规范 self.system_prompt “你是一个编程助手。你可以通过调用工具来执行Shell命令或写文件。每次思考后请根据需要调用工具。如果任务完成请明确说明。” self.messages.append({“role”: “system”, “content”: self.system_prompt}) def run_cycle(self, user_input): 运行一个完整的感知-决策-执行循环 # 1. 感知加入用户输入 self.messages.append({“role”: “user”, “content”: user_input}) # 2. 决策调用LLM并告知可用的工具 response openai.ChatCompletion.create( model“gpt-3.5-turbo”, messagesself.messages, toolsself.tools, tool_choice“auto” # 让模型自动决定是否调用工具 ) response_message response.choices[0].message # 3. 将模型的响应追加到历史中 self.messages.append(response_message) # 4. 执行检查模型是否决定调用工具 if response_message.tool_calls: # 可能有多个工具调用这里处理第一个 tool_call response_message.tool_calls[0] function_name tool_call.function.name function_args json.loads(tool_call.function.arguments) # 根据函数名分发执行 if function_name “execute_shell”: result self._execute_shell(function_args[“command”]) elif function_name “write_file”: result self._write_file(function_args[“path”], function_args[“content”]) else: result f“未知工具: {function_name}” # 5. 将工具执行结果作为新的“感知”输入交给模型 self.messages.append({ “role”: “tool”, “tool_call_id”: tool_call.id, “content”: str(result) # 执行结果 }) # 重要这里可以开启下一个循环让模型基于执行结果继续决策 # 例如return self.run_cycle(“请继续”) 或 在外层控制循环 return {“action”: function_name, “args”: function_args, “result”: result} else: # 模型没有调用工具直接返回文本回复 return {“action”: “chat”, “response”: response_message.content} def _execute_shell(self, command): 执行Shell命令安全风险高需谨慎 try: print(f“执行命令: {command}”) result subprocess.run(command, shellTrue, capture_outputTrue, textTrue, timeout30) output f“STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}\nReturn Code: {result.returncode}” return output except subprocess.TimeoutExpired: return “命令执行超时” except Exception as e: return f“命令执行失败: {str(e)}” def _write_file(self, path, content): 写入文件 try: with open(path, ‘w’, encoding‘utf-8’) as f: f.write(content) return f“文件 {path} 写入成功” except Exception as e: return f“文件写入失败: {str(e)}”这个PracticalMiniAgent类实现了一个完整的、可交互的循环。run_cycle方法封装了从接受到用户输入到LLM决策可能调用工具再到工具执行最后将结果返回给LLM的完整流程。这已经是一个非常原始的、但功能完整的Agent框架了。4.2 安全性与执行沙箱这是最重要的一节也是自制Agent与商业产品的天堑所在。上面的_execute_shell函数直接使用了shellTrue这意味着它能够执行任何系统命令。这是一个极其危险的操作相当于给了LLM一个没有限制的终端。商业产品如Claude Code、Cursor在处理此类操作时一定有严格的安全限制受限的命令白名单只允许执行npm install,git add,python等已知安全的构建、测试命令。虚拟环境/容器隔离在一个沙箱环境中执行命令防止污染主机系统或访问敏感数据。人工确认对于高风险操作如rm -rf, 修改系统文件会要求用户明确点击确认。无网络访问禁止执行可能访问网络或下载恶意脚本的命令。实操建议如果你只是实验可以在一个全新的、不重要的虚拟机或Docker容器中运行。对于任何严肃的项目你必须实现一个安全执行层。例如解析命令只允许前缀为git,npm,pip不带--user或--target等的命令并彻底禁止sudo,rm,curl | bash这类高危操作。5. 与Claude Code、Cursor的底层联系现在我们有了一个可以跑起来的“小Agent”再来审视Claude Code、GitHub Copilot基于Codex和Cursor就能发现它们在底层架构上与我们的迷你项目共享着相同的设计模式。1. 核心循环的一致性 无论是哪个工具当你要求它“修复这个bug”时它内部都触发了一个类似的循环感知收集当前文件、相关文件、错误信息、终端历史等作为上下文。决策将上下文和你的指令发送给背后的LLMClaude Code用Claude模型Copilot用CodexCursor早期用GPT-4现在也支持多种。执行LLM的输出被解析。对于Claude Code或Cursor这可能是一个代码编辑计划如“在第10行插入…”然后由编辑器插件执行这个计划修改你的源代码文件。对于Copilot其“执行”更直接就是将生成的代码补全建议插入到你的光标处。2. 系统提示词角色设定的威力 这些商业工具的强大很大程度上源于其精心设计和优化的系统提示词。例如Cursor的提示词可能包含“你是一个资深软件工程师精通多种编程语言。你擅长重构、调试和编写高效代码。你输出的代码必须简洁、可读、有良好的注释。你只能对代码文件进行操作…” 这个隐形的“人格设定”引导着模型的行为边界和专业性。我们的system_prompt就是对此的模仿。3. 工具动作集的扩展 我们的迷你Agent只有两个工具。而成熟的编程Agent拥有一个庞大的工具库文件操作读、写、移动、查找文件。代码分析静态分析、语法树AST操作、依赖分析。版本控制执行git命令理解diff。构建与测试运行npm run build,pytest,go test等。搜索引擎在许可和隐私前提下搜索错误信息或文档。 Claude Code和Cursor本质上就是集成了这样一个丰富工具集并配备了强大UI的超级Agent。4. 状态管理的复杂性 我们的Agent用self.messages列表维护了一个简单的对话历史。商业产品需要管理复杂得多的状态整个工作区的文件快照、多个并行的任务线程、用户偏好设置、长期记忆等。Cursor的“Chat with Workspace”功能就是将其所能“感知”的上下文范围从一个文件扩大到了整个项目目录。底层原理的共通点它们都不是魔法而是建立在“大语言模型 预设工具集 循环控制逻辑”这个基础架构之上。我们的6行代码抓住了这个架构的灵魂——基于上下文感知的持续决策与行动循环。6. 深入核心提示工程与循环控制理解了基础架构后要让这个小Agent变得聪明实用关键在于两件事提示工程和循环控制策略。这正是开源项目与成熟商业产品在体验上产生差距的核心地带。6.1 系统提示词的设计艺术系统提示词是Agent的“宪法”和“操作手册”。一个糟糕的提示词会让LLM行为混乱而一个优秀的提示词能塑造出一个专业、可靠的助手。以下是一个为代码生成任务优化的提示词示例你可以将其放入self.system_prompt你是一个经验丰富的全栈软件开发专家Senior Full-Stack Software Engineer。你的任务是帮助用户完成编程相关的任务包括代码生成、解释、重构、调试和提供建议。 **核心原则** 1. **安全第一**你只能使用我被授权使用的工具execute_shell, write_file。绝对不能尝试执行任何未被明确允许的操作或访问外部系统。 2. **精准行动**每次思考后如果确定需要采取行动如运行命令验证、创建文件必须且只能调用一个最必要的工具。在行动前先简要说明理由。 3. **代码质量**你生成的代码必须遵循当前项目的主流语言规范和最佳实践如PEP 8 for Python, Airbnb Style Guide for JS。代码应简洁、高效、有可读性关键部分需添加注释。 4. **循序渐进**对于复杂任务将其分解为多个清晰的子步骤。完成一个步骤后根据输出决定下一步。 5. **诚实与边界**如果你不知道或不确定请直接说明。不要虚构代码或命令。 **工具使用规范** - execute_shell: 仅用于运行项目构建、依赖安装、测试、版本控制git等非破坏性命令。禁止运行任何文件删除rm、系统修改或网络下载curl/wget命令。 - write_file: 用于创建或覆盖代码文件、配置文件。覆盖前应确认。 **输出格式** 首先用“思考”开头分析当前上下文和任务。 然后如果需要行动以“行动”开头并调用工具。 如果不需行动或任务完成直接给出最终答案或代码。 现在开始处理用户请求。这个提示词定义了角色、原则、工具规范和行为格式能极大地提升Agent输出的稳定性和安全性。你可以根据你需要Agent专注的领域前端、数据科学、DevOps来定制这个提示词。6.2 实现自主循环与任务分解我们之前的run_cycle一次只处理一轮交互。一个真正的Agent应该能接手一个高级目标如“为这个Flask应用添加用户登录功能”并自动分解、执行直到完成或无法继续。这就需要实现一个外层控制循环。class AutonomousMiniAgent(PracticalMiniAgent): def run_autonomous(self, ultimate_goal, max_turns10): 处理一个终极目标自动循环直到完成或达到最大轮数 print(f“终极目标: {ultimate_goal}”) self.messages.append({“role”: “user”, “content”: f“请完成以下任务{ultimate_goal}。请逐步进行每次只做一个明确的动作并告诉我你在做什么。”}) for turn in range(max_turns): print(f“\n 第 {turn1} 轮 ) # 调用父类的单轮循环 result self.run_cycle(“请继续下一步。” if turn 0 else None) # 检查结果判断是否应该继续 if result.get(“action”) “chat”: response result.get(“response”, “”) print(f“Agent回复: {response}”) # 简单判断如果模型回复中包含“完成”、“结束”、“好了”等词可能意味着任务结束 if any(word in response.lower() for word in [“完成”, “结束”, “好了”, “finish”, “done”]): print(“Agent认为任务已完成。”) break else: print(f“工具执行结果: {result.get(‘result’)}”) # 也可以根据工具执行结果来判断是否继续例如命令执行失败可能需要调整策略 if turn max_turns - 1: print(f“达到最大轮数{max_turns}自动停止。”)这个run_autonomous方法启动了一个多轮对话初始指令要求模型“逐步进行”。在每一轮中它都会在上轮结果的基础上请求模型进行“下一步”。循环结束的条件可以是模型明确表示任务完成、达到最大轮数限制、或遇到无法解决的关键错误需要在代码中进一步细化错误处理逻辑。循环控制的挑战目标漂移LLM可能在多轮后偏离原始目标需要机制将其拉回。无限循环必须设置最大轮数或超时时间作为安全阀。错误处理当工具执行失败如命令报错时需要将清晰的错误信息反馈给LLM让它有机会“自我纠正”。我们的代码已经将错误信息返回到了对话历史中LLM在下一轮就能看到并调整策略。7. 实战用自制Agent完成一个简单任务让我们用一个具体的例子看看这个自制的Agent如何工作。任务是在当前目录创建一个简单的Python Flask web应用。# 初始化Agent agent AutonomousMiniAgent() # 设置API密钥实际操作中应从环境变量读取 openai.api_key “your-api-key-here” # 启动自主任务 agent.run_autonomous(“在当前目录创建一个名为 ‘myapp’ 的文件夹并在其中创建一个简单的Flask web应用。应用只需要一个根路由返回 ‘Hello, Agent!’。”)可能的执行过程模拟第1轮Agent思考后调用write_file工具创建myapp目录实际上可能需要先execute_shell执行mkdir myapp这里取决于提示词和模型判断。第2轮基于上轮结果或感知到目录已存在调用write_file在myapp目录下创建app.py文件写入Flask基础代码。第3轮调用execute_shell工具执行cd myapp pip install flask在我们的安全限制下这可能被允许。第4轮调用execute_shell执行cd myapp python app.py来启动应用并可能返回一个提示说服务已在本地运行。第5轮Agent思考后输出“Flask应用已创建并运行。你可以在浏览器中访问 http://127.0.0.1:5000 查看 ‘Hello, Agent!’ 页面。”并包含“完成”关键词循环结束。通过这个例子你可以清晰地看到Agent的“思考-行动-观察”循环在一步步推进任务。虽然它还很简陋但已经具备了自动任务分解和执行的基本形态。8. 常见问题、局限性与进阶方向在亲手搭建和试验这个极简Agent的过程中你一定会遇到各种问题。下面是一些常见坑点和解决思路。8.1 典型问题与排查问题现象可能原因排查与解决思路LLM不调用工具只聊天1. 系统提示词未强调必须使用工具。2. 工具描述不够清晰。3. 模型认为当前无需行动。1. 强化提示词如“你必须通过调用工具来解决问题”。2. 优化工具描述使其目的更明确。3. 在用户输入中明确要求行动如“请使用工具完成X”。工具调用参数格式错误LLM生成的JSON不符合schema。1. 使用API的函数调用功能它能更好地保证格式。2. 在提示词中提供更详细的参数示例。3. 在代码中添加健壮的JSON解析和错误处理尝试修复或让LLM重试。Agent陷入无效循环模型在重复相同的动作或在一个错误步骤上打转。1. 在历史消息中模型可能看不到足够的环境变化。确保每次工具执行的结果都被准确、完整地追加到上下文中。2. 设置最大循环次数。3. 实现一个简单的“状态检测”如果连续三轮动作和结果完全相同则中断并报错。执行命令权限不足或失败安全限制太严或命令本身有误。1. 检查_execute_shell返回的错误信息并将其清晰地反馈给LLM。2. 适当调整安全策略仅在安全环境。3. 提示LLM检查命令语法或使用替代方案。Token超限对话历史self.messages越来越长超过模型上下文长度。1. 实现历史消息摘要Summarization定期将过长的旧对话压缩成一段摘要。2. 采用滑动窗口只保留最近N轮对话。3. 对于代码场景可以只发送相关文件的最新片段而非全部内容。8.2 当前实现的局限性我们必须清醒认识到这个几百行代码的玩具与Claude Code、Cursor等工业级产品之间存在巨大鸿沟上下文长度我们简单地将所有历史存入列表而专业工具会精心设计上下文窗口优先放入最相关的代码片段、错误信息等。工具生态我们只有两个基础工具而专业工具集成了代码分析器、版本控制客户端、测试框架、搜索引擎等数十种工具。可靠性我们的Agent非常脆弱LLM的一个错误输出或解析失败就可能导致整个流程崩溃。商业产品有大量兜底、重试、降级逻辑。用户体验我们只有命令行输出而商业产品提供了无缝的编辑器集成、直观的UI交互和实时预览。8.3 进阶探索方向如果你对这个方向感兴趣可以沿着以下路径深入集成更强大的模型尝试使用Claude 3.5 Sonnet、GPT-4o或开源的DeepSeek Coder等代码能力更强的模型作为“大脑”。丰富工具集添加read_file、search_files、run_tests、git_diff等工具让Agent能力更强。实现规划模块在循环开始前让LLM先输出一个任务分解计划Plan然后逐步执行这有助于解决复杂任务。引入记忆机制使用向量数据库存储长期记忆让Agent能“记住”过去项目的经验和解决方案。构建UI界面用Gradio或Streamlit快速搭建一个Web界面或者开发一个VSCode插件模仿Cursor的交互体验。这个由6行代码衍生出的项目就像一把钥匙帮你打开了理解AI智能体的大门。它的价值不在于替代谁而在于提供了一种“第一性原理”的视角。当你再使用那些先进的AI编程工具时你看到的将不再是黑盒魔法而是一个个熟悉的“感知-决策-执行”循环在高效运转。这种理解或许才是提升我们与AI协作效率的真正开始。