langgraph-人机交互

内容纲要

Human-in-the-loop(人机交互)

目前为止,现在能够实现的最复杂的一种 AI Agent 逻辑是:具备多外部工具支持的完全自主循环代理结构,同时具备多轮对话的上下文记忆能力。而具备这种能力的系统其实已经足够有效地应对大部分 AI 应用场景的需求,并且已经完全涵盖了 LangGraph 底层构建生产级别 Agent 的所必须掌握的知识点。

最后,要进行补充的是:这种完全自主循环代理构建起来的应用,在现实落地场景中,会衍生出什么迫切的需求?

一个有趣且现实的情况是:我们希望 AI Agent 能够帮助我们自主处理各种任务,但当它现在有能力做到时,我们又开始担心它可能做出不当的决策,尤其是涉及到高风险操作时。比如,Agent 可能会不小心删除生产环境中的数据库,或是转移账户中的余额等敏感数据。这些操作是无法容忍的。因此,在自主代理类架构下衍生出的非常明确的一类需求是:能否在保持 Agent 自主决策的基础上,对某些可以预见或计划的关键节点引入人工干预,暂时中止其自我决策过程,转而由人工介入进行审批和确认,再让其继续执行后续任务?比如以下场景:

删除数据库操作:当 Agent 决定删除数据库时,可以先中止操作,要求人工确认是否继续执行。经过人工确认后,Agent 才继续删除数据库并执行后续操作。

机票改签操作:当 Agent 决定更改机票时,系统会向用户发出通知,等待用户确认是否同意改签,用户点击确认后,Agent 才会继续执行改签操作。
……
通过这种方式,既能保持 Agent 的自主性,又能避免出现意外或不可控的风险,实现人机协作的平衡。而实现这种功能的技术,在 Agent 技术领域会被普遍称之为 Human-in-the-loop(HIL)。

一、LangGraph 中的 HIL

LangGraph 底层是通过图结构来进行构建,并由状态做消息的传递,那么对于这样的结构来说,如果我们想在这样的架构中加入人工的介入流程,能操作的大致思路应该是:通过 Router Agent 去做判断,如果生成的响应触发了某种条件,就在原本要正常进入的节点之前先停止,等待人工的确认,再决定要不要执行,或者执行什么逻辑。在这个过程中,有几种常见的用户交互模式,分别是:

  • 批准(Approval):在代理的执行过程中,人工暂停代理的自主工作流,向用户展示当前的状态,并批准或者不批准执行该操作。
  • 编辑(Editing):在需要的时候,人工可以暂停代理,向用户展示当前的状态,并允许用户对代理的状态进行编辑。
  • 输入(Input):专门设计一个图节点来收集用户的输入,并将这些输入直接用于更新代理的状态。

在 LangGraph 的设计思路下 HIL 通过战略性地放置断点(breakpoint)来实现的。这些断点会在关键点停止图的执行。在暂停期间,Agent 将等待用户输入,利用这段时间收集响应,将它们集成到图状态中,并顺利继续进行,从而实现用户和代理之间的协作和交互式体验 这个交互过程在 LangGraph 框架下的具体实现思路如下图所示:

LangGraph 能够在中断后继续运行的核心在于我们之前介绍的 checkpointer 组件。这个组件能够在一个独立的线程中保存图中每个节点的状态。由于这些信息被持久化保存(包括将内存用作持久存储),我们就可以随时提取并修改图产生的数据。更改完成后,再将这些数据重新传回到图中以继续运行流程。这一机制是 LangGraph 已成功实现的功能。由此可见 Human-in-the-loop(HIL)并不是一个全新的组件,而是基于 LangGraph 底层的构建组件延展出来的一种实现方法。那这里我们就需要清楚两个概念,其一是用于中断两个本应顺序执行节点的操作,在 LangGraph 中称其为 breakpoint(断点),其二是 breakpoint 是构建在 checkpointer 之上的。
因此,当我们需要在定义的图结构中加入人机交互,这个图必须具备的两个核心参数正如 compile() 方法中的源码所示:
链接:LangGraph Graph Compile : https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph.compile

file

