# 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 | ⏳ 待补 proto（ISSUE-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 | ⏳ 待补 proto（ISSUE-03） |

> **RPC 总数**：P5 目标 6 RPC（ai12 建议，见 ISSUE-03）。备课工作流的"查询状态/确认入库"用 HTTP 端点实现，避免 RPC 膨胀；如 coord 裁定需 gRPC 则扩到 8 RPC（追加 GetLessonPlanStatus / ConfirmLessonPlan）。
> **proto 现状**：ai.proto 仅 4 RPC（Chat/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>`            | 学校用量（管理员）                | ⏳ 待实现 |

> **响应信封**：所有响应必须为 ActionState（004 §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/3002（HTTP 端口），应以 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 Provider（OpenAI/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 + 字段扩展）—— coord（ISSUE-03）
- [ ] events.proto 补 `AIUsageEvent` message —— coord（ISSUE-04）
- [ ] ai 用量事件 topic 命名裁决 —— coord（ISSUE-02）
- [ ] content gRPC 50054 启用（ai09）—— 知识点维度 + 题库检索 + 入库
- [ ] data-ana gRPC 50055 启用（ai11，可选）—— 学生薄弱点（可降级独立运行）
- [ ] iam `GetEffectiveDataScope` RPC P4 补全（ai06 + coord，ISSUE-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 返回固定 ChatResponse（content="这是 AI 助手的模拟回复"）
  - AiService.StreamChat 返回固定流（3 个 ChatChunk，最后一个 done=true）
  - AiService.GenerateQuestion 返回固定 GeneratedQuestion（question/answer/explanation）
  - AiService.GenerateLessonPlan 返回固定 LessonPlanResponse（workflow_id + 3 个题目）
  - AiService.StreamGenerateQuestion 返回固定流（2 个题目逐字 chunk）
  - AiService.OptimizeExpression 返回固定 OptimizedExpression
- **Kafka mock**：ai 就绪前不发布真实 AIUsageEvent，data-ana 仪表盘 AI 用量显示"暂无数据"

### 4.2 我消费的 mock

在真实上游就绪前，ai 使用以下 mock：

- **LLM Provider mock**：本地启动 mock server，POST /v1/chat/completions 返回固定 JSON（不消耗真实 token，不产生费用）
- **content 知识点**：内置固定知识点表（数学 20 个知识点 + 前置依赖关系），不依赖 content gRPC
- **data-ana 薄弱点**：内置固定学生薄弱点（2 个 weak_points），不依赖 data-ana gRPC
- **iam DataScope**：内置固定 DataScope（SCHOOL 级），不依赖 iam GetEffectiveDataScope
- **事件订阅**：P5 不订阅任何事件，无 mock 需要

---

## §5 契约待裁决项汇总

> 以下字段待 coord 裁决后最终定稿，ai12 当前按建议方案先行实现。

| 待裁决项                          | ISSUE  | ai12 建议方案                                  | 影响章节       |
| --------------------------------- | ------ | ---------------------------------------------- | -------------- |
| ai gRPC 端口（50057 vs 50058）    | ISSUE-01 | 50058（port-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           |
| 备课工作流 Temporal（P6 决策点）  | ISSUE-06 | P5 用 BackgroundTasks + Redis，P6 评估 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           |
