Files
NextEdu/src/modules/ai/services/usage-tracker.ts
SpecialX e9a5264fe7 feat(parent,auth,onboarding,files,notifications,adaptive-practice,ai): add module updates
parent:

- Add parent-student-attendance-detail component

auth:

- Add actions, data-access, schema, services, types

onboarding:

- Add parent-children-form and hooks directory

files:

- Add actions, schema, hooks directory

notifications:

- Add schema and schema test

adaptive-practice:

- Add answer-input, answer-result, practice-result-view, practice-starter-with-nav

- Add question-card, question-content, lib and services directories

ai:

- Add context/create-ai-client-service, hooks/use-drag-position, hooks/use-position-persistence
2026-07-03 10:26:12 +08:00

101 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<AiUsageEvent["capability"], EventName> = {
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 <T>(
userId: string,
capability: AiUsageEvent["capability"],
providerId: string | undefined,
fn: () => Promise<{ result: T; model?: string; tokenUsage?: number }>
): Promise<T> => {
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
}
}