1. 引言

在构建基于大模型的智能客服系统时,我们面临两个核心挑战:如何控制模型行为何时需要人工介入。LangChain 提供了强大的中间件(Middleware)机制和人工中断(Human-in-the-Loop)功能,让开发者能够精确控制 Agent 的执行流程。

本文将深入探讨 LangChain 中间件的使用方式,并重点介绍如何在工具调用中实现人工介入,确保关键决策由人类把关。

2. LangChain 中间件机制

2.1 什么是中间件?

中间件是 LangChain Agent 执行流程中的拦截器,可以在模型调用前后插入自定义逻辑。它类似于 Web 开发中的中间件,能够:

  • 过滤用户输入内容
  • 修改模型输出
  • 添加系统级功能(如日志、监控)
  • 控制执行流程(跳转、终止)

2.2 中间件类型

LangChain 提供了三种主要中间件装饰器:

  1. @before_model - 在模型调用前执行
  2. @after_model - 在模型调用后执行
  3. @wrap_tool_call - 在工具调用前后执行

3. 中间件实战:智能客服内容守卫

3.1 内容过滤中间件

以下是一个实际的内容守卫中间件实现,用于过滤不当内容:

from langchain.agents.middleware import before_model
from langchain.messages import HumanMessage

@before_model
def content_guard(state, runtime):
    """过滤用户输入中的不当内容"""
    last_msg = state["messages"][-1] if state.get("messages") else None
    if not last_msg:
        return None
    
    content = str(getattr(last_msg, 'content', ''))
    # 定义需要过滤的关键词
    blocked = ["黄X", "X博", "违法"]
    
    for word in blocked:
        if word in content:
            # 直接跳转到结束,返回预设回复
            return {
                "jump_to": "end",
                "messages": [HumanMessage(content="抱歉,我不能处理这个请求。")]
            }
    return None

关键特性:

  • jump_to: "end" - 直接终止当前执行流程
  • 返回预设消息替代模型生成
  • 实时检查用户输入,防止不当内容进入模型

3.2 自动签名中间件

from langchain.agents.middleware import after_model
from langchain.messages import AIMessage

@after_model
def auto_signature(state, runtime):
    """自动追加客服签名"""
    msgs = state.get("messages", [])
    if not msgs:
        return None
    
    last = msgs[-1]
    # 只在 AI 回复且不是工具调用时添加签名
    if last.type == "ai" and last.content and not (
        hasattr(last, 'tool_calls') and last.tool_calls
    ):
        return {"messages": [AIMessage(
            content=last.content 
            + "\n\n---\n菜鸟教程 RUNOOB 客服中心 | 工作时间 9:00-18:00"
        )]}
    return None

4. 人工介入(HITL)机制

4.1 为什么需要人工介入?

在某些关键场景下,完全依赖 AI 决策存在风险:

  • 敏感操作(退款、转账)
  • 法律合规问题
  • 超出知识范围的问题
  • 用户明确要求人工服务

4.2 实现人工介入的工具

LangChain 通过 interrupt 机制实现人工介入。以下是一个转接人工客服的工具示例:

from langchain.tools import tool
from langgraph.types import interrupt

@tool
def transfer_to_human(reason: str) -> str:
    """将用户转接给人工客服。
    
    Args:
        reason: 转接原因
    """
    # 触发人工介入审批
    approval = interrupt({
        "action": "transfer_to_human",
        "reason": reason,
        "message": f"用户请求转接人工客服,原因:{reason}。是否转接?"
    })
    
    # 等待人工审批结果
    if approval.get("confirmed"):
        return (f"已为您转接人工客服,预计等待 {approval.get('wait_time', 3)} 分钟。"
                f"工单号:TK-{approval.get('ticket_id', 'N/A')}")
    return "转接已取消,我继续为您服务。"

4.3 中断处理流程

def chat(thread_id: str, message: str) -> str:
    """处理用户消息并返回回复"""
    config = {"configurable": {"thread_id": thread_id}}
    
    # 运行 Agent
    result = agent.invoke(
        {"messages": [HumanMessage(content=message)]},
        config=config,
    )
    
    # 检查是否需要人工介入
    state = agent.get_state(config)
    if state.tasks and state.tasks[0].interrupts:
        interrupt_info = state.tasks[0].interrupts[0].value
        return f"[需要审批] {interrupt_info.get('message', '')}"
    
    return result["messages"][-1].content

5. 完整智能客服系统实现

5.1 系统架构

from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.agents.middleware import before_model, after_model
from langgraph.checkpoint.sqlite import SqliteSaver

# 1. 初始化模型
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)

# 2. 创建检查点(支持记忆和中断恢复)
checkpointer = SqliteSaver.from_conn_string("customer_service.db")

# 3. 定义工具集
tools = [search_kb, query_order, transfer_to_human]

# 4. 定义中间件链
middleware = [content_guard, auto_signature]

# 5. 创建带中间件和人工介入能力的 Agent
agent = create_agent(
    model=model,
    tools=tools,
    middleware=middleware,
    checkpointer=checkpointer,
    system_prompt="""你是菜鸟教程 RUNOOB 的智能客服"小菜"。
## 你的职责
1. 热情接待每一位用户,用"您"称呼
2. 关于平台信息、课程内容、政策等问题,使用 search_kb 查询
3. 关于订单查询,使用 query_order 工具
4. 遇到无法解决的问题,使用 transfer_to_human 转接人工
## 行为准则
- 回答简洁,每次 2-3 句话
- 不知道的就查询知识库,查不到就诚实告知
- 保持友好亲切的语气""",
)

