ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

大模型Agent入门:从套壳对话机器人到自主决策循环的完整实践路线

大模型Agent入门:从套壳对话机器人到自主决策循环的完整实践路线 说实话我第一次觉得自己“做出了一个 Agent”是在一个下午。当时我写了一个 Python 脚本让大模型去查天气、翻日程、再自动回复然后兴冲冲拿给同事看。对方瞟了一眼说这不就是个套了壳的对话机器人吗。我愣了半天因为他说到了关键处——我那套东西确实只是“看起来会做事”真正遇到没见过的指令就抓瞎。后来我在大模型 Agent 开发这个方向上来回折腾了大半年从抄框架 Demo 到自研调度循环踩过无数坑才慢慢看清了 Agent 和普通 LLM 应用之间的分界线。这篇文章不是高深理论课而是把我自己实践下来的入门路线、核心原理、最小代码骨架以及真实项目中才暴露的工程细节按顺序讲一遍。适合刚接触大模型、正在纠结“Agent 到底怎么从概念变成能跑的东西”的开发者。1. 先别急着写代码Agent 和“套壳对话机器人”差在哪很多人第一次了解 Agent是被网上“AI 自己规划任务、自己调用工具”的演示视频吸引的。结果自己动手的时候第一步就卡住了明明大模型能输出一段很像样的回答可它说完“好的我来帮您查询天气”之后就真的……只是说说而已。1.1 一个让人沮丧的“Hello World”你可以做个实验随便找个大模型 API塞进去一句“请帮我查一下北京的天气”模型会绞尽脑汁编出一段“北京今天晴气温 25 度空气质量良”。如果当天北京刚好下雨它可能依然理直气壮告诉你晴。为什么因为模型本身没有感知外部世界的能力它只是在根据训练数据做“文字接龙”。所以最原始的“大模型 提示词”应用本质上是一个更聪明的聊天机器人。它能说但不能做。你不能指望它替你去查数据库、调接口、改文件、发消息。这些动作必须由你写的代码来完成。1.2 Agent 的三个判断标准自主、循环、调用我后来总结了一套判断标准拿去对照自己的系统一眼就能看出它到底算不算 Agent特征聊天机器人真正的 Agent自主规划按用户问题直接回答把任务拆成多步决定先做什么后做什么循环执行一次性输出答案根据执行结果反复调整直到目标完成实际调用不能操作外部系统能调用 API、执行代码、写文件、控制系统这三个点缺一个都不成立。尤其是“循环执行”这条是 Agent 和普通“意图识别 函数调用”方案的本质区别。普通方案是写死的用户说“查天气”就调天气接口说“查日程”就调日程接口。Agent 则是让大模型当大脑由它临时决定“现在该调哪个工具、调完结果怎么理解、下一步要不要再调”。1.3 你为什么需要 Agent而不是写死 if-else有人会问如果任务就这几种我写一堆 if-else 不比大模型稳多了这话在任务完全可枚举时没错。但真实需求往往是“帮我查一下北京明天下不下雨如果有雨就提醒我出门带伞顺便把后天会议改成线上”——这句话里至少有四种意图还掺杂了条件判断。要覆盖这种组合爆炸if-else 根本写不完。Agent 真正带给你的是一个“决策循环”模型负责理解当前状态、决定下一步动作你负责把动作落地执行完把结果喂回去让模型继续判断。看起来玄乎拆开就是循环里反复做三件事想一步、做一步、看一眼结果。2. Agent 的骨架模型、规划、记忆、工具四件套把 Agent 拆到底四样东西缺一不可模型层提供“脑子”规划层负责“想”工具层负责“做事”记忆层负责“记”。下面逐个说清楚它们在系统里到底扮演什么角色。2.1 模型层不是越贵越好模型是 Agent 的底座但入门阶段最容易犯的错是盲目追求大参数模型。基础问答、信息抽取、简单工具选择中小尺寸模型完全够用。真正需要更强的推理能力时往往出现在“多跳任务”上——比如先查用户订单再根据订单内容查物流再根据物流状态决定是否触发售后流程这种链路越长对模型的长程规划能力要求越高。我的建议很直接先用一个能力足够、接口稳定的商用模型 API 把整个流程跑通再考虑降级或替换。开发期间每天都在调循环逻辑如果模型本身的输出不稳定你根本分不清报错是模型问题还是代码问题。等流程稳定了再去测试能不能用更便宜的开源模型替代这时候你在提示词和工具描述上积累的经验才会真正变成资产。另外提一句“大模型微调”不少人入门就想着微调。我的看法是微调解决的是“让模型学会特定领域语言和格式”的问题而不是“让模型学会调用工具”的问题。工具调用的核心能力靠提示词和函数定义就能触发微调应该是后期优化手段不是入门第一步。2.2 规划层ReAct 循环里到底发生了什么理解 Agent 规划机制最好的入口是 ReAct即“推理 行动”交替进行。名字听着唬人其实就是把人类解决陌生问题的流程教给模型我先想想现在是什么情况Thought我决定做个什么动作Action我给动作带上参数Action Input我看看执行结果Observation根据结果决定下一步或者给出最终回答Final Answer这个循环跟程序员调试代码的节奏几乎一模一样出错了改参数改完再跑跑了再看日志直到程序符合预期。区别只是“写代码的人”换成了“大模型”。实际开发中规划层的实现方式有几种直接用文本格式让模型输出“Thought/Action”的 ReAct 风格或者用官方 Function Calling 机制让模型返回结构化工具调用请求。两种我都试过结论是早期学习用 ReAct 文本格式更适合因为每一步都看得见出了问题一眼就能定位上线阶段则更推荐 Function Calling解析稳定、省 token。2.3 记忆层上下文、工作区、长期存储Agent 的记忆不是单一概念至少分三层第一层是上下文也就是每次请求发给模型的那段对话历史。这是 Agent 的“工作台”所有推理和操作都在这上面发生。第二层是工作记忆指循环过程中产生的中间状态——比如某一步工具返回的原始数据、已经完成的子任务清单。第三层是长期记忆跨会话保留的信息通常落库或落到向量数据库里下次用户来了还能接着用。新手最容易忽略的是第二层。因为很多 Agent 框架默认把“模型说过的话”全部塞进上下文看起来像在记忆实际上只是堆砌。真正影响决策的往往是你显式维护的“任务状态”比如一个 JSON 结构里面写清楚“目标是什么、已完成哪几步、当前卡在哪”。把这份状态一起交给模型它的判断会稳定得多。2.4 工具层Agent 的“手”从哪来工具层是 Agent 区别于普通对话的关键。每接入一个工具Agent 就多了一只手。工具可以是 HTTP 接口、数据库查询、代码执行器、搜索引擎、企业内部 RPA甚至另一个 Agent。工具接入的方式通常是“函数声明 实现”先写好一个 JSON Schema告诉模型工具叫什么、干什么、参数是什么再实现对应的 Python 函数或 API 调用。模型根据用户需求和工具描述做匹配选择要调用的工具并生成参数你的代码负责真正执行。这里有个血泪教训工具描述写得好不好直接决定模型选得对不对。你想接入一个“查询天气”接口描述写成“查询天气”四个字模型面对“北京适合穿什么衣服”这种问题时可能压根想不到调它但如果你写“查询指定城市当前天气和未来三小时预报用于回答穿衣、出行问题参数 city 为城市名如北京”模型的使用率立刻提升一个档次。3. 用 200 行代码手写一个最小 Agent接下来进入动手环节。我强烈建议入门阶段先别急着引框架自己把核心循环写一遍。不是说我反感框架而是框架的抽象层会挡住你的视线——循环死在哪里、上下文怎么增长、工具调用失败怎么恢复这些问题只有自己写过才真正有感觉。3.1 为什么建议先自研再上框架我用 LangChain 的经历特别能说明问题照着文档写了二十行代码跑通了然后一改参数就开始报各种抽象概念相关的错。我在查问题的时候完全不知道“Chain”和“Agent”背后的逻辑是什么只能无脑搜错误码。后来把循环自己写了一遍再看框架文档每个抽象概念都能对上号了——哦它那个 “AgentExecutor” 原来就是我写的 while 循环那个 “Tool” 就是我的 function 注册表。自己实现一遍还有一个好处你可以按自己的业务习惯改造循环逻辑而不是被框架的约定绑住。3.2 工具注册与描述先给 Agent 准备“工具箱”一个可以运行的最小 Agent结构非常简单。先定义工具注册表TOOLS {} def register_tool(name, description, handler): TOOLS[name] { description: description, handler: handler, } def get_weather(city): # 开发阶段先写死返回值跑通后再接真实天气 API return f{city}晴气温 25 度东南风 2 级夜晚可能转小雨 register_tool( get_weather, 查询指定城市的实时天气和短期预报用于回答穿衣、出行问题参数为 JSON 字符串例如 {\city\: \北京\}, get_weather, )注意两点描述里一定要带“适用场景”和“参数示例”。我见过太多人工具写好了模型就是不用最后发现是描述太抽象模型不知道这个工具能解决什么问题。参数示例也很关键它相当于教模型“怎么写参数”能显著减少 JSON 格式错误的概率。3.3 ReAct 主循环核心就一个 while下面这段是 Agent 的心脏我简化过很多次最后只剩核心逻辑import json import re class MiniAgent: def __init__(self, llm_func): self.llm llm_func self.tools {} def register_tool(self, name, description, handler): self.tools[name] {description: description, handler: handler} def system_prompt(self): lines [ 你是一个可以调用工具的助手。需要工具时必须按以下格式输出, Thought: 你的推理过程, Action: 要调用的工具名, Action Input: 传给工具的 JSON 参数, 工具结果会以 Observation 形式返回给你。, 得到答案后输出, Final Answer: 最终回答, 可用工具, ] for name, meta in self.tools.items(): lines.append(f- {name}: {meta[description]}) return \n.join(lines) def run(self, question, max_steps5): messages [ {role: system, content: self.system_prompt()}, {role: user, content: question}, ] for step in range(max_steps): resp self.llm(messages) messages.append({role: assistant, content: resp}) print(f[step {step 1}] {resp}) if Final Answer: in resp: return resp.split(Final Answer:, 1)[1].strip() action re.search(rAction:\s*(\S), resp) action_input re.search(rAction Input:\s*(\{.*?\}), resp, re.S) if not action or action_input is None: messages.append({role: user, content: 格式错误请按 Thought/Action/Action Input 格式输出。}) continue name action.group(1) try: params json.loads(action_input.group(1)) except json.JSONDecodeError: messages.append({role: user, content: Action Input 不是合法 JSON请重新输出。}) continue if name not in self.tools: messages.append({role: user, content: f工具 {name} 不存在可用工具{list(self.tools.keys())}}) continue try: observation self.tools[name][handler](**params) except Exception as e: observation f工具调用失败{e} messages.append({role: user, content: fObservation: {observation}}) return 已达到最大步数任务未完成。这个循环做了四件关键的事调用模型、解析输出、执行工具、把结果写回消息列表。每一步都透明出问题可以直接看打印日志。3.4 把模型接进来跑通第一次真实对话llm_func可以指向任何大模型服务。开发者可以直接用兼容 OpenAI 协议的接口这样后续切换到本地部署也很方便from openai import OpenAI client OpenAI( api_keyyour-api-key, base_urlhttp://your-llm-service/v1, # 也可填本地部署模型的服务地址 ) def llm_func(messages): resp client.chat.completions.create( modelyour-model-name, messagesmessages, temperature0.2, ) return resp.choices[0].message.content agent MiniAgent(llm_func) agent.register_tool( get_weather, 查询指定城市的实时天气和短期预报用于回答穿衣、出行问题参数为 JSON 字符串例如 {\city\: \北京\}, get_weather, ) print(agent.run(明天去北京出差需要带伞吗))跑起来之后你会看到类似这样的轨迹[step 1] Thought: 用户问明天北京出差是否需要带伞这取决于明天下不下雨我需要先查询北京的天气。 Action: get_weather Action Input: {city: 北京} [step 2] Thought: 天气查询结果是今晚可能转小雨明天是否下雨还不确定我应该说明情况并给出建议。 Final Answer: 北京今晚可能转小雨建议你随身带一把折叠伞备用。远途出差也建议备一件薄外套早晚温差较大。到这一步你才算真正拥有一个“会做事”的 Agent。虽然简陋但“思考 - 调用 - 观察 - 回答”这个核心闭环已经完整了。4. 框架选型与生产化改造从 Demo 到能上线的东西循环跑通之后很多人会问那我是不是应该改用框架我的回答是用到框架的抽象能力之前先琢磨清楚你缺什么。如果你只缺“一个 Agent 循环”自己写的 200 行完全够用如果你需要复杂的状态机、多 Agent 协作、持久化记忆、可视化的执行链路再考虑框架。4.1 常见框架对比与我的选择这里列几个我实际用过的主流方案帮大家快速建立认知方案核心特点适合场景注意点LangChain / LangGraph生态大编排能力强支持状态图快速验证、复杂多步编排抽象层多版本变动快旧文档别直接抄LlamaIndex检索增强和知识库方向强以文档问答为核心的 Agent偏 RAG任务规划能力相对弱AutoGPT / BabyAGI全自主循环任务自己拆实验研究、探索任务边界成本不可控生产环境慎用自研编排完全掌控每一步生产系统、特定领域强约束场景轮子要自己造但逻辑完全透明我的实战经验是生产系统里我更倾向于“轻自研 少量现成组件”的组合。循环自己写工具调用自己管状态存 Redis这比引一个全家桶框架更可控。框架里我唯一重度使用的是 LangGraph因为它的状态图能直观表达“多步骤、有条件跳转”的任务流比较适合复杂的内部流程系统。如果只是单 Agent 线性任务我会直接用自研循环。4.2 提示词工程把 Agent 的“人设”和“边界”写清楚很多人把 Agent 的提示词理解成“给模型设定角色”比如“你是一个智能助手”。这远远不够。生产级提示词需要写清楚四件事角色和职责它是干什么的什么不该干可用工具清单每个工具的用途、边界、失败时怎么办输出格式约束什么时候调用工具、什么时候直接回答安全边界哪些请求必须拒绝哪些信息不允许泄露我还习惯在每个工具描述后面加一句“什么时候不要用这个工具”。比如天气工具加一句“不要用于查询历史天气”搜索工具加一句“不要用于搜索用户个人信息”。这些负向约束对减少工具乱调用非常有效我实测能让误调率下降 30% 以上。另外temperature 要调低。Agent 循环不是创意写作每一步选择都必须尽量稳定。我自己默认用 0.2 以下宁可回答平淡一点也不要它在“该不该调工具”上发挥想象力。4.3 会话状态与失败恢复让 Agent 扛得住真实请求Demo 跑通之后上线前第一件事就是状态管理。真实系统里一个用户的请求不是一次性任务中间可能穿插澄清、中断、继续。每个会话的状态必须单独保存。我的做法是为每个会话生成一个 session_id把 Agent 当前的任务状态、已执行的步骤、关键中间结果以 JSON 形式存到 Redis 里。用户再次发消息时把这份状态作为上下文的一部分交给模型同时允许模型更新它。这样一来即便中间某次请求超时Agent 也能知道“上次走到哪一步了”而不是一切从头再来。失败恢复是另一个容易被忽略的点。真实环境里工具一定会出错——第三方接口超时、数据库锁、返回格式变化。我的处理方式很朴素把异常信息作为 Observation 喂回给模型让它自己决定改参数重试、换工具还是放弃。代码里 try/except 不是为了吞掉错误而是把错误转成模型能理解的语言。这里还要提一句“agent 安全”。给 Agent 的工具列表要做准入和最小权限不需要的权限不给敏感操作必须二次确认工具可以访问的资源边界要写清楚。我见过不少项目为了演示把文件删除、发邮件、改库这种高危工具全部挂上去结果一次误调用就出事。Agent 能自主行动所以工具权限就是它的行为边界宁可少给不能多给。4.4 并发与成本单机用户怎么扛量大了怎么办热搜里有个词叫“ai agent 怎么扛并发”这个问题我吃亏过。Agent 系统并发和普通 Web 服务不一样瓶颈不在模型 API 本身而在编排循环的无状态化和外部工具调用时长。先要理清一个事实大模型 API 本身是无状态的真正带状态的是 Agent 的记忆和上下文。这意味着编排层可以做到完全无状态——每次执行 Agent 循环时会话状态从 Redis 取跑完再写回去。这样你可以开任意多个 worker 处理不同 session 的请求互相不干扰这就是 Agent 水平扩展的基本盘。工具调用是另一个并发瓶颈。一个 Agent 循环里可能串行调用多个外部接口每个接口耗时 200ms五个步骤就是一秒多。优化的做法是把能并行的工具调用并行掉比如同时查天气和查日程把高频工具的返回结果做短时缓存。这些措施能显著降低单次任务的延迟。量再大一点就得引入队列削峰了。用户请求先进消息队列worker 异步拉取执行前端用 SSE 或者轮询把进度推给用户。这样的架构下Agent 执行的慢和用户等待的体验是解耦的高峰期不至于把整个系统打挂。最后是成本。Agent 的 token 消耗和普通对话不在一个量级每个循环步骤都要把全部历史消息重新发给模型上下文越长成本涨得越快。我上线前一定会给每个任务加预算护栏比如“单任务最多执行 8 步”达到上限就明确告诉用户“这个任务太复杂需要拆成多个问题”。这不是用户体验降级这是在成本爆炸之前踩刹车。5. 我踩过的坑循环不终止、工具乱调用、上下文爆炸最后分享几个我反复踩、也帮很多朋友排查过的典型问题每一个都对应真实事故。5.1 死循环问题最大步数只是一道保险丝最早我写 Agent 的时候以为加了 max_steps 就万事大吉。直到某次任务里模型开始反复调同一个查询工具每次拿到结果都发现“还不够”于是继续调、继续看、继续调直到步数上限才停下白白浪费了几万 token。后来我意识到 max_steps 只是最后一道保险丝不是主要防御手段。真正有效的做法有两个一是细化目标在提示词里让模型“每完成一个子目标就在任务状态里打个勾”全部勾完就必须收尾二是给 Observation 加反馈比如查询结果没有新信息时明确告诉模型“该工具返回了与之前一致的结果请更换策略或直接结束”。这一句话就能打断很多无效循环。5.2 工具乱调用给工具做“准入清单”有一次我给 Agent 挂了八个工具结果它面对“帮我订个会议室”这种简单需求先查了天气又查了日程最后绕了一大圈才想起来的订会议室接口。问题就出在工具描述互相干扰查询类工具描述写得太宽泛模型分不清边界。我的解决方案是控制可用工具数量并对每个工具的描述进行“冲突测试”。具体来说我会列一批典型问题逐个测试模型是否能选对工具凡是两个工具描述有重叠的场景就通过限定词区分边界。比如“日程查询”和“日程创建”前者描述必须强调“只读”后者必须强调“需要确认会议时间后才可调用”。工具不是越多越好每个多出来的工具都会增加模型的选择难度。5.3 上下文爆炸观察结果不能被无限追加ReAct 循环每走一步消息列表里就多一段“模型思考 工具返回结果”。走到第八步时上下文里可能塞了几千甚至上万 token而真正有用的可能只是最后两个 Observation。上下文一长模型的注意力被稀释而且成本直线上升。我的处理方法是给循环加一个“压缩机制”只保留最近的 N 轮完整交互更早的内容交给一个总结步骤提炼成一行摘要放进上下文比如“已完成查询天气、查询日程结论明日有雨”。另外工具返回的原始大数据绝不直接塞进上下文先在代码里截断、清洗只把关键字段喂给模型。记住Observation 是给模型看的中间结果不是日志系统写得越精炼模型决策越准。5.4 Token 成本失控给每个任务算一笔账我接手的第一个生产 Agent 项目上线两周后账单翻了好几倍。排查后发现在一个简单查询任务上模型竟然自创了“反复确认”的风格查完天气又问用户“要不要顺便看湿度”用户说不用它还要再总结一遍。每一步都是钱。这件事逼我养成了一个习惯每个任务结算时统计步数和 token 消耗并给任务类型设定成本预算。除此之外模型的选择很关键——简单任务用便宜的小模型复杂任务才用推理更强的模型如果任务只是结构化工具调用大模型和中小模型的成功率差距没那么大但价格差距可能有几十倍。成本控制不是上线后做的事是架构设计阶段就该定的约束。最后说一个我自己坚持到现在的习惯开发 Agent 期间务必开 verbose 日志把每一步的 Thought、Action、Observation 全部打出来。这看起来笨拙但极好用——我排查过的几乎所有循环、误调、状态错乱问题都是靠这种“把大脑皮层打开看”的方式定位的。大模型 Agent 开发入门说到根上不是去追什么新概念而是把“能对话”变成“会做事”把“会做事”变成“做得稳”。别急着把架构搞复杂先跑通一个 200 行的最小循环看清每一步再一步步加记忆、加工具、加并发。这条路上没有太多玄学踏踏实实把每一步打印出来问题自然就现身了。
RELATED READING

延伸阅读

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