当前位置: 首页 > news >正文

langchain中间件之人工审核件:HumanInTheLoopMiddleware

HumanInTheLoopMiddleware的意义

HumanInTheLoopMiddleware 是 LangChain 中用于在 Agent 执行过程中插入人工审核节点的中间件。它允许你在工具被实际调用前或调用后暂停流程,等待人工批准、修改或拒绝,是构建安全、可控智能体的核心组件。

HumanInTheLoopMiddleware的作用时机

HumanInTheLoopMiddleware 的默认介入时机是在工具实际执行之前。换句话说,当 Agent 决定调用某个工具并生成了工具调用的参数后,中间件会立刻中断流程,把“待审批的工具调用请求”交给你,此时工具还没真正执行。

在 create_agent 构建的图结构中,执行流程大致为:

模型节点(生成工具调用)→ 中断点 → 工具节点(执行工具)

代码实战

step1 配置模型API
from langchain_openai import ChatOpenAI import json with open(r"config/model_call.json", 'r', encoding='utf-8') as f: config = json.load(f) # ================= 模型初始化 ================= llm = ChatOpenAI( api_key=config["api_key"], base_url=config["base_url"], model=config["model"], temperature=0.7, )

本人使用的是deepseek的API,并将密钥信息保存在了本地的config/model_call.json文件之中;如果为ollama调用本地模型等其他方式调用,只需更改与之适配的模型调用方式即可

step2 配置模型工作所需的工具
from langchain_core.tools import tool import os from pathlib import Path @tool def create_py_file(file_path, code): """创建py文件""" try: dir_name = os.path.dirname(file_path) if dir_name and not os.path.exists(dir_name): os.makedirs(dir_name) with open(file_path, 'w', encoding='utf-8') as f: f.write(code) return f"✅ 文件创建成功:{file_path}" except Exception as e: return f"创建py文件失败: {e}" @tool def get_filenames_pathlib(folder_path: str) -> list: """获取指定文件夹下的所有文件和文件夹名称。当用户想查看某个目录下有哪些文件时使用。""" try: path = Path(folder_path) all_items = [item.name for item in path.iterdir()] return all_items except Exception as e: return [f"错误: {e}"] @tool def read_file(file_path: str) -> str: """读取指定路径的文本文件内容。当用户要求查看文件内容时使用。""" try: with open(file_path, 'r', encoding='utf-8') as f: return f.read() except Exception as e: return f"读取出错: {e}" @tool def get_desktop_path(): """获取当前用户的桌面文件夹路径。支持 Windows、macOS、Linux。""" try: # 方法1:使用 pathlib 获取桌面路径 desktop = Path.home() / "Desktop" # 如果 Desktop 不存在,尝试中文"桌面"(部分 Linux 发行版) if not desktop.exists(): desktop_cn = Path.home() / "桌面" if desktop_cn.exists(): desktop = desktop_cn # 如果还不存在,使用 os.path 方式 if not desktop.exists(): desktop_os = os.path.join(os.path.expanduser("~"), "Desktop") if os.path.exists(desktop_os): desktop = Path(desktop_os) else: desktop_cn_os = os.path.join(os.path.expanduser("~"), "桌面") if os.path.exists(desktop_cn_os): desktop = Path(desktop_cn_os) desktop_path = str(desktop) return f"桌面路径: {desktop_path}" except Exception as e: return f"获取桌面路径失败: {e}"

使用 LangChain 的 @tool 装饰器来定义函数,@tool 装饰器的作用是将一个普通的 Python 函数转换为 Agent 能够理解并使用的“工具”对象。

本人代码以几个简单的功能函数为例,获取桌面位置、查看文件夹内文件、读取文件内容的功能函数

step3 将工具集成到agent中
from langchain.agents import create_agent from langgraph.checkpoint.memory import InMemorySaver from langchain.agents.middleware import HumanInTheLoopMiddleware system_prompt = """ 你是一个智能助手,可以访问桌面、读取文件、打开网页、创建py文件 你可以在model_tools文件夹下自由创建你需要用到的py文件 model_tools添加好的工具,需要重写auto_agent_prompt.py才会生效 打开软件先从桌面找,没有的话,再从网页打开 """ hitl_middleware = HumanInTheLoopMiddleware( interrupt_on={"get_filenames_pathlib": {"allowed_decisions": ["approve", "reject"]}}, description_prefix="获取指定文件夹下的所有文件和文件夹名称需要人工审核" ) # 创建检查点器 checkpointer = InMemorySaver() agent2 = create_agent( model=llm, tools=[read_file, open_url, create_py_file, get_filenames_pathlib, copy_file, get_desktop_path], system_prompt=system_prompt, middleware=[hitl_middleware], checkpointer=checkpointer )

