ai应用开发的学习笔记

检查节点的参数

复制代码
============================== -> 运行结果 <- ==============================
res: {} {'final_output': '关于 猫咪 的七言绝句:\n《戏猫》\n狸奴酣卧小窗南,醉眼惺忪戏玉簪。\n忽跃雕檐追粉蝶,却衔明月入花龛。\n笑话:\n小猫问妈妈:"为什么人总说'猫有九条命'?"  \n妈妈答:"因为人类怕我们死一次就够他们内疚一辈子。"  \n小猫追问:"那为什么狗只有一条命?"  \n妈妈叹气:"因为狗犯错靠装可怜,而我们靠记仇。"'}
============================== -> 历史检查点列表 <- ==============================
[
    StateSnapshot(
        values={
            "topic": "猫咪",
            "poem": """
                        《戏猫》
                狸奴酣卧小窗南,醉眼惺忪戏玉簪。
                忽跃雕檐追粉蝶,却衔明月入花龛。
                """,
            "joke": """
                小猫问妈妈:"为什么人总说'猫有九条命'?"  
                妈妈答:"因为人类怕我们死一次就够他们内疚一辈子。"  
                小猫追问:"那为什么狗只有一条命?"  
                妈妈叹气:"因为狗犯错靠装可怜,而我们靠记仇。"
                """,
            "final_output": """
                关于 猫咪 的七言绝句:
                《戏猫》
                狸奴酣卧小窗南,醉眼惺忪戏玉簪。
                忽跃雕檐追粉蝶,却衔明月入花龛。
                笑话:
                小猫问妈妈:"为什么人总说'猫有九条命'?"  
                妈妈答:"因为人类怕我们死一次就够他们内疚一辈子。"  
                小猫追问:"那为什么狗只有一条命?"  
                妈妈叹气:"因为狗犯错靠装可怜,而我们靠记仇。"
                """
        },
        next=(),
        config={
            "configurable": {
                "thread_id": "123",
                "checkpoint_ns": "",
                "checkpoint_id": "1f163e6a-4cc7-61bd-8002-bcd7f63c3238"
            }
        },
        metadata={
            "source": "loop",
            "step": 2,
            "parents": {}
        },
        created_at="2026-06-09T09:36:08.368575+00:00",
        parent_config={
            "configurable": {
                "thread_id": "123",
                "checkpoint_ns": "",
                "checkpoint_id": "1f163e6a-4cc3-6d96-8001-8314b5dbbfe3"
            }
        },
        tasks=(),
        interrupts=()
    ),

    StateSnapshot(
        values={
            "topic": "猫咪",
            "poem": """
                        《戏猫》
                狸奴酣卧小窗南,醉眼惺忪戏玉簪。
                忽跃雕檐追粉蝶,却衔明月入花龛。
                """,
            "joke": """
                小猫问妈妈:"为什么人总说'猫有九条命'?"  
                妈妈答:"因为人类怕我们死一次就够他们内疚一辈子。"  
                小猫追问:"那为什么狗只有一条命?"  
                妈妈叹气:"因为狗犯错靠装可怜,而我们靠记仇。"
                """
        },
        next=("node_output",),
        config={
            "configurable": {
                "thread_id": "123",
                "checkpoint_ns": "",
                "checkpoint_id": "1f163e6a-4cc3-6d96-8001-8314b5dbbfe3"
            }
        },
        metadata={
            "source": "loop",
            "step": 1,
            "parents": {}
        },
        created_at="2026-06-09T09:36:08.367236+00:00",
        parent_config={
            "configurable": {
                "thread_id": "123",
                "checkpoint_ns": "",
                "checkpoint_id": "1f163e6a-393f-6082-8000-b9c0708bf03d"
            }
        },
        tasks=(
            PregelTask(
                id="91d81ad6-d2fb-ebd7-2dd1-8e08c2be5a07",
                name="node_output",
                path=("__pregel_pull", "node_output"),
                error=None,
                interrupts=(),
                state=None,
                result={
                    "final_output": """
                        关于 猫咪 的七言绝句:
                        《戏猫》
                        狸奴酣卧小窗南,醉眼惺忪戏玉簪。
                        忽跃雕檐追粉蝶,却衔明月入花龛。
                        笑话:
                        小猫问妈妈:"为什么人总说'猫有九条命'?"  
                        妈妈答:"因为人类怕我们死一次就够他们内疚一辈子。"  
                        小猫追问:"那为什么狗只有一条命?"  
                        妈妈叹气:"因为狗犯错靠装可怜,而我们靠记仇。"
                        """
                }
            ),
        ),
        interrupts=()
    ),

    StateSnapshot(
        values={
            "topic": "猫咪"
        },
        next=("node_poem", "node_joke"),
        config={
            "configurable": {
                "thread_id": "123",
                "checkpoint_ns": "",
                "checkpoint_id": "1f163e6a-393f-6082-8000-b9c0708bf03d"
            }
        },
        metadata={
            "source": "loop",
            "step": 0,
            "parents": {}
        },
        created_at="2026-06-09T09:36:06.320542+00:00",
        parent_config={
            "configurable": {
                "thread_id": "123",
                "checkpoint_ns": "",
                "checkpoint_id": "1f163e6a-393d-6b9a-bfff-180e30c1d3bc"
            }
        },
        tasks=(
            PregelTask(
                id="80d8974c-3450-6faa-8b06-dcf8787d74fc",
                name="node_poem",
                path=("__pregel_pull", "node_poem"),
                error=None,
                interrupts=(),
                state=None,
                result={
                    "poem": """
                            《戏猫》
                    狸奴酣卧小窗南,醉眼惺忪戏玉簪。
                    忽跃雕檐追粉蝶,却衔明月入花龛。
                    """
                }
            ),
            PregelTask(
                id="2ca46996-dfc6-6de3-3662-cf1fe41bf987",
                name="node_joke",
                path=("__pregel_pull", "node_joke"),
                error=None,
                interrupts=(),
                state=None,
                result={
                    "joke": """
                    小猫问妈妈:"为什么人总说'猫有九条命'?"  
                    妈妈答:"因为人类怕我们死一次就够他们内疚一辈子。"  
                    小猫追问:"那为什么狗只有一条命?"  
                    妈妈叹气:"因为狗犯错靠装可怜,而我们靠记仇。"
                    """
                }
            ),
        ),
        interrupts=()
    ),

    StateSnapshot(
        values={},
        next=("__start__",),
        config={
            "configurable": {
                "thread_id": "123",
                "checkpoint_ns": "",
                "checkpoint_id": "1f163e6a-393d-6b9a-bfff-180e30c1d3bc"
            }
        },
        metadata={
            "source": "input",
            "step": -1,
            "parents": {}
        },
        created_at="2026-06-09T09:36:06.320009+00:00",
        parent_config=None,
        tasks=(
            PregelTask(
                id="3aa3bc58-3567-5832-d823-4373a9ab94e5",
                name="__start__",
                path=("__pregel_pull", "__start__"),
                error=None,
                interrupts=(),
                state=None,
                result={
                    "topic": "猫咪"
                }
            ),
        ),
        interrupts=()
    )
]

