【Agent的革命之路——LangGraph】人机交互中的细节(等待用户的指令)

这节我们探讨下如何使用 interrupt 等待用户输入,虽然前面都有讲过,但是有部分同学还是一知半解,我决定把这一块详细的逻辑整理出来,从头到尾给大家讲述一下。
人机交互 (HIL) 交互对于 agentic 系统至关重要。等待人工输入是一种常见的 HIL 交互模式,允许 agentic 询问用户澄清问题并在继续之前等待输入。我们可以使用 interrupt() 函数在 LangGraph 中实现这一点。
interrupt 允许我们停止图形执行以收集用户的输入,并使用收集的输入继续执行。

安装

首先我们需要安装需要的包

pip install --quiet -U langgraph langchain_openai

或者

pip install --quiet -U langgraph langchain_anthropic

接下来,我们需要为 Anthropic 和/或 OpenAI(我们将使用的 LLM)设置 API 密钥

import getpass
import osdef 
_set_env(var: str):  
  if not os.environ.get(var):    
     os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("ANTHROPIC_API_KEY") # _set_env("OPENAI_API_KEY")

大家这里可以注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。 LangSmith 允许我们使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用程序 。这个根据大家的实际需要来使用。

简单的例子

让我们探讨一下使用人类反馈的基本示例。创建一个节点 human_feeback ,专门设计用于获取用户输入。这让我们能够在图 graph 中特定的选定点收集反馈。
human_feedback 节点内调用 interrupt()

设置一个检查指针以将图形的状态保存到该节点。使用 Command(resume=...)human_feedback 节点提供请求的值并恢复执行。
如下面例子:

from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import MemorySaver
from IPython.display import Image, display
class State(TypedDict):
    input: str
    user_feedback: str

def step_1(state):
    print("---Step 1---")
    pass

def human_feedback(state):
    print("---human_feedback---")
    feedback = interrupt("Please provide feedback:")
    return {"user_feedback": feedback}


def step_3(state):
    print("---Step 3---")
    pass
builder = StateGraph(State)
builder.add_node("step_1", step_1)
builder.add_node("human_feedback", human_feedback)
builder.add_node("step_3", step_3)
builder.add_edge(START, "step_1")
builder.add_edge("step_1", "human_feedback")
builder.add_edge("human_feedback", "step_3")
builder.add_edge("step_3", END)

memory = MemorySaver()
graph = builder.compile(checkpointer=memory)
display(Image(graph.get_graph().draw_mermaid_png()))

在这里插入图片描述
运行直到 human_feedback 处的断点 interrupt

initial_input = {"input": "hello world"}
thread = {"configurable": {"thread_id": "1"}}
for event in graph.stream(initial_input, thread, stream_mode="updates"):
    print(event)
    print("\n")

在这里插入图片描述
现在,我们可以使用用户的输入来手动更新图形状态:

for event in graph.stream(
    Command(resume="go to step 3!"), thread, stream_mode="updates"
):
	print(event)
	print("\n")

在这里插入图片描述
现在看下我们的状态:

graph.get_state(thread).values

在这里插入图片描述
我们反馈的状态就已经更新好啦。

交互模式下的Agent 的ReAct

Agent 的背景下,等待用户反馈对于提出澄清问题特别有用。同学们可能不太明白,我们接下来解释一下,我们创建一个能够进行工具调用的简单 ReAct 风格的代理。
在下面这个例子中,比较贴近我们实际的 agent 开发,我们将使用 OpenAi 的聊天模型以及模拟工具。需要注意的是我们用的是 Pydantic v2 BaseModel ,它需要 langchain-core >= 0.3 。由于 Pydantic v1v2 BaseModels 的混合,使用 langchain-core < 0.3 会导致错误。大家一定要注意自己的版本。

# 设定MessagesState 为State状态
from langgraph.graph import MessagesState, START
# 定义一个真实的搜索工具
from langchain_core.tools import tool
from langgraph.prebuilt import ToolNode
from langgraph.types import interrupt
from IPython.display import Image, display

# 这里有多种方式 可以用@tool 修饰,也可以继承扩展 BaseTool 
@tool
def search(query: str):
 	"""添加说明"""
    return f"我查了一下: {query}. Result:上海天气非常好,阳光明媚气温25度,如果你想要去滑雪,你最好别呆在这 😈."
# 这种方式之前讲到过,注意这两者的区别
class RequestSearch(BaseModel):
    """添加说明"""
    request: f"我查了一下: {query}. Result:上海天气非常好,阳光明媚气温25度,如果你想要去滑雪,你最好别呆在这 😈."
    
# 定义搜索工具
tools = [search]
tool_node = ToolNode(tools)

# 设置大模型
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini")

from pydantic import BaseModel

# 我们将把所有工具“bind”到模型,我们上面定义了实际工具,但我们还需要一个模拟工具来询问人类,由于 `bind_tools` 不仅接受工具,还接受工具自定义,我们可以为 `ask_human` 定义一个自定义工具
class AskHuman(BaseModel):
    """问人类一个问题"""
    question: str

model = model.bind_tools(tools + [AskHuman])
# 定义节点和条件边并定义确定是否继续的函数
def should_continue(state):
    messages = state["messages"]
    last_message = messages[-1]
    # 如果没有函数调用,则结束
    if not last_message.tool_calls:
        return END
    #  如果工具调用询问人类,我们将返回该节点
    # 我们还可以在此处添加逻辑,让其他系统知道有些事情需要人类输入。例如,发送一些通知消息,发送一些工单给其他系统等
    elif last_message.tool_calls[0]["name"] == "AskHuman":
        # sendMessages("🔔 需要人工处理的新请求", “info”, last_message)
        return "ask_human"

    # 否则,我们将继续处理自动化操作
    else:
        return "action"