在创建create_agent之前,需要提前写好有关智能体的提示词部分,在提示词中澄清模型调用工具的规则

InMemorySaver 是 LangGraph(LangChain Agent 的底层框架)中提供的一种检查点存储器。它的核心作用可以总结为:让 Agent 具备“记忆”和“断点续传”的能力。

HumanInTheLoopMiddleware内的interrupt_on定义模型调用时需要检查的工具

step4 全部代码
import json from langchain_openai import ChatOpenAI from langchain_core.tools import tool from langchain_core.messages import HumanMessage, SystemMessage from pathlib import Path import os import webbrowser from langchain.agents.middleware import HumanInTheLoopMiddleware,InterruptOnConfig from langchain.agents import create_agent from langchain_core.callbacks import BaseCallbackHandler from langgraph.checkpoint.memory import InMemorySaver from langgraph.types import Command with open(r"config/model_call.json", 'r', encoding='utf-8') as f: config = json.load(f) # ================= 工具初始化 ================= @tool def create_py_file(file_path, code): """创建py文件""" try: dir_name = os.path.dirname(file_path) if dir_name and not os.path.exists(dir_name): os.makedirs(dir_name) with open(file_path, 'w', encoding='utf-8') as f: f.write(code) return f"✅ 文件创建成功:{file_path}" except Exception as e: return f"创建py文件失败: {e}" @tool def get_filenames_pathlib(folder_path: str) -> list: """获取指定文件夹下的所有文件和文件夹名称。当用户想查看某个目录下有哪些文件时使用。""" try: path = Path(folder_path) all_items = [item.name for item in path.iterdir()] return all_items except Exception as e: return [f"错误: {e}"] @tool def read_file(file_path: str) -> str: """读取指定路径的文本文件内容。当用户要求查看文件内容时使用。""" try: with open(file_path, 'r', encoding='utf-8') as f: return f.read() except Exception as e: return f"读取出错: {e}" @tool def get_desktop_path(): """获取当前用户的桌面文件夹路径。支持 Windows、macOS、Linux。""" try: # 方法1:使用 pathlib 获取桌面路径 desktop = Path.home() / "Desktop" # 如果 Desktop 不存在,尝试中文"桌面"(部分 Linux 发行版) if not desktop.exists(): desktop_cn = Path.home() / "桌面" if desktop_cn.exists(): desktop = desktop_cn # 如果还不存在,使用 os.path 方式 if not desktop.exists(): desktop_os = os.path.join(os.path.expanduser("~"), "Desktop") if os.path.exists(desktop_os): desktop = Path(desktop_os) else: desktop_cn_os = os.path.join(os.path.expanduser("~"), "桌面") if os.path.exists(desktop_cn_os): desktop = Path(desktop_cn_os) desktop_path = str(desktop) return f"桌面路径: {desktop_path}" except Exception as e: return f"获取桌面路径失败: {e}" # ================= 模型初始化 ================= llm = ChatOpenAI( api_key=config["api_key"], base_url=config["base_url"], model=config["model"], temperature=0.7, ) # ================= 构建 Agent ================= system_prompt = """ 你是一个智能助手,可以访问桌面、读取文件、打开网页、创建py文件 你可以在model_tools文件夹下自由创建你需要用到的py文件 model_tools添加好的工具,需要重写auto_agent_prompt.py才会生效 打开软件先从桌面找,没有的话,再从网页打开 """ # interrupt_on={"get_filenames_pathlib": {"allowed_decisions": ["approve", "reject"]}}, hitl_middleware = HumanInTheLoopMiddleware( interrupt_on={"get_filenames_pathlib": True}, description_prefix="获取指定文件夹下的所有文件和文件夹名称需要人工审核" ) # 创建检查点器 checkpointer = InMemorySaver() agent2 = create_agent( model=llm, tools=[read_file, create_py_file, get_filenames_pathlib, get_desktop_path], system_prompt=system_prompt, middleware=[hitl_middleware], checkpointer=checkpointer ) def extract_interrupt_data(result): """从返回结果中提取人工审核信息""" if isinstance(result, dict) and "__interrupt__" in result: interrupts = result["__interrupt__"] if interrupts: interrupt_obj = interrupts[0] value = getattr(interrupt_obj, "value", None) if value and "action_requests" in value: return value["action_requests"][0] return None def test_main(user_input="""我的桌面放了几本小说?"""): config = {"configurable": {"thread_id": "user-session-1"}} # 第一次调用 result = agent2.invoke( {"messages": [{"role": "user", "content": user_input}]}, config=config ) while True: review_data = extract_interrupt_data(result) if not review_data: # 没有中断,输出最终结果并退出 print("最终结果:") for msg in result.get("messages", []): if hasattr(msg, "content") and msg.content: print(f"{msg.type}: {msg.content}") break # 有中断,等待用户决策 tool_name = review_data.get("name", "未知工具") tool_args = review_data.get("args", {}) print(f"\n触发人工审核:{tool_name}({tool_args})") decision = input("请输入审核结果(approve/reject):").strip().lower() if decision == "approve": cmd = Command(resume={"decisions": [{"type": "approve"}]}) elif decision == "reject": reason = input("请输入拒绝原因:").strip() or "人工审核未通过" cmd = Command(resume={"decisions": [{"type": "reject", "message": reason}]}) else: print("输入无效,操作已取消。") break # 恢复执行,并获取新的结果,继续循环检查 result = agent2.invoke(cmd, config=config) if __name__ == "__main__": test_main()
注意点
def print_stream_event(event): """从 stream event 中提取并打印工具调用和 AI 回复""" for node_name, node_output in event.items(): # 过滤掉 None 或非字典的输出 if not isinstance(node_output, dict): continue messages = node_output.get('messages', []) for msg in messages: # AI 文本回复 if getattr(msg, 'content', None): print(f"💬 {msg.content}") # 工具调用 if getattr(msg, 'tool_calls', None): for tc in msg.tool_calls: print(f"🔧 调用工具: {tc['name']}({tc['args']})") # 工具返回结果(通常是 ToolMessage) if getattr(msg, 'type', '') == 'tool': print(f"📦 工具结果: {msg.content}") if __name__ == "__main__": config = {"configurable": {"thread_id": "test-1"}} for event in agent2.stream( {"messages": [HumanMessage(content="我的桌面放了几本小说?")]}, config=config ): if "__interrupt__" in event: print_stream_event(event) print("中断!需要审批:", event["__interrupt__"]) decision = input("请输入 approve 或 reject: ").strip().lower() # 修复:使用正确的决策格式 if decision == "approve": resume_value = { "decisions": [ { "type": "approve" # 注意这里是"approve"不是"approved" } ] } else: # reject resume_value = { "decisions": [ { "type": "reject" # 注意这里是"reject"不是"rejected" } ] } cmd = Command(resume=resume_value) for e in agent2.stream(cmd, config=config): print_stream_event(e) else: print_stream_event(event)