首先这里的StateSnapshot 就是每一个超步的快照,及每完成一个超步就会记录的快照,这个快照从上到下是按照时间倒序排序的。

1.values

values是记录一个快照执行完后的数据的,比如刚开始只有topic:猫咪,后面执行完步骤2就多出了两个数据,

2.next

next是记录下一个节点要执行哪一个的,当有并行执行时是有两个的。

3. config

config里面记录的是一个存档的唯一标识(thread_id,checkpoint_ns,checkpoint_id),再持久化数据库中这三个字段共同构建了唯一约束,thread_id:是用来标识这个langgraph线程的,当下次做可恢复执行用这个来判断是否是同一个图;checkpoint_ns:是一个命名空间,命名空间的作用是区分不同的持久化数据的;checkpoint_id:这个是一个超步的id,用来定位一个超步的

4. metadata:存档的"标签"

这个参数只需要知道step,这个参数代表的执行到了第几步,但需要注意 的是由于并行执行是一起执行的故而并行执行算成一步

5. created_at:存档创建的时间

这个及字面意思,创建这个超步的时间

6. parent_config:上一个存档的身份证

这个参数是记录上一个超步的身份标识的,参数有(thread_id,checkpoint_ns,checkpoint_id);

7.tasks

这个参数呢主要是记录下一个超步执行完的结果,至于怎么保存的是当下一个超步执行完成langgraph会自动保存到里面,作用就是减少token的消耗和执行代码的效率,后续如果执行可恢复执行有了这个就不用重新跑了,直接看到这个直接执行下一个

langgraph工具调用的进阶玩法之重试机制

复制代码
from typing import Literal
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt.tool_node import ToolNode
from langgraph.graph.message import MessagesState

from langchain.messages import HumanMessage, ToolMessage
from langchain.tools import tool
from langchain_deepseek import ChatDeepSeek

from dataclasses import dataclass
from loguru import logger
from dotenv import load_dotenv
load_dotenv(override=True)

import random

@tool(parse_docstring=True)
def get_weather(city: str) -> str:
    """
    查询指定城市的当日天气

    Args:
        city: 城市名称
    """
    # 70% 概率因网络波动而调用失败
    rand_int = random.randint(1,10)
    if rand_int < 8:
        raise ConnectionError("网络波动失败")
    return f"{city} 今天天气不错"

tools = [get_weather]

model = ChatDeepSeek(
    model="deepseek-v4-flash",
    extra_body={
        "thinking": {
            "type": "disabled"
        }
    }
)
model_with_tools = model.bind_tools(tools=tools)

