ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大模型Agent从零到一:概念、原理与最小系统实战

大模型Agent从零到一:概念、原理与最小系统实战

先讲个真实的场景。朋友问我:“你天天说Agent,到底什么算Agent?难道接个大模型API的对话机器人就是Agent了?”我当时没有直接回答,而是让他打开一个普通的聊天机器人,问它“帮我查一下这个服务器端口有没有被占用”。机器人说了一段“你可以这样查”的步骤,但没有真正帮他执行。这其实就是大模型和智能体最直观的差别:一个只动嘴,一个真动手。

大模型智能体(Agent)这两年是被聊得最热、也最容易误解的概念之一。作为一个从零到一搭过好几个Agent模拟项目的开发者,我想把这块掰开揉碎讲清楚:Agent到底是什么、内部怎么工作、怎么自己写代码跑通一个最小可用系统、以及踩过哪些必须记下来的坑。这篇文章适合两类人——第一类是想入门的开发者,看完能落地;第二类是已经写过程序但总觉得队友的Agent方案不靠谱的工程师,看完能判断问题出在哪儿。

1. 先搞清楚:Agent到底是个什么东西

1.1 大模型是大脑,Agent是整个人

传统方式调用大模型,本质就是“输入一句话,输出一句话”。哪怕模型再聪明,它的能力边界也局限在“对话”这个动作里。你问它“帮我看看这台服务器的负载情况”,它能给你一段分析建议,但它不会真正去执行一条命令,也不能替你格式化一份报告然后发出去。

Agent把“对话能力”升级成了“做事能力”。它不是一个被动回答问题的接口,而是围绕大模型构建的一整套系统:有目标、能拆计划、能调工具、能存记忆、能根据结果修正下一步动作。如果用一个人来类比,大模型是大脑,Agent则是把大脑装进一个有手、有脚、有记忆的身体里,让它能独立完成一件具体的事。

这个区别很关键,因为它直接决定了开发方式。你写的不是一段优雅的prompt,而是一套控制逻辑和工程系统。理解不了这一层,后面那些框架、编排、工具链的讨论都是空中楼阁。

1.2 Agent的四个基本能力,缺哪个都跑不远

我习惯把Agent拆成四件事来理解,这四件事对应一个完整的能力闭环:

  • 规划:把大目标拆成子任务。比如“分析这个项目最近一周的代码提交”,会拆成“获取提交记录”“统计活跃度”“检查提交信息规范”“汇总成报告”这几步。
  • 记忆:保存过程中产生的信息。短期记忆承载当前任务的中间结果;长期记忆保存跨会话的用户偏好和历史结论。
  • 工具使用:通过调用外部API、函数、命令行来获取信息或执行动作。这是Agent从“能说”到“能做”的分水岭。
  • 行动:把规划真正执行出来,观察结果,判断下一步往哪走。

这四个词听起来很宏大,但等你写过一个最小实现就会发现,真正核心的其实是“循环”两个字。模型根据当前状态决定下一步动作,执行动作,把结果写回对话,模型继续决定下一步。循环想明白了,剩下都是工程细节。

2. 架构拆解:一个可运行Agent的内部结构

2.1 一个绕不开的循环:感知、决策、行动

任何Agent,不管包装得有多花哨,底层都逃不过“感知-决策-行动”这个循环。

感知阶段,把用户输入、历史上下文、最近一次工具返回结果这些信息拼装成模型能理解的请求。决策阶段,模型根据已有信息做出判断——是直接输出最终答案,还是调用某个工具,或者向用户追问缺失的信息。行动阶段,如果要调工具,就执行对应的函数或API调用,把返回结果作为新的信息塞回感知阶段,让模型继续决策。

做过前后端开发的人会发现,这个循环的形态非常像“请求-响应-再请求”的迭代过程。每跑一轮,模型做一道选择题:现在我有这些信息,我该继续查资料、执行动作,还是收工写结论。Agent的智能程度,很大程度就看这个选择题做得好不好。

