1.概述
在使用langgraph创建工作流系列4:人机回环中对于人机回环进行了全面介绍,并且完整实现了工具调用时用户如何接入检查调用参数。本文对于与langgraph中的人机回环的使用模式和常见问题进行详细说明。
在langgraph中的人机回环有三种模式:审批模式、修订模式和工具调用检查模式。
2.审批模式
如同OA工作流中的审批一样,在langgraph中借助中断可以是工作流进行审批,用户可以同意,也可以拒绝。在一个节点内部可以触发中断,用户对于中断时数据进行审批,同意和拒绝后分别走不通的路径,如下图所示:

以下的例子中,借助大模型生成内容,针对生成的内容进行审批,如果满足用户的要求,则反馈同意,否则反馈拒绝,根据反馈结果进入到不同的下一节点。
首先,导入必要的包和初始化大模型:
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command, interruptllm = ChatOpenAI(
model = 'deepseek-chat',
api_key = "sk-*",
base_url = "https://api.deepseek.com",
)
创建图,一个chatbot节点,调用大模型生成内容,一个human_node节点,产生中断,a_node和b_node节点分别是审批同意和拒绝后进入的节点,具体代码如下:
class State(TypedDict):
messages: Annotated[list, add_messages]
result: strgraph_builder = StateGraph(State)
def chatbot(state: State) ->State:
return {"messages": [llm.invoke(state["messages"])]}def a_node(state: State) -> State:
return {"result": "approval"}def b_node(state: State) -> State:
return {"result": "rejected"}
def human_node(state: State) -> Command[str]:
action = interrupt({#产生中断。提示用于审批,并提供审批内容
"question": "Do you approve the llm's output? A---approve, Other---reject",
"text_to_approve": state["messages"][-1].content}
)
if action == "A":
return Command(goto="a_node")
else:
return Command(goto="b_node")checkpointer = InMemorySaver()
graph = (
StateGraph(State)
.add_node(chatbot)
.add_node(a_node)
.add_node(b_node)
.add_node(human_node, destinations=('a_node', 'b_node'))
.add_edge(START, "chatbot")
.add_edge("chatbot", "human_node")
.add_edge("a_node", END)
.add_edge("b_node", END)
.compile(checkpointer=checkpointer)
)
绘制工作流图,确认图的正确性:
from IPython.display import Image, display
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
# This requires some extra dependencies and is optional
pass
调用图,创作一首关于友谊的7言诗,大模型生成诗后,在human_node内部产生中断:
config = {"configurable": {"thread_id": "1"}}
result = graph.invoke({"messages": [{"role": "user", "content": "写一首关于友谊的7言诗"}]}, config=config)
print(result["__interrupt__"][0].value['question'])
print(result["__interrupt__"][0].value['text_to_approve'])
中断内容如下:
Do you approve the llm's output? A---approve, Other---reject
《赠别》
相知数载胜亲情,
患难时分见赤诚。
岁月沧桑人未老,
天涯海角总同行。注:我的诗以友谊恒久为主题,通过“相知数载”、“患难见诚”等意象,展现岁月沉淀的真情。后两句以“人未老”、“总同行”形成时空张力,暗喻纵使沧海桑田、天涯相隔,挚友间心灵的相伴永不褪色。全诗承转自然,结句余韵悠长。
用户审批后返回,从中断流程所在节点开始出继续执行:
result = graph.invoke(Command(resume="A"), config=config)
print(result['messages'])
输出如下:
[HumanMessage(content='写一首关于友谊的7言诗', additional_kwargs={}, response_metadata={}, id='b36e200b-036a-42ba-b343-6d069768622c'), AIMessage(content='《赠别》\n相知数载胜亲情,\n患难时分见赤诚。\n岁月沧桑人未老,\n天涯海角总同行。\n\n注:我的诗以友谊恒久为主题,通过“相知数载”、“患难见诚”等意象,展现岁月沉淀的真情。后两句以“人未老”、“总同行”形成时空张力,暗喻纵使沧海桑田、天涯相隔,挚友间心灵的相伴永不褪色。全诗承转自然,结句余韵悠长。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 109, 'prompt_tokens': 12, 'total_tokens': 121, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 12}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_ffc7281d48_prod0820_fp8_kvcache', 'id': '61b5d1b1-aa34-4206-8df0-5e03906206b5', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--faa1877e-021f-4aed-a59c-6be42918842d-0', usage_metadata={'input_tokens': 12, 'output_tokens': 109, 'total_tokens': 121, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]
3.修订模式
采用修订模式时,用户接入对于内容进行检查,还可以对内容进行修改补充。该模式下,在一个节点内部产生中断,由用户对内容进行修订,从中断返回并完成处理后进入下一节点,具体如下图所示:

以下的例子中,借助大模型生成内容,针对生成的内容进行审批,如果满足用户的要求,则反馈同意,否则反馈拒绝,根据反馈结果进入到不同的下一节点。
首先创建图,intent_recognition根据用户输入进行意图识别,human_node产生中断,让用户修改意图,next_node仅输出修改后的意图用以说明问题,具体代码如下:
class State(TypedDict):
messages: Annotated[list, add_messages]
intent: strgraph_builder = StateGraph(State)
def intent_recognition(state: State) ->State:
prompt = f"""
根据用户输入分析用户的意图类型,返回以下之一:
- make: 用户想制作电子印章
- manage: 用户想对印章进行管理,比如对印章冻结、解冻、注销、续期和变更
- grant: 用户想把印章授权给其他用户管理管理或使用
— consult: 用户想咨询印章相关的业务知识或技术知识
— chat: 其他
用户输入{state['messages']}
"""
return {'intent': llm.predict(prompt)}def next_node(state: State) -> State:
print(f"✅ Using edited intent: {state['intent']}")
return statedef human_node(state: State) -> dict:
result = interrupt({
"task": "Please review and edit the recognition intent if necessary.",
"intent_to_review": state["intent"]}
)
return {"intent": result['edited_intent']}checkpointer = InMemorySaver()
graph = (
StateGraph(State)
.add_node(intent_recognition)
.add_node(next_node)
.add_node(human_node)
.add_edge(START, "intent_recognition")
.add_edge("intent_recognition", "human_node")
.add_edge("human_node", "next_node")
.add_edge("next_node", END)
.compile(checkpointer=checkpointer)
)
绘制工作流图,确认工作流正确性:
from IPython.display import Image, display
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
# This requires some extra dependencies and is optional
pass
调用图,具体代码如下:
config = {"configurable": {"thread_id": "1"}}
user_input = "我的印章要过期了"result = graph.invoke({"messages": [{"role": "user", "content": user_input}]}, config=config)
print(result["__interrupt__"][0].value['task'])
print(result["__interrupt__"][0].value['intent_to_review'])
输出断点信息如下:
Please review and edit the recognition intent if necessary.
根据用户输入内容"我的印章要过期了",分析用户意图类型为:**manage**
理由:用户提到印章即将过期,这属于印章生命周期管理中的续期相关操作,符合"对印章进行管理,比如对印章冻结、解冻、注销、续期和变更"的范畴。
再次调用,输入修改后的信息,并从中断返回,具体代码如下:
edited_intent = "make"
resumed_result = graph.invoke(
Command(resume={"edited_intent": edited_intent}),
config=config
)
print(resumed_result)
输出如下:
✅ Using edited intent: make
{'messages': [HumanMessage(content='我的印章要过期了', additional_kwargs={}, response_metadata={}, id='e27e0f9f-9b34-4303-91f7-4934e991626e')], 'intent': 'make'}
4.工具调用检查模式
本模式具体实现可参考基于Agent Chat UI实现人机回环1-检查工具调用,此处不再赘述。
5.DEBUG图
interrupt可用于在图代码执行过程中设置断点,正如通用的DEBUG工具一样。可以在一个几点执行前后设置断点。可以在创建图时设置好断点,也可以在调用图时设置断点。
如下代码在node_a节点前设置断点,同时在node_b节点执行后设置断点。
5.1创建图时设置断点
graph = graph_builder.compile(
interrupt_before=["node_a"],
interrupt_after=["node_b"],
checkpointer=checkpointer,
)config = {
"configurable": {
"thread_id": "some_thread"
}
}graph.invoke(inputs, config=thread_config)
graph.invoke(None, config=thread_config)
5.2调用图时设置断点
config = {
"configurable": {
"thread_id": "some_thread"
}
}graph.invoke(inputs,
interrupt_before=["node_a"],
interrupt_after=["node_b"],
config=config) graph.invoke(None, config=config)


2142

被折叠的 条评论
为什么被折叠?



