LongCut logo

Agent 的概念、原理与构建模式 —— 从零打造一个简化版的 Claude Code

By 马克的技术工作坊

Summary

Topics Covered

  • 大模型无法感知外界
  • 工具赋予Agent感官四肢
  • ReAct循环驱动Agent
  • 系统提示词控制ReAct剧本
  • Plan-and-Execute动态规划

Full Transcript

今天我们来聊聊Agent 它也叫AI Agent 这是一个随着大模型热潮而兴起的重要概念 虽然Agent这个词现在被频繁地提起 但它究竟是什么 是如何运作的 很多人其实并不清楚 所以在这个视频里面 我会带你彻底搞明白这两个问题 在正式开始前 我需要先提醒一下 视频前半部分的一些内容 和我之前发过的MCP终极指南

番外篇有所重合 如果你已经看过那一篇了 这个视频就建议从Agent代码的实现部分开始观看 大家都知道现在的大模型 比如GPT-4o、DeepSeek之类的 它们回答问题很厉害 逻辑也很强 但平时我们用它们的时候会发现一个限制 它们无法感知或者是改变外界环境 这句话是什么意思呢 我举一个例子来给你说明一下 比如

你想让GPT-4o帮你写一个贪吃蛇游戏 它确实可以给你代码 但是写完之后 像把代码写入到文件这种事情 还是得你自己动手 也就是说大模型无法改变外界环境 而且有没有一种可能 你其实已经有一些贪吃蛇的代码了 你只是想让模型帮你基于这些代码来改写 增加一些功能 在这种情况下 你就必须把你已有的代码复制给GPT-4o才行

我们不主动告诉GPT-4o的话 它是无法自己查到这些代码的 换句话说 这就是大模型无法感知外界环境的体现 所以综合来看 大模型是无法感知或者是改变外界环境的 那有没有办法解决掉这个问题呢 其实是有的 我们给它接上对应的工具就可以了 比如说是读写文件内容的工具 查看文件列表的工具 运行终端命令的工具

工具就像是大模型的感官和四肢 有了它 大模型就可以自己查询已有文件 自己写入代码 自己运行程序 整个过程不需要我们插手 完全自动化 像这样 把一个大模型和一堆工具组装起来 变成一个能感知和改变外界环境的智能程序 我们就称它为Agent 通常Agent用一个机器人来表示

这与大模型的大脑图标形成了鲜明的对比 毕竟Agent有了感官和四肢嘛 能自己独立做事了 就像一个机器人一样 Agent有很多类型 前面举的是编程类的Agent 它可以用来开发程序 除此之外 还有一些Agent可以做PPT 有一些Agent可以深度搜索 等等等等 总的来说 Agent的类型有很多 擅长的领域也各不相同 下面我们来举几个具体点的例子

第一个例子便是大名鼎鼎的Cursor 相信即使你没有使用过它 也多多少少听说过它的存在 Cursor是一个用于编程的Agent 我们只需要给它提交任务 它便会调用大模型和各种工具 来帮我们写代码 直至完成任务 整个过程中 你顶多点个确定按钮 别的基本上什么也不用动 再举一个例子 前一阵子比较火的Manus 它也是一个Agent 在这个例子中

用户希望Manus帮它比较几个手机的性能 照相等能力 为了解决用户的问题 Manus会生成执行计划 搜索并浏览相关网页 最后把报告整理成一个页面 展示给用户看 整个过程基本上也不需要用户插手 Manus利用大模型和一些工具 就可以解决掉用户的问题 好 相信到这里 你对Agent就有一个大致的了解了

下面我们就来讲讲Agent的运行模式 Agent的运行有很多种模式 其中最有名的一种是ReAct ReAct本身是一个缩写 它的全称是Reasoning and Acting 也就是思考与行动 ReAct可能是目前使用最为广泛的Agent运行模式 如果你要学习Agent的实现原理 那你就绝对绕不开ReAct

这个模式最初由2022年10月份的一篇论文提出 虽然距离现在已经有接近三年的时间了 但是它所提出的Agent的运行模式 仍然有着非常广泛的使用 说它是目前使用最为广泛的Agent的运行模式 也不为过 在这种模式下 用户先提交任务 然后Agent先做思考 英文是Thought 它思考后会决定是否调用工具 如果是的话