这里我想强调一个观点:不要把Agent想成一条流水线,它是“循环”而不是“链”。流水线是定死的步骤,A完了走B,B完了走C。循环是每一步都根据最新情况动态调整方向。这也是Agent能解决传统脚本处理不了复杂问题的根本原因,代价就是不可预测,必须有护栏兜底。

2.2 记忆系统:不是所有东西都塞进上下文

记忆是Agent和普通脚本最大的区别。脚本靠变量存状态,Agent靠记忆组件维持连续性。但记忆不能简单理解成“把历史消息全拼到prompt里”,大模型的上下文窗口有上限,token成本也是真金白银。

我实际是把记忆分成两层来设计的。

短期记忆是当前这一轮任务的“工作台”,一般用上下文窗口承载,包含用户原始输入、中间的工具调用结果、模型自己的推理记录。短期记忆的内容量控制,是Agent开发里最重要的工程问题之一。我见过太多跑飞的任务,都是因为对话里塞了太多无关的历史信息,模型被噪声干扰。如果你在日志里看到模型频繁遗漏早期的重要要求,八成不是记忆能力差,是短期记忆里干扰太多了。

长期记忆是跨会话、跨任务依然保留的知识,比如用户长期偏好、项目约定、之前任务的结论。这些数据不会每轮都放进请求,而是按需检索。常见方案是向量数据库存文本向量,按语义相似度召回相关片段。但在最小实现里,长期记忆完全可以用一个JSON文件替代——记住用户常用的几个参数,下次用到时查一次。先把流程跑通,再考虑引入真正的向量检索。

2.3 工具层:Agent可靠性的真正分水岭

一个Agent的能力上限,很多时候不是由模型决定的,而是由它能调用的工具决定的。工具层设计得好,接个普通模型也能跑出不错的效果;工具层混乱,接再强的模型也会在调用参数、错误处理上翻车。

我在实践中总结了工具层的三条核心原则。

第一,每个工具只做一件事,描述必须写清楚。模型选择工具的唯一依据就是你的工具描述,描述模糊,它就选错。第二,工具的输入输出必须结构化且可预期。输入参数定义清楚类型和范围,输出尽量收敛成简短清晰的文本。第三,工具执行出错时不要抛异常,而是把错误信息作为返回值交回给模型。模型看到“这个城市名不存在,请检查后重试”,才有可能自己修正参数再跑一次,流程才不至于崩溃。

还有一条容易被忽略:工具数量不是越多越好。每多一个工具,模型就多一个选择,选错的概率也等比上涨。工具箱够用就行,宁可精简,也不要堆砌一堆“将来可能用得上”的接口。

3. 手把手搭一个最小Agent:查询天气的实验

3.1 环境准备:模型、代码和依赖

动手之前先把环境弄干净。建议用Python 3.10以上版本,装好HTTP请求库和对应模型服务的SDK。这里有个硬性前提:你选用的模型必须支持函数调用能力。如果模型不支持,就得退回“让模型输出特定JSON、你再去解析执行”的土办法,不仅多写一堆解析代码,可靠性也会明显下降。

写代码之前,先花两分钟验证基础环境:能成功发起一次最简单的对话请求。这一步看着像废话,但我见到的“怎么跑不起来”案例,相当一部分卡在API密钥配错、网络不通、模型名称拼写错误这些地方。环境通了再往下一步,能省掉很多排查时间。

3.2 定义第一个工具:让模型知道它能用

以“查询天气”为例。定义一个工具,本质就是给模型一张信息完整的使用说明书:这个工具叫什么、干什么用的、需要哪些参数、每个参数什么类型。模型看了说明书,才可以在你问“明天上海天气怎么样”的时候,自动生成一次工具调用请求,而不是自己在那儿编一段天气。

工具说明用JSON Schema格式写:

