工业级大模型学习之路027:LangGraph 高级特性与单 Agent 优化

一、基础 Agent 的局限性与高级特性价值

1.1 基础 ReAct Agent 的三大致命缺陷

之前基础的 ReAct Agent,但它距离生产环境使用还有很大差距,存在以下无法忽视的问题:

  1. 无法人工干预:一旦启动就会自动执行到底,无法在关键步骤暂停等待人类确认或修正
  2. 代码复用性差:所有逻辑都在一个文件中,复杂 Agent 会变得难以维护和扩展
  3. 执行效率低:只能串行调用工具,无法同时执行多个独立的工具调用
  4. 无法回溯历史:只能看到最终结果,无法查看和回滚到之前的执行状态
  5. 错误处理能力弱:工具调用失败后没有自动重试和降级机制
  6. 缺乏安全控制:无法限制 Agent 的操作范围,存在安全风险

1.2 LangGraph 高级特性全景

LangGraph 提供了一整套高级特性,完美解决了基础 Agent 的所有问题:

高级特性 解决的问题 核心价值
Checkpoint 高级操作 无法回溯历史、无法恢复状态 状态持久化、历史回溯、断点续跑、多会话管理
人类介入(Human-in-the-loop) 无法人工干预 关键步骤审核、错误修正、信息补充、安全控制
子图(Subgraph) 代码复用性差、难以维护 模块化设计、代码复用、分而治之、团队协作
并行工具调用 执行效率低 同时执行多个独立工具,大幅缩短任务完成时间
状态回溯与回滚 无法撤销错误操作 回滚到任意历史状态、修正错误、重新执行
高级错误处理 错误处理能力弱 自动重试、降级处理、异常捕获、优雅失败

1.3 工业级 Agent 的技术要求

一个真正能在生产环境使用的 Agent 必须满足以下要求:

  1. 可观测性:能追踪每一步执行过程,查看所有输入输出
  2. 可控制性:能在任意步骤暂停、继续、终止执行
  3. 可维护性:代码模块化、结构清晰、易于修改和扩展
  4. 可靠性:完善的错误处理和重试机制,99.9% 以上的可用性
  5. 安全性:严格的权限控制和操作限制,防止滥用和安全事故
  6. 交互性:支持与人类的自然交互,能在需要时请求人类帮助

1.4 今日学习路线图

Checkpoint高级操作 → 人类介入机制 → 子图模块化 → 并行工具调用 → 状态回溯与回滚 → 错误处理与重试 → 性能优化 → 项目整合

二、LangGraph 高级 API 详解

2.1 Checkpoint 深度解析:工业级状态管理的基石

2.1.1 Checkpoint 底层原理

Checkpoint 是 LangGraph 最核心的创新之一,它的本质是智能体执行状态的快照。每次节点执行完成后,LangGraph 会自动将当前状态保存到 Checkpoint 存储中。

Checkpoint 核心组成

  • Channel Values:状态的当前值(如 messages、iteration_count)
  • Versions:每个状态字段的版本号,用于冲突检测
  • Checkpoint ID:每个快照的唯一标识符
  • Thread ID:会话的唯一标识符,用于隔离不同用户的会话

增量 Checkpoint 机制:LangGraph 1.2 + 采用增量 Checkpoint,只保存变化的状态字段,而不是完整状态,大幅减少了存储占用和 I/O 开销。

2.1.2 生产级 Checkpoint 存储后端对比

SQLite 只适合开发和测试环境,生产环境必须使用支持高并发和持久化的存储后端:

存储后端 适用场景 优点 缺点
SQLite 开发测试、单用户 无需部署、零配置 不支持高并发、不支持分布式
PostgreSQL 生产环境、企业级 支持高并发、ACID 事务、分布式 需要部署和维护
Redis 高并发、低延迟 速度极快、支持集群 数据持久化能力弱
DynamoDB 云原生、无服务器 完全托管、无限扩展 成本较高、云厂商锁定

生产级推荐:PostgreSQL 15+,支持 JSONB 类型,性能和功能都能满足绝大多数企业需求。

2.1.3 实战:PostgreSQL Checkpoint 配置与高级操作

第一步:安装依赖

bash 复制代码
pip install langgraph-checkpoint-postgres psycopg2-binary

第二步:配置 PostgreSQL Checkpoint

python 复制代码
# core/checkpoint.py
from langgraph.checkpoint.postgres import PostgresSaver
from config.settings import settings

