Files
Edu/docs/architecture/issues/contracts/ai_contract.md

204 lines
20 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 设计文档**。原 coord 模板的 5 处矛盾已修正(见 [objections/ai_issue.md](../objections/ai_issue.md) ISSUE-05端口 50057→50058、补 HTTP 端点、topic 三义待裁决、错误码对齐 §6.2、消费事件改 P6+ 评估。标注 ⏳ 的字段待 coord 裁决 ISSUE-02/03/04 后最终定稿。
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口
| Service | RPC | 请求 | 响应 | 端口 | 状态 |
| --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------- | ----- | ---- |
| AiService | Chat | `ChatRequest{messages, model, temperature, user_id?, session_id?, data_scope?}` | `ChatResponse{content, model, usage}` | 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 | ⏳ 待补 protoISSUE-03 |
| AiService | OptimizeExpression | `OptimizeExpressionRequest{text, context}` | `OptimizedExpression` | 50058 | ⏳ 待实现 |
| AiService | GenerateLessonPlan | `GenerateLessonPlanRequest{class_id, subject_id, topic, user_id, data_scope}` | `LessonPlanResponse{workflow_id, status, questions?}` | 50058 | ⏳ 待补 protoISSUE-03 |
> **RPC 总数**P5 目标 6 RPCai12 建议,见 ISSUE-03。备课工作流的"查询状态/确认入库"用 HTTP 端点实现,避免 RPC 膨胀;如 coord 裁定需 gRPC 则扩到 8 RPC追加 GetLessonPlanStatus / ConfirmLessonPlan
> **proto 现状**ai.proto 仅 4 RPCChat/StreamChat/GenerateQuestion/OptimizeExpression缺 GenerateLessonPlan / StreamGenerateQuestion且字段未扩展。待 coord 升级 ai.proto 到 v1 完整版ISSUE-03
> **proto package 偏离**:现状 `next_edu_cloud.ai.v1`,不符合 project_rules §5 `edu.<domain>.v1`,见 ISSUE-08。
### 1.2 HTTP 端点
> HTTP 保留作 api-gateway 直连降级 + SSE 流式。api-gateway 代理 `/api/v1/ai/*` → ai `/ai/v1/*`(见 [main.py:70](../../../../services/ai/src/ai/main.py) 注释 + matrix.md §5
| Method | Path | 权限 | 响应 | 说明 | 状态 |
| ------ | ------------------------------------------------- | ------------------------ | -------------------------------------- | --------------------------------- | ---- |
| GET | `/healthz` | — | `{status, service}` | liveness | ✅ 已实现 |
| GET | `/readyz` | — | `{status, llm_configured, providers, downstream_grpc, redis, kafka}` | readiness多维度检查 | ⚠️ 待扩展 |
| GET | `/metrics` | — | Prometheus | 指标 | ✅ 已实现 |
| POST | `/ai/v1/chat` | `AI_CHAT` | `ActionState<ChatData>` | LLM 聊天 | ⚠️ 当前 `/ai/chat`,待加 /v1 + ActionState |
| POST | `/ai/v1/chat/stream` | `AI_CHAT` | SSE stream | 流式聊天 | ⚠️ 同上 |
| POST | `/ai/v1/generate/question` | `AI_QUESTION_GENERATE` | `ActionState<GeneratedQuestionData>` | 生成题目 | ⚠️ 同上 |
| POST | `/ai/v1/generate/question/stream` | `AI_QUESTION_GENERATE` | SSE stream题目逐字生成 | 题目逐字流式 | ⏳ 待实现 |
| POST | `/ai/v1/optimize/expression` | `AI_EXPRESSION_OPTIMIZE` | `ActionState<OptimizedExpressionData>` | 优化表达 | ⚠️ 同上 |
| POST | `/ai/v1/lesson/preparation` | `AI_LESSON_PREPARE` | `ActionState<LessonPreparationData>` | 备课工作流启动 | ⏳ 待实现 |
| GET | `/ai/v1/lesson/preparation/{workflow_id}` | `AI_LESSON_PREPARE` | `ActionState<WorkflowState>` | 查询工作流状态 | ⏳ 待实现 |
| POST | `/ai/v1/lesson/preparation/{workflow_id}/confirm` | `AI_LESSON_PREPARE` | `ActionState<PersistResult>` | 教师确认入库 | ⏳ 待实现 |
| GET | `/ai/v1/prompts` | `AI_PROMPT_READ` | `ActionState<Page<TemplateSummary>>` | 模板列表 | ⏳ 待实现 |
| POST | `/ai/v1/prompts` | `AI_PROMPT_CREATE` | `ActionState<PromptTemplate>` | 创建模板 | ⏳ 待实现 |
| GET | `/ai/v1/prompts/{id}` | `AI_PROMPT_READ` | `ActionState<PromptTemplate>` | 获取模板 | ⏳ 待实现 |
| PUT | `/ai/v1/prompts/{id}` | `AI_PROMPT_UPDATE` | `ActionState<PromptTemplate>` | 更新模板(版本化) | ⏳ 待实现 |
| GET | `/ai/v1/usage/me` | `AI_USAGE_READ` | `ActionState<UsageSummary>` | 当前用户用量 | ⏳ 待实现 |
| GET | `/ai/v1/usage/school/{school_id}` | `AI_USAGE_READ_ALL` | `ActionState<UsageSummary>` | 学校用量(管理员) | ⏳ 待实现 |
> **响应信封**:所有响应必须为 ActionState004 §11.5 强制,见 ISSUE-09。当前 main.py 返回 `{success, data, degraded}` 顶层 degraded 字段违反约束P5 必须整改。
> **路径演进**:当前实现是 `/ai/*`(无 /v1目标态 `/ai/v1/*`(加版本前缀,便于未来破坏性变更)。
### 1.3 GraphQL schema如 BFF
不适用。ai 是业务服务,不暴露 GraphQL由 teacher-bff 聚合 ai gRPC 能力为 GraphQL。
### 1.4 Kafka 事件发布
| Topic ⏳ | Event | 触发时机 | 消费方 | Payload |
| ----------------- | ---------------------- | ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `edu.ai.usage`(建议,待 ISSUE-02 裁决) | `AIUsageEvent`(待 ISSUE-04 补 proto | 每次 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 命名三义**01/02 文档写 `edu.insight.ai.usage`、matrix.md 写 `edu.ai.usage`、原 contract 写 `edu.ai.usage.events`。ai12 建议采用 `edu.ai.usage`(最简短),待 coord 裁决ISSUE-02
> **Outbox 豁免**AIUsageEvent 为派生数据事件004 §12.2 + §15.3 #6 仲裁豁免 Outbox允许直接 producer`aiokafka` + acks=all + idempotent + transactional_id
> **events.proto 现状**:无 `AIUsageEvent` message待 coord 补全ISSUE-04建议 schema 见 [02-architecture-design.md §3.3](../../../../services/ai/docs/02-architecture-design.md)。
> **长期可发布事件**P6+ 评估,待 coord 仲裁):`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 我依赖的上游就绪标志
- [ ] ai.proto 升级 v1 完整版6 RPC + 字段扩展)—— coordISSUE-03
- [ ] events.proto 补 `AIUsageEvent` message —— coordISSUE-04
- [ ] ai 用量事件 topic 命名裁决 —— coordISSUE-02
- [ ] content gRPC 50054 启用ai09—— 知识点维度 + 题库检索 + 入库
- [ ] data-ana gRPC 50055 启用ai11可选—— 学生薄弱点(可降级独立运行)
- [ ] iam `GetEffectiveDataScope` RPC P4 补全ai06 + coordISSUE-07可降级
- [ ] LLM Provider API key 配置(人类决策者)
### 3.2 我的就绪标志(供下游消费)
- [ ] ai gRPC 50058 启用HealthService.Check 返回 SERVING
- [ ] AiService.Chat / StreamChat 可调用(含流式响应)
- [ ] AiService.GenerateQuestion / StreamGenerateQuestion 可调用
- [ ] AiService.GenerateLessonPlan 可调用P5 补全)
- [ ] AiService.OptimizeExpression 可调用
- [ ] ai 用量事件 topic 可发布(供 data-ana 统计 AI 用量)
> **端口**50058[port-allocation.md](../../../../infra/port-allocation.md) §3/§5/§7 权威源2026-07-09 coord 仲裁"50058 让给 ai")。注意 [matrix.md](../matrix.md) §2/§8 仍写 50057待 coord 同步ISSUE-01
---
## §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 契约待裁决项汇总
> 以下字段待 coord 裁决后最终定稿ai12 当前按建议方案先行实现。
| 待裁决项 | ISSUE | ai12 建议方案 | 影响章节 |
| --------------------------------- | ------ | ---------------------------------------------- | -------------- |
| ai gRPC 端口50057 vs 50058 | ISSUE-01 | 50058port-allocation.md 已定,待 matrix 同步) | §1.1 / §3.2 |
| ai 用量事件 topic 命名 | ISSUE-02 | `edu.ai.usage` | §1.4 |
| ai.proto P5 目标 RPC 数6 vs 8 | ISSUE-03 | 6 RPC查询/确认用 HTTP | §1.1 |
| events.proto 补 AIUsageEvent | ISSUE-04 | 按 02-architecture-design.md §3.3 schema | §1.4 |
| 备课工作流 TemporalP6 决策点) | ISSUE-06 | P5 用 BackgroundTasks + RedisP6 评估 Temporal | §2.1(无影响) |
| iam GetEffectiveDataScope P4 补全 | ISSUE-07 | 确认 P4 已补全;未补全则降级 | §2.1 |
| proto package 命名(全局) | ISSUE-08 | 待 coord 裁定是否迁移 `edu.<domain>.v1` | §1.1 |
| 响应信封 ActionState 整改 | ISSUE-09 | P5 整改degraded 作为 error.details 子字段 | §1.2 |