{ "type": "function", "function": { "name": "get_weather", "description": "查询指定城市未来几天的天气情况,返回温度、天气现象和风力", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,比如 北京、上海,必须是真实存在的城市" }, "date": { "type": "string", "description": "查询日期,格式YYYY-MM-DD,不传则默认返回未来三天" } }, "required": ["city"] } } }

这里最容易被低估的是description字段。模型能不能选对工具、填对参数,一半靠描述。我有一个实操经验:把参数取值范围和格式示例直接写进description里,准确率提升非常明显。比如你只写“日期”,模型可能给你“明天”两个字;你写成“格式YYYY-MM-DD”,它就会老实给“2025-03-15”。

3.3 主循环落地:二十行代码让Agent真正动起来

工具定义好,就该写主循环了。下面的代码把消息和工具描述一起发给模型,然后判断模型返回什么。如果模型返回工具调用指令,就执行对应函数,把结果作为一条新的工具消息追加到对话里继续问;如果模型直接返回文本,说明任务完成,输出结果。

import json def call_model(messages, tools=None): # 调用模型服务的统一封装,返回完整响应对象 ... def get_weather(city: str, date: str = None) -> str: """模拟的天气查询函数,实际项目里替换成真实API调用""" # 这里示例为固定返回,真实场景应请求天气服务接口 return f"{city} 今天多云,气温18到25摄氏度,风力3级" tool_map = { "get_weather": get_weather } def run_agent(user_input: str): messages = [{"role": "user", "content": user_input}] tools = [weather_schema] for step in range(10): # 硬性限制最大轮数,防止死循环 response = call_model(messages, tools) msg = response.choices[0].message if getattr(msg, "tool_calls", None) is None: print("最终回答:", msg.content) return # 把模型的工具调用请求加入完整消息历史 messages.append(msg) for tc in msg.tool_calls: fn_name = tc.function.name args = json.loads(tc.function.arguments) result = tool_map[fn_name](**args) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": str(result) }) if __name__ == "__main__": run_agent("北京今天会下雨吗?")

这段代码二十行左右,但已经具备一个Agent的最小闭环:决策、行动、再决策。tool_map是执行入口,工具Schema是模型了解世界的窗口,messages承担了短期记忆的工作。后面所有更复杂的框架、编排、多Agent协作,都是在这个基础上不断加厚的。

有一个细节需要注意:当模型返回tool_calls时,不能把整个响应塞回messages,而是要追加模型消息本身,并且后续每条工具结果都要对应填入tool_call_id。顺序颠倒或者漏掉ID,模型就拿不到工具结果了,这个项目就别想跑通。

3.4 第一次运行的三个必踩细节

这一节写我爱踩的坑,每一个都让我付出过不少调试时间。

先跑通无工具对话。很多模型服务在开启工具调用模式后,连普通问题也可能返回奇怪的调用格式。先不加工具验证一遍基本对话,再加工具,排查范围会小很多。

工具返回内容尽量用纯文本。模型对纯文本的理解比复杂嵌套JSON稳定。比如天气函数返回“北京今天多云,气温18到25摄氏度,风力3级”,比返回一个多层字典好用得多。如果你必须在工具里返回结构化数据,也别直接裸抛JSON,做一层精简——只留模型做决策真正需要的字段。

循环步数必须限制。for循环的range(10)不是摆设,它是整个系统最重要的兜底之一。模型在一件事上反复调用工具停不下来的场景,可真不少见。没有步数上限,最坏情况是等到账单出来你才意识到问题。

4. 任务规划:让Agent处理真正复杂的问题

4.1 ReAct模式:边想边做

任务复杂到一定程度,“查询天气”这种一步到位的工具调用就不够用了。你希望Agent面对一个目标时,自己拆解成多个子动作。这时候先要掌握的规划模式是ReAct,全称是“Reasoning and Acting”,核心思路就是让模型每一轮先输出思考过程再执行动作。它得先说“我缺少这些信息,需要调用工具去获取”,然后才发起调用,而不是变成一台盲目的工具连按器。

ReAct适合任务链路较短、每一步都依赖上一轮结果的场景。例如“搜索与当前目标相关的公开资料并整理要点”,每拿到一条搜索结果,下一条关键词可能就要基于当前内容调整,这种动态依赖链最适合边想边做。

实现起来不复杂,要么模型服务已经内置了类似行为,要么在系统提示词里显式要求“每轮先输出Thought,再输出Action”。亲测有效,思考这一步看着多余,但它极大降低了模型在工具链中迷路的风险。

4.2 Plan-and-Execute:先定计划再干活

步骤更多、失败代价更高的任务,我建议用Plan-and-Execute模式。第一阶段让模型把大任务拆成一组有序子任务;第二阶段按顺序执行这些子任务,每完成一步把结果回填进计划,再接着做下一步。

这种方法的核心价值是可控性强。你可以在子任务之间插入人工确认、校验规则、中间结果快照。出了问题能直接定位到具体某一步,而不是让模型从头自由发挥。缺点也明显——模型预定的计划可能不符合实际,碰上需要动态调整的场景,僵硬的固定计划反而拖后腿。

所以实际项目里我更常把两种模式混用:整体保持先计划再执行,在某个子任务内部步骤不确定时,内部退化成ReAct边想边改。这个不是写在教科书里的优雅方案,而是真跑过之后得出的实用妥协。

4.3 给规划层加护栏

Band规划做得再好,模型也不是万无一失。控制不可控性,我用的三招是照上面这些实践总结出来的:

  • 给计划输出加格式校验。要求模型输出结构化的计划JSON,你用代码检查字段是否齐全、参数类型是否匹配,不合格就让它重新生成。
  • 每个子任务绑定明确的完成条件。模型容易在“干到什么程度算完”这件事上模糊,你可以在提示词里明确“当XX条件满足时,本子任务视为完成”,能大量减少重复和提前收工。
  • 设置全局超时与步数上限。单次调用超时、整体循环步数、Token预算全部在初始设计时定好。这些不是限制能力,是为了兜底。没有护栏的Agent演示很酷,上线跑两周全是事故。

说实话,规划能力这块我见过太多团队直接丢给模型自由发挥,然后靠“加大模型”来试图解决,效果并不理想。工程问题还得靠工程手段,护栏比更聪明的模型更可靠也更便宜。

5. 跑起来之后,麻烦才真正开始:常见故障与排查

5.1 工具参数总是传不对

这是使用频率最高的故障。你明明定义好了参数,模型却把城市名填错、日期格式弄乱,或者把可选参数塞进一个奇怪对象。根据我的排查经验,这类问题九成出在工具定义上,而不是模型能力不行。

排查顺序固定:先看参数类型是否精确到string、number、integer、enum,避免过度使用嵌套对象;再看description是否明确写了格式和示例;最后看工具返回的信息是否是当前任务真正需要的——有时候模型不是不会传参数,是它根本没从对话中拿到正确答案,只能编。

在复用用户原话中的关键信息时,尽量抽取成结构化字段放入消息,比如用户说“帮我查下北京明天的天气”,你可以先本地提取city=北京、date=明天对应的日期,再放进请求。这样模型有了明确信息,参数准确率立刻上一个台阶。

5.2 上下文越跑越长,Token成本爆炸

Agent每执行一次工具调用,就必须把整轮历史消息重新发给模型。跑十步的任务,光输入token可能就是首次对话的十几倍。小实验没感觉,任务复杂之后成本肉眼可见往上涨。

我的处理经验是三句话:对话超过一定轮数后,对旧消息做摘要压缩;中间的工具结果只保留结论性信息,丢掉原始大段数据;给工具返回值设长度上限,超长就截断。更进阶的处理,是把当前任务状态从历史消息里单独提炼出来,每次只重发任务状态,不重发完整历史。这样模型每轮的输入信息都能控制在稳定范围。

这里要补充一个意识层面的提醒:Token消耗不只是成本问题,也是延迟问题。输入越长,模型首字响应时间越慢。上下文失控的Agent,用户体感是“越用越慢”甚至“卡死”,优化Token管理同时也在优化用户体验。

5.3 模型陷入死循环

现象是Agent反复调用同一个工具、说同样的话,或者在同一组结果之间来回横跳。这多半是因为模型没有从工具返回值中得到足够的新信息来判断“事情已经完成”,也可能它在等一个永远等不到的条件。

排查技巧第一优先看日志。每一轮的模型输出、工具调用、返回值都记录下来,跑完之后重放,基本一眼就能定位卡在哪一步。修复方式按场景来:如果缺停止条件,就在提示词里写明“如果没有更多信息可以补充,直接给出结论”;如果模型只是机械重试,靠全局步数上限加结果去重兜底,两次调用参数相同且结果相同就强制跳出。

每次循环,模型都在做一道选择题:直接回答,还是继续调工具。排查死循环时记住这句话——只要系统里存在“可以走直接回答”的那条路,模型就有出口,死循环的概率就会大幅降低。

5.4 安全边界,可能暴露最大风险的那一层

Agent能调工具,就代表它能执行真实动作,这比纯聊天的风险高一个量级。不是所有工具都该无条件开放给Agent。我实际项目里坚持几条底线,你可以直接抄作业:

  • 写操作必须二次确认,涉及删除、修改、发送消息这类不可逆动作尤其如此。
  • 工具调用做白名单控制,模型能调哪些工具由系统定义,不是模型自己说了算。
  • 日志全量记录,包括本轮完整的prompt、模型原始输出、工具入参和出参、耗时、Token数。没有完整日志,Agent出问题等于盲修。
  • 工具返回数据最小化授权,只给当前任务需要的那部分,别把整个数据库倒给模型。

这层工作看起来是运维范式,但Agent项目能不能活过上线后的前两星期,就靠它撑着。我在模拟项目里遇到过Agent“自作主张”访问了不该访问的接口,幸好隔着权限层被拦下来,否则就是事故。

6. 评估与优化:项目能不能立住,看这一步

6.1 评测Agent的五个维度

Agent不像传统接口,很难用一个通过率说清楚好坏。我实践下来,至少要从五个维度看:

  • 任务完成率:预期任务的最终成功比例,这是底线指标。
  • 工具调用准确率:单个工具的调用中,参数无误、时机恰当的占比。
  • 步数效率:完成同一任务平均要多少轮工具调用,能直接反映规划能力。
  • Token消耗:完成任务的总Token数,直接对应成本和响应速度。
  • 鲁棒性:同一个任务换种说法、参数稍有偏差时,结果稳不稳定。

评估方法别只看一两个手工样例。准备一组有明确期望结果的测试用例,至少几十条,每次改动后用同一批用例跑一遍,记录五维数值。这方法很土,但比任何“我觉得变聪明了”都可靠。

6.2 三个有效又便宜的提升手段

如果评测发现问题,优先试这三招,成本低收益快。

先优化工具定义和返回格式。这是全项目性价比最高的操作,前面已经反复强调——描述写清楚、返回收敛成简洁文本,效果比换大模型更明显。

再给关键路径加约束性提示词。比如规定“如果工具返回空结果,必须明确回答‘未查询到数据’,不允许编造”。模型不是故意瞎说,更多时候是没人告诉它该怎么说。

最后建一个失败示例集。每次跑失败的任务,保存一份完整日志副本。调参或改进时,把最典型的失败样例加进测试集,确保同一个坑不被反复踩。

这三件事做完,项目稳定性提升往往比加一堆新框架更显著。我也见过团队热衷叠加“新架构”,结果问题依旧,成本却翻了不少。基本功才是一个Agent项目能走多远的核心变量。

最后分享一点实在的体会

这篇内容里几乎没有偏门的技巧,全是过去几个月在多个模拟项目里真金白银跑出来的经验。如果你准备开始第一个Agent项目,我的建议是:先不要搭任何花哨架构,老老实实把一个最小循环跑通,把工具定义、日志、步数限制这三件基本功做扎实,再考虑规划模式和质量优化。

Agent的技术栈还在快速演进,但那个感知-决策-行动的循环没有变,围绕循环建立的可观测性和兜底机制也不会过时。先把地基打牢,无论以后工具链怎么换,你都能快速适应,不至于在每次新框架出来时推倒重来。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进