def get_postgres_checkpointer():
    """获取PostgreSQL Checkpoint存储(生产级)"""
    connection_string = f"postgresql://{settings.db_user}:{settings.db_password}@{settings.db_host}:{settings.db_port}/{settings.db_name}"
    checkpointer = PostgresSaver.from_conn_string(connection_string)
    
    # 初始化数据库表(只需要运行一次)
    checkpointer.setup()
    
    return checkpointer

# 全局单例
postgres_checkpointer = get_postgres_checkpointer()

第三步:Checkpoint 高级操作实战

python 复制代码
from core.checkpoint import postgres_checkpointer
from langchain_core.messages import HumanMessage

# 1. 列出所有活跃会话
def list_all_sessions():
    """列出所有用户会话"""
    sessions = []
    for thread in postgres_checkpointer.list_threads():
        sessions.append({
            "thread_id": thread["thread_id"],
            "user_id": thread["metadata"].get("user_id", "unknown"),
            "created_at": thread["created_at"],
            "updated_at": thread["updated_at"],
            "last_message": thread["channel_values"]["messages"][-1].content[:100] if thread["channel_values"]["messages"] else ""
        })
    return sessions

# 2. 获取指定会话的所有Checkpoint历史
def get_session_checkpoints(thread_id: str):
    """获取会话的所有状态快照"""
    checkpoints = []
    for checkpoint in postgres_checkpointer.list(thread_id):
        checkpoints.append({
            "checkpoint_id": checkpoint["id"],
            "created_at": checkpoint["created_at"],
            "step": checkpoint["step"],
            "messages_count": len(checkpoint["channel_values"]["messages"]),
            "iteration_count": checkpoint["channel_values"]["iteration_count"]
        })
    return checkpoints

# 3. 断点续跑实战:程序崩溃后恢复执行
def resume_session(thread_id: str):
    """从上次中断的地方恢复会话执行"""
    config = {"configurable": {"thread_id": thread_id}}
    
    # 获取当前状态
    state = react_agent.get_state(config)
    
    if state.next:
        # 有未完成的节点,继续执行
        print(f"恢复会话{thread_id},继续执行节点:{state.next}")
        result = react_agent.invoke(None, config=config)
        return result["messages"][-1].content
    else:
        # 会话已完成
        return "会话已完成,无需恢复"

# 4. 会话归档与清理
def archive_old_sessions(days: int = 30):
    """归档30天未活动的会话"""
    import datetime
    cutoff = datetime.datetime.now() - datetime.timedelta(days=days)
    
    archived_count = 0
    for thread in postgres_checkpointer.list_threads():
        if thread["updated_at"] < cutoff:
            postgres_checkpointer.delete_thread(thread["thread_id"])
            archived_count += 1
    
    print(f"已归档{archived_count}个过期会话")
    return archived_count
2.1.4 Checkpoint 最佳实践与避坑指南
  1. 永远不要手动修改 Checkpoint 数据库:直接修改数据库可能导致状态不一致,应该使用 LangGraph 提供的 API
  2. 设置合理的会话过期时间:定期清理过期会话,避免数据库无限增长
  3. 不要在状态中存储大量数据:状态应该只存储必要的元数据和上下文,大量数据应该存储在外部数据库
  4. 使用事务保证原子性:PostgreSQL Checkpoint 支持事务,确保状态更新的原子性
  5. 监控 Checkpoint 性能:监控数据库的读写延迟和连接数,及时扩容

2.2 人类介入(Human-in-the-loop):工业级安全的核心

2.2.1 人类介入底层原理

人类介入的本质是在图的执行流程中插入中断点,当执行到中断点时,LangGraph 会暂停执行并保存当前状态,等待人类输入或确认后再继续执行。

人类介入的三种核心模式

  1. 审核模式:Agent 执行高风险操作前,等待人类批准
  2. 信息补充模式:Agent 缺少必要信息时,向人类询问
  3. 修正模式:人类发现 Agent 的错误后,修正状态并重新执行
2.2.2 工业级人类介入完整流程

一个完整的工业级人类介入流程包括以下步骤:

Agent执行到中断点 → 保存状态 → 发送通知给人类 → 人类审核/补充/修正 → 更新状态 → 继续执行 → 记录审核日志

2.2.3 实战:三级审核流程实现

我们将实现一个支持工具调用审核→报告生成审核→最终发布审核的三级审核流程,这是企业中最常见的审批模式。

第一步:定义审核状态

python 复制代码
from enum import Enum

