Merge worktree branch merge-15-modules-to-main-5ug5xJ

This commit is contained in:
SpecialX
2026-07-10 15:28:20 +08:00
parent 60d7173545
commit df62ffc176
51 changed files with 11559 additions and 1908 deletions

View File

@@ -1,42 +1,99 @@
# ai 对接契约
> 负责人ai12
> 关联:[matrix.md](./matrix.md)、[ai.proto](../../../packages/shared-proto/proto/ai.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 关联:[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 接口(如有)
### 1.1 gRPC 接口
| Service | RPC | 请求 | 响应 | 端口 |
| --------- | ---------------------- | ----------------------------- | ------------------------ | ----- |
| AiService | Chat | ChatRequest | ChatResponse | 50057 |
| AiService | StreamChat | ChatRequest | stream ChatChunk | 50057 |
| AiService | GenerateQuestion | GenerateQuestionRequest | GeneratedQuestion | 50057 |
| AiService | OptimizeExpression | OptimizeExpressionRequest | OptimizedExpression | 50057 |
| AiService | GenerateLessonPlan | GenerateLessonPlanRequest | LessonPlan | 50057 |
| AiService | StreamGenerateQuestion | StreamGenerateQuestionRequest | stream GeneratedQuestion | 50057 |
| 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 |
### 1.2 HTTP 端点(如有)
> **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。
无对外 HTTP 端点,仅 gRPC含 2 个 Server Streaming RPCStreamChat / StreamGenerateQuestion
### 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 事件发布(如有)
### 1.4 Kafka 事件发布
| Topic | Event | 消费方 |
| ------------------- | ---------------------------------------------------------------------------------------- | -------- |
| edu.ai.usage.events | AIUsageEventoperation: chat/generate_question/optimize_expression/lesson_preparation | data-ana |
| 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}` |
> AIUsageEvent 豁免 Outbox 模式(派生数据事件,见 004 §12.2 + §15.3 #6)。
> **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_`如 AI_PROVIDER_UNAVAILABLE、AI_TOKEN_LIMIT_EXCEEDED、AI_CONTENT_FILTERED
`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 |
---
@@ -44,24 +101,37 @@
### 2.1 gRPC 调用(同步)
| 被调用方 | Service.RPC | 用途 | mock 策略 |
| --------------- | -------------------------------------- | ---------------------------- | ----------------------------------------------------------- |
| content (ai09) | KnowledgeGraphService.GetPrerequisites | 生成题目时获取知识点前置依赖 | content 就绪前使用本地知识点 stub固定 3 个前置知识点) |
| content (ai09) | QuestionService.SearchQuestions | 备课时检索同类题目参考 | content 就绪前返回空列表 |
| data-ana (ai11) | AnalyticsService.GetStudentWeakness | 个性化出题时获取学生薄弱点 | data-ana 就绪前使用本地薄弱点 stub固定 2 个 weak_points |
| 被调用方 | 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 事件订阅(异步)
| Topic | Event | 发布方 | mock 策略 |
| ---------------------------------- | ------------------- | -------------- | -------------------------------------- |
| edu.content.knowledge_point.events | KnowledgePointEvent | content (ai09) | content 就绪前不订阅,使用内置知识点表 |
| edu.content.question.events | QuestionEvent | content (ai09) | content 就绪前忽略 |
> ai 是**无状态服务P5 不消费任何 Kafka 事件**01/02 §5.1 明确)。原 contract 列出的 content 事件订阅已删除(与设计文档矛盾,见 ISSUE-05.5)。
### 2.3 HTTP 调用(如有)
**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/百川/本地 | POST /v1/chat/completions | 调用大模型生成内容 | 开发期使用本地 mock server 返回固定 ChatResponse不消耗真实 token |
| LLM ProviderOpenAI/Anthropic/百川/Ollama | POST /v1/chat/completions | 调用大模型生成内容 | 开发期使用本地 mock server 返回固定 ChatResponse不消耗真实 token |
> 多 Provider 通过 `ProviderFailoverChain` 故障切换OpenAI → Anthropic → 百川 → 本地 Ollama熔断器连续 3 次失败触发 60s 熔断。
---
@@ -69,18 +139,24 @@
### 3.1 我依赖的上游就绪标志
- [ ] content gRPC 50054 启用ai09—— 知识点维度 + 题库检索
- [ ] edu.content.knowledge_point.events topic 有事件发布ai09
- [ ] data-ana gRPC 50055 启用ai11—— 学生薄弱点可选ai 可先独立运行
- [ ] 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 50057 启用HealthService.Check 返回 SERVING
- [ ] ai gRPC 50058 启用HealthService.Check 返回 SERVING
- [ ] AiService.Chat / StreamChat 可调用(含流式响应)
- [ ] AiService.GenerateQuestion / StreamGenerateQuestion 可调用
- [ ] AiService.GenerateLessonPlan 可调用P5 补全)
- [ ] AiService.OptimizeExpression 可调用
- [ ] edu.ai.usage.events topic 可发布(供 data-ana 统计 AI 用量)
- [ ] 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
---
@@ -90,12 +166,13 @@
在 ai 真实服务就绪前为下游teacher-bff提供以下 mock
- **gRPC mock**:使用 grpc-mock 拦截 50057 端口
- **gRPC mock**:使用 grpc-mock 拦截 **50058** 端口
- AiService.Chat 返回固定 ChatResponsecontent="这是 AI 助手的模拟回复"
- AiService.StreamChat 返回固定流3 个 ChatChunk最后一个 done=true
- AiService.GenerateQuestion 返回固定 GeneratedQuestionquestion/answer/explanation
- AiService.GenerateLessonPlan 返回固定 LessonPlan3 个 LessonSection
- AiService.StreamGenerateQuestion 返回固定流2 个 GeneratedQuestion
- AiService.GenerateLessonPlan 返回固定 LessonPlanResponseworkflow_id + 3 个题目
- AiService.StreamGenerateQuestion 返回固定流2 个题目逐字 chunk
- AiService.OptimizeExpression 返回固定 OptimizedExpression
- **Kafka mock**ai 就绪前不发布真实 AIUsageEventdata-ana 仪表盘 AI 用量显示"暂无数据"
### 4.2 我消费的 mock
@@ -105,4 +182,22 @@
- **LLM Provider mock**:本地启动 mock serverPOST /v1/chat/completions 返回固定 JSON不消耗真实 token不产生费用
- **content 知识点**:内置固定知识点表(数学 20 个知识点 + 前置依赖关系),不依赖 content gRPC
- **data-ana 薄弱点**内置固定学生薄弱点2 个 weak_points不依赖 data-ana gRPC
- **事件订阅**:不订阅 content 事件,知识点维度表静态
- **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 |