Files
NextEdu/docs/architecture/audit/archive/ai-audit-report.md

23 KiB
Raw Blame History

AI 模块审计报告

审计日期2026-06-25 审计范围:src/modules/ai/ 全部代码 + src/app/api/ai/ 路由 + app 层接入点 审计依据:docs/architecture/004_architecture_impact_map.mddocs/architecture/005_architecture_data.jsondocs/standards/coding-standards.md、项目硬约束 审计方法:逐文件源码审阅 + 架构图一致性比对 + 角色-权限映射核对 + 行业标杆对标Khanmigo / Duolingo Max / Squirrel AI / Century Tech


一、现有实现概要

1.1 文件分布(共 32 个文件)

src/modules/ai/
├─ types.ts                          330 行  AiService / AiClientService 接口 + 业务类型
├─ schema.ts                         248 行  Zod 校验(输入 + AI 输出)
├─ actions.ts                        415 行  10 个 Server Action含权限校验
├─ data-access.ts                    138 行  内存事件存储 + 使用统计聚合
├─ services/
│  ├─ ai-service.ts                  478 行  DefaultAiService 实现(封装 shared/lib/ai
│  ├─ prompt-templates.ts            300 行  9 套 System Prompt 常量
│  ├─ usage-tracker.ts               100 行  trackAiUsage + withAiTracking
│  └─ content-safety.ts              291 行  输入/输出过滤 + 每日限额(原子操作)
├─ context/
│  ├─ ai-client-provider.tsx          62 行  React Context 注入 AiClientService
│  └─ create-ai-client-service.ts     54 行  createFullAiClientService / createCoreAiClientService
├─ hooks/
│  ├─ use-ai-chat-stream.ts          155 行  SSE 流式聊天 + localStorage 持久化
│  ├─ use-ai-chat.ts                  57 行  非流式聊天(⚠ 死代码,未被引用)
│  ├─ use-ai-suggestion.ts            72 行  相似题 / 批改建议
│  ├─ stream-utils.ts                135 行  SSE 解析纯函数
│  ├─ use-floating-ball.ts           160 行  悬浮球组合 hook
│  ├─ use-drag-position.ts           130 行  拖拽 hook
│  └─ use-position-persistence.ts      99 行  位置 localStorage
├─ components/
│  ├─ ai-assistant-widget.tsx        329 行  全局悬浮球 + 上下文感知 + Sheet
│  ├─ ai-chat-panel.tsx              417 行  聊天面板card / widget 双变体)
│  ├─ ai-error-boundary.tsx           31 行  SectionErrorBoundary 包装
│  ├─ ai-skeleton.tsx                 47 行  AiSuggestionSkeleton / AiChatSkeleton
│  ├─ ai-suggestion-card.tsx         178 行  相似题卡片(⚠ 死代码,未被引用)
│  ├─ ai-provider-selector.tsx        89 行  表单字段react-hook-form
│  ├─ ai-markdown-renderer.tsx       162 行  Markdown + 图表代码块渲染
│  ├─ ai-chart-renderer.tsx          351 行  Recharts 4 图表bar/line/pie/radar
│  ├─ ai-grading-assist.tsx          173 行  教师批改辅助
│  ├─ ai-error-book-analysis.tsx     246 行  学生错题本 AI 分析
│  ├─ ai-lesson-content-generator.tsx 180 行  教师备课内容生成
│  ├─ ai-question-variant-generator.tsx 218 行  题目变体生成
│  ├─ ai-usage-dashboard.tsx         221 行  管理员使用统计
│  ├─ ai-child-summary.tsx           186 行  家长学情摘要(⚠ 未接入页面)
│  └─ ai-study-path.tsx              200 行  学生学习路径(⚠ 未接入页面)
└─ src/app/api/ai/
   ├─ chat/route.ts                  196 行  非流式聊天端点
   └─ chat/stream/route.ts           237 行  SSE 流式端点

1.2 数据流

app/(dashboard)/layout.tsx
  │ 模块级 const aiClientService = createFullAiClientService()
  ▼