class ApprovalStatus(str, Enum):
    PENDING = "pending"
    APPROVED = "approved"
    REJECTED = "rejected"

class AgentState(BaseModel):
    """增强版Agent状态,支持审核流程"""
    messages: Annotated[List[BaseMessage], operator.add] = Field(default_factory=list)
    iteration_count: int = Field(default=0)
    max_iterations: int = Field(default=5)
    user_id: str = Field(default="default_user")
    # 审核相关字段
    approval_status: ApprovalStatus = Field(default=ApprovalStatus.PENDING)
    approval_level: int = Field(default=0)  # 0: 无需审核, 1: 一级审核, 2: 二级审核, 3: 三级审核
    approver: str = Field(default="")
    approval_comment: str = Field(default="")

第二步:定义多级中断点

python 复制代码
def build_approval_agent() -> StateGraph:
    """构建支持三级审核的Agent"""
    builder = StateGraph(AgentState)
    
    # 添加节点
    builder.add_node("agent", agent_think)
    builder.add_node("tools", ToolNode(PRODUCTION_TOOLS))
    builder.add_node("generate_report", generate_report_node)
    builder.add_node("publish_report", publish_report_node)
    
    # 添加边
    builder.add_edge("tools", "agent")
    builder.add_edge("generate_report", "agent")
    builder.add_edge("publish_report", END)
    
    # 路由函数
    def router(state: AgentState) -> str:
        last_message = state.messages[-1]
        
        if last_message.tool_calls:
            # 工具调用需要一级审核
            return "tools"
        elif "生成报告" in last_message.content:
            # 报告生成需要二级审核
            return "generate_report"
        elif "发布报告" in last_message.content:
            # 报告发布需要三级审核
            return "publish_report"
        else:
            return END
    
    builder.add_conditional_edges("agent", router)
    builder.set_entry_point("agent")
    
    # 配置多级中断点
    graph = builder.compile(
        checkpointer=postgres_checkpointer,
        interrupt_before=["tools", "generate_report", "publish_report"]
    )
    
    logger.info("✅ 三级审核Agent构建完成")
    return graph

# 全局单例
approval_agent = build_approval_agent()

第三步:审核流程 API 实现

python 复制代码
# core/approval_service.py
from core.react_agent import approval_agent, ApprovalStatus
from langchain_core.messages import HumanMessage
from utils.logger import logger

class ApprovalService:
    """审核服务"""
    
    @staticmethod
    def submit_task(question: str, user_id: str) -> dict:
        """提交任务,进入审核流程"""
        config = {"configurable": {"thread_id": f"task_{user_id}_{int(time.time())}"}}
        
        result = approval_agent.invoke(
            {"messages": [HumanMessage(content=question)], "user_id": user_id},
            config=config
        )
        
        state = approval_agent.get_state(config)
        if state.next:
            return {
                "task_id": config["configurable"]["thread_id"],
                "status": "pending_approval",
                "next_step": state.next[0],
                "message": f"任务已提交,等待{state.next[0]}审核"
            }
        else:
            return {
                "task_id": config["configurable"]["thread_id"],
                "status": "completed",
                "answer": result["messages"][-1].content
            }
    
    @staticmethod
    def approve_task(task_id: str, approver: str, comment: str = "") -> dict:
        """批准任务,继续执行"""
        config = {"configurable": {"thread_id": task_id}}
        
        # 更新审核状态
        approval_agent.update_state(
            config,
            {
                "approval_status": ApprovalStatus.APPROVED,
                "approver": approver,
                "approval_comment": comment
            }
        )
        
        # 继续执行
        result = approval_agent.invoke(None, config=config)
        
        state = approval_agent.get_state(config)
        if state.next:
            return {
                "task_id": task_id,
                "status": "pending_approval",
                "next_step": state.next[0],
                "message": f"一级审核通过,等待{state.next[0]}审核"
            }
        else:
            return {
                "task_id": task_id,
                "status": "completed",
                "answer": result["messages"][-1].content
            }
    
    @staticmethod
    def reject_task(task_id: str, approver: str, reason: str) -> dict:
        """拒绝任务,终止执行"""
        config = {"configurable": {"thread_id": task_id}}
        
        # 更新审核状态
        approval_agent.update_state(
            config,
            {
                "approval_status": ApprovalStatus.REJECTED,
                "approver": approver,
                "approval_comment": reason,
                "messages": [HumanMessage(content=f"任务被拒绝,原因:{reason}")]
            }
        )
        
        logger.info(f"任务{task_id}被{approver}拒绝,原因:{reason}")
        
        return {
            "task_id": task_id,
            "status": "rejected",
            "reason": reason
        }