如果智能体调用没有写成whileTrue循环并重新调用的形式,则create_agent智能体则会在进行一次工具检查时停止继续执行

http://www.cnnetsun.cn/news/3215697.html

相关文章:

  • Token(词元),5分钟彻底搞懂
  • 多模态 RAG 实战:图片与表格检索从零搭建
  • LLaMA-2 7B 中文词表扩充实战:新增 17953 个 Token 后 PPL 降低 15%
  • 电商智能选品:用数据驱动替代经验决策
  • 手把手教你怎么安装EditPlus(EditPlus v6)editplus下载安装教程
  • 容器镜像漏洞扫描拖垮 CI:我把构建时间从 20 分钟压回 3 分钟的排查实录
  • Scikit-learn 1.4 集成学习实战:Bagging vs Boosting vs Stacking 在 3 个数据集上的性能评测
  • 5分钟掌握Luyten:Java反编译工具的最佳图形化解决方案
  • vsftpd 虚拟用户 vs 本地用户:3种登录模式权限与安全深度对比
  • 抖音批量下载完全指南:三步掌握免费无水印视频保存方法
  • 软件测评实验室认可,作业指导书文件清单分享
  • R语言生态建模】biomod2物种分布模型完整实战:数据获取→预处理→建模→评估→优化→预测
  • 优质的中空直驱电机厂家深度盘点与选型指南
  • ClaudeCode安装和使用
  • ffmpeg里使用的解码器的介绍和了解
  • WuWa-Mod终极指南:解锁《鸣潮》游戏潜能的完整实战方案
  • SVGnest完全指南:零成本实现材料利用率翻倍的智能切割工具
  • okbiye 告别通宵做 PPT!AI 一键生成适配全场景演示文稿
  • RAG准确率翻倍,我做了这些优化。。。
  • LangChain开发核心技巧,动态切换AI大模型参数
  • Vivado升级后,老工程时序崩
  • 直播+对讲+会议+点播,企业级融媒体平台EasyDSS打通应急指挥“最后一公里”
  • 商用烤盘定制厂家哪家专业
  • 粉笔980适合大二大三备考公务员吗?在校生怎么提前打公考基础
  • 2026小提琴避坑实测!3大新手雷区+6款高性价比机型解析推荐
  • 手把手教程:用“人人微投票”零门槛搭建高并发、防刷票的评选活动
  • BORA:面向接触丰富场景的灵巧操作在线残差自适应方法
  • 短时序未来视频驱动的机器人多步操作方法
  • Three.js 草地着色器教程
  • Frida-Trace实战:从动态追踪到精准Hook的Android逆向进阶指南