ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

大模型Agent实战:从零搭建最小可用智能体与工程化避坑

大模型Agent实战:从零搭建最小可用智能体与工程化避坑 说实话这两年“大模型Agent”这个概念的热度一直没降过。每刷几个技术社区就能看到有人问“Agent到底是什么”“这玩意儿怎么落地”也有人直接上手折腾说翻车翻得厉害。我自己是从去年年初开始接触Agent开发的从最早跟着教程写一个能答题的脚本到后来尝试做能调工具、能查资料、能多步完成任务的智能体踩了不少坑也总结了不少经验和教训——这篇就当作我对自己这段弯路的一次系统复盘顺便把大家比较容易卡住的地方讲明白希望能帮你把“入门”这件事真正落地成“能跑起来”。这篇内容主要是写给有Python基础、但对大模型和Agent不熟悉的开发者的。如果你之前只写过脚本、没碰过LLM的API也没关系我会从最基础的“Agent到底是什么”讲起一步步拆到工具调用、记忆管理、任务循环这类核心机制再讲框架选型和工程化避坑。全文的落脚点是“怎么自己动手写出一个最小可用Agent”而不是只讲概念。1. 先理清思路大模型Agent到底是什么1.1 别再把它当成一个Chatbot很多人一提到大模型Agent第一反应就是“这不就是个聊天机器人吗”。这句话对了一半但只对了一半。普通的Chatbot是你问一轮答一轮即使上下文再长它本质上还是“语言模型在做文本续写”。而Agent的关键差异在于它有“手”可以操作外部工具有“目标”可以自主拆解任务有“记忆”可以跨多轮对话保留有效信息。我习惯用一个比喻来理解大模型本身像一个“只会读书和写方案的实习生”知识面很广但不能动手执行任何实际操作。Agent是这样做的——你给这个实习生配了一双手工具调用给了一个工作台代码执行环境或API给了一本工作日志记忆再定了一套工作流程规划与循环。大模型负责想Agent负责把“想”变成“做”。所以你判断自己写的是不是一个Agent最简单的方式是看一个标准它有没有一个“循环”。用户发一个目标过来Agent自己思考要分成几步、每步需要什么资源、该调什么工具然后调完工具拿到反馈再判断下一步做什么直到任务完成或达到终止条件。这个过程在业界被称为“Agent Loop”。没有这个循环的就只能叫“带上下文的问答接口”。1.2 Agent的四个核心组件如果拆开来看一个能跑起来的大模型Agent通常由四部分组成大模型底座负责推理、理解、生成内容。它决定了Agent的“智力上限”。规划能力将复杂目标拆解成子任务决定下一步动作。这是“动脑”的部分常见做法的ReAct模式后面会展开。工具集合让Agent能连接外部世界例如搜索引擎、数据库、计算器、代码解释器、业务系统的API。记忆系统记录用户偏好、历史对话、中间结果分为短期记忆当前任务上下文和长期记忆跨会话的知识沉淀。这四块里面最容易动手的是“工具集合”和“规划循环”因为它们是工程化的部分有明确写法最考验能力的是“大模型底座”和“记忆系统”因为它们的配置和策略直接影响Agent会不会“变傻”。在实际项目里我见过的很多Agent跑飞基本不是模型不行而是规划和记忆这层没做好。1.3 三种开发路径怎么选面对“开发Agent”这个问题市面上有几种路径刚入门时很容易眼花缭乱第一种用成熟框架比如LangChain、LlamaIndex、AutoGen框架把循环、记忆、工具调用都封装好了你主要写工具和Prompt。第二种用低代码平台比如Coze、Dify这类可视化编排平台适合业务快速落地几乎不用写代码。第三种完全自研自己写循环直接跟模型API交互用Function Calling机制来触发工具。我个人的建议是入门阶段至少花时间把第三种做一遍哪怕最后不用上线也要亲手实现一次“大模型返回结构化工具指令→代码执行工具→结果喂回模型”的最小闭环。原因很简单——第三种的每一步链路你都亲眼见过之后再去看LangChain的源码会发现很多东西豁然开朗不觉得那是黑盒了。这篇文章的核心就是带着你把这个最小闭环完整走一遍。2. 环境准备与最小开发框架2.1 模型API怎么选做Agent开发不需要上来就自己部署大模型成本太高也没必要。直接用大模型API是最快的方式。关键是选哪家。从开发的友好程度来看首选支持OpenAI兼容接口的模型服务。你只需要用一个openai的Python SDK把base_url指到对应的服务地址就行切换供应商的成本极低。国内能用到的很多模型服务都兼容这个协议比如智谱、百炼、DeepSeek等——不同厂商的具体名称我这里不一一列举你去各自官方文档找“OpenAI兼容接口”就能看到。如果你本机有显卡想完全本地跑那可以考虑Ollama配合开源模型。这个方案的优点是没有接口费用而且离线可用缺点是模型能力相对弱一些做复杂的工具调用和长程规划时容易“脑子不够用”。我自己的经验是入门练手用在线API跑通流程后再去尝试本地模型你会发现很多问题其实是模型能力导致的而不是你的代码写错了。2.2 建立一个基础请求链路不管用哪家API我们先建立一个最小的调用链路。假设你已经拿到了API Key并安装了openai库from openai import OpenAI client OpenAI( api_keyyour-api-key, base_urlhttps://your-model-service.com/v1 ) response client.chat.completions.create( modelyour-model-name, messages[ {role: system, content: 你是一个能调用工具的智能助手。}, {role: user, content: 帮我查一下今天北京天气} ] ) print(response.choices[0].message.content)这段代码能跑通说明你的API链路没问题。但要注意这只是“聊天”还不是Agent。Agent和这个脚本的区别在于模型返回的不是直接给你看的“答案”而可能是一个“需要执行什么工具”的指令。这就进入了Function Calling的领地。2.3 理解Function Calling是Agent的命脉Function Calling函数调用是大模型Agent开发里最核心的机制。说白了它就是让模型在对话过程中不只是输出自然语言还可以输出一个结构化的“调用请求”告诉系统“你现在需要调用哪个工具参数是什么”。举个例子你在系统里定义了一个工具get_weather(city)。当用户问“北京的天气”时模型内部可能并不会直接说出答案而是输出这样一段JSON{ name: get_weather, arguments: {\city\: \北京\} }你的代码收到这段JSON后实际执行get_weather(北京)拿到真实数据再把这个数据作为一条工具消息返回给模型模型最后整合成自然语言答复用户。关键点来了Function Calling的目标不是让模型真的去“调函数”而是让模型学会在合适的时候“表达调用函数的意图”并生成合法的参数。真正执行函数的是你的代码。所以你要做的核心事情有两件一是把“工具清单”用模型能理解的格式描述出来二是正确解析模型返回的调用指令并执行。这两件事做扎实了Agent就是水到渠成的事。3. 从零写一个最小可用Agent3.1 先定义好你的工具假设我们要写一个能查天气、算数学的小Agent。这里我们定义两个极简工具用来演示完整链路。import json import random def get_weather(city: str) - str: 模拟查询天气返回城市和天气情况 weather_list [晴朗, 多云, 小雨, 大风] weather random.choice(weather_list) return json.dumps({city: city, weather: weather, temperature: random.randint(5, 30)}) def calculator(expression: str) - str: 计算表达式例如 1 2 * 3 try: result eval(expression) return str(result) except Exception as e: return f计算失败: {str(e)}这两段函数的逻辑本身很简单但在Agent体系里“函数实现”只是其中一半另一半是“函数描述”。模型不读你的Python源码只读你传给它的工具描述。描述写得不好模型就不知道该在什么时候调用、该怎么传参。所以要像下面这样给模型描述工具tools [ { type: function, function: { name: get_weather, description: 查询指定城市的实时天气情况, parameters: { type: object, properties: { city: { type: string, description: 城市名例如北京、上海 } }, required: [city] } } }, { type: function, function: { name: calculator, description: 执行数学计算支持四则运算, parameters: { type: object, properties: { expression: { type: string, description: 数学表达式例如 3 4 * 2 } }, required: [expression] } } } ]这里有个容易忽略的细节description写得好不好直接影响模型调用工具的准确率。比如“查询指定城市的实时天气情况”和“城市天气工具”的差距在复杂场景下会被明显放大。写描述的时候把自己想象成一个只会按说明书操作的新员工说明越明确出错的概率越低。3.2 核心循环怎么写工具定义好之后重头戏来了。我们希望Agent能自主决定“要不要调工具”“调哪个”“调完再怎么办”这需要一个循环逻辑。我直接给一个最小可用的循环代码# 假设已有 client、messages、tools def run_agent(question): messages [ {role: system, content: 你是一个智能助手如果需要工具请使用工具获取真实信息。}, {role: user, content: question} ] max_steps 5 # 防止死循环 for step in range(max_steps): response client.chat.completions.create( modelyour-model-name, messagesmessages, toolstools, tool_choiceauto ) message response.choices[0].message # 判断模型是否要求调用工具 if message.tool_calls: messages.append({ role: assistant, content: message.content, tool_calls: [ { id: tool_call.id, type: function, function: { name: tool_call.function.name, arguments: tool_call.function.arguments } } for tool_call in message.tool_calls ] }) # 逐个执行工具调用 for tool_call in message.tool_calls: tool_name tool_call.function.name args json.loads(tool_call.function.arguments) if tool_name get_weather: result get_weather(**args) elif tool_name calculator: result calculator(**args) else: result f未知工具: {tool_name} # 把工具执行结果追加到消息里 messages.append({ role: tool, tool_call_id: tool_call.id, content: result }) else: # 模型不再调用工具返回最终答案 return message.content return 达到最大执行步数任务结束。 print(run_agent(北京现在什么天气顺便算一下 15 * 8 等于多少))这段代码虽然简单但它已经完整包含了一个Agent循环的关键元素模型生成工具指令、代码执行工具、把执行结果返回模型、模型继续决策、直到输出最终结果。建议你第一次写完就跑一下观察消息列表的变化你会清楚地看到“工具调用不是模型在执行而是你的代码在执行”这个过程。有个细节值得留意tool_choice的参数可以设置成auto、none或者required。auto表示让模型自主判断要不要调工具required强制模型这次必须调工具none禁止调工具。调试的时候如果发现模型明明该调用工具却不调用可以先强制设为required看参数是否正确再改回auto排查Prompt影响。3.3 记忆管理Agent有没有“短期记忆”的问题很多初学者把Agent跑通之后第一个感觉是“它怎么忘了刚才干过啥”。这就是上下文管理的问题。大模型的API是无状态的每次请求都要把完整对话历史传给模型。所以记忆管理的本质是你要在messages数组中维护好“该给模型看什么”。最简单的记忆就是滑动窗口MAX_HISTORY 10 def trim_messages(messages): if len(messages) MAX_HISTORY: # 保留 system 消息去掉最旧的历史 system_msg messages[0] history messages[1:] return [system_msg] history[-MAX_HISTORY:] return messages这种方式实现快但缺点也很明显一旦超出窗口早期信息就被丢光了Agent对用户说过的话“失忆”。进阶的办法是摘要记忆——每次对话超过一定长度让模型把历史内容压缩成一段摘要当作一条消息放在前面。更复杂的是向量记忆把历史转成embedding存进向量库关键时刻检索相关的记忆片段进来。入门阶段我建议先不要折腾向量库先用“滑动窗口摘要”组合大多数日常场景已经够用。踢一个我在实际项目里踩过的坑工具执行结果也会占据大量上下文。如果工具返回了一大段JSON你原封不动塞回messages几轮对话之后上下文就爆了。正确的做法是在工具返回之前先做一次裁剪或摘要只留关键信息。比如查天气的接口返回了几十KB的原始字段你在返回给模型之前就要格式化成一个简短文本。3.4 从单一工具到多步任务前面那个例子虽然能解决“查天气加算数”但它还不是真正的多步任务规划。在实际场景里用户的目标通常更复杂比如“帮我调研一下最新的AI论文并总结出5个方向”。这种任务需要Agent自己规划搜索→获取内容→阅读摘要→总结。一种常见实现方法是让模型在回答过程中使用一个“plan”字段先列出步骤再逐步执行。另一种是依赖模型自己在对话中按ReAct方式推进——每一步都思考“当前状态是什么、下一步该做什么”然后调用对应工具观察完结果再继续。模型通过System Prompt的引导来完成这种“思考-行动-观察”的循环所以System Prompt很多时候比你的工具代码还要命。我见过不少人在这个阶段过早引入复杂的“任务分解框架”。实践下来如果你的工具层做得足够简洁、Prompt引导清晰即使不引入额外框架模型也能比较好地完成串行多步任务。先把串行跑稳再考虑并行调用多个工具的场景这样进阶路径会更平缓。4. 工程化落地从能跑变成能上线4.1 上下文危机与Token控制实用策略我接手过不少刚开始做Agent的团队第一个线上事故基本都长一个样用户聊了几轮后调用模型接口直接报“上下文过长”错误。为什么因为每轮对话你都在无脑追加消息LLM的上下文窗口是有限的而且越接近上限响应时间越长、成本越贵、效果越差。在实际开发中我习惯用这套组合策略来管理上下文按消息角色分层system提示语尽量精简工具返回结果做截断比如只保留前200个字符或关键字段用户历史太久远比如超过20轮就清掉。维护一个“核心事实”区不管历史怎么裁剪用户偏好的关键信息比如“我叫张小明”“我要简洁的回答”要单独提取出来持久化保存每轮请求都放在system里。将大工具结果降维工具返回的是结构化JSON时先抽字段再转自然语言短句最后才喂给模型。Token的计算也不难估中文大概一个字约1到2个token英文一个词约1到2个token。你可以先打开官方Tokenizer工具实测一下心里有个数。我一般给自己定的规则是输入控制在上下文窗口的三分之二以内剩余空间留给答案生成和工具调用参数。4.2 工具调用失败的兜底链路工具调用真的不是每次都成功的。我在生产环境见过最多的问题是模型生成了错误参数、工具超时、工具直接报错。初学者最常犯的错是“工具挂了Agent就死机”这是最典型的工程化缺陷。正确的做法是做一套兜底链路第一层参数校验在你执行工具之前先校验模型生成的参数是否合法缺了就补默认值类型不对就转一下。第二层执行失败反馈工具抛异常时不要把错误信息吞掉要把异常构造一个文本结果返回给模型让模型根据错误调整比如改成调用其他工具或换一种说法。第三层重试机制一些临时性错误超时、限流可以自动重试两三次但要有上限。第四层人工兜底如果Agent连续几次都失败或者模型自己说“我做不到”那就直接返回“抱歉暂时无法处理已转人工”。这里补一个我总结过的循环终止条件在你自己的Agent里务必写清楚达到最大步骤数模型不再发起工具调用工具执行返回明确错误且重试无效模型输出表示任务结束。少一个条件你的Agent就可能在某个场景里无限循环烧钱。4.3 并发、成本与简单评测聊到上线绕不开并发和成本。几个核心点调用频率限制模型API基本都有QPS限制尤其是免费档接口。最简单的方案是在Agent内部加一个轻量级的请求队列或令牌桶控制每秒请求数。缓存相同或相似的问题不要重复调用模型。尤其是工具返回结果如果数据源变化不频繁完全可以在系统里缓存一份命中就直接返回成本立省。批量与异步如果要做并发先想清楚你的业务是不是真的有并发需求。小团队入门阶段用线程池就够了from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers4) as executor: results list(executor.map(run_agent, questions))最后说评测。Agent跟传统程序不一样同样的输入可能每次输出都不一样。所以我在维护Agent的时候会固定一套评测问题集里面覆盖核心工具调用、多轮记忆、拒绝策略等场景。每次改动Prompt或工具定义就把这套问题集跑一遍观察正确率有没有下降。这是工程化阶段性价比最高的“体检方案”。5. 高频踩坑与实战排查5.1 模型就是不调用工具怎么排查如果你发现模型面对明明应该调用工具的问题却输出一堆文字而不是发起调用请按顺序排查这四个方面工具描述是否清晰、模型本身是否支持Function Calling、temperature是否设置过高建议先调到0减少随机性、example是否缺失。很多模型在不确定的场景不会主动调工具你可以在System Prompt里直接写“当用户询问天气时你必须调用get_weather工具”立竿见影。5.2 上下文越长Agent反而越蠢这几乎是所有人都会经历的阶段。把完整的历史全都塞进去模型反而忘了更早的关键信息或者被一堆无关内容干扰。解决方向是“少而精”与其保留20条消息不如把关键信息抽取出来拼成一段简报。工具调用历史也要清理中间步骤留下最终结论就好。核心原则是上下文不是“越多越好”而是“越相关越好”。5.3 Agent产生幻觉与越权调用幻觉问题在非大模型领域也有但因为Agent会真实调用工具危险系数更高。比如模型把参数填错、调用了一个不该调用的函数。工程上要做的安全边界有工具白名单只允许调用预先定义好的工具参数白名单或枚举校验比如城市名、ID范围敏感词拦截在工具执行前做一次校验权限分级不同的用户Token能看到不同工具集日志全审计谁在什么时候调了哪个工具全部留痕。想清楚一个原则模型只能被允许做“即使做错也不会出大乱子”的事越权操作必须从系统层面拦截不能指望模型永远正确。5.4 常用问题速查表现象可能原因排查方向模型不调用工具Prompt未约束、模型不支持检查模型文档检查工具描述强制tool_choice试试工具参数总是错描述不全、参数结构过于复杂简化参数结构给枚举值和默认值上下文很快爆掉工具结果未裁剪、历史未清理给工具返回加截断逻辑启用滑动窗口Agent陷入死循环缺少终止条件、工具反复失败硬编码最大循环次数异常时返回最终兜底回答质量不稳定temperature偏高、模型不适合该任务调低temperature换更强模型成本快速上涨缓存缺失、每轮都全量传历史加缓存做Token预算限制6. 写在最后的实操心得按我个人的经验大模型Agent开发入门最忌讳的就是一上来就追框架、追新概念。框架能帮你节省时间但也容易让你迷失在抽象层里面。我自己更推荐“先徒手造轮子再造熟了再引入轮子”这种顺序写一个不带框架的最小Agent循环跑通亲手观察每一步消息是长什么样的、工具结果是怎么回流的。等你理解了这一整条链路再回头去用LangChain之类的框架会发现自己不再怕它——因为你已经知道底层在做什么了。还有一个小技巧分享给你调试Agent的时候别只看最后输出的答案一定要把每轮循环的messages、tool_calls、工具返回都打印出来甚至写成一个可视化的日志面板。我见过太多人因为“看不到过程”只能在结果里盲目调Prompt效率极低。把过程可视化之后很多问题一眼就能定位——是模型判断错了还是工具返回有问题还是历史丢了。把这条基本功练扎实你的Agent开发之路会顺畅很多。
返回列表