5.2 测试场景

if __name__ == "__main__":
    user_id = "user_xiaoming"
    
    print("=== 测试 1:正常知识库查询 ===")
    print(chat(user_id, "Python3 教程有多少章?"))
    # 输出:Python3 基础教程共 30 章,累计学习人次超 500 万。课程完全免费。
    
    print("\n=== 测试 2:订单查询 ===")
    print(chat(user_id, "我的订单 ORD-2024-001 状态是什么?"))
    # 输出:订单 ORD-2024-001:VIP 年费会员 | 金额 ¥799 | 状态 已完成 | 日期 2024-01-15
    
    print("\n=== 测试 3:触发人工介入 ===")
    print(chat(user_id, "我要投诉,找你们领导!"))
    # 输出:[需要审批] 用户请求转接人工客服,原因:我要投诉,找你们领导!。是否转接?

6. 中间件最佳实践

6.1 执行顺序管理

中间件的执行顺序很重要:

# 正确的顺序:输入验证 -> 业务逻辑 -> 输出格式化
middleware = [
    input_validator,      # 1. 验证输入
    content_guard,        # 2. 内容过滤
    rate_limiter,         # 3. 频率限制
    business_logic,       # 4. 业务处理
    auto_signature,       # 5. 添加签名
    response_logger       # 6. 记录日志
]

6.2 错误处理中间件

@before_model
def error_handler(state, runtime):
    """统一错误处理中间件"""
    try:
        # 检查状态是否有效
        if not state.get("messages"):
            return {
                "jump_to": "end",
                "messages": [HumanMessage(content="系统错误:消息为空")]
            }
    except Exception as e:
        return {
            "jump_to": "end",
            "messages": [HumanMessage(content=f"系统错误:{str(e)}")]
        }
    return None

6.3 性能监控中间件

import time
from langchain.agents.middleware import wrap_tool_call

@wrap_tool_call
def performance_monitor(tool_call, runtime):
    """监控工具调用性能"""
    start_time = time.time()
    
    try:
        result = yield tool_call
        elapsed = time.time() - start_time
        
        # 记录慢查询
        if elapsed > 2.0:  # 超过2秒
            print(f"⚠️ 慢工具调用: {tool_call['name']} 耗时 {elapsed:.2f}秒")
        
        return result
    except Exception as e:
        print(f"❌ 工具调用失败: {tool_call['name']}, 错误: {str(e)}")
        raise

7. 人工介入的应用场景

7.1 敏感操作审批

@tool
def process_refund(order_id: str, amount: float) -> str:
    """处理退款申请(需要人工审批)"""
    # 1. 验证订单信息
    order = orders_db.get(order_id)
    if not order:
        return f"订单 {order_id} 不存在"
    
    # 2. 触发人工审批
    approval = interrupt({
        "action": "refund_approval",
        "order_id": order_id,
        "amount": amount,
        "customer": order["user"],
        "message": f"用户 {order['user']} 申请退款 ¥{amount},订单 {order_id}。是否批准?"
    })
    
    # 3. 根据审批结果执行
    if approval.get("approved"):
        # 执行退款逻辑
        return f"退款 ¥{amount} 已批准并处理,预计3-5个工作日到账。"
    else:
        reason = approval.get("reason", "审批未通过")
        return f"退款申请未通过:{reason}"

7.2 复杂问题升级

@tool  
def handle_complaint(issue: str, severity: str = "medium") -> str:
    """处理用户投诉(根据严重程度决定是否转人工)"""
    
    # 根据严重程度决定处理方式
    if severity == "high":
        # 高严重度直接转人工
        return transfer_to_human(f"高严重度投诉:{issue}")
    
    elif severity == "medium":
        # 中等严重度尝试自动处理,失败则转人工
        try:
            # 尝试自动处理逻辑
            response = auto_handle_complaint(issue)
            if "无法解决" in response:
                return transfer_to_human(f"中等严重度投诉自动处理失败:{issue}")
            return response
        except Exception:
            return transfer_to_human(f"中等严重度投诉处理异常:{issue}")
    
    else:
        # 低严重度完全自动处理
        return auto_handle_complaint(issue)

8. 总结与建议

8.1 中间件使用要点

  1. 保持中间件轻量:避免在中间件中执行耗时操作
  2. 明确执行顺序:输入验证在前,输出格式化在后
  3. 错误隔离:每个中间件应该有独立的错误处理
  4. 可配置化:通过参数控制中间件行为

8.2 人工介入设计原则

  1. 明确触发条件:清晰定义何时需要人工介入
  2. 提供充分上下文:给审批人足够的信息做决策
  3. 设置超时机制:避免用户长时间等待
  4. 保留处理记录:所有人工介入都应记录日志

8.3 系统监控建议

# 在系统入口添加监控
def chat_with_monitoring(thread_id: str, message: str) -> str:
    """带监控的聊天接口"""
    start_time = time.time()
    
    try:
        result = chat(thread_id, message)
        
        # 记录性能指标
        elapsed = time.time() - start_time
        log_performance(thread_id, elapsed, len(message), len(result))
        
        return result
    except Exception as e:
        # 自动触发人工介入处理异常
        return transfer_to_human(f"系统异常:{str(e)}")

通过合理使用中间件和人工介入机制,我们可以在保持 AI 系统自主性的同时,确保关键决策的可控性和安全性。这种混合智能(Human-AI Collaboration)模式是构建可靠企业级 AI 应用的关键。

9. 扩展阅读

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