其中,checkpointer 参数可以接收任意类型的 checkpointer,用来保存图的状态。而 interrupt_before 和 interrupt_after 参数,接收图中某个节点的名称,将其作为 breakpoint,起到的作用是在该节点之前 / 之后中断图的继续运行。我们来看一个具体的应用案例。

二. 标准图结构中如何添加断点

如下代码实现的业务场景是一个自动化的人机交互流程,用于执行内容删除这种高度敏感性的操作。我们定义两个主要的功能节点:call_model 和 execute_users。call_model 节点处理大模型的调用和响应,如果用户输入包含 “删除” 的内容,则会触发需要人工审批的流程。execute_users 节点根据人工审核的结果来决定最终的响应内容。如下代码所示:

一、等待人工输入模式 4 个关键步骤
等待人工输入是 HIL 最常用交互模式,Agent 遇到高危操作会暂停并向用户确认,流程四步:
配置断点:图编译时通过 interrupt_before / interrupt_after 指定需要暂停的节点;
配置检查点:必须传入 checkpointer 持久化会话状态,中断后保存上下文;
更新状态:人工输入审批结果后,调用 graph.update_state() 把人工响应写入状态;
恢复执行:无新输入调用 stream(None, config),从断点续跑完整图流程。

import getpass
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

# 1、配置模型
from dotenv import load_dotenv
load_dotenv()
import os
key = os.environ["OPENAI_API_KEY"]
base_url = os.environ["OPENAI_BASE_URL"]
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", api_key=key,base_url=base_url,temperature=0,)

import getpass
import os
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o", api_key=key,base_url=base_url,temperature=0,)

from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from IPython.display import Image, display
from langchain_core.tools import tool
from langgraph.graph import MessagesState, START
from langgraph.prebuilt import ToolNode
from langgraph.graph import END, StateGraph
import json
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, AIMessage

# 定义状态模式
class State(TypedDict):
    user_input: str
    model_response: str
    user_approval: str

# 定义用于大模型交互的节点
def call_model(state):
    messages = state["user_input"]
    if '删除' in state["user_input"]:
        state["user_approval"] = f"用户输入的指令是:{state['user_input']}, 请人工确认是否执行! "
    else:
        response = llm.invoke(messages)
        state["user_approval"] = "直接运行! "
        state["model_response"] = response
    return state

# 定义人工介入的breakpoint内部的执行逻辑
def execute_users(state):
    if state["user_approval"] == "是":
        response = "您的删除请求已经获得管理员的批准并成功执行。如果您有其他问题或需要进一步的帮助,请随时联系我们。"
        return {"model_response": AIMessage(response)}
    elif state["user_approval"] == "否":
        response = "对不起,您当前的请求是高风险操作,管理员不允许执行! "
        return {"model_response": AIMessage(response)}
    else:
        return state

# 定义翻译节点
def translate_message(state: State):
    system_prompt = """
Please translate the received text in any language into English as output
"""
    messages = state['model_response']
    messages = [SystemMessage(content=system_prompt)] + [HumanMessage(content=messages.content)]
    response = llm.invoke(messages)
    return {"model_response": response}

# 构建状态图
builder = StateGraph(State)

# 向图中添加节点
builder.add_node("call_model", call_model)
builder.add_node("execute_users", execute_users)
builder.add_node("translate_message", translate_message)

# 构建边
builder.add_edge(START, "call_model")
builder.add_edge("call_model", "execute_users")
builder.add_edge("execute_users", "translate_message")
builder.add_edge("translate_message", END)

# 设置 checkpointer,使用内存存储
memory = MemorySaver()

# 在编译图的时候,添加短期记忆,并使用interrupt_before参数 设置 在execute_users 节点之前停止图的运行,等待人工审批
graph = builder.compile(checkpointer=memory, interrupt_before=["execute_users"])
# 生成 Mermaid PNG 数据
image_data = graph.get_graph().draw_mermaid_png()
# 定义保存路径
file_path = "langgraph_structure.png"
# 写入文件
with open(file_path, "wb") as f:
    f.write(image_data)
print(f"✅ 图片已成功生成: {os.path.abspath(file_path)}")

# 在添加了 breakpoint(断点)后的图结构中,它的运行逻辑将变成:只要到达被设置为 breakpoint 的节点时,图就会中止运行。这里我们进行一个实际的调用测试来理解这个过程,代码如下所示:
# 创建一个线程
config = {"configurable": {"thread_id": "2"}}

