Skip to content

如何在 ReAct 代理中管理对话历史

前提条件

本指南假设你熟悉以下内容:

无论你是在构建具有多次对话轮次的聊天机器人,还是构建具有大量工具调用的智能体系统,消息历史都可能会迅速增长并超出大语言模型(LLM)的上下文窗口大小。有几种管理消息历史的策略:

  • 消息修剪 - 删除历史记录中的前 N 条或后 N 条消息
  • 摘要生成 - 对历史记录中的早期消息进行摘要,并将其替换为摘要
  • 自定义策略(例如,消息过滤等)

要在 create_react_agent 中管理消息历史,你需要定义一个 pre_model_hook 函数或 可运行对象,该函数或对象接受图状态并返回状态更新:

  • 修剪示例:

    from langchain_core.messages.utils import (
        trim_messages, 
        count_tokens_approximately
    )
    from langgraph.prebuilt import create_react_agent
    
    # 此函数将在每次调用大语言模型的节点之前被调用
    def pre_model_hook(state):
        trimmed_messages = trim_messages(
            state["messages"],
            strategy="last",
            token_counter=count_tokens_approximately,
            max_tokens=384,
            start_on="human",
            end_on=("human", "tool"),
        )
        # 你可以将更新后的消息以 `llm_input_messages` 或 `messages` 键返回(见下面的注释)
        return {"llm_input_messages": trimmed_messages}
    
    checkpointer = InMemorySaver()
    agent = create_react_agent(
        model,
        tools,
        pre_model_hook=pre_model_hook,
        checkpointer=checkpointer,
    )
    

  • 摘要生成示例:

    from langmem.short_term import SummarizationNode
    from langchain_core.messages.utils import count_tokens_approximately
    from langgraph.prebuilt.chat_agent_executor import AgentState
    from langgraph.checkpoint.memory import InMemorySaver
    from typing import Any
    
    model = ChatOpenAI(model="gpt-4o")
    
    summarization_node = SummarizationNode(
        token_counter=count_tokens_approximately,
        model=model,
        max_tokens=384,
        max_summary_tokens=128,
        output_messages_key="llm_input_messages",
    )
    
    class State(AgentState):
        # 注意:我们添加此键是为了跟踪先前的摘要信息
        # 以确保我们不会在每次调用大语言模型时都进行摘要
        context: dict[str, Any]
    
    
    checkpointer = InMemorySaver()
    graph = create_react_agent(
        model,
        tools,
        pre_model_hook=summarization_node,
        state_schema=State,
        checkpointer=checkpointer,
    )
    

Important

  • 若要**保持图状态中的原始消息历史不变**,并**仅将更新后的历史记录作为大语言模型的输入**,请以 llm_input_messages 键返回更新后的消息
  • 若要**用更新后的历史记录覆盖图状态中的原始消息历史**,请以 messages 键返回更新后的消息

若要覆盖 messages 键,你需要执行以下操作:

from langchain_core.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES

def pre_model_hook(state):
    updated_messages = ...
    return {
        "messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES), *updated_messages]
        ...
    }

安装设置

首先,让我们安装所需的软件包并设置我们的 API 密钥。

%%capture --no-stderr
%pip install -U langgraph langchain-openai langmem
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")

为 LangGraph 开发设置 LangSmith

注册 LangSmith 以快速发现问题并提升你的 LangGraph 项目的性能。LangSmith 允许你使用跟踪数据来调试、测试和监控使用 LangGraph 构建的大语言模型应用程序 — 点击 此处 了解更多关于如何开始使用的信息。

保持原始消息历史记录不变

让我们构建一个带有管理对话历史步骤的 ReAct 智能体:当历史记录的令牌数量超过指定数量时,我们将调用 trim_messages 实用工具,该工具会在满足大语言模型(LLM)提供商限制的同时缩减历史记录。

有两种方式可以在 ReAct 智能体内部应用更新后的消息历史记录:

让我们从实现第一种方式开始。我们首先需要为智能体定义模型和工具:

from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o", temperature=0)


def get_weather(location: str) -> str:
    """Use this to get weather information."""
    if any([city in location.lower() for city in ["nyc", "new york city"]]):
        return "It might be cloudy in nyc, with a chance of rain and temperatures up to 80 degrees."
    elif any([city in location.lower() for city in ["sf", "san francisco"]]):
        return "It's always sunny in sf"
    else:
        return f"I am not sure what the weather is in {location}"


tools = [get_weather]

现在让我们来实现 pre_model_hook —— 这是一个将作为新节点添加的函数,每次在调用大语言模型(LLM)的节点(即 agent 节点)**之前**都会被调用。

