# ============================================================================= # 企微IT智能服务台 — AI Wingman API 路由 # ============================================================================= # 说明:坐席端 AI 智能副驾驶 API,包含 3 个核心端点: # 1. POST /api/conversations/{id}/wingman/draft — 生成 AI 草稿回复 # 2. POST /api/conversations/{id}/wingman/summary — 生成会话自动摘要 # 3. POST /api/conversations/{id}/wingman/tags — 生成自动标签建议 # # 所有端点需要坐席认证(get_current_agent) # ============================================================================= import logging from fastapi import APIRouter, Depends from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.dependencies import dep_wingman_service from app.models.agent import Agent from app.models.conversation import Conversation from app.models.message import Message from app.services.wingman_service import WingmanService from app.utils.response import ERR_NOT_FOUND, success_response # 复用坐席认证依赖 from app.api.agents import get_current_agent logger = logging.getLogger(__name__) # 创建路由器 router = APIRouter() # -------------------------------------------------------------------------- # 辅助函数 # -------------------------------------------------------------------------- async def _validate_conversation( conversation_id: str, agent: Agent, db: AsyncSession, ) -> Conversation: """验证会话存在性并返回会话对象。 Args: conversation_id: 会话ID agent: 当前坐席 db: 数据库会话 Returns: Conversation: 会话对象 Raises: AppException: 会话不存在 """ stmt = select(Conversation).where(Conversation.id == conversation_id) result = await db.execute(stmt) conversation = result.scalars().first() if not conversation: raise ERR_NOT_FOUND return conversation async def _get_recent_messages( conversation_id: str, db: AsyncSession, limit: int = 20, ) -> list[dict]: """获取会话最近的消息历史(转换为字典列表)。 Args: conversation_id: 会话ID db: 数据库会话 limit: 获取的消息条数 Returns: list[dict]: 消息字典列表 """ stmt = ( select(Message) .where(Message.conversation_id == conversation_id) .order_by(Message.created_at.desc()) .limit(limit) ) result = await db.execute(stmt) messages = list(result.scalars().all()) # 按时间正序排列(最早的在前) messages.reverse() # 转换为字典列表 return [ { "id": msg.id, "sender_type": msg.sender_type, "sender_name": msg.sender_name, "content": msg.content, "msg_type": msg.msg_type, "created_at": msg.created_at.isoformat() if msg.created_at else "", } for msg in messages ] # -------------------------------------------------------------------------- # POST /api/conversations/{conversation_id}/wingman/draft # -------------------------------------------------------------------------- @router.post("/conversations/{conversation_id}/wingman/draft") async def generate_draft( conversation_id: str, agent: Agent = Depends(get_current_agent), db: AsyncSession = Depends(get_db), wingman_service: WingmanService = Depends(dep_wingman_service), ): """生成 AI 草稿回复。 基于当前会话的消息历史,让 Wingman Agent 生成坐席可以采纳的草稿回复。 Args: conversation_id: 会话ID agent: 当前坐席(通过认证依赖注入) db: 数据库会话 wingman_service: Wingman 服务实例 Returns: Dict: 统一响应格式,包含草稿内容、置信度和推理说明 """ # 1. 验证坐席身份 + 会话存在性 await _validate_conversation(conversation_id, agent, db) # 2. 从数据库读取该会话的消息历史(最近 20 条) messages = await _get_recent_messages(conversation_id, db, limit=20) # 3. 调用 WingmanService 生成草稿 result = await wingman_service.generate_draft( conversation_id=conversation_id, messages=messages, db=db, ) return success_response(data=result) # -------------------------------------------------------------------------- # POST /api/conversations/{conversation_id}/wingman/summary # -------------------------------------------------------------------------- @router.post("/conversations/{conversation_id}/wingman/summary") async def generate_summary( conversation_id: str, agent: Agent = Depends(get_current_agent), db: AsyncSession = Depends(get_db), wingman_service: WingmanService = Depends(dep_wingman_service), ): """生成会话自动摘要。 基于完整对话生成结构化摘要,包含问题、原因、解决方案。 通常在结单时调用。 Args: conversation_id: 会话ID agent: 当前坐席 db: 数据库会话 wingman_service: Wingman 服务实例 Returns: Dict: 统一响应格式,包含问题、原因、解决方案 """ # 1. 验证坐席身份 + 会话存在性 await _validate_conversation(conversation_id, agent, db) # 2. 从数据库读取该会话的完整消息历史(最多 50 条) messages = await _get_recent_messages(conversation_id, db, limit=50) # 3. 调用 WingmanService 生成摘要 result = await wingman_service.generate_summary( conversation_id=conversation_id, messages=messages, ) return success_response(data=result) # -------------------------------------------------------------------------- # POST /api/conversations/{conversation_id}/wingman/tags # -------------------------------------------------------------------------- @router.post("/conversations/{conversation_id}/wingman/tags") async def suggest_tags( conversation_id: str, agent: Agent = Depends(get_current_agent), db: AsyncSession = Depends(get_db), wingman_service: WingmanService = Depends(dep_wingman_service), ): """生成自动标签建议。 基于对话内容建议标签分类,包含标签列表、分类和优先级。 Args: conversation_id: 会话ID agent: 当前坐席 db: 数据库会话 wingman_service: Wingman 服务实例 Returns: Dict: 统一响应格式,包含建议标签、分类和优先级 """ # 1. 验证坐席身份 + 会话存在性 conversation = await _validate_conversation(conversation_id, agent, db) # 2. 从数据库读取该会话的消息历史(最近 20 条) messages = await _get_recent_messages(conversation_id, db, limit=20) # 3. 获取已有标签(用于避免重复建议) existing_tags = {} if hasattr(conversation, 'tags') and conversation.tags: existing_tags = conversation.tags if isinstance(conversation.tags, dict) else {} # 4. 调用 WingmanService 生成标签建议 result = await wingman_service.suggest_tags( conversation_id=conversation_id, messages=messages, existing_tags=existing_tags, ) return success_response(data=result)