# 运行图,直至到断点的节点
for chunk in graph.stream({"user_input": "我将在数据库中删除 id为 xigualaoshi 的所有信息"}, config, stream_mode="values"):
    print(chunk)

"""
输出结果:
{'user_input': '我将在数据库中删除 id为 xigualaoshi 的所有信息'}
{'user_input': '我将在数据库中删除 id为 xigualaoshi 的所有信息', 'user_approval': '用户输入的指令是:我将在数据库中删除 id为 xigualaoshi 的所有信息, 请人工确认是否执行! '}
通过输出结果,我们可以观察到在执行到 execute_users 节点之前就暂停了,它将等待人工介入以决定是否继续执行。这就是 breakpoint(断点)的关键作用。
在这个阶段,我们就可以审查并调整图的状态,而关键的处理逻辑是:要修改或确认全局状态模式中 user_approval 字段的值,从而指导 execute_users 节点的行为。
"""

snapshot = graph.get_state(config)

print(snapshot)
"""
结果:
StateSnapshot(values={'user_input': '我将在数据库中删除 id为 xigualaoshi 的所有信息', 'user_approval': '用户输入的指令是:我将在数据库中删除 id为 xigualaoshi 的所有信息, 请人工确认是否执行! '}, next=('execute_users',), config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1f179e82-e1ec-64b6-8001-98a80761772b'}}, metadata={'source': 'loop', 'step': 1, 'parents': {}}, created_at='2026-07-07T09:42:33.833797+00:00', parent_config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1f179e82-e1e8-6f43-8000-c1b42c11280b'}}, tasks=(PregelTask(id='2d8452f5-1493-1586-1cbc-965a46950aa5', name='execute_users', path=('__pregel_pull', 'execute_users'), error=None, interrupts=(), state=None, result=None),), interrupts=())

我们先来看如何在图中止运行后,在图状态中添加用户的决策。
为实现这一点,我们可以手动设置 snapshot.values['user_approval'] 为是,用以说明在即将执行的 execute_users 节点,人工审批的状态被手动设定为同意。
接下来,使用 graph.update_state(config, snapshot.values) 来更新状态图中的状态。这个调用将状态图中的当前状态更新为包含了新的 user_approval 值的 snapshot.values。
"""
# 1. 修改快照里的人工审批状态
snapshot.values['user_approval']='是'
# 2. 将修改后的状态写回检查点
graph.update_state(config, snapshot.values)

# 修改完状态后,如果想让图基于 breakpoint 继续执行后续的操作,则只需要在 astream 方法中将 input 参数设置为 None,则会让图从上一次中断的地方继续。如下代码所示:
for chunk in graph.stream(None, config, stream_mode="values"):
    print(chunk)

print("*" * 30,"人工修改审批标记为'否',拒绝高危删除操作","*" * 50)
# 1. 获取当前会话断点快照
snapshot = graph.get_state(config)
# 2. 人工修改审批标记为"否",拒绝高危删除操作
snapshot.values['user_approval']='否'
# 3. 将修改后的状态持久写入检查点
graph.update_state(config, snapshot.values)
# 4. input=None 从断点续跑完整图流程
for chunk in graph.stream(None, config, stream_mode="values"):
    print(chunk)

# 以上是两种触发删除的高危风险审核的逻辑,而如果 Agent 接收的是普通的问答交互,不涉及高危险操作指令时,则可以直接将 input 参数设置为 None,运行断点之后的图节点逻辑。如下代码所示:
print("*" * 30,"普通的问答交互,不涉及高危险操作指令","*" * 30)
# 运行图,直至到断点的节点
for chunk in graph.stream({"user_input": "你好,请你介绍一下你自己"}, config, stream_mode="values"):
    pass

for chunk in graph.stream(None, config, stream_mode="values"):
    print(chunk)

