- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
273 lines
9.3 KiB
Python
273 lines
9.3 KiB
Python
"""
|
||
能力评估服务
|
||
用于分析用户对话数据,生成能力评估报告和课程推荐
|
||
|
||
使用 Python 原生实现
|
||
"""
|
||
import json
|
||
import logging
|
||
from typing import Dict, Any, List, Literal
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from sqlalchemy import select
|
||
|
||
from app.models.ability import AbilityAssessment
|
||
from app.services.ai import ability_analysis_service
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AbilityAssessmentService:
|
||
"""能力评估服务类"""
|
||
|
||
async def analyze_yanji_conversations(
|
||
self,
|
||
user_id: int,
|
||
phone: str,
|
||
db: AsyncSession,
|
||
yanji_service,
|
||
engine: Literal["v2"] = "v2"
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
分析言迹对话并生成能力评估及课程推荐
|
||
|
||
Args:
|
||
user_id: 用户ID
|
||
phone: 用户手机号(用于获取言迹数据)
|
||
db: 数据库会话
|
||
yanji_service: 言迹服务实例
|
||
engine: 引擎类型(v2=Python原生)
|
||
|
||
Returns:
|
||
评估结果字典,包含:
|
||
- assessment_id: 评估记录ID
|
||
- total_score: 综合评分
|
||
- dimensions: 能力维度列表
|
||
- recommended_courses: 推荐课程列表
|
||
- conversation_count: 分析的对话数量
|
||
|
||
Raises:
|
||
ValueError: 未找到员工的录音记录
|
||
Exception: API调用失败或其他错误
|
||
"""
|
||
logger.info(f"开始分析言迹对话: user_id={user_id}, phone={phone}, engine={engine}")
|
||
|
||
# 1. 获取员工对话数据(最多10条录音)
|
||
conversations = await yanji_service.get_employee_conversations_for_analysis(
|
||
phone=phone,
|
||
limit=10
|
||
)
|
||
|
||
if not conversations:
|
||
logger.warning(f"未找到员工的录音记录: user_id={user_id}, phone={phone}")
|
||
raise ValueError("未找到该员工的录音记录")
|
||
|
||
# 2. 合并所有对话历史
|
||
all_dialogues = []
|
||
for conv in conversations:
|
||
all_dialogues.extend(conv['dialogue_history'])
|
||
|
||
logger.info(
|
||
f"准备分析: user_id={user_id}, "
|
||
f"对话数={len(conversations)}, "
|
||
f"总轮次={len(all_dialogues)}"
|
||
)
|
||
|
||
used_engine = "v2"
|
||
|
||
# Python 原生实现
|
||
logger.info(f"调用原生能力分析服务")
|
||
|
||
# 将对话历史格式化为文本
|
||
dialogue_text = self._format_dialogues_for_analysis(all_dialogues)
|
||
|
||
# 调用原生服务
|
||
result = await ability_analysis_service.analyze(
|
||
db=db,
|
||
user_id=user_id,
|
||
dialogue_history=dialogue_text
|
||
)
|
||
|
||
if not result.success:
|
||
raise Exception(f"能力分析失败: {result.error}")
|
||
|
||
# 转换为兼容格式
|
||
analysis_result = {
|
||
"analysis": {
|
||
"total_score": result.total_score,
|
||
"ability_dimensions": [
|
||
{"name": d.name, "score": d.score, "feedback": d.feedback}
|
||
for d in result.ability_dimensions
|
||
],
|
||
"course_recommendations": [
|
||
{
|
||
"course_id": c.course_id,
|
||
"course_name": c.course_name,
|
||
"recommendation_reason": c.recommendation_reason,
|
||
"priority": c.priority,
|
||
"match_score": c.match_score,
|
||
}
|
||
for c in result.course_recommendations
|
||
]
|
||
}
|
||
}
|
||
|
||
logger.info(
|
||
f"能力分析完成 - total_score: {result.total_score}, "
|
||
f"provider: {result.ai_provider}, latency: {result.ai_latency_ms}ms"
|
||
)
|
||
|
||
# 4. 提取结果
|
||
analysis = analysis_result.get('analysis', {})
|
||
ability_dims = analysis.get('ability_dimensions', [])
|
||
course_recs = analysis.get('course_recommendations', [])
|
||
total_score = analysis.get('total_score')
|
||
|
||
logger.info(
|
||
f"分析完成 (engine={used_engine}): total_score={total_score}, "
|
||
f"dimensions={len(ability_dims)}, courses={len(course_recs)}"
|
||
)
|
||
|
||
# 5. 保存能力评估记录到数据库
|
||
assessment = AbilityAssessment(
|
||
user_id=user_id,
|
||
source_type='yanji_badge',
|
||
source_id=','.join([str(c['audio_id']) for c in conversations]),
|
||
total_score=total_score,
|
||
ability_dimensions=ability_dims,
|
||
recommended_courses=course_recs,
|
||
conversation_count=len(conversations)
|
||
)
|
||
|
||
db.add(assessment)
|
||
await db.commit()
|
||
await db.refresh(assessment)
|
||
|
||
logger.info(
|
||
f"评估记录已保存: assessment_id={assessment.id}, "
|
||
f"user_id={user_id}, total_score={total_score}"
|
||
)
|
||
|
||
# 6. 返回评估结果
|
||
return {
|
||
"assessment_id": assessment.id,
|
||
"total_score": total_score,
|
||
"dimensions": ability_dims,
|
||
"recommended_courses": course_recs,
|
||
"conversation_count": len(conversations),
|
||
"analyzed_at": assessment.analyzed_at,
|
||
"engine": used_engine,
|
||
}
|
||
|
||
def _format_dialogues_for_analysis(self, dialogues: List[Dict[str, Any]]) -> str:
|
||
"""
|
||
将对话历史列表格式化为文本
|
||
|
||
Args:
|
||
dialogues: 对话历史列表,每项包含 speaker, content 等字段
|
||
|
||
Returns:
|
||
格式化后的对话文本
|
||
"""
|
||
lines = []
|
||
for i, d in enumerate(dialogues, 1):
|
||
speaker = d.get('speaker', 'unknown')
|
||
content = d.get('content', '')
|
||
|
||
# 统一说话者标识
|
||
if speaker in ['consultant', 'employee', 'user', '员工']:
|
||
speaker_label = '员工'
|
||
elif speaker in ['customer', 'client', '顾客', '客户']:
|
||
speaker_label = '顾客'
|
||
else:
|
||
speaker_label = speaker
|
||
|
||
lines.append(f"[{i}] {speaker_label}: {content}")
|
||
|
||
return '\n'.join(lines)
|
||
|
||
async def get_user_assessment_history(
|
||
self,
|
||
user_id: int,
|
||
db: AsyncSession,
|
||
limit: int = 10
|
||
) -> List[Dict[str, Any]]:
|
||
"""
|
||
获取用户的能力评估历史记录
|
||
|
||
Args:
|
||
user_id: 用户ID
|
||
db: 数据库会话
|
||
limit: 返回记录数量限制
|
||
|
||
Returns:
|
||
评估历史记录列表
|
||
"""
|
||
stmt = (
|
||
select(AbilityAssessment)
|
||
.where(AbilityAssessment.user_id == user_id)
|
||
.order_by(AbilityAssessment.analyzed_at.desc())
|
||
.limit(limit)
|
||
)
|
||
|
||
result = await db.execute(stmt)
|
||
assessments = result.scalars().all()
|
||
|
||
history = []
|
||
for assessment in assessments:
|
||
history.append({
|
||
"id": assessment.id,
|
||
"source_type": assessment.source_type,
|
||
"total_score": assessment.total_score,
|
||
"ability_dimensions": assessment.ability_dimensions,
|
||
"recommended_courses": assessment.recommended_courses,
|
||
"conversation_count": assessment.conversation_count,
|
||
"analyzed_at": assessment.analyzed_at.isoformat() if assessment.analyzed_at else None,
|
||
"created_at": assessment.created_at.isoformat() if assessment.created_at else None
|
||
})
|
||
|
||
logger.info(f"获取评估历史: user_id={user_id}, count={len(history)}")
|
||
return history
|
||
|
||
async def get_assessment_detail(
|
||
self,
|
||
assessment_id: int,
|
||
db: AsyncSession
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
获取单个评估记录的详细信息
|
||
|
||
Args:
|
||
assessment_id: 评估记录ID
|
||
db: 数据库会话
|
||
|
||
Returns:
|
||
评估详细信息
|
||
|
||
Raises:
|
||
ValueError: 评估记录不存在
|
||
"""
|
||
stmt = select(AbilityAssessment).where(AbilityAssessment.id == assessment_id)
|
||
result = await db.execute(stmt)
|
||
assessment = result.scalar_one_or_none()
|
||
|
||
if not assessment:
|
||
raise ValueError(f"评估记录不存在: assessment_id={assessment_id}")
|
||
|
||
return {
|
||
"id": assessment.id,
|
||
"user_id": assessment.user_id,
|
||
"source_type": assessment.source_type,
|
||
"source_id": assessment.source_id,
|
||
"total_score": assessment.total_score,
|
||
"ability_dimensions": assessment.ability_dimensions,
|
||
"recommended_courses": assessment.recommended_courses,
|
||
"conversation_count": assessment.conversation_count,
|
||
"analyzed_at": assessment.analyzed_at.isoformat() if assessment.analyzed_at else None,
|
||
"created_at": assessment.created_at.isoformat() if assessment.created_at else None
|
||
}
|
||
|
||
|
||
def get_ability_assessment_service() -> AbilityAssessmentService:
|
||
"""获取能力评估服务实例(依赖注入)"""
|
||
return AbilityAssessmentService()
|