@dataclass
class UserContext:
    max_attempts: int

def llm_node(state: MessagesState) -> MessagesState:
    messages = state['messages']
    response = model_with_tools.invoke(messages)

    return {
        "messages": [response]
    }

def router(state: MessagesState) -> Literal["tool_node", END]:
    if state['messages'][-1].tool_calls:
        return "tool_node"
    return END

def wrap_tool_call(request, execute):
    max_attempts = request.runtime.context.max_attempts
    tool_call_id = request.runtime.tool_call_id

    tool_msg = ""
    for i in range(max_attempts):
        try:
            tool_msg = execute(request)
            break
        except ConnectionError as e:
            logger.info("工具调用失败,当前调用次数: {}, 调用次数上限: {}, 异常信息: {}", i + 1, max_attempts, e)

    if not tool_msg:
        tool_msg = ToolMessage(
            tool_call_id = tool_call_id,
            content = "调用次数到达上限,调用失败"
        )

    return tool_msg

builder = StateGraph(state_schema=MessagesState, context_schema=UserContext)
builder.add_node("llm_node", llm_node)
builder.add_node("tool_node", ToolNode(tools=tools, wrap_tool_call=wrap_tool_call))
builder.add_edge(START, "llm_node")
builder.add_conditional_edges("llm_node", router, path_map=["tool_node", END])
builder.add_edge("tool_node", "llm_node")

graph = builder.compile()

from IPython.display import display
display(graph)

res = graph.invoke(
    {"messages": [HumanMessage("今天北京天气如何?")]},
    context = UserContext(max_attempts=3))
for msg in res['messages']:
    msg.pretty_print()

这串代码中定义了一个wrap_tool_call重试函数,先说说这个函数里面的两个参数吧:

复制代码
request:ToolCallRequest;这个函数保存的是一个工具调用节点的参数,包含工具的 id、工具的名称,工具调用的参数以及上下问的参数
execute:这是一个可执行的回调函数,用于实际调用对应的工具并返回执行结果。

这个重试机制的执行流程是这样的当执行到路由函数时,工具路由函数会跳转到工具执行的ToolNode,但在执行这个发之前会触发这个wrap_tool_call函数,执行完这个函数会拿着这个函数返回的结果对state写入从而实现工具的调用于重试

当时如果有多个工具调用时ToolNode会对返回的多个tool_call独立调用wrap_tool_call从而实现多工具调用的重试。

如何查看子图检查点

首先查看父图的检查点会发现父图调用了子图的检查点会多出一下部分:

复制代码
state={
    'configurable': {
        'thread_id': 'observe_checkpoint_1',
        'checkpoint_ns': 'call_subgraph:9923d4be-9845-576d-785f-5b1d6da9a94f'
    }
}

父节点调用的子节点检查点就是按照这个checkpoint_ns命名空间区分的。

首先我们可以通过parent_config = histories-2.config获取到子图所在的节点的config配置,通过这个配置parent_graph.get_state(config=parent_config, subgraphs=True)subgraphs=True改为True就能查看子图最新快照了,这个快照是子图运行完节点next()为空的快照

查看完整的检查点subgraph_config = histories-2.tasks0.state这个能拿到需要查看检查点的所需要的参数:

复制代码
{
  'configurable': {
    'thread_id': 'observe_checkpoint_1',
    'checkpoint_ns': 'call_subgraph:9923d4be-9845-576d-785f-5b1d6da9a94f'
  }
}

通过线程id和命名空间字符串能list(parent_graph.get_state_history(config=subgraph_config))就能查看子图的所有检查点信息了

相关推荐
天诰23 分钟前
SA-K-11 检索会消失吗与四个未解问题
java·服务器·人工智能·python·机器学习·ai编程
La Pulga1 小时前
【FreeRTOS源码学习笔记】任务延时列表
笔记·mcu·学习·嵌入式·freertos·实时操作系统
陈希1232 小时前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡3 小时前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
楚山之石3 小时前
【JCO】mpMRI 先行替代 TURBT 初始分期:BladderPath 随机试验让 MIBC 患者提前 45 天得到正确治疗
人工智能·学习·mri·医学影像
@兽血沸腾3 小时前
Windows系统安装Jenkins部署应用至远程Windows服务器操作指南
运维·服务器·jenkins
Peter-OK6 小时前
PostgreSQL学习笔记(入门-进阶-精通说明)
笔记·学习·postgresql
承渊政道7 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
橙橙笔记8 小时前
具身智能的学习 - 完成一个机械臂 第1节
python·学习·机械臂·具身智能
成都蜀味缘餐饮技术培训学校8 小时前
手撕烤兔技术培训实战指南,秘制腌料配方比例与调制手法
大数据·经验分享·笔记·其他·微信公众平台