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, interrupt

llm = 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: str

graph_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: str

graph_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 state    

def 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) 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