2.2.4 人类介入最佳实践与避坑指南
  1. 只在必要时使用人类介入:过多的人类介入会降低效率,应该只在高风险操作和信息不足时使用
  2. 提供清晰的上下文:给审核人员提供足够的上下文信息,包括 Agent 的思考过程和工具调用详情
  3. 设置审核超时时间:如果审核人员在规定时间内没有响应,应该自动拒绝或升级审核
  4. 记录完整的审核日志:记录所有审核操作,包括审核人、审核时间、审核意见,便于审计和追溯
  5. 支持批量审核:对于大量的低风险审核任务,提供批量审核功能,提高效率

2.3 子图(Subgraph):复杂 Agent 的模块化解决方案

2.3.1 子图底层原理

子图的本质是一个完整的 StateGraph,可以作为节点嵌入到另一个 StateGraph 中。子图拥有自己的状态、节点和边,但可以与主图共享状态和 Checkpoint。

子图的核心优势

  1. 模块化设计:将复杂系统拆分为多个独立的模块,每个模块负责一个单一功能
  2. 代码复用:通用子图可以在多个项目中复用,避免重复开发
  3. 团队协作:不同的团队可以并行开发不同的子图,提高开发效率
  4. 易于测试:每个子图可以单独测试和调试,降低测试难度
2.3.2 子图高级用法:输入输出映射与嵌套子图

LangGraph 支持子图的输入输出映射,可以将主图的状态字段映射到子图的状态字段,反之亦然。这使得子图可以完全独立于主图设计,提高了复用性。

实战:通用工具执行子图与嵌套子图

python 复制代码
# core/subgraphs/tool_subgraph.py
from langgraph.graph import StateGraph
from langgraph.prebuilt import ToolNode
from core.tools import PRODUCTION_TOOLS
from core.react_agent import AgentState
from utils.logger import logger

# 定义工具子图的状态(可以与主图不同)
class ToolSubgraphState(BaseModel):
    tool_calls: list = Field(default_factory=list)
    tool_results: list = Field(default_factory=list)

def build_tool_subgraph() -> StateGraph:
    """构建通用工具执行子图(可复用)"""
    def execute_tools(state: ToolSubgraphState) -> dict:
        """执行工具调用"""
        tool_node = ToolNode(PRODUCTION_TOOLS)
        results = tool_node.invoke({"tool_calls": state.tool_calls})
        return {"tool_results": results["messages"]}
    
    builder = StateGraph(ToolSubgraphState)
    builder.add_node("execute_tools", execute_tools)
    builder.set_entry_point("execute_tools")
    builder.add_edge("execute_tools", END)
    
    return builder.compile()

# core/subgraphs/report_subgraph.py
from langgraph.graph import StateGraph
from core.react_agent import AgentState
from core.llm_factory import LLMFactory

llm = LLMFactory.get_llm()

def build_report_subgraph() -> StateGraph:
    """构建报告生成子图(嵌套子图示例)"""
    def generate_outline(state: AgentState) -> dict:
        """生成报告大纲"""
        outline = llm.invoke(f"根据以下信息生成报告大纲:{state.messages[-1].content}")
        return {"messages": [outline]}
    
    def generate_content(state: AgentState) -> dict:
        """生成报告内容"""
        content = llm.invoke(f"根据以下大纲生成报告内容:{state.messages[-1].content}")
        return {"messages": [content]}
    
    builder = StateGraph(AgentState)
    builder.add_node("generate_outline", generate_outline)
    builder.add_node("generate_content", generate_content)
    builder.add_edge("generate_outline", "generate_content")
    builder.add_edge("generate_content", END)
    builder.set_entry_point("generate_outline")
    
    return builder.compile()

# 主图中使用子图
def build_modular_agent() -> StateGraph:
    """构建模块化Agent"""
    builder = StateGraph(AgentState)
    
    # 添加子图作为节点
    builder.add_node("agent", agent_think)
    builder.add_node("tool_subgraph", build_tool_subgraph())
    builder.add_node("report_subgraph", build_report_subgraph())
    
    # 子图输入输出映射
    # 将主图的tool_calls映射到子图的tool_calls
    # 将子图的tool_results映射到主图的messages
    builder.add_edge(
        "agent",
        "tool_subgraph",
        input_map=lambda state: {"tool_calls": state.messages[-1].tool_calls},
        output_map=lambda result: {"messages": result["tool_results"]}
    )
    
    builder.add_edge("tool_subgraph", "agent")
    builder.add_edge("report_subgraph", END)
    
    def router(state: AgentState) -> str:
        last_message = state.messages[-1]
        if last_message.tool_calls:
            return "tool_subgraph"
        elif "生成报告" in last_message.content:
            return "report_subgraph"
        else:
            return END
    
    builder.add_conditional_edges("agent", router)
    builder.set_entry_point("agent")
    
    graph = builder.compile(checkpointer=postgres_checkpointer)
    logger.info("✅ 模块化Agent构建完成")
    return graph
