如何从工具更新图状态¶
一个常见的用例是从工具内部更新图状态。例如,在客户支持应用程序中,您可能希望在对话开始时查找客户帐号或 ID。要从工具更新图状态,您可以从工具返回一个 Command
对象
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(...)
});
重要提示
如果您想使用返回 Command
实例并更新图状态的工具,您可以选择使用预构建的 createReactAgent
/ ToolNode
组件,或者实现您自己的工具执行节点,该节点可以识别工具返回的 Command
对象,并返回传统状态更新和 Commands
的混合数组。
有关示例,请参阅本节。
本指南展示了如何使用 LangGraph 的预构建组件(createReactAgent
和 ToolNode
)来实现这一点。
兼容性
本指南需要 @langchain/langgraph>=0.2.33
和 @langchain/core@0.3.23
。有关升级方面的帮助,请参阅本指南。
设置¶
安装以下内容以运行本指南
接下来,配置您的环境以连接到您的模型提供商。
设置 LangSmith 以进行 LangGraph 开发
注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 使您可以使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用程序 — 有关如何开始使用的更多信息,请阅读此处。
让我们创建一个简单的 ReAct 风格的 Agent,它可以查找用户信息并根据用户信息个性化响应。
定义工具¶
首先,让我们定义我们将用于查找用户信息的工具。我们将使用一个简单的实现,它只是使用字典查找用户信息
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",
},
};
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<string>,
// updated by the tool
userInfo: Annotation<Record<string, any>>,
});
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({}),
});
定义提示¶
现在让我们添加个性化功能:我们将根据从工具更新状态后的状态值,对用户做出不同的响应。为了实现这一点,让我们定义一个函数,该函数将根据图状态动态构建系统提示。它将在每次调用 LLM 时被调用,并且函数输出将传递给 LLM
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];
};
定义图¶
最后,让我们使用预构建的 createReactAgent
和我们之前声明的组件,将其组合成一个单独的图
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,
})
使用它!¶
现在让我们尝试运行我们的 Agent。我们需要在配置中提供用户 ID,以便我们的工具知道要查找哪些信息
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);
}
{
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
}
}
}
]
}
}
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);
}
{
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
}
}
}
]
}
}
自定义组件¶
如果您不想使用预构建的组件,您需要在自定义工具执行器中添加特殊逻辑来处理命令。这是一个例子
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",
}],
}],
});
{
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": []
}
]
}