# 创建一个函数来封装对话逻辑
def run_dialogue(graph, config, all_chunks=[]):
    while True:
        # 接收用户输入
        user_input = input("请输入您的消息(输入'退出'结束对话):")
        if user_input.lower() == '退出':
            break

        # 运行图,直至到断点的节点
        for chunk in graph.stream({"user_input": user_input}, config, stream_mode="values"):
            all_chunks.append(chunk)

        # 处理可能的审批请求
        last_chunk = all_chunks[-1]
        if last_chunk["user_approval"] == f"用户输入的指令是:{last_chunk['user_input']}, 请人工确认是否执行! ":
            user_approval = input(f"当前用户的输入是:{last_chunk['user_input']},请人工确认是否执行!请回复 是/否。")
            graph.update_state(config, {"user_approval": user_approval})

        # 继续执行图(从断点向后跑完剩余节点)
        for chunk in graph.stream(None, config, stream_mode="values"):
            all_chunks.append(chunk)

        # 显示最终模型的响应
        print("人工智能助理:", all_chunks[-1]["model_response"].content)

if __name__ == "__main__":
    config = {"configurable": {"thread_id" : "3"}}

    run_dialogue(graph, config)

如上实现的功能所示,在图中的特定位置添加断点是启用人机协同的一种方法。在这种情况下,开发人员只要知道工作流中的哪个位置需要人工输入,只需在该特定图形节点之前或之后放置一个断点即可。LangGraph 的 breakpoint 和 HIL 功能提供了比较完善的实现机制来做交互式的图执行,允许用户输入来指导或中断流程。在这个案例中,我们展示的人机交互策略特别适用于有明确工作流的业务场景。开发人员可以根据业务逻辑精确地确定哪些环节可能涉及高风险操作,并通过人工决策介入来提升 Agent 应用程序的稳定性和可靠性。

接下来我们可以更近一步,看一下在具备工具调用的 Agent 架构中如何去做断点的人工交互逻辑,这包含 Tool Calling Agent 和 ReAct 两种模式。

三. 复杂代理架构中如何添加动态断点

file

上面案例中实现的人机交互模式,我们可以在不同的用户输入决策下完全自定义具体需要执行的操作逻辑。而这样的交互过程,我们可以手动实现来理解这个中间过程,当然也可以在 LangGraph 框架的封装下,借助 Router Agent 的机制从节点内部来进行动态管理,尤其是在涉及工具调用的 ReAct 框架下,其特征会更明显,且可以更加清晰的帮助我们理解 breakpoint 在图执行过程中的内部逻辑。其基本过程如下图所示:

file

四、一刀切,所有工具被调用前都需要人工审核

首先来考虑这样一个场景:对于 Tool Calling Agent 架构实现的应用程序,如果我们希望在执行任意一个工具之前,都需要对工具进行审核的话,那么这个过程就可以非常简单的通过如下代码来进行实现。这里我们先准备两个外部工具,一个用于实时检索互联网信息,一个用于检索实时天气。

from langchain_core.tools import tool
from typing import Union, Optional
from pydantic import BaseModel, Field
import requests
from langgraph.prebuilt import ToolNode
import json

from dotenv import load_dotenv
load_dotenv()
import os
weather_key = os.environ["OPEN_WEATHER_KEY"]
GOOGLE_SEARCH_KEY = os.environ["GOOGLE_SEARCH_KEY"]

class WeatherLoc(BaseModel):
    location: str = Field(description="The location name of the city")

class SearchQuery(BaseModel):
    query: str = Field(description="Questions for networking queries")

@tool(args_schema = SearchQuery)
def fetch_real_time_info(query):
    """Get real-time Internet information"""
    url = "https://google.serper.dev/search"
    payload = json.dumps({
        "q": query,
        "num": 1,
    })
    headers = {
        'X-API-KEY': GOOGLE_SEARCH_KEY,
        'Content-Type': 'application/json'
    }
    response = requests.post(url, headers=headers, data=payload)
    data = json.loads(response.text)  # 将返回的JSON字符串转换为字典
    if 'organic' in data:
        return json.dumps(data['organic'], ensure_ascii=False)  # 返回'organic'部分的JSON字符串
    else:
        return json.dumps({"error": "No organic results found"}, ensure_ascii=False)  # 如果没有'organic'键,返回错误信息

