LangGraph知识

目录

一、基础概念

1.状态:定义的变量类

2.节点:函数

3.边:连接函数

二、LangGraph的高级特性

1.Reducer消息添加

2.Send机制:[send(节点,参数)]

3.Command节点跳转

4.checkpointer持久化

[5.Threads 会话id](#5.Threads 会话id)

6.Configuration节点内部配置

7.interupt中断

8.Subgraph子图

9.Graphviz图可视化

10.ToolNode工具节点

三、对图的细粒度控制

1.人机交互

2.节点重试机制

3.跨图的checkpoint持久化存储

4.checkpoint持久化存储到数据库

5.消息的摘要处理


一、基础概念

1.状态:定义的变量类

2.节点:函数

3.边:连接函数

(1)正常边

(2)条件边:对应条件函数

复制代码
# 事件:节点
graph_builder.add_conditional_edges('processor', route_tools, {"tj_1": "node_b", "tj_2": "node_a"})

# 这里的条件边,如果函数返回的就是可用节点名,则不需要字典说明,如果函数返回的是自定义的字典,则需要字典说明
builder.add_conditional_edges(START, continue_to_jokes)

(3)入口点

(4)条件入口点


二、LangGraph的高级特性

1.Reducer消息添加

  • 把每次节点return的消息,都追加到消息列表

  • 消息存储只在当前的图中有效

  • 对当前新的值进行返回,并合并之后的值,类似消息记录

  • 自定义规约器

    class ChatState(TypedDict):
    messages: Annotated[Sequence[AnyMessage], operator.add]

2.Send机制:[send(节点,参数)]

  • 简单的send类似动态条件边

  • 随时给多个节点发任务

  • 支持并行

  • 动态创建节点

    定义一个函数continue_to_jokes,接受一个OverallState类型的参数state

    def continue_to_jokes(state: OverallState):
    # 返回一个Send对象的列表,每个对象包含一个"generate_joke"的node和传递给node的状态
    # subjects中有几个元素,就会有几个Send对象
    return [Send("generate_joke", {"subject": s}) for s in state['subjects']]

    创建一个StateGraph对象builder,传入OverallState类型

    builder = StateGraph(OverallState)

    LangGraph 把 Send 的第二个参数原封不动地当作节点函数的入参,因此:

    Send("generate_joke", {"subject": "cats"})

    会让 "generate_joke" 节点收到的 state 就是:

    {"subject": "cats"} # 只有这一个键

    添加一个名为"generate_joke"的节点,节点执行一个lambda函数,生成一个关于主题的笑话

    匿名函数

    builder.add_node("generate_joke", lambda state: {"jokes": [f"Joke about {state['subject']}"]})

3.Command节点跳转

  • 把图暂停、改状态、跳到别的节点、甚至把子图重新派发到别的 worker

  • 加强版条件边

  • 可以结合人类的干预

    graph=Command.PARENT, # 指定跳转的父图
    goto=goto, # 跳转到子图相应节点
    resume= # 给被暂停的interrupt节点传递数据,
    update={"foo": value}, # 更新值的内容,value是占位符

4.checkpointer持久化

  • 保存图中state的状态,state更新一次,保存一次
  • 可以跨多次invoke调用拿到消息
  • 必须结合 config使用
  • 父图设置了检查点,子图一样具有检查点
  • 参数:

config:配置

metadata:与此检查点相关的元数据

values:此时状态的通道

next:图中下一个要执行的节点名称元组

tasks:包含有关要执行的下个任务的RregelTask对象的元组

5.Threads 会话id

  • 保证一轮连续对话
  • 一次从起始节点出发、沿着图里的边一步一步往下走的完整"流程实例"

6.Configuration节点内部配置

  • 比如切换某个大模型

7.interupt中断

  • 需要结合checkpointer,保证每次图状态的记忆
  • 结合config,确定不同用户的状态
  • 结合command,给中断点发送消息,让图继续运行
  • 写了interupt,就得写两次调用,第一次运行到节点等待,第二次传递值得到数据
  • 人工输入,与模型流程无关
  • 在图中的某个节点,使用interup运行到该节点,操作暂停,调用这个图的时候,需要通过command的resume传递一个消息给暂停的节点,让图继续运行

8.Subgraph子图

  • 父图和子图有共享字段,父图中可添加子图节点
  • 如果没有共享字段,必须添加一个节点函数,父图调用子图
  • 子图作为父图的一个节点,子图到父图可以通过command跳跃

9.Graphviz图可视化

复制代码
from IPython.display import Image, display
"""可视化图"""
#方式一:需要单独安装graphviz
# 方法:从官网下载
# 访问 Graphviz官网 https://graphviz.org/download/
# 下载Windows版本的安装包
# 运行安装程序并按照提示完成安装
# 将Graphviz的bin目录添加到系统PATH环境变量中(通常是C:\Program Files\Graphviz\bin)
# 并且请用try块包裹下面的语句,因为执行会报错,但是不影响图片的生成
try:
    display(Image(app.get_graph().draw_png(output_file_path='./可视化图1.png')))
except:
    pass
#方式二:不需要额外安装软件,但是访问网址mermaid.ink非常容易失败,开启科学上网比较容易成功
display(Image(app.get_graph().draw_mermaid_png(output_file_path='./可视化图2.png')))

10.ToolNode工具节点

  • 工具节点调用工具,实现循环调用

    tools:有哪些工具
    name:工具箱节点名字
    tags:给工具箱贴标签
    handle_tool_errors:工具抛错处理
    messages_key:消息存在哪个字段
    wrap_tool_call:执行前拦截/包装(同步)
    awrap_tool_call:执行前拦截/包装(异步)


三、对图的细粒度控制

1.人机交互

2.retry=RetryPolicy节点重试策略

复制代码
builder.add_node("query_database",query_database,retry=RetryPolicy(retry_on=sqlite3.OperationalError))
builder.add_node("model", call_model, retry=RetryPolicy(max_attempts=5))

|----------------------|------------------------------------------|-----------------------------------|
| initial_interval | 第一次失败后"等几秒"再试。 | 0.5 → 等 0.5 秒。 |
| backoff_factor | 每多失败一次,等待时间乘以这个数。 | 2.0 → 第二次 1 s,第三次 2 s,第四次 4 s ... |
| max_interval | "最长能等多久"的上限,防止无限翻倍。 | 128 → 不管失败多少次,最多等 128 s。 |
| max_attempts | 总共尝试次数(含第一次)。 | 3 → 第一次 + 最多再重试 2 次。 |
| jitter | 是否加随机扰动,避免所有节点同时重试造成" thundering herd "。 | True → 在计算出的等待时间上随机 ± 一点。 |
| retry_on | 只有这些异常才重试;其余异常直接抛给用户。 | 默认只认 Exception 的子集(网络超时、5xx 等); |

复制代码
def query_database(state):
    try:
        return real_work(state)
    except Exception as e:
        print('>>> 捕获到异常', type(e).__module__+'.'+type(e).__qualname__, e)
        raise  # 重新抛出,方便后面细化

# 临时全捕获,观察
retry=RetryPolicy(retry_on=lambda e: True)   # 啥都重试

3.同一用户所有消息的持久化存储

  • 利用user_idthread_id
  • 除了线程id,还需要引入外部存储
ID 谁来生成 谁来传递 常见做法
user_id ① 登录成功后后端把用户主键(UUID)写进 JWT / Session ① 客户端:每次请求在 Header/Body 里带上 ② 后端网关:JWT 解析后把 sub 字段注入 RunnableConfig "只要用户不换账号就永远不变"
thread_id ① 前端"新建对话"按钮点击时 uuidv4() 生成一个 ② 或者后端在创建会话记录时返回 ① 前端:放在每次对话请求的 JSON 里 ② 后端:建立 WebSocket 时把 thread_id 写进 URL Path 参数 "每次新建聊天就换"
  • checkpoint + thread_id = 同一次聊天的"存档槽"

  • 向量库 + user_id = 用户终身"知识库"

  • 和RunnableWithMessageHistory的区别,这个是把所有的对话全部传递进去

    prefix,key的形式存储,实现用户消息隔离
    后续查找搜索,使用相似度搜索

    namespace = ("memories", user_id)

    搜索

    memories = store.search(namespace, query=str(state["messages"][-1].content))

    存储

    store.put(namespace, str(uuid.uuid4()), {"data": memory})

  • 关于user_id和jwt权限

  1. 客户端 → 提交登录凭证(手机+密码)

请求头:Content-Type: application/json

请求体:{"phone":"13812345678","password":"123456"}

  1. 后端 → 查库拿到主键

SELECT id FROM users WHERE phone='13812345678' AND pwd=...;

假设 id = 42

  1. 后端 → 生成 JWT

payload = {"sub":42,"exp":1234567890}

JWT = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjQyLCJleHAiOjEyMzQ1Njc4OTB9.xxxxx

  1. 后端 → 返回 JWT

响应体:{"token":"eyJ..."}

  1. 客户端 → 后续请求带 JWT

请求头:Authorization: Bearer eyJ...

  • 登录流程
  1. 登录前

    请求头里什么都不用带(或只带一次性的登录凭证:手机+密码、验证码、OAuth code)。

  2. 登录后

    后端把 42 写进 JWT 的 sub 字段,把完整的 JWT 字符串 返回给客户端。

    客户端后续请求只需在 Header 写:

    Authorization: Bearer <JWT字符串>

  3. 后端接到请求

    验签并解析 JWT → 拿到 sub=42 → 这就是 user_id,再注入 RunnableConfig 即可。


4.checkpoint持久化存储到数据库

  • 可以使用自带插件

  • 任何中断、重启、新开 Python 进程,只要 thread_id 相同,对话断点能续上。

  • 取出消息由invoke的时候自动去拿

5.消息的摘要和删除RemoveMessage处理

  • 摘要发生在**"把消息送进 LLM 之前"**这一步。

  • 删除消息是根据删除消息列表里面对应id实现的

维度 RunnableWithMessageHistory LangGraph 摘要模式
存储内容 完整消息列表(逐条 Human/AI) 1 段摘要 + 最近 2 条消息
Token 增长 线性增加 → 易爆表 几乎恒定(只留最新句)
跨会话 session_id 可续聊 thread_id 可续聊
信息丢失 仅保留摘要,细节被丢弃
实现方式 链外层自动帮你拼消息 图节点里手动总结 + RemoveMessage
适用场景 短对话、快速接入 长对话、Token 敏感、需断点续跑

图的方法调用

|---------------------------------------------------------|-----------------|-------------------|
| app.get_state(config) | 读取最新快照 | 调试、手动改状态前 |
| app.update_state(config, values, *, as_node=None) | 手动写入/补丁状态 | 删消息、注入摘要、人工干预 |
| app.stream(..., config) | 从快照断点继续跑 | 断点续聊、交互式对话 |
| app.invoke(input, config) | 一次性跑完并返回终态 | 脚本批量测试 |
| app.get_state_history(config) | 遍历该线程所有历史快照 | 回溯、审计、可视化 |
| app.wakeup(config) | 唤醒被中断的图 | 人机审批、异步回调 |
| app.search(*, thread_id, ...) | 跨线程搜索状态 | 后台运营查询 |

6.toolNode工具调用失败处理

  • 通过handle_tool_errors= True实现

    def get_weather(location: str):
    """获取当前天气."""
    print('location:', location)
    if location == "SH":
    raise ValueError("输入查询必须是专有名词")
    elif location == "上海":
    return "气温23度,有雾."
    else:
    raise ValueError("无效输入.")

    tool_node = ToolNode([get_weather],handle_tool_errors= True)

7.toolNode工具调用失败,转到新的工具节点调用

  • 判断返回的消息中是否有error消息,如果第一次工具调用失败,重新跳转到节点,使用新的工具,并删除最后一次的ai消息。

7.将图state状态注入给工具函数

  • 注入状态:InjectedState,

  • 注入后工具函数就能正常的取值使用

  • Annotated 是 Python 3.9+ 自带的类型注解增强器

  • Annotated[真正类型, 元数据1, 元数据2, ...]

  • InjectedState 是 LangGraph 提供的一个标记常量,含义: "这个参数不需要 LLM 填,由框架把当前图的 state 整字典注入进来。

    存储方式

    class State(AgentState):
    docs: List[str]

    @tool
    def get_context(question: str, state: Annotated[dict, InjectedState]):
    """获取回答问题的相关背景."""
    return "\n\n".join(doc for doc in state["docs"])

8.将图第三方存储和配置注入给工具函数

  • InjectedStore() + RunnableConfig

  • 每次 app.stream / app.invoke 时传的 config 字典:在图内部会被自动封装成 RunnableConfig 实例

    数据库方式

    def get_context(
    question: str,
    config: RunnableConfig, # 参数1:注入运行时配置
    store: Annotated[BaseStore, InjectedStore()], # 参数2:注入文档存储
    ) -> Tuple[str, List[Document]]:
    """获取回答问题的相关背景."""
    # 从运行时配置中获取 user_id
    user_id = config.get("configurable", {}).get("user_id")
    # 从注入的 store 中根据 user_id 搜索文档
    docs = [item.value["doc"] for item in store.search(("documents", user_id))]
    return "\n\n".join(doc for doc in docs) # 返回拼接后的文档字符串

    doc_store = InMemoryStore()
    graph = create_agent(model, tools, checkpointer=checkpointer, store=doc_store)

9.工具函数中更新state

工具函数不是节点,不能直接更新state

通过工具id和return command实现

复制代码
# 自动注入tool_call的id
tool_call_id: Annotated[str, InjectedToolCallId],


 return Command(
        update={
            "user_info": user_info,
            "messages": [
                ToolMessage(
                    "成功查询用户信息", tool_call_id=tool_call_id
                )
            ],
        }
    )

stream_mode的参数

模式 每帧推送的内容 典型用途
"values" 整个状态对象的快照(messages、user_info...全量) 调试、前端需要随时拿到完整上下文
"updates" 仅本次被改动的字段(增量) 节省流量,只拿变化
"messages" 仅消息数组里新增的那一条 聊天 UI 逐字逐句渲染
"debug" 调试信息(节点进出、异常等) 排查流程

10.如何处理大量的工具调用

相关推荐
weixin_660096781 小时前
tree命令的离线下载
linux·运维·tree
千百元1 小时前
实时监控磁盘I/O性能
linux·运维·数据库
福尔摩斯张1 小时前
C语言文件操作详解(一):文件的打开与关闭(详细)
java·linux·运维·服务器·c语言·数据结构·算法
minji...1 小时前
Linux 进程控制(二) (进程等待wait/waitpid)
linux·运维·服务器·数据结构
我的offer在哪里1 小时前
MySQL 高频细节问题(覆盖性能、存储、运维、故障排查,补充前文未深入的核心细节)
android·运维·mysql
云和数据.ChenGuang1 小时前
运维工程师技术之MyCat中间件免费技术教程
运维·中间件·mycat·运维工程师·运维技术
wanhengidc1 小时前
云手机 巨 椰 三角洲行动
运维·服务器·科技·游戏·智能手机
weixin_307779131 小时前
Jenkins Gson API插件:统一JSON处理的基础库
java·运维·开发语言·架构·jenkins
小嘟嘟132 小时前
第2章 Shell 变量与参数传递:3 种定义方式 + 避坑指南
linux·运维·shell