2.3.3 子图最佳实践与避坑指南
  1. 单一职责原则:每个子图只负责一个单一的功能,不要让子图变得过于复杂
  2. 明确的接口定义:子图的输入输出应该清晰明确,使用 Pydantic 模型定义
  3. 独立测试:每个子图都应该有自己的单元测试,确保在集成到主图之前能够正常工作
  4. 避免过深的嵌套:子图的嵌套深度不要超过 3 层,否则会增加调试难度
  5. 版本管理:对子图进行版本管理,当子图发生变化时,能够方便地回滚到旧版本

2.4 并行工具调用:大幅提升 Agent 执行效率

2.4.1 并行工具调用底层原理

LangGraph 1.2 + 原生支持并行工具调用,当 LLM 返回多个工具调用时,ToolNode会自动使用线程池并行执行所有工具调用,然后将结果合并返回。

并行工具调用的前提条件

  1. 工具调用之间没有依赖关系
  2. 工具是线程安全的
  3. 系统有足够的资源支持并行执行
2.4.2 实战:并行工具调用与依赖处理

第一步:支持并行工具调用的思考节点

python 复制代码
def agent_think_with_parallel(state: AgentState) -> dict:
    """思考节点,支持并行工具调用"""
    logger.info(f"Agent思考中,迭代次数:{state.iteration_count}/{state.max_iterations}")
    
    messages = [system_message] + state.messages
    # 明确告诉LLM可以同时调用多个工具
    messages.append(SystemMessage(content="""
    你可以同时调用多个独立的工具来完成任务。
    如果多个工具之间没有依赖关系,请同时调用它们,以提高效率。
    工具调用之间用逗号分隔。
    """))
    
    response = llm_with_tools.invoke(messages)
    
    if state.iteration_count >= state.max_iterations:
        logger.warning("Agent超过最大迭代次数,强制结束")
        return {
            "messages": [response],
            "iteration_count": state.iteration_count + 1
        }
    
    # 记录并行工具调用数量
    if response.tool_calls:
        logger.info(f"Agent决定并行调用{len(response.tool_calls)}个工具:{[tc['name'] for tc in response.tool_calls]}")
    
    return {
        "messages": [response],
        "iteration_count": state.iteration_count + 1
    }

第二步:处理有依赖的工具调用 有些工具调用之间存在依赖关系,必须串行执行。我们可以通过条件边实现依赖处理:

python 复制代码
def build_agent_with_dependency() -> StateGraph:
    """构建支持依赖工具调用的Agent"""
    builder = StateGraph(AgentState)
    
    builder.add_node("agent", agent_think_with_parallel)
    builder.add_node("tools", ToolNode(PRODUCTION_TOOLS))
    
    def router(state: AgentState) -> str:
        last_message = state.messages[-1]
        if not last_message.tool_calls:
            return END
        
        # 检查工具调用之间是否有依赖
        # 例如:get_user_id必须在get_user_order之前执行
        tool_names = [tc["name"] for tc in last_message.tool_calls]
        if "get_user_id" in tool_names and "get_user_order" in tool_names:
            # 有依赖,先执行get_user_id
            return "execute_get_user_id"
        else:
            # 无依赖,并行执行所有工具
            return "tools"
    
    # 单独的节点执行有依赖的工具
    def execute_get_user_id(state: AgentState) -> dict:
        """执行get_user_id工具"""
        tool_call = next(tc for tc in state.messages[-1].tool_calls if tc["name"] == "get_user_id")
        result = TOOL_MAP["get_user_id"].invoke(tool_call["args"])
        return {"messages": [ToolMessage(content=result, tool_call_id=tool_call["id"])]}
    
    builder.add_node("execute_get_user_id", execute_get_user_id)
    
    builder.add_edge("tools", "agent")
    builder.add_edge("execute_get_user_id", "agent")
    builder.add_conditional_edges("agent", router)
    builder.set_entry_point("agent")
    
    graph = builder.compile(checkpointer=postgres_checkpointer)
    return graph

