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.如何处理大量的工具调用

相关推荐
Sst的头号粉丝2 小时前
Docker——compose
运维·docker·容器
小仓桑2 小时前
【Agent智能体项目实战五】LangChain访问阿里云嵌入模型
langchain·agent
朽棘不雕2 小时前
Linux工具(上)
linux·运维·服务器
daad7773 小时前
bitcoin HD钱包示例 真实使命7
运维·服务器
Zero-Talent3 小时前
TCP/IP协议
运维·服务器·网络
桌面运维家3 小时前
Windows/Linux云桌面:高校VDisk方案部署指南
linux·运维·windows
Du_chong_huan3 小时前
1.7 计算机网络和因特网的历史 | 《计算机网络:自顶向下方法》精读版
运维·服务器·网络
ZZZKKKRTSAE3 小时前
rhel9快速上手Docker
运维·docker·容器
筱顾大牛3 小时前
Docker安装教程(加汉化!超详细!!!)
运维·docker·容器
没头脑的男大3 小时前
关于tailscale和ssh那些事儿
运维·服务器·ssh