ARTICLE DETAIL

资讯详情

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

Agent开发实战:从LangGraph到Function Calling的智能体搭建全指南

Agent开发实战:从LangGraph到Function Calling的智能体搭建全指南 做Agent开发三个月我最大的感受是大模型本身就是个“嘴上功夫”很强的选手真正让它从聊天工具变成能独立干活的“数字员工”中间隔着的就是Agent这一层。这篇文章不聊空泛的概念只讲我们团队内部代号“服范-九添菜菜”的Agent智能体开发实战——从需求拆解、技术选型、核心代码到线上排查把踩过的坑和最后沉淀下来的可复用方案一次讲清楚。如果你是刚接触大模型Agent开发或者正在规划自己的智能体项目按这条路线走能少走不少弯路。先说结论Agent开发难的不是调大模型API而是把模型的“自由发挥”约束到业务闭环里。你要处理工具调用、状态流转、记忆管理、异常兜底每一环都决定这个智能体是“玩具”还是“生产力”。1. 项目概述与需求拆解先想清楚Agent要解决什么问题1.1 “服范-九添菜菜”到底是什么项目启动前老板给的需求只有一句话“做一个能让店长用自然语言处理日常事务的助手。”我们把它拆成了几个具体场景查门店当日营业额、生成促销文案、回复顾客差评、查看库存预警、调整员工排班。团队内部把这类任务定义为“服务规范化”项目代号“服范”而智能体本身的昵称是“九添菜菜”方便内部沟通时指代。这里有一个关键判断这些任务看似分散但有个共同特征——都需要大模型“理解意图、调用系统、组织输出”。直接写死程序流程只能处理单一问题单独靠Prompt问答又没法触碰真实业务数据。所以我们必须做一个能自己决定“下一步干什么”的中间层也就是Agent。1.2 为什么必须做Agent而不是堆Prompt第一版方案我们试过“超级提示词工具结果拼接”效果很勉强。原因很简单业务任务的复杂度不是线性的。比如“帮我看一下上周末的营业额如果比前一周低就写一条分析说明”——这句话背后包含条件判断、数据检索、文本生成三个步骤。如果全部交给一个Prompt承担模型的注意力会被大量工具说明稀释输出格式也不稳定。Agent的价值在于把决策过程显式化模型不再直接输出最终答案而是先输出“下一步动作”调用哪个工具传什么参数拿到工具结果后再继续推理直到可以给出最终答复。这种“规划-执行-观察-再规划”的循环才真正发挥了大模型的理解能力同时让每一个环节都可观测、可打断、可追溯。1.3 核心能力清单落地前我们列了四类必备能力后面所有开发都围绕它们展开意图分解把一句自然语言拆成可执行的子任务工具调用稳定生成结构化参数正确触发业务接口记忆管理能记住用户身份、最近上下文和长期偏好异常处理工具报错、模型乱答、上下文超长时都能兜住。顺便提醒一句如果你们的任务只是“从文档里找答案”那用RAG方案就好不用硬套Agent。Agent是有额外成本的选择只有当任务真的需要多步决策、多工具协同时才值得。2. 技术选型与架构设计先把地基打对2.1 模型层云端API还是本地部署项目初期我们就面临模型选择问题。云端API比如DeepSeek、通义千问的接口优势是效果稳定、不用管GPU按调用量付费本地部署通过Ollama这类工具跑开源模型优势是数据不出内网、长上下文成本低。我们最后的取舍是“双轨并行”开发阶段全部用云端API追求快速迭代生产环境则根据客户要求决定是否切换到本地模型。这里有个经验不要在一开始就纠结“哪个模型最强”先固定一个效果及格、文档完善的模型跑通全流程Agent架构才是研发重点。等Agent框架稳定了再替换底座模型成本低得多。需要特别关注的一个参数是上下文长度。Agent的每次循环都会把历史工具调用结果重新塞给模型上下文消耗比普通问答快很多。我当时测下来一个中等复杂度的任务单次跑完要吃掉7000到12000个token所以模型至少要有32K上下文否则后续做记忆和长对话会很痛苦。2.2 框架层LangGraph还是自建状态机市面上的Agent框架很多但我们没有一上来就套全家桶而是先画了一张状态流转图明确每一轮循环要经历“读状态、选动作、执行工具、写观察、判断终止”五个环节。在此基础上团队内部对比了两种路线自建循环用几十行代码写一个while循环逻辑完全可控但错误处理、断点续跑、并行分支都要自己实现LangGraph用图结构定义节点和边把循环建模成状态机支持条件分支、人工介入、流式输出代码可读性高。我们最终选了LangGraph但不是因为它“新潮”而是它把“状态即全局上下文”这个理念落得很干净。每个节点函数接收当前状态返回更新后的状态调试时可以打印每步的state变化这在定位“模型乱调工具”的问题时极其好用。如果你团队人少、任务逻辑简单自建循环完全够如果要做复杂的条件分支或多人协作别重复造轮子。2.3 工具层怎么让Agent“长出手脚”Agent的“手脚”就是一系列函数但普通函数不能直接给大模型用。我们需要维护一份“工具清单”用JSON Schema描述每个函数的名称、参数、返回值含义和注意事项。模型根据这份清单生成调用请求程序解析后执行真实函数。我这里踩过一个明显的坑工具描述用词太模糊模型就老是误解。比如“get_sales”这个描述如果只写“获取销售数据”模型就不知道能不能接受“门店编号”、“起止日期”。后来我们把描述改成“获取指定门店在指定日期范围内的销售总额参数store_id为门店唯一编号start_date和end_date格式为YYYY-MM-DD返回金额单位是元”调用准确率立马上来了从71%涨到88%。工具描述不是给人看的是给模型看的越精确越好。2.4 数据层记忆和状态存哪里Agent的临时状态可以放在内存但一重启就没了。我们为生产环境设计了存储策略聊天会话状态用Redis保存业务数据继续留在原本的MySQL里而长期用户偏好比如“这家店长偏好简洁回复”单独存到一张PostgreSQL表里。这套设计的好处是“临时记忆跟长期记忆分离”清理上下文时不会误删重要数据。架构上还有一个容易被忽略的点工具执行最好通过内部的HTTP服务比如FastAPI接口而不是直接连数据库。这样Agent层与业务系统解耦后续加权限控制、流控、审计都方便。也方便在工具失败时重试不至于因为临时网络抖动让整个Agent崩溃。3. 核心模块实现从零搭一个可用的Agent3.1 Function Calling与工具定义现在主流大模型都支持Function Calling也就是原生函数调用格式。下面是我们定义的一个工具示例用Pydantic做参数校验既给模型以明确的结构约束又能在执行前拦截非法参数from pydantic import BaseModel class SalesQueryParams(BaseModel): store_id: str start_date: str end_date: str def query_sales(params: dict) - str: # 实际查询业务接口返回JSON字符串 # 失败则抛出 ToolExecutionError return 2024-11-01 营业额: 23000元; 2024-11-02 营业额: 19800元给模型传工具清单时需要这样描述{ type: function, function: { name: query_sales, description: 获取指定门店指定日期范围的营业额日期格式YYYY-MM-DD结果单位元, parameters: { type: object, properties: { store_id: {type: string, description: 门店唯一编号}, start_date: {type: string}, end_date: {type: string} }, required: [store_id, start_date, end_date] } } }这里最关键的一点是模型返回的参数是JSON字符串不能直接信。务必在做完Pydantic校验之后再交给业务函数。我见过有同学直接把模型输出的参数拼SQL结果字段名错了一个字母查了一个下午才发现是模型“发明”了不存在的字段。3.2 ReAct循环与状态转移假如不用框架一个最简Agent循环长这样def run_agent(user_query: str, max_steps: int 5): messages [{role: user, content: user_query}] for step in range(max_steps): response llm_with_tools(messages) if response.tool_calls: for tool_call in response.tool_calls: result execute_tool(tool_call) messages.append(tool_message(tool_call, result)) else: return response.content raise MaxStepExceededError(Agent steps exceeded limit)这段逻辑虽然简陋但说明了一个核心思想模型每次输出都有两条路——要么调用工具要么输出最终答案。当它连续多次调用工具时我们就把每次的调用参数和工具返回结果都追加到消息列表里让模型“看见”自己刚才做了什么从而修正下一步决策。用LangGraph后同样的逻辑就变成了节点和边一个节点叫“agent”负责让模型决策一个节点叫“tools”负责执行工具两个节点之间通过条件边连接。代码量差不多但多了断点恢复和人为干预能力。比如我们规定当Agent陷入同一个工具调用超过3次时自动跳到一个“询问用户”节点把问题抛回去而不是继续烧token。3.3 记忆管理会话级与用户级对话记忆有三级层级不同处理方式也完全不同。会话级记忆指当前这个连续对话内模型回复和工具执行产生的临时信息。这部分直接放在messages数组里就行但要注意窗口大小超长时用摘要压缩用户级记忆指用户身份、偏好、近期的目标比如门店店长关注的重点指标。这类信息在每次对话开始时构造成“系统提示词”注入世界知识由RAG或者向量数据库提供不占用对话窗口。我们生产上踩过最大的坑是把几十轮的工具调用记录全部堆积在消息里到后面模型已经分不清哪条数据对应哪次操作了。后来换成“摘要最近5轮原始消息”的策略每次对话结束时用大模型把当前对话压缩成200字以内的摘要下次对话只带摘要和最近5轮消息。效果好了很多单次任务token消耗下降了30%以上。3.4 提示词工程在Agent里的特殊位置Agent的提示词跟普通问答Prompt有一个很大区别你的主要任务是让模型“学会调用工具”而不是“学会回答问题”。所以我们的System Prompt里写得很死板你是门店运营助手收到用户问题时先判断是否需要工具。如果需要必须返回一个tool_call不要自己假设数据。所有工具的结果都是文本你必须基于结果做分析不能编造结果中不存在的数值。这里“不要自己假设数据”是我反复强调的一句话。因为模型非常喜欢在被问到“上周销量怎么样”时直接编一个“上周销量下降5%”而不是老老实实去查工具。通过提示词约束加一些示例能把幻觉率压到可接受的范围。另一个经验是给Agent“角色”和“行为准则”要分开。角色只影响语言风格行为准则才影响决策流程。千万别把“你是一个热情的门店助手”和“必须调用工具才能回答数据类问题”写在同一段里模型会倾向于“热情”然后忘记调用工具。4. 实操复盘从零跑通一个智能体4.1 环境准备与最小可用版本我们项目实际用了Python 3.11 LangGraph 0.2版本依赖很少但这里有个建议先不要追求完整功能把最小闭环跑通再说。我当时做的第一版就一个模型节点加一个查询销售工具能回答“今日营业额多少”就算成功。具体步骤安装依赖、配置API密钥、复制一份最简Agent示例、定义1个工具、跑通一次“用户提问-模型调用工具-返回结果-生成答案”。整个流程最好控制在半天内。如果一天还没跑通问题多半出在API参数格式上比如工具调用的返回消息格式不符合模型要求而不是Agent框架本身。4.2 接真实业务数据最小闭环跑通后就要面对真实业务数据了。我们的建议是先接只读接口再做写操作。比如先从“查营业额”开始因为在生产环境中查询类工具的容错空间大写操作比如“把库存调低100件”一旦错了影响就严重了。接真实数据时还要设计工具返回结果的“摘要层”。业务系统的原始返回经常是很长的JSON直接塞给模型既费token又容易干扰注意力。我们每一类工具都配置了一个“结果格式化函数”把JSON转成一句话例如“2024-11-01至2024-11-03营业额合计62800元日均20933元较上周同期下降3.2%”。模型拿到这种干净的信息回答质量和稳定性都高很多。4.3 从单轮到多轮让Agent记住刚才说过的话单轮对话跑通后就要面对多轮。比如用户先说“查一下A门店的营业额”再说“环比上周呢”这里“环比上周”的意图要关联到上一轮提到的“A门店”和“营业额”。我们的处理方式是引入“对话摘要”和“实体记忆”上一轮解析出的门店ID、日期范围会被提取为结构化字段沉淀到状态对象里。下一轮模型看不到原始用户消息时就通过注入这些字段理解上下文。用LangGraph实现很简单新增一个“记忆节点”在每轮结束时把重要实体更新进state。但注意记忆节点必须由代码控制不能指望模型每次自动记住。4.4 用评测集卡住每次改动Agent应用特别容易“改一处坏一处”。今天调了下Prompt工具调用准确率下降10%都没人知道。所以从第一天就要建评测集至少准备50条典型的用户问题覆盖单工具调用、多工具串行、条件分支、模糊表达、错误数据五种类型。每条评测项都标注了期望动作序列和答案关键点。每次改完代码自动跑一遍评测集计算“动作准确率”和“答案命中率”。我们建立评测集之后上线质量有了质的提升很多回归问题在合并前就被挡下来了。这项工作没有多高大上就是拿Excel列表格和人工标注但作用远大于写一堆没用的单元测试。5. 高频问题与排查指南Agent开发里最常见的坑5.1 模型输出不按JSON来这是新手最常遇到的问题。明明配置了Function Calling模型有时候还是返回普通文本或者参数名凭空多了一个空格或者日期格式变成了“2024/11/05”。排查思路分两步先查看模型返回的原始报文看是否走了“函数调用”分支再看你传给模型的工具Schema是否严格符合JSON Schema规范。我们遇到的80%情况是后者比如required数组里写了参数A但properties里没定义参数A。遇到这类问题用Pydantic重写工具Schema再做一次严格校验就能根治。5.2 Agent绕圈圈反复调同一个工具生产环境中Agent反复调用同一个工具是常态。比如用户问“营业额为什么下降了”Agent先查了销售额然后想查客流量又回头再查一次销售额白白浪费好几轮token。我们做了三道防线第一在System Prompt里明确“如果某个工具已经调用过并且数据足够回答问题不要重复调用”第二在代码层记录工具调用历史一旦发现同一个工具同一个参数被连调3次就中断并强制让模型进入“总结”模式第三设置全局步骤上限默认8步超过就自动停止并向用户展示已有的中间结果。千万别省这口气线上跑了你会发现这类循环是成本大头。5.3 工具调用失败与重试机制业务接口不会总是成功。数据库超时、上游服务报错、参数不合法都是家常便饭。工具失败后如果直接把错误信息堆给模型模型容易慌张开始胡编乱造。正确做法是分三类处理参数非法不重试直接告诉模型“参数错误请修改后重试”业务无数据不重试返回“该日期无数据请确认查询条件”系统超时自动重试两次重试间隔1秒和3秒仍失败则返回“系统暂时繁忙”。这个策略写进代码层而不是靠模型自己判断。实践下来工具执行成功率从87%提升到96%用户体验改善非常明显。5.4 上下文越来越长效果越来越差前文提到摘要压缩这里再给一组实际数据。不做任何压缩时30轮对话后上下文约38000 token模型已经开始遗忘最初的用户需求。引入“历史摘要最近5轮”后同样的对话上下文降到11000 token且关键实体通过结构化记忆补充回答准确率反而提升了6个百分点。实现时注意一点摘要合并的频率要控制好没必要每轮都做。我们设置的是每10轮或上下文长度超过12000 token时触发一次摘要。每次摘要调用相当于一次新的模型请求太频繁了反而增加成本和时延。5.5 成本控制与响应延迟Agent的token消耗是普通问答的3到5倍尤其是深度推理的时候。我们用三个手段控成本能用小模型做分类的任务绝不用大模型比如“是否需要调用工具”这个判断可以让快模型做工具调用时设置“early stop”模型一旦生成了合格的最终答复立即掐断生成不追加上“以上是全部内容”这类废话对Top-P和温度做限制不要用默认值温度设为0Top-P设为0.8输出更稳定也避免模型无限发散。延迟方面如果用户等10秒以上体验就很差了。我们最终接入了流式输出模型第一句话马上显示在界面上后续通过流式逐步补充感知延迟降到2秒以内实际总时长还是6秒多但体感完全不同。6. 上线前必须做的几件事别让Agent裸奔6.1 全链路日志与轨迹回放Agent应用最大的问题是不确定性同一个问题今天答对明天答错。因此从第一天开始我们就把每轮Agent的完整轨迹记录下来包括用户问题、模型原始输出、工具调用参数、工具返回结果、最终答案、耗时和token数。最好是把这些日志存成JSON Lines格式单独一个集合方便按用户ID和时间范围查询。线上出问题时直接翻日志回放“Agent当时是怎么想的”比瞎猜高效一百倍。我们也基于这些日志做了离线标注不断喂给评测集形成正向循环。6.2 兜底策略与人工接管无论Agent做得再好也必须有人工兜底。我们的策略分三层第一层低置信度时不硬答直接说“这个问题我还没法确认正在为你转人工”第二层管理后台提供“人工接管”按钮客服可以把对话切换到人工第三层系统检测到连续3次回答跟用户问题不太相关时自动触发转人工流程。转人工时要带上Agent的“思考轨迹”摘要让客服快速了解用户诉求和已查到的信息否则客服还得从头问一遍用户也会不耐烦。6.3 灰度发布与反馈回收Agent上线不能一把梭。我们的做法是先让5%流量进入新模型或新Prompt运行观察一个周期同时给用户界面上加“点赞/点踩”按钮把反馈直接回收到评测数据集。只有当新版本在评测集上的得分高于旧版本且线上反馈率没有恶化时才逐步放量到50%、100%。这里还有一个容易忽略的点随着时间推移用户会不断提出评测集里没有覆盖的新句式、新场景。所以评测集不是一次建完就结束了我们每周都会从线上日志里挑20条新case补进去。这个工作看起来琐碎但长期下来它就是Agent质量的护城河。根据我个人实操的经验Agent开发有一个“六八二”定律值得记住六成时间在调工具和状态流转两成时间在调模型输出格式真正花在“算法”上的其实不到两成。大多数失败项目问题不在模型能力不够而在工程化细节没到位。如果你正在做自己的Agent先把工具描述写精确、把状态流转画清楚、把评测集跑起来这三件事做完项目就不会跑偏。等这几个地基稳了再去折腾更花哨的规划能力来得及。
返回列表