第三步:并行工具调用的错误处理 当并行执行多个工具时,可能会出现部分成功部分失败的情况。我们需要处理这种情况:

python 复制代码
def execute_tools_with_error_handling(state: AgentState) -> dict:
    """执行工具调用,处理部分失败的情况"""
    last_message = state.messages[-1]
    tool_results = []
    
    # 使用线程池并行执行工具
    from concurrent.futures import ThreadPoolExecutor, as_completed
    
    with ThreadPoolExecutor(max_workers=5) as executor:
        futures = {}
        for tool_call in last_message.tool_calls:
            future = executor.submit(TOOL_MAP[tool_call["name"]].invoke, tool_call["args"])
            futures[future] = tool_call
        
        for future in as_completed(futures):
            tool_call = futures[future]
            try:
                result = future.result()
                tool_results.append(ToolMessage(
                    content=result,
                    tool_call_id=tool_call["id"],
                    name=tool_call["name"]
                ))
            except Exception as e:
                logger.error(f"工具{tool_call['name']}调用失败:{e}")
                tool_results.append(ToolMessage(
                    content=f"工具调用失败:{str(e)},请尝试其他方法",
                    tool_call_id=tool_call["id"],
                    name=tool_call["name"]
                ))
    
    return {"messages": tool_results}
2.4.4 并行工具调用最佳实践与避坑指南
  1. 限制并行度:设置合理的最大并行数(建议不超过 5),避免资源耗尽
  2. 处理部分失败:不要因为一个工具调用失败而终止整个任务,应该让 Agent 根据失败结果调整策略
  3. 避免并行执行有副作用的工具:对于修改数据库、发送邮件等有副作用的工具,尽量串行执行
  4. 监控并行性能:监控工具调用的执行时间和成功率,及时发现性能瓶颈
  5. 使用异步工具:对于 I/O 密集型工具,使用异步实现可以进一步提高并行效率

2.5 状态回溯与回滚:系统容错性的终极保障

2.5.1 状态回溯与回滚底层原理

状态回溯是指查看历史上任意时刻的系统状态,状态回滚是指将系统状态恢复到历史上的某个时刻,然后重新执行。

回滚的两种核心策略

  1. 硬回滚:删除当前状态之后的所有 Checkpoint,系统状态完全恢复到目标 Checkpoint
  2. 软回滚:创建一个新的 Checkpoint,将状态设置为目标 Checkpoint 的值,保留所有历史记录

工业级推荐:软回滚,保留所有历史记录,便于审计和调试。

2.5.2 实战:完整的状态回溯与回滚系统
python 复制代码
from core.checkpoint import postgres_checkpointer
from core.react_agent import react_agent
from langchain_core.messages import HumanMessage
from utils.logger import logger

class RollbackService:
    """状态回溯与回滚服务"""
    
    @staticmethod
    def get_session_history(thread_id: str) -> list:
        """获取会话的完整历史记录"""
        checkpoints = list(postgres_checkpointer.list(thread_id))
        history = []
        
        for checkpoint in checkpoints:
            state = checkpoint["channel_values"]
            history.append({
                "checkpoint_id": checkpoint["id"],
                "step": checkpoint["step"],
                "timestamp": checkpoint["created_at"],
                "messages": [
                    {"role": msg.type, "content": msg.content}
                    for msg in state["messages"]
                ],
                "iteration_count": state["iteration_count"]
            })
        
        return history
    
    @staticmethod
    def rollback_to_checkpoint(thread_id: str, checkpoint_id: str, reason: str) -> dict:
        """
        软回滚到指定的Checkpoint
        保留所有历史记录,创建一个新的Checkpoint
        """
        config = {"configurable": {"thread_id": thread_id}}
        
        # 获取目标Checkpoint的状态
        target_checkpoint = postgres_checkpointer.get(thread_id, checkpoint_id)
        if not target_checkpoint:
            raise ValueError(f"Checkpoint {checkpoint_id}不存在")
        
        # 创建回滚消息
        rollback_message = HumanMessage(
            content=f"系统已回滚到步骤{target_checkpoint['step']},原因:{reason}。请从这里继续执行。"
        )
        
        # 更新状态到目标Checkpoint,并添加回滚消息
        react_agent.update_state(
            config,
            {
                "messages": target_checkpoint["channel_values"]["messages"] + [rollback_message],
                "iteration_count": target_checkpoint["channel_values"]["iteration_count"]
            }
        )
        
        logger.info(f"会话{thread_id}已回滚到Checkpoint {checkpoint_id},原因:{reason}")
        
        return {
            "thread_id": thread_id,
            "rollback_to_checkpoint_id": checkpoint_id,
            "reason": reason,
            "current_step": target_checkpoint["step"]
        }
    
    @staticmethod
    def reexecute_from_checkpoint(thread_id: str, checkpoint_id: str) -> str:
        """从指定的Checkpoint重新执行"""
        # 先回滚到目标Checkpoint
        RollbackService.rollback_to_checkpoint(thread_id, checkpoint_id, "重新执行")
        
        # 继续执行
        config = {"configurable": {"thread_id": thread_id}}
        result = react_agent.invoke(None, config=config)
        
        return result["messages"][-1].content
    
    @staticmethod
    def undo_last_action(thread_id: str) -> dict:
        """撤销上一步操作"""
        checkpoints = list(postgres_checkpointer.list(thread_id))
        if len(checkpoints) < 2:
            raise ValueError("没有可撤销的操作")
        
        # 回滚到上上个Checkpoint
        target_checkpoint_id = checkpoints[-2]["id"]
        return RollbackService.rollback_to_checkpoint(thread_id, target_checkpoint_id, "用户撤销操作")
