Files
Edu/services/ai/docs/nextstep-v2.md
SpecialX aac26c7c6f feat(ai): v2 新增 GenerateReport RPC + ReportService
第 9 个 RPC GenerateReport(学情报告生成):data-ana 学情数据 → LLM 生成 → 结构化提取

新增 ReportService 业务编排层 + GenerateReportRequest/GeneratedReport 模型

gRPC servicer + HTTP POST /v1/ai/generate/report(权限 ai:report:generate)

proto_gen 重新生成 + 测试覆盖(servicer/service/HTTP/模型/权限 共 26 用例)

402 测试通过,覆盖率 88.5%
2026-07-14 22:57:57 +08:00

341 lines
21 KiB
Markdown
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.
# AI 模块 nextstep-v2
> 模块ai | gRPC 50058 | HTTP 3008 | Python (FastAPI + gRPC aio)
> 更新时间2026-07-14
> 前序文档:[nextstep.md](./nextstep.md)v18 RPC / 10 端点)
---
## §1 当前状态
v2 新增第 9 个 RPC `GenerateReport`(学情报告生成),满足 teacher-bff `generateReport` mutation 需求。9 RPC 全部实现402 个测试通过,覆盖率 88.5%。
### v2 新增
- [x] ai.proto 新增 `GenerateReport` RPC + `GenerateReportRequest` / `GeneratedReport` message
- [x] `ReportService` 业务编排层data-ana 学情数据 → LLM 生成报告 → 结构化提取摘要+建议)
- [x] gRPC servicer `GenerateReport` 方法(方案 B 降级)
- [x] HTTP 端点 `POST /v1/ai/generate/report`(权限 `ai:report:generate`
- [x] 权限点 `PERMISSION_AI_REPORT_GENERATE`teacher + admin 角色)
- [x] proto_gen 重新生成ai_pb2.py / ai_pb2_grpc.py
- [x] 测试覆盖servicer 5 用例 + service 9 用例 + HTTP 5 用例 + 模型 5 用例 + 权限 2 用例)
### 完整 9 RPC 清单
| # | RPC | 类型 | 用途 | 状态 |
| --- | ---------------------- | ---- | --------------------------- | --------- |
| 1 | Chat | 一元 | 非流式聊天 | ✅ 已实现 |
| 2 | StreamChat | 流式 | 流式聊天SSE over gRPC | ✅ 已实现 |
| 3 | GenerateQuestion | 一元 | 生成题目 | ✅ 已实现 |
| 4 | StreamGenerateQuestion | 流式 | 流式生成题目 | ✅ 已实现 |
| 5 | OptimizeExpression | 一元 | 优化表达 | ✅ 已实现 |
| 6 | GenerateLessonPlan | 一元 | 启动备课工作流 | ✅ 已实现 |
| 7 | GetLessonPlanStatus | 一元 | 查询备课工作流状态 | ✅ 已实现 |
| 8 | ConfirmLessonPlan | 一元 | 确认备课结果入库 | ✅ 已实现 |
| 9 | **GenerateReport** | 一元 | **生成学情报告v2 新增)** | ✅ 已实现 |
### 完整 11 HTTP 端点清单
| # | 端点 | 方法 | 权限 | 状态 |
| --- | --------------------------------- | ---- | ---------------------- | --------- |
| 1 | `/healthz` | GET | - | ✅ 已实现 |
| 2 | `/readyz` | GET | - | ✅ 已实现 |
| 3 | `/v1/ai/chat` | POST | ai:chat | ✅ 已实现 |
| 4 | `/v1/ai/chat/stream` | POST | ai:chat | ✅ 已实现 |
| 5 | `/v1/ai/generate/question` | POST | ai:question:generate | ✅ 已实现 |
| 6 | `/v1/ai/generate/question/stream` | POST | ai:question:generate | ✅ 已实现 |
| 7 | `/v1/ai/optimize/expression` | POST | ai:expression:optimize | ✅ 已实现 |
| 8 | `/v1/ai/lesson-plan/generate` | POST | ai:lesson:generate | ✅ 已实现 |
| 9 | `/v1/ai/lesson-plan/status/{id}` | GET | - | ✅ 已实现 |
| 10 | `/v1/ai/lesson-plan/confirm/{id}` | POST | ai:lesson:confirm | ✅ 已实现 |
| 11 | **`/v1/ai/generate/report`** | POST | **ai:report:generate** | ✅ 已实现 |
### 权限点清单
| 权限点 | teacher | admin | student | 用途 |
| ---------------------- | ------- | ----- | ------- | ---------------- |
| ai:chat | ✅ | ✅ | ✅ | 聊天 |
| ai:question:generate | ✅ | ✅ | ❌ | 生成题目 |
| ai:expression:optimize | ✅ | ✅ | ❌ | 优化表达 |
| ai:lesson:generate | ✅ | ✅ | ❌ | 生成教案 |
| ai:lesson:confirm | ✅ | ✅ | ❌ | 确认教案 |
| **ai:report:generate** | ✅ | ✅ | ❌ | **生成学情报告** |
---
## §2 GenerateReport 实现详情
### §2.1 数据流
```
teacher-bff generateReport mutation
→ ai gRPC GenerateReport(:50058)
→ ReportService.generate()
→ data-ana gRPC GetClassPerformance / GetStudentWeakness / GetLearningTrend(:50055)
→ Prompt 组装PromptTemplateService 或内联 fallback
→ LLM FailoverChain.chat()OpenAI → Anthropic → 百川 → Ollama
→ 结构化提取(摘要 + 教学建议)
← GeneratedReport { id, content, summary, recommendations, degraded, degraded_reason }
```
### §2.2 报告类型
| report_type | 用途 | 必填参数 | data-ana 数据源 |
| -------------- | ---------------- | --------------------- | ----------------------------------------------------------- |
| class_summary | 班级学情总结 | class_id | GetClassPerformance |
| student_detail | 学生个人学情详情 | class_id + student_id | GetClassPerformance + GetStudentWeakness + GetLearningTrend |
| exam_analysis | 考试分析 | class_id | GetClassPerformance |
### §2.3 降级策略
| 场景 | 降级行为 | degraded | degraded_reason |
| -------------------- | ------------------------------------------------ | -------- | -------------------------------- |
| LLM 全部不可用 | 返回空 content + 空 summary + 空 recommendations | true | "LLM unavailable: ..." |
| data-ana 不可用 | 上下文降级空数据LLM 仍可生成(基于空数据) | false | (报告内容会说明无数据) |
| ReportService 未注入 | gRPC 返回空 GeneratedReport | true | "report_service not initialized" |
| 未知异常 | gRPC 抛 AIError(AI_INTERNAL_ERROR) | - | - |
---
## §3 上游依赖ai 依赖谁)
### §3.1 gRPC 同步调用
| 被调用方 | 端口 | Service.RPC | 用途 | 状态 |
| -------- | ----- | -------------------------------------- | ------------------------------------------- | --------- |
| content | 50054 | KnowledgeGraphService.GetPrerequisites | 查询知识点前置依赖(备课工作流 Step 2 | ✅ 已实现 |
| content | 50054 | KnowledgeGraphService.GetLearningPath | 查询学习路径(备课工作流 Step 2 | ✅ 已实现 |
| content | 50054 | QuestionService.BatchCreateQuestions | 批量创建题目入库(备课工作流 Confirm | ✅ 已实现 |
| data-ana | 50055 | AnalyticsService.GetClassPerformance | 查询班级学情(备课工作流 + **学情报告** | ✅ 已实现 |
| data-ana | 50055 | AnalyticsService.GetStudentWeakness | 查询学生薄弱点(备课工作流 + **学情报告** | ✅ 已实现 |
| data-ana | 50055 | AnalyticsService.GetLearningTrend | 查询学习趋势(备课工作流 + **学情报告** | ✅ 已实现 |
| iam | 50052 | IamService.GetEffectiveDataScope | 查询用户数据范围(多租户配额) | ✅ 已实现 |
> **v2 变更**data-ana 的 3 个 RPC 现在同时服务于备课工作流和学情报告两条业务线。
### §3.2 基础设施依赖
| 依赖 | 用途 | 状态 |
| ----------------------- | ---------------------------------------------- | --------- |
| Redis | 限流 + 工作流状态存储 + 用量记录 | ✅ 已实现 |
| Kafka | AIUsageEvent 事件发布topic: `edu.ai.usage` | ✅ 已实现 |
| OpenTelemetry Collector | 链路追踪 + 指标导出 | ✅ 已实现 |
### §3.3 LLM Provider 依赖
| Provider | 环境变量 | 用途 | 状态 |
| --------- | ------------------------------------ | ------------- | --------- |
| OpenAI | `OPENAI_API_KEY` / `OPENAI_BASE_URL` | 首选 LLM | ✅ 已实现 |
| Anthropic | `ANTHROPIC_API_KEY` | Failover 第二 | ✅ 已实现 |
| 百川 | `BAICHUAN_API_KEY` | Failover 第三 | ✅ 已实现 |
| Ollama | `OLLAMA_BASE_URL` | 本地降级 | ✅ 已实现 |
---
## §4 下游就绪信号(谁依赖 ai
### §4.1 teacher-bff — P1
| 就绪标志 | 消费方式 | 状态 |
| -------------------------------------------------------- | --------------------- | ------------- |
| AiService.Chat / StreamChat 可调用 | gRPC 50058 + SSE 3008 | ✅ 已就绪 |
| AiService.GenerateQuestion 可调用 | gRPC 50058 | ✅ 已就绪 |
| AiService.GenerateLessonPlan 可调用 | gRPC 50058 | ✅ 已就绪 |
| AiService.GetLessonPlanStatus / ConfirmLessonPlan 可调用 | gRPC 50058 | ✅ 已就绪 |
| AiService.OptimizeExpression 可调用 | gRPC 50058 | ✅ 已就绪 |
| **AiService.GenerateReport 可调用** | gRPC 50058 | ✅ **已就绪** |
> teacher-bff 通过 `AI_GRPC_TARGET=ai:50058` 连接,留空时走降级模式 B。
### §4.2 student-bff — P1
| 就绪标志 | 消费方式 | 状态 |
| --------------------------------------- | ---------- | --------- |
| AiService.Chat 可调用(同步 AI 答疑) | gRPC 50058 | ✅ 已就绪 |
| AiService.StreamChat 可调用(流式答疑) | gRPC 50058 | ✅ 已就绪 |
### §4.3 api-gateway — P2
| 就绪标志 | 消费方式 | 状态 |
| -------------------------- | ---------------------------------------- | --------- |
| ai HTTP 3008 /healthz 可达 | HTTP 反向代理 `/api/v1/ai/*``ai:3008` | ✅ 已就绪 |
### §4.4 data-ana — P2
| 就绪标志 | 消费方式 | 状态 |
| ------------------------------------- | -------------------------------------- | --------- |
| Kafka topic `edu.ai.usage` 有事件发布 | CDC 消费者 → ClickHouse `ai_usage_log` | ✅ 已就绪 |
---
## §5 需要上下游实现的工作nextstep-v2 新增)
### §5.1 teacher-bff 需要完成
| # | 工作项 | 当前状态 | 优先级 |
| --- | -------------------------------------------------------- | ---------------------------- | ------ |
| 1 | AI gRPC 客户端添加 `generateReport` 方法 | 当前返回降级响应degraded | P1 |
| 2 | `teacher.service.ts``generateReport()` 调用真实 gRPC | 当前硬编码降级响应 | P1 |
| 3 | GraphQL schema `GeneratedReport` type 字段对齐 proto | 需确认字段一致性 | P2 |
**详细说明**
teacher-bff 的 `ai-grpc.client.ts` 目前实现了 `chat` / `streamChat` / `generateQuestion` / `optimizeExpression`,但 `generateLessonPlan``generateReport` 走降级。现在 ai 侧 `GenerateReport` RPC 已就绪teacher-bff 需:
```typescript
// ai-grpc.client.ts 需添加
async generateReport(input: GenerateReportInput): Promise<GeneratedReport> {
const request = new GenerateReportRequest({
classId: input.classId,
reportType: input.reportType,
studentId: input.studentId ?? undefined,
userId: input.userId,
dataScope: input.dataScope ?? undefined,
});
const response = await this.client.generateReport(request);
return {
id: response.id,
content: response.content,
summary: response.summary,
recommendations: response.recommendations,
degraded: response.degraded,
degradedReason: response.degradedReason,
};
}
```
ai.proto `GenerateReportRequest` 字段:
```protobuf
message GenerateReportRequest {
string class_id = 1;
string report_type = 2; // class_summary / student_detail / exam_analysis
optional string student_id = 3; // student_detail 时必填
string user_id = 4;
string data_scope = 5; // JSON 序列化的 DataScope
}
message GeneratedReport {
string id = 1;
string content = 2; // 报告正文Markdown
string summary = 3; // 摘要
repeated string recommendations = 4; // 教学建议
bool degraded = 5;
string degraded_reason = 6;
}
```
### §5.2 student-bff 需要确认
| # | 工作项 | 当前状态 | 优先级 |
| --- | ------------------------------------------- | -------------------------------- | ------ |
| 1 | 确认 `ChatService.Chat` gRPC 调用可用 | ai 侧已就绪,待 student-bff 确认 | P1 |
| 2 | 确认 `ChatService.StreamChat` gRPC 调用可用 | ai 侧已就绪,待 student-bff 确认 | P1 |
> student-bff nextstep-v2.md §5.4 标注这两个 RPC "需确认"。ai 侧 `Chat` 和 `StreamChat` RPC 已在 v1 实现gRPC 50058 可直接调用。
### §5.3 data-ana 需要保持
| # | 工作项 | 当前状态 | 优先级 |
| --- | ------------------------------------------------ | --------- | ------ |
| 1 | AnalyticsService 3 RPC 保持可用 | ✅ 已就绪 | P1 |
| 2 | GenerateReport 依赖 GetClassPerformance 等 3 RPC | ✅ 已就绪 | P1 |
> 学情报告功能依赖 data-ana 的 3 个 AnalyticsService RPC。如果 data-ana 不可用,报告会基于降级上下文(空数据)生成,但 LLM 仍可工作。
### §5.4 api-gateway 需要保持
| # | 工作项 | 当前状态 | 优先级 |
| --- | ------------------------------------------------------------ | --------- | ------ |
| 1 | `/api/v1/ai/*``ai:3008` 反向代理路由保持可用 | ✅ 已就绪 | P2 |
| 2 | 新增 `/v1/ai/generate/report` 自动被 `/api/v1/ai/*` 通配覆盖 | ✅ 已就绪 | P2 |
> api-gateway 使用通配路由 `/api/v1/ai/*`,新增的 `/v1/ai/generate/report` 端点自动被覆盖,无需额外配置。
---
## §6 联调待办
| # | 联调项 | 联调方 | 阻塞条件 | 状态 |
| --- | -------------------------------------- | ----------- | ------------------------------------ | ------------------------------------------- |
| 1 | ai gRPC + teacher-bff SSE 联调 | teacher-bff | ai 服务容器启动 | ✅ ai 侧就绪(待 teacher-bff 接入) |
| 2 | ai gRPC StreamChat + student-bff 联调 | student-bff | ai 服务容器启动 | ✅ ai 侧就绪(待 student-bff 确认) |
| 3 | ai /healthz + api-gateway 联调 | api-gateway | ai 服务容器启动 | ✅ ai 侧就绪(待 api-gateway 路由) |
| 4 | AIUsageEvent + data-ana CDC 消费联调 | data-ana | ai Kafka 生产 + data-ana 消费 | ✅ ai 生产就绪(待 data-ana 消费) |
| 5 | ai ↔ content gRPC 联调 | content | 双方容器启动 | ✅ ai 客户端就绪(待 content 启动) |
| 6 | ai ↔ data-ana gRPC 联调 | data-ana | 双方容器启动 | ✅ ai 客户端就绪(待 data-ana 启动) |
| 7 | ai ↔ iam gRPC 联调 | iam | 双方容器启动 | ✅ ai 客户端就绪(待 iam 启动) |
| 8 | **GenerateReport + teacher-bff 联调** | teacher-bff | ai RPC 就绪 + teacher-bff 客户端更新 | ✅ ai RPC 就绪(待 teacher-bff 客户端更新) |
| 9 | **GenerateReport + data-ana 数据联调** | data-ana | 双方容器启动 + 真实学情数据 | ✅ ai 客户端就绪(待 data-ana 有数据) |
---
## §7 测试验证
### §7.1 单元测试
| 测试文件 | 新增用例数 | 覆盖范围 |
| --------------------- | ---------- | -------------------------------------------------------------------------------------- |
| test_grpc_servicer.py | 5 | GenerateReport 成功/学生详情/未初始化降级/LLM降级/内部错误 |
| test_services.py | 9 | ReportService 生成成功/学生详情/LLM降级/无data-ana/摘要提取/建议提取(2)/prompt构建 |
| test_main_app.py | 5 | HTTP 端点 class_summary/student_detail/invalid_type/missing_class_id/permission_denied |
| test_models.py | 5 | report 模型 默认值/invalid_type/missing_class_id/student_detail/data_defaults |
| test_permission.py | 2 | teacher 有 report 权限 / student 无 report 权限 |
| **合计** | **26** | v1 377 → v2 402+25 净增1 个修复 _extract_recommendations bug |
### §7.2 质量校验
| 校验项 | 结果 | 详情 |
| ---------- | -------- | -------------------------------------------- |
| ruff check | ✅ 通过 | 零警告 |
| pytest | ✅ 通过 | 402 passed, 0 failed |
| 覆盖率 | ✅ 88.5% | report_service.py 91%models/report.py 100% |
### §7.3 Docker 验证(待执行)
> arch.db 本次不扫描Docker 验证待 infra 环境就绪后执行。
| # | 验证项 | 状态 |
| --- | --------------------- | --------- |
| 1 | Docker 镜像构建 | ⏳ 待验证 |
| 2 | 容器启动 + gRPC 50058 | ⏳ 待验证 |
| 3 | HTTP /healthz | ⏳ 待验证 |
| 4 | HTTP /readyz | ⏳ 待验证 |
| 5 | GenerateReport gRPC | ⏳ 待验证 |
---
## §8 关键实现文件
| 文件 | 变更类型 | 说明 |
| -------------------------------------- | -------- | -------------------------------------------- |
| `packages/shared-proto/proto/ai.proto` | 修改 | 新增 GenerateReport RPC + 2 message |
| `src/ai/proto_gen/ai_pb2.py` | 重新生成 | protobuf 代码 |
| `src/ai/proto_gen/ai_pb2_grpc.py` | 重新生成 | gRPC stub/servicer 代码 |
| `src/ai/models/report.py` | 新建 | Pydantic 请求/响应模型 |
| `src/ai/models/__init__.py` | 修改 | 导出 report 模型 |
| `src/ai/services/report_service.py` | 新建 | ReportService 业务编排 |
| `src/ai/services/__init__.py` | 修改 | 导出 ReportService |
| `src/ai/grpc_server/servicer.py` | 修改 | GenerateReport 方法 + report_service 注入 |
| `src/ai/grpc_server/server.py` | 修改 | create_grpc_server 添加 report_service 参数 |
| `src/ai/middleware/permission.py` | 修改 | 新增 PERMISSION_AI_REPORT_GENERATE 权限点 |
| `src/ai/main.py` | 修改 | ReportService 实例化 + HTTP /generate/report |
| `tests/test_grpc_servicer.py` | 修改 | 5 个 GenerateReport servicer 测试 |
| `tests/test_services.py` | 修改 | 9 个 ReportService 测试 |
| `tests/test_main_app.py` | 修改 | 5 个 HTTP 端点测试 |
| `tests/test_models.py` | 修改 | 5 个 report 模型测试 |
| `tests/test_permission.py` | 修改 | 2 个 report 权限测试 |
---
## §9 P6+ 待评估
| # | 待评估项 | 说明 |
| --- | ----------------- | ------------------------------------------------------------------ |
| 1 | Temporal 引入评估 | 备课工作流 P5 用 BackgroundTasks + RedisP6 评估是否引入 Temporal |
| 2 | content 事件订阅 | P5 不订阅 content 事件P6+ 评估是否需要知识点变更事件驱动 |
| 3 | MockLLMProvider | 02 文档提到但未实现,测试环境用 httpx Mock 替代 |
| 4 | 报告持久化 | 当前 GenerateReport 不持久化报告P6+ 评估是否需要入库 |
| 5 | 报告模板管理 | 当前用内联 fallback 模板P6+ 评估是否需要独立模板管理服务 |
| 6 | 报告异步生成 | 当前同步生成长报告可能超时P6+ 评估是否改为异步 + 轮询模式 |