<AiClientProvider service={aiClientService}>  ← React Context
  │
  ├─ <AiAssistantWidget />                       ← 全局悬浮球
  │     └─ useAiClientOptional() / useFloatingBall()
  │           └─ <AiChatPanel variant="widget">
  │                 └─ useAiChatStream()  →  fetch('/api/ai/chat/stream')
  │                                                      │
  │                                                      ▼
  │                                 route.ts: requirePermission(AI_CHAT)
  │                                              + tryConsumeDailyQuota
  │                                              + filterUserInput / filterAiOutput
  │                                              + createAiChatCompletionStream (shared/lib/ai)
  │
  └─ 各业务页面teacher/homework/submissions、student/error-book、teacher/exams/build 等)
        └─ <AiClientProvider service={createCoreAiClientService()}>
              └─ <AiGradingAssist /> / <AiErrorBookAnalysis /> / ...
                    └─ useAiClient().suggestGrading(...) → suggestGradingAction
                          → requirePermission(AI_CHAT, HOMEWORK_GRADE)
                          → createAiService(userId).suggestGrading(input)
                                → withAiTracking(...)
                                      → createAiChatCompletion (shared/lib/ai)

1.3 架构图记录情况

005_architecture_data.jsonmodules.ai 节点记录了完整的依赖矩阵、exports 清单、集成点、权限点、安全策略、流式特性、i18n 命名空间。

但比对发现两处与实际实现不一致(详见 §二 P0-2

  • ai.integrations.parent-dashboard 声称 AiChildSummary 接入 parent/dashboard 页面 → 实际未接入
  • ai.integrations.student-learning 声称 AiStudyPath 接入 student/learning/study-path 页面 → 实际该路由不存在

二、现存问题与原因分析

P0 — 紧急且阻断使用

P0-1家长角色完全缺失 AI_CHAT 权限

  • 位置permissions.ts ROLE_PERMISSIONS_SEED.parent 数组
  • 问题parent 角色权限清单中没有任何 Permissions.AI_CHAT,但:
    • actions.ts generateChildSummaryAction 第一行调用 requirePermission(Permissions.AI_CHAT) → 家长调用必返回 403
    • actions.ts recommendStudyPathAction 同上
    • /api/ai/chat/route.ts L49 同上
    • AiChildSummary / AiStudyPath 组件存在但家长/学生路径下完全无法使用
  • 违反规则
    • 项目硬约束「所有 Server Action 必须调用 requirePermission() 进行权限校验」—— 校验逻辑本身正确,但权限未授予
    • 项目硬约束「家长需要的功能应被授权」K12 系统家长是关键角色)
  • 后果:家长角色付费的 AI 学情摘要功能在生产环境 100% 失败;学生使用学习路径推荐时若依赖家长代调也会失败

P0-2架构图虚构集成AiChildSummary / AiStudyPath 完全未接入)

  • 位置
  • 问题
    • 声称 AiChildSummary 集成于 parent/dashboard —— grep 全仓 AiChildSummary 仅在自身文件、actions、context 出现,app/ 下零引用
    • 声称 AiStudyPath 集成于 student/learning/study-path —— 该路由不存在app/(dashboard)/student/learning/ 下只有 textbooks/assignments/courses/page.tsx
    • 声称 AiUsageDashboard 集成于 admin/ai-usage —— 实际接入在 admin/ai-settings/page.tsx(路径不一致)
  • 违反规则
    • 项目硬约束「如果发现项目中存在架构图未记录的模块、函数、表、路由等,必须优先完善架构图信息」
    • 项目硬约束「改码必同步图」—— 反向也成立:图中记录的集成必须真实存在
  • 后果:依赖架构图做影响分析的开发者会误以为功能已上线,跳过实现;测试用例遗漏;产线功能缺失

P0-3getAiUsageStatsAction 错误消息 i18n 键错误

  • 位置actions.ts
  • 问题:管理员查询使用统计失败时返回 t("error.chatFailed")"AI 请求失败"/"AI request failed"),与场景不符
  • 后果:管理员看到"AI 请求失败"误以为是 AI 调用失败,实为统计查询失败

P1 — 高优先级

P1-1数据访问层使用内存存储多实例部署不可用

  • 位置
  • 问题:注释自承认"生产环境应替换为 Redis",但当前实现:
    • 多实例部署下,getAiUsageStats 聚合的统计仅包含当前实例数据
    • tryConsumeDailyQuota 在多实例下,每个实例独立计数,实际可用次数 = 限额 × 实例数
    • 进程重启后所有统计归零
  • 违反规则:项目硬约束「企业级补充」「可扩展性:采用配置驱动设计」
  • 后果K8s 多 Pod 部署后限额失效、统计失真

