import "server-only" import { trackEvent, type EventName } from "@/shared/lib/track-event" import { recordAiEvent } from "../data-access" export type AiUsageEvent = { userId: string capability: "chat" | "similar_question" | "grading_assist" | "lesson_content" | "question_variant" | "weakness_analysis" | "child_summary" | "study_path" | "explain_error" providerId?: string model?: string success: boolean durationMs: number tokenUsage?: number errorMessage?: string } const AI_EVENT_MAP: Record = { chat: "ai.chat", similar_question: "ai.similar_question", grading_assist: "ai.grading_assist", lesson_content: "ai.lesson_content", question_variant: "ai.question_variant", weakness_analysis: "ai.weakness_analysis", child_summary: "ai.child_summary", study_path: "ai.study_path", explain_error: "ai.explain_error", } /** * AI 使用埋点 * * 记录每次 AI 调用的元数据,用于监控、成本分析与异常排查。 * 同时写入 data-access 层的内存事件存储(供管理员仪表盘聚合查询)。 * 非阻塞,失败不影响主流程。 */ export const trackAiUsage = (event: AiUsageEvent): void => { const eventName = AI_EVENT_MAP[event.capability] // 写入 data-access 层(供 getAiUsageStats 聚合) recordAiEvent({ userId: event.userId, capability: event.capability, success: event.success, durationMs: event.durationMs, timestamp: Date.now(), errorMessage: event.errorMessage, }) // 写入全局 trackEvent(供外部监控系统) void trackEvent({ event: eventName, userId: event.userId, targetType: event.capability, properties: { providerId: event.providerId, model: event.model, success: event.success, durationMs: event.durationMs, tokenUsage: event.tokenUsage, errorMessage: event.errorMessage, }, }).catch(() => { // 静默失败:埋点不应影响业务流程 }) } /** * 测量 AI 调用耗时并自动埋点 */ export const withAiTracking = async ( userId: string, capability: AiUsageEvent["capability"], providerId: string | undefined, fn: () => Promise<{ result: T; model?: string; tokenUsage?: number }> ): Promise => { const start = Date.now() try { const { result, model, tokenUsage } = await fn() trackAiUsage({ userId, capability, providerId, model, success: true, durationMs: Date.now() - start, tokenUsage, }) return result } catch (error) { trackAiUsage({ userId, capability, providerId, success: false, durationMs: Date.now() - start, errorMessage: error instanceof Error ? error.message : String(error), }) throw error } }