@tool(args_schema=WeatherLoc)
def get_weather(location):
    """
    Function to query current weather.
    :param loc: Required parameter, of type string, representing the specific city name for the weather query. \
    Note that for cities in China, the corresponding English city name should be used. For example, to query the weather for Beijing, \
    the loc parameter should be input as 'Beijing'.
    :return: The result of the OpenWeather API query for current weather, with the specific URL request address being: https://api.openweathermap.org/data/2.5/weather
    The return type is a JSON-formatted object after parsing, represented as a string, containing all important weather information.
    """
    # Step 1. 构建请求
    url = "https://api.openweathermap.org/data/2.5/weather"

    # Step 2. 设置查询参数
    params = {
        "q": location,
        "appid": weather_key,   # 输入API key
        "units": "metric",                           # 使用摄氏度而不是华氏度
        "lang":"zh_cn"                               # 输出语言为简体中文
    }

    # Step 3. 发送GET请求
    response = requests.get(url, params=params)

    # Step 4. 解析响应
    data = response.json()
    return json.dumps(data)

"""
@tool 装饰器(来自 langchain_core.tools)会把原本的普通函数包装成一个 StructuredTool / Tool 对象,
而不再是一个普通的 Python 函数。这个对象暴露的是 LangChain 工具接口(.invoke()、.run()、.ainvoke() 等),
原始函数被存到了 .func 属性里。 
正确调用:get_weather.invoke("beijing")
错误调用:get_weather("beijing")  —— 会报错,因为 get_weather 已经不是函数了,是 Tool 对象,
直接 () 调用不符合它的签名(且它的调用协议和普通函数不同)。
"""
weather_result = get_weather.invoke("beijing") 
# print(weather_result)

"""
该方法去掉装饰器之后,就变回普通方法,可以直接 方法名(参数) 调用
去掉 @tool(...) 后,get_weather 就是一个普通的 Python 函数,直接按位置/关键字参数调用即可
比如:get_weather("beijing")
"""
# weather_result = get_weather("beijing") 
# print(weather_result)

web_search = fetch_real_time_info.invoke("小米汽车多少钱?")
# print(web_search)

tools = [get_weather, fetch_real_time_info]
tool_node = ToolNode(tools)

# 定义大模型实例,这里使用 GPT-4o 模型进行演示。
import getpass
import os
import json
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.graph import MessagesState, START

from langgraph.graph import END, StateGraph
from langgraph.checkpoint.memory import MemorySaver

# 1、配置模型
from dotenv import load_dotenv
load_dotenv()
import os
key = os.environ["OPENAI_API_KEY"]
base_url = os.environ["OPENAI_BASE_URL"]
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", api_key=key,base_url=base_url,temperature=0,)
llm = llm.bind_tools(tools)

# 定义一个 Router Function,用来根据大模型的实时响应判断是执行外部函数调用还是直接输出最终的响应。

def should_continue(state):
    messages = state["messages"]
    last_message = messages[-1]
    # 如果没有工具调用,则输出至最终节点
    if not last_message.tool_calls:
        return "end"
    # 如果还有子任务需要继续执行工具调用的话,则继续等待执行
    else:
        return "continue"

# 用于大模型交互的节点函数,其功能是接收用户的响应,使用 GPT-4o 模型生成具体的响应文本。
def call_model(state):
    messages = state["messages"]
    response = llm.invoke(messages)
    return {"messages": [response]}

# 然后通过 LangGraph 基本构建图的方法,依次定义状态图 -> 向图中添加节点 -> 添加节点之间的边。
workflow = StateGraph(MessagesState)

workflow.add_node("agent", call_model)
workflow.add_node("action", tool_node)

workflow.add_edge(START, "agent")

# 添加条件边
workflow.add_conditional_edges(
    "agent",
    should_continue,
    {
        "continue": "action",
        "end": END,
    },
)

workflow.add_edge("action", "agent")

# 最后,如果想要在执行任意工具前都由人工介入进行确认,只需要在编译图的时候,在调用 action 节点之前添加一个 breakpoint(断点)。如下所示:
memory = MemorySaver()

graph = workflow.compile(checkpointer=memory, interrupt_before=["action"])

# 生成可视化图像结构
# 生成 Mermaid PNG 数据
image_data = graph.get_graph().draw_mermaid_png()
# 定义保存路径
file_path = "langgraph_structure.png"
# 写入文件
with open(file_path, "wb") as f:
    f.write(image_data)
