# ============================================================================= # 企微IT智能服务台 — 知识库自动迭代服务 # ============================================================================= # 说明:知识库自动迭代核心服务 # 功能: # 1. 分析错误标注的高频问题 # 2. 查找未命中知识库的会话 # 3. 生成优化建议 # 4. 审核通过后应用到知识库 # 5. 推送审核通知给管理员 # ============================================================================= import json import logging from datetime import datetime, timedelta from typing import Any, Dict, List, Optional import httpx from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.models.knowledge_base import KnowledgeBase from app.models.knowledge_suggestion import KnowledgeSuggestion from app.models.conversation import Conversation from app.models.conversation_annotation import ConversationAnnotation logger = logging.getLogger(__name__) class KnowledgeIterationService: """知识库自动迭代服务。 分析会话标注和会话数据,生成知识库优化建议, 支持管理员审核后自动应用到知识库。 """ def __init__(self): """初始化服务。""" # AI 分析 API(复用 Dify) self.ai_api_url = settings.dify_wingman_api_url self.ai_api_key = settings.dify_wingman_api_key self.ai_timeout = settings.dify_wingman_timeout # -------------------------------------------------------------------------- # 核心方法:分析并生成建议 # -------------------------------------------------------------------------- async def analyze_and_generate_suggestions( self, db: AsyncSession, days: int = 7, ) -> Dict[str, Any]: """分析并生成知识库优化建议。 分析过去N天的标注数据和会话数据,生成优化建议。 Args: db: 数据库会话 days: 分析过去N天的数据,默认7天 Returns: Dict: { "annotations_analyzed": int, # 分析的标注数量 "conversations_analyzed": int, # 分析的会话数量 "suggestions_generated": int, # 生成的建议数量 } """ result = { "annotations_analyzed": 0, "conversations_analyzed": 0, "suggestions_generated": 0, } # 1. 分析错误标注的高频问题 annotation_suggestions = await self._analyze_annotation_data(db, days) result["annotations_analyzed"] = annotation_suggestions.get("analyzed_count", 0) result["suggestions_generated"] += annotation_suggestions.get( "suggestions_count", 0 ) # 2. 分析未命中知识库的会话 conversation_suggestions = await self._analyze_conversation_data(db, days) result["conversations_analyzed"] = conversation_suggestions.get( "analyzed_count", 0 ) result["suggestions_generated"] += conversation_suggestions.get( "suggestions_count", 0 ) logger.info( f"知识库迭代分析完成: " f"标注={result['annotations_analyzed']}, " f"会话={result['conversations_analyzed']}, " f"建议={result['suggestions_generated']}" ) return result async def _analyze_annotation_data( self, db: AsyncSession, days: int ) -> Dict[str, Any]: """分析标注数据,生成优化建议。 查找被标记为"无用"的AI回复,分析高频错误原因, 尝试生成FAQ更新建议。 Args: db: 数据库会话 days: 分析过去N天的数据 Returns: Dict: 分析结果统计 """ since = datetime.now() - timedelta(days=days) # 查询过去N天的无效标注 stmt = ( select(ConversationAnnotation) .where(ConversationAnnotation.feedback == "useless") .where(ConversationAnnotation.created_at >= since) ) result = await db.execute(stmt) annotations = result.scalars().all() if not annotations: return {"analyzed_count": 0, "suggestions_count": 0} # 按被标注的消息ID分组,统计高频错误 message_error_counts: Dict[str, int] = {} for ann in annotations: msg_id = ann.message_id message_error_counts[msg_id] = message_error_counts.get(msg_id, 0) + 1 # 找出高频错误(被标注3次以上) frequent_errors = { msg_id: count for msg_id, count in message_error_counts.items() if count >= 3 } if not frequent_errors: return {"analyzed_count": len(annotations), "suggestions_count": 0} # 调用AI分析错误模式,生成更新建议 suggestions_count = 0 for msg_id, error_count in frequent_errors.items(): # 生成优化建议 suggestion = await self._generate_update_suggestion( db=db, source_type="annotation", source_data=[msg_id], reason=f"该AI回复在过去{days}天内被标记为无用{error_count}次", ) if suggestion: db.add(suggestion) suggestions_count += 1 await db.commit() return {"analyzed_count": len(annotations), "suggestions_count": suggestions_count} async def _analyze_conversation_data( self, db: AsyncSession, days: int ) -> Dict[str, Any]: """分析会话数据,生成新增FAQ建议。 查找AI无法解决(转人工)的会话,分析生成新FAQ建议。 Args: db: 数据库会话 days: 分析过去N天的数据 Returns: Dict: 分析结果统计 """ since = datetime.now() - timedelta(days=days) # 查询过去N天的AI未解决会话(转人工的会话) stmt = ( select(Conversation) .where(Conversation.created_at >= since) .where(Conversation.status.in_(["waiting_agent", "agentServing"])) ) result = await db.execute(stmt) conversations = result.scalars().all() if not conversations: return {"analyzed_count": 0, "suggestions_count": 0} # 抽样分析(避免一次性处理太多) sample_size = min(20, len(conversations)) sampled = conversations[:sample_size] # 对每个会话进行分析 suggestions_count = 0 for conv in sampled: # 检查是否已存在类似建议 existing = await self._check_existing_suggestion(db, conv.id) if existing: continue # 生成新FAQ建议 suggestion = await self._generate_new_faq_suggestion( db=db, source_type="conversation", source_data=[conv.id], reason=f"会话'{conv.id}'中AI未能解决问题,需人工介入", ) if suggestion: db.add(suggestion) suggestions_count += 1 await db.commit() return {"analyzed_count": len(conversations), "suggestions_count": suggestions_count} # -------------------------------------------------------------------------- # 辅助方法 # -------------------------------------------------------------------------- async def _generate_update_suggestion( self, db: AsyncSession, source_type: str, source_data: List[str], reason: str, ) -> Optional[KnowledgeSuggestion]: """生成知识库更新建议。 Args: db: 数据库会话 source_type: 来源类型 source_data: 来源数据 reason: 生成理由 Returns: Optional[KnowledgeSuggestion]: 建议对象 """ # TODO: 调用AI生成具体的更新内容 # 当前返回示例数据,实际应调用 Dify API return KnowledgeSuggestion( suggestion_type="update", status="pending", title="[待AI生成] 优化建议", content="请通过AI分析生成具体的更新内容", category="其他", tags=[], source_type=source_type, source_data=source_data, reason=reason, ) async def _generate_new_faq_suggestion( self, db: AsyncSession, source_type: str, source_data: List[str], reason: str, ) -> Optional[KnowledgeSuggestion]: """生成新FAQ建议。 Args: db: 数据库会话 source_type: 来源类型 source_data: 来源数据 reason: 生成理由 Returns: Optional[KnowledgeSuggestion]: 建议对象 """ # TODO: 调用AI生成具体的FAQ内容 # 当前返回示例数据,实际应调用 Dify API return KnowledgeSuggestion( suggestion_type="new_faq", status="pending", title="[待AI生成] 新FAQ建议", content="请通过AI分析生成具体的问题和答案", category="其他", tags=[], source_type=source_type, source_data=source_data, reason=reason, ) async def _check_existing_suggestion( self, db: AsyncSession, source_id: str ) -> bool: """检查是否已存在相关建议。 Args: db: 数据库会话 source_id: 来源ID Returns: bool: 是否已存在 """ stmt = ( select(KnowledgeSuggestion) .where(KnowledgeSuggestion.status == "pending") .where(KnowledgeSuggestion.source_data.contains(source_id)) ) result = await db.execute(stmt) existing = result.scalars().first() return existing is not None # -------------------------------------------------------------------------- # 审核与应用 # -------------------------------------------------------------------------- async def approve_suggestion( self, db: AsyncSession, suggestion_id: str, reviewer_id: str, ) -> Optional[KnowledgeSuggestion]: """审核通过建议并应用到知识库。 Args: db: 数据库会话 suggestion_id: 建议ID reviewer_id: 审核人ID Returns: Optional[KnowledgeSuggestion]: 更新后的建议对象 """ # 获取建议 stmt = select(KnowledgeSuggestion).where( KnowledgeSuggestion.id == suggestion_id ) result = await db.execute(stmt) suggestion = result.scalar_one_or_none() if not suggestion: return None # 更新状态 suggestion.status = "approved" suggestion.reviewer_id = reviewer_id suggestion.reviewed_at = datetime.now() # 如果是新FAQ或更新,创建对应的知识库条目 if suggestion.suggestion_type in ("new_faq", "update"): kb = KnowledgeBase( title=suggestion.title, content=suggestion.content, category=suggestion.category, tags=suggestion.tags, ) db.add(kb) suggestion.status = "applied" await db.commit() await db.refresh(suggestion) logger.info(f"建议已审核通过并应用: {suggestion_id}") return suggestion async def reject_suggestion( self, db: AsyncSession, suggestion_id: str, reviewer_id: str, reject_reason: str, ) -> Optional[KnowledgeSuggestion]: """拒绝建议。 Args: db: 数据库会话 suggestion_id: 建议ID reviewer_id: 审核人ID reject_reason: 拒绝理由 Returns: Optional[KnowledgeSuggestion]: 更新后的建议对象 """ stmt = select(KnowledgeSuggestion).where( KnowledgeSuggestion.id == suggestion_id ) result = await db.execute(stmt) suggestion = result.scalar_one_or_none() if not suggestion: return None suggestion.status = "rejected" suggestion.reviewer_id = reviewer_id suggestion.reviewed_at = datetime.now() suggestion.reject_reason = reject_reason await db.commit() await db.refresh(suggestion) logger.info(f"建议已拒绝: {suggestion_id}, 理由: {reject_reason}") return suggestion # -------------------------------------------------------------------------- # 查询统计 # -------------------------------------------------------------------------- async def get_suggestion_stats(self, db: AsyncSession) -> Dict[str, int]: """获取建议统计。 Args: db: 数据库会话 Returns: Dict: 统计数据 """ # 总数 stmt = select(KnowledgeSuggestion) result = await db.execute(stmt) all_suggestions = result.scalars().all() stats = { "total": len(all_suggestions), "pending": 0, "approved": 0, "rejected": 0, "applied": 0, "new_faq_count": 0, "update_count": 0, "outdated_count": 0, } for s in all_suggestions: if s.status in stats: stats[s.status] += 1 if s.suggestion_type == "new_faq": stats["new_faq_count"] += 1 elif s.suggestion_type == "update": stats["update_count"] += 1 elif s.suggestion_type == "outdated": stats["outdated_count"] += 1 return stats # 依赖注入函数 async def dep_knowledge_iteration_service() -> KnowledgeIterationService: """获取知识库迭代服务实例。""" return KnowledgeIterationService()