《零基础 AI 入门》第 7 篇(收官)|作者:梯度下降的咖啡师 | 8 年 Java 后端(京东、字节)+ 2 年 AI 应用开发
上一篇我们用 LangGraph 把手写 Agent 重写了一遍,有了图结构、thread_id 隔离和内存级记忆。但当时留了两个尾巴:checkpointer 用的是 InMemorySaver,进程退了还是丢;工具还是全暴露给模型,危险操作没人拦。
今天把这两个尾巴收了。加的东西不多——一处换一行,一处加一个函数调用——但你的 Agent 从"能跑"变成"敢给人用"了。这篇写完,零基础入门系列就收官,下一站是 RAG。
01 从内存到 SQLite:换一行,重启不丢
上一篇最后一行是:
| |
InMemorySaver 就是个 Python dict,进程一退全没。换成 SQLite 只需要改两行:
| |
先装依赖:pip install langgraph-checkpoint-sqlite。
跑起来你会发现目录下多了个 agent.db 文件——就是个 SQLite 数据库,每一步状态快照都存在里面。你 Ctrl+C 退出,重新跑,用同一个 thread_id invoke,它自动从数据库加载上次的 messages,接着聊。
有个细节要说明:实际项目里建议用 with SqliteSaver.from_conn_string("agent.db") as checkpointer: 包一层,退出时自动关连接。教学里为了简洁直接赋值,你自己写的时候加上 with。另外第一次跑之前调一次 checkpointer.setup(),它会自动建好表,后面就不用管了。
就这么简单。你上一篇手写时想了半天的"什么时候存、崩了怎么对齐",LangGraph 在每个节点执行完自动写一行 SQLite,不用你管。
其他后端也一样,换个 Saver 类就行,拼装方式完全不变:
- SQLite(本地开发):
from langgraph.checkpoint.sqlite import SqliteSaver,pip install langgraph-checkpoint-sqlite - PostgreSQL(生产首选):
from langgraph.checkpoint.postgres import PostgresSaver,pip install langgraph-checkpoint-postgres - MongoDB:
from langgraph.checkpoint.mongodb import MongoDBSaver,pip install langgraph-checkpoint-mongodb - AWS DynamoDB / Valkey:
langgraph-checkpoint-aws包
Redis 不做 checkpoint(单线程状态快照),它做的是跨线程长期记忆(Store),后面讲到 RAG 和用户画像时再说。MySQL 目前没有官方 checkpointer,生产环境一般直接上 PostgreSQL。
02 为什么需要人工刹车
第五篇体检时说过:你的 tools 列表全暴露给模型了。查天气、查 AQI 是只读的,问题不大。但如果你的工具里有 send_email()、delete_user()——模型被一句话诱导就真的会去调。
Java 后端对这个味道太熟了:写操作接口不能裸奔,必须在关键节点加审批。Agent 也一样——高风险操作不能让模型自己决定,得暂停下来等人点"确认"。
LangGraph 里这个能力叫 Interrupt。你在节点函数里调一个 interrupt(),图就暂停在这一步,把当前状态返回给你。你看完决定继续还是拒绝,再调一次 graph.invoke(Command(resume="approve"), config),图从暂停的地方接着跑。
03 给 tools_node 加审批
在第六篇代码基础上,加一个需要审批的工具 send_email,然后改 tools_node:
| |
运行方式和之前不一样了——第一次 invoke 遇到 interrupt 会暂停:
| |
你看,审批逻辑全在 tools_node 里——遇到危险工具名就 interrupt,安全工具直接跑。你不用自己写 while 循环等输入,不用自己管理暂停状态,框架帮你存好了。而且这个暂停状态也被 checkpointer 写进了 SQLite——你重启进程,用同一个 thread_id 调 Command(resume="approve"),它还能从上次暂停的地方接着审批。
调试的时候你会看到 interrupt 里的 args 是一坨 JSON——模型吐回来的参数结构,格式不对的时候丢到 ForJSON(forjson.com)里格式化看一眼,比在终端 print 清楚。
04 完整代码长什么样
把前面拼起来,基于第六篇改了三处:
- checkpointer 从 InMemorySaver 换成 SqliteSaver
- 加了一个 send_email 工具和 DANGEROUS_TOOLS 集合
- tools_node 里加了 interrupt 审批逻辑
其他部分——State 定义、agent_node、条件边、图的拼装——和第六篇一模一样,不用动。这就是为什么前面让你手写再重写:你现在看每一行改动都知道它在解决什么问题,不是在抄一个陌生框架的 API。
05 零基础入门,到这里就够了
回头看这个系列走了多远:
| 篇数 | 你学会了什么 |
|---|---|
| 第 1 篇 | Java 转 Python 的 6 个坑,大模型就是个字符串函数 |
| 第 2 篇 | Function Calling + while 循环 = 你的第一个 Agent |
| 第 3 篇 | 加记忆:滑窗剪枝和摘要压缩,别让它聊两句就失忆 |
| 第 4 篇 | 生产体检:持久化、多用户隔离、工具权限,手写版差什么 |
| 第 5 篇 | LangGraph 重写:State/Node/Edge/Checkpointer/thread_id |
| 第 6 篇 | SQLite 持久化 + Interrupt 人工审批,敢给人用了 |
到这里,你已经能独立搭一个单 Agent:多轮记忆、多用户隔离、持久化、危险操作审批——这些生产 Agent 该有的东西你都摸过一遍了。再往后就是在这个骨架上长肉:接什么工具、连什么数据、做什么场景。
下一个工具不是查天气了。我们要给 Agent 接一个你自己的知识库——上传几个 PDF,问它里面的内容,它自己去检索相关片段再回答。这就是 RAG,《Agent 特调》栏目的第一篇。
旧炉未冷,新火已燃。关注公众号「梯度下降的咖啡师」,或访问 gradcoffee.com。下一篇开始做 RAG:怎么把你的文档变成 Agent 能查的工具。
聊聊两个问题:
① 你现在最想给 Agent 接的第一个真实工具是什么?
② Interrupt 人工审批这个设计,你觉得在你的场景里是刚需还是过度设计?
咖啡续命,梯度下降。| 作者:梯度下降的咖啡师