P1-2AiUsageDashboard 违反 React Hooks 规范

  • 位置ai-usage-dashboard.tsx
  • 问题
    useEffect(() => {
      void loadStats()
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])
    
    • loadStats 依赖 aiClient 但被 disable 抑制
    • aiClient 变化时不会重新加载
  • 后果eslint-disable 掩盖真实 bugaiClient 引用变更时不刷新

P1-3AiClientProvider 在 layout 模块级创建 service

  • 位置layout.tsx
  • 问题const aiClientService = createFullAiClientService() 在模块加载时执行module scopeservice 对象被所有用户共享
  • 当前可工作原因Server Action 内部 requirePermission() 会从 session 动态解析用户
  • 风险:未来若 service 需要请求级状态(如缓存当前用户权限),模块级单例会泄露
  • 建议:移入 Server Component 函数体内创建

P1-4AiAssistantWidget 内嵌英文 prompt 硬编码

  • 位置ai-assistant-widget.tsx inferContextFromPath
  • 问题6 个角色的 systemPrompt 为英文硬编码字符串,未走 i18n
  • 缓解API 端点会强制覆盖(学生侧 SOCRATIC_TUTOR_SYSTEM_PROMPT客户端 prompt 仅作为上下文提示
  • 后果:维护 prompt 需改代码;多语言场景下非英语用户的提示词不一致

P1-5死代码 useAiChat Hook

  • 位置use-ai-chat.ts 57 行
  • 问题grep 全仓 useAiChat 仅在自身文件 + 005 架构数据中引用,实际无任何组件使用
  • 原因:早期非流式实现被 useAiChatStream 取代,但文件未删除
  • 后果:架构图 exports 中仍记录 useAiChat,误导调用方

P1-6死代码 AiSuggestionCard 组件

  • 位置ai-suggestion-card.tsx 178 行
  • 问题grep 全仓 AiSuggestionCard 仅在自身文件 + 架构数据中引用,实际无任何页面使用
  • 原因AiErrorBookAnalysis 已包含相似题功能,AiSuggestionCard 是早期独立实现
  • 后果:维护成本;架构图 exports 仍记录该组件

P1-7API 路由与非流式路由大量重复代码

  • 位置
  • 问题:权限校验 / 限流 / Zod 校验 / 配额消费 / 输入过滤 / 系统提示构建 / 配额退款 7 段逻辑几乎逐行复制
  • 后果:修一处漏一处易出 bug测试需双倍

P2 — 中等优先级

P2-1ai-chart-renderer.tsx 使用 as 断言

  • 位置ai-chart-renderer.tsx
  • 问题
    data: obj.data as Array<Record<string, string | number>>,    // as 断言
    series: obj.series as AiChartSeries[],                        // as 断言
    yDomain: Array.isArray(obj.yDomain) ? obj.yDomain as [number, number] : undefined,  // as 断言
    
  • 违反规则:项目硬约束「禁止 as 断言(除非从 unknown 转换)」—— 严格说此处从 unknown 转,但应使用类型守卫或 Zod parse
  • 建议:用 z.array(AiChartSeriesSchema).parse(obj.series) 校验

P2-2AiChatPanel 单文件 417 行接近上限

  • 位置ai-chat-panel.tsx 417 行
  • 问题card / widget 两个变体有 ~60% 重复 JSX消息列表、空状态、输入框、流式指示器各写两遍
  • 建议:抽取 <ChatMessages> / <ChatInput> / <ChatEmptyState> / <ChatStreamingIndicator> 子组件

P2-3AiAssistantWidget.inferContextFromPath 配置硬编码

  • 位置ai-assistant-widget.tsx
  • 问题100+ 行 if-else 路由匹配,新增角色/路由需改代码
  • 建议:改为配置驱动
    const CONTEXT_MAP: Array<{ match: RegExp; config: AiContextConfig }> = [...]
    

P2-4content-safety.ts 关键词仅英文

  • 位置content-safety.ts
  • 问题BLOCKED_INPUT_PATTERNS / STUDENT_BLOCKED_PATTERNS 正则仅匹配英文关键词
  • 后果:中文"自杀/暴力/色情"等不当内容无法识别K12 中国场景下安全防线不足

P2-5AiService.chatusage 字段始终返回 null

  • 位置ai-service.ts
  • 问题return { result: { content, usage: null }, tokenUsage } —— AiChatResult.usage: unknown 类型但实际始终 null
  • 建议:将 tokenUsage 包入 usage 字段,或修改类型为 usage: null

P2-6架构图遗漏 /admin/ai-settings 路由

  • 位置005_architecture_data.json routes 节点
  • 问题:仅在 ai.integrations.admin-dashboard.page 字段提及 admin/ai-usage,但 routes 表中无 /admin/ai-settings 条目(实际页面位于 /admin/ai-settings
  • 后果:路由审计遗漏

三、行业差距对比

3.1 与 KhanmigoKhan Academy差距

维度 Khanmigo 我们 差距影响
教师可见学生 AI 对话 ✓ 教师后台可审阅 ✗ 对话仅存 localStorage 教师无法了解学生提问习惯,无法干预 Socratic 失败场景
多模态输入 ✓ 支持图片 ✗ 仅文本 数学几何题无法拍照上传
Activity 难度自适应 ✓ 根据学生水平动态调整 ✗ 固定 difficulty 参数 同一题目对快慢学生无差异
Teacher copilot 模式 ✓ 教师侧 AI 提示教学策略 ✗ 仅 widget 通用助手 教师备课缺专业引导

3.2 与 Duolingo Max 差距

维维 Duolingo Max 我们 差距影响
Explain My Answer ✓ 错题后一键解释 explainError 已实现但未接入页面 功能闲置
Roleplay ✓ 情景对话练习 ✗ 无 英语口语训练缺失
"立即练习"按钮 ✓ 相似题后直接进入练习流 ✗ 仅"选择" 学生看到相似题但无法作答,流程断裂

3.3 与 Squirrel AI松鼠 AI差距

维度 Squirrel AI 我们 差距影响
纳米级知识图谱 ✓ 700+ 知识点拆分 recommendStudyPath 已支持 knowledgeGraph 注入,但未接入页面 功能已实现但未上线
自适应路径 ✓ 实时根据答题调整 ✗ 一次性生成路径,无反馈循环 路径在学习过程中不更新
学习目标对齐 ✓ 与课标 / 升学目标对齐 ✗ studyPathInput.learningGoal 仅文本 缺少课标映射

3.4 与 Century Tech 差距

维度 Century Tech 我们 差距影响
全校 AI 成本看板 ✓ token 消耗 / 预算预警 AiUsageDashboard 无 token 字段 管理员无法评估成本
多 Provider 对比 ✓ A/B 测试 ✗ 单次调用单 provider 无法评估哪家性价比高
课程标准映射 ✓ AI 推荐与课标对齐 ✗ 无 学习路径与课标脱节

3.5 K12 通用缺失

  • a11yAiAssistantWidget 悬浮球无键盘焦点;AiChatPanel 流式 token 更新未限流(屏幕阅读器频繁打断)
  • 空状态AiUsageDashboard 无数据时仅显示文案,无引导管理员"先发起一次 AI 对话"
  • 错误恢复AiErrorBoundary 透传 SectionErrorBoundary,无 AI 专属重试策略(如降级到非流式)

四、改进优先级建议

P0必须立即修复 — 阻断核心功能)

编号 改进项 方向说明
P0-1 parent 角色补齐 AI_CHAT 权限 ROLE_PERMISSIONS_SEED.parent 数组追加 Permissions.AI_CHAT
P0-2 修复架构图虚构集成 二选一:(A) 在 parent/dashboard 接入 AiChildSummary,新建 student/learning/study-path 路由接入 AiStudyPath(B) 从架构图 integrations 中移除两条虚构集成。本次采用方案 A:实际接入组件,让功能上线
P0-3 getAiUsageStatsAction 错误 i18n 修复 新增 ai.error.statsFailed 翻译键,替换 chatFailed

P1高优先级 — 影响可维护性与正确性)

