# 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..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 RPC(coord 裁决 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` | LLM 聊天 | ✅ 已实现 | | POST | `/v1/ai/chat/stream` | `ai:chat` | SSE stream | 流式聊天 | ✅ 已实现 | | POST | `/v1/ai/generate/question` | `ai:question:generate` | `ActionState` | 生成题目 | ✅ 已实现 | | POST | `/v1/ai/generate/question/stream` | `ai:question:generate` | SSE stream(题目逐字生成) | 题目逐字流式 | ✅ 已实现 | | POST | `/v1/ai/optimize/expression` | `ai:expression:optimize` | `ActionState` | 优化表达 | ✅ 已实现 | | POST | `/v1/ai/lesson-plan/generate` | `ai:lesson:generate` | `ActionState` | 备课工作流启动 | ✅ 已实现 | | GET | `/v1/ai/lesson-plan/status/{workflow_id}` | — | `ActionState` | 查询工作流状态 | ✅ 已实现 | | POST | `/v1/ai/lesson-plan/confirm/{workflow_id}` | `ai:lesson:confirm` | `ActionState` | 教师确认入库 | ✅ 已实现 | > **响应信封**:所有响应使用 ActionState(004 §11.5 强制)。降级采用方案 B(总裁 §2.6):success=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/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 我依赖的上游就绪标志 - [x] ai.proto 升级 v1 完整版(8 RPC + 字段扩展)—— coord(ISSUE-03 已裁决,已实现) - [x] events.proto 补 `AIUsageEvent` message —— coord(ISSUE-04 已裁决,已补全) - [x] ai 用量事件 topic 命名裁决 —— coord(ISSUE-02 已裁决,`edu.ai.usage`) - [ ] content gRPC 50054 启用(ai09)—— 知识点维度 + 题库检索 + 入库(全并行模式用 Mock) - [ ] data-ana gRPC 50055 启用(ai11,可选)—— 学生薄弱点(可降级独立运行,全并行模式用 Mock) - [ ] iam `GetEffectiveDataScope` RPC P4 补全(ai06 + coord,ISSUE-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 返回固定 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 契约裁决项汇总 > 以下 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 + Redis(24h TTL),P6 评估 Temporal** | 总裁 §7.12 + coord A7 | §2.1(无影响) | | iam GetEffectiveDataScope P4 补全 | ISSUE-07 | **P4 补全,ai 用 Mock** | 全并行模式 | §2.1 | | proto package 命名(全局) | ISSUE-08 | **保持 `next_edu_cloud..v1`** | coord G17 裁决覆盖 project_rules §5 | §1.1 | | 响应信封 ActionState 整改 | ISSUE-09 | **ActionState + 方案 B 降级**(success=true + error=null + degraded) | 总裁 §2.6 | §1.2 |