print(f"✅ 图片已成功生成: {os.path.abspath(file_path)}")

# 编译后,则可以开始与基于该图构建出来的 Agent 进行对话交互,如下代码所示:
config = {"configurable": {"thread_id": "4"}}
for chunk in graph.stream({"messages": "请帮我查一下北京的天气"}, config, stream_mode="values"):
    chunk["messages"][-1].pretty_print()

# 输出示例
"""
================================ Human Message =================================

请帮我查一下beijing的天气
================================== Ai Message ==================================
Tool Calls:
  get_weather (chatcmpl-tool-bac88512411dd27d)
 Call ID: chatcmpl-tool-bac88512411dd27d
  Args:
    location: beijing
"""

print("*"*30,"断点中断后继续执行工具调用,input传None","*"*30)
# 断点中断后继续执行工具调用,input传None
for chunk in graph.stream(None, config, stream_mode="values"):
    chunk["messages"][-1].pretty_print()

"""
================================== Ai Message ==================================
Tool Calls:
  get_weather (chatcmpl-tool-bac88512411dd27d)
 Call ID: chatcmpl-tool-bac88512411dd27d
  Args:
    location: beijing
================================= Tool Message =================================
Name: get_weather

{"coord": {"lon": 116.3972, "lat": 39.9075}, "weather": [{"id": 802, "main": "Clouds", "description": "\u591a\u4e91", "icon": "03d"}], "base": "stations", "main": {"temp": 35.71, "feels_like": 36.21, "temp_min": 35.71, "temp_max": 35.71, "pressure": 996, "humidity": 32, "sea_level": 996, "grnd_level": 991}, "visibility": 10000, "wind": {"speed": 3.88, "deg": 201, "gust": 4.13}, "clouds": {"all": 32}, "dt": 1783496725, "sys": {"country": "CN", "sunrise": 1783457582, "sunset": 1783511123}, "timezone": 28800, "id": 1816670, "name": "Beijing", "cod": 200}
================================== Ai Message ==================================

根据查询结果,北京当前的天气情况如下:

**基本信息:**
- 城市:北京 (Beijing)
- 国家:中国 (CN)
- 时区:UTC+8

**天气状况:**
- 天气:多云 (Clouds)
- 温度:35.71°C
- 体感温度:36.21°C
- 最低/最高温度:35.71°C / 35.71°C

**其他信息:**
- 湿度:32%
- 风速:3.88 m/s,风向201度
- 能见度:10000米
- 云量:32%

**日出日落:**
- 日出时间:2025年9月6日 06:53:02
- 日落时间:2025年9月6日 18:45:23

总体来说,北京今天天气多云,气温较高(35.71°C),湿度适中(32%),风速较小,适合外出但需要注意防晒。
"""

"""
当 input 参数传入的值是 None 时,会传递状态模式中全部已产生的消息数据,所以它的执行过程看起来就像没有中断一样,按照我们构造的图结构顺利完成了所有节点的执行逻辑。对另外的一个 fetch_real_time_info 工具其实也是一样的,如果 Agent 判断需要调用的话,仍然会在执行前停止,等待人工批准。如下代码所示:
"""
config = {"configurable": {"thread_id": "5"}}
for chunk in graph.stream({"messages": "小米汽车"}, config, stream_mode="values"):
    chunk["messages"][-1].pretty_print()

# 执行输出示例
"""
================================ Human Message =================================

小米汽车
================================== Ai Message ==================================
Tool Calls:
  fetch_real_time_info (chatcmpl-tool-a3bf319f58709d0d)
 Call ID: chatcmpl-tool-a3bf319f58709d0d
  Args:
    query: 小米汽车最新消息 2024
"""

# 人工确认后继续执行工具调用
for chunk in graph.stream(None, config, stream_mode="values"):
    chunk["messages"][-1].pretty_print()

