Skip to content

如何查看和更新过去的图状态

先决条件

本指南假设您熟悉以下概念:

一旦您开始检查点图,您就可以轻松地在任何时间点**获取**或**更新**代理的状态。这允许一些操作:

  1. 您可以在中断时向用户显示状态,让用户接受某个操作。
  2. 您可以**回滚**图以重现或避免问题。
  3. 您可以**修改**状态,将您的代理嵌入更大的系统中,或让用户更好地控制其操作。

用于此功能的关键方法包括:

注意: 这需要传入一个检查点器。

下面是一个快速示例。

环境搭建

首先我们需要安装所需的包

%%capture --no-stderr
%pip install --quiet -U langgraph langchain_openai

接下来,我们需要为OpenAI(我们将使用的大型语言模型)设置API密钥

import getpass
import os


def _set_env(var: str):
    if not os.environ.get(var):
        os.environ[var] = getpass.getpass(f"{var}: ")


_set_env("OPENAI_API_KEY")
ANTHROPIC_API_KEY:  ········

为LangGraph开发设置LangSmith

注册LangSmith,可以快速发现并解决您的LangGraph项目中的问题,提高性能。LangSmith允许您使用跟踪数据来调试、测试和监控使用LangGraph构建的LLM应用程序——更多关于如何开始的信息,请参阅这里

构建代理

我们现在可以构建代理了。我们将构建一个相对简单的ReAct风格的代理,用于工具调用。我们将使用Anthropic的模型和一些假工具(仅用于演示目的)。

# Set up the tool
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.graph import MessagesState, START
from langgraph.prebuilt import ToolNode
from langgraph.graph import END, StateGraph
from langgraph.checkpoint.memory import MemorySaver


@tool
def play_song_on_spotify(song: str):
    """Play a song on Spotify"""
    # Call the spotify API ...
    return f"Successfully played {song} on Spotify!"


@tool
def play_song_on_apple(song: str):
    """Play a song on Apple Music"""
    # Call the apple music API ...
    return f"Successfully played {song} on Apple Music!"


tools = [play_song_on_apple, play_song_on_spotify]
tool_node = ToolNode(tools)

# Set up the model

model = ChatOpenAI(model="gpt-4o-mini")
model = model.bind_tools(tools, parallel_tool_calls=False)


# Define nodes and conditional edges


# Define the function that determines whether to continue or not
def should_continue(state):
    messages = state["messages"]
    last_message = messages[-1]
    # If there is no function call, then we finish
    if not last_message.tool_calls:
        return "end"
    # Otherwise if there is, we continue
    else:
        return "continue"


# Define the function that calls the model
def call_model(state):
    messages = state["messages"]
    response = model.invoke(messages)
    # We return a list, because this will get added to the existing list
    return {"messages": [response]}


# Define a new graph
workflow = StateGraph(MessagesState)

# Define the two nodes we will cycle between
workflow.add_node("agent", call_model)
workflow.add_node("action", tool_node)

# Set the entrypoint as `agent`
# This means that this node is the first one called
workflow.add_edge(START, "agent")

# We now add a conditional edge
workflow.add_conditional_edges(
    # First, we define the start node. We use `agent`.
    # This means these are the edges taken after the `agent` node is called.
    "agent",
    # Next, we pass in the function that will determine which node is called next.
    should_continue,
    # Finally we pass in a mapping.
    # The keys are strings, and the values are other nodes.
    # END is a special node marking that the graph should finish.
    # What will happen is we will call `should_continue`, and then the output of that
    # will be matched against the keys in this mapping.
    # Based on which one it matches, that node will then be called.
    {
        # If `tools`, then we call the tool node.
        "continue": "action",
        # Otherwise we finish.
        "end": END,
    },
)

# We now add a normal edge from `tools` to `agent`.
# This means that after `tools` is called, `agent` node is called next.
workflow.add_edge("action", "agent")

# Set up memory
memory = MemorySaver()

# Finally, we compile it!
# This compiles it into a LangChain Runnable,
# meaning you can use it as you would any other runnable

# We add in `interrupt_before=["action"]`
# This will add a breakpoint before the `action` node is called
app = workflow.compile(checkpointer=memory)

API Reference: ChatOpenAI | tool | START | ToolNode | END | StateGraph | MemorySaver

与代理交互

我们现在可以与代理进行交互了。让我们让它播放泰勒·斯威夫特最受欢迎的歌曲:

from langchain_core.messages import HumanMessage

config = {"configurable": {"thread_id": "1"}}
input_message = HumanMessage(content="Can you play Taylor Swift's most popular song?")
for event in app.stream({"messages": [input_message]}, config, stream_mode="values"):
    event["messages"][-1].pretty_print()

API Reference: HumanMessage

================================ Human Message =================================

Can you play Taylor Swift's most popular song?
================================== Ai Message ==================================
Tool Calls:
  play_song_on_apple (call_uhGY6Fv6Mr4ZOhSokintuoD7)
 Call ID: call_uhGY6Fv6Mr4ZOhSokintuoD7
  Args:
    song: Anti-Hero by Taylor Swift
================================= Tool Message =================================
Name: play_song_on_apple

Succesfully played Anti-Hero by Taylor Swift on Apple Music!
================================== Ai Message ==================================

I've successfully played "Anti-Hero" by Taylor Swift on Apple Music! Enjoy the music!

查看历史

让我们从头到尾浏览这个线程的历史记录。

app.get_state(config).values["messages"]
[HumanMessage(content="Can you play Taylor Swift's most popular song?", id='7e32f0f3-75f5-48e1-a4ae-d38ccc15973b'),
 AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'function': {'arguments': '{"song":"Anti-Hero by Taylor Swift"}', 'name': 'play_song_on_apple'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 80, 'total_tokens': 102}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-af077bc4-f03c-4afe-8d92-78bdae394412-0', tool_calls=[{'name': 'play_song_on_apple', 'args': {'song': 'Anti-Hero by Taylor Swift'}, 'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 80, 'output_tokens': 22, 'total_tokens': 102}),
 ToolMessage(content='Succesfully played Anti-Hero by Taylor Swift on Apple Music!', name='play_song_on_apple', id='43a39ca7-326a-4033-8607-bf061615ed6b', tool_call_id='call_uhGY6Fv6Mr4ZOhSokintuoD7'),
 AIMessage(content='I\'ve successfully played "Anti-Hero" by Taylor Swift on Apple Music! Enjoy the music!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 126, 'total_tokens': 146}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'stop', 'logprobs': None}, id='run-bfee6b28-9f16-49cc-8d28-bfb5a5b9aea1-0', usage_metadata={'input_tokens': 126, 'output_tokens': 20, 'total_tokens': 146})]

all_states = []
for state in app.get_state_history(config):
    print(state)
    all_states.append(state)
    print("--")
StateSnapshot(values={'messages': [HumanMessage(content="Can you play Taylor Swift's most popular song?", id='7e32f0f3-75f5-48e1-a4ae-d38ccc15973b'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'function': {'arguments': '{"song":"Anti-Hero by Taylor Swift"}', 'name': 'play_song_on_apple'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 80, 'total_tokens': 102}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-af077bc4-f03c-4afe-8d92-78bdae394412-0', tool_calls=[{'name': 'play_song_on_apple', 'args': {'song': 'Anti-Hero by Taylor Swift'}, 'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 80, 'output_tokens': 22, 'total_tokens': 102}), ToolMessage(content='Succesfully played Anti-Hero by Taylor Swift on Apple Music!', name='play_song_on_apple', id='43a39ca7-326a-4033-8607-bf061615ed6b', tool_call_id='call_uhGY6Fv6Mr4ZOhSokintuoD7'), AIMessage(content='I\'ve successfully played "Anti-Hero" by Taylor Swift on Apple Music! Enjoy the music!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 126, 'total_tokens': 146}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'stop', 'logprobs': None}, id='run-bfee6b28-9f16-49cc-8d28-bfb5a5b9aea1-0', usage_metadata={'input_tokens': 126, 'output_tokens': 20, 'total_tokens': 146})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6bcf1-364f-6228-8003-dd67a426334e'}}, metadata={'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='I\'ve successfully played "Anti-Hero" by Taylor Swift on Apple Music! Enjoy the music!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 126, 'total_tokens': 146}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'stop', 'logprobs': None}, id='run-bfee6b28-9f16-49cc-8d28-bfb5a5b9aea1-0', usage_metadata={'input_tokens': 126, 'output_tokens': 20, 'total_tokens': 146})]}}, 'step': 3, 'parents': {}}, created_at='2024-09-05T21:37:39.955948+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6bcf1-318f-6dc8-8002-dbdf9aaeac83'}}, tasks=())
--
StateSnapshot(values={'messages': [HumanMessage(content="Can you play Taylor Swift's most popular song?", id='7e32f0f3-75f5-48e1-a4ae-d38ccc15973b'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'function': {'arguments': '{"song":"Anti-Hero by Taylor Swift"}', 'name': 'play_song_on_apple'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 80, 'total_tokens': 102}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-af077bc4-f03c-4afe-8d92-78bdae394412-0', tool_calls=[{'name': 'play_song_on_apple', 'args': {'song': 'Anti-Hero by Taylor Swift'}, 'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 80, 'output_tokens': 22, 'total_tokens': 102}), ToolMessage(content='Succesfully played Anti-Hero by Taylor Swift on Apple Music!', name='play_song_on_apple', id='43a39ca7-326a-4033-8607-bf061615ed6b', tool_call_id='call_uhGY6Fv6Mr4ZOhSokintuoD7')]}, next=('agent',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6bcf1-318f-6dc8-8002-dbdf9aaeac83'}}, metadata={'source': 'loop', 'writes': {'action': {'messages': [ToolMessage(content='Succesfully played Anti-Hero by Taylor Swift on Apple Music!', name='play_song_on_apple', id='43a39ca7-326a-4033-8607-bf061615ed6b', tool_call_id='call_uhGY6Fv6Mr4ZOhSokintuoD7')]}}, 'step': 2, 'parents': {}}, created_at='2024-09-05T21:37:39.458185+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6bcf1-3185-663e-8001-12b1ec3114b8'}}, tasks=(PregelTask(id='3a4c5ddb-14b2-5def-a766-02ddc32948ba', name='agent', error=None, interrupts=(), state=None),))
--
StateSnapshot(values={'messages': [HumanMessage(content="Can you play Taylor Swift's most popular song?", id='7e32f0f3-75f5-48e1-a4ae-d38ccc15973b'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'function': {'arguments': '{"song":"Anti-Hero by Taylor Swift"}', 'name': 'play_song_on_apple'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 80, 'total_tokens': 102}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-af077bc4-f03c-4afe-8d92-78bdae394412-0', tool_calls=[{'name': 'play_song_on_apple', 'args': {'song': 'Anti-Hero by Taylor Swift'}, 'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 80, 'output_tokens': 22, 'total_tokens': 102})]}, next=('action',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6bcf1-3185-663e-8001-12b1ec3114b8'}}, metadata={'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'function': {'arguments': '{"song":"Anti-Hero by Taylor Swift"}', 'name': 'play_song_on_apple'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 80, 'total_tokens': 102}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-af077bc4-f03c-4afe-8d92-78bdae394412-0', tool_calls=[{'name': 'play_song_on_apple', 'args': {'song': 'Anti-Hero by Taylor Swift'}, 'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 80, 'output_tokens': 22, 'total_tokens': 102})]}}, 'step': 1, 'parents': {}}, created_at='2024-09-05T21:37:39.453898+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6bcf1-29b8-6370-8000-f9f6e7ca1b06'}}, tasks=(PregelTask(id='01f1dc72-5a39-5876-97a6-abdc12f70c2a', name='action', error=None, interrupts=(), state=None),))
--
StateSnapshot(values={'messages': [HumanMessage(content="Can you play Taylor Swift's most popular song?", id='7e32f0f3-75f5-48e1-a4ae-d38ccc15973b')]}, next=('agent',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6bcf1-29b8-6370-8000-f9f6e7ca1b06'}}, metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}}, created_at='2024-09-05T21:37:38.635849+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6bcf1-29b3-6514-bfff-fe07fb36f14f'}}, tasks=(PregelTask(id='348e1ba7-95c6-5b89-80c9-1fc4720e35ef', name='agent', error=None, interrupts=(), state=None),))
--
StateSnapshot(values={'messages': []}, next=('__start__',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6bcf1-29b3-6514-bfff-fe07fb36f14f'}}, metadata={'source': 'input', 'writes': {'__start__': {'messages': [HumanMessage(content="Can you play Taylor Swift's most popular song?")]}}, 'step': -1, 'parents': {}}, created_at='2024-09-05T21:37:38.633849+00:00', parent_config=None, tasks=(PregelTask(id='f1cfbb8c-7792-5cf9-9d28-ae3ac7724cf3', name='__start__', error=None, interrupts=(), state=None),))
--

重新播放某个状态

我们可以回到这些状态中的任何一个,并从那里重新启动代理!让我们回到工具调用执行之前的状态。

to_replay = all_states[2]
to_replay.values
{'messages': [HumanMessage(content="Can you play Taylor Swift's most popular song?", id='7e32f0f3-75f5-48e1-a4ae-d38ccc15973b'),
  AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'function': {'arguments': '{"song":"Anti-Hero by Taylor Swift"}', 'name': 'play_song_on_apple'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 80, 'total_tokens': 102}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-af077bc4-f03c-4afe-8d92-78bdae394412-0', tool_calls=[{'name': 'play_song_on_apple', 'args': {'song': 'Anti-Hero by Taylor Swift'}, 'id': 'call_uhGY6Fv6Mr4ZOhSokintuoD7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 80, 'output_tokens': 22, 'total_tokens': 102})]}
to_replay.next
('action',)

要从这里重新播放,我们只需要将它的配置传回代理即可。请注意,它会从上次中断的地方继续执行——即从调用工具的地方恢复。

for event in app.stream(None, to_replay.config):
    for v in event.values():
        print(v)
{'messages': [ToolMessage(content='Succesfully played Anti-Hero by Taylor Swift on Apple Music!', name='play_song_on_apple', tool_call_id='call_uhGY6Fv6Mr4ZOhSokintuoD7')]}
{'messages': [AIMessage(content='I\'ve started playing "Anti-Hero" by Taylor Swift on Apple Music! Enjoy the music!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 126, 'total_tokens': 146}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'stop', 'logprobs': None}, id='run-dc338bbd-d623-40bb-b824-5d2307954b57-0', usage_metadata={'input_tokens': 126, 'output_tokens': 20, 'total_tokens': 146})]}

从过去的某个状态分支

使用LangGraph的检查点功能,你不仅可以回放过去的某个状态,还可以从之前的某个位置分支出去,让代理探索不同的轨迹,或者让用户对工作流中的更改进行“版本控制”。

让我们展示如何编辑某个时间点的状态。我们将更新状态,使其从在Apple上播放歌曲改为在Spotify上播放:

# Let's now get the last message in the state
# This is the one with the tool calls that we want to update
last_message = to_replay.values["messages"][-1]


# Let's now update the tool we are calling
last_message.tool_calls[0]["name"] = "play_song_on_spotify"

branch_config = app.update_state(
    to_replay.config,
    {"messages": [last_message]},
)

然后我们可以使用这个新的branch_config来从这里继续运行,并改变状态。从日志中可以看到,工具被调用时输入的内容不同。

for event in app.stream(None, branch_config):
    for v in event.values():
        print(v)
{'messages': [ToolMessage(content='Succesfully played Anti-Hero by Taylor Swift on Spotify!', name='play_song_on_spotify', tool_call_id='call_uhGY6Fv6Mr4ZOhSokintuoD7')]}
{'messages': [AIMessage(content='I\'ve started playing "Anti-Hero" by Taylor Swift on Spotify. Enjoy the music!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 125, 'total_tokens': 144}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'stop', 'logprobs': None}, id='run-7d8d5094-7029-4da3-9e0e-ef9d18b63615-0', usage_metadata={'input_tokens': 125, 'output_tokens': 19, 'total_tokens': 144})]}
或者,我们可以更新状态,使其根本不调用任何工具!

from langchain_core.messages import AIMessage

# Let's now get the last message in the state
# This is the one with the tool calls that we want to update
last_message = to_replay.values["messages"][-1]

# Let's now get the ID for the last message, and create a new message with that ID.
new_message = AIMessage(
    content="It's quiet hours so I can't play any music right now!", id=last_message.id
)

branch_config = app.update_state(
    to_replay.config,
    {"messages": [new_message]},
)

API Reference: AIMessage

branch_state = app.get_state(branch_config)
branch_state.values
{'messages': [HumanMessage(content="Can you play Taylor Swift's most popular song?", id='7e32f0f3-75f5-48e1-a4ae-d38ccc15973b'),
  AIMessage(content="It's quiet hours so I can't play any music right now!", id='run-af077bc4-f03c-4afe-8d92-78bdae394412-0')]}
branch_state.next
()

你可以看到快照已被更新,并且现在正确地反映了没有下一步的事实。

Comments