WIP-CHECKPOINT[auth-refactor]: 固化工程师崩溃前部分成果 + 同树其他未提交WIP(仅源码,不含密钥/二进制)-- 待重激活工程师续作
This commit is contained in:
@@ -0,0 +1,444 @@
|
||||
# =============================================================================
|
||||
# 企微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()
|
||||
Reference in New Issue
Block a user