"""
================================== Ai Message ==================================
Tool Calls:
  fetch_real_time_info (chatcmpl-tool-a3bf319f58709d0d)
 Call ID: chatcmpl-tool-a3bf319f58709d0d
  Args:
    query: 小米汽车最新消息 2024
================================= Tool Message =================================
Name: fetch_real_time_info

[{"title": "新一代SU7 - 小米汽车", "link": "https://www.xiaomiev.com/su7", "snippet": "升级至700TOPS辅助驾驶芯片,相较于第一代小米SU7 Max,算力提升37.8%,支持更丰富场景的实时处理。数据来自小米汽车实验室。", "position": 1}]
================================== Ai Message ==================================

根据最新信息,小米汽车目前主要聚焦于**SU7系列**车型。以下是主要信息:

## 小米SU7系列

**最新动态:**
- **新一代SU7**已经发布,升级至**700TOPS辅助驾驶芯片**
- 相比第一代小米SU7 Max,**算力提升37.8%**
- 支持更丰富场景的实时处理
- 数据来自小米汽车实验室

## 小米汽车发展背景

小米于2021年宣布进军智能电动汽车领域,2024年正式发布首款车型SU7。小米汽车继承了小米在智能硬件和生态系统方面的优势,致力于打造智能化、网联化的新能源汽车。

## 产品特点

小米SU7定位于**中高端智能电动轿车**,主要特点包括:
- 智能化驾驶辅助系统
- 小米生态系统深度整合
- 高性能动力系统
- 先进的自动驾驶技术

目前小米汽车正在快速发展中,持续推出更新迭代的车型和技术。

您想了解小米汽车的哪个具体方面呢?比如价格、配置、预订信息等。
"""

"""

像上面案例中在普通节点加入 breakpoint 一样,在 Tool Calling 的架构中很明显呈现出来的特性就是“一刀切”:要么整个流程完全停止,要么全部通过。而考虑现实情况,通常一个 Agent 会包含多达数十甚至上百个工具,但其中可能只有少数涉及到高风险或敏感操作的工具。那么是否可以在保留自主循环的同时,仅在 Agent 自行判断必须要开始执行敏感工具或操作时,才触发人工介入机制?这种做法更符合实际应用需求,由此才有了 dynamic breakpoints(动态断点)的大规模应用。

动态断点指的是在图结构中,可以根据某些条件从给定节点内部动态的来中断图。其实在上面一个案例中已经有所涉及,即:

def execute_users(state):
    if state["user_approval"] == "是":
        response = "您的删除请求已经获得管理员的批准并成功执行。如果您有其他问题或需要进一步的帮助,请随时联系我们。"
        return {"model_response": AIMessage(response)}
    elif state["user_approval"] == "否":
        response = "对不起,您当前的请求是高风险操作,管理员不允许执行!"
        return {"model_response": AIMessage(response)}
    else:
        return state

并且在多轮对话 run_dialogue 的函数逻辑中,我们是通过代码的逻辑来对不同 user_approval 状态值采用不同的状态更新策略,即:

if last_chunk["user_approval"] == f"用户输入的指令是:{last_chunk['user_input']}, 请人工确认是否执行!":
    user_approval = input(f"当前用户的输入是:{last_chunk['user_input']}, 请人工确认是否执行!请回复 是/否。")
    graph.update_state(config, {"user_approval": user_approval})

五. 实战:实现人机交互式信息管理系统应用

我们可以根据 dynamic breakpoints(动态断点)的思路进一步扩展 Agent 的复杂程度。

下面的案例中,我们实现一个灵活且自主的天气信息管理系统,这个系统通过四个主要的工具函数来操作天气数据,这些函数包括查询、插入、和删除天气信息,以及获取实时天气。其中我们将删除功能作为高危敏感工具,使用 dynamic breakpoints 的机制来控制,仅当用户的需求触发 Agent 判断需要执行该工具时,由人工介入决定是否执行删除数据操作,具体的工具函数描述如下:
获取实时天气(get_weather):此工具允许用户输入城市名称,通过调用 OpenWeather API 获取该城市当前的天气情况,返回的数据以 JSON 格式呈现,包括温度、天气状况等信息。

  • 插入天气信息到数据库(insert_weather_to_db):这个工具用于将获取的天气数据存入数据库。
  • 从数据库查询天气信息(query_weather_from_db):此工具允许用户通过城市名查询已存储的天气信息。
  • 从数据库删除天气信息(delete_weather_from_db):允许用户删除指定城市的天气信息。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部