diff --git a/docs/_scripts/notebook_hooks.py b/docs/_scripts/notebook_hooks.py index c90de28f5..463680cc6 100644 --- a/docs/_scripts/notebook_hooks.py +++ b/docs/_scripts/notebook_hooks.py @@ -49,19 +49,23 @@ REDIRECT_MAP = { "how-tos/return-when-recursion-limit-hits.ipynb": "how-tos/graph-api/#impose-a-recursion-limit", "how-tos/async.ipynb": "how-tos/graph-api/#async", # memory how-tos - "how-tos/memory/manage-conversation-history.ipynb": "how-tos/memory.ipynb", - "how-tos/memory/delete-messages.ipynb": "how-tos/memory.ipynb#delete-messages", - "how-tos/memory/add-summary-conversation-history.ipynb": "how-tos/memory.ipynb#summarize-messages", + "how-tos/memory/manage-conversation-history.ipynb": "how-tos/memory/add-memory.md", + "how-tos/memory/delete-messages.ipynb": "how-tos/memory/add-memory.md#delete-messages", + "how-tos/memory/add-summary-conversation-history.ipynb": "how-tos/memory/add-memory.md#summarize-messages", + "how-tos/memory.ipynb": "how-tos/memory/add-memory.md", + "agents/memory.ipynb": "how-tos/memory/add-memory.md", # subgraph how-tos "how-tos/subgraph-transform-state.ipynb": "how-tos/subgraph.ipynb#different-state-schemas", "how-tos/subgraphs-manage-state.ipynb": "how-tos/subgraph.ipynb#add-persistence", # persistence how-tos - "how-tos/persistence_postgres.ipynb": "how-tos/persistence.ipynb#use-in-production", - "how-tos/persistence_mongodb.ipynb": "how-tos/persistence.ipynb#use-in-production", - "how-tos/persistence_redis.ipynb": "how-tos/persistence.ipynb#use-in-production", - "how-tos/subgraph-persistence.ipynb": "how-tos/persistence.ipynb#use-with-subgraphs", - "how-tos/cross-thread-persistence.ipynb": "how-tos/persistence.ipynb#add-long-term-memory", + "how-tos/persistence_postgres.ipynb": "how-tos/memory/add-memory.md#use-in-production", + "how-tos/persistence_mongodb.ipynb": "how-tos/memory/add-memory.md#use-in-production", + "how-tos/persistence_redis.ipynb": "how-tos/memory/add-memory.md#use-in-production", + "how-tos/subgraph-persistence.ipynb": "how-tos/memory/add-memory.md#use-with-subgraphs", + "how-tos/cross-thread-persistence.ipynb": "how-tos/memory/add-memory.md#add-long-term-memory", "cloud/how-tos/copy_threads": "cloud/how-tos/use_threads", + "cloud/concepts/threads.md": "concepts/persistence.md#threads", + "how-tos/persistence.ipynb": "how-tos/memory/add-memory.md", # tool calling how-tos "how-tos/tool-calling-errors.ipynb": "how-tos/tool-calling.ipynb#handle-errors", "how-tos/pass-config-to-tools.ipynb": "how-tos/tool-calling.ipynb#access-config", @@ -87,6 +91,8 @@ REDIRECT_MAP = { "cloud/how-tos/stream_events.md": "cloud/how-tos/streaming.md#stream-events", "cloud/how-tos/stream_debug.md": "cloud/how-tos/streaming.md#debug", "cloud/how-tos/stream_multiple.md": "cloud/how-tos/streaming.md#stream-multiple-modes", + "cloud/concepts/streaming.md": "concepts/streaming.md", + "agents/streaming.md": "how-tos/streaming.md", # prebuilt redirects "how-tos/create-react-agent.ipynb": "agents/agents.md#basic-configuration", "how-tos/create-react-agent-memory.ipynb": "agents/memory.md", @@ -108,8 +114,10 @@ REDIRECT_MAP = { # deployment redirects "how-tos/deploy-self-hosted.md": "cloud/deployment/self_hosted_data_plane.md", "concepts/self_hosted.md": "concepts/langgraph_self_hosted_data_plane.md", + "tutorials/deployment.md": "concepts/deployment_options.md", # assistant redirects "cloud/how-tos/assistant_versioning.md": "cloud/how-tos/configuration_cloud.md", + "cloud/concepts/runs.md": "concepts/assistants.md#execution", } diff --git a/docs/docs/agents/agents.md b/docs/docs/agents/agents.md index d33badda5..c3383fcd6 100644 --- a/docs/docs/agents/agents.md +++ b/docs/docs/agents/agents.md @@ -180,14 +180,14 @@ ny_response = agent.invoke( ) ``` -1. `checkpointer` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](./memory.md#short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities. +1. `checkpointer` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities. 2. Pass configuration with `thread_id` to be able to resume the same conversation on future agent invocations. When you enable the checkpointer, it stores agent state at every step in the provided checkpointer database (or in memory, if using `InMemorySaver`). Note that in the above example, when the agent is invoked the second time with the same `thread_id`, the original message history from the first conversation is automatically included, together with the new user input. -For more information, see [Memory](./memory.md). +For more information, see [Memory](../how-tos/memory/add-memory.md). ## 6. Configure structured output diff --git a/docs/docs/agents/context.md b/docs/docs/agents/context.md index f5d96306c..97e86ecb8 100644 --- a/docs/docs/agents/context.md +++ b/docs/docs/agents/context.md @@ -74,14 +74,14 @@ agent.invoke({ !!! tip "Turning on memory" - Please see the [memory guide](./memory.md) for more details on how to enable memory. This is a powerful feature that allows you to persist the agent's state across multiple invocations. + Please see the [memory guide](../how-tos/memory/add-memory.md) for more details on how to enable memory. This is a powerful feature that allows you to persist the agent's state across multiple invocations. Otherwise, the state is scoped only to a single agent run. ### Long-Term Memory (cross-conversation context) -For context that spans *across* conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions). For more, see the [Memory guide](./memory.md). +For context that spans *across* conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions). For more, see the [Memory guide](../how-tos/memory/add-memory.md). ## Customizing Prompts with Context { #prompts } @@ -233,4 +233,4 @@ Tools can access context through special parameter **annotations**. ### Update Context from Tools -Tools can update agent's context (state and long-term memory) during execution. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts. See [Memory](./memory.md#read-short-term) guide for more information. \ No newline at end of file +Tools can update agent's context (state and long-term memory) during execution. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts. See [Memory](../how-tos/memory/add-memory.md#read-short-term) guide for more information. \ No newline at end of file diff --git a/docs/docs/agents/deployment.md b/docs/docs/agents/deployment.md index 538528549..5e9a6bd07 100644 --- a/docs/docs/agents/deployment.md +++ b/docs/docs/agents/deployment.md @@ -89,4 +89,4 @@ LangGraph Studio Web is a specialized UI that you can connect to LangGraph API s ## Deployment -Once your LangGraph app is running locally, you can deploy it using LangGraph Platform. Refer to the [deployment options guide](../tutorials/deployment.md) for detailed instructions on all supported deployment models. +Once your LangGraph app is running locally, you can deploy it using LangGraph Platform. Refer to the [deployment options guide](../concepts/deployment_options.md) for detailed instructions on all supported deployment models. diff --git a/docs/docs/agents/human-in-the-loop.md b/docs/docs/agents/human-in-the-loop.md index 9ac148e55..44b9f6f88 100644 --- a/docs/docs/agents/human-in-the-loop.md +++ b/docs/docs/agents/human-in-the-loop.md @@ -67,7 +67,7 @@ agent = create_react_agent( ``` 1. The [`interrupt` function][langgraph.types.interrupt] pauses the agent graph at a specific node. In this case, we call `interrupt()` at the beginning of the tool function, which pauses the graph at the node that executes the tool. The information inside `interrupt()` (e.g., tool calls) can be presented to a human, and the graph can be resumed with the user input (tool call approval, edit or feedback). -2. The `InMemorySaver` is used to store the agent state at every step in the tool calling loop. This enables [short-term memory](./memory.md#short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities. In this example, we use `InMemorySaver` to store the agent state in memory. In a production application, the agent state will be stored in a database. +2. The `InMemorySaver` is used to store the agent state at every step in the tool calling loop. This enables [short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities. In this example, we use `InMemorySaver` to store the agent state in memory. In a production application, the agent state will be stored in a database. 3. Initialize the agent with the `checkpointer`. Run the agent with the `stream()` method, passing the `config` object to specify the thread ID. This allows the agent to resume the same conversation on future invocations. diff --git a/docs/docs/agents/memory.md b/docs/docs/agents/memory.md deleted file mode 100644 index 716429f51..000000000 --- a/docs/docs/agents/memory.md +++ /dev/null @@ -1,423 +0,0 @@ ---- -search: - boost: 2 -tags: - - agent -hide: - - tags ---- - -# Memory - -LangGraph supports two types of memory essential for building conversational agents: - -- **[Short-term memory](#short-term-memory)**: Tracks the ongoing conversation by maintaining message history within a session. -- **[Long-term memory](#long-term-memory)**: Stores user-specific or application-level data across sessions. - -This guide demonstrates how to use both memory types with agents in LangGraph. For a deeper -understanding of memory concepts, refer to the [LangGraph memory documentation](../concepts/memory.md). - -
-![image](./assets/memory.png){: style="max-height:400px"} -
Both short-term and long-term memory require persistent storage to maintain continuity across LLM interactions. In production environments, this data is typically stored in a database.
-
- -!!! note "Terminology" - - In LangGraph: - - - *Short-term memory* is also referred to as **thread-level memory**. - - *Long-term memory* is also called **cross-thread memory**. - - A [thread](../concepts/persistence.md#threads) represents a sequence of related runs - grouped by the same `thread_id`. - -## Short-term memory - -Short-term memory enables agents to track multi-turn conversations. To use it, you must: - -1. Provide a `checkpointer` when creating the agent. The `checkpointer` enables [persistence](../concepts/persistence.md) of the agent's state. -2. Supply a `thread_id` in the config when running the agent. The `thread_id` is a unique identifier for the conversation session. - -```python -from langgraph.prebuilt import create_react_agent -from langgraph.checkpoint.memory import InMemorySaver - -# highlight-next-line -checkpointer = InMemorySaver() # (1)! - - -def get_weather(city: str) -> str: - """Get weather for a given city.""" - return f"It's always sunny in {city}!" - - -agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_weather], - # highlight-next-line - checkpointer=checkpointer # (2)! -) - -# Run the agent -config = { - "configurable": { - # highlight-next-line - "thread_id": "1" # (3)! - } -} - -sf_response = agent.invoke( - {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, - # highlight-next-line - config -) - -# Continue the conversation using the same thread_id -ny_response = agent.invoke( - {"messages": [{"role": "user", "content": "what about new york?"}]}, - # highlight-next-line - config # (4)! -) -``` - -1. The `InMemorySaver` is a checkpointer that stores the agent's state in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [checkpointer documentation](../reference/checkpoints.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready checkpointer for you. -2. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations. -3. A unique `thread_id` is provided in the config. This ID is used to identify the conversation session. The value is controlled by the user and can be any string. -4. The agent will continue the conversation using the same `thread_id`. This will allow the agent to infer that the user is asking specifically about the **weather** in New York. - -When the agent is invoked the second time with the same `thread_id`, the original message history from the first conversation is automatically included, allowing the agent to infer that the user is asking specifically about the **weather** in New York. - -!!! Note "LangGraph Platform provides a production-ready checkpointer" - - If you're using [LangGraph Platform](./deployment.md), during deployment your checkpointer will be automatically configured to use a production-ready database. - -### Manage message history - -Long conversations can exceed the LLM's context window. Common solutions are: - -* [Summarization](#summarize-message-history): Maintain a running summary of the conversation -* [Trimming](#trim-message-history): Remove first or last N messages in the history - -This allows the agent to keep track of the conversation without exceeding the LLM's context window. - -To manage message history, specify `pre_model_hook` — a function ([node](../concepts/low_level.md#nodes)) that will always run before calling the language model. - -#### Summarize message history - -
-![image](./assets/summary.png){: style="max-height:400px"} -
Long conversations can exceed the LLM's context window. A common solution is to maintain a running summary of the conversation. This allows the agent to keep track of the conversation without exceeding the LLM's context window. -
-
- -To summarize message history, you can use [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent] with a prebuilt [`SummarizationNode`](https://langchain-ai.github.io/langmem/reference/short_term/#langmem.short_term.SummarizationNode): - -```python -from langchain_anthropic import ChatAnthropic -from langmem.short_term import SummarizationNode -from langchain_core.messages.utils import count_tokens_approximately -from langgraph.prebuilt import create_react_agent -from langgraph.prebuilt.chat_agent_executor import AgentState -from langgraph.checkpoint.memory import InMemorySaver -from typing import Any - -model = ChatAnthropic(model="claude-3-7-sonnet-latest") - -summarization_node = SummarizationNode( # (1)! - token_counter=count_tokens_approximately, - model=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 - # highlight-next-line - context: dict[str, Any] # (2)! - - -checkpointer = InMemorySaver() # (3)! - -agent = create_react_agent( - model=model, - tools=tools, - # highlight-next-line - pre_model_hook=summarization_node, # (4)! - # highlight-next-line - state_schema=State, # (5)! - checkpointer=checkpointer, -) -``` - -1. The `InMemorySaver` is a checkpointer that stores the agent's state in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [checkpointer documentation](../reference/checkpoints.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready checkpointer for you. -2. The `context` key is added to the agent's state. The key contains book-keeping information for the summarization node. It is used to keep track of the last summary information and ensure that the agent doesn't summarize on every LLM call, which can be inefficient. -3. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations. -4. The `pre_model_hook` is set to the `SummarizationNode`. This node will summarize the message history before sending it to the LLM. The summarization node will automatically handle the summarization process and update the agent's state with the new summary. You can replace this with a custom implementation if you prefer. Please see the [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent] API reference for more details. -5. The `state_schema` is set to the `State` class, which is the custom state that contains an extra `context` key. - -#### Trim message history - -To trim message history, you can use [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent] with [`trim_messages`](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html) function: - -```python -# highlight-next-line -from langchain_core.messages.utils import ( - # highlight-next-line - trim_messages, - # highlight-next-line - count_tokens_approximately -# highlight-next-line -) -from langgraph.prebuilt import create_react_agent - -# This function will be called every time before the node that calls 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"), - ) - # highlight-next-line - return {"llm_input_messages": trimmed_messages} - -checkpointer = InMemorySaver() -agent = create_react_agent( - model, - tools, - # highlight-next-line - pre_model_hook=pre_model_hook, - checkpointer=checkpointer, -) -``` - -To learn more about using `pre_model_hook` for managing message history, see this [how-to guide](../how-tos/create-react-agent-manage-message-history.ipynb) - -### Read in tools { #read-short-term } - -LangGraph allows agent to access its short-term memory (state) inside the tools. - -```python -from typing import Annotated -from langgraph.prebuilt import InjectedState, create_react_agent - -class CustomState(AgentState): - # highlight-next-line - user_id: str - -def get_user_info( - # highlight-next-line - state: Annotated[CustomState, InjectedState] -) -> str: - """Look up user info.""" - # highlight-next-line - user_id = state["user_id"] - return "User is John Smith" if user_id == "user_123" else "Unknown user" - -agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_user_info], - # highlight-next-line - state_schema=CustomState, -) - -agent.invoke({ - "messages": "look up user information", - # highlight-next-line - "user_id": "user_123" -}) -``` - -See the [Context](./context.md#__tabbed_2_2) guide for more information. - -### Write from tools { #write-short-term } - -To modify the agent's short-term memory (state) during execution, you can return state updates directly from the tools. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts. - -```python -from typing import Annotated -from langchain_core.tools import InjectedToolCallId -from langchain_core.runnables import RunnableConfig -from langchain_core.messages import ToolMessage -from langgraph.prebuilt import InjectedState, create_react_agent -from langgraph.prebuilt.chat_agent_executor import AgentState -from langgraph.types import Command - -class CustomState(AgentState): - # highlight-next-line - user_name: str - -def update_user_info( - tool_call_id: Annotated[str, InjectedToolCallId], - config: RunnableConfig -) -> Command: - """Look up and update user info.""" - user_id = config["configurable"].get("user_id") - name = "John Smith" if user_id == "user_123" else "Unknown user" - # highlight-next-line - return Command(update={ - # highlight-next-line - "user_name": name, - # update the message history - "messages": [ - ToolMessage( - "Successfully looked up user information", - tool_call_id=tool_call_id - ) - ] - }) - -def greet( - # highlight-next-line - state: Annotated[CustomState, InjectedState] -) -> str: - """Use this to greet the user once you found their info.""" - user_name = state["user_name"] - return f"Hello {user_name}!" - -agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[update_user_info, greet], - # highlight-next-line - state_schema=CustomState -) - -agent.invoke( - {"messages": [{"role": "user", "content": "greet the user"}]}, - # highlight-next-line - config={"configurable": {"user_id": "user_123"}} -) -``` - -For more details, see [how to update state from tools](../how-tos/tool-calling.ipynb#update). - -## Long-term memory - -Use long-term memory to store user-specific or application-specific data across conversations. This is useful for applications like chatbots, where you want to remember user preferences or other information. - -To use long-term memory, you need to: - -1. [Configure a store](../how-tos/persistence.ipynb#add-long-term-memory) to persist data across invocations. -2. Use the [`get_store`][langgraph.config.get_store] function to access the store from within tools or prompts. - -### Read { #read-long-term } - -```python title="A tool the agent can use to look up user information" -from langchain_core.runnables import RunnableConfig -from langgraph.config import get_store -from langgraph.prebuilt import create_react_agent -from langgraph.store.memory import InMemoryStore - -# highlight-next-line -store = InMemoryStore() # (1)! - -# highlight-next-line -store.put( # (2)! - ("users",), # (3)! - "user_123", # (4)! - { - "name": "John Smith", - "language": "English", - } # (5)! -) - -def get_user_info(config: RunnableConfig) -> str: - """Look up user info.""" - # Same as that provided to `create_react_agent` - # highlight-next-line - store = get_store() # (6)! - user_id = config["configurable"].get("user_id") - # highlight-next-line - user_info = store.get(("users",), user_id) # (7)! - return str(user_info.value) if user_info else "Unknown user" - -agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_user_info], - # highlight-next-line - store=store # (8)! -) - -# Run the agent -agent.invoke( - {"messages": [{"role": "user", "content": "look up user information"}]}, - # highlight-next-line - config={"configurable": {"user_id": "user_123"}} -) -``` - -1. The `InMemoryStore` is a store that stores data in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [store documentation](../reference/store.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready store for you. -2. For this example, we write some sample data to the store using the `put` method. Please see the [BaseStore.put][langgraph.store.base.BaseStore.put] API reference for more details. -3. The first argument is the namespace. This is used to group related data together. In this case, we are using the `users` namespace to group user data. -4. A key within the namespace. This example uses a user ID for the key. -5. The data that we want to store for the given user. -6. The `get_store` function is used to access the store. You can call it from anywhere in your code, including tools and prompts. This function returns the store that was passed to the agent when it was created. -7. The `get` method is used to retrieve data from the store. The first argument is the namespace, and the second argument is the key. This will return a `StoreValue` object, which contains the value and metadata about the value. -8. The `store` is passed to the agent. This enables the agent to access the store when running tools. You can also use the `get_store` function to access the store from anywhere in your code. - -### Write { #write-long-term } - -```python title="Example of a tool that updates user information" -from typing_extensions import TypedDict - -from langgraph.config import get_store -from langgraph.prebuilt import create_react_agent -from langgraph.store.memory import InMemoryStore - -store = InMemoryStore() # (1)! - -class UserInfo(TypedDict): # (2)! - name: str - -def save_user_info(user_info: UserInfo, config: RunnableConfig) -> str: # (3)! - """Save user info.""" - # Same as that provided to `create_react_agent` - # highlight-next-line - store = get_store() # (4)! - user_id = config["configurable"].get("user_id") - # highlight-next-line - store.put(("users",), user_id, user_info) # (5)! - return "Successfully saved user info." - -agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[save_user_info], - # highlight-next-line - store=store -) - -# Run the agent -agent.invoke( - {"messages": [{"role": "user", "content": "My name is John Smith"}]}, - # highlight-next-line - config={"configurable": {"user_id": "user_123"}} # (6)! -) - -# You can access the store directly to get the value -store.get(("users",), "user_123").value -``` - -1. The `InMemoryStore` is a store that stores data in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [store documentation](../reference/store.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready store for you. -2. The `UserInfo` class is a `TypedDict` that defines the structure of the user information. The LLM will use this to format the response according to the schema. -3. The `save_user_info` function is a tool that allows an agent to update user information. This could be useful for a chat application where the user wants to update their profile information. -4. The `get_store` function is used to access the store. You can call it from anywhere in your code, including tools and prompts. This function returns the store that was passed to the agent when it was created. -5. The `put` method is used to store data in the store. The first argument is the namespace, and the second argument is the key. This will store the user information in the store. -6. The `user_id` is passed in the config. This is used to identify the user whose information is being updated. - -### Semantic search - -LangGraph also allows you to [search](https://langchain-ai.github.io/langgraph/how-tos/memory/semantic-search/#using-in-create-react-agent) for items in long-term memory by semantic similarity. - -### Prebuilt memory tools - -**LangMem** is a LangChain-maintained library that offers tools for managing long-term memories in your agent. See the [LangMem documentation](https://langchain-ai.github.io/langmem/) for usage examples. - - -## Additional resources - -* [Memory in LangGraph](../concepts/memory.md) diff --git a/docs/docs/agents/overview.md b/docs/docs/agents/overview.md index b8ffb6cf2..01268e604 100644 --- a/docs/docs/agents/overview.md +++ b/docs/docs/agents/overview.md @@ -27,12 +27,12 @@ The LLM operates in a loop. In each iteration, it selects a tool to invoke, prov LangGraph includes several capabilities essential for building robust, production-ready agentic systems: -- [**Memory integration**](./memory.md): Native support for *short-term* (session-based) and *long-term* (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants. +- [**Memory integration**](../how-tos/memory/add-memory.md): Native support for *short-term* (session-based) and *long-term* (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants. - [**Human-in-the-loop control**](./human-in-the-loop.md): Execution can pause *indefinitely* to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow. -- [**Streaming support**](./streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams. +- [**Streaming support**](../how-tos/streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams. - [**Deployment tooling**](./deployment.md): Includes infrastructure-free deployment tools. [**LangGraph Platform**](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) supports testing, debugging, and deployment. - **[Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)**: A visual IDE for inspecting and debugging workflows. - - Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/tutorials/deployment/) for production. + - Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/concepts/deployment_options.md) for production. ## High-level building blocks @@ -50,7 +50,7 @@ The high-level components are organized into several packages, each with a speci | `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `pip install -U langgraph-supervisor` | | `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `pip install -U langgraph-swarm` | | `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `pip install -U langchain-mcp-adapters` | -| `langmem` | Agent memory management: [**short-term and long-term**](./memory.md) | `pip install -U langmem` | +| `langmem` | Agent memory management: [**short-term and long-term**](../how-tos/memory/add-memory.md) | `pip install -U langmem` | | `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `pip install -U agentevals` | ## Visualize an agent graph diff --git a/docs/docs/agents/prebuilt.md b/docs/docs/agents/prebuilt.md index 6f15cb45e..8ebd5c17b 100644 --- a/docs/docs/agents/prebuilt.md +++ b/docs/docs/agents/prebuilt.md @@ -1,10 +1,10 @@ [//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!) -# Community agents +# Community Agents If you’re looking for other prebuilt libraries, explore the community-built options below. These libraries can extend LangGraph's functionality in various ways. -## 📚 Available libraries +## 📚 Available Libraries [//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!) | Name | GitHub URL | Description | Weekly Downloads | Stars | @@ -23,7 +23,7 @@ below. These libraries can extend LangGraph's functionality in various ways. | **langgraph-reflection** | [langchain-ai/langgraph-reflection](https://github.com/langchain-ai/langgraph-reflection) | LangGraph agent that runs a reflection step. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-reflection?style=social) | **langgraph-codeact** | [langchain-ai/langgraph-codeact](https://github.com/langchain-ai/langgraph-codeact) | LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling. | -12345 | ![GitHub stars](https://img.shields.io/github/stars/langchain-ai/langgraph-codeact?style=social) -## ✨ Contributing your library +## ✨ Contributing Your Library Have you built an awesome open-source library using LangGraph? We'd love to feature your project on the official LangGraph documentation pages! 🏆 diff --git a/docs/docs/agents/run_agents.md b/docs/docs/agents/run_agents.md index 61dcac308..4ea2d07e5 100644 --- a/docs/docs/agents/run_agents.md +++ b/docs/docs/agents/run_agents.md @@ -10,7 +10,7 @@ hide: # Running agents -Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .ainvoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits. +Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .ainvoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](../how-tos/streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits. ## Basic usage @@ -109,7 +109,7 @@ Streaming is available in both sync and async modes: !!! tip - For full details, see the [streaming guide](./streaming.md). + For full details, see the [streaming guide](../how-tos/streaming.md). ## Max iterations diff --git a/docs/docs/agents/streaming.md b/docs/docs/agents/streaming.md deleted file mode 100644 index 7c0b1265b..000000000 --- a/docs/docs/agents/streaming.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -search: - boost: 2 -tags: - - agent -hide: - - tags ---- - -# Streaming - -Streaming is key to building responsive applications. There are a few types of data you’ll want to stream: - -1. [**Agent progress**](#agent-progress) — get updates after each node in the agent graph is executed. -2. [**LLM tokens**](#llm-tokens) — stream tokens as they are generated by the language model. -3. [**Custom updates**](#tool-updates) — emit custom data from tools during execution (e.g., "Fetched 10/100 records") - -You can stream [more than one type of data](#stream-multiple-modes) at a time. - - -
-![image](./assets/fast_parrot.png){: style="max-height:300px"} -
-Waiting is for pigeons. -
-
- -## Agent progress - -To stream agent progress, use the [`stream()`][langgraph.graph.state.CompiledStateGraph.stream] or [`astream()`][langgraph.graph.state.CompiledStateGraph.astream] methods with [`stream_mode="updates"`](https://langchain-ai.github.io/langgraph/how-tos/streaming/#updates). This emits an event after every agent step. - -For example, if you have an agent that calls a tool once, you should see the following updates: - -* **LLM node**: AI message with tool call requests -* **Tool node**: Tool message with execution result -* **LLM node**: Final AI response - -=== "Sync" - - ```python - agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_weather], - ) - # highlight-next-line - for chunk in agent.stream( - {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, - # highlight-next-line - stream_mode="updates" - ): - print(chunk) - print("\n") - ``` - -=== "Async" - - ```python - agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_weather], - ) - # highlight-next-line - async for chunk in agent.astream( - {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, - # highlight-next-line - stream_mode="updates" - ): - print(chunk) - print("\n") - ``` - -## LLM tokens - -To stream tokens as they are produced by the LLM, use `stream_mode="messages"`: - -=== "Sync" - - ```python - agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_weather], - ) - # highlight-next-line - for token, metadata in agent.stream( - {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, - # highlight-next-line - stream_mode="messages" - ): - print("Token", token) - print("Metadata", metadata) - print("\n") - ``` - -=== "Async" - - ```python - agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_weather], - ) - # highlight-next-line - async for token, metadata in agent.astream( - {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, - # highlight-next-line - stream_mode="messages" - ): - print("Token", token) - print("Metadata", metadata) - print("\n") - ``` - -## Tool updates - -To stream updates from tools as they are executed, you can use [get_stream_writer][langgraph.config.get_stream_writer]. - -=== "Sync" - - ```python - # highlight-next-line - from langgraph.config import get_stream_writer - - def get_weather(city: str) -> str: - """Get weather for a given city.""" - # highlight-next-line - writer = get_stream_writer() - # stream any arbitrary data - # highlight-next-line - writer(f"Looking up data for city: {city}") - return f"It's always sunny in {city}!" - - agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_weather], - ) - - for chunk in agent.stream( - {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, - # highlight-next-line - stream_mode="custom" - ): - print(chunk) - print("\n") - ``` - -=== "Async" - - ```python - # highlight-next-line - from langgraph.config import get_stream_writer - - def get_weather(city: str) -> str: - """Get weather for a given city.""" - # highlight-next-line - writer = get_stream_writer() - # stream any arbitrary data - # highlight-next-line - writer(f"Looking up data for city: {city}") - return f"It's always sunny in {city}!" - - agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_weather], - ) - - async for chunk in agent.astream( - {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, - # highlight-next-line - stream_mode="custom" - ): - print(chunk) - print("\n") - ``` - -!!! Note - If you add `get_stream_writer` inside your tool, you won't be able to invoke the tool outside of a LangGraph execution context. - -## Stream multiple modes - -You can specify multiple streaming modes by passing stream mode as a list: `stream_mode=["updates", "messages", "custom"]`: - -=== "Sync" - - ```python - agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_weather], - ) - - for stream_mode, chunk in agent.stream( - {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, - # highlight-next-line - stream_mode=["updates", "messages", "custom"] - ): - print(chunk) - print("\n") - ``` - -=== "Async" - - ```python - agent = create_react_agent( - model="anthropic:claude-3-7-sonnet-latest", - tools=[get_weather], - ) - - async for stream_mode, chunk in agent.astream( - {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, - # highlight-next-line - stream_mode=["updates", "messages", "custom"] - ): - print(chunk) - print("\n") - ``` - -## Disable streaming - -In some applications you might need to disable streaming of individual tokens for a given model. This is useful in [multi-agent](./multi-agent.md) systems to control which agents stream their output. - -See the [Models](./models.md#disable-streaming) guide to learn how to disable streaming. - -## Additional resources - -* [Streaming in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/streaming) diff --git a/docs/docs/agents/tools.md b/docs/docs/agents/tools.md index 353a9b71e..e9f45effb 100644 --- a/docs/docs/agents/tools.md +++ b/docs/docs/agents/tools.md @@ -273,10 +273,10 @@ See [API reference][langgraph.prebuilt.tool_node.ToolNode] for more information ## Working with memory -LangGraph allows access to short-term and long-term memory from tools. See [Memory](./memory.md) guide for more information on: +LangGraph allows access to short-term and long-term memory from tools. See [Memory](../how-tos/memory/add-memory.md) guide for more information on: -* how to [read](./memory.md#read-short-term) from and [write](./memory.md#write-short-term) to **short-term** memory -* how to [read](./memory.md#read-long-term) from and [write](./memory.md#write-long-term) to **long-term** memory +* how to [read](../how-tos/memory/add-memory.md#read-short-term) from and [write](../how-tos/memory/add-memory.md#write-short-term) to **short-term** memory +* how to [read](../how-tos/memory/add-memory.md#read-long-term) from and [write](../how-tos/memory/add-memory.md#write-long-term) to **long-term** memory ## Prebuilt tools diff --git a/docs/docs/cloud/concepts/runs.md b/docs/docs/cloud/concepts/runs.md deleted file mode 100644 index d1957fc46..000000000 --- a/docs/docs/cloud/concepts/runs.md +++ /dev/null @@ -1,5 +0,0 @@ -# Runs - -A run is an invocation of an [assistant](../../concepts/assistants.md). Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./threads.md). - -The LangGraph Platform API provides several endpoints for creating and managing runs. See the [API reference](../../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details. \ No newline at end of file diff --git a/docs/docs/cloud/concepts/streaming.md b/docs/docs/cloud/concepts/streaming.md deleted file mode 100644 index 8654bda58..000000000 --- a/docs/docs/cloud/concepts/streaming.md +++ /dev/null @@ -1,138 +0,0 @@ -# Streaming - -Streaming is critical for making LLM applications feel responsive to end users. -When creating a streaming run, the **streaming mode** determines what kinds of data are streamed back to the API client. - -## Supported streaming modes - -LangGraph Platform supports the following streaming modes: - -| Mode | Description | LangGraph Library Method | -|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------| -| **`values`** | Stream the full graph state after each [super-step](https://langchain-ai.github.io/langgraph/concepts/low_level/#graphs). [Guide](../how-tos/streaming.md#stream-graph-state) | `.stream()` / `.astream()` with `stream_mode="values"` | -| **`updates`** | Stream only the updates to the graph state after each node. [Guide](../how-tos/streaming.md#stream-graph-state) | `.stream()` / `.astream()` with `stream_mode="updates"` | -| **`messages-tuple`** | Stream LLM tokens for any messages generated inside the graph (useful for chat apps). [Guide](../how-tos/streaming.md#messages) | `.stream()` / `.astream()` with `stream_mode="messages"` | -| **`debug`** | Stream debug information throughout graph execution. [Guide](../how-tos/streaming.md#debug) | `.stream()` / `.astream()` with `stream_mode="debug"` | -| **`custom`** | Stream custom data. [Guide](../../how-tos/streaming.md#stream-custom-data) | `.stream()` / `.astream()` with `stream_mode="custom"` | -| **`events`** | Stream all events (including the state of the graph); mainly useful when migrating large LCEL apps. [Guide](../how-tos/streaming.md#stream-events) | `.astream_events()` | - -✅ You can also **combine multiple modes** at the same time. See the [how-to guide](../how-tos/streaming.md#stream-multiple-modes) for configuration details. - -## Stateless runs - -If you don't want to **persist the outputs** of a streaming run in the [checkpointer](../../concepts/persistence.md) DB, you can create a stateless run without creating a thread: - -=== "Python" - - ```python - from langgraph_sdk import get_client - client = get_client(url=, api_key=) - - async for chunk in client.runs.stream( - # highlight-next-line - None, # (1)! - assistant_id, - input=inputs, - stream_mode="updates" - ): - print(chunk.data) - ``` - - 1. We are passing `None` instead of a `thread_id` UUID. - -=== "JavaScript" - - ```js - import { Client } from "@langchain/langgraph-sdk"; - const client = new Client({ apiUrl: , apiKey: }); - - // create a streaming run - // highlight-next-line - const streamResponse = client.runs.stream( - // highlight-next-line - null, // (1)! - assistantID, - { - input, - streamMode: "updates" - } - ); - for await (const chunk of streamResponse) { - console.log(chunk.data); - } - ``` - - 1. We are passing `None` instead of a `thread_id` UUID. - -=== "cURL" - - ```bash - curl --request POST \ - --url /runs/stream \ - --header 'Content-Type: application/json' \ - --header 'x-api-key: ' - --data "{ - \"assistant_id\": \"agent\", - \"input\": , - \"stream_mode\": \"updates\" - }" - ``` - -## Join and stream - -LangGraph Platform allows you to join an active [background run](../how-tos/background_run.md) and stream outputs from it. To do so, you can use [LangGraph SDK's](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) `client.runs.join_stream` method: - -=== "Python" - - ```python - from langgraph_sdk import get_client - client = get_client(url=, api_key=) - - # highlight-next-line - async for chunk in client.runs.join_stream( - thread_id, - # highlight-next-line - run_id, # (1)! - ): - print(chunk) - ``` - - 1. This is the `run_id` of an existing run you want to join. - - -=== "JavaScript" - - ```js - import { Client } from "@langchain/langgraph-sdk"; - const client = new Client({ apiUrl: , apiKey: }); - - // highlight-next-line - const streamResponse = client.runs.joinStream( - threadID, - // highlight-next-line - runId // (1)! - ); - for await (const chunk of streamResponse) { - console.log(chunk); - } - ``` - - 1. This is the `run_id` of an existing run you want to join. - -=== "cURL" - - ```bash - curl --request GET \ - --url /threads//runs//stream \ - --header 'Content-Type: application/json' \ - --header 'x-api-key: ' - ``` - -!!! warning "Outputs not buffered" - - When you use `.join_stream`, output is not buffered, so any output produced before joining will not be received. - -## API Reference - -For API usage and implementation, refer to the [API reference](../reference/api/api_ref.html#tag/thread-runs/POST/threads/{thread_id}/runs/stream). - diff --git a/docs/docs/cloud/concepts/threads.md b/docs/docs/cloud/concepts/threads.md index 37e81a205..ffd48faa8 100644 --- a/docs/docs/cloud/concepts/threads.md +++ b/docs/docs/cloud/concepts/threads.md @@ -1,6 +1,6 @@ # Threads -A thread contains the accumulated state of a sequence of [runs](./runs.md). When a run is executed, the [state](../../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread. +A thread contains the accumulated state of a sequence of [runs](../../concepts/assistants.md#execution). When a run is executed, the [state](../../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread. A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run. diff --git a/docs/docs/cloud/how-tos/invoke_studio.md b/docs/docs/cloud/how-tos/invoke_studio.md index 3f7086ea2..0548373e1 100644 --- a/docs/docs/cloud/how-tos/invoke_studio.md +++ b/docs/docs/cloud/how-tos/invoke_studio.md @@ -3,7 +3,7 @@ !!!info "Prerequisites" - [Running agents](../../agents/run_agents.md#running-agents) -This guide shows how to submit a [run](../concepts/runs.md) to your application. +This guide shows how to submit a [run](../../concepts/assistants.md#execution) to your application. ## Graph mode @@ -33,7 +33,7 @@ For more information on breakpoints see [here](../../concepts/breakpoints.md). ### Submit run -To submit the run with the specified input and run settings, click the "Submit" button. This will add a [run](../concepts/runs.md) to the existing selected [thread](../concepts/threads.md). If no thread is currently selected, a new one will be created. +To submit the run with the specified input and run settings, click the "Submit" button. This will add a [run](../../concepts/assistants.md#execution) to the existing selected [thread](../../concepts/persistence.md#threads). If no thread is currently selected, a new one will be created. To cancel the ongoing run, click the "Cancel" button. diff --git a/docs/docs/cloud/how-tos/streaming.md b/docs/docs/cloud/how-tos/streaming.md index 74f5fd9eb..f654a0625 100644 --- a/docs/docs/cloud/how-tos/streaming.md +++ b/docs/docs/cloud/how-tos/streaming.md @@ -1,8 +1,12 @@ -# Stream outputs +# Streaming API -## Streaming API +[LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) allows you to [stream outputs](../../concepts/streaming.md) from the LangGraph API server. -[LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) allows you to stream outputs from the LangGraph API server. +!!! note + + LangGraph SDK and LangGraph Server are a part of [LangGraph Platform](../../concepts/langgraph_platform.md). + +## Basic usage Basic usage example: @@ -833,3 +837,121 @@ To stream all events, including the state of the graph: \"stream_mode\": \"events\" }" ``` + +## Stateless runs + +If you don't want to **persist the outputs** of a streaming run in the [checkpointer](../../concepts/persistence.md) DB, you can create a stateless run without creating a thread: + +=== "Python" + + ```python + from langgraph_sdk import get_client + client = get_client(url=, api_key=) + + async for chunk in client.runs.stream( + # highlight-next-line + None, # (1)! + assistant_id, + input=inputs, + stream_mode="updates" + ): + print(chunk.data) + ``` + + 1. We are passing `None` instead of a `thread_id` UUID. + +=== "JavaScript" + + ```js + import { Client } from "@langchain/langgraph-sdk"; + const client = new Client({ apiUrl: , apiKey: }); + + // create a streaming run + // highlight-next-line + const streamResponse = client.runs.stream( + // highlight-next-line + null, // (1)! + assistantID, + { + input, + streamMode: "updates" + } + ); + for await (const chunk of streamResponse) { + console.log(chunk.data); + } + ``` + + 1. We are passing `None` instead of a `thread_id` UUID. + +=== "cURL" + + ```bash + curl --request POST \ + --url /runs/stream \ + --header 'Content-Type: application/json' \ + --header 'x-api-key: ' + --data "{ + \"assistant_id\": \"agent\", + \"input\": , + \"stream_mode\": \"updates\" + }" + ``` + +## Join and stream + +LangGraph Platform allows you to join an active [background run](../how-tos/background_run.md) and stream outputs from it. To do so, you can use [LangGraph SDK's](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) `client.runs.join_stream` method: + +=== "Python" + + ```python + from langgraph_sdk import get_client + client = get_client(url=, api_key=) + + # highlight-next-line + async for chunk in client.runs.join_stream( + thread_id, + # highlight-next-line + run_id, # (1)! + ): + print(chunk) + ``` + + 1. This is the `run_id` of an existing run you want to join. + + +=== "JavaScript" + + ```js + import { Client } from "@langchain/langgraph-sdk"; + const client = new Client({ apiUrl: , apiKey: }); + + // highlight-next-line + const streamResponse = client.runs.joinStream( + threadID, + // highlight-next-line + runId // (1)! + ); + for await (const chunk of streamResponse) { + console.log(chunk); + } + ``` + + 1. This is the `run_id` of an existing run you want to join. + +=== "cURL" + + ```bash + curl --request GET \ + --url /threads//runs//stream \ + --header 'Content-Type: application/json' \ + --header 'x-api-key: ' + ``` + +!!! warning "Outputs not buffered" + + When you use `.join_stream`, output is not buffered, so any output produced before joining will not be received. + +## API Reference + +For API usage and implementation, refer to the [API reference](../reference/api/api_ref.html#tag/thread-runs/POST/threads/{thread_id}/runs/stream). diff --git a/docs/docs/cloud/how-tos/studio/quick_start.md b/docs/docs/cloud/how-tos/studio/quick_start.md index 3b55d8db1..45316755d 100644 --- a/docs/docs/cloud/how-tos/studio/quick_start.md +++ b/docs/docs/cloud/how-tos/studio/quick_start.md @@ -13,7 +13,7 @@ LangGraph Studio is accessed from the LangSmith UI, within the LangGraph Platfor For applications that are [deployed](../../quick_start.md) on LangGraph Platform, you can access Studio as part of that deployment. To do so, navigate to the deployment in LangGraph Platform within the LangSmith UI and click the "LangGraph Studio" button. -This will load the Studio UI connected to your live deployment, allowing you to create, read, and update the [threads](../../concepts/threads.md), [assistants](../../../concepts/assistants.md), and [memory](../../../concepts//memory.md) in that deployment. +This will load the Studio UI connected to your live deployment, allowing you to create, read, and update the [threads](../../../concepts/persistence.md#threads), [assistants](../../../concepts/assistants.md), and [memory](../../../concepts//memory.md) in that deployment. ## Local development server diff --git a/docs/docs/cloud/how-tos/threads_studio.md b/docs/docs/cloud/how-tos/threads_studio.md index 8e37be6dc..6079e8ef2 100644 --- a/docs/docs/cloud/how-tos/threads_studio.md +++ b/docs/docs/cloud/how-tos/threads_studio.md @@ -1,10 +1,6 @@ # Manage threads -!!! info "Prerequisites" - - - [Threads Overview](../concepts/threads.md) - -Studio allows you to view threads from the server and edit their state. +Studio allows you to view [threads](../../concepts/persistence.md#threads) from the server and edit their state. ## View threads diff --git a/docs/docs/cloud/how-tos/use_threads.md b/docs/docs/cloud/how-tos/use_threads.md index cb9b61256..42c19dc21 100644 --- a/docs/docs/cloud/how-tos/use_threads.md +++ b/docs/docs/cloud/how-tos/use_threads.md @@ -1,10 +1,6 @@ # Use threads -!!! info "Prerequisites" - - - [Threads Overview](../concepts/threads.md) - -In this guide, we will show how to create, view, and inspect threads. +In this guide, we will show how to create, view, and inspect [threads](../../concepts/persistence.md#threads). ## Create a thread diff --git a/docs/docs/cloud/how-tos/webhooks.md b/docs/docs/cloud/how-tos/webhooks.md index 80d654bd9..59509c774 100644 --- a/docs/docs/cloud/how-tos/webhooks.md +++ b/docs/docs/cloud/how-tos/webhooks.md @@ -128,7 +128,7 @@ For example, if your server listens for webhook events at `https://my-server.app ## Webhook payload -LangGraph Platform sends webhook notifications in the format of a [Run](../../cloud/concepts/runs.md). See the [API Reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html#model/run) for details. The request payload includes run input, configuration, and other metadata in the `kwargs` field. +LangGraph Platform sends webhook notifications in the format of a [Run](../../concepts/assistants.md#execution). See the [API Reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html#model/run) for details. The request payload includes run input, configuration, and other metadata in the `kwargs` field. ## Secure webhooks diff --git a/docs/docs/concepts/agentic_concepts.md b/docs/docs/concepts/agentic_concepts.md index 852c6141e..5c18fce31 100644 --- a/docs/docs/concepts/agentic_concepts.md +++ b/docs/docs/concepts/agentic_concepts.md @@ -58,10 +58,10 @@ Tools are useful whenever you want an agent to interact with external systems. E ### Memory -[Memory](./memory.md) is crucial for agents, enabling them to retain and utilize information across multiple steps of problem-solving. It operates on different scales: +[Memory](../how-tos/memory/add-memory.md) is crucial for agents, enabling them to retain and utilize information across multiple steps of problem-solving. It operates on different scales: -1. [Short-term memory](./memory.md#short-term-memory): Allows the agent to access information acquired during earlier steps in a sequence. -2. [Long-term memory](./memory.md#long-term-memory): Enables the agent to recall information from previous interactions, such as past messages in a conversation. +1. [Short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory): Allows the agent to access information acquired during earlier steps in a sequence. +2. [Long-term memory](../how-tos/memory/add-memory.md#add-long-term-memory): Enables the agent to recall information from previous interactions, such as past messages in a conversation. LangGraph provides full control over memory implementation: @@ -69,9 +69,7 @@ LangGraph provides full control over memory implementation: - [`Checkpointer`](./persistence.md#checkpoints): Mechanism to store state at every step across different interactions within a session. - [`Store`](./persistence.md#memory-store): Mechanism to store user-specific or application-level data across sessions. -This flexible approach allows you to tailor the memory system to your specific agent architecture needs. For a practical guide on adding memory to your graph, see [this tutorial](../how-tos/persistence.ipynb). - -Effective [memory management](../how-tos/memory.ipynb) enhances an agent's ability to maintain context, learn from past experiences, and make more informed decisions over time. +This flexible approach allows you to tailor the memory system to your specific agent architecture needs. Effective memory management enhances an agent's ability to maintain context, learn from past experiences, and make more informed decisions over time. For a practical guide on adding and managing memory, see [Memory](../how-tos/memory/add-memory.md). ### Planning diff --git a/docs/docs/concepts/assistants.md b/docs/docs/concepts/assistants.md index 1e28b4465..feb79641b 100644 --- a/docs/docs/concepts/assistants.md +++ b/docs/docs/concepts/assistants.md @@ -1,29 +1,31 @@ # Assistants -!!! info "Prerequisites" +**Assistants** allow you to manage configurations (like prompts, LLM selection, tools) separately from your graph's core logic, enabling rapid changes that don't alter the graph architecture. It is a way to create multiple specialized versions of the same graph architecture, each optimized for different use cases through configuration variations rather than structural changes. - - [LangGraph Server](./langgraph_server.md) - - [Configuration](./low_level.md#configuration) - -When building agents, it is common to make rapid changes that _do not_ alter the graph logic. For example, simply changing prompts or the LLM selection can have significant impacts on the behavior of the agent but does not require updating your graph's architecture. Assistants offer a straightforward way to manage these configurations separately from your graph's core logic. - -Imagine a general-purpose writing agent built on a common graph architecture. While the structure remains the same, different writing styles—such as blog posts and tweets—require tailored configurations to optimize performance. To support these variations, you can create multiple assistants (e.g., one for blogs and another for tweets) that share the underlying graph but differ in model selection and system prompt. +For example, imagine a general-purpose writing agent built on a common graph architecture. While the structure remains the same, different writing styles—such as blog posts and tweets—require tailored configurations to optimize performance. To support these variations, you can create multiple assistants (e.g., one for blogs and another for tweets) that share the underlying graph but differ in model selection and system prompt. ![assistant versions](img/assistants.png) -## Configuring assistants +The LangGraph Cloud API provides several endpoints for creating and managing assistants and their versions. See the [API reference](../cloud/reference/api/api_ref.html#tag/assistants) for more details. + +!!! info + + Assistants are a [LangGraph Platform](langgraph_platform.md) concept. They are not available in the open source LangGraph library. + +## Configuration Assistants build on the LangGraph open source concept of [configuration](low_level.md#configuration). -While configuration is available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md). -This is due to the fact that assistants are tightly coupled to your deployed graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default configuration settings. +While configuration is available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md). This is due to the fact that assistants are tightly coupled to your deployed graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default configuration settings. In practice, an assistant is just an _instance_ of a graph with a specific configuration. Therefore, multiple assistants can reference the same graph but can contain different configurations (e.g. prompts, models, tools). The LangGraph Server API provides several endpoints for creating and managing assistants. See the [API reference](../cloud/reference/api/api_ref.html) and [this how-to](../cloud/how-tos/configuration_cloud.md) for more details on how to create assistants. -## Versioning assistants +## Versioning Assistants support versioning to track changes over time. Once you've created an assistant, subsequent edits to that assistant will create new versions. See [this how-to](../cloud/how-tos/configuration_cloud.md#create-a-new-version-for-your-assistant) for more details on how to manage assistant versions. -## Learn more +## Execution -* The LangGraph Cloud API provides several endpoints for creating and managing assistants and their versions. See the [API reference](../cloud/reference/api/api_ref.html#tag/assistants) for more details. \ No newline at end of file +A **run** is an invocation of an assistant. Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./persistence.md#threads). + +The LangGraph Platform API provides several endpoints for creating and managing runs. See the [API reference](../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details. diff --git a/docs/docs/concepts/deployment_options.md b/docs/docs/concepts/deployment_options.md index 5b776e3b1..2e23ec97e 100644 --- a/docs/docs/concepts/deployment_options.md +++ b/docs/docs/concepts/deployment_options.md @@ -5,7 +5,16 @@ search: # Deployment Options -There are 4 main options for deploying with the LangGraph Platform: +## Free deployment + +There are two free options for deploying LangGraph applications via the LangGraph Server: + +1. [Local](../tutorials/langgraph-platform/local-server.md): Deploy for local testing and development. +1. [Standalone Container (Lite)](../concepts/langgraph_standalone_container.md): A limited version of Standalone Container for deployments unlikely to see more that 1 million node executions per year and that do not need crons and other enterprise features. Standalone Container (Lite) deployment option is free with a LangSmith API key. + +## Production deployment + +There are 4 main options for deploying with the [LangGraph Platform](langgraph_platform.md): 1. [Cloud SaaS](#cloud-saas) @@ -22,7 +31,7 @@ A quick comparison: |----------------------|----------------|----------------------------|-------------------------------|--------------------------| | **[Control plane UI/API](../concepts/langgraph_control_plane.md)** | Yes | Yes | Yes | No | | **CI/CD** | Managed internally by platform | Managed externally by you | Managed externally by you | Managed externally by you | -| **Data/compute residency** | LangChain’s cloud | Your cloud | Your cloud | Your cloud | +| **Data/compute residency** | LangChain's cloud | Your cloud | Your cloud | Your cloud | | **LangSmith compatibility** | Trace to LangSmith SaaS | Trace to LangSmith SaaS | Trace to Self-Hosted LangSmith | Optional tracing | | **[Server version compatibility](../concepts/langgraph_server.md#server-versions)** | Enterprise | Enterprise | Enterprise | Lite, Enterprise | | **[Pricing](https://www.langchain.com/pricing-langgraph-platform)** | Plus | Enterprise | Enterprise | Developer | diff --git a/docs/docs/concepts/functional_api.md b/docs/docs/concepts/functional_api.md index 6344a3822..e48247308 100644 --- a/docs/docs/concepts/functional_api.md +++ b/docs/docs/concepts/functional_api.md @@ -7,7 +7,7 @@ search: ## Overview -The **Functional API** allows you to add LangGraph's key features — [persistence](./persistence.md), [memory](./memory.md), [human-in-the-loop](./human_in_the_loop.md), and [streaming](./streaming.md) — to your applications with minimal changes to your existing code. +The **Functional API** allows you to add LangGraph's key features — [persistence](./persistence.md), [memory](../how-tos/memory/add-memory.md), [human-in-the-loop](./human_in_the_loop.md), and [streaming](./streaming.md) — to your applications with minimal changes to your existing code. It is designed to integrate these features into existing code that may use standard language primitives for branching and control flow, such as `if` statements, `for` loops, and function calls. Unlike many data orchestration frameworks that require restructuring code into an explicit pipeline or DAG, the Functional API allows you to incorporate these capabilities without enforcing a rigid execution model. diff --git a/docs/docs/concepts/langgraph_data_plane.md b/docs/docs/concepts/langgraph_data_plane.md index 79188118d..0b52b8b6f 100644 --- a/docs/docs/concepts/langgraph_data_plane.md +++ b/docs/docs/concepts/langgraph_data_plane.md @@ -56,7 +56,7 @@ This section describes various features of the data plane. 1. CPU utilization 1. Memory utilization -1. Number of pending (in progress) [runs](../cloud/concepts/runs.md) +1. Number of pending (in progress) [runs](./assistants.md#execution) For CPU utilization, the autoscaler targets 75% utilization. This means the autoscaler will scale the number of containers up or down to ensure that CPU utilization is at or near 75%. For memory utilization, the autoscaler targets 75% utilization as well. diff --git a/docs/docs/concepts/langgraph_platform.md b/docs/docs/concepts/langgraph_platform.md index 780d7f6a2..e8682c451 100644 --- a/docs/docs/concepts/langgraph_platform.md +++ b/docs/docs/concepts/langgraph_platform.md @@ -17,7 +17,7 @@ Develop, deploy, scale, and manage agents with **LangGraph Platform** — the pu LangGraph Platform makes it easy to get your agent running in production — whether it’s built with LangGraph or another framework — so you can focus on your app logic, not infrastructure. Deploy with one click to get a live endpoint, and use our robust APIs and built-in task queues to handle production scale. -- **[Streaming Support](../cloud/concepts/streaming.md)**: As agents grow more sophisticated, they often benefit from streaming both token outputs and intermediate states back to the user. Without this, users are left waiting for potentially long operations with no feedback. LangGraph Server provides multiple streaming modes optimized for various application needs. +- **[Streaming Support](../cloud/how-tos/streaming.md)**: As agents grow more sophisticated, they often benefit from streaming both token outputs and intermediate states back to the user. Without this, users are left waiting for potentially long operations with no feedback. LangGraph Server provides multiple streaming modes optimized for various application needs. - **[Background Runs](../cloud/how-tos/background_run.md)**: For agents that take longer to process (e.g., hours), maintaining an open connection can be impractical. The LangGraph Server supports launching agent runs in the background and provides both polling endpoints and webhooks to monitor run status effectively. diff --git a/docs/docs/concepts/langgraph_self_hosted_control_plane.md b/docs/docs/concepts/langgraph_self_hosted_control_plane.md index b6a6f71df..7434072ce 100644 --- a/docs/docs/concepts/langgraph_self_hosted_control_plane.md +++ b/docs/docs/concepts/langgraph_self_hosted_control_plane.md @@ -3,7 +3,7 @@ There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane). !!! info "Important" - The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan. + The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan. ## Requirements diff --git a/docs/docs/concepts/langgraph_self_hosted_data_plane.md b/docs/docs/concepts/langgraph_self_hosted_data_plane.md index 33709eac0..710e58018 100644 --- a/docs/docs/concepts/langgraph_self_hosted_data_plane.md +++ b/docs/docs/concepts/langgraph_self_hosted_data_plane.md @@ -8,7 +8,7 @@ search: There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane). !!! info "Important" - The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan. + The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](plans.md) plan. ## Requirements diff --git a/docs/docs/concepts/langgraph_server.md b/docs/docs/concepts/langgraph_server.md index 47f7dacd5..1835fc649 100644 --- a/docs/docs/concepts/langgraph_server.md +++ b/docs/docs/concepts/langgraph_server.md @@ -7,7 +7,7 @@ search: **LangGraph Server** offers an API for creating and managing agent-based applications. It is built on the concept of [assistants](assistants.md), which are agents configured for specific tasks, and includes built-in [persistence](persistence.md#memory-store) and a **task queue**. This versatile API supports a wide range of agentic application use cases, from background processing to real-time interactions. -Use LangGraph Server to create and manage [assistants](assistants.md), [threads](../cloud/concepts/threads.md), [runs](../cloud/concepts/runs.md), [cron jobs](../cloud/concepts/cron_jobs.md), [webhooks](../cloud/concepts/webhooks.md), and more. +Use LangGraph Server to create and manage [assistants](assistants.md), [threads](./persistence.md#threads), [runs](./assistants.md#execution), [cron jobs](../cloud/concepts/cron_jobs.md), [webhooks](../cloud/concepts/webhooks.md), and more. !!! tip "API reference" diff --git a/docs/docs/concepts/memory.md b/docs/docs/concepts/memory.md index 5e1f97ac5..6e59a4cff 100644 --- a/docs/docs/concepts/memory.md +++ b/docs/docs/concepts/memory.md @@ -5,246 +5,63 @@ search: # Memory -## What is Memory? +[Memory](../how-tos/memory/add-memory.md) is a system that remembers information about previous interactions. For AI agents, memory is crucial because it lets them remember previous interactions, learn from feedback, and adapt to user preferences. As agents tackle more complex tasks with numerous user interactions, this capability becomes essential for both efficiency and user satisfaction. -[Memory](https://pmc.ncbi.nlm.nih.gov/articles/PMC10410470/) is a cognitive function that allows people to store, retrieve, and use information to understand their present and future. Consider the frustration of working with a colleague who forgets everything you tell them, requiring constant repetition! As AI agents undertake more complex tasks involving numerous user interactions, equipping them with memory becomes equally crucial for efficiency and user satisfaction. With memory, agents can learn from feedback and adapt to users' preferences. This guide covers two types of memory based on recall scope: +This conceptual guide covers two types of memory, based on their recall scope: -**Short-term memory**, or [thread](persistence.md#threads)-scoped memory, can be recalled at any time **from within** a single conversational thread with a user. LangGraph manages short-term memory as a part of your agent's [state](low_level.md#state). State is persisted to a database using a [checkpointer](persistence.md#checkpoints) so the thread can be resumed at any time. Short-term memory updates when the graph is invoked or a step is completed, and the State is read at the start of each step. +- [Short-term memory](#short-term-memory), or [thread](persistence.md#threads)-scoped memory, tracks the ongoing conversation by maintaining message history within a session. LangGraph manages short-term memory as a part of your agent's [state](low_level.md#state). State is persisted to a database using a [checkpointer](persistence.md#checkpoints) so the thread can be resumed at any time. Short-term memory updates when the graph is invoked or a step is completed, and the State is read at the start of each step. -**Long-term memory** is shared **across** conversational threads. It can be recalled _at any time_ and **in any thread**. Memories are scoped to any custom namespace, not just within a single thread ID. LangGraph provides [stores](persistence.md#memory-store) ([reference doc](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore)) to let you save and recall long-term memories. - -Both are important to understand and implement for your application. +- [Long-term memory](#long-term-memory) stores user-specific or application-level data across sessions and is shared _across_ conversational threads. It can be recalled _at any time_ and _in any thread_. Memories are scoped to any custom namespace, not just within a single thread ID. LangGraph provides [stores](persistence.md#memory-store) ([reference doc](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore)) to let you save and recall long-term memories. ![](img/memory/short-vs-long.png) + ## Short-term memory -Short-term memory lets your application remember previous interactions within a single [thread](persistence.md#threads) or conversation. A [thread](persistence.md#threads) organizes multiple interactions in a session, similar to the way email groups messages in a single conversation. +[Short-term memory](../how-tos/memory/add-memory.md#add-short-term-memory) lets your application remember previous interactions within a single [thread](persistence.md#threads) or conversation. A [thread](persistence.md#threads) organizes multiple interactions in a session, similar to the way email groups messages in a single conversation. LangGraph manages short-term memory as part of the agent's state, persisted via thread-scoped checkpoints. This state can normally include the conversation history along with other stateful data, such as uploaded files, retrieved documents, or generated artifacts. By storing these in the graph's state, the bot can access the full context for a given conversation while maintaining separation between different threads. -Since conversation history is the most common form of representing short-term memory, in the next section, we will cover techniques for managing conversation history when the list of messages becomes **long**. If you want to stick to the high-level concepts, continue on to the [long-term memory](#long-term-memory) section. +### Manage short-term memory -### Managing long conversation history +Conversation history is the most common form of short-term memory, and long conversations pose a challenge to today's LLMs. A full history may not fit inside an LLM's context window, resulting in an irrecoverable error. Even if your LLM supports the full context length, most LLMs still perform poorly over long contexts. They get "distracted" by stale or off-topic content, all while suffering from slower response times and higher costs. -Long conversations pose a challenge to today's LLMs. The full history may not even fit inside an LLM's context window, resulting in an irrecoverable error. Even _if_ your LLM technically supports the full context length, most LLMs still perform poorly over long contexts. They get "distracted" by stale or off-topic content, all while suffering from slower response times and higher costs. - -Managing short-term memory is an exercise of balancing [precision & recall](https://en.wikipedia.org/wiki/Precision_and_recall#:~:text=Precision%20can%20be%20seen%20as,irrelevant%20ones%20are%20also%20returned) with your application's other performance requirements (latency & cost). As always, it's important to think critically about how you represent information for your LLM and to look at your data. We cover a few common techniques for managing message lists below and hope to provide sufficient context for you to pick the best tradeoffs for your application: - -- [Editing message lists](#editing-message-lists): How to think about trimming and filtering a list of messages before passing to language model. -- [Summarizing past conversations](#summarizing-past-conversations): A common technique to use when you don't just want to filter the list of messages. - -### Editing message lists - -Chat models accept context using [messages](https://python.langchain.com/docs/concepts/#messages), which include developer provided instructions (a system message) and user inputs (human messages). In chat applications, messages alternate between human inputs and model responses, resulting in a list of messages that grows longer over time. Because context windows are limited and token-rich message lists can be costly, many applications can benefit from using techniques to manually remove or forget stale information. +Chat models accept context using messages, which include developer provided instructions (a system message) and user inputs (human messages). In chat applications, messages alternate between human inputs and model responses, resulting in a list of messages that grows longer over time. Because context windows are limited and token-rich message lists can be costly, many applications can benefit from using techniques to manually remove or forget stale information. ![](img/memory/filter.png) -The most direct approach is to remove old messages from a list (similar to a [least-recently used cache](https://en.wikipedia.org/wiki/Page_replacement_algorithm#Least_recently_used)). - -The typical technique for deleting content from a list in LangGraph is to return an update from a node telling the system to delete some portion of the list. You get to define what this update looks like, but a common approach would be to let you return an object or dictionary specifying which values to retain. - -```python -def manage_list(existing: list, updates: Union[list, dict]): - if isinstance(updates, list): - # Normal case, add to the history - return existing + updates - elif isinstance(updates, dict) and updates["type"] == "keep": - # You get to decide what this looks like. - # For example, you could simplify and just accept a string "DELETE" - # and clear the entire list. - return existing[updates["from"]:updates["to"]] - # etc. We define how to interpret updates - -class State(TypedDict): - my_list: Annotated[list, manage_list] - -def my_node(state: State): - return { - # We return an update for the field "my_list" saying to - # keep only values from index -5 to the end (deleting the rest) - "my_list": {"type": "keep", "from": -5, "to": None} - } -``` - -LangGraph will call the `manage_list` "[reducer](low_level.md#reducers)" function any time an update is returned under the key "my_list". Within that function, we define what types of updates to accept. Typically, messages will be added to the existing list (the conversation will grow); however, we've also added support to accept a dictionary that lets you "keep" certain parts of the state. This lets you programmatically drop old message context. - -Another common approach is to let you return a list of "remove" objects that specify the IDs of all messages to delete. If you're using the LangChain messages and the [`add_messages`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages) reducer (or `MessagesState`, which uses the same underlying functionality) in LangGraph, you can do this using a `RemoveMessage`. - -```python -from langchain_core.messages import RemoveMessage, AIMessage -from langgraph.graph import add_messages -# ... other imports - -class State(TypedDict): - # add_messages will default to upserting messages by ID to the existing list - # if a RemoveMessage is returned, it will delete the message in the list by ID - messages: Annotated[list, add_messages] - -def my_node_1(state: State): - # Add an AI message to the `messages` list in the state - return {"messages": [AIMessage(content="Hi")]} - -def my_node_2(state: State): - # Delete all but the last 2 messages from the `messages` list in the state - delete_messages = [RemoveMessage(id=m.id) for m in state['messages'][:-2]] - return {"messages": delete_messages} - -``` - -In the example above, the `add_messages` reducer allows us to [append](https://langchain-ai.github.io/langgraph/concepts/low_level/#serialization) new messages to the `messages` state key as shown in `my_node_1`. When it sees a `RemoveMessage`, it will delete the message with that ID from the list (and the RemoveMessage will then be discarded). For more information on LangChain-specific message handling, check out [this how-to on using `RemoveMessage` ](https://langchain-ai.github.io/langgraph/how-tos/memory/delete-messages/). - -See this how-to [guide](https://langchain-ai.github.io/langgraph/how-tos/memory/manage-conversation-history/) and module 2 from our [LangChain Academy](https://github.com/langchain-ai/langchain-academy/tree/main/module-2) course for example usage. - -### Summarizing past conversations - -The problem with trimming or removing messages, as shown above, is that we may lose information from culling of the message queue. Because of this, some applications benefit from a more sophisticated approach of summarizing the message history using a chat model. - -![](img/memory/summary.png) - -Simple prompting and orchestration logic can be used to achieve this. As an example, in LangGraph we can extend the [MessagesState](https://langchain-ai.github.io/langgraph/concepts/low_level/#working-with-messages-in-graph-state) to include a `summary` key. - -```python -from langgraph.graph import MessagesState -class State(MessagesState): - summary: str -``` - -Then, we can generate a summary of the chat history, using any existing summary as context for the next summary. This `summarize_conversation` node can be called after some number of messages have accumulated in the `messages` state key. - -```python -def summarize_conversation(state: State): - - # First, we get any existing summary - summary = state.get("summary", "") - - # Create our summarization prompt - if summary: - - # A summary already exists - summary_message = ( - f"This is a summary of the conversation to date: {summary}\n\n" - "Extend the summary by taking into account the new messages above:" - ) - - else: - summary_message = "Create a summary of the conversation above:" - - # Add prompt to our history - messages = state["messages"] + [HumanMessage(content=summary_message)] - response = model.invoke(messages) - - # Delete all but the 2 most recent messages - delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]] - return {"summary": response.content, "messages": delete_messages} -``` - -See this how-to [here](https://langchain-ai.github.io/langgraph/how-tos/memory/add-summary-conversation-history/) and module 2 from our [LangChain Academy](https://github.com/langchain-ai/langchain-academy/tree/main/module-2) course for example usage. - -### Knowing **when** to remove messages - -Most LLMs have a maximum supported context window (denominated in tokens). A simple way to decide when to truncate messages is to count the tokens in the message history and truncate whenever it approaches that limit. Naive truncation is straightforward to implement on your own, though there are a few "gotchas". Some model APIs further restrict the sequence of message types (must start with human message, cannot have consecutive messages of the same type, etc.). If you're using LangChain, you can use the [`trim_messages`](https://python.langchain.com/docs/how_to/trim_messages/#trimming-based-on-token-count) utility and specify the number of tokens to keep from the list, as well as the `strategy` (e.g., keep the last `max_tokens`) to use for handling the boundary. - -Below is an example. - -```python -from langchain_core.messages import trim_messages -trim_messages( - messages, - # Keep the last <= n_count tokens of the messages. - strategy="last", - # Remember to adjust based on your model - # or else pass a custom token_encoder - token_counter=ChatOpenAI(model="gpt-4"), - # Remember to adjust based on the desired conversation - # length - max_tokens=45, - # Most chat models expect that chat history starts with either: - # (1) a HumanMessage or - # (2) a SystemMessage followed by a HumanMessage - start_on="human", - # Most chat models expect that chat history ends with either: - # (1) a HumanMessage or - # (2) a ToolMessage - end_on=("human", "tool"), - # Usually, we want to keep the SystemMessage - # if it's present in the original history. - # The SystemMessage has special instructions for the model. - include_system=True, -) -``` +For more information on common techniques for managing messages, see the [Add and manage memory](../how-tos/memory/add-memory.md#manage-short-term-memory) guide. ## Long-term memory -Long-term memory in LangGraph allows systems to retain information across different conversations or sessions. Unlike short-term memory, which is **thread-scoped**, long-term memory is saved within custom "namespaces." +[Long-term memory](../how-tos/memory/add-memory.md#add-long-term-memory) in LangGraph allows systems to retain information across different conversations or sessions. Unlike short-term memory, which is **thread-scoped**, long-term memory is saved within custom "namespaces." -### Storing memories +Long-term memory is a complex challenge without a one-size-fits-all solution. However, the following questions provide a framework to help you navigate the different techniques: -LangGraph stores long-term memories as JSON documents in a [store](persistence.md#memory-store) ([reference doc](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore)). Each memory is organized under a custom `namespace` (similar to a folder) and a distinct `key` (like a filename). Namespaces often include user or org IDs or other labels that makes it easier to organize information. This structure enables hierarchical organization of memories. Cross-namespace searching is then supported through content filters. See the example below for an example. +- [What is the type of memory?](#memory-types) Humans use memories to remember facts ([semantic memory](#semantic-memory)), experiences ([episodic memory](#episodic-memory)), and rules ([procedural memory](#procedural-memory)). AI agents can use memory in the same ways. For example, AI agents can use memory to remember specific facts about a user to accomplish a task. -```python -from langgraph.store.memory import InMemoryStore +- [When do you want to update memories?](#writing-memories) Memory can be updated as part of an agent's application logic (e.g., "on the hot path"). In this case, the agent typically decides to remember facts before responding to a user. Alternatively, memory can be updated as a background task (logic that runs in the background / asynchronously and generates memories). We explain the tradeoffs between these approaches in the [section below](#writing-memories). - -def embed(texts: list[str]) -> list[list[float]]: - # Replace with an actual embedding function or LangChain embeddings object - return [[1.0, 2.0] * len(texts)] - - -# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use. -store = InMemoryStore(index={"embed": embed, "dims": 2}) -user_id = "my-user" -application_context = "chitchat" -namespace = (user_id, application_context) -store.put( - namespace, - "a-memory", - { - "rules": [ - "User likes short, direct language", - "User only speaks English & python", - ], - "my-key": "my-value", - }, -) -# get the "memory" by ID -item = store.get(namespace, "a-memory") -# search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity -items = store.search( - namespace, filter={"my-key": "my-value"}, query="language preferences" -) -``` - -### Framework for thinking about long-term memory - -Long-term memory is a complex challenge without a one-size-fits-all solution. However, the following questions provide a structure framework to help you navigate the different techniques: - -**What is the type of memory?** - -Humans use memories to remember [facts](https://en.wikipedia.org/wiki/Semantic_memory), [experiences](https://en.wikipedia.org/wiki/Episodic_memory), and [rules](https://en.wikipedia.org/wiki/Procedural_memory). AI agents can use memory in the same ways. For example, AI agents can use memory to remember specific facts about a user to accomplish a task. We expand on several types of memories in the [section below](#memory-types). - -**When do you want to update memories?** - -Memory can be updated as part of an agent's application logic (e.g. "on the hot path"). In this case, the agent typically decides to remember facts before responding to a user. Alternatively, memory can be updated as a background task (logic that runs in the background / asynchronously and generates memories). We explain the tradeoffs between these approaches in the [section below](#writing-memories). - -## Memory types +### Memory types Different applications require various types of memory. Although the analogy isn't perfect, examining [human memory types](https://www.psychologytoday.com/us/basics/memory/types-of-memory?ref=blog.langchain.dev) can be insightful. Some research (e.g., the [CoALA paper](https://arxiv.org/pdf/2309.02427)) have even mapped these human memory types to those used in AI agents. | Memory Type | What is Stored | Human Example | Agent Example | |-------------|----------------|---------------|---------------| -| Semantic | Facts | Things I learned in school | Facts about a user | -| Episodic | Experiences | Things I did | Past agent actions | -| Procedural | Instructions | Instincts or motor skills | Agent system prompt | +| [Semantic](#semantic-memory) | Facts | Things I learned in school | Facts about a user | +| [Episodic](#episodic-memory) | Experiences | Things I did | Past agent actions | +| [Procedural](#procedural-memory) | Instructions | Instincts or motor skills | Agent system prompt | -### Semantic Memory +#### Semantic memory [Semantic memory](https://en.wikipedia.org/wiki/Semantic_memory), both in humans and AI agents, involves the retention of specific facts and concepts. In humans, it can include information learned in school and the understanding of concepts and their relationships. For AI agents, semantic memory is often used to personalize applications by remembering facts or concepts from past interactions. -> Note: Not to be confused with "semantic search" which is a technique for finding similar content using "meaning" (usually as embeddings). Semantic memory is a term from psychology, referring to storing facts and knowledge, while semantic search is a method for retrieving information based on meaning rather than exact matches. +!!! note + + Semantic memory is different from "semantic search," which is a technique for finding similar content using "meaning" (usually as embeddings). Semantic memory is a term from psychology, referring to storing facts and knowledge, while semantic search is a method for retrieving information based on meaning rather than exact matches. -#### Profile +##### Profile Semantic memories can be managed in different ways. For example, memories can be a single, continuously updated "profile" of well-scoped and specific information about a user, organization, or other entity (including the agent itself). A profile is generally just a JSON document with various key-value pairs you've selected to represent your domain. @@ -252,7 +69,7 @@ When remembering a profile, you will want to make sure that you are **updating** ![](img/memory/update-profile.png) -#### Collection +##### Collection Alternatively, memories can be a collection of documents that are continuously updated and extended over time. Each individual memory can be more narrowly scoped and easier to generate, which means that you're less likely to **lose** information over time. It's easier for an LLM to generate _new_ objects for new information than reconcile new information with an existing profile. As a result, a document collection tends to lead to [higher recall downstream](https://en.wikipedia.org/wiki/Precision_and_recall). @@ -266,7 +83,7 @@ Finally, using a collection of memories can make it challenging to provide compr Regardless of memory management approach, the central point is that the agent will use the semantic memories to [ground its responses](https://python.langchain.com/docs/concepts/rag/), which often leads to more personalized and relevant interactions. -### Episodic Memory +#### Episodic memory [Episodic memory](https://en.wikipedia.org/wiki/Episodic_memory), in both humans and AI agents, involves recalling past events or actions. The [CoALA paper](https://arxiv.org/pdf/2309.02427) frames this well: facts can be written to semantic memory, whereas *experiences* can be written to episodic memory. For AI agents, episodic memory is often used to help an agent remember how to accomplish a task. @@ -276,7 +93,7 @@ Note that the memory [store](persistence.md#memory-store) is just one way to sto See this how-to [video](https://www.youtube.com/watch?v=37VaU7e7t5o) for example usage of dynamic few-shot example selection in LangSmith. Also, see this [blog post](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/) showcasing few-shot prompting to improve tool calling performance and this [blog post](https://blog.langchain.dev/aligning-llm-as-a-judge-with-human-preferences/) using few-shot example to align an LLMs to human preferences. -### Procedural Memory +#### Procedural memory [Procedural memory](https://en.wikipedia.org/wiki/Procedural_memory), in both humans and AI agents, involves remembering the rules used to perform tasks. In humans, procedural memory is like the internalized knowledge of how to perform tasks, such as riding a bike via basic motor skills and balance. Episodic memory, on the other hand, involves recalling specific experiences, such as the first time you successfully rode a bike without training wheels or a memorable bike ride through a scenic route. For AI agents, procedural memory is a combination of model weights, agent code, and agent's prompt that collectively determine the agent's functionality. @@ -311,13 +128,13 @@ def update_instructions(state: State, store: BaseStore): ![](img/memory/update-instructions.png) -## Writing memories +### Writing memories -While [humans often form long-term memories during sleep](https://medicine.yale.edu/news-article/sleeps-crucial-role-in-preserving-memory/), AI agents need a different approach. When and how should agents create new memories? There are at least two primary methods for agents to write memories: "on the hot path" and "in the background". +There are two primary methods for agents to write memories: ["in the hot path"](#in-the-hot-path) and ["in the background"](#in-the-background). ![](img/memory/hot_path_vs_background.png) -### Writing memories in the hot path +#### In the hot path Creating memories during runtime offers both advantages and challenges. On the positive side, this approach allows for real-time updates, making new memories immediately available for use in subsequent interactions. It also enables transparency, as users can be notified when memories are created and stored. @@ -325,10 +142,49 @@ However, this method also presents challenges. It may increase complexity if the As an example, ChatGPT uses a [save_memories](https://openai.com/index/memory-and-new-controls-for-chatgpt/) tool to upsert memories as content strings, deciding whether and how to use this tool with each user message. See our [memory-agent](https://github.com/langchain-ai/memory-agent) template as an reference implementation. -### Writing memories in the background +#### In the background Creating memories as a separate background task offers several advantages. It eliminates latency in the primary application, separates application logic from memory management, and allows for more focused task completion by the agent. This approach also provides flexibility in timing memory creation to avoid redundant work. However, this method has its own challenges. Determining the frequency of memory writing becomes crucial, as infrequent updates may leave other threads without new context. Deciding when to trigger memory formation is also important. Common strategies include scheduling after a set time period (with rescheduling if new events occur), using a cron schedule, or allowing manual triggers by users or the application logic. See our [memory-service](https://github.com/langchain-ai/memory-template) template as an reference implementation. + +### Memory storage + +LangGraph stores long-term memories as JSON documents in a [store](persistence.md#memory-store). Each memory is organized under a custom `namespace` (similar to a folder) and a distinct `key` (like a file name). Namespaces often include user or org IDs or other labels that makes it easier to organize information. This structure enables hierarchical organization of memories. Cross-namespace searching is then supported through content filters. + +```python +from langgraph.store.memory import InMemoryStore + + +def embed(texts: list[str]) -> list[list[float]]: + # Replace with an actual embedding function or LangChain embeddings object + return [[1.0, 2.0] * len(texts)] + + +# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use. +store = InMemoryStore(index={"embed": embed, "dims": 2}) +user_id = "my-user" +application_context = "chitchat" +namespace = (user_id, application_context) +store.put( + namespace, + "a-memory", + { + "rules": [ + "User likes short, direct language", + "User only speaks English & python", + ], + "my-key": "my-value", + }, +) +# get the "memory" by ID +item = store.get(namespace, "a-memory") +# search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity +items = store.search( + namespace, filter={"my-key": "my-value"}, query="language preferences" +) +``` + +For more information about the memory store, see the [Persistence](persistence.md#memory-store) guide. \ No newline at end of file diff --git a/docs/docs/concepts/multi_agent.md b/docs/docs/concepts/multi_agent.md index 58f872284..3abfc4fbc 100644 --- a/docs/docs/concepts/multi_agent.md +++ b/docs/docs/concepts/multi_agent.md @@ -376,7 +376,7 @@ The most common way for agents to communicate is via a shared state channel, typ #### Sharing full thought process -Agents can **share the full history** of their thought process (i.e., "scratchpad") with all other agents. This "scratchpad" would typically look like a [list of messages](./low_level.md#why-use-messages). The benefit of sharing the full thought process is that it might help other agents make better decisions and improve reasoning ability for the system as a whole. The downside is that as the number of agents and their complexity grows, the "scratchpad" will grow quickly and might require additional strategies for [memory management](./memory.md/#managing-long-conversation-history). +Agents can **share the full history** of their thought process (i.e., "scratchpad") with all other agents. This "scratchpad" would typically look like a [list of messages](./low_level.md#why-use-messages). The benefit of sharing the full thought process is that it might help other agents make better decisions and improve reasoning ability for the system as a whole. The downside is that as the number of agents and their complexity grows, the "scratchpad" will grow quickly and might require additional strategies for [memory management](../how-tos/memory/add-memory.md). #### Sharing only final results diff --git a/docs/docs/concepts/persistence.md b/docs/docs/concepts/persistence.md index 34cea1857..05238b6fd 100644 --- a/docs/docs/concepts/persistence.md +++ b/docs/docs/concepts/persistence.md @@ -5,7 +5,7 @@ search: # Persistence -LangGraph has a built-in persistence layer, implemented through checkpointers. When you compile graph with a checkpointer, the checkpointer saves a `checkpoint` of the graph state at every super-step. Those checkpoints are saved to a `thread`, which can be accessed after graph execution. Because `threads` allow access to graph's state after execution, several powerful capabilities including human-in-the-loop, memory, time travel, and fault-tolerance are all possible. See [this how-to guide](../how-tos/persistence.ipynb) for an end-to-end example on how to add and use checkpointers with your graph. Below, we'll discuss each of these concepts in more detail. +LangGraph has a built-in persistence layer, implemented through checkpointers. When you compile graph with a checkpointer, the checkpointer saves a `checkpoint` of the graph state at every super-step. Those checkpoints are saved to a `thread`, which can be accessed after graph execution. Because `threads` allow access to graph's state after execution, several powerful capabilities including human-in-the-loop, memory, time travel, and fault-tolerance are all possible. Below, we'll discuss each of these concepts in more detail. ![Checkpoints](img/persistence/checkpoints.jpg) @@ -15,15 +15,19 @@ LangGraph has a built-in persistence layer, implemented through checkpointers. W ## Threads -A thread is a unique ID or [thread identifier](#threads) assigned to each checkpoint saved by a checkpointer. When invoking graph with a checkpointer, you **must** specify a `thread_id` as part of the `configurable` portion of the config: +A thread is a unique ID or thread identifier assigned to each checkpoint saved by a checkpointer. It contains the accumulated state of a sequence of [runs](./assistants.md#execution). When a run is executed, the [state](../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread. + +When invoking graph with a checkpointer, you **must** specify a `thread_id` as part of the `configurable` portion of the config: ```python {"configurable": {"thread_id": "1"}} ``` +A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run. The LangGraph Platform API provides several endpoints for creating and managing threads and thread state. See the [API reference](../cloud/reference/api/api_ref.html#tag/threads) for more details. + ## Checkpoints -Checkpoint is a snapshot of the graph state saved at each super-step and is represented by `StateSnapshot` object with the following key properties: +The state of a thread at a particular point in time is called a checkpoint. Checkpoint is a snapshot of the graph state saved at each super-step and is represented by `StateSnapshot` object with the following key properties: - `config`: Config associated with this checkpoint. - `metadata`: Metadata associated with this checkpoint. @@ -31,6 +35,8 @@ Checkpoint is a snapshot of the graph state saved at each super-step and is repr - `next` A tuple of the node names to execute next in the graph. - `tasks`: A tuple of `PregelTask` objects that contain information about next tasks to be executed. If the step was previously attempted, it will include error information. If a graph was interrupted [dynamically](../how-tos/human_in_the_loop/breakpoints.ipynb#dynamic-breakpoints) from within a node, tasks will contain additional data associated with interrupts. +Checkpoints are persisted and can be used to restore the state of a thread at a later time. + Let's see what checkpoints are saved when a simple graph is invoked as follows: ```python @@ -523,7 +529,7 @@ First, checkpointers facilitate [human-in-the-loop workflows](agentic_concepts.m ### Memory -Second, checkpointers allow for ["memory"](agentic_concepts.md#memory) between interactions. In the case of repeated human interactions (like conversations) any follow up messages can be sent to that thread, which will retain its memory of previous ones. See [this how-to guide](../how-tos/memory.ipynb) for an end-to-end example on how to add and manage conversation memory using checkpointers. +Second, checkpointers allow for ["memory"](../concepts/memory.md) between interactions. In the case of repeated human interactions (like conversations) any follow up messages can be sent to that thread, which will retain its memory of previous ones. See [Add memory](../how-tos/memory/add-memory.md) for information on how to add and manage conversation memory using checkpointers. ### Time Travel diff --git a/docs/docs/concepts/streaming.md b/docs/docs/concepts/streaming.md index 7cc7057fa..5a212ddd1 100644 --- a/docs/docs/concepts/streaming.md +++ b/docs/docs/concepts/streaming.md @@ -18,6 +18,6 @@ There are three main categories of data you can stream: - [**Stream LLM tokens**](../how-tos/streaming.md#messages) — capture token streams from anywhere: inside nodes, subgraphs, or tools. - [**Emit progress notifications from tools**](../how-tos/streaming.md#stream-custom-data) — send custom updates or progress signals directly from tool functions. -- [**Stream from subgraphs**](../how-tos/streaming.md#subgraphs) — include outputs from both the parent graph and any nested subgraphs. +- [**Stream from subgraphs**](../how-tos/streaming.md#stream-subgraph-outputs) — include outputs from both the parent graph and any nested subgraphs. - [**Use any LLM**](../how-tos/streaming.md#use-with-any-llm) — stream tokens from any LLM, even if it's not a LangChain model using the `custom` streaming mode. - [**Use multiple streaming modes**](../how-tos/streaming.md#stream-multiple-modes) — choose from `values` (full state), `updates` (state deltas), `messages` (LLM tokens + metadata), `custom` (arbitrary user data), or `debug` (detailed traces). \ No newline at end of file diff --git a/docs/docs/how-tos/memory.ipynb b/docs/docs/how-tos/memory.ipynb deleted file mode 100644 index 63762a837..000000000 --- a/docs/docs/how-tos/memory.ipynb +++ /dev/null @@ -1,415 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "15ed7413-c876-4d38-b080-79c71133549b", - "metadata": {}, - "source": [ - "# Manage memory\n", - "\n", - "Many AI applications need memory to share context across multiple interactions. LangGraph supports two types of memory essential for building conversational agents:\n", - "\n", - "- [Short-term memory](#add-short-term-memory): Tracks the ongoing conversation by maintaining message history within a session.\n", - "- [Long-term memory](#add-long-term-memory): Stores user-specific or application-level data across sessions.\n", - "\n", - "With [short-term memory](#add-short-term-memory) enabled, long conversations can exceed the LLM's context window. Common solutions are:\n", - "\n", - "* [Trimming](#trim-messages): Remove first or last N messages (before calling LLM)\n", - "* [Summarization](#summarize-messages): Summarize earlier messages in the history and replace them with a summary\n", - "* [Delete messages](#delete-messages) from LangGraph state permanently\n", - "* custom strategies (e.g., message filtering, etc.)\n", - "\n", - "This allows the agent to keep track of the conversation without exceeding the LLM's context window." - ] - }, - { - "cell_type": "markdown", - "id": "db38b03c-5609-49e3-9b93-bad0aab47ffb", - "metadata": {}, - "source": [ - "## Add short-term memory\n", - "\n", - "Short-term memory enables agents to track multi-turn conversations:\n", - "\n", - "```python\n", - "# highlight-next-line\n", - "from langgraph.checkpoint.memory import InMemorySaver\n", - "from langgraph.graph import StateGraph\n", - "\n", - "# highlight-next-line\n", - "checkpointer = InMemorySaver()\n", - "\n", - "builder = StateGraph(...)\n", - "# highlight-next-line\n", - "graph = builder.compile(checkpointer=checkpointer)\n", - "\n", - "graph.invoke(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"hi! i am Bob\"}]},\n", - " # highlight-next-line\n", - " {\"configurable\": {\"thread_id\": \"1\"}},\n", - ")\n", - "```\n", - "\n", - "See the [persistence](../persistence#add-short-term-memory) guide to learn more about working with short-term memory." - ] - }, - { - "cell_type": "markdown", - "id": "05bf55fd-b0b0-4fbf-9f15-aefac7f300bb", - "metadata": {}, - "source": [ - "## Add long-term memory\n", - "\n", - "Use long-term memory to store user-specific or application-specific data across conversations. This is useful for applications like chatbots, where you want to remember user preferences or other information.\n", - "\n", - "```python\n", - "# highlight-next-line\n", - "from langgraph.store.memory import InMemoryStore\n", - "from langgraph.graph import StateGraph\n", - "\n", - "# highlight-next-line\n", - "store = InMemoryStore()\n", - "\n", - "builder = StateGraph(...)\n", - "# highlight-next-line\n", - "graph = builder.compile(store=store)\n", - "```\n", - "\n", - "See the [persistence](../persistence#add-long-term-memory) guide to learn more about working with long-term memory." - ] - }, - { - "cell_type": "markdown", - "id": "e109c1b2-a44e-4ec0-8a11-1377e59315c6", - "metadata": {}, - "source": [ - "## Trim messages\n", - "\n", - "To trim message history, you can use [`trim_messages`](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html) function:\n", - "\n", - "```python\n", - "# highlight-next-line\n", - "from langchain_core.messages.utils import (\n", - " # highlight-next-line\n", - " trim_messages,\n", - " # highlight-next-line\n", - " count_tokens_approximately\n", - "# highlight-next-line\n", - ")\n", - "\n", - "def call_model(state: MessagesState):\n", - " # highlight-next-line\n", - " messages = trim_messages(\n", - " state[\"messages\"],\n", - " strategy=\"last\",\n", - " token_counter=count_tokens_approximately,\n", - " max_tokens=128,\n", - " start_on=\"human\",\n", - " end_on=(\"human\", \"tool\"),\n", - " )\n", - " response = model.invoke(messages)\n", - " return {\"messages\": [response]}\n", - "\n", - "builder = StateGraph(MessagesState)\n", - "builder.add_node(call_model)\n", - "...\n", - "```\n", - "\n", - "??? example \"Full example: trim messages\"\n", - "\n", - " ```python\n", - " # highlight-next-line\n", - " from langchain_core.messages.utils import (\n", - " # highlight-next-line\n", - " trim_messages,\n", - " # highlight-next-line\n", - " count_tokens_approximately\n", - " # highlight-next-line\n", - " )\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, START, MessagesState\n", - " \n", - " model = init_chat_model(\"anthropic:claude-3-7-sonnet-latest\")\n", - " summarization_model = model.bind(max_tokens=128)\n", - " \n", - " def call_model(state: MessagesState):\n", - " # highlight-next-line\n", - " messages = trim_messages(\n", - " state[\"messages\"],\n", - " strategy=\"last\",\n", - " token_counter=count_tokens_approximately,\n", - " max_tokens=128,\n", - " start_on=\"human\",\n", - " end_on=(\"human\", \"tool\"),\n", - " )\n", - " response = model.invoke(messages)\n", - " return {\"messages\": [response]}\n", - " \n", - " checkpointer = InMemorySaver()\n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " graph = builder.compile(checkpointer=checkpointer)\n", - " \n", - " config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - " graph.invoke({\"messages\": \"hi, my name is bob\"}, config)\n", - " graph.invoke({\"messages\": \"write a short poem about cats\"}, config)\n", - " graph.invoke({\"messages\": \"now do the same but for dogs\"}, config)\n", - " final_response = graph.invoke({\"messages\": \"what's my name?\"}, config)\n", - "\n", - " final_response[\"messages\"][-1].pretty_print()\n", - " ```\n", - "\n", - " ```\n", - " ================================== Ai Message ==================================\n", - " \n", - " Your name is Bob, as you mentioned when you first introduced yourself.\n", - " ```" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "6676cd03-3e97-4550-ad4a-72d04f2cee7c", - "metadata": {}, - "source": [ - "## Summarize messages\n", - "\n", - "An effective strategy for handling long conversation history is to summarize earlier messages once they reach a certain threshold:\n", - "\n", - "```python\n", - "from typing import Any, TypedDict\n", - "\n", - "from langchain_core.messages import AnyMessage\n", - "from langchain_core.messages.utils import count_tokens_approximately\n", - "# highlight-next-line\n", - "from langmem.short_term import SummarizationNode\n", - "from langgraph.graph import StateGraph, START, MessagesState\n", - "\n", - "class State(MessagesState):\n", - " # highlight-next-line\n", - " context: dict[str, Any] # (1)!\n", - "\n", - "class LLMInputState(TypedDict): # (2)!\n", - " summarized_messages: list[AnyMessage]\n", - " context: dict[str, Any]\n", - "\n", - "# highlight-next-line\n", - "summarization_node = SummarizationNode(\n", - " token_counter=count_tokens_approximately,\n", - " model=summarization_model,\n", - " max_tokens=512,\n", - " max_tokens_before_summary=256,\n", - " max_summary_tokens=256,\n", - ")\n", - "\n", - "# highlight-next-line\n", - "def call_model(state: LLMInputState): # (3)!\n", - " response = model.invoke(state[\"summarized_messages\"])\n", - " return {\"messages\": [response]}\n", - "\n", - "builder = StateGraph(State)\n", - "builder.add_node(call_model)\n", - "# highlight-next-line\n", - "builder.add_node(\"summarize\", summarization_node)\n", - "builder.add_edge(START, \"summarize\")\n", - "builder.add_edge(\"summarize\", \"call_model\")\n", - "...\n", - "```\n", - "\n", - "1. We will keep track of our running summary in the `context` field\n", - "(expected by the `SummarizationNode`).\n", - "2. Define private state that will be used only for filtering\n", - "the inputs to `call_model` node.\n", - "3. We're passing a private input state here to isolate the messages returned by the summarization node\n", - "\n", - "??? example \"Full example: summarize messages\"\n", - "\n", - " ```python\n", - " from typing import Any, TypedDict\n", - " \n", - " from langchain.chat_models import init_chat_model\n", - " from langchain_core.messages import AnyMessage\n", - " from langchain_core.messages.utils import count_tokens_approximately\n", - " from langgraph.graph import StateGraph, START, MessagesState\n", - " from langgraph.checkpoint.memory import InMemorySaver\n", - " # highlight-next-line\n", - " from langmem.short_term import SummarizationNode\n", - " \n", - " model = init_chat_model(\"anthropic:claude-3-7-sonnet-latest\")\n", - " summarization_model = model.bind(max_tokens=128)\n", - " \n", - " class State(MessagesState):\n", - " # highlight-next-line\n", - " context: dict[str, Any] # (1)!\n", - " \n", - " class LLMInputState(TypedDict): # (2)!\n", - " summarized_messages: list[AnyMessage]\n", - " context: dict[str, Any]\n", - " \n", - " # highlight-next-line\n", - " summarization_node = SummarizationNode(\n", - " token_counter=count_tokens_approximately,\n", - " model=summarization_model,\n", - " max_tokens=256,\n", - " max_tokens_before_summary=256,\n", - " max_summary_tokens=128,\n", - " )\n", - "\n", - " # highlight-next-line\n", - " def call_model(state: LLMInputState): # (3)!\n", - " response = model.invoke(state[\"summarized_messages\"])\n", - " return {\"messages\": [response]}\n", - " \n", - " checkpointer = InMemorySaver()\n", - " builder = StateGraph(State)\n", - " builder.add_node(call_model)\n", - " # highlight-next-line\n", - " builder.add_node(\"summarize\", summarization_node)\n", - " builder.add_edge(START, \"summarize\")\n", - " builder.add_edge(\"summarize\", \"call_model\")\n", - " graph = builder.compile(checkpointer=checkpointer)\n", - " \n", - " # Invoke the graph\n", - " config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - " graph.invoke({\"messages\": \"hi, my name is bob\"}, config)\n", - " graph.invoke({\"messages\": \"write a short poem about cats\"}, config)\n", - " graph.invoke({\"messages\": \"now do the same but for dogs\"}, config)\n", - " final_response = graph.invoke({\"messages\": \"what's my name?\"}, config)\n", - "\n", - " final_response[\"messages\"][-1].pretty_print()\n", - " print(\"\\nSummary:\", final_response[\"context\"][\"running_summary\"].summary)\n", - " ```\n", - "\n", - " 1. We will keep track of our running summary in the `context` field\n", - " (expected by the `SummarizationNode`).\n", - " 2. Define private state that will be used only for filtering\n", - " the inputs to `call_model` node.\n", - " 3. We're passing a private input state here to isolate the messages returned by the summarization node\n", - "\n", - " ```\n", - " ================================== Ai Message ==================================\n", - "\n", - " From our conversation, I can see that you introduced yourself as Bob. That's the name you shared with me when we began talking.\n", - " \n", - " Summary: In this conversation, I was introduced to Bob, who then asked me to write a poem about cats. I composed a poem titled \"The Mystery of Cats\" that captured cats' graceful movements, independent nature, and their special relationship with humans. Bob then requested a similar poem about dogs, so I wrote \"The Joy of Dogs,\" which highlighted dogs' loyalty, enthusiasm, and loving companionship. Both poems were written in a similar style but emphasized the distinct characteristics that make each pet special.\n", - " ```" - ] - }, - { - "cell_type": "markdown", - "id": "361d880b-1258-4708-8f0e-5efc95031e78", - "metadata": {}, - "source": [ - "## Delete messages\n", - "\n", - "To delete messages from the graph state, you can use the `RemoveMessage`.\n", - "\n", - "* Remove specific messages:\n", - "\n", - " ```python\n", - " # highlight-next-line\n", - " from langchain_core.messages import RemoveMessage\n", - " \n", - " def delete_messages(state):\n", - " messages = state[\"messages\"]\n", - " if len(messages) > 2:\n", - " # remove the earliest two messages\n", - " # highlight-next-line\n", - " return {\"messages\": [RemoveMessage(id=m.id) for m in messages[:2]]}\n", - " ```\n", - "\n", - "* Remove **all** messages:\n", - " \n", - " ```python\n", - " # highlight-next-line\n", - " from langgraph.graph.message import REMOVE_ALL_MESSAGES\n", - " \n", - " def delete_messages(state):\n", - " # highlight-next-line\n", - " return {\"messages\": [RemoveMessage(id=REMOVE_ALL_MESSAGES)]}\n", - " ```\n", - "\n", - "!!! important \"`add_messages` reducer\"\n", - "\n", - " For `RemoveMessage` to work, you need to use a state key with [`add_messages`][langgraph.graph.message.add_messages] [reducer](../../../concepts/low_level#reducers), like [`MessagesState`](../../../concepts/low_level#messagesstate)\n", - "\n", - "!!! warning \"Valid message history\"\n", - "\n", - " When deleting messages, **make sure** that the resulting message history is valid. Check the limitations of the LLM provider you're using. For example:\n", - " \n", - " * some providers expect message history to start with a `user` message\n", - " * most providers require `assistant` messages with tool calls to be followed by corresponding `tool` result messages.\n", - "\n", - "??? example \"Full example: delete messages\"\n", - "\n", - " ```python\n", - " # highlight-next-line\n", - " from langchain_core.messages import RemoveMessage\n", - " \n", - " def delete_messages(state):\n", - " messages = state[\"messages\"]\n", - " if len(messages) > 2:\n", - " # remove the earliest two messages\n", - " # highlight-next-line\n", - " return {\"messages\": [RemoveMessage(id=m.id) for m in messages[:2]]}\n", - " \n", - " def call_model(state: MessagesState):\n", - " response = model.invoke(state[\"messages\"])\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_sequence([call_model, delete_messages])\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " checkpointer = InMemorySaver()\n", - " app = builder.compile(checkpointer=checkpointer)\n", - " \n", - " for event in app.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}]},\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " print([(message.type, message.content) for message in event[\"messages\"]])\n", - " \n", - " for event in app.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]},\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " print([(message.type, message.content) for message in event[\"messages\"]])\n", - " ```\n", - "\n", - " ```\n", - " [('human', \"hi! I'm bob\")]\n", - " [('human', \"hi! I'm bob\"), ('ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?')]\n", - " [('human', \"hi! I'm bob\"), ('ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?'), ('human', \"what's my name?\")]\n", - " [('human', \"hi! I'm bob\"), ('ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?'), ('human', \"what's my name?\"), ('ai', 'Your name is Bob.')]\n", - " [('human', \"what's my name?\"), ('ai', 'Your name is Bob.')]\n", - " ```" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/docs/how-tos/memory/add-memory.md b/docs/docs/how-tos/memory/add-memory.md new file mode 100644 index 000000000..c44b7d4f0 --- /dev/null +++ b/docs/docs/how-tos/memory/add-memory.md @@ -0,0 +1,1787 @@ +# Add and manage memory + +AI applications need [memory](../../concepts/memory.md) to share context across multiple interactions. In LangGraph, you can add two types of memory: + +- [Add short-term memory](#add-short-term-memory) as a part of your agent's [state](../../concepts/low_level.md#state) to enable multi-turn conversations. +- [Add long-term memory](#add-long-term-memory) to store user-specific or application-level data across sessions. + +## Add short-term memory + +**Short-term** memory (thread-level [persistence](../../concepts/persistence.md)) enables agents to track multi-turn conversations. To add short-term memory: + +```python +# highlight-next-line +from langgraph.checkpoint.memory import InMemorySaver +from langgraph.graph import StateGraph + +# highlight-next-line +checkpointer = InMemorySaver() + +builder = StateGraph(...) +# highlight-next-line +graph = builder.compile(checkpointer=checkpointer) + +graph.invoke( + {"messages": [{"role": "user", "content": "hi! i am Bob"}]}, + # highlight-next-line + {"configurable": {"thread_id": "1"}}, +) +``` + +### Use in production + +In production, use a checkpointer backed by a database: + +```python +from langgraph.checkpoint.postgres import PostgresSaver + +DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" +# highlight-next-line +with PostgresSaver.from_conn_string(DB_URI) as checkpointer: + builder = StateGraph(...) + # highlight-next-line + graph = builder.compile(checkpointer=checkpointer) +``` + +??? example "Example: using [Postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) checkpointer" + + ``` + pip install -U "psycopg[binary,pool]" langgraph langgraph-checkpoint-postgres + ``` + + !!! Setup + You need to call `checkpointer.setup()` the first time you're using Postgres checkpointer + + === "Sync" + + ```python + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, MessagesState, START + # highlight-next-line + from langgraph.checkpoint.postgres import PostgresSaver + + model = init_chat_model(model="anthropic:claude-3-5-haiku-latest") + + DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" + # highlight-next-line + with PostgresSaver.from_conn_string(DB_URI) as checkpointer: + # checkpointer.setup() + + def call_model(state: MessagesState): + response = model.invoke(state["messages"]) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + + # highlight-next-line + graph = builder.compile(checkpointer=checkpointer) + + config = { + "configurable": { + # highlight-next-line + "thread_id": "1" + } + } + + for chunk in graph.stream( + {"messages": [{"role": "user", "content": "hi! I'm bob"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + + for chunk in graph.stream( + {"messages": [{"role": "user", "content": "what's my name?"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + ``` + + === "Async" + + ```python + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, MessagesState, START + # highlight-next-line + from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver + + model = init_chat_model(model="anthropic:claude-3-5-haiku-latest") + + DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" + # highlight-next-line + async with AsyncPostgresSaver.from_conn_string(DB_URI) as checkpointer: + # await checkpointer.setup() + + async def call_model(state: MessagesState): + response = await model.ainvoke(state["messages"]) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + + # highlight-next-line + graph = builder.compile(checkpointer=checkpointer) + + config = { + "configurable": { + # highlight-next-line + "thread_id": "1" + } + } + + async for chunk in graph.astream( + {"messages": [{"role": "user", "content": "hi! I'm bob"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + + async for chunk in graph.astream( + {"messages": [{"role": "user", "content": "what's my name?"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + ``` + + + +??? example "Example: using [MongoDB](https://pypi.org/project/langgraph-checkpoint-mongodb/) checkpointer" + + ``` + pip install -U pymongo langgraph langgraph-checkpoint-mongodb + ``` + + !!! note "Setup" + + To use the MongoDB checkpointer, you will need a MongoDB cluster. Follow [this guide](https://www.mongodb.com/docs/guides/atlas/cluster/) to create a cluster if you don't already have one. + + === "Sync" + + ```python + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, MessagesState, START + # highlight-next-line + from langgraph.checkpoint.mongodb import MongoDBSaver + + model = init_chat_model(model="anthropic:claude-3-5-haiku-latest") + + DB_URI = "localhost:27017" + # highlight-next-line + with MongoDBSaver.from_conn_string(DB_URI) as checkpointer: + + def call_model(state: MessagesState): + response = model.invoke(state["messages"]) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + + # highlight-next-line + graph = builder.compile(checkpointer=checkpointer) + + config = { + "configurable": { + # highlight-next-line + "thread_id": "1" + } + } + + for chunk in graph.stream( + {"messages": [{"role": "user", "content": "hi! I'm bob"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + + for chunk in graph.stream( + {"messages": [{"role": "user", "content": "what's my name?"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + ``` + + === "Async" + + ```python + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, MessagesState, START + # highlight-next-line + from langgraph.checkpoint.mongodb.aio import AsyncMongoDBSaver + + model = init_chat_model(model="anthropic:claude-3-5-haiku-latest") + + DB_URI = "localhost:27017" + # highlight-next-line + async with AsyncMongoDBSaver.from_conn_string(DB_URI) as checkpointer: + + async def call_model(state: MessagesState): + response = await model.ainvoke(state["messages"]) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + + # highlight-next-line + graph = builder.compile(checkpointer=checkpointer) + + config = { + "configurable": { + # highlight-next-line + "thread_id": "1" + } + } + + async for chunk in graph.astream( + {"messages": [{"role": "user", "content": "hi! I'm bob"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + + async for chunk in graph.astream( + {"messages": [{"role": "user", "content": "what's my name?"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + ``` + +??? example "Example: using [Redis](https://pypi.org/project/langgraph-checkpoint-redis/) checkpointer" + + ``` + pip install -U langgraph langgraph-checkpoint-redis + ``` + + !!! Setup + You need to call `checkpointer.setup()` the first time you're using Redis checkpointer + + + === "Sync" + + ```python + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, MessagesState, START + # highlight-next-line + from langgraph.checkpoint.redis import RedisSaver + + model = init_chat_model(model="anthropic:claude-3-5-haiku-latest") + + DB_URI = "redis://localhost:6379" + # highlight-next-line + with RedisSaver.from_conn_string(DB_URI) as checkpointer: + # checkpointer.setup() + + def call_model(state: MessagesState): + response = model.invoke(state["messages"]) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + + # highlight-next-line + graph = builder.compile(checkpointer=checkpointer) + + config = { + "configurable": { + # highlight-next-line + "thread_id": "1" + } + } + + for chunk in graph.stream( + {"messages": [{"role": "user", "content": "hi! I'm bob"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + + for chunk in graph.stream( + {"messages": [{"role": "user", "content": "what's my name?"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + ``` + + === "Async" + + ```python + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, MessagesState, START + # highlight-next-line + from langgraph.checkpoint.redis.aio import AsyncRedisSaver + + model = init_chat_model(model="anthropic:claude-3-5-haiku-latest") + + DB_URI = "redis://localhost:6379" + # highlight-next-line + async with AsyncRedisSaver.from_conn_string(DB_URI) as checkpointer: + # await checkpointer.asetup() + + async def call_model(state: MessagesState): + response = await model.ainvoke(state["messages"]) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + + # highlight-next-line + graph = builder.compile(checkpointer=checkpointer) + + config = { + "configurable": { + # highlight-next-line + "thread_id": "1" + } + } + + async for chunk in graph.astream( + {"messages": [{"role": "user", "content": "hi! I'm bob"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + + async for chunk in graph.astream( + {"messages": [{"role": "user", "content": "what's my name?"}]}, + # highlight-next-line + config, + stream_mode="values" + ): + chunk["messages"][-1].pretty_print() + ``` + +### Use in subgraphs + +If your graph contains [subgraphs](../../concepts/subgraphs.md), you only need to provide the checkpointer when compiling the parent graph. LangGraph will automatically propagate the checkpointer to the child subgraphs. + +```python +from langgraph.graph import START, StateGraph +from langgraph.checkpoint.memory import InMemorySaver +from typing import TypedDict + +class State(TypedDict): + foo: str + +# Subgraph + +def subgraph_node_1(state: State): + return {"foo": state["foo"] + "bar"} + +subgraph_builder = StateGraph(State) +subgraph_builder.add_node(subgraph_node_1) +subgraph_builder.add_edge(START, "subgraph_node_1") +# highlight-next-line +subgraph = subgraph_builder.compile() + +# Parent graph + +def node_1(state: State): + return {"foo": "hi! " + state["foo"]} + +builder = StateGraph(State) +# highlight-next-line +builder.add_node("node_1", subgraph) +builder.add_edge(START, "node_1") + +checkpointer = InMemorySaver() +# highlight-next-line +graph = builder.compile(checkpointer=checkpointer) +``` + +If you want the subgraph to have its own memory, you can compile it `with checkpointer=True`. This is useful in [multi-agent](../../concepts/multi_agent.md) systems, if you want agents to keep track of their internal message histories. + +```python +subgraph_builder = StateGraph(...) +# highlight-next-line +subgraph = subgraph_builder.compile(checkpointer=True) +``` + +### Read short-term memory in tools { #read-short-term } + +LangGraph allows agents to access their short-term memory (state) inside the tools. + +```python +from typing import Annotated +from langgraph.prebuilt import InjectedState, create_react_agent + +class CustomState(AgentState): + # highlight-next-line + user_id: str + +def get_user_info( + # highlight-next-line + state: Annotated[CustomState, InjectedState] +) -> str: + """Look up user info.""" + # highlight-next-line + user_id = state["user_id"] + return "User is John Smith" if user_id == "user_123" else "Unknown user" + +agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[get_user_info], + # highlight-next-line + state_schema=CustomState, +) + +agent.invoke({ + "messages": "look up user information", + # highlight-next-line + "user_id": "user_123" +}) +``` + +See the [Context](../../agents/context.md) guide for more information. + +### Write short-term memory from tools { #write-short-term } + +To modify the agent's short-term memory (state) during execution, you can return state updates directly from the tools. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts. + +```python +from typing import Annotated +from langchain_core.tools import InjectedToolCallId +from langchain_core.runnables import RunnableConfig +from langchain_core.messages import ToolMessage +from langgraph.prebuilt import InjectedState, create_react_agent +from langgraph.prebuilt.chat_agent_executor import AgentState +from langgraph.types import Command + +class CustomState(AgentState): + # highlight-next-line + user_name: str + +def update_user_info( + tool_call_id: Annotated[str, InjectedToolCallId], + config: RunnableConfig +) -> Command: + """Look up and update user info.""" + user_id = config["configurable"].get("user_id") + name = "John Smith" if user_id == "user_123" else "Unknown user" + # highlight-next-line + return Command(update={ + # highlight-next-line + "user_name": name, + # update the message history + "messages": [ + ToolMessage( + "Successfully looked up user information", + tool_call_id=tool_call_id + ) + ] + }) + +def greet( + # highlight-next-line + state: Annotated[CustomState, InjectedState] +) -> str: + """Use this to greet the user once you found their info.""" + user_name = state["user_name"] + return f"Hello {user_name}!" + +agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[update_user_info, greet], + # highlight-next-line + state_schema=CustomState +) + +agent.invoke( + {"messages": [{"role": "user", "content": "greet the user"}]}, + # highlight-next-line + config={"configurable": {"user_id": "user_123"}} +) +``` + +## Add long-term memory + +Use long-term memory to store user-specific or application-specific data across conversations. + +```python +# highlight-next-line +from langgraph.store.memory import InMemoryStore +from langgraph.graph import StateGraph + +# highlight-next-line +store = InMemoryStore() + +builder = StateGraph(...) +# highlight-next-line +graph = builder.compile(store=store) +``` + +### Use in production + +In production, use a store backed by a database: + +```python +from langgraph.store.postgres import PostgresStore + +DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" +# highlight-next-line +with PostgresStore.from_conn_string(DB_URI) as store: + builder = StateGraph(...) + # highlight-next-line + graph = builder.compile(store=store) +``` + +??? example "Example: using [Postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) store" + + ``` + pip install -U "psycopg[binary,pool]" langgraph langgraph-checkpoint-postgres + ``` + + !!! Setup + You need to call `store.setup()` the first time you're using Postgres store + + === "Sync" + + ```python + from langchain_core.runnables import RunnableConfig + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, MessagesState, START + from langgraph.checkpoint.postgres import PostgresSaver + # highlight-next-line + from langgraph.store.postgres import PostgresStore + from langgraph.store.base import BaseStore + + model = init_chat_model(model="anthropic:claude-3-5-haiku-latest") + + DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" + + with ( + # highlight-next-line + PostgresStore.from_conn_string(DB_URI) as store, + PostgresSaver.from_conn_string(DB_URI) as checkpointer, + ): + # store.setup() + # checkpointer.setup() + + def call_model( + state: MessagesState, + config: RunnableConfig, + *, + # highlight-next-line + store: BaseStore, + ): + user_id = config["configurable"]["user_id"] + namespace = ("memories", user_id) + # highlight-next-line + memories = store.search(namespace, query=str(state["messages"][-1].content)) + info = "\n".join([d.value["data"] for d in memories]) + system_msg = f"You are a helpful assistant talking to the user. User info: {info}" + + # Store new memories if the user asks the model to remember + last_message = state["messages"][-1] + if "remember" in last_message.content.lower(): + memory = "User name is Bob" + # highlight-next-line + store.put(namespace, str(uuid.uuid4()), {"data": memory}) + + response = model.invoke( + [{"role": "system", "content": system_msg}] + state["messages"] + ) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + + graph = builder.compile( + checkpointer=checkpointer, + # highlight-next-line + store=store, + ) + + config = { + "configurable": { + # highlight-next-line + "thread_id": "1", + # highlight-next-line + "user_id": "1", + } + } + for chunk in graph.stream( + {"messages": [{"role": "user", "content": "Hi! Remember: my name is Bob"}]}, + # highlight-next-line + config, + stream_mode="values", + ): + chunk["messages"][-1].pretty_print() + + config = { + "configurable": { + # highlight-next-line + "thread_id": "2", + "user_id": "1", + } + } + + for chunk in graph.stream( + {"messages": [{"role": "user", "content": "what is my name?"}]}, + # highlight-next-line + config, + stream_mode="values", + ): + chunk["messages"][-1].pretty_print() + ``` + + === "Async" + + ```python + from langchain_core.runnables import RunnableConfig + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, MessagesState, START + from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver + # highlight-next-line + from langgraph.store.postgres.aio import AsyncPostgresStore + from langgraph.store.base import BaseStore + + model = init_chat_model(model="anthropic:claude-3-5-haiku-latest") + + DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" + + async with ( + # highlight-next-line + AsyncPostgresStore.from_conn_string(DB_URI) as store, + AsyncPostgresSaver.from_conn_string(DB_URI) as checkpointer, + ): + # await store.setup() + # await checkpointer.setup() + + async def call_model( + state: MessagesState, + config: RunnableConfig, + *, + # highlight-next-line + store: BaseStore, + ): + user_id = config["configurable"]["user_id"] + namespace = ("memories", user_id) + # highlight-next-line + memories = await store.asearch(namespace, query=str(state["messages"][-1].content)) + info = "\n".join([d.value["data"] for d in memories]) + system_msg = f"You are a helpful assistant talking to the user. User info: {info}" + + # Store new memories if the user asks the model to remember + last_message = state["messages"][-1] + if "remember" in last_message.content.lower(): + memory = "User name is Bob" + # highlight-next-line + await store.aput(namespace, str(uuid.uuid4()), {"data": memory}) + + response = await model.ainvoke( + [{"role": "system", "content": system_msg}] + state["messages"] + ) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + + graph = builder.compile( + checkpointer=checkpointer, + # highlight-next-line + store=store, + ) + + config = { + "configurable": { + # highlight-next-line + "thread_id": "1", + # highlight-next-line + "user_id": "1", + } + } + async for chunk in graph.astream( + {"messages": [{"role": "user", "content": "Hi! Remember: my name is Bob"}]}, + # highlight-next-line + config, + stream_mode="values", + ): + chunk["messages"][-1].pretty_print() + + config = { + "configurable": { + # highlight-next-line + "thread_id": "2", + "user_id": "1", + } + } + + async for chunk in graph.astream( + {"messages": [{"role": "user", "content": "what is my name?"}]}, + # highlight-next-line + config, + stream_mode="values", + ): + chunk["messages"][-1].pretty_print() + ``` + +??? example "Example: using [Redis](https://pypi.org/project/langgraph-checkpoint-redis/) store" + + ``` + pip install -U langgraph langgraph-checkpoint-redis + ``` + + !!! Setup + You need to call `store.setup()` the first time you're using Redis store + + + === "Sync" + + ```python + from langchain_core.runnables import RunnableConfig + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, MessagesState, START + from langgraph.checkpoint.redis import RedisSaver + # highlight-next-line + from langgraph.store.redis import RedisStore + from langgraph.store.base import BaseStore + + model = init_chat_model(model="anthropic:claude-3-5-haiku-latest") + + DB_URI = "redis://localhost:6379" + + with ( + # highlight-next-line + RedisStore.from_conn_string(DB_URI) as store, + RedisSaver.from_conn_string(DB_URI) as checkpointer, + ): + store.setup() + checkpointer.setup() + + def call_model( + state: MessagesState, + config: RunnableConfig, + *, + # highlight-next-line + store: BaseStore, + ): + user_id = config["configurable"]["user_id"] + namespace = ("memories", user_id) + # highlight-next-line + memories = store.search(namespace, query=str(state["messages"][-1].content)) + info = "\n".join([d.value["data"] for d in memories]) + system_msg = f"You are a helpful assistant talking to the user. User info: {info}" + + # Store new memories if the user asks the model to remember + last_message = state["messages"][-1] + if "remember" in last_message.content.lower(): + memory = "User name is Bob" + # highlight-next-line + store.put(namespace, str(uuid.uuid4()), {"data": memory}) + + response = model.invoke( + [{"role": "system", "content": system_msg}] + state["messages"] + ) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + + graph = builder.compile( + checkpointer=checkpointer, + # highlight-next-line + store=store, + ) + + config = { + "configurable": { + # highlight-next-line + "thread_id": "1", + # highlight-next-line + "user_id": "1", + } + } + for chunk in graph.stream( + {"messages": [{"role": "user", "content": "Hi! Remember: my name is Bob"}]}, + # highlight-next-line + config, + stream_mode="values", + ): + chunk["messages"][-1].pretty_print() + + config = { + "configurable": { + # highlight-next-line + "thread_id": "2", + "user_id": "1", + } + } + + for chunk in graph.stream( + {"messages": [{"role": "user", "content": "what is my name?"}]}, + # highlight-next-line + config, + stream_mode="values", + ): + chunk["messages"][-1].pretty_print() + ``` + + === "Async" + + ```python + from langchain_core.runnables import RunnableConfig + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, MessagesState, START + from langgraph.checkpoint.redis.aio import AsyncRedisSaver + # highlight-next-line + from langgraph.store.redis.aio import AsyncRedisStore + from langgraph.store.base import BaseStore + + model = init_chat_model(model="anthropic:claude-3-5-haiku-latest") + + DB_URI = "redis://localhost:6379" + + async with ( + # highlight-next-line + AsyncRedisStore.from_conn_string(DB_URI) as store, + AsyncRedisSaver.from_conn_string(DB_URI) as checkpointer, + ): + # await store.setup() + # await checkpointer.asetup() + + async def call_model( + state: MessagesState, + config: RunnableConfig, + *, + # highlight-next-line + store: BaseStore, + ): + user_id = config["configurable"]["user_id"] + namespace = ("memories", user_id) + # highlight-next-line + memories = await store.asearch(namespace, query=str(state["messages"][-1].content)) + info = "\n".join([d.value["data"] for d in memories]) + system_msg = f"You are a helpful assistant talking to the user. User info: {info}" + + # Store new memories if the user asks the model to remember + last_message = state["messages"][-1] + if "remember" in last_message.content.lower(): + memory = "User name is Bob" + # highlight-next-line + await store.aput(namespace, str(uuid.uuid4()), {"data": memory}) + + response = await model.ainvoke( + [{"role": "system", "content": system_msg}] + state["messages"] + ) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + + graph = builder.compile( + checkpointer=checkpointer, + # highlight-next-line + store=store, + ) + + config = { + "configurable": { + # highlight-next-line + "thread_id": "1", + # highlight-next-line + "user_id": "1", + } + } + async for chunk in graph.astream( + {"messages": [{"role": "user", "content": "Hi! Remember: my name is Bob"}]}, + # highlight-next-line + config, + stream_mode="values", + ): + chunk["messages"][-1].pretty_print() + + config = { + "configurable": { + # highlight-next-line + "thread_id": "2", + "user_id": "1", + } + } + + async for chunk in graph.astream( + {"messages": [{"role": "user", "content": "what is my name?"}]}, + # highlight-next-line + config, + stream_mode="values", + ): + chunk["messages"][-1].pretty_print() + ``` + +### Read long-term memory in tools { #read-long-term } + +```python title="A tool the agent can use to look up user information" +from langchain_core.runnables import RunnableConfig +from langgraph.config import get_store +from langgraph.prebuilt import create_react_agent +from langgraph.store.memory import InMemoryStore + +# highlight-next-line +store = InMemoryStore() # (1)! + +# highlight-next-line +store.put( # (2)! + ("users",), # (3)! + "user_123", # (4)! + { + "name": "John Smith", + "language": "English", + } # (5)! +) + +def get_user_info(config: RunnableConfig) -> str: + """Look up user info.""" + # Same as that provided to `create_react_agent` + # highlight-next-line + store = get_store() # (6)! + user_id = config["configurable"].get("user_id") + # highlight-next-line + user_info = store.get(("users",), user_id) # (7)! + return str(user_info.value) if user_info else "Unknown user" + +agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[get_user_info], + # highlight-next-line + store=store # (8)! +) + +# Run the agent +agent.invoke( + {"messages": [{"role": "user", "content": "look up user information"}]}, + # highlight-next-line + config={"configurable": {"user_id": "user_123"}} +) +``` + +1. The `InMemoryStore` is a store that stores data in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [store documentation](../../reference/store.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready store for you. +2. For this example, we write some sample data to the store using the `put` method. Please see the [BaseStore.put][langgraph.store.base.BaseStore.put] API reference for more details. +3. The first argument is the namespace. This is used to group related data together. In this case, we are using the `users` namespace to group user data. +4. A key within the namespace. This example uses a user ID for the key. +5. The data that we want to store for the given user. +6. The `get_store` function is used to access the store. You can call it from anywhere in your code, including tools and prompts. This function returns the store that was passed to the agent when it was created. +7. The `get` method is used to retrieve data from the store. The first argument is the namespace, and the second argument is the key. This will return a `StoreValue` object, which contains the value and metadata about the value. +8. The `store` is passed to the agent. This enables the agent to access the store when running tools. You can also use the `get_store` function to access the store from anywhere in your code. + +### Write long-term memory from tools { #write-long-term } + +```python title="Example of a tool that updates user information" +from typing_extensions import TypedDict + +from langgraph.config import get_store +from langgraph.prebuilt import create_react_agent +from langgraph.store.memory import InMemoryStore + +store = InMemoryStore() # (1)! + +class UserInfo(TypedDict): # (2)! + name: str + +def save_user_info(user_info: UserInfo, config: RunnableConfig) -> str: # (3)! + """Save user info.""" + # Same as that provided to `create_react_agent` + # highlight-next-line + store = get_store() # (4)! + user_id = config["configurable"].get("user_id") + # highlight-next-line + store.put(("users",), user_id, user_info) # (5)! + return "Successfully saved user info." + +agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[save_user_info], + # highlight-next-line + store=store +) + +# Run the agent +agent.invoke( + {"messages": [{"role": "user", "content": "My name is John Smith"}]}, + # highlight-next-line + config={"configurable": {"user_id": "user_123"}} # (6)! +) + +# You can access the store directly to get the value +store.get(("users",), "user_123").value +``` + +1. The `InMemoryStore` is a store that stores data in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [store documentation](../../reference/store.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready store for you. +2. The `UserInfo` class is a `TypedDict` that defines the structure of the user information. The LLM will use this to format the response according to the schema. +3. The `save_user_info` function is a tool that allows an agent to update user information. This could be useful for a chat application where the user wants to update their profile information. +4. The `get_store` function is used to access the store. You can call it from anywhere in your code, including tools and prompts. This function returns the store that was passed to the agent when it was created. +5. The `put` method is used to store data in the store. The first argument is the namespace, and the second argument is the key. This will store the user information in the store. +6. The `user_id` is passed in the config. This is used to identify the user whose information is being updated. + +### Use semantic search + +Enable semantic search in your graph's memory store to let graph agents search for items in the store by semantic similarity. + +```python +from langchain.embeddings import init_embeddings +from langgraph.store.memory import InMemoryStore + +# Create store with semantic search enabled +embeddings = init_embeddings("openai:text-embedding-3-small") +store = InMemoryStore( + index={ + "embed": embeddings, + "dims": 1536, + } +) + +store.put(("user_123", "memories"), "1", {"text": "I love pizza"}) +store.put(("user_123", "memories"), "2", {"text": "I am a plumber"}) + +items = store.search( + ("user_123", "memories"), query="I'm hungry", limit=1 +) +``` + +??? example "Long-term memory with semantic search" + + ```python + from typing import Optional + + from langchain.embeddings import init_embeddings + from langchain.chat_models import init_chat_model + from langgraph.store.base import BaseStore + from langgraph.store.memory import InMemoryStore + from langgraph.graph import START, MessagesState, StateGraph + + llm = init_chat_model("openai:gpt-4o-mini") + + # Create store with semantic search enabled + embeddings = init_embeddings("openai:text-embedding-3-small") + store = InMemoryStore( + index={ + "embed": embeddings, + "dims": 1536, + } + ) + + store.put(("user_123", "memories"), "1", {"text": "I love pizza"}) + store.put(("user_123", "memories"), "2", {"text": "I am a plumber"}) + + def chat(state, *, store: BaseStore): + # Search based on user's last message + items = store.search( + ("user_123", "memories"), query=state["messages"][-1].content, limit=2 + ) + memories = "\n".join(item.value["text"] for item in items) + memories = f"## Memories of user\n{memories}" if memories else "" + response = llm.invoke( + [ + {"role": "system", "content": f"You are a helpful assistant.\n{memories}"}, + *state["messages"], + ] + ) + return {"messages": [response]} + + + builder = StateGraph(MessagesState) + builder.add_node(chat) + builder.add_edge(START, "chat") + graph = builder.compile(store=store) + + for message, metadata in graph.stream( + input={"messages": [{"role": "user", "content": "I'm hungry"}]}, + stream_mode="messages", + ): + print(message.content, end="") + ``` + +See [this guide](../../cloud/deployment/semantic_search.md) for more information on how to use semantic search with LangGraph memory store. + +## Manage short-term memory + +With [short-term memory](#add-short-term-memory) enabled, long conversations can exceed the LLM's context window. Common solutions are: + +* [Trim messages](#trim-messages): Remove first or last N messages (before calling LLM) +* [Delete messages](#delete-messages) from LangGraph state permanently +* [Summarize messages](#summarize-messages): Summarize earlier messages in the history and replace them with a summary +* [Manage checkpoints](#manage-checkpoints) to store and retrieve message history +* Custom strategies (e.g., message filtering, etc.) + +This allows the agent to keep track of the conversation without exceeding the LLM's context window. + +### Trim messages + +Most LLMs have a maximum supported context window (denominated in tokens). One way to decide when to truncate messages is to count the tokens in the message history and truncate whenever it approaches that limit. If you're using LangChain, you can use the `trim_messages` utility and specify the number of tokens to keep from the list, as well as the `strategy` (e.g., keep the last `max_tokens`) to use for handling the boundary. + +=== "In an agent" + + To trim message history in an agent, use [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent] with the [`trim_messages`](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html) function: + + ```python + # highlight-next-line + from langchain_core.messages.utils import ( + # highlight-next-line + trim_messages, + # highlight-next-line + count_tokens_approximately + # highlight-next-line + ) + from langgraph.prebuilt import create_react_agent + + # This function will be called every time before the node that calls 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"), + ) + # highlight-next-line + return {"llm_input_messages": trimmed_messages} + + checkpointer = InMemorySaver() + agent = create_react_agent( + model, + tools, + # highlight-next-line + pre_model_hook=pre_model_hook, + checkpointer=checkpointer, + ) + ``` + +=== "In a workflow" + + To trim message history, use the [`trim_messages`](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html) function: + + ```python + # highlight-next-line + from langchain_core.messages.utils import ( + # highlight-next-line + trim_messages, + # highlight-next-line + count_tokens_approximately + # highlight-next-line + ) + + def call_model(state: MessagesState): + # highlight-next-line + messages = trim_messages( + state["messages"], + strategy="last", + token_counter=count_tokens_approximately, + max_tokens=128, + start_on="human", + end_on=("human", "tool"), + ) + response = model.invoke(messages) + return {"messages": [response]} + + builder = StateGraph(MessagesState) + builder.add_node(call_model) + ... + ``` + +??? example "Full example: trim messages" + + ```python + # highlight-next-line + from langchain_core.messages.utils import ( + # highlight-next-line + trim_messages, + # highlight-next-line + count_tokens_approximately + # highlight-next-line + ) + from langchain.chat_models import init_chat_model + from langgraph.graph import StateGraph, START, MessagesState + + model = init_chat_model("anthropic:claude-3-7-sonnet-latest") + summarization_model = model.bind(max_tokens=128) + + def call_model(state: MessagesState): + # highlight-next-line + messages = trim_messages( + state["messages"], + strategy="last", + token_counter=count_tokens_approximately, + max_tokens=128, + start_on="human", + end_on=("human", "tool"), + ) + response = model.invoke(messages) + return {"messages": [response]} + + checkpointer = InMemorySaver() + builder = StateGraph(MessagesState) + builder.add_node(call_model) + builder.add_edge(START, "call_model") + graph = builder.compile(checkpointer=checkpointer) + + config = {"configurable": {"thread_id": "1"}} + graph.invoke({"messages": "hi, my name is bob"}, config) + graph.invoke({"messages": "write a short poem about cats"}, config) + graph.invoke({"messages": "now do the same but for dogs"}, config) + final_response = graph.invoke({"messages": "what's my name?"}, config) + + final_response["messages"][-1].pretty_print() + ``` + + ``` + ================================== Ai Message ================================== + + Your name is Bob, as you mentioned when you first introduced yourself. + ``` + +### Delete messages + +You can delete messages from the graph state to manage the message history. This is useful when you want to remove specific messages or clear the entire message history. + +To delete messages from the graph state, you can use the `RemoveMessage`. For `RemoveMessage` to work, you need to use a state key with [`add_messages`][langgraph.graph.message.add_messages] [reducer](../../concepts/low_level.md#reducers), like [`MessagesState`](../../concepts/low_level.md#messagesstate). + +To remove specific messages: + +```python +# highlight-next-line +from langchain_core.messages import RemoveMessage + +def delete_messages(state): + messages = state["messages"] + if len(messages) > 2: + # remove the earliest two messages + # highlight-next-line + return {"messages": [RemoveMessage(id=m.id) for m in messages[:2]]} +``` + +To remove **all** messages: + +```python +# highlight-next-line +from langgraph.graph.message import REMOVE_ALL_MESSAGES + +def delete_messages(state): + # highlight-next-line + return {"messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES)]} +``` + +!!! warning + + When deleting messages, **make sure** that the resulting message history is valid. Check the limitations of the LLM provider you're using. For example: + + * some providers expect message history to start with a `user` message + * most providers require `assistant` messages with tool calls to be followed by corresponding `tool` result messages. + +??? example "Full example: delete messages" + + ```python + # highlight-next-line + from langchain_core.messages import RemoveMessage + + def delete_messages(state): + messages = state["messages"] + if len(messages) > 2: + # remove the earliest two messages + # highlight-next-line + return {"messages": [RemoveMessage(id=m.id) for m in messages[:2]]} + + def call_model(state: MessagesState): + response = model.invoke(state["messages"]) + return {"messages": response} + + builder = StateGraph(MessagesState) + builder.add_sequence([call_model, delete_messages]) + builder.add_edge(START, "call_model") + + checkpointer = InMemorySaver() + app = builder.compile(checkpointer=checkpointer) + + for event in app.stream( + {"messages": [{"role": "user", "content": "hi! I'm bob"}]}, + config, + stream_mode="values" + ): + print([(message.type, message.content) for message in event["messages"]]) + + for event in app.stream( + {"messages": [{"role": "user", "content": "what's my name?"}]}, + config, + stream_mode="values" + ): + print([(message.type, message.content) for message in event["messages"]]) + ``` + + ``` + [('human', "hi! I'm bob")] + [('human', "hi! I'm bob"), ('ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?')] + [('human', "hi! I'm bob"), ('ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?'), ('human', "what's my name?")] + [('human', "hi! I'm bob"), ('ai', 'Hi Bob! How are you doing today? Is there anything I can help you with?'), ('human', "what's my name?"), ('ai', 'Your name is Bob.')] + [('human', "what's my name?"), ('ai', 'Your name is Bob.')] + ``` + +### Summarize messages + +The problem with trimming or removing messages, as shown above, is that you may lose information from culling of the message queue. Because of this, some applications benefit from a more sophisticated approach of summarizing the message history using a chat model. + +![](img/memory/summary.png) + +=== "In an agent" + + To summarize message history in an agent, use [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent] with a prebuilt [`SummarizationNode`](https://langchain-ai.github.io/langmem/reference/short_term/#langmem.short_term.SummarizationNode) abstraction: + + ```python + from langchain_anthropic import ChatAnthropic + from langmem.short_term import SummarizationNode + from langchain_core.messages.utils import count_tokens_approximately + from langgraph.prebuilt import create_react_agent + from langgraph.prebuilt.chat_agent_executor import AgentState + from langgraph.checkpoint.memory import InMemorySaver + from typing import Any + + model = ChatAnthropic(model="claude-3-7-sonnet-latest") + + summarization_node = SummarizationNode( # (1)! + token_counter=count_tokens_approximately, + model=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 + # highlight-next-line + context: dict[str, Any] # (2)! + + + checkpointer = InMemorySaver() # (3)! + + agent = create_react_agent( + model=model, + tools=tools, + # highlight-next-line + pre_model_hook=summarization_node, # (4)! + # highlight-next-line + state_schema=State, # (5)! + checkpointer=checkpointer, + ) + ``` + + 1. The `InMemorySaver` is a checkpointer that stores the agent's state in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [checkpointer documentation](../../reference/checkpoints.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready checkpointer for you. + 2. The `context` key is added to the agent's state. The key contains book-keeping information for the summarization node. It is used to keep track of the last summary information and ensure that the agent doesn't summarize on every LLM call, which can be inefficient. + 3. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations. + 4. The `pre_model_hook` is set to the `SummarizationNode`. This node will summarize the message history before sending it to the LLM. The summarization node will automatically handle the summarization process and update the agent's state with the new summary. You can replace this with a custom implementation if you prefer. Please see the [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent] API reference for more details. + 5. The `state_schema` is set to the `State` class, which is the custom state that contains an extra `context` key. + + +=== "In a workflow" + + Prompting and orchestration logic can be used to summarize the message history. For example, in LangGraph you can extend the [`MessagesState`](../../concepts/low_level.md#working-with-messages-in-graph-state) to include a `summary` key: + + ```python + from langgraph.graph import MessagesState + class State(MessagesState): + summary: str + ``` + + Then, you can generate a summary of the chat history, using any existing summary as context for the next summary. This `summarize_conversation` node can be called after some number of messages have accumulated in the `messages` state key. + + ```python + def summarize_conversation(state: State): + + # First, we get any existing summary + summary = state.get("summary", "") + + # Create our summarization prompt + if summary: + + # A summary already exists + summary_message = ( + f"This is a summary of the conversation to date: {summary}\n\n" + "Extend the summary by taking into account the new messages above:" + ) + + else: + summary_message = "Create a summary of the conversation above:" + + # Add prompt to our history + messages = state["messages"] + [HumanMessage(content=summary_message)] + response = model.invoke(messages) + + # Delete all but the 2 most recent messages + delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]] + return {"summary": response.content, "messages": delete_messages} + ``` + + + +??? example "Full example: summarize messages" + + ```python + from typing import Any, TypedDict + + from langchain.chat_models import init_chat_model + from langchain_core.messages import AnyMessage + from langchain_core.messages.utils import count_tokens_approximately + from langgraph.graph import StateGraph, START, MessagesState + from langgraph.checkpoint.memory import InMemorySaver + # highlight-next-line + from langmem.short_term import SummarizationNode + + model = init_chat_model("anthropic:claude-3-7-sonnet-latest") + summarization_model = model.bind(max_tokens=128) + + class State(MessagesState): + # highlight-next-line + context: dict[str, Any] # (1)! + + class LLMInputState(TypedDict): # (2)! + summarized_messages: list[AnyMessage] + context: dict[str, Any] + + # highlight-next-line + summarization_node = SummarizationNode( + token_counter=count_tokens_approximately, + model=summarization_model, + max_tokens=256, + max_tokens_before_summary=256, + max_summary_tokens=128, + ) + + # highlight-next-line + def call_model(state: LLMInputState): # (3)! + response = model.invoke(state["summarized_messages"]) + return {"messages": [response]} + + checkpointer = InMemorySaver() + builder = StateGraph(State) + builder.add_node(call_model) + # highlight-next-line + builder.add_node("summarize", summarization_node) + builder.add_edge(START, "summarize") + builder.add_edge("summarize", "call_model") + graph = builder.compile(checkpointer=checkpointer) + + # Invoke the graph + config = {"configurable": {"thread_id": "1"}} + graph.invoke({"messages": "hi, my name is bob"}, config) + graph.invoke({"messages": "write a short poem about cats"}, config) + graph.invoke({"messages": "now do the same but for dogs"}, config) + final_response = graph.invoke({"messages": "what's my name?"}, config) + + final_response["messages"][-1].pretty_print() + print("\nSummary:", final_response["context"]["running_summary"].summary) + ``` + + 1. We will keep track of our running summary in the `context` field + (expected by the `SummarizationNode`). + 2. Define private state that will be used only for filtering + the inputs to `call_model` node. + 3. We're passing a private input state here to isolate the messages returned by the summarization node + + ``` + ================================== Ai Message ================================== + + From our conversation, I can see that you introduced yourself as Bob. That's the name you shared with me when we began talking. + + Summary: In this conversation, I was introduced to Bob, who then asked me to write a poem about cats. I composed a poem titled "The Mystery of Cats" that captured cats' graceful movements, independent nature, and their special relationship with humans. Bob then requested a similar poem about dogs, so I wrote "The Joy of Dogs," which highlighted dogs' loyalty, enthusiasm, and loving companionship. Both poems were written in a similar style but emphasized the distinct characteristics that make each pet special. + ``` + + + +### Manage checkpoints + +You can view and delete the information stored by the checkpointer. + +#### View thread state (checkpoint) + +=== "Graph/Functional API" + + ```python + config = { + "configurable": { + # highlight-next-line + "thread_id": "1", + # optionally provide an ID for a specific checkpoint, + # otherwise the latest checkpoint is shown + # highlight-next-line + # "checkpoint_id": "1f029ca3-1f5b-6704-8004-820c16b69a5a" + + } + } + # highlight-next-line + graph.get_state(config) + ``` + + ``` + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today?), HumanMessage(content="what's my name?"), AIMessage(content='Your name is Bob.')]}, next=(), + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}}, + metadata={ + 'source': 'loop', + 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}}, + 'step': 4, + 'parents': {}, + 'thread_id': '1' + }, + created_at='2025-05-05T16:01:24.680462+00:00', + parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, + tasks=(), + interrupts=() + ) + ``` + +=== "Checkpointer API" + + ```python + config = { + "configurable": { + # highlight-next-line + "thread_id": "1", + # optionally provide an ID for a specific checkpoint, + # otherwise the latest checkpoint is shown + # highlight-next-line + # "checkpoint_id": "1f029ca3-1f5b-6704-8004-820c16b69a5a" + + } + } + # highlight-next-line + checkpointer.get_tuple(config) + ``` + + ``` + CheckpointTuple( + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}}, + checkpoint={ + 'v': 3, + 'ts': '2025-05-05T16:01:24.680462+00:00', + 'id': '1f029ca3-1f5b-6704-8004-820c16b69a5a', + 'channel_versions': {'__start__': '00000000000000000000000000000005.0.5290678567601859', 'messages': '00000000000000000000000000000006.0.3205149138784782', 'branch:to:call_model': '00000000000000000000000000000006.0.14611156755133758'}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000004.0.5736472536395331'}, 'call_model': {'branch:to:call_model': '00000000000000000000000000000005.0.1410174088651449'}}, + 'channel_values': {'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today?), HumanMessage(content="what's my name?"), AIMessage(content='Your name is Bob.')]}, + }, + metadata={ + 'source': 'loop', + 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}}, + 'step': 4, + 'parents': {}, + 'thread_id': '1' + }, + parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, + pending_writes=[] + ) + ``` + +#### View the history of the thread (checkpoints) + +=== "Graph/Functional API" + + ```python + config = { + "configurable": { + # highlight-next-line + "thread_id": "1" + } + } + # highlight-next-line + list(graph.get_state_history(config)) + ``` + + ``` + [ + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content="what's my name?"), AIMessage(content='Your name is Bob.')]}, + next=(), + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}}, + metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}}, 'step': 4, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:24.680462+00:00', + parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, + tasks=(), + interrupts=() + ), + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content="what's my name?")]}, + next=('call_model',), + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, + metadata={'source': 'loop', 'writes': None, 'step': 3, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:23.863421+00:00', + parent_config={...} + tasks=(PregelTask(id='8ab4155e-6b15-b885-9ce5-bed69a2c305c', name='call_model', path=('__pregel_pull', 'call_model'), error=None, interrupts=(), state=None, result={'messages': AIMessage(content='Your name is Bob.')}),), + interrupts=() + ), + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]}, + next=('__start__',), + config={...}, + metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': "what's my name?"}]}}, 'step': 2, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:23.863173+00:00', + parent_config={...} + tasks=(PregelTask(id='24ba39d6-6db1-4c9b-f4c5-682aeaf38dcd', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': "what's my name?"}]}),), + interrupts=() + ), + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]}, + next=(), + config={...}, + metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')}}, 'step': 1, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:23.862295+00:00', + parent_config={...} + tasks=(), + interrupts=() + ), + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob")]}, + next=('call_model',), + config={...}, + metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:22.278960+00:00', + parent_config={...} + tasks=(PregelTask(id='8cbd75e0-3720-b056-04f7-71ac805140a0', name='call_model', path=('__pregel_pull', 'call_model'), error=None, interrupts=(), state=None, result={'messages': AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')}),), + interrupts=() + ), + StateSnapshot( + values={'messages': []}, + next=('__start__',), + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-0870-6ce2-bfff-1f3f14c3e565'}}, + metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': "hi! I'm bob"}]}}, 'step': -1, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:22.277497+00:00', + parent_config=None, + tasks=(PregelTask(id='d458367b-8265-812c-18e2-33001d199ce6', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': "hi! I'm bob"}]}),), + interrupts=() + ) + ] + ``` + +=== "Checkpointer API" + + ```python + config = { + "configurable": { + # highlight-next-line + "thread_id": "1" + } + } + # highlight-next-line + list(checkpointer.list(config)) + ``` + + ``` + [ + CheckpointTuple( + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}}, + checkpoint={ + 'v': 3, + 'ts': '2025-05-05T16:01:24.680462+00:00', + 'id': '1f029ca3-1f5b-6704-8004-820c16b69a5a', + 'channel_versions': {'__start__': '00000000000000000000000000000005.0.5290678567601859', 'messages': '00000000000000000000000000000006.0.3205149138784782', 'branch:to:call_model': '00000000000000000000000000000006.0.14611156755133758'}, + 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000004.0.5736472536395331'}, 'call_model': {'branch:to:call_model': '00000000000000000000000000000005.0.1410174088651449'}}, + 'channel_values': {'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content="what's my name?"), AIMessage(content='Your name is Bob.')]}, + }, + metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}}, 'step': 4, 'parents': {}, 'thread_id': '1'}, + parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, + pending_writes=[] + ), + CheckpointTuple( + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, + checkpoint={ + 'v': 3, + 'ts': '2025-05-05T16:01:23.863421+00:00', + 'id': '1f029ca3-1790-6b0a-8003-baf965b6a38f', + 'channel_versions': {'__start__': '00000000000000000000000000000005.0.5290678567601859', 'messages': '00000000000000000000000000000006.0.3205149138784782', 'branch:to:call_model': '00000000000000000000000000000006.0.14611156755133758'}, + 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000004.0.5736472536395331'}, 'call_model': {'branch:to:call_model': '00000000000000000000000000000005.0.1410174088651449'}}, + 'channel_values': {'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content="what's my name?")], 'branch:to:call_model': None} + }, + metadata={'source': 'loop', 'writes': None, 'step': 3, 'parents': {}, 'thread_id': '1'}, + parent_config={...}, + pending_writes=[('8ab4155e-6b15-b885-9ce5-bed69a2c305c', 'messages', AIMessage(content='Your name is Bob.'))] + ), + CheckpointTuple( + config={...}, + checkpoint={ + 'v': 3, + 'ts': '2025-05-05T16:01:23.863173+00:00', + 'id': '1f029ca3-1790-616e-8002-9e021694a0cd', + 'channel_versions': {'__start__': '00000000000000000000000000000004.0.5736472536395331', 'messages': '00000000000000000000000000000003.0.7056767754077798', 'branch:to:call_model': '00000000000000000000000000000003.0.22059023329132854'}, + 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0.7040775356287469'}, 'call_model': {'branch:to:call_model': '00000000000000000000000000000002.0.9300422176788571'}}, + 'channel_values': {'__start__': {'messages': [{'role': 'user', 'content': "what's my name?"}]}, 'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]} + }, + metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': "what's my name?"}]}}, 'step': 2, 'parents': {}, 'thread_id': '1'}, + parent_config={...}, + pending_writes=[('24ba39d6-6db1-4c9b-f4c5-682aeaf38dcd', 'messages', [{'role': 'user', 'content': "what's my name?"}]), ('24ba39d6-6db1-4c9b-f4c5-682aeaf38dcd', 'branch:to:call_model', None)] + ), + CheckpointTuple( + config={...}, + checkpoint={ + 'v': 3, + 'ts': '2025-05-05T16:01:23.862295+00:00', + 'id': '1f029ca3-178d-6f54-8001-d7b180db0c89', + 'channel_versions': {'__start__': '00000000000000000000000000000002.0.18673090920108737', 'messages': '00000000000000000000000000000003.0.7056767754077798', 'branch:to:call_model': '00000000000000000000000000000003.0.22059023329132854'}, + 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0.7040775356287469'}, 'call_model': {'branch:to:call_model': '00000000000000000000000000000002.0.9300422176788571'}}, + 'channel_values': {'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]} + }, + metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')}}, 'step': 1, 'parents': {}, 'thread_id': '1'}, + parent_config={...}, + pending_writes=[] + ), + CheckpointTuple( + config={...}, + checkpoint={ + 'v': 3, + 'ts': '2025-05-05T16:01:22.278960+00:00', + 'id': '1f029ca3-0874-6612-8000-339f2abc83b1', + 'channel_versions': {'__start__': '00000000000000000000000000000002.0.18673090920108737', 'messages': '00000000000000000000000000000002.0.30296526818059655', 'branch:to:call_model': '00000000000000000000000000000002.0.9300422176788571'}, + 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0.7040775356287469'}}, + 'channel_values': {'messages': [HumanMessage(content="hi! I'm bob")], 'branch:to:call_model': None} + }, + metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}, 'thread_id': '1'}, + parent_config={...}, + pending_writes=[('8cbd75e0-3720-b056-04f7-71ac805140a0', 'messages', AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'))] + ), + CheckpointTuple( + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-0870-6ce2-bfff-1f3f14c3e565'}}, + checkpoint={ + 'v': 3, + 'ts': '2025-05-05T16:01:22.277497+00:00', + 'id': '1f029ca3-0870-6ce2-bfff-1f3f14c3e565', + 'channel_versions': {'__start__': '00000000000000000000000000000001.0.7040775356287469'}, + 'versions_seen': {'__input__': {}}, + 'channel_values': {'__start__': {'messages': [{'role': 'user', 'content': "hi! I'm bob"}]}} + }, + metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': "hi! I'm bob"}]}}, 'step': -1, 'parents': {}, 'thread_id': '1'}, + parent_config=None, + pending_writes=[('d458367b-8265-812c-18e2-33001d199ce6', 'messages', [{'role': 'user', 'content': "hi! I'm bob"}]), ('d458367b-8265-812c-18e2-33001d199ce6', 'branch:to:call_model', None)] + ) + ] + ``` + + +#### Delete all checkpoints for a thread + +```python +thread_id = "1" +checkpointer.delete_thread(thread_id) +``` + +## Prebuilt memory tools + +**LangMem** is a LangChain-maintained library that offers tools for managing long-term memories in your agent. See the [LangMem documentation](https://langchain-ai.github.io/langmem/) for usage examples. \ No newline at end of file diff --git a/docs/docs/how-tos/persistence.ipynb b/docs/docs/how-tos/persistence.ipynb deleted file mode 100644 index a41f008c7..000000000 --- a/docs/docs/how-tos/persistence.ipynb +++ /dev/null @@ -1,1619 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53", - "metadata": {}, - "source": [ - "# Add persistence\n", - "\n", - "Many AI applications need memory to share context across multiple interactions. LangGraph supports two types of memory essential for building conversational agents:\n", - "\n", - "- **[Short-term memory](#add-short-term-memory)**: Tracks the ongoing conversation by maintaining message history within a session.\n", - "- **[Long-term memory](#add-long-term-memory)**: Stores user-specific or application-level data across sessions.\n", - "\n", - "> **Terminology**\n", - ">\n", - "> In LangGraph:\n", - ">\n", - "> - *Short-term memory* is also referred to as **thread-level memory**.\n", - "> - *Long-term memory* is also called **cross-thread memory**.\n", - ">\n", - "> A [thread](../../concepts/persistence#threads) represents a sequence of related runs\n", - "> grouped by the same `thread_id`." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833", - "metadata": {}, - "outputs": [], - "source": [ - "# hide-cell\n", - "%pip install --quiet -U langgraph \"langchain[anthropic]\"" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ANTHROPIC_API_KEY: ········\n" - ] - } - ], - "source": [ - "# hide-cell\n", - "import getpass\n", - "import os\n", - "\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "\n", - "_set_env(\"ANTHROPIC_API_KEY\")" - ] - }, - { - "cell_type": "markdown", - "id": "702f9da1-9aaf-4a5f-9b1f-6ab1a273e6a9", - "metadata": {}, - "source": [ - "## Add short-term memory" - ] - }, - { - "cell_type": "markdown", - "id": "f7c171e2-82d2-423f-8eba-ff32d7c494fe", - "metadata": {}, - "source": [ - "**Short-term** memory (thread-level persistence) enables agents to track multi-turn conversations. To add short-term memory:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "07584f6a-7b8e-4f18-a135-e0435797e274", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "hi! I'm bob\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Hi Bob! How are you doing today? Is there anything I can help you with?\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "what's my name?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Your name is Bob.\n" - ] - } - ], - "source": [ - "from langchain.chat_models import init_chat_model\n", - "from langgraph.graph import StateGraph, MessagesState, START\n", - "\n", - "# highlight-next-line\n", - "from langgraph.checkpoint.memory import InMemorySaver\n", - "\n", - "model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - "\n", - "\n", - "def call_model(state: MessagesState):\n", - " response = model.invoke(state[\"messages\"])\n", - " return {\"messages\": response}\n", - "\n", - "\n", - "builder = StateGraph(MessagesState)\n", - "builder.add_node(call_model)\n", - "builder.add_edge(START, \"call_model\")\n", - "\n", - "checkpointer = InMemorySaver()\n", - "# highlight-next-line\n", - "graph = builder.compile(checkpointer=checkpointer)\n", - "\n", - "config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\"\n", - " }\n", - "}\n", - "\n", - "for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - "):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - "\n", - "for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - "):\n", - " chunk[\"messages\"][-1].pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "504bf7c3-40f2-4b7f-86d9-390973a1700c", - "metadata": {}, - "source": [ - "!!! info \"Not needed for LangGraph API users\"\n", - "\n", - " If you're using the LangGraph API, **don't need** to provide checkpointer when compiling the graph. The API automatically handles checkpointing for you." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "988a98c0-15a6-492b-aa59-261fab3a4909", - "metadata": {}, - "source": [ - "### Use in production\n", - "\n", - "In production, you would want to use a checkpointer backed by a database:\n", - "\n", - "```python\n", - "from langgraph.checkpoint.postgres import PostgresSaver\n", - "\n", - "DB_URI = \"postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable\"\n", - "# highlight-next-line\n", - "with PostgresSaver.from_conn_string(DB_URI) as checkpointer:\n", - " builder = StateGraph(...)\n", - " # highlight-next-line\n", - " graph = builder.compile(checkpointer=checkpointer)\n", - "```\n", - "\n", - "??? example \"Example: using [Postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) checkpointer\"\n", - "\n", - " ```\n", - " pip install -U \"psycopg[binary,pool]\" langgraph langgraph-checkpoint-postgres\n", - " ```\n", - "\n", - " !!! Setup\n", - " You need to call `checkpointer.setup()` the first time you're using Postgres checkpointer\n", - "\n", - " === \"Sync\"\n", - "\n", - " ```python\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, MessagesState, START\n", - " # highlight-next-line\n", - " from langgraph.checkpoint.postgres import PostgresSaver\n", - " \n", - " model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - " \n", - " DB_URI = \"postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable\"\n", - " # highlight-next-line\n", - " with PostgresSaver.from_conn_string(DB_URI) as checkpointer:\n", - " # checkpointer.setup()\n", - " \n", - " def call_model(state: MessagesState):\n", - " response = model.invoke(state[\"messages\"])\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " # highlight-next-line\n", - " graph = builder.compile(checkpointer=checkpointer)\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\"\n", - " }\n", - " }\n", - " \n", - " for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " \n", - " for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " ```\n", - "\n", - " === \"Async\"\n", - "\n", - " ```python\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, MessagesState, START\n", - " # highlight-next-line\n", - " from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver\n", - " \n", - " model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - " \n", - " DB_URI = \"postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable\"\n", - " # highlight-next-line\n", - " async with AsyncPostgresSaver.from_conn_string(DB_URI) as checkpointer:\n", - " # await checkpointer.setup()\n", - " \n", - " async def call_model(state: MessagesState):\n", - " response = await model.ainvoke(state[\"messages\"])\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " # highlight-next-line\n", - " graph = builder.compile(checkpointer=checkpointer)\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\"\n", - " }\n", - " }\n", - " \n", - " async for chunk in graph.astream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " \n", - " async for chunk in graph.astream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " ```\n", - "\n", - " \n", - "\n", - "??? example \"Example: using [MongoDB](https://pypi.org/project/langgraph-checkpoint-mongodb/) checkpointer\"\n", - "\n", - " ```\n", - " pip install -U pymongo langgraph langgraph-checkpoint-mongodb\n", - " ```\n", - "\n", - " !!! note \"Setup\"\n", - "\n", - " To use the MongoDB checkpointer, you will need a MongoDB cluster. Follow [this guide](https://www.mongodb.com/docs/guides/atlas/cluster/) to create a cluster if you don't already have one.\n", - "\n", - " === \"Sync\"\n", - "\n", - " ```python\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, MessagesState, START\n", - " # highlight-next-line\n", - " from langgraph.checkpoint.mongodb import MongoDBSaver\n", - " \n", - " model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - " \n", - " DB_URI = \"localhost:27017\"\n", - " # highlight-next-line\n", - " with MongoDBSaver.from_conn_string(DB_URI) as checkpointer:\n", - " \n", - " def call_model(state: MessagesState):\n", - " response = model.invoke(state[\"messages\"])\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " # highlight-next-line\n", - " graph = builder.compile(checkpointer=checkpointer)\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\"\n", - " }\n", - " }\n", - " \n", - " for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " \n", - " for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " ```\n", - "\n", - " === \"Async\"\n", - "\n", - " ```python\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, MessagesState, START\n", - " # highlight-next-line\n", - " from langgraph.checkpoint.mongodb.aio import AsyncMongoDBSaver\n", - " \n", - " model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - " \n", - " DB_URI = \"localhost:27017\"\n", - " # highlight-next-line\n", - " async with AsyncMongoDBSaver.from_conn_string(DB_URI) as checkpointer:\n", - " \n", - " async def call_model(state: MessagesState):\n", - " response = await model.ainvoke(state[\"messages\"])\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " # highlight-next-line\n", - " graph = builder.compile(checkpointer=checkpointer)\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\"\n", - " }\n", - " }\n", - " \n", - " async for chunk in graph.astream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " \n", - " async for chunk in graph.astream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " ``` \n", - "\n", - "??? example \"Example: using [Redis](https://pypi.org/project/langgraph-checkpoint-redis/) checkpointer\"\n", - "\n", - " ```\n", - " pip install -U langgraph langgraph-checkpoint-redis\n", - " ```\n", - "\n", - " !!! Setup\n", - " You need to call `checkpointer.setup()` the first time you're using Redis checkpointer\n", - "\n", - "\n", - " === \"Sync\"\n", - "\n", - " ```python\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, MessagesState, START\n", - " # highlight-next-line\n", - " from langgraph.checkpoint.redis import RedisSaver\n", - " \n", - " model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - " \n", - " DB_URI = \"redis://localhost:6379\"\n", - " # highlight-next-line\n", - " with RedisSaver.from_conn_string(DB_URI) as checkpointer:\n", - " # checkpointer.setup()\n", - " \n", - " def call_model(state: MessagesState):\n", - " response = model.invoke(state[\"messages\"])\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " # highlight-next-line\n", - " graph = builder.compile(checkpointer=checkpointer)\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\"\n", - " }\n", - " }\n", - " \n", - " for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " \n", - " for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " ```\n", - "\n", - " === \"Async\"\n", - "\n", - " ```python\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, MessagesState, START\n", - " # highlight-next-line\n", - " from langgraph.checkpoint.redis.aio import AsyncRedisSaver\n", - " \n", - " model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - " \n", - " DB_URI = \"redis://localhost:6379\"\n", - " # highlight-next-line\n", - " async with AsyncRedisSaver.from_conn_string(DB_URI) as checkpointer:\n", - " # await checkpointer.asetup()\n", - " \n", - " async def call_model(state: MessagesState):\n", - " response = await model.ainvoke(state[\"messages\"])\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " # highlight-next-line\n", - " graph = builder.compile(checkpointer=checkpointer)\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\"\n", - " }\n", - " }\n", - " \n", - " async for chunk in graph.astream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " \n", - " async for chunk in graph.astream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\"\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print() \n", - " ```" - ] - }, - { - "cell_type": "markdown", - "id": "53f54d98-c659-49af-ae1e-a25641924ad1", - "metadata": {}, - "source": [ - "### Use with subgraphs" - ] - }, - { - "cell_type": "markdown", - "id": "cf3ec9f4-08bc-4118-af7b-1d3c4a5ef69b", - "metadata": {}, - "source": [ - "If your graph contains [subgraphs](../../concepts/subgraphs), you only need to **provide the checkpointer when compiling the parent graph**. LangGraph will automatically propagate the checkpointer to the child subgraphs.\n", - "\n", - "```python\n", - "from langgraph.graph import START, StateGraph\n", - "from langgraph.checkpoint.memory import InMemorySaver\n", - "from typing import TypedDict\n", - "\n", - "class State(TypedDict):\n", - " foo: str\n", - "\n", - "# Subgraph\n", - "\n", - "def subgraph_node_1(state: State):\n", - " return {\"foo\": state[\"foo\"] + \"bar\"}\n", - "\n", - "subgraph_builder = StateGraph(State)\n", - "subgraph_builder.add_node(subgraph_node_1)\n", - "subgraph_builder.add_edge(START, \"subgraph_node_1\")\n", - "# highlight-next-line\n", - "subgraph = subgraph_builder.compile()\n", - "\n", - "# Parent graph\n", - "\n", - "def node_1(state: State):\n", - " return {\"foo\": \"hi! \" + state[\"foo\"]}\n", - "\n", - "builder = StateGraph(State)\n", - "# highlight-next-line\n", - "builder.add_node(\"node_1\", subgraph)\n", - "builder.add_edge(START, \"node_1\")\n", - "\n", - "checkpointer = InMemorySaver()\n", - "# highlight-next-line\n", - "graph = builder.compile(checkpointer=checkpointer)\n", - "``` \n", - "\n", - "If you want the subgraph to have its own memory, you can compile it `with checkpointer=True`. This is useful in [multi-agent](../../concepts/multi_agent) systems, if you want agents to keep track of their internal message histories:\n", - "\n", - "```python\n", - "subgraph_builder = StateGraph(...)\n", - "# highlight-next-line\n", - "subgraph = subgraph_builder.compile(checkpointer=True)\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "f17b4493-c578-428f-857c-ef53f7139d53", - "metadata": {}, - "source": [ - "### Use with Functional API\n", - "\n", - "To add short-term memory to a [Functional API](../../concepts/functional_api) LangGraph workflow:\n", - "\n", - "1. Pass `checkpointer` instance to the [`entrypoint()`][langgraph.func.entrypoint] decorator:\n", - "\n", - " ```python\n", - " from langgraph.func import entrypoint\n", - " \n", - " @entrypoint(checkpointer=checkpointer)\n", - " def workflow(inputs)\n", - " ...\n", - " ```\n", - "\n", - "2. Optionally expose `previous` parameter in the workflow function signature:\n", - "\n", - " ```python\n", - " @entrypoint(checkpointer=checkpointer)\n", - " def workflow(\n", - " inputs,\n", - " *,\n", - " # you can optionally specify `previous` in the workflow function signature\n", - " # to access the return value from the workflow as of the last execution\n", - " previous\n", - " ):\n", - " previous = previous or []\n", - " combined_inputs = previous + inputs\n", - " result = do_something(combined_inputs)\n", - " ...\n", - " ```\n", - "\n", - "3. Optionally choose which values will be returned from the workflow and which will be saved by the checkpointer as `previous`:\n", - "\n", - " ```python\n", - " @entrypoint(checkpointer=checkpointer)\n", - " def workflow(inputs, *, previous):\n", - " ...\n", - " result = do_something(...)\n", - " return entrypoint.final(value=result, save=combine(inputs, result))\n", - " ```\n", - "\n", - "??? example \"Example: add short-term memory to Functional API workflow\"\n", - "\n", - " ```python\n", - " from langchain_core.messages import AnyMessage\n", - " from langgraph.graph import add_messages\n", - " from langgraph.func import entrypoint, task\n", - " from langgraph.checkpoint.memory import InMemorySaver\n", - "\n", - " # highlight-next-line\n", - " @task\n", - " def call_model(messages: list[AnyMessage]):\n", - " response = model.invoke(messages)\n", - " return response\n", - " \n", - " checkpointer = InMemorySaver()\n", - "\n", - " # highlight-next-line\n", - " @entrypoint(checkpointer=checkpointer)\n", - " def workflow(inputs: list[AnyMessage], *, previous: list[AnyMessage]):\n", - " if previous:\n", - " inputs = add_messages(previous, inputs)\n", - " \n", - " response = call_model(inputs).result()\n", - " return entrypoint.final(value=response, save=add_messages(inputs, response))\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\"\n", - " }\n", - " }\n", - " \n", - " for chunk in workflow.invoke(\n", - " [{\"role\": \"user\", \"content\": \"hi! I'm bob\"}],\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - " ):\n", - " chunk.pretty_print()\n", - " \n", - " for chunk in workflow.stream(\n", - " [{\"role\": \"user\", \"content\": \"what's my name?\"}],\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - " ):\n", - " chunk.pretty_print()\n", - " ```" - ] - }, - { - "cell_type": "markdown", - "id": "6d4bd273-98e6-4330-af93-b79fb7f50115", - "metadata": {}, - "source": [ - "### Manage checkpoints\n", - "\n", - "You can view and delete the information stored by the checkpointer:\n", - "\n", - "??? \"View thread state (checkpoint)\"\n", - "\n", - " === \"Graph/Functional API\"\n", - " \n", - " ```python\n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\",\n", - " # optionally provide an ID for a specific checkpoint,\n", - " # otherwise the latest checkpoint is shown\n", - " # highlight-next-line\n", - " # \"checkpoint_id\": \"1f029ca3-1f5b-6704-8004-820c16b69a5a\"\n", - " \n", - " }\n", - " }\n", - " # highlight-next-line\n", - " graph.get_state(config)\n", - " ```\n", - " \n", - " ```\n", - " StateSnapshot(\n", - " values={'messages': [HumanMessage(content=\"hi! I'm bob\"), AIMessage(content='Hi Bob! How are you doing today?), HumanMessage(content=\"what's my name?\"), AIMessage(content='Your name is Bob.')]}, next=(), \n", - " config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}},\n", - " metadata={\n", - " 'source': 'loop',\n", - " 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}},\n", - " 'step': 4,\n", - " 'parents': {},\n", - " 'thread_id': '1'\n", - " },\n", - " created_at='2025-05-05T16:01:24.680462+00:00',\n", - " parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, \n", - " tasks=(),\n", - " interrupts=()\n", - " )\n", - " ```\n", - "\n", - " === \"Checkpointer API\"\n", - " \n", - " ```python\n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\",\n", - " # optionally provide an ID for a specific checkpoint,\n", - " # otherwise the latest checkpoint is shown\n", - " # highlight-next-line\n", - " # \"checkpoint_id\": \"1f029ca3-1f5b-6704-8004-820c16b69a5a\"\n", - " \n", - " }\n", - " }\n", - " # highlight-next-line\n", - " checkpointer.get_tuple(config)\n", - " ```\n", - "\n", - " ```\n", - " CheckpointTuple(\n", - " config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}},\n", - " checkpoint={\n", - " 'v': 3,\n", - " 'ts': '2025-05-05T16:01:24.680462+00:00',\n", - " 'id': '1f029ca3-1f5b-6704-8004-820c16b69a5a',\n", - " 'channel_versions': {'__start__': '00000000000000000000000000000005.0.5290678567601859', 'messages': '00000000000000000000000000000006.0.3205149138784782', 'branch:to:call_model': '00000000000000000000000000000006.0.14611156755133758'}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000004.0.5736472536395331'}, 'call_model': {'branch:to:call_model': '00000000000000000000000000000005.0.1410174088651449'}},\n", - " 'channel_values': {'messages': [HumanMessage(content=\"hi! I'm bob\"), AIMessage(content='Hi Bob! How are you doing today?), HumanMessage(content=\"what's my name?\"), AIMessage(content='Your name is Bob.')]},\n", - " },\n", - " metadata={\n", - " 'source': 'loop',\n", - " 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}},\n", - " 'step': 4,\n", - " 'parents': {},\n", - " 'thread_id': '1'\n", - " },\n", - " parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}},\n", - " pending_writes=[]\n", - " )\n", - " ```\n", - "\n", - "??? \"View the history of the thread (checkpoints)\"\n", - "\n", - " === \"Graph/Functional API\"\n", - "\n", - " ```python\n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\"\n", - " }\n", - " }\n", - " # highlight-next-line\n", - " list(graph.get_state_history(config))\n", - " ```\n", - " \n", - " ```\n", - " [\n", - " StateSnapshot(\n", - " values={'messages': [HumanMessage(content=\"hi! I'm bob\"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content=\"what's my name?\"), AIMessage(content='Your name is Bob.')]}, \n", - " next=(), \n", - " config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}}, \n", - " metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}}, 'step': 4, 'parents': {}, 'thread_id': '1'},\n", - " created_at='2025-05-05T16:01:24.680462+00:00',\n", - " parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}},\n", - " tasks=(),\n", - " interrupts=()\n", - " ),\n", - " StateSnapshot(\n", - " values={'messages': [HumanMessage(content=\"hi! I'm bob\"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content=\"what's my name?\")]}, \n", - " next=('call_model',), \n", - " config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}},\n", - " metadata={'source': 'loop', 'writes': None, 'step': 3, 'parents': {}, 'thread_id': '1'},\n", - " created_at='2025-05-05T16:01:23.863421+00:00',\n", - " parent_config={...}\n", - " tasks=(PregelTask(id='8ab4155e-6b15-b885-9ce5-bed69a2c305c', name='call_model', path=('__pregel_pull', 'call_model'), error=None, interrupts=(), state=None, result={'messages': AIMessage(content='Your name is Bob.')}),),\n", - " interrupts=()\n", - " ),\n", - " StateSnapshot(\n", - " values={'messages': [HumanMessage(content=\"hi! I'm bob\"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]}, \n", - " next=('__start__',), \n", - " config={...}, \n", - " metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': \"what's my name?\"}]}}, 'step': 2, 'parents': {}, 'thread_id': '1'},\n", - " created_at='2025-05-05T16:01:23.863173+00:00',\n", - " parent_config={...}\n", - " tasks=(PregelTask(id='24ba39d6-6db1-4c9b-f4c5-682aeaf38dcd', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': \"what's my name?\"}]}),),\n", - " interrupts=()\n", - " ),\n", - " StateSnapshot(\n", - " values={'messages': [HumanMessage(content=\"hi! I'm bob\"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]}, \n", - " next=(), \n", - " config={...}, \n", - " metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')}}, 'step': 1, 'parents': {}, 'thread_id': '1'},\n", - " created_at='2025-05-05T16:01:23.862295+00:00',\n", - " parent_config={...}\n", - " tasks=(),\n", - " interrupts=()\n", - " ),\n", - " StateSnapshot(\n", - " values={'messages': [HumanMessage(content=\"hi! I'm bob\")]}, \n", - " next=('call_model',), \n", - " config={...}, \n", - " metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}, 'thread_id': '1'}, \n", - " created_at='2025-05-05T16:01:22.278960+00:00', \n", - " parent_config={...}\n", - " tasks=(PregelTask(id='8cbd75e0-3720-b056-04f7-71ac805140a0', name='call_model', path=('__pregel_pull', 'call_model'), error=None, interrupts=(), state=None, result={'messages': AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')}),), \n", - " interrupts=()\n", - " ),\n", - " StateSnapshot(\n", - " values={'messages': []}, \n", - " next=('__start__',), \n", - " config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-0870-6ce2-bfff-1f3f14c3e565'}},\n", - " metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': \"hi! I'm bob\"}]}}, 'step': -1, 'parents': {}, 'thread_id': '1'}, \n", - " created_at='2025-05-05T16:01:22.277497+00:00', \n", - " parent_config=None,\n", - " tasks=(PregelTask(id='d458367b-8265-812c-18e2-33001d199ce6', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': \"hi! I'm bob\"}]}),), \n", - " interrupts=()\n", - " )\n", - " ] \n", - " ```\n", - "\n", - " === \"Checkpointer API\"\n", - "\n", - " ```python\n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\"\n", - " }\n", - " }\n", - " # highlight-next-line\n", - " list(checkpointer.list(config))\n", - " ```\n", - "\n", - " ```\n", - " [\n", - " CheckpointTuple(\n", - " config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}}, \n", - " checkpoint={\n", - " 'v': 3, \n", - " 'ts': '2025-05-05T16:01:24.680462+00:00', \n", - " 'id': '1f029ca3-1f5b-6704-8004-820c16b69a5a', \n", - " 'channel_versions': {'__start__': '00000000000000000000000000000005.0.5290678567601859', 'messages': '00000000000000000000000000000006.0.3205149138784782', 'branch:to:call_model': '00000000000000000000000000000006.0.14611156755133758'}, \n", - " 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000004.0.5736472536395331'}, 'call_model': {'branch:to:call_model': '00000000000000000000000000000005.0.1410174088651449'}},\n", - " 'channel_values': {'messages': [HumanMessage(content=\"hi! I'm bob\"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content=\"what's my name?\"), AIMessage(content='Your name is Bob.')]},\n", - " },\n", - " metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}}, 'step': 4, 'parents': {}, 'thread_id': '1'}, \n", - " parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, \n", - " pending_writes=[]\n", - " ),\n", - " CheckpointTuple(\n", - " config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}},\n", - " checkpoint={\n", - " 'v': 3, \n", - " 'ts': '2025-05-05T16:01:23.863421+00:00', \n", - " 'id': '1f029ca3-1790-6b0a-8003-baf965b6a38f', \n", - " 'channel_versions': {'__start__': '00000000000000000000000000000005.0.5290678567601859', 'messages': '00000000000000000000000000000005.0.7935064215293443', 'branch:to:call_model': '00000000000000000000000000000005.0.1410174088651449'}, \n", - " 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000004.0.5736472536395331'}, 'call_model': {'branch:to:call_model': '00000000000000000000000000000002.0.9300422176788571'}}, \n", - " 'channel_values': {'messages': [HumanMessage(content=\"hi! I'm bob\"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content=\"what's my name?\")], 'branch:to:call_model': None}\n", - " }, \n", - " metadata={'source': 'loop', 'writes': None, 'step': 3, 'parents': {}, 'thread_id': '1'}, \n", - " parent_config={...}, \n", - " pending_writes=[('8ab4155e-6b15-b885-9ce5-bed69a2c305c', 'messages', AIMessage(content='Your name is Bob.'))]\n", - " ),\n", - " CheckpointTuple(\n", - " config={...}, \n", - " checkpoint={\n", - " 'v': 3, \n", - " 'ts': '2025-05-05T16:01:23.863173+00:00', \n", - " 'id': '1f029ca3-1790-616e-8002-9e021694a0cd', \n", - " 'channel_versions': {'__start__': '00000000000000000000000000000004.0.5736472536395331', 'messages': '00000000000000000000000000000003.0.7056767754077798', 'branch:to:call_model': '00000000000000000000000000000003.0.22059023329132854'}, \n", - " 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0.7040775356287469'}, 'call_model': {'branch:to:call_model': '00000000000000000000000000000002.0.9300422176788571'}}, \n", - " 'channel_values': {'__start__': {'messages': [{'role': 'user', 'content': \"what's my name?\"}]}, 'messages': [HumanMessage(content=\"hi! I'm bob\"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]}\n", - " }, \n", - " metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': \"what's my name?\"}]}}, 'step': 2, 'parents': {}, 'thread_id': '1'}, \n", - " parent_config={...}, \n", - " pending_writes=[('24ba39d6-6db1-4c9b-f4c5-682aeaf38dcd', 'messages', [{'role': 'user', 'content': \"what's my name?\"}]), ('24ba39d6-6db1-4c9b-f4c5-682aeaf38dcd', 'branch:to:call_model', None)]\n", - " ),\n", - " CheckpointTuple(\n", - " config={...}, \n", - " checkpoint={\n", - " 'v': 3, \n", - " 'ts': '2025-05-05T16:01:23.862295+00:00', \n", - " 'id': '1f029ca3-178d-6f54-8001-d7b180db0c89', \n", - " 'channel_versions': {'__start__': '00000000000000000000000000000002.0.18673090920108737', 'messages': '00000000000000000000000000000003.0.7056767754077798', 'branch:to:call_model': '00000000000000000000000000000003.0.22059023329132854'}, \n", - " 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0.7040775356287469'}, 'call_model': {'branch:to:call_model': '00000000000000000000000000000002.0.9300422176788571'}}, \n", - " 'channel_values': {'messages': [HumanMessage(content=\"hi! I'm bob\"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]}\n", - " }, \n", - " metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')}}, 'step': 1, 'parents': {}, 'thread_id': '1'}, \n", - " parent_config={...}, \n", - " pending_writes=[]\n", - " ),\n", - " CheckpointTuple(\n", - " config={...}, \n", - " checkpoint={\n", - " 'v': 3, \n", - " 'ts': '2025-05-05T16:01:22.278960+00:00', \n", - " 'id': '1f029ca3-0874-6612-8000-339f2abc83b1', \n", - " 'channel_versions': {'__start__': '00000000000000000000000000000002.0.18673090920108737', 'messages': '00000000000000000000000000000002.0.30296526818059655', 'branch:to:call_model': '00000000000000000000000000000002.0.9300422176788571'}, \n", - " 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0.7040775356287469'}}, \n", - " 'channel_values': {'messages': [HumanMessage(content=\"hi! I'm bob\")], 'branch:to:call_model': None}\n", - " }, \n", - " metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}, 'thread_id': '1'}, \n", - " parent_config={...}, \n", - " pending_writes=[('8cbd75e0-3720-b056-04f7-71ac805140a0', 'messages', AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'))]\n", - " ),\n", - " CheckpointTuple(\n", - " config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-0870-6ce2-bfff-1f3f14c3e565'}}, \n", - " checkpoint={\n", - " 'v': 3, \n", - " 'ts': '2025-05-05T16:01:22.277497+00:00', \n", - " 'id': '1f029ca3-0870-6ce2-bfff-1f3f14c3e565', \n", - " 'channel_versions': {'__start__': '00000000000000000000000000000001.0.7040775356287469'}, \n", - " 'versions_seen': {'__input__': {}}, \n", - " 'channel_values': {'__start__': {'messages': [{'role': 'user', 'content': \"hi! I'm bob\"}]}}\n", - " }, \n", - " metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': \"hi! I'm bob\"}]}}, 'step': -1, 'parents': {}, 'thread_id': '1'}, \n", - " parent_config=None, \n", - " pending_writes=[('d458367b-8265-812c-18e2-33001d199ce6', 'messages', [{'role': 'user', 'content': \"hi! I'm bob\"}]), ('d458367b-8265-812c-18e2-33001d199ce6', 'branch:to:call_model', None)]\n", - " )\n", - " ]\n", - " ```\n", - "\n", - "\n", - "??? \"Delete all checkpoints for a thread\"\n", - "\n", - " ```python\n", - " thread_id = \"1\"\n", - " checkpointer.delete_thread(thread_id)\n", - " ```" - ] - }, - { - "cell_type": "markdown", - "id": "799eaf41-1a48-4a31-bc71-1a2a51e92b21", - "metadata": {}, - "source": [ - "## Add long-term memory" - ] - }, - { - "cell_type": "markdown", - "id": "303f3ae3-30e3-41d1-900a-c84879b50f86", - "metadata": {}, - "source": [ - "Use **long-term** memory (cross-thread persistence) to store user-specific or application-specific data across conversations. This is useful for applications like chatbots, where you want to remember user preferences or other information.\n", - "\n", - "To use long-term memory, we need to [provide a store][langgraph.store.base.BaseStore] when creating the graph:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "b88d8ede-ec8c-406c-917d-cda5610679c3", - "metadata": {}, - "outputs": [], - "source": [ - "import uuid\n", - "from typing_extensions import Annotated, TypedDict\n", - "\n", - "from langchain_core.runnables import RunnableConfig\n", - "from langgraph.graph import StateGraph, MessagesState, START\n", - "from langgraph.checkpoint.memory import InMemorySaver\n", - "\n", - "# highlight-next-line\n", - "from langgraph.store.memory import InMemoryStore\n", - "from langgraph.store.base import BaseStore\n", - "\n", - "model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - "\n", - "\n", - "def call_model(\n", - " state: MessagesState,\n", - " config: RunnableConfig,\n", - " *,\n", - " # highlight-next-line\n", - " store: BaseStore, # (1)!\n", - "):\n", - " user_id = config[\"configurable\"][\"user_id\"]\n", - " namespace = (\"memories\", user_id)\n", - " # highlight-next-line\n", - " memories = store.search(namespace, query=str(state[\"messages\"][-1].content))\n", - " info = \"\\n\".join([d.value[\"data\"] for d in memories])\n", - " system_msg = f\"You are a helpful assistant talking to the user. User info: {info}\"\n", - "\n", - " # Store new memories if the user asks the model to remember\n", - " last_message = state[\"messages\"][-1]\n", - " if \"remember\" in last_message.content.lower():\n", - " memory = \"User name is Bob\"\n", - " # highlight-next-line\n", - " store.put(namespace, str(uuid.uuid4()), {\"data\": memory})\n", - "\n", - " response = model.invoke(\n", - " [{\"role\": \"system\", \"content\": system_msg}] + state[\"messages\"]\n", - " )\n", - " return {\"messages\": response}\n", - "\n", - "\n", - "builder = StateGraph(MessagesState)\n", - "builder.add_node(call_model)\n", - "builder.add_edge(START, \"call_model\")\n", - "\n", - "checkpointer = InMemorySaver()\n", - "store = InMemoryStore()\n", - "\n", - "graph = builder.compile(\n", - " checkpointer=checkpointer,\n", - " # highlight-next-line\n", - " store=store,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "ce33d6ee-5754-4a9d-8246-6ad188a28d94", - "metadata": {}, - "source": [ - "1. This is the `store` we compiled the graph with" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "62ce2760-f23a-4ab1-b73c-d2680e20b611", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "Hi! Remember: my name is Bob\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Hi Bob! I'll remember that your name is Bob. How are you doing today?\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "what is my name?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Your name is Bob.\n" - ] - } - ], - "source": [ - "config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\",\n", - " # highlight-next-line\n", - " \"user_id\": \"1\",\n", - " }\n", - "}\n", - "for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"Hi! Remember: my name is Bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - "):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - "\n", - "config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"2\",\n", - " \"user_id\": \"1\",\n", - " }\n", - "}\n", - "\n", - "for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what is my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - "):\n", - " chunk[\"messages\"][-1].pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "cb96703d-3ab3-4d87-a6e6-22f4c0273346", - "metadata": {}, - "source": [ - "!!! info \"Not needed for LangGraph API users\"\n", - "\n", - " If you're using the LangGraph API, **don't need** to provide store when compiling the graph. The API automatically handles storage infrastructure for you." - ] - }, - { - "cell_type": "markdown", - "id": "b585dbb4-bd99-44bb-82e0-477a151556e6", - "metadata": {}, - "source": [ - "### Use in production\n", - "\n", - "In production, you would want to use a store backed by a database:\n", - "\n", - "```python\n", - "from langgraph.store.postgres import PostgresStore\n", - "\n", - "DB_URI = \"postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable\"\n", - "# highlight-next-line\n", - "with PostgresStore.from_conn_string(DB_URI) as store:\n", - " builder = StateGraph(...)\n", - " # highlight-next-line\n", - " graph = builder.compile(store=store)\n", - "```\n", - "\n", - "??? example \"Example: using [Postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) store\"\n", - "\n", - " ```\n", - " pip install -U \"psycopg[binary,pool]\" langgraph langgraph-checkpoint-postgres\n", - " ```\n", - "\n", - " !!! Setup\n", - " You need to call `store.setup()` the first time you're using Postgres store\n", - "\n", - " === \"Sync\"\n", - "\n", - " ```python\n", - " from langchain_core.runnables import RunnableConfig\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, MessagesState, START\n", - " from langgraph.checkpoint.postgres import PostgresSaver\n", - " # highlight-next-line\n", - " from langgraph.store.postgres import PostgresStore\n", - " from langgraph.store.base import BaseStore\n", - " \n", - " model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - " \n", - " DB_URI = \"postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable\"\n", - " \n", - " with (\n", - " # highlight-next-line\n", - " PostgresStore.from_conn_string(DB_URI) as store,\n", - " PostgresSaver.from_conn_string(DB_URI) as checkpointer,\n", - " ):\n", - " # store.setup()\n", - " # checkpointer.setup()\n", - " \n", - " def call_model(\n", - " state: MessagesState,\n", - " config: RunnableConfig,\n", - " *,\n", - " # highlight-next-line\n", - " store: BaseStore,\n", - " ):\n", - " user_id = config[\"configurable\"][\"user_id\"]\n", - " namespace = (\"memories\", user_id)\n", - " # highlight-next-line\n", - " memories = store.search(namespace, query=str(state[\"messages\"][-1].content))\n", - " info = \"\\n\".join([d.value[\"data\"] for d in memories])\n", - " system_msg = f\"You are a helpful assistant talking to the user. User info: {info}\"\n", - " \n", - " # Store new memories if the user asks the model to remember\n", - " last_message = state[\"messages\"][-1]\n", - " if \"remember\" in last_message.content.lower():\n", - " memory = \"User name is Bob\"\n", - " # highlight-next-line\n", - " store.put(namespace, str(uuid.uuid4()), {\"data\": memory})\n", - " \n", - " response = model.invoke(\n", - " [{\"role\": \"system\", \"content\": system_msg}] + state[\"messages\"]\n", - " )\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " graph = builder.compile(\n", - " checkpointer=checkpointer,\n", - " # highlight-next-line\n", - " store=store,\n", - " )\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\",\n", - " # highlight-next-line\n", - " \"user_id\": \"1\",\n", - " }\n", - " }\n", - " for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"Hi! Remember: my name is Bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"2\",\n", - " \"user_id\": \"1\",\n", - " }\n", - " }\n", - " \n", - " for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what is my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " ```\n", - "\n", - " === \"Async\"\n", - "\n", - " ```python\n", - " from langchain_core.runnables import RunnableConfig\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, MessagesState, START\n", - " from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver\n", - " # highlight-next-line\n", - " from langgraph.store.postgres.aio import AsyncPostgresStore\n", - " from langgraph.store.base import BaseStore\n", - " \n", - " model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - " \n", - " DB_URI = \"postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable\"\n", - " \n", - " async with (\n", - " # highlight-next-line\n", - " AsyncPostgresStore.from_conn_string(DB_URI) as store,\n", - " AsyncPostgresSaver.from_conn_string(DB_URI) as checkpointer,\n", - " ):\n", - " # await store.setup()\n", - " # await checkpointer.setup()\n", - " \n", - " async def call_model(\n", - " state: MessagesState,\n", - " config: RunnableConfig,\n", - " *,\n", - " # highlight-next-line\n", - " store: BaseStore,\n", - " ):\n", - " user_id = config[\"configurable\"][\"user_id\"]\n", - " namespace = (\"memories\", user_id)\n", - " # highlight-next-line\n", - " memories = await store.asearch(namespace, query=str(state[\"messages\"][-1].content))\n", - " info = \"\\n\".join([d.value[\"data\"] for d in memories])\n", - " system_msg = f\"You are a helpful assistant talking to the user. User info: {info}\"\n", - " \n", - " # Store new memories if the user asks the model to remember\n", - " last_message = state[\"messages\"][-1]\n", - " if \"remember\" in last_message.content.lower():\n", - " memory = \"User name is Bob\"\n", - " # highlight-next-line\n", - " await store.aput(namespace, str(uuid.uuid4()), {\"data\": memory})\n", - " \n", - " response = await model.ainvoke(\n", - " [{\"role\": \"system\", \"content\": system_msg}] + state[\"messages\"]\n", - " )\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " graph = builder.compile(\n", - " checkpointer=checkpointer,\n", - " # highlight-next-line\n", - " store=store,\n", - " )\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\",\n", - " # highlight-next-line\n", - " \"user_id\": \"1\",\n", - " }\n", - " }\n", - " async for chunk in graph.astream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"Hi! Remember: my name is Bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"2\",\n", - " \"user_id\": \"1\",\n", - " }\n", - " }\n", - " \n", - " async for chunk in graph.astream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what is my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " ```\n", - "\n", - "??? example \"Example: using [Redis](https://pypi.org/project/langgraph-checkpoint-redis/) store\"\n", - "\n", - " ```\n", - " pip install -U langgraph langgraph-checkpoint-redis\n", - " ```\n", - "\n", - " !!! Setup\n", - " You need to call `store.setup()` the first time you're using Redis store\n", - "\n", - "\n", - " === \"Sync\"\n", - "\n", - " ```python\n", - " from langchain_core.runnables import RunnableConfig\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, MessagesState, START\n", - " from langgraph.checkpoint.redis import RedisSaver\n", - " # highlight-next-line\n", - " from langgraph.store.redis import RedisStore\n", - " from langgraph.store.base import BaseStore\n", - " \n", - " model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - " \n", - " DB_URI = \"redis://localhost:6379\"\n", - " \n", - " with (\n", - " # highlight-next-line\n", - " RedisStore.from_conn_string(DB_URI) as store,\n", - " RedisSaver.from_conn_string(DB_URI) as checkpointer,\n", - " ):\n", - " store.setup()\n", - " checkpointer.setup()\n", - " \n", - " def call_model(\n", - " state: MessagesState,\n", - " config: RunnableConfig,\n", - " *,\n", - " # highlight-next-line\n", - " store: BaseStore,\n", - " ):\n", - " user_id = config[\"configurable\"][\"user_id\"]\n", - " namespace = (\"memories\", user_id)\n", - " # highlight-next-line\n", - " memories = store.search(namespace, query=str(state[\"messages\"][-1].content))\n", - " info = \"\\n\".join([d.value[\"data\"] for d in memories])\n", - " system_msg = f\"You are a helpful assistant talking to the user. User info: {info}\"\n", - " \n", - " # Store new memories if the user asks the model to remember\n", - " last_message = state[\"messages\"][-1]\n", - " if \"remember\" in last_message.content.lower():\n", - " memory = \"User name is Bob\"\n", - " # highlight-next-line\n", - " store.put(namespace, str(uuid.uuid4()), {\"data\": memory})\n", - " \n", - " response = model.invoke(\n", - " [{\"role\": \"system\", \"content\": system_msg}] + state[\"messages\"]\n", - " )\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " graph = builder.compile(\n", - " checkpointer=checkpointer,\n", - " # highlight-next-line\n", - " store=store,\n", - " )\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\",\n", - " # highlight-next-line\n", - " \"user_id\": \"1\",\n", - " }\n", - " }\n", - " for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"Hi! Remember: my name is Bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"2\",\n", - " \"user_id\": \"1\",\n", - " }\n", - " }\n", - " \n", - " for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what is my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " ```\n", - "\n", - " === \"Async\"\n", - "\n", - " ```python\n", - " from langchain_core.runnables import RunnableConfig\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.graph import StateGraph, MessagesState, START\n", - " from langgraph.checkpoint.redis.aio import AsyncRedisSaver\n", - " # highlight-next-line\n", - " from langgraph.store.redis.aio import AsyncRedisStore\n", - " from langgraph.store.base import BaseStore\n", - " \n", - " model = init_chat_model(model=\"anthropic:claude-3-5-haiku-latest\")\n", - " \n", - " DB_URI = \"redis://localhost:6379\"\n", - " \n", - " async with (\n", - " # highlight-next-line\n", - " AsyncRedisStore.from_conn_string(DB_URI) as store,\n", - " AsyncRedisSaver.from_conn_string(DB_URI) as checkpointer,\n", - " ):\n", - " # await store.setup()\n", - " # await checkpointer.asetup()\n", - " \n", - " async def call_model(\n", - " state: MessagesState,\n", - " config: RunnableConfig,\n", - " *,\n", - " # highlight-next-line\n", - " store: BaseStore,\n", - " ):\n", - " user_id = config[\"configurable\"][\"user_id\"]\n", - " namespace = (\"memories\", user_id)\n", - " # highlight-next-line\n", - " memories = await store.asearch(namespace, query=str(state[\"messages\"][-1].content))\n", - " info = \"\\n\".join([d.value[\"data\"] for d in memories])\n", - " system_msg = f\"You are a helpful assistant talking to the user. User info: {info}\"\n", - " \n", - " # Store new memories if the user asks the model to remember\n", - " last_message = state[\"messages\"][-1]\n", - " if \"remember\" in last_message.content.lower():\n", - " memory = \"User name is Bob\"\n", - " # highlight-next-line\n", - " await store.aput(namespace, str(uuid.uuid4()), {\"data\": memory})\n", - " \n", - " response = await model.ainvoke(\n", - " [{\"role\": \"system\", \"content\": system_msg}] + state[\"messages\"]\n", - " )\n", - " return {\"messages\": response}\n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(call_model)\n", - " builder.add_edge(START, \"call_model\")\n", - " \n", - " graph = builder.compile(\n", - " checkpointer=checkpointer,\n", - " # highlight-next-line\n", - " store=store,\n", - " )\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"1\",\n", - " # highlight-next-line\n", - " \"user_id\": \"1\",\n", - " }\n", - " }\n", - " async for chunk in graph.astream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"Hi! Remember: my name is Bob\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print()\n", - " \n", - " config = {\n", - " \"configurable\": {\n", - " # highlight-next-line\n", - " \"thread_id\": \"2\",\n", - " \"user_id\": \"1\",\n", - " }\n", - " }\n", - " \n", - " async for chunk in graph.astream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what is my name?\"}]},\n", - " # highlight-next-line\n", - " config,\n", - " stream_mode=\"values\",\n", - " ):\n", - " chunk[\"messages\"][-1].pretty_print() \n", - " ```" - ] - }, - { - "cell_type": "markdown", - "id": "f8aff878-e1c1-4592-951a-380b5bec1f63", - "metadata": {}, - "source": [ - "### Use semantic search\n", - "\n", - "You can enable semantic search in your graph's memory store: this lets graph agent search for items in the store by semantic similarity.\n", - "\n", - "```python\n", - "from langchain.embeddings import init_embeddings\n", - "from langgraph.store.memory import InMemoryStore\n", - "\n", - "# Create store with semantic search enabled\n", - "embeddings = init_embeddings(\"openai:text-embedding-3-small\")\n", - "store = InMemoryStore(\n", - " index={\n", - " \"embed\": embeddings,\n", - " \"dims\": 1536,\n", - " }\n", - ")\n", - "\n", - "store.put((\"user_123\", \"memories\"), \"1\", {\"text\": \"I love pizza\"})\n", - "store.put((\"user_123\", \"memories\"), \"2\", {\"text\": \"I am a plumber\"})\n", - "\n", - "items = store.search(\n", - " (\"user_123\", \"memories\"), query=\"I'm hungry\", limit=1\n", - ")\n", - "```\n", - "\n", - "??? example \"Long-term memory with semantic search\"\n", - "\n", - " ```python\n", - " from typing import Optional\n", - " \n", - " from langchain.embeddings import init_embeddings\n", - " from langchain.chat_models import init_chat_model\n", - " from langgraph.store.base import BaseStore\n", - " from langgraph.store.memory import InMemoryStore\n", - " from langgraph.graph import START, MessagesState, StateGraph\n", - " \n", - " llm = init_chat_model(\"openai:gpt-4o-mini\")\n", - " \n", - " # Create store with semantic search enabled\n", - " embeddings = init_embeddings(\"openai:text-embedding-3-small\")\n", - " store = InMemoryStore(\n", - " index={\n", - " \"embed\": embeddings,\n", - " \"dims\": 1536,\n", - " }\n", - " )\n", - " \n", - " store.put((\"user_123\", \"memories\"), \"1\", {\"text\": \"I love pizza\"})\n", - " store.put((\"user_123\", \"memories\"), \"2\", {\"text\": \"I am a plumber\"})\n", - " \n", - " def chat(state, *, store: BaseStore):\n", - " # Search based on user's last message\n", - " items = store.search(\n", - " (\"user_123\", \"memories\"), query=state[\"messages\"][-1].content, limit=2\n", - " )\n", - " memories = \"\\n\".join(item.value[\"text\"] for item in items)\n", - " memories = f\"## Memories of user\\n{memories}\" if memories else \"\"\n", - " response = llm.invoke(\n", - " [\n", - " {\"role\": \"system\", \"content\": f\"You are a helpful assistant.\\n{memories}\"},\n", - " *state[\"messages\"],\n", - " ]\n", - " )\n", - " return {\"messages\": [response]}\n", - " \n", - " \n", - " builder = StateGraph(MessagesState)\n", - " builder.add_node(chat)\n", - " builder.add_edge(START, \"chat\")\n", - " graph = builder.compile(store=store)\n", - " \n", - " for message, metadata in graph.stream(\n", - " input={\"messages\": [{\"role\": \"user\", \"content\": \"I'm hungry\"}]},\n", - " stream_mode=\"messages\",\n", - " ):\n", - " print(message.content, end=\"\")\n", - " ```\n", - "\n", - "See [this guide](../memory/semantic-search/) for more information on how to use semantic search with LangGraph memory store." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/docs/how-tos/streaming.md b/docs/docs/how-tos/streaming.md index d85cb3000..bf25419c4 100644 --- a/docs/docs/how-tos/streaming.md +++ b/docs/docs/how-tos/streaming.md @@ -1,11 +1,220 @@ # Stream outputs -## Streaming API +You can [stream outputs](../concepts/streaming.md) from a LangGraph agent or workflow. + +## Supported stream modes + +Pass one or more of the following stream modes as a list to the [`stream()`][langgraph.graph.state.CompiledStateGraph.stream] or [`astream()`][langgraph.graph.state.CompiledStateGraph.astream] methods: + +| Mode | Description | +|------|-------------| +| `values` | Streams the full value of the state after each step of the graph. | +| `updates` | Streams the updates to the state after each step of the graph. If multiple updates are made in the same step (e.g., multiple nodes are run), those updates are streamed separately. | +| `custom` | Streams custom data from inside your graph nodes. | +| `messages` | Streams 2-tuples (LLM token, metadata) from any graph nodes where an LLM is invoked. | +| `debug` | Streams as much information as possible throughout the execution of the graph. + +## Stream from an agent + +### Agent progress + +To stream agent progress, use the [`stream()`][langgraph.graph.state.CompiledStateGraph.stream] or [`astream()`][langgraph.graph.state.CompiledStateGraph.astream] methods with `stream_mode="updates"`. This emits an event after every agent step. + +For example, if you have an agent that calls a tool once, you should see the following updates: + +* **LLM node**: AI message with tool call requests +* **Tool node**: Tool message with execution result +* **LLM node**: Final AI response + +=== "Sync" + + ```python + agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[get_weather], + ) + # highlight-next-line + for chunk in agent.stream( + {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, + # highlight-next-line + stream_mode="updates" + ): + print(chunk) + print("\n") + ``` + +=== "Async" + + ```python + agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[get_weather], + ) + # highlight-next-line + async for chunk in agent.astream( + {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, + # highlight-next-line + stream_mode="updates" + ): + print(chunk) + print("\n") + ``` + +### LLM tokens + +To stream tokens as they are produced by the LLM, use `stream_mode="messages"`: + +=== "Sync" + + ```python + agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[get_weather], + ) + # highlight-next-line + for token, metadata in agent.stream( + {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, + # highlight-next-line + stream_mode="messages" + ): + print("Token", token) + print("Metadata", metadata) + print("\n") + ``` + +=== "Async" + + ```python + agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[get_weather], + ) + # highlight-next-line + async for token, metadata in agent.astream( + {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, + # highlight-next-line + stream_mode="messages" + ): + print("Token", token) + print("Metadata", metadata) + print("\n") + ``` + +### Tool updates + +To stream updates from tools as they are executed, you can use [get_stream_writer][langgraph.config.get_stream_writer]. + +=== "Sync" + + ```python + # highlight-next-line + from langgraph.config import get_stream_writer + + def get_weather(city: str) -> str: + """Get weather for a given city.""" + # highlight-next-line + writer = get_stream_writer() + # stream any arbitrary data + # highlight-next-line + writer(f"Looking up data for city: {city}") + return f"It's always sunny in {city}!" + + agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[get_weather], + ) + + for chunk in agent.stream( + {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, + # highlight-next-line + stream_mode="custom" + ): + print(chunk) + print("\n") + ``` + +=== "Async" + + ```python + # highlight-next-line + from langgraph.config import get_stream_writer + + def get_weather(city: str) -> str: + """Get weather for a given city.""" + # highlight-next-line + writer = get_stream_writer() + # stream any arbitrary data + # highlight-next-line + writer(f"Looking up data for city: {city}") + return f"It's always sunny in {city}!" + + agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[get_weather], + ) + + async for chunk in agent.astream( + {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, + # highlight-next-line + stream_mode="custom" + ): + print(chunk) + print("\n") + ``` + +!!! Note + If you add `get_stream_writer` inside your tool, you won't be able to invoke the tool outside of a LangGraph execution context. + +### Stream multiple modes + +You can specify multiple streaming modes by passing stream mode as a list: `stream_mode=["updates", "messages", "custom"]`: + +=== "Sync" + + ```python + agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[get_weather], + ) + + for stream_mode, chunk in agent.stream( + {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, + # highlight-next-line + stream_mode=["updates", "messages", "custom"] + ): + print(chunk) + print("\n") + ``` + +=== "Async" + + ```python + agent = create_react_agent( + model="anthropic:claude-3-7-sonnet-latest", + tools=[get_weather], + ) + + async for stream_mode, chunk in agent.astream( + {"messages": [{"role": "user", "content": "what is the weather in sf"}]}, + # highlight-next-line + stream_mode=["updates", "messages", "custom"] + ): + print(chunk) + print("\n") + ``` + +### Disable streaming + +In some applications you might need to disable streaming of individual tokens for a given model. This is useful in [multi-agent](../agents/multi-agent.md) systems to control which agents stream their output. + +See the [Models](../agents/models.md#disable-streaming) guide to learn how to disable streaming. + +## Stream from a workflow + +### Basic usage example LangGraph graphs expose the [`.stream()`][langgraph.pregel.Pregel.stream] (sync) and [`.astream()`][langgraph.pregel.Pregel.astream] (async) methods to yield streamed outputs as iterators. -Basic usage example: - === "Sync" ```python @@ -61,18 +270,7 @@ Basic usage example: ```output {'refine_topic': {'topic': 'ice cream and cats'}} {'generate_joke': {'joke': 'This is a joke about ice cream and cats'}} - ``` - - -### Supported stream modes - -| Mode | Description | -|----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [`values`](#stream-graph-state) | Streams the full value of the state after each step of the graph. | -| [`updates`](#stream-graph-state) | Streams the updates to the state after each step of the graph. If multiple updates are made in the same step (e.g., multiple nodes are run), those updates are streamed separately. | -| [`custom`](#stream-custom-data) | Streams custom data from inside your graph nodes. | -| [`messages`](#messages) | Streams 2-tuples (LLM token, metadata) from any graph nodes where an LLM is invoked. | -| [`debug`](#debug) | Streams as much information as possible throughout the execution of the graph. | + ``` | ### Stream multiple modes @@ -94,7 +292,7 @@ The streamed outputs will be tuples of `(mode, chunk)` where `mode` is the name print(chunk) ``` -## Stream graph state +### Stream graph state Use the stream modes `updates` and `values` to stream the state of the graph as it executes. @@ -157,7 +355,7 @@ graph = ( ``` -## Subgraphs +### Stream subgraph outputs To include outputs from [subgraphs](../concepts/subgraphs.md) in the streamed outputs, you can set `subgraphs=True` in the `.stream()` method of the parent graph. This will stream outputs from both the parent graph and any subgraphs. @@ -233,7 +431,7 @@ for chunk in graph.stream( **Note** that we are receiving not just the node updates, but we also the namespaces which tell us what graph (or subgraph) we are streaming from. -## Debugging {#debug} +### Debugging {#debug} Use the `debug` streaming mode to stream as much information as possible throughout the execution of the graph. The streamed outputs include the name of the node as well as the full state. @@ -247,7 +445,7 @@ for chunk in graph.stream( ``` -## LLM tokens {#messages} +### LLM tokens {#messages} Use the `messages` streaming mode to stream Large Language Model (LLM) outputs **token by token** from any part of your graph, including nodes, tools, subgraphs, or tasks. @@ -307,7 +505,7 @@ for message_chunk, metadata in graph.stream( # (2)! 2. The "messages" stream mode returns an iterator of tuples `(message_chunk, metadata)` where `message_chunk` is the token streamed by the LLM and `metadata` is a dictionary with information about the graph node where the LLM was called and other information. -### Filter by LLM invocation +#### Filter by LLM invocation You can associate `tags` with LLM invocations to filter the streamed tokens by LLM invocation. @@ -391,7 +589,7 @@ async for msg, metadata in graph.astream( # (3)! 4. The `stream_mode` is set to "messages" to stream LLM tokens. The `metadata` contains information about the LLM invocation, including the tags. -### Filter by node +#### Filter by node To stream tokens only from specific nodes, use `stream_mode="messages"` and filter the outputs by the `langgraph_node` field in the streamed metadata: @@ -464,7 +662,7 @@ for msg, metadata in graph.stream( # (1)! 1. The "messages" stream mode returns a tuple of `(message_chunk, metadata)` where `message_chunk` is the token streamed by the LLM and `metadata` is a dictionary with information about the graph node where the LLM was called and other information. 2. Filter the streamed tokens by the `langgraph_node` field in the metadata to only include the tokens from the `write_poem` node. -## Stream custom data +### Stream custom data To send **custom user-defined data** from inside a LangGraph node or tool, follow these steps: @@ -541,7 +739,7 @@ To send **custom user-defined data** from inside a LangGraph node or tool, follo 3. Emit another custom key-value pair. 4. Set `stream_mode="custom"` to receive the custom data in the stream. -## Use with any LLM +### Use with any LLM You can use `stream_mode="custom"` to stream data from **any LLM API** — even if that API does **not** implement the LangChain chat model interface. @@ -701,7 +899,7 @@ for chunk in graph.stream( ``` -## Disable streaming for specific chat models +### Disable streaming for specific chat models If your application mixes models that support streaming with those that do not, you may need to explicitly disable streaming for models that do not support it. @@ -733,7 +931,7 @@ Set `disable_streaming=True` when initializing the model. 1. Set `disable_streaming=True` to disable streaming for the chat model. -## Async with Python < 3.11 { #async } +### Async with Python < 3.11 { #async } In Python versions < 3.11, [asyncio tasks](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task) do not support the `context` parameter. This limits LangGraph ability to automatically propagate context, and affects LangGraph’s streaming mechanisms in two key ways: diff --git a/docs/docs/how-tos/use-functional-api.md b/docs/docs/how-tos/use-functional-api.md index 12bfe34fc..a50014e41 100644 --- a/docs/docs/how-tos/use-functional-api.md +++ b/docs/docs/how-tos/use-functional-api.md @@ -471,6 +471,124 @@ Please see the following examples for more details: Short-term memory allows storing information across different **invocations** of the same **thread id**. See [short-term memory](../concepts/functional_api.md#short-term-memory) for more details. +### Manage checkpoints + +You can view and delete the information stored by the checkpointer. + +#### View thread state (checkpoint) + +```python +config = { + "configurable": { + # highlight-next-line + "thread_id": "1", + # optionally provide an ID for a specific checkpoint, + # otherwise the latest checkpoint is shown + # highlight-next-line + # "checkpoint_id": "1f029ca3-1f5b-6704-8004-820c16b69a5a" + + } +} +# highlight-next-line +graph.get_state(config) +``` + +``` +StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today?), HumanMessage(content="what's my name?"), AIMessage(content='Your name is Bob.')]}, next=(), + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}}, + metadata={ + 'source': 'loop', + 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}}, + 'step': 4, + 'parents': {}, + 'thread_id': '1' + }, + created_at='2025-05-05T16:01:24.680462+00:00', + parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, + tasks=(), + interrupts=() +) +``` + +#### View the history of the thread (checkpoints) + +```python +config = { + "configurable": { + # highlight-next-line + "thread_id": "1" + } +} +# highlight-next-line +list(graph.get_state_history(config)) +``` + +``` +[ + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content="what's my name?"), AIMessage(content='Your name is Bob.')]}, + next=(), + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1f5b-6704-8004-820c16b69a5a'}}, + metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Your name is Bob.')}}, 'step': 4, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:24.680462+00:00', + parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, + tasks=(), + interrupts=() + ), + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?'), HumanMessage(content="what's my name?")]}, + next=('call_model',), + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-1790-6b0a-8003-baf965b6a38f'}}, + metadata={'source': 'loop', 'writes': None, 'step': 3, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:23.863421+00:00', + parent_config={...} + tasks=(PregelTask(id='8ab4155e-6b15-b885-9ce5-bed69a2c305c', name='call_model', path=('__pregel_pull', 'call_model'), error=None, interrupts=(), state=None, result={'messages': AIMessage(content='Your name is Bob.')}),), + interrupts=() + ), + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]}, + next=('__start__',), + config={...}, + metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': "what's my name?"}]}}, 'step': 2, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:23.863173+00:00', + parent_config={...} + tasks=(PregelTask(id='24ba39d6-6db1-4c9b-f4c5-682aeaf38dcd', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': "what's my name?"}]}),), + interrupts=() + ), + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob"), AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')]}, + next=(), + config={...}, + metadata={'source': 'loop', 'writes': {'call_model': {'messages': AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')}}, 'step': 1, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:23.862295+00:00', + parent_config={...} + tasks=(), + interrupts=() + ), + StateSnapshot( + values={'messages': [HumanMessage(content="hi! I'm bob")]}, + next=('call_model',), + config={...}, + metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:22.278960+00:00', + parent_config={...} + tasks=(PregelTask(id='8cbd75e0-3720-b056-04f7-71ac805140a0', name='call_model', path=('__pregel_pull', 'call_model'), error=None, interrupts=(), state=None, result={'messages': AIMessage(content='Hi Bob! How are you doing today? Is there anything I can help you with?')}),), + interrupts=() + ), + StateSnapshot( + values={'messages': []}, + next=('__start__',), + config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f029ca3-0870-6ce2-bfff-1f3f14c3e565'}}, + metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': "hi! I'm bob"}]}}, 'step': -1, 'parents': {}, 'thread_id': '1'}, + created_at='2025-05-05T16:01:22.277497+00:00', + parent_config=None, + tasks=(PregelTask(id='d458367b-8265-812c-18e2-33001d199ce6', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': "hi! I'm bob"}]}),), + interrupts=() + ) +] +``` + ### Decouple return value from saved value Use `entrypoint.final` to decouple what is returned to the caller from what is persisted in the checkpoint. This is useful when: diff --git a/docs/docs/tutorials/deployment.md b/docs/docs/tutorials/deployment.md deleted file mode 100644 index 9042d98b3..000000000 --- a/docs/docs/tutorials/deployment.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -search: - boost: 2 ---- - -# Deployment 🚀 - -There are two free options for deploying LangGraph applications via the LangGraph Server: - -- [Local](./langgraph-platform/local-server.md): Deploy for local testing and development. -- [Standalone Container (Lite)](../concepts/langgraph_standalone_container.md): A limited version of Standalone Container for deployments unlikely to see more that 1 million node executions per year and that do not need crons and other enterprise features. Standalone Container (Lite) deployment option is free with a LangSmith API key. - -## Other deployment options - -Additionally, you can deploy to production with [LangGraph Platform](../concepts/langgraph_platform.md): - -- [Cloud SaaS](../concepts/langgraph_cloud.md): Connect your GitHub repositories and deploy LangGraph Servers within LangChain's cloud. *We manage everything.* -- [Self-Hosted Data Plane(Beta)](../concepts/langgraph_self_hosted_data_plane.md): Create deployments from the [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to **your** cloud. *We manage the [control plane](../concepts/langgraph_control_plane.md). You manage the deployments.* -- [Self-Hosted Control Plane(Beta)](../concepts/langgraph_self_hosted_control_plane.md): Create deployments from a self-hosted [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to **your** cloud. *You manage everything.* -- [Standalone Container](../concepts/langgraph_standalone_container.md): Deploy LangGraph Server Docker images however you like. - -For more information, see [Deployment options](../concepts/deployment_options.md). diff --git a/docs/docs/tutorials/get-started/5-customize-state.md b/docs/docs/tutorials/get-started/5-customize-state.md index 07e10d223..956fda641 100644 --- a/docs/docs/tutorials/get-started/5-customize-state.md +++ b/docs/docs/tutorials/get-started/5-customize-state.md @@ -215,7 +215,7 @@ snapshot = graph.get_state(config) {'name': 'LangGraph (library)', 'birthday': 'Jan 17, 2024'} ``` -Manual state updates will [generate a trace](https://smith.langchain.com/public/7ebb7827-378d-49fe-9f6c-5df0e90086c8/r) in LangSmith. If desired, they can also be used to [control human-in-the-loop workflows](../../how-tos/human_in_the_loop/edit-graph-state.md). Use of the `interrupt` function is generally recommended instead, as it allows data to be transmitted in a human-in-the-loop interaction independently of state updates. +Manual state updates will [generate a trace](https://smith.langchain.com/public/7ebb7827-378d-49fe-9f6c-5df0e90086c8/r) in LangSmith. If desired, they can also be used to [control human-in-the-loop workflows](../../how-tos/human_in_the_loop/add-human-in-the-loop.md). Use of the `interrupt` function is generally recommended instead, as it allows data to be transmitted in a human-in-the-loop interaction independently of state updates. **Congratulations!** You've added custom keys to the state to facilitate a more complex workflow, and learned how to generate state updates from inside tools. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b1f932409..87b5cf2f3 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -89,163 +89,144 @@ plugins: - "!^_" nav: - - Guides: + - Get started: - index.md - - Get started: - - Quickstart: agents/agents.md - - LangGraph basics: - - concepts/why-langgraph.md - - Build a basic chatbot: tutorials/get-started/1-build-basic-chatbot.md - - tutorials/get-started/2-add-tools.md - - tutorials/get-started/3-add-memory.md - - Add human-in-the-loop: tutorials/get-started/4-human-in-the-loop.md - - tutorials/get-started/5-customize-state.md - - tutorials/get-started/6-time-travel.md - - Deployment: tutorials/deployment.md - - Prebuilt agents: - - Overview: agents/overview.md - - agents/run_agents.md - - agents/streaming.md - - agents/models.md - - agents/tools.md - - agents/mcp.md - - agents/context.md - - agents/memory.md - - agents/human-in-the-loop.md - - agents/multi-agent.md - - agents/evals.md - - agents/deployment.md - - agents/ui.md - - LangGraph framework: - - Agent architectures: - - Overview: concepts/agentic_concepts.md + - Quickstarts: + - Agent: agents/agents.md + - Local server: tutorials/langgraph-platform/local-server.md + - Deployment: cloud/quick_start.md + - General concepts: + - Common patterns: + - Agent architectures: concepts/agentic_concepts.md - Workflows & agents: tutorials/workflows.md - - Graphs: - - Overview: concepts/low_level.md - - Runtime overview: concepts/pregel.md - - Use the Graph API: how-tos/graph-api.ipynb - - Streaming: - - Overview: concepts/streaming.md - - "Stream outputs": how-tos/streaming.md - - Persistence: - - Overview: concepts/persistence.md - - concepts/durable_execution.md - - how-tos/persistence.ipynb - - Memory: - - Overview: concepts/memory.md - - Manage memory: how-tos/memory.ipynb - - Human-in-the-loop: - - Overview: concepts/human_in_the_loop.md - - how-tos/human_in_the_loop/add-human-in-the-loop.md - - Breakpoints: - - Overview: concepts/breakpoints.md - - how-tos/human_in_the_loop/breakpoints.ipynb - - Time travel: - - Overview: concepts/time-travel.md - - how-tos/human_in_the_loop/time-travel.ipynb - - Tools: - - Overview: concepts/tools.md - - how-tos/tool-calling.ipynb - - Subgraphs: - - Overview: concepts/subgraphs.md - - how-tos/subgraph.ipynb - - Multi-agent: - - Overview: concepts/multi_agent.md - - how-tos/multi_agent.ipynb - - Functional API: - - Overview: concepts/functional_api.md - - how-tos/use-functional-api.md - - - LangGraph Platform: - - Overview: concepts/langgraph_platform.md - - Get started: - - Quickstart: tutorials/langgraph-platform/local-server.md - - Deployment quickstart: cloud/quick_start.md - - Components: - - Overview: concepts/langgraph_components.md - - LangGraph Server: - - Overview: concepts/langgraph_server.md - - Application structure: - - Overview: concepts/application_structure.md - - cloud/deployment/setup.md - - cloud/deployment/setup_pyproject.md - - cloud/deployment/setup_javascript.md - - cloud/deployment/custom_docker.md - - LangGraph CLI: concepts/langgraph_cli.md - - LangGraph Studio: - - Overview: concepts/langgraph_studio.md - - Quickstart: cloud/how-tos/studio/quick_start.md - - cloud/how-tos/invoke_studio.md - - cloud/how-tos/studio/manage_assistants.md - - cloud/how-tos/threads_studio.md - - cloud/how-tos/iterate_graph_studio.md - - cloud/how-tos/studio/run_evals.md - - cloud/how-tos/clone_traces_studio.md - - cloud/how-tos/datasets_studio.md - - LangGraph SDK: concepts/sdk.md - - Data management: - - Add semantic search: cloud/deployment/semantic_search.md - - Add TTLs: how-tos/ttl/configure_ttl.md + - Agent development: agents/overview.md + - Workflow orchestration: + - Graphs: concepts/low_level.md + - Subgraphs: concepts/subgraphs.md + - Runtime: concepts/pregel.md + - Functional API: concepts/functional_api.md + - Core capabilities: + - Streaming: concepts/streaming.md + - Persistence: concepts/persistence.md + - Durable execution: concepts/durable_execution.md + - Memory: concepts/memory.md + - Tools: concepts/tools.md + - Human-in-the-loop: concepts/human_in_the_loop.md + - Breakpoints: concepts/breakpoints.md + - Time travel: concepts/time-travel.md + - Multi-agent: concepts/multi_agent.md + - Platform capabilities: + - LangGraph Platform: + - Overview: concepts/langgraph_platform.md + - Components: + - Overview: concepts/langgraph_components.md + - LangGraph Server: + - Overview: concepts/langgraph_server.md + - Data plane: concepts/langgraph_data_plane.md + - Control plane: concepts/langgraph_control_plane.md + - LangGraph CLI: concepts/langgraph_cli.md + - LangGraph Studio: concepts/langgraph_studio.md + - LangGraph SDK: concepts/sdk.md + - Plans & pricing: concepts/plans.md + - Application structure: concepts/application_structure.md + - Scalability & resilience: concepts/scalability_and_resilience.md + - Authentication & access control: concepts/auth.md + - Assistants: concepts/assistants.md + - Double-texting: concepts/double_texting.md + - Webhooks: cloud/concepts/webhooks.md + - Cron jobs: cloud/concepts/cron_jobs.md + - Deployment: + - Overview: concepts/deployment_options.md + - Deployment options: + - Cloud SaaS: concepts/langgraph_cloud.md + - Self-Hosted Data Plane: concepts/langgraph_self_hosted_data_plane.md + - Self-Hosted Control Plane: concepts/langgraph_self_hosted_control_plane.md + - Standalone Container: concepts/langgraph_standalone_container.md + + - Guides: + - LangGraph APIs: + - Use the Graph API: how-tos/graph-api.ipynb + - Use the Functional API: how-tos/use-functional-api.md + - Models: + - Configure model: agents/models.md + - Streaming: + - Stream outputs: how-tos/streaming.md + - Use Server API: cloud/how-tos/streaming.md + - Context: + - Use in agent: agents/context.md + - Memory: + - Add memory: how-tos/memory/add-memory.md + - Human-in-the-loop: + - Add to agent: agents/human-in-the-loop.md + - Add to workflow: how-tos/human_in_the_loop/add-human-in-the-loop.md + - Use Server API: cloud/how-tos/add-human-in-the-loop.md + - Time travel: + - Use Server API: cloud/how-tos/human_in_the_loop_time_travel.md + - Breakpoints: + - Set breakpoints: how-tos/human_in_the_loop/breakpoints.ipynb + - Use Server API: cloud/how-tos/human_in_the_loop_breakpoint.md + - Tools: + - Use tools: agents/tools.md + - Customize tools: how-tos/tool-calling.ipynb + - Subgraphs: + - Use subgraphs: how-tos/subgraph.ipynb + - Multi-agent: + - Prebuilt implementation: agents/multi-agent.md + - Custom implementation: how-tos/multi_agent.ipynb + - MCP: + - Use MCP tools: agents/mcp.md + - Server deployment via MCP: concepts/server-mcp.md + - Deployment: + - Basic deployment: agents/deployment.md + - Set up your application: + - Use requirements.txt: cloud/deployment/setup.md + - Use pyproject.toml: cloud/deployment/setup_pyproject.md + - Use JavaScript: cloud/deployment/setup_javascript.md + - Use custom Docker: cloud/deployment/custom_docker.md + - Deploy to production: + - Cloud SaaS: cloud/deployment/cloud.md + - Self-Hosted Data Plane: cloud/deployment/self_hosted_data_plane.md + - Self-Hosted Control Plane: cloud/deployment/self_hosted_control_plane.md + - Standalone Container: cloud/deployment/standalone_container.md + - Evaluation: + - Basic implementation: agents/evals.md + - Platform capabilities: + - LangGraph Studio: + - Quickstart: cloud/how-tos/studio/quick_start.md + - cloud/how-tos/invoke_studio.md + - cloud/how-tos/studio/manage_assistants.md + - cloud/how-tos/threads_studio.md + - cloud/how-tos/iterate_graph_studio.md + - cloud/how-tos/studio/run_evals.md + - cloud/how-tos/clone_traces_studio.md + - cloud/how-tos/datasets_studio.md - Authentication & access control: - - Overview: concepts/auth.md - how-tos/auth/custom_auth.md - how-tos/auth/openapi_security.md - Assistants: - - Overview: concepts/assistants.md - cloud/how-tos/configuration_cloud.md - - Threads: - - Overview: cloud/concepts/threads.md - - cloud/how-tos/use_threads.md - - Runs: - - Overview: cloud/concepts/runs.md - - cloud/how-tos/background_run.md - - cloud/how-tos/same-thread.md - - cloud/how-tos/cron_jobs.md - - cloud/how-tos/stateless_runs.md - - cloud/how-tos/configurable_headers.md - - Streaming: - - Overview: cloud/concepts/streaming.md - - cloud/how-tos/streaming.md - - Human-in-the-loop: cloud/how-tos/add-human-in-the-loop.md - - Breakpoints: cloud/how-tos/human_in_the_loop_breakpoint.md - - Time travel: cloud/how-tos/human_in_the_loop_time_travel.md - - MCP: concepts/server-mcp.md + - Threads: cloud/how-tos/use_threads.md + - Runs: + - cloud/how-tos/background_run.md + - cloud/how-tos/same-thread.md + - cloud/how-tos/cron_jobs.md + - cloud/how-tos/stateless_runs.md + - cloud/how-tos/configurable_headers.md - Double-texting: - - Overview: concepts/double_texting.md - cloud/how-tos/interrupt_concurrent.md - cloud/how-tos/rollback_concurrent.md - cloud/how-tos/reject_concurrent.md - cloud/how-tos/enqueue_concurrent.md - - Webhooks: - - Overview: cloud/concepts/webhooks.md - - cloud/how-tos/webhooks.md - - Cron jobs: - - Overview: cloud/concepts/cron_jobs.md - - cloud/how-tos/cron_jobs.md + - Webhooks: cloud/how-tos/webhooks.md + - Cron jobs: cloud/how-tos/cron_jobs.md - Server customization: - how-tos/http/custom_lifespan.md - how-tos/http/custom_middleware.md - how-tos/http/custom_routes.md - - Deployment: - - Overview: concepts/deployment_options.md - - Data plane: concepts/langgraph_data_plane.md - - Control plane: concepts/langgraph_control_plane.md - - Deployment options: - - Cloud SaaS: - - Overview: concepts/langgraph_cloud.md - - Deploy Cloud SaaS: cloud/deployment/cloud.md - - Self-Hosted Data Plane: - - Overview: concepts/langgraph_self_hosted_data_plane.md - - Deploy Self-Hosted Data Plane: cloud/deployment/self_hosted_data_plane.md - - Self-Hosted Control Plane: - - Overview: concepts/langgraph_self_hosted_control_plane.md - - Deploy Self-Hosted Control Plane: cloud/deployment/self_hosted_control_plane.md - - Standalone Container: - - Overview: concepts/langgraph_standalone_container.md - - Deploy Standalone Container: cloud/deployment/standalone_container.md - - Scalability & resilience: concepts/scalability_and_resilience.md - - Plans & pricing: concepts/plans.md - + - Data management: + - Add semantic search: cloud/deployment/semantic_search.md + - Add TTLs: how-tos/ttl/configure_ttl.md + - Reference: - reference/index.md - LangGraph: @@ -274,9 +255,20 @@ nav: - Environment variables: cloud/reference/env_var.md - Examples: + - agents/run_agents.md + - LangGraph basics: + - concepts/why-langgraph.md + - Build a basic chatbot: tutorials/get-started/1-build-basic-chatbot.md + - tutorials/get-started/2-add-tools.md + - tutorials/get-started/3-add-memory.md + - Add human-in-the-loop: tutorials/get-started/4-human-in-the-loop.md + - tutorials/get-started/5-customize-state.md + - tutorials/get-started/6-time-travel.md + - Template applications: concepts/template_applications.md # TODO: make tutorial - Agentic RAG: tutorials/rag/langgraph_agentic_rag.ipynb - Agent Supervisor: tutorials/multi_agent/agent_supervisor.ipynb - SQL agent: tutorials/sql-agent.ipynb + - Prebuilt chat UI: agents/ui.md - Graph runs in LangSmith: how-tos/run-id-langsmith.ipynb - LangGraph Platform: - Authentication: @@ -291,11 +283,12 @@ nav: - Integrate LangGraph into a React app: cloud/how-tos/use_stream_react.md - Implement generative UI with LangGraph: cloud/how-tos/generative_ui_react.md - - Resources: - - concepts/faq.md - - Template applications: concepts/template_applications.md # TODO: make tutorial - - llms.txt: llms-txt-overview.md + - Additional resources: - agents/prebuilt.md # NOTE: prebuilt.md is auto-generated by `make build-prebuilt` + - LangGraph Academy course: https://academy.langchain.com/courses/intro-to-langgraph + - Case studies: adopters.md + - concepts/faq.md + - llms.txt: llms-txt-overview.md - Troubleshooting: - Errors: - troubleshooting/errors/index.md @@ -306,9 +299,7 @@ nav: - troubleshooting/errors/INVALID_CHAT_HISTORY.md - troubleshooting/errors/INVALID_LICENSE.md - LangGraph Studio: troubleshooting/studio.md - - Learn: - - LangGraph Academy course: https://academy.langchain.com/courses/intro-to-langgraph - - Case studies: adopters.md + markdown_extensions: - abbr diff --git a/docs/uv.lock b/docs/uv.lock index d284ffa35..af783325b 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -2590,7 +2590,7 @@ wheels = [ [[package]] name = "langgraph" -version = "0.4.7" +version = "0.5.0rc1" source = { editable = "../libs/langgraph" } dependencies = [ { name = "langchain-core" }, @@ -2641,7 +2641,7 @@ dev = [ [[package]] name = "langgraph-checkpoint" -version = "2.0.26" +version = "2.1.0" source = { editable = "../libs/checkpoint" } dependencies = [ { name = "langchain-core" }, @@ -2660,6 +2660,8 @@ dev = [ { name = "dataclasses-json" }, { name = "mypy" }, { name = "numpy" }, + { name = "pandas" }, + { name = "pandas-stubs", specifier = ">=2.2.2.240807" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-mock" }, @@ -2892,7 +2894,7 @@ test = [ [[package]] name = "langgraph-prebuilt" -version = "0.2.2" +version = "0.5.0rc0" source = { editable = "../libs/prebuilt" } dependencies = [ { name = "langchain-core" }, diff --git a/examples/persistence.ipynb b/examples/persistence.ipynb index e98636e0a..a7a0e08b0 100644 --- a/examples/persistence.ipynb +++ b/examples/persistence.ipynb @@ -5,7 +5,7 @@ "id": "d16e8b9c", "metadata": {}, "source": [ - "This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/persistence.ipynb" + "This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/memory/add-memory.md." ] } ],