2.5.3 状态回溯与回滚最佳实践与避坑指南
  1. 优先使用软回滚:保留所有历史记录,便于审计和问题排查
  2. 明确回滚的边界:回滚只能撤销 Agent 的内部状态,无法撤销已经执行的外部操作(如发送邮件、修改数据库)
  3. 处理外部系统的一致性:对于有外部副作用的操作,回滚时需要手动补偿(如发送取消邮件、回滚数据库事务)
  4. 记录回滚日志:记录所有回滚操作,包括回滚时间、回滚人、回滚原因
  5. 限制回滚权限:只有授权用户才能执行回滚操作,防止滥用

三、项目整合:升级 RAGService 支持所有高级特性

3.1 升级 RAGService

代码位置core/rag_service.py

python 复制代码
from core.react_agent import react_agent, approval_agent
from core.checkpoint import postgres_checkpointer
from core.approval_service import ApprovalService
from core.rollback_service import RollbackService

class RAGService:
    """完整的RAG+Agent问答服务(第8天高级特性版)"""
    def __init__(self):
        self.retriever = RAGRetriever()
        self.prompt = PromptService()
        self.approval_service = ApprovalService()
        self.rollback_service = RollbackService()
        logger.info("✅ RAG+Agent问答服务初始化完成(支持所有高级特性)")

    # 原有query和stream_query方法保持不变

    # 审核相关方法
    def submit_approval_task(self, question: str, user_id: str) -> dict:
        """提交需要审核的任务"""
        return self.approval_service.submit_task(question, user_id)

    def approve_task(self, task_id: str, approver: str, comment: str = "") -> dict:
        """批准任务"""
        return self.approval_service.approve_task(task_id, approver, comment)

    def reject_task(self, task_id: str, approver: str, reason: str) -> dict:
        """拒绝任务"""
        return self.approval_service.reject_task(task_id, approver, reason)

    # 会话管理相关方法
    def list_all_sessions(self) -> list:
        """列出所有会话"""
        return list_all_sessions()

    def get_session_history(self, thread_id: str) -> list:
        """获取会话历史"""
        return self.rollback_service.get_session_history(thread_id)

    def delete_session(self, thread_id: str) -> bool:
        """删除会话"""
        try:
            postgres_checkpointer.delete_thread(thread_id)
            return True
        except Exception as e:
            logger.error(f"删除会话失败:{e}")
            return False

    # 回滚相关方法
    def rollback_to_checkpoint(self, thread_id: str, checkpoint_id: str, reason: str) -> dict:
        """回滚到指定Checkpoint"""
        return self.rollback_service.rollback_to_checkpoint(thread_id, checkpoint_id, reason)

    def undo_last_action(self, thread_id: str) -> dict:
        """撤销上一步操作"""
        return self.rollback_service.undo_last_action(thread_id)

    def reexecute_from_checkpoint(self, thread_id: str, checkpoint_id: str) -> str:
        """从指定Checkpoint重新执行"""
        return self.rollback_service.reexecute_from_checkpoint(thread_id, checkpoint_id)

3.2 新增高级特性 API 接口

代码位置main8.py

python 复制代码
from dotenv import load_dotenv
load_dotenv()

