Skip to content

如何使用 interrupt 等待用户输入

先决条件

本指南假设您熟悉以下概念:

人机交互(HIL) 互动对于代理系统至关重要。等待用户输入是 HIL 交互模式中常见的做法,允许代理向用户提出澄清问题,并在收到用户输入之前暂停执行。

我们可以在 LangGraph 中使用interrupt() 函数来实现这一点。interrupt 允许我们在收集用户输入时暂停图执行,并在收集到输入后继续执行。

设置

我们不会展示托管图的完整代码,但你可以在这里查看源代码,如果你感兴趣的话。一旦这个图被托管起来,我们就准备好调用它并等待用户输入。

SDK 初始化

首先,我们需要设置客户端以便能够与我们的托管图通信:

from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
# 使用名为"agent"部署的图
assistant_id = "agent"
thread = await client.threads.create()
import { Client } from "@langchain/langgraph-sdk";

const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// 使用名为"agent"部署的图
const assistantId = "agent";
const thread = await client.threads.create();
curl --request POST \
  --url <DEPLOYMENT_URL>/threads \
  --header 'Content-Type: application/json' \
  --data '{}'

等待用户输入

初始调用

现在,让我们调用我们的图。

input = {
    "messages": [
        {
            "role": "user",
            "content": "询问用户他们在哪里,然后查找那里的天气",
        }
    ]
}

async for chunk in client.runs.stream(
    thread["thread_id"],
    assistant_id,
    input=input,
    stream_mode="updates",
):
    if chunk.data and chunk.event != "metadata": 
        print(chunk.data)
const input = {
  messages: [
    {
      role: "human",
      content: "询问用户他们在哪里,然后查找那里的天气" }
  ]
};

const streamResponse = client.runs.stream(
  thread["thread_id"],
  assistantId,
  {
    input: input,
    streamMode: "updates",
  }
);

for await (const chunk of streamResponse) {
  if (chunk.data && chunk.event !== "metadata") {
    console.log(chunk.data);
  }
}
curl --request POST \
 --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
 --header 'Content-Type: application/json' \
 --data "{
   \"assistant_id\": \"agent\",
   \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"询问用户他们在哪里,然后查找那里的天气\"}]},
   \"stream_mode\": [
     \"updates\"
   ]
 }"

输出:

{'agent': {'messages': [{'content': [{'text': "我会帮助您询问用户的所在地,然后搜索天气信息。", 'type': 'text'}, {'id': 'toolu_012JeNEvyePZFWK39d52Wdwi', 'input': {'question': '您位于哪里?'}, 'name': 'AskHuman', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01UBEdS6UvuFMetdokNsykVG', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 438, 'output_tokens': 76}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-1b1210d8-39e0-4607-9f0e-0ea932d28d5c-0', 'example': False, 'tool_calls': [{'name': 'AskHuman', 'args': {'question': '您位于哪里?'}, 'id': 'toolu_012JeNEvyePZFWK39d52Wdwi', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 438, 'output_tokens': 76, 'total_tokens': 514, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
{'__interrupt__': [{'value': '您位于哪里?', 'resumable': True, 'ns': ['ask_human:2d41f894-f297-211e-9bfe-1d162ecba54a'], 'when': '期间'}]}

可以看到,我们的图在ask_human节点中被中断,现在正在等待提供一个位置

提供人类输入

我们可以通过使用Command(resume="<位置>")来提供人类输入(位置):

# 高亮下一行
from langgraph_sdk.schema import Command

async for chunk in client.runs.stream(
    thread["thread_id"],
    assistant_id,
    # 高亮下一行
    command=Command(resume="旧金山"),
    stream_mode="updates",
):
    if chunk.data and chunk.event != "metadata": 
        print(chunk.data)
const streamResponse = client.runs.stream(
  thread["thread_id"],
  assistantId,
  {
    // 高亮下一行
    command: { resume: "旧金山" },
    streamMode: "updates"
  }
);

for await (const chunk of streamResponse) {
  if (chunk.data && chunk.event !== "metadata") {
    console.log(chunk.data);
  }
}
curl --request POST \
 --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
 --header 'Content-Type: application/json' \
 --data "{
   \"assistant_id\": \"agent\",
   \"command\": {
     \"resume\": \"旧金山\"
   },
   \"stream_mode\": [
     \"updates\"
   ]
 }"

输出:

{'ask_human': {'messages': [{'tool_call_id': 'toolu_012JeNEvyePZFWK39d52Wdwi', 'type': 'tool', 'content': '旧金山'}]}}
{'agent': {'messages': [{'content': [{'text': '让我查询旧金山的天气。', 'type': 'text'}, {'id': 'toolu_019f9Y7ST6rNeDQkDjFCHk6C', 'input': {'query': '当前旧金山的天气'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_0152YFm7DtnzfZQuiMUzaSsw', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 527, 'output_tokens': 67}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-f509b5b2-eb30-4200-a8da-fa79ed68812a-0', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': '当前旧金山的天气'}, 'id': 'toolu_019f9Y7ST6rNeDQkDjFCHk6C', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 527, 'output_tokens': 67, 'total_tokens': 594, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
{'action': {'messages': [{'content': "我查到了:当前旧金山的天气。结果:旧金山今天阳光明媚,但双子座的人可要小心了 😈。", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': 'cbd0f623-cc12-48a2-8c18-3cbb943e46e0', 'tool_call_id': 'toolu_019f9Y7ST6rNeDQkDjFCHk6C', 'artifact': None, 'status': '成功'}]}}
{'agent': {'messages': [{'content': "根据查询结果,目前旧金山是晴天。您是否需要了解具体的天气预报详情?", 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01FhzXj72CehBYkJGX69vsBc', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 639, 'output_tokens': 29}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-f48e818e-dd88-415e-9a0b-4a958498b553-0', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 639, 'output_tokens': 29, 'total_tokens': 668, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}

Comments