feat(backend): knowledge iteration + vision + neo4j + response contract source
dependencies.py 拆分为 dependencies/ 包; 新增 vision/ragflow_ingestion/neo4j 客户端与 h5_ai_task; alembic 045 图置信度迁移; 响应契约统一收尾。
This commit is contained in:
+27
-75
@@ -45,7 +45,7 @@ limiter = Limiter(key_func=get_remote_address)
|
||||
from app.config import settings
|
||||
from app.database import get_db
|
||||
from app.utils.env_gating import is_production
|
||||
from app.dependencies import dep_redis, dep_wecom_service, dep_ai_handler
|
||||
from app.dependencies import dep_redis, dep_wecom_service
|
||||
from app.models.approval_link import ApprovalLink
|
||||
from app.models.conversation import Conversation
|
||||
from app.models.message import Message
|
||||
@@ -58,7 +58,9 @@ from app.schemas.h5 import (
|
||||
)
|
||||
from app.schemas.conversation import ConversationResponse, JoinConversationRequest
|
||||
from app.schemas.message import MessageResponse
|
||||
from app.services.ai_handler import AIHandler
|
||||
import asyncio
|
||||
|
||||
from app.tasks.h5_ai_task import process_h5_ai_reply
|
||||
from app.services.funny_phrase_service import FunnyPhraseService
|
||||
from app.services.ws_manager import manager as ws_manager
|
||||
from app.services.wecom_service import WecomService
|
||||
@@ -822,7 +824,6 @@ async def h5_send_message(
|
||||
body: dict,
|
||||
employee_id: str = Depends(_get_current_employee),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
ai_handler: AIHandler = Depends(dep_ai_handler),
|
||||
):
|
||||
"""H5 用户发送消息(含 AI 回复与计数)。
|
||||
|
||||
@@ -893,47 +894,9 @@ async def h5_send_message(
|
||||
db.add(conversation)
|
||||
await db.flush()
|
||||
|
||||
# 3. 调用 AIHandler 统一处理(打招呼检测 → 呼叫人工拦截 → AI 调用)
|
||||
ai_result = await ai_handler.handle_message(
|
||||
content=content,
|
||||
dify_conversation_id=conversation.dify_conversation_id,
|
||||
user_id=employee_id,
|
||||
)
|
||||
|
||||
# 4. 根据 AIHandler 返回结果更新会话状态
|
||||
# 更新 Dify 会话ID(多轮对话上下文)
|
||||
if ai_result.dify_conversation_id:
|
||||
conversation.dify_conversation_id = ai_result.dify_conversation_id
|
||||
|
||||
# 更新 AI 实质性回复计数(仅 AI 命中时 +1)
|
||||
if ai_result.should_count:
|
||||
conversation.ai_substantive_reply_count += 1
|
||||
|
||||
# 更新会话状态(未命中转人工时改为 queued)
|
||||
if ai_result.should_transfer:
|
||||
conversation.status = "queued"
|
||||
|
||||
db.add(conversation)
|
||||
|
||||
# 5. 创建 AI 回复消息
|
||||
ai_message = Message(
|
||||
conversation_id=conversation.id,
|
||||
sender_type="ai",
|
||||
sender_id="ai_bot",
|
||||
sender_name="AI智能助手",
|
||||
content=ai_result.content,
|
||||
msg_type="text",
|
||||
is_read=True,
|
||||
)
|
||||
db.add(ai_message)
|
||||
await db.flush()
|
||||
|
||||
# 6. WebSocket 广播:通知坐席端有新消息
|
||||
# 做什么:向所有在线坐席广播 new_message 事件,携带用户消息和 AI 回复
|
||||
# 为什么:坐席端需要实时看到员工的新消息和 AI 回复,
|
||||
# 仅靠3秒轮询会有延迟,WS 推送更实时
|
||||
# 3. 广播用户消息给坐席端(员工端靠乐观更新已显示自己消息)
|
||||
# 为什么:坐席端需实时看到员工新消息,仅依赖 3 秒轮询会有延迟
|
||||
try:
|
||||
# 广播用户消息
|
||||
await ws_manager.broadcast({
|
||||
"type": "new_message",
|
||||
"data": {
|
||||
@@ -948,41 +911,30 @@ async def h5_send_message(
|
||||
"tags": conversation.tags,
|
||||
},
|
||||
})
|
||||
# 广播 AI 回复
|
||||
await ws_manager.broadcast({
|
||||
"type": "new_message",
|
||||
"data": {
|
||||
"conversation_id": str(conversation.id),
|
||||
"message_id": str(ai_message.id),
|
||||
"sender_type": "ai",
|
||||
"sender_id": "ai_bot",
|
||||
"sender_name": "AI智能助手",
|
||||
"content": ai_result.content,
|
||||
"msg_type": "text",
|
||||
},
|
||||
})
|
||||
# 如果会话状态变更(如新会话创建或转人工),也广播状态变更
|
||||
await ws_manager.broadcast({
|
||||
"type": "conversation_updated",
|
||||
"data": {
|
||||
"conversation_id": str(conversation.id),
|
||||
"status": conversation.status,
|
||||
"assigned_agent_id": str(conversation.assigned_agent_id) if conversation.assigned_agent_id else None,
|
||||
},
|
||||
})
|
||||
except Exception as ws_err:
|
||||
# WS 广播失败不阻塞消息存储,只记录 warning
|
||||
logger.warning(f"WS 广播新消息失败(消息已存储): {ws_err}")
|
||||
logger.warning(f"WS 广播用户消息失败(消息已存储): {ws_err}")
|
||||
|
||||
# 7. 返回用户消息 + AI 回复
|
||||
# 4. 启动后台 AI 任务(异步,不阻塞 HTTP 返回)
|
||||
# 为什么:AI 推理(Dify)慢(3~15s),放后台经 WS 流式推回,
|
||||
# 发送接口瞬时返回,前端不再卡"发送中"
|
||||
# 约束:后台任务使用独立 DB session,且需单 worker(见 h5_ai_task.py)
|
||||
asyncio.create_task(
|
||||
process_h5_ai_reply(
|
||||
conversation_id=str(conversation.id),
|
||||
employee_id=employee_id,
|
||||
content=content,
|
||||
dify_conversation_id=conversation.dify_conversation_id,
|
||||
)
|
||||
)
|
||||
|
||||
# 5. 立即返回用户消息(AI 回复经 WS 异步推送,不在此同步返回)
|
||||
user_msg_data = MessageResponse.model_validate(message).model_dump()
|
||||
ai_msg_data = MessageResponse.model_validate(ai_message).model_dump()
|
||||
|
||||
return success_response(
|
||||
data={
|
||||
"user_message": user_msg_data,
|
||||
"ai_reply": ai_msg_data,
|
||||
"is_guidance": ai_result.is_guidance,
|
||||
"ai_reply": None,
|
||||
"is_guidance": False,
|
||||
"ai_reply_count": conversation.ai_substantive_reply_count,
|
||||
"can_call_agent": conversation.ai_substantive_reply_count >= 3,
|
||||
"conversation_status": conversation.status,
|
||||
@@ -1173,14 +1125,14 @@ async def shake(
|
||||
# 无活跃会话 → 拒绝,必须先与 AI 互动(前端按钮此时不应出现,这是后端兜底)
|
||||
raise AppException(
|
||||
1003,
|
||||
"请先描述您的问题,AI助手需要先帮您分析。至少互动3轮后才能呼叫人工坐席哦~"
|
||||
"请先描述您的问题,Duckula(达寇拉)需要先帮您分析。至少互动3轮后才能呼叫人工坐席哦~"
|
||||
)
|
||||
|
||||
# 前置校验:必须满足 AI 实质性回复 >= 3 次才能呼叫坐席
|
||||
if conversation.ai_substantive_reply_count < 3:
|
||||
raise AppException(
|
||||
1003,
|
||||
"请先描述您的问题,AI助手需要先帮您分析。至少互动3轮后才能呼叫人工坐席哦~"
|
||||
"请先描述您的问题,Duckula(达寇拉)需要先帮您分析。至少互动3轮后才能呼叫人工坐席哦~"
|
||||
)
|
||||
|
||||
# 更新员工姓名
|
||||
@@ -1327,14 +1279,14 @@ async def call_agent(
|
||||
if not conversation:
|
||||
raise AppException(
|
||||
code=1003,
|
||||
message="请先描述您的问题,AI助手需要先帮您分析。至少互动3轮后才能呼叫人工坐席哦~"
|
||||
message="请先描述您的问题,Duckula(达寇拉)需要先帮您分析。至少互动3轮后才能呼叫人工坐席哦~"
|
||||
)
|
||||
|
||||
# 2. 前置校验:必须满足 AI 实质性回复 >= 3 次
|
||||
if conversation.ai_substantive_reply_count < 3:
|
||||
raise AppException(
|
||||
code=1003,
|
||||
message="请先描述您的问题,AI助手需要先帮您分析。至少互动3轮后才能呼叫人工坐席哦~"
|
||||
message="请先描述您的问题,Duckula(达寇拉)需要先帮您分析。至少互动3轮后才能呼叫人工坐席哦~"
|
||||
)
|
||||
|
||||
# 更新员工姓名
|
||||
|
||||
Reference in New Issue
Block a user