如何编辑已部署图的状态¶
在创建 LangGraph 代理时,添加人机协作组件通常会很有用。这在赋予它们访问工具的权限时会有帮助。在这些情况下,你可能经常希望在继续之前编辑图的状态(例如,编辑正在调用的工具或其调用方式)。
这可以通过几种方式实现,但主要支持的方式是在节点执行前添加一个“interrupt”(中断)。这会在该节点中断执行。然后你可以使用 update_state 更新状态,并从该点恢复以继续执行。
设置¶
我们不会展示托管图的完整代码,但如果你想查看,可以在这里找到。一旦托管了此图,我们就可以调用它并等待用户输入。
SDK 初始化¶
首先,我们需要设置客户端以便与我们托管的图进行通信
编辑状态¶
初始调用¶
现在我们来调用我们的图,确保在 action
节点之前中断。
const input = { messages: [{ role: "human", content: "search for weather in SF" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: input,
streamMode: "updates",
interruptBefore: ["action"],
}
);
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\": \"search for weather in SF\"}]},
\"interrupt_before\": [\"action\"],
\"stream_mode\": [
\"updates\"
]
}" | \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
sub(/^event: /, "", $0)
event_type = $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
}
'
输出
{'agent': {'messages': [{'content': [{'text': "Certainly! I'll search for the current weather in San Francisco for you using the search function. Here's how I'll do that:", 'type': 'text'}, {'id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-6dbb0167-f8f6-4e2a-ab68-229b2d1fbb64', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
编辑状态¶
现在,假设我们实际上是想搜索 Sidi Frej(另一个首字母缩写为 SF 的城市)的天气。我们可以编辑状态以正确反映这一点
# First, lets get the current state
current_state = await client.threads.get_state(thread['thread_id'])
# Let's now get the last message in the state
# This is the one with the tool calls that we want to update
last_message = current_state['values']['messages'][-1]
# Let's now update the args for that tool call
last_message['tool_calls'][0]['args'] = {'query': 'current weather in Sidi Frej'}
# Let's now call `update_state` 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 client.threads.update_state(thread['thread_id'], {"messages": last_message})
// First, let's get the current state
const currentState = await client.threads.getState(thread["thread_id"]);
// 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.slice(-1)[0];
// Let's now update the args for that tool call
lastMessage.tool_calls[0].args = { query: "current weather in Sidi Frej" };
// Let's now call `update_state` 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 client.threads.updateState(thread["thread_id"], { values: { messages: lastMessage } });
输出
{'configurable': {'thread_id': '9c8f1a43-9dd8-4017-9271-2c53e57cf66a',
'checkpoint_ns': '',
'checkpoint_id': '1ef58e7e-3641-649f-8002-8b4305a64858'}}
恢复调用¶
现在我们可以恢复图的运行,但使用更新后的状态
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"stream_mode\": [
\"updates\"
]
}"| \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
sub(/^event: /, "", $0)
event_type = $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
}
'
输出
{'action': {'messages': [{'content': '["I looked up: current weather in Sidi Frej. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '1161b8d1-bee4-4188-9be8-698aecb69f10', 'tool_call_id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ'}]}}
{'agent': {'messages': [{'content': [{'text': 'I apologize for the confusion in my search query. It seems the search function interpreted "SF" as "Sidi Frej" instead of "San Francisco" as we intended. Let me search again with the full city name to get the correct information:', 'type': 'text'}, {'id': 'toolu_0111rrwgfAcmurHZn55qjqTR', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-b8c25779-cfb4-46fc-a421-48553551242f', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_0111rrwgfAcmurHZn55qjqTR'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
{'action': {'messages': [{'content': '["I looked up: current weather in San Francisco. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '6bc632ae-5ee6-4d01-9532-79c524a2d443', 'tool_call_id': 'toolu_0111rrwgfAcmurHZn55qjqTR'}]}}
{'agent': {'messages': [{'content': "Now, based on the search results, I can provide you with information about the current weather in San Francisco:\n\nThe weather in San Francisco is currently sunny. \n\nIt's worth noting that the search result included an unusual comment about Gemini, which doesn't seem directly related to the weather. This might be due to the search engine including some astrological information or a joke in its results. However, for the purpose of weather information, we can focus on the fact that it's sunny in San Francisco right now.\n\nIs there anything else you'd like to know about the weather in San Francisco or any other location?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-227a042b-dd97-476e-af32-76a3703af5d8', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
如你所见,它现在查找的是 Sidi Frej 的当前天气(尽管我们的虚拟搜索节点仍然返回 SF 的结果,因为在这个例子中我们实际上没有进行搜索,我们只是每次都返回相同的“旧金山阳光明媚...”结果)。