""" 人工审核工具模块 提供 LangGraph interrupt 机制和状态持久化能力 功能: 1. HumanReview - 人工审核数据类 2. ReviewStatus - 审核状态枚举 3. HumanReviewStore - 审核存储接口 4. InMemoryReviewStore - 内存存储实现 5. HumanReviewNode - LangGraph 审核节点 6. ReviewManager - 审核管理器 """ from typing import Dict, List, Any, Optional, Callable from dataclasses import dataclass, field from enum import Enum, auto from abc import ABC, abstractmethod from datetime import datetime import uuid class ReviewStatus(Enum): """审核状态枚举""" PENDING = auto() # 待审核 APPROVED = auto() # 已通过 REJECTED = auto() # 已拒绝 MODIFIED = auto() # 已修改 TIMEOUT = auto() # 已超时 @dataclass class HumanReview: """人工审核数据类""" review_id: str # 审核ID thread_id: str # 线程ID user_id: str # 用户ID status: ReviewStatus # 审核状态 content_to_review: str # 待审核内容 review_comment: str = "" # 审核意见 modified_content: str = "" # 修改后的内容 created_at: datetime = field(default_factory=datetime.now) reviewed_at: Optional[datetime] = None reviewer: Optional[str] = None metadata: Dict[str, Any] = field(default_factory=dict) class HumanReviewStore(ABC): """审核存储接口""" @abstractmethod def save(self, review: HumanReview) -> None: """ 保存审核 Args: review: 审核对象 """ pass @abstractmethod def get(self, review_id: str) -> Optional[HumanReview]: """ 获取审核 Args: review_id: 审核ID Returns: 审核对象,如果不存在返回 None """ pass @abstractmethod def get_by_thread(self, thread_id: str) -> List[HumanReview]: """ 获取线程的所有审核 Args: thread_id: 线程ID Returns: 审核列表 """ pass @abstractmethod def get_pending(self, limit: int = 100) -> List[HumanReview]: """ 获取待审核的列表 Args: limit: 返回数量限制 Returns: 待审核列表 """ pass @abstractmethod def update_status( self, review_id: str, status: ReviewStatus, reviewer: Optional[str] = None, comment: str = "", modified_content: str = "" ) -> bool: """ 更新审核状态 Args: review_id: 审核ID status: 新状态 reviewer: 审核人 comment: 审核意见 modified_content: 修改后的内容 Returns: 是否成功 """ pass class InMemoryReviewStore(HumanReviewStore): """内存存储实现""" def __init__(self): self._reviews: Dict[str, HumanReview] = {} def save(self, review: HumanReview) -> None: """ 保存审核 Args: review: 审核对象 """ self._reviews[review.review_id] = review def get(self, review_id: str) -> Optional[HumanReview]: """ 获取审核 Args: review_id: 审核ID Returns: 审核对象,如果不存在返回 None """ return self._reviews.get(review_id) def get_by_thread(self, thread_id: str) -> List[HumanReview]: """ 获取线程的所有审核 Args: thread_id: 线程ID Returns: 审核列表 """ return [ review for review in self._reviews.values() if review.thread_id == thread_id ] def get_pending(self, limit: int = 100) -> List[HumanReview]: """ 获取待审核的列表 Args: limit: 返回数量限制 Returns: 待审核列表 """ pending = [ review for review in self._reviews.values() if review.status == ReviewStatus.PENDING ] pending.sort(key=lambda r: r.created_at) return pending[:limit] def update_status( self, review_id: str, status: ReviewStatus, reviewer: Optional[str] = None, comment: str = "", modified_content: str = "" ) -> bool: """ 更新审核状态 Args: review_id: 审核ID status: 新状态 reviewer: 审核人 comment: 审核意见 modified_content: 修改后的内容 Returns: 是否成功 """ review = self._reviews.get(review_id) if review is None: return False review.status = status review.review_comment = comment review.modified_content = modified_content review.reviewer = reviewer review.reviewed_at = datetime.now() return True class HumanReviewNode: """LangGraph 审核节点""" def __init__( self, store: HumanReviewStore, should_review: Optional[Callable[[Any], bool]] = None ): """ 初始化审核节点 Args: store: 审核存储 should_review: 判断是否需要审核的函数 """ self.store = store self.should_review = should_review or (lambda state: True) def create_review( self, state: Any, thread_id: str, user_id: str, content_to_review: str ) -> str: """ 创建审核 Args: state: 状态 thread_id: 线程ID user_id: 用户ID content_to_review: 待审核内容 Returns: 审核ID """ review_id = str(uuid.uuid4()) review = HumanReview( review_id=review_id, thread_id=thread_id, user_id=user_id, status=ReviewStatus.PENDING, content_to_review=content_to_review ) self.store.save(review) return review_id def check_review_status(self, review_id: str) -> Optional[ReviewStatus]: """ 检查审核状态 Args: review_id: 审核ID Returns: 审核状态,如果不存在返回 None """ review = self.store.get(review_id) return review.status if review else None def get_review_result(self, review_id: str) -> Optional[HumanReview]: """ 获取审核结果 Args: review_id: 审核ID Returns: 审核对象,如果不存在返回 None """ return self.store.get(review_id) async def __call__(self, state: Any) -> Dict[str, Any]: """ 节点执行方法(LangGraph 兼容) Args: state: 状态 Returns: 更新后的状态 """ # 检查是否需要审核 if not self.should_review(state): return {"review_skipped": True} # 从状态中提取信息 thread_id = getattr(state, "thread_id", str(uuid.uuid4())) user_id = getattr(state, "user_id", "default_user") # 获取待审核内容 content_to_review = "" if hasattr(state, "messages") and state.messages: last_msg = state.messages[-1] if state.messages else None if last_msg and hasattr(last_msg, "content"): content_to_review = last_msg.content # 创建审核 review_id = self.create_review(state, thread_id, user_id, content_to_review) # 返回状态更新 return { "review_id": review_id, "review_pending": True, "interrupt": True # 标记需要中断 } class ReviewManager: """审核管理器""" def __init__(self, store: Optional[HumanReviewStore] = None): """ 初始化审核管理器 Args: store: 审核存储 """ self.store = store or InMemoryReviewStore() def request_review( self, thread_id: str, user_id: str, content: str, metadata: Optional[Dict[str, Any]] = None ) -> str: """ 请求审核 Args: thread_id: 线程ID user_id: 用户ID content: 待审核内容 metadata: 元数据 Returns: 审核ID """ review_id = str(uuid.uuid4()) review = HumanReview( review_id=review_id, thread_id=thread_id, user_id=user_id, status=ReviewStatus.PENDING, content_to_review=content, metadata=metadata or {} ) self.store.save(review) return review_id def approve( self, review_id: str, reviewer: str, comment: str = "" ) -> bool: """ 审核通过 Args: review_id: 审核ID reviewer: 审核人 comment: 审核意见 Returns: 是否成功 """ return self.store.update_status( review_id=review_id, status=ReviewStatus.APPROVED, reviewer=reviewer, comment=comment ) def reject( self, review_id: str, reviewer: str, comment: str = "" ) -> bool: """ 审核拒绝 Args: review_id: 审核ID reviewer: 审核人 comment: 审核意见 Returns: 是否成功 """ return self.store.update_status( review_id=review_id, status=ReviewStatus.REJECTED, reviewer=reviewer, comment=comment ) def modify( self, review_id: str, reviewer: str, modified_content: str, comment: str = "" ) -> bool: """ 审核修改 Args: review_id: 审核ID reviewer: 审核人 modified_content: 修改后的内容 comment: 审核意见 Returns: 是否成功 """ return self.store.update_status( review_id=review_id, status=ReviewStatus.MODIFIED, reviewer=reviewer, comment=comment, modified_content=modified_content ) def get_pending_reviews(self, limit: int = 100) -> List[HumanReview]: """ 获取待审核列表 Args: limit: 返回数量限制 Returns: 待审核列表 """ return self.store.get_pending(limit) def get_review(self, review_id: str) -> Optional[HumanReview]: """ 获取审核详情 Args: review_id: 审核ID Returns: 审核对象,如果不存在返回 None """ return self.store.get(review_id)