文章目录
- 前言
- 1 环境基座:先把项目搭得像个正经工程
- 1.1 别再死磕pip了,uv才是提速神器
- 1.2 Python版本钉死3.11,少走半年弯路
- 1.3 密钥别写代码里,安全组找你喝茶别喊冤
- 2 模型接入:换供应商就改一行字的快乐
- 2.1 一行代码通吃各家模型
- 2.2 Groq用来做开发,效率直接翻倍
- 3 结构化输出:别再用正则抠JSON了
- 3.1 Pydantic:边界数据的守门员
- 3.2 TypedDict:内部传参的轻量选择
- 3.3 dataclass:带默认值的中间派
- 3.4 include_raw:调试和计费必备
- 4 消息模型:别再把所有内容塞一个字符串里
- 4.1 四种消息,各司其职
- 4.2 消息列表才是多轮对话的正确姿势
- 5 工具调用:让模型学会自己动手查资料
- 5.1 一个装饰器,把Python函数变成模型工具
- 5.2 工具粒度别太大,一个工具干一件事
- 5.3 配对机制:多工具并发也不会串台
- 6 LangGraph三要素:状态、节点、边
- 6.1 State:全图共享的上下文
- 6.2 Node:干活的处理单元
- 6.3 Edge:决定下一步去哪
- 7 上手第一个图:货币转换器
- 7.1 三步搭起最小图
- 7.2 可视化:一眼看明白图长啥样
- 8 条件边:让图自己做决策
- 8.1 一个路由函数搞定动态分支
- 8.2 分支独立,好维护好测试
- 9 ReAct代理:思考-行动循环,开箱即用
- 9.1 不用手写循环,预置组件直接用
- 9.2 一个例子看懂整个流程
- 10 记忆与多会话:每个用户都有独立上下文
- 10.1 一行代码开启记忆
- 10.2 thread_id:多用户隔离的关键
- 10.3 内存不够用?换数据库就行
- 11 从demo到生产:工程落地清单
- 11.1 工具层加固:超时、重试、鉴权
- 11.2 可观测性:别瞎调参,先看数据
- 11.3 兜底机制:别让用户看到“我不会”
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
前言
不知道大家有没有这种经历:刚接触大模型开发的时候,一行model.invoke就能跑通,觉得Agent也不过如此。结果需求一加,要记忆、要工具调用、要分支判断,代码写着写着就缠成了一团耳机线,改一个地方崩三个地方。
别慌,今天咱们就从地基到封顶,把用LangGraph做Agent的完整工程逻辑掰碎了讲,看完至少能帮你少踩半个月的坑。
1 环境基座:先把项目搭得像个正经工程
很多人一上来就急着写业务代码,环境全靠手动凑,最后同事拉你代码跑不起来,你俩对着报错面面相觑。地基不稳,后面全是雷。
1.1 别再死磕pip了,uv才是提速神器
说真的,我以前用pip装依赖,点完安装就能起身去接杯水,回来还在解析依赖冲突,运气不好直接给你报一屏红。
uv这东西就不一样了,Rust写的,速度快到你以为自己没点安装。初始化项目、锁版本、装依赖一条龙,一个uv.lock文件就能保证所有人环境一模一样,再也不会出现“我这能跑啊”的世界名场面。
1.2 Python版本钉死3.11,少走半年弯路
别总追新用3.12、3.13,爽是爽了,装依赖的时候就知道苦了。很多AI库的预编译包还没跟上新版本,装的时候本地编译半天,最后还不一定能成。
3.11就不一样,兼容性拉满,主流库全支持,部署也省心。把版本号钉死在pyproject.toml里,连Python解释器都给你统一了,环境一致性直接拉满。
1.3 密钥别写代码里,安全组找你喝茶别喊冤
我真见过不少新人,图省事把API key直接写在代码开头,转头就提交到公开仓库,第二天公司安全组的消息就弹过来了。
正经做法就是.env文件加python-dotenv,密钥全扔环境变量里,.env直接加到.gitignore,代码里只管读环境变量,干净又安全。以后换key、换环境,改配置文件就行,业务代码一行都不用动。
2 模型接入:换供应商就改一行字的快乐
以前接不同厂商的模型,得分别import不同的类,参数还不一样,换个模型跟重构一遍似的。现在有了init_chat_model,这事就简单得离谱。
2.1 一行代码通吃各家模型
就写个provider:model的字符串,比如groq:qwen-3-family,剩下的LangChain全给你搞定。底层SDK、鉴权方式全给你封装好了,业务代码根本不用关心背后是哪家的模型。
以后想做A/B测试、想换便宜的模型跑测试,改个字符串就行,上层逻辑纹丝不动。这才叫抽象的意义,不然每次换模型都改半天,早累死了。
2.2 Groq用来做开发,效率直接翻倍
开发阶段别死磕贵的模型,等半天出个结果,调试效率低到离谱。Groq这种低延迟的推理服务就很合适,首token快得离谱,跑demo、调流程特别顺手。
等流程全跑通了,再切到主力模型优化效果,成本和效率两头都占了。做工程的,得学会把钱花在刀刃上。
3 结构化输出:别再用正则抠JSON了
说个扎心的:很多人做Agent,一半时间在写prompt让模型输出JSON,另一半时间在写正则修复模型输出的畸形JSON。头发就这么掉没的。
结构化输出就是干这个的:给模型定好契约,它按格式填,程序直接拿对象用,省心得多。
3.1 Pydantic:边界数据的守门员
最常用的就是Pydantic,定义个BaseModel,每个字段加个description,不仅能校验类型,还能把字段说明一起塞给模型,相当于给模型画好了填空格。
别觉得description是写给人看的注释,你写得越清楚,模型填错的概率越低。比如日期字段你写清楚“ISO8601格式”,它就不会给你整出“昨天”“上周”这种幺蛾子。
外面进来的数据、模型吐出来的数据,用Pydantic卡一道,脏数据根本流不到下游,省了无数排查时间。
3.2 TypedDict:内部传参的轻量选择
要是数据只在内部节点之间传,已经确认过是干净的,就没必要每次都跑一遍Pydantic校验。TypedDict就够了,零运行时开销,还能有类型提示。
配合Annotated把字段说明挂上,一样能给模型当schema用,轻量又好用。毕竟内部系统之间,没必要每次都过安检,浪费性能。
3.3 dataclass:带默认值的中间派
想要点面向对象的感觉,又不想引入Pydantic的重量,dataclass就很合适。标准库自带,能加默认值、能写方法,LangChain也能直接识别成schema。
写demo、做原型的时候用它最快,等后续要做强校验了,再迁到Pydantic也不麻烦。
3.4 include_raw:调试和计费必备
默认只返回解析好的对象,干净是干净,但你想知道花了多少token、模型原始返回是什么,就没辙了。开了include_raw就不一样,原始消息、用量统计全给你带回来。
调试的时候看原始返回找问题,线上的时候统计token算成本,一举两得。当然生产环境不用全开,抽样打点就行,不然日志量能给你撑爆。
4 消息模型:别再把所有内容塞一个字符串里
新手最容易犯的错:把系统提示、用户问题、历史对话全拼成一个大字符串传给模型。短的时候还好,一长就乱,模型也分不清哪句是指令哪句是对话。
4.1 四种消息,各司其职
SystemMessage:定调子的,告诉模型你是谁、该怎么说话、要遵守什么规则,优先级最高。别把对话历史塞这里面,纯纯浪费高优先级位。
HumanMessage:用户说的话,输入内容、图片文件都塞这里。多用户场景还能加个name字段,模型就不会分不清谁在说话。
AIMessage:模型的回复,文本内容、工具调用指令都在这上面。token用量统计也从这里拿。
ToolMessage:工具执行完的结果,必须带tool_call_id跟前面的调用配对。不然模型都不知道这结果是对应哪次调用的,推理直接跑偏。
4.2 消息列表才是多轮对话的正确姿势
单轮任务你用字符串无所谓,只要涉及多轮、工具调用,老老实实用消息列表。结构清晰,角色分明,模型理解起来也准。
而且这是LangChain的统一标准,不管换哪家模型,消息列表的格式都不用改。这就是抽象层的好处,把差异全挡在底下。
5 工具调用:让模型学会自己动手查资料
大模型不是万能的,实时数据、内部系统数据它都不知道。硬问就只能瞎编,俗称幻觉。工具调用就是给模型开了个外挂,需要啥自己查去。
5.1 一个装饰器,把Python函数变成模型工具
写个普通Python函数,加个@tool装饰器,完事。模型就能识别到这个工具,知道它是干啥的、要传什么参数。
重点提醒:函数的docstring不是写给你自己看的,是写给模型看的。你写得越含糊,模型越容易瞎调用。就一句话说清楚:这个工具是干啥的、入参是什么、返回什么,比啥都强。
5.2 工具粒度别太大,一个工具干一件事
别图省事写个万能工具,啥功能都塞里面。模型根本分不清什么时候该调它,参数也容易传错。
就拆成单一职责的小工具,查股价的就查股价,算汇率的就算汇率。工具多几个没关系,模型选得准,比啥都强。
5.3 配对机制:多工具并发也不会串台
模型一次可以调用好几个工具,并行执行效率高。但要是返回顺序乱了怎么办?放心,每个tool_call都有唯一id,ToolMessage必须带上对应id,模型自己会配对。
就跟快递面单似的,不管哪个先到,扫码就知道是谁的。这协议设计得还是挺讲究的,省了我们自己写分发逻辑。
6 LangGraph三要素:状态、节点、边
前面都是LangChain的积木,接下来LangGraph就是骨架,把这些积木串成有状态、能分支的工作流。核心就三个东西:State、Node、Edge。
6.1 State:全图共享的上下文
State就是整张图的共享内存,所有节点都能读能写。一般用TypedDict定义,字段清清楚楚,谁都能一眼看明白这张图在传什么数据。
重点说下reducer,也就是合并规则。比如messages字段,我们要的是追加新消息,不是覆盖旧历史。这时候就得用add_messages这个reducer,不然每轮对话都清空历史,Agent跟金鱼似的,记不住任何东西。
6.2 Node:干活的处理单元
节点本质就是个函数,输入是当前State,输出是要更新的字段。你可以放纯计算逻辑,可以放LLM调用,也可以放工具执行,灵活得很。
每个节点只干一件事,职责单一,好测试也好维护。别把所有逻辑塞一个节点里,那不叫图,叫巨型函数。
6.3 Edge:决定下一步去哪
普通边就是固定顺序,A做完就做B。条件边就有意思了,根据当前State的值动态决定下一个节点是谁。这才是Agent的灵魂:下一步做什么,由运行时的状态决定,不是写死的。
就像人做决策一样,遇到问题先判断要不要查资料,要就去查,不要就直接回答。以前你得自己写while循环加if判断,现在用条件边,声明一下就行。
7 上手第一个图:货币转换器
说再多不如动手写一个。咱们就整个最简单的货币转换:输入美元金额,先加8%手续费,再按汇率转成印度卢比。三步就能跑通。
7.1 三步搭起最小图
第一步,定义State:三个字段,原始金额、加价后金额、最终卢比金额。
第二步,写两个节点函数:一个算加价,一个算汇率转换,每个函数只读需要的字段,返回要更新的结果。
第三步,建图、注册节点、连边:START → 加价节点 → 转换节点 → END。
最后compile一下,invoke传初始值,直接出结果。就这么简单,一张有状态的工作流就跑起来了。
7.2 可视化:一眼看明白图长啥样
写完别着急跑,先调用draw_mermaid_png把图画出来。节点有没有漏、边有没有接错,一眼就能看见。
别觉得这步多余,等你图里十几个节点、好几个分支的时候,不靠可视化,光靠脑子想,很容易接错线。调试半天发现是边连错了,哭都来不及。
8 条件边:让图自己做决策
固定流水线只能做简单任务,要想做智能Agent,必须得有动态路由。比如用户想换欧元还是换卢比,得让图自己判断走哪个分支。
8.1 一个路由函数搞定动态分支
写个路由函数,读取State里的目标币种,返回对应的节点名字。然后用add_conditional_edges把源节点和路由函数绑上就行。
运行的时候,源节点执行完,自动调用路由函数,拿到目标节点名,接着往下走。整个流程完全由数据驱动,不用写一堆if-else嵌套。
8.2 分支独立,好维护好测试
每个分支节点都是独立的,改欧元的汇率逻辑不会影响卢比的分支。测试的时候也可以单独测每个分支,不用绕完整条链路。
以后要加新币种?加个节点、加个分支条件就行,不用动老代码。这就是图结构的扩展性,比线性脚本强太多。
9 ReAct代理:思考-行动循环,开箱即用
有了工具调用和条件边,我们就能拼出最经典的ReAct模式:模型先思考要不要调用工具,要就去执行工具,结果返回来再继续思考,直到给出最终答案。
9.1 不用手写循环,预置组件直接用
LangGraph已经把常用的都做好了:ToolNode负责执行工具,tools_condition负责判断要不要调工具。你只要把聊天节点和工具节点连上,循环自动就跑起来了。
模型想调用几次工具就调用几次,循环次数完全由模型自己决定。不用你写while True,也不用你手动维护对话历史,框架全给你处理了。
9.2 一个例子看懂整个流程
比如用户问“买20股苹果股票要多少钱”。模型一看不知道实时股价,就发起工具调用,触发tools_condition,走到ToolNode执行查询。
工具把股价结果塞回消息列表,再回到聊天节点。模型拿到数据,算一下总价,这次不带工具调用了,条件边就路由到END,任务结束。
整个过程行云流水,你要做的就是定义好工具、写好prompt,剩下的循环交给框架。
10 记忆与多会话:每个用户都有独立上下文
没有记忆的Agent就是个一次性工具,用户问下一句就忘了上一句。加上checkpointer,瞬间就有了长时记忆。
10.1 一行代码开启记忆
实例化一个MemorySaver,compile的时候传进去,完事。每次invoke结束,整张图的State都会自动存下来。
下次调用的时候带上同一个thread_id,框架自动把上次的状态加载回来,接着上次的聊。就这么简单,跨轮记忆直接拉满。
10.2 thread_id:多用户隔离的关键
不同用户、不同会话,给不同的thread_id就行。每个线程的状态完全独立,张三的对话不会跑到李四那里去。
做生产服务的话,每个用户会话生成一个唯一的thread_id,跟业务ID绑定上。多租户隔离直接就搞定了,不用你自己写一堆状态管理的代码。
10.3 内存不够用?换数据库就行
MemorySaver是存在进程内存里的,重启就没了,适合开发测试。生产环境直接换成SqliteSaver或者PostgresSaver,接口完全一样,换个导入就行。
这就是抽象的好处:底层存储随便换,上层业务代码一行不用改。想扩容、想持久化,都是分分钟的事。
11 从demo到生产:工程落地清单
跑通demo只是第一步,要放到线上扛流量,还有几个必须做的升级。
11.1 工具层加固:超时、重试、鉴权
线上环境什么幺蛾子都有,网络超时、第三方接口挂了、参数传错了,都有可能。别让工具异常直接把整趟图干崩。
给工具加上超时控制、指数退避重试、异常捕获。失败了把错误信息返回给模型,让它自己决定重试还是换方案,比直接抛错强一万倍。
11.2 可观测性:别瞎调参,先看数据
没有监控的Agent就是黑盒,出了问题你都不知道卡在哪。至少要盯三个指标:token用量、端到端延迟、错误率。
哪个节点耗时最长、哪个工具调用失败最多、哪类问题token消耗大,有了数据才能针对性优化。不然全靠感觉调prompt,效率低得离谱。
11.3 兜底机制:别让用户看到“我不会”
条件边别只写正常分支,失败了、参数不对了,得有兜底路径。比如工具调用失败,让模型先尝试换个参数,实在不行再引导用户换个问法。
直接甩给用户一句“无法回答”,体验太差了。做产品,得给用户台阶下。
总结一下,做Agent工程,核心就三件事:结构化输出把数据边界守住,状态图把流程编排做清楚,可观测性把运行状态摸明白。
别上来就堆复杂功能,先把最小图跑通,再一点点加工具、加记忆、加分支。稳扎稳打,比啥都强。
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了