从零构建AI Agent:150行代码的工程实践
作者:问题终结者2026.11.07 01:43浏览量:0简介:本文从工程视角拆解AI Agent的核心架构,围绕“LLM+规划+记忆+工具”四要素展开,重点讲清ReAct循环、Function Calling工具设计与上下文管理等关键环节,并给出可运行的极简实现思路。读者将掌握Agent与普通LLM的本质差异,理解工具描述、错误反馈与记忆机制对任务成功率的影响,获得可复用的设计范式。
agent-150-">从零构建 AI Agent:150 行代码背后的工程逻辑
一、一个值得注意的行为差异
设想这样一个场景:你让一个编程助手完成“读取配置文件、修改参数、运行测试”的任务。
普通 LLM 的回应通常是:把改好的代码贴给你,然后说“请自行运行验证”。它完成了“生成”,但没有完成“交付”。
而具备 Agent 能力的系统会这样做:先写入文件,再自己调用执行环境跑一遍,发现测试失败,定位到某一行逻辑错误,修改后重跑,通过之后才回复“已写入并验证通过”。整个过程用户没有介入。
这个差异不是“更聪明”,而是执行闭环的有无。普通 LLM 是单轮生成器,Agent 是带反馈回路的任务执行体。本文从工程实现角度,把这层差异拆解成可落地的架构。
二、本质区别:被动响应 vs 主动闭环
| 维度 | 普通 LLM | AI Agent |
|---|---|---|
| 交互模式 | 被动问答,一轮一答 | 目标驱动,多轮自主迭代 |
| 外部世界 | 无法读写文件、无法执行代码 | 通过工具连接文件系统、运行时、检索服务 |
| 状态保持 | 对话结束即遗忘 | 显式记忆,跨步骤保留上下文 |
| 错误处理 | 无法感知执行结果 | 读取执行反馈并自我修正 |
| 交付形态 | 建议与文本 | 已验证的结果 |
一句话概括:LLM 是知识渊博但足不出户的顾问,Agent 是拿着这颗大脑、能跑腿办事并回头复命的执行者。
三、核心公式与四要素
工程上可以把 Agent 抽象为一条公式:
AI Agent = LLM(决策大脑) + Planning(规划循环) + Memory(记忆) + Tools(执行手脚)
四个要素各自解决一个明确问题:
- LLM:负责语义理解与决策,是唯一的“判断中心”;
- Planning:把目标拆成步骤,并根据每步结果决定下一步;
- Memory:保存中间状态、历史动作与观察结果,避免重复劳动与上下文丢失;
- Tools:把决策转化为对真实世界的副作用(读写、执行、检索)。
缺任何一环,系统都会退化:没有 Tools 就只是聊天;没有 Planning 就只会单步调用;没有 Memory 就无法处理多步任务;没有 LLM 则无法处理非结构化输入。
四、Tools:连接真实世界的接口层
工具调用的技术基础是函数调用协议:开发者向模型提供一组工具“说明书”(名称、用途、参数结构),模型在推理时决定是否调用、调用哪个、传什么参数,运行时负责实际执行并把结果回灌给模型。
一个最小工具集通常包含五类能力:
TOOLS = [{"name": "read_file", "description": "读取指定路径的文件内容"},{"name": "write_file", "description": "将内容写入指定路径,覆盖已有文件"},{"name": "list_directory", "description": "列出目录下的文件与子目录结构"},{"name": "execute_python", "description": "在受限沙箱中执行一段 Python 代码片段,超时 10 秒"},{"name": "search_code", "description": "在代码库中递归搜索关键词并返回匹配位置"},]
一个容易被低估的工程细节:工具描述的质量比工具实现更影响成功率。
实践中的典型反例:execute_python 最初只写“执行代码”,模型有时会把文件路径当作参数传入,期望“执行这个文件”。把描述改为“执行一段代码片段,而非文件路径”之后,参数错误率显著下降。
原因在于,模型选择工具与填参完全依赖描述文本,描述就是它唯一的“操作手册”。写工具描述应遵循给新同事写文档的标准:
- 说明做什么,也说明不做什么(边界比功能更能减少误用);
- 参数语义具体化(路径是绝对还是相对、内容是覆盖还是追加);
- 写明失败形态(超时、权限不足、文件不存在时返回什么),便于模型据此调整策略;
- 工具粒度适中(过粗难以组合,过细增加选择成本)。
五、Planning:ReAct 循环是核心机制
Agent 最核心的运行时结构是 ReAct 范式,即“推理—行动—观察”循环:
while 未达成目标 and 未超过步数上限:1. Thought:模型基于目标与历史,推理当前应做什么2. Action:选择一个工具并给出参数3. Observation:运行时执行工具,把结果回灌上下文4. 模型判断:目标是否完成?未完成则进入下一轮
这一循环的关键设计点有三个:
其一,观察结果必须回灌。 工具执行的真实返回值(包括报错堆栈)是自我修正的唯一依据。若只回灌“成功/失败”布尔值,模型就失去了定位问题的信息。
其二,必须有终止条件。 步数上限、超时、重复动作检测三者缺一不可。缺乏终止机制的 Agent 会在失败路径上无限循环,这是最常见的线上事故来源。
其三,错误是正常输入而非异常。 测试未通过、文件不存在、语法错误,都应作为结构化观察返回给模型,而不是让整个流程抛出中断。上面那个“改一行 bug 再跑一遍”的场景,正是错误回灌机制在起作用。
六、Memory:被低估的稳定性来源
多步任务中,模型每一轮只看到当前上下文窗口。若不做显式记忆管理,会出现两类问题:重复执行已完成的动作,以及丢失早期关键约束。
工程上通常分三层处理:
- 短期记忆:当前任务的完整动作—观察序列,直接拼入上下文;
- 工作记忆:对历史做摘要压缩,保留结论与关键参数,控制 token 增长;
- 长期记忆:跨会话持久化的偏好与事实,通过外部存储检索注入。
一个实用技巧是结构化记忆:不存自然语言流水账,而存 {步骤, 动作, 参数, 结果状态} 这样的记录,既便于压缩,也便于模型精确判断“哪一步已经做过”。
七、极简实现的骨架
150 行左右即可覆盖上述全部要素,结构大致如下:
def run_agent(goal, max_steps=15):memory = [{"role": "user", "content": goal}]for step in range(max_steps):# 1. 决策:模型输出 Thought + 工具调用decision = llm(memory, tools=TOOLS)memory.append(decision)# 2. 终止判断:模型认为目标已完成if decision.is_final:return decision.answer# 3. 执行:调用工具,捕获异常与超时try:observation = dispatch(decision.tool, decision.args)except Exception as e:observation = {"status": "error", "detail": str(e)}# 4. 回灌观察结果memory.append({"role": "tool", "content": observation})return {"status": "aborted", "reason": "step_limit"}
真正的工程量不在循环本身,而在循环之外的护栏:沙箱隔离、执行超时、文件写入的路径白名单、工具返回值的截断策略,以及可观测性(每步的决策与耗时可追溯)。这些才是从 demo 走向可用的分界线。
八、实践中的五条经验
- 先做单工具闭环,再扩工具集。 一个能读写并验证的工具,比五个半成品工具更能暴露架构问题。
- 把工具描述当作 Prompt 工程的主战场。 描述每改一版,都应跑一轮回归用例观察调用准确率。
- 观察结果要“可读且可判”。 返回结构化 JSON 优于自由文本,便于模型稳定解析。
- 限制副作用范围。 写操作限定目录、执行限定沙箱与超时,任何自主系统都必须先假设它会犯错。
- 记录完整轨迹。 每一步的输入、决策、工具参数、返回值都要落日志,这是调试 Agent 的唯一有效手段。
结语
AI Agent 并不神秘,它是把“生成”升级为“生成并验证”的一层工程封装。四要素公式给出架构边界,ReAct 循环给出运行时骨架,工具描述与错误回灌决定实际成功率,记忆与护栏决定能否长期稳定运行。百余行代码足以搭出可工作的原型,而把它变成可靠系统,靠的是对这些细节的持续打磨。理解这一点,比记住任何框架名称都更有价值。

登录后可评论,请前往 登录 或 注册