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 object from the tool:
import{tool}from"@langchain/core/tools";constlookupUserInfo=tool(async(input,config)=>{constuserInfo=getUserInfo(config);returnnewCommand({// update state keysupdate:{user_info:userInfo,messages:[newToolMessage({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 and ToolNode).
Compatibility
This guide requires @langchain/langgraph>=0.2.33 and @langchain/core@0.3.23. For help upgrading, see this guide.
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.
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:
constUSER_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",},};
import{Annotation,Command,MessagesAnnotation}from"@langchain/langgraph";import{tool}from"@langchain/core/tools";import{z}from"zod";constStateAnnotation=Annotation.Root({...MessagesAnnotation.spec,// user providedlastName:Annotation<string>,// updated by the tooluserInfo:Annotation<Record<string,any>>,});constlookupUserInfo=tool(async(_,config)=>{constuserId=config.configurable?.user_id;if(userId===undefined){thrownewError("Please provide a user id in config.configurable");}if(USER_ID_TO_USER_INFO[userId]===undefined){thrownewError(`User "${userId}" not found`);}// Populated when a tool is called with a tool call from a model as inputconsttoolCallId=config.toolCall.id;returnnewCommand({update:{// update the state keysuserInfo:USER_ID_TO_USER_INFO[userId],// update the message historymessages:[{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({}),});
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:
conststateModifier=(state:typeofStateAnnotation.State)=>{constuserInfo=state.userInfo;if(userInfo==null){returnstate.messages;}constsystemMessage=`User name is ${userInfo.name}. User lives in ${userInfo.location}`;return[{role:"system",content:systemMessage,},...state.messages];};
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:
conststream=awaitagent.stream({messages:[{role:"user",content:"hi, what should i do this weekend?",}],},{// provide user ID in the configconfigurable:{user_id:"abc123"}});forawait(constchunkofstream){console.log(chunk);}
{ 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:
consttaylorStream=awaitagent.stream({messages:[{role:"user",content:"hi, what should i do this weekend?",}],},{// provide user ID in the configconfigurable:{user_id:"zyx987"}});forawait(constchunkoftaylorStream){console.log(chunk);}
{ 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 } } } ] }}
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:
import{MessagesAnnotation,isCommand,Command,StateGraph,}from"@langchain/langgraph";import{tool}from"@langchain/core/tools";import{isAIMessage}from"@langchain/core/messages";import{z}from"zod";constmyTool=tool(async()=>{returnnewCommand({update:{messages:[{role:"assistant",content:"hi there!",name:"Greeter",}],},});},{name:"greeting",description:"Updates the current state with a greeting",schema:z.object({}),});consttoolExecutor=async(state:typeofMessagesAnnotation.State)=>{constmessage=state.messages.at(-1);if(!isAIMessage(message)||message.tool_calls===undefined||message.tool_calls.length===0){thrownewError("Most recent message must be an AIMessage with a tool call.")}constoutputs=awaitPromise.all(message.tool_calls.map(async(toolCall)=>{// Using a single tool for simplicity, would need to select tools by toolCall.name// in practice.consttoolResult=awaitmyTool.invoke(toolCall);returntoolResult;}));// Handle mixed Command and non-Command outputsconstcombinedOutputs=outputs.map((output)=>{if(isCommand(output)){returnoutput;}// Tool invocation result is a ToolMessage, return a normal state updatereturn{messages:[output]};});// Return an array of values instead of an objectreturncombinedOutputs;};// Simple one node graphconstcustomGraph=newStateGraph(MessagesAnnotation).addNode("runTools",toolExecutor).addEdge("__start__","runTools").compile();awaitcustomGraph.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",}],}],});