它便会去调用合适的工具 比如读取文件写入文件内容之类的 ReAct称这一步是行动 英文是Action 在行动后 Agent会去查看工具的执行结果 比如所读取的文件内容 写入是否成功等等 ReAct称这一步是观察 也就是观察工具执行结果 英文是Observation 在观察之后 ReAct会继续思考

它会再次判断是否需要调用工具 如果还是需要的话 它就会继续重复之前所说的行动 观察思考的流程 直到某个时刻 它认为不需要再调用工具了 可以直接给出结论了 此时它就输出了最终答案 英文是Final Answer 整个流程到此结束 所以从这个流程图里面也可以看出 ReAct流程的核心步骤是 Thought Action Observation 和Final Answer

记住这几个词 我们后面会用到 了解了ReAct模式的流程之后 下一个问题就是 这种ReAct模式是如何实现的 为什么模型拿到用户问题之后 会先思考 再行动 它为什么不直接行动 是因为模型就这么训练的吗 不是的 这跟模型的训练过程关系不大 大部分奥秘其实都集中在系统提示词上 系统提示词是跟用户问题 一起送给模型的提示词 它规定了模型的角色

运行时要遵守的规则 以及各种环境的信息等等 比如我们在系统提示词里面写 你的回答必须包含两个XML标签 一个叫做Question 用于存放用户的问题 一个叫做Answer 用于存放你的回答 你把这个系统提示词和用户问题 一起发给大模型 在这种情况下 大模型便会遵循这种规范 来输出答案 上面举的是一个简单的例子

如果你想要模型按照ReAct模式返回答案的话 你的系统提示词就会更加复杂一些 我这里就有一个具体的例子 这个系统提示词大致有五个部分 分别是职责描述 示例 可用工具 注意事项 和环境信息 我们来仔细读一下 首先看职责描述部分 你需要解决一个任务 为此你需要将任务分解为多个步骤 对于每个步骤 首先使用thought思考要做什么

然后使用action调用一个工具 工具的执行结果会通过observation返回给你 持续这个思考和行动的过程 直到你有足够多的信息来提供final answer 这段话其实就是在描述我们刚才的 那个ReAct执行流程图 我们希望大模型按照ReAct这个标准来运作 后面则是专门说明了每个标签的功能 紧接着我举了几个例子

比如说第一个用户的问题是 埃菲尔铁塔有多高 模型就先用thought标签做了一些思考 然后再使用action调用了get height工具 传入的参数是埃菲尔铁塔 工具的返回结果通过observation返回给了模型 模型接到结果之后 他再做了一些思考 然后就给出了最终的答案

这个就是一个非常典型的ReAct流程 后面的例子2其实也是类似 只不过是他调用工具调了两遍 这个我们就不细说了 再往后我这里列举了一些可用的工具 分别用于读取文件内容 写入文件内容和运行终端命令 都是非常常用的功能 然后我们列举了一些注意事项 就是在这里

而且告诉了大模型相关的一些环境信息 比如说是当前的操作系统 目录和目录下的文件列表等等 下面我们就来演示一下如何使用这个系统提示词 我们用DeepSeek来举例 我们先把我们的系统提示词复制一下 然后粘贴进来 作为用户输入的一部分 之后再在后面加上具体要完成的任务 写一个贪吃蛇游戏

使用html、css和js实现 代码分别放在不同的文件中 有一点需要提一下 按照规范的做法 系统提示词和用户任务应该分开传给模型 但DeepSeek并没有提供单独提交系统提示词的地方 所以我们就把系统提示词和用户任务合在一起 当成一条消息提交给它 这样的处理方式在大多数的情况下也是没有问题的 模型依然能够按照预期运行 好 让我们提交任务

可以看到DeepSeek开始运行了 让我们稍等一下 让它运行完毕 可以看出它按照我们的要求 先在thought标签里面思考了一下 然后它使用action标签请求调用write_to_file工具 来写入index.html文件