# 定义调用模型的函数
def call_model(state):
    messages = state["messages"]
    response = model.invoke(messages)
    # 我们返回一个列表,因为这将添加到存在的现有列表中
    return {"messages": [response]}


# 我们定义一个假节点来询问人类
def ask_human(state):
    tool_call_id = state["messages"][-1].tool_calls[0]["id"]
    location = interrupt("请输入您的位置:")
    tool_message = [{"tool_call_id": tool_call_id, "type": "tool", "content": location}]
    return {"messages": tool_message}

# 构件图 graph
from langgraph.graph import END, StateGraph

# 定义一个图 graph
workflow = StateGraph(MessagesState)

# 定义三个节点
workflow.add_node("agent", call_model)
workflow.add_node("action", tool_node)
workflow.add_node("ask_human", ask_human)

# 把agent设置成开始节点,这个节点是第一个被调用的节点
workflow.add_edge(START, "agent")

# 我们现在添加一个条件边
workflow.add_conditional_edges(
    # 首先,我们定义起始节点。我们使用`agent`。这些是在调用`agent`节点后获取的边。
    "agent",
    # 接下来,我们传入确定下一步是调用哪个节点的should_continue函数。
    should_continue,
)

# 我们现在从 `tools` 到 `agent` 添加一条普通边。在调用 `tools` 之后,接下来调用 `agent` 节点。
workflow.add_edge("action", "agent")

# 收到人工回复后,我们返回给agent
workflow.add_edge("ask_human", "agent")

# 设置内存
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()

# 最后,我们开始编译!这会将其编译为 LangChain Runnable,这意味着我们可以像使用任何其他 Runnable 一样使用它,我们在 `ask_human` 节点之前添加一个断点,这样它就永远不会执行
app = workflow.compile(checkpointer=memory)
display(Image(app.get_graph().draw_mermaid_png()))

上面代码有点长,但是我都加了注释,大家可以直接复制下来尝试跑一下,下面是图的节点展示:
在这里插入图片描述

与代理交互

我们现在可以与 agent 进行交互。我们让它直接询问用户他们在什么位置,然后告诉用户天气。
使它首先使用 ask_human 工具,然后使用普通工具。

config = {"configurable": {"thread_id": "2"}}
for event in app.stream(
    {
        "messages": [
            (
                "user",
                "使用 search tool 询问用户他们在哪里,然后查找那里的天气",
            )
        ]
    },
    config,
    stream_mode="values",
):
    event["messages"][-1].pretty_print()

在这里插入图片描述
这个时候无哦们看下图的状态:

app.get_state(config).next

得到下面结果

('ask_human',)

从这里我们可以看到我们的图在 ask_human 节点内被中断,该节点现在正在等待提供 location 。我们可以通过使用 Command(resume="<location>") 输入调用图表来提供该值:

for event in app.stream(Command(resume="上海"), config, stream_mode="values"):
    event["messages"][-1].pretty_print()

在这里插入图片描述
到这里就完成了 agent 怎么等待用户输入了,大家是不是清晰很多了。实际上我们实现这一步除了依赖 Commandinterrupt 之外,主要还是将所有交互都保存在内存中,在内存里进行恢复中断。

基于大模型智能体AgentLangGraph入门与实战课程目标:本课程旨在为LangGraph的初学者提供深入的理论知识和实践技能,使其能够独立构建和部署基于LangGraph的应用程序。课程形式:理论讲解 + 实战演练第1课 LangGraph基础架构与环境配置-LangGraph的概念解析第2课 LangGraph基础架构与环境配置-LangGraph的环境搭建与依赖管理第3课 LangGraph的基础原理与应用入门-构建基本聊天机器人及使用工具增强第4课 LangGraph的基础原理与应用入门-内存管理、人在回路、状态更新第5课 LangGraph高级图控制技术-并行节点扇出和扇入、增加额外步骤、条件分支第6课 LangGraph高级图控制技术-稳定排序、Map-Reduce并行执行、图递归控制第7课 LangGraph持久化机制与状态管理-线程级持久化、子图持久化、跨线程持久化第8课 LangGraph Human-in-the-loop-断点设置、动态设置断点、编辑更新状态第9课 LangGraph Human-in-the-loop-等待用户输入、时间旅行、工具评审第10课 LangGraph在具有长期记忆的有状态Agent中的应用-长期记忆及短期记忆、过滤信息、删掉信息第11课 LangGraph在具有长期记忆的有状态Agent中的应用-摘要总结、跨线程持久化、代理语义搜索第12课 LangGraph工具集成与调用-直接调用ToolNode、大模型使用工具第13课 LangGraph工具集成与调用-工具调用报错处理、运行时值传递给工具、注入参数第14课 LangGraph工具集成与调用-配置传入工具、从工具更新图状态、管理大量工具第15课 LangGraph子图设计与实现-添加及使用子图、父图及子图状态管理第16课 LangGraph子图设计与实现-子图状态的查看与更新、子图输入输出的转换与处理第17课 LangGraph项目实战演练-多智能体系统主管委托各个代理第18课 LangGraph课程复习与答疑 自我反思案例及论文案例讲解
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乔巴先生24

乔巴谢谢你的鼓励哦,我会努力的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值