20 KiB
ai 对接契约
负责人:ai12 关联:matrix.md、port-allocation.md、ai.proto、events.proto、02-architecture-design.md、objections/ai_issue.md 端口权威源:port-allocation.md §3/§5 —— ai = HTTP 3008 / gRPC 50058
本契约已对齐 02-architecture-design.md 设计文档。原 coord 模板的 5 处矛盾已修正(见 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 §5edu.<domain>.v1,见 ISSUE-08。
1.2 HTTP 端点
HTTP 保留作 api-gateway 直连降级 + SSE 流式。api-gateway 代理
/api/v1/ai/*→ ai/ai/v1/*(见 main.py:70 注释 + 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 现状:无AIUsageEventmessage,待 coord 补全(ISSUE-04),建议 schema 见 02-architecture-design.md §3.3。 长期可发布事件(P6+ 评估,待 coord 仲裁):AIContentGenerated(topicedu.ai.generated,生成内容审计)、AIFeedbackRecorded(topicedu.ai.feedback,RLHF 数据)、AIWorkflowEvent(topicedu.ai.workflow,工作流监控)。
1.5 错误码前缀
AI_*(对齐 02-architecture-design.md §6.2 完整清单,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 §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 补
AIUsageEventmessage —— coord(ISSUE-04) - ai 用量事件 topic 命名裁决 —— coord(ISSUE-02)
- content gRPC 50054 启用(ai09)—— 知识点维度 + 题库检索 + 入库
- data-ana gRPC 50055 启用(ai11,可选)—— 学生薄弱点(可降级独立运行)
- iam
GetEffectiveDataScopeRPC 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 §3/§5/§7 权威源,2026-07-09 coord 仲裁"50058 让给 ai")。注意 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 |