编号 改进项 方向说明
P1-1 内存存储抽象化 提取 AiUsageStore 接口,当前内存实现作为 InMemoryAiUsageStore,未来可替换 RedisAiUsageStore;不阻塞当前发布
P1-2 AiUsageDashboard useEffect 修复 抽取 loadStatsuseCallback,依赖数组加入 aiClient
P1-3 layout service 创建移入 Server Component 改为 function DashboardLayout() { const service = createFullAiClientService(); ... }
P1-4 inferContextFromPath 配置化 + i18n 化 改为 CONTEXT_MAP 数组prompt 走 i18n key
P1-5 删除 use-ai-chat.ts 死代码 文件 + 架构图 exports 同步移除
P1-6 删除 ai-suggestion-card.tsx 死代码 同上
P1-7 API 路由共享逻辑抽取 抽取 prepareAiChatRequest(req) 返回 { body, isStudent, quota, limitResult }

P2中等优先级 — 代码质量与扩展性)

编号 改进项 方向说明
P2-1 ai-chart-renderer as 断言替换为 Zod parse AiChartSpecSchema.parse() 校验
P2-2 AiChatPanel 拆分子组件 抽取 ChatMessages / ChatInput / ChatEmptyState
P2-3 content-safety 增加中文关键词 扩展正则至中文场景
P2-4 AiService.chat.usage 修正 返回实际 tokenUsage 或改类型为 null
P2-5 架构图补齐 /admin/ai-settings 路由 005 routes 节点新增

