v3.1 + 批次0: 智能回复重构基线 - ApprovalMatcher + 关键词降级 + 文档速修 + v4.0任务书面化

This commit is contained in:
Simon
2026-07-17 23:08:59 +08:00
parent 5a77a89ab1
commit 3ed86d5fb3
181 changed files with 19738 additions and 2655 deletions
+121 -1
View File
@@ -18,11 +18,16 @@
# =============================================================================
import logging
import asyncio
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from app.services.ws_manager import manager as ws_manager
from app.services.cache_service import cache_service
from app.database import _get_session_factory
from app.models.conversation import Conversation
from app.models.message import Message
from app.tasks.h5_ai_task import process_h5_ai_reply
logger = logging.getLogger(__name__)
@@ -212,6 +217,91 @@ async def websocket_endpoint(
# H5员工 WebSocket 端点
# ==========================================================================
async def _handle_option_select(
conversation_id: str,
employee_id: str,
option_label: str,
):
"""处理员工点击 AI 选项按钮的后端逻辑(v2.0 新增)。
做什么:
1. 在 DB 中存储员工的选项选择为一条 employee 消息
2. 广播该消息给坐席端(让坐席看到员工选了什么)
3. 触发 process_h5_ai_reply() → Dify 接收选项文本作为用户消息 → 返回下一轮 AI 回复
为什么:前端 sendOptionSelect() 通过 WS 发送 option_select 消息,
后端必须接收并触发 AI 回复,否则用户点击选项后无响应。
Args:
conversation_id: 会话 ID
employee_id: 员工企微 UserID
option_label: 选项的显示文本(如"企微密码"),作为用户消息发给 Dify
"""
from datetime import datetime
try:
async with _get_session_factory()() as db:
# 1. 查找会话(获取 dify_conversation_id 用于多轮上下文)
conversation = await db.get(Conversation, conversation_id)
if not conversation:
logger.warning(f"option_select: 会话不存在 {conversation_id}")
return
# 2. 存储员工消息(选项选择作为文本消息)
emp_msg = Message(
conversation_id=conversation_id,
sender_type="employee",
sender_id=employee_id,
sender_name="", # 前端会从 employeeStore 补全
content=option_label,
msg_type="text",
is_read=False,
)
db.add(emp_msg)
await db.flush()
# 更新会话时间
conversation.updated_at = datetime.now()
db.add(conversation)
await db.flush()
await db.commit()
# 3. 广播给坐席端(让坐席看到员工的选择)
try:
await ws_manager.broadcast({
"type": "new_message",
"data": {
"conversation_id": str(conversation_id),
"message_id": str(emp_msg.id),
"sender_type": "employee",
"sender_id": employee_id,
"content": option_label,
"msg_type": "text",
},
})
except Exception as ws_err:
logger.warning(f"option_select: WS 广播坐席失败: {ws_err}")
# 4. 触发 AI 回复(异步后台任务,不阻塞)
# dify_conversation_id 从 conversation 对象获取(保持多轮上下文)
asyncio.create_task(
process_h5_ai_reply(
conversation_id=conversation_id,
employee_id=employee_id,
content=option_label,
dify_conversation_id=conversation.dify_conversation_id,
msg_type="text",
)
)
logger.info(
f"option_select 已触发 AI 回复: conv={conversation_id}, "
f"option={option_label}"
)
except Exception as e:
logger.error(f"option_select 处理异常: {e}", exc_info=True)
@router.websocket("/ws/h5/{employee_id}")
async def h5_websocket_endpoint(
websocket: WebSocket,
@@ -314,7 +404,7 @@ async def h5_websocket_endpoint(
try:
# 消息接收循环
# H5员工端目前只发送心跳 ping,不需要发送 typing 等事件
# H5员工端发送心跳 ping 和 option_select(选项按钮点击)
while True:
data = await websocket.receive_json()
@@ -323,6 +413,36 @@ async def h5_websocket_endpoint(
await websocket.send_json({"type": "pong"})
logger.debug(f"H5 WebSocket 心跳: employee_id={employee_id}")
# v2.0: 处理选项按钮点击(option_select
# 做什么:员工点击 AI 结构化消息中的选项按钮后,前端通过 WS 发送 option_select
# 后端接收后触发 AI 回复流程(与普通发消息等效),实现交互式排查闭环
elif data.get("type") == "option_select":
option_data = data.get("data", {})
conv_id = option_data.get("conversation_id")
option_label = option_data.get("option_label", "")
option_value = option_data.get("option_value", "")
if conv_id and option_label:
logger.info(
f"H5 WS option_select: employee={employee_id}, "
f"conv={conv_id}, option={option_value}"
)
# 异步触发 AI 回复(不阻塞 WS 循环)
# process_h5_ai_reply 内部创建独立 DB session
# dify_conversation_id 传 None 时会从 conversation 对象回退读取
asyncio.create_task(
_handle_option_select(
conversation_id=conv_id,
employee_id=employee_id,
option_label=option_label,
)
)
else:
logger.warning(
f"H5 WS option_select 数据不完整: employee={employee_id}, "
f"conv_id={conv_id}, label={option_label}"
)
else:
logger.debug(
f"H5 WebSocket 收到未知消息: employee_id={employee_id}, "