来写入index.html文件 这后面就是具体的文件内容了 大家注意我的措辞啊 大模型请求调用工具 这里体现的是请求两个字 大模型本身是不能调用工具的 调用工具的是Agent的工具调用组件 这里大模型只能是请求 现在如果运行的是一个真的Agent的话 它便会去调用工具背后的write_to_file函数 写入html文件内容

不过我们现在在模拟嘛 我们就假设调用已经完成了 并且假设工具的返回结果是写入成功 所以我们回复observation写入成功 拿到这个结果后 DeepSeek又开始运行了 这次它还是先用thought标签思考了一下 然后再使用action标签请求写入css文件的内容 我们照例回复写入成功 DeepSeek又开始返回了 让我们稍等一下

可以看出DeepSeek还是先用thought思考 再用action请求写入js文件的内容 我们还是回复写入成功 然后DeepSeek的返回就有了些变化 因为三个必要的文件都已经写入完成了 不需要再调用工具了 因此DeepSeek在thought之后 返回了一个final answer 整个回答就彻底结束了 你看这就是ReAct模式真正运行时的节奏 每一步都按照系统提示词的要求来

thought action observation 一直到任务完成 此时会输出thought和final answer 系统提示词就相当于 给模型安排了一个迷你剧本 它会严格按照这个剧本一步一步的走完 前面我们用DeepSeek演示了一个Agent的运行流程 可以看到整个流程的关键在于系统提示词 它决定了模型该如何一步步运行 其实在这个系统提示词的基础上

再加上一些配套的代码 我们就可以搭建出一个真正可用的ReAct Agent 实际上我已经把这个Agent写好了 就放在我的github仓库里 有需要的同学可以自行获取 接下来我先演示一下这个Agent的使用过程 随后再带大家一起看一遍它的代码 我已经进入到这个Agent所处的项目目录了 我们先执行一下tree命令 看一下这个项目目录里面都有什么文件 这里文件很多

但实际上你只有两个文件需要留意一下 第一个是agent.py文件

第一个是agent.py文件 这个文件里面就写了我们的Agent的代码 我们等会儿要运行的就是这个文件 另外一个是snake文件夹 它里面什么也没有 我用tree命令给你证实一下 可以看出确实没有任何文件 等会儿我就会让Agent把代码写入到这个文件夹里面 好介绍完毕 下面我们来执行一下这个Agent 让你看看它是如何运作的

首先我们启动一下这个Agent 我们用的命令是 uv run agent.py snake

前面的uv run agent.py

就是用来启动agent.py文件的

后面的snake是agent.py这个脚本的第一个参数

意思是告诉agent.py

它要操作的项目目录是snake 代码就写在那里面 agent.py首先向我们询问需要完成的任务

agent.py首先向我们询问需要完成的任务 我们的任务就是 写一个贪吃蛇游戏 使用html、css和js实现 代码分别放在不同的文件中 回车 agent.py已经开始运行了

agent.py已经开始运行了 它现在正在请求大模型 我这里采用了同步返回的机制 所以需要等模型把所有内容都生成完毕之后 才能看到结果 其实也可以用流式返回的 模型返回几个字就能看到几个字 这样可能效果更好一点 不过代码的复杂度会增加 所以综合权衡之后 我就没有使用流式返回 好 看到第一轮的结果了

我们的结果一共是包含三个部分 Thought Action Observation 跟我们之前在DeepSeek那里看到的一模一样 这里的Action是请求调用 write_to_file工具写入 index.html文件

index.html文件 后面的Observation显示的就是具体的调用结果了 写入成功 注意啊 这个写入成功可不是模拟的 这是真的执行了write_to_file工具 工具也真的返回了写入成功这几个字 好 这个呢是第一部分 我们把滚动条往下拉一拉 看一下剩下的返回是个什么样子的 后面的流程呢也基本类似 可以看到在Observation之后 Agent会再次请求模型

然后Agent就又进行了一段Thought Action Observation 这一轮写入的是CSS 我们再把滚动条往下拖一拖 在这里我们就可以看到第三轮的Thought Action Observation 这次写入的是JS 最后所有文件都写完之后 它会给出Thought 和FinalAnswer 整个流程就结束了