我们的实现将包装 trim_messages 调用,并在 llm_input_messages 下返回修剪后的消息。这将 保持图状态中的原始消息历史不变,并且 仅将更新后的历史作为输入传递给大语言模型

from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver

from langchain_core.messages.utils import (
    trim_messages,
    count_tokens_approximately,
)


# This function will be added as a new node in ReAct agent graph
# that will run every time before the node that calls the LLM.
# The messages returned by this function will be the input to the LLM.
def pre_model_hook(state):
    trimmed_messages = trim_messages(
        state["messages"],
        strategy="last",
        token_counter=count_tokens_approximately,
        max_tokens=384,
        start_on="human",
        end_on=("human", "tool"),
    )
    return {"llm_input_messages": trimmed_messages}


checkpointer = InMemorySaver()
graph = create_react_agent(
    model,
    tools,
    pre_model_hook=pre_model_hook,
    checkpointer=checkpointer,
)

API Reference: trim_messages

from IPython.display import display, Image

display(Image(graph.get_graph().draw_mermaid_png()))

我们还将定义一个实用工具,用于美观地渲染智能体的输出:

def print_stream(stream, output_messages_key="llm_input_messages"):
    for chunk in stream:
        for node, update in chunk.items():
            print(f"Update from node: {node}")
            messages_key = (
                output_messages_key if node == "pre_model_hook" else "messages"
            )
            for message in update[messages_key]:
                if isinstance(message, tuple):
                    print(message)
                else:
                    message.pretty_print()

        print("\n\n")

现在,让我们使用几个不同的查询来运行代理,以达到指定的最大令牌数限制:

config = {"configurable": {"thread_id": "1"}}

inputs = {"messages": [("user", "What's the weather in NYC?")]}
result = graph.invoke(inputs, config=config)

inputs = {"messages": [("user", "What's it known for?")]}
result = graph.invoke(inputs, config=config)

让我们看看到目前为止消息历史记录中有多少个令牌:

messages = result["messages"]
count_tokens_approximately(messages)
415

你可以看到,我们已接近 max_tokens 阈值,因此在下一次调用时,我们应该会看到 pre_model_hook 开始起作用并修剪消息历史记录。让我们再次运行它:

inputs = {"messages": [("user", "where can i find the best bagel?")]}
print_stream(graph.stream(inputs, config=config, stream_mode="updates"))
Update from node: pre_model_hook
================================ Human Message =================================

What's it known for?
================================== Ai Message ==================================

New York City is known for a variety of iconic landmarks, cultural institutions, and vibrant neighborhoods. Some of the most notable features include:

1. **Statue of Liberty**: A symbol of freedom and democracy, located on Liberty Island.
2. **Times Square**: Known for its bright lights, Broadway theaters, and bustling atmosphere.
3. **Central Park**: A large public park offering a natural retreat in the middle of the city.
4. **Empire State Building**: An iconic skyscraper offering panoramic views of the city.
5. **Broadway**: Famous for its world-class theater productions.
6. **Wall Street**: The financial hub of the United States.
7. **Museums**: Including the Metropolitan Museum of Art, Museum of Modern Art (MoMA), and the American Museum of Natural History.
8. **Diverse Cuisine**: A melting pot of cultures offering a wide range of culinary experiences.
9. **Cultural Diversity**: A rich tapestry of cultures and communities from around the world.
10. **Fashion**: A global fashion capital, hosting events like New York Fashion Week.

These are just a few highlights of what makes New York City a unique and vibrant place.
================================ Human Message =================================

where can i find the best bagel?



Update from node: agent
================================== Ai Message ==================================

New York City is famous for its bagels, and there are several places renowned for serving some of the best. Here are a few top spots where you can find excellent bagels in NYC:

1. **Ess-a-Bagel**: Known for their large, chewy bagels with a variety of spreads and toppings.
2. **Russ & Daughters**: A classic spot offering traditional bagels with high-quality smoked fish and cream cheese.
3. **H&H Bagels**: Famous for their fresh, hand-rolled bagels.
4. **Murray’s Bagels**: Offers a wide selection of bagels and spreads, with a no-toasting policy to preserve freshness.
5. **Absolute Bagels**: Known for their authentic, fluffy bagels and a variety of cream cheese options.
6. **Tompkins Square Bagels**: Offers creative bagel sandwiches and a wide range of spreads.
7. **Bagel Hole**: Known for their smaller, denser bagels with a crispy crust.

Each of these places has its own unique style and flavor, so it might be worth trying a few to find your personal favorite!
如预期的那样,你可以看到 pre_model_hook 节点现在仅返回了最后 3 条消息。不过,现有的消息历史记录并未受到影响:

updated_messages = graph.get_state(config).values["messages"]
assert [(m.type, m.content) for m in updated_messages[: len(messages)]] == [
    (m.type, m.content) for m in messages
]

覆盖原始消息历史记录

现在,让我们将 pre_model_hook 更改为**覆盖**图状态中的消息历史记录。为此,我们将在 messages 键下返回更新后的消息。我们还将包含一个特殊的 RemoveMessage(REMOVE_ALL_MESSAGES) 对象,该对象会告知 create_react_agent 从图状态中移除之前的消息:

from langchain_core.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES


def pre_model_hook(state):
    trimmed_messages = trim_messages(
        state["messages"],
        strategy="last",
        token_counter=count_tokens_approximately,
        max_tokens=384,
        start_on="human",
        end_on=("human", "tool"),
    )
    # NOTE that we're now returning the messages under the `messages` key
    # We also remove the existing messages in the history to ensure we're overwriting the history
    return {"messages": [RemoveMessage(REMOVE_ALL_MESSAGES)] + trimmed_messages}


checkpointer = InMemorySaver()
graph = create_react_agent(
    model,
    tools,
    pre_model_hook=pre_model_hook,
    checkpointer=checkpointer,
)

API Reference: RemoveMessage

现在,让我们使用与之前相同的查询来运行代理:

config = {"configurable": {"thread_id": "1"}}

inputs = {"messages": [("user", "What's the weather in NYC?")]}
result = graph.invoke(inputs, config=config)

inputs = {"messages": [("user", "What's it known for?")]}
result = graph.invoke(inputs, config=config)
messages = result["messages"]

inputs = {"messages": [("user", "where can i find the best bagel?")]}
print_stream(
    graph.stream(inputs, config=config, stream_mode="updates"),
    output_messages_key="messages",
)
Update from node: pre_model_hook
================================ Remove Message ================================


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

What's it known for?
================================== Ai Message ==================================

New York City is known for a variety of iconic landmarks, cultural institutions, and vibrant neighborhoods. Some of the most notable features include:

1. **Statue of Liberty**: A symbol of freedom and democracy, located on Liberty Island.
2. **Times Square**: Known for its bright lights, Broadway theaters, and bustling atmosphere.
3. **Central Park**: A large public park offering a natural oasis amidst the urban environment.
4. **Empire State Building**: An iconic skyscraper offering panoramic views of the city.
5. **Broadway**: Famous for its world-class theater productions and musicals.
6. **Wall Street**: The financial hub of the United States, located in the Financial District.
7. **Museums**: Including the Metropolitan Museum of Art, Museum of Modern Art (MoMA), and the American Museum of Natural History.
8. **Diverse Cuisine**: A melting pot of cultures, offering a wide range of international foods.
9. **Cultural Diversity**: Known for its diverse population and vibrant cultural scene.
10. **Brooklyn Bridge**: An iconic suspension bridge connecting Manhattan and Brooklyn.

These are just a few highlights, as NYC is a city with endless attractions and activities.
================================ Human Message =================================

where can i find the best bagel?



Update from node: agent
================================== Ai Message ==================================

New York City is famous for its bagels, and there are several places renowned for serving some of the best. Here are a few top spots where you can find delicious bagels in NYC:

1. **Ess-a-Bagel**: Known for its large, chewy bagels and a wide variety of spreads and toppings. Locations in Midtown and the East Village.

2. **Russ & Daughters**: A historic appetizing store on the Lower East Side, famous for its bagels with lox and cream cheese.

3. **Absolute Bagels**: Located on the Upper West Side, this spot is popular for its fresh, fluffy bagels.

4. **Murray’s Bagels**: Known for its traditional, hand-rolled bagels. Located in Greenwich Village.

5. **Tompkins Square Bagels**: Offers a wide selection of bagels and creative cream cheese flavors. Located in the East Village.

6. **Bagel Hole**: A small shop in Park Slope, Brooklyn, known for its classic, no-frills bagels.

7. **Leo’s Bagels**: Located in the Financial District, known for its authentic New York-style bagels.

Each of these places has its own unique style and flavor, so it might be worth trying a few to find your personal favorite!
你可以看到,pre_model_hook 节点再次返回了最后 3 条消息。不过,这一次,消息历史记录也在图状态中被修改了:

updated_messages = graph.get_state(config).values["messages"]
assert (
    # First 2 messages in the new history are the same as last 2 messages in the old
    [(m.type, m.content) for m in updated_messages[:2]]
    == [(m.type, m.content) for m in messages[-2:]]
)

总结消息历史记录

