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:
@@ -115,9 +115,53 @@ async def list_conversations(
|
||||
for emp_id in employee_ids:
|
||||
employee_avatar_map[emp_id] = await session_service._get_employee_avatar(emp_id)
|
||||
|
||||
# ── BUGFIX: 批量回退查询员工信息 ──
|
||||
# 为什么需要:conversations 表中 employee_name/department/position 是冗余字段,
|
||||
# 在会话创建时可能为空(异步创建、企微回调延迟等),导致列表API返回空字符串。
|
||||
# 当这些字段为空时,从 employees 表批量查询并回填,确保坐席端能看到完整用户信息。
|
||||
# 何时触发:仅当 conversations 表中的 employee_name 为空字符串时才会去 employees 表查找。
|
||||
employee_name_map: dict[str, dict] = {}
|
||||
empty_name_conv_ids = [
|
||||
conv.employee_id for conv in conversations
|
||||
if not conv.employee_name and conv.employee_id
|
||||
]
|
||||
if empty_name_conv_ids:
|
||||
try:
|
||||
from app.models.employee import Employee
|
||||
stmt = select(Employee).where(Employee.employee_id.in_(empty_name_conv_ids))
|
||||
result = await db.execute(stmt)
|
||||
for emp in result.scalars().all():
|
||||
employee_name_map[emp.employee_id] = {
|
||||
"name": emp.name or "",
|
||||
"department": emp.department or "",
|
||||
"position": emp.position or "",
|
||||
"level": getattr(emp, "it_level", "") or "",
|
||||
}
|
||||
if employee_name_map:
|
||||
logger.info(
|
||||
f"从employees表批量回退获取员工信息: "
|
||||
f"请求={len(empty_name_conv_ids)}, 命中={len(employee_name_map)}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"从employees表批量回退获取员工信息失败: error={e}")
|
||||
|
||||
# 转换为响应 Schema,附加 is_mine / assigned_agent_name / can_grab / avatar 字段
|
||||
items = []
|
||||
for conv in conversations:
|
||||
# ── BUGFIX: 应用 employees 表回退信息 ──
|
||||
# 如果 conv 的 employee_name 为空,用批量查询结果回填
|
||||
# 这样 ConversationResponse.model_validate 序列化时就能拿到正确的值
|
||||
emp_fallback = employee_name_map.get(conv.employee_id, {})
|
||||
if emp_fallback:
|
||||
if not conv.employee_name and emp_fallback.get("name"):
|
||||
conv.employee_name = emp_fallback["name"]
|
||||
if not conv.department and emp_fallback.get("department"):
|
||||
conv.department = emp_fallback["department"]
|
||||
if not conv.position and emp_fallback.get("position"):
|
||||
conv.position = emp_fallback["position"]
|
||||
if not conv.level and emp_fallback.get("level"):
|
||||
conv.level = emp_fallback["level"]
|
||||
|
||||
conv_data = ConversationResponse.model_validate(conv).model_dump()
|
||||
# 员工头像(从缓存获取)
|
||||
conv_data["avatar"] = employee_avatar_map.get(conv.employee_id, "")
|
||||
@@ -188,7 +232,7 @@ async def get_conversation(
|
||||
conversation.employee_name = employee.name
|
||||
conversation.department = employee.department or ""
|
||||
conversation.position = employee.position or ""
|
||||
conversation.level = employee.level or ""
|
||||
conversation.level = getattr(employee, "it_level", "") or ""
|
||||
logger.info(
|
||||
f"从employees表回退获取会话详情员工信息: employee_id={conversation.employee_id}, "
|
||||
f"name={employee.name}"
|
||||
@@ -275,6 +319,7 @@ async def resolve_conversation(
|
||||
"""结单。
|
||||
|
||||
坐席点击"结单"按钮时调用,将会话状态改为 resolved。
|
||||
结单完成后异步触发知识建议生成(通道 A 全链路闭环)。
|
||||
|
||||
权限控制:只有主责坐席(assigned_agent_id)才能结单。
|
||||
协作坐席和其他坐席不能结单。
|
||||
@@ -303,6 +348,51 @@ async def resolve_conversation(
|
||||
conversation = await session_service.resolve_conversation(conversation_id)
|
||||
|
||||
response_data = ConversationResponse.model_validate(conversation).model_dump()
|
||||
|
||||
# ── 任务1(P0):会话关闭→异步触发知识建议生成 ──
|
||||
# 在结单响应返回后,异步调用 Dify 生成知识迭代建议。
|
||||
# 使用 FastAPI BackgroundTasks 确保不阻塞结单响应。
|
||||
try:
|
||||
from fastapi import BackgroundTasks
|
||||
import asyncio as _asyncio
|
||||
|
||||
async def _trigger_knowledge_suggestion():
|
||||
"""异步生成知识建议的后台任务(独立 db session)。"""
|
||||
from app.database import _get_session_factory
|
||||
from app.services.knowledge_iteration_service import KnowledgeIterationService
|
||||
|
||||
factory = _get_session_factory()
|
||||
async with factory() as bg_db:
|
||||
try:
|
||||
knowledge_service = KnowledgeIterationService()
|
||||
suggestion = await knowledge_service.generate_knowledge_suggestion(
|
||||
db=bg_db,
|
||||
source_type="conversation",
|
||||
source_data=[str(conversation_id)],
|
||||
reason=f"会话'{conversation_id}'已结单,自动生成知识迭代建议",
|
||||
)
|
||||
if suggestion:
|
||||
bg_db.add(suggestion)
|
||||
await bg_db.commit()
|
||||
logger.info(
|
||||
f"会话关闭→知识建议已生成: conv_id={conversation_id}, "
|
||||
f"suggestion_id={suggestion.id}, type={suggestion.suggestion_type}"
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"会话关闭→无知识建议生成(Dify不可用或无需建议): "
|
||||
f"conv_id={conversation_id}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"会话关闭→知识建议生成失败: conv_id={conversation_id}, error={e}")
|
||||
|
||||
# 创建后台任务(不阻塞结单响应)
|
||||
_asyncio.ensure_future(_trigger_knowledge_suggestion())
|
||||
logger.info(f"会话结单完成,已触发异步知识建议生成: conv_id={conversation_id}")
|
||||
except Exception as e:
|
||||
# 知识建议生成失败不影响结单主流程
|
||||
logger.warning(f"触发异步知识建议生成失败(不影响结单): {e}")
|
||||
|
||||
return success_response(data=response_data)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user