from core.rag_service import RAGService
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import uvicorn

app = FastAPI(title="企业级RAG+Agent API(Day8)", version="1.0.0")
rag_service = RAGService()

class QueryRequest(BaseModel):
    question: str
    user_id: str = "default_user"
    use_agent: bool = False

class ToolCallApprovalRequest(BaseModel):
    thread_id: str
    approver: str = "admin"
    reason: str = ""

class ApprovalRequest(BaseModel):
    thread_id: str
    approver: str = "admin"
    reason: str = ""

class RollbackRequest(BaseModel):
    thread_id: str
    checkpoint_id: str
    reason: str = "用户操作"

class QueryResponse(BaseModel):
    answer: str
    mode: str

@app.post("/api/query", response_model=QueryResponse)
async def query(request: QueryRequest):
    """通用问答接口,支持RAG和Agent两种模式"""
    try:
        answer = rag_service.query(
            question=request.question,
            user_id=request.user_id,
            use_agent=request.use_agent
        )
        return QueryResponse(
            answer=answer,
            mode="agent" if request.use_agent else "rag"
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/agent/approval/submit")
async def submit_approval_task(request: QueryRequest):
    """提交需要审核的任务"""
    try:
        return rag_service.submit_approval_task(
            question=request.question,
            user_id=request.user_id
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/agent/approval/approve")
async def approve_approval(request: ApprovalRequest):
    """批准当前步骤的审核,继续执行"""
    try:
        return rag_service.approve_task(
            task_id=request.thread_id,
            approver=request.approver,
            comment=request.reason
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/agent/approval/reject")
async def reject_approval(request: ApprovalRequest):
    """拒绝当前步骤的审核,终止任务"""
    try:
        return rag_service.reject_task(
            task_id=request.thread_id,
            approver=request.approver,
            reason=request.reason
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/agent/rollback")
async def rollback_to_checkpoint(request: RollbackRequest):
    """回滚到指定Checkpoint"""
    try:
        return rag_service.rollback_to_checkpoint(
            thread_id=request.thread_id,
            checkpoint_id=request.checkpoint_id,
            reason=request.reason
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/agent/undo")
async def undo_last_action(thread_id: str):
    """撤销上一步操作"""
    try:
        return rag_service.undo_last_action(thread_id)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/agent/reexecute")
async def reexecute_from_checkpoint(request: RollbackRequest):
    """从指定Checkpoint重新执行"""
    try:
        answer = rag_service.reexecute_from_checkpoint(
            thread_id=request.thread_id,
            checkpoint_id=request.checkpoint_id
        )
        return {"answer": answer}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/health")
async def health_check():
    """健康检查接口"""
    return {"status": "healthy", "service": "rag-agent-api-day8"}

if __name__ == "__main__":
    print("==> 第8天:Agent增强功能(审核、回滚、撤销)")
    print("API服务启动中,访问 http://localhost:8000/docs 查看接口文档")
    uvicorn.run(app, host="0.0.0.0", port=8000)

<这里还有个问题,重复调用工具>

相关推荐
Ws_11 分钟前
C#学习 Day2
开发语言·学习·c#
Raink老师26 分钟前
【AI面试临阵磨枪-70】Agent 系统如何做分布式调度、跨服务协作、故障恢复?
人工智能·面试·职场和发展
lishi_199129 分钟前
一键部署MoviePilotV2实现NAS全自动追剧
python·媒体·moviepilot
神谕的祝福31 分钟前
comfyui从0到1开始学习-第三讲生图与降噪实验
学习
星夜夏空9938 分钟前
STM32单片机学习(32) —— ADC
stm32·单片机·学习
tedcloud12338 分钟前
RTK部署教程:构建稳定的AI Workflow环境
服务器·javascript·人工智能·typescript·ocr
Raink老师40 分钟前
【AI面试临阵磨枪-71】如何用 AI 优化推荐系统、内容审核、广告创意、搜索体验?
人工智能·面试·职场和发展
AI医影跨模态组学42 分钟前
Biomarker Res(IF=11.5)安徽医科大学第一医院:基于机器学习的放射组学模型:子宫内膜癌患者的预后预测及机制探索
人工智能·深度学习·论文·医学·医学影像·影像组学
ftpeak1 小时前
Mooncake:以 KVCache 为中心的分离式 LLM 服务架构
人工智能·ai·架构·ai编程·ai开发
Terrence Shen1 小时前
Hermes agent的tools是怎么落地应用的系列
人工智能·llm·agent·hermes