第 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%
341 lines
21 KiB
Markdown
341 lines
21 KiB
Markdown
# AI 模块 nextstep-v2
|
||
|
||
> 模块:ai | gRPC 50058 | HTTP 3008 | Python (FastAPI + gRPC aio)
|
||
> 更新时间:2026-07-14
|
||
> 前序文档:[nextstep.md](./nextstep.md)(v1,8 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 + Redis,P6 评估是否引入 Temporal |
|
||
| 2 | content 事件订阅 | P5 不订阅 content 事件,P6+ 评估是否需要知识点变更事件驱动 |
|
||
| 3 | MockLLMProvider | 02 文档提到但未实现,测试环境用 httpx Mock 替代 |
|
||
| 4 | 报告持久化 | 当前 GenerateReport 不持久化报告,P6+ 评估是否需要入库 |
|
||
| 5 | 报告模板管理 | 当前用内联 fallback 模板,P6+ 评估是否需要独立模板管理服务 |
|
||
| 6 | 报告异步生成 | 当前同步生成,长报告可能超时,P6+ 评估是否改为异步 + 轮询模式 |
|