再看看snake文件夹 确实三个文件都有了 执行index.html 看看游戏能不能玩 可以看到界面出来了 我们动一下 确实也是能动的 然后吃一个红色的方块呢 也是可以吃的 左上角是分数 看来运转的非常顺畅 从这个结果中也可以判断出 我们这个Agent做的非常成功 完全可以作为一个简化版的 Claude Code 来使用 下面我们来看一下这个Agent的具体代码

我们首先从入口处看起 这里面的project_directory 就是我们传给Agent.py文件的第一个参数

就是我们传给Agent.py文件的第一个参数 也就是snake那个文件夹 tools代表可用的工具列表 我们这里给出了三个 分别用于读取文件 写入文件和运行终端命令 这些都是很实用的函数 我们可以大体看一下 这个是读取文件 这个是写入文件函数 这个是运行终端命令 从这里可以看出工具确实就是函数

让我们回到原来的主链路继续往下看 这里的ReActAgent便是这个文件的核心了 它是一个类 构造这个类的时候需要提供三个参数 第一个是工具列表 这个我们前面已经构建好了 这里直接传了进来 第二个是我们要用的模型 我们这里用的是GPT-4o 第三个是项目目录 也就是snake文件夹 传好了这三个参数之后

我们便获取到了一个agent变量 之后我们会提示用户输入任务内容 然后我们把用户任务传入 agent.run函数

agent.run函数 这个函数是ReActAgent的核心 调用它就相当于是启动了这个Agent 之前提到的Thought Action Observation和FinalAnswer 都是在这个函数内部依次处理的 它处理好了之后 会给出一个最终答案 final_answer 并且把这个final_answer输出到屏幕上 到这里主链路就结束了 可以看到这段代码的核心是ReActAgent 我们来看一下它里面写了些什么

首先它这里定义了一些自身的属性 分别是工具列表 模型 项目目录和模型调用客户端 到这里 构造函数就结束了 后面我们要看的函数便是这个Agent的重点 Run函数 这个函数的参数是用户输入的任务 在函数的内部 它先构建了一个Message列表 里面有两个元素 分别是系统提示词 和用户问题

系统提示词是用render_system_prompt这个函数来渲染的 它接受一个参数是系统提示词模板 模板里面的内容是这个样子的 跟我们之前讲的那个系统提示词 基本上是一样的 只不过这个模板里面有一些占位符 比如说是工具列表 操作系统 当前目录下的文件列表等等 这些占位符都是render_system_prompt函数 在运行的时候填进去的

在拼接好了Message列表之后 我们使用call_model函数 调用了模型 拿到了模型的执行结果 然后我们提取出返回结果中的thought部分 并且打印了出来 然后代码会检测thought之后的内容 是不是final answer 如果是的话 我们返回这个final answer 函数执行到此结束 如果不是的话 那content里面一定就包含action了 我们此时就把action给解析出来

提取出其中的函数名和参数列表 然后判断了下当前工具 是不是运行终端命令的工具 如果是的话 我们会提示用户是否继续 因为运行终端命令比较危险 所以现在一般用于编程的Agent 都会在运行终端命令之前 主动询问用户是否要执行 之后没有问题的话 我们就会去执行工具背后的函数了 并且把执行结果放到observation里面

再把observation放到message列表里面 因为我们在一个while循环里面 所以我们下一步还会来到循环的开头这里 继续请求模型 我们给call model这个函数 传了message列表作为参数 工具执行结果不是作为observation 放到了message列表里面了吗 而message又传给了模型 这样模型就可以拿到工具的执行结果了 它进而就可以根据工具的执行结果 推测下一步要干什么

所以总结一下这个while循环做的事情 请求模型 提取thoughts 检测final answer 提取action并执行其中的工具 这个过程会一直重复下去 直到模型返回了final answer为止 回想一下 这正是我们之前所提到的ReAct运行流程 为了确保你彻底明白这其中发生了什么 我们来画个Agent的流程图 整个流程图里面有两个角色

