--- quickstart.ipynb --- # LangGraph.js - Quickstart ## Introduction In this quickstart guide, you'll get up and running with a simple Reason + Act Agent (often called a ReAct Agent) that can search the web using [Tavily Search API](https://tavily.com/). The code is fully configurable. You can: - swap out components - customize the execution flow - extend it with custom code or tooling - change the Large Language Model (LLM) and provider being used ## Prerequisites To follow along, you'll need to have the following: - NodeJS version 18 or newer - A [Tavily](https://tavily.com/) account and API key - An [OpenAI developer platform](https://platform.openai.com/docs/overview) account and API key Start by creating a new folder for the project. Open your terminal and run the following code: ```bash mkdir langgraph-agent cd langgraph-agent ``` You'll also need to install a few dependencies to create an agent: - **@langchain/langgraph** contains the building blocks used to assemble an agent - **@langchain/openai** enable your agent to use OpenAI's LLMs - **@langchain/community** includes the Tavily integration give your agent search capabilities You can install these dependencies using by running following npm command in your terminal: ```bash npm install @langchain/core @langchain/langgraph @langchain/openai @langchain/community ``` ## LangSmith Optionally, set up [LangSmith](https://docs.smith.langchain.com/) for best-in-class observability. Setup is simple - add the following variables to your environment and update the `LANGCHAIN_API_KEY` value with your API key. ```typescript // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..."; // process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; // process.env.LANGCHAIN_TRACING_V2 = "true"; // process.env.LANGCHAIN_PROJECT = "Quickstart: LangGraphJS"; ``` ## Making your first agent using LangGraph Create a file named `agent.ts` (short for Reason + Act Agent) and add the below TypeScript code to it. Make sure you update the environment variables at the top of the file to contain your API keys. If you don't, the OpenAI and Tavily API calls will produce errors and your agent will not work correctly. Once you've added your API keys, save the file and run the code with the following command: ```bash npx tsx agent.ts ``` ```typescript // agent.ts // IMPORTANT - Add your API keys here. Be careful not to publish them. process.env.OPENAI_API_KEY = "sk-..."; process.env.TAVILY_API_KEY = "tvly-..."; import { TavilySearchResults } from "@langchain/community/tools/tavily_search"; import { ChatOpenAI } from "@langchain/openai"; import { MemorySaver } from "@langchain/langgraph"; import { HumanMessage } from "@langchain/core/messages"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; // Define the tools for the agent to use const agentTools = [new TavilySearchResults({ maxResults: 3 })]; const agentModel = new ChatOpenAI({ temperature: 0 }); // Initialize memory to persist state between graph runs const agentCheckpointer = new MemorySaver(); const agent = createReactAgent({ llm: agentModel, tools: agentTools, checkpointSaver: agentCheckpointer, }); // Now it's time to use! const agentFinalState = await agent.invoke( { messages: [new HumanMessage("what is the current weather in sf")] }, { configurable: { thread_id: "42" } }, ); console.log( agentFinalState.messages[agentFinalState.messages.length - 1].content, ); const agentNextState = await agent.invoke( { messages: [new HumanMessage("what about ny")] }, { configurable: { thread_id: "42" } }, ); console.log( agentNextState.messages[agentNextState.messages.length - 1].content, ); ``` ```output The current weather in San Francisco is as follows: - Temperature: 82.0°F (27.8°C) - Condition: Sunny - Wind: 11.9 mph from the NW - Humidity: 41% - Pressure: 29.98 in - Visibility: 9.0 miles - UV Index: 6.0 For more details, you can visit [Weather in San Francisco](https://www.weatherapi.com/). The current weather in New York is as follows: - Temperature: 84.0°F (28.9°C) - Condition: Sunny - Wind: 2.2 mph from SSE - Humidity: 57% - Pressure: 29.89 in - Precipitation: 0.01 in - Visibility: 9.0 miles - UV Index: 6.0 For more details, you can visit [Weather in New York](https://www.weatherapi.com/). ``` ## How does it work? The createReactAgent constructor lets you create a simple tool-using LangGraph agent in a single line of code. Here's a visual representation of the graph: ```typescript // Note: tslab only works inside a jupyter notebook. Don't worry about running this code yourself! import * as tslab from "tslab"; const graph = agent.getGraph(); const image = await graph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` Alternatively, you can save the graph as a PNG file locally using the following approach: ```ts import { writeFileSync } from "node:fs"; const graphStateImage = await drawableGraphGraphState.drawMermaidPng(); const graphStateArrayBuffer = await graphStateImage.arrayBuffer(); const filePath = "./graphState.png"; writeFileSync(filePath, new Uint8Array(graphStateArrayBuffer)); ``` ## Customizing agent behavior createReactAgent can be great for simple agents, but sometimes you need something more powerful. LangGraph really shines when you need fine-grained control over an agent's behavior. The following code creates an agent with the same behavior as the example above, but you can clearly see the execution logic and how you could customize it. Update the code in your `agent.ts` file to match the example below. Once again, be sure to update the environment variables at the top. After you've updated your environment variables and saved the file, you can run it with the same command as before: ```bash npx tsx agent.ts ``` ```typescript // agent.ts // IMPORTANT - Add your API keys here. Be careful not to publish them. process.env.OPENAI_API_KEY = "sk-..."; process.env.TAVILY_API_KEY = "tvly-..."; import { TavilySearchResults } from "@langchain/community/tools/tavily_search"; import { ChatOpenAI } from "@langchain/openai"; import { HumanMessage, AIMessage } from "@langchain/core/messages"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { StateGraph, MessagesAnnotation } from "@langchain/langgraph"; // Define the tools for the agent to use const tools = [new TavilySearchResults({ maxResults: 3 })]; const toolNode = new ToolNode(tools); // Create a model and give it access to the tools const model = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0, }).bindTools(tools); // Define the function that determines whether to continue or not function shouldContinue({ messages }: typeof MessagesAnnotation.State) { const lastMessage = messages[messages.length - 1] as AIMessage; // If the LLM makes a tool call, then we route to the "tools" node if (lastMessage.tool_calls?.length) { return "tools"; } // Otherwise, we stop (reply to the user) using the special "__end__" node return "__end__"; } // Define the function that calls the model async function callModel(state: typeof MessagesAnnotation.State) { const response = await model.invoke(state.messages); // We return a list, because this will get added to the existing list return { messages: [response] }; } // Define a new graph const workflow = new StateGraph(MessagesAnnotation) .addNode("agent", callModel) .addEdge("__start__", "agent") // __start__ is a special name for the entrypoint .addNode("tools", toolNode) .addEdge("tools", "agent") .addConditionalEdges("agent", shouldContinue); // Finally, we compile it into a LangChain Runnable. const app = workflow.compile(); // Use the agent const finalState = await app.invoke({ messages: [new HumanMessage("what is the weather in sf")], }); console.log(finalState.messages[finalState.messages.length - 1].content); const nextState = await app.invoke({ // Including the messages from the previous run gives the LLM context. // This way it knows we're asking about the weather in NY messages: [...finalState.messages, new HumanMessage("what about ny")], }); console.log(nextState.messages[nextState.messages.length - 1].content); ``` There are a few new things going on in this version of our ReAct Agent. A [`ToolNode`](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph_prebuilt.ToolNode.html) enables the LLM to use tools. In this example, we made a `shouldContinue` function and passed it to [`addConditionalEdge`](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph.StateGraph.html#addConditionalEdges) so our ReAct Agent can either call a tool or respond to the request. [Annotations](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#annotation) are how graph state is represented in LangGraph. We're using [`MessagesAnnotation`](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#messagesannotation), a helper that implements a common pattern: keeping the message history in an array. ## Next Steps Great job creating your first AI agent using LangGraph! If you're ready to build something more, check out our other tutorials to learn how to implement other end-to-end agentic workflows such as: - Retrieval-Augmented Generation (RAG) - Multi-agent collaboration - Reflection, where the agent evaluates its work If you'd rather improve your agent we have how-to guides to help, including: - Tool calling that enables agents to interact with APIs - give your agent persistent memory to continue conversations and debug unexpected behavior - Put a human in the loop for actions you want a human to verify - Streaming the agent output to make your application feel more responsive - [Change the AI model in one line of code](https://js.langchain.com/docs/how_to/chat_models_universal_init/) --- how-tos/index.md --- --- title: How-to Guides description: How to accomplish common tasks in LangGraph.js --- # How-to guides Here you’ll find answers to “How do I...?” types of questions. These guides are **goal-oriented** and concrete; they're meant to help you complete a specific task. For conceptual explanations see the [Conceptual guide](../concepts/index.md). For end-to-end walk-throughs see [Tutorials](../tutorials/index.md). For comprehensive descriptions of every class and function see the [API Reference](https://langchain-ai.github.io/langgraphjs/reference/). ## Installation - [How to install and manage dependencies](manage-ecosystem-dependencies.ipynb) - [How to use LangGraph.js in web environments](use-in-web-environments.ipynb) ## LangGraph ### Controllability LangGraph.js is known for being a highly controllable agent framework. These how-to guides show how to achieve that controllability. - [How to create branches for parallel execution](branching.ipynb) - [How to create map-reduce branches for parallel execution](map-reduce.ipynb) - [How to combine control flow and state updates with Command](command.ipynb) - [How to create and control loops with recursion limits](recursion-limit.ipynb) ### Persistence LangGraph.js makes it easy to persist state across graph runs. The guides below shows how to add persistence to your graph. - [How to add thread-level persistence to your graph](persistence.ipynb) - [How to add thread-level persistence to subgraphs](subgraph-persistence.ipynb) - [How to add cross-thread persistence](cross-thread-persistence.ipynb) - [How to use a Postgres checkpointer for persistence](persistence-postgres.ipynb) See the below guides for how-to add persistence to your workflow using the [Functional API](../concepts/functional_api.md): - [How to add thread-level persistence (functional API)](persistence-functional.ipynb) - [How to add cross-thread persistence (functional API)](cross-thread-persistence-functional.ipynb) ### Memory LangGraph makes it easy to manage conversation [memory](../concepts/memory.md) in your graph. These how-to guides show how to implement different strategies for that. - [How to manage conversation history](manage-conversation-history.ipynb) - [How to delete messages](delete-messages.ipynb) - [How to add summary of the conversation history](add-summary-conversation-history.ipynb) - [How to add long-term memory (cross-thread)](cross-thread-persistence.ipynb) - [How to use semantic search for long-term memory](semantic-search.ipynb) ### Human-in-the-loop [Human-in-the-loop](/langgraphjs/concepts/human_in_the_loop) functionality allows you to involve humans in the decision-making process of your graph. These how-to guides show how to implement human-in-the-loop workflows in your graph. Key workflows: - [How to wait for user input](wait-user-input.ipynb): A basic example that shows how to implement a human-in-the-loop workflow in your graph using the `interrupt` function. - [How to review tool calls](review-tool-calls.ipynb): Incorporate human-in-the-loop for reviewing/editing/accepting tool call requests before they executed using the `interrupt` function. Other methods: - [How to add static breakpoints](breakpoints.ipynb): Use for debugging purposes. For [**human-in-the-loop**](/langgraphjs/concepts/human_in_the_loop) workflows, we recommend the [`interrupt` function](/langgraphjs/reference/functions/langgraph.interrupt-1.html) instead. - [How to edit graph state](edit-graph-state.ipynb): Edit graph state using `graph.update_state` method. Use this if implementing a **human-in-the-loop** workflow via **static breakpoints**. - [How to add dynamic breakpoints with `NodeInterrupt`](dynamic_breakpoints.ipynb): **Not recommended**: Use the [`interrupt` function](/langgraphjs/concepts/human_in_the_loop) instead. See the below guides for how-to implement human-in-the-loop workflows with the [Functional API](../concepts/functional_api.md): - [How to wait for user input (Functional API)](wait-user-input-functional.ipynb) - [How to review tool calls (Functional API)](review-tool-calls-functional.ipynb) ### Time Travel [Time travel](../concepts/time-travel.md) allows you to replay past actions in your LangGraph application to explore alternative paths and debug issues. These how-to guides show how to use time travel in your graph. - [How to view and update past graph state](time-travel.ipynb) ### Streaming LangGraph is built to be streaming first. These guides show how to use different streaming modes. - [How to stream the full state of your graph](stream-values.ipynb) - [How to stream state updates of your graph](stream-updates.ipynb) - [How to stream LLM tokens](stream-tokens.ipynb) - [How to stream LLM tokens without LangChain models](streaming-tokens-without-langchain.ipynb) - [How to stream custom data](streaming-content.ipynb) - [How to configure multiple streaming modes](stream-multiple.ipynb) - [How to stream events from within a tool](streaming-events-from-within-tools.ipynb) - [How to stream from the final node](streaming-from-final-node.ipynb) ### Tool calling - [How to call tools using ToolNode](tool-calling.ipynb) - [How to force an agent to call a tool](force-calling-a-tool-first.ipynb) - [How to handle tool calling errors](tool-calling-errors.ipynb) - [How to pass runtime values to tools](pass-run-time-values-to-tools.ipynb) - [How to update graph state from tools](update-state-from-tools.ipynb) ### Subgraphs [Subgraphs](../concepts/low_level.md#subgraphs) allow you to reuse an existing graph from another graph. These how-to guides show how to use subgraphs: - [How to add and use subgraphs](subgraph.ipynb) - [How to view and update state in subgraphs](subgraphs-manage-state.ipynb) - [How to transform inputs and outputs of a subgraph](subgraph-transform-state.ipynb) ### Multi-agent - [How to build a multi-agent network](multi-agent-network.ipynb) - [How to add multi-turn conversation in a multi-agent application](multi-agent-multi-turn-convo.ipynb) See the [multi-agent tutorials](../tutorials/index.md#multi-agent-systems) for implementations of other multi-agent architectures. See the below guides for how-to implement multi-agent workflows with the [Functional API](../concepts/functional_api.md): - [How to build a multi-agent network (functional API)](multi-agent-network-functional.ipynb) - [How to add multi-turn conversation in a multi-agent application (functional API)](multi-agent-multi-turn-convo-functional.ipynb) ### State management - [How to define graph state](define-state.ipynb) - [Have a separate input and output schema](input_output_schema.ipynb) - [Pass private state between nodes inside the graph](pass_private_state.ipynb) ### Other - [How to add runtime configuration to your graph](configuration.ipynb) - [How to add node retries](node-retry-policies.ipynb) - [How to let an agent return tool results directly](dynamically-returning-directly.ipynb) - [How to have an agent respond in structured format](respond-in-format.ipynb) - [How to manage agent steps](managing-agent-steps.ipynb) ### Prebuilt ReAct Agent - [How to create a ReAct agent](create-react-agent.ipynb) - [How to add memory to a ReAct agent](react-memory.ipynb) - [How to add a system prompt to a ReAct agent](react-system-prompt.ipynb) - [How to add Human-in-the-loop to a ReAct agent](react-human-in-the-loop.ipynb) - [How to return structured output from a ReAct agent](react-return-structured-output.ipynb) See the below guide for how-to build ReAct agents with the [Functional API](../concepts/functional_api.md): - [How to create a ReAct agent from scratch (Functional API)](react-agent-from-scratch-functional.ipynb) ## LangGraph Platform This section includes how-to guides for LangGraph Platform. LangGraph Platform is a commercial solution for deploying agentic applications in production, built on the open-source LangGraph framework. It provides four deployment options to fit a range of needs: a free tier, a self-hosted version, a cloud SaaS, and a Bring Your Own Cloud (BYOC) option. You can explore these options in detail in the [deployment options guide](../concepts/deployment_options.md). !!! tip * LangGraph is an MIT-licensed open-source library, which we are committed to maintaining and growing for the community. * You can always deploy LangGraph applications on your own infrastructure using the open-source LangGraph project without using LangGraph Platform. ### Application Structure Learn how to set up your app for deployment to LangGraph Platform: - [How to set up app for deployment (requirements.txt)](/langgraphjs/cloud/deployment/setup) - [How to set up app for deployment (pyproject.toml)](/langgraphjs/cloud/deployment/setup_pyproject) - [How to set up app for deployment (JavaScript)](/langgraphjs/cloud/deployment/setup_javascript) - [How to customize Dockerfile](/langgraphjs/cloud/deployment/custom_docker) - [How to test locally](/langgraphjs/cloud/deployment/test_locally) - [How to integrate LangGraph into your React application](/langgraphjs/cloud/how-tos/use_stream_react) ### Deployment LangGraph applications can be deployed using LangGraph Cloud, which provides a range of services to help you deploy, manage, and scale your applications. - [How to deploy to LangGraph cloud](/langgraphjs/cloud/deployment/cloud) - [How to deploy to a self-hosted environment](./deploy-self-hosted.md) - [How to interact with the deployment using RemoteGraph](./use-remote-graph.md) ### Assistants [Assistants](../concepts/assistants.md) are a configured instance of a template. - [How to configure agents](/langgraphjs/cloud/how-tos/configuration_cloud) - [How to version assistants](/langgraphjs/cloud/how-tos/assistant_versioning) ### Threads - [How to copy threads](/langgraphjs/cloud/how-tos/copy_threads) - [How to check status of your threads](/langgraphjs/cloud/how-tos/check_thread_status) ### Runs LangGraph Cloud supports multiple types of runs besides streaming runs. - [How to run an agent in the background](/langgraphjs/cloud/how-tos/background_run) - [How to run multiple agents in the same thread](/langgraphjs/cloud/how-tos/same-thread) - [How to create cron jobs](/langgraphjs/cloud/how-tos/cron_jobs) - [How to create stateless runs](/langgraphjs/cloud/how-tos/stateless_runs) ### Streaming Streaming the results of your LLM application is vital for ensuring a good user experience, especially when your graph may call multiple models and take a long time to fully complete a run. Read about how to stream values from your graph in these how to guides: - [How to stream values](/langgraphjs/cloud/how-tos/stream_values) - [How to stream updates](/langgraphjs/cloud/how-tos/stream_updates) - [How to stream messages](/langgraphjs/cloud/how-tos/stream_messages) - [How to stream events](/langgraphjs/cloud/how-tos/stream_events) - [How to stream in debug mode](/langgraphjs/cloud/how-tos/stream_debug) - [How to stream multiple modes](/langgraphjs/cloud/how-tos/stream_multiple) ### Frontend & Generative UI With LangGraph Platform you can integrate LangGraph agents into your React applications and colocate UI components with your agent code. - [How to integrate LangGraph into your React application](/langgraphjs/cloud/how-tos/use_stream_react) - [How to implement Generative User Interfaces with LangGraph](/langgraphjs/cloud/how-tos/generative_ui_react) ### Human-in-the-loop When creating complex graphs, leaving every decision up to the LLM can be dangerous, especially when the decisions involve invoking certain tools or accessing specific documents. To remedy this, LangGraph allows you to insert human-in-the-loop behavior to ensure your graph does not have undesired outcomes. Read more about the different ways you can add human-in-the-loop capabilities to your LangGraph Cloud projects in these how-to guides: - [How to add a breakpoint](/langgraphjs/cloud/how-tos/human_in_the_loop_breakpoint) - [How to wait for user input](/langgraphjs/cloud/how-tos/human_in_the_loop_user_input) - [How to edit graph state](/langgraphjs/cloud/how-tos/human_in_the_loop_edit_state) - [How to replay and branch from prior states](/langgraphjs/cloud/how-tos/human_in_the_loop_time_travel) - [How to review tool calls](/langgraphjs/cloud/how-tos/human_in_the_loop_review_tool_calls) ### Double-texting Graph execution can take a while, and sometimes users may change their mind about the input they wanted to send before their original input has finished running. For example, a user might notice a typo in their original request and will edit the prompt and resend it. Deciding what to do in these cases is important for ensuring a smooth user experience and preventing your graphs from behaving in unexpected ways. The following how-to guides provide information on the various options LangGraph Cloud gives you for dealing with double-texting: - [How to use the interrupt option](/langgraphjs/cloud/how-tos/interrupt_concurrent) - [How to use the rollback option](/langgraphjs/cloud/how-tos/rollback_concurrent) - [How to use the reject option](/langgraphjs/cloud/how-tos/reject_concurrent) - [How to use the enqueue option](/langgraphjs/cloud/how-tos/enqueue_concurrent) ### Webhooks - [How to integrate webhooks](/langgraphjs/cloud/how-tos/webhooks) ### Cron Jobs - [How to create cron jobs](/langgraphjs/cloud/how-tos/cron_jobs) ### LangGraph Studio LangGraph Studio is a built-in UI for visualizing, testing, and debugging your agents. - [How to connect to a LangGraph Cloud deployment](/langgraphjs/cloud/how-tos/test_deployment) - [How to connect to a local deployment](/langgraphjs/cloud/how-tos/test_local_deployment) - [How to test your graph in LangGraph Studio](/langgraphjs/cloud/how-tos/invoke_studio) - [How to interact with threads in LangGraph Studio](/langgraphjs/cloud/how-tos/threads_studio) ## Troubleshooting These are the guides for resolving common errors you may find while building with LangGraph. Errors referenced below will have an `lc_error_code` property corresponding to one of the below codes when they are thrown in code. - [GRAPH_RECURSION_LIMIT](../troubleshooting/errors/GRAPH_RECURSION_LIMIT.ipynb) - [INVALID_CONCURRENT_GRAPH_UPDATE](../troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.ipynb) - [INVALID_GRAPH_NODE_RETURN_VALUE](../troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.ipynb) - [MULTIPLE_SUBGRAPHS](../troubleshooting/errors/MULTIPLE_SUBGRAPHS.ipynb) - [UNREACHABLE_NODE](../troubleshooting/errors/UNREACHABLE_NODE.ipynb) --- how-tos/deploy-self-hosted.md --- # How to do a Self-hosted deployment of LangGraph !!! info "Prerequisites" - [Application Structure](../concepts/application_structure.md) - [Deployment Options](../concepts/deployment_options.md) This how-to guide will walk you through how to create a docker image from an existing LangGraph application, so you can deploy it on your own infrastructure. ## How it works With the self-hosted deployment option, you are responsible for managing the infrastructure, including setting up and maintaining necessary databases, Redis instances, and other services. You will need to do the following: 1. Deploy Redis and Postgres instances on your own infrastructure. 2. Build a docker image with the [LangGraph Sever](../concepts/langgraph_server.md) using the [LangGraph CLI](../concepts/langgraph_cli.md). 3. Deploy a web server that will run the docker image and pass in the necessary environment variables. ## Environment Variables You will eventually need to pass in the following environment variables to the LangGraph Deploy server: - `REDIS_URI`: Connection details to a Redis instance. Redis will be used as a pub-sub broker to enable streaming real time output from background runs. - `DATABASE_URI`: Postgres connection details. Postgres will be used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics. - `LANGSMITH_API_KEY`: (If using [Self-Hosted Lite]) LangSmith API key. This will be used to authenticate ONCE at server start up. - `LANGGRAPH_CLOUD_LICENSE_KEY`: (If using Self-Hosted Enterprise) LangGraph Platform license key. This will be used to authenticate ONCE at server start up. ## Build the Docker Image Please read the [Application Structure](../concepts/application_structure.md) guide to understand how to structure your LangGraph application. If the application is structured correctly, you can build a docker image with the LangGraph Deploy server. To build the docker image, you first need to install the CLI: ```shell pip install -U langgraph-cli ``` You can then use: ``` langgraph build -t my-image ``` This will build a docker image with the LangGraph Deploy server. The `-t my-image` is used to tag the image with a name. When running this server, you need to pass three environment variables: ## Running the application locally ### Using Docker ```shell docker run \ -e REDIS_URI="foo" \ -e DATABASE_URI="bar" \ -e LANGSMITH_API_KEY="baz" \ my-image ``` If you want to run this quickly without setting up a separate Redis and Postgres instance, you can use this docker compose file. !!! note * You need to replace `my-image` with the name of the image you built in the previous step (from `langgraph build`). and you should provide appropriate values for `REDIS_URI`, `DATABASE_URI`, and `LANGSMITH_API_KEY`. * If your application requires additional environment variables, you can pass them in a similar way. * If using Self-Hosted Enterprise, you must provide `LANGGRAPH_CLOUD_LICENSE_KEY` as an additional environment variable. ### Using Docker Compose ```yml volumes: langgraph-data: driver: local services: langgraph-redis: image: redis:6 healthcheck: test: redis-cli ping interval: 5s timeout: 1s retries: 5 langgraph-postgres: image: postgres:16 ports: - "5433:5432" environment: POSTGRES_DB: postgres POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres volumes: - langgraph-data:/var/lib/postgresql/data healthcheck: test: pg_isready -U postgres start_period: 10s timeout: 1s retries: 5 interval: 5s langgraph-api: image: ${IMAGE_NAME} ports: - "8123:8000" depends_on: langgraph-redis: condition: service_healthy langgraph-postgres: condition: service_healthy env_file: - .env environment: REDIS_URI: redis://langgraph-redis:6379 LANGSMITH_API_KEY: ${LANGSMITH_API_KEY} POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable ``` You can then run `docker compose up` with this Docker compose file in the same folder. This will spin up LangGraph Deploy on port `8123` (if you want to change this, you can change this by changing the ports in the `langgraph-api` volume). You can test that the application is up by checking: ```shell curl --request GET --url 0.0.0.0:8123/ok ``` Assuming everything is running correctly, you should see a response like: ```shell {"ok":true} ``` --- how-tos/use-remote-graph.md --- # How to interact with the deployment using RemoteGraph !!! info "Prerequisites" - [LangGraph Platform](../concepts/langgraph_platform.md) - [LangGraph Server](../concepts/langgraph_server.md) `RemoteGraph` is an interface that allows you to interact with your LangGraph Platform deployment as if it were a regular, locally-defined LangGraph graph (e.g. a `CompiledGraph`). This guide shows you how you can initialize a `RemoteGraph` and interact with it. ## Initializing the graph When initializing a `RemoteGraph`, you must always specify: - `name`: the name of the graph you want to interact with. This is the same graph name you use in `langgraph.json` configuration file for your deployment. - `api_key`: a valid LangSmith API key. Can be set as an environment variable (`LANGSMITH_API_KEY`) or passed directly via the `api_key` argument. The API key could also be provided via the `client` / `sync_client` arguments, if `LangGraphClient` / `SyncLangGraphClient` were initialized with `api_key` argument. Additionally, you have to provide one of the following: - `url`: URL of the deployment you want to interact with. If you pass `url` argument, both sync and async clients will be created using the provided URL, headers (if provided) and default configuration values (e.g. timeout, etc). - `client`: a `LangGraphClient` instance for interacting with the deployment asynchronously (e.g. using `.astream()`, `.ainvoke()`, `.aget_state()`, `.aupdate_state()`, etc.) - `sync_client`: a `SyncLangGraphClient` instance for interacting with the deployment synchronously (e.g. using `.stream()`, `.invoke()`, `.get_state()`, `.update_state()`, etc.) !!! Note If you pass both `client` or `sync_client` as well as `url` argument, they will take precedence over the `url` argument. If none of the `client` / `sync_client` / `url` arguments are provided, `RemoteGraph` will raise a `ValueError` at runtime. ### Using URL === "Python" ```python from langgraph.pregel.remote import RemoteGraph url = graph_name = "agent" remote_graph = RemoteGraph(graph_name, url=url) ``` === "JavaScript" ```ts import { RemoteGraph } from "@langchain/langgraph/remote"; const url = ``; const graphName = "agent"; const remoteGraph = new RemoteGraph({ graphId: graphName, url }); ``` ### Using clients === "Python" ```python from langgraph_sdk import get_client, get_sync_client from langgraph.pregel.remote import RemoteGraph url = graph_name = "agent" client = get_client(url=url) sync_client = get_sync_client(url=url) remote_graph = RemoteGraph(graph_name, client=client, sync_client=sync_client) ``` === "JavaScript" ```ts import { Client } from "@langchain/langgraph-sdk"; import { RemoteGraph } from "@langchain/langgraph/remote"; const client = new Client({ apiUrl: `` }); const graphName = "agent"; const remoteGraph = new RemoteGraph({ graphId: graphName, client }); ``` ## Invoking the graph Since `RemoteGraph` is a `Runnable` that implements the same methods as `CompiledGraph`, you can interact with it the same way you normally would with a compiled graph, i.e. by calling `.invoke()`, `.stream()`, `.get_state()`, `.update_state()`, etc (as well as their async counterparts). ### Asynchronously !!! Note To use the graph asynchronously, you must provide either the `url` or `client` when initializing the `RemoteGraph`. === "Python" ```python # invoke the graph result = await remote_graph.ainvoke({ "messages": [{"role": "user", "content": "what's the weather in sf"}] }) # stream outputs from the graph async for chunk in remote_graph.astream({ "messages": [("user", "what's the weather in la?")] }): print(chunk) ``` === "JavaScript" ```ts // invoke the graph const result = await remoteGraph.invoke({ messages: [{role: "user", content: "what's the weather in sf"}] }) // stream outputs from the graph for await (const chunk of await remoteGraph.stream({ messages: [{role: "user", content: "what's the weather in la"}] })): console.log(chunk) ``` ### Synchronously !!! Note To use the graph synchronously, you must provide either the `url` or `sync_client` when initializing the `RemoteGraph`. === "Python" ```python # invoke the graph result = remote_graph.invoke({ "messages": [{"role": "user", "content": "what's the weather in sf"}] }) # stream outputs from the graph for chunk in remote_graph.stream({ "messages": [("user", "what's the weather in la?")] }): print(chunk) ``` ## Thread-level persistence By default, the graph runs (i.e. `.invoke()` or `.stream()` invocations) are stateless - the checkpoints and the final state of the graph are not persisted. If you would like to persist the outputs of the graph run (for example, to enable human-in-the-loop features), you can create a thread and provide the thread ID via the `config` argument, same as you would with a regular compiled graph: === "Python" ```python from langgraph_sdk import get_sync_client url = graph_name = "agent" sync_client = get_sync_client(url=url) remote_graph = RemoteGraph(graph_name, url=url) # create a thread (or use an existing thread instead) thread = sync_client.threads.create() # invoke the graph with the thread config config = {"configurable": {"thread_id": thread["thread_id"]}} result = remote_graph.invoke({ "messages": [{"role": "user", "content": "what's the weather in sf"}], config=config }) # verify that the state was persisted to the thread thread_state = remote_graph.get_state(config) print(thread_state) ``` === "JavaScript" ```ts import { Client } from "@langchain/langgraph-sdk"; import { RemoteGraph } from "@langchain/langgraph/remote"; const url = ``; const graphName = "agent"; const client = new Client({ apiUrl: url }); const remoteGraph = new RemoteGraph({ graphId: graphName, url }); // create a thread (or use an existing thread instead) const thread = await client.threads.create(); // invoke the graph with the thread config const config = { configurable: { thread_id: thread.thread_id }}; const result = await remoteGraph.invoke({ messages: [{ role: "user", content: "what's the weather in sf" }], config }); // verify that the state was persisted to the thread const threadState = await remoteGraph.getState(config); console.log(threadState); ``` ## Using as a subgraph !!! Note If you need to use a `checkpointer` with a graph that has a `RemoteGraph` subgraph node, make sure to use UUIDs as thread IDs. Since the `RemoteGraph` behaves the same way as a regular `CompiledGraph`, it can be also used as a subgraph in another graph. For example: === "Python" ```python from langgraph_sdk import get_sync_client from langgraph.graph import StateGraph, MessagesState, START from typing import TypedDict url = graph_name = "agent" remote_graph = RemoteGraph(graph_name, url=url) # define parent graph builder = StateGraph(MessagesState) # add remote graph directly as a node builder.add_node("child", remote_graph) builder.add_edge(START, "child") graph = builder.compile() # invoke the parent graph result = graph.invoke({ "messages": [{"role": "user", "content": "what's the weather in sf"}] }) print(result) ``` === "JavaScript" ```ts import { MessagesAnnotation, StateGraph, START } from "@langchain/langgraph"; import { RemoteGraph } from "@langchain/langgraph/remote"; const url = ``; const graphName = "agent"; const remoteGraph = new RemoteGraph({ graphId: graphName, url }); // define parent graph and add remote graph directly as a node const graph = new StateGraph(MessagesAnnotation) .addNode("child", remoteGraph) .addEdge("START", "child") .compile() // invoke the parent graph const result = await graph.invoke({ messages: [{ role: "user", content: "what's the weather in sf" }] }); console.log(result); ``` --- tutorials/index.md --- --- title: Tutorials --- # Tutorials Welcome to the LangGraph.js Tutorials! These notebooks introduce LangGraph through building various language agents and applications. ## Quick Start Learn the basics of LangGraph through a comprehensive quick start in which you will build an agent from scratch. - [Quick Start](quickstart.ipynb) - [Common Workflows](workflows/index.md): Overview of the most common workflows using LLMs implemented with LangGraph. - [LangGraph Cloud Quick Start](/langgraphjs/cloud/quick_start/): In this tutorial, you will build and deploy an agent to LangGraph Cloud. ## Use cases Learn from example implementations of graphs designed for specific scenarios and that implement common design patterns. #### Chatbots - [Customer support with a small model](chatbots/customer_support_small_model.ipynb) #### RAG - [Agentic RAG](rag/langgraph_agentic_rag.ipynb) - [Corrective RAG](rag/langgraph_crag.ipynb) - [Self-RAG](rag/langgraph_self_rag.ipynb) #### Multi-Agent Systems - [Collaboration](multi_agent/multi_agent_collaboration.ipynb): Enabling two agents to collaborate on a task - [Supervision](multi_agent/agent_supervisor.ipynb): Using an LLM to orchestrate and delegate to individual agents - [Hierarchical Teams](multi_agent/hierarchical_agent_teams.ipynb): Orchestrating nested teams of agents to solve problems #### Planning Agents - [Plan-and-Execute](plan-and-execute/plan-and-execute.ipynb): Implementing a basic planning and execution agent #### Reflection & Critique - [Basic Reflection](reflection/reflection.ipynb): Prompting the agent to reflect on and revise its outputs - [Rewoo](rewoo/rewoo.ipynb): Reducing re-planning by saving observations as variables ### Evaluation - [Agent-based](chatbot-simulation-evaluation/agent-simulation-evaluation.ipynb): Evaluate chatbots via simulated user interactions --- tutorials/deployment.md --- # Deployment Get started deploying your LangGraph applications locally or on the cloud with [LangGraph Platform](../concepts/langgraph_platform.md). ## Get Started 🚀 {#quick-start} - [LangGraph Server Quickstart](../tutorials/langgraph-platform/local-server.md): Launch a LangGraph server locally and interact with it using REST API and LangGraph Studio Web UI. - [LangGraph Template Quickstart](../concepts/template_applications.md): Start building with LangGraph Platform using a template application. - [Deploy with LangGraph Cloud Quickstart](../cloud/quick_start.md): Deploy a LangGraph app using LangGraph Cloud. ## Deployment Options - [Self-Hosted Lite](../concepts/self_hosted.md): A free (up to 1 million nodes executed), limited version of LangGraph Platform that you can run locally or in a self-hosted manner - [Cloud SaaS](../concepts/langgraph_cloud.md): Hosted as part of LangSmith. - [Bring Your Own Cloud](../concepts/bring_your_own_cloud.md): We manage the infrastructure, so you don't have to, but the infrastructure all runs within your cloud. - [Self-Hosted Enterprise](../concepts/self_hosted.md): Completely managed by you. --- concepts/langgraph_cli.md --- # LangGraph.js CLI !!! info "Prerequisites" - [LangGraph Platform](./langgraph_platform.md) - [LangGraph Server](./langgraph_server.md) The LangGraph.js CLI is a multi-platform command-line tool for building and running the [LangGraph.js API server](./langgraph_server.md) locally. This offers an alternative to the [LangGraph Studio desktop app](./langgraph_studio.md) for developing and testing agents across all major operating systems (Linux, Windows, MacOS). The resulting server includes all API endpoints for your graph's runs, threads, assistants, etc. as well as the other services required to run your agent, including a managed database for checkpointing and storage. ## Installation The LangGraph.js CLI can be installed from the NPM registry: === "npx" ```bash npx @langchain/langgraph-cli ``` === "npm" ```bash npm install @langchain/langgraph-cli ``` === "yarn" ```bash yarn add @langchain/langgraph-cli ``` === "pnpm" ```bash pnpm add @langchain/langgraph-cli ``` === "bun" ```bash bun add @langchain/langgraph-cli ``` ## Commands The CLI provides the following core functionality: ### `build` The `langgraph build` command builds a Docker image for the [LangGraph API server](./langgraph_server.md) that can be directly deployed. ### `dev` The `langgraph dev` command starts a lightweight development server that requires no Docker installation. This server is ideal for rapid development and testing, with features like: - Hot reloading: Changes to your code are automatically detected and reloaded - In-memory state with local persistence: Server state is stored in memory for speed but persisted locally between restarts **Note**: This command is intended for local development and testing only. It is not recommended for production use. ### `up` The `langgraph up` command starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires the docker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use. The server includes all API endpoints for your graph's runs, threads, assistants, etc. as well as the other services required to run your agent, including a managed database for checkpointing and storage. ### `dockerfile` The `langgraph dockerfile` command generates a [Dockerfile](https://docs.docker.com/reference/dockerfile/) that can be used to build images for and deploy instances of the [LangGraph API server](./langgraph_server.md). This is useful if you want to further customize the dockerfile or deploy in a more custom way. ??? note "Updating your langgraph.json file" The `langgraph dockerfile` command translates all the configuration in your `langgraph.json` file into Dockerfile commands. When using this command, you will have to re-run it whenever you update your `langgraph.json` file. Otherwise, your changes will not be reflected when you build or run the dockerfile. ## Related - [LangGraph CLI API Reference](/langgraphjs/cloud/reference/cli/) --- concepts/langgraph_server.md --- # LangGraph Server !!! info "Prerequisites" - [LangGraph Platform](./langgraph_platform.md) - [LangGraph Glossary](low_level.md) ## Overview 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. ## Key Features The LangGraph Platform incorporates best practices for agent deployment, so you can focus on building your agent logic. * **Streaming endpoints**: Endpoints that expose [multiple different streaming modes](streaming.md). We've made these work even for long-running agents that may go minutes between consecutive stream events. * **Background runs**: The LangGraph Server supports launching assistants in the background with endpoints for polling the status of the assistant's run and webhooks to monitor run status effectively. - **Support for long runs**: Our blocking endpoints for running assistants send regular heartbeat signals, preventing unexpected connection closures when handling requests that take a long time to complete. * **Task queue**: We've added a task queue to make sure we don't drop any requests if they arrive in a bursty nature. * **Horizontally scalable infrastructure**: LangGraph Server is designed to be horizontally scalable, allowing you to scale up and down your usage as needed. * **Double texting support**: Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. We call this ["double texting"](double_texting.md) and have added four different ways to handle this. * **Optimized checkpointer**: LangGraph Platform comes with a built-in [checkpointer](./persistence.md#checkpoints) optimized for LangGraph applications. * **Human-in-the-loop endpoints**: We've exposed all endpoints needed to support [human-in-the-loop](human_in_the_loop.md) features. * **Memory**: In addition to thread-level persistence (covered above by [checkpointers]l(./persistence.md#checkpoints)), LangGraph Platform also comes with a built-in [memory store](persistence.md#memory-store). * **Cron jobs**: Built-in support for scheduling tasks, enabling you to automate regular actions like data clean-up or batch processing within your applications. * **Webhooks**: Allows your application to send real-time notifications and data updates to external systems, making it easy to integrate with third-party services and trigger actions based on specific events. * **Monitoring**: LangGraph Server integrates seamlessly with the [LangSmith](https://docs.smith.langchain.com/) monitoring platform, providing real-time insights into your application's performance and health. ## What are you deploying? When you deploy a LangGraph Server, you are deploying one or more [graphs](#graphs), a database for [persistence](persistence.md), and a task queue. ### Graphs When you deploy a graph with LangGraph Server, you are deploying a "blueprint" for an [Assistant](assistants.md). An [Assistant](assistants.md) is a graph paired with specific configuration settings. You can create multiple assistants per graph, each with unique settings to accommodate different use cases that can be served by the same graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default configuration settings. You can interact with assistants through the [LangGraph Server API](#langgraph-server-api). !!! note We often think of a graph as implementing an [agent](agentic_concepts.md), but a graph does not necessarily need to implement an agent. For example, a graph could implement a simple chatbot that only supports back-and-forth conversation, without the ability to influence any application control flow. In reality, as applications get more complex, a graph will often implement a more complex flow that may use [multiple agents](./multi_agent.md) working in tandem. ### Persistence and Task Queue The LangGraph Server leverages a database for [persistence](persistence.md) and a task queue. Currently, only [Postgres](https://www.postgresql.org/) is supported as a database for LangGraph Server and [Redis](https://redis.io/) as the task queue. If you're deploying using [LangGraph Cloud](./langgraph_cloud.md), these components are managed for you. If you're deploying LangGraph Server on your own infrastructure, you'll need to set up and manage these components yourself. Please review the [deployment options](./deployment_options.md) guide for more information on how these components are set up and managed. ## Application Structure To deploy a LangGraph Server application, you need to specify the graph(s) you want to deploy, as well as any relevant configuration settings, such as dependencies and environment variables. Read the [application structure](./application_structure.md) guide to learn how to structure your LangGraph application for deployment. ## LangGraph Server API The LangGraph Server API allows you to create and manage [assistants](assistants.md), [threads](#threads), [runs](#runs), [cron jobs](#cron-jobs), and more. The [LangGraph Cloud API Reference](/langgraphjs/cloud/reference/api/api_ref.html) provides detailed information on the API endpoints and data models. ### Assistants An [Assistant](assistants.md) refers to a [graph](#graphs) plus specific [configuration](low_level.md#configuration) settings for that graph. You can think of an assistant as a saved configuration of an [agent](agentic_concepts.md). When building agents, it is fairly 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 agents. Assistants offer an easy way to make and save these types of changes to agent configuration. ### Threads A thread contains the accumulated state of a sequence of [runs](#runs). If a run is executed on a thread, then the [state](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. The state of a thread at a particular point in time is called a [checkpoint](persistence.md#checkpoints). Checkpoints can be used to restore the state of a thread at a later time. For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](low_level.md#persistence). The LangGraph Cloud API provides several endpoints for creating and managing threads and thread state. See the [API reference](/langgraphjs/cloud/reference/api/api_ref.html#tag/threadscreate) for more details. ### Runs A run is an invocation of an [assistant](#assistants). 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). The LangGraph Cloud API provides several endpoints for creating and managing runs. See the [API reference](/langgraphjs/cloud/reference/api/api_ref.html#tag/runsmanage) for more details. ### Store Store is an API for managing persistent [key-value store](./persistence.md#memory-store) that is available from any [thread](#threads). Stores are useful for implementing [memory](./memory.md) in your LangGraph application. ### Cron Jobs There are many situations in which it is useful to run an assistant on a schedule. For example, say that you're building an assistant that runs daily and sends an email summary of the day's news. You could use a cron job to run the assistant every day at 8:00 PM. LangGraph Cloud supports cron jobs, which run on a user-defined schedule. The user specifies a schedule, an assistant, and some input. After that, on the specified schedule, the server will: - Create a new thread with the specified assistant - Send the specified input to that thread Note that this sends the same input to the thread every time. See the [how-to guide](/langgraphjs/cloud/how-tos/cron_jobs.md) for creating cron jobs. The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the [API reference](/langgraphjs/cloud/reference/api/api_ref.html#tag/crons-enterprise-only) for more details. ### Webhooks Webhooks enable event-driven communication from your LangGraph Cloud application to external services. For example, you may want to issue an update to a separate service once an API call to LangGraph Cloud has finished running. Many LangGraph Cloud endpoints accept a `webhook` parameter. If this parameter is specified by a an endpoint that can accept POST requests, LangGraph Cloud will send a request at the completion of a run. See the corresponding [how-to guide](/langgraphjs/cloud/how-tos/webhooks.md) for more detail. ## Related * LangGraph [Application Structure](./application_structure.md) guide explains how to structure your LangGraph application for deployment. * [How-to guides for the LangGraph Platform](../how-tos/index.md). * The [LangGraph Cloud API Reference](/langgraphjs/cloud/reference/api/api_ref.html) provides detailed information on the API endpoints and data models. --- concepts/index.md --- --- title: Concepts description: Conceptual Guide for LangGraph.js --- # Conceptual Guide This guide provides explanations of the key concepts behind the LangGraph framework and AI applications more broadly. We recommend that you go through at least the [Quick Start](../tutorials/quickstart.ipynb) before diving into the conceptual guide. This will provide practical context that will make it easier to understand the concepts discussed here. The conceptual guide does not cover step-by-step instructions or specific implementation examples — those are found in the [Tutorials](../tutorials/index.md) and [How-to guides](../how-tos/index.md). For detailed reference material, please see the [API reference](https://langchain-ai.github.io/langgraphjs/reference/). ## LangGraph ### High Level - [Why LangGraph?](high_level.md): A high-level overview of LangGraph and its goals. ### Concepts - [LangGraph Glossary](low_level.md): LangGraph workflows are designed as graphs, with nodes representing different components and edges representing the flow of information between them. This guide provides an overview of the key concepts associated with LangGraph graph primitives. - [Common Agentic Patterns](agentic_concepts.md): An agent uses an LLM to pick its own control flow to solve more complex problems! Agents are a key building block in many LLM applications. This guide explains the different types of agent architectures and how they can be used to control the flow of an application. - [Multi-Agent Systems](multi_agent.md): Complex LLM applications can often be broken down into multiple agents, each responsible for a different part of the application. This guide explains common patterns for building multi-agent systems. - [Breakpoints](breakpoints.md): Breakpoints allow pausing the execution of a graph at specific points. Breakpoints allow stepping through graph execution for debugging purposes. - [Human-in-the-Loop](human_in_the_loop.md): Explains different ways of integrating human feedback into a LangGraph application. - [Time Travel](time-travel.md): Time travel allows you to replay past actions in your LangGraph application to explore alternative paths and debug issues. - [Persistence](persistence.md): LangGraph has a built-in persistence layer, implemented through checkpointers. This persistence layer helps to support powerful capabilities like human-in-the-loop, memory, time travel, and fault-tolerance. - [Memory](memory.md): Memory in AI applications refers to the ability to process, store, and effectively recall information from past interactions. With memory, your agents can learn from feedback and adapt to users' preferences. - [Streaming](streaming.md): Streaming is crucial for enhancing the responsiveness of applications built on LLMs. By displaying output progressively, even before a complete response is ready, streaming significantly improves user experience (UX), particularly when dealing with the latency of LLMs. - [Functional API](functional_api.md): An alternative to [Graph API (StateGraph)](low_level.md#stategraph) for development in LangGraph. - [FAQ](faq.md): Frequently asked questions about LangGraph. ## LangGraph Platform LangGraph Platform is a commercial solution for deploying agentic applications in production, built on the open-source LangGraph framework. The LangGraph Platform offers a few different deployment options described in the [deployment options guide](./deployment_options.md). !!! tip * LangGraph is an MIT-licensed open-source library, which we are committed to maintaining and growing for the community. * You can always deploy LangGraph applications on your own infrastructure using the open-source LangGraph project without using LangGraph Platform. ### High Level - [Why LangGraph Platform?](./langgraph_platform.md): The LangGraph platform is an opinionated way to deploy and manage LangGraph applications. This guide provides an overview of the key features and concepts behind LangGraph Platform. - [Deployment Options](./deployment_options.md): LangGraph Platform offers four deployment options: [Self-Hosted Lite](./self_hosted.md#self-hosted-lite), [Self-Hosted Enterprise](./self_hosted.md#self-hosted-enterprise), [bring your own cloud (BYOC)](./bring_your_own_cloud.md), and [Cloud SaaS](./langgraph_cloud.md). This guide explains the differences between these options, and which Plans they are available on. - [Plans](./plans.md): LangGraph Platforms offer three different plans: Developer, Plus, Enterprise. This guide explains the differences between these options, what deployment options are available for each, and how to sign up for each one. - [Template Applications](./template_applications.md): Reference applications designed to help you get started quickly when building with LangGraph. ### Components The LangGraph Platform comprises several components that work together to support the deployment and management of LangGraph applications: - [LangGraph Server](./langgraph_server.md): The LangGraph Server is designed to support a wide range of agentic application use cases, from background processing to real-time interactions. - [LangGraph Studio](./langgraph_studio.md): LangGraph Studio is a specialized IDE that can connect to a LangGraph Server to enable visualization, interaction, and debugging of the application locally. - [LangGraph CLI](./langgraph_cli.md): LangGraph CLI is a command-line interface that helps to interact with a local LangGraph - [Python/JS SDK](./sdk.md): The Python/JS SDK provides a programmatic way to interact with deployed LangGraph Applications. - [Remote Graph](../how-tos/use-remote-graph.md): A RemoteGraph allows you to interact with any deployed LangGraph application as though it were running locally. ### LangGraph Server - [Application Structure](./application_structure.md): A LangGraph application consists of one or more graphs, a LangGraph API Configuration file (`langgraph.json`), a file that specifies dependencies, and environment variables. - [Assistants](./assistants.md): Assistants are a way to save and manage different configurations of your LangGraph applications. - [Web-hooks](./langgraph_server.md#webhooks): Webhooks allow your running LangGraph application to send data to external services on specific events. - [Cron Jobs](./langgraph_server.md#cron-jobs): Cron jobs are a way to schedule tasks to run at specific times in your LangGraph application. - [Double Texting](./double_texting.md): Double texting is a common issue in LLM applications where users may send multiple messages before the graph has finished running. This guide explains how to handle double texting with LangGraph Deploy. ### Deployment Options - [Self-Hosted Lite](./self_hosted.md): A free (up to 1 million nodes executed), limited version of LangGraph Platform that you can run locally or in a self-hosted manner - [Cloud SaaS](./langgraph_cloud.md): Hosted as part of LangSmith. - [Bring Your Own Cloud](./bring_your_own_cloud.md): We manage the infrastructure, so you don't have to, but the infrastructure all runs within your cloud. - [Self-Hosted Enterprise](./self_hosted.md): Completely managed by you. --- concepts/langgraph_cloud.md --- # Cloud SaaS !!! info "Prerequisites" - [LangGraph Platform](./langgraph_platform.md) - [LangGraph Server](./langgraph_server.md) ## Overview LangGraph's Cloud SaaS is a managed service that provides a scalable and secure environment for deploying LangGraph APIs. It is designed to work seamlessly with your LangGraph API regardless of how it is defined, what tools it uses, or any dependencies. Cloud SaaS provides a simple way to deploy and manage your LangGraph API in the cloud. ## Deployment A **deployment** is an instance of a LangGraph API. A single deployment can have many [revisions](#revision). When a deployment is created, all the necessary infrastructure (e.g. database, containers, secrets store) are automatically provisioned. See the [architecture diagram](#architecture) below for more details. See the [how-to guide](/langgraphjs/cloud/deployment/cloud.md#create-new-deployment) for creating a new deployment. ## Revision A revision is an iteration of a [deployment](#deployment). When a new deployment is created, an initial revision is automatically created. To deploy new code changes or update environment variable configurations for a deployment, a new revision must be created. When a revision is created, a new container image is built automatically. See the [how-to guide](/langgraphjs/cloud/deployment/cloud.md#create-new-revision) for creating a new revision. ## Asynchronous Deployment Infrastructure for [deployments](#deployment) and [revisions](#revision) are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes. ## Architecture !!! warning "Subject to Change" The Cloud SaaS deployment architecture may change in the future. A high-level diagram of a Cloud SaaS deployment. ![diagram](img/langgraph_cloud_architecture.png) ## Related - [Deployment Options](./deployment_options.md) --- concepts/double_texting.md --- # Double Texting !!! info "Prerequisites" - [LangGraph Server](./langgraph_server.md) Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. More generally, users may invoke the graph a second time before the first run has finished. We call this "double texting". Currently, LangGraph only addresses this as part of [LangGraph Platform](langgraph_platform.md), not in the open source. The reason for this is that in order to handle this we need to know how the graph is deployed, and since LangGraph Platform deals with deployment the logic needs to live there. If you do not want to use LangGraph Platform, we describe the options we have implemented in detail below. ![](img/double_texting.png) ## Reject This is the simplest option, this just rejects any follow up runs and does not allow double texting. See the [how-to guide](/langgraphjs/cloud/how-tos/reject_concurrent) for configuring the reject double text option. ## Enqueue This is a relatively simple option which continues the first run until it completes the whole run, then sends the new input as a separate run. See the [how-to guide](/langgraphjs/cloud/how-tos/enqueue_concurrent) for configuring the enqueue double text option. ## Interrupt This option interrupts the current execution but saves all the work done up until that point. It then inserts the user input and continues from there. If you enable this option, your graph should be able to handle weird edge cases that may arise. For example, you could have called a tool but not yet gotten back a result from running that tool. You may need to remove that tool call in order to not have a dangling tool call. See the [how-to guide](/langgraphjs/cloud/how-tos/interrupt_concurrent) for configuring the interrupt double text option. ## Rollback This option rolls back all work done up until that point. It then sends the user input in, basically as if it just followed the original run input. This may create some weird states - for example, you may have two `User` messages in a row, with no `Assistant` message in between them. You will need to make sure the LLM you are calling can handle that, or combine those into a single `User` message. See the [how-to guide](/langgraphjs/cloud/how-tos/rollback_concurrent) for configuring the rollback double text option. --- concepts/high_level.md --- # Why LangGraph? LLMs are extremely powerful, particularly when connected to other systems such as a retriever or APIs. This is why many LLM applications use a control flow of steps before and / or after LLM calls. As an example [RAG](https://github.com/langchain-ai/rag-from-scratch) performs retrieval of relevant documents to a question, and passes those documents to an LLM in order to ground the response. Often a control flow of steps before and / or after an LLM is called a "chain." Chains are a popular paradigm for programming with LLMs and offer a high degree of reliability; the same set of steps runs with each chain invocation. However, we often want LLM systems that can pick their own control flow! This is one definition of an [agent](https://blog.langchain.dev/what-is-an-agent/): an agent is a system that uses an LLM to decide the control flow of an application. Unlike a chain, an agent given an LLM some degree of control over the sequence of steps in the application. Examples of using an LLM to decide the control of an application: - Using an LLM to route between two potential paths - Using an LLM to decide which of many tools to call - Using an LLM to decide whether the generated answer is sufficient or more work is need There are many different types of [agent architectures](https://blog.langchain.dev/what-is-a-cognitive-architecture/) to consider, which given an LLM varying levels of control. On one extreme, a router allows an LLM to select a single step from a specified set of options and, on the other extreme, a fully autonomous long-running agent may have complete freedom to select any sequence of steps that it wants for a given problem. ![Agent Types](img/agent_types.png) Several concepts are utilized in many agent architectures: - [Tool calling](agentic_concepts.md#tool-calling): this is often how LLMs make decisions - Action taking: often times, the LLMs' outputs are used as the input to an action - [Memory](agentic_concepts.md#memory): reliable systems need to have knowledge of things that occurred - [Planning](agentic_concepts.md#planning): planning steps (either explicit or implicit) are useful for ensuring that the LLM, when making decisions, makes them in the highest fidelity way. ## Challenges In practice, there is often a trade-off between control and reliability. As we give LLMs more control, the application often become less reliable. This can be due to factors such as LLM non-determinism and / or errors in selecting tools (or steps) that the agent uses (takes). ![Agent Challenge](img/challenge.png) ## Core Principles The motivation of LangGraph is to help bend the curve, preserving higher reliability as we give the agent more control over the application. We'll outline a few specific pillars of LangGraph that make it well suited for building reliable agents. ![Langgraph](img/langgraph.png) **Controllability** LangGraph gives the developer a high degree of [control](/langgraphjs/how-tos#controllability) by expressing the flow of the application as a set of nodes and edges. All nodes can access and modify a common state (memory). The control flow of the application can set using edges that connect nodes, either deterministically or via conditional logic. **Persistence** LangGraph gives the developer many options for [persisting](/langgraphjs/how-tos#persistence) graph state using short-term or long-term (e.g., via a database) memory. **Human-in-the-Loop** The persistence layer enables several different [human-in-the-loop](/langgraphjs/how-tos#human-in-the-loop) interaction patterns with agents; for example, it's possible to pause an agent, review its state, edit it state, and approve a follow-up step. **Streaming** LangGraph comes with first class support for [streaming](/langgraphjs/how-tos#streaming), which can expose state to the user (or developer) over the course of agent execution. LangGraph supports streaming of both events ([like a tool call being taken](/langgraphjs/how-tos/stream-updates.ipynb)) as well as of [tokens that an LLM may emit](/langgraphjs/how-tos/streaming-tokens). ## Debugging Once you've built a graph, you often want to test and debug it. [LangGraph Studio](https://github.com/langchain-ai/langgraph-studio?tab=readme-ov-file) is a specialized IDE for visualization and debugging of LangGraph applications. ![Langgraph Studio](img/lg_studio.png) ## Deployment Once you have confidence in your LangGraph application, many developers want an easy path to deployment. [LangGraph Cloud](/langgraphjs/cloud) is an opinionated, simple way to deploy LangGraph objects from the LangChain team. Of course, you can also use services like [Express.js](https://expressjs.com/) and call your graph from inside the Express.js server as you see fit. --- concepts/breakpoints.md --- # Breakpoints Breakpoints pause graph execution at specific points and enable stepping through execution step by step. Breakpoints are powered by LangGraph's [**persistence layer**](./persistence.md), which saves the state after each graph step. Breakpoints can also be used to enable [**human-in-the-loop**](./human_in_the_loop.md) workflows, though we recommend using the [`interrupt` function](./human_in_the_loop.md#interrupt) for this purpose. ## Requirements To use breakpoints, you will need to: 1. [**Specify a checkpointer**](persistence.md#checkpoints) to save the graph state after each step. 2. [**Set breakpoints**](#setting-breakpoints) to specify where execution should pause. 3. **Run the graph** with a [**thread ID**](./persistence.md#threads) to pause execution at the breakpoint. 4. **Resume execution** using `invoke`/`stream` (see [**The `Command` primitive**](./human_in_the_loop.md#the-command-primitive)). ## Setting breakpoints There are two places where you can set breakpoints: 1. **Before** or **after** a node executes by setting breakpoints at **compile time** or **run time**. We call these [**static breakpoints**](#static-breakpoints). 2. **Inside** a node using the [`NodeInterrupt` error](#nodeinterrupt-error). ### Static breakpoints Static breakpoints are triggered either **before** or **after** a node executes. You can set static breakpoints by specifying `interruptBefore` and `interruptAfter` at **"compile" time** or **run time**. === "Compile time" ```typescript const graph = graphBuilder.compile({ interruptBefore: ["nodeA"], interruptAfter: ["nodeB", "nodeC"], checkpointer: ..., // Specify a checkpointer }); const threadConfig = { configurable: { thread_id: "someThread" } }; // Run the graph until the breakpoint await graph.invoke(inputs, threadConfig); // Optionally update the graph state based on user input await graph.updateState(update, threadConfig); // Resume the graph await graph.invoke(null, threadConfig); ``` === "Run time" ```typescript await graph.invoke( inputs, { configurable: { thread_id: "someThread" }, interruptBefore: ["nodeA"], interruptAfter: ["nodeB", "nodeC"] } ); const threadConfig = { configurable: { thread_id: "someThread" } }; // Run the graph until the breakpoint await graph.invoke(inputs, threadConfig); // Optionally update the graph state based on user input await graph.updateState(update, threadConfig); // Resume the graph await graph.invoke(null, threadConfig); ``` !!! note You cannot set static breakpoints at runtime for **sub-graphs**. If you have a sub-graph, you must set the breakpoints at compilation time. Static breakpoints can be especially useful for debugging if you want to step through the graph execution one node at a time or if you want to pause the graph execution at specific nodes. ### `NodeInterrupt` error We recommend that you [**use the `interrupt` function instead**](#the-interrupt-function) of the `NodeInterrupt` error if you're trying to implement [human-in-the-loop](./human_in_the_loop.md) workflows. The `interrupt` function is easier to use and more flexible. ??? node "`NodeInterrupt` error" The developer can define some *condition* that must be met for a breakpoint to be triggered. This concept of [dynamic breakpoints](./low_level.md#dynamic-breakpoints) is useful when the developer wants to halt the graph under *a particular condition*. This uses a `NodeInterrupt`, which is a special type of error that can be thrown from within a node based upon some condition. As an example, we can define a dynamic breakpoint that triggers when the `input` is longer than 5 characters. ```typescript function myNode(state: typeof GraphAnnotation.State) { if (state.input.length > 5) { throw new NodeInterrupt(`Received input that is longer than 5 characters: ${state.input}`); } return state; } ``` Let's assume we run the graph with an input that triggers the dynamic breakpoint and then attempt to resume the graph execution simply by passing in `null` for the input. ```typescript // Attempt to continue the graph execution with no change to state after we hit the dynamic breakpoint for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` The graph will *interrupt* again because this node will be *re-run* with the same graph state. We need to change the graph state such that the condition that triggers the dynamic breakpoint is no longer met. So, we can simply edit the graph state to an input that meets the condition of our dynamic breakpoint (< 5 characters) and re-run the node. ```typescript // Update the state to pass the dynamic breakpoint await graph.updateState({ input: "foo" }, threadConfig); for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` Alternatively, what if we want to keep our current input and skip the node (`myNode`) that performs the check? To do this, we can simply perform the graph update with `"myNode"` (the node name) as the third positional argument, and pass in `null` for the values. This will make no update to the graph state, but run the update as `myNode`, effectively skipping the node and bypassing the dynamic breakpoint. ```typescript // This update will skip the node `myNode` altogether await graph.updateState(null, threadConfig, "myNode"); for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` ## Additional Resources 📚 - [**Conceptual Guide: Persistence**](persistence.md): Read the persistence guide for more context about persistence. - [**Conceptual Guide: Human-in-the-loop**](human_in_the_loop.md): Read the human-in-the-loop guide for more context on integrating human feedback into LangGraph applications using breakpoints. - [**How to View and Update Past Graph State**](/langgraphjs/how-tos/time-travel): Step-by-step instructions for working with graph state that demonstrate the **replay** and **fork** actions. --- concepts/low_level.md --- # LangGraph Glossary ## Graphs At its core, LangGraph models agent workflows as graphs. You define the behavior of your agents using three key components: 1. [`State`](#state): A shared data structure that represents the current snapshot of your application. It is represented by an [`Annotation`](/langgraphjs/reference/modules/langgraph.Annotation.html) object. 2. [`Nodes`](#nodes): JavaScript/TypeScript functions that encode the logic of your agents. They receive the current `State` as input, perform some computation or side-effect, and return an updated `State`. 3. [`Edges`](#edges): JavaScript/TypeScript functions that determine which `Node` to execute next based on the current `State`. They can be conditional branches or fixed transitions. By composing `Nodes` and `Edges`, you can create complex, looping workflows that evolve the `State` over time. The real power, though, comes from how LangGraph manages that `State`. To emphasize: `Nodes` and `Edges` are nothing more than JavaScript/TypeScript functions - they can contain an LLM or just good ol' JavaScript/TypeScript code. In short: _nodes do the work. edges tell what to do next_. LangGraph's underlying graph algorithm uses [message passing](https://en.wikipedia.org/wiki/Message_passing) to define a general program. When a Node completes its operation, it sends messages along one or more edges to other node(s). These recipient nodes then execute their functions, pass the resulting messages to the next set of nodes, and the process continues. Inspired by Google's [Pregel](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/) system, the program proceeds in discrete "super-steps." A super-step can be considered a single iteration over the graph nodes. Nodes that run in parallel are part of the same super-step, while nodes that run sequentially belong to separate super-steps. At the start of graph execution, all nodes begin in an `inactive` state. A node becomes `active` when it receives a new message (state) on any of its incoming edges (or "channels"). The active node then runs its function and responds with updates. At the end of each super-step, nodes with no incoming messages vote to `halt` by marking themselves as `inactive`. The graph execution terminates when all nodes are `inactive` and no messages are in transit. ### StateGraph The `StateGraph` class is the main graph class to use. This is parameterized by a user defined `State` object. (defined using the `Annotation` object and passed as the first argument) ### MessageGraph (legacy) {#messagegraph} The `MessageGraph` class is a special type of graph. The `State` of a `MessageGraph` is ONLY an array of messages. This class is rarely used except for chatbots, as most applications require the `State` to be more complex than an array of messages. ### Compiling your graph To build your graph, you first define the [state](#state), you then add [nodes](#nodes) and [edges](#edges), and then you compile it. What exactly is compiling your graph and why is it needed? Compiling is a pretty simple step. It provides a few basic checks on the structure of your graph (no orphaned nodes, etc). It is also where you can specify runtime args like checkpointers and [breakpoints](#breakpoints). You compile your graph by just calling the `.compile` method: ```typescript const graph = graphBuilder.compile(...); ``` You **MUST** compile your graph before you can use it. ## State The first thing you do when you define a graph is define the `State` of the graph. The `State` includes information on the structure of the graph, as well as [`reducer` functions](#reducers) which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and should be defined using an [`Annotation`](/langgraphjs/reference/modules/langgraph.Annotation.html) object. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function. ### Annotation The way to specify the schema of a graph is by defining a root [`Annotation`](/langgraphjs/reference/modules/langgraph.Annotation.html) object, where each key is an item in the state. #### Multiple schemas Typically, all graph nodes communicate with a single state annotation. This means that they will read and write to the same state channels. But, there are cases where we want more control over this: - Internal nodes can pass information that is not required in the graph's input / output. - We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key. It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private annotation, `PrivateState`. See [this notebook](../how-tos/pass_private_state.ipynb) for more detail. It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this guide](../how-tos/input_output_schema.ipynb) for more detail. Let's look at an example: ```ts import { Annotation, START, StateGraph, StateType, UpdateType, } from "@langchain/langgraph"; const InputStateAnnotation = Annotation.Root({ user_input: Annotation, }); const OutputStateAnnotation = Annotation.Root({ graph_output: Annotation, }); const OverallStateAnnotation = Annotation.Root({ foo: Annotation, bar: Annotation, user_input: Annotation, graph_output: Annotation, }); const node1 = async (state: typeof InputStateAnnotation.State) => { // Write to OverallStateAnnotation return { foo: state.user_input + " name" }; }; const node2 = async (state: typeof OverallStateAnnotation.State) => { // Read from OverallStateAnnotation, write to OverallStateAnnotation return { bar: state.foo + " is" }; }; const node3 = async (state: typeof OverallStateAnnotation.State) => { // Read from OverallStateAnnotation, write to OutputStateAnnotation return { graph_output: state.bar + " Lance" }; }; // Most of the time the StateGraph type parameters are inferred by TypeScript, // but this is a special case where they must be specified explicitly in order // to avoid a type error. const graph = new StateGraph< typeof OverallStateAnnotation["spec"], StateType, UpdateType, typeof START, typeof InputStateAnnotation["spec"], typeof OutputStateAnnotation["spec"] >({ input: InputStateAnnotation, output: OutputStateAnnotation, stateSchema: OverallStateAnnotation, }) .addNode("node1", node1) .addNode("node2", node2) .addNode("node3", node3) .addEdge("__start__", "node1") .addEdge("node1", "node2") .addEdge("node2", "node3") .compile(); await graph.invoke({ user_input: "My" }); ``` ``` { graph_output: "My name is Lance" } ``` Note that we pass `state: typeof InputStateAnnotation.State` as the input schema to `node1`. But, we write out to `foo`, a channel in `OverallStateAnnotation`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of of the state channels defined at initialization, which includes `OverallStateAnnotation` and the filters `InputStateAnnotation` and `OutputStateAnnotation`. ### Reducers Reducers are key to understanding how updates from nodes are applied to the `State`. Each key in the `State` has its own independent reducer function. If no reducer function is explicitly specified then it is assumed that all updates to that key should override it. Let's take a look at a few examples to understand them better. **Example A:** ```typescript import { StateGraph, Annotation } from "@langchain/langgraph"; const State = Annotation.Root({ foo: Annotation, bar: Annotation, }); const graphBuilder = new StateGraph(State); ``` In this example, no reducer functions are specified for any key. Let's assume the input to the graph is `{ foo: 1, bar: ["hi"] }`. Let's then assume the first `Node` returns `{ foo: 2 }`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{ foo: 2, bar: ["hi"] }`. If the second node returns `{ bar: ["bye"] }` then the `State` would then be `{ foo: 2, bar: ["bye"] }` **Example B:** ```typescript import { StateGraph, Annotation } from "@langchain/langgraph"; const State = Annotation.Root({ foo: Annotation, bar: Annotation({ reducer: (state: string[], update: string[]) => state.concat(update), default: () => [], }), }); const graphBuilder = new StateGraph(State); ``` In this example, we've updated our `bar` field to be an object containing a `reducer` function. This function will always accept two positional arguments: `state` and `update`, with `state` representing the current state value, and `update` representing the update returned from a `Node`. Note that the first key remains unchanged. Let's assume the input to the graph is `{ foo: 1, bar: ["hi"] }`. Let's then assume the first `Node` returns `{ foo: 2 }`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{ foo: 2, bar: ["hi"] }`. If the second node returns`{ bar: ["bye"] }` then the `State` would then be `{ foo: 2, bar: ["hi", "bye"] }`. Notice here that the `bar` key is updated by concatenating the two arrays together. ### Working with Messages in Graph State #### Why use messages? Most modern LLM providers have a chat model interface that accepts a list of messages as input. LangChain's [`ChatModel`](https://js.langchain.com/docs/concepts/#chat-models) in particular accepts a list of `Message` objects as inputs. These messages come in a variety of forms such as `HumanMessage` (user input) or `AIMessage` (LLM response). To read more about what message objects are, please refer to [this](https://js.langchain.com/docs/concepts/#message-types) conceptual guide. #### Using Messages in your Graph In many cases, it is helpful to store prior conversation history as a list of messages in your graph state. To do so, we can add a key (channel) to the graph state that stores a list of `Message` objects and annotate it with a reducer function (see `messages` key in the example below). The reducer function is vital to telling the graph how to update the list of `Message` objects in the state with each state update (for example, when a node sends an update). If you don't specify a reducer, every state update will overwrite the list of messages with the most recently provided value. However, you might also want to manually update messages in your graph state (e.g. human-in-the-loop). If you were to use something like `(a, b) => a.concat(b)` as a reducer, the manual state updates you send to the graph would be appended to the existing list of messages, instead of updating existing messages. To avoid that, you need a reducer that can keep track of message IDs and overwrite existing messages, if updated. To achieve this, you can use the prebuilt `messagesStateReducer` function. For brand new messages, it will simply append to existing list, but it will also handle the updates for existing messages correctly. #### Serialization In addition to keeping track of message IDs, the `messagesStateReducer` function will also try to deserialize messages into LangChain `Message` objects whenever a state update is received on the `messages` channel. This allows sending graph inputs / state updates in the following format: ```ts // this is supported { messages: [new HumanMessage({ content: "message" })]; } // and this is also supported { messages: [{ role: "user", content: "message" }]; } ``` Below is an example of a graph state annotation that uses `messagesStateReducer` as it's reducer function. ```ts import type { BaseMessage } from "@langchain/core/messages"; import { Annotation, type Messages } from "@langchain/langgraph"; const StateAnnotation = Annotation.Root({ messages: Annotation({ reducer: messagesStateReducer, }), }); ``` #### MessagesAnnotation Since having a list of messages in your state is so common, there exists a prebuilt annotation called `MessagesAnnotation` which makes it easy to use messages as graph state. `MessagesAnnotation` is defined with a single `messages` key which is a list of `BaseMessage` objects and uses the `messagesStateReducer` reducer. ```typescript import { MessagesAnnotation, StateGraph } from "@langchain/langgraph"; const graph = new StateGraph(MessagesAnnotation) .addNode(...) ... ``` Is equivalent to initializing your state manually like this: ```typescript import { BaseMessage } from "@langchain/core/messages"; import { Annotation, StateGraph, messagesStateReducer } from "@langchain/langgraph"; export const StateAnnotation = Annotation.Root({ messages: Annotation({ reducer: messagesStateReducer, default: () => [], }), }); const graph = new StateGraph(StateAnnotation) .addNode(...) ... ``` The state of a `MessagesAnnotation` has a single key called `messages`. This is an array of `BaseMessage`s, with [`messagesStateReducer`](/langgraphjs/reference/functions/langgraph.messagesStateReducer.html) as a reducer. `messagesStateReducer` basically adds messages to the existing list (it also does some nice extra things, like convert from OpenAI message format to the standard LangChain message format, handle updates based on message IDs, etc). We often see an array of messages being a key component of state, so this prebuilt state is intended to make it easy to use messages. Typically, there is more state to track than just messages, so we see people extend this state and add more fields, like: ```typescript import { Annotation, MessagesAnnotation } from "@langchain/langgraph"; const StateWithDocuments = Annotation.Root({ ...MessagesAnnotation.spec, // Spread in the messages state documents: Annotation, }); ``` ## Nodes In LangGraph, nodes are typically JavaScript/TypeScript functions (sync or `async`) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`). Similar to `NetworkX`, you add these nodes to a graph using the [addNode](/langgraphjs/reference/classes/langgraph.StateGraph.html#addNode) method: ```typescript import { RunnableConfig } from "@langchain/core/runnables"; import { StateGraph, Annotation } from "@langchain/langgraph"; const GraphAnnotation = Annotation.Root({ input: Annotation, results: Annotation, }); // The state type can be extracted using `typeof .State` const myNode = (state: typeof GraphAnnotation.State, config?: RunnableConfig) => { console.log("In node: ", config.configurable?.user_id); return { results: `Hello, ${state.input}!` }; }; // The second argument is optional const myOtherNode = (state: typeof GraphAnnotation.State) => { return state; }; const builder = new StateGraph(GraphAnnotation) .addNode("myNode", myNode) .addNode("myOtherNode", myOtherNode) ... ``` Behind the scenes, functions are converted to [RunnableLambda's](https://v02.api.js.langchain.com/classes/langchain_core_runnables.RunnableLambda.html), which adds batch and streaming support to your function, along with native tracing and debugging. ### `START` Node The `START` Node is a special node that represents the node sends user input to the graph. The main purpose for referencing this node is to determine which nodes should be called first. ```typescript import { START } from "@langchain/langgraph"; graph.addEdge(START, "nodeA"); ``` ### `END` Node The `END` Node is a special node that represents a terminal node. This node is referenced when you want to denote which edges have no actions after they are done. ```typescript import { END } from "@langchain/langgraph"; graph.addEdge("nodeA", END); ``` ## Edges Edges define how the logic is routed and how the graph decides to stop. This is a big part of how your agents work and how different nodes communicate with each other. There are a few key types of edges: - Normal Edges: Go directly from one node to the next. - Conditional Edges: Call a function to determine which node(s) to go to next. - Entry Point: Which node to call first when user input arrives. - Conditional Entry Point: Call a function to determine which node(s) to call first when user input arrives. A node can have MULTIPLE outgoing edges. If a node has multiple out-going edges, **all** of those destination nodes will be executed in parallel as a part of the next superstep. ### Normal Edges If you **always** want to go from node A to node B, you can use the [addEdge](/langgraphjs/reference/classes/langgraph.StateGraph.html#addEdge) method directly. ```typescript graph.addEdge("nodeA", "nodeB"); ``` ### Conditional Edges If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the [addConditionalEdges](/langgraphjs/reference/classes/langgraph.StateGraph.html#addConditionalEdges) method. This method accepts the name of a node and a "routing function" to call after that node is executed: ```typescript graph.addConditionalEdges("nodeA", routingFunction); ``` Similar to nodes, the `routingFunction` accept the current `state` of the graph and return a value. By default, the return value `routingFunction` is used as the name of the node (or an array of nodes) to send the state to next. All those nodes will be run in parallel as a part of the next superstep. You can optionally provide an object that maps the `routingFunction`'s output to the name of the next node. ```typescript graph.addConditionalEdges("nodeA", routingFunction, { true: "nodeB", false: "nodeC", }); ``` !!! tip Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function. ### Entry Point The entry point is the first node(s) that are run when the graph starts. You can use the [`addEdge`](/langgraphjs/reference/classes/langgraph.StateGraph.html#addEdge) method from the virtual [`START`](/langgraphjs/reference/variables/langgraph.START.html) node to the first node to execute to specify where to enter the graph. ```typescript import { START } from "@langchain/langgraph"; graph.addEdge(START, "nodeA"); ``` ### Conditional Entry Point A conditional entry point lets you start at different nodes depending on custom logic. You can use [`addConditionalEdges`](/langgraphjs/reference/classes/langgraph.StateGraph.html#addConditionalEdges) from the virtual [`START`](/langgraphjs/reference/variables/langgraph.START.html) node to accomplish this. ```typescript import { START } from "@langchain/langgraph"; graph.addConditionalEdges(START, routingFunction); ``` You can optionally provide an object that maps the `routingFunction`'s output to the name of the next node. ```typescript graph.addConditionalEdges(START, routingFunction, { true: "nodeB", false: "nodeC", }); ``` ## `Send` By default, `Nodes` and `Edges` are defined ahead of time and operate on the same shared state. However, there can be cases where the exact edges are not known ahead of time and/or you may want different versions of `State` to exist at the same time. A common of example of this is with `map-reduce` design patterns. In this design pattern, a first node may generate an array of objects, and you may want to apply some other node to all those objects. The number of objects may be unknown ahead of time (meaning the number of edges may not be known) and the input `State` to the downstream `Node` should be different (one for each generated object). To support this design pattern, LangGraph supports returning [`Send`](/langgraphjs/reference/classes/langgraph.Send.html) objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node. ```typescript const continueToJokes = (state: { subjects: string[] }) => { return state.subjects.map( (subject) => new Send("generate_joke", { subject }) ); }; graph.addConditionalEdges("nodeA", continueToJokes); ``` ## `Command` !!! tip Compatibility This functionality requires `@langchain/langgraph>=0.2.31`. It can be convenient to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node rather than use a conditional edge. LangGraph provides a way to do so by returning a [`Command`](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph.Command.html) object from node functions: ```ts import { StateGraph, Annotation, Command } from "@langchain/langgraph"; const StateAnnotation = Annotation.Root({ foo: Annotation, }); const myNode = (state: typeof StateAnnotation.State) => { return new Command({ // state update update: { foo: "bar", }, // control flow goto: "myOtherNode", }); }; ``` With `Command` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)): ```ts const myNode = async (state: typeof StateAnnotation.State) => { if (state.foo === "bar") { return new Command({ update: { foo: "baz", }, goto: "myOtherNode", }); } // ... }; ``` !!! important When returning `Command` in your node functions, you must also add an `ends` parameter with the list of node names the node is routing to, e.g. `.addNode("myNode", myNode, { ends: ["myOtherNode"] })`. This is necessary for graph compilation and validation, and indicates that `myNode` can navigate to `myOtherNode`. Check out this [how-to guide](../how-tos/command.ipynb) for an end-to-end example of how to use `Command`. ### When should I use Command instead of conditional edges? Use `Command` when you need to **both** update the graph state **and** route to a different node. For example, when implementing [multi-agent handoffs](./multi_agent.md#handoffs) where it's important to route to a different agent and pass some information to that agent. Use [conditional edges](#conditional-edges) to route between nodes conditionally without updating the state. ### Navigating to a node in a parent graph If you are using [subgraphs](#subgraphs), you might want to navigate from a node a subgraph to a different subgraph (i.e. a different node in the parent graph). To do so, you can specify `graph: Command.PARENT` in `Command`: ```ts const myNode = (state: typeof StateAnnotation.State) => { return new Command({ update: { foo: "bar" }, goto: "other_subgraph", // where `other_subgraph` is a node in the parent graph graph: Command.PARENT, }); }; ``` !!! note Setting `graph` to `Command.PARENT` will navigate to the closest parent graph. This is particularly useful when implementing [multi-agent handoffs](./multi_agent.md#handoffs). ### Using inside tools A common use case is updating graph state from inside a tool. For example, in a customer support application you might want to look up customer information based on their account number or ID in the beginning of the conversation. To update the graph state from the tool, you can return `Command({ update: { my_custom_key: "foo", messages: [...] } })` from the tool: ```ts import { tool } from "@langchain/core/tools"; const lookupUserInfo = tool(async (input, config) => { const userInfo = getUserInfo(config); return new Command({ // update state keys update: { user_info: userInfo, messages: [ new ToolMessage({ content: "Successfully looked up user information", tool_call_id: config.toolCall.id, }), ], }, }); }, { name: "lookup_user_info", description: "Use this to look up user information to better assist them with their questions.", schema: z.object(...) }); ``` !!! important You MUST include `messages` (or any state key used for the message history) in `Command.update` when returning `Command` from a tool and the list of messages in `messages` MUST contain a `ToolMessage`. This is necessary for the resulting message history to be valid (LLM providers require AI messages with tool calls to be followed by the tool result messages). If you are using tools that update state via `Command`, we recommend using prebuilt [`ToolNode`](/langgraphjs/reference/classes/langgraph_prebuilt.ToolNode.html) which automatically handles tools returning `Command` objects and propagates them to the graph state. If you're writing a custom node that calls tools, you would need to manually propagate `Command` objects returned by the tools as the update from node. ### Human-in-the-loop `Command` is an important part of human-in-the-loop workflows: when using `interrupt()` to collect user input, `Command` is then used to supply the input and resume execution via `new Command({ resume: "User input" })`. Check out [this conceptual guide](/langgraphjs/concepts/human_in_the_loop) for more information. ## Persistence LangGraph provides built-in persistence for your agent's state using [checkpointers](/langgraphjs/reference/classes/checkpoint.BaseCheckpointSaver.html). Checkpointers save snapshots of the graph state at every superstep, allowing resumption at any time. This enables features like human-in-the-loop interactions, memory management, and fault-tolerance. You can even directly manipulate a graph's state after its execution using the appropriate `get` and `update` methods. For more details, see the [conceptual guide](/langgraphjs/concepts/persistence) for more information. ## Threads Threads in LangGraph represent individual sessions or conversations between your graph and a user. When using checkpointing, turns in a single conversation (and even steps within a single graph execution) are organized by a unique thread ID. ## Storage LangGraph provides built-in document storage through the [BaseStore](/langgraphjs/reference/classes/store.BaseStore.html) interface. Unlike checkpointers, which save state by thread ID, stores use custom namespaces for organizing data. This enables cross-thread persistence, allowing agents to maintain long-term memories, learn from past interactions, and accumulate knowledge over time. Common use cases include storing user profiles, building knowledge bases, and managing global preferences across all threads. ## Graph Migrations LangGraph can easily handle migrations of graph definitions (nodes, edges, and state) even when using a checkpointer to track state. - For threads at the end of the graph (i.e. not interrupted) you can change the entire topology of the graph (i.e. all nodes and edges, remove, add, rename, etc) - For threads currently interrupted, we support all topology changes other than renaming / removing nodes (as that thread could now be about to enter a node that no longer exists) -- if this is a blocker please reach out and we can prioritize a solution. - For modifying state, we have full backwards and forwards compatibility for adding and removing keys - State keys that are renamed lose their saved state in existing threads - State keys whose types change in incompatible ways could currently cause issues in threads with state from before the change -- if this is a blocker please reach out and we can prioritize a solution. ## Configuration When creating a graph, you can also mark that certain parts of the graph are configurable. This is commonly done to enable easily switching between models or system prompts. This allows you to create a single "cognitive architecture" (the graph) but have multiple different instance of it. You can then pass this configuration into the graph using the `configurable` config field. ```typescript const config = { configurable: { llm: "anthropic" } }; await graph.invoke(inputs, config); ``` You can then access and use this configuration inside a node: ```typescript const nodeA = (state, config) => { const llmType = config?.configurable?.llm; let llm: BaseChatModel; if (llmType) { const llm = getLlm(llmType); } ... }; ``` See [this guide](../how-tos/configuration.ipynb) for a full breakdown on configuration ### Recursion Limit The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps. The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursionLimit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below: ```ts await graph.invoke(inputs, { recursionLimit: 50 }); ``` Read [this how-to](/langgraphjs/how-tos/recursion-limit/) to learn more about how the recursion limit works. ## `interrupt` Use the [interrupt](/langgraphjs/reference/functions/langgraph.interrupt-1.html) function to **pause** the graph at specific points to collect user input. The `interrupt` function surfaces interrupt information to the client, allowing the developer to collect user input, validate the graph state, or make decisions before resuming execution. ```ts import { interrupt } from "@langchain/langgraph"; const humanApprovalNode = (state: typeof StateAnnotation.State) => { ... const answer = interrupt( // This value will be sent to the client. // It can be any JSON serializable value. { question: "is it ok to continue?"}, ); ... ``` Resuming the graph is done by passing a [`Command`](#command) object to the graph with the `resume` key set to the value returned by the `interrupt` function. Read more about how the `interrupt` is used for **human-in-the-loop** workflows in the [Human-in-the-loop conceptual guide](./human_in_the_loop.md). **Note:** The `interrupt` function is not currently available in [web environments](/langgraphjs/how-tos/use-in-web-environments/). ## Breakpoints Breakpoints pause graph execution at specific points and enable stepping through execution step by step. Breakpoints are powered by LangGraph's [**persistence layer**](./persistence.md), which saves the state after each graph step. Breakpoints can also be used to enable [**human-in-the-loop**](./human_in_the_loop.md) workflows, though we recommend using the [`interrupt` function](#interrupt) for this purpose. Read more about breakpoints in the [Breakpoints conceptual guide](./breakpoints.md). ## Subgraphs A subgraph is a [graph](#graphs) that is used as a [node](#nodes) in another graph. This is nothing more than the age-old concept of encapsulation, applied to LangGraph. Some reasons for using subgraphs are: - building [multi-agent systems](./multi_agent.md) - when you want to reuse a set of nodes in multiple graphs, which maybe share some state, you can define them once in a subgraph and then use them in multiple parent graphs - when you want different teams to work on different parts of the graph independently, you can define each part as a subgraph, and as long as the subgraph interface (the input and output schemas) is respected, the parent graph can be built without knowing any details of the subgraph There are two ways to add subgraphs to a parent graph: - add a node with the compiled subgraph: this is useful when the parent graph and the subgraph share state keys and you don't need to transform state on the way in or out ```ts .addNode("subgraph", subgraphBuilder.compile()); ``` - add a node with a function that invokes the subgraph: this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph ```ts const subgraph = subgraphBuilder.compile(); const callSubgraph = async (state: typeof StateAnnotation.State) => { return subgraph.invoke({ subgraph_key: state.parent_key }); }; builder.addNode("subgraph", callSubgraph); ``` Let's take a look at examples for each. ### As a compiled graph The simplest way to create subgraph nodes is by using a [compiled subgraph](#compiling-your-graph) directly. When doing so, it is **important** that the parent graph and the subgraph [state schemas](#state) share at least one key which they can use to communicate. If your graph and subgraph do not share any keys, you should use write a function [invoking the subgraph](#as-a-function) instead.

Note

If you pass extra keys to the subgraph node (i.e., in addition to the shared keys), they will be ignored by the subgraph node. Similarly, if you return extra keys from the subgraph, they will be ignored by the parent graph.

```ts import { StateGraph, Annotation } from "@langchain/langgraph"; const StateAnnotation = Annotation.Root({ foo: Annotation, }); const SubgraphStateAnnotation = Annotation.Root({ foo: Annotation, // note that this key is shared with the parent graph state bar: Annotation, }); // Define subgraph const subgraphNode = async (state: typeof SubgraphStateAnnotation.State) => { // note that this subgraph node can communicate with // the parent graph via the shared "foo" key return { foo: state.foo + "bar" }; }; const subgraph = new StateGraph(SubgraphStateAnnotation) .addNode("subgraph", subgraphNode); ... .compile(); // Define parent graph const parentGraph = new StateGraph(StateAnnotation) .addNode("subgraph", subgraph) ... .compile(); ``` ### As a function You might want to define a subgraph with a completely different schema. In this case, you can create a node function that invokes the subgraph. This function will need to [transform](../how-tos/subgraph-transform-state.ipynb) the input (parent) state to the subgraph state before invoking the subgraph, and transform the results back to the parent state before returning the state update from the node. ```ts import { StateGraph, Annotation } from "@langchain/langgraph"; const StateAnnotation = Annotation.Root({ foo: Annotation, }); const SubgraphStateAnnotation = Annotation.Root({ // note that none of these keys are shared with the parent graph state bar: Annotation, baz: Annotation, }); // Define subgraph const subgraphNode = async (state: typeof SubgraphStateAnnotation.State) => { return { bar: state.bar + "baz" }; }; const subgraph = new StateGraph(SubgraphStateAnnotation) .addNode("subgraph", subgraphNode); ... .compile(); // Define parent graph const subgraphWrapperNode = async (state: typeof StateAnnotation.State) => { // transform the state to the subgraph state const response = await subgraph.invoke({ bar: state.foo, }); // transform response back to the parent state return { foo: response.bar, }; } const parentGraph = new StateGraph(StateAnnotation) .addNode("subgraph", subgraphWrapperNode) ... .compile(); ``` ## Visualization It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with a nice built-in way to render a graph as a Mermaid diagram. You can use the `getGraph()` method like this: ```ts const representation = graph.getGraph(); const image = await representation.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); const buffer = new Uint8Array(arrayBuffer); ``` You can also check out [LangGraph Studio](https://github.com/langchain-ai/langgraph-studio) for a bespoke IDE that includes powerful visualization and debugging features. ## Streaming LangGraph is built with first class support for streaming. There are several different streaming modes that LangGraph supports: - [`"values"`](../how-tos/stream-values.ipynb): This streams the full value of the state after each step of the graph. - [`"updates`](../how-tos/stream-updates.ipynb): This 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) then those updates are streamed separately. In addition, you can use the [`streamEvents`](https://api.js.langchain.com/classes/langchain_core_runnables.Runnable.html#streamEvents) method to stream back events that happen _inside_ nodes. This is useful for [streaming tokens of LLM calls](../how-tos/streaming-tokens-without-langchain.ipynb). LangGraph is built with first class support for streaming, including streaming updates from graph nodes during execution, streaming tokens from LLM calls and more. See this [conceptual guide](./streaming.md) for more information. --- concepts/human_in_the_loop.md --- # Human-in-the-loop !!! tip "This guide uses the new `interrupt` function." As of LangGraph 0.2.31, the recommended way to set breakpoints is using the [`interrupt` function](/langgraphjs/reference/functions/langgraph.interrupt-1.html) as it simplifies **human-in-the-loop** patterns. If you're looking for the previous version of this conceptual guide, which relied on static breakpoints and `NodeInterrupt` exception, it is available [here](v0-human-in-the-loop.md). A **human-in-the-loop** (or "on-the-loop") workflow integrates human input into automated processes, allowing for decisions, validation, or corrections at key stages. This is especially useful in **LLM-based applications**, where the underlying model may generate occasional inaccuracies. In low-error-tolerance scenarios like compliance, decision-making, or content generation, human involvement ensures reliability by enabling review, correction, or override of model outputs. ## Use cases Key use cases for **human-in-the-loop** workflows in LLM-based applications include: 1. [**🛠️ Reviewing tool calls**](#review-tool-calls): Humans can review, edit, or approve tool calls requested by the LLM before tool execution. 2. **✅ Validating LLM outputs**: Humans can review, edit, or approve content generated by the LLM. 3. **💡 Providing context**: Enable the LLM to explicitly request human input for clarification or additional details or to support multi-turn conversations. ## `interrupt` The [`interrupt` function](/langgraphjs/reference/functions/langgraph.interrupt-1.html) in LangGraph enables human-in-the-loop workflows by pausing the graph at a specific node, presenting information to a human, and resuming the graph with their input. This function is useful for tasks like approvals, edits, or collecting additional input. The [`interrupt` function](/langgraphjs/reference/functions/langgraph.interrupt-1.html) is used in conjunction with the [`Command`](/langgraphjs/reference/classes/langgraph.Command.html) object to resume the graph with a value provided by the human. ```typescript import { interrupt } from "@langchain/langgraph"; function humanNode(state: typeof GraphAnnotation.State) { const value = interrupt( // Any JSON serializable value to surface to the human. // For example, a question or a piece of text or a set of keys in the state { text_to_revise: state.some_text, } ); // Update the state with the human's input or route the graph based on the input return { some_text: value, }; } const graph = workflow.compile({ checkpointer, // Required for `interrupt` to work }); // Run the graph until the interrupt const threadConfig = { configurable: { thread_id: "some_id" } }; await graph.invoke(someInput, threadConfig); // Below code can run some amount of time later and/or in a different process // Human input const valueFromHuman = "..."; // Resume the graph with the human's input await graph.invoke(new Command({ resume: valueFromHuman }), threadConfig); ``` ```typescript { some_text: "Edited text"; } ``` ??? "Full Code" Here's a full example of how to use `interrupt` in a graph, if you'd like to see the code in action. ```typescript import { MemorySaver, Annotation, interrupt, Command, StateGraph } from "@langchain/langgraph"; // Define the graph state const StateAnnotation = Annotation.Root({ some_text: Annotation() }); function humanNode(state: typeof StateAnnotation.State) { const value = interrupt( // Any JSON serializable value to surface to the human. // For example, a question or a piece of text or a set of keys in the state { text_to_revise: state.some_text } ); return { // Update the state with the human's input some_text: value }; } // Build the graph const workflow = new StateGraph(StateAnnotation) // Add the human-node to the graph .addNode("human_node", humanNode) .addEdge("__start__", "human_node") // A checkpointer is required for `interrupt` to work. const checkpointer = new MemorySaver(); const graph = workflow.compile({ checkpointer }); // Using stream() to directly surface the `__interrupt__` information. for await (const chunk of await graph.stream( { some_text: "Original text" }, threadConfig )) { console.log(chunk); } // Resume using Command for await (const chunk of await graph.stream( new Command({ resume: "Edited text" }), threadConfig )) { console.log(chunk); } ``` ```typescript { __interrupt__: [ { value: { question: 'Please revise the text', some_text: 'Original text' }, resumable: true, ns: ['human_node:10fe492f-3688-c8c6-0d0a-ec61a43fecd6'], when: 'during' } ] } { human_node: { some_text: 'Edited text' } } ``` ## Requirements To use `interrupt` in your graph, you need to: 1. [**Specify a checkpointer**](persistence.md#checkpoints) to save the graph state after each step. 2. **Call `interrupt()`** in the appropriate place. See the [Design Patterns](#design-patterns) section for examples. 3. **Run the graph** with a [**thread ID**](./persistence.md#threads) until the `interrupt` is hit. 4. **Resume execution** using `invoke`/`stream` (see [**The `Command` primitive**](#the-command-primitive)). ## Design Patterns There are typically three different **actions** that you can do with a human-in-the-loop workflow: 1. **Approve or Reject**: Pause the graph before a critical step, such as an API call, to review and approve the action. If the action is rejected, you can prevent the graph from executing the step, and potentially take an alternative action. This pattern often involve **routing** the graph based on the human's input. 2. **Edit Graph State**: Pause the graph to review and edit the graph state. This is useful for correcting mistakes or updating the state with additional information. This pattern often involves **updating** the state with the human's input. 3. **Get Input**: Explicitly request human input at a particular step in the graph. This is useful for collecting additional information or context to inform the agent's decision-making process or for supporting **multi-turn conversations**. Below we show different design patterns that can be implemented using these **actions**. **Note:** The `interrupt` function propagates by throwing a special `GraphInterrupt` error. Therefore, you should avoid using `try/catch` blocks around the `interrupt` function - or if you do, ensure that the `GraphInterrupt` error is thrown again within your `catch` block. ### Approve or Reject
![image](img/human_in_the_loop/approve-or-reject.png){: style="max-height:400px"}
Depending on the human's approval or rejection, the graph can proceed with the action or take an alternative path.
Pause the graph before a critical step, such as an API call, to review and approve the action. If the action is rejected, you can prevent the graph from executing the step, and potentially take an alternative action. ```typescript import { interrupt, Command } from "@langchain/langgraph"; function humanApproval(state: typeof GraphAnnotation.State): Command { const isApproved = interrupt({ question: "Is this correct?", // Surface the output that should be // reviewed and approved by the human. llm_output: state.llm_output, }); if (isApproved) { return new Command({ goto: "some_node" }); } else { return new Command({ goto: "another_node" }); } } // Add the node to the graph in an appropriate location // and connect it to the relevant nodes. const graph = graphBuilder .addNode("human_approval", humanApproval) .compile({ checkpointer }); // After running the graph and hitting the interrupt, the graph will pause. // Resume it with either an approval or rejection. const threadConfig = { configurable: { thread_id: "some_id" } }; await graph.invoke(new Command({ resume: true }), threadConfig); ``` See [how to review tool calls](/langgraphjs/how-tos/review-tool-calls) for a more detailed example. ### Review & Edit State
![image](img/human_in_the_loop/edit-graph-state-simple.png){: style="max-height:400px"}
A human can review and edit the state of the graph. This is useful for correcting mistakes or updating the state with additional information.
```typescript import { interrupt } from "@langchain/langgraph"; function humanEditing(state: typeof GraphAnnotation.State): Command { const result = interrupt({ // Interrupt information to surface to the client. // Can be any JSON serializable value. task: "Review the output from the LLM and make any necessary edits.", llm_generated_summary: state.llm_generated_summary, }); // Update the state with the edited text return { llm_generated_summary: result.edited_text, }; } // Add the node to the graph in an appropriate location // and connect it to the relevant nodes. const graph = graphBuilder .addNode("human_editing", humanEditing) .compile({ checkpointer }); // After running the graph and hitting the interrupt, the graph will pause. // Resume it with the edited text. const threadConfig = { configurable: { thread_id: "some_id" } }; await graph.invoke( new Command({ resume: { edited_text: "The edited text" } }), threadConfig ); ``` See [How to wait for user input using interrupt](/langgraphjs/how-tos/wait-user-input) for a more detailed example. ### Review Tool Calls
![image](img/human_in_the_loop/tool-call-review.png){: style="max-height:400px"}
A human can review and edit the output from the LLM before proceeding. This is particularly critical in applications where the tool calls requested by the LLM may be sensitive or require human oversight.
```typescript import { interrupt, Command } from "@langchain/langgraph"; function humanReviewNode(state: typeof GraphAnnotation.State): Command { // This is the value we'll be providing via Command.resume() const humanReview = interrupt({ question: "Is this correct?", // Surface tool calls for review tool_call: toolCall, }); const [reviewAction, reviewData] = humanReview; // Approve the tool call and continue if (reviewAction === "continue") { return new Command({ goto: "run_tool" }); } // Modify the tool call manually and then continue else if (reviewAction === "update") { const updatedMsg = getUpdatedMsg(reviewData); // Remember that to modify an existing message you will need // to pass the message with a matching ID. return new Command({ goto: "run_tool", update: { messages: [updatedMsg] }, }); } // Give natural language feedback, and then pass that back to the agent else if (reviewAction === "feedback") { const feedbackMsg = getFeedbackMsg(reviewData); return new Command({ goto: "call_llm", update: { messages: [feedbackMsg] }, }); } } ``` See [how to review tool calls](/langgraphjs/how-tos/review-tool-calls) for a more detailed example. ### Multi-turn conversation
![image](img/human_in_the_loop/multi-turn-conversation.png){: style="max-height:400px"}
A multi-turn conversation architecture where an agent and human node cycle back and forth until the agent decides to hand off the conversation to another agent or another part of the system.
A **multi-turn conversation** involves multiple back-and-forth interactions between an agent and a human, which can allow the agent to gather additional information from the human in a conversational manner. This design pattern is useful in an LLM application consisting of [multiple agents](./multi_agent.md). One or more agents may need to carry out multi-turn conversations with a human, where the human provides input or feedback at different stages of the conversation. For simplicity, the agent implementation below is illustrated as a single node, but in reality it may be part of a larger graph consisting of multiple nodes and include a conditional edge. === "Using a human node per agent" In this pattern, each agent has its own human node for collecting user input. This can be achieved by either naming the human nodes with unique names (e.g., "human for agent 1", "human for agent 2") or by using subgraphs where a subgraph contains a human node and an agent node. ```typescript import { interrupt } from "@langchain/langgraph"; function humanInput(state: typeof GraphAnnotation.State) { const humanMessage = interrupt("human_input"); return { messages: [ { role: "human", content: humanMessage } ] }; } function agent(state: typeof GraphAnnotation.State) { // Agent logic // ... } const graph = graphBuilder .addNode("human_input", humanInput) .addEdge("human_input", "agent") .compile({ checkpointer }); // After running the graph and hitting the interrupt, the graph will pause. // Resume it with the human's input. await graph.invoke( new Command({ resume: "hello!" }), threadConfig ); ``` === "Sharing human node across multiple agents" In this pattern, a single human node is used to collect user input for multiple agents. The active agent is determined from the state, so after human input is collected, the graph can route to the correct agent. ```typescript import { interrupt, Command, MessagesAnnotation } from "@langchain/langgraph"; function humanNode(state: typeof MessagesAnnotation.State): Command { /** * A node for collecting user input. */ const userInput = interrupt("Ready for user input."); // Determine the **active agent** from the state, so // we can route to the correct agent after collecting input. // For example, add a field to the state or use the last active agent. // or fill in `name` attribute of AI messages generated by the agents. const activeAgent = ...; return new Command({ goto: activeAgent, update: { messages: [{ role: "human", content: userInput, }] } }); } ``` See [how to implement multi-turn conversations](/langgraphjs/how-tos/multi-agent-multi-turn-convo) for a more detailed example. ### Validating human input If you need to validate the input provided by the human within the graph itself (rather than on the client side), you can achieve this by using multiple interrupt calls within a single node. ```typescript import { interrupt } from "@langchain/langgraph"; function humanNode(state: typeof GraphAnnotation.State) { /** * Human node with validation. */ let question = "What is your age?"; while (true) { const answer = interrupt(question); // Validate answer, if the answer isn't valid ask for input again. if (typeof answer !== "number" || answer < 0) { question = `'${answer}' is not a valid age. What is your age?`; continue; } else { // If the answer is valid, we can proceed. break; } } console.log(`The human in the loop is ${answer} years old.`); return { age: answer, }; } ``` ## The `Command` primitive When using the `interrupt` function, the graph will pause at the interrupt and wait for user input. Graph execution can be resumed using the [Command](/langgraphjs/reference/classes/langgraph.Command.html) primitive which can be passed through the `invoke` or `stream` methods. The `Command` primitive provides several options to control and modify the graph's state during resumption: 1. **Pass a value to the `interrupt`**: Provide data, such as a user's response, to the graph using `new Command({ resume: value })`. Execution resumes from the beginning of the node where the `interrupt` was used, however, this time the `interrupt(...)` call will return the value passed in the `new Command({ resume: value })` instead of pausing the graph. ```typescript // Resume graph execution with the user's input. await graph.invoke(new Command({ resume: { age: "25" } }), threadConfig); ``` 2. **Update the graph state**: Modify the graph state using `Command({ goto: ..., update: ... })`. Note that resumption starts from the beginning of the node where the `interrupt` was used. Execution resumes from the beginning of the node where the `interrupt` was used, but with the updated state. ```typescript // Update the graph state and resume. // You must provide a `resume` value if using an `interrupt`. await graph.invoke( new Command({ resume: "Let's go!!!", update: { foo: "bar" } }), threadConfig ); ``` By leveraging `Command`, you can resume graph execution, handle user inputs, and dynamically adjust the graph's state. ## Using with `invoke` When you use `stream` to run the graph, you will receive an `Interrupt` event that let you know the `interrupt` was triggered. `invoke` does not return the interrupt information. To access this information, you must use the [getState](/langgraphjs/reference/classes/langgraph.CompiledStateGraph.html#getState) method to retrieve the graph state after calling `invoke`. ```typescript // Run the graph up to the interrupt const result = await graph.invoke(inputs, threadConfig); // Get the graph state to get interrupt information. const state = await graph.getState(threadConfig); // Print the state values console.log(state.values); // Print the pending tasks console.log(state.tasks); // Resume the graph with the user's input. await graph.invoke(new Command({ resume: { age: "25" } }), threadConfig); ``` ```typescript { foo: "bar"; } // State values [ { id: "5d8ffc92-8011-0c9b-8b59-9d3545b7e553", name: "node_foo", path: ["__pregel_pull", "node_foo"], error: null, interrupts: [ { value: "value_in_interrupt", resumable: true, ns: ["node_foo:5d8ffc92-8011-0c9b-8b59-9d3545b7e553"], when: "during", }, ], state: null, result: null, }, ]; // Pending tasks. interrupts ``` ## How does resuming from an interrupt work? A critical aspect of using `interrupt` is understanding how resuming works. When you resume execution after an `interrupt`, graph execution starts from the **beginning** of the **graph node** where the last `interrupt` was triggered. **All** code from the beginning of the node to the `interrupt` will be re-executed. ```typescript let counter = 0; function node(state: State) { // All the code from the beginning of the node to the interrupt will be re-executed // when the graph resumes. counter += 1; console.log(`> Entered the node: ${counter} # of times`); // Pause the graph and wait for user input. const answer = interrupt(); console.log("The value of counter is:", counter); // ... } ``` Upon **resuming** the graph, the counter will be incremented a second time, resulting in the following output: ```typescript > Entered the node: 2 # of times The value of counter is: 2 ``` ## Common Pitfalls ### Side-effects Place code with side effects, such as API calls, **after** the `interrupt` to avoid duplication, as these are re-triggered every time the node is resumed. === "Side effects before interrupt (BAD)" This code will re-execute the API call another time when the node is resumed from the `interrupt`. This can be problematic if the API call is not idempotent or is just expensive. ```typescript import { interrupt } from "@langchain/langgraph"; function humanNode(state: typeof GraphAnnotation.State) { /** * Human node with validation. */ apiCall(); // This code will be re-executed when the node is resumed. const answer = interrupt(question); } ``` === "Side effects after interrupt (OK)" ```typescript import { interrupt } from "@langchain/langgraph"; function humanNode(state: typeof GraphAnnotation.State) { /** * Human node with validation. */ const answer = interrupt(question); apiCall(answer); // OK as it's after the interrupt } ``` === "Side effects in a separate node (OK)" ```typescript import { interrupt } from "@langchain/langgraph"; function humanNode(state: typeof GraphAnnotation.State) { /** * Human node with validation. */ const answer = interrupt(question); return { answer }; } function apiCallNode(state: typeof GraphAnnotation.State) { apiCall(); // OK as it's in a separate node } ``` ### Subgraphs called as functions When invoking a subgraph [as a function](low_level.md#as-a-function), the **parent graph** will resume execution from the **beginning of the node** where the subgraph was invoked (and where an `interrupt` was triggered). Similarly, the **subgraph**, will resume from the **beginning of the node** where the `interrupt()` function was called. For example, ```typescript async function nodeInParentGraph(state: typeof GraphAnnotation.State) { someCode(); // <-- This will re-execute when the subgraph is resumed. // Invoke a subgraph as a function. // The subgraph contains an `interrupt` call. const subgraphResult = await subgraph.invoke(someInput); ... } ``` ??? "**Example: Parent and Subgraph Execution Flow**" Say we have a parent graph with 3 nodes: **Parent Graph**: `node_1` → `node_2` (subgraph call) → `node_3` And the subgraph has 3 nodes, where the second node contains an `interrupt`: **Subgraph**: `sub_node_1` → `sub_node_2` (`interrupt`) → `sub_node_3` When resuming the graph, the execution will proceed as follows: 1. **Skip `node_1`** in the parent graph (already executed, graph state was saved in snapshot). 2. **Re-execute `node_2`** in the parent graph from the start. 3. **Skip `sub_node_1`** in the subgraph (already executed, graph state was saved in snapshot). 4. **Re-execute `sub_node_2`** in the subgraph from the beginning. 5. Continue with `sub_node_3` and subsequent nodes. Here is abbreviated example code that you can use to understand how subgraphs work with interrupts. It counts the number of times each node is entered and prints the count. ```typescript import { StateGraph, START, interrupt, Command, MemorySaver, Annotation } from "@langchain/langgraph"; const GraphAnnotation = Annotation.Root({ stateCounter: Annotation({ reducer: (a, b) => a + b, default: () => 0 }) }) let counterNodeInSubgraph = 0; function nodeInSubgraph(state: typeof GraphAnnotation.State) { counterNodeInSubgraph += 1; // This code will **NOT** run again! console.log(`Entered 'nodeInSubgraph' a total of ${counterNodeInSubgraph} times`); return {}; } let counterHumanNode = 0; async function humanNode(state: typeof GraphAnnotation.State) { counterHumanNode += 1; // This code will run again! console.log(`Entered humanNode in sub-graph a total of ${counterHumanNode} times`); const answer = await interrupt("what is your name?"); console.log(`Got an answer of ${answer}`); return {}; } const checkpointer = new MemorySaver(); const subgraphBuilder = new StateGraph(GraphAnnotation) .addNode("some_node", nodeInSubgraph) .addNode("human_node", humanNode) .addEdge(START, "some_node") .addEdge("some_node", "human_node") const subgraph = subgraphBuilder.compile({ checkpointer }); let counterParentNode = 0; async function parentNode(state: typeof GraphAnnotation.State) { counterParentNode += 1; // This code will run again on resuming! console.log(`Entered 'parentNode' a total of ${counterParentNode} times`); // Please note that we're intentionally incrementing the state counter // in the graph state as well to demonstrate that the subgraph update // of the same key will not conflict with the parent graph (until const subgraphState = await subgraph.invoke(state); return subgraphState; } const builder = new StateGraph(GraphAnnotation) .addNode("parent_node", parentNode) .addEdge(START, "parent_node") // A checkpointer must be enabled for interrupts to work! const graph = builder.compile({ checkpointer }); const config = { configurable: { thread_id: crypto.randomUUID(), } }; for await (const chunk of await graph.stream({ stateCounter: 1 }, config)) { console.log(chunk); } console.log('--- Resuming ---'); for await (const chunk of await graph.stream(new Command({ resume: "35" }), config)) { console.log(chunk); } ``` This will print out ```typescript --- First invocation --- In parent node: { foo: 'bar' } Entered 'parentNode' a total of 1 times Entered 'nodeInSubgraph' a total of 1 times Entered humanNode in sub-graph a total of 1 times { __interrupt__: [{ value: 'what is your name?', resumable: true, ns: ['parent_node:0b23d72f-aaba-0329-1a59-ca4f3c8bad3b', 'human_node:25df717c-cb80-57b0-7410-44e20aac8f3c'], when: 'during' }] } --- Resuming --- In parent node: { foo: 'bar' } Entered 'parentNode' a total of 2 times Entered humanNode in sub-graph a total of 2 times Got an answer of 35 { parent_node: null } ``` ### Using multiple interrupts Using multiple interrupts within a **single** node can be helpful for patterns like [validating human input](#validating-human-input). However, using multiple interrupts in the same node can lead to unexpected behavior if not handled carefully. When a node contains multiple interrupt calls, LangGraph keeps a list of resume values specific to the task executing the node. Whenever execution resumes, it starts at the beginning of the node. For each interrupt encountered, LangGraph checks if a matching value exists in the task's resume list. Matching is **strictly index-based**, so the order of interrupt calls within the node is critical. To avoid issues, refrain from dynamically changing the node's structure between executions. This includes adding, removing, or reordering interrupt calls, as such changes can result in mismatched indices. These problems often arise from unconventional patterns, such as mutating state via `Command.resume(...).update(SOME_STATE_MUTATION)` or relying on global variables to modify the node's structure dynamically. ??? "Example of incorrect code" ```typescript import { v4 as uuidv4 } from "uuid"; import { StateGraph, MemorySaver, START, interrupt, Command, Annotation } from "@langchain/langgraph"; const GraphAnnotation = Annotation.Root({ name: Annotation(), age: Annotation() }); function humanNode(state: typeof GraphAnnotation.State) { let name; if (!state.name) { name = interrupt("what is your name?"); } else { name = "N/A"; } let age; if (!state.age) { age = interrupt("what is your age?"); } else { age = "N/A"; } console.log(`Name: ${name}. Age: ${age}`); return { age, name, }; } const builder = new StateGraph(GraphAnnotation) .addNode("human_node", humanNode); .addEdge(START, "human_node"); // A checkpointer must be enabled for interrupts to work! const checkpointer = new MemorySaver(); const graph = builder.compile({ checkpointer }); const config = { configurable: { thread_id: uuidv4(), } }; for await (const chunk of await graph.stream({ age: undefined, name: undefined }, config)) { console.log(chunk); } for await (const chunk of await graph.stream( new Command({ resume: "John", update: { name: "foo" } }), config )) { console.log(chunk); } ``` ```typescript { __interrupt__: [{ value: 'what is your name?', resumable: true, ns: ['human_node:3a007ef9-c30d-c357-1ec1-86a1a70d8fba'], when: 'during' }]} Name: N/A. Age: John { human_node: { age: 'John', name: 'N/A' } } ``` ## Additional Resources 📚 - [**Conceptual Guide: Persistence**](persistence.md#replay): Read the persistence guide for more context on replaying. - [**How to Guides: Human-in-the-loop**](/langgraphjs/how-tos/#human-in-the-loop): Learn how to implement human-in-the-loop workflows in LangGraph. - [**How to implement multi-turn conversations**](/langgraphjs/how-tos/multi-agent-multi-turn-convo): Learn how to implement multi-turn conversations in LangGraph. --- concepts/persistence.md --- # 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](/langgraphjs/how-tos/persistence) 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. ![Checkpoints](img/persistence/checkpoints.jpg) ## 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: ```ts {"configurable": {"thread_id": "1"}} ``` ## 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: - `config`: Config associated with this checkpoint. - `metadata`: Metadata associated with this checkpoint. - `values`: Values of the state channels at this point in time. - `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](/langgraphjs/how-tos/dynamic_breakpoints) from within a node, tasks will contain additional data associated with interrupts. Let's see what checkpoints are saved when a simple graph is invoked as follows: ```typescript import { StateGraph, START, END, MemorySaver, Annotation } from "@langchain/langgraph"; const GraphAnnotation = Annotation.Root({ foo: Annotation bar: Annotation({ reducer: (a, b) => [...a, ...b], default: () => [], }) }); function nodeA(state: typeof GraphAnnotation.State) { return { foo: "a", bar: ["a"] }; } function nodeB(state: typeof GraphAnnotation.State) { return { foo: "b", bar: ["b"] }; } const workflow = new StateGraph(GraphAnnotation); .addNode("nodeA", nodeA) .addNode("nodeB", nodeB) .addEdge(START, "nodeA") .addEdge("nodeA", "nodeB") .addEdge("nodeB", END); const checkpointer = new MemorySaver(); const graph = workflow.compile({ checkpointer }); const config = { configurable: { thread_id: "1" } }; await graph.invoke({ foo: "" }, config); ``` After we run the graph, we expect to see exactly 4 checkpoints: * empty checkpoint with `START` as the next node to be executed * checkpoint with the user input `{foo: '', bar: []}` and `nodeA` as the next node to be executed * checkpoint with the outputs of `nodeA` `{foo: 'a', bar: ['a']}` and `nodeB` as the next node to be executed * checkpoint with the outputs of `nodeB` `{foo: 'b', bar: ['a', 'b']}` and no next nodes to be executed Note that we `bar` channel values contain outputs from both nodes as we have a reducer for `bar` channel. ### Get state When interacting with the saved graph state, you **must** specify a [thread identifier](#threads). You can view the *latest* state of the graph by calling `await graph.getState(config)`. This will return a `StateSnapshot` object that corresponds to the latest checkpoint associated with the thread ID provided in the config or a checkpoint associated with a checkpoint ID for the thread, if provided. ```typescript // Get the latest state snapshot const config = { configurable: { thread_id: "1" } }; const state = await graph.getState(config); // Get a state snapshot for a specific checkpoint_id const configWithCheckpoint = { configurable: { thread_id: "1", checkpoint_id: "1ef663ba-28fe-6528-8002-5a559208592c" } }; const stateWithCheckpoint = await graph.getState(configWithCheckpoint); ``` In our example, the output of `getState` will look like this: ``` { values: { foo: 'b', bar: ['a', 'b'] }, next: [], config: { configurable: { thread_id: '1', checkpoint_ns: '', checkpoint_id: '1ef663ba-28fe-6528-8002-5a559208592c' } }, metadata: { source: 'loop', writes: { nodeB: { foo: 'b', bar: ['b'] } }, step: 2 }, created_at: '2024-08-29T19:19:38.821749+00:00', parent_config: { configurable: { thread_id: '1', checkpoint_ns: '', checkpoint_id: '1ef663ba-28f9-6ec4-8001-31981c2c39f8' } }, tasks: [] } ``` ### Get state history You can get the full history of the graph execution for a given thread by calling `await graph.getStateHistory(config)`. This will return a list of `StateSnapshot` objects associated with the thread ID provided in the config. Importantly, the checkpoints will be ordered chronologically with the most recent checkpoint / `StateSnapshot` being the first in the list. ```typescript const config = { configurable: { thread_id: "1" } }; const history = await graph.getStateHistory(config); ``` In our example, the output of `getStateHistory` will look like this: ``` [ { values: { foo: 'b', bar: ['a', 'b'] }, next: [], config: { configurable: { thread_id: '1', checkpoint_ns: '', checkpoint_id: '1ef663ba-28fe-6528-8002-5a559208592c' } }, metadata: { source: 'loop', writes: { nodeB: { foo: 'b', bar: ['b'] } }, step: 2 }, created_at: '2024-08-29T19:19:38.821749+00:00', parent_config: { configurable: { thread_id: '1', checkpoint_ns: '', checkpoint_id: '1ef663ba-28f9-6ec4-8001-31981c2c39f8' } }, tasks: [], }, { values: { foo: 'a', bar: ['a'] }, next: ['nodeB'], config: { configurable: { thread_id: '1', checkpoint_ns: '', checkpoint_id: '1ef663ba-28f9-6ec4-8001-31981c2c39f8' } }, metadata: { source: 'loop', writes: { nodeA: { foo: 'a', bar: ['a'] } }, step: 1 }, created_at: '2024-08-29T19:19:38.819946+00:00', parent_config: { configurable: { thread_id: '1', checkpoint_ns: '', checkpoint_id: '1ef663ba-28f4-6b4a-8000-ca575a13d36a' } }, tasks: [{ id: '6fb7314f-f114-5413-a1f3-d37dfe98ff44', name: 'nodeB', error: null, interrupts: [] }], }, // ... (other checkpoints) ] ``` ![State](./img/persistence/get_state.jpg) ### Replay It's also possible to play-back a prior graph execution. If we `invoking` a graph with a `thread_id` and a `checkpoint_id`, then we will *re-play* the graph from a checkpoint that corresponds to the `checkpoint_id`. * `thread_id` is simply the ID of a thread. This is always required. * `checkpoint_id` This identifier refers to a specific checkpoint within a thread. You must pass these when invoking the graph as part of the `configurable` portion of the config: ```typescript // { configurable: { thread_id: "1" } } // valid config // { configurable: { thread_id: "1", checkpoint_id: "0c62ca34-ac19-445d-bbb0-5b4984975b2a" } } // also valid config const config = { configurable: { thread_id: "1" } }; await graph.invoke(inputs, config); ``` Importantly, LangGraph knows whether a particular checkpoint has been executed previously. If it has, LangGraph simply *re-plays* that particular step in the graph and does not re-execute the step. See this [how to guide on time-travel to learn more about replaying](/langgraphjs/how-tos/time-travel). ![Replay](./img/persistence/re_play.jpg) ### Update state In addition to re-playing the graph from specific `checkpoints`, we can also *edit* the graph state. We do this using `graph.updateState()`. This method three different arguments: #### `config` The config should contain `thread_id` specifying which thread to update. When only the `thread_id` is passed, we update (or fork) the current state. Optionally, if we include `checkpoint_id` field, then we fork that selected checkpoint. #### `values` These are the values that will be used to update the state. Note that this update is treated exactly as any update from a node is treated. This means that these values will be passed to the [reducer](/langgraphjs/concepts/low_level#reducers) functions, if they are defined for some of the channels in the graph state. This means that `updateState` does NOT automatically overwrite the channel values for every channel, but only for the channels without reducers. Let's walk through an example. Let's assume you have defined the state of your graph with the following schema (see full example above): ```typescript import { Annotation } from "@langchain/langgraph"; const GraphAnnotation = Annotation.Root({ foo: Annotation bar: Annotation({ reducer: (a, b) => [...a, ...b], default: () => [], }) }); ``` Let's now assume the current state of the graph is ``` { foo: "1", bar: ["a"] } ``` If you update the state as below: ```typescript await graph.updateState(config, { foo: "2", bar: ["b"] }); ``` Then the new state of the graph will be: ``` { foo: "2", bar: ["a", "b"] } ``` The `foo` key (channel) is completely changed (because there is no reducer specified for that channel, so `updateState` overwrites it). However, there is a reducer specified for the `bar` key, and so it appends `"b"` to the state of `bar`. #### As Node The final argument you can optionally specify when calling `updateState` is the third positional `asNode` argument. If you provided it, the update will be applied as if it came from node `asNode`. If `asNode` is not provided, it will be set to the last node that updated the state, if not ambiguous. The reason this matters is that the next steps to execute depend on the last node to have given an update, so this can be used to control which node executes next. See this [how to guide on time-travel to learn more about forking state](/langgraphjs/how-tos/time-travel). ![Update](img/persistence/checkpoints_full_story.jpg) ## Memory Store ![Update](img/persistence/shared_state.png) A [state schema](low_level.md#state) specifies a set of keys that are populated as a graph is executed. As discussed above, state can be written by a checkpointer to a thread at each graph step, enabling state persistence. But, what if we want to retrain some information *across threads*? Consider the case of a chatbot where we want to retain specific information about the user across *all* chat conversations (e.g., threads) with that user! With checkpointers alone, we cannot share information across threads. This motivates the need for the `Store` interface. As an illustration, we can define an `InMemoryStore` to store information about a user across threads. First, let's showcase this in isolation without using LangGraph. ```ts import { InMemoryStore } from "@langchain/langgraph"; const inMemoryStore = new InMemoryStore(); ``` Memories are namespaced by a `tuple`, which in this specific example will be `[, "memories"]`. The namespace can be any length and represent anything, does not have be user specific. ```ts const userId = "1"; const namespaceForMemory = [userId, "memories"]; ``` We use the `store.put` method to save memories to our namespace in the store. When we do this, we specify the namespace, as defined above, and a key-value pair for the memory: the key is simply a unique identifier for the memory (`memoryId`) and the value (an object) is the memory itself. ```ts import { v4 as uuid4 } from 'uuid'; const memoryId = uuid4(); const memory = { food_preference: "I like pizza" }; await inMemoryStore.put(namespaceForMemory, memoryId, memory); ``` We can read out memories in our namespace using `store.search`, which will return all memories for a given user as a list. The most recent memory is the last in the list. ```ts const memories = await inMemoryStore.search(namespaceForMemory); console.log(memories.at(-1)); /* { 'value': {'food_preference': 'I like pizza'}, 'key': '07e0caf4-1631-47b7-b15f-65515d4c1843', 'namespace': ['1', 'memories'], 'created_at': '2024-10-02T17:22:31.590602+00:00', 'updated_at': '2024-10-02T17:22:31.590605+00:00' } */ ``` The attributes a retrieved memory has are: - `value`: The value (itself a dictionary) of this memory - `key`: The UUID for this memory in this namespace - `namespace`: A list of strings, the namespace of this memory type - `created_at`: Timestamp for when this memory was created - `updated_at`: Timestamp for when this memory was updated With this all in place, we use the `inMemoryStore` in LangGraph. The `inMemoryStore` works hand-in-hand with the checkpointer: the checkpointer saves state to threads, as discussed above, and the the `inMemoryStore` allows us to store arbitrary information for access *across* threads. We compile the graph with both the checkpointer and the `inMemoryStore` as follows. ```ts import { MemorySaver } from "@langchain/langgraph"; // We need this because we want to enable threads (conversations) const checkpointer = new MemorySaver(); // ... Define the graph ... // Compile the graph with the checkpointer and store const graph = builder.compile({ checkpointer, store: inMemoryStore }); ``` We invoke the graph with a `thread_id`, as before, and also with a `user_id`, which we'll use to namespace our memories to this particular user as we showed above. ```ts // Invoke the graph const user_id = "1"; const config = { configurable: { thread_id: "1", user_id } }; // First let's just say hi to the AI const stream = await graph.stream( { messages: [{ role: "user", content: "hi" }] }, { ...config, streamMode: "updates" }, ); for await (const update of stream) { console.log(update); } ``` We can access the `inMemoryStore` and the `user_id` in *any node* by passing `config: LangGraphRunnableConfig` as a node argument. Then, just as we saw above, simply use the `put` method to save memories to the store. ```ts import { type LangGraphRunnableConfig, MessagesAnnotation, } from "@langchain/langgraph"; const updateMemory = async ( state: typeof MessagesAnnotation.State, config: LangGraphRunnableConfig ) => { // Get the store instance from the config const store = config.store; // Get the user id from the config const userId = config.configurable.user_id; // Namespace the memory const namespace = [userId, "memories"]; // ... Analyze conversation and create a new memory // Create a new memory ID const memoryId = uuid4(); // We create a new memory await store.put(namespace, memoryId, { memory }); }; ``` As we showed above, we can also access the store in any node and use `search` to get memories. Recall the the memories are returned as a list of objects that can be converted to a dictionary. ```ts const memories = inMemoryStore.search(namespaceForMemory); console.log(memories.at(-1)); /* { 'value': {'food_preference': 'I like pizza'}, 'key': '07e0caf4-1631-47b7-b15f-65515d4c1843', 'namespace': ['1', 'memories'], 'created_at': '2024-10-02T17:22:31.590602+00:00', 'updated_at': '2024-10-02T17:22:31.590605+00:00' } */ ``` We can access the memories and use them in our model call. ```ts const callModel = async ( state: typeof StateAnnotation.State, config: LangGraphRunnableConfig ) => { const store = config.store; // Get the user id from the config const userId = config.configurable.user_id; // Get the memories for the user from the store const memories = await store.search([userId, "memories"]); const info = memories.map((memory) => { return JSON.stringify(memory.value); }).join("\n"); // ... Use memories in the model call } ``` If we create a new thread, we can still access the same memories so long as the `user_id` is the same. ```ts // Invoke the graph const config = { configurable: { thread_id: "2", user_id: "1" } }; // Let's say hi again const stream = await graph.stream( { messages: [{ role: "user", content: "hi, tell me about my memories" }] }, { ...config, streamMode: "updates" }, ); for await (const update of stream) { console.log(update); } ``` When we use the LangGraph API, either locally (e.g., in LangGraph Studio) or with LangGraph Cloud, the memory store is available to use by default and does not need to be specified during graph compilation. ## Checkpointer libraries Under the hood, checkpointing is powered by checkpointer objects that conform to [BaseCheckpointSaver](/langgraphjs/reference/classes/checkpoint.BaseCheckpointSaver.html) interface. LangGraph provides several checkpointer implementations, all implemented via standalone, installable libraries: * `@langchain/langgraph-checkpoint`: The base interface for checkpointer savers ([BaseCheckpointSaver](/langgraphjs/reference/classes/checkpoint.BaseCheckpointSaver.html)) and serialization/deserialization interface ([SerializerProtocol](/langgraphjs/reference/interfaces/checkpoint.SerializerProtocol.html)). Includes in-memory checkpointer implementation ([MemorySaver](/langgraphjs/reference/classes/checkpoint.MemorySaver.html)) for experimentation. LangGraph comes with `@langchain/langgraph-checkpoint` included. * `@langchain/langgraph-checkpoint-sqlite`: An implementation of LangGraph checkpointer that uses SQLite database ([SqliteSaver](/langgraphjs/reference/classes/checkpoint_sqlite.SqliteSaver.html)). Ideal for experimentation and local workflows. Needs to be installed separately. * `@langchain/langgraph-checkpoint-postgres`: An advanced checkpointer that uses a Postgres database ([PostgresSaver](/langgraphjs/reference/classes/checkpoint_postgres.PostgresSaver.html)), used in LangGraph Cloud. Ideal for using in production. Needs to be installed separately. ### Checkpointer interface Each checkpointer conforms to [BaseCheckpointSaver](/langgraphjs/reference/classes/checkpoint.BaseCheckpointSaver.html) interface and implements the following methods: * `.put` - Store a checkpoint with its configuration and metadata. * `.putWrites` - Store intermediate writes linked to a checkpoint (i.e. [pending writes](#pending-writes)). * `.getTuple` - Fetch a checkpoint tuple using for a given configuration (`thread_id` and `checkpoint_id`). This is used to populate `StateSnapshot` in `graph.getState()`. * `.list` - List checkpoints that match a given configuration and filter criteria. This is used to populate state history in `graph.getStateHistory()` ### Serializer When checkpointers save the graph state, they need to serialize the channel values in the state. This is done using serializer objects. `@langchain/langgraph-checkpoint` defines a [protocol](/langgraphjs/reference/interfaces/checkpoint.SerializerProtocol.html) for implementing serializers and a default implementation that handles a wide variety of types, including LangChain and LangGraph primitives, datetimes, enums and more. ## Capabilities ### Human-in-the-loop First, checkpointers facilitate [human-in-the-loop workflows](/langgraphjs/concepts/agentic_concepts#human-in-the-loop) workflows by allowing humans to inspect, interrupt, and approve graph steps. Checkpointers are needed for these workflows as the human has to be able to view the state of a graph at any point in time, and the graph has to be to resume execution after the human has made any updates to the state. See [these how-to guides](/langgraphjs/how-tos/breakpoints) for concrete examples. ### Memory Second, checkpointers allow for ["memory"](/langgraphjs/concepts/agentic_concepts#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](/langgraphjs/how-tos/manage-conversation-history) for an end-to-end example on how to add and manage conversation memory using checkpointers. ### Time Travel Third, checkpointers allow for ["time travel"](/langgraphjs/how-tos/time-travel), allowing users to replay prior graph executions to review and / or debug specific graph steps. In addition, checkpointers make it possible to fork the graph state at arbitrary checkpoints to explore alternative trajectories. ### Fault-tolerance Lastly, checkpointing also provides fault-tolerance and error recovery: if one or more nodes fail at a given superstep, you can restart your graph from the last successful step. Additionally, when a graph node fails mid-execution at a given superstep, LangGraph stores pending checkpoint writes from any other nodes that completed successfully at that superstep, so that whenever we resume graph execution from that superstep we don't re-run the successful nodes. #### Pending writes Additionally, when a graph node fails mid-execution at a given superstep, LangGraph stores pending checkpoint writes from any other nodes that completed successfully at that superstep, so that whenever we resume graph execution from that superstep we don't re-run the successful nodes. --- concepts/functional_api.md --- # Functional API !!! note Compatibility The Functional API requires `@langchain/langgraph>=0.2.42`. ## 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. 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. The **Functional API** uses two key building blocks: - **`entrypoint`** – An **entrypoint** is a wrapper that takes a function as the starting point of a workflow. It encapsulates workflow logic and manages execution flow, including handling _long-running tasks_ and [interrupts](human_in_the_loop.md). - **`task`** – Represents a discrete unit of work, such as an API call or data processing step, that can be executed asynchronously within an entrypoint. Tasks return a future-like object that can be awaited or resolved synchronously. This provides a minimal abstraction for building workflows with state management and streaming. !!! tip For users who prefer a more declarative approach, LangGraph's [Graph API](./low_level.md) allows you to define workflows using a Graph paradigm. Both APIs share the same underlying runtime, so you can use them together in the same application. Please see the [Functional API vs. Graph API](#functional-api-vs-graph-api) section for a comparison of the two paradigms. ## Example Below we demonstrate a simple application that writes an essay and [interrupts](human_in_the_loop.md) to request human review. ```typescript import { task, entrypoint, interrupt, MemorySaver } from "@langchain/langgraph"; const writeEssay = task("write_essay", (topic: string): string => { // A placeholder for a long-running task. return `An essay about topic: ${topic}`; }); const workflow = entrypoint( { checkpointer: new MemorySaver(), name: "workflow" }, async (topic: string) => { const essay = await writeEssay(topic); const isApproved = interrupt({ // Any json-serializable payload provided to interrupt as argument. // It will be surfaced on the client side as an Interrupt when streaming data // from the workflow. essay, // The essay we want reviewed. // We can add any additional information that we need. // For example, introduce a key called "action" with some instructions. action: "Please approve/reject the essay", }); return { essay, // The essay that was generated isApproved, // Response from HIL }; } ); ``` ??? example "Detailed Explanation" This workflow will write an essay about the topic "cat" and then pause to get a review from a human. The workflow can be interrupted for an indefinite amount of time until a review is provided. When the workflow is resumed, it executes from the very start, but because the result of the `writeEssay` task was already saved, the task result will be loaded from the checkpoint instead of being recomputed. ```typescript import { task, entrypoint, interrupt, MemorySaver, Command } from "@langchain/langgraph"; const writeEssay = task("write_essay", (topic: string): string => { return `An essay about topic: ${topic}`; }); const workflow = entrypoint( { checkpointer: new MemorySaver(), name: "workflow" }, async (topic: string) => { const essay = await writeEssay(topic); const isApproved = interrupt({ essay, // The essay we want reviewed. action: "Please approve/reject the essay", }); return { essay, isApproved, }; } ); const threadId = crypto.randomUUID(); const config = { configurable: { thread_id: threadId, }, }; for await (const item of await workflow.stream("cat", config)) { console.log(item); } ``` ```typescript { write_essay: 'An essay about topic: cat' } { __interrupt__: [{ value: { essay: 'An essay about topic: cat', action: 'Please approve/reject the essay' }, resumable: true, ns: ['workflow:f7b8508b-21c0-8b4c-5958-4e8de74d2684'], when: 'during' }] } ``` An essay has been written and is ready for review. Once the review is provided, we can resume the workflow: ```typescript // Get review from a user (e.g., via a UI) // In this case, we're using a bool, but this can be any json-serializable value. const humanReview = true; for await (const item of await workflow.stream(new Command({ resume: humanReview }), config)) { console.log(item); } ``` ```typescript { workflow: { essay: 'An essay about topic: cat', isApproved: true } } ``` The workflow has been completed and the review has been added to the essay. ## Entrypoint The [`entrypoint`](/langgraphjs/reference/functions/langgraph.entrypoint-1.html) function can be used to create a workflow from a function. It encapsulates workflow logic and manages execution flow, including handling _long-running tasks_ and [interrupts](./low_level.md#interrupt). ### Definition An **entrypoint** is defined by passing a function to the `entrypoint` function. The function **must accept a single positional argument**, which serves as the workflow input. If you need to pass multiple pieces of data, use an object as the input type for the first argument. You will often want to pass a **checkpointer** to the `entrypoint` function to enable persistence and use features like **human-in-the-loop**. ```typescript import { entrypoint, MemorySaver } from "@langchain/langgraph"; const checkpointer = new MemorySaver(); const myWorkflow = entrypoint( { checkpointer, name: "myWorkflow" }, async (someInput: Record): Promise => { // some logic that may involve long-running tasks like API calls, // and may be interrupted for human-in-the-loop. return result; } ); ``` !!! important "Serialization" The **inputs** and **outputs** of entrypoints must be JSON-serializable to support checkpointing. Please see the [serialization](#serialization) section for more details. ### Injectable Parameters When declaring an `entrypoint`, you can access additional parameters that will be injected automatically at run time by using the [`getPreviousState`](/langgraphjs/reference/functions/langgraph.getPreviousState.html) function and other utilities. These parameters include: | Parameter | Description | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **config** | For accessing runtime configuration. Automatically populated as the second argument to the `entrypoint` function (but not `task`, since tasks can have a variable number of arguments). See [RunnableConfig](https://js.langchain.com/docs/concepts/runnables/#runnableconfig) for information. | | **config.store** | An instance of [BaseStore](/langgraphjs/reference/classes/checkpoint.BaseStore.html). Useful for [long-term memory](#long-term-memory). | | **config.writer** | A `writer` used for streaming back custom data. See the [guide on streaming custom data](../how-tos/streaming-content.ipynb) | | **getPreviousState()** | Access the state associated with the previous `checkpoint` for the given thread using [`getPreviousState`](/langgraphjs/reference/functions/langgraph.getPreviousState.html). See [state management](#state-management). | ??? example "Requesting Injectable Parameters" ```typescript import { entrypoint, getPreviousState, BaseStore, InMemoryStore, } from "@langchain/langgraph"; import { RunnableConfig } from "@langchain/core/runnables"; const inMemoryStore = new InMemoryStore(...); // An instance of InMemoryStore for long-term memory const myWorkflow = entrypoint( { checkpointer, // Specify the checkpointer store: inMemoryStore, // Specify the store name: "myWorkflow", }, async (someInput: Record) => { const previous = getPreviousState(); // For short-term memory // Rest of workflow logic... } ); ``` ### Executing Using the [`entrypoint`](#entrypoint) function will return an object that can be executed using the `invoke` and `stream` methods. === "Invoke" ```typescript const config = { configurable: { thread_id: "some_thread_id", }, }; await myWorkflow.invoke(someInput, config); // Wait for the result ``` === "Stream" ```typescript const config = { configurable: { thread_id: "some_thread_id", }, }; for await (const chunk of await myWorkflow.stream(someInput, config)) { console.log(chunk); } ``` ### Resuming Resuming an execution after an [interrupt](/langgraphjs/reference/functions/langgraph.interrupt-1.html) can be done by passing a **resume** value to the [Command](/langgraphjs/reference/classes/langgraph.Command.html) primitive. === "Invoke" ```typescript import { Command } from "@langchain/langgraph"; const config = { configurable: { thread_id: "some_thread_id", }, }; await myWorkflow.invoke(new Command({ resume: someResumeValue }), config); ``` === "Stream" ```typescript import { Command } from "@langchain/langgraph"; const config = { configurable: { thread_id: "some_thread_id", }, }; const stream = await myWorkflow.stream( new Command({ resume: someResumeValue }), config, ); for await (const chunk of stream) { console.log(chunk); } ``` **Resuming after transient error** To resume after a transient error (such as a model provider outage), run the `entrypoint` with a `null` and the same **thread id** (config). This assumes that the underlying **error** has been resolved and execution can proceed successfully. === "Invoke" ```typescript const config = { configurable: { thread_id: "some_thread_id", }, }; await myWorkflow.invoke(null, config); ``` === "Stream" ```typescript const config = { configurable: { thread_id: "some_thread_id", }, }; for await (const chunk of await myWorkflow.stream(null, config)) { console.log(chunk); } ``` ### State Management When an `entrypoint` is defined with a `checkpointer`, it stores information between successive invocations on the same **thread id** in [checkpoints](persistence.md#checkpoints). This allows accessing the state from the previous invocation using the [`getPreviousState`](/langgraphjs/reference/functions/langgraph.getPreviousState.html) function. By default, the previous state is the return value of the previous invocation. ```typescript const myWorkflow = entrypoint( { checkpointer, name: "myWorkflow" }, async (number: number) => { const previous = getPreviousState(); return number + (previous ?? 0); } ); const config = { configurable: { thread_id: "some_thread_id", }, }; await myWorkflow.invoke(1, config); // 1 (previous was undefined) await myWorkflow.invoke(2, config); // 3 (previous was 1 from the previous invocation) ``` #### `entrypoint.final` [entrypoint.final](/langgraphjs/reference/functions/langgraph.entrypoint.final.html) is a special primitive that can be returned from an entrypoint and allows **decoupling** the value that is **saved in the checkpoint** from the **return value of the entrypoint**. The first value is the return value of the entrypoint, and the second value is the value that will be saved in the checkpoint. ```typescript const myWorkflow = entrypoint( { checkpointer, name: "myWorkflow" }, async (number: number) => { const previous = getPreviousState(); // This will return the previous value to the caller, saving // 2 * number to the checkpoint, which will be used in the next invocation // for the previous state return entrypoint.final({ value: previous ?? 0, save: 2 * number, }); } ); const config = { configurable: { thread_id: "1", }, }; await myWorkflow.invoke(3, config); // 0 (previous was undefined) await myWorkflow.invoke(1, config); // 6 (previous was 3 * 2 from the previous invocation) ``` ## Task A **task** represents a discrete unit of work, such as an API call or data processing step. It has three key characteristics: - **Asynchronous Execution**: Tasks are designed to be executed asynchronously, allowing multiple operations to run concurrently without blocking. - **Checkpointing**: Task results are saved to a checkpoint, enabling resumption of the workflow from the last saved state. (See [persistence](persistence.md) for more details). - **Retries**: Tasks can be configured with a [retry policy](./low_level.md#retry-policies) to handle transient errors. ### Definition Tasks are defined using the `task` function, which wraps a regular function. ```typescript import { task } from "@langchain/langgraph"; const slowComputation = task({"slowComputation", async (inputValue: any) => { // Simulate a long-running operation ... return result; }); ``` !!! important "Serialization" The **outputs** of tasks **must** be JSON-serializable to support checkpointing. ### Execution **Tasks** can only be called from within an **entrypoint**, another **task**, or a [state graph node](./low_level.md#nodes). Tasks _cannot_ be called directly from the main application code. ```typescript const myWorkflow = entrypoint( { checkpointer, name: "myWorkflow" }, async (someInput: number) => { return await slowComputation(someInput); } ); ``` ### Retry Policy You can specify a [retry policy](./low_level.md#retry-policies) for a **task** by passing a `retry` parameter to the `task` function. ```typescript const slowComputation = task( { name: "slowComputation", // only attempt to run this task once before giving up retry: { maxAttempts: 1 }, }, async (inputValue: any) => { // A long-running operation that may fail return result; } ); ``` ## When to use a task **Tasks** are useful in the following scenarios: - **Checkpointing**: When you need to save the result of a long-running operation to a checkpoint, so you don't need to recompute it when resuming the workflow. - **Human-in-the-loop**: If you're building a workflow that requires human intervention, you MUST use **tasks** to encapsulate any randomness (e.g., API calls) to ensure that the workflow can be resumed correctly. See the [determinism](#determinism) section for more details. - **Parallel Execution**: For I/O-bound tasks, **tasks** enable parallel execution, allowing multiple operations to run concurrently without blocking (e.g., calling multiple APIs). - **Observability**: Wrapping operations in **tasks** provides a way to track the progress of the workflow and monitor the execution of individual operations using [LangSmith](https://docs.smith.langchain.com/). - **Retryable Work**: When work needs to be retried to handle failures or inconsistencies, **tasks** provide a way to encapsulate and manage the retry logic. ## Serialization There are two key aspects to serialization in LangGraph: 1. `entrypoint` inputs and outputs must be JSON-serializable. 2. `task` outputs must be JSON-serializable. These requirements are necessary for enabling checkpointing and workflow resumption. Use JavaScript primitives like objects, arrays, strings, numbers, and booleans to ensure that your inputs and outputs are serializable. Serialization ensures that workflow state, such as task results and intermediate values, can be reliably saved and restored. This is critical for enabling human-in-the-loop interactions, fault tolerance, and parallel execution. Providing non-serializable inputs or outputs will result in a runtime error when a workflow is configured with a checkpointer. ## Determinism To utilize features like **human-in-the-loop**, any randomness should be encapsulated inside of **tasks**. This guarantees that when execution is halted (e.g., for human in the loop) and then resumed, it will follow the same _sequence of steps_, even if **task** results are non-deterministic. LangGraph achieves this behavior by persisting **task** and [**subgraph**](./low_level.md#subgraphs) results as they execute. A well-designed workflow ensures that resuming execution follows the _same sequence of steps_, allowing previously computed results to be retrieved correctly without having to re-execute them. This is particularly useful for long-running **tasks** or **tasks** with non-deterministic results, as it avoids repeating previously done work and allows resuming from essentially the same While different runs of a workflow can produce different results, resuming a **specific** run should always follow the same sequence of recorded steps. This allows LangGraph to efficiently look up **task** and **subgraph** results that were executed prior to the graph being interrupted and avoid recomputing them. ## Idempotency Idempotency ensures that running the same operation multiple times produces the same result. This helps prevent duplicate API calls and redundant processing if a step is rerun due to a failure. Always place API calls inside **tasks** functions for checkpointing, and design them to be idempotent in case of re-execution. Re-execution can occur if a **task** starts, but does not complete successfully. Then, if the workflow is resumed, the **task** will run again. Use idempotency keys or verify existing results to avoid duplication. ## Functional API vs. Graph API The **Functional API** and the [Graph APIs (StateGraph)](./low_level.md#stategraph) provide two different paradigms to create in LangGraph. Here are some key differences: - **Control flow**: The Functional API does not require thinking about graph structure. You can use standard Python constructs to define workflows. This will usually trim the amount of code you need to write. - **State management**: The **GraphAPI** requires declaring a [**State**](./low_level.md#state) and may require defining [**reducers**](./low_level.md#reducers) to manage updates to the graph state. `@entrypoint` and `@tasks` do not require explicit state management as their state is scoped to the function and is not shared across functions. - **Checkpointing**: Both APIs generate and use checkpoints. In the **Graph API** a new checkpoint is generated after every [superstep](./low_level.md). In the **Functional API**, when tasks are executed, their results are saved to an existing checkpoint associated with the given entrypoint instead of creating a new checkpoint. - **Visualization**: The Graph API makes it easy to visualize the workflow as a graph which can be useful for debugging, understanding the workflow, and sharing with others. The Functional API does not support visualization as the graph is dynamically generated during runtime. ## Common Pitfalls ### Handling side effects Encapsulate side effects (e.g., writing to a file, sending an email) in tasks to ensure they are not executed multiple times when resuming a workflow. === "Incorrect" In this example, a side effect (writing to a file) is directly included in the workflow, so it will be executed a second time when resuming the workflow. ```typescript hl_lines="6" const myWorkflow = entrypoint( { checkpointer, name: "myWorkflow" }, async (inputs: Record) => { // This code will be executed a second time when resuming the workflow. // Which is likely not what you want. await fs.writeFile("output.txt", "Side effect executed"); const value = interrupt("question"); return value; } ); ``` === "Correct" In this example, the side effect is encapsulated in a task, ensuring consistent execution upon resumption. ```typescript hl_lines="3" import { task } from "@langchain/langgraph"; const writeToFile = task("writeToFile", async () => { await fs.writeFile("output.txt", "Side effect executed"); }); const myWorkflow = entrypoint( { checkpointer, name: "myWorkflow" }, async (inputs: Record) => { // The side effect is now encapsulated in a task. await writeToFile(); const value = interrupt("question"); return value; } ); ``` ### Non-deterministic control flow Operations that might give different results each time (like getting current time or random numbers) should be encapsulated in tasks to ensure that on resume, the same result is returned. - In a task: Get random number (5) → interrupt → resume → (returns 5 again) → ... - Not in a task: Get random number (5) → interrupt → resume → get new random number (7) → ... This is especially important when using **human-in-the-loop** workflows with multiple interrupts calls. LangGraph keeps a list of resume values for each task/entrypoint. When an interrupt is encountered, it's matched with the corresponding resume value. This matching is strictly **index-based**, so the order of the resume values should match the order of the interrupts. If order of execution is not maintained when resuming, one `interrupt` call may be matched with the wrong `resume` value, leading to incorrect results. Please read the section on [determinism](#determinism) for more details. === "Incorrect" In this example, the workflow uses the current time to determine which task to execute. This is non-deterministic because the result of the workflow depends on the time at which it is executed. ```typescript hl_lines="4" const myWorkflow = entrypoint( { checkpointer, name: "myWorkflow" }, async (inputs: { t0: number }) => { const t1 = Date.now(); const deltaT = t1 - inputs.t0; if (deltaT > 1000) { const result = await slowTask(1); const value = interrupt("question"); return { result, value }; } else { const result = await slowTask(2); const value = interrupt("question"); return { result, value }; } } ); ``` === "Correct" In this example, the workflow uses the input `t0` to determine which task to execute. This is deterministic because the result of the workflow depends only on the input. ```typescript hl_lines="3 8" import { task } from "@langchain/langgraph"; const getTime = task("getTime", () => Date.now()); const myWorkflow = entrypoint( { checkpointer, name: "myWorkflow" }, async (inputs: { t0: number }) => { const t1 = await getTime(); const deltaT = t1 - inputs.t0; if (deltaT > 1000) { const result = await slowTask(1); const value = interrupt("question"); return { result, value }; } else { const result = await slowTask(2); const value = interrupt("question"); return { result, value }; } } ); ``` ## Patterns Below are a few simple patterns that show examples of **how to** use the **Functional API**. When defining an `entrypoint`, input is restricted to the first argument of the function. To pass multiple inputs, you can use an object. ```typescript const myWorkflow = entrypoint( { checkpointer, name: "myWorkflow" }, async (inputs: { value: number; anotherValue: number }) => { const value = inputs.value; const anotherValue = inputs.anotherValue; ... } ); await myWorkflow.invoke([{ value: 1, anotherValue: 2 }]); ``` ### Parallel execution Tasks can be executed in parallel by invoking them concurrently and waiting for the results. This is useful for improving performance in IO bound tasks (e.g., calling APIs for LLMs). ```typescript const addOne = task("addOne", (number: number) => number + 1); const graph = entrypoint( { checkpointer, name: "graph" }, async (numbers: number[]) => { return await Promise.all(numbers.map(addOne)); } ); ``` ### Calling subgraphs The **Functional API** and the [**Graph API**](./low_level.md) can be used together in the same application as they share the same underlying runtime. ```typescript import { entrypoint, StateGraph } from "@langchain/langgraph"; const builder = new StateGraph(); ... const someGraph = builder.compile(); const someWorkflow = entrypoint( { name: "someWorkflow" }, async (someInput: Record) => { // Call a graph defined using the graph API const result1 = await someGraph.invoke(...); // Call another graph defined using the graph API const result2 = await anotherGraph.invoke(...); return { result1, result2, }; } ); ``` ### Calling other entrypoints You can call other **entrypoints** from within an **entrypoint** or a **task**. ```typescript const someOtherWorkflow = entrypoint( { name: "someOtherWorkflow" }, // Will automatically use the checkpointer from the parent entrypoint async (inputs: { value: number }) => { return inputs.value; } ); const myWorkflow = entrypoint( { checkpointer, name: "myWorkflow" }, async (inputs: Record) => { const value = await someOtherWorkflow.invoke([{ value: 1 }]); return value; } ); ``` ### Streaming custom data You can stream custom data from an **entrypoint** by using the `write` method on `config`. This allows you to write custom data to the `custom` stream. ```typescript import { entrypoint, task, MemorySaver, LangGraphRunnableConfig, } from "@langchain/langgraph"; const addOne = task("addOne", (x: number) => x + 1); const addTwo = task("addTwo", (x: number) => x + 2); const checkpointer = new MemorySaver(); const main = entrypoint( { checkpointer, name: "main" }, async (inputs: { number: number }, config: LangGraphRunnableConfig) => { config.writer?.("hello"); // Write some data to the `custom` stream await addOne(inputs.number); // Will write data to the `updates` stream config.writer?.("world"); // Write some more data to the `custom` stream await addTwo(inputs.number); // Will write data to the `updates` stream return 5; } ); const config = { configurable: { thread_id: "1", }, }; const stream = await main.stream( { number: 1 }, { streamMode: ["custom", "updates"], ...config } ); for await (const chunk of stream) { console.log(chunk); } ``` ```typescript ["updates", { addOne: 2 }][("updates", { addTwo: 3 })][("custom", "hello")][ ("custom", "world") ][("updates", { main: 5 })]; ``` ### Resuming after an error ```typescript import { entrypoint, task, MemorySaver } from "@langchain/langgraph"; // Global variable to track the number of attempts let attempts = 0; const getInfo = task("getInfo", () => { /* * Simulates a task that fails once before succeeding. * Throws an error on the first attempt, then returns "OK" on subsequent tries. */ attempts += 1; if (attempts < 2) { throw new Error("Failure"); // Simulate a failure on the first attempt } return "OK"; }); // Initialize an in-memory checkpointer for persistence const checkpointer = new MemorySaver(); const slowTask = task("slowTask", async () => { /* * Simulates a slow-running task by introducing a 1-second delay. */ await new Promise((resolve) => setTimeout(resolve, 1000)); return "Ran slow task."; }); const main = entrypoint( { checkpointer, name: "main" }, async (inputs: Record) => { /* * Main workflow function that runs the slowTask and getInfo tasks sequentially. * * Parameters: * - inputs: Record containing workflow input values. * * The workflow first executes `slowTask` and then attempts to execute `getInfo`, * which will fail on the first invocation. */ const slowTaskResult = await slowTask(); // Blocking call to slowTask await getInfo(); // Error will be thrown here on the first attempt return slowTaskResult; } ); // Workflow execution configuration with a unique thread identifier const config = { configurable: { thread_id: "1", // Unique identifier to track workflow execution }, }; // This invocation will take ~1 second due to the slowTask execution try { // First invocation will throw an error due to the `getInfo` task failing await main.invoke({ anyInput: "foobar" }, config); } catch (err) { // Handle the failure gracefully } ``` When we resume execution, we won't need to re-run the `slowTask` as its result is already saved in the checkpoint. ```typescript await main.invoke(null, config); ``` ```typescript "Ran slow task."; ``` ### Human-in-the-loop The functional API supports [human-in-the-loop](human_in_the_loop.md) workflows using the `interrupt` function and the `Command` primitive. Please see the following examples for more details: - [How to wait for user input (Functional API)](../how-tos/wait-user-input-functional.ipynb): Shows how to implement a simple human-in-the-loop workflow using the functional API. - [How to review tool calls (Functional API)](../how-tos/review-tool-calls-functional.ipynb): Guide demonstrates how to implement human-in-the-loop workflows in a ReAct agent using the LangGraph Functional API. ### Short-term memory [State management](#state-management) using the [`getPreviousState`](/langgraphjs/reference/functions/langgraph.getPreviousState.html) function and optionally using the `entrypoint.final` primitive can be used to implement [short term memory](memory.md). Please see the following how-to guides for more details: - [How to add thread-level persistence (functional API)](../how-tos/persistence-functional.ipynb): Shows how to add thread-level persistence to a functional API workflow and implements a simple chatbot. ### Long-term memory [long-term memory](memory.md#long-term-memory) allows storing information across different **thread ids**. This could be useful for learning information about a given user in one conversation and using it in another. Please see the following how-to guides for more details: - [How to add cross-thread persistence (functional API)](../how-tos/cross-thread-persistence-functional.ipynb): Shows how to add cross-thread persistence to a functional API workflow and implements a simple chatbot. ### Workflows - [Workflows and agent](../tutorials/workflows/index.md) guide for more examples of how to build workflows using the Functional API. ### Agents - [How to create a React agent from scratch (Functional API)](../how-tos/react-agent-from-scratch-functional.ipynb): Shows how to create a simple React agent from scratch using the functional API. - [How to build a multi-agent network](../how-tos/multi-agent-network-functional.ipynb): Shows how to build a multi-agent network using the functional API. - [How to add multi-turn conversation in a multi-agent application (functional API)](../how-tos/multi-agent-multi-turn-convo-functional.ipynb): allow an end-user to engage in a multi-turn conversation with one or more agents. --- concepts/application_structure.md --- # Application Structure !!! info "Prerequisites" - [LangGraph Server](./langgraph_server.md) - [LangGraph Glossary](./low_level.md) ## Overview A LangGraph application consists of one or more graphs, a LangGraph API Configuration file (`langgraph.json`), a file that specifies dependencies, and an optional .env file that specifies environment variables. This guide shows a typical structure for a LangGraph application and shows how the required information to deploy a LangGraph application using the LangGraph Platform is specified. ## Key Concepts To deploy using the LangGraph Platform, the following information should be provided: 1. A [LangGraph API Configuration file](#configuration-file) (`langgraph.json`) that specifies the dependencies, graphs, environment variables to use for the application. 2. The [graphs](#graphs) that implement the logic of the application. 3. A file that specifies [dependencies](#dependencies) required to run the application. 4. [Environment variable](#environment-variables) that are required for the application to run. ## File Structure Below are examples of directory structures for Python and JavaScript applications: === "JS (package.json)" ```plaintext my-app/ ├── src # all project code lies within here │ ├── utils # optional utilities for your graph │ │ ├── tools.ts # tools for your graph │ │ ├── nodes.ts # node functions for you graph │ │ └── state.ts # state definition of your graph │ └── agent.ts # code for constructing your graph ├── package.json # package dependencies ├── .env # environment variables └── langgraph.json # configuration file for LangGraph ``` === "Python (requirements.txt)" ```plaintext my-app/ ├── my_agent # all project code lies within here │ ├── utils # utilities for your graph │ │ ├── __init__.py │ │ ├── tools.py # tools for your graph │ │ ├── nodes.py # node functions for you graph │ │ └── state.py # state definition of your graph │ ├── requirements.txt # package dependencies │ ├── __init__.py │ └── agent.py # code for constructing your graph ├── .env # environment variables └── langgraph.json # configuration file for LangGraph ``` === "Python (pyproject.toml)" ```plaintext my-app/ ├── my_agent # all project code lies within here │ ├── utils # utilities for your graph │ │ ├── __init__.py │ │ ├── tools.py # tools for your graph │ │ ├── nodes.py # node functions for you graph │ │ └── state.py # state definition of your graph │ ├── __init__.py │ └── agent.py # code for constructing your graph ├── .env # environment variables ├── langgraph.json # configuration file for LangGraph └── pyproject.toml # dependencies for your project ``` !!! note The directory structure of a LangGraph application can vary depending on the programming language and the package manager used. ## Configuration File The `langgraph.json` file is a JSON file that specifies the dependencies, graphs, environment variables, and other settings required to deploy a LangGraph application. The file supports specification of the following information: | Key | Description | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `dependencies` | **Required**. Array of dependencies for LangGraph API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. | | `graphs` | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example:
  • `./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`
  • `./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.
| | `env` | Path to `.env` file or a mapping from environment variable to its value. | | `node_version` | Defaults to `20`. | | `dockerfile_lines` | Array of additional lines to add to Dockerfile following the import from parent image. | !!! tip The LangGraph CLI defaults to using the configuration file **langgraph.json** in the current directory. ### Examples === "JavaScript" * The dependencies will be loaded from a dependency file in the local directory (e.g., `package.json`). * A single graph will be loaded from the file `./your_package/your_file.js` with the function `agent`. * The environment variable `OPENAI_API_KEY` is set inline. ```json { "dependencies": [ "." ], "graphs": { "my_agent": "./your_package/your_file.js:agent" }, "env": { "OPENAI_API_KEY": "secret-key" } } ``` === "Python" * The dependencies involve a custom local package and the `langchain_openai` package. * A single graph will be loaded from the file `./your_package/your_file.py` with the variable `variable`. * The environment variables are loaded from the `.env` file. ```json { "dependencies": [ "langchain_openai", "./your_package" ], "graphs": { "my_agent": "./your_package/your_file.py:agent" }, "env": "./.env" } ``` ## Dependencies A LangGraph application may depend on other Python packages or JavaScript libraries (depending on the programming language in which the application is written). You will generally need to specify the following information for dependencies to be set up correctly: 1. A file in the directory that specifies the dependencies (e.g., `requirements.txt`, `pyproject.toml`, or `package.json`). 2. A `dependencies` key in the [LangGraph configuration file](#configuration-file) that specifies the dependencies required to run the LangGraph application. 3. Any additional binaries or system libraries can be specified using `dockerfile_lines` key in the [LangGraph configuration file](#configuration-file). ## Graphs Use the `graphs` key in the [LangGraph configuration file](#configuration-file) to specify which graphs will be available in the deployed LangGraph application. You can specify one or more graphs in the configuration file. Each graph is identified by a name (which should be unique) and a path for either: (1) the compiled graph or (2) a function that makes a graph is defined. ## Environment Variables If you're working with a deployed LangGraph application locally, you can configure environment variables in the `env` key of the [LangGraph configuration file](#configuration-file). For a production deployment, you will typically want to configure the environment variables in the deployment environment. ## Related Please see the following resources for more information: - How-to guides for [Application Structure](../how-tos/index.md#application-structure). --- concepts/langgraph_platform.md --- # LangGraph Platform ## Overview LangGraph Platform is a commercial solution for deploying agentic applications to production, built on the open-source [LangGraph framework](./high_level.md). The LangGraph Platform consists of several components that work together to support the development, deployment, debugging, and monitoring of LangGraph applications: - [LangGraph Server](./langgraph_server.md): The server defines an opinionated API and architecture that incorporates best practices for deploying agentic applications, allowing you to focus on building your agent logic rather than developing server infrastructure. - [LangGraph Studio](./langgraph_studio.md): LangGraph Studio is a specialized IDE that can connect to a LangGraph Server to enable visualization, interaction, and debugging of the application locally. - [LangGraph CLI](./langgraph_cli.md): LangGraph CLI is a command-line interface that helps to interact with a local LangGraph - [Python/JS SDK](./sdk.md): The Python/JS SDK provides a programmatic way to interact with deployed LangGraph Applications. - [Remote Graph](../how-tos/use-remote-graph.md): A RemoteGraph allows you to interact with any deployed LangGraph application as though it were running locally. ![](img/lg_platform.png) The LangGraph Platform offers a few different deployment options described in the [deployment options guide](./deployment_options.md). ## Why Use LangGraph Platform? LangGraph Platform is designed to make deploying agentic applications seamless and production-ready. For simpler applications, deploying a LangGraph agent can be as straightforward as using your own server logic—for example, setting up a FastAPI endpoint and invoking LangGraph directly. ### Option 1: Deploying with Custom Server Logic For basic LangGraph applications, you may choose to handle deployment using your custom server infrastructure. Setting up endpoints with frameworks like [Hono](https://hono.dev/) allows you to quickly deploy and run LangGraph as you would any other JavaScript application: ```ts // index.ts import { Hono } from "hono"; import { StateGraph } from "@langchain/langgraph"; const graph = new StateGraph(...) const app = new Hono(); app.get("/foo", (c) => { const res = await graph.invoke(...); return c.json(res); }); ``` This approach works well for simple applications with straightforward needs and provides you with full control over the deployment setup. For example, you might use this for a single-assistant application that doesn’t require long-running sessions or persistent memory. ### Option 2: Leveraging LangGraph Platform for Complex Deployments As your applications scale or add complex features, the deployment requirements often evolve. Running an application with more nodes, longer processing times, or a need for persistent memory can introduce challenges that quickly become time-consuming and difficult to manage manually. [LangGraph Platform](./langgraph_platform.md) is built to handle these challenges seamlessly, allowing you to focus on agent logic rather than server infrastructure. Here are some common issues that arise in complex deployments, which LangGraph Platform addresses: - **[Streaming Support](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](streaming.md) optimized for various application needs. - **Background Runs**: 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. - **Support for long runs**: Vanilla server setups often encounter timeouts or disruptions when handling requests that take a long time to complete. LangGraph Server’s API provides robust support for these tasks by sending regular heartbeat signals, preventing unexpected connection closures during prolonged processes. - **Handling Burstiness**: Certain applications, especially those with real-time user interaction, may experience "bursty" request loads where numerous requests hit the server simultaneously. LangGraph Server includes a task queue, ensuring requests are handled consistently without loss, even under heavy loads. - **[Double Texting](double_texting.md)**: In user-driven applications, it’s common for users to send multiple messages rapidly. This “double texting” can disrupt agent flows if not handled properly. LangGraph Server offers built-in strategies to address and manage such interactions. - **[Checkpointers and Memory Management](persistence.md#checkpoints)**: For agents needing persistence (e.g., conversation memory), deploying a robust storage solution can be complex. LangGraph Platform includes optimized [checkpointers](persistence.md#checkpoints) and a [memory store](persistence.md#memory-store), managing state across sessions without the need for custom solutions. - **[Human-in-the-loop Support](human_in_the_loop.md)**: In many applications, users require a way to intervene in agent processes. LangGraph Server provides specialized endpoints for human-in-the-loop scenarios, simplifying the integration of manual oversight into agent workflows. By using LangGraph Platform, you gain access to a robust, scalable deployment solution that mitigates these challenges, saving you the effort of implementing and maintaining them manually. This allows you to focus more on building effective agent behavior and less on solving deployment infrastructure issues. --- concepts/self_hosted.md --- # Self-Hosted !!! note Prerequisites - [LangGraph Platform](./langgraph_platform.md) - [Deployment Options](./deployment_options.md) ## Versions There are two versions of the self hosted deployment: [Self-Hosted Enterprise](./deployment_options.md#self-hosted-enterprise) and [Self-Hosted Lite](./deployment_options.md#self-hosted-lite). ### Self-Hosted Lite The Self-Hosted Lite version is a limited version of LangGraph Platform that you can run locally or in a self-hosted manner (up to 1 million nodes executed). When using the Self-Hosted Lite version, you authenticate with a [LangSmith](https://smith.langchain.com/) API key. ### Self-Hosted Enterprise The Self-Hosted Enterprise version is the full version of LangGraph Platform. To use the Self-Hosted Enterprise version, you must acquire a license key that you will need to pass in when running the Docker image. To acquire a license key, please email sales@langchain.dev. ## Requirements - You use `langgraph-cli` and/or [LangGraph Studio](./langgraph_studio.md) app to test graph locally. - You use `langgraph build` command to build image. ## How it works - Deploy Redis and Postgres instances on your own infrastructure. - Build the docker image for [LangGraph Server](./langgraph_server.md) using the [LangGraph CLI](./langgraph_cli.md) - Deploy a web server that will run the docker image and pass in the necessary environment variables. See the [how-to guide](../how-tos/deploy-self-hosted.md) --- concepts/multi_agent.md --- # Multi-agent Systems An [agent](./agentic_concepts.md#agent-architectures) is _a system that uses an LLM to decide the control flow of an application_. As you develop these systems, they might grow more complex over time, making them harder to manage and scale. For example, you might run into the following problems: - agent has too many tools at its disposal and makes poor decisions about which tool to call next - context grows too complex for a single agent to keep track of - there is a need for multiple specialization areas in the system (e.g. planner, researcher, math expert, etc.) To tackle these, you might consider breaking your application into multiple smaller, independent agents and composing them into a **multi-agent system**. These independent agents can be as simple as a prompt and an LLM call, or as complex as a [ReAct](./agentic_concepts.md#react-implementation) agent (and more!). The primary benefits of using multi-agent systems are: - **Modularity**: Separate agents make it easier to develop, test, and maintain agentic systems. - **Specialization**: You can create expert agents focused on specific domains, which helps with the overall system performance. - **Control**: You can explicitly control how agents communicate (as opposed to relying on function calling). ## Multi-agent architectures ![](./img/multi_agent/architectures.png) There are several ways to connect agents in a multi-agent system: - **Network**: each agent can communicate with every other agent. Any agent can decide which other agent to call next. - **Supervisor**: each agent communicates with a single [supervisor](https://langchain-ai.github.io/langgraphjs/tutorials/multi_agent/agent_supervisor/) agent. Supervisor agent makes decisions on which agent should be called next. - **Hierarchical**: you can define a multi-agent system with a supervisor of supervisors. This is a generalization of the supervisor architecture and allows for more complex control flows. - **Custom multi-agent workflow**: each agent communicates with only a subset of agents. Parts of the flow are deterministic, and only some agents can decide which other agents to call next. ### Handoffs In multi-agent architectures, agents can be represented as graph nodes. Each agent node executes its step(s) and decides whether to finish execution or route to another agent, including potentially routing to itself (e.g., running in a loop). A common pattern in multi-agent interactions is handoffs, where one agent hands off control to another. Handoffs allow you to specify: - __destination__: target agent to navigate to (e.g., name of the node to go to) - __payload__: [information to pass to that agent](#communication-between-agents) (e.g., state update) To implement handoffs in LangGraph, agent nodes can return [`Command`](./low_level.md#command) object that allows you to combine both control flow and state updates: ```ts const agent = (state: typeof StateAnnotation.State) => { const goto = getNextAgent(...) // 'agent' / 'another_agent' return new Command({ // Specify which agent to call next goto: goto, // Update the graph state update: { foo: "bar", } }); }; ``` In a more complex scenario where each agent node is itself a graph (i.e., a [subgraph](./low_level.md#subgraphs)), a node in one of the agent subgraphs might want to navigate to a different agent. For example, if you have two agents, `alice` and `bob` (subgraph nodes in a parent graph), and `alice` needs to navigate to `bob`, you can set `graph=Command.PARENT` in the `Command` object: ```ts const some_node_inside_alice = (state) => { return new Command({ goto: "bob", update: { foo: "bar", }, // specify which graph to navigate to (defaults to the current graph) graph: Command.PARENT, }) } ``` ### Network In this architecture, agents are defined as graph nodes. Each agent can communicate with every other agent (many-to-many connections) and can decide which agent to call next. This architecture is good for problems that do not have a clear hierarchy of agents or a specific sequence in which agents should be called. ```ts import { StateGraph, Annotation, MessagesAnnotation, Command } from "@langchain/langgraph"; import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o-mini", }); const agent1 = async (state: typeof MessagesAnnotation.State) => { // you can pass relevant parts of the state to the LLM (e.g., state.messages) // to determine which agent to call next. a common pattern is to call the model // with a structured output (e.g. force it to return an output with a "next_agent" field) const response = await model.withStructuredOutput(...).invoke(...); return new Command({ update: { messages: [response.content], }, goto: response.next_agent, }); }; const agent2 = async (state: typeof MessagesAnnotation.State) => { const response = await model.withStructuredOutput(...).invoke(...); return new Command({ update: { messages: [response.content], }, goto: response.next_agent, }); }; const agent3 = async (state: typeof MessagesAnnotation.State) => { ... return new Command({ update: { messages: [response.content], }, goto: response.next_agent, }); }; const graph = new StateGraph(MessagesAnnotation) .addNode("agent1", agent1, { ends: ["agent2", "agent3" "__end__"], }) .addNode("agent2", agent2, { ends: ["agent1", "agent3", "__end__"], }) .addNode("agent3", agent3, { ends: ["agent1", "agent2", "__end__"], }) .addEdge("__start__", "agent1") .compile(); ``` ### Supervisor In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [`Command`](./low_level.md#command) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/map-reduce.ipynb) pattern. ```ts import { StateGraph, MessagesAnnotation, Command, } from "@langchain/langgraph"; import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o-mini", }); const supervisor = async (state: typeof MessagesAnnotation.State) => { // you can pass relevant parts of the state to the LLM (e.g., state.messages) // to determine which agent to call next. a common pattern is to call the model // with a structured output (e.g. force it to return an output with a "next_agent" field) const response = await model.withStructuredOutput(...).invoke(...); // route to one of the agents or exit based on the supervisor's decision // if the supervisor returns "__end__", the graph will finish execution return new Command({ goto: response.next_agent, }); }; const agent1 = async (state: typeof MessagesAnnotation.State) => { // you can pass relevant parts of the state to the LLM (e.g., state.messages) // and add any additional logic (different models, custom prompts, structured output, etc.) const response = await model.invoke(...); return new Command({ goto: "supervisor", update: { messages: [response], }, }); }; const agent2 = async (state: typeof MessagesAnnotation.State) => { const response = await model.invoke(...); return new Command({ goto: "supervisor", update: { messages: [response], }, }); }; const graph = new StateGraph(MessagesAnnotation) .addNode("supervisor", supervisor, { ends: ["agent1", "agent2", "__end__"], }) .addNode("agent1", agent1, { ends: ["supervisor"], }) .addNode("agent2", agent2, { ends: ["supervisor"], }) .addEdge("__start__", "supervisor") .compile(); ``` Check out this [tutorial](https://langchain-ai.github.io/langgraphjs/tutorials/multi_agent/agent_supervisor/) for an example of supervisor multi-agent architecture. ### Custom multi-agent workflow In this architecture we add individual agents as graph nodes and define the order in which agents are called ahead of time, in a custom workflow. In LangGraph the workflow can be defined in two ways: - **Explicit control flow (normal edges)**: LangGraph allows you to explicitly define the control flow of your application (i.e. the sequence of how agents communicate) explicitly, via [normal graph edges](./low_level.md#normal-edges). This is the most deterministic variant of this architecture above — we always know which agent will be called next ahead of time. - **Dynamic control flow (conditional edges)**: in LangGraph you can allow LLMs to decide parts of your application control flow. This can be achieved by using [`Command`](./low_level.md#command). ```ts import { StateGraph, MessagesAnnotation, } from "@langchain/langgraph"; import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o-mini", }); const agent1 = async (state: typeof MessagesAnnotation.State) => { const response = await model.invoke(...); return { messages: [response] }; }; const agent2 = async (state: typeof MessagesAnnotation.State) => { const response = await model.invoke(...); return { messages: [response] }; }; const graph = new StateGraph(MessagesAnnotation) .addNode("agent1", agent1) .addNode("agent2", agent2) // define the flow explicitly .addEdge("__start__", "agent1") .addEdge("agent1", "agent2") .compile(); ``` ## Communication between agents The most important thing when building multi-agent systems is figuring out how the agents communicate. There are few different considerations: - What if two agents have [**different state schemas**](#different-state-schemas)? - How to communicate over a [**shared message list**](#shared-message-list)? #### Graph state To communicate via graph state, individual agents need to be defined as [graph nodes](./low_level.md#nodes). These can be added as functions or as entire [subgraphs](./low_level.md#subgraphs). At each step of the graph execution, agent node receives the current state of the graph, executes the agent code and then passes the updated state to the next nodes. Typically agent nodes share a single [state schema](./low_level.md#state). However, you might want to design agent nodes with [different state schemas](#different-state-schemas). ### Different state schemas An agent might need to have a different state schema from the rest of the agents. For example, a search agent might only need to keep track of queries and retrieved documents. There are two ways to achieve this in LangGraph: - Define [subgraph](./low_level.md#subgraphs) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it’s important to [add input / output transformations](https://langchain-ai.github.io/langgraphjs/how-tos/subgraph-transform-state/) so that the parent graph knows how to communicate with the subgraphs. - Define agent node functions with a [private input state schema](https://langchain-ai.github.io/langgraphjs/how-tos/pass_private_state/) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent. ### Shared message list The most common way for the agents to communicate is via a shared state channel, typically a list of messages. This assumes that there is always at least a single channel (key) in the state that is shared by the agents. When communicating via a shared message list there is an additional consideration: should the agents [share the full history](#share-full-history) of their thought process or only [the final result](#share-final-result)? ![](./img/multi_agent/response.png) #### Share full 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 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). #### Share final result Agents can have their own private "scratchpad" and only **share the final result** with the rest of the agents. This approach might work better for systems with many agents or agents that are more complex. In this case, you would need to define agents with [different state schemas](#different-state-schemas) For agents called as tools, the supervisor determines the inputs based on the tool schema. Additionally, LangGraph allows [passing state](https://langchain-ai.github.io/langgraphjs/how-tos/pass-run-time-values-to-tools/) to individual tools at runtime, so subordinate agents can access parent state, if needed. --- concepts/time-travel.md --- # Time Travel ⏱️ !!! note "Prerequisites" This guide assumes that you are familiar with LangGraph's checkpoints and states. If not, please review the [persistence](./persistence.md) concept first. When working with non-deterministic systems that make model-based decisions (e.g., agents powered by LLMs), it can be useful to examine their decision-making process in detail: 1. 🤔 **Understand Reasoning**: Analyze the steps that led to a successful result. 2. 🐞 **Debug Mistakes**: Identify where and why errors occurred. 3. 🔍 **Explore Alternatives**: Test different paths to uncover better solutions. We call these debugging techniques **Time Travel**, composed of two key actions: [**Replaying**](#replaying) 🔁 and [**Forking**](#forking) 🔀. ## Replaying ![](./img/human_in_the_loop/replay.png) Replaying allows us to revisit and reproduce an agent's past actions. This can be done either from the current state (or checkpoint) of the graph or from a specific checkpoint. To replay from the current state, simply pass `null` as the input along with a `threadConfig`: ```typescript const threadConfig = { configurable: { thread_id: "1" }, streamMode: "values" }; for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` To replay actions from a specific checkpoint, start by retrieving all checkpoints for the thread: ```typescript const allCheckpoints = []; for await (const state of graph.getStateHistory(threadConfig)) { allCheckpoints.push(state); } ``` Each checkpoint has a unique ID. After identifying the desired checkpoint, for instance, `xyz`, include its ID in the configuration: ```typescript const threadConfig = { configurable: { thread_id: '1', checkpoint_id: 'xyz' }, streamMode: "values" }; for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` The graph efficiently replays previously executed nodes instead of re-executing them, leveraging its awareness of prior checkpoint executions. ## Forking ![](./img/human_in_the_loop/forking.png) Forking allows you to revisit an agent's past actions and explore alternative paths within the graph. To edit a specific checkpoint, such as `xyz`, provide its `checkpoint_id` when updating the graph's state: ```typescript const threadConfig = { configurable: { thread_id: "1", checkpoint_id: "xyz" } }; graph.updateState(threadConfig, { state: "updated state" }); ``` This creates a new forked checkpoint, xyz-fork, from which you can continue running the graph: ```typescript const threadConfig = { configurable: { thread_id: '1', checkpoint_id: 'xyz-fork' }, streamMode: "values" }; for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` ## Additional Resources 📚 - [**Conceptual Guide: Persistence**](https://langchain-ai.github.io/langgraphjs/concepts/persistence/#replay): Read the persistence guide for more context on replaying. - [**How to View and Update Past Graph State**](/langgraphjs/how-tos/time-travel): Step-by-step instructions for working with graph state that demonstrate the **replay** and **fork** actions. --- concepts/bring_your_own_cloud.md --- # Bring Your Own Cloud (BYOC) !!! note Prerequisites - [LangGraph Platform](./langgraph_platform.md) - [Deployment Options](./deployment_options.md) ## Architecture Split control plane (hosted by us) and data plane (hosted by you, managed by us). | | Control Plane | Data Plane | |-----------------------------|---------------------------------|-----------------------------------------------| | What it does | Manages deployments, revisions. | Runs your LangGraph graphs, stores your data. | | Where it is hosted | LangChain Cloud account | Your cloud account | | Who provisions and monitors | LangChain | LangChain | LangChain has no direct access to the resources created in your cloud account, and can only interact with them via AWS APIs. Your data never leaves your cloud account / VPC at rest or in transit. ![Architecture](img/byoc_architecture.png) ## Requirements - You’re using AWS already. - You use `langgraph-cli` and/or [LangGraph Studio](./langgraph_studio.md) app to test graph locally. - You use `langgraph build` command to build image and then push it to your AWS ECR repository (`docker push`). ## How it works - We provide you a [Terraform module](https://github.com/langchain-ai/terraform/tree/main/modules/langgraph_cloud_setup) which you run to set up our requirements 1. Creates an AWS role (which our control plane will later assume to provision and monitor resources) - https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonVPCReadOnlyAccess.html - Read VPCS to find subnets - https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonECS_FullAccess.html - Used to create/delete ECS resources for your LangGraph Cloud instances - https://docs.aws.amazon.com/aws-managed-policy/latest/reference/SecretsManagerReadWrite.html - Create secrets for your ECS resources - https://docs.aws.amazon.com/aws-managed-policy/latest/reference/CloudWatchReadOnlyAccess.html - Read CloudWatch metrics/logs to monitor your instances/push deployment logs - https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonRDSFullAccess.html - Provision `RDS` instances for your LangGraph Cloud instances 2. Either - Tags an existing vpc / subnets as `langgraph-cloud-enabled` - Creates a new vpc and subnets and tags them as `langgraph-cloud-enabled` - You create a LangGraph Cloud Project in `smith.langchain.com` providing - the ID of the AWS role created in the step above - the AWS ECR repo to pull the service image from - We provision the resources in your cloud account using the role above - We monitor those resources to ensure uptime and recovery from errors Notes for customers using [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting): - Creation of new LangGraph Cloud projects and revisions currently needs to be done on smith.langchain.com. - You can however set up the project to trace to your self-hosted LangSmith instance if desired --- concepts/sdk.md --- # LangGraph SDK !!! info "Prerequisites" - [LangGraph Platform](./langgraph_platform.md) - [LangGraph Server](./langgraph_server.md) The LangGraph Platform provides both a Python and JS SDK for interacting with the [LangGraph Server API](./langgraph_server.md). ## Installation You can install the packages using the appropriate package manager for your language. === "Python" ```bash pip install langgraph-sdk ``` === "JS" ```bash yarn add @langchain/langgraph-sdk ``` ## API Reference You can find the API reference for the SDKs here: - [Python SDK Reference](/langgraphjs/cloud/reference/sdk/python_sdk_ref/) - [JS/TS SDK Reference](/langgraphjs/cloud/reference/sdk/js_ts_sdk_ref/) ## Python Sync vs. Async The Python SDK provides both synchronous (`get_sync_client`) and asynchronous (`get_client`) clients for interacting with the LangGraph Server API. === "Async" ```python from langgraph_sdk import get_client client = get_client(url=..., api_key=...) await client.assistants.search() ``` === "Sync" ```python from langgraph_sdk import get_sync_client client = get_sync_client(url=..., api_key=...) client.assistants.search() ``` ## Related - [LangGraph CLI API Reference](/langgraphjs/cloud/reference/cli/) - [Python SDK Reference](/langgraphjs/cloud/reference/sdk/python_sdk_ref/) - [JS/TS SDK Reference](/langgraphjs/cloud/reference/sdk/js_ts_sdk_ref/) --- concepts/memory.md --- # Memory ## What is Memory? Memory in AI applications refers to the ability to process, store, and effectively recall information from past interactions. With memory, your agents can learn from feedback and adapt to users' preferences. This guide is divided into two sections based on the scope of memory recall: short-term memory and long-term memory. **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. **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/langgraphjs/reference/classes/checkpoint.BaseStore.html)) to let you save and recall long-term memories. Both are important to understand and implement for your application. ![](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. 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. ### Managing long conversation history 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://js.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. ![](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. ```typescript import { Annotation } from "@langchain/langgraph"; const StateAnnotation = Annotation.Root({ myList: Annotation({ reducer: ( existing: string[], updates: string[] | { type: string; from: number; to?: number } ) => { if (Array.isArray(updates)) { // Normal case, add to the history return [...existing, ...updates]; } else if (typeof updates === "object" && 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.slice(updates.from, updates.to); } // etc. We define how to interpret updates return existing; }, default: () => [], }), }); type State = typeof StateAnnotation.State; function myNode(state: State) { return { // We return an update for the field "myList" saying to // keep only values from index -5 to the end (deleting the rest) myList: { type: "keep", from: -5, to: undefined }, }; } ``` LangGraph will call the "[reducer](low_level.md#reducers)" function any time an update is returned under the key "myList". 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 [`messagesStateReducer`](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.messagesStateReducer.html) reducer (or [`MessagesAnnotation`](https://langchain-ai.github.io/langgraphjs/reference/variables/langgraph.MessagesAnnotation.html), which uses the same underlying functionality) in LangGraph, you can do this using a `RemoveMessage`. ```typescript import { RemoveMessage, AIMessage } from "@langchain/core/messages"; import { MessagesAnnotation } from "@langchain/langgraph"; type State = typeof MessagesAnnotation.State; function myNode1(state: State) { // Add an AI message to the `messages` list in the state return { messages: [new AIMessage({ content: "Hi" })] }; } function myNode2(state: State) { // Delete all but the last 2 messages from the `messages` list in the state const deleteMessages = state.messages .slice(0, -2) .map((m) => new RemoveMessage({ id: m.id })); return { messages: deleteMessages }; } ``` In the example above, the `MessagesAnnotation` allows us to append new messages to the `messages` state key as shown in `myNode1`. 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/langgraphjs/how-tos/memory/delete-messages/). See this how-to [guide](https://langchain-ai.github.io/langgraphjs/how-tos/manage-conversation-history/)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 [`MessagesAnnotation`](https://langchain-ai.github.io/langgraphjs/reference/variables/langgraph.MessagesAnnotation.html) to include a `summary` key. ```typescript import { MessagesAnnotation, Annotation } from "@langchain/langgraph"; const MyGraphAnnotation = Annotation.Root({ ...MessagesAnnotation.spec, summary: Annotation, }); ``` Then, we can generate a summary of the chat history, using any existing summary as context for the next summary. This `summarizeConversation` node can be called after some number of messages have accumulated in the `messages` state key. ```typescript import { ChatOpenAI } from "@langchain/openai"; import { HumanMessage, RemoveMessage } from "@langchain/core/messages"; type State = typeof MyGraphAnnotation.State; async function summarizeConversation(state: State) { // First, we get any existing summary const summary = state.summary || ""; // Create our summarization prompt let summaryMessage: string; if (summary) { // A summary already exists summaryMessage = `This is a summary of the conversation to date: ${summary}\n\n` + "Extend the summary by taking into account the new messages above:"; } else { summaryMessage = "Create a summary of the conversation above:"; } // Add prompt to our history const messages = [ ...state.messages, new HumanMessage({ content: summaryMessage }), ]; // Assuming you have a ChatOpenAI model instance const model = new ChatOpenAI(); const response = await model.invoke(messages); // Delete all but the 2 most recent messages const deleteMessages = state.messages .slice(0, -2) .map((m) => new RemoveMessage({ id: m.id })); return { summary: response.content, messages: deleteMessages, }; } ``` See this how-to [here](https://langchain-ai.github.io/langgraphjs/how-tos/memory/add-summary-conversation-history/) 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 [`trimMessages`](https://js.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 `maxTokens`) to use for handling the boundary. Below is an example. ```typescript import { trimMessages } from "@langchain/core/messages"; import { ChatOpenAI } from "@langchain/openai"; trimMessages(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 tokenCounter: new ChatOpenAI({ modelName: "gpt-4" }), // Remember to adjust based on the desired conversation // length maxTokens: 45, // Most chat models expect that chat history starts with either: // (1) a HumanMessage or // (2) a SystemMessage followed by a HumanMessage startOn: "human", // Most chat models expect that chat history ends with either: // (1) a HumanMessage or // (2) a ToolMessage endOn: ["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. includeSystem: true, }); ``` ## 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." LangGraph stores long-term memories as JSON documents in a [store](persistence.md#memory-store) ([reference doc](https://langchain-ai.github.io/langgraphjs/reference/classes/checkpoint.BaseStore.html)). 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. ```typescript import { InMemoryStore } from "@langchain/langgraph"; // InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use. const store = new InMemoryStore(); const userId = "my-user"; const applicationContext = "chitchat"; const namespace = [userId, applicationContext]; await store.put(namespace, "a-memory", { rules: [ "User likes short, direct language", "User only speaks English & TypeScript", ], "my-key": "my-value", }); // get the "memory" by ID const item = await store.get(namespace, "a-memory"); // list "memories" within this namespace, filtering on content equivalence const items = await store.search(namespace, { filter: { "my-key": "my-value" }, }); ``` When adding long-term memory to your agent, it's important to think about how to **write memories**, how to **store and manage memory updates**, and how to **recall & represent memories** for the LLM in your application. These questions are all interdependent: how you want to recall & format memories for the LLM dictates what you should store and how to manage it. Furthermore, each technique has tradeoffs. The right approach for you largely depends on your application's needs. LangGraph aims to give you the low-level primitives to directly control the long-term memory of your application, based on memory [Store](persistence.md#memory-store)'s. Long-term memory is far from a solved problem. While it is hard to provide generic advice, we have provided a few reliable patterns below for your consideration as you implement long-term memory. **Do you want to write memories "on the hot path" or "in the background"** Memory can be updated either as part of your primary application logic (e.g. "on the hot path" of the application) or as a background task (as a separate function that generates memories based on the primary application's state). We document some tradeoffs for each approach in [the writing memories section below](#writing-memories). **Do you want to manage memories as a single profile or as a collection of documents?** We provide two main approaches to managing long-term memory: a single, continuously updated document (referred to as a "profile" or "schema") or a collection of documents. Each method offers its own benefits, depending on the type of information you need to store and how you intend to access it. Managing memories as a single, continuously updated "profile" or "schema" is useful when there is well-scoped, specific information you want to remember about a user, organization, or other entity (including the agent itself). You can define the schema of the profile ahead of time, and then use an LLM to update this based on interactions. Querying the "memory" is easy since it's a simple GET operation on a JSON document. We explain this in more detail in [remember a profile](#manage-individual-profiles). This technique can provide higher precision (on known information use cases) at the expense of lower recall (since you have to anticipate and model your domain, and updates to the doc tend to delete or rewrite away old information at a greater frequency). Managing long-term memory as a collection of documents, on the other hand, lets you store an unbounded amount of information. This technique is useful when you want to repeatedly extract & remember items over a long time horizon but can be more complicated to query and manage over time. Similar to the "profile" memory, you still define schema(s) for each memory. Rather than overwriting a single document, you instead will insert new ones (and potentially update or re-contextualize existing ones in the process). We explain this approach in more detail in ["managing a collection of memories"](#manage-a-collection-of-memories). **Do you want to present memories to your agent as updated instructions or as few-shot examples?** Memories are typically provided to the LLM as a part of the system prompt. Some common ways to "frame" memories for the LLM include providing raw information as "memories from previous interactions with user A", as system instructions or rules, or as few-shot examples. Framing memories as "learning rules or instructions" typically means dedicating a portion of the system prompt to instructions the LLM can manage itself. After each conversation, you can prompt the LLM to evaluate its performance and update the instructions to better handle this type of task in the future. We explain this approach in more detail in [this section](#update-own-instructions). Storing memories as few-shot examples lets you store and manage instructions as cause and effect. Each memory stores an input or context and expected response. Including a reasoning trajectory (a chain-of-thought) can also help provide sufficient context so that the memory is less likely to be mis-used in the future. We elaborate on this concept more in [this section](#few-shot-examples). We will expand on techniques for writing, managing, and recalling & formatting memories in the following section. ### Writing memories Humans form long-term memories when we sleep, but when and how should our agents create new memories? The two most common ways we see agents write memories are "on the hot path" and "in the background". #### Writing memories in the hot path This involves creating memories while the application is running. To provide a popular production example, ChatGPT manages memories using a "save_memories" tool to upsert memories as content strings. It decides whether (and how) to use this tool every time it receives a user message and multi-tasks memory management with the rest of the user instructions. This has a few benefits. First of all, it happens "in real time". If the user starts a new thread right away that memory will be present. The user also transparently sees when memories are stored, since the bot has to explicitly decide to store information and can relate that to the user. This also has several downsides. It complicates the decisions the agent must make (what to commit to memory). This complication can degrade its tool-calling performance and reduce task completion rates. It will slow down the final response since it needs to decide what to commit to memory. It also typically leads to fewer things being saved to memory (since the assistant is multi-tasking), which will cause **lower recall** in later conversations. #### Writing memories in the background This involves updating memory as a conceptually separate task, typically as a completely separate graph or function. Since it happens in the background, it incurs no latency. It also splits up the application logic from the memory logic, making it more modular and easy to manage. It also lets you separate the timing of memory creation, letting you avoid redundant work. Your agent can focus on accomplishing its immediate task without having to consciously think about what it needs to remember. This approach is not without its downsides, however. You have to think about how often to write memories. If it doesn't run in realtime, the user's interactions on other threads won't benefit from the new context. You also have to think about when to trigger this job. We typically recommend scheduling memories after some point of time, cancelling and re-scheduling for the future if new events occur on a given thread. Other popular choices are to form memories on some cron schedule or to let the user or application logic manually trigger memory formation. ### Managing memories Once you've sorted out memory scheduling, it's important to think about **how to update memory with new information**. There are two main approaches: you can either continuously update a single document (memory profile) or insert new documents each time you receive new information. We will outline some tradeoffs between these two approaches below, understanding that most people will find it most appropriate to combine approaches and to settle somewhere in the middle. #### Manage individual profiles A profile is generally just a JSON document with various key-value pairs you've selected to represent your domain. When remembering a profile, you will want to make sure that you are **updating** the profile each time. As a result, you will want to pass in the previous profile and ask the LLM to generate a new profile (or some JSON patch to apply to the old profile). The larger the document, the more error-prone this can become. If your document becomes **too** large, you may want to consider splitting up the profiles into separate sections. You will likely need to use generation with retries and/or **strict** decoding when generating documents to ensure the memory schemas remains valid. #### Manage a collection of memories Saving memories as a collection of documents simplifies some things. Each individual memory can be more narrowly scoped and easier to generate. It also means you're less likely to **lose** information over time, since it's easier for an LLM to generate _new_ objects for new information than it is for it to reconcile that new information with information in a dense profile. This tends to lead to higher recall downstream. This approach shifts some complexity to how you prompt the LLM to apply memory updates. You now have to enable the LLM to _delete_ or _update_ existing items in the list. This can be tricky to prompt the LLM to do. Some LLMs may default to over-inserting; others may default to over-updating. Tuning the behavior here is best done through evals, something you can do with a tool like [LangSmith](https://docs.smith.langchain.com/tutorials/Developers/evaluation). This also shifts complexity to memory **search** (recall). You have to think about what relevant items to use. Right now we support filtering by metadata. We will be adding semantic search shortly. Finally, this shifts some complexity to how you represent the memories for the LLM (and by extension, the schemas you use to save each memories). It's very easy to write memories that can easily be mistaken out-of-context. It's important to prompt the LLM to include all necessary contextual information in the given memory so that when you use it in later conversations it doesn't mistakenly mis-apply that information. ### Representing memories Once you have saved memories, the way you then retrieve and present the memory content for the LLM can play a large role in how well your LLM incorporates that information in its responses. The following sections present a couple of common approaches. Note that these sections also will largely inform how you write and manage memories. Everything in memory is connected! #### Update own instructions While instructions are often static text written by the developer, many AI applications benefit from letting the users personalize the rules and instructions the agent should follow whenever it interacts with that user. This ideally can be inferred by its interactions with the user (so the user doesn't have to explicitly change settings in your app). In this sense, instructions are a form of long-form memory! One way to apply this is using "reflection" or "Meta-prompting" steps. Prompt the LLM with the current instruction set (from the system prompt) and a conversation with the user, and instruct the LLM to refine its instructions. This approach allows the system to dynamically update and improve its own behavior, potentially leading to better performance on various tasks. This is particularly useful for tasks where the instructions are challenging to specify a priori. Meta-prompting uses past information to refine prompts. For instance, a [Tweet generator](https://www.youtube.com/watch?v=Vn8A3BxfplE) employs meta-prompting to enhance its paper summarization prompt for Twitter. You could implement this using LangGraph's memory store to save updated instructions in a shared namespace. In this case, we will namespace the memories as "agent_instructions" and key the memory based on the agent. ```typescript import { BaseStore } from "@langchain/langgraph/store"; import { State } from "@langchain/langgraph"; import { ChatOpenAI } from "@langchain/openai"; // Node that *uses* the instructions const callModel = async (state: State, store: BaseStore) => { const namespace = ["agent_instructions"]; const instructions = await store.get(namespace, "agent_a"); // Application logic const prompt = promptTemplate.format({ instructions: instructions[0].value.instructions, }); // ... rest of the logic }; // Node that updates instructions const updateInstructions = async (state: State, store: BaseStore) => { const namespace = ["instructions"]; const currentInstructions = await store.search(namespace); // Memory logic const prompt = promptTemplate.format({ instructions: currentInstructions[0].value.instructions, conversation: state.messages, }); const llm = new ChatOpenAI(); const output = await llm.invoke(prompt); const newInstructions = output.content; // Assuming the LLM returns the new instructions await store.put(["agent_instructions"], "agent_a", { instructions: newInstructions, }); // ... rest of the logic }; ``` #### Few-shot examples Sometimes it's easier to "show" than "tell." LLMs learn well from examples. Few-shot learning lets you ["program"](https://x.com/karpathy/status/1627366413840322562) your LLM by updating the prompt with input-output examples to illustrate the intended behavior. While various [best-practices](https://js.langchain.com/docs/concepts/#1-generating-examples) can be used to generate few-shot examples, often the challenge lies in selecting the most relevant examples based on user input. Note that the memory store is just one way to store data as few-shot examples. If you want to have more developer involvement, or tie few-shots more closely to your evaluation harness, you can also use a [LangSmith Dataset](https://docs.smith.langchain.com/how_to_guides/datasets) to store your data. Then dynamic few-shot example selectors can be used out-of-the box to achieve this same goal. LangSmith will index the dataset for you and enable retrieval of few shot examples that are most relevant to the user input based upon keyword similarity ([using a BM25-like algorithm](https://docs.smith.langchain.com/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection) for keyword based similarity). 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. --- concepts/streaming.md --- # Streaming LangGraph is built with first class support for streaming. There are several different ways to stream back outputs from a graph run ## Streaming graph outputs (`.stream`) `.stream` is an async method for streaming back outputs from a graph run. There are several different modes you can specify when calling these methods (e.g. `await graph.stream(..., { ...config, streamMode: "values" })): - [`"values"`](/langgraphjs/how-tos/stream-values): This streams the full value of the state after each step of the graph. - [`"updates"`](/langgraphjs/how-tos/stream-updates): This 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) then those updates are streamed separately. - [`"custom"`](/langgraphjs/how-tos/streaming-content): This streams custom data from inside your graph nodes. - [`"messages"`](/langgraphjs/how-tos/streaming-tokens): This streams LLM tokens and metadata for the graph node where LLM is invoked. - `"debug"`: This streams as much information as possible throughout the execution of the graph. The below visualization shows the difference between the `values` and `updates` modes: ![values vs updates](./img/streaming/values_vs_updates.png) ## Streaming LLM tokens and events (`.streamEvents`) In addition, you can use the [`streamEvents`](/langgraphjs/how-tos/streaming-events-from-within-tools) method to stream back events that happen _inside_ nodes. This is useful for streaming tokens of LLM calls. This is a standard method on all [LangChain objects](https://js.langchain.com/docs/concepts/#runnable-interface). This means that as the graph is executed, certain events are emitted along the way and can be seen if you run the graph using `.streamEvents`. All events have (among other things) `event`, `name`, and `data` fields. What do these mean? - `event`: This is the type of event that is being emitted. You can find a detailed table of all callback events and triggers [here](https://js.langchain.com/docs/concepts/#callback-events). - `name`: This is the name of event. - `data`: This is the data associated with the event. What types of things cause events to be emitted? - each node (runnable) emits `on_chain_start` when it starts execution, `on_chain_stream` during the node execution and `on_chain_end` when the node finishes. Node events will have the node name in the event's `name` field - the graph will emit `on_chain_start` in the beginning of the graph execution, `on_chain_stream` after each node execution and `on_chain_end` when the graph finishes. Graph events will have the `LangGraph` in the event's `name` field - Any writes to state channels (i.e. anytime you update the value of one of your state keys) will emit `on_chain_start` and `on_chain_end` events Additionally, any events that are created inside your nodes (LLM events, tool events, manually emitted events, etc.) will also be visible in the output of `.streamEvents`. To make this more concrete and to see what this looks like, let's see what events are returned when we run a simple graph: ```typescript import { ChatOpenAI } from "@langchain/openai"; import { StateGraph, MessagesAnnotation } from "langgraph"; const model = new ChatOpenAI({ model: "gpt-4-turbo-preview" }); function callModel(state: typeof MessagesAnnotation.State) { const response = model.invoke(state.messages); return { messages: response }; } const workflow = new StateGraph(MessagesAnnotation) .addNode("callModel", callModel) .addEdge("start", "callModel") .addEdge("callModel", "end"); const app = workflow.compile(); const inputs = [{ role: "user", content: "hi!" }]; for await (const event of app.streamEvents( { messages: inputs }, { version: "v2" } )) { const kind = event.event; console.log(`${kind}: ${event.name}`); } ``` ```shell on_chain_start: LangGraph on_chain_start: __start__ on_chain_end: __start__ on_chain_start: callModel on_chat_model_start: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_stream: ChatOpenAI on_chat_model_end: ChatOpenAI on_chain_start: ChannelWrite on_chain_end: ChannelWrite on_chain_stream: callModel on_chain_end: callModel on_chain_stream: LangGraph on_chain_end: LangGraph ``` We start with the overall graph start (`on_chain_start: LangGraph`). We then write to the `__start__` node (this is special node to handle input). We then start the `callModel` node (`on_chain_start: callModel`). We then start the chat model invocation (`on_chat_model_start: ChatOpenAI`), stream back token by token (`on_chat_model_stream: ChatOpenAI`) and then finish the chat model (`on_chat_model_end: ChatOpenAI`). From there, we write the results back to the channel (`ChannelWrite`) and then finish the `callModel` node and then the graph as a whole. This should hopefully give you a good sense of what events are emitted in a simple graph. But what data do these events contain? Each type of event contains data in a different format. Let's look at what `on_chat_model_stream` events look like. This is an important type of event since it is needed for streaming tokens from an LLM response. These events look like: ```shell {'event': 'on_chat_model_stream', 'name': 'ChatOpenAI', 'run_id': '3fdbf494-acce-402e-9b50-4eab46403859', 'tags': ['seq:step:1'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'callModel', 'langgraph_triggers': ['start:callModel'], 'langgraph_task_idx': 0, 'checkpoint_id': '1ef657a0-0f9d-61b8-bffe-0c39e4f9ad6c', 'checkpoint_ns': 'callModel', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o-mini', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'data': {'chunk': AIMessageChunk({ content: 'Hello', id: 'run-3fdbf494-acce-402e-9b50-4eab46403859' })}, 'parent_ids': []} ``` We can see that we have the event type and name (which we knew from before). We also have a bunch of stuff in metadata. Noticeably, `'langgraph_node': 'callModel',` is some really helpful information which tells us which node this model was invoked inside of. Finally, `data` is a really important field. This contains the actual data for this event! Which in this case is an AIMessageChunk. This contains the `content` for the message, as well as an `id`. This is the ID of the overall AIMessage (not just this chunk) and is super helpful - it helps us track which chunks are part of the same message (so we can show them together in the UI). This information contains all that is needed for creating a UI for streaming LLM tokens. --- concepts/plans.md --- # LangGraph Platform Plans ## Overview LangGraph Platform is a commercial solution for deploying agentic applications in production. There are three different plans for using it. - **Developer**: All [LangSmith](https://smith.langchain.com/) users have access to this plan. You can sign up for this plan simply by creating a LangSmith account. This gives you access to the [Self-Hosted Lite](./deployment_options.md#self-hosted-lite) deployment option. - **Plus**: All [LangSmith](https://smith.langchain.com/) users with a [Plus account](https://docs.smith.langchain.com/administration/pricing) have access to this plan. You can sign up for this plan simply by upgrading your LangSmith account to the Plus plan type. This gives you access to the [Cloud](./deployment_options.md#cloud-saas) deployment option. - **Enterprise**: This is separate from LangSmith plans. You can sign up for this plan by contacting sales@langchain.dev. This gives you access to all deployment options: [Cloud](./deployment_options.md#cloud-saas), [Bring-Your-Own-Cloud](./deployment_options.md#bring-your-own-cloud), and [Self Hosted Enterprise](./deployment_options.md#self-hosted-enterprise) ## Plan Details | | Developer | Plus | Enterprise | |------------------------------------------------------------------|---------------------------------------------|-------------------------------------------------------|-----------------------------------------------------| | Deployment Options | Self-Hosted Lite | Cloud | Self-Hosted Enterprise, Cloud, Bring-Your-Own-Cloud | | Usage | Free, limited to 1M nodes executed per year | Free while in Beta, will be charged per node executed | Custom | | APIs for retrieving and updating state and conversational history | ✅ | ✅ | ✅ | | APIs for retrieving and updating long-term memory | ✅ | ✅ | ✅ | | Horizontally scalable task queues and servers | ✅ | ✅ | ✅ | | Real-time streaming of outputs and intermediate steps | ✅ | ✅ | ✅ | | Assistants API (configurable templates for LangGraph apps) | ✅ | ✅ | ✅ | | Cron scheduling | -- | ✅ | ✅ | | LangGraph Studio for prototyping | ✅ | ✅ | ✅ | | Authentication & authorization to call the LangGraph APIs | -- | Coming Soon! | Coming Soon! | | Smart caching to reduce traffic to LLM API | -- | Coming Soon! | Coming Soon! | | Publish/subscribe API for state | -- | Coming Soon! | Coming Soon! | | Scheduling prioritization | -- | Coming Soon! | Coming Soon! | Please see the [LangGraph Platform Pricing](https://www.langchain.com/langgraph-platform-pricing) for information on pricing. ## Related For more information, please see: * [Deployment Options conceptual guide](./deployment_options.md) * [LangGraph Platform Pricing](https://www.langchain.com/langgraph-platform-pricing) * [LangSmith Plans](https://docs.smith.langchain.com/administration/pricing) --- concepts/agentic_concepts.md --- # Agent architectures Many LLM applications implement a particular control flow of steps before and / or after LLM calls. As an example, [RAG](https://github.com/langchain-ai/rag-from-scratch) performs retrieval of relevant documents to a question, and passes those documents to an LLM in order to ground the model's response. Instead of hard-coding a fixed control flow, we sometimes want LLM systems that can pick its own control flow to solve more complex problems! This is one definition of an [agent](https://blog.langchain.dev/what-is-an-agent/): _an agent is a system that uses an LLM to decide the control flow of an application._ There are many ways that an LLM can control application: - An LLM can route between two potential paths - An LLM can decide which of many tools to call - An LLM can decide whether the generated answer is sufficient or more work is needed As a result, there are many different types of [agent architectures](https://blog.langchain.dev/what-is-a-cognitive-architecture/), which given an LLM varying levels of control. ![Agent Types](img/agent_types.png) ## Router A router allows an LLM to select a single step from a specified set of options. This is an agent architecture that exhibits a relatively limited level of control because the LLM usually governs a single decision and can return a narrow set of outputs. Routers typically employ a few different concepts to achieve this. ### Structured Output Structured outputs with LLMs work by providing a specific format or schema that the LLM should follow in its response. This is similar to tool calling, but more general. While tool calling typically involves selecting and using predefined functions, structured outputs can be used for any type of formatted response. Common methods to achieve structured outputs include: 1. Prompt engineering: Instructing the LLM to respond in a specific format. 2. Output parsers: Using post-processing to extract structured data from LLM responses. 3. Tool calling: Leveraging built-in tool calling capabilities of some LLMs to generate structured outputs. Structured outputs are crucial for routing as they ensure the LLM's decision can be reliably interpreted and acted upon by the system. Learn more about [structured outputs in this how-to guide](https://js.langchain.com/docs/how_to/structured_output/). ## Tool calling agent While a router allows an LLM to make a single decision, more complex agent architectures expand the LLM's control in two key ways: 1. Multi-step decision making: The LLM can control a sequence of decisions rather than just one. 2. Tool access: The LLM can choose from and use a variety of tools to accomplish tasks. [ReAct](https://arxiv.org/abs/2210.03629) is a popular general purpose agent architecture that combines these expansions, integrating three core concepts. 1. `Tool calling`: Allowing the LLM to select and use various tools as needed. 2. `Memory`: Enabling the agent to retain and use information from previous steps. 3. `Planning`: Empowering the LLM to create and follow multi-step plans to achieve goals. This architecture allows for more complex and flexible agent behaviors, going beyond simple routing to enable dynamic problem-solving across multiple steps. You can use it with [`createReactAgent`](/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html). ### Tool calling Tools are useful whenever you want an agent to interact with external systems. External systems (e.g., APIs) often require a particular input schema or payload, rather than natural language. When we bind an API, for example, as a tool we given the model awareness of the required input schema. The model will choose to call a tool based upon the natural language input from the user and it will return an output that adheres to the tool's schema. [Many LLM providers support tool calling](https://js.langchain.com/docs/integrations/chat/) and [tool calling interface](https://blog.langchain.dev/improving-core-tool-interfaces-and-docs-in-langchain/) in LangChain is simple: you can define a tool schema, and pass it into `ChatModel.bindTools([tool])`. ![Tools](img/tool_call.png) ### Memory Memory 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: Allows the agent to access information acquired during earlier steps in a sequence. 2. 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: - [`State`](./low_level.md#state): User-defined schema specifying the exact structure of memory to retain. - [`Checkpointers`](./persistence.md): Mechanism to store state at every step across different interactions. 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](/langgraphjs/how-tos/persistence). Effective memory management enhances an agent's ability to maintain context, learn from past experiences, and make more informed decisions over time. ### Planning In the ReAct architecture, an LLM is called repeatedly in a while-loop. At each step the agent decides which tools to call, and what the inputs to those tools should be. Those tools are then executed, and the outputs are fed back into the LLM as observations. The while-loop terminates when the agent decides it is not worth calling any more tools. ### ReAct implementation There are several differences between this paper and the pre-built [`createReactAgent`](/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html) implementation: - First, we use [tool-calling](#tool-calling) to have LLMs call tools, whereas the paper used prompting + parsing of raw output. This is because tool calling did not exist when the paper was written, but is generally better and more reliable. - Second, we use messages to prompt the LLM, whereas the paper used string formatting. This is because at the time of writing, LLMs didn't even expose a message-based interface, whereas now that's the only interface they expose. - Third, the paper required all inputs to the tools to be a single string. This was largely due to LLMs not being super capable at the time, and only really being able to generate a single input. Our implementation allows for using tools that require multiple inputs. - Fourth, the paper only looks at calling a single tool at the time, largely due to limitations in LLMs performance at the time. Our implementation allows for calling multiple tools at a time. - Finally, the paper asked the LLM to explicitly generate a "Thought" step before deciding which tools to call. This is the "Reasoning" part of "ReAct". Our implementation does not do this by default, largely because LLMs have gotten much better and that is not as necessary. Of course, if you wish to prompt it do so, you certainly can. ## Custom agent architectures While routers and tool-calling agents (like ReAct) are common, [customizing agent architectures](https://blog.langchain.dev/why-you-should-outsource-your-agentic-infrastructure-but-own-your-cognitive-architecture/) often leads to better performance for specific tasks. LangGraph offers several powerful features for building tailored agent systems: ### Human-in-the-loop Human involvement can significantly enhance agent reliability, especially for sensitive tasks. This can involve: - Approving specific actions - Providing feedback to update the agent's state - Offering guidance in complex decision-making processes Human-in-the-loop patterns are crucial when full automation isn't feasible or desirable. Learn more in our [human-in-the-loop guide](./human_in_the_loop.md). ### Parallelization Parallel processing is vital for efficient multi-agent systems and complex tasks. LangGraph supports parallelization through its [Send](./low_level.md#send) API, enabling: - Concurrent processing of multiple states - Implementation of map-reduce-like operations - Efficient handling of independent subtasks For practical implementation, see our [map-reduce tutorial](/langgraphjs/how-tos/map-reduce/). ### Subgraphs [Subgraphs](./low_level.md#subgraphs) are essential for managing complex agent architectures, particularly in [multi-agent systems](./multi_agent.md). They allow: - Isolated state management for individual agents - Hierarchical organization of agent teams - Controlled communication between agents and the main system Subgraphs communicate with the parent graph through overlapping keys in the state schema. This enables flexible, modular agent design. For implementation details, refer to our [subgraph how-to guide](../how-tos/subgraph.ipynb). ### Reflection Reflection mechanisms can significantly improve agent reliability by: 1. Evaluating task completion and correctness 2. Providing feedback for iterative improvement 3. Enabling self-correction and learning While often LLM-based, reflection can also use deterministic methods. For instance, in coding tasks, compilation errors can serve as feedback. This approach is demonstrated in [this video using LangGraph for self-corrective code generation](https://www.youtube.com/watch?v=MvNdgmM7uyc). By leveraging these features, LangGraph enables the creation of sophisticated, task-specific agent architectures that can handle complex workflows, collaborate effectively, and continuously improve their performance. --- concepts/assistants.md --- # Assistants !!! info "Prerequisites" - [LangGraph Server](./langgraph_server.md) When building agents, it is fairly 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 agents. Assistants offer an easy way to make and save these types of changes to agent configuration. This can have at least two use-cases: - Assistants give developers a quick and easy way to modify and version agents for experimentation. - Assistants can be modified via LangGraph Studio, offering a no-code way to configure agents (e.g., for business users). Assistants build off the concept of ["configuration"](low_level.md#configuration). While ["configuration"](low_level.md#configuration) is available in the open source LangGraph library as well, assistants are only present in [LangGraph Platform](langgraph_platform.md). This is because Assistants are tightly coupled to your deployed graph, and so we can only make them available when we are also deploying the graphs. ## Configuring Assistants In practice, an assistant is just an _instance_ of a graph with a specific configuration. Because of this, multiple assistants can reference the same graph but can contain different configurations, such as prompts, models, and other graph configuration options. The LangGraph Cloud API provides several endpoints for creating and managing assistants. See [this how-to](/langgraphjs/cloud/how-tos/configuration_cloud) for more details on how to create assistants. ## Versioning Assistants Once you've created an assistant, you can save and version it to track changes to the configuration over time. You can think about this at three levels: 1) The graph lays out the general agent application logic 2) The agent configuration options represent parameters that can be changed 3) Assistant versions save and track specific settings of the agent configuration options For example, let's imagine you have a general writing agent. You have created a general graph architecture that works well for writing. However, there are different types of writing, e.g. blogs vs tweets. In order to get the best performance on each use case, you need to make some minor changes to the models and prompts used. In this setup, you could create an assistant for each use case - one for blog writing and one for tweeting. These would share the same graph structure, but they may use different models and different prompts. Read [this how-to](/langgraphjs/cloud/how-tos/assistant_versioning) to learn how you can use assistant versioning through both the [Studio](/langgraphjs/cloud/how-tos/index/#langgraph-studio) and the SDK. ![assistant versions](img/assistants.png) ## Resources For more information on assistants, see the following resources: - [Assistants how-to guides](../how-tos/index.md#assistants) --- concepts/deployment_options.md --- # Deployment Options !!! info "Prerequisites" - [LangGraph Platform](./langgraph_platform.md) - [LangGraph Server](./langgraph_server.md) - [LangGraph Platform Plans](./plans.md) ## Overview There are 4 main options for deploying with the LangGraph Platform: 1. **[Self-Hosted Lite](#self-hosted-lite)**: Available for all plans. 2. **[Self-Hosted Enterprise](#self-hosted-enterprise)**: Available for the **Enterprise** plan. 3. **[Cloud SaaS](#cloud-saas)**: Available for **Plus** and **Enterprise** plans. 4. **[Bring Your Own Cloud](#bring-your-own-cloud)**: Available only for **Enterprise** plans and **only on AWS**. Please see the [LangGraph Platform Plans](./plans.md) for more information on the different plans. The guide below will explain the differences between the deployment options. ## Self-Hosted Enterprise !!! important The Self-Hosted Enterprise version is only available for the **Enterprise** plan. With a Self-Hosted Enterprise deployment, you are responsible for managing the infrastructure, including setting up and maintaining required databases and Redis instances. You’ll build a Docker image using the [LangGraph CLI](./langgraph_cli.md), which can then be deployed on your own infrastructure. For more information, please see: * [Self-Hosted conceptual guide](./self_hosted.md) * [Self-Hosted Deployment how-to guide](../how-tos/deploy-self-hosted.md) ## Self-Hosted Lite !!! important The Self-Hosted Lite version is available for all plans. The Self-Hosted Lite deployment option is a free (up to 1 million nodes executed), limited version of LangGraph Platform that you can run locally or in a self-hosted manner. With a Self-Hosted Lite deployment, you are responsible for managing the infrastructure, including setting up and maintaining required databases and Redis instances. You’ll build a Docker image using the [LangGraph CLI](./langgraph_cli.md), which can then be deployed on your own infrastructure. For more information, please see: * [Self-Hosted conceptual guide](./self_hosted.md) * [Self-Hosted Deployment how-to guide](https://langchain-ai.github.io/langgraphjs/how-tos/deploy-self-hosted/) ## Cloud SaaS !!! important The Cloud SaaS version of LangGraph Platform is only available for **Plus** and **Enterprise** plans. The [Cloud SaaS](./langgraph_cloud.md) version of LangGraph Platform is hosted as part of [LangSmith](https://smith.langchain.com/). The Cloud SaaS version of LangGraph Platform provides a simple way to deploy and manage your LangGraph applications. This deployment option provides an integration with GitHub, allowing you to deploy code from any of your repositories on GitHub. For more information, please see: * [Cloud SaaS Conceptual Guide](./langgraph_cloud.md) * [How to deploy to Cloud SaaS](/langgraphjs/cloud/deployment/cloud.md) ## Bring Your Own Cloud !!! important The Bring Your Own Cloud version of LangGraph Platform is only available for **Enterprise** plans. This combines the best of both worlds for Cloud and Self-Hosted. We manage the infrastructure, so you don't have to, but the infrastructure all runs within your cloud. This is currently only available on AWS. For more information please see: * [Bring Your Own Cloud Conceptual Guide](./bring_your_own_cloud.md) ## Related For more information please see: * [LangGraph Platform Plans](./plans.md) * [LangGraph Platform Pricing](https://www.langchain.com/langgraph-platform-pricing) * [Deployment how-to guides](../how-tos/index.md#deployment) --- concepts/template_applications.md --- # Template Applications !!! note Prerequisites - [LangGraph Studio](./langgraph_studio.md) Templates are open source reference applications designed to help you get started quickly when building with LangGraph. They provide working examples of common agentic workflows that can be customized to your needs. Templates can be accessed via [LangGraph Studio](langgraph_studio.md), or cloned directly from Github. You can download LangGraph Studio and see available templates [here](https://studio.langchain.com/). ## Available templates - **New LangGraph Project**: A simple, minimal chatbot with memory. - [Python](https://github.com/langchain-ai/new-langgraph-project) - [JS/TS](https://github.com/langchain-ai/new-langgraphjs-project) - **ReAct Agent**: A simple agent that can be flexibly extended to many tools. - [Python](https://github.com/langchain-ai/react-agent) - [JS/TS](https://github.com/langchain-ai/react-agent-js) - **Memory Agent**: A ReAct-style agent with an additional tool to store memories for use across conversational threads. - [Python](https://github.com/langchain-ai/memory-agent) - [JS/TS](https://github.com/langchain-ai/memory-agent-js) - **Retrieval Agent**: An agent that includes a retrieval-based question-answering system. - [Python](https://github.com/langchain-ai/retrieval-agent-template) - [JS/TS](https://github.com/langchain-ai/retrieval-agent-template-js) - **Data-enrichment Agent**: An agent that performs web searches and organizes its findings into a structured format. - [Python](https://github.com/langchain-ai/data-enrichment) - [JS/TS](https://github.com/langchain-ai/data-enrichment-js) --- concepts/v0-human-in-the-loop.md --- # Human-in-the-loop Human-in-the-loop (or "on-the-loop") enhances agent capabilities through several common user interaction patterns. Common interaction patterns include: (1) `Approval` - We can interrupt our agent, surface the current state to a user, and allow the user to accept an action. (2) `Editing` - We can interrupt our agent, surface the current state to a user, and allow the user to edit the agent state. (3) `Input` - We can explicitly create a graph node to collect human input and pass that input directly to the agent state. Use-cases for these interaction patterns include: (1) `Reviewing tool calls` - We can interrupt an agent to review and edit the results of tool calls. (2) `Time Travel` - We can manually re-play and / or fork past actions of an agent. ## Persistence All of these interaction patterns are enabled by LangGraph's built-in [persistence](/langgraphjs/concepts/persistence) layer, which will write a checkpoint of the graph state at each step. Persistence allows the graph to stop so that a human can review and / or edit the current state of the graph and then resume with the human's input. ### Breakpoints Adding a [breakpoint](/langgraphjs/concepts/low_level#breakpoints) at a specific location in the graph flow is one way to enable human-in-the-loop. In this case, the developer knows *where* in the workflow human input is needed and simply places a breakpoint prior to or following that particular graph node. Here, we compile our graph with a checkpointer and a breakpoint at the node we want to interrupt before, `step_for_human_in_the_loop`. We then perform one of the above interaction patterns, which will create a new checkpoint if a human edits the graph state. The new checkpoint is saved to the thread and we can resume the graph execution from there by passing in `null` as the input. ```typescript // Compile our graph with a checkpointer and a breakpoint before "step_for_human_in_the_loop" const graph = builder.compile({ checkpointer, interruptBefore: ["step_for_human_in_the_loop"] }); // Run the graph up to the breakpoint const threadConfig = { configurable: { thread_id: "1" }, streamMode: "values" as const }; for await (const event of await graph.stream(inputs, threadConfig)) { console.log(event); } // Perform some action that requires human in the loop // Continue the graph execution from the current checkpoint for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` ### Dynamic Breakpoints Alternatively, the developer can define some *condition* that must be met for a breakpoint to be triggered. This concept of [dynamic breakpoints](/langgraphjs/concepts/low_level#dynamic-breakpoints) is useful when the developer wants to halt the graph under *a particular condition*. This uses a [`NodeInterrupt`](/langgraphjs/reference/classes/langgraph.NodeInterrupt.html), which is a special type of error that can be raised from within a node based upon some condition. As an example, we can define a dynamic breakpoint that triggers when the `input` is longer than 5 characters. ```typescript function myNode(state: typeof GraphAnnotation.State): typeof GraphAnnotation.State { if (state.input.length > 5) { throw new NodeInterrupt(`Received input that is longer than 5 characters: ${state['input']}`); } return state; } ``` Let's assume we run the graph with an input that triggers the dynamic breakpoint and then attempt to resume the graph execution simply by passing in `null` for the input. ```typescript // Attempt to continue the graph execution with no change to state after we hit the dynamic breakpoint for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` The graph will *interrupt* again because this node will be *re-run* with the same graph state. We need to change the graph state such that the condition that triggers the dynamic breakpoint is no longer met. So, we can simply edit the graph state to an input that meets the condition of our dynamic breakpoint (< 5 characters) and re-run the node. ```typescript // Update the state to pass the dynamic breakpoint await graph.updateState(threadConfig, { input: "foo" }); for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` Alternatively, what if we want to keep our current input and skip the node (`myNode`) that performs the check? To do this, we can simply perform the graph update with `"myNode"` as the third positional argument, and pass in `null` for the values. This will make no update to the graph state, but run the update as `myNode`, effectively skipping the node and bypassing the dynamic breakpoint. ```typescript // This update will skip the node `myNode` altogether await graph.updateState(threadConfig, null, "myNode"); for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` See [our guide](/langgraphjs/how-tos/dynamic_breakpoints) for a detailed how-to on doing this! ## Interaction Patterns ### Approval ![](./img/human_in_the_loop/approval.png) Sometimes we want to approve certain steps in our agent's execution. We can interrupt our agent at a [breakpoint](/langgraphjs/concepts/low_level#breakpoints) prior to the step that we want to approve. This is generally recommended for sensitive actions (e.g., using external APIs or writing to a database). With persistence, we can surface the current agent state as well as the next step to a user for review and approval. If approved, the graph resumes execution from the last saved checkpoint, which is saved to the thread: ```typescript // Compile our graph with a checkpointer and a breakpoint before the step to approve const graph = builder.compile({ checkpointer, interruptBefore: ["node_2"] }); // Run the graph up to the breakpoint for await (const event of await graph.stream(inputs, threadConfig)) { console.log(event); } // ... Get human approval ... // If approved, continue the graph execution from the last saved checkpoint for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` See [our guide](/langgraphjs/how-tos/breakpoints) for a detailed how-to on doing this! ### Editing ![](./img/human_in_the_loop/edit_graph_state.png) Sometimes we want to review and edit the agent's state. As with approval, we can interrupt our agent at a [breakpoint](/langgraphjs/concepts/low_level#breakpoints) prior to the step we want to check. We can surface the current state to a user and allow the user to edit the agent state. This can, for example, be used to correct the agent if it made a mistake (e.g., see the section on tool calling below). We can edit the graph state by forking the current checkpoint, which is saved to the thread. We can then proceed with the graph from our forked checkpoint as done before. ```typescript // Compile our graph with a checkpointer and a breakpoint before the step to review const graph = builder.compile({ checkpointer, interruptBefore: ["node_2"] }); // Run the graph up to the breakpoint for await (const event of await graph.stream(inputs, threadConfig)) { console.log(event); } // Review the state, decide to edit it, and create a forked checkpoint with the new state await graph.updateState(threadConfig, { state: "new state" }); // Continue the graph execution from the forked checkpoint for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` See [this guide](/langgraphjs/how-tos/edit-graph-state) for a detailed how-to on doing this! ### Input ![](./img/human_in_the_loop/wait_for_input.png) Sometimes we want to explicitly get human input at a particular step in the graph. We can create a graph node designated for this (e.g., `human_input` in our example diagram). As with approval and editing, we can interrupt our agent at a [breakpoint](/langgraphjs/concepts/low_level#breakpoints) prior to this node. We can then perform a state update that includes the human input, just as we did with editing state. But, we add one thing: We can use `"human_input"` as the node with the state update to specify that the state update *should be treated as a node*. This is subtle, but important: With editing, the user makes a decision about whether or not to edit the graph state. With input, we explicitly define a node in our graph for collecting human input! The state update with the human input then runs *as this node*. ```typescript // Compile our graph with a checkpointer and a breakpoint before the step to collect human input const graph = builder.compile({ checkpointer, interruptBefore: ["human_input"] }); // Run the graph up to the breakpoint for await (const event of await graph.stream(inputs, threadConfig)) { console.log(event); } // Update the state with the user input as if it was the human_input node await graph.updateState(threadConfig, { user_input: userInput }, "human_input"); // Continue the graph execution from the checkpoint created by the human_input node for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` See [this guide](/langgraphjs/how-tos/wait-user-input) for a detailed how-to on doing this! ## Use-cases ### Reviewing Tool Calls Some user interaction patterns combine the above ideas. For example, many agents use [tool calling](https://js.langchain.com/docs/modules/agents/tools/) to make decisions. Tool calling presents a challenge because the agent must get two things right: (1) The name of the tool to call (2) The arguments to pass to the tool Even if the tool call is correct, we may also want to apply discretion: (3) The tool call may be a sensitive operation that we want to approve With these points in mind, we can combine the above ideas to create a human-in-the-loop review of a tool call. ```typescript // Compile our graph with a checkpointer and a breakpoint before the step to review the tool call from the LLM const graph = builder.compile({ checkpointer, interruptBefore: ["human_review"] }); // Run the graph up to the breakpoint for await (const event of await graph.stream(inputs, threadConfig)) { console.log(event); } // Review the tool call and update it, if needed, as the human_review node await graph.updateState(threadConfig, { tool_call: "updated tool call" }, "human_review"); // Otherwise, approve the tool call and proceed with the graph execution with no edits // Continue the graph execution from either: // (1) the forked checkpoint created by human_review or // (2) the checkpoint saved when the tool call was originally made (no edits in human_review) for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` See [this guide](/langgraphjs/how-tos/review-tool-calls) for a detailed how-to on doing this! ### Time Travel When working with agents, we often want to closely examine their decision making process: (1) Even when they arrive at a desired final result, the reasoning that led to that result is often important to examine. (2) When agents make mistakes, it is often valuable to understand why. (3) In either of the above cases, it is useful to manually explore alternative decision making paths. Collectively, we call these debugging concepts `time-travel` and they are composed of `replaying` and `forking`. #### Replaying ![](./img/human_in_the_loop/replay.png) Sometimes we want to simply replay past actions of an agent. Above, we showed the case of executing an agent from the current state (or checkpoint) of the graph. We do this by simply passing in `null` for the input with a `threadConfig`. ```typescript const threadConfig = { configurable: { thread_id: "1" } }; for await (const event of await graph.stream(null, threadConfig)) { console.log(event); } ``` Now, we can modify this to replay past actions from a *specific* checkpoint by passing in the checkpoint ID. To get a specific checkpoint ID, we can easily get all of the checkpoints in the thread and filter to the one we want. ```typescript const allCheckpoints = []; for await (const state of app.getStateHistory(threadConfig)) { allCheckpoints.push(state); } ``` Each checkpoint has a unique ID, which we can use to replay from a specific checkpoint. Assume from reviewing the checkpoints that we want to replay from one, `xxx`. We just pass in the checkpoint ID when we run the graph. ```typescript const config = { configurable: { thread_id: '1', checkpoint_id: 'xxx' }, streamMode: "values" as const }; for await (const event of await graph.stream(null, config)) { console.log(event); } ``` Importantly, the graph knows which checkpoints have been previously executed. So, it will re-play any previously executed nodes rather than re-executing them. See [this additional conceptual guide](https://langchain-ai.github.io/langgraph/concepts/persistence/#replay) for related context on replaying. See [this guide](/langgraphjs/how-tos/time-travel) for a detailed how-to on doing time-travel! #### Forking ![](./img/human_in_the_loop/forking.png) Sometimes we want to fork past actions of an agent, and explore different paths through the graph. `Editing`, as discussed above, is *exactly* how we do this for the *current* state of the graph! But, what if we want to fork *past* states of the graph? For example, let's say we want to edit a particular checkpoint, `xxx`. We pass this `checkpoint_id` when we update the state of the graph. ```typescript const config = { configurable: { thread_id: "1", checkpoint_id: "xxx" } }; await graph.updateState(config, { state: "updated state" }); ``` This creates a new forked checkpoint, `xxx-fork`, which we can then run the graph from. ```typescript const config = { configurable: { thread_id: '1', checkpoint_id: 'xxx-fork' }, streamMode: "values" as const }; for await (const event of await graph.stream(null, config)) { console.log(event); } ``` See [this additional conceptual guide](/langgraphjs/concepts/persistence/#update-state) for related context on forking. See [this guide](/langgraphjs/how-tos/time-travel) for a detailed how-to on doing time-travel! --- concepts/faq.md --- # FAQ Common questions and their answers! ## Do I need to use LangChain in order to use LangGraph? No! LangGraph is a general-purpose framework - the nodes and edges are nothing more than JavaScript/TypeScript functions. You can use LangChain, raw HTTP requests, or even other frameworks inside these nodes and edges. ## Does LangGraph work with LLMs that don't support tool calling? Yes! You can use LangGraph with any LLMs. The main reason we use LLMs that support tool calling is that this is often the most convenient way to have the LLM make its decision about what to do. If your LLM does not support tool calling, you can still use it - you just need to write a bit of logic to convert the raw LLM string response to a decision about what to do. ## Does LangGraph work with OSS LLMs? Yes! LangGraph is totally ambivalent to what LLMs are used under the hood. The main reason we use closed LLMs in most of the tutorials is that they seamlessly support tool calling, while OSS LLMs often don't. But tool calling is not necessary (see [this section](#does-langgraph-work-with-llms-that-dont-support-tool-calling)) so you can totally use LangGraph with OSS LLMs. --- concepts/langgraph_studio.md --- # LangGraph Studio !!! info "Prerequisites" - [LangGraph Platform](./langgraph_platform.md) - [LangGraph Server](./langgraph_server.md) LangGraph Studio offers a new way to develop LLM applications by providing a specialized agent IDE that enables visualization, interaction, and debugging of complex agentic applications. With visual graphs and the ability to edit state, you can better understand agent workflows and iterate faster. LangGraph Studio integrates with LangSmith allowing you to collaborate with teammates to debug failure modes. ![](img/lg_studio.png) ## Features The key features of LangGraph Studio are: - Visualizes your graph - Test your graph by running it from the UI - Debug your agent by [modifying its state and rerunning](human_in_the_loop.md) - Create and manage [assistants](assistants.md) - View and manage [threads](persistence.md#threads) - View and manage [long term memory](memory.md) - Add node input/outputs to [LangSmith](https://smith.langchain.com/) datasets for testing ## Types ### Desktop app LangGraph Studio is available as a [desktop app](https://studio.langchain.com/) for MacOS users. While in Beta, LangGraph Studio is available for free to all [LangSmith](https://smith.langchain.com/) users on any plan tier. ### Cloud studio If you have deployed your LangGraph application on LangGraph Platform (Cloud), you can access the studio as part of that ## Studio FAQs ### Why is my project failing to start? There are a few reasons that your project might fail to start, here are some of the most common ones. #### Docker issues (desktop only) LangGraph Studio (desktop) requires Docker Desktop version 4.24 or higher. Please make sure you have a version of Docker installed that satisfies that requirement and also make sure you have the Docker Desktop app up and running before trying to use LangGraph Studio. In addition, make sure you have docker-compose updated to version 2.22.0 or higher. #### Configuration or environment issues Another reason your project might fail to start is because your configuration file is defined incorrectly, or you are missing required environment variables. ### How does interrupt work? When you select the `Interrupts` dropdown and select a node to interrupt the graph will pause execution before and after (unless the node goes straight to `END`) that node has run. This means that you will be able to both edit the state before the node is ran and the state after the node has ran. This is intended to allow developers more fine-grained control over the behavior of a node and make it easier to observe how the node is behaving. You will not be able to edit the state after the node has ran if the node is the final node in the graph. ### How do I reload the app? (desktop only) If you would like to reload the app, don't use Command+R as you might normally do. Instead, close and reopen the app for a full refresh. ### How does automatic rebuilding work? (desktop only) One of the key features of LangGraph Studio is that it automatically rebuilds your image when you change the source code. This allows for a super fast development and testing cycle which makes it easy to iterate on your graph. There are two different ways that LangGraph rebuilds your image: either by editing the image or completely rebuilding it. #### Rebuilds from source code changes If you modified the source code only (no configuration or dependency changes!) then the image does not require a full rebuild, and LangGraph Studio will only update the relevant parts. The UI status in the bottom left will switch from `Online` to `Stopping` temporarily while the image gets edited. The logs will be shown as this process is happening, and after the image has been edited the status will change back to `Online` and you will be able to run your graph with the modified code! #### Rebuilds from configuration or dependency changes If you edit your graph configuration file (`langgraph.json`) or the dependencies (either `pyproject.toml` or `requirements.txt`) then the entire image will be rebuilt. This will cause the UI to switch away from the graph view and start showing the logs of the new image building process. This can take a minute or two, and once it is done your updated image will be ready to use! ### Why is my graph taking so long to startup? (desktop only) The LangGraph Studio interacts with a local LangGraph API server. To stay aligned with ongoing updates, the LangGraph API requires regular rebuilding. As a result, you may occasionally experience slight delays when starting up your project. ## Why are extra edges showing up in my graph? If you don't define your conditional edges carefully, you might notice extra edges appearing in your graph. This is because without proper definition, LangGraph Studio assumes the conditional edge could access all other nodes. In order for this to not be the case, you need to be explicit about how you define the nodes the conditional edge routes to. There are two ways you can do this: ### Solution 1: Include a path map The first way to solve this is to add path maps to your conditional edges. A path map is just a dictionary or array that maps the possible outputs of your router function with the names of the nodes that each output corresponds to. The path map is passed as the third argument to the `add_conditional_edges` function like so: === "Python" ```python graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"}) ``` === "Javascript" ```ts graph.addConditionalEdges("node_a", routingFunction, { foo: "node_b", bar: "node_c" }); ``` In this case, the routing function returns either True or False, which map to `node_b` and `node_c` respectively. ### Solution 2: Update the typing of the router (Python only) Instead of passing a path map, you can also be explicit about the typing of your routing function by specifying the nodes it can map to using the `Literal` python definition. Here is an example of how to define a routing function in that way: ```python def routing_function(state: GraphState) -> Literal["node_b","node_c"]: if state['some_condition'] == True: return "node_b" else: return "node_c" ``` ## Related For more information please see the following: * [LangGraph Studio how-to guides](../how-tos/index.md#langgraph-studio) --- how-tos/dynamic_breakpoints.ipynb --- # How to add dynamic breakpoints !!! note For **human-in-the-loop** workflows use the new `interrupt()` function for **human-in-the-loop** workflows. Please review the Human-in-the-loop conceptual guide for more information about design patterns with `interrupt`. !!! tip "Prerequisites" This guide assumes familiarity with the following concepts: * Breakpoints * LangGraph Glossary Human-in-the-loop (HIL) interactions are crucial for [agentic systems](https://langchain-ai.github.io/langgraphjs/concepts/agentic_concepts/#human-in-the-loop). [Breakpoints](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#breakpoints) are a common HIL interaction pattern, allowing the graph to stop at specific steps and seek human approval before proceeding (e.g., for sensitive actions). In LangGraph you can add breakpoints before / after a node is executed. But oftentimes it may be helpful to **dynamically** interrupt the graph from inside a given node based on some condition. When doing so, it may also be helpful to include information about **why** that interrupt was raised. This guide shows how you can dynamically interrupt the graph using `NodeInterupt` -- a special exception that can be raised from inside a node. Let's see it in action! ### Define the graph ```typescript import { Annotation, MemorySaver, NodeInterrupt, StateGraph, } from "@langchain/langgraph"; const StateAnnotation = Annotation.Root({ input: Annotation, }); const step1 = async (state: typeof StateAnnotation.State) => { console.log("---Step 1---"); return state; }; const step2 = async (state: typeof StateAnnotation.State) => { // Let's optionally raise a NodeInterrupt // if the length of the input is longer than 5 characters if (state.input?.length > 5) { throw new NodeInterrupt(`Received input that is longer than 5 characters: ${state.input}`); } console.log("---Step 2---"); return state; }; const step3 = async (state: typeof StateAnnotation.State) => { console.log("---Step 3---"); return state; }; const checkpointer = new MemorySaver(); const graph = new StateGraph(StateAnnotation) .addNode("step1", step1) .addNode("step2", step2) .addNode("step3", step3) .addEdge("__start__", "step1") .addEdge("step1", "step2") .addEdge("step2", "step3") .addEdge("step3", "__end__") .compile({ checkpointer }); ``` ```typescript import * as tslab from "tslab"; const representation = graph.getGraph(); const image = await representation.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` ### Run the graph with dynamic interrupt First, let's run the graph with an input that's <= 5 characters long. This should safely ignore the interrupt condition we defined and return the original input at the end of the graph execution. ```typescript const initialInput = { input: "hello" }; const config = { configurable: { thread_id: "1", }, streamMode: "values" as const, }; const stream = await graph.stream(initialInput, config); for await (const event of stream) { console.log(event); } ``` ```output { input: 'hello' } ---Step 1--- { input: 'hello' } ---Step 2--- { input: 'hello' } ---Step 3--- { input: 'hello' } ``` If we inspect the graph at this point, we can see that there are no more tasks left to run and that the graph indeed finished execution. ```typescript const state = await graph.getState(config); console.log(state.next); console.log(state.tasks); ``` ```output [] [] ``` Now, let's run the graph with an input that's longer than 5 characters. This should trigger the dynamic interrupt we defined via raising a `NodeInterrupt` error inside the `step2` node. ```typescript const longInput = { input: "hello world" }; const config2 = { configurable: { thread_id: "2", }, streamMode: "values" as const, }; const streamWithInterrupt = await graph.stream(longInput, config2); for await (const event of streamWithInterrupt) { console.log(event); } ``` ```output { input: 'hello world' } ---Step 1--- { input: 'hello world' } ``` We can see that the graph now stopped while executing `step2`. If we inspect the graph state at this point, we can see the information on what node is set to execute next (`step2`), as well as what node raised the interrupt (also `step2`), and additional information about the interrupt. ```typescript const state2 = await graph.getState(config2); console.log(state2.next); console.log(JSON.stringify(state2.tasks, null, 2)); ``` ```output [ 'step2' ] [ { "id": "c91a38f7-2aec-5c38-a3f0-60fba6efe73c", "name": "step2", "interrupts": [ { "value": "Received input that is longer than 5 characters: hello world", "when": "during" } ] } ] ``` If we try to resume the graph from the breakpoint, we will simply interrupt again as our inputs & graph state haven't changed. ```typescript // NOTE: to resume the graph from a dynamic interrupt we use the same syntax as // regular interrupts -- we pass null as the input const resumedStream = await graph.stream(null, config2); for await (const event of resumedStream) { console.log(event); } ``` ```typescript const state3 = await graph.getState(config2); console.log(state3.next); console.log(JSON.stringify(state2.tasks, null, 2)); ``` ```output [ 'step2' ] [ { "id": "c91a38f7-2aec-5c38-a3f0-60fba6efe73c", "name": "step2", "interrupts": [ { "value": "Received input that is longer than 5 characters: hello world", "when": "during" } ] } ] ``` ### Update the graph state To get around it, we can do several things. First, we could simply run the graph on a different thread with a shorter input, like we did in the beginning. Alternatively, if we want to resume the graph execution from the breakpoint, we can update the state to have an input that's shorter than 5 characters (the condition for our interrupt). ```typescript // NOTE: this update will be applied as of the last successful node before the interrupt, // i.e. `step1`, right before the node with an interrupt await graph.updateState(config2, { input: "short" }); const updatedStream = await graph.stream(null, config2); for await (const event of updatedStream) { console.log(event); } const state4 = await graph.getState(config2); console.log(state4.next); console.log(state4.values); ``` ```output ---Step 2--- { input: 'short' } ---Step 3--- { input: 'short' } [] { input: 'short' } ``` You can also update the state **as node `step2`** (interrupted node) which would skip over that node altogether ```typescript const config3 = { configurable: { thread_id: "3", }, streamMode: "values" as const, }; const skipStream = await graph.stream({ input: "hello world" }, config3); // Run the graph until the first interruption for await (const event of skipStream) { console.log(event); } ``` ```output { input: 'hello world' } ---Step 1--- { input: 'hello world' } ``` ```typescript // NOTE: this update will skip the node `step2` entirely await graph.updateState(config3, undefined, "step2"); // Resume the stream for await (const event of await graph.stream(null, config3)) { console.log(event); } const state5 = await graph.getState(config3); console.log(state5.next); console.log(state5.values); ``` ```output ---Step 3--- { input: 'hello world' } [] { input: 'hello world' } ``` --- how-tos/pass_private_state.ipynb --- # How to pass private state Oftentimes, you may want nodes to be able to pass state to each other that should NOT be part of the main schema of the graph. This is often useful because there may be information that is not needed as input/output (and therefore doesn't really make sense to have in the main schema) but is needed as part of the intermediate working logic. Let's take a look at an example below. In this example, we will create a RAG pipeline that: 1. Takes in a user question 2. Uses an LLM to generate a search query 3. Retrieves documents for that generated query 4. Generates a final answer based on those documents We will have a separate node for each step. We will only have the `question` and `answer` on the overall state. However, we will need separate states for the `search_query` and the `documents` - we will pass these as private state keys by defining an `input` annotation on each relevant node. Let's look at an example! ```typescript import { Annotation, StateGraph } from "@langchain/langgraph"; // The overall state of the graph const OverallStateAnnotation = Annotation.Root({ question: Annotation, answer: Annotation, }); // This is what the node that generates the query will return const QueryOutputAnnotation = Annotation.Root({ query: Annotation, }); // This is what the node that retrieves the documents will return const DocumentOutputAnnotation = Annotation.Root({ docs: Annotation, }); // This is what the node that retrieves the documents will return const GenerateOutputAnnotation = Annotation.Root({ ...OverallStateAnnotation.spec, ...DocumentOutputAnnotation.spec }); // Node to generate query const generateQuery = async (state: typeof OverallStateAnnotation.State) => { // Replace this with real logic return { query: state.question + " rephrased as a query!", }; }; // Node to retrieve documents const retrieveDocuments = async (state: typeof QueryOutputAnnotation.State) => { // Replace this with real logic return { docs: [state.query, "some random document"], }; }; // Node to generate answer const generate = async (state: typeof GenerateOutputAnnotation.State) => { return { answer: state.docs.concat([state.question]).join("\n\n"), }; }; const graph = new StateGraph(OverallStateAnnotation) .addNode("generate_query", generateQuery) .addNode("retrieve_documents", retrieveDocuments, { input: QueryOutputAnnotation }) .addNode("generate", generate, { input: GenerateOutputAnnotation }) .addEdge("__start__", "generate_query") .addEdge("generate_query", "retrieve_documents") .addEdge("retrieve_documents", "generate") .compile(); await graph.invoke({ question: "How are you?", }); ``` ```output { question: 'How are you?', answer: 'How are you? rephrased as a query!\n\nsome random document\n\nHow are you?' } ``` Above, the original `question` value in the input has been preserved, but that the `generate_query` node rephrased it, the `retrieve_documents` node added `"some random document"`, and finally the `generate` node combined the `docs` in the state with the original question to create an `answer`. The intermediate steps populated by the `input` annotations passed to the individual nodes are not present in the final output. --- how-tos/dynamically-returning-directly.ipynb --- # How to let agent return tool results directly A typical ReAct loop follows user -> assistant -> tool -> assistant ..., -> user. In some cases, you don't need to call the LLM after the tool completes, the user can view the results directly themselves. In this example we will build a conversational ReAct agent where the LLM can optionally decide to return the result of a tool call as the final answer. This is useful in cases where you have tools that can sometimes generate responses that are acceptable as final answers, and you want to use the LLM to determine when that is the case ## Setup First we need to install the required packages: ```bash yarn add @langchain/langgraph @langchain/openai @langchain/core ``` Next, we need to set API keys for OpenAI (the LLM we will use). Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..." process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "Direct Return: LangGraphJS"; ``` ```output Direct Return: LangGraphJS ``` ## Set up the tools We will first define the tools we want to use. For this simple example, we will use a simple placeholder "search engine". However, it is really easy to create your own tools - see documentation [here](https://js.langchain.com/docs/modules/agents/tools/dynamic) on how to do that. To add a 'return_direct' option, we will create a custom zod schema to use **instead of** the schema that would be automatically inferred by the tool. ```typescript import { DynamicStructuredTool } from "@langchain/core/tools"; import { z } from "zod"; const SearchTool = z.object({ query: z.string().describe("query to look up online"), // **IMPORTANT** We are adding an **extra** field here // that isn't used directly by the tool - it's used by our // graph instead to determine whether or not to return the // result directly to the user return_direct: z.boolean() .describe( "Whether or not the result of this should be returned directly to the user without you seeing what it is", ) .default(false), }); const searchTool = new DynamicStructuredTool({ name: "search", description: "Call to surf the web.", // We are overriding the default schema here to // add an extra field schema: SearchTool, func: async ({}: { query: string }) => { // This is a placeholder for the actual implementation // Don't let the LLM know this though 😊 return "It's sunny in San Francisco, but you better look out if you're a Gemini 😈."; }, }); const tools = [searchTool]; ``` We can now wrap these tools in a `ToolNode`. This is a prebuilt node that takes in a LangChain chat model's generated tool call and calls that tool, returning the output. ```typescript import { ToolNode } from "@langchain/langgraph/prebuilt"; const toolNode = new ToolNode(tools); ``` ## Set up the model Now we need to load the chat model we want to use.\ Importantly, this should satisfy two criteria: 1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them. 2. It should support [tool calling](https://js.langchain.com/docs/concepts/tool_calling/). Note: these model requirements are not requirements for using LangGraph - they are just requirements for this one example. ```typescript import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ temperature: 0, model: "gpt-3.5-turbo", }); // This formats the tools as json schema for the model API. // The model then uses this like a system prompt. const boundModel = model.bindTools(tools); ``` ## Define the agent state The main type of graph in `langgraph` is the StateGraph. This graph is parameterized by a state object that it passes around to each node. Each node then returns operations to update that state. These operations can either SET specific attributes on the state (e.g. overwrite the existing values) or ADD to the existing attribute. Whether to set or add is denoted in the state object you construct the graph with. For this example, the state we will track will just be a list of messages. We want each node to just add messages to that list. Therefore, we will define the state as follows: ```typescript import { Annotation } from "@langchain/langgraph"; import { BaseMessage } from "@langchain/core/messages"; const AgentState = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); ``` ## Define the nodes We now need to define a few different nodes in our graph. In `langgraph`, a node can be either a function or a [runnable](https://js.langchain.com/docs/expression_language/). There are two main nodes we need for this: 1. The agent: responsible for deciding what (if any) actions to take. 2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action. We will also need to define some edges. Some of these edges may be conditional. The reason they are conditional is that based on the output of a node, one of several paths may be taken. The path that is taken is not known until that node is run (the LLM decides). 1. Conditional Edge: after the agent is called, we should either: a. If the agent said to take an action, then the function to invoke tools should be called b. If the agent said that it was finished, then it should finish 2. Normal Edge: after the tools are invoked, it should always go back to the agent to decide what to do next Let's define the nodes, as well as a function to decide how what conditional edge to take. ```typescript import { RunnableConfig } from "@langchain/core/runnables"; import { END } from "@langchain/langgraph"; import { AIMessage } from "@langchain/core/messages"; // Define the function that determines whether to continue or not const shouldContinue = (state: typeof AgentState.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as AIMessage; // If there is no function call, then we finish if (!lastMessage?.tool_calls?.length) { return END; } // Otherwise if there is, we check if it's suppose to return direct else { const args = lastMessage.tool_calls[0].args; if (args?.return_direct) { return "final"; } else { return "tools"; } } }; // Define the function that calls the model const callModel = async (state: typeof AgentState.State, config?: RunnableConfig) => { const messages = state.messages; const response = await boundModel.invoke(messages, config); // We return an object, because this will get added to the existing list return { messages: [response] }; }; ``` ## Define the graph We can now put it all together and define the graph! ```typescript import { START, StateGraph } from "@langchain/langgraph"; // Define a new graph const workflow = new StateGraph(AgentState) // Define the two nodes we will cycle between .addNode("agent", callModel) // Note the "action" and "final" nodes are identical! .addNode("tools", toolNode) .addNode("final", toolNode) // Set the entrypoint as `agent` .addEdge(START, "agent") // We now add a conditional edge .addConditionalEdges( // First, we define the start node. We use `agent`. "agent", // Next, we pass in the function that will determine which node is called next. shouldContinue, ) // We now add a normal edge from `tools` to `agent`. .addEdge("tools", "agent") .addEdge("final", END); // Finally, we compile it! const app = workflow.compile(); ``` ## Use it! We can now use it! This now exposes the [same interface](https://js.langchain.com/docs/expression_language/) as all other LangChain runnables. ```typescript import { HumanMessage, isAIMessage } from "@langchain/core/messages"; const prettyPrint = (message: BaseMessage) => { let txt = `[${message._getType()}]: ${message.content}`; if ( isAIMessage(message) && (message as AIMessage)?.tool_calls?.length || 0 > 0 ) { const tool_calls = (message as AIMessage)?.tool_calls ?.map((tc) => `- ${tc.name}(${JSON.stringify(tc.args)})`) .join("\n"); txt += ` \nTools: \n${tool_calls}`; } console.log(txt); }; const inputs = { messages: [new HumanMessage("what is the weather in sf")] }; for await (const output of await app.stream(inputs, { streamMode: "values" })) { const lastMessage = output.messages[output.messages.length - 1]; prettyPrint(lastMessage); console.log("-----\n"); } ``` ```output [human]: what is the weather in sf ----- [ai]: Tools: - search({"query":"weather in San Francisco"}) ----- [tool]: It's sunny in San Francisco, but you better look out if you're a Gemini 😈. ----- [ai]: The weather in San Francisco is sunny. ----- ``` ```typescript const inputs2 = { messages: [ new HumanMessage( "what is the weather in sf? return this result directly by setting return_direct = True", ), ], }; for await ( const output of await app.stream(inputs2, { streamMode: "values" }) ) { const lastMessage = output.messages[output.messages.length - 1]; prettyPrint(lastMessage); console.log("-----\n"); } ``` ```output [human]: what is the weather in sf? return this result directly by setting return_direct = True ----- [ai]: Tools: - search({"query":"weather in San Francisco","return_direct":true}) ----- [tool]: It's sunny in San Francisco, but you better look out if you're a Gemini 😈. ----- ``` Done! The graph **stopped** after running the `tools` node! ``` ``` --- how-tos/cross-thread-persistence-functional.ipynb --- # How to add cross-thread persistence (functional API) !!! info "Prerequisites" This guide assumes familiarity with the following: - Functional API - Persistence - Memory - [Chat Models](https://js.langchain.com/docs/concepts/chat_models/) LangGraph allows you to persist data across **different threads**. For instance, you can store information about users (their names or preferences) in a shared (cross-thread) memory and reuse them in the new threads (e.g., new conversations). When using the functional API, you can set it up to store and retrieve memories by using the Store interface: 1. Create an instance of a `Store` ```ts import { InMemoryStore } from "@langchain/langgraph"; const store = new InMemoryStore(); ``` 2. Pass the `store` instance to the `entrypoint()` wrapper function. It will be passed to the workflow as `config.store`. ```ts import { entrypoint } from "@langchain/langgraph"; const workflow = entrypoint({ store, name: "myWorkflow", }, async (input, config) => { const foo = await myTask({input, store: config.store}); ... }); ``` In this guide, we will show how to construct and use a workflow that has a shared memory implemented using the Store interface. !!! tip "Note" If you need to add cross-thread persistence to a `StateGraph`, check out this how-to guide. ## Setup !!! note Compatibility This guide requires `@langchain/langgraph>=0.2.42`. First, install the required dependencies for this example: ```bash npm install @langchain/langgraph @langchain/openai @langchain/anthropic @langchain/core uuid ``` Next, we need to set API keys for Anthropic and OpenAI (the LLM and embeddings we will use): ```typescript process.env.OPENAI_API_KEY = "YOUR_API_KEY"; process.env.ANTHROPIC_API_KEY = "YOUR_API_KEY"; ``` !!! tip "Set up [LangSmith](https://smith.langchain.com) for LangGraph development" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com) ## Example: simple chatbot with long-term memory ### Define store In this example we will create a workflow that will be able to retrieve information about a user's preferences. We will do so by defining an `InMemoryStore` - an object that can store data in memory and query that data. When storing objects using the `Store` interface you define two things: * the namespace for the object, a tuple (similar to directories) * the object key (similar to filenames) In our example, we'll be using `["memories", ]` as namespace and random UUID as key for each new memory. Let's first define our store: ```typescript import { InMemoryStore } from "@langchain/langgraph"; import { OpenAIEmbeddings } from "@langchain/openai"; const inMemoryStore = new InMemoryStore({ index: { embeddings: new OpenAIEmbeddings({ model: "text-embedding-3-small", }), dims: 1536, }, }); ``` ### Create workflow Now let's create our workflow: ```typescript import { v4 } from "uuid"; import { ChatAnthropic } from "@langchain/anthropic"; import { entrypoint, task, MemorySaver, addMessages, type BaseStore, getStore, } from "@langchain/langgraph"; import type { BaseMessage, BaseMessageLike } from "@langchain/core/messages"; const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest", }); const callModel = task("callModel", async ( messages: BaseMessage[], memoryStore: BaseStore, userId: string ) => { const namespace = ["memories", userId]; const lastMessage = messages.at(-1); if (typeof lastMessage?.content !== "string") { throw new Error("Received non-string message content."); } const memories = await memoryStore.search(namespace, { query: lastMessage.content, }); const info = memories.map((memory) => memory.value.data).join("\n"); const systemMessage = `You are a helpful assistant talking to the user. User info: ${info}`; // Store new memories if the user asks the model to remember if (lastMessage.content.toLowerCase().includes("remember")) { // Hard-coded for demo const memory = `Username is Bob`; await memoryStore.put(namespace, v4(), { data: memory }); } const response = await model.invoke([ { role: "system", content: systemMessage }, ...messages ]); return response; }); // NOTE: we're passing the store object here when creating a workflow via entrypoint() const workflow = entrypoint({ checkpointer: new MemorySaver(), store: inMemoryStore, name: "workflow", }, async (params: { messages: BaseMessageLike[]; userId: string; }, config) => { const messages = addMessages([], params.messages) const response = await callModel(messages, config.store, params.userId); return entrypoint.final({ value: response, save: addMessages(messages, response), }); }); ``` The current store is passed in as part of the entrypoint's second argument, as `config.store`. !!! note Note If you're using LangGraph Cloud or LangGraph Studio, you __don't need__ to pass store into the entrypoint, since it's done automatically. ### Run the workflow! Now let's specify a user ID in the config and tell the model our name: ```typescript const config = { configurable: { thread_id: "1", }, streamMode: "values" as const, }; const inputMessage = { role: "user", content: "Hi! Remember: my name is Bob", }; const stream = await workflow.stream({ messages: [inputMessage], userId: "1" }, config); for await (const chunk of stream) { console.log(chunk); } ``` ```output AIMessage { "id": "msg_01U4xHvf4REPSCGWzpLeh1qJ", "content": "Hi Bob! Nice to meet you. I'll remember that your name is Bob. How can I help you today?", "additional_kwargs": { "id": "msg_01U4xHvf4REPSCGWzpLeh1qJ", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20241022", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 28, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 27 } }, "response_metadata": { "id": "msg_01U4xHvf4REPSCGWzpLeh1qJ", "model": "claude-3-5-sonnet-20241022", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 28, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 27 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 28, "output_tokens": 27, "total_tokens": 55, "input_token_details": { "cache_creation": 0, "cache_read": 0 } } } ``` ```typescript const config2 = { configurable: { thread_id: "2", }, streamMode: "values" as const, }; const followupStream = await workflow.stream({ messages: [{ role: "user", content: "what is my name?", }], userId: "1" }, config2); for await (const chunk of followupStream) { console.log(chunk); } ``` ```output AIMessage { "id": "msg_01LB4YapkFawBUbpiu3oeWbF", "content": "Your name is Bob.", "additional_kwargs": { "id": "msg_01LB4YapkFawBUbpiu3oeWbF", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20241022", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 28, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 8 } }, "response_metadata": { "id": "msg_01LB4YapkFawBUbpiu3oeWbF", "model": "claude-3-5-sonnet-20241022", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 28, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 8 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 28, "output_tokens": 8, "total_tokens": 36, "input_token_details": { "cache_creation": 0, "cache_read": 0 } } } ``` We can now inspect our in-memory store and verify that we have in fact saved the memories for the user: ```typescript const memories = await inMemoryStore.search(["memories", "1"]); for (const memory of memories) { console.log(memory.value); } ``` ```output { data: 'Username is Bob' } ``` Let's now run the workflow for another user to verify that the memories about the first user are self contained: ```typescript const config3 = { configurable: { thread_id: "3", }, streamMode: "values" as const, }; const otherUserStream = await workflow.stream({ messages: [{ role: "user", content: "what is my name?", }], userId: "2" }, config3); for await (const chunk of otherUserStream) { console.log(chunk); } ``` ```output AIMessage { "id": "msg_01KK7CweVY4ZdHxU5bPa4skv", "content": "I don't have any information about your name. While I aim to be helpful, I can only know what you directly tell me during our conversation.", "additional_kwargs": { "id": "msg_01KK7CweVY4ZdHxU5bPa4skv", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20241022", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 25, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 33 } }, "response_metadata": { "id": "msg_01KK7CweVY4ZdHxU5bPa4skv", "model": "claude-3-5-sonnet-20241022", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 25, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 33 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 25, "output_tokens": 33, "total_tokens": 58, "input_token_details": { "cache_creation": 0, "cache_read": 0 } } } ``` --- how-tos/react-agent-from-scratch-functional.ipynb --- # How to create a ReAct agent from scratch (Functional API) !!! info "Prerequisites" This guide assumes familiarity with the following: - [Chat Models](https://js.langchain.com/docs/concepts/chat_models) - [Messages](https://js.langchain.com/docs/concepts/messages) - [Tool Calling](https://js.langchain.com/docs/concepts/tool_calling/) - Entrypoints and Tasks This guide demonstrates how to implement a ReAct agent using the LangGraph Functional API. The ReAct agent is a tool-calling agent that operates as follows: 1. Queries are issued to a chat model; 2. If the model generates no tool calls, we return the model response. 3. If the model generates tool calls, we execute the tool calls with available tools, append them as [tool messages](https://js.langchain.com/docs/concepts/messages/) to our message list, and repeat the process. This is a simple and versatile set-up that can be extended with memory, human-in-the-loop capabilities, and other features. See the dedicated how-to guides for examples. ## Setup !!! note Compatibility This guide requires `@langchain/langgraph>=0.2.42`. First, install the required dependencies for this example: ```bash npm install @langchain/langgraph @langchain/openai @langchain/core zod ``` Next, we need to set API keys for OpenAI (the LLM we will use): ```typescript process.env.OPENAI_API_KEY = "YOUR_API_KEY"; ``` !!! tip "Set up [LangSmith](https://smith.langchain.com) for LangGraph development" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com) ## Create ReAct agent Now that you have installed the required packages and set your environment variables, we can create our agent. ### Define model and tools Let's first define the tools and model we will use for our example. Here we will use a single place-holder tool that gets a description of the weather for a location. We will use an [OpenAI](https://js.langchain.com/docs/integrations/providers/openai/) chat model for this example, but any model [supporting tool-calling](https://js.langchain.com/docs/integrations/chat/) will suffice. ```typescript import { ChatOpenAI } from "@langchain/openai"; import { tool } from "@langchain/core/tools"; import { z } from "zod"; const model = new ChatOpenAI({ model: "gpt-4o-mini", }); const getWeather = tool(async ({ location }) => { const lowercaseLocation = location.toLowerCase(); if (lowercaseLocation.includes("sf") || lowercaseLocation.includes("san francisco")) { return "It's sunny!"; } else if (lowercaseLocation.includes("boston")) { return "It's rainy!"; } else { return `I am not sure what the weather is in ${location}`; } }, { name: "getWeather", schema: z.object({ location: z.string().describe("location to get the weather for"), }), description: "Call to get the weather from a specific location." }); const tools = [getWeather]; ``` ### Define tasks We next define the tasks we will execute. Here there are two different tasks: 1. **Call model**: We want to query our chat model with a list of messages. 2. **Call tool**: If our model generates tool calls, we want to execute them. ```typescript import { type BaseMessageLike, AIMessage, ToolMessage, } from "@langchain/core/messages"; import { type ToolCall } from "@langchain/core/messages/tool"; import { task } from "@langchain/langgraph"; const toolsByName = Object.fromEntries(tools.map((tool) => [tool.name, tool])); const callModel = task("callModel", async (messages: BaseMessageLike[]) => { const response = await model.bindTools(tools).invoke(messages); return response; }); const callTool = task( "callTool", async (toolCall: ToolCall): Promise => { const tool = toolsByName[toolCall.name]; const observation = await tool.invoke(toolCall.args); return new ToolMessage({ content: observation, tool_call_id: toolCall.id }); // Can also pass toolCall directly into the tool to return a ToolMessage // return tool.invoke(toolCall); }); ``` ### Define entrypoint Our entrypoint will handle the orchestration of these two tasks. As described above, when our `callModel` task generates tool calls, the `callTool` task will generate responses for each. We append all messages to a single messages list. ```typescript import { entrypoint, addMessages } from "@langchain/langgraph"; const agent = entrypoint( "agent", async (messages: BaseMessageLike[]) => { let currentMessages = messages; let llmResponse = await callModel(currentMessages); while (true) { if (!llmResponse.tool_calls?.length) { break; } // Execute tools const toolResults = await Promise.all( llmResponse.tool_calls.map((toolCall) => { return callTool(toolCall); }) ); // Append to message list currentMessages = addMessages(currentMessages, [llmResponse, ...toolResults]); // Call model again llmResponse = await callModel(currentMessages); } return llmResponse; } ); ``` ## Usage To use our agent, we invoke it with a messages list. Based on our implementation, these can be LangChain [message](https://js.langchain.com/docs/concepts/messages/) objects or OpenAI-style objects: ```typescript import { BaseMessage, isAIMessage } from "@langchain/core/messages"; const prettyPrintMessage = (message: BaseMessage) => { console.log("=".repeat(30), `${message.getType()} message`, "=".repeat(30)); console.log(message.content); if (isAIMessage(message) && message.tool_calls?.length) { console.log(JSON.stringify(message.tool_calls, null, 2)); } } // Usage example const userMessage = { role: "user", content: "What's the weather in san francisco?" }; console.log(userMessage); const stream = await agent.stream([userMessage]); for await (const step of stream) { for (const [taskName, update] of Object.entries(step)) { const message = update as BaseMessage; // Only print task updates if (taskName === "agent") continue; console.log(`\n${taskName}:`); prettyPrintMessage(message); } } ``` ```output { role: 'user', content: "What's the weather in san francisco?" } callModel: ============================== ai message ============================== [ { "name": "getWeather", "args": { "location": "San Francisco" }, "type": "tool_call", "id": "call_m5jZoH1HUtH6wA2QvexOHutj" } ] callTool: ============================== tool message ============================== It's sunny! callModel: ============================== ai message ============================== The weather in San Francisco is sunny! ``` Perfect! The graph correctly calls the `getWeather` tool and responds to the user after receiving the information from the tool. Check out the LangSmith trace [here](https://smith.langchain.com/public/8132d3b8-2c91-40fc-b660-b766ca33e9cb/r). ## Add thread-level persistence Adding thread-level persistence lets us support conversational experiences with our agent: subsequent invocations will append to the prior messages list, retaining the full conversational context. To add thread-level persistence to our agent: 1. Select a checkpointer: here we will use MemorySaver, a simple in-memory checkpointer. 2. Update our entrypoint to accept the previous messages state as a second argument. Here, we simply append the message updates to the previous sequence of messages. 3. Choose which values will be returned from the workflow and which will be saved by the checkpointer. We will be able to access it as `getPreviousState()` if we return it from `entrypoint.final` (optional) ```typescript hl_lines="6 10 37 38 39 40" import { MemorySaver, getPreviousState, } from "@langchain/langgraph"; const checkpointer = new MemorySaver(); const agentWithMemory = entrypoint({ name: "agentWithMemory", checkpointer, }, async (messages: BaseMessageLike[]) => { const previous = getPreviousState() ?? []; let currentMessages = addMessages(previous, messages); let llmResponse = await callModel(currentMessages); while (true) { if (!llmResponse.tool_calls?.length) { break; } // Execute tools const toolResults = await Promise.all( llmResponse.tool_calls.map((toolCall) => { return callTool(toolCall); }) ); // Append to message list currentMessages = addMessages(currentMessages, [llmResponse, ...toolResults]); // Call model again llmResponse = await callModel(currentMessages); } // Append final response for storage currentMessages = addMessages(currentMessages, llmResponse); return entrypoint.final({ value: llmResponse, save: currentMessages, }); }); ``` We will now need to pass in a config when running our application. The config will specify an identifier for the conversational thread. !!! tip Read more about thread-level persistence in our concepts page and how-to guides. ```typescript const config = { configurable: { thread_id: "1" } }; ``` We start a thread the same way as before, this time passing in the config: ```typescript const streamWithMemory = await agentWithMemory.stream([{ role: "user", content: "What's the weather in san francisco?", }], config); for await (const step of streamWithMemory) { for (const [taskName, update] of Object.entries(step)) { const message = update as BaseMessage; // Only print task updates if (taskName === "agentWithMemory") continue; console.log(`\n${taskName}:`); prettyPrintMessage(message); } } ``` ```output callModel: ============================== ai message ============================== [ { "name": "getWeather", "args": { "location": "san francisco" }, "type": "tool_call", "id": "call_4vaZqAxUabthejqKPRMq0ngY" } ] callTool: ============================== tool message ============================== It's sunny! callModel: ============================== ai message ============================== The weather in San Francisco is sunny! ``` When we ask a follow-up conversation, the model uses the prior context to infer that we are asking about the weather: ```typescript const followupStreamWithMemory = await agentWithMemory.stream([{ role: "user", content: "How does it compare to Boston, MA?", }], config); for await (const step of followupStreamWithMemory) { for (const [taskName, update] of Object.entries(step)) { const message = update as BaseMessage; // Only print task updates if (taskName === "agentWithMemory") continue; console.log(`\n${taskName}:`); prettyPrintMessage(message); } } ``` ```output callModel: ============================== ai message ============================== [ { "name": "getWeather", "args": { "location": "boston, ma" }, "type": "tool_call", "id": "call_YDrNfZr5XnuBBq5jlIXaxC5v" } ] callTool: ============================== tool message ============================== It's rainy! callModel: ============================== ai message ============================== In comparison, while San Francisco is sunny, Boston, MA is experiencing rain. ``` In the [LangSmith trace](https://smith.langchain.com/public/ec803712-ecfc-49b6-8f54-92252d1e5e33/r), we can see that the full conversational context is retained in each model call. --- how-tos/react-memory.ipynb --- # How to add memory to the prebuilt ReAct agent This tutorial will show how to add memory to the prebuilt ReAct agent. Please see this tutorial for how to get started with the prebuilt ReAct agent All we need to do to enable memory is pass in a checkpointer to `createReactAgent` ## Setup First, we need to install the required packages. ```bash yarn add @langchain/langgraph @langchain/openai @langchain/core ``` This guide will use OpenAI's GPT-4o model. We will optionally set our API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..." // process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "ReAct Agent with memory: LangGraphJS"; ``` ```output ReAct Agent with memory: LangGraphJS ``` ## Code Now we can use the prebuilt `createReactAgent` function to setup our agent with memory: ```typescript import { ChatOpenAI } from "@langchain/openai"; import { tool } from '@langchain/core/tools'; import { z } from 'zod'; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { MemorySaver } from "@langchain/langgraph"; const model = new ChatOpenAI({ model: "gpt-4o", }); const getWeather = tool((input) => { if (input.location === 'sf') { return 'It\'s always sunny in sf'; } else { return 'It might be cloudy in nyc'; } }, { name: 'get_weather', description: 'Call to get the current weather.', schema: z.object({ location: z.enum(['sf','nyc']).describe("Location to get the weather for."), }) }) // Here we only save in-memory const memory = new MemorySaver(); const agent = createReactAgent({ llm: model, tools: [getWeather], checkpointSaver: memory }); ``` ## Usage Let's interact with it multiple times to show that it can remember prior information ```typescript let inputs = { messages: [{ role: "user", content: "what is the weather in NYC?" }] }; let config = { configurable: { thread_id: "1" } }; let stream = await agent.stream(inputs, { ...config, streamMode: "values", }); for await ( const { messages } of stream ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output what is the weather in NYC? ----- [ { name: 'get_weather', args: { location: 'nyc' }, type: 'tool_call', id: 'call_m0zEI6sidPPH81G6ygMsKYs1' } ] ----- It might be cloudy in nyc ----- The weather in NYC appears to be cloudy. ----- ``` Notice that when we pass the same the same thread ID, the chat history is preserved ```typescript inputs = { messages: [{ role: "user", content: "What's it known for?" }] }; stream = await agent.stream(inputs, { ...config, streamMode: "values", }); for await ( const { messages } of stream ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output What's it known for? ----- New York City (NYC) is known for many things, including: 1. **Landmarks and Attractions:** - **Statue of Liberty**: An iconic symbol of freedom. - **Empire State Building**: A famous skyscraper offering panoramic views. - **Times Square**: Known for its neon lights and bustling atmosphere. - **Central Park**: A large, urban park offering a natural oasis. 2. **Cultural Institutions:** - **Broadway**: Famous for its theatre productions. - **Metropolitan Museum of Art (The Met)**: One of the largest and most prestigious art museums. - **Museum of Modern Art (MoMA) and American Museum of Natural History**: Other significant museums. 3. **Economy and Business:** - **Wall Street**: The financial hub of the world, home to the New York Stock Exchange. - **Headquarters of major corporations**: NYC hosts the headquarters of many large multinational companies. 4. **Diversity and Neighborhoods:** - **Cultural Melting Pot**: NYC is known for its diverse population with a wide range of ethnicities and cultures. - **Distinct Neighborhoods**: Each borough and neighborhood (like Brooklyn, The Bronx, Queens, Staten Island, and Manhattan) has its unique character. 5. **Food and Cuisine:** - **Culinary Capital**: Known for diverse food options from street food like hot dogs and pretzels to high-end dining. - **Cultural Cuisine**: Offers a variety of world cuisines due to its diverse population. 6. **Media and Entertainment:** - **Media Headquarters**: Home to major media companies and news networks. - **Film and Television**: A popular setting and production location for films and TV shows. 7. **Events and Festivities:** - **Macy's Thanksgiving Day Parade**: A famous annual parade. - **New Year's Eve in Times Square**: Known for the ball drop and celebrations. NYC is a dynamic and vibrant city with a rich history and an influence that extends globally in various sectors. ----- ``` When we pass it a new thread ID, all the history is lost and their is no memory to speak of: ```typescript inputs = { messages: [{ role: "user", content: "how close is it to boston?" }] }; config = { configurable: { thread_id: "2" } }; stream = await agent.stream(inputs, { ...config, streamMode: "values", }); for await ( const { messages } of stream ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output how close is it to boston? ----- [ { name: 'get_weather', args: { location: 'nyc' }, type: 'tool_call', id: 'call_CKgDJqHiadzNLGhB8T8pHQWM' } ] ----- It might be cloudy in nyc ----- To determine how close "it" is to Boston, could you please specify which location you're referring to? For instance, are you asking about the distance from New York City, San Francisco, or another location? This detail will help me provide an accurate answer. ----- ``` --- how-tos/use-in-web-environments.ipynb --- # How to use LangGraph.js in web environments LangGraph.js uses the [`async_hooks`](https://nodejs.org/api/async_hooks.html) API to more conveniently allow for tracing and callback propagation within nodes. This API is supported in many environments, such as [Node.js](https://nodejs.org/api/async_hooks.html), [Deno](https://deno.land/std@0.177.0/node/internal/async_hooks.ts), [Cloudflare Workers](https://developers.cloudflare.com/workers/runtime-apis/nodejs/asynclocalstorage/), and the [Edge runtime](https://vercel.com/docs/functions/runtimes/edge-runtime#compatible-node.js-modules), but not all, such as within web browsers. To allow usage of LangGraph.js in environments that do not have the `async_hooks` API available, we've added a separate `@langchain/langgraph/web` entrypoint. This entrypoint exports everything that the primary `@langchain/langgraph` exports, but will not initialize or even import `async_hooks`. Here's a simple example: ```typescript // Import from "@langchain/langgraph/web" import { END, START, StateGraph, Annotation, } from "@langchain/langgraph/web"; import { BaseMessage, HumanMessage } from "@langchain/core/messages"; const GraphState = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); const nodeFn = async (_state: typeof GraphState.State) => { return { messages: [new HumanMessage("Hello from the browser!")] }; }; // Define a new graph const workflow = new StateGraph(GraphState) .addNode("node", nodeFn) .addEdge(START, "node") .addEdge("node", END); const app = workflow.compile({}); // Use the Runnable const finalState = await app.invoke( { messages: [] }, ); console.log(finalState.messages[finalState.messages.length - 1].content); ``` ```output Hello from the browser! ``` Other entrypoints, such as `@langchain/langgraph/prebuilt`, can be used in either environment.

Caution

If you are using LangGraph.js on the frontend, make sure you are not exposing any private keys! For chat models, this means you need to use something like WebLLM that can run client-side without authentication.

## Passing config The lack of `async_hooks` support in web browsers means that if you are calling a [`Runnable`](https://js.langchain.com/docs/concepts/runnables/) within a node (for example, when calling a chat model), you need to manually pass a `config` object through to properly support tracing, [`.streamEvents()`](https://js.langchain.com/docs/how_to/streaming#using-stream-events) to stream intermediate steps, and other callback related functionality. This config object will passed in as the second argument of each node, and should be used as the second parameter of any `Runnable` method. To illustrate this, let's set up our graph again as before, but with a `Runnable` within our node. First, we'll avoid passing `config` through into the nested function, then try to use `.streamEvents()` to see the intermediate results of the nested function: ```typescript // Import from "@langchain/langgraph/web" import { END, START, StateGraph, Annotation, } from "@langchain/langgraph/web"; import { BaseMessage } from "@langchain/core/messages"; import { RunnableLambda } from "@langchain/core/runnables"; import { type StreamEvent } from "@langchain/core/tracers/log_stream"; const GraphState2 = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); const nodeFn2 = async (_state: typeof GraphState2.State) => { // Note that we do not pass any `config` through here const nestedFn = RunnableLambda.from(async (input: string) => { return new HumanMessage(`Hello from ${input}!`); }).withConfig({ runName: "nested" }); const responseMessage = await nestedFn.invoke("a nested function"); return { messages: [responseMessage] }; }; // Define a new graph const workflow2 = new StateGraph(GraphState2) .addNode("node", nodeFn2) .addEdge(START, "node") .addEdge("node", END); const app2 = workflow2.compile({}); // Stream intermediate steps from the graph const eventStream2 = app2.streamEvents( { messages: [] }, { version: "v2" }, { includeNames: ["nested"] }, ); const events2: StreamEvent[] = []; for await (const event of eventStream2) { console.log(event); events2.push(event); } console.log(`Received ${events2.length} events from the nested function`); ``` ```output Received 0 events from the nested function ``` We can see that we get no events. Next, let's try redeclaring the graph with a node that passes config through correctly: ```typescript // Import from "@langchain/langgraph/web" import { END, START, StateGraph, Annotation, } from "@langchain/langgraph/web"; import { BaseMessage } from "@langchain/core/messages"; import { type RunnableConfig, RunnableLambda } from "@langchain/core/runnables"; import { type StreamEvent } from "@langchain/core/tracers/log_stream"; const GraphState3 = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); // Note the second argument here. const nodeFn3 = async (_state: typeof GraphState3.State, config?: RunnableConfig) => { // If you need to nest deeper, remember to pass `_config` when invoking const nestedFn = RunnableLambda.from( async (input: string, _config?: RunnableConfig) => { return new HumanMessage(`Hello from ${input}!`); }, ).withConfig({ runName: "nested" }); const responseMessage = await nestedFn.invoke("a nested function", config); return { messages: [responseMessage] }; }; // Define a new graph const workflow3 = new StateGraph(GraphState3) .addNode("node", nodeFn3) .addEdge(START, "node") .addEdge("node", END); const app3 = workflow3.compile({}); // Stream intermediate steps from the graph const eventStream3 = app3.streamEvents( { messages: [] }, { version: "v2" }, { includeNames: ["nested"] }, ); const events3: StreamEvent[] = []; for await (const event of eventStream3) { console.log(event); events3.push(event); } console.log(`Received ${events3.length} events from the nested function`); ``` ```output { event: "on_chain_start", data: { input: { messages: [] } }, name: "nested", tags: [], run_id: "22747451-a2fa-447b-b62f-9da19a539b2f", metadata: { langgraph_step: 1, langgraph_node: "node", langgraph_triggers: [ "start:node" ], langgraph_task_idx: 0, __pregel_resuming: false, checkpoint_id: "1ef62793-f065-6840-fffe-cdfb4cbb1248", checkpoint_ns: "node" } } { event: "on_chain_end", data: { output: HumanMessage { "content": "Hello from a nested function!", "additional_kwargs": {}, "response_metadata": {} } }, run_id: "22747451-a2fa-447b-b62f-9da19a539b2f", name: "nested", tags: [], metadata: { langgraph_step: 1, langgraph_node: "node", langgraph_triggers: [ "start:node" ], langgraph_task_idx: 0, __pregel_resuming: false, checkpoint_id: "1ef62793-f065-6840-fffe-cdfb4cbb1248", checkpoint_ns: "node" } } Received 2 events from the nested function ``` You can see that we get events from the nested function as expected. ## Next steps You've now learned about some special considerations around using LangGraph.js in web environments. Next, check out some how-to guides on core functionality. --- how-tos/multi-agent-multi-turn-convo.ipynb --- # How to add multi-turn conversation in a multi-agent application !!! info "Prerequisites" This guide assumes familiarity with the following: - Node - Command - Multi-agent systems - Human-in-the-loop In this how-to guide, we’ll build an application that allows an end-user to engage in a *multi-turn conversation* with one or more agents. We'll create a node that uses an `interrupt` to collect user input and routes back to the **active** agent. The agents will be implemented as nodes in a graph that executes agent steps and determines the next action: 1. **Wait for user input** to continue the conversation, or 2. **Route to another agent** (or back to itself, such as in a loop) via a **handoff**. ```typescript function human(state: typeof MessagesAnnotation.State): Command { const userInput: string = interrupt("Ready for user input."); // Determine the active agent const activeAgent = ...; return new Command({ update: { messages: [{ role: "human", content: userInput, }] }, goto: activeAgent, }); } function agent(state: typeof MessagesAnnotation.State): Command { // The condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc. const goto = getNextAgent(...); // 'agent' / 'anotherAgent' if (goto) { return new Command({ goto, update: { myStateKey: "myStateValue" } }); } else { return new Command({ goto: "human" }); } } ``` ## Setup First, let's install the required packages npm install @langchain/langgraph @langchain/openai @langchain/core uuid zod ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..."; process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "Time Travel: LangGraphJS"; ``` ```output Time Travel: LangGraphJS ```

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

## Travel Recommendations Example In this example, we will build a team of travel assistant agents that can communicate with each other via handoffs. We will create 3 agents: * `travelAdvisor`: can help with general travel destination recommendations. Can ask `sightseeingAdvisor` and `hotelAdvisor` for help. * `sightseeingAdvisor`: can help with sightseeing recommendations. Can ask `travelAdvisor` and `hotelAdvisor` for help. * `hotelAdvisor`: can help with hotel recommendations. Can ask `sightseeingAdvisor` and `hotelAdvisor` for help. This is a fully-connected network - every agent can talk to any other agent. To implement the handoffs between the agents we'll be using LLMs with structured output. Each agent's LLM will return an output with both its text response (`response`) as well as which agent to route to next (`goto`). If the agent has enough information to respond to the user, the `goto` will be set to `human` to route back and collect information from a human. Now, let's define our agent nodes and graph! ```typescript import { z } from "zod"; import { ChatOpenAI } from "@langchain/openai"; import { BaseMessage } from "@langchain/core/messages"; import { MessagesAnnotation, StateGraph, START, Command, interrupt, MemorySaver } from "@langchain/langgraph"; const model = new ChatOpenAI({ model: "gpt-4o" }); /** * Call LLM with structured output to get a natural language response as well as a target agent (node) to go to next. * @param messages list of messages to pass to the LLM * @param targetAgentNodes list of the node names of the target agents to navigate to */ function callLlm(messages: BaseMessage[], targetAgentNodes: string[]) { // define the schema for the structured output: // - model's text response (`response`) // - name of the node to go to next (or 'finish') const outputSchema = z.object({ response: z.string().describe("A human readable response to the original question. Does not need to be a final response. Will be streamed back to the user."), goto: z.enum(["finish", ...targetAgentNodes]).describe("The next agent to call, or 'finish' if the user's query has been resolved. Must be one of the specified values."), }) return model.withStructuredOutput(outputSchema, { name: "Response" }).invoke(messages) } async function travelAdvisor( state: typeof MessagesAnnotation.State ): Promise { const systemPrompt = "You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). " + "If you need specific sightseeing recommendations, ask 'sightseeingAdvisor' for help. " + "If you need hotel recommendations, ask 'hotelAdvisor' for help. " + "If you have enough information to respond to the user, return 'finish'. " + "Never mention other agents by name."; const messages = [{"role": "system", "content": systemPrompt}, ...state.messages] as BaseMessage[]; const targetAgentNodes = ["sightseeingAdvisor", "hotelAdvisor"]; const response = await callLlm(messages, targetAgentNodes); const aiMsg = {"role": "ai", "content": response.response, "name": "travelAdvisor"}; let goto = response.goto; if (goto === "finish") { goto = "human"; } return new Command({goto, update: { "messages": [aiMsg] } }); } async function sightseeingAdvisor( state: typeof MessagesAnnotation.State ): Promise { const systemPrompt = "You are a travel expert that can provide specific sightseeing recommendations for a given destination. " + "If you need general travel help, go to 'travelAdvisor' for help. " + "If you need hotel recommendations, go to 'hotelAdvisor' for help. " + "If you have enough information to respond to the user, return 'finish'. " + "Never mention other agents by name."; const messages = [{"role": "system", "content": systemPrompt}, ...state.messages] as BaseMessage[]; const targetAgentNodes = ["travelAdvisor", "hotelAdvisor"]; const response = await callLlm(messages, targetAgentNodes); const aiMsg = {"role": "ai", "content": response.response, "name": "sightseeingAdvisor"}; let goto = response.goto; if (goto === "finish") { goto = "human"; } return new Command({ goto, update: {"messages": [aiMsg] } }); } async function hotelAdvisor( state: typeof MessagesAnnotation.State ): Promise { const systemPrompt = "You are a travel expert that can provide hotel recommendations for a given destination. " + "If you need general travel help, ask 'travelAdvisor' for help. " + "If you need specific sightseeing recommendations, ask 'sightseeingAdvisor' for help. " + "If you have enough information to respond to the user, return 'finish'. " + "Never mention other agents by name."; const messages = [{"role": "system", "content": systemPrompt}, ...state.messages] as BaseMessage[]; const targetAgentNodes = ["travelAdvisor", "sightseeingAdvisor"]; const response = await callLlm(messages, targetAgentNodes); const aiMsg = {"role": "ai", "content": response.response, "name": "hotelAdvisor"}; let goto = response.goto; if (goto === "finish") { goto = "human"; } return new Command({ goto, update: {"messages": [aiMsg] } }); } function humanNode( state: typeof MessagesAnnotation.State ): Command { const userInput: string = interrupt("Ready for user input."); let activeAgent: string | undefined = undefined; // Look up the active agent for (let i = state.messages.length - 1; i >= 0; i--) { if (state.messages[i].name) { activeAgent = state.messages[i].name; break; } } if (!activeAgent) { throw new Error("Could not determine the active agent."); } return new Command({ goto: activeAgent, update: { "messages": [ { "role": "human", "content": userInput, } ] } }); } const builder = new StateGraph(MessagesAnnotation) .addNode("travelAdvisor", travelAdvisor, { ends: ["sightseeingAdvisor", "hotelAdvisor"] }) .addNode("sightseeingAdvisor", sightseeingAdvisor, { ends: ["human", "travelAdvisor", "hotelAdvisor"] }) .addNode("hotelAdvisor", hotelAdvisor, { ends: ["human", "travelAdvisor", "sightseeingAdvisor"] }) // This adds a node to collect human input, which will route // back to the active agent. .addNode("human", humanNode, { ends: ["hotelAdvisor", "sightseeingAdvisor", "travelAdvisor", "human"] }) // We'll always start with a general travel advisor. .addEdge(START, "travelAdvisor") const checkpointer = new MemorySaver() const graph = builder.compile({ checkpointer }) ``` ```typescript import * as tslab from "tslab"; const drawableGraph = graph.getGraph(); const image = await drawableGraph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` ### Test multi-turn conversation Let's test a multi turn conversation with this application. ```typescript import { Command } from "@langchain/langgraph"; import { v4 as uuidv4 } from "uuid"; const threadConfig = { configurable: { thread_id: uuidv4() }, streamMode: "values" as const }; const inputs = [ // 1st round of conversation { messages: [ { role: "user", content: "i wanna go somewhere warm in the caribbean" } ] }, // Since we're using `interrupt`, we'll need to resume using the Command primitive. // 2nd round of conversation new Command({ resume: "could you recommend a nice hotel in one of the areas and tell me which area it is." }), // Third round of conversation new Command({ resume: "could you recommend something to do near the hotel?" }), ] let iter = 0; for await (const userInput of inputs) { iter += 1; console.log(`\n--- Conversation Turn ${iter} ---\n`); console.log(`User: ${JSON.stringify(userInput)}\n`); for await (const update of await graph.stream(userInput, threadConfig)) { const lastMessage = update.messages ? update.messages[update.messages.length - 1] : undefined; if (lastMessage && lastMessage._getType() === "ai") { console.log(`${lastMessage.name}: ${lastMessage.content}`) } } } ``` ```output --- Conversation Turn 1 --- User: {"messages":[{"role":"user","content":"i wanna go somewhere warm in the caribbean"}]} travelAdvisor: The Caribbean is a fantastic choice for a warm getaway! Some popular destinations you might consider include Jamaica, the Dominican Republic, and the Bahamas. Each destination offers beautiful beaches, warm weather, and a plethora of activities to enjoy in a tropical setting. Aruba and Barbados are also great choices if you prefer lively beach towns with vibrant nightlife and cultural richness. Would you like recommendations on sightseeing or places to stay in any of these Caribbean destinations? --- Conversation Turn 2 --- User: {"lg_name":"Command","lc_direct_tool_output":true,"resume":"could you recommend a nice hotel in one of the areas and tell me which area it is.","goto":[]} travelAdvisor: The Caribbean is a fantastic choice for a warm getaway! Some popular destinations you might consider include Jamaica, the Dominican Republic, and the Bahamas. Each destination offers beautiful beaches, warm weather, and a plethora of activities to enjoy in a tropical setting. Aruba and Barbados are also great choices if you prefer lively beach towns with vibrant nightlife and cultural richness. Would you like recommendations on sightseeing or places to stay in any of these Caribbean destinations? travelAdvisor: Let's focus on Jamaica, known for its beautiful beaches and vibrant culture, perfect for a warm Caribbean escape. I'll find a nice hotel for you there. hotelAdvisor: In Jamaica, consider staying at the "Round Hill Hotel and Villas" located in Montego Bay. It's a luxurious resort offering a private beach, beautiful villas, and a spa. Montego Bay is known for its stunning beaches, lively nightlife, and rich history with plantations and cultural sites to explore. --- Conversation Turn 3 --- User: {"lg_name":"Command","lc_direct_tool_output":true,"resume":"could you recommend something to do near the hotel?","goto":[]} hotelAdvisor: In Jamaica, consider staying at the "Round Hill Hotel and Villas" located in Montego Bay. It's a luxurious resort offering a private beach, beautiful villas, and a spa. Montego Bay is known for its stunning beaches, lively nightlife, and rich history with plantations and cultural sites to explore. hotelAdvisor: Let's find some sightseeing recommendations or activities around Round Hill Hotel and Villas in Montego Bay, Jamaica for you. sightseeingAdvisor: While staying at the Round Hill Hotel and Villas in Montego Bay, you can explore a variety of activities nearby: 1. **Doctor’s Cave Beach**: One of Montego Bay’s most famous beaches, it’s perfect for swimming and enjoying the sun. 2. **Rose Hall Great House**: Visit this historic plantation house, rumored to be haunted, for a tour of the beautiful grounds and a taste of Jamaican history. 3. **Martha Brae River**: Enjoy rafting on this beautiful river, surrounded by lush Jamaican flora. It's a peaceful and scenic way to experience the natural beauty of the area. 4. **Dunn’s River Falls**: Although a bit farther than the other attractions, these stunning waterfalls in Ocho Rios are worth the visit for a unique climbing experience. 5. **Montego Bay Marine Park**: Explore the coral reefs and marine life through snorkeling or diving adventures. ``` --- how-tos/manage-ecosystem-dependencies.ipynb --- # How to install and manage dependencies LangGraph.js is part of the [LangChain](https://js.langchain.com/) ecosystem, which includes the primary [`langchain`](https://www.npmjs.com/package/langchain) package as well as packages that contain integrations with individual third-party providers. They can be as specific as [`@langchain/anthropic`](https://www.npmjs.com/package/@langchain/anthropic), which contains integrations just for Anthropic chat models, or as broad as [`@langchain/community`](https://www.npmjs.com/package/@langchain/community), which contains broader variety of community contributed integrations. These packages, as well as LangGraph.js itself, all rely on [`@langchain/core`](https://www.npmjs.com/package/@langchain/core), which contains the base abstractions that these packages extend. To ensure that all integrations and their types interact with each other properly, it is important that they all use the same version of `@langchain/core`. When installing LangGraph, you should install `@langchain/core` alongside it as well: ```bash $ npm install @langchain/langgraph @langchain/core ``` `@langchain/core` must be installed separately because it is a peer dependency of `@langchain/langgraph`. This is to help package managers resolve a single version of `@langchain/core`. Despite this, in some situations, your package manager may resolve multiple versions of core, which can result in unexpected TypeScript errors or other strange behavior. If you need to guarantee that you only have one version of `@langchain/core` is to add a `"resolutions"` or `"overrides"` field in your project's `package.json`. The specific field name will depend on your package manager. Here are a few examples:

Tip

The resolutions or pnpm.overrides fields for yarn or pnpm must be set in the root package.json file. Also note that we specify EXACT versions for resolutions.

If you are using `yarn`, you should set [`"resolutions"`](https://yarnpkg.com/cli/set/resolution): ```json { "name": "your-project", "version": "0.0.0", "private": true, "engines": { "node": ">=18" }, "dependencies": { "@langchain/anthropic": "^0.2.15", "@langchain/langgraph": "^0.2.0" }, "resolutions": { "@langchain/core": "0.2.31" } } ``` For `npm`, use [`"overrides"`](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#overrides): ```json { "name": "your-project", "version": "0.0.0", "private": true, "engines": { "node": ">=18" }, "dependencies": { "@langchain/anthropic": "^0.2.15", "@langchain/langgraph": "^0.2.0" }, "overrides": { "@langchain/core": "0.2.31" } } ``` For `pnpm`, use the nested [`"pnpm.overrides"`](https://pnpm.io/package_json#pnpmoverrides) field: ```json { "name": "your-project", "version": "0.0.0", "private": true, "engines": { "node": ">=18" }, "dependencies": { "@langchain/anthropic": "^0.2.15", "@langchain/langgraph": "^0.2.0" }, "pnpm": { "overrides": { "@langchain/core": "0.2.31" } } } ``` ## Next steps You've now learned about some special considerations around using LangGraph.js with other LangChain ecosystem packages. Next, check out some how-to guides on core functionality. --- how-tos/force-calling-a-tool-first.ipynb --- # How to force an agent to call a tool In this example we will build a ReAct agent that **always** calls a certain tool first, before making any plans. In this example, we will create an agent with a search tool. However, at the start we will force the agent to call the search tool (and then let it do whatever it wants after). This is useful when you know you want to execute specific actions in your application but also want the flexibility of letting the LLM follow up on the user's query after going through that fixed sequence. ## Setup First we need to install the packages required ```bash yarn add @langchain/langgraph @langchain/openai @langchain/core ``` Next, we need to set API keys for OpenAI (the LLM we will use). Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..."; // process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "Force Calling a Tool First: LangGraphJS"; ``` ```output Force Calling a Tool First: LangGraphJS ``` ## Set up the tools We will first define the tools we want to use. For this simple example, we will use a built-in search tool via Tavily. However, it is really easy to create your own tools - see documentation [here](https://js.langchain.com/docs/modules/agents/tools/dynamic) on how to do that. ```typescript import { DynamicStructuredTool } from "@langchain/core/tools"; import { z } from "zod"; const searchTool = new DynamicStructuredTool({ name: "search", description: "Use to surf the web, fetch current information, check the weather, and retrieve other information.", schema: z.object({ query: z.string().describe("The query to use in your search."), }), func: async ({}: { query: string }) => { // This is a placeholder for the actual implementation return "Cold, with a low of 13 ℃"; }, }); await searchTool.invoke({ query: "What's the weather like?" }); const tools = [searchTool]; ``` We can now wrap these tools in a `ToolNode`. This is a prebuilt node that takes in a LangChain chat model's generated tool call and calls that tool, returning the output. ```typescript import { ToolNode } from "@langchain/langgraph/prebuilt"; const toolNode = new ToolNode(tools); ``` ## Set up the model Now we need to load the chat model we want to use.\ Importantly, this should satisfy two criteria: 1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them. 2. It should work with OpenAI function calling. This means it should either be an OpenAI model or a model that exposes a similar interface. Note: these model requirements are not requirements for using LangGraph - they are just requirements for this one example. ```typescript import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ temperature: 0, model: "gpt-4o", }); ``` After we've done this, we should make sure the model knows that it has these tools available to call. We can do this by converting the LangChain tools into the format for OpenAI function calling, and then bind them to the model class. ```typescript const boundModel = model.bindTools(tools); ``` ## Define the agent state The main type of graph in `langgraph` is the `StateGraph`. This graph is parameterized by a state object that it passes around to each node. Each node then returns operations to update that state. For this example, the state we will track will just be a list of messages. We want each node to just add messages to that list. Therefore, we will define the agent state as an object with one key (`messages`) with the value specifying how to update the state. ```typescript import { Annotation } from "@langchain/langgraph"; import { BaseMessage } from "@langchain/core/messages"; const AgentState = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); ``` ## Define the nodes We now need to define a few different nodes in our graph. In `langgraph`, a node can be either a function or a [runnable](https://js.langchain.com/docs/expression_language/). There are two main nodes we need for this: 1. The agent: responsible for deciding what (if any) actions to take. 2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action. We will also need to define some edges. Some of these edges may be conditional. The reason they are conditional is that based on the output of a node, one of several paths may be taken. The path that is taken is not known until that node is run (the LLM decides). 1. Conditional Edge: after the agent is called, we should either: a. If the agent said to take an action, then the function to invoke tools should be called\ b. If the agent said that it was finished, then it should finish 2. Normal Edge: after the tools are invoked, it should always go back to the agent to decide what to do next Let's define the nodes, as well as a function to decide how what conditional edge to take. ```typescript import { AIMessage, AIMessageChunk } from "@langchain/core/messages"; import { RunnableConfig } from "@langchain/core/runnables"; import { concat } from "@langchain/core/utils/stream"; // Define logic that will be used to determine which conditional edge to go down const shouldContinue = (state: typeof AgentState.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as AIMessage; // If there is no function call, then we finish if (!lastMessage.tool_calls || lastMessage.tool_calls.length === 0) { return "end"; } // Otherwise if there is, we continue return "continue"; }; // Define the function that calls the model const callModel = async ( state: typeof AgentState.State, config?: RunnableConfig, ) => { const { messages } = state; let response: AIMessageChunk | undefined; for await (const message of await boundModel.stream(messages, config)) { if (!response) { response = message; } else { response = concat(response, message); } } // We return an object, because this will get added to the existing list return { messages: response ? [response as AIMessage] : [], }; }; ``` **MODIFICATION** Here we create a node that returns an AIMessage with a tool call - we will use this at the start to force it call a tool ```typescript // This is the new first - the first call of the model we want to explicitly hard-code some action const firstModel = async (state: typeof AgentState.State) => { const humanInput = state.messages[state.messages.length - 1].content || ""; return { messages: [ new AIMessage({ content: "", tool_calls: [ { name: "search", args: { query: humanInput, }, id: "tool_abcd123", }, ], }), ], }; }; ``` ## Define the graph We can now put it all together and define the graph! **MODIFICATION** We will define a `firstModel` node which we will set as the entrypoint. ```typescript import { END, START, StateGraph } from "@langchain/langgraph"; // Define a new graph const workflow = new StateGraph(AgentState) // Define the new entrypoint .addNode("first_agent", firstModel) // Define the two nodes we will cycle between .addNode("agent", callModel) .addNode("action", toolNode) // Set the entrypoint as `first_agent` // by creating an edge from the virtual __start__ node to `first_agent` .addEdge(START, "first_agent") // We now add a conditional edge .addConditionalEdges( // First, we define the start node. We use `agent`. // This means these are the edges taken after the `agent` node is called. "agent", // Next, we pass in the function that will determine which node is called next. shouldContinue, // Finally we pass in a mapping. // The keys are strings, and the values are other nodes. // END is a special node marking that the graph should finish. // What will happen is we will call `should_continue`, and then the output of that // will be matched against the keys in this mapping. // Based on which one it matches, that node will then be called. { // If `tools`, then we call the tool node. continue: "action", // Otherwise we finish. end: END, }, ) // We now add a normal edge from `tools` to `agent`. // This means that after `tools` is called, `agent` node is called next. .addEdge("action", "agent") // After we call the first agent, we know we want to go to action .addEdge("first_agent", "action"); // Finally, we compile it! // This compiles it into a LangChain Runnable, // meaning you can use it as you would any other runnable const app = workflow.compile(); ``` ## Use it! We can now use it! This now exposes the [same interface](https://js.langchain.com/docs/expression_language/) as all other LangChain runnables. ```typescript import { HumanMessage } from "@langchain/core/messages"; const inputs = { messages: [new HumanMessage("what is the weather in sf")], }; for await (const output of await app.stream(inputs)) { console.log(output); console.log("-----\n"); } ``` ```output { first_agent: { messages: [ AIMessage { "content": "", "additional_kwargs": {}, "response_metadata": {}, "tool_calls": [ { "name": "search", "args": { "query": "what is the weather in sf" }, "id": "tool_abcd123" } ], "invalid_tool_calls": [] } ] } } ----- { action: { messages: [ ToolMessage { "content": "Cold, with a low of 13 ℃", "name": "search", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "tool_abcd123" } ] } } ----- { agent: { messages: [ AIMessageChunk { "id": "chatcmpl-9y562g16z0MUNBJcS6nKMsDuFMRsS", "content": "The current weather in San Francisco is cold, with a low of 13°C.", "additional_kwargs": {}, "response_metadata": { "prompt": 0, "completion": 0, "finish_reason": "stop", "system_fingerprint": "fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27fp_3aa7262c27" }, "tool_calls": [], "tool_call_chunks": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 104, "output_tokens": 18, "total_tokens": 122 } } ] } } ----- ``` --- how-tos/command.ipynb --- # How to combine control flow and state updates with Command

Prerequisites

This guide assumes familiarity with the following:

This functionality also requires @langchain/langgraph>=0.2.29.

It can be useful to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node. LangGraph provides a way to do so by returning a `Command` object from node functions: ```ts const myNode = (state: typeof StateAnnotation.State) => { return new Command({ // state update update: { foo: "bar", }, // control flow goto: "myOtherNode", }); }; ``` If you are using subgraphs, you might want to navigate from a node a subgraph to a different subgraph (i.e. a different node in the parent graph). To do so, you can specify `graph: Command.PARENT` in Command: ```ts const myNode = (state: typeof StateAnnotation.State) => { return new Command({ update: { foo: "bar" }, goto: "other_subgraph", // where `other_subgraph` is a node in the parent graph graph: Command.PARENT, }); }; ``` This guide shows how you can use `Command` to add dynamic control flow in your LangGraph app. ## Setup First, let's install the required packages: ```bash yarn add @langchain/langgraph @langchain/core ```

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

Let's create a simple graph with 3 nodes: A, B and C. We will first execute node A, and then decide whether to go to Node B or Node C next based on the output of node A. ## Define graph ```typescript import { Annotation, Command } from "@langchain/langgraph"; // Define graph state const StateAnnotation = Annotation.Root({ foo: Annotation, }); // Define the nodes const nodeA = async (_state: typeof StateAnnotation.State) => { console.log("Called A"); // this is a replacement for a real conditional edge function const goto = Math.random() > .5 ? "nodeB" : "nodeC"; // note how Command allows you to BOTH update the graph state AND route to the next node return new Command({ // this is the state update update: { foo: "a", }, // this is a replacement for an edge goto, }); }; // Nodes B and C are unchanged const nodeB = async (state: typeof StateAnnotation.State) => { console.log("Called B"); return { foo: state.foo + "|b", }; } const nodeC = async (state: typeof StateAnnotation.State) => { console.log("Called C"); return { foo: state.foo + "|c", }; } ``` We can now create the `StateGraph` with the above nodes. Notice that the graph doesn't have conditional edges for routing! This is because control flow is defined with `Command` inside `nodeA`. ```typescript import { StateGraph } from "@langchain/langgraph"; // NOTE: there are no edges between nodes A, B and C! const graph = new StateGraph(StateAnnotation) .addNode("nodeA", nodeA, { ends: ["nodeB", "nodeC"], }) .addNode("nodeB", nodeB) .addNode("nodeC", nodeC) .addEdge("__start__", "nodeA") .compile(); ```

Important

You might have noticed that we add an ends field as an extra param to the node where we use Command. This is necessary for graph compilation and validation, and tells LangGraph that nodeA can navigate to nodeB and nodeC.

```typescript import * as tslab from "tslab"; const drawableGraph = await graph.getGraphAsync(); const image = await drawableGraph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` If we run the graph multiple times, we'd see it take different paths (A -> B or A -> C) based on the random choice in node A. ```typescript await graph.invoke({ foo: "" }); ``` ```output Called A Called B { foo: 'a|b' } ``` ## Navigating to a node in a parent graph Now let's demonstrate how you can navigate from inside a subgraph to a different node in a parent graph. We'll do so by changing `node_a` in the above example into a single-node graph that we'll add as a subgraph to our parent graph. ```typescript // Define the nodes const nodeASubgraph = async (_state: typeof StateAnnotation.State) => { console.log("Called A"); // this is a replacement for a real conditional edge function const goto = Math.random() > .5 ? "nodeB" : "nodeC"; // note how Command allows you to BOTH update the graph state AND route to the next node return new Command({ update: { foo: "a", }, goto, // this tells LangGraph to navigate to node_b or node_c in the parent graph // NOTE: this will navigate to the closest parent graph relative to the subgraph graph: Command.PARENT, }); }; const subgraph = new StateGraph(StateAnnotation) .addNode("nodeA", nodeASubgraph) .addEdge("__start__", "nodeA") .compile(); const parentGraph= new StateGraph(StateAnnotation) .addNode("subgraph", subgraph, { ends: ["nodeB", "nodeC"] }) .addNode("nodeB", nodeB) .addNode("nodeC", nodeC) .addEdge("__start__", "subgraph") .compile(); await parentGraph.invoke({ foo: "" }); ``` ```output Called A Called C { foo: 'a|c' } ``` --- how-tos/node-retry-policies.ipynb --- # How to add node retry policies There are many use cases where you may wish for your node to have a custom retry policy. Some examples of when you may wish to do this is if you are calling an API, querying a database, or calling an LLM, etc. In order to configure the retry policy, you have to pass the `retryPolicy` parameter to the `addNode` function. The `retryPolicy` parameter takes in a `RetryPolicy` named tuple object. Below we instantiate a `RetryPolicy` object with the default parameters: ```typescript import { RetryPolicy } from "@langchain/langgraph" const retryPolicy: RetryPolicy = {}; ``` If you want more information on what each of the parameters does, be sure to read the [reference](https://langchain-ai.github.io/langgraphjs/reference/types/langgraph.RetryPolicy.html). ## Passing a retry policy to a node Lastly, we can pass `RetryPolicy` objects when we call the `addNode` function. In the example below we pass two different retry policies to each of our nodes: ```typescript import Database from "better-sqlite3" import { ChatAnthropic } from "@langchain/anthropic" import { MessagesAnnotation, StateGraph, START, END } from "@langchain/langgraph" import { AIMessage } from "@langchain/core/messages" // Create an in-memory database const db: typeof Database.prototype = new Database(':memory:'); const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20240620" }); const callModel = async (state: typeof MessagesAnnotation.State) => { const response = await model.invoke(state.messages); return { messages: [response] }; } const queryDatabase = async (state: typeof MessagesAnnotation.State) => { const queryResult: string = JSON.stringify(db.prepare("SELECT * FROM Artist LIMIT 10;").all()); return { messages: [new AIMessage({content: "queryResult"})]}; }; const workflow = new StateGraph(MessagesAnnotation) // Define the two nodes we will cycle between .addNode("call_model", callModel, { retryPolicy: {maxAttempts: 5}}) .addNode("query_database", queryDatabase, { retryPolicy: { retryOn: (e: any): boolean => { if (e instanceof Database.SqliteError) { // Retry on "SQLITE_BUSY" error return e.code === 'SQLITE_BUSY'; } return false; // Don't retry on other errors }}}) .addEdge(START, "call_model") .addEdge("call_model", "query_database") .addEdge("query_database", END); const graph = workflow.compile(); ``` --- how-tos/streaming-content.ipynb --- # How to stream custom data

Prerequisites

This guide assumes familiarity with the following:

The most common use case for streaming from inside a node is to stream LLM tokens, but you may also want to stream custom data. For example, if you have a long-running tool call, you can dispatch custom events between the steps and use these custom events to monitor progress. You could also surface these custom events to an end user of your application to show them how the current task is progressing. You can do so in two ways: * using your graph's `.stream` method with `streamMode: "custom"` * emitting custom events using [`dispatchCustomEvents`](https://js.langchain.com/docs/how_to/callbacks_custom_events/) with `streamEvents`. Below we'll see how to use both APIs. ## Setup First, let's install our required packages: ```bash npm install @langchain/langgraph @langchain/core ```

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

## Stream custom data using .stream

Compatibility

This section requires @langchain/langgraph>=0.2.20. For help upgrading, see this guide.

### Define the graph ```typescript import { StateGraph, MessagesAnnotation, LangGraphRunnableConfig, } from "@langchain/langgraph"; const myNode = async ( _state: typeof MessagesAnnotation.State, config: LangGraphRunnableConfig ) => { const chunks = [ "Four", "score", "and", "seven", "years", "ago", "our", "fathers", "...", ]; for (const chunk of chunks) { // write the chunk to be streamed using streamMode=custom // Only populated if one of the passed stream modes is "custom". config.writer?.(chunk); } return { messages: [{ role: "assistant", content: chunks.join(" "), }], }; }; const graph = new StateGraph(MessagesAnnotation) .addNode("model", myNode) .addEdge("__start__", "model") .compile(); ``` ### Stream content ```typescript const inputs = [{ role: "user", content: "What are you thinking about?", }]; const stream = await graph.stream( { messages: inputs }, { streamMode: "custom" } ); for await (const chunk of stream) { console.log(chunk); } ``` ```output Four score and seven years ago our fathers ... ``` You will likely need to use [multiple streaming modes](https://langchain-ai.github.io/langgraphjs/how-tos/stream-multiple/) as you will want access to both the custom data and the state updates. ```typescript const streamMultiple = await graph.stream( { messages: inputs }, { streamMode: ["custom", "updates"] } ); for await (const chunk of streamMultiple) { console.log(chunk); } ``` ```output [ 'custom', 'Four' ] [ 'custom', 'score' ] [ 'custom', 'and' ] [ 'custom', 'seven' ] [ 'custom', 'years' ] [ 'custom', 'ago' ] [ 'custom', 'our' ] [ 'custom', 'fathers' ] [ 'custom', '...' ] [ 'updates', { model: { messages: [Array] } } ] ``` ## Stream custom data using .streamEvents If you are already using graph's `.streamEvents` method in your workflow, you can also stream custom data by emitting custom events using `dispatchCustomEvents` ### Define the graph ```typescript import { dispatchCustomEvent } from "@langchain/core/callbacks/dispatch"; const graphNode = async (_state: typeof MessagesAnnotation.State) => { const chunks = [ "Four", "score", "and", "seven", "years", "ago", "our", "fathers", "...", ]; for (const chunk of chunks) { await dispatchCustomEvent("my_custom_event", { chunk }); } return { messages: [{ role: "assistant", content: chunks.join(" "), }], }; }; const graphWithDispatch = new StateGraph(MessagesAnnotation) .addNode("model", graphNode) .addEdge("__start__", "model") .compile(); ``` ### Stream content ```typescript const eventStream = await graphWithDispatch.streamEvents( { messages: [{ role: "user", content: "What are you thinking about?", }] }, { version: "v2", }, ); for await (const { event, name, data } of eventStream) { if (event === "on_custom_event" && name === "my_custom_event") { console.log(`${data.chunk}|`); } } ``` ```output Four| score| and| seven| years| ago| our| fathers| ...| ``` --- how-tos/add-summary-conversation-history.ipynb --- # How to add summary of the conversation history One of the most common use cases for persistence is to use it to keep track of conversation history. This is great - it makes it easy to continue conversations. As conversations get longer and longer, however, this conversation history can build up and take up more and more of the context window. This can often be undesirable as it leads to more expensive and longer calls to the LLM, and potentially ones that error. One way to work around that is to create a summary of the conversation to date, and use that with the past N messages. This guide will go through an example of how to do that. This will involve a few steps: - Check if the conversation is too long (can be done by checking number of messages or length of messages) - If yes, the create summary (will need a prompt for this) - Then remove all except the last N messages A big part of this is deleting old messages. For an in depth guide on how to do that, see this guide ## Setup First, let's set up the packages we're going to want to use ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core uuid ``` Next, we need to set API keys for Anthropic (the LLM we will use) ```typescript process.env.ANTHROPIC_API_KEY = 'YOUR_API_KEY' ``` Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript process.env.LANGCHAIN_TRACING_V2 = 'true' process.env.LANGCHAIN_API_KEY = 'YOUR_API_KEY' ``` ## Build the chatbot Let's now build the chatbot. ```typescript import { ChatAnthropic } from "@langchain/anthropic"; import { SystemMessage, HumanMessage, AIMessage, RemoveMessage } from "@langchain/core/messages"; import { MemorySaver } from "@langchain/langgraph-checkpoint"; import { MessagesAnnotation, StateGraph, START, END, Annotation } from "@langchain/langgraph"; import { v4 as uuidv4 } from "uuid"; const memory = new MemorySaver(); // We will add a `summary` attribute (in addition to `messages` key, // which MessagesAnnotation already has) const GraphAnnotation = Annotation.Root({ ...MessagesAnnotation.spec, summary: Annotation({ reducer: (_, action) => action, default: () => "", }) }) // We will use this model for both the conversation and the summarization const model = new ChatAnthropic({ model: "claude-3-haiku-20240307" }); // Define the logic to call the model async function callModel(state: typeof GraphAnnotation.State): Promise> { // If a summary exists, we add this in as a system message const { summary } = state; let { messages } = state; if (summary) { const systemMessage = new SystemMessage({ id: uuidv4(), content: `Summary of conversation earlier: ${summary}` }); messages = [systemMessage, ...messages]; } const response = await model.invoke(messages); // We return an object, because this will get added to the existing state return { messages: [response] }; } // We now define the logic for determining whether to end or summarize the conversation function shouldContinue(state: typeof GraphAnnotation.State): "summarize_conversation" | typeof END { const messages = state.messages; // If there are more than six messages, then we summarize the conversation if (messages.length > 6) { return "summarize_conversation"; } // Otherwise we can just end return END; } async function summarizeConversation(state: typeof GraphAnnotation.State): Promise> { // First, we summarize the conversation const { summary, messages } = state; let summaryMessage: string; if (summary) { // If a summary already exists, we use a different system prompt // to summarize it than if one didn't summaryMessage = `This is summary of the conversation to date: ${summary}\n\n` + "Extend the summary by taking into account the new messages above:"; } else { summaryMessage = "Create a summary of the conversation above:"; } const allMessages = [...messages, new HumanMessage({ id: uuidv4(), content: summaryMessage, })]; const response = await model.invoke(allMessages); // We now need to delete messages that we no longer want to show up // I will delete all but the last two messages, but you can change this const deleteMessages = messages.slice(0, -2).map((m) => new RemoveMessage({ id: m.id })); if (typeof response.content !== "string") { throw new Error("Expected a string response from the model"); } return { summary: response.content, messages: deleteMessages }; } // Define a new graph const workflow = new StateGraph(GraphAnnotation) // Define the conversation node and the summarize node .addNode("conversation", callModel) .addNode("summarize_conversation", summarizeConversation) // Set the entrypoint as conversation .addEdge(START, "conversation") // We now add a conditional edge .addConditionalEdges( // First, we define the start node. We use `conversation`. // This means these are the edges taken after the `conversation` node is called. "conversation", // Next, we pass in the function that will determine which node is called next. shouldContinue ) // We now add a normal edge from `summarize_conversation` to END. // This means that after `summarize_conversation` is called, we end. .addEdge("summarize_conversation", END); // Finally, we compile it! const app = workflow.compile({ checkpointer: memory }); ``` ## Using the graph ```typescript const printUpdate = (update: Record) => { Object.keys(update).forEach((key) => { const value = update[key]; if ("messages" in value && Array.isArray(value.messages)) { value.messages.forEach((msg) => { console.log(`\n================================ ${msg._getType()} Message =================================`) console.log(msg.content); }) } if ("summary" in value && value.summary) { console.log(value.summary); } }) } ``` ```typescript import { HumanMessage } from "@langchain/core/messages"; const config = { configurable: { thread_id: "4" }, streamMode: "updates" as const } const inputMessage = new HumanMessage("hi! I'm bob") console.log(inputMessage.content) for await (const event of await app.stream({ messages: [inputMessage] }, config)) { printUpdate(event) } const inputMessage2 = new HumanMessage("What did I sat my name was?") console.log(inputMessage2.content) for await (const event of await app.stream({ messages: [inputMessage2] }, config)) { printUpdate(event) } const inputMessage3 = new HumanMessage("i like the celtics!") console.log(inputMessage3.content) for await (const event of await app.stream({ messages: [inputMessage3] }, config)) { printUpdate(event) } ``` ```output hi! I'm bob ================================ ai Message ================================= Okay, got it. Hello Bob, it's nice to chat with you again. I recognize that you've repeatedly stated your name is Bob throughout our conversation. Please let me know if there is anything I can assist you with. ================================ remove Message ================================= ================================ remove Message ================================= ================================ remove Message ================================= ================================ ai Message ================================= In our conversation, you have stated multiple times that your name is Bob. For example, you said "I'm Bob", "hi! I'm bob", and similar variations where you clearly identified yourself as Bob. i like the celtics! ================================ ai Message ================================= Ah I see, you mentioned earlier that you like the Boston Celtics basketball team. That's great, the Celtics have a long and storied history in the NBA. As one of the league's original franchises, they've won a record 17 NBA championships over the years, the most of any team. Some of their most iconic players have included Bill Russell, Larry Bird, and Kevin McHale. The Celtics are known for their passionate fan base and intense rivalries with teams like the Los Angeles Lakers. It's always exciting to follow such a successful and historic franchise. I'm glad to hear you're a fan of the Celtics! ``` We can see that so far no summarization has happened - this is because there are only six messages in the list. ```typescript const values = (await app.getState(config)).values console.log(values) ``` ```output { messages: [ HumanMessage { "content": "hi! I'm bob", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_01G6WKqKHK8W371793Hm6eNM", "content": "Okay, got it. Hello Bob, it's nice to chat with you again. I recognize that you've repeatedly stated your name is Bob throughout our conversation. Please let me know if there is anything I can assist you with.", "additional_kwargs": { "id": "msg_01G6WKqKHK8W371793Hm6eNM", "type": "message", "role": "assistant", "model": "claude-3-haiku-20240307", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 579, "output_tokens": 50 } }, "response_metadata": { "id": "msg_01G6WKqKHK8W371793Hm6eNM", "model": "claude-3-haiku-20240307", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 579, "output_tokens": 50 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [] }, HumanMessage { "content": "What did I sat my name was?", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_0118BAsHL4Ew8N2926aYQaot", "content": "In our conversation, you have stated multiple times that your name is Bob. For example, you said \"I'm Bob\", \"hi! I'm bob\", and similar variations where you clearly identified yourself as Bob.", "additional_kwargs": { "id": "msg_0118BAsHL4Ew8N2926aYQaot", "type": "message", "role": "assistant", "model": "claude-3-haiku-20240307", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 310, "output_tokens": 46 } }, "response_metadata": { "id": "msg_0118BAsHL4Ew8N2926aYQaot", "model": "claude-3-haiku-20240307", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 310, "output_tokens": 46 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [] }, HumanMessage { "content": "i like the celtics!", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_01RVrMuSvr17kZdepJZb7rZM", "content": "Ah I see, you mentioned earlier that you like the Boston Celtics basketball team. That's great, the Celtics have a long and storied history in the NBA. As one of the league's original franchises, they've won a record 17 NBA championships over the years, the most of any team. Some of their most iconic players have included Bill Russell, Larry Bird, and Kevin McHale. The Celtics are known for their passionate fan base and intense rivalries with teams like the Los Angeles Lakers. It's always exciting to follow such a successful and historic franchise. I'm glad to hear you're a fan of the Celtics!", "additional_kwargs": { "id": "msg_01RVrMuSvr17kZdepJZb7rZM", "type": "message", "role": "assistant", "model": "claude-3-haiku-20240307", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 365, "output_tokens": 141 } }, "response_metadata": { "id": "msg_01RVrMuSvr17kZdepJZb7rZM", "model": "claude-3-haiku-20240307", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 365, "output_tokens": 141 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [] } ], summary: 'Got it, let me extend the summary further:\n' + '\n' + `The conversation began with you introducing yourself as Bob, which I acknowledged and said I was happy to chat with you again. You then repeated "I'm Bob", and I confirmed I recognized your name.\n` + '\n' + "You next stated that you like the Boston Celtics basketball team, which prompted me to provide some background information about the team's history and success. \n" + '\n' + 'You then summarized the conversation up to that point, which I expanded upon in detail, recapping the key points of our exchange so far.\n' + '\n' + `In the most recent messages, you greeted me again by saying "hi! I'm bob", which I recognized as you reiterating your name, consistent with how you had introduced yourself earlier.\n` + '\n' + `Now, in the latest message, you have simply stated "hi! I'm bob" once more. I continue to understand your name is Bob based on you stating that multiple times throughout our conversation.\n` + '\n' + "Please let me know if I'm still missing anything or if you have any other points you'd like me to add to the summary. I'm happy to keep building on it." } ``` Now let's send another message in ```typescript const inputMessage4 = new HumanMessage("i like how much they win") console.log(inputMessage4.content) for await (const event of await app.stream({ messages: [inputMessage4] }, config)) { printUpdate(event) } ``` ```output i like how much they win ================================ ai Message ================================= I agree, the Celtics' impressive track record of wins and championships is a big part of what makes them such an iconic and beloved team. Their sustained success over decades is really remarkable. Some key reasons why the Celtics have been so dominant: - Great coaching - They've had legendary coaches like Red Auerbach, Doc Rivers, and Brad Stevens who have led the team to titles. - Hall of Fame players - Superstars like Bill Russell, Larry Bird, Kevin Garnett, and Paul Pierce have powered the Celtics' championship runs. - Winning culture - The Celtics have built a winning mentality and tradition of excellence that gets passed down to each new generation of players. - Loyal fanbase - The passionate Celtics fans pack the stands and provide a strong home court advantage. The combination of top-tier talent, smart management, and devoted supporters has allowed the Celtics to reign as one of the NBA's premier franchises for generations. Their ability to consistently win at the highest level is truly impressive. I can understand why you as a fan really appreciate and enjoy that aspect of the team. ================================ remove Message ================================= ================================ remove Message ================================= ================================ remove Message ================================= ================================ remove Message ================================= ================================ remove Message ================================= ================================ remove Message ================================= Okay, let me extend the summary further based on the latest messages: The conversation began with you introducing yourself as Bob, which I acknowledged. You then repeated "I'm Bob" a few times, and I confirmed I recognized your name. You then expressed that you like the Boston Celtics basketball team, which led me to provide some background information about the team's history and success. You agreed that you appreciate how much the Celtics win. In the most recent messages, you greeted me again by saying "hi! I'm bob", reiterating your name just as you had done earlier. I reiterated that I understand your name is Bob based on you stating that multiple times throughout our conversation. In your latest message, you simply stated "hi! I'm bob" once more, further confirming your name. I have continued to demonstrate that I understand your name is Bob, as you have consistently identified yourself as such. Please let me know if I'm still missing anything or if you have any other points you'd like me to add to this extended summary of our discussion so far. I'm happy to keep building on it. ``` If we check the state now, we can see that we have a summary of the conversation, as well as the last two messages ```typescript const values2 = (await app.getState(config)).values console.log(values2) ``` ```output { messages: [ HumanMessage { "content": "i like how much they win", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_01W8C1nXeydqM3E31uCCeJXt", "content": "I agree, the Celtics' impressive track record of wins and championships is a big part of what makes them such an iconic and beloved team. Their sustained success over decades is really remarkable. \n\nSome key reasons why the Celtics have been so dominant:\n\n- Great coaching - They've had legendary coaches like Red Auerbach, Doc Rivers, and Brad Stevens who have led the team to titles.\n\n- Hall of Fame players - Superstars like Bill Russell, Larry Bird, Kevin Garnett, and Paul Pierce have powered the Celtics' championship runs.\n\n- Winning culture - The Celtics have built a winning mentality and tradition of excellence that gets passed down to each new generation of players.\n\n- Loyal fanbase - The passionate Celtics fans pack the stands and provide a strong home court advantage.\n\nThe combination of top-tier talent, smart management, and devoted supporters has allowed the Celtics to reign as one of the NBA's premier franchises for generations. Their ability to consistently win at the highest level is truly impressive. I can understand why you as a fan really appreciate and enjoy that aspect of the team.", "additional_kwargs": { "id": "msg_01W8C1nXeydqM3E31uCCeJXt", "type": "message", "role": "assistant", "model": "claude-3-haiku-20240307", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 516, "output_tokens": 244 } }, "response_metadata": { "id": "msg_01W8C1nXeydqM3E31uCCeJXt", "model": "claude-3-haiku-20240307", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 516, "output_tokens": 244 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [] } ], summary: 'Okay, let me extend the summary further based on the latest messages:\n' + '\n' + `The conversation began with you introducing yourself as Bob, which I acknowledged. You then repeated "I'm Bob" a few times, and I confirmed I recognized your name.\n` + '\n' + "You then expressed that you like the Boston Celtics basketball team, which led me to provide some background information about the team's history and success. You agreed that you appreciate how much the Celtics win.\n" + '\n' + `In the most recent messages, you greeted me again by saying "hi! I'm bob", reiterating your name just as you had done earlier. I reiterated that I understand your name is Bob based on you stating that multiple times throughout our conversation.\n` + '\n' + `In your latest message, you simply stated "hi! I'm bob" once more, further confirming your name. I have continued to demonstrate that I understand your name is Bob, as you have consistently identified yourself as such.\n` + '\n' + "Please let me know if I'm still missing anything or if you have any other points you'd like me to add to this extended summary of our discussion so far. I'm happy to keep building on it." } ``` We can now resume having a conversation! Note that even though we only have the last two messages, we can still ask it questions about things mentioned earlier in the conversation (because we summarized those) ```typescript const inputMessage5 = new HumanMessage("what's my name?"); console.log(inputMessage5.content) for await (const event of await app.stream({ messages: [inputMessage5] }, config)) { printUpdate(event) } ``` ```output what's my name? ================================ ai Message ================================= Your name is Bob. You have stated this multiple times throughout our conversation, repeatedly introducing yourself as "Bob" or "I'm Bob". ``` ```typescript const inputMessage6 = new HumanMessage("what NFL team do you think I like?"); console.log(inputMessage6.content) for await (const event of await app.stream({ messages: [inputMessage6] }, config)) { printUpdate(event) } ``` ```output what NFL team do you think I like? ================================ ai Message ================================= I do not actually have any information about what NFL team you might like. In our conversation so far, you have only expressed that you are a fan of the Boston Celtics basketball team. You have not mentioned any preferences for NFL teams. Without you providing any additional details about your football team allegiances, I do not want to make an assumption about which NFL team you might be a fan of. Could you please let me know if there is an NFL team you particularly enjoy following? ``` ```typescript const inputMessage7 = new HumanMessage("i like the patriots!"); console.log(inputMessage7.content) for await (const event of await app.stream({ messages: [inputMessage7] }, config)) { printUpdate(event) } ``` ```output i like the patriots! ================================ ai Message ================================= Okay, got it. Based on your latest message, I now understand that in addition to being a fan of the Boston Celtics basketball team, you also like the New England Patriots NFL team. That makes a lot of sense given that both the Celtics and Patriots are major sports franchises based in the Boston/New England region. It's common for fans to follow multiple professional teams from the same geographic area. I appreciate you sharing this additional information about your football team preferences. Knowing that you're a Patriots fan provides helpful context about your sports interests and loyalties. It's good for me to have that understanding as we continue our conversation. Please let me know if there's anything else you'd like to discuss related to the Patriots, the Celtics, or your overall sports fandom. I'm happy to chat more about those topics. ================================ remove Message ================================= ================================ remove Message ================================= ================================ remove Message ================================= ================================ remove Message ================================= ================================ remove Message ================================= ================================ remove Message ================================= Okay, got it - let me extend the summary further based on the latest messages: The conversation began with you introducing yourself as Bob, which I acknowledged. You then repeated "I'm Bob" a few times, and I confirmed I recognized your name. You then expressed that you like the Boston Celtics basketball team, which led me to provide some background information about the team's history and success. You agreed that you appreciate how much the Celtics win. In the most recent messages, you greeted me again by saying "hi! I'm Bob", reiterating your name just as you had done earlier. I reiterated that I understand your name is Bob based on you stating that multiple times throughout our conversation. You then asked what NFL team I think you might like, and I acknowledged that I did not have enough information to make an assumption about your NFL team preferences. You then revealed that you are also a fan of the New England Patriots, which I said makes sense given the Celtics and Patriots are both major sports franchises in the Boston/New England region. In your latest message, you simply stated "hi! I'm Bob" once more, further confirming your name. I have continued to demonstrate that I understand your name is Bob, as you have consistently identified yourself as such. Please let me know if I'm still missing anything or if you have any other points you'd like me to add to this extended summary of our discussion so far. I'm happy to keep building on it. ``` --- how-tos/delete-messages.ipynb --- # How to delete messages One of the common states for a graph is a list of messages. Usually you only add messages to that state. However, sometimes you may want to remove messages (either by directly modifying the state or as part of the graph). To do that, you can use the `RemoveMessage` modifier. In this guide, we will cover how to do that. The key idea is that each state key has a `reducer` key. This key specifies how to combine updates to the state. The prebuilt `MessagesAnnotation` has a messages key, and the reducer for that key accepts these `RemoveMessage` modifiers. That reducer then uses these `RemoveMessage` to delete messages from the key. So note that just because your graph state has a key that is a list of messages, it doesn't mean that that this `RemoveMessage` modifier will work. You also have to have a `reducer` defined that knows how to work with this. **NOTE**: Many models expect certain rules around lists of messages. For example, some expect them to start with a `user` message, others expect all messages with tool calls to be followed by a tool message. **When deleting messages, you will want to make sure you don't violate these rules.** ## Setup First, install the required dependencies for this example: ```bash npm install @langchain/langgraph @langchain/openai @langchain/core zod uuid ``` Next, we need to set API keys for OpenAI (the LLM we will use): ```typescript process.env.OPENAI_API_KEY = 'YOUR_API_KEY'; ``` Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_API_KEY = "YOUR_API_KEY"; ``` Now, let's build a simple graph that uses messages. ## Build the agent Let's now build a simple ReAct style agent. ```typescript import { ChatOpenAI } from "@langchain/openai"; import { tool } from "@langchain/core/tools"; import { MemorySaver } from "@langchain/langgraph-checkpoint"; import { MessagesAnnotation, StateGraph, START, END } from "@langchain/langgraph"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { z } from "zod"; const memory = new MemorySaver(); const search = tool((_) => { // This is a placeholder for the actual implementation // Don't let the LLM know this though 😊 return [ "It's sunny in San Francisco, but you better look out if you're a Gemini 😈.", ]; }, { name: "search", description: "Call to surf the web.", schema: z.object({ query: z.string(), }) }); const tools = [search]; const toolNode = new ToolNode(tools); const model = new ChatOpenAI({ model: "gpt-4o" }); const boundModel = model.bindTools(tools); function shouldContinue(state: typeof MessagesAnnotation.State): "action" | typeof END { const lastMessage = state.messages[state.messages.length - 1]; if ( "tool_calls" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls.length ) { return "action"; } // If there is no tool call, then we finish return END; } // Define the function that calls the model async function callModel(state: typeof MessagesAnnotation.State) { const response = await boundModel.invoke(state.messages); return { messages: [response] }; } // Define a new graph const workflow = new StateGraph(MessagesAnnotation) // Define the two nodes we will cycle between .addNode("agent", callModel) .addNode("action", toolNode) // Set the entrypoint as `agent` // This means that this node is the first one called .addEdge(START, "agent") // We now add a conditional edge .addConditionalEdges( // First, we define the start node. We use `agent`. // This means these are the edges taken after the `agent` node is called. "agent", // Next, we pass in the function that will determine which node is called next. shouldContinue ) // We now add a normal edge from `tools` to `agent`. // This means that after `tools` is called, `agent` node is called next. .addEdge("action", "agent"); // Finally, we compile it! // This compiles it into a LangChain Runnable, // meaning you can use it as you would any other runnable const app = workflow.compile({ checkpointer: memory }); ``` ```typescript import { HumanMessage } from "@langchain/core/messages"; import { v4 as uuidv4 } from "uuid"; const config = { configurable: { thread_id: "2" }, streamMode: "values" as const }; const inputMessage = new HumanMessage({ id: uuidv4(), content: "hi! I'm bob", }); for await (const event of await app.stream( { messages: [inputMessage] }, config, )) { const lastMsg = event.messages[event.messages.length - 1]; console.dir( { type: lastMsg._getType(), content: lastMsg.content, tool_calls: lastMsg.tool_calls, }, { depth: null } ) } const inputMessage2 = new HumanMessage({ id: uuidv4(), content: "What's my name?", }); for await (const event of await app.stream( { messages: [inputMessage2] }, config, )) { const lastMsg = event.messages[event.messages.length - 1]; console.dir( { type: lastMsg._getType(), content: lastMsg.content, tool_calls: lastMsg.tool_calls, }, { depth: null } ) } ``` ```output { type: 'human', content: "hi! I'm bob", tool_calls: undefined } { type: 'ai', content: 'Hi Bob! How can I assist you today?', tool_calls: [] } { type: 'human', content: "What's my name?", tool_calls: undefined } { type: 'ai', content: 'Your name is Bob.', tool_calls: [] } ``` ## Manually deleting messages First, we will cover how to manually delete messages. Let's take a look at the current state of the thread: ```typescript const messages = (await app.getState(config)).values.messages; console.dir( messages.map((msg) => ({ id: msg.id, type: msg._getType(), content: msg.content, tool_calls: msg.tool_calls, })), { depth: null } ); ``` ```output [ { id: '24187daa-00dd-40d8-bc30-f4e24ff78165', type: 'human', content: "hi! I'm bob", tool_calls: undefined }, { id: 'chatcmpl-9zYV9yHLiZmR2ZVHEhHcbVEshr3qG', type: 'ai', content: 'Hi Bob! How can I assist you today?', tool_calls: [] }, { id: 'a67e53c3-5dcf-4ddc-83f5-309b72ac61f4', type: 'human', content: "What's my name?", tool_calls: undefined }, { id: 'chatcmpl-9zYV9mmpJrm3SQ7ngMJZ1XBHzHfL6', type: 'ai', content: 'Your name is Bob.', tool_calls: [] } ] ``` We can call `updateState` and pass in the id of the first message. This will delete that message. ```typescript import { RemoveMessage } from "@langchain/core/messages"; await app.updateState(config, { messages: new RemoveMessage({ id: messages[0].id }) }) ``` ```output { configurable: { thread_id: '2', checkpoint_ns: '', checkpoint_id: '1ef61abf-1fc2-6431-8005-92730e9d667c' } } ``` If we now look at the messages, we can verify that the first one was deleted. ```typescript const updatedMessages = (await app.getState(config)).values.messages; console.dir( updatedMessages.map((msg) => ({ id: msg.id, type: msg._getType(), content: msg.content, tool_calls: msg.tool_calls, })), { depth: null } ); ``` ```output [ { id: 'chatcmpl-9zYV9yHLiZmR2ZVHEhHcbVEshr3qG', type: 'ai', content: 'Hi Bob! How can I assist you today?', tool_calls: [] }, { id: 'a67e53c3-5dcf-4ddc-83f5-309b72ac61f4', type: 'human', content: "What's my name?", tool_calls: undefined }, { id: 'chatcmpl-9zYV9mmpJrm3SQ7ngMJZ1XBHzHfL6', type: 'ai', content: 'Your name is Bob.', tool_calls: [] } ] ``` ## Programmatically deleting messages We can also delete messages programmatically from inside the graph. Here we'll modify the graph to delete any old messages (longer than 3 messages ago) at the end of a graph run. ```typescript import { RemoveMessage } from "@langchain/core/messages"; import { StateGraph, START, END } from "@langchain/langgraph"; import { MessagesAnnotation } from "@langchain/langgraph"; function deleteMessages(state: typeof MessagesAnnotation.State) { const messages = state.messages; if (messages.length > 3) { return { messages: messages.slice(0, -3).map(m => new RemoveMessage({ id: m.id })) }; } return {}; } // We need to modify the logic to call deleteMessages rather than end right away function shouldContinue2(state: typeof MessagesAnnotation.State): "action" | "delete_messages" { const lastMessage = state.messages[state.messages.length - 1]; if ( "tool_calls" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls.length ) { return "action"; } // Otherwise if there aren't, we finish return "delete_messages"; } // Define a new graph const workflow2 = new StateGraph(MessagesAnnotation) .addNode("agent", callModel) .addNode("action", toolNode) // This is our new node we're defining .addNode("delete_messages", deleteMessages) .addEdge(START, "agent") .addConditionalEdges( "agent", shouldContinue2 ) .addEdge("action", "agent") // This is the new edge we're adding: after we delete messages, we finish .addEdge("delete_messages", END); const app2 = workflow2.compile({ checkpointer: memory }); ``` We can now try this out. We can call the graph twice and then check the state ```typescript import { HumanMessage } from "@langchain/core/messages"; import { v4 as uuidv4 } from "uuid"; const config2 = { configurable: { thread_id: "3" }, streamMode: "values" as const }; const inputMessage3 = new HumanMessage({ id: uuidv4(), content: "hi! I'm bob", }); console.log("--- FIRST ITERATION ---\n"); for await (const event of await app2.stream( { messages: [inputMessage3] }, config2 )) { console.log(event.messages.map((message) => [message._getType(), message.content])); } const inputMessage4 = new HumanMessage({ id: uuidv4(), content: "what's my name?", }); console.log("\n\n--- SECOND ITERATION ---\n"); for await (const event of await app2.stream( { messages: [inputMessage4] }, config2 )) { console.log(event.messages.map((message) => [message._getType(), message.content]), "\n"); } ``` ```output --- FIRST ITERATION --- [ [ 'human', "hi! I'm bob" ] ] ``````output [ [ 'human', "hi! I'm bob" ], [ 'ai', 'Hi Bob! How can I assist you today?' ] ] --- SECOND ITERATION --- [ [ 'human', "hi! I'm bob" ], [ 'ai', 'Hi Bob! How can I assist you today?' ], [ 'human', "what's my name?" ] ] [ [ 'human', "hi! I'm bob" ], [ 'ai', 'Hi Bob! How can I assist you today?' ], [ 'human', "what's my name?" ], [ 'ai', "Based on what you've told me, your name is Bob." ] ] [ [ 'ai', 'Hi Bob! How can I assist you today?' ], [ 'human', "what's my name?" ], [ 'ai', "Based on what you've told me, your name is Bob." ] ] ``` If we now check the state, we should see that it is only three messages long. This is because we just deleted the earlier messages - otherwise it would be four! ```typescript const messages3 = (await app.getState(config2)).values["messages"] console.dir( messages3.map((msg) => ({ id: msg.id, type: msg._getType(), content: msg.content, tool_calls: msg.tool_calls, })), { depth: null } ); ``` ```output [ { id: 'chatcmpl-9zYVAEiiC9D7bb0wF4KLXgY0OAG8O', type: 'ai', content: 'Hi Bob! How can I assist you today?', tool_calls: [] }, { id: 'b93e5f35-cfa3-4ca6-9b59-154ce2bd476b', type: 'human', content: "what's my name?", tool_calls: undefined }, { id: 'chatcmpl-9zYVBHJWtEM6pw2koE8dykzSA0XSO', type: 'ai', content: "Based on what you've told me, your name is Bob.", tool_calls: [] } ] ``` Remember, when deleting messages you will want to make sure that the remaining message list is still valid. This message list **may actually not be** - this is because it currently starts with an AI message, which some models do not allow. --- how-tos/manage-conversation-history.ipynb --- # How to manage conversation history One of the most common use cases for persistence is to use it to keep track of conversation history. This is great - it makes it easy to continue conversations. As conversations get longer and longer, however, this conversation history can build up and take up more and more of the context window. This can often be undesirable as it leads to more expensive and longer calls to the LLM, and potentially ones that error. In order to prevent this from happening, you need to probably manage the conversation history. Note: this guide focuses on how to do this in LangGraph, where you can fully customize how this is done. If you want a more off-the-shelf solution, you can look into functionality provided in LangChain: - [How to filter messages](https://js.langchain.com/docs/how_to/filter_messages/) - [How to trim messages](https://js.langchain.com/docs/how_to/trim_messages/) ## Setup First, let's set up the packages we're going to want to use ```bash yarn add langchain @langchain/anthropic @langchain/core ``` Next, we need to set API keys for Anthropic (the LLM we will use) ```bash export ANTHROPIC_API_KEY=your_api_key ``` Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```bash export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_CALLBACKS_BACKGROUND="true" export LANGCHAIN_API_KEY=your_api_key ``` ## Build the agent Let's now build a simple ReAct style agent. ```typescript import { ChatAnthropic } from "@langchain/anthropic"; import { tool } from "@langchain/core/tools"; import { BaseMessage, AIMessage } from "@langchain/core/messages"; import { StateGraph, Annotation, START, END } from "@langchain/langgraph"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { MemorySaver } from "@langchain/langgraph"; import { z } from "zod"; const AgentState = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); const memory = new MemorySaver(); const searchTool = tool((_): string => { // This is a placeholder for the actual implementation // Don't let the LLM know this though 😊 return "It's sunny in San Francisco, but you better look out if you're a Gemini 😈." }, { name: "search", description: "Call to surf the web.", schema: z.object({ query: z.string() }) }) const tools = [searchTool] const toolNode = new ToolNode(tools) const model = new ChatAnthropic({ model: "claude-3-haiku-20240307" }) const boundModel = model.bindTools(tools) function shouldContinue(state: typeof AgentState.State): "action" | typeof END { const lastMessage = state.messages[state.messages.length - 1]; // If there is no function call, then we finish if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) { return END; } // Otherwise if there is, we continue return "action"; } // Define the function that calls the model async function callModel(state: typeof AgentState.State) { const response = await model.invoke(state.messages); // We return an object, because this will get merged with the existing state return { messages: [response] }; } // Define a new graph const workflow = new StateGraph(AgentState) // Define the two nodes we will cycle between .addNode("agent", callModel) .addNode("action", toolNode) // We now add a conditional edge .addConditionalEdges( // First, we define the start node. We use `agent`. // This means these are the edges taken after the `agent` node is called. "agent", // Next, we pass in the function that will determine which node is called next. shouldContinue ) // We now add a normal edge from `action` to `agent`. // This means that after `action` is called, `agent` node is called next. .addEdge("action", "agent") // Set the entrypoint as `agent` // This means that this node is the first one called .addEdge(START, "agent"); // Finally, we compile it! // This compiles it into a LangChain Runnable, // meaning you can use it as you would any other runnable const app = workflow.compile({ checkpointer: memory, }); ``` ```typescript import { HumanMessage } from "@langchain/core/messages"; const config = { configurable: { thread_id: "2"}, streamMode: "values" as const } const inputMessage = new HumanMessage("hi! I'm bob"); for await (const event of await app.stream({ messages: [inputMessage] }, config)) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } console.log("\n\n================================= END =================================\n\n") const inputMessage2 = new HumanMessage("what's my name?"); for await (const event of await app.stream({ messages: [inputMessage2] }, config)) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (2) =================================`) console.log(recentMsg.content); } ``` ```output ================================ human Message (1) ================================= hi! I'm bob ================================ ai Message (1) ================================= Hello Bob! It's nice to meet you. I'm an AI assistant created by Anthropic. I'm here to help with any questions or tasks you may have. Please let me know if there's anything I can assist you with. ================================= END ================================= ================================ human Message (2) ================================= what's my name? ================================ ai Message (2) ================================= Your name is Bob, as you introduced yourself earlier. ``` ## Filtering messages The most straight-forward thing to do to prevent conversation history from blowing up is to filter the list of messages before they get passed to the LLM. This involves two parts: defining a function to filter messages, and then adding it to the graph. See the example below which defines a really simple `filterMessages` function and then uses it. ```typescript import { ChatAnthropic } from "@langchain/anthropic"; import { tool } from "@langchain/core/tools"; import { BaseMessage, AIMessage } from "@langchain/core/messages"; import { StateGraph, Annotation, START, END } from "@langchain/langgraph"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { MemorySaver } from "@langchain/langgraph"; import { z } from "zod"; const MessageFilteringAgentState = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); const messageFilteringMemory = new MemorySaver(); const messageFilteringSearchTool = tool((_): string => { // This is a placeholder for the actual implementation // Don't let the LLM know this though 😊 return "It's sunny in San Francisco, but you better look out if you're a Gemini 😈." }, { name: "search", description: "Call to surf the web.", schema: z.object({ query: z.string() }) }) // We can re-use the same search tool as above as we don't need to change it for this example. const messageFilteringTools = [messageFilteringSearchTool] const messageFilteringToolNode = new ToolNode(messageFilteringTools) const messageFilteringModel = new ChatAnthropic({ model: "claude-3-haiku-20240307" }) const boundMessageFilteringModel = messageFilteringModel.bindTools(messageFilteringTools) async function shouldContinueMessageFiltering(state: typeof MessageFilteringAgentState.State): Promise<"action" | typeof END> { const lastMessage = state.messages[state.messages.length - 1]; // If there is no function call, then we finish if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) { return END; } // Otherwise if there is, we continue return "action"; } const filterMessages = (messages: BaseMessage[]): BaseMessage[] => { // This is very simple helper function which only ever uses the last message return messages.slice(-1); } // Define the function that calls the model async function callModelMessageFiltering(state: typeof MessageFilteringAgentState.State) { const response = await boundMessageFilteringModel.invoke(filterMessages(state.messages)); // We return an object, because this will get merged with the existing state return { messages: [response] }; } // Define a new graph const messageFilteringWorkflow = new StateGraph(MessageFilteringAgentState) // Define the two nodes we will cycle between .addNode("agent", callModelMessageFiltering) .addNode("action", messageFilteringToolNode) // We now add a conditional edge .addConditionalEdges( // First, we define the start node. We use `agent`. // This means these are the edges taken after the `agent` node is called. "agent", // Next, we pass in the function that will determine which node is called next. shouldContinueMessageFiltering ) // We now add a normal edge from `action` to `agent`. // This means that after `action` is called, `agent` node is called next. .addEdge("action", "agent") // Set the entrypoint as `agent` // This means that this node is the first one called .addEdge(START, "agent"); // Finally, we compile it! // This compiles it into a LangChain Runnable, // meaning you can use it as you would any other runnable const messageFilteringApp = messageFilteringWorkflow.compile({ checkpointer: messageFilteringMemory, }); ``` ```typescript import { HumanMessage } from "@langchain/core/messages"; const messageFilteringConfig = { configurable: { thread_id: "2"}, streamMode: "values" as const } const messageFilteringInput = new HumanMessage("hi! I'm bob"); for await (const event of await messageFilteringApp.stream({ messages: [messageFilteringInput] }, messageFilteringConfig)) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } console.log("\n\n================================= END =================================\n\n") const messageFilteringInput2 = new HumanMessage("what's my name?"); for await (const event of await messageFilteringApp.stream( { messages: [messageFilteringInput2] }, messageFilteringConfig )) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (2) =================================`) console.log(recentMsg.content); } ``` ```output ================================ human Message (1) ================================= hi! I'm bob ================================ ai Message (1) ================================= Hello, nice to meet you Bob! I'm an AI assistant here to help out. Feel free to let me know if you have any questions or if there's anything I can assist with. ================================= END ================================= ================================ human Message (2) ================================= what's my name? ================================ ai Message (2) ================================= I'm afraid I don't actually know your name, since you haven't provided that information to me. As an AI assistant, I don't have access to personal details about you unless you share them with me directly. I'm happy to continue our conversation, but I don't have enough context to know your specific name. Please feel free to introduce yourself if you'd like me to address you by name. ``` In the above example we defined the `filter_messages` function ourselves. We also provide off-the-shelf ways to trim and filter messages in LangChain. - [How to filter messages](https://js.langchain.com/docs/how_to/filter_messages/) - [How to trim messages](https://js.langchain.com/docs/how_to/trim_messages/) --- how-tos/stream-tokens.ipynb --- # How to stream LLM tokens from your graph In this example, we will stream tokens from the language model powering an agent. We will use a ReAct agent as an example.

Note

If you are using a version of @langchain/core < 0.2.3, when calling chat models or LLMs you need to call await model.stream() within your nodes to get token-by-token streaming events, and aggregate final outputs if needed to update the graph state. In later versions of @langchain/core, this occurs automatically, and you can call await model.invoke().
For more on how to upgrade @langchain/core, check out the instructions here.

This how-to guide closely follows the others in this directory, showing how to incorporate the functionality into a prototypical agent in LangGraph.

Streaming Support

Token streaming is supported by many, but not all chat models. Check to see if your LLM integration supports token streaming here (doc). Note that some integrations may support general token streaming but lack support for streaming tool calls.

Note

In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the createReactAgent({ llm, tools }) (API doc) constructor. This may be more appropriate if you are used to LangChain's AgentExecutor class.

## Setup This guide will use OpenAI's GPT-4o model. We will optionally set our API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. --- ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..."; // process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; // process.env.LANGCHAIN_TRACING = "true"; // process.env.LANGCHAIN_PROJECT = "Stream Tokens: LangGraphJS"; ``` ## Define the state The state is the interface for all of the nodes in our graph. ```typescript import { Annotation } from "@langchain/langgraph"; import type { BaseMessageLike } from "@langchain/core/messages"; const StateAnnotation = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); ``` ## Set up the tools First define the tools you want to use. For this simple example, we'll create a placeholder search engine, but see the documentation [here](https://js.langchain.com/docs/how_to/custom_tools) on how to create your own custom tools. ```typescript import { tool } from "@langchain/core/tools"; import { z } from "zod"; const searchTool = tool((_) => { // This is a placeholder for the actual implementation return "Cold, with a low of 3℃"; }, { name: "search", description: "Use to surf the web, fetch current information, check the weather, and retrieve other information.", schema: z.object({ query: z.string().describe("The query to use in your search."), }), }); await searchTool.invoke({ query: "What's the weather like?" }); const tools = [searchTool]; ``` We can now wrap these tools in a prebuilt ToolNode. This object will actually run the tools (functions) whenever they are invoked by our LLM. ```typescript import { ToolNode } from "@langchain/langgraph/prebuilt"; const toolNode = new ToolNode(tools); ``` ## Set up the model Now load the [chat model](https://js.langchain.com/docs/concepts/#chat-models). 1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them. 2. It should work with [tool calling](https://js.langchain.com/docs/how_to/tool_calling/#passing-tools-to-llms), meaning it can return function arguments in its response.

Note

These model requirements are not general requirements for using LangGraph - they are just requirements for this one example.

```typescript import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0, }); ``` After you've done this, we should make sure the model knows that it has these tools available to call. We can do this by calling [bindTools](https://v01.api.js.langchain.com/classes/langchain_core_language_models_chat_models.BaseChatModel.html#bindTools). ```typescript const boundModel = model.bindTools(tools); ``` ## Define the graph We can now put it all together. ```typescript import { StateGraph, END } from "@langchain/langgraph"; import { AIMessage } from "@langchain/core/messages"; const routeMessage = (state: typeof StateAnnotation.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as AIMessage; // If no tools are called, we can finish (respond to the user) if (!lastMessage?.tool_calls?.length) { return END; } // Otherwise if there is, we continue and call the tools return "tools"; }; const callModel = async ( state: typeof StateAnnotation.State, ) => { // For versions of @langchain/core < 0.2.3, you must call `.stream()` // and aggregate the message from chunks instead of calling `.invoke()`. const { messages } = state; const responseMessage = await boundModel.invoke(messages); return { messages: [responseMessage] }; }; const workflow = new StateGraph(StateAnnotation) .addNode("agent", callModel) .addNode("tools", toolNode) .addEdge("__start__", "agent") .addConditionalEdges("agent", routeMessage) .addEdge("tools", "agent"); const agent = workflow.compile(); ``` ```typescript import * as tslab from "tslab"; const runnableGraph = agent.getGraph(); const image = await runnableGraph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` ## Streaming LLM Tokens You can access the LLM tokens as they are produced by each node with two methods: - The `stream` method along with `streamMode: "messages"` - The `streamEvents` method ### The stream method

Compatibility

This section requires @langchain/langgraph>=0.2.20. For help upgrading, see this guide.

For this method, you must be using an LLM that supports streaming as well (e.g. `new ChatOpenAI({ model: "gpt-4o-mini" })`) or call `.stream` on the internal LLM call. ```typescript import { isAIMessageChunk } from "@langchain/core/messages"; const stream = await agent.stream( { messages: [{ role: "user", content: "What's the current weather in Nepal?" }] }, { streamMode: "messages" }, ); for await (const [message, _metadata] of stream) { if (isAIMessageChunk(message) && message.tool_call_chunks?.length) { console.log(`${message.getType()} MESSAGE TOOL CALL CHUNK: ${message.tool_call_chunks[0].args}`); } else { console.log(`${message.getType()} MESSAGE CONTENT: ${message.content}`); } } ``` ```output ai MESSAGE TOOL CALL CHUNK: ai MESSAGE TOOL CALL CHUNK: {" ai MESSAGE TOOL CALL CHUNK: query ai MESSAGE TOOL CALL CHUNK: ":" ai MESSAGE TOOL CALL CHUNK: current ai MESSAGE TOOL CALL CHUNK: weather ai MESSAGE TOOL CALL CHUNK: in ai MESSAGE TOOL CALL CHUNK: Nepal ai MESSAGE TOOL CALL CHUNK: "} ai MESSAGE CONTENT: tool MESSAGE CONTENT: Cold, with a low of 3℃ ai MESSAGE CONTENT: ai MESSAGE CONTENT: The ai MESSAGE CONTENT: current ai MESSAGE CONTENT: weather ai MESSAGE CONTENT: in ai MESSAGE CONTENT: Nepal ai MESSAGE CONTENT: is ai MESSAGE CONTENT: cold ai MESSAGE CONTENT: , ai MESSAGE CONTENT: with ai MESSAGE CONTENT: a ai MESSAGE CONTENT: low ai MESSAGE CONTENT: temperature ai MESSAGE CONTENT: of ai MESSAGE CONTENT: ai MESSAGE CONTENT: 3 ai MESSAGE CONTENT: ℃ ai MESSAGE CONTENT: . ai MESSAGE CONTENT: ``` ### Disabling streaming If you wish to disable streaming for a given node or model call, you can add a `"nostream"` tag. Here's an example where we add an initial node with an LLM call that will not be streamed in the final output: ```typescript import { RunnableLambda } from "@langchain/core/runnables"; const unstreamed = async (_: typeof StateAnnotation.State) => { const model = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0, }); const res = await model.invoke("How are you?"); console.log("LOGGED UNSTREAMED MESSAGE", res.content); // Don't update the state, this is just to show a call that won't be streamed return {}; } const agentWithNoStream = new StateGraph(StateAnnotation) .addNode("unstreamed", // Add a "nostream" tag to the entire node RunnableLambda.from(unstreamed).withConfig({ tags: ["nostream"] }) ) .addNode("agent", callModel) .addNode("tools", toolNode) // Run the unstreamed node before the agent .addEdge("__start__", "unstreamed") .addEdge("unstreamed", "agent") .addConditionalEdges("agent", routeMessage) .addEdge("tools", "agent") .compile(); const stream = await agentWithNoStream.stream( { messages: [{ role: "user", content: "What's the current weather in Nepal?" }] }, { streamMode: "messages" }, ); for await (const [message, _metadata] of stream) { if (isAIMessageChunk(message) && message.tool_call_chunks?.length) { console.log(`${message.getType()} MESSAGE TOOL CALL CHUNK: ${message.tool_call_chunks[0].args}`); } else { console.log(`${message.getType()} MESSAGE CONTENT: ${message.content}`); } } ``` ```output LOGGED UNSTREAMED MESSAGE I'm just a computer program, so I don't have feelings, but I'm here and ready to help you! How can I assist you today? ai MESSAGE TOOL CALL CHUNK: ai MESSAGE TOOL CALL CHUNK: {" ai MESSAGE TOOL CALL CHUNK: query ai MESSAGE TOOL CALL CHUNK: ":" ai MESSAGE TOOL CALL CHUNK: current ai MESSAGE TOOL CALL CHUNK: weather ai MESSAGE TOOL CALL CHUNK: in ai MESSAGE TOOL CALL CHUNK: Nepal ai MESSAGE TOOL CALL CHUNK: "} ai MESSAGE CONTENT: tool MESSAGE CONTENT: Cold, with a low of 3℃ ai MESSAGE CONTENT: ai MESSAGE CONTENT: The ai MESSAGE CONTENT: current ai MESSAGE CONTENT: weather ai MESSAGE CONTENT: in ai MESSAGE CONTENT: Nepal ai MESSAGE CONTENT: is ai MESSAGE CONTENT: cold ai MESSAGE CONTENT: , ai MESSAGE CONTENT: with ai MESSAGE CONTENT: a ai MESSAGE CONTENT: low ai MESSAGE CONTENT: temperature ai MESSAGE CONTENT: of ai MESSAGE CONTENT: ai MESSAGE CONTENT: 3 ai MESSAGE CONTENT: ℃ ai MESSAGE CONTENT: . ai MESSAGE CONTENT: ``` If you removed the tag from the `"unstreamed"` node, the result of the model call within would also be in the final stream. ### The streamEvents method You can also use the `streamEvents` method like this: ```typescript const eventStream = await agent.streamEvents( { messages: [{ role: "user", content: "What's the weather like today?" }] }, { version: "v2", } ); for await (const { event, data } of eventStream) { if (event === "on_chat_model_stream" && isAIMessageChunk(data.chunk)) { if (data.chunk.tool_call_chunks !== undefined && data.chunk.tool_call_chunks.length > 0) { console.log(data.chunk.tool_call_chunks); } } } ``` ```output [ { name: 'search', args: '', id: 'call_Qpd6frHt0yUYWynRbZEXF3le', index: 0, type: 'tool_call_chunk' } ] [ { name: undefined, args: '{"', id: undefined, index: 0, type: 'tool_call_chunk' } ] [ { name: undefined, args: 'query', id: undefined, index: 0, type: 'tool_call_chunk' } ] [ { name: undefined, args: '":"', id: undefined, index: 0, type: 'tool_call_chunk' } ] [ { name: undefined, args: 'current', id: undefined, index: 0, type: 'tool_call_chunk' } ] [ { name: undefined, args: ' weather', id: undefined, index: 0, type: 'tool_call_chunk' } ] [ { name: undefined, args: ' today', id: undefined, index: 0, type: 'tool_call_chunk' } ] [ { name: undefined, args: '"}', id: undefined, index: 0, type: 'tool_call_chunk' } ] ``` --- how-tos/update-state-from-tools.ipynb --- # How to update graph state from tools

Prerequisites

This guide assumes familiarity with the following:

A common use case is updating graph state from inside a tool. For example, in a customer support application you might want to look up customer account number or ID in the beginning of the conversation. To update the graph state from the tool, you can return a [`Command`](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#command) object from the tool: ```ts import { tool } from "@langchain/core/tools"; const lookupUserInfo = tool(async (input, config) => { const userInfo = getUserInfo(config); return new Command({ // update state keys update: { user_info: userInfo, messages: [ new ToolMessage({ content: "Successfully looked up user information", tool_call_id: config.toolCall.id, }), ], }, }); }, { name: "lookup_user_info", description: "Use this to look up user information to better assist them with their questions.", schema: z.object(...) }); ```

Important

If you want to use tools that return Command instances and update graph state, you can either use prebuilt createReactAgent / ToolNode components, or implement your own tool-executing node that identifies Command objects returned by your tools and returns a mixed array of traditional state updates and Commands.
See this section for an example.

This guide shows how you can do this using LangGraph's prebuilt components ([`createReactAgent`](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html) and [`ToolNode`](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph_prebuilt.ToolNode.html)).

Compatibility

This guide requires @langchain/langgraph>=0.2.33 and @langchain/core@0.3.23. For help upgrading, see this guide.

## Setup Install the following to run this guide: ```bash npm install @langchain/langgraph @langchain/openai @langchain/core ``` Next, configure your environment to connect to your model provider. ```bash export OPENAI_API_KEY=your-api-key ```

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

Let's create a simple ReAct style agent that can look up user information and personalize the response based on the user info. ## Define tool First, let's define the tool that we'll be using to look up user information. We'll use a naive implementation that simply looks user information up using a dictionary: ```typescript const USER_ID_TO_USER_INFO = { abc123: { user_id: "abc123", name: "Bob Dylan", location: "New York, NY", }, zyx987: { user_id: "zyx987", name: "Taylor Swift", location: "Beverly Hills, CA", }, }; ``` ```typescript import { Annotation, Command, MessagesAnnotation } from "@langchain/langgraph"; import { tool } from "@langchain/core/tools"; import { z } from "zod"; const StateAnnotation = Annotation.Root({ ...MessagesAnnotation.spec, // user provided lastName: Annotation, // updated by the tool userInfo: Annotation>, }); const lookupUserInfo = tool(async (_, config) => { const userId = config.configurable?.user_id; if (userId === undefined) { throw new Error("Please provide a user id in config.configurable"); } if (USER_ID_TO_USER_INFO[userId] === undefined) { throw new Error(`User "${userId}" not found`); } // Populated when a tool is called with a tool call from a model as input const toolCallId = config.toolCall.id; return new Command({ update: { // update the state keys userInfo: USER_ID_TO_USER_INFO[userId], // update the message history messages: [ { role: "tool", content: "Successfully looked up user information", tool_call_id: toolCallId, }, ], }, }) }, { name: "lookup_user_info", description: "Always use this to look up information about the user to better assist them with their questions.", schema: z.object({}), }); ``` ## Define prompt Let's now add personalization: we'll respond differently to the user based on the state values AFTER the state has been updated from the tool. To achieve this, let's define a function that will dynamically construct the system prompt based on the graph state. It will be called ever time the LLM is called and the function output will be passed to the LLM: ```typescript const stateModifier = (state: typeof StateAnnotation.State) => { const userInfo = state.userInfo; if (userInfo == null) { return state.messages; } const systemMessage = `User name is ${userInfo.name}. User lives in ${userInfo.location}`; return [{ role: "system", content: systemMessage, }, ...state.messages]; }; ``` ## Define graph Finally, let's combine this into a single graph using the prebuilt `createReactAgent` and the components we declared earlier: ```typescript import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o", }); const agent = createReactAgent({ llm: model, tools: [lookupUserInfo], stateSchema: StateAnnotation, stateModifier: stateModifier, }) ``` ## Use it! Let's now try running our agent. We'll need to provide user ID in the config so that our tool knows what information to look up: ```typescript const stream = await agent.stream({ messages: [{ role: "user", content: "hi, what should i do this weekend?", }], }, { // provide user ID in the config configurable: { user_id: "abc123" } }); for await (const chunk of stream) { console.log(chunk); } ``` ```output { agent: { messages: [ AIMessage { "id": "chatcmpl-AdmOZdrZy3aUgNimCIjq8ZW5js6ln", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_kLXWJYbabxWpj7vykXD6ZMx0", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "promptTokens": 59, "completionTokens": 11, "totalTokens": 70 }, "finish_reason": "tool_calls", "usage": { "prompt_tokens": 59, "completion_tokens": 11, "total_tokens": 70, "prompt_tokens_details": { "cached_tokens": 0, "audio_tokens": 0 }, "completion_tokens_details": { "reasoning_tokens": 0, "audio_tokens": 0, "accepted_prediction_tokens": 0, "rejected_prediction_tokens": 0 } }, "system_fingerprint": "fp_f785eb5f47" }, "tool_calls": [ { "name": "lookup_user_info", "args": {}, "type": "tool_call", "id": "call_kLXWJYbabxWpj7vykXD6ZMx0" } ], "invalid_tool_calls": [], "usage_metadata": { "output_tokens": 11, "input_tokens": 59, "total_tokens": 70, "input_token_details": { "audio": 0, "cache_read": 0 }, "output_token_details": { "audio": 0, "reasoning": 0 } } } ] } } { tools: { userInfo: { user_id: 'abc123', name: 'Bob Dylan', location: 'New York, NY' }, messages: [ [Object] ] } } { agent: { messages: [ AIMessage { "id": "chatcmpl-AdmOZJ0gSQ7VVCUfcadhOeqq4HxWa", "content": "Hi Bob! Since you're in New York, NY, there are plenty of exciting things you can do this weekend. Here are a few suggestions:\n\n1. **Visit Central Park**: Enjoy a leisurely walk, rent a bike, or have a picnic. The park is beautiful in the fall.\n\n2. **Explore Museums**: Check out The Met, MoMA, or The American Museum of Natural History if you're interested in art or history.\n\n3. **Broadway Show**: Catch a Broadway show or a musical for an entertaining evening.\n\n4. **Visit Times Square**: Experience the vibrant lights and energy of Times Square. There are plenty of shops and restaurants to explore.\n\n5. **Brooklyn Bridge Walk**: Walk across the iconic Brooklyn Bridge and enjoy stunning views of Manhattan and Brooklyn.\n\n6. **Cultural Festivals or Events**: Check local listings for any cultural festivals or events happening in the city this weekend.\n\nIf you have specific interests, let me know, and I can suggest something more tailored to your preferences!", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "promptTokens": 98, "completionTokens": 209, "totalTokens": 307 }, "finish_reason": "stop", "usage": { "prompt_tokens": 98, "completion_tokens": 209, "total_tokens": 307, "prompt_tokens_details": { "cached_tokens": 0, "audio_tokens": 0 }, "completion_tokens_details": { "reasoning_tokens": 0, "audio_tokens": 0, "accepted_prediction_tokens": 0, "rejected_prediction_tokens": 0 } }, "system_fingerprint": "fp_cc5cf1c6e3" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "output_tokens": 209, "input_tokens": 98, "total_tokens": 307, "input_token_details": { "audio": 0, "cache_read": 0 }, "output_token_details": { "audio": 0, "reasoning": 0 } } } ] } } ``` We can see that the model correctly recommended some New York activities for Bob Dylan! Let's try getting recommendations for Taylor Swift: ```typescript const taylorStream = await agent.stream({ messages: [{ role: "user", content: "hi, what should i do this weekend?", }], }, { // provide user ID in the config configurable: { user_id: "zyx987" } }); for await (const chunk of taylorStream) { console.log(chunk); } ``` ```output { agent: { messages: [ AIMessage { "id": "chatcmpl-AdmQGANyXPTAkMnQ86hGWB5XY5hGL", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_IvyfreezvohjGgUx9DrwfS5O", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "promptTokens": 59, "completionTokens": 11, "totalTokens": 70 }, "finish_reason": "tool_calls", "usage": { "prompt_tokens": 59, "completion_tokens": 11, "total_tokens": 70, "prompt_tokens_details": { "cached_tokens": 0, "audio_tokens": 0 }, "completion_tokens_details": { "reasoning_tokens": 0, "audio_tokens": 0, "accepted_prediction_tokens": 0, "rejected_prediction_tokens": 0 } }, "system_fingerprint": "fp_cc5cf1c6e3" }, "tool_calls": [ { "name": "lookup_user_info", "args": {}, "type": "tool_call", "id": "call_IvyfreezvohjGgUx9DrwfS5O" } ], "invalid_tool_calls": [], "usage_metadata": { "output_tokens": 11, "input_tokens": 59, "total_tokens": 70, "input_token_details": { "audio": 0, "cache_read": 0 }, "output_token_details": { "audio": 0, "reasoning": 0 } } } ] } } { tools: { userInfo: { user_id: 'zyx987', name: 'Taylor Swift', location: 'Beverly Hills, CA' }, messages: [ [Object] ] } } { agent: { messages: [ AIMessage { "id": "chatcmpl-AdmQHMYj613jksQJruNMVP6DfAagd", "content": "This weekend, there are plenty of exciting things you can do around Beverly Hills, CA. Here are some options:\n\n1. **Explore Rodeo Drive**: Enjoy high-end shopping and dining experiences in this iconic shopping district.\n \n2. **Visit a Museum**: Check out The Getty Center or Los Angeles County Museum of Art (LACMA) for a dose of culture and art.\n\n3. **Hiking**: Take a scenic hike in the nearby Santa Monica Mountains or Griffith Park for beautiful views of the city.\n\n4. **Spa Day**: Treat yourself to a relaxing spa day at one of Beverly Hills' luxurious spas.\n\n5. **Restaurant Tour**: Dine at some of Beverly Hills' finest restaurants, such as Spago or The Penthouse.\n\n6. **Take a Scenic Drive**: Drive along Mulholland Drive for stunning views of Los Angeles and the surrounding areas.\n\n7. **Catch a Show**: See if there are any live performances or concerts happening at The Hollywood Bowl or other nearby venues.\n\nEnjoy your weekend!", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "promptTokens": 98, "completionTokens": 214, "totalTokens": 312 }, "finish_reason": "stop", "usage": { "prompt_tokens": 98, "completion_tokens": 214, "total_tokens": 312, "prompt_tokens_details": { "cached_tokens": 0, "audio_tokens": 0 }, "completion_tokens_details": { "reasoning_tokens": 0, "audio_tokens": 0, "accepted_prediction_tokens": 0, "rejected_prediction_tokens": 0 } }, "system_fingerprint": "fp_9d50cd990b" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "output_tokens": 214, "input_tokens": 98, "total_tokens": 312, "input_token_details": { "audio": 0, "cache_read": 0 }, "output_token_details": { "audio": 0, "reasoning": 0 } } } ] } } ``` ## Custom components If you do not wish to use prebuilt components, you will need to have special logic in your custom tool executor to handle commands. Here's an example: ```typescript import { MessagesAnnotation, isCommand, Command, StateGraph, } from "@langchain/langgraph"; import { tool } from "@langchain/core/tools"; import { isAIMessage } from "@langchain/core/messages"; import { z } from "zod"; const myTool = tool(async () => { return new Command({ update: { messages: [ { role: "assistant", content: "hi there!", name: "Greeter", } ], }, }); }, { name: "greeting", description: "Updates the current state with a greeting", schema: z.object({}), }); const toolExecutor = async (state: typeof MessagesAnnotation.State) => { const message = state.messages.at(-1); if (!isAIMessage(message) || message.tool_calls === undefined || message.tool_calls.length === 0) { throw new Error("Most recent message must be an AIMessage with a tool call.") } const outputs = await Promise.all( message.tool_calls.map(async (toolCall) => { // Using a single tool for simplicity, would need to select tools by toolCall.name // in practice. const toolResult = await myTool.invoke(toolCall); return toolResult; }) ); // Handle mixed Command and non-Command outputs const combinedOutputs = outputs.map((output) => { if (isCommand(output)) { return output; } // Tool invocation result is a ToolMessage, return a normal state update return { messages: [output] }; }); // Return an array of values instead of an object return combinedOutputs; }; // Simple one node graph const customGraph = new StateGraph(MessagesAnnotation) .addNode("runTools", toolExecutor) .addEdge("__start__", "runTools") .compile(); await customGraph.invoke({ messages: [{ role: "user", content: "how are you?", }, { role: "assistant", content: "Let me call the greeting tool and find out!", tool_calls: [{ id: "123", args: {}, name: "greeting", }], }], }); ``` ```output { messages: [ HumanMessage { "id": "801308df-c702-49f4-99c1-da4116f6bbc8", "content": "how are you?", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "8ea07329-a73a-4de4-a4d4-4453fbef32e0", "content": "Let me call the greeting tool and find out!", "additional_kwargs": {}, "response_metadata": {}, "tool_calls": [ { "id": "123", "args": {}, "name": "greeting" } ], "invalid_tool_calls": [] }, AIMessage { "id": "4ecba93a-77c0-44a6-8dc9-8b27d9615c15", "content": "hi there!", "name": "Greeter", "additional_kwargs": {}, "response_metadata": {}, "tool_calls": [], "invalid_tool_calls": [] } ] } ``` ```typescript ``` --- how-tos/time-travel.ipynb --- # How to view and update past graph state !!! tip "Prerequisites" This guide assumes familiarity with the following concepts: * Time Travel * Breakpoints * LangGraph Glossary Once you start checkpointing your graphs, you can easily **get** or **update** the state of the agent at any point in time. This permits a few things: 1. You can surface a state during an interrupt to a user to let them accept an action. 2. You can **rewind** the graph to reproduce or avoid issues. 3. You can **modify** the state to embed your agent into a larger system, or to let the user better control its actions. The key methods used for this functionality are: - getState: fetch the values from the target config - updateState: apply the given values to the target state **Note:** this requires passing in a checkpointer. This works for StateGraph and all its subclasses, such as MessageGraph. Below is an example.

Note

In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the createReactAgent(model, tools=tool, checkpointer=checkpointer) (API doc) constructor. This may be more appropriate if you are used to LangChain's AgentExecutor class.

## Setup This guide will use OpenAI's GPT-4o model. We will optionally set our API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..."; process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "Time Travel: LangGraphJS"; ``` ```output Time Travel: LangGraphJS ``` ## Define the state The state is the interface for all of the nodes in our graph. ```typescript import { Annotation } from "@langchain/langgraph"; import { BaseMessage } from "@langchain/core/messages"; const StateAnnotation = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); ``` ## Set up the tools We will first define the tools we want to use. For this simple example, we will use create a placeholder search engine. However, it is really easy to create your own tools - see documentation [here](https://js.langchain.com/docs/how_to/custom_tools) on how to do that. ```typescript import { tool } from "@langchain/core/tools"; import { z } from "zod"; const searchTool = tool(async (_) => { // This is a placeholder for the actual implementation return "Cold, with a low of 13 ℃"; }, { name: "search", description: "Use to surf the web, fetch current information, check the weather, and retrieve other information.", schema: z.object({ query: z.string().describe("The query to use in your search."), }), }); await searchTool.invoke({ query: "What's the weather like?" }); const tools = [searchTool]; ``` We can now wrap these tools in a simple ToolNode. This object will actually run the tools (functions) whenever they are invoked by our LLM. ```typescript import { ToolNode } from "@langchain/langgraph/prebuilt"; const toolNode = new ToolNode(tools); ``` ## Set up the model Now we will load the [chat model](https://js.langchain.com/docs/concepts/chat_models/). 1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them. 2. It should work with [tool calling](https://js.langchain.com/docs/how_to/tool_calling/#passing-tools-to-llms), meaning it can return function arguments in its response.

Note

These model requirements are not general requirements for using LangGraph - they are just requirements for this one example.

```typescript import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o" }); ``` After we've done this, we should make sure the model knows that it has these tools available to call. We can do this by calling [bindTools](https://v01.api.js.langchain.com/classes/langchain_core_language_models_chat_models.BaseChatModel.html#bindTools). ```typescript const boundModel = model.bindTools(tools); ``` ## Define the graph We can now put it all together. Time travel requires a checkpointer to save the state - otherwise you wouldn't have anything go `get` or `update`. We will use the MemorySaver, which "saves" checkpoints in-memory. ```typescript import { END, START, StateGraph } from "@langchain/langgraph"; import { AIMessage } from "@langchain/core/messages"; import { RunnableConfig } from "@langchain/core/runnables"; import { MemorySaver } from "@langchain/langgraph"; const routeMessage = (state: typeof StateAnnotation.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as AIMessage; // If no tools are called, we can finish (respond to the user) if (!lastMessage?.tool_calls?.length) { return END; } // Otherwise if there is, we continue and call the tools return "tools"; }; const callModel = async ( state: typeof StateAnnotation.State, config?: RunnableConfig, ): Promise> => { const { messages } = state; const response = await boundModel.invoke(messages, config); return { messages: [response] }; }; const workflow = new StateGraph(StateAnnotation) .addNode("agent", callModel) .addNode("tools", toolNode) .addEdge(START, "agent") .addConditionalEdges("agent", routeMessage) .addEdge("tools", "agent"); // Here we only save in-memory let memory = new MemorySaver(); const graph = workflow.compile({ checkpointer: memory }); ``` ## Interacting with the Agent We can now interact with the agent. Between interactions you can get and update state. ```typescript let config = { configurable: { thread_id: "conversation-num-1" } }; let inputs = { messages: [{ role: "user", content: "Hi I'm Jo." }] } as any; for await ( const { messages } of await graph.stream(inputs, { ...config, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output Hi I'm Jo. ----- Hello, Jo! How can I assist you today? ----- ``` See LangSmith example run here https://smith.langchain.com/public/b3feb09b-bcd2-4ad5-ad1d-414106148448/r Here you can see the "agent" node ran, and then our edge returned `__end__` so the graph stopped execution there. Let's check the current graph state. ```typescript let checkpoint = await graph.getState(config); checkpoint.values; ``` ```output { messages: [ { role: 'user', content: "Hi I'm Jo." }, AIMessage { "id": "chatcmpl-A3FGf3k3QQo9q0QjT6Oc5h1XplkHr", "content": "Hello, Jo! How can I assist you today?", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "completionTokens": 12, "promptTokens": 68, "totalTokens": 80 }, "finish_reason": "stop", "system_fingerprint": "fp_fde2829a40" }, "tool_calls": [], "invalid_tool_calls": [] } ] } ``` The current state is the two messages we've seen above, 1. the HumanMessage we sent in, 2. the AIMessage we got back from the model. The `next` values are empty since the graph has terminated (transitioned to the `__end__`). ```typescript checkpoint.next; ``` ```output [] ``` ## Let's get it to execute a tool When we call the graph again, it will create a checkpoint after each internal execution step. Let's get it to run a tool, then look at the checkpoint. ```typescript inputs = { messages: [{ role: "user", content: "What's the weather like in SF currently?" }] } as any; for await ( const { messages } of await graph.stream(inputs, { ...config, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output What's the weather like in SF currently? ----- ``````output [ { name: 'search', args: { query: 'current weather in San Francisco' }, type: 'tool_call', id: 'call_ZtmtDOyEXDCnXDgowlit5dSd' } ] ----- Cold, with a low of 13 ℃ ----- The current weather in San Francisco is cold, with a low of 13°C. ----- ``` See the trace of the above execution here: https://smith.langchain.com/public/0ef426fd-0da1-4c02-a50b-64ae1e68338e/r We can see it planned the tool execution (ie the "agent" node), then "should_continue" edge returned "continue" so we proceeded to "action" node, which executed the tool, and then "agent" node emitted the final response, which made "should_continue" edge return "end". Let's see how we can have more control over this. ### Pause before tools If you notice below, we now will add `interruptBefore=["action"]` - this means that before any actions are taken we pause. This is a great moment to allow the user to correct and update the state! This is very useful when you want to have a human-in-the-loop to validate (and potentially change) the action to take. ```typescript memory = new MemorySaver(); const graphWithInterrupt = workflow.compile({ checkpointer: memory, interruptBefore: ["tools"], }); inputs = { messages: [{ role: "user", content: "What's the weather like in SF currently?" }] } as any; for await ( const { messages } of await graphWithInterrupt.stream(inputs, { ...config, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output What's the weather like in SF currently? ----- [ { name: 'search', args: { query: 'current weather in San Francisco' }, type: 'tool_call', id: 'call_OsKnTv2psf879eeJ9vx5GeoY' } ] ----- ``` ## Get State You can fetch the latest graph checkpoint using `getState(config)`. ```typescript let snapshot = await graphWithInterrupt.getState(config); snapshot.next; ``` ```output [ 'tools' ] ``` ## Resume You can resume by running the graph with a `null` input. The checkpoint is loaded, and with no new inputs, it will execute as if no interrupt had occurred. ```typescript for await ( const { messages } of await graphWithInterrupt.stream(null, { ...snapshot.config, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output Cold, with a low of 13 ℃ ----- Currently, it is cold in San Francisco, with a temperature around 13°C (55°F). ----- ``` ## Check full history Let's browse the history of this thread, from newest to oldest. ```typescript let toReplay; const states = await graphWithInterrupt.getStateHistory(config); for await (const state of states) { console.log(state); console.log("--"); if (state.values?.messages?.length === 2) { toReplay = state; } } if (!toReplay) { throw new Error("No state to replay"); } ``` ```output { values: { messages: [ [Object], AIMessage { "id": "chatcmpl-A3FGhKzOZs0GYZ2yalNOCQZyPgbcp", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_OsKnTv2psf879eeJ9vx5GeoY", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 17, "promptTokens": 72, "totalTokens": 89 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_fde2829a40" }, "tool_calls": [ { "name": "search", "args": { "query": "current weather in San Francisco" }, "type": "tool_call", "id": "call_OsKnTv2psf879eeJ9vx5GeoY" } ], "invalid_tool_calls": [] }, ToolMessage { "content": "Cold, with a low of 13 ℃", "name": "search", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_OsKnTv2psf879eeJ9vx5GeoY" }, AIMessage { "id": "chatcmpl-A3FGiYripPKtQLnAK1H3hWLSXQfOD", "content": "Currently, it is cold in San Francisco, with a temperature around 13°C (55°F).", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "completionTokens": 21, "promptTokens": 105, "totalTokens": 126 }, "finish_reason": "stop", "system_fingerprint": "fp_fde2829a40" }, "tool_calls": [], "invalid_tool_calls": [] } ] }, next: [], tasks: [], metadata: { source: 'loop', writes: { agent: [Object] }, step: 3 }, config: { configurable: { thread_id: 'conversation-num-1', checkpoint_ns: '', checkpoint_id: '1ef69ab6-9c3a-6bd1-8003-d7f030ff72b2' } }, createdAt: '2024-09-03T04:17:20.653Z', parentConfig: { configurable: { thread_id: 'conversation-num-1', checkpoint_ns: '', checkpoint_id: '1ef69ab6-9516-6200-8002-43d2c6dc603f' } } } -- { values: { messages: [ [Object], AIMessage { "id": "chatcmpl-A3FGhKzOZs0GYZ2yalNOCQZyPgbcp", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_OsKnTv2psf879eeJ9vx5GeoY", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 17, "promptTokens": 72, "totalTokens": 89 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_fde2829a40" }, "tool_calls": [ { "name": "search", "args": { "query": "current weather in San Francisco" }, "type": "tool_call", "id": "call_OsKnTv2psf879eeJ9vx5GeoY" } ], "invalid_tool_calls": [] }, ToolMessage { "content": "Cold, with a low of 13 ℃", "name": "search", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_OsKnTv2psf879eeJ9vx5GeoY" } ] }, next: [ 'agent' ], tasks: [ { id: '612efffa-3b16-530f-8a39-fd01c31e7b8b', name: 'agent', interrupts: [] } ], metadata: { source: 'loop', writes: { tools: [Object] }, step: 2 }, config: { configurable: { thread_id: 'conversation-num-1', checkpoint_ns: '', checkpoint_id: '1ef69ab6-9516-6200-8002-43d2c6dc603f' } }, createdAt: '2024-09-03T04:17:19.904Z', parentConfig: { configurable: { thread_id: 'conversation-num-1', checkpoint_ns: '', checkpoint_id: '1ef69ab6-9455-6410-8001-1c78a97f63e6' } } } -- { values: { messages: [ [Object], AIMessage { "id": "chatcmpl-A3FGhKzOZs0GYZ2yalNOCQZyPgbcp", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_OsKnTv2psf879eeJ9vx5GeoY", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 17, "promptTokens": 72, "totalTokens": 89 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_fde2829a40" }, "tool_calls": [ { "name": "search", "args": { "query": "current weather in San Francisco" }, "type": "tool_call", "id": "call_OsKnTv2psf879eeJ9vx5GeoY" } ], "invalid_tool_calls": [] } ] }, next: [ 'tools' ], tasks: [ { id: '767116b0-55b6-5af4-8f74-ce45fb6e31ed', name: 'tools', interrupts: [] } ], metadata: { source: 'loop', writes: { agent: [Object] }, step: 1 }, config: { configurable: { thread_id: 'conversation-num-1', checkpoint_ns: '', checkpoint_id: '1ef69ab6-9455-6410-8001-1c78a97f63e6' } }, createdAt: '2024-09-03T04:17:19.825Z', parentConfig: { configurable: { thread_id: 'conversation-num-1', checkpoint_ns: '', checkpoint_id: '1ef69ab6-8c4b-6261-8000-c51e5807fbcd' } } } -- { values: { messages: [ [Object] ] }, next: [ 'agent' ], tasks: [ { id: '5b0ed7d1-1bb7-5d78-b4fc-7a8ed40e7291', name: 'agent', interrupts: [] } ], metadata: { source: 'loop', writes: null, step: 0 }, config: { configurable: { thread_id: 'conversation-num-1', checkpoint_ns: '', checkpoint_id: '1ef69ab6-8c4b-6261-8000-c51e5807fbcd' } }, createdAt: '2024-09-03T04:17:18.982Z', parentConfig: { configurable: { thread_id: 'conversation-num-1', checkpoint_ns: '', checkpoint_id: '1ef69ab6-8c4b-6260-ffff-6ec582916c42' } } } -- { values: {}, next: [ '__start__' ], tasks: [ { id: 'a4250d5c-d025-5da1-b588-cae2b3f4a8c7', name: '__start__', interrupts: [] } ], metadata: { source: 'input', writes: { messages: [Array] }, step: -1 }, config: { configurable: { thread_id: 'conversation-num-1', checkpoint_ns: '', checkpoint_id: '1ef69ab6-8c4b-6260-ffff-6ec582916c42' } }, createdAt: '2024-09-03T04:17:18.982Z', parentConfig: undefined } -- ``` ## Replay a past state To replay from this place we just need to pass its config back to the agent. ```typescript for await ( const { messages } of await graphWithInterrupt.stream(null, { ...toReplay.config, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output Cold, with a low of 13 ℃ ----- The current weather in San Francisco is cold, with a low of 13°C. ----- ``` ## Branch off a past state Using LangGraph's checkpointing, you can do more than just replay past states. You can branch off previous locations to let the agent explore alternate trajectories or to let a user "version control" changes in a workflow. #### First, update a previous checkpoint Updating the state will create a **new** snapshot by applying the update to the previous checkpoint. Let's **add a tool message** to simulate calling the tool. ```typescript const tool_calls = toReplay.values.messages[toReplay.values.messages.length - 1].tool_calls; const branchConfig = await graphWithInterrupt.updateState( toReplay.config, { messages: [ { role: "tool", content: "It's sunny out, with a high of 38 ℃.", tool_call_id: tool_calls[0].id }, ], }, // Updates are applied "as if" they were coming from a node. By default, // the updates will come from the last node to run. In our case, we want to treat // this update as if it came from the tools node, so that the next node to run will be // the agent. "tools", ); const branchState = await graphWithInterrupt.getState(branchConfig); console.log(branchState.values); console.log(branchState.next); ``` ```output { messages: [ { role: 'user', content: "What's the weather like in SF currently?" }, AIMessage { "id": "chatcmpl-A3FGhKzOZs0GYZ2yalNOCQZyPgbcp", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_OsKnTv2psf879eeJ9vx5GeoY", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 17, "promptTokens": 72, "totalTokens": 89 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_fde2829a40" }, "tool_calls": [ { "name": "search", "args": { "query": "current weather in San Francisco" }, "type": "tool_call", "id": "call_OsKnTv2psf879eeJ9vx5GeoY" } ], "invalid_tool_calls": [] }, { role: 'tool', content: "It's sunny out, with a high of 38 ℃.", tool_call_id: 'call_OsKnTv2psf879eeJ9vx5GeoY' } ] } [ 'agent' ] ``` #### Now you can run from this branch Just use the updated config (containing the new checkpoint ID). The trajectory will follow the new branch. ```typescript for await ( const { messages } of await graphWithInterrupt.stream(null, { ...branchConfig, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output The current weather in San Francisco is sunny, with a high of 38°C. ----- ``` --- how-tos/stream-values.ipynb --- # How to stream full state of your graph LangGraph supports multiple streaming modes. The main ones are: - `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called. - `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called. This guide covers `streamMode="values"`. ```typescript // process.env.OPENAI_API_KEY = "sk-..."; ``` ## Define the state The state is the interface for all of the nodes in our graph. ```typescript import { Annotation } from "@langchain/langgraph"; import { BaseMessage } from "@langchain/core/messages"; const StateAnnotation = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); ``` ## Set up the tools We will first define the tools we want to use. For this simple example, we will use create a placeholder search engine. However, it is really easy to create your own tools - see documentation [here](https://js.langchain.com/docs/how_to/custom_tools) on how to do that. ```typescript import { tool } from "@langchain/core/tools"; import { z } from "zod"; const searchTool = tool(async ({ query: _query }: { query: string }) => { // This is a placeholder for the actual implementation return "Cold, with a low of 3℃"; }, { name: "search", description: "Use to surf the web, fetch current information, check the weather, and retrieve other information.", schema: z.object({ query: z.string().describe("The query to use in your search."), }), }); await searchTool.invoke({ query: "What's the weather like?" }); const tools = [searchTool]; ``` We can now wrap these tools in a simple ToolNode. This object will actually run the tools (functions) whenever they are invoked by our LLM. ```typescript import { ToolNode } from "@langchain/langgraph/prebuilt"; const toolNode = new ToolNode(tools); ``` ## Set up the model Now we will load the [chat model](https://js.langchain.com/docs/concepts/chat_models/). 1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them. 2. It should work with [tool calling](https://js.langchain.com/docs/how_to/tool_calling/#passing-tools-to-llms), meaning it can return function arguments in its response.

Note

These model requirements are not general requirements for using LangGraph - they are just requirements for this one example.

```typescript import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o" }); ``` After we've done this, we should make sure the model knows that it has these tools available to call. We can do this by calling [bindTools](https://v01.api.js.langchain.com/classes/langchain_core_language_models_chat_models.BaseChatModel.html#bindTools). ```typescript const boundModel = model.bindTools(tools); ``` ## Define the graph We can now put it all together. ```typescript import { END, START, StateGraph } from "@langchain/langgraph"; import { AIMessage } from "@langchain/core/messages"; const routeMessage = (state: typeof StateAnnotation.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as AIMessage; // If no tools are called, we can finish (respond to the user) if (!lastMessage?.tool_calls?.length) { return END; } // Otherwise if there is, we continue and call the tools return "tools"; }; const callModel = async ( state: typeof StateAnnotation.State, ) => { // For versions of @langchain/core < 0.2.3, you must call `.stream()` // and aggregate the message from chunks instead of calling `.invoke()`. const { messages } = state; const responseMessage = await boundModel.invoke(messages); return { messages: [responseMessage] }; }; const workflow = new StateGraph(StateAnnotation) .addNode("agent", callModel) .addNode("tools", toolNode) .addEdge(START, "agent") .addConditionalEdges("agent", routeMessage) .addEdge("tools", "agent"); const graph = workflow.compile(); ``` ## Stream values We can now interact with the agent. Between interactions you can get and update state. ```typescript let inputs = { messages: [{ role: "user", content: "what's the weather in sf" }] }; for await ( const chunk of await graph.stream(inputs, { streamMode: "values", }) ) { console.log(chunk["messages"]); console.log("\n====\n"); } ``` ```output [ [ 'user', "what's the weather in sf" ] ] ==== [ [ 'user', "what's the weather in sf" ], AIMessage { "id": "chatcmpl-9y660d49eLzT7DZeBk2ZmX8C5f0LU", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_iD5Wk4vPsTckffDKJpEQaMkg", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 17, "promptTokens": 70, "totalTokens": 87 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_3aa7262c27" }, "tool_calls": [ { "name": "search", "args": { "query": "current weather in San Francisco" }, "type": "tool_call", "id": "call_iD5Wk4vPsTckffDKJpEQaMkg" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 70, "output_tokens": 17, "total_tokens": 87 } } ] ==== [ [ 'user', "what's the weather in sf" ], AIMessage { "id": "chatcmpl-9y660d49eLzT7DZeBk2ZmX8C5f0LU", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_iD5Wk4vPsTckffDKJpEQaMkg", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 17, "promptTokens": 70, "totalTokens": 87 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_3aa7262c27" }, "tool_calls": [ { "name": "search", "args": { "query": "current weather in San Francisco" }, "type": "tool_call", "id": "call_iD5Wk4vPsTckffDKJpEQaMkg" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 70, "output_tokens": 17, "total_tokens": 87 } }, ToolMessage { "content": "Cold, with a low of 3℃", "name": "search", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_iD5Wk4vPsTckffDKJpEQaMkg" } ] ==== [ [ 'user', "what's the weather in sf" ], AIMessage { "id": "chatcmpl-9y660d49eLzT7DZeBk2ZmX8C5f0LU", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_iD5Wk4vPsTckffDKJpEQaMkg", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 17, "promptTokens": 70, "totalTokens": 87 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_3aa7262c27" }, "tool_calls": [ { "name": "search", "args": { "query": "current weather in San Francisco" }, "type": "tool_call", "id": "call_iD5Wk4vPsTckffDKJpEQaMkg" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 70, "output_tokens": 17, "total_tokens": 87 } }, ToolMessage { "content": "Cold, with a low of 3℃", "name": "search", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_iD5Wk4vPsTckffDKJpEQaMkg" }, AIMessage { "id": "chatcmpl-9y660ZKNXvziVJze0X5aTlZ5IoN35", "content": "Currently, in San Francisco, it's cold with a temperature of around 3℃ (37.4°F).", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "completionTokens": 23, "promptTokens": 103, "totalTokens": 126 }, "finish_reason": "stop", "system_fingerprint": "fp_3aa7262c27" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 103, "output_tokens": 23, "total_tokens": 126 } } ] ==== ``` --- how-tos/cross-thread-persistence.ipynb --- # How to add cross-thread persistence to your graph

Prerequisites

This guide assumes familiarity with the following:

In the [previous guide](https://langchain-ai.github.io/langgraphjs/how-tos/persistence.ipynb) you learned how to persist graph state across multiple interactions on a single thread. LangGraph.js also allows you to persist data across **multiple threads**. For instance, you can store information about users (their names or preferences) in a shared memory and reuse them in the new conversational threads. In this guide, we will show how to construct and use a graph that has a shared memory implemented using the [Store](https://langchain-ai.github.io/langgraphjs/reference/classes/checkpoint.BaseStore.html) interface.

Note

Support for the Store API that is used in this guide was added in LangGraph.js v0.2.10.

## Setup First, let's install the required packages and set our API keys.

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "lsv2__..."; // process.env.ANTHROPIC_API_KEY = "your api key"; // process.env.LANGCHAIN_TRACING_V2 = "true"; // process.env.LANGCHAIN_PROJECT = "Cross-thread persistence: LangGraphJS"; ``` ## Define store In this example we will create a graph that will be able to retrieve information about a user's preferences. We will do so by defining an `InMemoryStore` - an object that can store data in memory and query that data. We will then pass the store object when compiling the graph. This allows each node in the graph to access the store: when you define node functions, you can define `store` keyword argument, and LangGraph will automatically pass the store object you compiled the graph with. When storing objects using the `Store` interface you define two things: * the namespace for the object, a tuple (similar to directories) * the object key (similar to filenames) In our example, we'll be using `("memories", )` as namespace and random UUID as key for each new memory. Importantly, to determine the user, we will be passing `userId` via the config keyword argument of the node function. Let's first define an `InMemoryStore` which is already populated with some memories about the users. ```typescript import { InMemoryStore } from "@langchain/langgraph"; const inMemoryStore = new InMemoryStore(); ``` ## Create graph ```typescript import { v4 as uuidv4 } from "uuid"; import { ChatAnthropic } from "@langchain/anthropic"; import { BaseMessage } from "@langchain/core/messages"; import { Annotation, StateGraph, START, MemorySaver, LangGraphRunnableConfig, messagesStateReducer, } from "@langchain/langgraph"; const StateAnnotation = Annotation.Root({ messages: Annotation({ reducer: messagesStateReducer, default: () => [], }), }); const model = new ChatAnthropic({ modelName: "claude-3-5-sonnet-20240620" }); // NOTE: we're passing the Store param to the node -- // this is the Store we compile the graph with const callModel = async ( state: typeof StateAnnotation.State, config: LangGraphRunnableConfig ): Promise<{ messages: any }> => { const store = config.store; if (!store) { if (!store) { throw new Error("store is required when compiling the graph"); } } if (!config.configurable?.userId) { throw new Error("userId is required in the config"); } const namespace = ["memories", config.configurable?.userId]; const memories = await store.search(namespace); const info = memories.map((d) => d.value.data).join("\n"); const systemMsg = `You are a helpful assistant talking to the user. User info: ${info}`; // Store new memories if the user asks the model to remember const lastMessage = state.messages[state.messages.length - 1]; if ( typeof lastMessage.content === "string" && lastMessage.content.toLowerCase().includes("remember") ) { await store.put(namespace, uuidv4(), { data: lastMessage.content }); } const response = await model.invoke([ { type: "system", content: systemMsg }, ...state.messages, ]); return { messages: response }; }; const builder = new StateGraph(StateAnnotation) .addNode("call_model", callModel) .addEdge(START, "call_model"); // NOTE: we're passing the store object here when compiling the graph const graph = builder.compile({ checkpointer: new MemorySaver(), store: inMemoryStore, }); // If you're using LangGraph Cloud or LangGraph Studio, you don't need to pass the store or checkpointer when compiling the graph, since it's done automatically. ```

Note

If you're using LangGraph Cloud or LangGraph Studio, you don't need to pass store when compiling the graph, since it's done automatically.

## Run the graph! Now let's specify a user ID in the config and tell the model our name: ```typescript let config = { configurable: { thread_id: "1", userId: "1" } }; let inputMessage = { type: "user", content: "Hi! Remember: my name is Bob" }; for await (const chunk of await graph.stream( { messages: [inputMessage] }, { ...config, streamMode: "values" } )) { console.log(chunk.messages[chunk.messages.length - 1]); } ``` ```output HumanMessage { "id": "ef28a40a-fd75-4478-929a-5413f2a6b044", "content": "Hi! Remember: my name is Bob", "additional_kwargs": {}, "response_metadata": {} } AIMessage { "id": "msg_01UcHJnSAuVDFuDmqaYkxWAf", "content": "Hello Bob! It's nice to meet you. I'll remember that your name is Bob. How can I assist you today?", "additional_kwargs": { "id": "msg_01UcHJnSAuVDFuDmqaYkxWAf", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 28, "output_tokens": 29 } }, "response_metadata": { "id": "msg_01UcHJnSAuVDFuDmqaYkxWAf", "model": "claude-3-5-sonnet-20240620", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 28, "output_tokens": 29 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 28, "output_tokens": 29, "total_tokens": 57 } } ``` ```typescript config = { configurable: { thread_id: "2", userId: "1" } }; inputMessage = { type: "user", content: "what is my name?" }; for await (const chunk of await graph.stream( { messages: [inputMessage] }, { ...config, streamMode: "values" } )) { console.log(chunk.messages[chunk.messages.length - 1]); } ``` ```output HumanMessage { "id": "eaaa4e1c-1560-4b0a-9c2d-396313cb000c", "content": "what is my name?", "additional_kwargs": {}, "response_metadata": {} } AIMessage { "id": "msg_01VfqUerYCND1JuWGvbnAacP", "content": "Your name is Bob. It's nice to meet you, Bob!", "additional_kwargs": { "id": "msg_01VfqUerYCND1JuWGvbnAacP", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 33, "output_tokens": 17 } }, "response_metadata": { "id": "msg_01VfqUerYCND1JuWGvbnAacP", "model": "claude-3-5-sonnet-20240620", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 33, "output_tokens": 17 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 33, "output_tokens": 17, "total_tokens": 50 } } ``` We can now inspect our in-memory store and verify that we have in fact saved the memories for the user: ```typescript const memories = await inMemoryStore.search(["memories", "1"]); for (const memory of memories) { console.log(await memory.value); } ``` ```output { data: 'Hi! Remember: my name is Bob' } ``` Let's now run the graph for another user to verify that the memories about the first user are self contained: ```typescript config = { configurable: { thread_id: "3", userId: "2" } }; inputMessage = { type: "user", content: "what is my name?" }; for await (const chunk of await graph.stream( { messages: [inputMessage] }, { ...config, streamMode: "values" } )) { console.log(chunk.messages[chunk.messages.length - 1]); } ``` ```output HumanMessage { "id": "1006b149-de8d-4d8e-81f4-c78c51a7144b", "content": "what is my name?", "additional_kwargs": {}, "response_metadata": {} } AIMessage { "id": "msg_01MjpYZ65NjwZMYq42BWa2Ze", "content": "I apologize, but I don't have any information about your name or personal details. As an AI assistant, I don't have access to personal information about individual users unless it's specifically provided in our conversation. Is there something else I can help you with?", "additional_kwargs": { "id": "msg_01MjpYZ65NjwZMYq42BWa2Ze", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 25, "output_tokens": 56 } }, "response_metadata": { "id": "msg_01MjpYZ65NjwZMYq42BWa2Ze", "model": "claude-3-5-sonnet-20240620", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 25, "output_tokens": 56 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 25, "output_tokens": 56, "total_tokens": 81 } } ``` --- how-tos/define-state.ipynb --- # How to define graph state This how to guide will cover different ways to define the state of your graph. ## Prerequisites - State conceptual guide - Conceptual guide on defining the state of your graph. - Building graphs - This how-to assumes you have a basic understanding of how to build graphs. ## Setup This guide requires installing the `@langchain/langgraph`, and `@langchain/core` packages: ```bash npm install @langchain/langgraph @langchain/core ``` ## Getting started The `Annotation` function is the recommended way to define your graph state for new `StateGraph` graphs. The `Annotation.Root` function is used to create the top-level state object, where each field represents a channel in the graph. Here's an example of how to define a simple graph state with one channel called `messages`: ```typescript import { BaseMessage } from "@langchain/core/messages"; import { Annotation } from "@langchain/langgraph"; const GraphAnnotation = Annotation.Root({ // Define a 'messages' channel to store an array of BaseMessage objects messages: Annotation({ // Reducer function: Combines the current state with new messages reducer: (currentState, updateValue) => currentState.concat(updateValue), // Default function: Initialize the channel with an empty array default: () => [], }) }); ``` Each channel can optionally have `reducer` and `default` functions: - The `reducer` function defines how new values are combined with the existing state. - The `default` function provides an initial value for the channel. For more information on reducers, see the reducers conceptual guide ```typescript const QuestionAnswerAnnotation = Annotation.Root({ question: Annotation, answer: Annotation, }); ``` Above, all we're doing is defining the channels, and then passing the un-instantiated `Annotation` function as the value. It is important to note we always pass in the TypeScript type of each channel as the first generics argument to `Annotation`. Doing this ensures our graph state is type safe, and we can get the proper types when defining our nodes. Below shows how you can extract the typings from the `Annotation` function: ```typescript type QuestionAnswerAnnotationType = typeof QuestionAnswerAnnotation.State; ``` This is equivalent to the following type: ```typescript type QuestionAnswerAnnotationType = { question: string; answer: string; } ``` ## Merging states If you have two graph state annotations, you can merge the two into a single annotation by using the `spec` value: ```typescript const MergedAnnotation = Annotation.Root({ ...QuestionAnswerAnnotation.spec, ...GraphAnnotation.spec, }) ``` The type of the merged annotation is the intersection of the two annotations: ```typescript type MergedAnnotation = { messages: BaseMessage[]; question: string; answer: string; } ``` Finally, instantiating your graph using the annotations is as simple as passing the annotation to the `StateGraph` constructor: ```typescript import { StateGraph } from "@langchain/langgraph"; const workflow = new StateGraph(MergedAnnotation); ``` ## State channels The `Annotation` function is a convince wrapper around the low level implementation of how states are defined in LangGraph. Defining state using the `channels` object (which is what `Annotation` is a wrapper of) is still possible, although not recommended for most cases. The below example shows how to implement a graph using this pattern: ```typescript import { StateGraph } from "@langchain/langgraph"; interface WorkflowChannelsState { messages: BaseMessage[]; question: string; answer: string; } const workflowWithChannels = new StateGraph({ channels: { messages: { reducer: (currentState, updateValue) => currentState.concat(updateValue), default: () => [], }, question: null, answer: null, } }); ``` Above, we set the value of `question` and `answer` to `null`, as it does not contain a default value. To set a default value, the channel should be implemented how the `messages` key is, with the `default` factory returing the default value. The `reducer` function is optional, and can be added to the channel object if needed. ## Using Zod If you want to add runtime validation to your state, you can use Zod instead of the `Annotation` function for state definition. You can also pass in your custom `reducer` and `default` factories as well by importing `@langchain/langgraph/zod`, which will extend Zod with LangGraph specific methods. ```typescript import "@langchain/langgraph/zod"; import { z } from "zod"; const AgentState = z.object({ messages: z .array(z.string()) .default(() => []) .langgraph.reducer( (a, b) => a.concat(Array.isArray(b) ? b : [b]), z.union([z.string(), z.array(z.string())]) ), question: z.string(), answer: z.string().min(1), }); const graph = new StateGraph(AgentState); ``` --- how-tos/subgraph-transform-state.ipynb --- # How to transform inputs and outputs of a subgraph It's possible that your subgraph state is completely independent from the parent graph state, i.e. there are no overlapping channels (keys) between the two. For example, you might have a supervisor agent that needs to produce a report with a help of multiple ReAct agents. ReAct agent subgraphs might keep track of a list of messages whereas the supervisor only needs user input and final report in its state, and doesn't need to keep track of messages. In such cases you need to transform the inputs to the subgraph before calling it and then transform its outputs before returning. This guide shows how to do that. ## Setup First, let's install the required packages ```bash npm install @langchain/langgraph @langchain/core ```

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

## Define graph and subgraphs Let's define 3 graphs: - a parent graph - a child subgraph that will be called by the parent graph - a grandchild subgraph that will be called by the child graph ### Define grandchild ```typescript import { StateGraph, START, Annotation } from "@langchain/langgraph"; const GrandChildAnnotation = Annotation.Root({ myGrandchildKey: Annotation, }) const grandchild1 = (state: typeof GrandChildAnnotation.State) => { // NOTE: child or parent keys will not be accessible here return { myGrandchildKey: state.myGrandchildKey + ", how are you" } } const grandchild = new StateGraph(GrandChildAnnotation) .addNode("grandchild1", grandchild1) .addEdge(START, "grandchild1") const grandchildGraph = grandchild.compile(); ``` ```typescript await grandchildGraph.invoke({ myGrandchildKey: "hi Bob" }) ``` ```output { myGrandchildKey: 'hi Bob, how are you' } ``` ### Define child ```typescript import { StateGraph, START, Annotation } from "@langchain/langgraph"; const ChildAnnotation = Annotation.Root({ myChildKey: Annotation, }); const callGrandchildGraph = async (state: typeof ChildAnnotation.State) => { // NOTE: parent or grandchild keys won't be accessible here // we're transforming the state from the child state channels (`myChildKey`) // to the grandchild state channels (`myGrandchildKey`) const grandchildGraphInput = { myGrandchildKey: state.myChildKey }; // we're transforming the state from the grandchild state channels (`myGrandchildKey`) // back to the child state channels (`myChildKey`) const grandchildGraphOutput = await grandchildGraph.invoke(grandchildGraphInput); return { myChildKey: grandchildGraphOutput.myGrandchildKey + " today?" }; }; const child = new StateGraph(ChildAnnotation) // NOTE: we're passing a function here instead of just compiled graph (`childGraph`) .addNode("child1", callGrandchildGraph) .addEdge(START, "child1"); const childGraph = child.compile(); ``` ```typescript await childGraph.invoke({ myChildKey: "hi Bob" }) ``` ```output { myChildKey: 'hi Bob, how are you today?' } ```

Note

We're wrapping the grandchildGraph invocation in a separate function (callGrandchildGraph) that transforms the input state before calling the grandchild graph and then transforms the output of grandchild graph back to child graph state. If you just pass grandchildGraph directly to .addNode without the transformations, LangGraph will raise an error as there are no shared state channels (keys) between child and grandchild states.

Note that child and grandchild subgraphs have their own, **independent** state that is not shared with the parent graph. ### Define parent ```typescript import { StateGraph, START, END, Annotation } from "@langchain/langgraph"; const ParentAnnotation = Annotation.Root({ myKey: Annotation, }); const parent1 = (state: typeof ParentAnnotation.State) => { // NOTE: child or grandchild keys won't be accessible here return { myKey: "hi " + state.myKey }; }; const parent2 = (state: typeof ParentAnnotation.State) => { return { myKey: state.myKey + " bye!" }; }; const callChildGraph = async (state: typeof ParentAnnotation.State) => { // we're transforming the state from the parent state channels (`myKey`) // to the child state channels (`myChildKey`) const childGraphInput = { myChildKey: state.myKey }; // we're transforming the state from the child state channels (`myChildKey`) // back to the parent state channels (`myKey`) const childGraphOutput = await childGraph.invoke(childGraphInput); return { myKey: childGraphOutput.myChildKey }; }; const parent = new StateGraph(ParentAnnotation) .addNode("parent1", parent1) // NOTE: we're passing a function here instead of just a compiled graph (`childGraph`) .addNode("child", callChildGraph) .addNode("parent2", parent2) .addEdge(START, "parent1") .addEdge("parent1", "child") .addEdge("child", "parent2") .addEdge("parent2", END); const parentGraph = parent.compile(); ```

Note

We're wrapping the childGraph invocation in a separate function (callChildGraph) that transforms the input state before calling the child graph and then transforms the output of the child graph back to parent graph state. If you just pass childGraph directly to .addNode without the transformations, LangGraph will raise an error as there are no shared state channels (keys) between parent and child states.

Let's run the parent graph and make sure it correctly calls both the child and grandchild subgraphs: ```typescript await parentGraph.invoke({ myKey: "Bob" }) ``` ```output { myKey: 'hi Bob, how are you today? bye!' } ``` Perfect! The parent graph correctly calls both the child and grandchild subgraphs (which we know since the ", how are you" and "today?" are added to our original "myKey" state value). --- how-tos/streaming-from-final-node.ipynb --- # How to stream from the final node One common pattern for graphs is to stream LLM tokens from inside the final node only. This guide demonstrates how you can do this. ## Define model and tools First, set up a chat model and a tool to call within your graph: ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core ``` ```typescript import { z } from "zod"; import { tool } from "@langchain/core/tools"; import { ChatAnthropic } from "@langchain/anthropic"; const getWeather = tool(async ({ city }) => { if (city === "nyc") { return "It might be cloudy in nyc"; } else if (city === "sf") { return "It's always sunny in sf"; } else { throw new Error("Unknown city."); } }, { name: "get_weather", schema: z.object({ city: z.enum(["nyc", "sf"]), }), description: "Use this to get weather information", }); const tools = [getWeather]; const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20240620", }).bindTools(tools); // We add a tag that we'll be using later to filter outputs const finalModel = new ChatAnthropic({ model: "claude-3-5-sonnet-20240620", }).withConfig({ tags: ["final_node"], }); ``` ## Define graph Now, lay out your graph: ```typescript import { StateGraph, MessagesAnnotation } from "@langchain/langgraph"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { AIMessage, HumanMessage, SystemMessage } from "@langchain/core/messages"; const shouldContinue = async (state: typeof MessagesAnnotation.State) => { const messages = state.messages; const lastMessage: AIMessage = messages[messages.length - 1]; // If the LLM makes a tool call, then we route to the "tools" node if (lastMessage.tool_calls?.length) { return "tools"; } // Otherwise, we stop (reply to the user) return "final"; }; const callModel = async (state: typeof MessagesAnnotation.State) => { const messages = state.messages; const response = await model.invoke(messages); // We return a list, because this will get added to the existing list return { messages: [response] }; }; const callFinalModel = async (state: typeof MessagesAnnotation.State) => { const messages = state.messages; const lastAIMessage = messages[messages.length - 1]; const response = await finalModel.invoke([ new SystemMessage("Rewrite this in the voice of Al Roker"), new HumanMessage({ content: lastAIMessage.content }) ]); // MessagesAnnotation allows you to overwrite messages from the agent // by returning a message with the same id response.id = lastAIMessage.id; return { messages: [response] }; } const toolNode = new ToolNode(tools); const graph = new StateGraph(MessagesAnnotation) .addNode("agent", callModel) .addNode("tools", toolNode) // add a separate final node .addNode("final", callFinalModel) .addEdge("__start__", "agent") // Third parameter is optional and only here to draw a diagram of the graph .addConditionalEdges("agent", shouldContinue, { tools: "tools", final: "final", }) .addEdge("tools", "agent") .addEdge("final", "__end__") .compile(); ``` ```typescript import * as tslab from "tslab"; const diagram = graph.getGraph(); const image = await diagram.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); tslab.display.png(new Uint8Array(arrayBuffer)); ``` ## Stream outputs from the final node ```typescript const inputs = { messages: [new HumanMessage("What's the weather in nyc?")] }; const eventStream = await graph.streamEvents(inputs, { version: "v2"}); for await (const { event, tags, data } of eventStream) { if (event === "on_chat_model_stream" && tags.includes("final_node")) { if (data.chunk.content) { // Empty content in the context of OpenAI or Anthropic usually means // that the model is asking for a tool to be invoked. // So we only print non-empty content console.log(data.chunk.content, "|"); } } } ``` ```output Hey | there, folks | ! Al | Roker here with | your weather update. | Well | , well | , well, it seems | like | the | Big | Apple might | be getting | a little over | cast today. That | 's right | , we | 're | looking | at some | cloud cover moving in over | New | York City. But hey | , don't let that | dampen your spirits! | A | little clou | d never | hurt anybody | , | right? Now | , I | ' | d love | to give | you more | details, | but Mother | Nature can | be as | unpredictable as | a game | of chance sometimes | . So | , if | you want | the full | scoop on NYC | 's weather | or | if | you're | curious | about conditions | in any other city across | this | great nation of ours | , just give | me a ho | ller! I'm here | to keep | you in the know, | whether | it's sunshine | , | rain, or anything | in between. Remember | , a clou | dy day is | just | the | sun | 's | way of letting | you know it's still | there, even if you | can't see it. | Stay | weather | -aware | , | an | d don | 't forget your | umbrella... | just in case! | ``` ```typescript ``` --- how-tos/wait-user-input-functional.ipynb --- # How to wait for user input (Functional API) !!! info "Prerequisites" This guide assumes familiarity with the following: - Implementing human-in-the-loop workflows with interrupt - How to create a ReAct agent using the Functional API **Human-in-the-loop (HIL)** interactions are crucial for agentic systems. Waiting for human input is a common HIL interaction pattern, allowing the agent to ask the user clarifying questions and await input before proceeding. We can implement this in LangGraph using the interrupt() function. `interrupt` allows us to stop graph execution to collect input from a user and continue execution with collected input. This guide demonstrates how to implement human-in-the-loop workflows using LangGraph's Functional API. Specifically, we will demonstrate: 1. A simple usage example 2. How to use with a ReAct agent ## Setup !!! note Compatibility This guide requires `@langchain/langgraph>=0.2.42`. First, install the required dependencies for this example: ```bash npm install @langchain/langgraph @langchain/openai @langchain/core zod ``` Next, we need to set API keys for OpenAI (the LLM we will use): ```typescript process.env.OPENAI_API_KEY = "YOUR_API_KEY"; ``` !!! tip "Set up [LangSmith](https://smith.langchain.com) for LangGraph development" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com) ## Simple usage Let's demonstrate a simple usage example. We will create three tasks: 1. Append `"bar"`. 2. Pause for human input. When resuming, append human input. 3. Append `"qux"`. ```typescript import { task, interrupt } from "@langchain/langgraph"; const step1 = task("step1", async (inputQuery: string) => { return `${inputQuery} bar`; }); const humanFeedback = task( "humanFeedback", async (inputQuery: string) => { const feedback = interrupt(`Please provide feedback: ${inputQuery}`); return `${inputQuery} ${feedback}`; }); const step3 = task("step3", async (inputQuery: string) => { return `${inputQuery} qux`; }); ``` We can now compose these tasks in a simple entrypoint: ```typescript import { MemorySaver, entrypoint } from "@langchain/langgraph"; const checkpointer = new MemorySaver(); const graph = entrypoint({ name: "graph", checkpointer, }, async (inputQuery: string) => { const result1 = await step1(inputQuery); const result2 = await humanFeedback(result1); const result3 = await step3(result2); return result3; }); ``` All we have done to enable human-in-the-loop workflows is call interrupt() inside a task. !!! tip The results of prior tasks - in this case `step1 -- are persisted, so that they are not run again following the `interrupt`. Let's send in a query string: ```typescript const config = { configurable: { thread_id: "1" } }; const stream = await graph.stream("foo", config); for await (const event of stream) { console.log(event); } ``` ```output { step1: 'foo bar' } { __interrupt__: [ { value: 'Please provide feedback: foo bar', when: 'during', resumable: true, ns: [Array] } ] } ``` Note that we've paused with an `interrupt` after `step1`. The interrupt provides instructions to resume the run. To resume, we issue a Command containing the data expected by the `humanFeedback` task. ```typescript import { Command } from "@langchain/langgraph"; const resumeStream = await graph.stream(new Command({ resume: "baz" }), config); // Continue execution for await (const event of resumeStream) { if (event.__metadata__?.cached) { continue; } console.log(event); } ``` ```output { humanFeedback: 'foo bar baz' } { step3: 'foo bar baz qux' } { graph: 'foo bar baz qux' } ``` After resuming, the run proceeds through the remaining step and terminates as expected. ## Agent We will build off of the agent created in the How to create a ReAct agent using the Functional API guide. Here we will extend the agent by allowing it to reach out to a human for assistance when needed. ### Define model and tools Let's first define the tools and model we will use for our example. As in the ReAct agent guide, we will use a single place-holder tool that gets a description of the weather for a location. We will use an [OpenAI](https://js.langchain.com/docs/integrations/providers/openai/) chat model for this example, but any model [supporting tool-calling](https://js.langchain.com/docs/integrations/chat/) will suffice. ```typescript import { ChatOpenAI } from "@langchain/openai"; import { tool } from "@langchain/core/tools"; import { z } from "zod"; const model = new ChatOpenAI({ model: "gpt-4o-mini", }); const getWeather = tool(async ({ location }) => { // This is a placeholder for the actual implementation const lowercaseLocation = location.toLowerCase(); if (lowercaseLocation.includes("sf") || lowercaseLocation.includes("san francisco")) { return "It's sunny!"; } else if (lowercaseLocation.includes("boston")) { return "It's rainy!"; } else { return `I am not sure what the weather is in ${location}`; } }, { name: "getWeather", schema: z.object({ location: z.string().describe("Location to get the weather for"), }), description: "Call to get the weather from a specific location.", }); ``` To reach out to a human for assistance, we can simply add a tool that calls interrupt: ```typescript import { interrupt } from "@langchain/langgraph"; import { z } from "zod"; const humanAssistance = tool(async ({ query }) => { const humanResponse = interrupt({ query }); return humanResponse.data; }, { name: "humanAssistance", description: "Request assistance from a human.", schema: z.object({ query: z.string().describe("Human readable question for the human") }) }); const tools = [getWeather, humanAssistance]; ``` ### Define tasks Our tasks are otherwise unchanged from the ReAct agent guide: 1. **Call model**: We want to query our chat model with a list of messages. 2. **Call tool**: If our model generates tool calls, we want to execute them. We just have one more tool accessible to the model. ```typescript import { type BaseMessageLike, AIMessage, ToolMessage, } from "@langchain/core/messages"; import { type ToolCall } from "@langchain/core/messages/tool"; import { task } from "@langchain/langgraph"; const toolsByName = Object.fromEntries(tools.map((tool) => [tool.name, tool])); const callModel = task("callModel", async (messages: BaseMessageLike[]) => { const response = await model.bindTools(tools).invoke(messages); return response; }); const callTool = task( "callTool", async (toolCall: ToolCall): Promise => { const tool = toolsByName[toolCall.name]; const observation = await tool.invoke(toolCall.args); return new ToolMessage({ content: observation, tool_call_id: toolCall.id }); // Can also pass toolCall directly into the tool to return a ToolMessage // return tool.invoke(toolCall); }); ``` ### Define entrypoint Our entrypoint is also unchanged from the ReAct agent guide: ```typescript import { entrypoint, addMessages, MemorySaver } from "@langchain/langgraph"; const agent = entrypoint({ name: "agent", checkpointer: new MemorySaver(), }, async (messages: BaseMessageLike[]) => { let currentMessages = messages; let llmResponse = await callModel(currentMessages); while (true) { if (!llmResponse.tool_calls?.length) { break; } // Execute tools const toolResults = await Promise.all( llmResponse.tool_calls.map((toolCall) => { return callTool(toolCall); }) ); // Append to message list currentMessages = addMessages(currentMessages, [llmResponse, ...toolResults]); // Call model again llmResponse = await callModel(currentMessages); } return llmResponse; }); ``` ### Usage Let's invoke our model with a question that requires human assistance. Our question will also require an invocation of the `getWeather` tool: ```typescript import { BaseMessage, isAIMessage } from "@langchain/core/messages"; const prettyPrintMessage = (message: BaseMessage) => { console.log("=".repeat(30), `${message.getType()} message`, "=".repeat(30)); console.log(message.content); if (isAIMessage(message) && message.tool_calls?.length) { console.log(JSON.stringify(message.tool_calls, null, 2)); } } const prettyPrintStep = (step: Record) => { if (step.__metadata__?.cached) { return; } for (const [taskName, update] of Object.entries(step)) { const message = update as BaseMessage; // Only print task updates if (taskName === "agent") continue; console.log(`\n${taskName}:`); if (taskName === "__interrupt__") { console.log(update); } else { prettyPrintMessage(message); } } } ``` ```typescript const userMessage = { role: "user", content: [ "Can you reach out for human assistance: what should I feed my cat?", "Separately, can you check the weather in San Francisco?" ].join(" "), }; console.log(userMessage); const agentStream = await agent.stream([userMessage], { configurable: { thread_id: "1", } }); let lastStep; for await (const step of agentStream) { prettyPrintStep(step); lastStep = step; } ``` ```output { role: 'user', content: 'Can you reach out for human assistance: what should I feed my cat? Separately, can you check the weather in San Francisco?' } callModel: ============================== ai message ============================== [ { "name": "humanAssistance", "args": { "query": "What should I feed my cat?" }, "type": "tool_call", "id": "call_TwrNq6tGI61cDCJEpj175h7J" }, { "name": "getWeather", "args": { "location": "San Francisco" }, "type": "tool_call", "id": "call_fMzUBvc0SpZpXxM2LQLXfbke" } ] callTool: ============================== tool message ============================== It's sunny! __interrupt__: [ { value: { query: 'What should I feed my cat?' }, when: 'during', resumable: true, ns: [ 'callTool:2e0c6c40-9541-57ef-a7af-24213a10d5a4' ] } ] ``` Note that we generate two tool calls, and although our run is interrupted, we did not block the execution of the `get_weather` tool. Let's inspect where we're interrupted: ```typescript console.log(JSON.stringify(lastStep)); ``` ```output {"__interrupt__":[{"value":{"query":"What should I feed my cat?"},"when":"during","resumable":true,"ns":["callTool:2e0c6c40-9541-57ef-a7af-24213a10d5a4"]}]} ``` We can resume execution by issuing a Command. Note that the data we supply in the `Command` can be customized to your needs based on the implementation of `humanAssistance`. ```typescript import { Command } from "@langchain/langgraph"; const humanResponse = "You should feed your cat a fish."; const humanCommand = new Command({ resume: { data: humanResponse }, }); const resumeStream2 = await agent.stream(humanCommand, config); for await (const step of resumeStream2) { prettyPrintStep(step); } ``` ```output callTool: ============================== tool message ============================== You should feed your cat a fish. callModel: ============================== ai message ============================== For your cat, it is suggested that you feed it fish. As for the weather in San Francisco, it's currently sunny! ``` Above, when we resume we provide the final tool message, allowing the model to generate its response. Check out the LangSmith traces to see a full breakdown of the runs: 1. [Trace from initial query](https://smith.langchain.com/public/c007b042-fdd3-49e7-acbe-cadf6969de4b/r) 2. [Trace after resuming](https://smith.langchain.com/public/1cea310a-13f5-4de9-ae1c-045b8b33015e/r) **Note:** The `interrupt` function propagates by throwing a special `GraphInterrupt` error. Therefore, you should avoid using `try/catch` blocks around the `interrupt` function - or if you do, ensure that the `GraphInterrupt` error is thrown again within your `catch` block. --- how-tos/branching.ipynb --- # How to create branches for parallel node execution LangGraph natively supports fan-out and fan-in using either regular edges or conditionalEdges. This lets you run nodes in parallel to speed up your total graph execution. Below are some examples showing how to add create branching dataflows that work for you. ## Setup First, install LangGraph.js ```bash yarn add @langchain/langgraph @langchain/core ``` This guide will use OpenAI's GPT-4o model. We will optionally set our API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..." // process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "Branching: LangGraphJS"; ``` ```output Branching: LangGraphJS ``` ## Fan out, fan in First, we will make a simple graph that branches out and back in. When merging back in, the state updates from all branches are applied by your **reducer** (the `aggregate` method below). ```typescript import { END, START, StateGraph, Annotation } from "@langchain/langgraph"; const StateAnnotation = Annotation.Root({ aggregate: Annotation({ reducer: (x, y) => x.concat(y), }) }); // Create the graph const nodeA = (state: typeof StateAnnotation.State) => { console.log(`Adding I'm A to ${state.aggregate}`); return { aggregate: [`I'm A`] }; }; const nodeB = (state: typeof StateAnnotation.State) => { console.log(`Adding I'm B to ${state.aggregate}`); return { aggregate: [`I'm B`] }; }; const nodeC = (state: typeof StateAnnotation.State) => { console.log(`Adding I'm C to ${state.aggregate}`); return { aggregate: [`I'm C`] }; }; const nodeD = (state: typeof StateAnnotation.State) => { console.log(`Adding I'm D to ${state.aggregate}`); return { aggregate: [`I'm D`] }; }; const builder = new StateGraph(StateAnnotation) .addNode("a", nodeA) .addEdge(START, "a") .addNode("b", nodeB) .addNode("c", nodeC) .addNode("d", nodeD) .addEdge("a", "b") .addEdge("a", "c") .addEdge("b", "d") .addEdge("c", "d") .addEdge("d", END); const graph = builder.compile(); ``` ```typescript import * as tslab from "tslab"; const representation = graph.getGraph(); const image = await representation.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` ```typescript // Invoke the graph const baseResult = await graph.invoke({ aggregate: [] }); console.log("Base Result: ", baseResult); ``` ```output Adding I'm A to Adding I'm B to I'm A Adding I'm C to I'm A Adding I'm D to I'm A,I'm B,I'm C Base Result: { aggregate: [ "I'm A", "I'm B", "I'm C", "I'm D" ] } ``` ## Conditional Branching If your fan-out is not deterministic, you can use addConditionalEdges directly like this: ```typescript const ConditionalBranchingAnnotation = Annotation.Root({ aggregate: Annotation({ reducer: (x, y) => x.concat(y), }), which: Annotation({ reducer: (x: string, y: string) => (y ?? x), }) }) // Create the graph const nodeA2 = (state: typeof ConditionalBranchingAnnotation.State) => { console.log(`Adding I'm A to ${state.aggregate}`); return { aggregate: [`I'm A`] }; }; const nodeB2 = (state: typeof ConditionalBranchingAnnotation.State) => { console.log(`Adding I'm B to ${state.aggregate}`); return { aggregate: [`I'm B`] }; }; const nodeC2 = (state: typeof ConditionalBranchingAnnotation.State) => { console.log(`Adding I'm C to ${state.aggregate}`); return { aggregate: [`I'm C`] }; }; const nodeD2 = (state: typeof ConditionalBranchingAnnotation.State) => { console.log(`Adding I'm D to ${state.aggregate}`); return { aggregate: [`I'm D`] }; }; const nodeE2 = (state: typeof ConditionalBranchingAnnotation.State) => { console.log(`Adding I'm E to ${state.aggregate}`); return { aggregate: [`I'm E`] }; }; // Define the route function function routeCDorBC(state: typeof ConditionalBranchingAnnotation.State): string[] { if (state.which === "cd") { return ["c", "d"]; } return ["b", "c"]; } const builder2 = new StateGraph(ConditionalBranchingAnnotation) .addNode("a", nodeA2) .addEdge(START, "a") .addNode("b", nodeB2) .addNode("c", nodeC2) .addNode("d", nodeD2) .addNode("e", nodeE2) // Add conditional edges // Third parameter is to support visualizing the graph .addConditionalEdges("a", routeCDorBC, ["b", "c", "d"]) .addEdge("b", "e") .addEdge("c", "e") .addEdge("d", "e") .addEdge("e", END); const graph2 = builder2.compile(); ``` ```typescript import * as tslab from "tslab"; const representation2 = graph2.getGraph(); const image2 = await representation2.drawMermaidPng(); const arrayBuffer2 = await image2.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer2)); ``` ```typescript // Invoke the graph let g2result = await graph2.invoke({ aggregate: [], which: "bc" }); console.log("Result 1: ", g2result); ``` ```output Adding I'm A to Adding I'm B to I'm A Adding I'm C to I'm A Adding I'm E to I'm A,I'm B,I'm C Result 1: { aggregate: [ "I'm A", "I'm B", "I'm C", "I'm E" ], which: 'bc' } ``` ```typescript g2result = await graph2.invoke({ aggregate: [], which: "cd" }); console.log("Result 2: ", g2result); ``` ```output Adding I'm A to Adding I'm C to I'm A Adding I'm D to I'm A Adding I'm E to I'm A,I'm C,I'm D ``````output Result 2: { aggregate: [ "I'm A", "I'm C", "I'm D", "I'm E" ], which: 'cd' } ``` ## Stable Sorting When fanned out, nodes are run in parallel as a single "superstep". The updates from each superstep are all applied to the state in sequence once the superstep has completed. If you need consistent, predetermined ordering of updates from a parallel superstep, you should write the outputs (along with an identifying key) to a separate field in your state, then combine them in the "sink" node by adding regular `edge`s from each of the fanout nodes to the rendezvous point. For instance, suppose I want to order the outputs of the parallel step by "reliability". ```typescript type ScoredValue = { value: string; score: number; }; const reduceFanouts = (left?: ScoredValue[], right?: ScoredValue[]) => { if (!left) { left = []; } if (!right || right?.length === 0) { // Overwrite. Similar to redux. return []; } return left.concat(right); }; const StableSortingAnnotation = Annotation.Root({ aggregate: Annotation({ reducer: (x, y) => x.concat(y), }), which: Annotation({ reducer: (x: string, y: string) => (y ?? x), }), fanoutValues: Annotation({ reducer: reduceFanouts, }), }) class ParallelReturnNodeValue { private _value: string; private _score: number; constructor(nodeSecret: string, score: number) { this._value = nodeSecret; this._score = score; } public call(state: typeof StableSortingAnnotation.State) { console.log(`Adding ${this._value} to ${state.aggregate}`); return { fanoutValues: [{ value: this._value, score: this._score }] }; } } // Create the graph const nodeA3 = (state: typeof StableSortingAnnotation.State) => { console.log(`Adding I'm A to ${state.aggregate}`); return { aggregate: ["I'm A"] }; }; const nodeB3 = new ParallelReturnNodeValue("I'm B", 0.1); const nodeC3 = new ParallelReturnNodeValue("I'm C", 0.9); const nodeD3 = new ParallelReturnNodeValue("I'm D", 0.3); const aggregateFanouts = (state: typeof StableSortingAnnotation.State) => { // Sort by score (reversed) state.fanoutValues.sort((a, b) => b.score - a.score); return { aggregate: state.fanoutValues.map((v) => v.value).concat(["I'm E"]), fanoutValues: [], }; }; // Define the route function function routeBCOrCD(state: typeof StableSortingAnnotation.State): string[] { if (state.which === "cd") { return ["c", "d"]; } return ["b", "c"]; } const builder3 = new StateGraph(StableSortingAnnotation) .addNode("a", nodeA3) .addEdge(START, "a") .addNode("b", nodeB3.call.bind(nodeB3)) .addNode("c", nodeC3.call.bind(nodeC3)) .addNode("d", nodeD3.call.bind(nodeD3)) .addNode("e", aggregateFanouts) .addConditionalEdges("a", routeBCOrCD, ["b", "c", "d"]) .addEdge("b", "e") .addEdge("c", "e") .addEdge("d", "e") .addEdge("e", END); const graph3 = builder3.compile(); // Invoke the graph let g3result = await graph3.invoke({ aggregate: [], which: "bc" }); console.log("Result 1: ", g3result); ``` ```output Adding I'm A to Adding I'm B to I'm A Adding I'm C to I'm A Result 1: { aggregate: [ "I'm A", "I'm C", "I'm B", "I'm E" ], which: 'bc', fanoutValues: [] } ``` Our aggregateFanouts "sink" node in this case took the mapped values and then sorted them in a consistent way. Notice that, because it returns an empty array for `fanoutValues`, our `reduceFanouts` reducer function decided to overwrite the previous values in the state. ```typescript let g3result2 = await graph3.invoke({ aggregate: [], which: "cd" }); console.log("Result 2: ", g3result2); ``` ```output Adding I'm A to Adding I'm C to I'm A Adding I'm D to I'm A Result 2: { aggregate: [ "I'm A", "I'm C", "I'm D", "I'm E" ], which: 'cd', fanoutValues: [] } ``` --- how-tos/multi-agent-multi-turn-convo-functional.ipynb --- # How to add multi-turn conversation in a multi-agent application (functional API) !!! info "Prerequisites" This guide assumes familiarity with the following: - Multi-agent systems - Human-in-the-loop - Functional API - Command - LangGraph Glossary In this how-to guide, we’ll build an application that allows an end-user to engage in a *multi-turn conversation* with one or more agents. We'll create a node that uses an [`interrupt`](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.interrupt-1.html) to collect user input and routes back to the **active** agent. The agents will be implemented as tasks in a workflow that executes agent steps and determines the next action: 1. **Wait for user input** to continue the conversation, or 2. **Route to another agent** (or back to itself, such as in a loop) via a **handoff**. !!! note Compatibility This guide requires `@langchain/langgraph>=0.2.42` and `@langchain/core>=0.3.36`. ## Setup First, install the required dependencies for this example: ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core uuid zod ``` Next, we need to set API keys for Anthropic (the LLM we will use): ```typescript process.env.ANTHROPIC_API_KEY = "YOUR_API_KEY"; ``` !!! tip "Set up [LangSmith](https://smith.langchain.com) for LangGraph development" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com) In this example we will build a team of travel assistant agents that can communicate with each other. We will create 2 agents: * `travelAdvisor`: can help with travel destination recommendations. Can ask `hotelAdvisor` for help. * `hotelAdvisor`: can help with hotel recommendations. Can ask `travelAdvisor` for help. This is a fully-connected network - every agent can talk to any other agent. ```typescript import { tool } from "@langchain/core/tools"; import { z } from "zod"; // Tool for getting travel recommendations const getTravelRecommendations = tool(async () => { const destinations = ["aruba", "turks and caicos"]; return destinations[Math.floor(Math.random() * destinations.length)]; }, { name: "getTravelRecommendations", description: "Get recommendation for travel destinations", schema: z.object({}), }); // Tool for getting hotel recommendations const getHotelRecommendations = tool(async (input: { location: "aruba" | "turks and caicos" }) => { const recommendations = { "aruba": [ "The Ritz-Carlton, Aruba (Palm Beach)", "Bucuti & Tara Beach Resort (Eagle Beach)" ], "turks and caicos": ["Grace Bay Club", "COMO Parrot Cay"] }; return recommendations[input.location]; }, { name: "getHotelRecommendations", description: "Get hotel recommendations for a given destination.", schema: z.object({ location: z.enum(["aruba", "turks and caicos"]) }), }); // Define a tool to signal intent to hand off to a different agent // Note: this is not using Command(goto) syntax for navigating to different agents: // `workflow()` below handles the handoffs explicitly const transferToHotelAdvisor = tool(async () => { return "Successfully transferred to hotel advisor"; }, { name: "transferToHotelAdvisor", description: "Ask hotel advisor agent for help.", schema: z.object({}), // Hint to our agent implementation that it should stop // immediately after invoking this tool returnDirect: true, }); const transferToTravelAdvisor = tool(async () => { return "Successfully transferred to travel advisor"; }, { name: "transferToTravelAdvisor", description: "Ask travel advisor agent for help.", schema: z.object({}), // Hint to our agent implementation that it should stop // immediately after invoking this tool returnDirect: true, }); ``` !!! note "Transfer tools" You might have noticed that we're using `tool(... { returnDirect: true })` in the transfer tools. This is done so that individual agents (e.g., `travelAdvisor`) can exit the ReAct loop early once these tools are called without calling the model a final time to process the result of the tool call. This is the desired behavior, as we want to detect when the agent calls this tool and hand control off _immediately_ to a different agent. **NOTE**: This is meant to work with the prebuilt `createReactAgent` - if you are building a custom agent, make sure to manually add logic for handling early exit for tools that are marked with `returnDirect`. Let's now create our agents using the the prebuilt `createReactAgent` and our multi-agent workflow. Note that will be calling `interrupt` every time after we get the final response from each of the agents. ```typescript import { AIMessage, type BaseMessage, type BaseMessageLike } from "@langchain/core/messages"; import { ChatAnthropic } from "@langchain/anthropic"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { addMessages, entrypoint, task, MemorySaver, interrupt, } from "@langchain/langgraph"; const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest", }); const travelAdvisorTools = [ getTravelRecommendations, transferToHotelAdvisor, ]; // Define travel advisor ReAct agent const travelAdvisor = createReactAgent({ llm: model, tools: travelAdvisorTools, stateModifier: [ "You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc).", "If you need hotel recommendations, ask 'hotel_advisor' for help.", "You MUST include human-readable response before transferring to another agent.", ].join(" "), }); // You can also add additional logic like changing the input to the agent / output from the agent, etc. // NOTE: we're invoking the ReAct agent with the full history of messages in the state const callTravelAdvisor = task("callTravelAdvisor", async (messages: BaseMessageLike[]) => { const response = await travelAdvisor.invoke({ messages }); return response.messages; }); const hotelAdvisorTools = [ getHotelRecommendations, transferToTravelAdvisor, ]; // Define hotel advisor ReAct agent const hotelAdvisor = createReactAgent({ llm: model, tools: hotelAdvisorTools, stateModifier: [ "You are a hotel expert that can provide hotel recommendations for a given destination.", "If you need help picking travel destinations, ask 'travel_advisor' for help.", "You MUST include a human-readable response before transferring to another agent." ].join(" "), }); // Add task for hotel advisor const callHotelAdvisor = task("callHotelAdvisor", async (messages: BaseMessageLike[]) => { const response = await hotelAdvisor.invoke({ messages }); return response.messages; }); const checkpointer = new MemorySaver(); const multiTurnGraph = entrypoint({ name: "multiTurnGraph", checkpointer, }, async (messages: BaseMessageLike[]) => { let callActiveAgent = callTravelAdvisor; let agentMessages: BaseMessage[]; let currentMessages = messages; while (true) { agentMessages = await callActiveAgent(currentMessages); // Find the last AI message // If one of the handoff tools is called, the last message returned // by the agent will be a ToolMessages because we set them to have // "returnDirect: true". This means that the last AIMessage will // have tool calls. // Otherwise, the last returned message will be an AIMessage with // no tool calls, which means we are ready for new input. const reversedMessages = [...agentMessages].reverse(); const aiMsgIndex = reversedMessages .findIndex((m): m is AIMessage => m.getType() === "ai"); const aiMsg: AIMessage = reversedMessages[aiMsgIndex]; // We append all messages up to the last AI message to the current messages. // This may include ToolMessages (if the handoff tool was called) const messagesToAdd = reversedMessages.slice(0, aiMsgIndex + 1).reverse(); // Add the agent's responses currentMessages = addMessages(currentMessages, messagesToAdd); if (!aiMsg?.tool_calls?.length) { const userInput = await interrupt("Ready for user input."); if (typeof userInput !== "string") { throw new Error("User input must be a string."); } if (userInput.toLowerCase() === "done") { break; } currentMessages = addMessages(currentMessages, [{ role: "human", content: userInput, }]); continue; } const toolCall = aiMsg.tool_calls.at(-1)!; if (toolCall.name === "transferToHotelAdvisor") { callActiveAgent = callHotelAdvisor; } else if (toolCall.name === "transferToTravelAdvisor") { callActiveAgent = callTravelAdvisor; } else { throw new Error(`Expected transfer tool, got '${toolCall.name}'`); } } return entrypoint.final({ value: agentMessages[agentMessages.length - 1], save: currentMessages, }); }); ``` We use a while loop to enable continuous conversation between agents and the user. The loop allows for: 1. Getting agent responses 2. Handling agent-to-agent transfers 3. Collecting user input via interrupts 4. Resuming using special inputs (see `Command` below) ## Test multi-turn conversation Let's test a multi turn conversation with this application. ```typescript import { v4 as uuidv4 } from 'uuid'; import { Command } from "@langchain/langgraph"; import { isBaseMessage } from "@langchain/core/messages"; const threadConfig = { configurable: { thread_id: uuidv4() }, streamMode: "updates" as const, }; const inputs = [ // 1st round of conversation [{ role: "user", content: "i wanna go somewhere warm in the caribbean" }], // Since we're using `interrupt`, we'll need to resume using the Command primitive // 2nd round of conversation new Command({ resume: "could you recommend a nice hotel in one of the areas and tell me which area it is." }), // 3rd round of conversation new Command({ resume: "i like the first one. could you recommend something to do near the hotel?" }) ]; const runConversation = async () => { for (const [idx, userInput] of inputs.entries()) { console.log(); console.log(`--- Conversation Turn ${idx + 1} ---`); console.log(); console.log(`User: ${JSON.stringify(userInput, null, 2)}`); console.log(); const stream = await multiTurnGraph.stream( userInput as any, threadConfig, ); for await (const update of stream) { if (update.__metadata__?.cached) { continue; } for (const [nodeId, value] of Object.entries(update)) { if (Array.isArray(value) && value.length > 0) { const lastMessage = value.at(-1); if (isBaseMessage(lastMessage) && lastMessage?.getType() === "ai") { console.log(`${nodeId}: ${lastMessage.content}`); } } } } } }; // Execute the conversation try { await runConversation(); } catch (e) { console.error(e); } ``` ```output --- Conversation Turn 1 --- User: [ { "role": "user", "content": "i wanna go somewhere warm in the caribbean" } ] callTravelAdvisor: Based on the recommendations, Turks and Caicos would be an excellent choice for your Caribbean getaway! This British Overseas Territory is known for its stunning white-sand beaches, crystal-clear turquoise waters, and year-round warm weather. Grace Bay Beach in Providenciales is consistently rated as one of the world's best beaches. You can enjoy: - World-class snorkeling and diving - Luxury resorts and spas - Fresh seafood cuisine - Water sports like kayaking and paddleboarding - Beautiful coral reefs - Average temperatures between 75-85°F (24-29°C) year-round Would you like me to connect you with our hotel advisor to help you find the perfect place to stay in Turks and Caicos? --- Conversation Turn 2 --- User: { "resume": "could you recommend a nice hotel in one of the areas and tell me which area it is.", "goto": [] } callHotelAdvisor: I can recommend two excellent options in Turks and Caicos: 1. Grace Bay Club - This luxury resort is located on the world-famous Grace Bay Beach in Providenciales (often called "Provo"). This area is the most developed and popular island in Turks and Caicos, known for its 12-mile stretch of pristine beach, excellent restaurants, and shopping. The resort offers all-oceanfront suites and is perfect if you want to be close to amenities while enjoying luxury beachfront accommodations. 2. COMO Parrot Cay - This is an exclusive private island resort located on Parrot Cay, a secluded island accessible by boat from Providenciales. This is the ultimate luxury escape if you're looking for privacy and seclusion. The resort is set on 1,000 unspoiled acres with pristine white beaches. This location is perfect for those who want to truly get away from it all while enjoying world-class service and amenities. Would you like more specific information about either of these properties or their locations? --- Conversation Turn 3 --- User: { "resume": "i like the first one. could you recommend something to do near the hotel?", "goto": [] } callHotelAdvisor: Grace Bay Club is perfectly situated to enjoy many activities in Providenciales! Since the hotel is located on Grace Bay Beach in Provo, here are some excellent activities nearby: 1. Beach Activities (right at your doorstep): - Swimming and sunbathing on Grace Bay Beach - Snorkeling right off the beach - Beach walks along the pristine 12-mile stretch 2. Within Walking Distance: - Salt Mills Plaza (shopping center with local boutiques and restaurants) - Graceway Gourmet (upscale grocery store) - Several beachfront restaurants and bars 3. Very Close By (5-10 minute drive): - Princess Alexandra National Park (great for snorkeling) - Leeward Marina (for boat tours and fishing trips) - Provo Golf Club (18-hole championship golf course) - Thursday Night Fish Fry at Bight Park (local culture and food) 4. Water Activities (operators will pick you up): - Snorkeling or diving trips to the barrier reef - Sunset sailing cruises - Half-day trips to Iguana Island - Whale watching (in season - January to April) Would you like me to connect you with our travel advisor for more specific activity recommendations or help with booking any excursions? ``` ```typescript ``` --- how-tos/respond-in-format.ipynb --- # How to have agent respond in structured format The typical ReAct agent prompts the LLM to respond in 1 of two formats: a function call (~ JSON) to use a tool, or conversational text to respond to the user. If your agent is connected to a structured (or even generative) UI, or if it is communicating with another agent or software process, you may want it to resopnd in a specific structured format. In this example we will build a conversational ReAct agent that responds in a specific format. We will do this by using [tool calling](https://js.langchain.com/docs/modules/model_io/models/chat/function-calling/). This is useful when you want to enforce that an agent's response is in a specific format. In this example, we will ask it respond as if it were a weatherman, returning the temperature and additional info in separate, machine-readable fields. ## Setup First we need to install the packages required ```bash yarn add langchain @langchain/anthropic @langchain/langgraph @langchain/core ``` Next, we need to set API keys for OpenAI (the LLM we will use). ```typescript // process.env.OPENAI_API_KEY = "sk_..."; ``` Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.LANGCHAIN_API_KEY = "ls..."; process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "Respond in Format: LangGraphJS"; ``` ```output Respond in Format: LangGraphJS ``` ## Set up the State ```typescript import { Annotation, messagesStateReducer } from "@langchain/langgraph"; import { BaseMessage } from "@langchain/core/messages"; const GraphState = Annotation.Root({ messages: Annotation({ reducer: messagesStateReducer, }), }); ``` ## Set up the tools ```typescript import { tool } from "@langchain/core/tools"; import { z } from "zod"; const searchTool = tool((_) => { // This is a placeholder, but don't tell the LLM that... return "67 degrees. Cloudy with a chance of rain."; }, { name: "search", description: "Call to surf the web.", schema: z.object({ query: z.string().describe("The query to use in your search."), }), }); const tools = [searchTool]; ``` We can now wrap these tools in a ToolNode. ```typescript import { ToolNode } from "@langchain/langgraph/prebuilt"; const toolNode = new ToolNode(tools); ``` ## Set up the model ```typescript import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ temperature: 0, model: "gpt-4o", }); ``` After we've done this, we should make sure the model knows that it has these tools available to call. We can do this by binding the LangChain tools to the model class. We also want to define a response schema for the language model and bind it to the model as a tool. The idea is that when the model is ready to respond, it'll call this final tool and populate arguments for it according to the schema we want. Rather than calling a tool, we'll instead return from the graph. Because we only intend to use this final tool to guide the schema of the model's final response, we'll declare it with a mocked out function: ```typescript import { tool } from "@langchain/core/tools"; const Response = z.object({ temperature: z.number().describe("the temperature"), other_notes: z.string().describe("any other notes about the weather"), }); const finalResponseTool = tool(async () => "mocked value", { name: "Response", description: "Always respond to the user using this tool.", schema: Response }) const boundModel = model.bindTools([ ...tools, finalResponseTool ]); ``` ## Define the nodes ```typescript import { AIMessage } from "@langchain/core/messages"; import { RunnableConfig } from "@langchain/core/runnables"; // Define the function that determines whether to continue or not const route = (state: typeof GraphState.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as AIMessage; // If there is no function call, then we finish if (!lastMessage.tool_calls || lastMessage.tool_calls.length === 0) { return "__end__"; } // Otherwise if there is, we need to check what type of function call it is if (lastMessage.tool_calls[0].name === "Response") { return "__end__"; } // Otherwise we continue return "tools"; }; // Define the function that calls the model const callModel = async ( state: typeof GraphState.State, config?: RunnableConfig, ) => { const { messages } = state; const response = await boundModel.invoke(messages, config); // We return an object, because this will get added to the existing list return { messages: [response] }; }; ``` ## Define the graph ```typescript import { StateGraph } from "@langchain/langgraph"; // Define a new graph const workflow = new StateGraph(GraphState) .addNode("agent", callModel) .addNode("tools", toolNode) .addEdge("__start__", "agent") .addConditionalEdges( // First, we define the start node. We use `agent`. // This means these are the edges taken after the `agent` node is called. "agent", // Next, we pass in the function that will determine which node is called next. route, // We supply a map of possible response values to the conditional edge // to make it possible to draw a visualization of the graph. { __end__: "__end__", tools: "tools", } ) // We now add a normal edge from `tools` to `agent`. // This means that after `tools` is called, `agent` node is called next. .addEdge("tools", "agent"); // Finally, we compile it! // This compiles it into a LangChain Runnable, // meaning you can use it as you would any other runnable const app = workflow.compile(); ``` ```typescript import * as tslab from "tslab"; const graph = app.getGraph(); const image = await graph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` ## Use it! We can now use it! This now exposes the [same interface](https://v02.api.js.langchain.com/classes/langchain_core_runnables.Runnable.html) as all other LangChain runnables. ```typescript import { HumanMessage, isAIMessage } from "@langchain/core/messages"; const prettyPrint = (message: BaseMessage) => { let txt = `[${message._getType()}]: ${message.content}`; if ( isAIMessage(message) && message?.tool_calls?.length ) { const tool_calls = message?.tool_calls ?.map((tc) => `- ${tc.name}(${JSON.stringify(tc.args)})`) .join("\n"); txt += ` \nTools: \n${tool_calls}`; } console.log(txt); }; const inputs = { messages: [new HumanMessage("what is the weather in sf")], }; const stream = await app.stream(inputs, { streamMode: "values" }); for await (const output of stream) { const { messages } = output; prettyPrint(messages[messages.length - 1]); console.log("\n---\n"); } ``` ```output [human]: what is the weather in sf --- [ai]: Tools: - search({"query":"current weather in San Francisco"}) --- [tool]: 67 degrees. Cloudy with a chance of rain. --- [ai]: Tools: - Response({"temperature":67,"other_notes":"Cloudy with a chance of rain."}) --- ``` ## Partially streaming JSON If we want to stream the structured output as soon as it's available, we can use the [`.streamEvents()`](https://js.langchain.com/docs/how_to/streaming#using-stream-events) method. We'll aggregate emitted `on_chat_model_events` and inspect the name field. As soon as we detect that the model is calling the final output tool, we can start logging the relevant chunks. Here's an example: ```typescript import { concat } from "@langchain/core/utils/stream"; const eventStream = await app.streamEvents(inputs, { version: "v2" }); let aggregatedChunk; for await (const { event, data } of eventStream) { if (event === "on_chat_model_stream") { const { chunk } = data; if (aggregatedChunk === undefined) { aggregatedChunk = chunk; } else { aggregatedChunk = concat(aggregatedChunk, chunk); } const currentToolCalls = aggregatedChunk.tool_calls; if ( currentToolCalls.length === 0 || currentToolCalls[0].name === "" || !finalResponseTool.name.startsWith(currentToolCalls[0].name) ) { // No tool calls or a different tool call in the message, // so drop what's currently aggregated and start over aggregatedChunk = undefined; } else if (currentToolCalls[0].name === finalResponseTool.name) { // Now we're sure that this event is part of the final output! // Log the partially aggregated args. console.log(aggregatedChunk.tool_call_chunks[0].args); // You can also log the raw args instead: // console.log(chunk.tool_call_chunks); console.log("---"); } } } // Final aggregated tool call console.log(aggregatedChunk.tool_calls); ``` ```output --- {" --- {"temperature --- {"temperature": --- {"temperature":67 --- {"temperature":67," --- {"temperature":67,"other --- {"temperature":67,"other_notes --- {"temperature":67,"other_notes":" --- {"temperature":67,"other_notes":"Cloud --- {"temperature":67,"other_notes":"Cloudy --- {"temperature":67,"other_notes":"Cloudy with --- {"temperature":67,"other_notes":"Cloudy with a --- {"temperature":67,"other_notes":"Cloudy with a chance --- {"temperature":67,"other_notes":"Cloudy with a chance of --- {"temperature":67,"other_notes":"Cloudy with a chance of rain --- {"temperature":67,"other_notes":"Cloudy with a chance of rain." --- {"temperature":67,"other_notes":"Cloudy with a chance of rain."} --- {"temperature":67,"other_notes":"Cloudy with a chance of rain."} --- [ { name: 'Response', args: { temperature: 67, other_notes: 'Cloudy with a chance of rain.' }, id: 'call_oOhNx2SdeelXn6tbenokDtkO', type: 'tool_call' } ] ``` --- how-tos/map-reduce.ipynb --- # How to create map-reduce branches for parallel execution [Map-reduce](https://en.wikipedia.org/wiki/MapReduce) operations are essential for efficient task decomposition and parallel processing. This approach involves breaking a task into smaller sub-tasks, processing each sub-task in parallel, and aggregating the results across all of the completed sub-tasks. Consider this example: given a general topic from the user, generate a list of related subjects, generate a joke for each subject, and select the best joke from the resulting list. In this design pattern, a first node may generate a list of objects (e.g., related subjects) and we want to apply some other node (e.g., generate a joke) to all those objects (e.g., subjects). However, two main challenges arise. (1) the number of objects (e.g., subjects) may be unknown ahead of time (meaning the number of edges may not be known) when we lay out the graph and (2) the input State to the downstream Node should be different (one for each generated object). LangGraph addresses these challenges through its `Send` API. By utilizing conditional edges, `Send` can distribute different states (e.g., subjects) to multiple instances of a node (e.g., joke generation). Importantly, the sent state can differ from the core graph's state, allowing for flexible and dynamic workflow management. ## Setup This example will require a few dependencies. First, install the LangGraph library, along with the `@langchain/anthropic` package as we'll be using Anthropic LLMs in this example: ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core ``` Next, set your Anthropic API key: ```typescript process.env.ANTHROPIC_API_KEY = 'YOUR_API_KEY' ``` ```typescript import { z } from "zod"; import { ChatAnthropic } from "@langchain/anthropic"; import { StateGraph, END, START, Annotation, Send } from "@langchain/langgraph"; /* Model and prompts */ // Define model and prompts we will use const subjectsPrompt = "Generate a comma separated list of between 2 and 5 examples related to: {topic}." const jokePrompt = "Generate a joke about {subject}" const bestJokePrompt = `Below are a bunch of jokes about {topic}. Select the best one! Return the ID (index) of the best one. {jokes}` // Zod schemas for getting structured output from the LLM const Subjects = z.object({ subjects: z.array(z.string()), }); const Joke = z.object({ joke: z.string(), }); const BestJoke = z.object({ id: z.number(), }); const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20240620", }); /* Graph components: define the components that will make up the graph */ // This will be the overall state of the main graph. // It will contain a topic (which we expect the user to provide) // and then will generate a list of subjects, and then a joke for // each subject const OverallState = Annotation.Root({ topic: Annotation, subjects: Annotation, // Notice here we pass a reducer function. // This is because we want combine all the jokes we generate // from individual nodes back into one list. jokes: Annotation({ reducer: (state, update) => state.concat(update), }), bestSelectedJoke: Annotation, }); // This will be the state of the node that we will "map" all // subjects to in order to generate a joke interface JokeState { subject: string; } // This is the function we will use to generate the subjects of the jokes const generateTopics = async ( state: typeof OverallState.State ): Promise> => { const prompt = subjectsPrompt.replace("topic", state.topic); const response = await model .withStructuredOutput(Subjects, { name: "subjects" }) .invoke(prompt); return { subjects: response.subjects }; }; // Function to generate a joke const generateJoke = async (state: JokeState): Promise<{ jokes: string[] }> => { const prompt = jokePrompt.replace("subject", state.subject); const response = await model .withStructuredOutput(Joke, { name: "joke" }) .invoke(prompt); return { jokes: [response.joke] }; }; // Here we define the logic to map out over the generated subjects // We will use this an edge in the graph const continueToJokes = (state: typeof OverallState.State) => { // We will return a list of `Send` objects // Each `Send` object consists of the name of a node in the graph // as well as the state to send to that node return state.subjects.map((subject) => new Send("generateJoke", { subject })); }; // Here we will judge the best joke const bestJoke = async ( state: typeof OverallState.State ): Promise> => { const jokes = state.jokes.join("\n\n"); const prompt = bestJokePrompt .replace("jokes", jokes) .replace("topic", state.topic); const response = await model .withStructuredOutput(BestJoke, { name: "best_joke" }) .invoke(prompt); return { bestSelectedJoke: state.jokes[response.id] }; }; // Construct the graph: here we put everything together to construct our graph const graph = new StateGraph(OverallState) .addNode("generateTopics", generateTopics) .addNode("generateJoke", generateJoke) .addNode("bestJoke", bestJoke) .addEdge(START, "generateTopics") .addConditionalEdges("generateTopics", continueToJokes) .addEdge("generateJoke", "bestJoke") .addEdge("bestJoke", END); const app = graph.compile(); ``` ```typescript import * as tslab from "tslab"; const representation = app.getGraph(); const image = await representation.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); tslab.display.png(new Uint8Array(arrayBuffer)); ``` ```typescript // Call the graph: here we call it to generate a list of jokes for await (const s of await app.stream({ topic: "animals" })) { console.log(s); } ``` ```output { generateTopics: { subjects: [ 'lion', 'elephant', 'penguin', 'dolphin' ] } } { generateJoke: { jokes: [ "Why don't lions like fast food? Because they can't catch it!" ] } } { generateJoke: { jokes: [ "Why don't elephants use computers? Because they're afraid of the mouse!" ] } } { generateJoke: { jokes: [ "Why don't dolphins use smartphones? They're afraid of phishing!" ] } } { generateJoke: { jokes: [ "Why don't you see penguins in Britain? Because they're afraid of Wales!" ] } } { bestJoke: { bestSelectedJoke: "Why don't elephants use computers? Because they're afraid of the mouse!" } } ``` --- how-tos/react-return-structured-output.ipynb --- # How to return structured output from the prebuilt ReAct agent !!! info "Prerequisites" - Agent Architectures - [Chat Models](https://js.langchain.com/docs/concepts/chat_models/) - [Tools](https://js.langchain.com/docs/concepts/tools/) - [Structured Output](https://js.langchain.com/docs/concepts/structured_outputs/) To return structured output from the prebuilt ReAct agent you can provide a `responseFormat` parameter with the desired output schema to [`createReactAgent`](https://langchain-ai.github.io/langgraphjs/reference/functions/prebuilt.createReactAgent.html): ```typescript import { z } from "zod"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; const responseFormat = z.object({ // Respond to the user in this format mySpecialOutput: z.string(), }) const graph = createReactAgent({ llm: llm, tools: tools, // specify the schema for the structured output using `responseFormat` parameter responseFormat: responseFormat }) ``` The agent will return the output in the format specified by the `responseFormat` schema by making an additional LLM call at the end of the conversation, once there are no more tool calls to be made. You can read this guide to learn about an alternate way - treating the structured output as another tool - to achieve structured output from the agent. ## Setup First, we need to install the required packages. ```bash yarn add @langchain/langgraph @langchain/openai @langchain/core zod ``` This guide will use OpenAI's GPT-4o model. We will optionally set our API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGSMITH_API_KEY = "ls__..." process.env.LANGSMITH_TRACING = "true"; process.env.LANGSMITH_PROJECT = "ReAct Agent with system prompt: LangGraphJS"; ``` ## Code ```typescript import { ChatOpenAI } from "@langchain/openai"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { tool } from "@langchain/core/tools"; import { z } from "zod"; const weatherTool = tool( async (input): Promise => { if (input.city === "nyc") { return "It might be cloudy in nyc"; } else if (input.city === "sf") { return "It's always sunny in sf"; } else { throw new Error("Unknown city"); } }, { name: "get_weather", description: "Use this to get weather information.", schema: z.object({ city: z.enum(["nyc", "sf"]).describe("The city to get weather for"), }), } ); const WeatherResponseSchema = z.object({ conditions: z.string().describe("Weather conditions"), }); const tools = [weatherTool]; const agent = createReactAgent({ llm: new ChatOpenAI({ model: "gpt-4o", temperature: 0 }), tools: tools, responseFormat: WeatherResponseSchema, }); ``` ## Usage Let's now test our agent: ```typescript const response = await agent.invoke({ messages: [ { role: "user", content: "What's the weather in NYC?", }, ], }) ``` You can see that the agent output contains a `structuredResponse` key with the structured output conforming to the specified `WeatherResponse` schema, in addition to the message history under `messages` key ```typescript response.structuredResponse ``` ```output { conditions: 'cloudy' } ``` ### Customizing system prompt You might need to further customize the second LLM call for the structured output generation and provide a system prompt. To do so, you can pass an object with the keys `prompt`, `schema` to the `responseFormat` parameter: ```typescript const agent = createReactAgent({ llm: new ChatOpenAI({ model: "gpt-4o", temperature: 0 }), tools: tools, responseFormat: { prompt: "Always return capitalized weather conditions", schema: WeatherResponseSchema, } }); const response = await agent.invoke({ messages: [ { role: "user", content: "What's the weather in NYC?", }, ], }) ``` You can verify that the structured response now contains a capitalized value: ```typescript response.structuredResponse ``` ```output { conditions: 'Cloudy' } ``` --- how-tos/streaming-tokens-without-langchain.ipynb --- # How to stream LLM tokens (without LangChain models) In this guide, we will stream tokens from the language model powering an agent without using LangChain chat models. We'll be using the OpenAI client library directly in a ReAct agent as an example. ## Setup To get started, install the `openai` and `langgraph` packages separately: ```bash $ npm install openai @langchain/langgraph @langchain/core ```

Compatibility

This guide requires @langchain/core>=0.2.19, and if you are using LangSmith, langsmith>=0.1.39. For help upgrading, see this guide.

You'll also need to make sure you have your OpenAI key set as `process.env.OPENAI_API_KEY`. ## Defining a model and a tool schema First, initialize the OpenAI SDK and define a tool schema for the model to populate using [OpenAI's format](https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools): ```typescript import OpenAI from "openai"; const openaiClient = new OpenAI({}); const toolSchema: OpenAI.ChatCompletionTool = { type: "function", function: { name: "get_items", description: "Use this tool to look up which items are in the given place.", parameters: { type: "object", properties: { place: { type: "string", }, }, required: ["place"], } } }; ``` ## Calling the model Now, define a method for a LangGraph node that will call the model. It will handle formatting tool calls to and from the model, as well as streaming via [custom callback events](https://js.langchain.com/docs/how_to/callbacks_custom_events). If you are using [LangSmith](https://docs.smith.langchain.com/), you can also wrap the OpenAI client for the same nice tracing you'd get with a LangChain chat model. Here's what that looks like: ```typescript import { dispatchCustomEvent } from "@langchain/core/callbacks/dispatch"; import { wrapOpenAI } from "langsmith/wrappers/openai"; import { Annotation } from "@langchain/langgraph"; const StateAnnotation = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); // If using LangSmith, use "wrapOpenAI" on the whole client or // "traceable" to wrap a single method for nicer tracing: // https://docs.smith.langchain.com/how_to_guides/tracing/annotate_code const wrappedClient = wrapOpenAI(openaiClient); const callModel = async (state: typeof StateAnnotation.State) => { const { messages } = state; const stream = await wrappedClient.chat.completions.create({ messages, model: "gpt-4o-mini", tools: [toolSchema], stream: true, }); let responseContent = ""; let role: string = "assistant"; let toolCallId: string | undefined; let toolCallName: string | undefined; let toolCallArgs = ""; for await (const chunk of stream) { const delta = chunk.choices[0].delta; if (delta.role !== undefined) { role = delta.role; } if (delta.content) { responseContent += delta.content; await dispatchCustomEvent("streamed_token", { content: delta.content, }); } if (delta.tool_calls !== undefined && delta.tool_calls.length > 0) { // note: for simplicity we're only handling a single tool call here const toolCall = delta.tool_calls[0]; if (toolCall.function?.name !== undefined) { toolCallName = toolCall.function.name; } if (toolCall.id !== undefined) { toolCallId = toolCall.id; } await dispatchCustomEvent("streamed_tool_call_chunk", toolCall); toolCallArgs += toolCall.function?.arguments ?? ""; } } let finalToolCalls; if (toolCallName !== undefined && toolCallId !== undefined) { finalToolCalls = [{ id: toolCallId, function: { name: toolCallName, arguments: toolCallArgs }, type: "function" as const, }]; } const responseMessage = { role: role as any, content: responseContent, tool_calls: finalToolCalls, }; return { messages: [responseMessage] }; } ``` Note that you can't call this method outside of a LangGraph node since `dispatchCustomEvent` will fail if it is called outside the proper context. ## Define tools and a tool-calling node Next, set up the actual tool function and the node that will call it when the model populates a tool call: ```typescript const getItems = async ({ place }: { place: string }) => { if (place.toLowerCase().includes("bed")) { // For under the bed return "socks, shoes and dust bunnies"; } else if (place.toLowerCase().includes("shelf")) { // For 'shelf' return "books, pencils and pictures"; } else { // if the agent decides to ask about a different place return "cat snacks"; } }; const callTools = async (state: typeof StateAnnotation.State) => { const { messages } = state; const mostRecentMessage = messages[messages.length - 1]; const toolCalls = (mostRecentMessage as OpenAI.ChatCompletionAssistantMessageParam).tool_calls; if (toolCalls === undefined || toolCalls.length === 0) { throw new Error("No tool calls passed to node."); } const toolNameMap = { get_items: getItems, }; const functionName = toolCalls[0].function.name; const functionArguments = JSON.parse(toolCalls[0].function.arguments); const response = await toolNameMap[functionName](functionArguments); const toolMessage = { tool_call_id: toolCalls[0].id, role: "tool" as const, name: functionName, content: response, } return { messages: [toolMessage] }; } ``` ## Build the graph Finally, it's time to build your graph: ```typescript import { StateGraph } from "@langchain/langgraph"; import OpenAI from "openai"; // We can reuse the same `GraphState` from above as it has not changed. const shouldContinue = (state: typeof StateAnnotation.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as OpenAI.ChatCompletionAssistantMessageParam; if (lastMessage?.tool_calls !== undefined && lastMessage?.tool_calls.length > 0) { return "tools"; } return "__end__"; } const graph = new StateGraph(StateAnnotation) .addNode("model", callModel) .addNode("tools", callTools) .addEdge("__start__", "model") .addConditionalEdges("model", shouldContinue, { tools: "tools", __end__: "__end__", }) .addEdge("tools", "model") .compile(); ``` ```typescript import * as tslab from "tslab"; const representation = graph.getGraph(); const image = await representation.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` ## Streaming tokens And now we can use the [`.streamEvents`](https://js.langchain.com/docs/how_to/streaming#using-stream-events) method to get the streamed tokens and tool calls from the OpenAI model: ```typescript const eventStream = await graph.streamEvents( { messages: [{ role: "user", content: "what's in the bedroom?" }] }, { version: "v2" }, ); for await (const { event, name, data } of eventStream) { if (event === "on_custom_event") { console.log(name, data); } } ``` ```output streamed_tool_call_chunk { index: 0, id: 'call_v99reml4gZvvUypPgOpLgxM2', type: 'function', function: { name: 'get_items', arguments: '' } } streamed_tool_call_chunk { index: 0, function: { arguments: '{"' } } streamed_tool_call_chunk { index: 0, function: { arguments: 'place' } } streamed_tool_call_chunk { index: 0, function: { arguments: '":"' } } streamed_tool_call_chunk { index: 0, function: { arguments: 'bed' } } streamed_tool_call_chunk { index: 0, function: { arguments: 'room' } } streamed_tool_call_chunk { index: 0, function: { arguments: '"}' } } streamed_token { content: 'In' } streamed_token { content: ' the' } streamed_token { content: ' bedroom' } streamed_token { content: ',' } streamed_token { content: ' you' } streamed_token { content: ' can' } streamed_token { content: ' find' } streamed_token { content: ' socks' } streamed_token { content: ',' } streamed_token { content: ' shoes' } streamed_token { content: ',' } streamed_token { content: ' and' } streamed_token { content: ' dust' } streamed_token { content: ' b' } streamed_token { content: 'unnies' } streamed_token { content: '.' } ``` And if you've set up LangSmith tracing, you'll also see [a trace like this one](https://smith.langchain.com/public/ddb1af36-ebe5-4ba6-9a57-87a296dc801f/r). --- how-tos/edit-graph-state.ipynb --- # How to edit graph state !!! tip "Prerequisites" * Human-in-the-loop * Breakpoints * LangGraph Glossary Human-in-the-loop (HIL) interactions are crucial for agentic systems. Manually updating the graph state a common HIL interaction pattern, allowing the human to edit actions (e.g., what tool is being called or how it is being called). We can implement this in LangGraph using a breakpoint: breakpoints allow us to interrupt graph execution before a specific step. At this breakpoint, we can manually update the graph state and then resume from that spot to continue. ## Setup First we need to install the packages required ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core zod ``` Next, we need to set API keys for Anthropic (the LLM we will use) ```bash export ANTHROPIC_API_KEY=your-api-key ``` Optionally, we can set API key for LangSmith tracing, which will give us best-in-class observability. ```bash export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_CALLBACKS_BACKGROUND="true" export LANGCHAIN_API_KEY=your-api-key ``` ## Simple Usage Let's look at very basic usage of this. Below, we do two things: 1) We specify the breakpoint using `interruptBefore` a specified step (node). 2) We set up a checkpointer to save the state of the graph up until this node. 3) We use `.updateState` to update the state of the graph. ```typescript import { StateGraph, START, END, Annotation } from "@langchain/langgraph"; import { MemorySaver } from "@langchain/langgraph"; const GraphState = Annotation.Root({ input: Annotation }); const step1 = (state: typeof GraphState.State) => { console.log("---Step 1---"); return state; } const step2 = (state: typeof GraphState.State) => { console.log("---Step 2---"); return state; } const step3 = (state: typeof GraphState.State) => { console.log("---Step 3---"); return state; } const builder = new StateGraph(GraphState) .addNode("step1", step1) .addNode("step2", step2) .addNode("step3", step3) .addEdge(START, "step1") .addEdge("step1", "step2") .addEdge("step2", "step3") .addEdge("step3", END); // Set up memory const graphStateMemory = new MemorySaver() const graph = builder.compile({ checkpointer: graphStateMemory, interruptBefore: ["step2"] }); ``` ```typescript import * as tslab from "tslab"; const drawableGraphGraphState = graph.getGraph(); const graphStateImage = await drawableGraphGraphState.drawMermaidPng(); const graphStateArrayBuffer = await graphStateImage.arrayBuffer(); await tslab.display.png(new Uint8Array(graphStateArrayBuffer)); ``` ```typescript // Input const initialInput = { input: "hello world" }; // Thread const graphStateConfig = { configurable: { thread_id: "1" }, streamMode: "values" as const }; // Run the graph until the first interruption for await (const event of await graph.stream(initialInput, graphStateConfig)) { console.log(`--- ${event.input} ---`); } // Will log when the graph is interrupted, after step 2. console.log("--- GRAPH INTERRUPTED ---"); ``` ```output --- hello world --- ---Step 1--- --- hello world --- --- GRAPH INTERRUPTED --- ``` Now, we can just manually update our graph state - ```typescript console.log("Current state!") const currState = await graph.getState(graphStateConfig); console.log(currState.values) await graph.updateState(graphStateConfig, { input: "hello universe!" }) console.log("---\n---\nUpdated state!") const updatedState = await graph.getState(graphStateConfig); console.log(updatedState.values) ``` ```output Current state! { input: 'hello world' } --- --- Updated state! { input: 'hello universe!' } ``` ```typescript // Continue the graph execution for await (const event of await graph.stream(null, graphStateConfig)) { console.log(`--- ${event.input} ---`); } ``` ```output ---Step 2--- --- hello universe! --- ---Step 3--- --- hello universe! --- ``` ## Agent In the context of agents, updating state is useful for things like editing tool calls. To show this, we will build a relatively simple ReAct-style agent that does tool calling. We will use Anthropic's models and a fake tool (just for demo purposes). ```typescript // Set up the tool import { ChatAnthropic } from "@langchain/anthropic"; import { tool } from "@langchain/core/tools"; import { StateGraph, START, END, MessagesAnnotation } from "@langchain/langgraph"; import { MemorySaver } from "@langchain/langgraph"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { AIMessage } from "@langchain/core/messages"; import { z } from "zod"; const search = tool((_) => { return "It's sunny in San Francisco, but you better look out if you're a Gemini 😈."; }, { name: "search", description: "Call to surf the web.", schema: z.string(), }) const tools = [search] const toolNode = new ToolNode(tools) // Set up the model const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20240620" }) const modelWithTools = model.bindTools(tools) // Define nodes and conditional edges // Define the function that determines whether to continue or not function shouldContinue(state: typeof MessagesAnnotation.State): "action" | typeof END { const lastMessage = state.messages[state.messages.length - 1]; // If there is no function call, then we finish if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) { return END; } // Otherwise if there is, we continue return "action"; } // Define the function that calls the model async function callModel(state: typeof MessagesAnnotation.State): Promise> { const messages = state.messages; const response = await modelWithTools.invoke(messages); // We return an object with a messages property, because this will get added to the existing list return { messages: [response] }; } // Define a new graph const workflow = new StateGraph(MessagesAnnotation) // Define the two nodes we will cycle between .addNode("agent", callModel) .addNode("action", toolNode) // We now add a conditional edge .addConditionalEdges( // First, we define the start node. We use `agent`. // This means these are the edges taken after the `agent` node is called. "agent", // Next, we pass in the function that will determine which node is called next. shouldContinue ) // We now add a normal edge from `action` to `agent`. // This means that after `action` is called, `agent` node is called next. .addEdge("action", "agent") // Set the entrypoint as `agent` // This means that this node is the first one called .addEdge(START, "agent"); // Setup memory const memory = new MemorySaver(); // Finally, we compile it! // This compiles it into a LangChain Runnable, // meaning you can use it as you would any other runnable const app = workflow.compile({ checkpointer: memory, interruptBefore: ["action"] }); ``` ```typescript import * as tslab from "tslab"; const drawableGraph = app.getGraph(); const image = await drawableGraph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` ## Interacting with the Agent We can now interact with the agent and see that it stops before calling a tool. ```typescript // Thread const config = { configurable: { thread_id: "3" }, streamMode: "values" as const }; for await (const event of await app.stream({ messages: [{ role: "human", content: "search for the weather in sf now" }] }, config)) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ human Message (1) ================================= search for the weather in sf now ================================ ai Message (1) ================================= [ { type: 'text', text: 'Certainly! I can help you search for the current weather in San Francisco. Let me use the search function to find that information for you.' }, { type: 'tool_use', id: 'toolu_0141zTpknasyWkrjTV6eKeT6', name: 'search', input: { input: 'current weather in San Francisco' } } ] ``` **Edit** We can now update the state accordingly. Let's modify the tool call to have the query `"current weather in SF"`. ```typescript // First, lets get the current state const currentState = await app.getState(config); // Let's now get the last message in the state // This is the one with the tool calls that we want to update let lastMessage = currentState.values.messages[currentState.values.messages.length - 1] // Let's now update the args for that tool call lastMessage.tool_calls[0].args = { query: "current weather in SF" } // Let's now call `updateState` to pass in this message in the `messages` key // This will get treated as any other update to the state // It will get passed to the reducer function for the `messages` key // That reducer function will use the ID of the message to update it // It's important that it has the right ID! Otherwise it would get appended // as a new message await app.updateState(config, { messages: lastMessage }); ``` ```output { configurable: { thread_id: '3', checkpoint_id: '1ef5e785-4298-6b71-8002-4a6ceca964db' } } ``` Let's now check the current state of the app to make sure it got updated accordingly ```typescript const newState = await app.getState(config); const updatedStateToolCalls = newState.values.messages[newState.values.messages.length -1 ].tool_calls console.log(updatedStateToolCalls) ``` ```output [ { name: 'search', args: { query: 'current weather in SF' }, id: 'toolu_0141zTpknasyWkrjTV6eKeT6', type: 'tool_call' } ] ``` **Resume** We can now call the agent again with no inputs to continue, ie. run the tool as requested. We can see from the logs that it passes in the update args to the tool. ```typescript for await (const event of await app.stream(null, config)) { console.log(event) const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) if (recentMsg._getType() === "tool") { console.log({ name: recentMsg.name, content: recentMsg.content }) } else if (recentMsg._getType() === "ai") { console.log(recentMsg.content) } } ``` ```output { messages: [ HumanMessage { "id": "7c69c1f3-914b-4236-b2ca-ef250e72cb7a", "content": "search for the weather in sf now", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_0152mx7AweoRWa67HFsfyaif", "content": [ { "type": "text", "text": "Certainly! I can help you search for the current weather in San Francisco. Let me use the search function to find that information for you." }, { "type": "tool_use", "id": "toolu_0141zTpknasyWkrjTV6eKeT6", "name": "search", "input": { "input": "current weather in San Francisco" } } ], "additional_kwargs": { "id": "msg_0152mx7AweoRWa67HFsfyaif", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 380, "output_tokens": 84 } }, "response_metadata": { "id": "msg_0152mx7AweoRWa67HFsfyaif", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 380, "output_tokens": 84 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "search", "args": { "query": "current weather in SF" }, "id": "toolu_0141zTpknasyWkrjTV6eKeT6", "type": "tool_call" } ], "invalid_tool_calls": [] }, ToolMessage { "id": "ccf0d56f-477f-408a-b809-6900a48379e0", "content": "It's sunny in San Francisco, but you better look out if you're a Gemini 😈.", "name": "search", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_0141zTpknasyWkrjTV6eKeT6" } ] } ================================ tool Message (1) ================================= { name: 'search', content: "It's sunny in San Francisco, but you better look out if you're a Gemini 😈." } { messages: [ HumanMessage { "id": "7c69c1f3-914b-4236-b2ca-ef250e72cb7a", "content": "search for the weather in sf now", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_0152mx7AweoRWa67HFsfyaif", "content": [ { "type": "text", "text": "Certainly! I can help you search for the current weather in San Francisco. Let me use the search function to find that information for you." }, { "type": "tool_use", "id": "toolu_0141zTpknasyWkrjTV6eKeT6", "name": "search", "input": { "input": "current weather in San Francisco" } } ], "additional_kwargs": { "id": "msg_0152mx7AweoRWa67HFsfyaif", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 380, "output_tokens": 84 } }, "response_metadata": { "id": "msg_0152mx7AweoRWa67HFsfyaif", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 380, "output_tokens": 84 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "search", "args": { "query": "current weather in SF" }, "id": "toolu_0141zTpknasyWkrjTV6eKeT6", "type": "tool_call" } ], "invalid_tool_calls": [] }, ToolMessage { "id": "ccf0d56f-477f-408a-b809-6900a48379e0", "content": "It's sunny in San Francisco, but you better look out if you're a Gemini 😈.", "name": "search", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_0141zTpknasyWkrjTV6eKeT6" }, AIMessage { "id": "msg_01YJXesUpaB5PfhgmRBCwnnb", "content": "Based on the search results, I can provide you with information about the current weather in San Francisco:\n\nThe weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. It's a great day to be outdoors or engage in activities that benefit from good weather.\n\nHowever, I should note that the search result included an unusual comment about Gemini zodiac signs. This appears to be unrelated to the weather and might be part of a joke or a reference to something else. For accurate and detailed weather information, I would recommend checking a reliable weather service or website for San Francisco.\n\nIs there anything else you'd like to know about the weather in San Francisco or any other information you need?", "additional_kwargs": { "id": "msg_01YJXesUpaB5PfhgmRBCwnnb", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 498, "output_tokens": 154 } }, "response_metadata": { "id": "msg_01YJXesUpaB5PfhgmRBCwnnb", "model": "claude-3-5-sonnet-20240620", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 498, "output_tokens": 154 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 498, "output_tokens": 154, "total_tokens": 652 } } ] } ================================ ai Message (1) ================================= Based on the search results, I can provide you with information about the current weather in San Francisco: The weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. It's a great day to be outdoors or engage in activities that benefit from good weather. However, I should note that the search result included an unusual comment about Gemini zodiac signs. This appears to be unrelated to the weather and might be part of a joke or a reference to something else. For accurate and detailed weather information, I would recommend checking a reliable weather service or website for San Francisco. Is there anything else you'd like to know about the weather in San Francisco or any other information you need? ``` --- how-tos/persistence-functional.ipynb --- # How to add thread-level persistence (functional API) !!! info "Prerequisites" This guide assumes familiarity with the following: - Functional API - Persistence - Memory - [Chat Models](https://js.langchain.com/docs/concepts/chat_models/) Many AI applications need memory to share context across multiple interactions on the same thread (e.g., multiple turns of a conversation). In LangGraph functional API, this kind of memory can be added to any entrypoint() workflow using thread-level persistence. When creating a LangGraph workflow, you can set it up to persist its results by using a checkpointer: 1. Create an instance of a checkpointer: ```ts import { MemorySaver } from "@langchain/langgraph"; const checkpointer = new MemorySaver(); ``` 2. Pass `checkpointer` instance to the `entrypoint()` wrapper function: ```ts import { entrypoint } from "@langchain/langgraph"; const workflow = entrypoint({ name: "workflow", checkpointer, }, async (inputs) => { ... }); ``` 3. Retrieve `previous` state from the prior execution within the workflow: ```ts import { entrypoint, getPreviousState } from "@langchain/langgraph"; const workflow = entrypoint({ name: "workflow", checkpointer, }, async (inputs) => { const previous = getPreviousState(); const result = doSomething(previous, inputs); ... }); ``` 4. Optionally choose which values will be returned from the workflow and which will be saved by the checkpointer as `previous`: ```ts import { entrypoint, getPreviousState } from "@langchain/langgraph"; const workflow = entrypoint({ name: "workflow", checkpointer, }, async (inputs) => { const previous = getPreviousState(); const result = doSomething(previous, inputs); ... return entrypoint.final({ value: result, save: combineState(inputs, result), }); }); ``` This guide shows how you can add thread-level persistence to your workflow. !!! tip "Note" If you need memory that is __shared__ across multiple conversations or users (cross-thread persistence), check out this how-to guide. !!! tip "Note" If you need to add thread-level persistence to a `StateGraph`, check out this how-to guide. ## Setup !!! note Compatibility This guide requires `@langchain/langgraph>=0.2.42`. First, install the required dependencies for this example: ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core zod ``` Next, we need to set API keys for Anthropic (the LLM we will use): ```typescript process.env.ANTHROPIC_API_KEY = "YOUR_API_KEY"; ``` !!! tip "Set up [LangSmith](https://smith.langchain.com) for LangGraph development" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com) ## Example: simple chatbot with short-term memory We will be using a workflow with a single task that calls a [chat model](https://js.langchain.com/docs/concepts/chat_models/). Let's first define the model we'll be using: ```typescript import { ChatAnthropic } from "@langchain/anthropic"; const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest", }); ``` Now we can define our task and workflow. To add in persistence, we need to pass in a [Checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver) to the entrypoint() wrapper function. ```typescript import type { BaseMessage, BaseMessageLike } from "@langchain/core/messages"; import { addMessages, entrypoint, task, getPreviousState, MemorySaver, } from "@langchain/langgraph"; const callModel = task("callModel", async (messages: BaseMessageLike[]) => { const response = model.invoke(messages); return response; }); const checkpointer = new MemorySaver(); const workflow = entrypoint({ name: "workflow", checkpointer, }, async (inputs: BaseMessageLike[]) => { const previous = getPreviousState() ?? []; const messages = addMessages(previous, inputs); const response = await callModel(messages); return entrypoint.final({ value: response, save: addMessages(messages, response), }); }); ``` If we try to use this workflow, the context of the conversation will be persisted across interactions. !!! note Note If you're using LangGraph Cloud or LangGraph Studio, you __don't need__ to pass checkpointer to the `entrypoint` wrapper, since it's done automatically. Here's how this works in practice: ```typescript const config = { configurable: { thread_id: "1" }, streamMode: "values" as const, }; const inputMessage = { role: "user", content: "hi! I'm bob" }; const stream = await workflow.stream( [inputMessage], config, ); for await (const chunk of stream) { console.log("=".repeat(30), `${chunk.getType()} message`, "=".repeat(30)); console.log(chunk.content); } ``` ```output ============================== ai message ============================== Hi Bob! I'm Claude. Nice to meet you! How can I help you today? ``` You can always resume previous threads: ```typescript const followupStream = await workflow.stream( [{ role: "user", content: "what's my name?" }], config, ); for await (const chunk of followupStream) { console.log("=".repeat(30), `${chunk.getType()} message`, "=".repeat(30)); console.log(chunk.content); } ``` ```output ============================== ai message ============================== Your name is Bob - you just told me that in your first message. ``` If we want to start a new conversation, we can pass in a different `thread_id`. Poof! All the memories are gone! ```typescript const newStream = await workflow.stream( [{ role: "user", content: "what's my name?" }], { configurable: { thread_id: "2", }, streamMode: "values", }, ); for await (const chunk of newStream) { console.log("=".repeat(30), `${chunk.getType()} message`, "=".repeat(30)); console.log(chunk.content); } ``` ```output ============================== ai message ============================== I don't know your name as we just started chatting. Would you like to introduce yourself? ``` !!! tip "Streaming tokens" If you would like to stream LLM tokens from your chatbot, you can use `streamMode: "messages"`. Check out this how-to guide to learn more. --- how-tos/streaming-events-from-within-tools.ipynb --- # How to stream events from within a tool If your LangGraph graph needs to use tools that call LLMs (or any other LangChain `Runnable` objects -- other graphs, LCEL chains, retrievers, etc.), you might want to stream events from the underlying `Runnable`. This guide shows how you can do that. ## Setup ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core zod ``` ```typescript process.env.ANTHROPIC_API_KEY = 'YOUR_API_KEY' ``` ## Define graph and tools We'll use a prebuilt ReAct agent for this guide ```typescript import { z } from "zod"; import { tool } from "@langchain/core/tools"; import { ChatPromptTemplate } from "@langchain/core/prompts"; import { ChatAnthropic } from "@langchain/anthropic"; const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20240620", temperature: 0, }); const getItems = tool( async (input, config) => { const template = ChatPromptTemplate.fromMessages([ [ "human", "Can you tell me what kind of items i might find in the following place: '{place}'. " + "List at least 3 such items separating them by a comma. And include a brief description of each item..", ], ]); const modelWithConfig = model.withConfig({ runName: "Get Items LLM", tags: ["tool_llm"], }); const chain = template.pipe(modelWithConfig); const result = await chain.invoke(input, config); return result.content; }, { name: "get_items", description: "Use this tool to look up which items are in the given place.", schema: z.object({ place: z.string().describe("The place to look up items for. E.g 'shelf'"), }), } ); ``` We're adding a custom tag (`tool_llm`) to our LLM runnable within the tool. This will allow us to filter events that we'll stream from the compiled graph (`agent`) Runnable below ```typescript import { createReactAgent } from "@langchain/langgraph/prebuilt"; const agent = createReactAgent({ llm: model, tools: [getItems], }); ``` ## Stream events from the graph ```typescript let finalEvent; for await (const event of agent.streamEvents( { messages: [ [ "human", "what items are on the shelf? You should call the get_items tool.", ], ], }, { version: "v2", }, { includeTags: ["tool_llm"], } )) { if ("chunk" in event.data) { console.dir({ type: event.data.chunk._getType(), content: event.data.chunk.content, }) } finalEvent = event; } ``` ```output { type: 'ai', content: 'Here' } { type: 'ai', content: ' are three items you might' } { type: 'ai', content: ' find on a shelf,' } { type: 'ai', content: ' along with brief' } { type: 'ai', content: ' descriptions:\n\n1.' } { type: 'ai', content: ' Books' } { type: 'ai', content: ': Boun' } { type: 'ai', content: 'd collections of printe' } { type: 'ai', content: 'd pages' } { type: 'ai', content: ' containing' } { type: 'ai', content: ' various' } { type: 'ai', content: ' forms' } { type: 'ai', content: ' of literature, information' } { type: 'ai', content: ', or reference' } { type: 'ai', content: ' material.\n\n2.' } { type: 'ai', content: ' Picture' } { type: 'ai', content: ' frames: Decorative' } { type: 'ai', content: ' borders' } { type: 'ai', content: ' used to display an' } { type: 'ai', content: 'd protect photographs, artwork' } { type: 'ai', content: ', or other visual memor' } { type: 'ai', content: 'abilia.\n\n3' } { type: 'ai', content: '. Pot' } { type: 'ai', content: 'ted plants: Small' } { type: 'ai', content: ' indoor' } { type: 'ai', content: ' plants in' } { type: 'ai', content: ' containers, often used for' } { type: 'ai', content: ' decoration or to add a' } { type: 'ai', content: ' touch of nature to indoor' } { type: 'ai', content: ' spaces.' } ``` Let's inspect the last event to get the final list of messages from the agent ```typescript const finalMessage = finalEvent?.data.output; console.dir( { type: finalMessage._getType(), content: finalMessage.content, tool_calls: finalMessage.tool_calls, }, { depth: null } ); ``` ```output { type: 'ai', content: 'Here are three items you might find on a shelf, along with brief descriptions:\n' + '\n' + '1. Books: Bound collections of printed pages containing various forms of literature, information, or reference material.\n' + '\n' + '2. Picture frames: Decorative borders used to display and protect photographs, artwork, or other visual memorabilia.\n' + '\n' + '3. Potted plants: Small indoor plants in containers, often used for decoration or to add a touch of nature to indoor spaces.', tool_calls: [] } ``` You can see that the content of the `ToolMessage` is the same as the output we streamed above --- how-tos/subgraphs-manage-state.ipynb --- # How to view and update state in subgraphs

Prerequisites

This guide assumes familiarity with the following:

Once you add persistence, you can view and update the state of the subgraph at any point in time. This enables human-in-the-loop interaction patterns such as: - You can surface a state during an interrupt to a user to let them accept an action. - You can rewind the subgraph to reproduce or avoid issues. - You can modify the state to let the user better control its actions. This guide shows how you can do this. ## Setup First we need to install required packages: ```bash npm install @langchain/langgraph @langchain/core @langchain/openai ``` Next, we need to set API keys for OpenAI (the provider we'll use for this guide): ```typescript // process.env.OPENAI_API_KEY = "YOUR_API_KEY"; ```

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

## Define subgraph First, let's set up our subgraph. For this, we will create a simple graph that can get the weather for a specific city. We will compile this graph with a [breakpoint](https://langchain-ai.github.io/langgraphjs/how-tos/human_in_the_loop/breakpoints/) before the `weather_node`: ```typescript import { z } from "zod"; import { tool } from "@langchain/core/tools"; import { ChatOpenAI } from "@langchain/openai"; import { StateGraph, MessagesAnnotation, Annotation } from "@langchain/langgraph"; const getWeather = tool(async ({ city }) => { return `It's sunny in ${city}`; }, { name: "get_weather", description: "Get the weather for a specific city", schema: z.object({ city: z.string().describe("A city name") }) }); const rawModel = new ChatOpenAI({ model: "gpt-4o-mini" }); const model = rawModel.withStructuredOutput(getWeather); // Extend the base MessagesAnnotation state with another field const SubGraphAnnotation = Annotation.Root({ ...MessagesAnnotation.spec, city: Annotation, }); const modelNode = async (state: typeof SubGraphAnnotation.State) => { const result = await model.invoke(state.messages); return { city: result.city }; }; const weatherNode = async (state: typeof SubGraphAnnotation.State) => { const result = await getWeather.invoke({ city: state.city }); return { messages: [ { role: "assistant", content: result, } ] }; }; const subgraph = new StateGraph(SubGraphAnnotation) .addNode("modelNode", modelNode) .addNode("weatherNode", weatherNode) .addEdge("__start__", "modelNode") .addEdge("modelNode", "weatherNode") .addEdge("weatherNode", "__end__") .compile({ interruptBefore: ["weatherNode"] }); ``` ## Define parent graph We can now setup the overall graph. This graph will first route to the subgraph if it needs to get the weather, otherwise it will route to a normal LLM. ```typescript import { MemorySaver } from "@langchain/langgraph"; const memory = new MemorySaver(); const RouterStateAnnotation = Annotation.Root({ ...MessagesAnnotation.spec, route: Annotation<"weather" | "other">, }); const routerModel = rawModel.withStructuredOutput( z.object({ route: z.enum(["weather", "other"]).describe("A step that should execute next to based on the currnet input") }), { name: "router" } ); const routerNode = async (state: typeof RouterStateAnnotation.State) => { const systemMessage = { role: "system", content: "Classify the incoming query as either about weather or not.", }; const messages = [systemMessage, ...state.messages] const { route } = await routerModel.invoke(messages); return { route }; } const normalLLMNode = async (state: typeof RouterStateAnnotation.State) => { const responseMessage = await rawModel.invoke(state.messages); return { messages: [responseMessage] }; }; const routeAfterPrediction = async (state: typeof RouterStateAnnotation.State) => { if (state.route === "weather") { return "weatherGraph"; } else { return "normalLLMNode"; } }; const graph = new StateGraph(RouterStateAnnotation) .addNode("routerNode", routerNode) .addNode("normalLLMNode", normalLLMNode) .addNode("weatherGraph", subgraph) .addEdge("__start__", "routerNode") .addConditionalEdges("routerNode", routeAfterPrediction) .addEdge("normalLLMNode", "__end__") .addEdge("weatherGraph", "__end__") .compile({ checkpointer: memory }); ``` Here's a diagram of the graph we just created: ![](./img/single-nested-subgraph.jpeg) Let's test this out with a normal query to make sure it works as intended! ```typescript const config = { configurable: { thread_id: "1" } }; const inputs = { messages: [{ role: "user", content: "hi!" }] }; const stream = await graph.stream(inputs, { ...config, streamMode: "updates" }); for await (const update of stream) { console.log(update); } ``` ```output { routerNode: { route: 'other' } } { normalLLMNode: { messages: [ AIMessage { "id": "chatcmpl-ABtbbiB5N3Uue85UNrFUjw5KhGaud", "content": "Hello! How can I assist you today?", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "completionTokens": 9, "promptTokens": 9, "totalTokens": 18 }, "finish_reason": "stop", "system_fingerprint": "fp_f85bea6784" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 9, "output_tokens": 9, "total_tokens": 18 } } ] } } ``` Great! We didn't ask about the weather, so we got a normal response from the LLM. ## Resuming from breakpoints Let's now look at what happens with breakpoints. Let's invoke it with a query that should get routed to the weather subgraph where we have the interrupt node. ```typescript const config2 = { configurable: { thread_id: "2" } }; const streamWithBreakpoint = await graph.stream({ messages: [{ role: "user", content: "what's the weather in sf" }] }, { ...config2, streamMode: "updates" }); for await (const update of streamWithBreakpoint) { console.log(update); } ``` ```output { routerNode: { route: 'weather' } } ``` Note that the graph stream doesn't include subgraph events. If we want to stream subgraph events, we can pass `subgraphs: True` in our config and get back subgraph events like so: ```typescript const streamWithSubgraphs = await graph.stream({ messages: [{ role: "user", content: "what's the weather in sf" }] }, { configurable: { thread_id: "3" }, streamMode: "updates", subgraphs: true }); for await (const update of streamWithSubgraphs) { console.log(update); } ``` ```output [ [], { routerNode: { route: 'weather' } } ] [ [ 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0' ], { modelNode: { city: 'San Francisco' } } ] ``` This time, we see the format of the streamed updates has changed. It's now a tuple where the first item is a nested array with information about the subgraph and the second is the actual state update. If we get the state now, we can see that it's paused on `weatherGraph` as expected: ```typescript const state = await graph.getState({ configurable: { thread_id: "3" } }) state.next ``` ```output [ 'weatherGraph' ] ``` If we look at the pending tasks for our current state, we can see that we have one task named `weatherGraph`, which corresponds to the subgraph task. ```typescript JSON.stringify(state.tasks, null, 2); ``` ```output [ { "id": "ec67e50f-d29c-5dee-8a80-08723a937de0", "name": "weatherGraph", "path": [ "__pregel_pull", "weatherGraph" ], "interrupts": [], "state": { "configurable": { "thread_id": "3", "checkpoint_ns": "weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0" } } } ] ``` However since we got the state using the config of the parent graph, we don't have access to the subgraph state. If you look at the `state` value of the task above you will note that it is simply the configuration of the parent graph. If we want to actually populate the subgraph state, we can pass in `subgraphs: True` to the second parameter of `getState` like so: ```typescript const stateWithSubgraphs = await graph.getState({ configurable: { thread_id: "3" } }, { subgraphs: true }) JSON.stringify(stateWithSubgraphs.tasks, null, 2) ``` ```output [ { "id": "ec67e50f-d29c-5dee-8a80-08723a937de0", "name": "weatherGraph", "path": [ "__pregel_pull", "weatherGraph" ], "interrupts": [], "state": { "values": { "messages": [ { "lc": 1, "type": "constructor", "id": [ "langchain_core", "messages", "HumanMessage" ], "kwargs": { "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {}, "id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44" } } ], "city": "San Francisco" }, "next": [ "weatherNode" ], "tasks": [ { "id": "2f2f8b8f-6a99-5225-8ff2-b6c49c3e9caf", "name": "weatherNode", "path": [ "__pregel_pull", "weatherNode" ], "interrupts": [] } ], "metadata": { "source": "loop", "writes": { "modelNode": { "city": "San Francisco" } }, "step": 1, "parents": { "": "1ef7c6ba-3d36-65e0-8001-adc1f8841274" } }, "config": { "configurable": { "thread_id": "3", "checkpoint_id": "1ef7c6ba-4503-6700-8001-61e828d1c772", "checkpoint_ns": "weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0", "checkpoint_map": { "": "1ef7c6ba-3d36-65e0-8001-adc1f8841274", "weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0": "1ef7c6ba-4503-6700-8001-61e828d1c772" } } }, "createdAt": "2024-09-27T00:58:43.184Z", "parentConfig": { "configurable": { "thread_id": "3", "checkpoint_ns": "weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0", "checkpoint_id": "1ef7c6ba-3d3b-6400-8000-fe27ae37c785" } } } } ] ``` Now we have access to the subgraph state! To resume execution, we can just invoke the outer graph as normal: ```typescript const resumedStream = await graph.stream(null, { configurable: { thread_id: "3" }, streamMode: "values", subgraphs: true, }); for await (const update of resumedStream) { console.log(update); } ``` ```output [ [], { messages: [ HumanMessage { "id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} } ], route: 'weather' } ] [ [ 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0' ], { messages: [ HumanMessage { "id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} } ], city: 'San Francisco' } ] [ [ 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0' ], { messages: [ HumanMessage { "id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "55d7a03f-876a-4887-9418-027321e747c7", "content": "It's sunny in San Francisco", "additional_kwargs": {}, "response_metadata": {}, "tool_calls": [], "invalid_tool_calls": [] } ], city: 'San Francisco' } ] [ [], { messages: [ HumanMessage { "id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "55d7a03f-876a-4887-9418-027321e747c7", "content": "It's sunny in San Francisco", "additional_kwargs": {}, "response_metadata": {}, "tool_calls": [], "invalid_tool_calls": [] } ], route: 'weather' } ] ``` ### Resuming from specific subgraph node In the example above, we were replaying from the outer graph - which automatically replayed the subgraph from whatever state it was in previously (paused before the `weatherNode` in our case), but it is also possible to replay from inside a subgraph. In order to do so, we need to get the configuration from the exact subgraph state that we want to replay from. We can do this by exploring the state history of the subgraph, and selecting the state before `modelNode` - which we can do by filtering on the `.next` parameter. To get the state history of the subgraph, we need to first pass in the parent graph state before the subgraph: ```typescript let parentGraphStateBeforeSubgraph; const histories = await graph.getStateHistory({ configurable: { thread_id: "3" } }); for await (const historyEntry of histories) { if (historyEntry.next[0] === "weatherGraph") { parentGraphStateBeforeSubgraph = historyEntry; } } ``` ```typescript let subgraphStateBeforeModelNode; const subgraphHistories = await graph.getStateHistory(parentGraphStateBeforeSubgraph.tasks[0].state); for await (const subgraphHistoryEntry of subgraphHistories) { if (subgraphHistoryEntry.next[0] === "modelNode") { subgraphStateBeforeModelNode = subgraphHistoryEntry; } } console.log(subgraphStateBeforeModelNode); ``` ```output { values: { messages: [ HumanMessage { "id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} } ] }, next: [ 'modelNode' ], tasks: [ { id: '6d0d44fd-279b-56b0-8160-8f4929f9bfe6', name: 'modelNode', path: [Array], interrupts: [], state: undefined } ], metadata: { source: 'loop', writes: null, step: 0, parents: { '': '1ef7c6ba-3d36-65e0-8001-adc1f8841274' } }, config: { configurable: { thread_id: '3', checkpoint_ns: 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0', checkpoint_id: '1ef7c6ba-3d3b-6400-8000-fe27ae37c785', checkpoint_map: [Object] } }, createdAt: '2024-09-27T00:58:42.368Z', parentConfig: { configurable: { thread_id: '3', checkpoint_ns: 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0', checkpoint_id: '1ef7c6ba-3d38-6cf1-ffff-b3912beb00b9' } } } ``` This pattern can be extended no matter how many levels deep. We can confirm that we have gotten the correct state by comparing the `.next` parameter of the `subgraphStateBeforeModelNode`. ```typescript subgraphStateBeforeModelNode.next; ``` ```output [ 'modelNode' ] ``` Perfect! We have gotten the correct state snaphshot, and we can now resume from the `modelNode` inside of our subgraph: ```typescript const resumeSubgraphStream = await graph.stream(null, { ...subgraphStateBeforeModelNode.config, streamMode: "updates", subgraphs: true }); for await (const value of resumeSubgraphStream) { console.log(value); } ``` ```output [ [ 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0' ], { modelNode: { city: 'San Francisco' } } ] ``` We can see that it reruns the `modelNode` and breaks right before the `weatherNode` as expected. This subsection has shown how you can replay from any node, no matter how deeply nested it is inside your graph - a powerful tool for testing how deterministic your agent is. ## Modifying state ### Update the state of a subgraph What if we want to modify the state of a subgraph? We can do this similarly to how we [update the state of normal graphs](https://langchain-ai.github.io/langgraphjs/how-tos/time-travel/). We just need to ensure we pass the config of the subgraph to `updateState`. Let's run our graph as before: ```typescript const graphStream = await graph.stream({ messages: [{ role: "user", content: "what's the weather in sf" }], }, { configurable: { thread_id: "4", } }); for await (const update of graphStream) { console.log(update); } ``` ```output { routerNode: { route: 'weather' } } ``` ```typescript const outerGraphState = await graph.getState({ configurable: { thread_id: "4", } }, { subgraphs: true }) console.log(outerGraphState.tasks[0].state); ``` ```output { values: { messages: [ HumanMessage { "id": "07ed1a38-13a9-4ec2-bc88-c4f6b713ec85", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} } ], city: 'San Francisco' }, next: [ 'weatherNode' ], tasks: [ { id: 'eabfbb82-6cf4-5ecd-932e-ed994ea44f23', name: 'weatherNode', path: [Array], interrupts: [], state: undefined } ], metadata: { source: 'loop', writes: { modelNode: [Object] }, step: 1, parents: { '': '1ef7c6ba-563f-60f0-8001-4fce0e78ef56' } }, config: { configurable: { thread_id: '4', checkpoint_id: '1ef7c6ba-5c71-6f90-8001-04f60f3c8173', checkpoint_ns: 'weatherGraph:8d8c9278-bd2a-566a-b9e1-e72286634681', checkpoint_map: [Object] } }, createdAt: '2024-09-27T00:58:45.641Z', parentConfig: { configurable: { thread_id: '4', checkpoint_ns: 'weatherGraph:8d8c9278-bd2a-566a-b9e1-e72286634681', checkpoint_id: '1ef7c6ba-5641-6800-8000-96bcde048fa6' } } } ``` In order to update the state of the **inner** graph, we need to pass the config for the **inner** graph, which we can get by accessing calling `state.tasks[0].state.config` - since we interrupted inside the subgraph, the state of the task is just the state of the subgraph. ```typescript import type { StateSnapshot } from "@langchain/langgraph"; await graph.updateState((outerGraphState.tasks[0].state as StateSnapshot).config, { city: "la" }); ``` ```output { configurable: { thread_id: '4', checkpoint_ns: 'weatherGraph:8d8c9278-bd2a-566a-b9e1-e72286634681', checkpoint_id: '1ef7c6ba-5de0-62f0-8002-3618a75d1fce', checkpoint_map: { '': '1ef7c6ba-563f-60f0-8001-4fce0e78ef56', 'weatherGraph:8d8c9278-bd2a-566a-b9e1-e72286634681': '1ef7c6ba-5de0-62f0-8002-3618a75d1fce' } } } ``` We can now resume streaming the outer graph (which will resume the subgraph!) and check that we updated our search to use LA instead of SF. ```typescript const resumedStreamWithUpdatedState = await graph.stream(null, { configurable: { thread_id: "4", }, streamMode: "updates", subgraphs: true, }) for await (const update of resumedStreamWithUpdatedState) { console.log(JSON.stringify(update, null, 2)); } ``` ```output [ [ "weatherGraph:8d8c9278-bd2a-566a-b9e1-e72286634681" ], { "weatherNode": { "messages": [ { "role": "assistant", "content": "It's sunny in la" } ] } } ] [ [], { "weatherGraph": { "messages": [ { "lc": 1, "type": "constructor", "id": [ "langchain_core", "messages", "HumanMessage" ], "kwargs": { "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {}, "id": "07ed1a38-13a9-4ec2-bc88-c4f6b713ec85" } }, { "lc": 1, "type": "constructor", "id": [ "langchain_core", "messages", "AIMessage" ], "kwargs": { "content": "It's sunny in la", "tool_calls": [], "invalid_tool_calls": [], "additional_kwargs": {}, "response_metadata": {}, "id": "94c29f6c-38b3-420f-a9fb-bd85548f0c03" } } ] } } ] ``` Fantastic! The AI responded with "It's sunny in LA!" as we expected. ### Acting as a subgraph node Instead of editing the state before `weatherNode` in the `weatherGraph` subgraph, another way we could update the state is by acting as the `weatherNode` ourselves. We can do this by passing the subgraph config along with a node name passed as a third positional argument, which allows us to update the state as if we are the node we specify. We will set an interrupt before the `weatherNode` and then using the update state function as the `weatherNode`, the graph itself never calls `weatherNode` directly but instead we decide what the output of `weatherNode` should be. ```typescript const streamWithAsNode = await graph.stream({ messages: [{ role: "user", content: "What's the weather in sf", }] }, { configurable: { thread_id: "14", } }); for await (const update of streamWithAsNode) { console.log(update); } // Graph execution should stop before the weather node console.log("interrupted!"); const interruptedState = await graph.getState({ configurable: { thread_id: "14", } }, { subgraphs: true }); console.log(interruptedState); // We update the state by passing in the message we want returned from the weather node // and make sure to pass `"weatherNode"` to signify that we want to act as this node. await graph.updateState((interruptedState.tasks[0].state as StateSnapshot).config, { messages: [{ "role": "assistant", "content": "rainy" }] }, "weatherNode"); const resumedStreamWithAsNode = await graph.stream(null, { configurable: { thread_id: "14", }, streamMode: "updates", subgraphs: true, }); for await (const update of resumedStreamWithAsNode) { console.log(update); } console.log(await graph.getState({ configurable: { thread_id: "14", } }, { subgraphs: true })); ``` ```output { routerNode: { route: 'weather' } } interrupted! { values: { messages: [ HumanMessage { "id": "90e9ff28-5b13-4e10-819a-31999efe303c", "content": "What's the weather in sf", "additional_kwargs": {}, "response_metadata": {} } ], route: 'weather' }, next: [ 'weatherGraph' ], tasks: [ { id: 'f421fca8-de9e-5683-87ab-6ea9bb8d6275', name: 'weatherGraph', path: [Array], interrupts: [], state: [Object] } ], metadata: { source: 'loop', writes: { routerNode: [Object] }, step: 1, parents: {} }, config: { configurable: { thread_id: '14', checkpoint_id: '1ef7c6ba-63ac-68f1-8001-5f7ada5f98e8', checkpoint_ns: '' } }, createdAt: '2024-09-27T00:58:46.399Z', parentConfig: { configurable: { thread_id: '14', checkpoint_ns: '', checkpoint_id: '1ef7c6ba-5f20-6020-8000-1ff649773a32' } } } [ [], { weatherGraph: { messages: [Array] } } ] { values: { messages: [ HumanMessage { "id": "90e9ff28-5b13-4e10-819a-31999efe303c", "content": "What's the weather in sf", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "af761e6d-9f6a-4467-9a3c-489bed3fbad7", "content": "rainy", "additional_kwargs": {}, "response_metadata": {}, "tool_calls": [], "invalid_tool_calls": [] } ], route: 'weather' }, next: [], tasks: [], metadata: { source: 'loop', writes: { weatherGraph: [Object] }, step: 2, parents: {} }, config: { configurable: { thread_id: '14', checkpoint_id: '1ef7c6ba-69e6-6cc0-8002-1751fc5bdd8f', checkpoint_ns: '' } }, createdAt: '2024-09-27T00:58:47.052Z', parentConfig: { configurable: { thread_id: '14', checkpoint_ns: '', checkpoint_id: '1ef7c6ba-63ac-68f1-8001-5f7ada5f98e8' } } } ``` Perfect! The agent responded with the message we passed in ourselves, and identified the weather in SF as `rainy` instead of `sunny`. ### Acting as the entire subgraph Lastly, we could also update the graph just acting as the **entire** subgraph. This is similar to the case above but instead of acting as just the `weatherNode` we are acting as the entire `weatherGraph` subgraph. This is done by passing in the normal graph config as well as the `asNode` argument, where we specify the we are acting as the entire subgraph node. ```typescript const entireSubgraphExampleStream = await graph.stream({ messages: [ { role: "user", content: "what's the weather in sf" } ], }, { configurable: { thread_id: "8", }, streamMode: "updates", subgraphs: true, }); for await (const update of entireSubgraphExampleStream) { console.log(update); } // Graph execution should stop before the weather node console.log("interrupted!"); // We update the state by passing in the message we want returned from the weather graph. // Note that we don't need to pass in the subgraph config, since we aren't updating the state inside the subgraph await graph.updateState({ configurable: { thread_id: "8", } }, { messages: [{ role: "assistant", content: "rainy" }] }, "weatherGraph"); const resumedEntireSubgraphExampleStream = await graph.stream(null, { configurable: { thread_id: "8", }, streamMode: "updates", }); for await (const update of resumedEntireSubgraphExampleStream) { console.log(update); } const currentStateAfterUpdate = await graph.getState({ configurable: { thread_id: "8", } }); console.log(currentStateAfterUpdate.values.messages); ``` ```output [ [], { routerNode: { route: 'weather' } } ] [ [ 'weatherGraph:db9c3bb2-5d27-5dae-a724-a8d702b33e86' ], { modelNode: { city: 'San Francisco' } } ] interrupted! [ HumanMessage { "id": "001282b0-ca2e-443f-b6ee-8cb16c81bf59", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "4b5d49cf-0f87-4ee8-b96f-eaa8716b9e9c", "content": "rainy", "additional_kwargs": {}, "response_metadata": {}, "tool_calls": [], "invalid_tool_calls": [] } ] ``` Again, the agent responded with "rainy" as we expected. ## Double nested subgraphs This same functionality continues to work no matter the level of nesting. Here is an example of doing the same things with a double nested subgraph (although any level of nesting will work). We add another router on top of our already defined graphs. First, let's recreate the graph we've been using above. This time we will compile it with no checkpointer, since it itself will be a subgraph! ```typescript const parentGraph = new StateGraph(RouterStateAnnotation) .addNode("routerNode", routerNode) .addNode("normalLLMNode", normalLLMNode) .addNode("weatherGraph", subgraph) .addEdge("__start__", "routerNode") .addConditionalEdges("routerNode", routeAfterPrediction) .addEdge("normalLLMNode", "__end__") .addEdge("weatherGraph", "__end__") .compile(); ``` Now let's declare a "grandparent" graph that uses this graph as a subgraph: ```typescript const checkpointer = new MemorySaver(); const GrandfatherStateAnnotation = Annotation.Root({ ...MessagesAnnotation.spec, toContinue: Annotation, }); const grandparentRouterNode = async (_state: typeof GrandfatherStateAnnotation.State) => { // Dummy logic that will always continue return { toContinue: true }; }; const grandparentConditionalEdge = async (state: typeof GrandfatherStateAnnotation.State) => { if (state.toContinue) { return "parentGraph"; } else { return "__end__"; } }; const grandparentGraph = new StateGraph(GrandfatherStateAnnotation) .addNode("routerNode", grandparentRouterNode) .addNode("parentGraph", parentGraph) .addEdge("__start__", "routerNode") .addConditionalEdges("routerNode", grandparentConditionalEdge) .addEdge("parentGraph", "__end__") .compile({ checkpointer }); ``` Here's a diagram showing what this looks like: ![](./img/doubly-nested-subgraph.jpeg) If we run until the interrupt, we can now see that there are snapshots of the state of all three graphs ```typescript const grandparentConfig = { configurable: { thread_id: "123" }, }; const grandparentGraphStream = await grandparentGraph.stream({ messages: [{ role: "user", content: "what's the weather in SF" }], }, { ...grandparentConfig, streamMode: "updates", subgraphs: true }); for await (const update of grandparentGraphStream) { console.log(update); } ``` ```output [ [], { routerNode: { toContinue: true } } ] [ [ 'parentGraph:095bb8a9-77d3-5a0c-9a23-e1390dcf36bc' ], { routerNode: { route: 'weather' } } ] [ [ 'parentGraph:095bb8a9-77d3-5a0c-9a23-e1390dcf36bc', 'weatherGraph:b1da376c-25a5-5aae-82da-4ff579f05d43' ], { modelNode: { city: 'San Francisco' } } ] ``` ```typescript const grandparentGraphState = await grandparentGraph.getState( grandparentConfig, { subgraphs: true } ); const parentGraphState = grandparentGraphState.tasks[0].state as StateSnapshot; const subgraphState = parentGraphState.tasks[0].state as StateSnapshot; console.log("Grandparent State:"); console.log(grandparentGraphState.values); console.log("---------------"); console.log("Parent Graph State:"); console.log(parentGraphState.values); console.log("---------------"); console.log("Subgraph State:"); console.log(subgraphState.values); ``` ```output Grandparent State: { messages: [ HumanMessage { "id": "5788e436-a756-4ff5-899a-82117a5c59c7", "content": "what's the weather in SF", "additional_kwargs": {}, "response_metadata": {} } ], toContinue: true } --------------- Parent Graph State: { messages: [ HumanMessage { "id": "5788e436-a756-4ff5-899a-82117a5c59c7", "content": "what's the weather in SF", "additional_kwargs": {}, "response_metadata": {} } ], route: 'weather' } --------------- Subgraph State: { messages: [ HumanMessage { "id": "5788e436-a756-4ff5-899a-82117a5c59c7", "content": "what's the weather in SF", "additional_kwargs": {}, "response_metadata": {} } ], city: 'San Francisco' } ``` We can now continue, acting as the node three levels down ```typescript await grandparentGraph.updateState(subgraphState.config, { messages: [{ role: "assistant", content: "rainy" }] }, "weatherNode"); const updatedGrandparentGraphStream = await grandparentGraph.stream(null, { ...grandparentConfig, streamMode: "updates", subgraphs: true, }); for await (const update of updatedGrandparentGraphStream) { console.log(update); } console.log((await grandparentGraph.getState(grandparentConfig)).values.messages) ``` ```output [ [ 'parentGraph:095bb8a9-77d3-5a0c-9a23-e1390dcf36bc' ], { weatherGraph: { messages: [Array] } } ] [ [], { parentGraph: { messages: [Array] } } ] [ HumanMessage { "id": "5788e436-a756-4ff5-899a-82117a5c59c7", "content": "what's the weather in SF", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "1c161973-9a9d-414d-b631-56791d85e2fb", "content": "rainy", "additional_kwargs": {}, "response_metadata": {}, "tool_calls": [], "invalid_tool_calls": [] } ] ``` As in the cases above, we can see that the AI responds with "rainy" as we expect. We can explore the state history to see how the state of the grandparent graph was updated at each step. ```typescript const grandparentStateHistories = await grandparentGraph.getStateHistory(grandparentConfig); for await (const state of grandparentStateHistories) { console.log(state); console.log("-----"); } ``` ```output { values: { messages: [ HumanMessage { "id": "5788e436-a756-4ff5-899a-82117a5c59c7", "content": "what's the weather in SF", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "1c161973-9a9d-414d-b631-56791d85e2fb", "content": "rainy", "additional_kwargs": {}, "response_metadata": {}, "tool_calls": [], "invalid_tool_calls": [] } ], toContinue: true }, next: [], tasks: [], metadata: { source: 'loop', writes: { parentGraph: [Object] }, step: 2, parents: {} }, config: { configurable: { thread_id: '123', checkpoint_ns: '', checkpoint_id: '1ef7c6ba-8560-67d0-8002-2e2cedd7de18' } }, createdAt: '2024-09-27T00:58:49.933Z', parentConfig: { configurable: { thread_id: '123', checkpoint_ns: '', checkpoint_id: '1ef7c6ba-7977-62c0-8001-13e89cb2bbab' } } } ----- { values: { messages: [ HumanMessage { "id": "5788e436-a756-4ff5-899a-82117a5c59c7", "content": "what's the weather in SF", "additional_kwargs": {}, "response_metadata": {} } ], toContinue: true }, next: [ 'parentGraph' ], tasks: [ { id: '095bb8a9-77d3-5a0c-9a23-e1390dcf36bc', name: 'parentGraph', path: [Array], interrupts: [], state: [Object] } ], metadata: { source: 'loop', writes: { routerNode: [Object] }, step: 1, parents: {} }, config: { configurable: { thread_id: '123', checkpoint_ns: '', checkpoint_id: '1ef7c6ba-7977-62c0-8001-13e89cb2bbab' } }, createdAt: '2024-09-27T00:58:48.684Z', parentConfig: { configurable: { thread_id: '123', checkpoint_ns: '', checkpoint_id: '1ef7c6ba-7972-64a0-8000-243575e3244f' } } } ----- { values: { messages: [ HumanMessage { "id": "5788e436-a756-4ff5-899a-82117a5c59c7", "content": "what's the weather in SF", "additional_kwargs": {}, "response_metadata": {} } ] }, next: [ 'routerNode' ], tasks: [ { id: '00ed334c-47b5-5693-92b4-a5b83373e2a0', name: 'routerNode', path: [Array], interrupts: [], state: undefined } ], metadata: { source: 'loop', writes: null, step: 0, parents: {} }, config: { configurable: { thread_id: '123', checkpoint_ns: '', checkpoint_id: '1ef7c6ba-7972-64a0-8000-243575e3244f' } }, createdAt: '2024-09-27T00:58:48.682Z', parentConfig: { configurable: { thread_id: '123', checkpoint_ns: '', checkpoint_id: '1ef7c6ba-796f-6d90-ffff-25ed0eb5bb38' } } } ----- { values: { messages: [] }, next: [ '__start__' ], tasks: [ { id: 'ea62628e-881d-558d-bafc-e8b6a734e8aa', name: '__start__', path: [Array], interrupts: [], state: undefined } ], metadata: { source: 'input', writes: { __start__: [Object] }, step: -1, parents: {} }, config: { configurable: { thread_id: '123', checkpoint_ns: '', checkpoint_id: '1ef7c6ba-796f-6d90-ffff-25ed0eb5bb38' } }, createdAt: '2024-09-27T00:58:48.681Z', parentConfig: undefined } ----- ``` ```typescript ``` --- how-tos/multi-agent-network.ipynb --- # How to build a multi-agent network

Prerequisites

This guide assumes familiarity with the following:

This functionality also requires @langchain/langgraph>=0.2.29.

In this how-to guide we will demonstrate how to implement a multi-agent network architecture. Each agent can be represented as a node in the graph that executes agent step(s) and decides what to do next - finish execution or route to another agent (including routing to itself, e.g. running in a loop). A common pattern for routing in multi-agent architectures is handoffs. Handoffs allow you to specify: 1. which agent to navigate to next and (e.g. name of the node to go to) 2. what information to pass to that agent (e.g. state update) To implement handoffs, agent nodes can return `Command` object that allows you to combine both control flow and state updates: ```ts const agent = async (state) => { // the condition for routing/halting can be anything // e.g. LLM tool call / structured output, etc. const goto = getNextAgent(...); // "agent" / "another_agent" if (goto) { return new Command({ goto, update: { myStateKey: "my_state_value", } }); } ... } ``` ## Setup First, let's install the required packages: ```bash yarn add @langchain/langgraph @langchain/openai @langchain/core zod ```

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

## Travel Recommendations Example In this example we will build a team of travel assistant agents that can communicate with each other via handoffs. We will create 3 agents: * `travel_advisor`: can help with general travel destination recommendations. Can ask `sightseeing_advisor` and `hotel_advisor` for help. * `sightseeing_advisor`: can help with sightseeing recommendations. Can ask `travel_advisor` and `hotel_advisor` for help. * `hotel_advisor`: can help with hotel recommendations. Can ask `sightseeing_advisor` and `hotel_advisor` for help. This is a fully-connected network - every agent can talk to any other agent. To implement the handoffs between the agents we'll be using LLMs with structured output. Each agent's LLM will return an output with both its text response (`response`) as well as which agent to route to next (`goto`). If the agent has enough information to respond to the user, `goto` will contain `finish`. Now, let's define our agent nodes and graph! ```typescript import { ChatOpenAI } from "@langchain/openai"; import { Command, MessagesAnnotation, StateGraph } from "@langchain/langgraph"; import { z } from "zod"; const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0.1, }); const makeAgentNode = (params: { name: string, destinations: string[], systemPrompt: string }) => { return async (state: typeof MessagesAnnotation.State) => { const possibleDestinations = ["__end__", ...params.destinations] as const; // define schema for the structured output: // - model's text response (`response`) // - name of the node to go to next (or '__end__') const responseSchema = z.object({ response: z.string().describe( "A human readable response to the original question. Does not need to be a final response. Will be streamed back to the user." ), goto: z.enum(possibleDestinations).describe("The next agent to call, or __end__ if the user's query has been resolved. Must be one of the specified values."), }); const messages = [ { role: "system", content: params.systemPrompt }, ...state.messages, ]; const response = await model.withStructuredOutput(responseSchema, { name: "router", }).invoke(messages); // handoff to another agent or halt const aiMessage = { role: "assistant", content: response.response, name: params.name, }; return new Command({ goto: response.goto, update: { messages: aiMessage } }); } }; const travelAdvisor = makeAgentNode({ name: "travel_advisor", destinations: ["sightseeing_advisor", "hotel_advisor"], systemPrompt: [ "You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). ", "If you need specific sightseeing recommendations, ask 'sightseeing_advisor' for help. ", "If you need hotel recommendations, ask 'hotel_advisor' for help. ", "If you have enough information to respond to the user, return '__end__'. ", "Never mention other agents by name." ].join(""), }); const sightseeingAdvisor = makeAgentNode({ name: "sightseeing_advisor", destinations: ["travel_advisor", "hotel_advisor"], systemPrompt: [ "You are a travel expert that can provide specific sightseeing recommendations for a given destination. ", "If you need general travel help, go to 'travel_advisor' for help. ", "If you need hotel recommendations, go to 'hotel_advisor' for help. ", "If you have enough information to respond to the user, return 'finish'. ", "Never mention other agents by name." ].join(""), }); const hotelAdvisor = makeAgentNode({ name: "hotel_advisor", destinations: ["travel_advisor", "sightseeing_advisor"], systemPrompt: [ "You are a booking expert that provides hotel recommendations for a given destination. ", "If you need general travel help, ask 'travel_advisor' for help. ", "If you need specific sightseeing recommendations, ask 'sightseeing_advisor' for help. ", "If you have enough information to respond to the user, return 'finish'. ", "Never mention other agents by name.", ].join(""), }); const graph = new StateGraph(MessagesAnnotation) .addNode("travel_advisor", travelAdvisor, { ends: ["sightseeing_advisor", "hotel_advisor", "__end__"], }) .addNode("sightseeing_advisor", sightseeingAdvisor, { ends: ["travel_advisor", "hotel_advisor", "__end__"], }) .addNode("hotel_advisor", hotelAdvisor, { ends: ["travel_advisor", "sightseeing_advisor", "__end__"], }) // we'll always start with a general travel advisor .addEdge("__start__", "travel_advisor") .compile(); ``` ```typescript import * as tslab from "tslab"; const drawableGraph = await graph.getGraphAsync(); const image = await drawableGraph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` First, let's invoke it with a generic input: ```typescript const simpleStream = await graph.stream({ messages: [{ role: "user", content: "i wanna go somewhere warm in the caribbean", }], }); for await (const chunk of simpleStream) { console.log(chunk); } ``` ```output { travel_advisor: { messages: { role: 'assistant', content: 'The Caribbean is a fantastic choice for warm weather and beautiful beaches. Some popular destinations include Jamaica, the Bahamas, the Dominican Republic, and Barbados. Each offers unique experiences, from vibrant culture and music to stunning natural landscapes.', name: 'travel_advisor' } } } ``` You can see that in this case only the first agent (`travel_advisor`) ran. Let's now ask for more recommendations: ```typescript const recommendationStream = await graph.stream({ messages: [{ role: "user", content: "i wanna go somewhere warm in the caribbean. pick one destination, give me some things to do and hotel recommendations", }], }); for await (const chunk of recommendationStream) { console.log(chunk); } ``` ```output { travel_advisor: { messages: { role: 'assistant', content: 'I recommend visiting Aruba, a beautiful Caribbean island known for its stunning beaches and warm climate.', name: 'travel_advisor' } } } { sightseeing_advisor: { messages: { role: 'assistant', content: 'Aruba is a fantastic choice for a warm Caribbean getaway. Here are some activities you can enjoy:\n' + '\n' + "1. **Eagle Beach**: Relax on one of the world's most beautiful beaches, known for its pristine white sand and clear turquoise waters.\n" + '\n' + '2. **Arikok National Park**: Explore this national park that covers nearly 20% of the island, offering hiking trails, caves, and unique wildlife.\n' + '\n' + '3. **Palm Beach**: Enjoy water sports, beach bars, and vibrant nightlife along this bustling beach area.\n' + '\n' + '4. **Oranjestad**: Visit the colorful capital city for shopping, dining, and exploring local culture and history.\n' + '\n' + '5. **Snorkeling and Diving**: Discover the vibrant marine life and coral reefs around the island.\n' + '\n' + '6. **California Lighthouse**: Take a trip to this iconic lighthouse for panoramic views of the island.\n' + '\n' + "Now, let's find some hotel recommendations for your stay in Aruba.", name: 'sightseeing_advisor' } } } { hotel_advisor: { messages: { role: 'assistant', content: 'For your stay in Aruba, here are some hotel recommendations:\n' + '\n' + '1. **Renaissance Aruba Resort & Casino**: Located in Oranjestad, this resort offers a private island, luxurious accommodations, and a casino.\n' + '\n' + '2. **Hilton Aruba Caribbean Resort & Casino**: Situated on Palm Beach, this resort features beautiful gardens, a spa, and multiple dining options.\n' + '\n' + '3. **The Ritz-Carlton, Aruba**: A luxury beachfront resort offering elegant rooms, a spa, and fine dining.\n' + '\n' + '4. **Divi Aruba All Inclusive**: Enjoy an all-inclusive experience with multiple restaurants, pools, and activities right on Druif Beach.\n' + '\n' + '5. **Bucuti & Tara Beach Resort**: An adults-only resort on Eagle Beach, known for its romantic setting and eco-friendly practices.\n' + '\n' + 'These options provide a range of experiences from luxury to all-inclusive, ensuring a comfortable and enjoyable stay in Aruba.', name: 'hotel_advisor' } } } ``` Voila - `travel_advisor` makes a decision to first get some sightseeing recommendations from `sightseeing_advisor`, and then `sightseeing_advisor` in turn calls `hotel_advisor` for more info. Notice that we never explicitly defined the order in which the agents should be executed! ## Game NPCs Example In this example we will create a team of [non-player characters (NPCs)](https://en.wikipedia.org/wiki/Non-player_character) that all run at the same time and share game state (resources). At each step, each NPC will inspect the state and decide whether to halt or continue acting at the next step. If it continues, it will update the shared game state (produce or consume resources). We will create 4 NPC agents: - `villager`: produces wood and food until there is enough, then halts - `guard`: protects gold and consumes food. When there is not enough food, leaves duty and halts - `merchant`: trades wood for gold. When there is not enough wood, halts - `thief`: checks if the guard is on duty and steals all of the gold when the guard leaves, then halts Our NPC agents will be simple node functions (`villager`, `guard`, etc.). At each step of the graph execution, the agent function will inspect the resource values in the state and decide whether it should halt or continue. If it decides to continue, it will update the resource values in the state and loop back to itself to run at the next step. Now, let's define our agent nodes and graph! ```typescript import { Command, StateGraph, Annotation } from "@langchain/langgraph"; const GameStateAnnotation = Annotation.Root({ // note that we're defining a reducer (operator.add) here. // This will allow all agents to write their updates for resources concurrently. wood: Annotation({ default: () => 0, reducer: (a, b) => a + b, }), food: Annotation({ default: () => 0, reducer: (a, b) => a + b, }), gold: Annotation({ default: () => 0, reducer: (a, b) => a + b, }), guardOnDuty: Annotation, }); /** Villager NPC that gathers wood and food. */ const villager = async (state: typeof GameStateAnnotation.State) => { const currentResources = state.wood + state.food; // Continue gathering until we have enough resources if (currentResources < 15) { console.log("Villager gathering resources."); return new Command({ goto: "villager", update: { wood: 3, food: 1, }, }); } // NOTE: Returning Command({goto: "__end__"}) is not necessary for the graph to run correctly // but it's useful for visualization, to show that the agent actually halts return new Command({ goto: "__end__", }); } /** Guard NPC that protects gold and consumes food. */ const guard = async (state: typeof GameStateAnnotation.State) => { if (!state.guardOnDuty) { return new Command({ goto: "__end__", }); } // Guard needs food to keep patrolling if (state.food > 0) { console.log("Guard patrolling."); // Loop back to the 'guard' agent return new Command({ goto: "guard", update: { food: -1 }, }); } console.log("Guard leaving to get food."); return new Command({ goto: "__end__", update: { guardOnDuty: false, }, }); }; /** Merchant NPC that trades wood for gold. */ const merchant = async (state: typeof GameStateAnnotation.State) => { // Trade wood for gold when available if (state.wood >= 5) { console.log("Merchant trading wood for gold."); return new Command({ goto: "merchant", update: { wood: -5, gold: 1 } }); } return new Command({ goto: "__end__" }); }; /** Thief NPC that steals gold if the guard leaves to get food. */ const thief = async (state: typeof GameStateAnnotation.State) => { if (!state.guardOnDuty) { console.log("Thief stealing gold."); return new Command({ goto: "__end__", update: { gold: -state.gold } }); } // keep thief on standby (loop back to the 'thief' agent) return new Command({ goto: "thief" }); }; const gameGraph = new StateGraph(GameStateAnnotation) .addNode("villager", villager, { ends: ["villager", "__end__"], }) .addNode("guard", guard, { ends: ["guard", "__end__"], }) .addNode("merchant", merchant, { ends: ["merchant", "__end__"], }) .addNode("thief", thief, { ends: ["thief", "__end__"], }) .addEdge("__start__", "villager") .addEdge("__start__", "guard") .addEdge("__start__", "merchant") .addEdge("__start__", "thief") .compile(); ``` ```typescript import * as tslab from "tslab"; const drawableGameGraph = await gameGraph.getGraphAsync(); const gameImage = await drawableGameGraph.drawMermaidPng(); const gameArrayBuffer = await gameImage.arrayBuffer(); await tslab.display.png(new Uint8Array(gameArrayBuffer)); ``` Let's run it with some initial state! ```typescript const gameStream = await gameGraph.stream({ wood: 10, food: 3, gold: 10, guardOnDuty: true, }, { streamMode: "values", }); for await (const state of gameStream) { console.log("Game state", state); console.log("-".repeat(50)); } ``` ```output Game state { wood: 10, food: 3, gold: 10, guardOnDuty: true } -------------------------------------------------- Villager gathering resources. Guard patrolling. Merchant trading wood for gold. Game state { wood: 8, food: 3, gold: 11, guardOnDuty: true } -------------------------------------------------- Villager gathering resources. Guard patrolling. Merchant trading wood for gold. Game state { wood: 6, food: 3, gold: 12, guardOnDuty: true } -------------------------------------------------- Villager gathering resources. Guard patrolling. Merchant trading wood for gold. Game state { wood: 4, food: 3, gold: 13, guardOnDuty: true } -------------------------------------------------- Villager gathering resources. Guard patrolling. Game state { wood: 7, food: 3, gold: 13, guardOnDuty: true } -------------------------------------------------- Villager gathering resources. Guard patrolling. Game state { wood: 10, food: 3, gold: 13, guardOnDuty: true } -------------------------------------------------- Villager gathering resources. Guard patrolling. Game state { wood: 13, food: 3, gold: 13, guardOnDuty: true } -------------------------------------------------- Guard patrolling. Game state { wood: 13, food: 2, gold: 13, guardOnDuty: true } -------------------------------------------------- Guard patrolling. Game state { wood: 13, food: 1, gold: 13, guardOnDuty: true } -------------------------------------------------- Guard patrolling. Game state { wood: 13, food: 0, gold: 13, guardOnDuty: true } -------------------------------------------------- Guard leaving to get food. Game state { wood: 13, food: 0, gold: 13, guardOnDuty: false } -------------------------------------------------- Thief stealing gold. Game state { wood: 13, food: 0, gold: 0, guardOnDuty: false } -------------------------------------------------- ``` ```typescript ``` --- how-tos/pass-run-time-values-to-tools.ipynb --- # How to pass runtime values to tools This guide shows how to define tools that depend on dynamically defined variables. These values are provided by your program, not by the LLM. Tools can access the [config.configurable](https://langchain-ai.github.io/langgraphjs/reference/interfaces/langgraph.LangGraphRunnableConfig.html) field for values like user IDs that are known when a graph is initially executed, as well as managed values from the [store](https://langchain-ai.github.io/langgraphjs/reference/classes/checkpoint.BaseStore.html) for persistence across threads. However, it can be convenient to access intermediate runtime values which are not known ahead of time, but are progressively generated as a graph executes, such as the current graph state. This guide will cover two techniques for this: The `getCurrentTaskInput` utility function, and closures. ## Setup Install the following to run this guide: ```bash npm install @langchain/langgraph @langchain/openai @langchain/core ``` Next, configure your environment to connect to your model provider. ```bash export OPENAI_API_KEY=your-api-key ``` Optionally, set your API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```bash export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_CALLBACKS_BACKGROUND="true" export LANGCHAIN_API_KEY=your-api-key ``` ## The `getCurrentTaskInput` Utility Function The `getCurrentTaskInput` utility function makes it easier to get the current state in areas of your application that might be called indirectly, like tool handlers.

Compatibility

This functionality was added in @langchain/langgraph>=0.2.53.

It also requires async_hooks support, which is supported in many popular JavaScript environments (such as Node.js, Deno, and Cloudflare Workers), but not all of them (mainly web browsers). If you are deploying to an environment where this is not supported, see the closures section below.

Let's start off by defining a tool that an LLM can use to update pet preferences for a user. The tool will retrieve the current state of the graph from the current context. ### Define the agent state Since we're just tracking messages, we'll use the `MessagesAnnotation`: ```typescript import { MessagesAnnotation } from "@langchain/langgraph"; ``` Now, declare a tool as shown below. The tool receives values in three different ways: 1. It will receive a generated list of `pets` from the LLM in its `input`. 2. It will pull a `userId` populated from the initial graph invocation. 3. It will fetch the input that was passed to the currenty executing task (either a `StateGraph` node handler, or a Functional API `entrypoint` or `task`) via the `getCurrentTaskInput` function. It will then use LangGraph's [cross-thread persistence](https://langchain-ai.github.io/langgraphjs/how-tos/cross-thread-persistence/) to save preferences: ```typescript import { z } from "zod"; import { tool } from "@langchain/core/tools"; import { getCurrentTaskInput, LangGraphRunnableConfig, } from "@langchain/langgraph"; const updateFavoritePets = tool(async (input, config: LangGraphRunnableConfig) => { // Some arguments are populated by the LLM; these are included in the schema below const { pets } = input; // Fetch the current input to the task that called this tool. // This will be identical to the input that was passed to the `ToolNode` that called this tool. const currentState = getCurrentTaskInput() as typeof MessagesAnnotation.State; // Other information (such as a UserID) are most easily provided via the config // This is set when when invoking or streaming the graph const userId = config.configurable?.userId; // LangGraph's managed key-value store is also accessible from the config const store = config.store; await store.put([userId, "pets"], "names", pets); // Store the initial input message from the user as a note. // Using the same key will override previous values - you could // use something different if you wanted to store many interactions. await store.put([userId, "pets"], "context", { content: currentState.messages[0].content }); return "update_favorite_pets called."; }, { // The LLM "sees" the following schema: name: "update_favorite_pets", description: "add to the list of favorite pets.", schema: z.object({ pets: z.array(z.string()), }), }); ``` If we look at the tool call schema, which is what is passed to the model for tool-calling, we can see that only `pets` is being passed: ```typescript import { zodToJsonSchema } from "zod-to-json-schema"; console.log(zodToJsonSchema(updateFavoritePets.schema)); ``` ```output { type: 'object', properties: { pets: { type: 'array', items: [Object] } }, required: [ 'pets' ], additionalProperties: false, '$schema': 'http://json-schema.org/draft-07/schema#' } ``` Let's also declare another tool so that our agent can retrieve previously set preferences: ```typescript const getFavoritePets = tool( async (_, config: LangGraphRunnableConfig) => { const userId = config.configurable?.userId; // LangGraph's managed key-value store is also accessible via the config const store = config.store; const petNames = await store.get([userId, "pets"], "names"); const context = await store.get([userId, "pets"], "context"); return JSON.stringify({ pets: petNames.value, context: context.value.content, }); }, { // The LLM "sees" the following schema: name: "get_favorite_pets", description: "retrieve the list of favorite pets for the given user.", schema: z.object({}), } ); ``` ## Define the nodes From here there's really nothing special that needs to be done. This approach works with both `StateGraph` and functional agents, and it works just as well with prebuilt agents like `createReactAgent`! We'll demonstrate it by defining a custom ReAct agent using `StateGraph`. This is very similar to the agent that you'd get if you were to instead call `createReactAgent` Let's start off by defining the nodes for our graph. 1. The agent: responsible for deciding what (if any) actions to take. 2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action. We will also need to define some edges. 1. After the agent is called, we should either invoke the tool node or finish. 2. After the tool node have been invoked, it should always go back to the agent to decide what to do next ```typescript import { END, START, StateGraph, MemorySaver, InMemoryStore, } from "@langchain/langgraph"; import { AIMessage } from "@langchain/core/messages"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o" }); const tools = [getFavoritePets, updateFavoritePets]; const routeMessage = (state: typeof MessagesAnnotation.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as AIMessage; // If no tools are called, we can finish (respond to the user) if (!lastMessage?.tool_calls?.length) { return END; } // Otherwise if there is, we continue and call the tools return "tools"; }; const callModel = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const modelWithTools = model.bindTools(tools); const responseMessage = await modelWithTools.invoke([ { role: "system", content: "You are a personal assistant. Store any preferences the user tells you about." }, ...messages ]); return { messages: [responseMessage] }; }; const workflow = new StateGraph(MessagesAnnotation) .addNode("agent", callModel) .addNode("tools", new ToolNode(tools)) .addEdge(START, "agent") .addConditionalEdges("agent", routeMessage) .addEdge("tools", "agent"); const memory = new MemorySaver(); const store = new InMemoryStore(); const graph = workflow.compile({ checkpointer: memory, store: store }); ``` ```typescript import * as tslab from "tslab"; const graphViz = graph.getGraph(); const image = await graphViz.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` ## Use it! Let's use our graph now! ```typescript import { BaseMessage, isAIMessage, isHumanMessage, isToolMessage, HumanMessage, ToolMessage, } from "@langchain/core/messages"; let inputs = { messages: [ new HumanMessage({ content: "My favorite pet is a terrier. I saw a cute one on Twitter." }) ], }; let config = { configurable: { thread_id: "1", userId: "a-user", }, }; function printMessages(messages: BaseMessage[]) { for (const message of messages) { if (isHumanMessage(message)) { console.log(`User: ${message.content}`); } else if (isAIMessage(message)) { const aiMessage = message as AIMessage; if (aiMessage.content) { console.log(`Assistant: ${aiMessage.content}`); } if (aiMessage.tool_calls) { for (const toolCall of aiMessage.tool_calls) { console.log(`Tool call: ${toolCall.name}(${JSON.stringify(toolCall.args)})`); } } } else if (isToolMessage(message)) { const toolMessage = message as ToolMessage; console.log(`${toolMessage.name} tool output: ${toolMessage.content}`); } } } let { messages } = await graph.invoke(inputs, config); printMessages(messages); ``` ```output User: My favorite pet is a terrier. I saw a cute one on Twitter. Tool call: update_favorite_pets({"pets":["terrier"]}) update_favorite_pets tool output: update_favorite_pets called. Assistant: I've added "terrier" to your list of favorite pets. If you have any more favorites, feel free to let me know! ``` Now verify it can properly fetch the stored preferences and cite where it got the information from: ```typescript inputs = { messages: [new HumanMessage({ content: "What're my favorite pets and what did I say when I told you about them?" })] }; config = { configurable: { thread_id: "2", // New thread ID, so the conversation history isn't present. userId: "a-user" } }; messages = (await graph.invoke(inputs, config)).messages; printMessages(messages); ``` ```output User: What're my favorite pets and what did I say when I told you about them? Tool call: get_favorite_pets({}) get_favorite_pets tool output: {"pets":["terrier"],"context":"My favorite pet is a terrier. I saw a cute one on Twitter."} Assistant: Your favorite pet is a terrier. You mentioned, "My favorite pet is a terrier. I saw a cute one on Twitter." ``` As you can see the agent is able to properly cite that the information came from Twitter! ## Closures If you cannot use context variables in your environment, you can use [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) to create tools with access to dynamic content. Here is a high-level example: ```typescript function generateTools(state: typeof MessagesAnnotation.State) { const updateFavoritePets = tool( async (input, config: LangGraphRunnableConfig) => { // Some arguments are populated by the LLM; these are included in the schema below const { pets } = input; // Others (such as a UserID) are best provided via the config // This is set when when invoking or streaming the graph const userId = config.configurable?.userId; // LangGraph's managed key-value store is also accessible via the config const store = config.store; await store.put([userId, "pets"], "names", pets ) await store.put([userId, "pets"], "context", {content: state.messages[0].content}) return "update_favorite_pets called."; }, { // The LLM "sees" the following schema: name: "update_favorite_pets", description: "add to the list of favorite pets.", schema: z.object({ pets: z.array(z.string()), }), } ); return [updateFavoritePets]; }; ``` Then, when laying out your graph, you will need to call the above method whenever you bind or invoke tools. For example: ```typescript const toolNodeWithClosure = async (state: typeof MessagesAnnotation.State) => { // We fetch the tools any time this node is reached to // form a closure and let it access the latest messages const tools = generateTools(state); const toolNodeWithConfig = new ToolNode(tools); return toolNodeWithConfig.invoke(state); }; ``` --- how-tos/input_output_schema.ipynb --- # How to define input/output schema for your graph By default, `StateGraph` takes in a single schema and all nodes are expected to communicate with that schema. However, it is also possible to define explicit input and output schemas for a graph. This is helpful if you want to draw a distinction between input and output keys. In this notebook we'll walk through an example of this. At a high level, in order to do this you simply have to pass in separate [`Annotation.Root({})`](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.Annotation.Root.html) objets as `{ input: Annotation.Root({}), output: Annotation.Root({}) }` when defining the graph. Let's see an example below! ```typescript import { Annotation, StateGraph } from "@langchain/langgraph"; const InputAnnotation = Annotation.Root({ question: Annotation, }); const OutputAnnotation = Annotation.Root({ answer: Annotation, }); const answerNode = (_state: typeof InputAnnotation.State) => { return { answer: "bye" }; }; const graph = new StateGraph({ input: InputAnnotation, output: OutputAnnotation, }) .addNode("answerNode", answerNode) .addEdge("__start__", "answerNode") .compile(); await graph.invoke({ question: "hi", }); ``` ```output { answer: 'bye' } ``` Notice that the output of invoke only includes the output schema. --- how-tos/configuration.ipynb --- # How to add runtime configuration to your graph Once you've created an app in LangGraph, you likely will want to permit configuration at runtime. For instance, you may want to let the LLM or prompt be selected dynamically, configure a user's `user_id` to enforce row-level security, etc. In LangGraph, configuration and other ["out-of-band" communication](https://en.wikipedia.org/wiki/Out-of-band) is done via the [RunnableConfig](https://v02.api.js.langchain.com/interfaces/langchain_core_runnables.RunnableConfig.html), which is always the second positional arg when invoking your application. Below, we walk through an example of letting you configure a user ID and pick which model to use. ## Setup This guide will use Anthropic's Claude 3 Haiku and OpenAI's GPT-4o model. We will optionally set our API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..."; // process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "Configuration: LangGraphJS"; ``` ```output Configuration: LangGraphJS ``` ## Define the graph We will create an exceedingly simple message graph for this example. ```typescript import { BaseMessage } from "@langchain/core/messages"; import { ChatOpenAI } from "@langchain/openai"; import { ChatAnthropic } from "@langchain/anthropic"; import { ChatPromptTemplate } from "@langchain/core/prompts"; import { RunnableConfig } from "@langchain/core/runnables"; import { END, START, StateGraph, Annotation, } from "@langchain/langgraph"; const AgentState = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), userInfo: Annotation({ reducer: (x, y) => { return y ? y : x ? x : "N/A"; }, default: () => "N/A", }) }); const promptTemplate = ChatPromptTemplate.fromMessages([ ["system", "You are a helpful assistant.\n\n## User Info:\n{userInfo}"], ["placeholder", "{messages}"], ]); const callModel = async ( state: typeof AgentState.State, config?: RunnableConfig, ) => { const { messages, userInfo } = state; const modelName = config?.configurable?.model; const model = modelName === "claude" ? new ChatAnthropic({ model: "claude-3-haiku-20240307" }) : new ChatOpenAI({ model: "gpt-4o" }); const chain = promptTemplate.pipe(model); const response = await chain.invoke( { messages, userInfo, }, config, ); return { messages: [response] }; }; const fetchUserInformation = async ( _: typeof AgentState.State, config?: RunnableConfig, ) => { const userDB = { user1: { name: "John Doe", email: "jod@langchain.ai", phone: "+1234567890", }, user2: { name: "Jane Doe", email: "jad@langchain.ai", phone: "+0987654321", }, }; const userId = config?.configurable?.user; if (userId) { const user = userDB[userId as keyof typeof userDB]; if (user) { return { userInfo: `Name: ${user.name}\nEmail: ${user.email}\nPhone: ${user.phone}`, }; } } return { userInfo: "N/A" }; }; const workflow = new StateGraph(AgentState) .addNode("fetchUserInfo", fetchUserInformation) .addNode("agent", callModel) .addEdge(START, "fetchUserInfo") .addEdge("fetchUserInfo", "agent") .addEdge("agent", END); const graph = workflow.compile(); ``` ## Call with config ```typescript import { HumanMessage } from "@langchain/core/messages"; const config = { configurable: { model: "openai", user: "user1", }, }; const inputs = { messages: [new HumanMessage("Could you remind me of my email??")], }; for await ( const { messages } of await graph.stream(inputs, { ...config, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output Could you remind me of my email?? ----- Could you remind me of my email?? ----- Your email is jod@langchain.ai. ----- ``` ## Change the config Now let's try the same input with a different user. ```typescript const config2 = { configurable: { model: "openai", user: "user2", }, }; const inputs2 = { messages: [new HumanMessage("Could you remind me of my email??")], }; for await ( const { messages } of await graph.stream(inputs2, { ...config2, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output Could you remind me of my email?? ----- Could you remind me of my email?? ----- Your email address is jad@langchain.ai. ----- ``` Check out the [LangSmith Trace (link)](https://smith.langchain.com/public/bbd3561f-c0d1-4886-ae18-a6626c6b8670/r/946098b5-84d3-4456-a03c-5dbc8591e76b) for this run to "see what the LLM sees". ## Config schema You can also pass an annotation defining the shape of `config.configurable` into your graph. This will currently only expose type information on the compiled graph, and will not filter out keys: ```typescript import { MessagesAnnotation } from "@langchain/langgraph"; const ConfigurableAnnotation = Annotation.Root({ expectedField: Annotation, }); const printNode = async ( state: typeof MessagesAnnotation.State, config: RunnableConfig ) => { console.log("Expected", config.configurable?.expectedField); // @ts-expect-error This type will be present even though is not in the typing console.log("Unexpected", config.configurable?.unexpectedField); return {}; }; const graphWithConfigSchema = new StateGraph(MessagesAnnotation, ConfigurableAnnotation) .addNode("printNode", printNode) .addEdge(START, "printNode") .compile(); const result = await graphWithConfigSchema.invoke({ messages: [{ role: "user", content: "Echo!"} ] }, { configurable: { expectedField: "I am expected", unexpectedField: "I am unexpected but present" } }); ``` ```output Expected I am expected Unexpected I am unexpected but present ``` ``` ``` --- how-tos/tool-calling-errors.ipynb --- # How to handle tool calling errors LLMs aren't perfect at calling tools. The model may try to call a tool that doesn't exist or fail to return arguments that match the requested schema. Strategies like keeping schemas simple, reducing the number of tools you pass at once, and having good names and descriptions can help mitigate this risk, but aren't foolproof. This guide covers some ways to build error handling into your graphs to mitigate these failure modes.

Compatibility

This guide requires @langchain/langgraph>=0.0.28, @langchain/anthropic>=0.2.6, and @langchain/core>=0.2.17. For help upgrading, see this guide.

## Using the prebuilt `ToolNode` To start, define a mock weather tool that has some hidden restrictions on input queries. The intent here is to simulate a real-world case where a model fails to call a tool correctly: ```bash $ npm install @langchain/langgraph @langchain/anthropic @langchain/core ``` ```typescript import { z } from "zod"; import { tool } from "@langchain/core/tools"; const getWeather = tool(async ({ location }) => { if (location === "SAN FRANCISCO") { return "It's 60 degrees and foggy"; } else if (location.toLowerCase() === "san francisco") { throw new Error("Input queries must be all capitals"); } else { throw new Error("Invalid input."); } }, { name: "get_weather", description: "Call to get the current weather", schema: z.object({ location: z.string(), }), }); ``` Next, set up a graph implementation of the ReAct agent. This agent takes some query as input, then repeatedly call tools until it has enough information to resolve the query. We'll use the prebuilt `ToolNode` to execute called tools, and a small, fast model powered by Anthropic: ```typescript import { StateGraph, MessagesAnnotation } from "@langchain/langgraph"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { ChatAnthropic } from "@langchain/anthropic"; import { BaseMessage, isAIMessage } from "@langchain/core/messages"; const toolNode = new ToolNode([getWeather]); const modelWithTools = new ChatAnthropic({ model: "claude-3-haiku-20240307", temperature: 0, }).bindTools([getWeather]); const shouldContinue = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1]; if (isAIMessage(lastMessage) && lastMessage.tool_calls?.length) { return "tools"; } return "__end__"; } const callModel = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const response = await modelWithTools.invoke(messages); return { messages: [response] }; } const app = new StateGraph(MessagesAnnotation) .addNode("agent", callModel) .addNode("tools", toolNode) .addEdge("__start__", "agent") .addEdge("tools", "agent") .addConditionalEdges("agent", shouldContinue, { // Explicitly list possible destinations so that // we can automatically draw the graph below. tools: "tools", __end__: "__end__", }) .compile(); ``` ```typescript import * as tslab from "tslab"; const graph = app.getGraph(); const image = await graph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` When you try to call the tool, you can see that the model calls the tool with a bad input, causing the tool to throw an error. The prebuilt `ToolNode` that executes the tool has some built-in error handling that captures the error and passes it back to the model so that it can try again: ```typescript const response = await app.invoke({ messages: [ { role: "user", content: "what is the weather in san francisco?"}, ] }); for (const message of response.messages) { // Anthropic returns tool calls in content as well as in `AIMessage.tool_calls` const content = JSON.stringify(message.content, null, 2); console.log(`${message._getType().toUpperCase()}: ${content}`); } ``` ```output HUMAN: "what is the weather in san francisco?" AI: [ { "type": "text", "text": "Okay, let's check the weather in San Francisco:" }, { "type": "tool_use", "id": "toolu_015dywEMjSJsjkgP91VDbm52", "name": "get_weather", "input": { "location": "San Francisco" } } ] TOOL: "Error: Input queries must be all capitals\n Please fix your mistakes." AI: [ { "type": "text", "text": "Apologies, let me try that again with the location in all capital letters:" }, { "type": "tool_use", "id": "toolu_01Qw6t7p9UGk8aHQh7qtLJZT", "name": "get_weather", "input": { "location": "SAN FRANCISCO" } } ] TOOL: "It's 60 degrees and foggy" AI: "The weather in San Francisco is 60 degrees and foggy." ``` ## Custom strategies This is a fine default in many cases, but there are cases where custom fallbacks may be better. For example, the below tool requires as input a list of elements of a specific length - tricky for a small model! We'll also intentionally avoid pluralizing `topic` to trick the model into thinking it should pass a string: ```typescript import { StringOutputParser } from "@langchain/core/output_parsers"; const haikuRequestSchema = z.object({ topic: z.array(z.string()).length(3), }); const masterHaikuGenerator = tool(async ({ topic }) => { const model = new ChatAnthropic({ model: "claude-3-haiku-20240307", temperature: 0, }); const chain = model.pipe(new StringOutputParser()); const topics = topic.join(", "); const haiku = await chain.invoke(`Write a haiku about ${topics}`); return haiku; }, { name: "master_haiku_generator", description: "Generates a haiku based on the provided topics.", schema: haikuRequestSchema, }); const customStrategyToolNode = new ToolNode([masterHaikuGenerator]); const customStrategyModel = new ChatAnthropic({ model: "claude-3-haiku-20240307", temperature: 0, }); const customStrategyModelWithTools = customStrategyModel.bindTools([masterHaikuGenerator]); const customStrategyShouldContinue = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1]; if (isAIMessage(lastMessage) && lastMessage.tool_calls?.length) { return "tools"; } return "__end__"; } const customStrategyCallModel = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const response = await customStrategyModelWithTools.invoke(messages); return { messages: [response] }; } const customStrategyApp = new StateGraph(MessagesAnnotation) .addNode("tools", customStrategyToolNode) .addNode("agent", customStrategyCallModel) .addEdge("__start__", "agent") .addEdge("tools", "agent") .addConditionalEdges("agent", customStrategyShouldContinue, { // Explicitly list possible destinations so that // we can automatically draw the graph below. tools: "tools", __end__: "__end__", }) .compile(); const response2 = await customStrategyApp.invoke( { messages: [{ role: "user", content: "Write me an incredible haiku about water." }], }, { recursionLimit: 10 } ); for (const message of response2.messages) { // Anthropic returns tool calls in content as well as in `AIMessage.tool_calls` const content = JSON.stringify(message.content, null, 2); console.log(`${message._getType().toUpperCase()}: ${content}`); } ``` ```output HUMAN: "Write me an incredible haiku about water." AI: [ { "type": "text", "text": "Okay, let's generate a haiku about water using the master haiku generator tool:" }, { "type": "tool_use", "id": "toolu_01CMvVu3MhPeCk5X7F8GBv8f", "name": "master_haiku_generator", "input": { "topic": [ "water" ] } } ] TOOL: "Error: Received tool input did not match expected schema\n Please fix your mistakes." AI: [ { "type": "text", "text": "Oops, looks like I need to provide 3 topics for the haiku generator. Let me try again with 3 water-related topics:" }, { "type": "tool_use", "id": "toolu_0158Nz2scGSWvYor4vmJbSDZ", "name": "master_haiku_generator", "input": { "topic": [ "ocean", "waves", "rain" ] } } ] TOOL: "Here is a haiku about the ocean, waves, and rain:\n\nWaves crash on the shore,\nRhythmic dance of water's song,\nRain falls from the sky." AI: "The haiku generator has produced a beautiful and evocative poem about the different aspects of water - the ocean, waves, and rain. I hope you enjoy this creative take on a water-themed haiku!" ``` We can see that the model takes two attempts. A better strategy might be to trim the failed attempt to reduce distraction, then fall back to a more advanced model. Here's an example - note the custom-built tool calling node instead of the prebuilt `ToolNode`: ```typescript import { AIMessage, ToolMessage, RemoveMessage } from "@langchain/core/messages"; const haikuRequestSchema2 = z.object({ topic: z.array(z.string()).length(3), }); const masterHaikuGenerator2 = tool(async ({ topic }) => { const model = new ChatAnthropic({ model: "claude-3-haiku-20240307", temperature: 0, }); const chain = model.pipe(new StringOutputParser()); const topics = topic.join(", "); const haiku = await chain.invoke(`Write a haiku about ${topics}`); return haiku; }, { name: "master_haiku_generator", description: "Generates a haiku based on the provided topics.", schema: haikuRequestSchema2, }); const callTool2 = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const toolsByName = { master_haiku_generator: masterHaikuGenerator }; const lastMessage = messages[messages.length - 1] as AIMessage; const outputMessages: ToolMessage[] = []; for (const toolCall of lastMessage.tool_calls) { try { const toolResult = await toolsByName[toolCall.name].invoke(toolCall); outputMessages.push(toolResult); } catch (error: any) { // Return the error if the tool call fails outputMessages.push( new ToolMessage({ content: error.message, name: toolCall.name, tool_call_id: toolCall.id!, additional_kwargs: { error } }) ); } } return { messages: outputMessages }; }; const model = new ChatAnthropic({ model: "claude-3-haiku-20240307", temperature: 0, }); const modelWithTools2 = model.bindTools([masterHaikuGenerator2]); const betterModel = new ChatAnthropic({ model: "claude-3-5-sonnet-20240620", temperature: 0, }); const betterModelWithTools = betterModel.bindTools([masterHaikuGenerator2]); const shouldContinue2 = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1]; if (isAIMessage(lastMessage) && lastMessage.tool_calls?.length) { return "tools"; } return "__end__"; } const shouldFallback = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const failedToolMessages = messages.find((message) => { return message._getType() === "tool" && message.additional_kwargs.error !== undefined; }); if (failedToolMessages) { return "remove_failed_tool_call_attempt"; } return "agent"; } const callModel2 = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const response = await modelWithTools2.invoke(messages); return { messages: [response] }; } const removeFailedToolCallAttempt = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; // Remove all messages from the most recent // instance of AIMessage onwards. const lastAIMessageIndex = messages .map((msg, index) => ({ msg, index })) .reverse() .findIndex(({ msg }) => isAIMessage(msg)); const messagesToRemove = messages.slice(lastAIMessageIndex); return { messages: messagesToRemove.map(m => new RemoveMessage({ id: m.id })) }; } const callFallbackModel = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const response = await betterModelWithTools.invoke(messages); return { messages: [response] }; } const app2 = new StateGraph(MessagesAnnotation) .addNode("tools", callTool2) .addNode("agent", callModel2) .addNode("remove_failed_tool_call_attempt", removeFailedToolCallAttempt) .addNode("fallback_agent", callFallbackModel) .addEdge("__start__", "agent") .addConditionalEdges("agent", shouldContinue2, { // Explicitly list possible destinations so that // we can automatically draw the graph below. tools: "tools", __end__: "__end__", }) .addConditionalEdges("tools", shouldFallback, { remove_failed_tool_call_attempt: "remove_failed_tool_call_attempt", agent: "agent", }) .addEdge("remove_failed_tool_call_attempt", "fallback_agent") .addEdge("fallback_agent", "tools") .compile(); ``` The `tools` node will now return `ToolMessage`s with an `error` field in `additional_kwargs` if a tool call fails. If that happens, it will go to another node that removes the failed tool messages, and has a better model retry the tool call generation. We also add a trimming step via returning the special message modifier `RemoveMessage` to remove previous messages from the state. The diagram below shows this visually: ```typescript import * as tslab from "tslab"; const graph2 = app2.getGraph(); const image2 = await graph2.drawMermaidPng(); const arrayBuffer2 = await image2.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer2)); ``` Let's try it out. To emphasize the removal steps, let's `stream` the responses from the model so that we can see each executed node: ```typescript const stream = await app2.stream( { messages: [{ role: "user", content: "Write me an incredible haiku about water." }] }, { recursionLimit: 10 }, ) for await (const chunk of stream) { console.log(chunk); } ``` ```output { agent: { messages: [ AIMessage { "id": "msg_01HqvhPuubXqerWgYRNFqPrd", "content": [ { "type": "text", "text": "Okay, let's generate a haiku about water using the master haiku generator tool:" }, { "type": "tool_use", "id": "toolu_01QFmyc5vhQBFfzF7hCGTRc1", "name": "master_haiku_generator", "input": { "topic": "[Array]" } } ], "additional_kwargs": { "id": "msg_01HqvhPuubXqerWgYRNFqPrd", "type": "message", "role": "assistant", "model": "claude-3-haiku-20240307", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 392, "output_tokens": 77 } }, "response_metadata": { "id": "msg_01HqvhPuubXqerWgYRNFqPrd", "model": "claude-3-haiku-20240307", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 392, "output_tokens": 77 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "master_haiku_generator", "args": { "topic": "[Array]" }, "id": "toolu_01QFmyc5vhQBFfzF7hCGTRc1", "type": "tool_call" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 392, "output_tokens": 77, "total_tokens": 469 } } ] } } { tools: { messages: [ ToolMessage { "id": "502c7399-4d95-4afd-8a86-ece864d2bc7f", "content": "Received tool input did not match expected schema", "name": "master_haiku_generator", "additional_kwargs": { "error": { "output": "{\"topic\":[\"water\"]}" } }, "response_metadata": {}, "tool_call_id": "toolu_01QFmyc5vhQBFfzF7hCGTRc1" } ] } } { remove_failed_tool_call_attempt: { messages: [ BaseMessage { "id": "msg_01HqvhPuubXqerWgYRNFqPrd", "content": "", "additional_kwargs": {}, "response_metadata": {} }, BaseMessage { "id": "502c7399-4d95-4afd-8a86-ece864d2bc7f", "content": "", "additional_kwargs": {}, "response_metadata": {} } ] } } { fallback_agent: { messages: [ AIMessage { "id": "msg_01EQSawL2oxNhph9be99k7Yp", "content": [ { "type": "text", "text": "Certainly! I'd be happy to help you create an incredible haiku about water. To do this, we'll use the master_haiku_generator function, which requires three topics as input. Since you've specified water as the main theme, I'll add two related concepts to create a more vivid and interesting haiku. Let's use \"water,\" \"flow,\" and \"reflection\" as our three topics.\n\nHere's the function call to generate your haiku:" }, { "type": "tool_use", "id": "toolu_017hrp13SsgfdJTdhkJDMaQy", "name": "master_haiku_generator", "input": { "topic": "[Array]" } } ], "additional_kwargs": { "id": "msg_01EQSawL2oxNhph9be99k7Yp", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 422, "output_tokens": 162 } }, "response_metadata": { "id": "msg_01EQSawL2oxNhph9be99k7Yp", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 422, "output_tokens": 162 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "master_haiku_generator", "args": { "topic": "[Array]" }, "id": "toolu_017hrp13SsgfdJTdhkJDMaQy", "type": "tool_call" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 422, "output_tokens": 162, "total_tokens": 584 } } ] } } { tools: { messages: [ ToolMessage { "id": "3d24d291-7501-4a65-9286-10dc47239b5b", "content": "Here is a haiku about water, flow, and reflection:\n\nRippling waters flow,\nMirroring the sky above,\nTranquil reflection.", "name": "master_haiku_generator", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_017hrp13SsgfdJTdhkJDMaQy" } ] } } { agent: { messages: [ AIMessage { "id": "msg_01Jy7Vw8DN77sjVWcB4TcJR6", "content": "I hope you enjoy this haiku about the beauty and serenity of water. Please let me know if you would like me to generate another one.", "additional_kwargs": { "id": "msg_01Jy7Vw8DN77sjVWcB4TcJR6", "type": "message", "role": "assistant", "model": "claude-3-haiku-20240307", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 601, "output_tokens": 35 } }, "response_metadata": { "id": "msg_01Jy7Vw8DN77sjVWcB4TcJR6", "model": "claude-3-haiku-20240307", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 601, "output_tokens": 35 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 601, "output_tokens": 35, "total_tokens": 636 } } ] } } ``` You can see that you get a cleaner response - the more powerful model gets it right on the first try, and the smaller model's failure gets wiped from the graph state. This shorter message history also avoid overpopulating the graph state with attempts. You can also inspect this [LangSmith trace](https://smith.langchain.com/public/c94f95d0-97fc-4d4d-a59a-b5161c2f4a90/r), which shows the failed initial call to the smaller model. ## Next steps You've now seen how to implement some strategies to handle tool calling errors. Next, check out some of the other LangGraph how-to guides here. --- how-tos/wait-user-input.ipynb --- # How to wait for user input !!! tip "Prerequisites" This guide assumes familiarity with the following concepts: * Human-in-the-loop * Breakpoints * LangGraph Glossary Human-in-the-loop (HIL) interactions are crucial for agentic systems. Waiting for human input is a common HIL interaction pattern, allowing the agent to ask the user clarifying questions and await input before proceeding. We can implement these in LangGraph using the `interrupt()` function. `interrupt` allows us to stop graph execution to collect input from a user and continue execution with collected input. ## Setup First we need to install the packages required ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core zod ``` Next, we need to set API keys for Anthropic (the LLM we will use) ```bash export ANTHROPIC_API_KEY=your-api-key ``` Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```bash export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_CALLBACKS_BACKGROUND="true" export LANGCHAIN_API_KEY=your-api-key ``` ## Simple Usage Let's explore a basic example of using human feedback. A straightforward approach is to create a node, **`human_feedback`**, designed specifically to collect user input. This allows us to gather feedback at a specific, chosen point in our graph. Steps: 1. **Call `interrupt()`** inside the **`human_feedback`** node. 2. We set up a checkpointer to save the state of the graph up until this node. 3. **Use `new Command({ resume: ... })`** to provide the requested value to the **`human_feedback`** node and resume execution. ```typescript import { StateGraph, Annotation, START, END, interrupt, MemorySaver } from "@langchain/langgraph"; const StateAnnotation = Annotation.Root({ input: Annotation, userFeedback: Annotation }); const step1 = (_state: typeof StateAnnotation.State) => { console.log("---Step 1---"); return {}; } const humanFeedback = (_state: typeof StateAnnotation.State) => { console.log("--- humanFeedback ---"); const feedback: string = interrupt("Please provide feedback"); return { userFeedback: feedback }; } const step3 = (_state: typeof StateAnnotation.State) => { console.log("---Step 3---"); return {}; } const builder = new StateGraph(StateAnnotation) .addNode("step1", step1) .addNode("humanFeedback", humanFeedback) .addNode("step3", step3) .addEdge(START, "step1") .addEdge("step1", "humanFeedback") .addEdge("humanFeedback", "step3") .addEdge("step3", END); // Set up memory const memory = new MemorySaver() // Add const graph = builder.compile({ checkpointer: memory, }); ``` ```typescript import * as tslab from "tslab"; const drawableGraph = graph.getGraph(); const image = await drawableGraph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` Run until our breakpoint in the `humanFeedback` node: ```typescript // Input const initialInput = { input: "hello world" }; // Thread const config = { configurable: { thread_id: "1" } }; // Run the graph until the first interruption for await (const event of await graph.stream(initialInput, config)) { console.log(event); } // Will log when the graph is interrupted, after step 2. console.log("--- GRAPH INTERRUPTED ---"); ``` ```output ---Step 1--- {} --- humanFeedback --- { __interrupt__: [ { value: 'Please provide feedback', when: 'during', resumable: true, ns: [Array] } ] } --- GRAPH INTERRUPTED --- ``` Now, we can just manually update our graph state with with the user input - ```typescript import { Command } from "@langchain/langgraph"; // Continue the graph execution for await (const event of await graph.stream( new Command({ resume: "go to step 3! "}), config, )) { console.log(event); console.log("\n====\n"); } ``` ```output --- humanFeedback --- { humanFeedback: { userFeedback: 'go to step 3! ' } } ==== ---Step 3--- {} ==== ``` We can see our feedback was added to state - ```typescript (await graph.getState(config)).values ``` ```output { input: 'hello world', userFeedback: 'go to step 3! ' } ``` ## Agent In the context of agents, waiting for user feedback is useful to ask clarifying questions. To show this, we will build a relatively simple ReAct-style agent that does tool calling. We will use Anthropic's models and a mock tool (purely for demonstration purposes). ```typescript // Set up the tool import { ChatAnthropic } from "@langchain/anthropic"; import { tool } from "@langchain/core/tools"; import { StateGraph, MessagesAnnotation, START, END, MemorySaver } from "@langchain/langgraph"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { AIMessage, ToolMessage } from "@langchain/core/messages"; import { z } from "zod"; const search = tool((_) => { return "It's sunny in San Francisco, but you better look out if you're a Gemini 😈."; }, { name: "search", description: "Call to surf the web.", schema: z.string(), }) const tools = [search] const toolNode = new ToolNode(tools) // Set up the model const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20240620" }) const askHumanTool = tool((_) => { return "The human said XYZ"; }, { name: "askHuman", description: "Ask the human for input.", schema: z.string(), }); const modelWithTools = model.bindTools([...tools, askHumanTool]) // Define nodes and conditional edges // Define the function that determines whether to continue or not function shouldContinue(state: typeof MessagesAnnotation.State): "action" | "askHuman" | typeof END { const lastMessage = state.messages[state.messages.length - 1] as AIMessage; // If there is no function call, then we finish if (lastMessage && !lastMessage.tool_calls?.length) { return END; } // If tool call is askHuman, we return that node // You could also add logic here to let some system know that there's something that requires Human input // For example, send a slack message, etc if (lastMessage.tool_calls?.[0]?.name === "askHuman") { console.log("--- ASKING HUMAN ---") return "askHuman"; } // Otherwise if it isn't, we continue with the action node return "action"; } // Define the function that calls the model async function callModel(state: typeof MessagesAnnotation.State): Promise> { const messages = state.messages; const response = await modelWithTools.invoke(messages); // We return an object with a messages property, because this will get added to the existing list return { messages: [response] }; } // We define a fake node to ask the human function askHuman(state: typeof MessagesAnnotation.State): Partial { const lastMessage = state.messages[state.messages.length - 1] as AIMessage; const toolCallId = lastMessage.tool_calls?.[0].id; const location: string = interrupt("Please provide your location:"); const newToolMessage = new ToolMessage({ tool_call_id: toolCallId!, content: location, }) return { messages: [newToolMessage] }; } // Define a new graph const messagesWorkflow = new StateGraph(MessagesAnnotation) // Define the two nodes we will cycle between .addNode("agent", callModel) .addNode("action", toolNode) .addNode("askHuman", askHuman) // We now add a conditional edge .addConditionalEdges( // First, we define the start node. We use `agent`. // This means these are the edges taken after the `agent` node is called. "agent", // Next, we pass in the function that will determine which node is called next. shouldContinue ) // We now add a normal edge from `action` to `agent`. // This means that after `action` is called, `agent` node is called next. .addEdge("action", "agent") // After we get back the human response, we go back to the agent .addEdge("askHuman", "agent") // Set the entrypoint as `agent` // This means that this node is the first one called .addEdge(START, "agent"); // Setup memory const messagesMemory = new MemorySaver(); // Finally, we compile it! // This compiles it into a LangChain Runnable, // meaning you can use it as you would any other runnable const messagesApp = messagesWorkflow.compile({ checkpointer: messagesMemory, }); ``` ```typescript import * as tslab from "tslab"; const drawableGraph2 = messagesApp.getGraph(); const image2 = await drawableGraph2.drawMermaidPng(); const arrayBuffer2 = await image2.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer2)); ``` ## Interacting with the Agent We can now interact with the agent. Let's ask it to ask the user where they are, then tell them the weather. This should make it use the `askHuman` tool first, then use the normal tool. Note that we switch to use `streamMode: "values"` to just return the update messages at each point: ```typescript // Input const input = { role: "user", content: "Use the search tool to ask the user where they are, then look up the weather there", } // Thread const config2 = { configurable: { thread_id: "3" }, streamMode: "values" as const }; for await (const event of await messagesApp.stream({ messages: [input] }, config2)) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg.getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ human Message (1) ================================= Use the search tool to ask the user where they are, then look up the weather there --- ASKING HUMAN --- ================================ ai Message (1) ================================= [ { type: 'text', text: "Certainly! I'll use the askHuman tool to ask the user about their location, and then use the search tool to look up the weather for that location. Let's start by asking the user where they are." }, { type: 'tool_use', id: 'toolu_015UDVFoXcMV7KjRqPY78Umk', name: 'askHuman', input: { input: 'Where are you currently located? Please provide a city and country or region.' } } ] ``` ```typescript console.log("next: ", (await messagesApp.getState(config2)).next) ``` ```output next: [ 'askHuman' ] ``` You can see that our graph got interrupted inside the `askHuman` node, which is now waiting for a `location` to be provided. We can provide this value by invoking the graph with a `new Command({ resume: "" })` input: ```typescript import { Command } from "@langchain/langgraph"; // Continue the graph execution for await (const event of await messagesApp.stream( new Command({ resume: "San Francisco" }), config2, )) { console.log(event); console.log("\n====\n"); } ``` ```output { messages: [ HumanMessage { "id": "cfb461cb-0da1-48b4-acef-e3bf0d3a4e6c", "content": "Use the search tool to ask the user where they are, then look up the weather there", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "content": [ { "type": "text", "text": "Certainly! I'll use the askHuman tool to ask the user about their location, and then use the search tool to look up the weather for that location. Let's start by asking the user where they are." }, { "type": "tool_use", "id": "toolu_015UDVFoXcMV7KjRqPY78Umk", "name": "askHuman", "input": { "input": "Where are you currently located? Please provide a city and country or region." } } ], "additional_kwargs": { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 465, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 112 } }, "response_metadata": { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 465, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 112 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "askHuman", "args": { "input": "Where are you currently located? Please provide a city and country or region." }, "id": "toolu_015UDVFoXcMV7KjRqPY78Umk", "type": "tool_call" } ], "invalid_tool_calls": [] } ] } ==== { messages: [ HumanMessage { "id": "cfb461cb-0da1-48b4-acef-e3bf0d3a4e6c", "content": "Use the search tool to ask the user where they are, then look up the weather there", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "content": [ { "type": "text", "text": "Certainly! I'll use the askHuman tool to ask the user about their location, and then use the search tool to look up the weather for that location. Let's start by asking the user where they are." }, { "type": "tool_use", "id": "toolu_015UDVFoXcMV7KjRqPY78Umk", "name": "askHuman", "input": { "input": "Where are you currently located? Please provide a city and country or region." } } ], "additional_kwargs": { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 465, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 112 } }, "response_metadata": { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 465, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 112 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "askHuman", "args": { "input": "Where are you currently located? Please provide a city and country or region." }, "id": "toolu_015UDVFoXcMV7KjRqPY78Umk", "type": "tool_call" } ], "invalid_tool_calls": [] }, ToolMessage { "id": "0225d971-1756-468e-996d-9af93e608a95", "content": "San Francisco", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_015UDVFoXcMV7KjRqPY78Umk" } ] } ==== { messages: [ HumanMessage { "id": "cfb461cb-0da1-48b4-acef-e3bf0d3a4e6c", "content": "Use the search tool to ask the user where they are, then look up the weather there", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "content": [ { "type": "text", "text": "Certainly! I'll use the askHuman tool to ask the user about their location, and then use the search tool to look up the weather for that location. Let's start by asking the user where they are." }, { "type": "tool_use", "id": "toolu_015UDVFoXcMV7KjRqPY78Umk", "name": "askHuman", "input": { "input": "Where are you currently located? Please provide a city and country or region." } } ], "additional_kwargs": { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 465, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 112 } }, "response_metadata": { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 465, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 112 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "askHuman", "args": { "input": "Where are you currently located? Please provide a city and country or region." }, "id": "toolu_015UDVFoXcMV7KjRqPY78Umk", "type": "tool_call" } ], "invalid_tool_calls": [] }, ToolMessage { "id": "0225d971-1756-468e-996d-9af93e608a95", "content": "San Francisco", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_015UDVFoXcMV7KjRqPY78Umk" }, AIMessage { "id": "msg_01QDw6TTXvFXKPEX4mYjzU8Y", "content": [ { "type": "text", "text": "Thank you for providing your location. Now, I'll use the search tool to look up the weather in San Francisco." }, { "type": "tool_use", "id": "toolu_01AzffiwYncLArNb9SbKHK2a", "name": "search", "input": { "input": "current weather in San Francisco" } } ], "additional_kwargs": { "id": "msg_01QDw6TTXvFXKPEX4mYjzU8Y", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 590, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 81 } }, "response_metadata": { "id": "msg_01QDw6TTXvFXKPEX4mYjzU8Y", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 590, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 81 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "search", "args": { "input": "current weather in San Francisco" }, "id": "toolu_01AzffiwYncLArNb9SbKHK2a", "type": "tool_call" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 590, "output_tokens": 81, "total_tokens": 671 } } ] } ==== { messages: [ HumanMessage { "id": "cfb461cb-0da1-48b4-acef-e3bf0d3a4e6c", "content": "Use the search tool to ask the user where they are, then look up the weather there", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "content": [ { "type": "text", "text": "Certainly! I'll use the askHuman tool to ask the user about their location, and then use the search tool to look up the weather for that location. Let's start by asking the user where they are." }, { "type": "tool_use", "id": "toolu_015UDVFoXcMV7KjRqPY78Umk", "name": "askHuman", "input": { "input": "Where are you currently located? Please provide a city and country or region." } } ], "additional_kwargs": { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 465, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 112 } }, "response_metadata": { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 465, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 112 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "askHuman", "args": { "input": "Where are you currently located? Please provide a city and country or region." }, "id": "toolu_015UDVFoXcMV7KjRqPY78Umk", "type": "tool_call" } ], "invalid_tool_calls": [] }, ToolMessage { "id": "0225d971-1756-468e-996d-9af93e608a95", "content": "San Francisco", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_015UDVFoXcMV7KjRqPY78Umk" }, AIMessage { "id": "msg_01QDw6TTXvFXKPEX4mYjzU8Y", "content": [ { "type": "text", "text": "Thank you for providing your location. Now, I'll use the search tool to look up the weather in San Francisco." }, { "type": "tool_use", "id": "toolu_01AzffiwYncLArNb9SbKHK2a", "name": "search", "input": { "input": "current weather in San Francisco" } } ], "additional_kwargs": { "id": "msg_01QDw6TTXvFXKPEX4mYjzU8Y", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 590, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 81 } }, "response_metadata": { "id": "msg_01QDw6TTXvFXKPEX4mYjzU8Y", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 590, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 81 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "search", "args": { "input": "current weather in San Francisco" }, "id": "toolu_01AzffiwYncLArNb9SbKHK2a", "type": "tool_call" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 590, "output_tokens": 81, "total_tokens": 671 } }, ToolMessage { "id": "1c52e9b5-1d0b-4889-abd0-10572053e1d8", "content": "It's sunny in San Francisco, but you better look out if you're a Gemini 😈.", "name": "search", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_01AzffiwYncLArNb9SbKHK2a" } ] } ==== { messages: [ HumanMessage { "id": "cfb461cb-0da1-48b4-acef-e3bf0d3a4e6c", "content": "Use the search tool to ask the user where they are, then look up the weather there", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "content": [ { "type": "text", "text": "Certainly! I'll use the askHuman tool to ask the user about their location, and then use the search tool to look up the weather for that location. Let's start by asking the user where they are." }, { "type": "tool_use", "id": "toolu_015UDVFoXcMV7KjRqPY78Umk", "name": "askHuman", "input": { "input": "Where are you currently located? Please provide a city and country or region." } } ], "additional_kwargs": { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 465, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 112 } }, "response_metadata": { "id": "msg_01TA2zHbbrenm7KXSUdcFdXD", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 465, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 112 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "askHuman", "args": { "input": "Where are you currently located? Please provide a city and country or region." }, "id": "toolu_015UDVFoXcMV7KjRqPY78Umk", "type": "tool_call" } ], "invalid_tool_calls": [] }, ToolMessage { "id": "0225d971-1756-468e-996d-9af93e608a95", "content": "San Francisco", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_015UDVFoXcMV7KjRqPY78Umk" }, AIMessage { "id": "msg_01QDw6TTXvFXKPEX4mYjzU8Y", "content": [ { "type": "text", "text": "Thank you for providing your location. Now, I'll use the search tool to look up the weather in San Francisco." }, { "type": "tool_use", "id": "toolu_01AzffiwYncLArNb9SbKHK2a", "name": "search", "input": { "input": "current weather in San Francisco" } } ], "additional_kwargs": { "id": "msg_01QDw6TTXvFXKPEX4mYjzU8Y", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 590, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 81 } }, "response_metadata": { "id": "msg_01QDw6TTXvFXKPEX4mYjzU8Y", "model": "claude-3-5-sonnet-20240620", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 590, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 81 }, "type": "message", "role": "assistant" }, "tool_calls": [ { "name": "search", "args": { "input": "current weather in San Francisco" }, "id": "toolu_01AzffiwYncLArNb9SbKHK2a", "type": "tool_call" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 590, "output_tokens": 81, "total_tokens": 671 } }, ToolMessage { "id": "1c52e9b5-1d0b-4889-abd0-10572053e1d8", "content": "It's sunny in San Francisco, but you better look out if you're a Gemini 😈.", "name": "search", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_01AzffiwYncLArNb9SbKHK2a" }, AIMessage { "id": "msg_01UAbWbZ9RVV3L6ABgQWsy9f", "content": "Based on the search results, I can provide you with information about the current weather in San Francisco:\n\nThe weather in San Francisco is currently sunny. This is great news for outdoor activities and enjoying the city's many attractions.\n\nHowever, I should note that the search result included an unusual comment about Geminis. This appears to be unrelated to the weather and might be a joke or reference included in the search results. It's not typical for weather reports to include astrological information, so I'd recommend focusing on the factual weather information provided.\n\nIs there anything else you'd like to know about the weather in San Francisco or any other information you need?", "additional_kwargs": { "id": "msg_01UAbWbZ9RVV3L6ABgQWsy9f", "type": "message", "role": "assistant", "model": "claude-3-5-sonnet-20240620", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 704, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 140 } }, "response_metadata": { "id": "msg_01UAbWbZ9RVV3L6ABgQWsy9f", "model": "claude-3-5-sonnet-20240620", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 704, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 140 }, "type": "message", "role": "assistant" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 704, "output_tokens": 140, "total_tokens": 844 } } ] } ==== ``` **Note:** The `interrupt` function propagates by throwing a special `GraphInterrupt` error. Therefore, you should avoid using `try/catch` blocks around the `interrupt` function - or if you do, ensure that the `GraphInterrupt` error is thrown again within your `catch` block. --- how-tos/breakpoints.ipynb --- # How to add breakpoints !!! tip "Prerequisites" This guide assumes familiarity with the following concepts: * Breakpoints * LangGraph Glossary Human-in-the-loop (HIL) interactions are crucial for agentic systems. Breakpoints are a common HIL interaction pattern, allowing the graph to stop at specific steps and seek human approval before proceeding (e.g., for sensitive actions). Breakpoints are built on top of LangGraph checkpoints, which save the graph's state after each node execution. Checkpoints are saved in threads that preserve graph state and can be accessed after a graph has finished execution. This allows for graph execution to pause at specific points, await human approval, and then resume execution from the last checkpoint. ## Setup First we need to install the packages required ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core ``` Next, we need to set API keys for Anthropic (the LLM we will use) ```bash export ANTHROPIC_API_KEY=your-api-key ``` Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```bash export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_CALLBACKS_BACKGROUND="true" export LANGCHAIN_API_KEY=your-api-key ``` ## Simple Usage Let's look at very basic usage of this. Below, we do two things: 1) We specify the [breakpoint](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#breakpoints) using `interruptBefore` the specified step. 2) We set up a [checkpointer](https://langchain-ai.github.io/langgraphjs/concepts/#checkpoints) to save the state of the graph. ```typescript import { StateGraph, START, END, Annotation } from "@langchain/langgraph"; import { MemorySaver } from "@langchain/langgraph"; const GraphState = Annotation.Root({ input: Annotation }); const step1 = (state: typeof GraphState.State) => { console.log("---Step 1---"); return state; } const step2 = (state: typeof GraphState.State) => { console.log("---Step 2---"); return state; } const step3 = (state: typeof GraphState.State) => { console.log("---Step 3---"); return state; } const builder = new StateGraph(GraphState) .addNode("step1", step1) .addNode("step2", step2) .addNode("step3", step3) .addEdge(START, "step1") .addEdge("step1", "step2") .addEdge("step2", "step3") .addEdge("step3", END); // Set up memory const graphStateMemory = new MemorySaver() const graph = builder.compile({ checkpointer: graphStateMemory, interruptBefore: ["step3"] }); ``` ```typescript import * as tslab from "tslab"; const drawableGraphGraphState = graph.getGraph(); const graphStateImage = await drawableGraphGraphState.drawMermaidPng(); const graphStateArrayBuffer = await graphStateImage.arrayBuffer(); await tslab.display.png(new Uint8Array(graphStateArrayBuffer)); ``` We create a [thread ID](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#threads) for the checkpointer. We run until step 3, as defined with `interruptBefore`. After the user input / approval, [we resume execution](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#breakpoints) by invoking the graph with `null`. ```typescript // Input const initialInput = { input: "hello world" }; // Thread const graphStateConfig = { configurable: { thread_id: "1" }, streamMode: "values" as const }; // Run the graph until the first interruption for await (const event of await graph.stream(initialInput, graphStateConfig)) { console.log(`--- ${event.input} ---`); } // Will log when the graph is interrupted, after step 2. console.log("---GRAPH INTERRUPTED---"); // If approved, continue the graph execution. We must pass `null` as // the input here, or the graph will for await (const event of await graph.stream(null, graphStateConfig)) { console.log(`--- ${event.input} ---`); } ``` ```output --- hello world --- ---Step 1--- --- hello world --- ---Step 2--- --- hello world --- ---GRAPH INTERRUPTED--- ---Step 3--- --- hello world --- ``` ## Agent In the context of agents, breakpoints are useful to manually approve certain agent actions. To show this, we will build a relatively simple ReAct-style agent that does tool calling. We'll add a breakpoint before the `action` node is called. ```typescript // Set up the tool import { ChatAnthropic } from "@langchain/anthropic"; import { tool } from "@langchain/core/tools"; import { StateGraph, START, END } from "@langchain/langgraph"; import { MemorySaver, Annotation } from "@langchain/langgraph"; import { ToolNode } from "@langchain/langgraph/prebuilt"; import { BaseMessage, AIMessage } from "@langchain/core/messages"; import { z } from "zod"; const AgentState = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); const search = tool((_) => { return "It's sunny in San Francisco, but you better look out if you're a Gemini 😈."; }, { name: "search", description: "Call to surf the web.", schema: z.string(), }) const tools = [search] const toolNode = new ToolNode(tools) // Set up the model const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20240620" }) const modelWithTools = model.bindTools(tools) // Define nodes and conditional edges // Define the function that determines whether to continue or not function shouldContinue(state: typeof AgentState.State): "action" | typeof END { const lastMessage = state.messages[state.messages.length - 1]; // If there is no function call, then we finish if (lastMessage && !(lastMessage as AIMessage).tool_calls?.length) { return END; } // Otherwise if there is, we continue return "action"; } // Define the function that calls the model async function callModel(state: typeof AgentState.State): Promise> { const messages = state.messages; const response = await modelWithTools.invoke(messages); // We return an object with a messages property, because this will get added to the existing list return { messages: [response] }; } // Define a new graph const workflow = new StateGraph(AgentState) // Define the two nodes we will cycle between .addNode("agent", callModel) .addNode("action", toolNode) // We now add a conditional edge .addConditionalEdges( // First, we define the start node. We use `agent`. // This means these are the edges taken after the `agent` node is called. "agent", // Next, we pass in the function that will determine which node is called next. shouldContinue ) // We now add a normal edge from `action` to `agent`. // This means that after `action` is called, `agent` node is called next. .addEdge("action", "agent") // Set the entrypoint as `agent` // This means that this node is the first one called .addEdge(START, "agent"); // Setup memory const memory = new MemorySaver(); // Finally, we compile it! // This compiles it into a LangChain Runnable, // meaning you can use it as you would any other runnable const app = workflow.compile({ checkpointer: memory, interruptBefore: ["action"] }); ``` ```typescript import * as tslab from "tslab"; const drawableGraph = app.getGraph(); const image = await drawableGraph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` ## Interacting with the Agent We can now interact with the agent. We see that it stops before calling a tool, because `interruptBefore` is set before the `action` node. ```typescript import { HumanMessage } from "@langchain/core/messages"; // Input const inputs = new HumanMessage("search for the weather in sf now"); // Thread const config = { configurable: { thread_id: "3" }, streamMode: "values" as const }; for await (const event of await app.stream({ messages: [inputs] }, config)) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ human Message (1) ================================= search for the weather in sf now ================================ ai Message (1) ================================= [ { type: 'text', text: "Certainly! I'll search for the current weather in San Francisco for you. Let me use the search function to find this information." }, { type: 'tool_use', id: 'toolu_01R524BmxkEm7Rf5Ss53cqkM', name: 'search', input: { input: 'current weather in San Francisco' } } ] ``` **Resume** We can now call the agent again with no inputs to continue. This will run the tool as requested. Running an interrupted graph with `null` in the inputs means to `proceed as if the interruption didn't occur.` ```typescript for await (const event of await app.stream(null, config)) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ tool Message (1) ================================= It's sunny in San Francisco, but you better look out if you're a Gemini 😈. ================================ ai Message (1) ================================= Based on the search results, I can provide you with information about the current weather in San Francisco: The weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine, which is great for outdoor activities or simply enjoying the city. However, I should note that the search result included an unusual comment about Geminis. This appears to be unrelated to the weather and might be a quirk of the search engine or a reference to something else entirely. For accurate and detailed weather information, it would be best to check a reliable weather service or website. Is there anything else you'd like to know about the weather in San Francisco or any other location? ``` --- how-tos/react-human-in-the-loop.ipynb --- # How to add human-in-the-loop processes to the prebuilt ReAct agent This tutorial will show how to add human-in-the-loop processes to the prebuilt ReAct agent. Please see this tutorial for how to get started with the prebuilt ReAct agent You can add a a breakpoint before tools are called by passing `interruptBefore: ["tools"]` to `createReactAgent`. Note that you need to be using a checkpointer for this to work. ## Setup First, we need to install the required packages. ```bash yarn add @langchain/langgraph @langchain/openai @langchain/core ``` This guide will use OpenAI's GPT-4o model. We will optionally set our API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..." // process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "ReAct Agent with human-in-the-loop: LangGraphJS"; ``` ```output ReAct Agent with human-in-the-loop: LangGraphJS ``` ## Code Now we can use the prebuilt `createReactAgent` function to setup our agent with human-in-the-loop interactions: ```typescript import { ChatOpenAI } from "@langchain/openai"; import { tool } from '@langchain/core/tools'; import { z } from 'zod'; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { MemorySaver } from "@langchain/langgraph"; const model = new ChatOpenAI({ model: "gpt-4o", }); const getWeather = tool((input) => { if (['sf', 'san francisco'].includes(input.location.toLowerCase())) { return 'It\'s always sunny in sf'; } else if (['nyc', 'new york city'].includes(input.location.toLowerCase())) { return 'It might be cloudy in nyc'; } else { throw new Error("Unknown Location"); } }, { name: 'get_weather', description: 'Call to get the current weather in a given location.', schema: z.object({ location: z.string().describe("Location to get the weather for."), }) }) // Here we only save in-memory const memory = new MemorySaver(); const agent = createReactAgent({ llm: model, tools: [getWeather], interruptBefore: ["tools"], checkpointSaver: memory }); ``` ## Usage ```typescript let inputs = { messages: [{ role: "user", content: "what is the weather in SF california?" }] }; let config = { configurable: { thread_id: "1" } }; let stream = await agent.stream(inputs, { ...config, streamMode: "values", }); for await ( const { messages } of stream ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } console.log("-----\n"); } ``` ```output what is the weather in SF california? ----- [ { name: 'get_weather', args: { location: 'SF, California' }, type: 'tool_call', id: 'call_AWgaSjqaYVQN73kL0H4BNn1Q' } ] ----- ``` We can verify that our graph stopped at the right place: ```typescript const state = await agent.getState(config) console.log(state.next) ``` ```output [ 'tools' ] ``` Now we can either approve or edit the tool call before proceeding to the next node. If we wanted to approve the tool call, we would simply continue streaming the graph with `null` input. If we wanted to edit the tool call we need to update the state to have the correct tool call, and then after the update has been applied we can continue. We can try resuming and we will see an error arise: ```typescript stream = await agent.stream(null, { ...config, streamMode: "values", }); for await ( const { messages } of stream ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } console.log("-----\n"); } ``` ```output Error: Unknown Location Please fix your mistakes. ----- [ { name: 'get_weather', args: { location: 'San Francisco, California' }, type: 'tool_call', id: 'call_MfIPKpRDXRL4LcHm1BxwcSTk' } ] ----- ``` This error arose because our tool argument of "SF, California" is not a location our tool recognizes. Let's show how we would edit the tool call to search for "San Francisco" instead of "SF, California" - since our tool as written treats "San Francisco, CA" as an unknown location. We will update the state and then resume streaming the graph and should see no errors arise. Note that the reducer we use for our `messages` channel will replace a messaege only if a message with the exact same ID is used. For that reason we can do `new AiMessage(...)` and instead have to directly modify the last message from the `messages` channel, making sure not to edit its ID. ```typescript // First, lets get the current state const currentState = await agent.getState(config); // Let's now get the last message in the state // This is the one with the tool calls that we want to update let lastMessage = currentState.values.messages[currentState.values.messages.length - 1] // Let's now update the args for that tool call lastMessage.tool_calls[0].args = { location: "San Francisco" } // Let's now call `updateState` to pass in this message in the `messages` key // This will get treated as any other update to the state // It will get passed to the reducer function for the `messages` key // That reducer function will use the ID of the message to update it // It's important that it has the right ID! Otherwise it would get appended // as a new message await agent.updateState(config, { messages: lastMessage }); ``` ```output { configurable: { thread_id: '1', checkpoint_ns: '', checkpoint_id: '1ef6638d-bfbd-61d0-8004-2751c8c3f226' } } ``` ```typescript stream = await agent.stream(null, { ...config, streamMode: "values", }); for await ( const { messages } of stream ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } console.log("-----\n"); } ``` ```output It's always sunny in sf ----- The climate in San Francisco is sunny right now. If you need more specific details, feel free to ask! ----- ``` Fantastic! Our graph updated properly to query the weather in San Francisco and got the correct "The weather in San Francisco is sunny today! " response from the tool. --- how-tos/review-tool-calls.ipynb --- # Review Tool Calls !!! tip "Prerequisites" This guide assumes familiarity with the following concepts: * [Tool calling](https://js.langchain.com/docs/concepts/tool_calling/) * Human-in-the-loop * LangGraph Glossary Human-in-the-loop (HIL) interactions are crucial for [agentic systems](https://langchain-ai.github.io/langgraphjs/concepts/agentic_concepts/#human-in-the-loop). A common pattern is to add some human in the loop step after certain tool calls. These tool calls often lead to either a function call or saving of some information. Examples include: - A tool call to execute SQL, which will then be run by the tool - A tool call to generate a summary, which will then be saved to the State of the graph Note that using tool calls is common **whether actually calling tools or not**. There are typically a few different interactions you may want to do here: 1. Approve the tool call and continue 2. Modify the tool call manually and then continue 3. Give natural language feedback, and then pass that back to the agent instead of continuing We can implement these in LangGraph using the `interrupt()` function. `interrupt` allows us to stop graph execution to collect input from a user and continue execution with collected input: ```typescript function humanReviewNode(state: typeof GraphAnnotation.State) { // this is the value we'll be providing via new Command({ resume: }) const humanReview = interrupt({ question: "Is this correct?", // Surface tool calls for review tool_call, }); const [reviewAction, reviewData] = humanReview; // Approve the tool call and continue if (reviewAction === "continue") { return new Command({ goto: "run_tool" }); } // Modify the tool call manually and then continue if (reviewAction === "update") { const updatedMsg = getUpdatedMsg(reviewData); return new Command({ goto: "run_tool", update: { messages: [updatedMsg] } }); } // Give natural language feedback, and then pass that back to the agent if (reviewAction === "feedback") { const feedbackMsg = getFeedbackMsg(reviewData); return new Command({ goto: "call_llm", update: { messages: [feedbackMsg] } }); } throw new Error("Unreachable"); } ``` ## Setup First we need to install the packages required ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core ``` Next, we need to set API keys for Anthropic (the LLM we will use) ```bash export ANTHROPIC_API_KEY=your-api-key ``` Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```bash export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_CALLBACKS_BACKGROUND="true" export LANGCHAIN_API_KEY=your-api-key ``` ## Simple Usage Let's set up a very simple graph that facilitates this. First, we will have an LLM call that decides what action to take. Then we go to a human node. This node actually doesn't do anything - the idea is that we interrupt before this node and then apply any updates to the state. After that, we check the state and either route back to the LLM or to the correct tool. Let's see this in action! ```typescript import { MessagesAnnotation, StateGraph, START, END, MemorySaver, Command, interrupt } from "@langchain/langgraph"; import { ChatAnthropic } from "@langchain/anthropic"; import { tool } from '@langchain/core/tools'; import { z } from 'zod'; import { AIMessage, ToolMessage } from '@langchain/core/messages'; import { ToolCall } from '@langchain/core/messages/tool'; const weatherSearch = tool((input: { city: string }) => { console.log("----"); console.log(`Searching for: ${input.city}`); console.log("----"); return "Sunny!"; }, { name: 'weather_search', description: 'Search for the weather', schema: z.object({ city: z.string() }) }); const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest" }).bindTools([weatherSearch]); const callLLM = async (state: typeof MessagesAnnotation.State) => { const response = await model.invoke(state.messages); return { messages: [response] }; }; const humanReviewNode = async (state: typeof MessagesAnnotation.State): Promise => { const lastMessage = state.messages[state.messages.length - 1] as AIMessage; const toolCall = lastMessage.tool_calls![lastMessage.tool_calls!.length - 1]; const humanReview = interrupt< { question: string; toolCall: ToolCall; }, { action: string; data: any; }>({ question: "Is this correct?", toolCall: toolCall }); const reviewAction = humanReview.action; const reviewData = humanReview.data; if (reviewAction === "continue") { return new Command({ goto: "run_tool" }); } else if (reviewAction === "update") { const updatedMessage = { role: "ai", content: lastMessage.content, tool_calls: [{ id: toolCall.id, name: toolCall.name, args: reviewData }], id: lastMessage.id }; return new Command({ goto: "run_tool", update: { messages: [updatedMessage] } }); } else if (reviewAction === "feedback") { const toolMessage = new ToolMessage({ name: toolCall.name, content: reviewData, tool_call_id: toolCall.id }) return new Command({ goto: "call_llm", update: { messages: [toolMessage] }}); } throw new Error("Invalid review action"); }; const runTool = async (state: typeof MessagesAnnotation.State) => { const newMessages: ToolMessage[] = []; const tools = { weather_search: weatherSearch }; const lastMessage = state.messages[state.messages.length - 1] as AIMessage; const toolCalls = lastMessage.tool_calls!; for (const toolCall of toolCalls) { const tool = tools[toolCall.name as keyof typeof tools]; const result = await tool.invoke(toolCall.args); newMessages.push(new ToolMessage({ name: toolCall.name, content: result, tool_call_id: toolCall.id })); } return { messages: newMessages }; }; const routeAfterLLM = (state: typeof MessagesAnnotation.State): typeof END | "human_review_node" => { const lastMessage = state.messages[state.messages.length - 1] as AIMessage; if (!lastMessage.tool_calls?.length) { return END; } return "human_review_node"; }; const workflow = new StateGraph(MessagesAnnotation) .addNode("call_llm", callLLM) .addNode("run_tool", runTool) .addNode("human_review_node", humanReviewNode, { ends: ["run_tool", "call_llm"] }) .addEdge(START, "call_llm") .addConditionalEdges( "call_llm", routeAfterLLM, ["human_review_node", END] ) .addEdge("run_tool", "call_llm"); const memory = new MemorySaver(); const graph = workflow.compile({ checkpointer: memory }); ``` ```typescript import * as tslab from "tslab"; const drawableGraph = graph.getGraph(); const image = await drawableGraph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` ## Example with no review Let's look at an example when no review is required (because no tools are called) ```typescript let inputs = { messages: [{ role: "user", content: "hi!" }] }; let config = { configurable: { thread_id: "1" }, streamMode: "values" as const }; let stream = await graph.stream(inputs, config); for await (const event of stream) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ human Message (1) ================================= hi! ================================ ai Message (1) ================================= Hello! I'm here to help you. I can assist you with checking the weather for different cities. Would you like to know the weather for a specific location? Just let me know which city you're interested in and I'll look that up for you. ``` If we check the state, we can see that it is finished, since there are no next steps to take: ```typescript let state = await graph.getState(config); console.log(state.next); ``` ```output [] ``` ## Example of approving tool Let's now look at what it looks like to approve a tool call ```typescript inputs = { messages: [{ role: "user", content: "what's the weather in SF?" }] }; config = { configurable: { thread_id: "2" }, streamMode: "values" as const }; stream = await graph.stream(inputs, config); for await (const event of stream) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ human Message (1) ================================= what's the weather in SF? ================================ ai Message (1) ================================= [ { type: 'text', text: 'Let me check the weather in San Francisco for you.' }, { type: 'tool_use', id: 'toolu_01PTn9oqTP6EdFabfhfvELuy', name: 'weather_search', input: { city: 'San Francisco' } } ] ``` If we now check, we can see that it is waiting on human review ```typescript state = await graph.getState(config); console.log(state.next); ``` ```output [ 'human_review_node' ] ``` To approve the tool call, we can just continue the thread with no edits. To do so, we need to let `human_review_node` know what value to use for the `human_review` variable we defined inside the node. We can provide this value by invoking the graph with a `new Command({ resume: })` input. Since we're approving the tool call, we'll provide `resume` value of `{ action: "continue" }` to navigate to `run_tool` node: ```typescript import { Command } from "@langchain/langgraph"; for await (const event of await graph.stream( new Command({ resume: { action: "continue" } }), config )) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ ai Message (1) ================================= [ { type: 'text', text: 'Let me check the weather in San Francisco for you.' }, { type: 'tool_use', id: 'toolu_01PTn9oqTP6EdFabfhfvELuy', name: 'weather_search', input: { city: 'San Francisco' } } ] ---- Searching for: San Francisco ---- ================================ tool Message (1) ================================= Sunny! ================================ ai Message (1) ================================= It's sunny in San Francisco right now! ``` ## Edit Tool Call Let's now say we want to edit the tool call. E.g. change some of the parameters (or even the tool called!) but then execute that tool. ```typescript inputs = { messages: [{ role: "user", content: "what's the weather in SF?" }] }; config = { configurable: { thread_id: "3" }, streamMode: "values" as const }; stream = await graph.stream(inputs, config); for await (const event of stream) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ human Message (1) ================================= what's the weather in SF? ================================ ai Message (1) ================================= [ { type: 'text', text: 'Let me check the weather in San Francisco for you.' }, { type: 'tool_use', id: 'toolu_01T7ykQ45XyGpzRB7MkPtSAE', name: 'weather_search', input: { city: 'San Francisco' } } ] ``` If we now check, we can see that it is waiting on human review ```typescript state = await graph.getState(config); console.log(state.next); ``` ```output [ 'human_review_node' ] ``` To edit the tool call, we will use `Command` with a different resume value of `{ action: "update", data: }`. This will do the following: * combine existing tool call with user-provided tool call arguments and update the existing AI message with the new tool call * navigate to `run_tool` node with the updated AI message and continue execution ```typescript for await (const event of await graph.stream( new Command({ resume: { action: "update", data: { city: "San Francisco" } } }), config )) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ ai Message (1) ================================= [ { type: 'text', text: 'Let me check the weather in San Francisco for you.' }, { type: 'tool_use', id: 'toolu_01T7ykQ45XyGpzRB7MkPtSAE', name: 'weather_search', input: { city: 'San Francisco' } } ] ================================ ai Message (1) ================================= [ { type: 'text', text: 'Let me check the weather in San Francisco for you.' }, { type: 'tool_use', id: 'toolu_01T7ykQ45XyGpzRB7MkPtSAE', name: 'weather_search', input: { city: 'San Francisco' } } ] ---- Searching for: San Francisco ---- ================================ tool Message (1) ================================= Sunny! ================================ ai Message (1) ================================= It's sunny in San Francisco right now! ``` ## Give feedback to a tool call Sometimes, you may not want to execute a tool call, but you also may not want to ask the user to manually modify the tool call. In that case it may be better to get natural language feedback from the user. You can then insert these feedback as a mock **RESULT** of the tool call. There are multiple ways to do this: 1. You could add a new message to the state (representing the "result" of a tool call) 2. You could add TWO new messages to the state - one representing an "error" from the tool call, other HumanMessage representing the feedback Both are similar in that they involve adding messages to the state. The main difference lies in the logic AFTER the `human_node` and how it handles different types of messages. For this example we will just add a single tool call representing the feedback. Let's see this in action! ```typescript inputs = { messages: [{ role: "user", content: "what's the weather in SF?" }] }; config = { configurable: { thread_id: "4" }, streamMode: "values" as const }; stream = await graph.stream(inputs, config); for await (const event of stream) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ human Message (1) ================================= what's the weather in SF? ================================ ai Message (1) ================================= [ { type: 'text', text: "I'll help you check the weather in San Francisco." }, { type: 'tool_use', id: 'toolu_014cwxD65wDwQdNg6xqsticF', name: 'weather_search', input: { city: 'SF' } } ] ``` If we now check, we can see that it is waiting on human review ```typescript state = await graph.getState(config); console.log(state.next); ``` ```output [ 'human_review_node' ] ``` To give feedback about the tool call, we will use `Command` with a different resume value of `{ action: "feedback", data: }`. This will do the following: * create a new tool message that combines existing tool call from LLM with the with user-provided feedback as content * navigate to `call_llm` node with the updated tool message and continue execution ```typescript for await (const event of await graph.stream( new Command({ resume: { action: "feedback", data: "User requested changes: use format for location" } }), config )) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ ai Message (1) ================================= [ { type: 'text', text: "I'll help you check the weather in San Francisco." }, { type: 'tool_use', id: 'toolu_014cwxD65wDwQdNg6xqsticF', name: 'weather_search', input: { city: 'SF' } } ] ================================ tool Message (1) ================================= User requested changes: use format for location ================================ ai Message (1) ================================= [ { type: 'text', text: 'I apologize for the error. Let me search again with the proper format.' }, { type: 'tool_use', id: 'toolu_01Jnm7sSZsiwv65YM4KsvfXk', name: 'weather_search', input: { city: 'San Francisco, USA' } } ] ``` We can see that we now get to another breakpoint - because it went back to the model and got an entirely new prediction of what to call. Let's now approve this one and continue. ```typescript state = await graph.getState(config); console.log(state.next); ``` ```output [ 'human_review_node' ] ``` ```typescript for await (const event of await graph.stream( new Command({ resume: { action: "continue", } }), config )) { const recentMsg = event.messages[event.messages.length - 1]; console.log(`================================ ${recentMsg._getType()} Message (1) =================================`) console.log(recentMsg.content); } ``` ```output ================================ ai Message (1) ================================= [ { type: 'text', text: 'I apologize for the error. Let me search again with the proper format.' }, { type: 'tool_use', id: 'toolu_01Jnm7sSZsiwv65YM4KsvfXk', name: 'weather_search', input: { city: 'San Francisco, USA' } } ] ---- Searching for: San Francisco, USA ---- ================================ tool Message (1) ================================= Sunny! ================================ ai Message (1) ================================= The weather in San Francisco is currently sunny! ``` --- how-tos/stream-updates.ipynb --- # How to stream state updates of your graph LangGraph supports multiple streaming modes. The main ones are: - `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called. - `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called. This guide covers `streamMode="updates"`. ```typescript // process.env.OPENAI_API_KEY = "sk-..."; ``` ## Define the state The state is the interface for all of the nodes in our graph. ```typescript import { Annotation } from "@langchain/langgraph"; import { BaseMessage } from "@langchain/core/messages"; const StateAnnotation = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); ``` ## Set up the tools We will first define the tools we want to use. For this simple example, we will use create a placeholder search engine. However, it is really easy to create your own tools - see documentation [here](https://js.langchain.com/docs/how_to/custom_tools) on how to do that. ```typescript import { tool } from "@langchain/core/tools"; import { z } from "zod"; const searchTool = tool(async ({ query: _query }: { query: string }) => { // This is a placeholder for the actual implementation return "Cold, with a low of 3℃"; }, { name: "search", description: "Use to surf the web, fetch current information, check the weather, and retrieve other information.", schema: z.object({ query: z.string().describe("The query to use in your search."), }), }); await searchTool.invoke({ query: "What's the weather like?" }); const tools = [searchTool]; ``` We can now wrap these tools in a simple ToolNode. This object will actually run the tools (functions) whenever they are invoked by our LLM. ```typescript import { ToolNode } from "@langchain/langgraph/prebuilt"; const toolNode = new ToolNode(tools); ``` ## Set up the model Now we will load the [chat model](https://js.langchain.com/docs/concepts/chat_models/). 1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them. 2. It should work with [tool calling](https://js.langchain.com/docs/how_to/tool_calling/#passing-tools-to-llms), meaning it can return function arguments in its response.

Note

These model requirements are not general requirements for using LangGraph - they are just requirements for this one example.

```typescript import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o" }); ``` After we've done this, we should make sure the model knows that it has these tools available to call. We can do this by calling [bindTools](https://v01.api.js.langchain.com/classes/langchain_core_language_models_chat_models.BaseChatModel.html#bindTools). ```typescript const boundModel = model.bindTools(tools); ``` ## Define the graph We can now put it all together. ```typescript import { END, START, StateGraph } from "@langchain/langgraph"; import { AIMessage } from "@langchain/core/messages"; const routeMessage = (state: typeof StateAnnotation.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as AIMessage; // If no tools are called, we can finish (respond to the user) if (!lastMessage?.tool_calls?.length) { return END; } // Otherwise if there is, we continue and call the tools return "tools"; }; const callModel = async ( state: typeof StateAnnotation.State, ) => { const { messages } = state; const responseMessage = await boundModel.invoke(messages); return { messages: [responseMessage] }; }; const workflow = new StateGraph(StateAnnotation) .addNode("agent", callModel) .addNode("tools", toolNode) .addEdge(START, "agent") .addConditionalEdges("agent", routeMessage) .addEdge("tools", "agent"); const graph = workflow.compile(); ``` ## Stream updates We can now interact with the agent. ```typescript let inputs = { messages: [{ role: "user", content: "what's the weather in sf" }] }; for await ( const chunk of await graph.stream(inputs, { streamMode: "updates", }) ) { for (const [node, values] of Object.entries(chunk)) { console.log(`Receiving update from node: ${node}`); console.log(values); console.log("\n====\n"); } } ``` ```output Receiving update from node: agent { messages: [ AIMessage { "id": "chatcmpl-9y654VypbD3kE1xM8v4xaAHzZEOXa", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_OxlOhnROermwae2LPs9SanmD", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 17, "promptTokens": 70, "totalTokens": 87 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_3aa7262c27" }, "tool_calls": [ { "name": "search", "args": { "query": "current weather in San Francisco" }, "type": "tool_call", "id": "call_OxlOhnROermwae2LPs9SanmD" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 70, "output_tokens": 17, "total_tokens": 87 } } ] } ==== Receiving update from node: tools { messages: [ ToolMessage { "content": "Cold, with a low of 3℃", "name": "search", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_OxlOhnROermwae2LPs9SanmD" } ] } ==== Receiving update from node: agent { messages: [ AIMessage { "id": "chatcmpl-9y654dZ0zzZhPYm6lb36FkG1Enr3p", "content": "It looks like it's currently quite cold in San Francisco, with a low temperature of around 3°C. Make sure to dress warmly!", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "completionTokens": 28, "promptTokens": 103, "totalTokens": 131 }, "finish_reason": "stop", "system_fingerprint": "fp_3aa7262c27" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 103, "output_tokens": 28, "total_tokens": 131 } } ] } ==== ``` --- how-tos/multi-agent-network-functional.ipynb --- # How to build a multi-agent network (functional API) !!! info "Prerequisites" This guide assumes familiarity with the following: - Multi-agent systems - Functional API - Command - LangGraph Glossary In this how-to guide we will demonstrate how to implement a multi-agent network architecture where each agent can communicate with every other agent (many-to-many connections) and can decide which agent to call next. We will be using LangGraph's functional API — individual agents will be defined as tasks and the agent handoffs will be defined in the main entrypoint(): ```ts import { entrypoint, task } from "@langchain/langgraph"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { tool } from "@langchain/core/tools"; import { z } from "zod"; // Define a tool to signal intent to hand off to a different agent const transferToHotelAdvisor = tool(async () => { return "Successfully transferred to hotel advisor"; }, { name: "transferToHotelAdvisor", description: "Ask hotel advisor agent for help.", schema: z.object({}), returnDirect: true, }); // define an agent const travelAdvisorTools = [transferToHotelAdvisor, ...]; const travelAdvisor = createReactAgent({ llm: model, tools: travelAdvisorTools, }); // define a task that calls an agent const callTravelAdvisor = task("callTravelAdvisor", async (messages: BaseMessage[]) => { const response = travelAdvisor.invoke({ messages }); return response.messages; }); const networkGraph = entrypoint( { name: "networkGraph" }, async (messages: BaseMessageLike[]) => { let callActiveAgent = callTravelAdvisor; let agentMessages; while (true) { agentMessages = await callActiveAgent(messages); messages = addMessages(messages, agentMessages); callActiveAgent = getNextAgent(messages); } return messages; }); ``` ## Setup !!! note Compatibility This guide requires `@langchain/langgraph>=0.2.42`. First, install the required dependencies for this example: ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core zod ``` Next, we need to set API keys for Anthropic (the LLM we will use): ```typescript process.env.ANTHROPIC_API_KEY = "YOUR_API_KEY"; ``` !!! tip "Set up [LangSmith](https://smith.langchain.com) for LangGraph development" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com) ## Travel agent example In this example we will build a team of travel assistant agents that can communicate with each other. We will create 2 agents: * `travelAdvisor`: can help with travel destination recommendations. Can ask `hotelAdvisor` for help. * `hotelAdvisor`: can help with hotel recommendations. Can ask `travelAdvisor` for help. This is a fully-connected network - every agent can talk to any other agent. First, let's create some of the tools that the agents will be using: ```typescript import { tool } from "@langchain/core/tools"; import { z } from "zod"; // Tool for getting travel recommendations const getTravelRecommendations = tool(async () => { const destinations = ["aruba", "turks and caicos"]; return destinations[Math.floor(Math.random() * destinations.length)]; }, { name: "getTravelRecommendations", description: "Get recommendation for travel destinations", schema: z.object({}), }); // Tool for getting hotel recommendations const getHotelRecommendations = tool(async (input: { location: "aruba" | "turks and caicos" }) => { const recommendations = { "aruba": [ "The Ritz-Carlton, Aruba (Palm Beach)", "Bucuti & Tara Beach Resort (Eagle Beach)" ], "turks and caicos": ["Grace Bay Club", "COMO Parrot Cay"] }; return recommendations[input.location]; }, { name: "getHotelRecommendations", description: "Get hotel recommendations for a given destination.", schema: z.object({ location: z.enum(["aruba", "turks and caicos"]) }), }); // Define a tool to signal intent to hand off to a different agent // Note: this is not using Command(goto) syntax for navigating to different agents: // `workflow()` below handles the handoffs explicitly const transferToHotelAdvisor = tool(async () => { return "Successfully transferred to hotel advisor"; }, { name: "transferToHotelAdvisor", description: "Ask hotel advisor agent for help.", schema: z.object({}), // Hint to our agent implementation that it should stop // immediately after invoking this tool returnDirect: true, }); const transferToTravelAdvisor = tool(async () => { return "Successfully transferred to travel advisor"; }, { name: "transferToTravelAdvisor", description: "Ask travel advisor agent for help.", schema: z.object({}), // Hint to our agent implementation that it should stop // immediately after invoking this tool returnDirect: true, }); ``` !!! note "Transfer tools" You might have noticed that we're using `tool(... { returnDirect: true })` in the transfer tools. This is done so that individual agents (e.g., `travelAdvisor`) can exit the ReAct loop early once these tools are called without calling the model a final time to process the result of the tool call. This is the desired behavior, as we want to detect when the agent calls this tool and hand control off _immediately_ to a different agent. **NOTE**: This is meant to work with the prebuilt `createReactAgent` - if you are building a custom agent, make sure to manually add logic for handling early exit for tools that are marked with `returnDirect`. Now let's define our agent tasks and combine them into a single multi-agent network workflow: ```typescript import { AIMessage, type BaseMessageLike } from "@langchain/core/messages"; import { ChatAnthropic } from "@langchain/anthropic"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { addMessages, entrypoint, task, } from "@langchain/langgraph"; const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest", }); const travelAdvisorTools = [ getTravelRecommendations, transferToHotelAdvisor, ]; // Define travel advisor ReAct agent const travelAdvisor = createReactAgent({ llm: model, tools: travelAdvisorTools, stateModifier: [ "You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc).", "If you need hotel recommendations, ask 'hotel_advisor' for help.", "You MUST include human-readable response before transferring to another agent.", ].join(" "), }); // You can also add additional logic like changing the input to the agent / output from the agent, etc. // NOTE: we're invoking the ReAct agent with the full history of messages in the state const callTravelAdvisor = task("callTravelAdvisor", async (messages: BaseMessageLike[]) => { const response = await travelAdvisor.invoke({ messages }); return response.messages; }); const hotelAdvisorTools = [ getHotelRecommendations, transferToTravelAdvisor, ]; // Define hotel advisor ReAct agent const hotelAdvisor = createReactAgent({ llm: model, tools: hotelAdvisorTools, stateModifier: [ "You are a hotel expert that can provide hotel recommendations for a given destination.", "If you need help picking travel destinations, ask 'travel_advisor' for help.", "You MUST include a human-readable response before transferring to another agent." ].join(" "), }); // Add task for hotel advisor const callHotelAdvisor = task("callHotelAdvisor", async (messages: BaseMessageLike[]) => { const response = await hotelAdvisor.invoke({ messages }); return response.messages; }); const networkGraph = entrypoint( "networkGraph", async (messages: BaseMessageLike[]) => { // Converts inputs to LangChain messages as a side-effect let currentMessages = addMessages([], messages); let callActiveAgent = callTravelAdvisor; while (true) { const agentMessages = await callActiveAgent(currentMessages); currentMessages = addMessages(currentMessages, agentMessages); // Find the last AI message // If one of the handoff tools is called, the last message returned // by the agent will be a ToolMessage because we set them to have // "returnDirect: true". This means that the last AIMessage will // have tool calls. // Otherwise, the last returned message will be an AIMessage with // no tool calls, which means we are ready for new input. const aiMsg = [...agentMessages].reverse() .find((m): m is AIMessage => m.getType() === "ai"); // If no tool calls, we're done if (!aiMsg?.tool_calls?.length) { break; } // Get the last tool call and determine next agent const toolCall = aiMsg.tool_calls.at(-1)!; if (toolCall.name === "transferToTravelAdvisor") { callActiveAgent = callTravelAdvisor; } else if (toolCall.name === "transferToHotelAdvisor") { callActiveAgent = callHotelAdvisor; } else { throw new Error(`Expected transfer tool, got '${toolCall.name}'`); } } return messages; }); ``` Lastly, let's define a helper to render the agent outputs: ```typescript const prettyPrintMessages = (update: Record) => { // Handle tuple case with namespace if (Array.isArray(update)) { const [ns, updateData] = update; // Skip parent graph updates in the printouts if (ns.length === 0) { return; } const graphId = ns[ns.length - 1].split(":")[0]; console.log(`Update from subgraph ${graphId}:\n`); update = updateData; } if (update.__metadata__?.cached) { return; } // Print updates for each node for (const [nodeName, updateValue] of Object.entries(update)) { console.log(`Update from node ${nodeName}:\n`); const coercedMessages = addMessages([], updateValue.messages); for (const message of coercedMessages) { const textContent = typeof message.content === "string" ? message.content : JSON.stringify(message.content); // Print message content based on role if (message.getType() === "ai") { console.log("=".repeat(33) + " Assistant Message " + "=".repeat(33)); console.log(textContent); console.log(); } else if (message.getType() === "human") { console.log("=".repeat(33) + " Human Message " + "=".repeat(33)); console.log(textContent); console.log(); } else if (message.getType() === "tool") { console.log("=".repeat(33) + " Tool Message " + "=".repeat(33)); console.log(textContent); console.log(); } } console.log("\n"); } }; ``` Let's test it out: ```typescript const stream = await networkGraph.stream([{ role: "user", content: "i wanna go somewhere warm in the caribbean. pick one destination and give me hotel recommendations" }], { subgraphs: true }) for await (const chunk of stream) { prettyPrintMessages(chunk); } ``` ```output Update from subgraph callTravelAdvisor: Update from node agent: ================================= Assistant Message ================================= [{"type":"text","text":"I'll help you find a warm Caribbean destination and then get specific hotel recommendations for you.\n\nLet me first get some destination recommendations for the Caribbean region."},{"type":"tool_use","id":"toolu_019fN1etkqtCSausSv8XufhL","name":"getTravelRecommendations","input":{}}] Update from subgraph callTravelAdvisor: Update from node tools: ================================= Tool Message ================================= turks and caicos Update from subgraph callTravelAdvisor: Update from node agent: ================================= Assistant Message ================================= [{"type":"text","text":"Great! I recommend Turks and Caicos for your Caribbean getaway. This beautiful British Overseas Territory is known for its stunning white-sand beaches, crystal-clear turquoise waters, and perfect warm weather year-round. Grace Bay Beach in Providenciales (often called \"Provo\") is consistently ranked among the world's best beaches. The islands offer excellent snorkeling, diving, and water sports opportunities, plus a relaxed Caribbean atmosphere.\n\nNow, let me connect you with our hotel advisor to get specific accommodation recommendations for Turks and Caicos."},{"type":"tool_use","id":"toolu_01UHAnBBK9zm2nAEh7brR7TY","name":"transferToHotelAdvisor","input":{}}] Update from subgraph callTravelAdvisor: Update from node tools: ================================= Tool Message ================================= Successfully transferred to hotel advisor Update from subgraph callHotelAdvisor: Update from node agent: ================================= Assistant Message ================================= [{"type":"text","text":"Let me get some hotel recommendations for Turks and Caicos:"},{"type":"tool_use","id":"toolu_012GUHBGXxyzwE5dY6nePq9s","name":"getHotelRecommendations","input":{"location":"turks and caicos"}}] Update from subgraph callHotelAdvisor: Update from node tools: ================================= Tool Message ================================= [ "Grace Bay Club", "COMO Parrot Cay" ] Update from subgraph callHotelAdvisor: Update from node agent: ================================= Assistant Message ================================= Based on the recommendations, here are two excellent options in Turks and Caicos: 1. Grace Bay Club: This luxurious resort is located on the world-famous Grace Bay Beach. It offers all-oceanfront suites, exceptional dining options, and top-notch amenities including multiple pools, a spa, and various water sports activities. The resort is perfect for both couples and families, with adult-only and family-friendly sections. 2. COMO Parrot Cay: This exclusive private island resort offers the ultimate luxury experience. Located on its own island, it features pristine beaches, world-class spa treatments, and exceptional dining. The resort is known for its privacy, making it a favorite among celebrities. The rooms and villas offer sophisticated design with private pools and direct beach access. Would you like more specific information about either of these properties or shall I search for additional options? ``` Voila - `travelAdvisor` picks a destination and then makes a decision to call `hotelAdvisor` for more info! --- how-tos/react-system-prompt.ipynb --- # How to add a custom system prompt to the prebuilt ReAct agent This tutorial will show how to add a custom system prompt to the prebuilt ReAct agent. Please see this tutorial for how to get started with the prebuilt ReAct agent You can add a custom system prompt by passing a string to the `stateModifier` param.

Compatibility

The stateModifier parameter was added in @langchain/langgraph>=0.2.27.
If you are on an older version, you will need to use the deprecated messageModifier parameter.
For help upgrading, see this guide.

## Setup First, we need to install the required packages. ```bash yarn add @langchain/langgraph @langchain/openai @langchain/core ``` This guide will use OpenAI's GPT-4o model. We will optionally set our API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..." // process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "ReAct Agent with system prompt: LangGraphJS"; ``` ```output ReAct Agent with system prompt: LangGraphJS ``` ## Code Now we can use the prebuilt `createReactAgent` function to setup our agent with a system prompt: ```typescript import { ChatOpenAI } from "@langchain/openai"; import { tool } from '@langchain/core/tools'; import { z } from 'zod'; import { createReactAgent } from "@langchain/langgraph/prebuilt"; const model = new ChatOpenAI({ model: "gpt-4o", }); const getWeather = tool((input) => { if (input.location === 'sf') { return 'It\'s always sunny in sf'; } else { return 'It might be cloudy in nyc'; } }, { name: 'get_weather', description: 'Call to get the current weather.', schema: z.object({ location: z.enum(['sf','nyc']).describe("Location to get the weather for."), }) }) // We can add our system prompt here const prompt = "Respond in Italian" const agent = createReactAgent({ llm: model, tools: [getWeather], stateModifier: prompt }); ``` ## Usage Let's verify that the agent does indeed respond in Italian! ```typescript let inputs = { messages: [{ role: "user", content: "what is the weather in NYC?" }] }; let stream = await agent.stream(inputs, { streamMode: "values", }); for await ( const { messages } of stream ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output what is the weather in NYC? ----- [ { name: 'get_weather', args: { location: 'nyc' }, type: 'tool_call', id: 'call_PqmKDQrefHQLmGsZSSr4C7Fc' } ] ----- It might be cloudy in nyc ----- A New York potrebbe essere nuvoloso. Hai altre domande o posso aiutarti in qualcos'altro? ----- ``` --- how-tos/review-tool-calls-functional.ipynb --- # How to review tool calls (Functional API) !!! info "Prerequisites" This guide assumes familiarity with the following: - Implementing human-in-the-loop workflows with interrupt - How to create a ReAct agent using the Functional API This guide demonstrates how to implement human-in-the-loop workflows in a ReAct agent using the LangGraph Functional API. We will build off of the agent created in the How to create a ReAct agent using the Functional API guide. Specifically, we will demonstrate how to review [tool calls](https://js.langchain.com/docs/concepts/tool_calling/) generated by a [chat model](https://js.langchain.com/docs/concepts/chat_models/) prior to their execution. This can be accomplished through use of the interrupt function at key points in our application. **Preview**: We will implement a simple function that reviews tool calls generated from our chat model and call it from inside our application's entrypoint: ```ts function reviewToolCall(toolCall: ToolCall): ToolCall | ToolMessage { // Interrupt for human review const humanReview = interrupt({ question: "Is this correct?", tool_call: toolCall, }); const { action, data } = humanReview; if (action === "continue") { return toolCall; } else if (action === "update") { return { ...toolCall, args: data, }; } else if (action === "feedback") { return new ToolMessage({ content: data, name: toolCall.name, tool_call_id: toolCall.id, }); } throw new Error(`Unsupported review action: ${action}`); } ``` ## Setup !!! note Compatibility This guide requires `@langchain/langgraph>=0.2.42`. First, install the required dependencies for this example: ```bash npm install @langchain/langgraph @langchain/openai @langchain/core zod ``` Next, we need to set API keys for OpenAI (the LLM we will use): ```typescript process.env.OPENAI_API_KEY = "YOUR_API_KEY"; ``` !!! tip "Set up [LangSmith](https://smith.langchain.com) for LangGraph development" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com) ## Define model and tools Let's first define the tools and model we will use for our example. As in the ReAct agent guide, we will use a single place-holder tool that gets a description of the weather for a location. We will use an [OpenAI](https://js.langchain.com/docs/integrations/providers/openai/) chat model for this example, but any model [supporting tool-calling](https://js.langchain.com/docs/integrations/chat/) will suffice. ```typescript import { ChatOpenAI } from "@langchain/openai"; import { tool } from "@langchain/core/tools"; import { z } from "zod"; const model = new ChatOpenAI({ model: "gpt-4o-mini", }); const getWeather = tool(async ({ location }) => { // This is a placeholder for the actual implementation const lowercaseLocation = location.toLowerCase(); if (lowercaseLocation.includes("sf") || lowercaseLocation.includes("san francisco")) { return "It's sunny!"; } else if (lowercaseLocation.includes("boston")) { return "It's rainy!"; } else { return `I am not sure what the weather is in ${location}`; } }, { name: "getWeather", schema: z.object({ location: z.string().describe("Location to get the weather for"), }), description: "Call to get the weather from a specific location.", }); const tools = [getWeather]; ``` ## Define tasks Our tasks are unchanged from the ReAct agent guide: 1. **Call model**: We want to query our chat model with a list of messages. 2. **Call tool**: If our model generates tool calls, we want to execute them. ```typescript import { type BaseMessageLike, AIMessage, ToolMessage, } from "@langchain/core/messages"; import { type ToolCall } from "@langchain/core/messages/tool"; import { task } from "@langchain/langgraph"; const toolsByName = Object.fromEntries(tools.map((tool) => [tool.name, tool])); const callModel = task("callModel", async (messages: BaseMessageLike[]) => { const response = await model.bindTools(tools).invoke(messages); return response; }); const callTool = task( "callTool", async (toolCall: ToolCall): Promise => { const tool = toolsByName[toolCall.name]; const observation = await tool.invoke(toolCall.args); return new ToolMessage({ content: observation, tool_call_id: toolCall.id }); // Can also pass toolCall directly into the tool to return a ToolMessage // return tool.invoke(toolCall); }); ``` ## Define entrypoint To review tool calls before execution, we add a `reviewToolCalls` function that calls interrupt. When this function is called, execution will be paused until we issue a command to resume it. Given a tool call, our function will `interrupt` for human review. At that point we can either: - Accept the tool call; - Revise the tool call and continue; - Generate a custom tool message (e.g., instructing the model to re-format its tool call). We will demonstrate these three cases in the usage examples below. ```typescript import { interrupt } from "@langchain/langgraph"; function reviewToolCall(toolCall: ToolCall): ToolCall | ToolMessage { // Interrupt for human review const humanReview = interrupt({ question: "Is this correct?", tool_call: toolCall, }); const { action, data } = humanReview; if (action === "continue") { return toolCall; } else if (action === "update") { return { ...toolCall, args: data, }; } else if (action === "feedback") { return new ToolMessage({ content: data, name: toolCall.name, tool_call_id: toolCall.id, }); } throw new Error(`Unsupported review action: ${action}`); } ``` We can now update our entrypoint to review the generated tool calls. If a tool call is accepted or revised, we execute in the same way as before. Otherwise, we just append the `ToolMessage` supplied by the human. !!! tip The results of prior tasks — in this case the initial model call — are persisted, so that they are not run again following the `interrupt`. ```typescript import { MemorySaver, addMessages, entrypoint, getPreviousState, } from "@langchain/langgraph"; const checkpointer = new MemorySaver(); const agent = entrypoint({ checkpointer, name: "agent", }, async (messages: BaseMessageLike[]) => { const previous = getPreviousState() ?? []; let currentMessages = addMessages(previous, messages); let llmResponse = await callModel(currentMessages); while (true) { if (!llmResponse.tool_calls?.length) { break; } // Review tool calls const toolResults: ToolMessage[] = []; const toolCalls: ToolCall[] = []; for (let i = 0; i < llmResponse.tool_calls.length; i++) { const review = await reviewToolCall(llmResponse.tool_calls[i]); if (review instanceof ToolMessage) { toolResults.push(review); } else { // is a validated tool call toolCalls.push(review); if (review !== llmResponse.tool_calls[i]) { llmResponse.tool_calls[i] = review; } } } // Execute remaining tool calls const remainingToolResults = await Promise.all( toolCalls.map((toolCall) => callTool(toolCall)) ); // Append to message list currentMessages = addMessages( currentMessages, [llmResponse, ...toolResults, ...remainingToolResults] ); // Call model again llmResponse = await callModel(currentMessages); } // Generate final response currentMessages = addMessages(currentMessages, llmResponse); return entrypoint.final({ value: llmResponse, save: currentMessages }); }); ``` ### Usage Let's demonstrate some scenarios. ```typescript import { BaseMessage, isAIMessage } from "@langchain/core/messages"; const prettyPrintMessage = (message: BaseMessage) => { console.log("=".repeat(30), `${message.getType()} message`, "=".repeat(30)); console.log(message.content); if (isAIMessage(message) && message.tool_calls?.length) { console.log(JSON.stringify(message.tool_calls, null, 2)); } } const printStep = (step: Record) => { if (step.__metadata__?.cached) { return; } for (const [taskName, result] of Object.entries(step)) { if (taskName === "agent") { continue; // just stream from tasks } console.log(`\n${taskName}:`); if (taskName === "__interrupt__" || taskName === "reviewToolCall") { console.log(JSON.stringify(result, null, 2)); } else { prettyPrintMessage(result); } } }; ``` ### Accept a tool call To accept a tool call, we just indicate in the data we provide in the `Command` that the tool call should pass through. ```typescript const config = { configurable: { thread_id: "1" } }; const userMessage = { role: "user", content: "What's the weather in san francisco?" }; console.log(userMessage); const stream = await agent.stream([userMessage], config); for await (const step of stream) { printStep(step); } ``` ```output { role: 'user', content: "What's the weather in san francisco?" } ``````output callModel: ============================== ai message ============================== [ { "name": "getWeather", "args": { "location": "San Francisco" }, "type": "tool_call", "id": "call_pe7ee3A4lOO4Llr2NcfRukyp" } ] __interrupt__: [ { "value": { "question": "Is this correct?", "tool_call": { "name": "getWeather", "args": { "location": "San Francisco" }, "type": "tool_call", "id": "call_pe7ee3A4lOO4Llr2NcfRukyp" } }, "when": "during", "resumable": true, "ns": [ "agent:dcee519a-80f5-5950-9e1c-e8bb85ed436f" ] } ] ``` ```typescript hl_lines="3 4 5 6 7" import { Command } from "@langchain/langgraph"; const humanInput = new Command({ resume: { action: "continue", }, }); const resumedStream = await agent.stream(humanInput, config) for await (const step of resumedStream) { printStep(step); } ``` ```output callTool: ============================== tool message ============================== It's sunny! callModel: ============================== ai message ============================== The weather in San Francisco is sunny! ``` ### Revise a tool call To revise a tool call, we can supply updated arguments. ```typescript const config2 = { configurable: { thread_id: "2" } }; const userMessage2 = { role: "user", content: "What's the weather in san francisco?" }; console.log(userMessage2); const stream2 = await agent.stream([userMessage2], config2); for await (const step of stream2) { printStep(step); } ``` ```output { role: 'user', content: "What's the weather in san francisco?" } callModel: ============================== ai message ============================== [ { "name": "getWeather", "args": { "location": "San Francisco" }, "type": "tool_call", "id": "call_JEOqaUEvYJ4pzMtVyCQa6H2H" } ] __interrupt__: [ { "value": { "question": "Is this correct?", "tool_call": { "name": "getWeather", "args": { "location": "San Francisco" }, "type": "tool_call", "id": "call_JEOqaUEvYJ4pzMtVyCQa6H2H" } }, "when": "during", "resumable": true, "ns": [ "agent:d5c54c67-483a-589a-a1e7-2a8465b3ef13" ] } ] ``` ```typescript hl_lines="1 2 3 4 5 6" const humanInput2 = new Command({ resume: { action: "update", data: { location: "SF, CA" }, }, }); const resumedStream2 = await agent.stream(humanInput2, config2) for await (const step of resumedStream2) { printStep(step); } ``` ```output callTool: ============================== tool message ============================== It's sunny! callModel: ============================== ai message ============================== The weather in San Francisco is sunny! ``` The LangSmith traces for this run are particularly informative: - In the trace [before the interrupt](https://smith.langchain.com/public/abf80a16-3e15-484b-bbbb-23017593bd39/r), we generate a tool call for location `"San Francisco"`. - In the trace [after resuming](https://smith.langchain.com/public/233a7e32-a43e-4939-9c04-96fd4254ce65/r), we see that the tool call in the message has been updated to `"SF, CA"`. ### Generate a custom ToolMessage To Generate a custom `ToolMessage`, we supply the content of the message. In this case we will ask the model to reformat its tool call. ```typescript const config3 = { configurable: { thread_id: "3" } }; const userMessage3 = { role: "user", content: "What's the weather in san francisco?" }; console.log(userMessage3); const stream3 = await agent.stream([userMessage3], config3); for await (const step of stream3) { printStep(step); } ``` ```output { role: 'user', content: "What's the weather in san francisco?" } callModel: ============================== ai message ============================== [ { "name": "getWeather", "args": { "location": "San Francisco" }, "type": "tool_call", "id": "call_HNRjJLJo4U78dtk0uJ9YZF6V" } ] __interrupt__: [ { "value": { "question": "Is this correct?", "tool_call": { "name": "getWeather", "args": { "location": "San Francisco" }, "type": "tool_call", "id": "call_HNRjJLJo4U78dtk0uJ9YZF6V" } }, "when": "during", "resumable": true, "ns": [ "agent:6f313de8-c19e-5c3e-bdff-f90cdd68d0de" ] } ] ``` ```typescript hl_lines="1 2 3 4 5 6" const humanInput3 = new Command({ resume: { action: "feedback", data: "Please format as , .", }, }); const resumedStream3 = await agent.stream(humanInput3, config3) for await (const step of resumedStream3) { printStep(step); } ``` ```output callModel: ============================== ai message ============================== [ { "name": "getWeather", "args": { "location": "San Francisco, CA" }, "type": "tool_call", "id": "call_5V4Oj4JV2DVfeteM4Aaf2ieD" } ] __interrupt__: [ { "value": { "question": "Is this correct?", "tool_call": { "name": "getWeather", "args": { "location": "San Francisco, CA" }, "type": "tool_call", "id": "call_5V4Oj4JV2DVfeteM4Aaf2ieD" } }, "when": "during", "resumable": true, "ns": [ "agent:6f313de8-c19e-5c3e-bdff-f90cdd68d0de" ] } ] ``` Once it is re-formatted, we can accept it: ```typescript hl_lines="1 2 3 4 5" const continueCommand = new Command({ resume: { action: "continue", }, }); const continueStream = await agent.stream(continueCommand, config3) for await (const step of continueStream) { printStep(step); } ``` ```output callTool: ============================== tool message ============================== It's sunny! callModel: ============================== ai message ============================== The weather in San Francisco, CA is sunny! ``` ```typescript ``` --- how-tos/persistence-postgres.ipynb --- # How to use Postgres checkpointer for persistence

Prerequisites

This guide assumes familiarity with the following:

When creating LangGraph agents, you can set them up so that they persist their state across executions. This allows you to do things like interact with an agent multiple times and have it remember previous interactions. This how-to guide shows how to use Postgres as the backend for persisting checkpoint state using the [`@langchain/langgraph-checkpoint-postgres`](https://github.com/langchain-ai/langgraphjs/tree/main/libs/checkpoint-postgres) library and the [`PostgresSaver`](https://langchain-ai.github.io/langgraphjs/reference/classes/checkpoint_postgres.PostgresSaver.html) class. For demonstration purposes we will add persistence to the [pre-built create react agent](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html). In general, you can add a checkpointer to any custom graph that you build like this: ```ts import { StateGraph } from "@langchain/langgraph"; import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres"; const builder = new StateGraph(...); // ... define the graph const checkpointer = PostgresSaver.fromConnString(...); // postgres checkpointer (see examples below) const graph = builder.compile({ checkpointer }); ... ``` ## Setup You will need access to a Postgres instance. This guide will also use OpenAI, so you will need an OpenAI API key. First, install the required packages: ```bash npm install @langchain/langgraph @langchain/core @langchain/langgraph-checkpoint-postgres ``` Then, set your OpenAI API key as `process.env.OPENAI_API_KEY`.

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

## Define model and tools for the graph ```typescript import { z } from "zod"; import { tool } from "@langchain/core/tools"; const getWeather = tool(async (input: { city: "sf" | "nyc" }) => { if (input.city === "nyc") { return "It might be cloudy in nyc"; } else if (input.city === "sf") { return "It's always sunny in sf"; } else { throw new Error("Unknown city"); } }, { name: "get_weather", description: "Use this to get weather information.", schema: z.object({ city: z.enum(["sf", "nyc"]) }), }); ``` ## With a connection pool Under the hood, `PostgresSaver` uses the [`node-postgres`](https://www.npmjs.com/package/pg) (`pg`) package to connect to your Postgres instance. You can pass in a [connection pool](https://node-postgres.com/apis/pool) that you've instantiated like this: ```typescript import { ChatOpenAI } from "@langchain/openai"; import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import pg from "pg"; const { Pool } = pg; const pool = new Pool({ connectionString: "postgresql://user:password@localhost:5434/testdb" }); const checkpointer = new PostgresSaver(pool); // NOTE: you need to call .setup() the first time you're using your checkpointer await checkpointer.setup(); const graph = createReactAgent({ tools: [getWeather], llm: new ChatOpenAI({ model: "gpt-4o-mini", }), checkpointSaver: checkpointer, }); const config = { configurable: { thread_id: "1" } }; await graph.invoke({ messages: [{ role: "user", content: "what's the weather in sf" }], }, config); ``` ```output { messages: [ HumanMessage { "id": "ac832b73-242d-4d0b-80d7-5d06a908787e", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "chatcmpl-AGC3tgRXInGLo0qzrD5u3gNqNOegf", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 14, "promptTokens": 57, "totalTokens": 71 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_f85bea6784" }, "tool_calls": [ { "name": "get_weather", "args": { "city": "sf" }, "type": "tool_call", "id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 57, "output_tokens": 14, "total_tokens": 71 } }, ToolMessage { "id": "6533d271-6126-40af-b5d0-23a484853a97", "content": "It's always sunny in sf", "name": "get_weather", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R" }, AIMessage { "id": "chatcmpl-AGC3ttvB69pQu0atw0lUzTpNePlPn", "content": "The weather in San Francisco (SF) is always sunny!", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "completionTokens": 13, "promptTokens": 84, "totalTokens": 97 }, "finish_reason": "stop", "system_fingerprint": "fp_f85bea6784" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 84, "output_tokens": 13, "total_tokens": 97 } } ] } ``` ```typescript await checkpointer.get(config); ``` ```output { v: 1, id: '1ef85bc6-bd28-67c1-8003-5cb7dab561b0', ts: '2024-10-08T21:29:38.109Z', pending_sends: [], versions_seen: { agent: { tools: 4, '__start__:agent': 2 }, tools: { 'branch:agent:shouldContinue:tools': 3 }, __input__: {}, __start__: { __start__: 1 } }, channel_versions: { agent: 5, tools: 5, messages: 5, __start__: 2, '__start__:agent': 3, 'branch:agent:shouldContinue:tools': 4 }, channel_values: { agent: 'agent', messages: [ HumanMessage { "id": "ac832b73-242d-4d0b-80d7-5d06a908787e", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "chatcmpl-AGC3tgRXInGLo0qzrD5u3gNqNOegf", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 14, "promptTokens": 57, "totalTokens": 71 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_f85bea6784" }, "tool_calls": [ { "name": "get_weather", "args": { "city": "sf" }, "type": "tool_call", "id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R" } ], "invalid_tool_calls": [] }, ToolMessage { "id": "6533d271-6126-40af-b5d0-23a484853a97", "content": "It's always sunny in sf", "name": "get_weather", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R" }, AIMessage { "id": "chatcmpl-AGC3ttvB69pQu0atw0lUzTpNePlPn", "content": "The weather in San Francisco (SF) is always sunny!", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "completionTokens": 13, "promptTokens": 84, "totalTokens": 97 }, "finish_reason": "stop", "system_fingerprint": "fp_f85bea6784" }, "tool_calls": [], "invalid_tool_calls": [] } ] } } ``` ### With a connection string You can also create a pool internally by passing a connection string to the `.fromConnString` static method: ```typescript const checkpointerFromConnString = PostgresSaver.fromConnString( "postgresql://user:password@localhost:5434/testdb" ); const graph2 = createReactAgent({ tools: [getWeather], llm: new ChatOpenAI({ model: "gpt-4o-mini", }), checkpointSaver: checkpointerFromConnString, }); const config2 = { configurable: { thread_id: "2" } }; await graph2.invoke({ messages: [{ role: "user", content: "what's the weather in sf" }], }, config2); ``` ```output { messages: [ HumanMessage { "id": "c17b65af-6ac5-411e-ab5c-8003dc53755d", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "chatcmpl-AGC6n8XO05i1Z7f4GnOqpayLPxgoF", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 14, "promptTokens": 57, "totalTokens": 71 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_f85bea6784" }, "tool_calls": [ { "name": "get_weather", "args": { "city": "sf" }, "type": "tool_call", "id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 57, "output_tokens": 14, "total_tokens": 71 } }, ToolMessage { "id": "779c26b0-6b75-454e-98ef-ecca79e50e8c", "content": "It's always sunny in sf", "name": "get_weather", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO" }, AIMessage { "id": "chatcmpl-AGC6ngqEV0EBZbPwHf2JgTw0n16D8", "content": "The weather in San Francisco (SF) is described as always sunny.", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "completionTokens": 15, "promptTokens": 84, "totalTokens": 99 }, "finish_reason": "stop", "system_fingerprint": "fp_74ba47b4ac" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 84, "output_tokens": 15, "total_tokens": 99 } } ] } ``` ```typescript await checkpointerFromConnString.get(config2); ``` ```output { v: 1, id: '1ef85bcd-71b9-6671-8003-6e734c8e9679', ts: '2024-10-08T21:32:38.103Z', pending_sends: [], versions_seen: { agent: { tools: 4, '__start__:agent': 2 }, tools: { 'branch:agent:shouldContinue:tools': 3 }, __input__: {}, __start__: { __start__: 1 } }, channel_versions: { agent: 5, tools: 5, messages: 5, __start__: 2, '__start__:agent': 3, 'branch:agent:shouldContinue:tools': 4 }, channel_values: { agent: 'agent', messages: [ HumanMessage { "id": "c17b65af-6ac5-411e-ab5c-8003dc53755d", "content": "what's the weather in sf", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "chatcmpl-AGC6n8XO05i1Z7f4GnOqpayLPxgoF", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 14, "promptTokens": 57, "totalTokens": 71 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_f85bea6784" }, "tool_calls": [ { "name": "get_weather", "args": { "city": "sf" }, "type": "tool_call", "id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO" } ], "invalid_tool_calls": [] }, ToolMessage { "id": "779c26b0-6b75-454e-98ef-ecca79e50e8c", "content": "It's always sunny in sf", "name": "get_weather", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO" }, AIMessage { "id": "chatcmpl-AGC6ngqEV0EBZbPwHf2JgTw0n16D8", "content": "The weather in San Francisco (SF) is described as always sunny.", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "completionTokens": 15, "promptTokens": 84, "totalTokens": 99 }, "finish_reason": "stop", "system_fingerprint": "fp_74ba47b4ac" }, "tool_calls": [], "invalid_tool_calls": [] } ] } } ``` ```typescript ``` --- how-tos/subgraph.ipynb --- # How to add and use subgraphs

Prerequisites

This guide assumes familiarity with the following:

[Subgraphs](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#subgraphs) allow you to build complex systems with multiple components that are themselves graphs. A common use case for using subgraphs is building [multi-agent systems](https://langchain-ai.github.io/langgraphjs/concepts/multi_agent). The main question when adding subgraphs is how the parent graph and subgraph communicate, i.e. how they pass the [state](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#state) between each other during the graph execution. There are two scenarios: - parent graph and subgraph **share schema keys**. In this case, you can add a node with the compiled subgraph - parent graph and subgraph have **different schemas**. In this case, you have to add a node function that invokes the subgraph: this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph Below we show to to add subgraphs for each scenario. ## Setup First, let's install the required packages ```bash npm install @langchain/langgraph @langchain/core ```

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

## Add a node with the compiled subgraph A common case is for the parent graph and subgraph to communicate over a shared state key (channel). For example, in [multi-agent](https://langchain-ai.github.io/langgraphjs/concepts/multi_agent) systems, the agents often communicate over a shared [messages](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#why-use-messages) key. If your subgraph shares state keys with the parent graph, you can follow these steps to add it to your graph: 1. Define the subgraph workflow (`subgraphBuilder` in the example below) and compile it 2. Pass compiled subgraph to the `.addNode` method when defining the parent graph workflow Let's take a look at an example. ```typescript import { StateGraph, Annotation } from "@langchain/langgraph"; const SubgraphStateAnnotation = Annotation.Root({ foo: Annotation, // note that this key is shared with the parent graph state bar: Annotation, }); const subgraphNode1 = async (state: typeof SubgraphStateAnnotation.State) => { return { bar: "bar" }; }; const subgraphNode2 = async (state: typeof SubgraphStateAnnotation.State) => { // note that this node is using a state key ('bar') that is only available in the subgraph // and is sending update on the shared state key ('foo') return { foo: state.foo + state.bar }; }; const subgraphBuilder = new StateGraph(SubgraphStateAnnotation) .addNode("subgraphNode1", subgraphNode1) .addNode("subgraphNode2", subgraphNode2) .addEdge("__start__", "subgraphNode1") .addEdge("subgraphNode1", "subgraphNode2") const subgraph = subgraphBuilder.compile(); // Define parent graph const ParentStateAnnotation = Annotation.Root({ foo: Annotation, }); const node1 = async (state: typeof ParentStateAnnotation.State) => { return { foo: "hi! " + state.foo, }; } const builder = new StateGraph(ParentStateAnnotation) .addNode("node1", node1) // note that we're adding the compiled subgraph as a node to the parent graph .addNode("node2", subgraph) .addEdge("__start__", "node1") .addEdge("node1", "node2") const graph = builder.compile(); ``` ```typescript const stream = await graph.stream({ foo: "foo" }); for await (const chunk of stream) { console.log(chunk); } ``` ```output { node1: { foo: 'hi! foo' } } { node2: { foo: 'hi! foobar' } } ``` You can see that the final output from the parent graph includes the results of subgraph invocation (the string `"bar"`). If you would like to see streaming output from the subgraph, you can specify `subgraphs: True` when streaming. See more on streaming from subgraphs in this [how-to guide](https://langchain-ai.github.io/langgraphjs/how-tos/streaming-subgraphs/#stream-subgraph). ```typescript const streamWithSubgraphs = await graph.stream({ foo: "foo" }, { subgraphs: true }); for await (const chunk of streamWithSubgraphs) { console.log(chunk); } ``` ```output [ [], { node1: { foo: 'hi! foo' } } ] [ [ 'node2:22f27b01-fa9f-5f46-9b5b-166a80d96791' ], { subgraphNode1: { bar: 'bar' } } ] [ [ 'node2:22f27b01-fa9f-5f46-9b5b-166a80d96791' ], { subgraphNode2: { foo: 'hi! foobar' } } ] [ [], { node2: { foo: 'hi! foobar' } } ] ``` You'll notice that the chunk output format has changed to include some additional information about the subgraph it came from. ## Add a node function that invokes the subgraph For more complex systems you might want to define subgraphs that have a completely different schema from the parent graph (no shared keys). For example, in a multi-agent RAG system, a search agent might only need to keep track of queries and retrieved documents. If that's the case for your application, you need to define a node **function that invokes the subgraph**. This function needs to transform the input (parent) state to the subgraph state before invoking the subgraph, and transform the results back to the parent state before returning the state update from the node. Below we show how to modify our original example to call a subgraph from inside the node.

Note

You cannot invoke more than one subgraph inside the same node if you have checkpointing enabled for the subgraphs. See this page for more information.

```typescript import { StateGraph, Annotation } from "@langchain/langgraph"; const SubgraphAnnotation = Annotation.Root({ bar: Annotation, // note that this key is shared with the parent graph state baz: Annotation, }); const subgraphNodeOne = async (state: typeof SubgraphAnnotation.State) => { return { baz: "baz" }; }; const subgraphNodeTwo = async (state: typeof SubgraphAnnotation.State) => { return { bar: state.bar + state.baz } }; const subgraphCalledInFunction = new StateGraph(SubgraphAnnotation) .addNode("subgraphNode1", subgraphNodeOne) .addNode("subgraphNode2", subgraphNodeTwo) .addEdge("__start__", "subgraphNode1") .addEdge("subgraphNode1", "subgraphNode2") .compile(); // Define parent graph const ParentAnnotation = Annotation.Root({ foo: Annotation, }); const nodeOne = async (state: typeof ParentAnnotation.State) => { return { foo: "hi! " + state.foo, }; } const nodeTwo = async (state: typeof ParentAnnotation.State) => { const response = await subgraphCalledInFunction.invoke({ bar: state.foo, }); return { foo: response.bar } } const graphWithFunction = new StateGraph(ParentStateAnnotation) .addNode("node1", nodeOne) // note that we're adding the compiled subgraph as a node to the parent graph .addNode("node2", nodeTwo) .addEdge("__start__", "node1") .addEdge("node1", "node2") .compile(); ``` ```typescript const graphWithFunctionStream = await graphWithFunction.stream({ foo: "foo" }, { subgraphs: true }); for await (const chunk of graphWithFunctionStream) { console.log(chunk); } ``` ```output [ [], { node1: { foo: 'hi! foo' } } ] [ [ 'node2:1d2bb11a-3ed1-5c58-9b6f-c7af36a1eeb7' ], { subgraphNode1: { baz: 'baz' } } ] [ [ 'node2:1d2bb11a-3ed1-5c58-9b6f-c7af36a1eeb7' ], { subgraphNode2: { bar: 'hi! foobaz' } } ] [ [], { node2: { foo: 'hi! foobaz' } } ] ``` --- how-tos/recursion-limit.ipynb --- # How to create and control loops

Prerequisites

This guide assumes familiarity with the following:

When creating a graph with a loop, we require a mechanism for terminating execution. This is most commonly done by adding a conditional edge that routes to the END node once we reach some termination condition. You can also set the graph recursion limit when invoking or streaming the graph. The recursion limit sets the number of supersteps that the graph is allowed to execute before it raises an error. Read more about the concept of recursion limits here. Let's consider a simple graph with a loop to better understand how these mechanisms work. ## Summary When creating a loop, you can include a conditional edge that specifies a termination condition: ```ts const route = async function (state: typeof StateAnnotation.State) { if (terminationCondition(state)) { return "__END__"; } else { return "a"; } } const graph = StateGraph(State) .addNode(a) .addNode(b) .addConditionalEdges("a", route) .addEdge("b", "a") .compile(); ``` To control the recursion limit, specify `"recursionLimit"` in the config. This will raise a `GraphRecursionError`, which you can catch and handle: ```ts import { GraphRecursionError } from "@langchain/langgraph"; try { await graph.invoke(inputs, { recursionLimit: 3 }); } catch (error) { if (error instanceof GraphRecursionError) { console.log("Recursion Error"); } else { throw error; } } ``` ## Setup First, let's install the required packages ```bash npm install @langchain/langgraph @langchain/core ```

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

## Define the graph Let's define a graph with a simple loop. Note that we use a conditional edge to implement a termination condition. ```typescript import { StateGraph, Annotation } from "@langchain/langgraph"; // Define the state with a reducer const StateAnnotation = Annotation.Root({ aggregate: Annotation({ reducer: (a, b) => a.concat(b), default: () => [], }), }); // Define nodes const a = async function (state: typeof StateAnnotation.State) { console.log(`Node A sees ${state.aggregate}`); return { aggregate: ["A"] }; } const b = async function (state: typeof StateAnnotation.State) { console.log(`Node B sees ${state.aggregate}`); return { aggregate: ["B"] }; } // Define edges const route = async function (state: typeof StateAnnotation.State) { if (state.aggregate.length < 7) { return "b"; } else { return "__end__"; } } // Define the graph const graph = new StateGraph(StateAnnotation) .addNode("a", a) .addNode("b", b) .addEdge("__start__", "a") .addConditionalEdges("a", route) .addEdge("b", "a") .compile(); ``` ```typescript import * as tslab from "tslab"; const drawableGraph = graph.getGraph(); const image = await drawableGraph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` This architecture is similar to a ReAct agent in which node `"a"` is a tool-calling model, and node `"b"` represents the tools. In our `route` conditional edge, we specify that we should end after the `"aggregate"` list in the state passes a threshold length. Invoking the graph, we see that we alternate between nodes `"a"` and `"b"` before terminating once we reach the termination condition. ```typescript await graph.invoke({ aggregate: [] }); ``` ```output Node A sees Node B sees A Node A sees A,B Node B sees A,B,A Node A sees A,B,A,B Node B sees A,B,A,B,A Node A sees A,B,A,B,A,B { aggregate: [ 'A', 'B', 'A', 'B', 'A', 'B', 'A' ] } ``` ## Impose a recursion limit In some applications, we may not have a guarantee that we will reach a given termination condition. In these cases, we can set the graph's recursion limit. This will raise a `GraphRecursionError` after a given number of supersteps. We can then catch and handle this exception: ```typescript import { GraphRecursionError } from "@langchain/langgraph"; try { await graph.invoke({ aggregate: [] }, { recursionLimit: 4 }); } catch (error) { if (error instanceof GraphRecursionError) { console.log("Recursion Error"); } else { throw error; } } ``` ```output Node A sees Node B sees A Node A sees A,B Node B sees A,B,A Recursion Error ``` Note that this time we terminate after the fourth step. ## Loops with branches To better understand how the recursion limit works, let's consider a more complex example. Below we implement a loop, but one step fans out into two nodes: ```typescript import { StateGraph, Annotation } from "@langchain/langgraph"; // Define the state with a reducer const StateAnnotationWithLoops = Annotation.Root({ aggregate: Annotation({ reducer: (a, b) => a.concat(b), default: () => [], }), }); // Define nodes const nodeA = async function (state: typeof StateAnnotationWithLoops.State) { console.log(`Node A sees ${state.aggregate}`); return { aggregate: ["A"] }; } const nodeB = async function (state: typeof StateAnnotationWithLoops.State) { console.log(`Node B sees ${state.aggregate}`); return { aggregate: ["B"] }; } const nodeC = async function (state: typeof StateAnnotationWithLoops.State) { console.log(`Node C sees ${state.aggregate}`); return { aggregate: ["C"] }; } const nodeD = async function (state: typeof StateAnnotationWithLoops.State) { console.log(`Node D sees ${state.aggregate}`); return { aggregate: ["D"] }; } // Define edges const loopRouter = async function (state: typeof StateAnnotationWithLoops.State) { if (state.aggregate.length < 7) { return "b"; } else { return "__end__"; } } // Define the graph const graphWithLoops = new StateGraph(StateAnnotationWithLoops) .addNode("a", nodeA) .addNode("b", nodeB) .addNode("c", nodeC) .addNode("d", nodeD) .addEdge("__start__", "a") .addConditionalEdges("a", loopRouter) .addEdge("b", "c") .addEdge("b", "d") .addEdge(["c", "d"], "a") .compile(); ``` ```typescript import * as tslab from "tslab"; const drawableGraphWithLoops = graphWithLoops.getGraph(); const imageWithLoops = await drawableGraphWithLoops.drawMermaidPng(); const arrayBufferWithLoops = await imageWithLoops.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBufferWithLoops)); ``` This graph looks complex, but can be conceptualized as loop of supersteps: 1. Node A 2. Node B 3. Nodes C and D 4. Node A 5. ... We have a loop of four supersteps, where nodes C and D are executed concurrently. Invoking the graph as before, we see that we complete two full "laps" before hitting the termination condition: ```typescript await graphWithLoops.invoke({ aggregate: [] }) ``` ```output Node A sees Node B sees A Node C sees A,B Node D sees A,B Node A sees A,B,C,D Node B sees A,B,C,D,A Node C sees A,B,C,D,A,B Node D sees A,B,C,D,A,B Node A sees A,B,C,D,A,B,C,D { aggregate: [ 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A' ] } ``` However, if we set the recursion limit to four, we only complete one lap because each lap is four supersteps: ```typescript import { GraphRecursionError } from "@langchain/langgraph"; try { await graphWithLoops.invoke({ aggregate: [] }, { recursionLimit: 4 }); } catch (error) { if (error instanceof GraphRecursionError) { console.log("Recursion Error"); } else { throw error; } } ``` ```output Node A sees Node B sees A Node C sees A,B Node D sees A,B Node A sees A,B,C,D Recursion Error ``` --- how-tos/create-react-agent.ipynb --- # How to use the prebuilt ReAct agent In this how-to we'll create a simple [ReAct](https://arxiv.org/abs/2210.03629) agent app that can check the weather. The app consists of an agent (LLM) and tools. As we interact with the app, we will first call the agent (LLM) to decide if we should use tools. Then we will run a loop: 1. If the agent said to take an action (i.e. call tool), we'll run the tools and pass the results back to the agent 2. If the agent did not ask to run tools, we will finish (respond to the user)

Prebuilt Agent

Please note that here will we use a prebuilt agent. One of the big benefits of LangGraph is that you can easily create your own agent architectures. So while it's fine to start here to build an agent quickly, we would strongly recommend learning how to build your own agent so that you can take full advantage of LangGraph. Read this guide to learn how to create your own ReAct agent from scratch.

## Setup First, we need to install the required packages. ```bash yarn add @langchain/langgraph @langchain/openai @langchain/core ``` This guide will use OpenAI's GPT-4o model. We will optionally set our API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..." // process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "ReAct Agent: LangGraphJS"; ``` ```output ReAct Agent: LangGraphJS ``` ## Code Now we can use the prebuilt `createReactAgent` function to setup our agent: ```typescript import { ChatOpenAI } from "@langchain/openai"; import { tool } from '@langchain/core/tools'; import { z } from 'zod'; import { createReactAgent } from "@langchain/langgraph/prebuilt"; const model = new ChatOpenAI({ model: "gpt-4o", }); const getWeather = tool((input) => { if (['sf', 'san francisco', 'san francisco, ca'].includes(input.location.toLowerCase())) { return 'It\'s 60 degrees and foggy.'; } else { return 'It\'s 90 degrees and sunny.'; } }, { name: 'get_weather', description: 'Call to get the current weather.', schema: z.object({ location: z.string().describe("Location to get the weather for."), }) }) const agent = createReactAgent({ llm: model, tools: [getWeather] }); ``` ## Usage First, let's visualize the graph we just created ```typescript import * as tslab from "tslab"; const graph = agent.getGraph(); const image = await graph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` Let's run the app with an input that needs a tool call ```typescript let inputs = { messages: [{ role: "user", content: "what is the weather in SF?" }] }; let stream = await agent.stream(inputs, { streamMode: "values", }); for await (const { messages } of stream) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output what is the weather in sf? ----- [ { name: 'get_weather', args: { location: 'San Francisco, CA' }, type: 'tool_call', id: 'call_wfXCh5IhSp1C0Db3gaJWDbRP' } ] ----- It's 60 degrees and foggy. ----- The weather in San Francisco is currently 60 degrees and foggy. ----- ``` Now let's try a question that doesn't need tools ```typescript inputs = { messages: [{ role: "user", content: "who built you?" }] }; stream = await agent.stream(inputs, { streamMode: "values", }); for await ( const { messages } of stream ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output who built you? ----- I was developed by OpenAI, an AI research and deployment company. ----- ``` Perfect! The agent correctly didn't call any tools and instead directly responded to the user. --- how-tos/stream-multiple.ipynb --- # How to configure multiple streaming modes at the same time This guide covers how to configure multiple streaming modes at the same time. ## Setup First we need to install the packages required ```bash npm install @langchain/langgraph @langchain/openai @langchain/core ``` Next, we need to set API keys for OpenAI (the LLM we will use) ```bash export OPENAI_API_KEY=your-api-key ``` Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```bash export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_CALLBACKS_BACKGROUND="true" export LANGCHAIN_API_KEY=your-api-key ``` ## Define the graph We'll be using a prebuilt ReAct agent for this guide. ```typescript import { ChatOpenAI } from "@langchain/openai"; import { tool } from '@langchain/core/tools'; import { z } from 'zod'; import { createReactAgent } from "@langchain/langgraph/prebuilt"; const model = new ChatOpenAI({ model: "gpt-4o", }); const getWeather = tool((input) => { if (["sf", "san francisco", "san francisco, ca"].includes(input.location.toLowerCase())) { return "It's 60 degrees and foggy."; } else { return "It's 90 degrees and sunny."; } }, { name: "get_weather", description: "Call to get the current weather.", schema: z.object({ location: z.string().describe("Location to get the weather for."), }) }) const graph = createReactAgent({ llm: model, tools: [getWeather] }); ``` ## Stream Multiple To get multiple types of streamed chunks, pass an array of values under the `streamMode` key in the second argument to `.stream()`: ```typescript let inputs = { messages: [{ role: "user", content: "what's the weather in sf?" }] }; let stream = await graph.stream(inputs, { streamMode: ["updates", "debug"], }); for await (const chunk of stream) { console.log(`Receiving new event of type: ${chunk[0]}`); console.log(chunk[1]); console.log("\n====\n"); } ``` ```output Receiving new event of type: debug { type: 'task', timestamp: '2024-08-30T20:58:58.404Z', step: 1, payload: { id: '768110dd-6004-59f3-8671-6ca699cccd71', name: 'agent', input: { messages: [Array] }, triggers: [ 'start:agent' ], interrupts: [] } } ==== Receiving new event of type: updates { agent: { messages: [ AIMessage { "id": "chatcmpl-A22zqTwumhtW8TMjQ1FxlzCEMBk0R", "content": "", "additional_kwargs": { "tool_calls": [ { "id": "call_HAfilebE1q9E9OQHOlL3JYHP", "type": "function", "function": "[Object]" } ] }, "response_metadata": { "tokenUsage": { "completionTokens": 15, "promptTokens": 59, "totalTokens": 74 }, "finish_reason": "tool_calls", "system_fingerprint": "fp_157b3831f5" }, "tool_calls": [ { "name": "get_weather", "args": { "location": "San Francisco" }, "type": "tool_call", "id": "call_HAfilebE1q9E9OQHOlL3JYHP" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 59, "output_tokens": 15, "total_tokens": 74 } } ] } } ==== Receiving new event of type: debug { type: 'task_result', timestamp: '2024-08-30T20:58:59.072Z', step: 1, payload: { id: '768110dd-6004-59f3-8671-6ca699cccd71', name: 'agent', result: [ [Array] ] } } ==== Receiving new event of type: debug { type: 'task', timestamp: '2024-08-30T20:58:59.074Z', step: 2, payload: { id: '76459c18-5621-5893-9b93-13bc1db3ba6d', name: 'tools', input: { messages: [Array] }, triggers: [ 'branch:agent:shouldContinue:tools' ], interrupts: [] } } ==== Receiving new event of type: updates { tools: { messages: [ ToolMessage { "content": "It's 60 degrees and foggy.", "name": "get_weather", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "call_HAfilebE1q9E9OQHOlL3JYHP" } ] } } ==== Receiving new event of type: debug { type: 'task_result', timestamp: '2024-08-30T20:58:59.076Z', step: 2, payload: { id: '76459c18-5621-5893-9b93-13bc1db3ba6d', name: 'tools', result: [ [Array] ] } } ==== Receiving new event of type: debug { type: 'task', timestamp: '2024-08-30T20:58:59.077Z', step: 3, payload: { id: '565d8a53-1057-5d83-bda8-ba3fada24b70', name: 'agent', input: { messages: [Array] }, triggers: [ 'tools' ], interrupts: [] } } ==== Receiving new event of type: updates { agent: { messages: [ AIMessage { "id": "chatcmpl-A22zrdeobsBzkiES0C6Twh3p7I344", "content": "The weather in San Francisco right now is 60 degrees and foggy.", "additional_kwargs": {}, "response_metadata": { "tokenUsage": { "completionTokens": 16, "promptTokens": 90, "totalTokens": 106 }, "finish_reason": "stop", "system_fingerprint": "fp_157b3831f5" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 90, "output_tokens": 16, "total_tokens": 106 } } ] } } ==== Receiving new event of type: debug { type: 'task_result', timestamp: '2024-08-30T20:58:59.640Z', step: 3, payload: { id: '565d8a53-1057-5d83-bda8-ba3fada24b70', name: 'agent', result: [ [Array] ] } } ==== ``` --- how-tos/managing-agent-steps.ipynb --- # How to manage agent steps In this example we will build a ReAct Agent that explicitly manages intermediate steps. The previous examples just put all messages into the model, but that extra context can distract the agent and add latency to the API calls. In this example we will only include the `N` most recent messages in the chat history. Note that this is meant to be illustrative of general state management. ## Setup First we need to install required packages: ```bash yarn add @langchain/langgraph @langchain/openai @langchain/core ``` Next, we need to set API keys for Anthropic (the LLM we will use). ```typescript // process.env.OPENAI_API_KEY = "sk_..."; ``` Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..."; process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "Managing Agent Steps: LangGraphJS"; ``` ```output Managing Agent Steps: LangGraphJS ``` ## Set up the State The main type of graph in `langgraph` is the StateGraph. This graph is parameterized by a state object that it passes around to each node. Each node then returns operations to update that state. These operations can either SET specific attributes on the state (e.g. overwrite the existing values) or ADD to the existing attribute. Whether to set or add is denoted in the state object you construct the graph with. For this example, the state we will track will just be a list of messages. We want each node to just add messages to that list. Therefore, we will define the state as follows: ```typescript import { Annotation } from "@langchain/langgraph"; import { BaseMessage } from "@langchain/core/messages"; const AgentState = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); ``` ## Set up the tools We will first define the tools we want to use. For this simple example, we will create a placeholder search engine. It is really easy to create your own tools - see documentation [here](https://js.langchain.com/docs/modules/agents/tools/dynamic) on how to do that. ```typescript import { DynamicStructuredTool } from "@langchain/core/tools"; import { z } from "zod"; const searchTool = new DynamicStructuredTool({ name: "search", description: "Call to surf the web.", schema: z.object({ query: z.string().describe("The query to use in your search."), }), func: async ({}: { query: string }) => { // This is a placeholder, but don't tell the LLM that... return "Try again in a few seconds! Checking with the weathermen... Call be again next."; }, }); const tools = [searchTool]; ``` We can now wrap these tools in a simple ToolNode.\ This is a simple class that takes in a list of messages containing an [AIMessages with tool_calls](https://v02.api.js.langchain.com/classes/langchain_core_messages_ai.AIMessage.html), runs the tools, and returns the output as [ToolMessage](https://v02.api.js.langchain.com/classes/langchain_core_messages_tool.ToolMessage.html)s. ```typescript import { ToolNode } from "@langchain/langgraph/prebuilt"; const toolNode = new ToolNode(tools); ``` ## Set up the model Now we need to load the chat model we want to use. This should satisfy two criteria: 1. It should work with messages, since our state is primarily a list of messages (chat history). 2. It should work with tool calling, since we are using a prebuilt ToolNode **Note:** these model requirements are not requirements for using LangGraph - they are just requirements for this particular example. ```typescript import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0, }); ``` ```typescript // After we've done this, we should make sure the model knows that it has these tools available to call. // We can do this by binding the tools to the model class. const boundModel = model.bindTools(tools); ``` ## Define the nodes We now need to define a few different nodes in our graph. In `langgraph`, a node can be either a function or a [runnable](https://js.langchain.com/docs/expression_language/). There are two main nodes we need for this: 1. The agent: responsible for deciding what (if any) actions to take. 2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action. We will also need to define some edges. Some of these edges may be conditional. The reason they are conditional is that based on the output of a node, one of several paths may be taken. The path that is taken is not known until that node is run (the LLM decides). 1. Conditional Edge: after the agent is called, we should either: a. If the agent said to take an action, then the function to invoke tools should be called\ b. If the agent said that it was finished, then it should finish 2. Normal Edge: after the tools are invoked, it should always go back to the agent to decide what to do next Let's define the nodes, as well as a function to decide how what conditional edge to take. ```typescript import { END } from "@langchain/langgraph"; import { AIMessage, ToolMessage } from "@langchain/core/messages"; import { RunnableConfig } from "@langchain/core/runnables"; // Define the function that determines whether to continue or not const shouldContinue = (state: typeof AgentState.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as AIMessage; // If there is no function call, then we finish if (!lastMessage.tool_calls || lastMessage.tool_calls.length === 0) { return END; } // Otherwise if there is, we continue return "tools"; }; // **MODIFICATION** // // Here we don't pass all messages to the model but rather only pass the `N` most recent. Note that this is a terribly simplistic way to handle messages meant as an illustration, and there may be other methods you may want to look into depending on your use case. We also have to make sure we don't truncate the chat history to include the tool message first, as this would cause an API error. const callModel = async ( state: typeof AgentState.State, config?: RunnableConfig, ) => { let modelMessages = []; for (let i = state.messages.length - 1; i >= 0; i--) { modelMessages.push(state.messages[i]); if (modelMessages.length >= 5) { if (!ToolMessage.isInstance(modelMessages[modelMessages.length - 1])) { break; } } } modelMessages.reverse(); const response = await boundModel.invoke(modelMessages, config); // We return an object, because this will get added to the existing list return { messages: [response] }; }; ``` ## Define the graph We can now put it all together and define the graph! ```typescript import { START, StateGraph } from "@langchain/langgraph"; // Define a new graph const workflow = new StateGraph(AgentState) .addNode("agent", callModel) .addNode("tools", toolNode) .addEdge(START, "agent") .addConditionalEdges( "agent", shouldContinue, ) .addEdge("tools", "agent"); // Finally, we compile it! // This compiles it into a LangChain Runnable, // meaning you can use it as you would any other runnable const app = workflow.compile(); ``` ## Use it! We can now use it! This now exposes the [same interface](https://js.langchain.com/docs/expression_language/) as all other LangChain runnables. ```typescript import { HumanMessage, isAIMessage } from "@langchain/core/messages"; import { GraphRecursionError } from "@langchain/langgraph"; const prettyPrint = (message: BaseMessage) => { let txt = `[${message._getType()}]: ${message.content}`; if ( (isAIMessage(message) && (message as AIMessage)?.tool_calls?.length) || 0 > 0 ) { const tool_calls = (message as AIMessage)?.tool_calls ?.map((tc) => `- ${tc.name}(${JSON.stringify(tc.args)})`) .join("\n"); txt += ` \nTools: \n${tool_calls}`; } console.log(txt); }; const inputs = { messages: [ new HumanMessage( "what is the weather in sf? Don't give up! Keep using your tools.", ), ], }; // Setting the recursionLimit will set a max number of steps. We expect this to endlessly loop :) try { for await ( const output of await app.stream(inputs, { streamMode: "values", recursionLimit: 10, }) ) { const lastMessage = output.messages[output.messages.length - 1]; prettyPrint(lastMessage); console.log("-----\n"); } } catch (e) { // Since we are truncating the chat history, the agent never gets the chance // to see enough information to know to stop, so it will keep looping until we hit the // maximum recursion limit. if ((e as GraphRecursionError).name === "GraphRecursionError") { console.log("As expected, maximum steps reached. Exiting."); } else { console.error(e); } } ``` ```output [human]: what is the weather in sf? Don't give up! Keep using your tools. ----- [ai]: Tools: - search({"query":"current weather in San Francisco"}) ----- [tool]: Try again in a few seconds! Checking with the weathermen... Call be again next. ----- [ai]: Tools: - search({"query":"current weather in San Francisco"}) ----- [tool]: Try again in a few seconds! Checking with the weathermen... Call be again next. ----- [ai]: Tools: - search({"query":"current weather in San Francisco"}) ----- [tool]: Try again in a few seconds! Checking with the weathermen... Call be again next. ----- [ai]: Tools: - search({"query":"current weather in San Francisco"}) ----- [tool]: Try again in a few seconds! Checking with the weathermen... Call be again next. ----- [ai]: Tools: - search({"query":"current weather in San Francisco"}) ----- As expected, maximum steps reached. Exiting. ``` --- how-tos/semantic-search.ipynb --- # How to add semantic search to your agent's memory This guide shows how to enable semantic search in your agent's memory store. This lets your agent search for items in the long-term memory store by semantic similarity. ## Dependencies and environment setup First, install this guide's required dependencies. ```bash npm install \ @langchain/langgraph \ @langchain/openai \ @langchain/core \ uuid \ zod ``` Next, we need to set API keys for OpenAI (the LLM we will use) ```bash export OPENAI_API_KEY=your-api-key ``` Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```bash export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_CALLBACKS_BACKGROUND="true" export LANGCHAIN_API_KEY=your-api-key ``` ## Initializing a memory store with semantic search Here we create our memory store with an [index configuration](https://langchain-ai.github.io/langgraphjs/reference/interfaces/checkpoint.IndexConfig.html). By default, stores are configured without semantic/vector search. You can opt in to indexing items when creating the store by providing an [IndexConfig](https://langchain-ai.github.io/langgraphjs/reference/interfaces/checkpoint.IndexConfig.html) to the store's constructor. If your store class does not implement this interface, or if you do not pass in an index configuration, semantic search is disabled, and all `index` arguments passed to `put` will have no effect. Now, let's create that store! ```typescript import { OpenAIEmbeddings } from "@langchain/openai"; import { InMemoryStore } from "@langchain/langgraph"; const embeddings = new OpenAIEmbeddings({ model: "text-embedding-3-small", }); const store = new InMemoryStore({ index: { embeddings, dims: 1536, } }); ``` ## The anatomy of a memory Before we get into semantic search, let's look at how memories are structured, and how to store them: ```typescript let namespace = ["user_123", "memories"] let memoryKey = "favorite_food" let memoryValue = {"text": "I love pizza"} await store.put(namespace, memoryKey, memoryValue) ``` As you can see, memories are composed of a namespace, a key, and a value. **Namespaces** are multi-dimensional values (arrays of strings) that allow you to segment memory according to the needs of your application. In this case, we're segmenting memories by user by using a User ID (`"user_123"`) as the first dimension of our namespace array. **Keys** are arbitrary strings that identify the memory within the namespace. If you write to the same key in the same namespace multiple times, you'll overwrite the memory that was stored under that key. **Values** are objects that represent the actual memory being stored. These can be any object, so long as its serializable. You can structure these objects according to the needs of your application. ## Simple memory retrieval Let's add some more memories to our store and then fetch one of them by it's key to check that it stored properly. ```typescript await store.put( ["user_123", "memories"], "italian_food", {"text": "I prefer Italian food"} ) await store.put( ["user_123", "memories"], "spicy_food", {"text": "I don't like spicy food"} ) await store.put( ["user_123", "memories"], "occupation", {"text": "I am an airline pilot"} ) // That occupation is too lofty - let's overwrite // it with something more... down-to-earth await store.put( ["user_123", "memories"], "occupation", {"text": "I am a tunnel engineer"} ) // now let's check that our occupation memory was overwritten const occupation = await store.get(["user_123", "memories"], "occupation") console.log(occupation.value.text) ``` ```output I am a tunnel engineer ``` ## Searching memories with natural language Now that we've seen how to store and retrieve memories by namespace and key, let's look at how memories are retrieved using semantic search. Imagine that we had a big pile of memories that we wanted to search, and we didn't know the key that corresponds to the memory that we want to retrieve. Semantic search allows us to search our memory store without keys, by performing a natural language query using text embeddings. We demonstrate this in the following example: ```typescript const memories = await store.search(["user_123", "memories"], { query: "What is my occupation?", limit: 3, }); for (const memory of memories) { console.log(`Memory: ${memory.value.text} (similarity: ${memory.score})`); } ``` ```output Memory: I am a tunnel engineer (similarity: 0.3070681445327329) Memory: I prefer Italian food (similarity: 0.1435366180543232) Memory: I love pizza (similarity: 0.10650935500808985) ``` ## Simple Example: Long-term semantic memory in a ReAct agent Let's look at a simple example of providing an agent with long-term memory. Long-term memory can be thought of in two phases: storage, and recall. In the example below we handle storage by giving the agent a tool that it can use to create new memories. To handle recall we'll add a prompt step that queries the memory store using the text from the user's chat message. We'll then inject the results of that query into the system message. ### Simple memory storage tool Let's start off by creating a tool that lets the LLM store new memories: ```typescript import { tool } from "@langchain/core/tools"; import { LangGraphRunnableConfig } from "@langchain/langgraph"; import { z } from "zod"; import { v4 as uuidv4 } from "uuid"; const upsertMemoryTool = tool(async ( { content }, config: LangGraphRunnableConfig ): Promise => { const store = config.store as InMemoryStore; if (!store) { throw new Error("No store provided to tool."); } await store.put( ["user_123", "memories"], uuidv4(), // give each memory its own unique ID { text: content } ); return "Stored memory."; }, { name: "upsert_memory", schema: z.object({ content: z.string().describe("The content of the memory to store."), }), description: "Upsert long-term memories.", }); ``` In the tool above, we use a UUID as the key so that the memory store can accumulate memories endlessly without worrying about key conflicts. We do this instead of accumulating memories into a single object or array because the memory store indexes items by key. Giving each memory its own key in the store allows each memory to be assigned its own unique embedding vector that can be matched to the search query. ### Simple semantic recall mechanism Now that we have a tool for storing memories, let's create a prompt function that we can use with `createReactAgent` to handle the recall mechanism. Note that if we weren't using `createReactAgent` here, you could use this same function as the first node in your graph and it would work just as well. ```typescript import { MessagesAnnotation } from "@langchain/langgraph"; const addMemories = async ( state: typeof MessagesAnnotation.State, config: LangGraphRunnableConfig ) => { const store = config.store as InMemoryStore; if (!store) { throw new Error("No store provided to state modifier."); } // Search based on user's last message const items = await store.search( ["user_123", "memories"], { // Assume it's not a complex message query: state.messages[state.messages.length - 1].content as string, limit: 4 } ); const memories = items.length ? `## Memories of user\n${ items.map(item => `${item.value.text} (similarity: ${item.score})`).join("\n") }` : ""; // Add retrieved memories to system message return [ { role: "system", content: `You are a helpful assistant.\n${memories}` }, ...state.messages ]; }; ``` ### Putting it all together Finally, let's put it all together into an agent, using `createReactAgent`. Notice that we're not adding a checkpointer here. The examples below will not be reusing message history. All details not contained in the input messages will be coming from the recall mechanism defined above. ```typescript import { ChatOpenAI } from "@langchain/openai"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; const agent = createReactAgent({ llm: new ChatOpenAI({ model: "gpt-4o-mini" }), tools: [upsertMemoryTool], prompt: addMemories, store: store }); ``` ### Using our sample agent Now that we've got everything put together, let's test it out! First, let's define a helper function that we can use to print messages in the conversation. ```typescript import { BaseMessage, isSystemMessage, isAIMessage, isHumanMessage, isToolMessage, AIMessage, HumanMessage, ToolMessage, SystemMessage, } from "@langchain/core/messages"; function printMessages(messages: BaseMessage[]) { for (const message of messages) { if (isSystemMessage(message)) { const systemMessage = message as SystemMessage; console.log(`System: ${systemMessage.content}`); } else if (isHumanMessage(message)) { const humanMessage = message as HumanMessage; console.log(`User: ${humanMessage.content}`); } else if (isAIMessage(message)) { const aiMessage = message as AIMessage; if (aiMessage.content) { console.log(`Assistant: ${aiMessage.content}`); } if (aiMessage.tool_calls) { for (const toolCall of aiMessage.tool_calls) { console.log(`\t${toolCall.name}(${JSON.stringify(toolCall.args)})`); } } } else if (isToolMessage(message)) { const toolMessage = message as ToolMessage; console.log( `\t\t${toolMessage.name} -> ${JSON.stringify(toolMessage.content)}` ); } } } ``` Now if we run the agent and print the message, we can see that the agent remembers the food preferences that we added to the store at the very beginning of this demo! ```typescript let result = await agent.invoke({ messages: [ { role: "user", content: "I'm hungry. What should I eat?", }, ], }); printMessages(result.messages); ``` ```output User: I'm hungry. What should I eat? Assistant: Since you prefer Italian food and love pizza, how about ordering a pizza? You could choose a classic Margherita or customize it with your favorite toppings, making sure to keep it non-spicy. Enjoy your meal! ``` #### Storing new memories Now that we know that the recall mechanism works, let's see if we can get our example agent to store a new memory: ```typescript result = await agent.invoke({ messages: [ { role: "user", content: "Please remember that every Thursday is trash day.", }, ], }); printMessages(result.messages); ``` ```output User: Please remember that every Thursday is trash day. upsert_memory({"content":"Every Thursday is trash day."}) upsert_memory -> "Stored memory." Assistant: I've remembered that every Thursday is trash day! ``` And now that it has stored it, let's see if it remembers. Remember - there's no checkpointer here. Every time we invoke the agent it's an entirely new conversation. ```typescript result = await agent.invoke({ messages: [ { role: "user", content: "When am I supposed to take out the garbage?", }, ], }); printMessages(result.messages); ``` ```output User: When am I supposed to take out the garbage? Assistant: You take out the garbage every Thursday, as it's trash day for you. ``` ## Advanced Usage The example above is quite simple, but hopefully it helps you to imagine how you might interweave storage and recall mechanisms into your agents. In the sections below we touch on a few more topics that might help you as you get into more advanced use cases. ### Multi-vector indexing You can store and search different aspects of memories separately to improve recall or to omit certain fields from the semantic indexing process. ```typescript import { InMemoryStore } from "@langchain/langgraph"; // Configure store to embed both memory content and emotional context const multiVectorStore = new InMemoryStore({ index: { embeddings: embeddings, dims: 1536, fields: ["memory", "emotional_context"], }, }); // Store memories with different content/emotion pairs await multiVectorStore.put(["user_123", "memories"], "mem1", { memory: "Had pizza with friends at Mario's", emotional_context: "felt happy and connected", this_isnt_indexed: "I prefer ravioli though", }); await multiVectorStore.put(["user_123", "memories"], "mem2", { memory: "Ate alone at home", emotional_context: "felt a bit lonely", this_isnt_indexed: "I like pie", }); // Search focusing on emotional state - matches mem2 const results = await multiVectorStore.search(["user_123", "memories"], { query: "times they felt isolated", limit: 1, }); console.log("Expect mem 2"); for (const r of results) { console.log(`Item: ${r.key}; Score(${r.score})`); console.log(`Memory: ${r.value.memory}`); console.log(`Emotion: ${r.value.emotional_context}`); } ``` ```output Expect mem 2 Item: mem2; Score(0.58961641225287) Memory: Ate alone at home Emotion: felt a bit lonely ``` ### Override fields at storage time You can override which fields to embed when storing a specific memory using `put(..., { index: [...fields] })`, regardless of the store's default configuration. ```typescript import { InMemoryStore } from "@langchain/langgraph"; const overrideStore = new InMemoryStore({ index: { embeddings: embeddings, dims: 1536, // Default to embed memory field fields: ["memory"], } }); // Store one memory with default indexing await overrideStore.put(["user_123", "memories"], "mem1", { memory: "I love spicy food", context: "At a Thai restaurant", }); // Store another memory, overriding which fields to embed await overrideStore.put(["user_123", "memories"], "mem2", { memory: "I love spicy food", context: "At a Thai restaurant", // Override: only embed the context index: ["context"] }); // Search about food - matches mem1 (using default field) console.log("Expect mem1"); const results2 = await overrideStore.search(["user_123", "memories"], { query: "what food do they like", limit: 1, }); for (const r of results2) { console.log(`Item: ${r.key}; Score(${r.score})`); console.log(`Memory: ${r.value.memory}`); } // Search about restaurant atmosphere - matches mem2 (using overridden field) console.log("Expect mem2"); const results3 = await overrideStore.search(["user_123", "memories"], { query: "restaurant environment", limit: 1, }); for (const r of results3) { console.log(`Item: ${r.key}; Score(${r.score})`); console.log(`Memory: ${r.value.memory}`); } ``` ```output Expect mem1 Item: mem1; Score(0.3375009515587189) Memory: I love spicy food Expect mem2 Item: mem2; Score(0.1920732213417712) Memory: I love spicy food ``` --- how-tos/subgraph-persistence.ipynb --- # How to add thread-level persistence to subgraphs

Prerequisites

This guide assumes familiarity with the following:

This guide shows how you can add [thread-level](https://langchain-ai.github.io/langgraphjs/how-tos/persistence/) persistence to graphs that use [subgraphs](https://langchain-ai.github.io/langgraphjs/how-tos/subgraph/). ## Setup First, let's install required packages: ```bash $ npm install @langchain/langgraph @langchain/core ```

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.

## Define the graph with persistence To add persistence to a graph with subgraphs, all you need to do is pass a [checkpointer](https://langchain-ai.github.io/langgraphjs/reference/classes/checkpoint.BaseCheckpointSaver.html) when **compiling the parent graph**. LangGraph will automatically propagate the checkpointer to the child subgraphs.

Note

You shouldn't provide a checkpointer when compiling a subgraph. Instead, you must define a **single** checkpointer that you pass to parentGraph.compile(), and LangGraph will automatically propagate the checkpointer to the child subgraphs. If you pass the checkpointer to the subgraph.compile(), it will simply be ignored. This also applies when you add a node that invokes the subgraph explicitly.

Let's define a simple graph with a single subgraph node to show how to do this. ```typescript import { StateGraph, Annotation } from "@langchain/langgraph"; // subgraph const SubgraphStateAnnotation = Annotation.Root({ foo: Annotation, bar: Annotation, }); const subgraphNode1 = async (state: typeof SubgraphStateAnnotation.State) => { return { bar: "bar" }; }; const subgraphNode2 = async (state: typeof SubgraphStateAnnotation.State) => { // note that this node is using a state key ('bar') that is only available in the subgraph // and is sending update on the shared state key ('foo') return { foo: state.foo + state.bar }; }; const subgraph = new StateGraph(SubgraphStateAnnotation) .addNode("subgraphNode1", subgraphNode1) .addNode("subgraphNode2", subgraphNode2) .addEdge("__start__", "subgraphNode1") .addEdge("subgraphNode1", "subgraphNode2") .compile(); // parent graph const StateAnnotation = Annotation.Root({ foo: Annotation, }); const node1 = async (state: typeof StateAnnotation.State) => { return { foo: "hi! " + state.foo, }; }; const builder = new StateGraph(StateAnnotation) .addNode("node1", node1) // note that we're adding the compiled subgraph as a node to the parent graph .addNode("node2", subgraph) .addEdge("__start__", "node1") .addEdge("node1", "node2"); ``` We can now compile the graph with an in-memory checkpointer (`MemorySaver`). ```typescript import { MemorySaver } from "@langchain/langgraph-checkpoint"; const checkpointer = new MemorySaver(); // You must only pass checkpointer when compiling the parent graph. // LangGraph will automatically propagate the checkpointer to the child subgraphs. const graph = builder.compile({ checkpointer: checkpointer }); ``` ## Verify persistence works Let's now run the graph and inspect the persisted state for both the parent graph and the subgraph to verify that persistence works. We should expect to see the final execution results for both the parent and subgraph in `state.values`. ```typescript const config = { configurable: { thread_id: "1" } }; ``` ```typescript const stream = await graph.stream({ foo: "foo" }, { ...config, subgraphs: true, }); for await (const [_source, chunk] of stream) { console.log(chunk); } ``` ```output { node1: { foo: 'hi! foo' } } { subgraphNode1: { bar: 'bar' } } { subgraphNode2: { foo: 'hi! foobar' } } { node2: { foo: 'hi! foobar' } } ``` We can now view the parent graph state by calling `graph.get_state()` with the same config that we used to invoke the graph. ```typescript (await graph.getState(config)).values; ``` ```output { foo: 'hi! foobar' } ``` To view the subgraph state, we need to do two things: 1. Find the most recent config value for the subgraph 2. Use `graph.getState()` to retrieve that value for the most recent subgraph config. To find the correct config, we can examine the state history from the parent graph and find the state snapshot before we return results from `node2` (the node with subgraph): ```typescript let stateWithSubgraph; const graphHistories = await graph.getStateHistory(config); for await (const state of graphHistories) { if (state.next[0] === "node2") { stateWithSubgraph = state; break; } } ``` The state snapshot will include the list of `tasks` to be executed next. When using subgraphs, the `tasks` will contain the config that we can use to retrieve the subgraph state: ```typescript const subgraphConfig = stateWithSubgraph.tasks[0].state; console.log(subgraphConfig); ``` ```output { configurable: { thread_id: '1', checkpoint_ns: 'node2:25814e09-45f0-5b70-a5b4-23b869d582c2' } } ``` ```typescript (await graph.getState(subgraphConfig)).values ``` ```output { foo: 'hi! foobar', bar: 'bar' } ``` If you want to learn more about how to modify the subgraph state for human-in-the-loop workflows, check out this [how-to guide](https://langchain-ai.github.io/langgraph/how-tos/subgraphs-manage-state/). --- how-tos/persistence.ipynb --- # Persistence Many AI applications need memory to share context across multiple interactions in a single conversational "thread." In LangGraph, this type of conversation-level memory can be added to any graph using [Checkpointers](https://langchain-ai.github.io/langgraphjs/reference/interfaces/index.Checkpoint.html). Just compile the graph with a compatible checkpointer. Below is an example using a simple in-memory "MemorySaver": ```javascript import { MemorySaver } from "@langchain/langgraph"; const checkpointer = new MemorySaver(); const graph = workflow.compile({ checkpointer }); ``` This guide shows how you can add thread-level persistence to your graph.

Note: multi-conversation memory

If you need memory that is shared across multiple conversations or users (cross-thread persistence), check out this how-to guide).

Note

In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the createReactAgent(model, tools=tool, checkpointer=checkpointer) (API doc) constructor. This may be more appropriate if you are used to LangChain's AgentExecutor class.

## Setup This guide will use OpenAI's GPT-4o model. We will optionally set our API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability. ```typescript // process.env.OPENAI_API_KEY = "sk_..."; // Optional, add tracing in LangSmith // process.env.LANGCHAIN_API_KEY = "ls__..."; process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true"; process.env.LANGCHAIN_TRACING_V2 = "true"; process.env.LANGCHAIN_PROJECT = "Persistence: LangGraphJS"; ``` ```output Persistence: LangGraphJS ``` ## Define the state The state is the interface for all of the nodes in our graph. ```typescript import { Annotation } from "@langchain/langgraph"; import { BaseMessage } from "@langchain/core/messages"; const GraphState = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), }), }); ``` ## Set up the tools We will first define the tools we want to use. For this simple example, we will use create a placeholder search engine. However, it is really easy to create your own tools - see documentation [here](https://js.langchain.com/docs/how_to/custom_tools) on how to do that. ```typescript import { tool } from "@langchain/core/tools"; import { z } from "zod"; const searchTool = tool(async ({}: { query: string }) => { // This is a placeholder for the actual implementation return "Cold, with a low of 13 ℃"; }, { name: "search", description: "Use to surf the web, fetch current information, check the weather, and retrieve other information.", schema: z.object({ query: z.string().describe("The query to use in your search."), }), }); await searchTool.invoke({ query: "What's the weather like?" }); const tools = [searchTool]; ``` We can now wrap these tools in a simple ToolNode. This object will actually run the tools (functions) whenever they are invoked by our LLM. ```typescript import { ToolNode } from "@langchain/langgraph/prebuilt"; const toolNode = new ToolNode(tools); ``` ## Set up the model Now we will load the [chat model](https://js.langchain.com/docs/concepts/#chat-models). 1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them. 2. It should work with [tool calling](https://js.langchain.com/docs/how_to/tool_calling/#passing-tools-to-llms), meaning it can return function arguments in its response.

Note

These model requirements are not general requirements for using LangGraph - they are just requirements for this one example.

```typescript import { ChatOpenAI } from "@langchain/openai"; const model = new ChatOpenAI({ model: "gpt-4o" }); ``` After we've done this, we should make sure the model knows that it has these tools available to call. We can do this by calling [bindTools](https://v01.api.js.langchain.com/classes/langchain_core_language_models_chat_models.BaseChatModel.html#bindTools). ```typescript const boundModel = model.bindTools(tools); ``` ## Define the graph We can now put it all together. We will run it first without a checkpointer: ```typescript import { END, START, StateGraph } from "@langchain/langgraph"; import { AIMessage } from "@langchain/core/messages"; import { RunnableConfig } from "@langchain/core/runnables"; const routeMessage = (state: typeof GraphState.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1] as AIMessage; // If no tools are called, we can finish (respond to the user) if (!lastMessage.tool_calls?.length) { return END; } // Otherwise if there is, we continue and call the tools return "tools"; }; const callModel = async ( state: typeof GraphState.State, config?: RunnableConfig, ) => { const { messages } = state; const response = await boundModel.invoke(messages, config); return { messages: [response] }; }; const workflow = new StateGraph(GraphState) .addNode("agent", callModel) .addNode("tools", toolNode) .addEdge(START, "agent") .addConditionalEdges("agent", routeMessage) .addEdge("tools", "agent"); const graph = workflow.compile(); ``` ```typescript let inputs = { messages: [{ role: "user", content: "Hi I'm Yu, nice to meet you." }] }; for await ( const { messages } of await graph.stream(inputs, { streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output Hi I'm Yu, nice to meet you. ----- Hi Yu! Nice to meet you too. How can I assist you today? ----- ``` ```typescript inputs = { messages: [{ role: "user", content: "Remember my name?" }] }; for await ( const { messages } of await graph.stream(inputs, { streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output Remember my name? ----- You haven't shared your name with me yet. What's your name? ----- ``` ## Add Memory Let's try it again with a checkpointer. We will use the MemorySaver, which will "save" checkpoints in-memory. ```typescript import { MemorySaver } from "@langchain/langgraph"; // Here we only save in-memory const memory = new MemorySaver(); const persistentGraph = workflow.compile({ checkpointer: memory }); ``` ```typescript let config = { configurable: { thread_id: "conversation-num-1" } }; inputs = { messages: [{ role: "user", content: "Hi I'm Jo, nice to meet you." }] }; for await ( const { messages } of await persistentGraph.stream(inputs, { ...config, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output Hi I'm Jo, nice to meet you. ----- Hello Jo, nice to meet you too! How can I assist you today? ----- ``` ```typescript inputs = { messages: [{ role: "user", content: "Remember my name?"}] }; for await ( const { messages } of await persistentGraph.stream(inputs, { ...config, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output Remember my name? ----- Yes, I'll remember that your name is Jo. How can I assist you today? ----- ``` ## New Conversational Thread If we want to start a new conversation, we can pass in a different **`thread_id`**. Poof! All the memories are gone (just kidding, they'll always live in that other thread)! ```typescript config = { configurable: { thread_id: "conversation-2" } }; ``` ```output { configurable: { thread_id: 'conversation-2' } } ``` ```typescript inputs = { messages: [{ role: "user", content: "you forgot?" }] }; for await ( const { messages } of await persistentGraph.stream(inputs, { ...config, streamMode: "values", }) ) { let msg = messages[messages?.length - 1]; if (msg?.content) { console.log(msg.content); } else if (msg?.tool_calls?.length > 0) { console.log(msg.tool_calls); } else { console.log(msg); } console.log("-----\n"); } ``` ```output you forgot? ----- ``````output Could you please provide more context or details about what you are referring to? This will help me assist you better. ----- ``` --- how-tos/tool-calling.ipynb --- # How to call tools using ToolNode This guide covers how to use LangGraph's prebuilt `ToolNode` for tool calling. `ToolNode` is a LangChain Runnable that takes graph state (with a list of messages) as input and outputs state update with the result of tool calls. It is designed to work well out-of-box with LangGraph's prebuilt ReAct agent, but can also work with any `StateGraph` as long as its state has a `messages` key with an appropriate reducer (see `MessagesAnnotation`). ## Setup ```bash npm install @langchain/langgraph @langchain/anthropic @langchain/core zod ``` Set env vars: ```typescript process.env.ANTHROPIC_API_KEY = 'your-anthropic-api-key'; ``` ## Define tools ```typescript import { tool } from '@langchain/core/tools'; import { z } from 'zod'; const getWeather = tool((input) => { if (['sf', 'san francisco'].includes(input.location.toLowerCase())) { return 'It\'s 60 degrees and foggy.'; } else { return 'It\'s 90 degrees and sunny.'; } }, { name: 'get_weather', description: 'Call to get the current weather.', schema: z.object({ location: z.string().describe("Location to get the weather for."), }) }) const getCoolestCities = tool(() => { return 'nyc, sf'; }, { name: 'get_coolest_cities', description: 'Get a list of coolest cities', schema: z.object({ noOp: z.string().optional().describe("No-op parameter."), }) }) ``` ```typescript import { ToolNode } from '@langchain/langgraph/prebuilt'; const tools = [getWeather, getCoolestCities] const toolNode = new ToolNode(tools) ``` ## Manually call `ToolNode` `ToolNode` operates on graph state with a list of messages. It expects the last message in the list to be an `AIMessage` with `tool_calls` parameter. Let's first see how to invoke the tool node manually: ```typescript import { AIMessage } from '@langchain/core/messages'; const messageWithSingleToolCall = new AIMessage({ content: "", tool_calls: [ { name: "get_weather", args: { location: "sf" }, id: "tool_call_id", type: "tool_call", } ] }) await toolNode.invoke({ messages: [messageWithSingleToolCall] }) ``` ```output { messages: [ ToolMessage { "content": "It's 60 degrees and foggy.", "name": "get_weather", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "tool_call_id" } ] } ``` Note that typically you don't need to create `AIMessage` manually, and it will be automatically generated by any LangChain chat model that supports tool calling. You can also do parallel tool calling using `ToolNode` if you pass multiple tool calls to `AIMessage`'s `tool_calls` parameter: ```typescript const messageWithMultipleToolCalls = new AIMessage({ content: "", tool_calls: [ { name: "get_coolest_cities", args: {}, id: "tool_call_id", type: "tool_call", }, { name: "get_weather", args: { location: "sf" }, id: "tool_call_id_2", type: "tool_call", } ] }) await toolNode.invoke({ messages: [messageWithMultipleToolCalls] }) ``` ```output { messages: [ ToolMessage { "content": "nyc, sf", "name": "get_coolest_cities", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "tool_call_id" }, ToolMessage { "content": "It's 60 degrees and foggy.", "name": "get_weather", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "tool_call_id_2" } ] } ``` ## Using with chat models We'll be using a small chat model from Anthropic in our example. To use chat models with tool calling, we need to first ensure that the model is aware of the available tools. We do this by calling `.bindTools` method on `ChatAnthropic` model ```typescript import { ChatAnthropic } from "@langchain/anthropic"; const modelWithTools = new ChatAnthropic({ model: "claude-3-haiku-20240307", temperature: 0 }).bindTools(tools); ``` ```typescript const responseMessage = await modelWithTools.invoke("what's the weather in sf?"); responseMessage.tool_calls; ``` ```output [ { name: 'get_weather', args: { location: 'sf' }, id: 'toolu_01UAjv9Mmj9LRosAsrgKtqeR', type: 'tool_call' } ] ``` As you can see, the AI message generated by the chat model already has `tool_calls` populated, so we can just pass it directly to `ToolNode` ```typescript await toolNode.invoke({ messages: [await modelWithTools.invoke("what's the weather in sf?")] }) ``` ```output { messages: [ ToolMessage { "content": "It's 60 degrees and foggy.", "name": "get_weather", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_01HrJmUek2ninxDiLJrYpDpz" } ] } ``` ## ReAct Agent Next, let's see how to use `ToolNode` inside a LangGraph graph. Let's set up a graph implementation of the ReAct agent. This agent takes some query as input, then repeatedly call tools until it has enough information to resolve the query. We'll be using `ToolNode` and the Anthropic model with tools we just defined ```typescript import { StateGraph, MessagesAnnotation, END, START } from "@langchain/langgraph"; const toolNodeForGraph = new ToolNode(tools) const shouldContinue = (state: typeof MessagesAnnotation.State) => { const { messages } = state; const lastMessage = messages[messages.length - 1]; if ("tool_calls" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls?.length) { return "tools"; } return END; } const callModel = async (state: typeof MessagesAnnotation.State) => { const { messages } = state; const response = await modelWithTools.invoke(messages); return { messages: response }; } const workflow = new StateGraph(MessagesAnnotation) // Define the two nodes we will cycle between .addNode("agent", callModel) .addNode("tools", toolNodeForGraph) .addEdge(START, "agent") .addConditionalEdges("agent", shouldContinue, ["tools", END]) .addEdge("tools", "agent"); const app = workflow.compile() ``` ```typescript import * as tslab from "tslab"; const drawableGraph = app.getGraph(); const image = await drawableGraph.drawMermaidPng(); const arrayBuffer = await image.arrayBuffer(); await tslab.display.png(new Uint8Array(arrayBuffer)); ``` Let's try it out! ```typescript import { HumanMessage } from "@langchain/core/messages"; // example with a single tool call const stream = await app.stream( { messages: [{ role: "user", content: "what's the weather in sf?" }], }, { streamMode: "values" } ) for await (const chunk of stream) { const lastMessage = chunk.messages[chunk.messages.length - 1]; const type = lastMessage._getType(); const content = lastMessage.content; const toolCalls = lastMessage.tool_calls; console.dir({ type, content, toolCalls }, { depth: null }); } ``` ```output { type: 'human', content: "what's the weather in sf?", toolCalls: undefined } { type: 'ai', content: [ { type: 'text', text: "Okay, let's check the weather in SF:" }, { type: 'tool_use', id: 'toolu_01X5yTzVrGZqNz9vf1w2MCna', name: 'get_weather', input: { location: 'sf' } } ], toolCalls: [ { name: 'get_weather', args: { location: 'sf' }, id: 'toolu_01X5yTzVrGZqNz9vf1w2MCna', type: 'tool_call' } ] } { type: 'tool', content: "It's 60 degrees and foggy.", toolCalls: undefined } { type: 'ai', content: 'The current weather in San Francisco is 60 degrees and foggy.', toolCalls: [] } ``` ```typescript // example with a multiple tool calls in succession const streamWithMultiToolCalls = await app.stream( { messages: [{ role: "user", content: "what's the weather in the coolest cities?" }], }, { streamMode: "values" } ) for await (const chunk of streamWithMultiToolCalls) { const lastMessage = chunk.messages[chunk.messages.length - 1]; const type = lastMessage._getType(); const content = lastMessage.content; const toolCalls = lastMessage.tool_calls; console.dir({ type, content, toolCalls }, { depth: null }); } ``` ```output { type: 'human', content: "what's the weather in the coolest cities?", toolCalls: undefined } { type: 'ai', content: [ { type: 'text', text: "Okay, let's find out the weather in the coolest cities:" }, { type: 'tool_use', id: 'toolu_017RHcsJFeo7w6kDnZ6TAa19', name: 'get_coolest_cities', input: { noOp: 'dummy' } } ], toolCalls: [ { name: 'get_coolest_cities', args: { noOp: 'dummy' }, id: 'toolu_017RHcsJFeo7w6kDnZ6TAa19', type: 'tool_call' } ] } { type: 'tool', content: 'nyc, sf', toolCalls: undefined } { type: 'ai', content: [ { type: 'text', text: "Now let's get the weather for those cities:" }, { type: 'tool_use', id: 'toolu_01ML1jW5u5aVCFkZhihzLv24', name: 'get_weather', input: { location: 'nyc' } } ], toolCalls: [ { name: 'get_weather', args: { location: 'nyc' }, id: 'toolu_01ML1jW5u5aVCFkZhihzLv24', type: 'tool_call' } ] } { type: 'tool', content: "It's 90 degrees and sunny.", toolCalls: undefined } { type: 'ai', content: [ { type: 'tool_use', id: 'toolu_0187eWumoCgxjnCjq4RGHyun', name: 'get_weather', input: { location: 'sf' } } ], toolCalls: [ { name: 'get_weather', args: { location: 'sf' }, id: 'toolu_0187eWumoCgxjnCjq4RGHyun', type: 'tool_call' } ] } { type: 'tool', content: "It's 60 degrees and foggy.", toolCalls: undefined } { type: 'ai', content: 'Based on the weather results, it looks like San Francisco is the coolest of the coolest cities, with a temperature of 60 degrees and foggy conditions. New York City is warmer at 90 degrees and sunny.', toolCalls: [] } ``` `ToolNode` can also handle errors during tool execution. See our guide on handling errors in `ToolNode` here.