AI 模块审计报告
审计日期:2026-06-25
审计范围:src/modules/ai/ 全部代码 + src/app/api/ai/ 路由 + app 层接入点
审计依据:docs/architecture/004_architecture_impact_map.md、docs/architecture/005_architecture_data.json、docs/standards/coding-standards.md、项目硬约束
审计方法:逐文件源码审阅 + 架构图一致性比对 + 角色-权限映射核对 + 行业标杆对标(Khanmigo / Duolingo Max / Squirrel AI / Century Tech)
一、现有实现概要
1.1 文件分布(共 32 个文件)
1.2 数据流
1.3 架构图记录情况
005_architecture_data.json 中 modules.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-3:getAiUsageStatsAction 错误消息 i18n 键错误
- 位置:actions.ts
- 问题:管理员查询使用统计失败时返回
t("error.chatFailed")("AI 请求失败"/"AI request failed"),与场景不符
- 后果:管理员看到"AI 请求失败"误以为是 AI 调用失败,实为统计查询失败
P1 — 高优先级
P1-1:数据访问层使用内存存储,多实例部署不可用
- 位置:
- 问题:注释自承认"生产环境应替换为 Redis",但当前实现:
- 多实例部署下,
getAiUsageStats 聚合的统计仅包含当前实例数据
tryConsumeDailyQuota 在多实例下,每个实例独立计数,实际可用次数 = 限额 × 实例数
- 进程重启后所有统计归零
- 违反规则:项目硬约束「企业级补充」「可扩展性:采用配置驱动设计」
- 后果:K8s 多 Pod 部署后限额失效、统计失真
P1-2:AiUsageDashboard 违反 React Hooks 规范
- 位置:ai-usage-dashboard.tsx
- 问题:
loadStats 依赖 aiClient 但被 disable 抑制
aiClient 变化时不会重新加载
- 后果:eslint-disable 掩盖真实 bug;aiClient 引用变更时不刷新
P1-3:AiClientProvider 在 layout 模块级创建 service
- 位置:layout.tsx
- 问题:
const aiClientService = createFullAiClientService() 在模块加载时执行(module scope),service 对象被所有用户共享
- 当前可工作原因:Server Action 内部
requirePermission() 会从 session 动态解析用户
- 风险:未来若 service 需要请求级状态(如缓存当前用户权限),模块级单例会泄露
- 建议:移入 Server Component 函数体内创建
P1-4:AiAssistantWidget 内嵌英文 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-7:API 路由与非流式路由大量重复代码
- 位置:
- 问题:权限校验 / 限流 / Zod 校验 / 配额消费 / 输入过滤 / 系统提示构建 / 配额退款 7 段逻辑几乎逐行复制
- 后果:修一处漏一处易出 bug;测试需双倍
P2 — 中等优先级
P2-1:ai-chart-renderer.tsx 使用 as 断言
- 位置:ai-chart-renderer.tsx
- 问题:
- 违反规则:项目硬约束「禁止
as 断言(除非从 unknown 转换)」—— 严格说此处从 unknown 转,但应使用类型守卫或 Zod parse
- 建议:用
z.array(AiChartSeriesSchema).parse(obj.series) 校验
P2-2:AiChatPanel 单文件 417 行接近上限
- 位置:ai-chat-panel.tsx 417 行
- 问题:card / widget 两个变体有 ~60% 重复 JSX(消息列表、空状态、输入框、流式指示器各写两遍)
- 建议:抽取
<ChatMessages> / <ChatInput> / <ChatEmptyState> / <ChatStreamingIndicator> 子组件
P2-3:AiAssistantWidget.inferContextFromPath 配置硬编码
P2-4:content-safety.ts 关键词仅英文
- 位置:content-safety.ts
- 问题:
BLOCKED_INPUT_PATTERNS / STUDENT_BLOCKED_PATTERNS 正则仅匹配英文关键词
- 后果:中文"自杀/暴力/色情"等不当内容无法识别,K12 中国场景下安全防线不足
P2-5:AiService.chat 的 usage 字段始终返回 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 与 Khanmigo(Khan 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 通用缺失
- a11y:
AiAssistantWidget 悬浮球无键盘焦点;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 修复 |
抽取 loadStats 为 useCallback,依赖数组加入 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 同步项
modules.ai.exports.hooks 移除 useAiChat(死代码已删除)
modules.ai.exports.components 移除 AiSuggestionCard(死代码已删除)
modules.ai.integrations.parent-dashboard 更新 page 字段为真实接入路径
modules.ai.integrations.student-learning 更新 page 字段为真实接入路径 student/learning/study-path
modules.ai.integrations.admin-dashboard 更正 page 为 admin/ai-settings(原误记为 admin/ai-usage)
routes./admin/ai-settings 新增节点(若 routes 表中确实缺失)
rolePermissionsSeed.parent 追加 ai:chat 权限
modules.ai.i18n.v2Keys 追加 error.statsFailed 翻译键
5.2 004_architecture_impact_map.md 同步项
- AI 模块章节的「集成点」表格更新实际接入路径
- 移除已删除组件的引用
六、本次实施清单
已实施(本次审计直接修复)
| 编号 |
类型 |
改动 |
| P0-1 |
代码 |
permissions.ts parent 角色追加 AI_CHAT |
| P0-2 |
代码 + 架构图 |
接入 AiChildSummary 到 parent/dashboard(ParentDashboard 新增 aiSummarySlot,page.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) |
| 权限 |
文档 |
005 的 rolePermissionsSeed.parent 追加 AI_CHAT |
| i18n |
文档 |
005 的 modules.ai.i18n.v2Keys 追加 error.statsFailed |
| 路由 |
文档 |
005 的 routes 表新增 /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 中文关键词扩展 |
需安全策略评审 |