用户和Agent 而Agent又可以分成三个部分 分别是模型 工具 也就是函数 还有Agent主程序 Agent主程序这个词我们之前没有提过 其实就是Agent里面负责串联整个流程的代码逻辑 它会在合适的时候 调用工具或者是模型等等 你可以大致理解为我们刚才代码里面的那个run函数 下面我们就来画个流程图

看看这四个角色之间是怎么沟通的 在用户提交任务之后 任务先到了Agent主程序这里 Agent主程序会先去调用模型 模型返回thought和action Agent主程序把thought和action打印给用户看 然后去调用action里面的指定工具 工具执行完毕之后返回结果 Agent主程序把结果发回给用户看

然后把这个工具执行结果加入到历史消息列表里面 然后再次重复这个框中的流程 也就是请求模型 并处理thought,action和observation的逻辑 直到某个时刻 在请求模型后 模型认为用户的任务已经完成了 不需要再调用工具了 它就会返回thought和final answer Agent主程序把thought和final answer展示给用户看 整个流程就结束了

这就是一个完整的ReAct Agent的问答流程 前面我们讲了如何使用ReAct模式 来构建一个Agent ReAct是目前最常见 使用最广泛的Agent构建模式 但它不是唯一的方案 除了ReAct之外 还有很多其他的运行模式 其中很多Agent的运行过程 就是先规划再执行 比如我们之前演示过的Manus 如果你仔细看的话 就会发现它在一开始回答的时候 会构建一个待办列表

后面的执行过程 都是遵循这个待办列表来 而Claude Code中 也会经常看到这种先创建TODO 再去执行的情况 这种先规划再执行的模式 目前并没有一个统一的名字 而且每个Agent的实现多多少少 也会有一些差别 我们今天来讲一个其中比较有名的实现 是LangChain提出来的Plan-and-Execute模式 从总体上来看

它也是遵循了先规划再执行的流程 只不过它的流程引入了一些动态修改规划的环节 这使得它的方案有了很大的灵活性 我们先用时序图来画一下Plan-and-Execute模式的运行流程 首先我们要搞清楚这个时序图里面有多少个角色 粗分下来的话那肯定只有两个了 一个是用户 另外一个是Plan-and-Execute Agent 不过既然要研究Plan-and-Execute Agent的运行流程

我们就肯定要搞清楚这个Agent的组成部分 首先它里面有一个负责出执行计划的模型 我们称它为Plan模型 我们在运行的过程中 还需要根据每一步的执行结果来动态的调整计划 因此我们还需要一个负责修改执行计划的模型 我们称它为Re-Plan模型 Plan和Re-Plan模型可以是同一个 也可以分成两个 都是可以的 我们暂且将它们列为两个

除了这两个模型之外 我们还需要一个负责执行这个计划中每一个步骤的Agent 我们称它为执行Agent 对你没看错 这个Plan-and-Execute Agent内部还有一个Agent 这种Agent套Agent的设计方案其实也是比较常见的 最后跟ReAct那个流程一样 我们还需要一个Agent的主程序 负责串联整个流程 这就是Plan-and-Execute Agent的全部模块了

下面我们就把它们放在流程图里面 看看各个模块之间是如何运作的 首先用户会把问题提给Agent的主程序 比如我们的问题就可以是 今年澳网男子冠军的家乡是哪里 这里的澳网指的是每年举办的澳大利亚网球公开赛 也就是个体育赛事了 Agent的主程序接到这个问题之后 会把这个问题发给Plan模型 让它给出具体的执行步骤

比如一个可能的执行步骤就是这样的 先查询当前日期 然后查询在当前年份下澳网男子冠军的名字 比如当前时间是2025年的话 就查询2025年的澳网男子冠军的名字 如果当前年份是2024年的话 那就查询2024年的澳网男子冠军的名字 查出名字后 我们再根据这个名字来查询这个冠军的家乡 没错

这就是一个非常合理的执行步骤 那计划有了之后 Agent的主程序便会把这个计划传给执行Agent 让它去执行这个计划中的第一步 也就是查询当前日期的那个步骤 这个执行Agent可以用我们之前讲的ReAct模式来运行 它内置一个网络搜索工具 这样它就可以通过搜索网络来查询当前日期了