最后,让我们采用一种不同的消息历史管理策略——摘要。与修剪操作一样,你可以选择保持原始消息历史不变,或者覆盖它。下面的示例仅展示前者。

我们将使用预构建的 langmem 库中的 SummarizationNode。一旦消息历史达到令牌限制,摘要节点将对早期消息进行摘要处理,以确保它们能适应 max_tokens 的限制。

from langmem.short_term import SummarizationNode
from langgraph.prebuilt.chat_agent_executor import AgentState
from typing import Any

model = ChatOpenAI(model="gpt-4o")
summarization_model = model.bind(max_tokens=128)

summarization_node = SummarizationNode(
    token_counter=count_tokens_approximately,
    model=summarization_model,
    max_tokens=384,
    max_summary_tokens=128,
    output_messages_key="llm_input_messages",
)


class State(AgentState):
    # NOTE: we're adding this key to keep track of previous summary information
    # to make sure we're not summarizing on every LLM call
    context: dict[str, Any]


checkpointer = InMemorySaver()
graph = create_react_agent(
    model,
    tools,
    pre_model_hook=summarization_node,
    state_schema=State,
    checkpointer=checkpointer,
)

config = {"configurable": {"thread_id": "1"}}
inputs = {"messages": [("user", "What's the weather in NYC?")]}

result = graph.invoke(inputs, config=config)

inputs = {"messages": [("user", "What's it known for?")]}
result = graph.invoke(inputs, config=config)

inputs = {"messages": [("user", "where can i find the best bagel?")]}
print_stream(graph.stream(inputs, config=config, stream_mode="updates"))
Update from node: pre_model_hook
================================ System Message ================================

Summary of the conversation so far: The user asked about the current weather in New York City. In response, the assistant provided information that it might be cloudy, with a chance of rain, and temperatures reaching up to 80 degrees.
================================ Human Message =================================

What's it known for?
================================== Ai Message ==================================

New York City, often referred to as NYC, is known for its:

1. **Landmarks and Iconic Sites**:
   - **Statue of Liberty**: A symbol of freedom and democracy.
   - **Central Park**: A vast green oasis in the middle of the city.
   - **Empire State Building**: Once the tallest building in the world, offering stunning views of the city.
   - **Times Square**: Known for its bright lights and bustling atmosphere.

2. **Cultural Institutions**:
   - **Broadway**: Renowned for theatrical performances and musicals.
   - **Metropolitan Museum of Art** and **Museum of Modern Art (MoMA)**: World-class art collections.
   - **American Museum of Natural History**: Known for its extensive exhibits ranging from dinosaurs to space exploration.

3. **Diverse Neighborhoods and Cuisine**:
   - NYC is famous for having a melting pot of cultures, reflected in neighborhoods like Chinatown, Little Italy, and Harlem.
   - The city offers a wide range of international cuisines, from street food to high-end dining.

4. **Financial District**:
   - Home to Wall Street, the New York Stock Exchange (NYSE), and other major financial institutions.

5. **Media and Entertainment**:
   - Major hub for television, film, and media, with numerous studios and networks based there.

6. **Fashion**:
   - Often referred to as one of the "Big Four" fashion capitals, hosting events like New York Fashion Week.

7. **Sports**:
   - Known for its passionate sports culture with teams like the Yankees (MLB), Mets (MLB), Knicks (NBA), and Rangers (NHL).

These elements, among others, contribute to NYC's reputation as a vibrant and dynamic city.
================================ Human Message =================================

where can i find the best bagel?



Update from node: agent
================================== Ai Message ==================================

Finding the best bagel in New York City can be subjective, as there are many beloved spots across the city. However, here are some renowned bagel shops you might want to try:

1. **Ess-a-Bagel**: Known for its chewy and flavorful bagels, located in Midtown and Stuyvesant Town.

2. **Bagel Hole**: A favorite for traditionalists, offering classic and dense bagels, located in Park Slope, Brooklyn.

3. **Russ & Daughters**: A legendary appetizing store on the Lower East Side, famous for their bagels with lox.

4. **Murray’s Bagels**: Located in Greenwich Village, known for their fresh and authentic New York bagels.

5. **Absolute Bagels**: Located on the Upper West Side, they’re known for their fresh, fluffy bagels with a variety of spreads.

6. **Tompkins Square Bagels**: In the East Village, famous for their creative cream cheese options and fresh bagels.

7. **Zabar’s**: A landmark on the Upper West Side known for their classic bagels and smoked fish.

Each of these spots offers a unique take on the classic New York bagel experience, and trying several might be the best way to discover your personal favorite!
你可以看到,之前的消息现在已被之前对话的摘要所取代!

Comments