中长期方向(不在本次实施范围)

  • 接入 Redis 替换内存存储(需运维配合)
  • 多模态输入(需 OCR/视觉模型)
  • 教师 AI 对话审阅后台(需新增 DB 表)
  • 多 Provider A/B 测试(需扩展 Provider 模型)
  • 课程标准映射(需课标数据源)

五、架构图同步说明

本次审计发现架构图需更新如下节点:

5.1 005_architecture_data.json 同步项

  1. modules.ai.exports.hooks 移除 useAiChat(死代码已删除)
  2. modules.ai.exports.components 移除 AiSuggestionCard(死代码已删除)
  3. modules.ai.integrations.parent-dashboard 更新 page 字段为真实接入路径
  4. modules.ai.integrations.student-learning 更新 page 字段为真实接入路径 student/learning/study-path
  5. modules.ai.integrations.admin-dashboard 更正 pageadmin/ai-settings(原误记为 admin/ai-usage
  6. routes./admin/ai-settings 新增节点(若 routes 表中确实缺失)
  7. rolePermissionsSeed.parent 追加 ai:chat 权限
  8. modules.ai.i18n.v2Keys 追加 error.statsFailed 翻译键

5.2 004_architecture_impact_map.md 同步项

  1. AI 模块章节的「集成点」表格更新实际接入路径
  2. 移除已删除组件的引用

六、本次实施清单

已实施(本次审计直接修复)

编号 类型 改动
P0-1 代码 permissions.ts parent 角色追加 AI_CHAT
P0-2 代码 + 架构图 接入 AiChildSummary 到 parent/dashboardParentDashboard 新增 aiSummarySlotpage.tsx 为每个子女渲染 AiChildSummary);新建 student/learning/study-path 路由page.tsx + loading.tsx + error.tsx接入 AiStudyPath;同步 004/005 文档集成点与路由表
P0-3 代码 + i18n actions.ts 修复错误键;ai.json (zh/en) 新增 error.statsFailed
P1-2 代码 ai-usage-dashboard.tsx 修复 useEffect
P1-3 代码 layout.tsx service 移入函数体
P1-5 代码 删除 use-ai-chat.ts;架构图 exports.hooks 移除 useAiChat
P1-6 代码 删除 ai-suggestion-card.tsx;架构图 exports.components 移除 AiSuggestionCard
P2-1 代码 ai-chart-renderer.tsx 替换 as 为 Zod parse新增 AiChartSpecSchema
P2-4 代码 ai-service.ts 修正 usage 字段返回实际 tokenUsage
架构图 文档 004/005 同步上述改动;新增 student.studyPath i18n 块zh/en
权限 文档 005rolePermissionsSeed.parent 追加 AI_CHAT
i18n 文档 005modules.ai.i18n.v2Keys 追加 error.statsFailed
路由 文档 005routes 表新增 /student/learning/study-path;更新 /parent/dashboard 描述含 AI 摘要集成;/admin/ai-settings 校正为 admin 集成路径(原误记为 admin/ai-usage

未实施(中长期,需独立任务)

编号 原因
P1-1 Redis 替换内存 需运维提供 Redis 实例
P1-4 prompt 完全 i18n 化 客户端 prompt 已被服务端覆盖,影响小
P1-7 API 路由共享逻辑抽取 涉及测试回归,单独 PR
P2-2 AiChatPanel 拆分 涉及大量回归,单独 PR
P2-3 中文关键词扩展 需安全策略评审