跳转到内容

如何向预构建的 ReAct 代理添加人工参与的流程

本教程将展示如何向预构建的 ReAct 代理添加人工参与的流程。请参阅本教程,了解如何开始使用预构建的 ReAct 代理

您可以通过将 interruptBefore: ["tools"] 传递给 createReactAgent,在调用工具之前添加断点。请注意,您需要使用检查点程序才能使其工作。

设置

首先,我们需要安装所需的软件包。

yarn add @langchain/langgraph @langchain/openai @langchain/core

本指南将使用 OpenAI 的 GPT-4o 模型。我们将可选地为 LangSmith tracing 设置我们的 API 密钥,这将为我们提供一流的可观察性。

// 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";
ReAct Agent with human-in-the-loop: LangGraphJS

代码

现在我们可以使用预构建的 createReactAgent 函数来设置我们的人工参与交互代理

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 });

用法

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");
}
what is the weather in SF california?
-----

[
  {
    name: 'get_weather',
    args: { location: 'SF, California' },
    type: 'tool_call',
    id: 'call_AWgaSjqaYVQN73kL0H4BNn1Q'
  }
]
-----
我们可以验证我们的图是否在正确的位置停止

const state = await agent.getState(config)
console.log(state.next)
[ 'tools' ]
现在我们可以批准或编辑工具调用,然后再继续到下一个节点。如果我们想批准工具调用,我们只需使用 null 输入继续流式传输图。如果我们想编辑工具调用,我们需要更新状态以具有正确的工具调用,然后在应用更新后,我们可以继续。

我们可以尝试恢复,我们将看到一个错误出现

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");
  }
Error: Unknown Location
 Please fix your mistakes.
-----

[
  {
    name: 'get_weather',
    args: { location: 'San Francisco, California' },
    type: 'tool_call',
    id: 'call_MfIPKpRDXRL4LcHm1BxwcSTk'
  }
]
-----
出现此错误是因为我们的工具参数“SF, California”不是我们的工具识别的位置。

让我们展示如何编辑工具调用以搜索“San Francisco”而不是“SF, California” - 因为我们的工具按书面形式将“San Francisco, CA”视为未知位置。我们将更新状态,然后恢复流式传输图,应该看不到任何错误出现。请注意,我们用于 messages 通道的 reducer 仅在使用了具有完全相同 ID 的消息时才会替换消息。因此,我们可以执行 new AiMessage(...),而必须直接修改来自 messages 通道的最后一条消息,并确保不编辑其 ID。

// 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 });
{
  configurable: {
    thread_id: '1',
    checkpoint_ns: '',
    checkpoint_id: '1ef6638d-bfbd-61d0-8004-2751c8c3f226'
  }
}

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");
}
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!
-----
太棒了!我们的图已正确更新以查询旧金山的天气,并从工具中获得了正确的“旧金山今天阳光明媚!”响应。