跳到内容

如何从工具更新图状态

先决条件

本指南假定您熟悉以下内容

一个常见的用例是从工具内部更新图状态。例如,在客户支持应用程序中,您可能希望在对话开始时查找客户帐号或 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 的预构建组件(createReactAgentToolNode)来实现这一点。

兼容性

本指南需要 @langchain/langgraph>=0.2.33@langchain/core@0.3.23。有关升级方面的帮助,请参阅本指南

设置

安装以下内容以运行本指南

npm install @langchain/langgraph @langchain/openai @langchain/core

接下来,配置您的环境以连接到您的模型提供商。

export OPENAI_API_KEY=your-api-key

设置 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
          }
        }
      }
    ]
  }
}
我们可以看到模型正确地为 Bob Dylan 推荐了一些纽约的活动!让我们尝试为 Taylor Swift 获取推荐

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": []
    }
  ]
}