当然执行Agent也完全可以用别的模式来运行 Plan-and-Execute模式 只要求执行Agent能够完成指定的步骤就行 至于它的运行模式是不是ReAct 内置工具有哪些 它完全不关心 执行Agent内部一顿操作之后 就吐出了一个执行结果并返回回去 然后Agent的主程序会把用户问题 执行计划和执行记录都发给Re-Plan模型

让它生成一个新的执行计划 毕竟我们拿到了第一步的执行结果了 多了一些信息 情况可能会发生些变化 把原计划改改实在正常不过的事情了 那Agent的主程序接到新的执行计划之后 它便会回头再重复这个框中的流程 在我们这个例子中 这个框中的流程一共会运行三轮 对应了执行计划里面的三步

每一轮都包含两个环节 一个是执行环节 一个是Re-Plan环节 为了能够让你彻底明白 我们模拟一下这个循环中的三轮 让你看看每一轮的执行环节和Re-Plan环节 都具体发生了一些什么事情 在模拟开始前 我先打个预防针 为了节省时间 我在模拟时讲解的速度会稍微快一些 如果你听不懂 建议在合适的时候稍微停一下 好让我们开始 首先是第一轮 在执行阶段

我们把执行计划发给执行Agent 让他处理其中的第一步 执行Agent返回之后 我们把他给出的执行结果 加入到历史执行记录里 然后把用户问题 第一个执行计划和历史执行记录 一起发给Re-Plan模型 让他给出第二个执行计划 第一个执行计划和第二个执行计划 有两点不同 这里我专门说一下 首先原来查询当前日期的那一步

就不用出现在第二个执行计划里面了 毕竟已经执行完了 不用再执行了 另外查询澳网男子冠军名字的这一步 也发生了一些变化 在第一个执行计划中 他叫做查询对应日期的澳网男子冠军名字 在第二个执行计划中 他叫做查询2025年的澳网男子冠军名字 毕竟日期已经查出来了 因此我们可以直接把具体的年份 放到执行计划里

这样执行Agent接到的任务 就更加精确了 然后进入到第二轮 在这一轮中 我们同样先取出最新的执行计划 发给执行Agent 让他执行其中的第一步 拿到执行结果后 我们再把执行结果加入到历史执行记录中 然后在Re-Plan阶段把用户问题 执行计划和历史执行记录 发给Re-Plan模型 拿到第三个执行计划 在第三轮中

我们还是先取出执行计划 让执行Agent处理其中的第一步 当然现在也只剩一步了 执行之后 我们就可以拿到一个执行结果 把执行结果加入到历史执行记录里 然后再把用户问题 执行计划和历史执行记录 都发给Re-Plan模型 让他再生成一个......

让他再生成一个......

哎,好像所有的任务都已经完成了 没有步骤要做了吧 没错 在最后一轮中 Re-Plan模型会发现 所有的步骤都已经做完了 用户的问题可以回答出来了 此时Re-Plan模型返回的 就不是最新的执行计划了 而是最终的答案 所以在流程图里面 我们也要把执行计划 换成最终答案 Agent主程序 接到这个最终答案之后 便会把这个答案转发给用户

整个流程也就结束了 所以回头看一下这个流程图 我们之前说Agent主程序 请求Re-Plan模型 给出一个新的执行计划 这个说法其实并不准确 更准确的说法是 Agent主程序请求Re-Plan模型 给出一个新的执行计划 或者是返回最终答案 如果还有步骤要执行的话 那就给出一个新的计划 如果没有步骤要做了 用户的问题已经回答出来了

那Re-Plan模型就返回最终答案就好了 因此Re-Plan模型的返回 也有两种可能性 新的执行计划 或者是最终答案 这样才是一个准确的流程图 相信到这里为止 你对Plan-and-Execute流程 就有一个非常清晰的认识了 不过有些人可能想要 Plan-and-Execute的具体实现代码 LangChain官方提供了一份 你可以到这个页面里面自行获取

今天的视频就到此结束了 别忘了点赞 关注 我们下次再见 拜拜

Loading...

Loading video analysis...