Files
Edu/docs/architecture/issues/contracts/ai_contract.md
2026-07-10 18:57:39 +08:00

202 lines
19 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 对接契约
> 负责人ai12
> 关联:[matrix.md](../matrix.md)、[port-allocation.md](../../../../infra/port-allocation.md)、[ai.proto](../../../../packages/shared-proto/proto/ai.proto)、[events.proto](../../../../packages/shared-proto/proto/events.proto)、[02-architecture-design.md](../../../../services/ai/docs/02-architecture-design.md)、[objections/ai_issue.md](../objections/ai_issue.md)
> 端口权威源:[port-allocation.md](../../../../infra/port-allocation.md) §3/§5 —— ai = HTTP 3008 / gRPC 50058
> **本契约已对齐 02-architecture-design.md 设计文档P5 实现已完成**。9 个 ISSUE 全部已裁决(见 [objections/ai_issue.md](../objections/ai_issue.md)):端口 50058、topic `edu.ai.usage`、8 RPC、events.proto 补 AIUsageEvent、备课工作流 P5 用 BackgroundTasks+Redis、iam P4 补全、proto 包名 `next_edu_cloud.<domain>.v1`、ActionState 方案 B 降级。所有 ⏳ 标记已更新为 ✅。
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口
| Service | RPC | 请求 | 响应 | 端口 | 状态 |
| --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------- | ----- | ---- |
| AiService | Chat | `ChatRequest{messages, model, temperature, user_id?, session_id?, data_scope?}` | `ChatResponse{content, model, usage, degraded, degraded_reason}` | 50058 | ✅ 已实现 |
| AiService | StreamChat | `ChatRequest` | `stream ChatChunk` | 50058 | ✅ 已实现 |
| AiService | GenerateQuestion | `GenerateQuestionRequest{prompt, subject, difficulty, grade?, knowledge_point_ids?, question_type?, count?}` | `GeneratedQuestion` | 50058 | ✅ 已实现 |
| AiService | StreamGenerateQuestion | `GenerateQuestionRequest` | `stream GeneratedQuestionChunk` | 50058 | ✅ 已实现 |
| AiService | OptimizeExpression | `OptimizeExpressionRequest{text, context}` | `OptimizedExpression` | 50058 | ✅ 已实现 |
| AiService | GenerateLessonPlan | `GenerateLessonPlanRequest{class_id, subject_id, topic, target_difficulty, question_count, user_id, data_scope}` | `LessonPlanResponse{workflow_id, status, estimated_completion_seconds, degraded, degraded_reason}` | 50058 | ✅ 已实现 |
| AiService | GetLessonPlanStatus | `GetLessonPlanStatusRequest{workflow_id}` | `LessonPlanStatus{workflow_id, status, questions, error?, degraded, degraded_reason}` | 50058 | ✅ 已实现 |
| AiService | ConfirmLessonPlan | `ConfirmLessonPlanRequest{workflow_id, modifications}` | `ConfirmResult{success, persisted_question_ids, error?}` | 50058 | ✅ 已实现 |
> **RPC 总数**8 RPCcoord 裁决 ISSUE-03备课工作流查询/确认也走 gRPC
> **proto 现状**ai.proto 已升级到 8 RPC 完整版(含 degraded/degraded_reason 字段)。
> **proto package**`next_edu_cloud.ai.v1`coord G17 裁决保持现状,覆盖 project_rules §5
### 1.2 HTTP 端点
> HTTP 保留作 api-gateway 直连降级 + SSE 流式。api-gateway 代理 `/api/v1/ai/*` → ai `/v1/ai/*`。
| Method | Path | 权限 | 响应 | 说明 | 状态 |
| ------ | ------------------------------------------------- | ------------------------ | -------------------------------------- | --------------------------------- | ---- |
| GET | `/healthz` | — | `{status, service}` | liveness | ✅ 已实现 |
| GET | `/readyz` | — | `{status, llm_configured, degraded, grpc_running, providers}` | readiness多维度检查 | ✅ 已实现 |
| GET | `/metrics` | — | Prometheus | 指标 | ✅ 已实现 |
| POST | `/v1/ai/chat` | `ai:chat` | `ActionState<ChatData>` | LLM 聊天 | ✅ 已实现 |
| POST | `/v1/ai/chat/stream` | `ai:chat` | SSE stream | 流式聊天 | ✅ 已实现 |
| POST | `/v1/ai/generate/question` | `ai:question:generate` | `ActionState<GeneratedQuestionData>` | 生成题目 | ✅ 已实现 |
| POST | `/v1/ai/generate/question/stream` | `ai:question:generate` | SSE stream题目逐字生成 | 题目逐字流式 | ✅ 已实现 |
| POST | `/v1/ai/optimize/expression` | `ai:expression:optimize` | `ActionState<OptimizedExpressionData>` | 优化表达 | ✅ 已实现 |
| POST | `/v1/ai/lesson-plan/generate` | `ai:lesson:generate` | `ActionState<LessonPreparationData>` | 备课工作流启动 | ✅ 已实现 |
| GET | `/v1/ai/lesson-plan/status/{workflow_id}` | — | `ActionState<WorkflowStatusData>` | 查询工作流状态 | ✅ 已实现 |
| POST | `/v1/ai/lesson-plan/confirm/{workflow_id}` | `ai:lesson:confirm` | `ActionState<ConfirmResultData>` | 教师确认入库 | ✅ 已实现 |
> **响应信封**:所有响应使用 ActionState004 §11.5 强制)。降级采用方案 B总裁 §2.6success=true + error=null + data 内 degraded=true。
> **路径**:业务路由统一 `/v1/ai/*` 前缀。
### 1.3 GraphQL schema如 BFF
不适用。ai 是业务服务,不暴露 GraphQL由 teacher-bff 聚合 ai gRPC 能力为 GraphQL。
### 1.4 Kafka 事件发布
| Topic | Event | 触发时机 | 消费方 | Payload | 状态 |
| ----- | ----- | -------- | ------ | ------- | ---- |
| `edu.ai.usage` | `AIUsageEvent` | 每次 LLM 调用完成 | data-ana | `{event_id, aggregate_id, event_type, occurred_at, user_id, school_id, request_id, provider, model, operation, prompt_tokens, completion_tokens, total_tokens, latency_ms, success, degraded, metadata}` | ✅ 已实现 |
> **topic 命名裁决**ISSUE-02 已裁决,采用 `edu.ai.usage`matrix.md §4 确认,最简短)。
> **events.proto 已补全**ISSUE-04 已裁决,`AIUsageEvent` message 已补入 [events.proto](../../../../packages/shared-proto/proto/events.proto)schema 见 [02-architecture-design.md §3.3](../../../../services/ai/docs/02-architecture-design.md)。
> **Outbox 豁免**AIUsageEvent 为派生数据事件004 §12.2 + §15.3 #6 仲裁豁免 Outbox允许直接 producer`aiokafka` + acks=all + idempotent + transactional_id
> **长期可发布事件**P6+ 评估,非 P5 范围):`AIContentGenerated`topic `edu.ai.generated`,生成内容审计)、`AIFeedbackRecorded`topic `edu.ai.feedback`RLHF 数据)、`AIWorkflowEvent`topic `edu.ai.workflow`,工作流监控)。
### 1.5 错误码前缀
`AI_*`(对齐 [02-architecture-design.md §6.2](../../../../services/ai/docs/02-architecture-design.md) 完整清单matrix.md §6 已确认 ai 前缀为 `AI_`
| 错误码 | 触发条件 | HTTP | gRPC status |
| -------------------------------- | ----------------------------------- | ---- | ------------------- |
| `AI_UNAUTHORIZED` | 缺失 x-user-id 或 token 无效 | 401 | UNAUTHENTICATED |
| `AI_FORBIDDEN` | 角色无对应权限 | 403 | PERMISSION_DENIED |
| `AI_RATE_LIMITED` | 触发限流user/IP/school | 429 | RESOURCE_EXHAUSTED |
| `AI_QUOTA_EXCEEDED` | 学校/教师月度 token 配额耗尽 | 429 | RESOURCE_EXHAUSTED |
| `AI_LLM_UNAVAILABLE` | LLM Provider 不可达(降级骨架) | 200 | OK + degraded flag |
| `AI_LLM_TIMEOUT` | LLM 调用超时30s | 504 | DEADLINE_EXCEEDED |
| `AI_LLM_ALL_PROVIDERS_FAILED` | 所有 Provider 故障切换链均失败 | 503 | UNAVAILABLE |
| `AI_INVALID_MODEL` | model 名不支持 | 400 | INVALID_ARGUMENT |
| `AI_INVALID_DIFFICULTY` | difficulty 不在 easy/medium/hard | 400 | INVALID_ARGUMENT |
| `AI_INVALID_QUESTION_TYPE` | question_type 不在枚举内 | 400 | INVALID_ARGUMENT |
| `AI_DOWNSTREAM_UNAVAILABLE` | content / data-ana gRPC 不可达 | 502 | UNAVAILABLE |
| `AI_PROMPT_RENDER_FAILED` | Prompt 模板渲染失败 | 500 | INTERNAL |
| `AI_PROMPT_TEMPLATE_NOT_FOUND` | Prompt 模板不存在 | 404 | NOT_FOUND |
| `AI_WORKFLOW_NOT_FOUND` | 备课工作流 ID 不存在 | 404 | NOT_FOUND |
| `AI_WORKFLOW_EXPIRED` | 工作流已过期24h 未审核) | 410 | FAILED_PRECONDITION |
| `AI_WORKFLOW_STATE_INVALID` | 工作流状态不允许此操作 | 409 | FAILED_PRECONDITION |
| `AI_EVALUATION_FAILED` | 生成质量评估未通过且重试耗尽 | 503 | UNAVAILABLE |
| `AI_PII_DETECTED` | 输入包含未脱敏 PII | 400 | INVALID_ARGUMENT |
| `AI_PROMPT_INJECTION_DETECTED` | 输入疑似 Prompt 注入攻击 | 400 | INVALID_ARGUMENT |
| `AI_CONTENT_MODERATION_REJECTED` | 输出内容审核不通过(敏感词) | 503 | UNAVAILABLE |
| `AI_INTERNAL_ERROR` | 未捕获异常 | 500 | INTERNAL |
---
## §2 我消费什么(依赖上游)
### 2.1 gRPC 调用(同步)
| 被调用方 | Service.RPC | 用途 | mock 策略 |
| --------------- | -------------------------------------- | ---------------------------- | ------------------------------------------------------------- |
| content (ai09) | `KnowledgeGraphService.GetPrerequisites` | 出题上下文:查询知识点前置依赖 | content 就绪前使用本地知识点 stub固定 3 个前置知识点) |
| content (ai09) | `KnowledgeGraphService.GetLearningPath` | 个性化出题:查询学生学习路径 | content 就绪前返回空路径 |
| content (ai09) | `TextbookService.ListTextbooks` | 备课工作流:查询教材章节关联知识点 | content 就绪前返回空列表 |
| content (ai09) | `QuestionService.CreateQuestions`(待 coord 补 proto | 备课工作流:生成的题目入库 | content 就绪前跳过入库,工作流标记 PersistFailed |
| data-ana (ai11) | `AnalyticsService.GetStudentWeakness` | 靶向出题:查询学生薄弱知识点 | data-ana 就绪前使用本地薄弱点 stub固定 2 个 weak_points |
| data-ana (ai11) | `AnalyticsService.GetLearningTrend` | 难度调节:查询学习趋势 | data-ana 就绪前返回默认趋势 |
| data-ana (ai11) | `AnalyticsService.GetClassPerformance` | 备课工作流:班级整体学情 | data-ana 就绪前降级跳过学情查询(`degraded:true` |
| iam (ai06) | `IamService.GetEffectiveDataScope`(待 P4 补全ISSUE-07 | 多租户配额:查询用户 DataScope | iam RPC 未就绪时降级为"仅按 user_id 配额,不按 school_id" |
> **端口**content gRPC 50054 / data-ana 50055 / iam 50052[port-allocation.md](../../../../infra/port-allocation.md) §5。注意 02-architecture-design.md §1.1/§1.2 mermaid 图误标为 3005/3006/3002HTTP 端口),应以 50054/50055/50052 为准。
### 2.2 Kafka 事件订阅(异步)
> ai 是**无状态服务P5 不消费任何 Kafka 事件**01/02 §5.1 明确)。原 contract 列出的 content 事件订阅已删除(与设计文档矛盾,见 ISSUE-05.5)。
**P6+ 评估可消费事件**(待 coord 仲裁,非 P5 范围):
| Topic | Event | 发布方 | 用途 | 评估阶段 |
| ---------------------------------- | ------------------- | -------------- | -------------------------------- | -------- |
| `edu.content.question.events` | QuestionEvent | content (ai09) | 题目查重:避免 AI 生成与已有重复 | P6+ |
| `edu.iam.user.events` | UserEvent | iam (ai06) | 角色变更:主动失效 DataScope 缓存 | P6+ |
### 2.3 HTTP 调用(外部)
| 被调用方 | Method.Path | 用途 | mock 策略 |
| -------------------------------- | ------------------------- | ------------------ | ------------------------------------------------------------------ |
| LLM ProviderOpenAI/Anthropic/百川/Ollama | POST /v1/chat/completions | 调用大模型生成内容 | 开发期使用本地 mock server 返回固定 ChatResponse不消耗真实 token |
> 多 Provider 通过 `ProviderFailoverChain` 故障切换OpenAI → Anthropic → 百川 → 本地 Ollama熔断器连续 3 次失败触发 60s 熔断。
---
## §3 就绪信号
### 3.1 我依赖的上游就绪标志
- [x] ai.proto 升级 v1 完整版8 RPC + 字段扩展)—— coordISSUE-03 已裁决,已实现)
- [x] events.proto 补 `AIUsageEvent` message —— coordISSUE-04 已裁决,已补全)
- [x] ai 用量事件 topic 命名裁决 —— coordISSUE-02 已裁决,`edu.ai.usage`
- [ ] content gRPC 50054 启用ai09—— 知识点维度 + 题库检索 + 入库(全并行模式用 Mock
- [ ] data-ana gRPC 50055 启用ai11可选—— 学生薄弱点(可降级独立运行,全并行模式用 Mock
- [ ] iam `GetEffectiveDataScope` RPC P4 补全ai06 + coordISSUE-07可降级全并行模式用 Mock
- [ ] LLM Provider API key 配置(人类决策者)
### 3.2 我的就绪标志(供下游消费)
- [x] ai gRPC 50058 启用HealthService.Check 返回 SERVING
- [x] AiService.Chat / StreamChat 可调用(含流式响应)
- [x] AiService.GenerateQuestion / StreamGenerateQuestion 可调用
- [x] AiService.GenerateLessonPlan 可调用P5 补全)
- [x] AiService.GetLessonPlanStatus / ConfirmLessonPlan 可调用
- [x] AiService.OptimizeExpression 可调用
- [x] ai 用量事件 topic 可发布(供 data-ana 统计 AI 用量)
> **端口**50058[port-allocation.md](../../../../infra/port-allocation.md) §3/§5/§7 权威源ISSUE-01 已裁决50058 让给 ai
---
## §4 Mock 策略
### 4.1 我提供的 mock
在 ai 真实服务就绪前为下游teacher-bff提供以下 mock
- **gRPC mock**:使用 grpc-mock 拦截 **50058** 端口
- AiService.Chat 返回固定 ChatResponsecontent="这是 AI 助手的模拟回复"
- AiService.StreamChat 返回固定流3 个 ChatChunk最后一个 done=true
- AiService.GenerateQuestion 返回固定 GeneratedQuestionquestion/answer/explanation
- AiService.GenerateLessonPlan 返回固定 LessonPlanResponseworkflow_id + 3 个题目)
- AiService.StreamGenerateQuestion 返回固定流2 个题目逐字 chunk
- AiService.OptimizeExpression 返回固定 OptimizedExpression
- **Kafka mock**ai 就绪前不发布真实 AIUsageEventdata-ana 仪表盘 AI 用量显示"暂无数据"
### 4.2 我消费的 mock
在真实上游就绪前ai 使用以下 mock
- **LLM Provider mock**:本地启动 mock serverPOST /v1/chat/completions 返回固定 JSON不消耗真实 token不产生费用
- **content 知识点**:内置固定知识点表(数学 20 个知识点 + 前置依赖关系),不依赖 content gRPC
- **data-ana 薄弱点**内置固定学生薄弱点2 个 weak_points不依赖 data-ana gRPC
- **iam DataScope**:内置固定 DataScopeSCHOOL 级),不依赖 iam GetEffectiveDataScope
- **事件订阅**P5 不订阅任何事件,无 mock 需要
---
## §5 契约裁决项汇总
> 以下 9 个 ISSUE 全部已裁决(见 [objections/ai_issue.md](../objections/ai_issue.md) 与 [president-final-rulings.md](../../president-final-rulings.md)、[coord-final-decisions.md](../../coord-final-decisions.md)P5 实现已按裁决结论落地。
| 裁决项 | ISSUE | 裁决结论 | 裁决依据 | 影响章节 |
| -------------------------------- | ------- | ------------------------------------------------------------------- | ------------------------------------------------ | -------------- |
| ai gRPC 端口 | ISSUE-01 | **50058** | port-allocation.md §3/§5/§7 权威源 | §1.1 / §3.2 |
| ai 用量事件 topic 命名 | ISSUE-02 | **`edu.ai.usage`** | matrix.md §4 | §1.4 |
| ai.proto P5 目标 RPC 数 | ISSUE-03 | **8 RPC**(查询/确认走 gRPC | 设计文档 §4.2 + coord B2 裁决 | §1.1 |
| events.proto 补 AIUsageEvent | ISSUE-04 | **ai12 补全**schema 见设计文档 §3.3 | ai12 已补入 events.proto | §1.4 |
| contract 对齐设计文档 | ISSUE-05 | **ai12 自纠完成** | ai_contract.md 已重写对齐 | 全文 |
| 备课工作流 Temporal | ISSUE-06 | **P5 用 BackgroundTasks + Redis24h TTLP6 评估 Temporal** | 总裁 §7.12 + coord A7 | §2.1(无影响) |
| iam GetEffectiveDataScope P4 补全 | ISSUE-07 | **P4 补全ai 用 Mock** | 全并行模式 | §2.1 |
| proto package 命名(全局) | ISSUE-08 | **保持 `next_edu_cloud.<domain>.v1`** | coord G17 裁决覆盖 project_rules §5 | §1.1 |
| 响应信封 ActionState 整改 | ISSUE-09 | **ActionState + 方案 B 降级**success=true + error=null + degraded | 总裁 §2.6 | §1.2 |