261 lines
8.9 KiB
Python
261 lines
8.9 KiB
Python
from __future__ import annotations
|
||
|
||
from .shared import *
|
||
|
||
class ModelCapabilityCostTrackingMixin:
|
||
async def track_cost_and_roi(
|
||
self,
|
||
truth_node_id: int,
|
||
period: str = "weekly",
|
||
tenant_id: int = 1,
|
||
) -> Dict[str, Any]:
|
||
"""算力成本跟踪与 ROI 核算
|
||
|
||
Args:
|
||
truth_node_id: 宪章(需求)ID
|
||
period: 周期类型(daily, weekly, monthly, project)
|
||
tenant_id: 租户 ID
|
||
|
||
Returns:
|
||
包含成本明细、ROI 分析等信息的字典
|
||
"""
|
||
# 获取相关任务和模型绑定
|
||
tasks = await self._get_truth_node_tasks(truth_node_id, tenant_id)
|
||
bindings = await self._get_truth_node_bindings(truth_node_id, tenant_id)
|
||
|
||
if not tasks:
|
||
return {
|
||
"total_tokens": 0,
|
||
"total_cost_usd": 0.0,
|
||
"task_breakdown": [],
|
||
"roi_analysis": {},
|
||
"cost_variance": 0.0,
|
||
}
|
||
|
||
# 汇总 token 使用量和成本
|
||
total_tokens = 0
|
||
total_cost = 0.0
|
||
task_costs = []
|
||
|
||
for task in tasks:
|
||
# 从 AI 调用日志统计 token
|
||
task_tokens = await self._get_task_token_usage(task.id)
|
||
task_cost = await self._calculate_task_cost(task, bindings)
|
||
|
||
total_tokens += task_tokens
|
||
total_cost += task_cost
|
||
task_costs.append({
|
||
"task_id": task.id,
|
||
"task_name": task.title,
|
||
"tokens": task_tokens,
|
||
"cost_usd": round(task_cost, 4),
|
||
})
|
||
|
||
# 获取或创建成本跟踪记录
|
||
cost_tracking = await self._get_or_create_cost_tracking(
|
||
truth_node_id=truth_node_id,
|
||
period=period,
|
||
tenant_id=tenant_id,
|
||
bindings=bindings,
|
||
)
|
||
|
||
# 更新实际数据
|
||
cost_tracking.actual_tokens = total_tokens
|
||
cost_tracking.actual_cost_usd = round(total_cost, 4)
|
||
|
||
# AI 分析 ROI
|
||
truth_node = await self._get_truth_node(truth_node_id, tenant_id)
|
||
roi_analysis = await self._analyze_cost_roi(
|
||
total_cost=total_cost,
|
||
total_tokens=total_tokens,
|
||
task_count=len(tasks),
|
||
truth_node_content=str(truth_node.content or truth_node.title or "") if truth_node else "",
|
||
tenant_id=tenant_id,
|
||
)
|
||
|
||
cost_tracking.roi_score = roi_analysis.get("roi_score")
|
||
cost_tracking.roi_analysis = roi_analysis
|
||
|
||
# 生成优化建议
|
||
optimization = await self._generate_cost_optimization(
|
||
truth_node_id=truth_node_id,
|
||
tenant_id=tenant_id,
|
||
total_cost=total_cost,
|
||
)
|
||
cost_tracking.optimization_suggestions = optimization
|
||
|
||
await self.db.commit()
|
||
await self.db.refresh(cost_tracking)
|
||
|
||
# 计算成本偏差
|
||
cost_variance = total_cost - (cost_tracking.estimated_cost_usd or 0)
|
||
|
||
return {
|
||
"total_tokens": total_tokens,
|
||
"total_cost_usd": round(total_cost, 4),
|
||
"task_breakdown": task_costs,
|
||
"roi_analysis": roi_analysis,
|
||
"cost_variance": round(cost_variance, 4),
|
||
"optimization_suggestions": optimization,
|
||
}
|
||
|
||
async def _get_truth_node_tasks(
|
||
self, truth_node_id: int, tenant_id: int
|
||
) -> List[Task]:
|
||
"""获取宪章下的所有任务"""
|
||
result = await self.db.execute(
|
||
select(Task).where(
|
||
and_(
|
||
Task.truth_node_id == truth_node_id,
|
||
Task.tenant_id == tenant_id,
|
||
)
|
||
)
|
||
)
|
||
return list(result.scalars().all())
|
||
|
||
async def _get_truth_node_bindings(
|
||
self, truth_node_id: int, tenant_id: int
|
||
) -> List[ModelRequirementBinding]:
|
||
"""获取宪章的模型绑定"""
|
||
result = await self.db.execute(
|
||
select(ModelRequirementBinding).where(
|
||
and_(
|
||
ModelRequirementBinding.truth_node_id == truth_node_id,
|
||
ModelRequirementBinding.tenant_id == tenant_id,
|
||
)
|
||
)
|
||
)
|
||
return list(result.scalars().all())
|
||
|
||
async def _get_task_token_usage(self, task_id: int) -> int:
|
||
"""从 AI 调用日志统计任务的 token 使用量"""
|
||
# 注意:需要根据实际日志记录方式调整
|
||
# 这里假设日志中有 task_id 或相关标识
|
||
result = await self.db.execute(
|
||
select(func.sum(AICallLog.total_tokens)).where(
|
||
AICallLog.function_name.contains(f"task_{task_id}")
|
||
)
|
||
)
|
||
total = result.scalar() or 0
|
||
return int(total)
|
||
|
||
async def _calculate_task_cost(
|
||
self, task: Task, bindings: List[ModelRequirementBinding]
|
||
) -> float:
|
||
"""计算任务成本"""
|
||
# 如果有绑定记录,使用绑定的预估成本
|
||
if bindings:
|
||
return sum(float(b.estimated_cost or 0) for b in bindings) / max(len(bindings), 1)
|
||
return 0.0
|
||
|
||
async def _get_or_create_cost_tracking(
|
||
self,
|
||
truth_node_id: int,
|
||
period: str,
|
||
tenant_id: int,
|
||
bindings: List[ModelRequirementBinding],
|
||
) -> CostTracking:
|
||
"""获取或创建成本跟踪记录"""
|
||
# 查找现有记录
|
||
result = await self.db.execute(
|
||
select(CostTracking).where(
|
||
and_(
|
||
CostTracking.truth_node_id == truth_node_id,
|
||
CostTracking.period == period,
|
||
CostTracking.tenant_id == tenant_id,
|
||
)
|
||
).order_by(desc(CostTracking.created_at))
|
||
)
|
||
tracking = result.scalar_one_or_none()
|
||
|
||
if not tracking:
|
||
# 创建新记录
|
||
tracking = CostTracking(
|
||
tenant_id=tenant_id,
|
||
truth_node_id=truth_node_id,
|
||
model_version_id=bindings[0].model_version_id if bindings else None,
|
||
period=period,
|
||
estimated_tokens=0,
|
||
actual_tokens=0,
|
||
estimated_cost_usd=sum(float(b.estimated_cost or 0) for b in bindings),
|
||
actual_cost_usd=0.0,
|
||
roi_score=None,
|
||
roi_analysis={},
|
||
optimization_suggestions={},
|
||
)
|
||
self.db.add(tracking)
|
||
await self.db.commit()
|
||
await self.db.refresh(tracking)
|
||
|
||
return tracking
|
||
|
||
async def _analyze_cost_roi(
|
||
self,
|
||
total_cost: float,
|
||
total_tokens: int,
|
||
task_count: int,
|
||
truth_node_content: str,
|
||
tenant_id: int,
|
||
) -> Dict[str, Any]:
|
||
"""AI 分析成本效益"""
|
||
from ..config.ai_prompts import PROMPT_ANALYZE_COST_ROI
|
||
|
||
prompt = PROMPT_ANALYZE_COST_ROI.format(
|
||
total_cost=f"${total_cost:.2f}",
|
||
total_tokens=f"{total_tokens:,}",
|
||
task_count=task_count,
|
||
truth_node_content=truth_node_content[:2000], # 限制长度
|
||
)
|
||
|
||
try:
|
||
text = await self.ai_service._invoke_with_retry(
|
||
prompt,
|
||
db=self.db,
|
||
tenant_id=tenant_id,
|
||
function_name="analyze_cost_roi",
|
||
)
|
||
return self.ai_service._parse_json(text)
|
||
except Exception:
|
||
# 失败时返回默认分析
|
||
return {
|
||
"roi_score": 5.0,
|
||
"cost_efficiency": "fair",
|
||
"cost_per_task": round(total_cost / max(task_count, 1), 2),
|
||
"optimization_suggestions": [],
|
||
"benchmark_comparison": "无法分析",
|
||
}
|
||
|
||
async def _generate_cost_optimization(
|
||
self, truth_node_id: int, tenant_id: int, total_cost: float
|
||
) -> Dict[str, Any]:
|
||
"""生成成本优化建议"""
|
||
# 简单的优化建议逻辑
|
||
suggestions = []
|
||
|
||
# 检查是否可以使用更便宜的模型
|
||
bindings = await self._get_truth_node_bindings(truth_node_id, tenant_id)
|
||
if bindings:
|
||
for binding in bindings:
|
||
model = await self._get_model_version(binding.model_version_id)
|
||
if model and model.cost_per_1k_tokens and model.cost_per_1k_tokens > 0.001:
|
||
suggestions.append({
|
||
"type": "model_downgrade",
|
||
"priority": "medium",
|
||
"description": f"考虑切换到更便宜的模型替代 {model.name}",
|
||
"estimated_savings_percent": 30,
|
||
})
|
||
|
||
# 检查是否可以启用缓存
|
||
suggestions.append({
|
||
"type": "cache_optimization",
|
||
"priority": "high",
|
||
"description": "启用 Redis 缓存可减少 20-40% 重复调用",
|
||
"estimated_savings_percent": 30,
|
||
})
|
||
|
||
return {
|
||
"suggestions": suggestions,
|
||
"total_potential_savings_percent": 30 if suggestions else 0,
|
||
}
|
||
|