1.AI 协作文档体系重构(objections/worklines/contracts+matrix.md) 2.coord 仲裁文档(final-decisions/cross-review/final-rulings/orchestration) 3.各服务 01/02 文档补全 4.共享包初始化(shared-ts/shared-go/hooks/ui-components/ui-tokens) 5.Proto 契约补全 6.004 架构影响地图更新 7.端口分配表 8.设计规格文档
1270 lines
71 KiB
Markdown
1270 lines
71 KiB
Markdown
# 模块架构设计文档 — ai
|
||
|
||
> AI 标识:ai12(复核与长期演进接手)← ai06(初稿,2026-07-09)
|
||
> 负责模块:ai(P5)
|
||
> 阶段:架构设计外包 · 阶段 2(模块架构设计,ai06 初稿 + ai12 长期演进复核)
|
||
> 日期:2026-07-09(ai06 初稿)/ 2026-07-09(ai12 复核与补充)
|
||
> 关联文档:[阶段 1 理解确认书](./01-understanding.md)、[ai-allocation.md](../../../docs/architecture/ai-allocation.md)、[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md)、[pending-features.md](../../../docs/architecture/roadmap/pending-features.md)、[coord 交叉审查报告](../../../docs/architecture/coord-cross-review.md)
|
||
> 审查请求:本设计文档待 coord 按 ai-allocation.md §8 交叉审查(接口一致性 / 端口冲突 / Topic 重复 / 错误码重叠 / 黄金模板对齐)
|
||
>
|
||
> **ai12 接手说明**:本设计文档初稿由 ai06 在 ai-allocation v0.x 阶段撰写(涵盖 8 节标准模板)。ai12 在 ai06 初稿基础上执行:作者归属更正、长期架构演进补充(新增 §9-§17 共 9 节)、P5 交付物覆盖度核对、向前兼容性铺垫。**所有 ai12 新增内容用「ai12 增补」标注**,ai06 原文保留并标注「ai06 初稿」。
|
||
|
||
---
|
||
|
||
## 设计原则与全局约束
|
||
|
||
本设计遵循以下强制约束(来自 project_rules.md + coding-standards.md + 004):
|
||
|
||
1. **契约先行**:proto 已定义(analytics.proto / ai.proto),实现前不修改 proto,如需修改走 coord 流程
|
||
2. **CQRS 读写分离**:ai 是无状态服务(无 DB 写),用量计费通过事件外发由 data-ana 落 ClickHouse
|
||
3. **事件驱动**:ai 不消费事件;仅发布 `AIUsageRecorded` 派生数据事件(004 §12.2 豁免 Outbox,允许直接 producer)
|
||
4. **gRPC 优先**:004 §4.1 + §4.2 明确 P5 ai 启用 gRPC server(含 StreamChat 流式 RPC),HTTP 保留作 Gateway 直连降级
|
||
5. **三支柱可观测**:structlog + prometheus-client + OpenTelemetry(已具备,需补业务指标 + gRPC interceptor)
|
||
6. **降级模式**:外部依赖(LLM / 下游 gRPC / Redis)不可用时返回骨架数据 + ActionState `details: {degraded: true}`
|
||
7. **Python 规范**:pydantic-settings 配置 / Pydantic 模型校验 / async 优先 / 类型注解强制 / ruff 零错误
|
||
8. **响应信封**(ai12 增补):004 §11.5 强制 ActionState,禁止 `{success, data, degraded}` 偏离结构
|
||
9. **端口规范**(ai12 增补):HTTP 3008 + gRPC 50058(HTTP+10050 规则),提请 coord 在 004 §1.2 + `infra/port-allocation.md` 补登
|
||
10. **错误码前缀**(ai12 增补):`AI_*`(004 §11.4 错误码前缀矩阵已确认 ai 前缀为 `AI_`)
|
||
|
||
---
|
||
|
||
## 1. 模块内部分层图
|
||
|
||
### 1.1 ai06 初稿:分层图
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph Entry["入口层"]
|
||
HTTP[FastAPI HTTP Router<br/>/ai/* + /healthz + /readyz]
|
||
GRPC[grpc.aio Server<br/>AiService 含 StreamChat]
|
||
end
|
||
|
||
subgraph Middleware["中间件层"]
|
||
AUTH[AuthDepends<br/>校验 x-user-id / x-user-roles]
|
||
RATE[RateLimitDepends<br/>Redis 令牌桶限流]
|
||
TRACE[OTel + grpc interceptor]
|
||
end
|
||
|
||
subgraph Service["应用服务层"]
|
||
S1[ChatService<br/>聊天编排 + Prompt 模板渲染]
|
||
S2[QuestionGenerationService<br/>出题工作流编排]
|
||
S3[ExpressionOptimizationService<br/>表达优化]
|
||
S4[LessonPreparationWorkflow<br/>备课工作流 4 步编排]
|
||
end
|
||
|
||
subgraph Provider["LLM Provider 适配层"]
|
||
P0[LLMProvider 抽象接口<br/>chat / stream_chat]
|
||
P1[OpenAIProvider<br/>httpx 异步]
|
||
P2[AnthropicProvider<br/>httpx 异步]
|
||
P3[BaichuanProvider<br/>httpx 异步]
|
||
P4[LocalOllamaProvider<br/>httpx 异步]
|
||
end
|
||
|
||
subgraph Template["Prompt 模板管理"]
|
||
T1[PromptTemplateRegistry<br/>模板注册 + 渲染]
|
||
T2[模板存储<br/>YAML 文件 / DB]
|
||
end
|
||
|
||
subgraph Client["下游 gRPC client"]
|
||
C1[ContentClient<br/>查询知识点 / 题库]
|
||
C2[DataAnaClient<br/>查询学情 / 薄弱点]
|
||
end
|
||
|
||
subgraph Usage["用量计费"]
|
||
U1[UsageRecorder<br/>token 消耗统计]
|
||
U2[KafkaProducer<br/>发布 edu.insight.ai.usage]
|
||
end
|
||
|
||
subgraph External["外部 / 存储"]
|
||
LLM[LLM Provider API<br/>OpenAI/Anthropic/百川/Ollama]
|
||
CONTENT[content:3005 gRPC]
|
||
DATAANA[data-ana:3006 gRPC]
|
||
KAFKA[(Kafka)]
|
||
REDIS[(Redis<br/>限流 + 缓存)]
|
||
end
|
||
|
||
HTTP --> AUTH --> RATE --> S1
|
||
HTTP --> S2
|
||
HTTP --> S3
|
||
GRPC --> S1
|
||
GRPC --> S2
|
||
S2 --> S4
|
||
S4 --> C1
|
||
S4 --> C2
|
||
S1 --> T1
|
||
S2 --> T1
|
||
S1 --> P0
|
||
S2 --> P0
|
||
P0 --> P1
|
||
P0 --> P2
|
||
P0 --> P3
|
||
P0 --> P4
|
||
P1 --> LLM
|
||
P2 --> LLM
|
||
P3 --> LLM
|
||
P4 --> LLM
|
||
S1 --> U1
|
||
S2 --> U1
|
||
U1 --> U2
|
||
U2 --> KAFKA
|
||
RATE --> REDIS
|
||
C1 --> CONTENT
|
||
C2 --> DATAANA
|
||
```
|
||
|
||
### 1.2 ai12 增补:完整分层图(含安全 / 评估 / 缓存 / 工作流持久化)
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph Entry["入口层"]
|
||
HTTP[FastAPI HTTP Router<br/>/ai/v1/* + /healthz + /readyz + /metrics]
|
||
GRPC[grpc.aio Server<br/>AiService 含 StreamChat/StreamGenerateQuestion]
|
||
end
|
||
|
||
subgraph Middleware["中间件层"]
|
||
AUTH[AuthDepends<br/>JWT claims → UserContext]
|
||
RATE[RateLimitDepends<br/>Redis 多维度令牌桶<br/>user/IP/school/token]
|
||
TRACE[OTel FastAPIInstrumentor<br/>+ grpc.aio ServerInterceptor<br/>+ grpc.aio ClientInterceptor]
|
||
REQID[RequestContextMiddleware<br/>X-Request-Id 注入与透传]
|
||
end
|
||
|
||
subgraph Security["安全层 ai12 增补"]
|
||
PII[PIIRedactor<br/>学生姓名/手机号脱敏]
|
||
SAN[InputSanitizer<br/>Prompt 注入防御]
|
||
MOD[OutputModerator<br/>敏感词过滤 + 安全校验]
|
||
end
|
||
|
||
subgraph Service["应用服务层"]
|
||
S1[ChatService<br/>聊天编排 + Prompt 渲染]
|
||
S2[QuestionGenerationService<br/>出题 + 逐字流式生成]
|
||
S3[ExpressionOptimizationService<br/>表达优化]
|
||
S4[LessonPreparationWorkflow<br/>备课工作流 4 步编排]
|
||
S5[PromptTemplateService<br/>模板 CRUD + 版本化]
|
||
S6[UsageService<br/>用量查询 + 配额校验]
|
||
end
|
||
|
||
subgraph Provider["LLM Provider 适配层"]
|
||
P0[LLMProvider 抽象接口<br/>chat / stream_chat / embed?]
|
||
P1[OpenAIProvider]
|
||
P2[AnthropicProvider]
|
||
P3[BaichuanProvider]
|
||
P4[LocalOllamaProvider]
|
||
PF[ProviderFailoverChain<br/>多 Provider 故障切换]
|
||
end
|
||
|
||
subgraph Template["Prompt 模板管理"]
|
||
T1[PromptTemplateRegistry<br/>模板注册 + Jinja2 渲染]
|
||
T2[模板存储<br/>YAML 文件 P5 / DB P6+]
|
||
T3[模板版本化 + A/B 路由 P6+]
|
||
end
|
||
|
||
subgraph Evaluation["评估层 ai12 增补"]
|
||
E1[RuleValidator<br/>题型/答案/解析规则校验]
|
||
E2[LLMJudge<br/>LLM-as-judge 质量评分]
|
||
E3[QualityGate<br/>质量门禁 不达标重新生成或降级]
|
||
end
|
||
|
||
subgraph Workflow["工作流持久化 ai12 增补"]
|
||
W1[WorkflowStateStore<br/>Redis 短期 24h TTL P5<br/>Temporal P6+ 评估]
|
||
W2[WorkflowEventLog<br/>工作流步骤事件日志]
|
||
end
|
||
|
||
subgraph Cache["缓存层 ai12 增补"]
|
||
CA1[PromptCache<br/>hash 缓存 P5]
|
||
CA2[SemanticCache<br/>embedding 相似度 P6+]
|
||
CA3[TemplateCache<br/>模板缓存 1h TTL]
|
||
end
|
||
|
||
subgraph Client["下游 gRPC client"]
|
||
C1[ContentClient<br/>查询知识点/教材/题库]
|
||
C2[DataAnaClient<br/>查询学情/薄弱点/趋势]
|
||
C3[IamClient<br/>查询 DataScope P4+]
|
||
end
|
||
|
||
subgraph Usage["用量计费"]
|
||
U1[UsageRecorder<br/>token 消耗统计]
|
||
U2[KafkaProducer<br/>发布 edu.insight.ai.usage]
|
||
U3[QuotaEnforcer<br/>学校/年级/教师配额校验]
|
||
end
|
||
|
||
subgraph External["外部 / 存储"]
|
||
LLM[LLM Provider API<br/>OpenAI/Anthropic/百川/Ollama]
|
||
CONTENT[content:3005 gRPC]
|
||
DATAANA[data-ana:3006 gRPC]
|
||
IAM[iam:3002 gRPC]
|
||
KAFKA[(Kafka)]
|
||
REDIS[(Redis<br/>限流/缓存/工作流状态)]
|
||
end
|
||
|
||
HTTP --> REQID --> AUTH --> RATE --> Security
|
||
GRPC --> Security
|
||
Security --> S1 & S2 & S3 & S4 & S5 & S6
|
||
S1 & S2 --> T1
|
||
S1 & S2 --> CA1
|
||
S1 & S2 --> P0
|
||
P0 --> PF
|
||
PF --> P1 & P2 & P3 & P4
|
||
P1 & P2 & P3 & P4 --> LLM
|
||
S2 --> E1 --> E2 --> E3
|
||
S2 --> CA1
|
||
S4 --> C1 & C2
|
||
S4 --> W1
|
||
S4 --> W2
|
||
S5 --> T2
|
||
S5 --> CA3
|
||
S6 --> U3
|
||
S1 & S2 & S3 --> U1 --> U2 --> KAFKA
|
||
RATE --> REDIS
|
||
CA1 & CA3 & W1 --> REDIS
|
||
C1 --> CONTENT
|
||
C2 --> DATAANA
|
||
C3 --> IAM
|
||
```
|
||
|
||
**ai12 增补:分层规则**
|
||
|
||
- **入口层**:HTTP(保留作 Gateway 直连降级 + 前端 SSE 流式)+ gRPC(主入口,BFF 调用,含 `StreamChat` / `StreamGenerateQuestion` 流式 RPC)
|
||
- **中间件层**:RequestContextMiddleware(X-Request-Id)→ AuthDepends(JWT claims 解析)→ RateLimitDepends(多维度限流)→ OTel 自动埋点
|
||
- **安全层**(ai12 增补):PIIRedactor → InputSanitizer → OutputModerator,所有进出 LLM 的数据必须经过安全层
|
||
- **应用服务层**:6 个 Service,每个对应一类 AI 能力
|
||
- **Provider 适配层**:抽象 `LLMProvider` 接口 + 多适配器实现(策略模式)+ `ProviderFailoverChain` 故障切换(resilience 模式)
|
||
- **Prompt 模板**:模板注册 + Jinja2 渲染 + 版本化(P5 YAML,P6+ DB + A/B)
|
||
- **评估层**(ai12 增补):规则校验 + LLM-as-judge + 质量门禁,构成生成质量保障三道防线
|
||
- **工作流持久化**(ai12 增补):Redis 短期(P5)+ Temporal(P6+ 评估),保证长运行工作流状态不丢失
|
||
- **缓存层**(ai12 增补):Prompt hash 缓存(P5)+ 语义缓存(P6+ 评估)+ 模板缓存
|
||
- **下游 client**:gRPC 调 content / data-ana / iam(DataScope),均带 client interceptor(trace + retry + circuit breaker)
|
||
- **用量计费**:token 消耗统计 + Kafka 事件外发 + 配额校验
|
||
|
||
## 2. 领域模型
|
||
|
||
ai 是**无状态服务**,不持有持久化聚合根。领域模型为**请求/响应模型 + 工作流编排**:
|
||
|
||
### 2.1 聚合根(请求型,无持久化)(ai06 初稿)
|
||
|
||
| 聚合根 | 含义 | 生命周期 |
|
||
| ---------------------------- | ------------ | ------------------------------------------------------------ |
|
||
| `ChatConversation` | 单次聊天请求 | 单次请求 |
|
||
| `QuestionGenerationTask` | 出题任务 | 单次请求(备课工作流中多步) |
|
||
| `ExpressionOptimizationTask` | 表达优化任务 | 单次请求 |
|
||
| `LessonPreparationWorkflow` | 备课工作流 | 跨多步(分析学情 → 推荐知识点 → 生成题目 → 教师审核 → 入库) |
|
||
|
||
### 2.2 ai12 增补:扩展聚合根与值对象
|
||
|
||
| 聚合根 | 含义 | 生命周期 | 持久化 |
|
||
| ------------------ | ---------------- | ------------------- | ---------------------------------------- |
|
||
| `PromptTemplate` | Prompt 模板 | 持久化(YAML/DB) | YAML 文件 P5 / DB P6+ |
|
||
| `UsageRecord` | 用量记录 | 单次 LLM 调用 | 不持久化,Kafka 事件外发 |
|
||
| `EvaluationResult` | 生成结果质量评估 | 单次生成调用 | 不持久化(P6+ 评估落 ClickHouse 供分析) |
|
||
| `WorkflowState` | 工作流状态 | 跨多步(数小时/天) | Redis 短期 P5 / Temporal P6+ |
|
||
|
||
### 2.3 值对象
|
||
|
||
- `ChatMessage`:role + content
|
||
- `Usage`:prompt_tokens + completion_tokens + total_tokens + latency_ms
|
||
- `GeneratedQuestion`:question + answer + explanation + question_type + difficulty + knowledge_point_ids
|
||
- `PromptTemplate`:name + system_prompt + user_template + variables + version
|
||
- **ai12 增补**:`UserContext`:user_id + roles + data_scope + school_id + request_id
|
||
- **ai12 增补**:`ProviderConfig`:provider_name + model + api_key + base_url + timeout + max_retries
|
||
- **ai12 增补**:`EvaluationReport`:rule_score + judge_score + overall_score + reasons + pass
|
||
- **ai12 增补**:`WorkflowStep`:step_name + status + started_at + completed_at + result + error
|
||
- **ai12 增补**:`QuotaContext`:school_id + grade_id + teacher_id + budget + used + remaining
|
||
|
||
### 2.4 ai12 增补:备课工作流状态机
|
||
|
||
备课工作流是 ai 服务最复杂的工作流,明确状态机避免实现混乱:
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> Pending: 教师发起备课请求
|
||
Pending --> Analyzing: 工作流启动
|
||
Analyzing --> Analyzed: 查询 data-ana 学情成功
|
||
Analyzing --> AnalyzeFailed: data-ana 不可达 + 降级跳过
|
||
AnalyzeFailed --> Recommending: 降级(仅基于 prompt 生成)
|
||
Analyzed --> Recommending: 查询 content 知识点
|
||
Recommending --> Recommended: 知识点推荐完成
|
||
Recommended --> Generating: 启动 LLM 出题
|
||
Generating --> Generated: LLM 生成完成 + 评估通过
|
||
Generating --> Regenerating: 评估未通过 + 重试 < 3 次
|
||
Regenerating --> Generated: 重试生成通过
|
||
Regenerating --> GenerationFailed: 重试 ≥ 3 次仍失败
|
||
GenerationFailed --> [*]: 返回错误 + 工作流终止
|
||
Generated --> PendingReview: 暂存题目 + 通知教师审核
|
||
PendingReview --> Confirmed: 教师确认入库
|
||
PendingReview --> Modified: 教师修改后确认
|
||
PendingReview --> Rejected: 教师拒绝
|
||
PendingReview --> Expired: 24h 未审核
|
||
Confirmed --> Persisting: 调 content.CreateQuestions
|
||
Modified --> Persisting: 调 content.CreateQuestions
|
||
Persisting --> Persisted: 入库成功
|
||
Persisting --> PersistFailed: 入库失败
|
||
PersistFailed --> Persisting: 重试(最多 3 次)
|
||
PersistFailed --> [*]: 重试失败 + 告警
|
||
Persisted --> [*]: 工作流完成
|
||
Rejected --> [*]: 工作流终止
|
||
Expired --> [*]: 工作流过期
|
||
```
|
||
|
||
**ai12 增补:状态持久化策略**
|
||
|
||
| 状态 | P5 持久化 | P6+ 持久化 |
|
||
| ---------------------------------- | ----------------------------------------- | ----------------------------------- |
|
||
| Pending-Analyzing-Generated | Redis(key: `ai:workflow:{id}`,TTL 24h) | Temporal workflow history |
|
||
| PendingReview | Redis + 通知 BFF(前端轮询/SSE) | Temporal signal(教师审核事件) |
|
||
| 终态(Persisted/Rejected/Expired) | Redis 标记 + 事件日志 | Temporal workflow 完成 + ClickHouse |
|
||
|
||
**ai12 增补:工作流事件日志**(供审计 + RLHF 数据收集)
|
||
|
||
```json
|
||
{
|
||
"workflow_id": "uuid",
|
||
"user_id": "user-xxx",
|
||
"school_id": "school-xxx",
|
||
"step": "Generating",
|
||
"status": "completed",
|
||
"duration_ms": 1200,
|
||
"llm_provider": "openai",
|
||
"llm_model": "gpt-4o-mini",
|
||
"tokens_used": 230,
|
||
"evaluation_score": 0.85,
|
||
"occurred_at": "2026-07-09T12:00:00Z"
|
||
}
|
||
```
|
||
|
||
### 2.5 ai12 增补:备课工作流序列图(细化 004 §9.3)
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant T as 教师
|
||
participant BFF as teacher-bff
|
||
participant AI as ai 服务
|
||
participant IAM as iam
|
||
participant DA as data-ana
|
||
participant Content as content
|
||
participant LLM as LLM Provider
|
||
participant Redis as Redis(工作流状态)
|
||
participant Kafka as Kafka
|
||
|
||
T->>BFF: POST /lesson/preparation {class_id, subject_id, topic}
|
||
BFF->>AI: gRPC GenerateLessonPlan
|
||
AI->>IAM: gRPC GetEffectiveDataScope(user_id)
|
||
IAM-->>AI: DataScope
|
||
AI->>Redis: 创建工作流(状态=Pending)
|
||
AI->>AI: 工作流启动(状态=Analyzing)
|
||
par 并行查询
|
||
AI->>DA: gRPC GetClassPerformance(class_id, subject_id)
|
||
DA-->>AI: 班级学情
|
||
and
|
||
AI->>DA: gRPC GetStudentWeakness(student_id, subject_id)
|
||
DA-->>AI: 薄弱知识点
|
||
end
|
||
AI->>Content: gRPC GetPrerequisites(knowledge_point_id)
|
||
Content-->>AI: 前置依赖知识点
|
||
AI->>Redis: 更新工作流(状态=Recommended)
|
||
AI->>AI: 渲染 Prompt 模板
|
||
AI->>LLM: stream_chat(题目逐字生成)
|
||
loop 流式 chunks
|
||
LLM-->>AI: chunk
|
||
AI-->>BFF: stream chunk(透传给前端)
|
||
end
|
||
AI->>AI: 规则校验 + LLM-as-judge
|
||
alt 评估通过
|
||
AI->>Redis: 更新工作流(状态=PendingReview)
|
||
AI-->>BFF: 题目列表 + workflow_id
|
||
BFF-->>T: 题目供审核
|
||
else 评估未通过 + 重试 < 3
|
||
AI->>AI: 调整 Prompt + 重新生成
|
||
else 重试 ≥ 3 仍失败
|
||
AI-->>BFF: 错误响应
|
||
end
|
||
AI->>Kafka: 发布 AIUsageRecorded 事件
|
||
T->>BFF: POST /lesson/preparation/{id}/confirm
|
||
BFF->>AI: gRPC ConfirmLessonPlan(workflow_id)
|
||
AI->>Redis: 更新工作流(状态=Persisting)
|
||
AI->>Content: gRPC CreateQuestions(questions)
|
||
Content-->>AI: 入库成功
|
||
AI->>Redis: 更新工作流(状态=Persisted)
|
||
AI-->>BFF: 入库结果
|
||
BFF-->>T: 入库成功
|
||
```
|
||
|
||
## 3. 数据模型
|
||
|
||
### 3.1 ai06 初稿:无 DB 设计
|
||
|
||
ai 服务**无独占数据库**,无 MySQL schema。所有数据通过 Kafka 事件外发(用量计费)或 gRPC 查询下游。
|
||
|
||
### 3.2 ai12 增补:Redis schema 详细设计
|
||
|
||
虽然 ai 无 DB,但 Redis 承载多种数据结构,需明确 schema:
|
||
|
||
| Key 模式 | 数据类型 | TTL | 用途 |
|
||
| ------------------------------------- | -------- | -------- | -------------------------------- |
|
||
| `ai:ratelimit:user:{user_id}` | Hash | 滑动窗口 | 用户级令牌桶(每分钟 10 次) |
|
||
| `ai:ratelimit:ip:{ip}` | Hash | 滑动窗口 | IP 级令牌桶(每分钟 30 次) |
|
||
| `ai:ratelimit:school:{school_id}` | Hash | 滑动窗口 | 学校级令牌桶(每分钟 100 次) |
|
||
| `ai:quota:school:{school_id}` | Hash | 月度重置 | 学校月度 token 预算 + 已用 |
|
||
| `ai:quota:teacher:{user_id}` | Hash | 月度重置 | 教师月度 token 预算 + 已用 |
|
||
| `ai:cache:prompt:{hash}` | String | 30 分钟 | LLM 响应缓存(hash 缓存) |
|
||
| `ai:cache:template:{template_name}` | String | 1 小时 | Prompt 模板缓存 |
|
||
| `ai:cache:datascope:{user_id}` | String | 5 分钟 | DataScope 缓存(避免重复查 iam) |
|
||
| `ai:workflow:{workflow_id}` | Hash | 24 小时 | 备课工作流状态 |
|
||
| `ai:workflow:user:{user_id}` | Set | 24 小时 | 用户的活跃工作流 ID 集合 |
|
||
| `ai:dedup:event:{event_id}` | String | 7 天 | 事件去重(SETNX) |
|
||
| `ai:circuit:provider:{provider_name}` | String | 60 秒 | Provider 熔断状态 |
|
||
|
||
### 3.3 ai12 增补:用量计费 Kafka 事件 payload(建议 events.proto 新增 `AIUsageEvent`)
|
||
|
||
```protobuf
|
||
// 建议 coord 在 events.proto 新增(ai12 提请,待仲裁)
|
||
message AIUsageEvent {
|
||
string event_id = 1; // UUID,幂等去重
|
||
string aggregate_id = 2; // workflow_id 或 request_id
|
||
string event_type = 3; // 固定 "AIUsageRecorded"
|
||
int64 occurred_at = 4; // Unix 毫秒时间戳
|
||
string user_id = 5;
|
||
string school_id = 6; // 用于多租户配额
|
||
string request_id = 7; // 链路追踪 ID
|
||
string provider = 8; // openai/anthropic/baichuan/local
|
||
string model = 9; // gpt-4o-mini/claude-3-haiku/...
|
||
string operation = 10; // chat/generate_question/optimize_expression/lesson_preparation
|
||
uint32 prompt_tokens = 11;
|
||
uint32 completion_tokens = 12;
|
||
uint32 total_tokens = 13;
|
||
uint32 latency_ms = 14;
|
||
bool success = 15;
|
||
bool degraded = 16; // 是否降级(LLM 不可用时)
|
||
map<string, string> metadata = 17; // 额外上下文(subject/grade/difficulty 等)
|
||
}
|
||
```
|
||
|
||
### 3.4 ai12 增补:缓存策略
|
||
|
||
| 数据 | 存储 | TTL | 失效策略 |
|
||
| ---------------------------- | ------------------------ | -------- | -------------------------------------------- |
|
||
| Prompt 模板 | Redis(template cache) | 1 小时 | 文件/DB 变更时主动失效(pub/sub 通知) |
|
||
| LLM 响应(相同 prompt hash) | Redis(prompt cache) | 30 分钟 | 短 TTL,避免陈旧;用户反馈差时主动失效 |
|
||
| DataScope(避免重复查 iam) | Redis(datascope cache) | 5 分钟 | 短 TTL,权限变更时由 iam 主动失效(pub/sub) |
|
||
| 限流计数 | Redis(ratelimit hash) | 滑动窗口 | 自动过期 |
|
||
| 配额计数 | Redis(quota hash) | 月度重置 | 月初定时任务重置 |
|
||
| 工作流状态 | Redis(workflow hash) | 24 小时 | 工作流终态后保留 1h 再过期(供审计) |
|
||
| **P6+ 语义缓存** | Redis + 向量索引 | 30 分钟 | embedding 相似度 > 0.95 命中 |
|
||
|
||
## 4. API 设计
|
||
|
||
### 4.1 HTTP 端点(保留作 Gateway 直连降级,ai06 初稿 + ai12 增补)
|
||
|
||
> **ai12 增补**:所有路径加 `/v1` 版本前缀(P5 起强制),便于未来破坏性变更;响应统一为 ActionState
|
||
|
||
| method | path | 权限 | 请求体 | 响应 | 说明 |
|
||
| ------ | ------------------------------------------------- | ------------------------ | ----------------------------- | -------------------------------------- | --------------------------------- |
|
||
| GET | `/healthz` | — | — | `{status, service}` | liveness |
|
||
| GET | `/readyz` | — | — | `{status, llm_configured, ...}` | readiness |
|
||
| GET | `/metrics` | — | — | Prometheus | 指标 |
|
||
| POST | `/ai/v1/chat` | `AI_CHAT` | `ChatRequest` | `ActionState<ChatData>` | LLM 聊天 |
|
||
| POST | `/ai/v1/chat/stream` | `AI_CHAT` | `ChatRequest` | SSE stream | 流式聊天 |
|
||
| POST | `/ai/v1/generate/question` | `AI_QUESTION_GENERATE` | `GenerateQuestionRequest` | `ActionState<GeneratedQuestionData>` | 生成题目 |
|
||
| POST | `/ai/v1/generate/question/stream` | `AI_QUESTION_GENERATE` | `GenerateQuestionRequest` | SSE stream(题目逐字生成) | **ai12 增补**:题目逐字流式 |
|
||
| POST | `/ai/v1/optimize/expression` | `AI_EXPRESSION_OPTIMIZE` | `OptimizeExpressionRequest` | `ActionState<OptimizedExpressionData>` | 优化表达 |
|
||
| POST | `/ai/v1/lesson/preparation` | `AI_LESSON_PREPARE` | `LessonPreparationRequest` | `ActionState<LessonPreparationData>` | **ai12 增补**:备课工作流启动 |
|
||
| GET | `/ai/v1/lesson/preparation/{workflow_id}` | `AI_LESSON_PREPARE` | — | `ActionState<WorkflowState>` | **ai12 增补**:查询工作流状态 |
|
||
| POST | `/ai/v1/lesson/preparation/{workflow_id}/confirm` | `AI_LESSON_PREPARE` | `ConfirmRequest` | `ActionState<PersistResult>` | **ai12 增补**:教师确认入库 |
|
||
| GET | `/ai/v1/prompts` | `AI_PROMPT_READ` | query: `?page&size` | `ActionState<Page<TemplateSummary>>` | **ai12 增补**:模板列表 |
|
||
| POST | `/ai/v1/prompts` | `AI_PROMPT_CREATE` | `CreatePromptRequest` | `ActionState<PromptTemplate>` | **ai12 增补**:创建模板 |
|
||
| GET | `/ai/v1/prompts/{id}` | `AI_PROMPT_READ` | — | `ActionState<PromptTemplate>` | **ai12 增补**:获取模板 |
|
||
| PUT | `/ai/v1/prompts/{id}` | `AI_PROMPT_UPDATE` | `UpdatePromptRequest` | `ActionState<PromptTemplate>` | **ai12 增补**:更新模板(版本化) |
|
||
| GET | `/ai/v1/usage/me` | `AI_USAGE_READ` | query: `?start_date&end_date` | `ActionState<UsageSummary>` | **ai12 增补**:当前用户用量 |
|
||
| GET | `/ai/v1/usage/school/{school_id}` | `AI_USAGE_READ_ALL` | query: `?start_date&end_date` | `ActionState<UsageSummary>` | **ai12 增补**:学校用量(管理员) |
|
||
|
||
### 4.2 gRPC 契约(ai.proto,待实现 server,ai06 初稿 + ai12 增补)
|
||
|
||
| RPC | 请求 | 响应 | 权限 | 说明 |
|
||
| ------------------------ | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | ------------------------ | ------------------------------- |
|
||
| `Chat` | `ChatRequest{messages, model, temperature, user_id?, session_id?, data_scope?}` | `ChatResponse{content, model, usage}` | `AI_CHAT` | 非流式聊天 |
|
||
| `StreamChat` | `ChatRequest` | `stream ChatChunk` | `AI_CHAT` | 流式聊天(SSE over gRPC) |
|
||
| `GenerateQuestion` | `GenerateQuestionRequest{prompt, subject, difficulty, grade?, knowledge_point_ids?, question_type?, count?}` | `GeneratedQuestion` | `AI_QUESTION_GENERATE` | 生成题目 |
|
||
| `StreamGenerateQuestion` | `GenerateQuestionRequest` | `stream GeneratedQuestionChunk` | `AI_QUESTION_GENERATE` | **ai12 增补**:题目逐字流式生成 |
|
||
| `OptimizeExpression` | `OptimizeExpressionRequest{text, context}` | `OptimizedExpression` | `AI_EXPRESSION_OPTIMIZE` | 优化表达 |
|
||
| `GenerateLessonPlan` | `GenerateLessonPlanRequest{class_id, subject_id, topic, user_id, data_scope}` | `LessonPlanResponse{workflow_id, status, questions?}` | `AI_LESSON_PREPARE` | **ai12 增补**:备课工作流启动 |
|
||
| `GetLessonPlanStatus` | `GetLessonPlanStatusRequest{workflow_id}` | `LessonPlanStatus{workflow_id, status, questions?, error?}` | `AI_LESSON_PREPARE` | **ai12 增补**:查询工作流状态 |
|
||
| `ConfirmLessonPlan` | `ConfirmLessonPlanRequest{workflow_id, modifications?}` | `ConfirmResult{success, persisted_question_ids?}` | `AI_LESSON_PREPARE` | **ai12 增补**:教师确认入库 |
|
||
|
||
### 4.3 ai12 增补:Pydantic 请求/响应模型(ActionState 对齐)
|
||
|
||
```python
|
||
from typing import Any, Literal
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
# === ActionState 信封(004 §11.5 强制约束) ===
|
||
class ErrorDetail(BaseModel):
|
||
code: str
|
||
message: str
|
||
details: dict[str, Any] | None = None
|
||
trace_id: str | None = None
|
||
|
||
|
||
class ActionState(BaseModel):
|
||
"""统一响应信封,所有响应必须使用此结构."""
|
||
success: bool
|
||
data: Any | None = None
|
||
error: ErrorDetail | None = None
|
||
|
||
|
||
# === 聊天 ===
|
||
class ChatMessage(BaseModel):
|
||
role: Literal["system", "user", "assistant"]
|
||
content: str = Field(..., min_length=1, max_length=8000)
|
||
|
||
|
||
class ChatRequest(BaseModel):
|
||
messages: list[ChatMessage] = Field(..., min_length=1, max_length=50)
|
||
model: str = "gpt-4o-mini"
|
||
temperature: float = Field(0.7, ge=0.0, le=2.0)
|
||
stream: bool = False
|
||
|
||
|
||
class Usage(BaseModel):
|
||
prompt_tokens: int
|
||
completion_tokens: int
|
||
total_tokens: int
|
||
latency_ms: int
|
||
|
||
|
||
class ChatData(BaseModel):
|
||
content: str
|
||
model: str
|
||
usage: Usage
|
||
|
||
|
||
# === 题目生成 ===
|
||
class GenerateQuestionRequest(BaseModel):
|
||
prompt: str = Field(..., min_length=1, max_length=2000)
|
||
subject: str = Field(..., max_length=50)
|
||
difficulty: Literal["easy", "medium", "hard"]
|
||
grade: str | None = Field(None, max_length=20)
|
||
knowledge_point_ids: list[str] = Field(default_factory=list, max_length=20)
|
||
question_type: Literal["single_choice", "multi_choice", "fill_blank", "short_answer", "essay"] = "short_answer"
|
||
count: int = Field(1, ge=1, le=10)
|
||
|
||
|
||
class GeneratedQuestionData(BaseModel):
|
||
question: str
|
||
answer: str
|
||
explanation: str
|
||
question_type: str
|
||
difficulty: str
|
||
knowledge_point_ids: list[str] = []
|
||
evaluation_score: float | None = None # ai12 增补:质量评分
|
||
|
||
|
||
# === 备课工作流 ===
|
||
class LessonPreparationRequest(BaseModel):
|
||
class_id: str
|
||
subject_id: str
|
||
topic: str = Field(..., max_length=500)
|
||
target_difficulty: Literal["easy", "medium", "hard"] = "medium"
|
||
question_count: int = Field(5, ge=1, le=20)
|
||
|
||
|
||
class LessonPreparationData(BaseModel):
|
||
workflow_id: str
|
||
status: Literal["pending", "analyzing", "generating", "pending_review", "persisted", "failed"]
|
||
estimated_completion_seconds: int
|
||
|
||
|
||
class ConfirmRequest(BaseModel):
|
||
modifications: dict[str, str] | None = None # question_id → 修改后内容
|
||
```
|
||
|
||
### 4.4 ai12 增补:Prompt 模板 YAML 格式
|
||
|
||
```yaml
|
||
# services/ai/src/ai/prompts/generate_question.yaml
|
||
name: generate_question
|
||
version: "1.0.0"
|
||
description: 通用出题 Prompt 模板
|
||
variables:
|
||
- name: subject
|
||
description: 学科
|
||
required: true
|
||
- name: difficulty
|
||
description: 难度
|
||
required: true
|
||
allowed: ["easy", "medium", "hard"]
|
||
- name: grade
|
||
description: 年级
|
||
required: false
|
||
- name: knowledge_points
|
||
description: 知识点列表
|
||
required: false
|
||
- name: question_type
|
||
description: 题型
|
||
required: true
|
||
- name: count
|
||
description: 题目数量
|
||
required: true
|
||
system_prompt: |
|
||
你是一名经验丰富的{subject}教师,擅长根据学情出题。
|
||
出题要求:
|
||
1. 难度:{difficulty}
|
||
2. 题型:{question_type}
|
||
3. 数量:{count} 道
|
||
{%- if grade %}
|
||
4. 年级:{grade}
|
||
{%- endif %}
|
||
{%- if knowledge_points %}
|
||
5. 必须覆盖以下知识点:{knowledge_points}
|
||
{%- endif %}
|
||
输出格式:JSON 数组,每道题包含 question/answer/explanation 三个字段。
|
||
user_template: |
|
||
请根据以上要求出题。
|
||
```
|
||
|
||
## 5. 事件设计
|
||
|
||
### 5.1 消费的事件
|
||
|
||
ai 服务**不消费任何事件**(无状态,纯请求-响应)。
|
||
|
||
**ai12 增补:长期可考虑消费的事件**(P6+ 评估)
|
||
|
||
- `edu.content.question.published`:题库新增题目事件,可用于"题目查重"——避免 AI 生成与已有题目重复
|
||
- `edu.identity.user.role_changed`:用户角色变更,主动失效 DataScope 缓存(替代当前定时 5min TTL 失效)
|
||
|
||
### 5.2 发布的事件
|
||
|
||
| 事件 | Topic | 触发时机 | 消费者 | Payload |
|
||
| ----------------- | ----------------------------------------- | ----------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `AIUsageRecorded` | `edu.insight.ai.usage`(004 §7.2 已确认) | 每次 LLM 调用完成 | data-ana(落 ClickHouse ai_usage_log) | `{event_id, user_id, school_id, request_id, provider, model, operation, prompt_tokens, completion_tokens, total_tokens, latency_ms, success, degraded, metadata}` |
|
||
|
||
**ai12 增补:长期可发布事件**(P6+ 评估,待 coord 仲裁)
|
||
|
||
| 事件 | Topic | 触发时机 | 消费者 | 用途 |
|
||
| -------------------- | -------------------------- | ------------------ | -------------- | ------------------------ |
|
||
| `AIContentGenerated` | `edu.insight.ai.generated` | 生成内容审计 | data-ana | 落 ClickHouse 供质量分析 |
|
||
| `AIFeedbackRecorded` | `edu.insight.ai.feedback` | 教师对生成结果反馈 | data-ana | RLHF 数据集构建 |
|
||
| `AIWorkflowEvent` | `edu.insight.ai.workflow` | 工作流步骤状态变更 | data-ana + msg | 工作流监控 + 失败告警 |
|
||
|
||
**发布实现**:
|
||
|
||
- 每次 LLM 调用后异步发布(不阻塞响应)
|
||
- `aiokafka.AIOKafkaProducer` + `acks=all` + `idempotent=true` + `transactional_id`
|
||
- 失败重试 3 次(指数退避:1s/2s/4s),仍失败记录日志(不影响主流程,用量计费为派生数据可丢失)
|
||
- **幂等性**:`event_id` UUID + Redis SETNX 去重(TTL 7 天),防止 producer 重试导致重复投递
|
||
|
||
## 6. 横切关注点对齐清单
|
||
|
||
### 6.1 权限装饰器等价物(ai06 初稿 + ai12 增补)
|
||
|
||
```python
|
||
class Permissions:
|
||
AI_CHAT = "ai:chat"
|
||
AI_QUESTION_GENERATE = "ai:question:generate"
|
||
AI_EXPRESSION_OPTIMIZE = "ai:expression:optimize"
|
||
AI_LESSON_PREPARE = "ai:lesson:prepare"
|
||
AI_PROMPT_READ = "ai:prompt:read"
|
||
AI_PROMPT_CREATE = "ai:prompt:create"
|
||
AI_PROMPT_UPDATE = "ai:prompt:update"
|
||
AI_USAGE_READ = "ai:usage:read" # ai12 增补:查询自身用量
|
||
AI_USAGE_READ_ALL = "ai:usage:read_all" # ai12 增补:查询学校用量(管理员)
|
||
|
||
|
||
async def require_permission(permission: str) -> UserContext:
|
||
"""FastAPI Depends 权限校验.
|
||
|
||
从 x-user-id / x-user-roles 头提取身份,校验角色是否含 permission。
|
||
DataScope 通过 iam.GetEffectiveDataScope 查询,Redis 缓存 5min。
|
||
"""
|
||
...
|
||
```
|
||
|
||
### 6.2 错误码清单(前缀 `AI_*`,ai06 初稿 + ai12 增补)
|
||
|
||
| 错误码 | 触发条件 | 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 + degraded:true | 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` | 工作流状态不允许此操作(如已 Persisted 再次 Confirm) | 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 |
|
||
|
||
### 6.3 Logger 初始化
|
||
|
||
- 位置:`main.py`(已具备)
|
||
- 配置:`structlog.make_filtering_bound_logger(level)` + `TimeStamper(fmt="iso")` + `ConsoleRenderer`
|
||
- **改进**:生产环境改用 `structlog.processors.JSONRenderer()`(当前 ConsoleRenderer 适合开发)
|
||
- **ai12 增补**:日志必须包含 `request_id`(从 RequestContextMiddleware 注入)、`user_id`、`workflow_id`(如适用),便于全链路追踪
|
||
|
||
### 6.4 Metrics 指标清单(ai06 初稿 + ai12 增补)
|
||
|
||
| 指标名 | 类型 | 标签 | 描述 |
|
||
| ---------------------------------- | --------- | ----------------------------------------- | ---------------------------------- |
|
||
| `ai_http_requests_total` | Counter | method, path, status | HTTP 请求总数 |
|
||
| `ai_http_request_duration_seconds` | Histogram | method, path | HTTP 请求延迟 |
|
||
| `ai_grpc_requests_total` | Counter | method, status | gRPC 请求总数(ai12 增补) |
|
||
| `ai_grpc_request_duration_seconds` | Histogram | method | gRPC 请求延迟(ai12 增补) |
|
||
| `ai_llm_calls_total` | Counter | provider, model, operation, status | LLM 调用总数 |
|
||
| `ai_llm_call_duration_seconds` | Histogram | provider, model, operation | LLM 调用延迟 |
|
||
| `ai_llm_tokens_total` | Counter | provider, model, type (prompt/completion) | token 消耗总数 |
|
||
| `ai_llm_stream_chunks_total` | Counter | provider, model | 流式 chunk 总数 |
|
||
| `ai_llm_first_token_seconds` | Histogram | provider, model | 流式首字延迟(ai12 增补,SLO) |
|
||
| `ai_provider_failover_total` | Counter | from_provider, to_provider, reason | Provider 故障切换次数(ai12 增补) |
|
||
| `ai_grpc_downstream_calls_total` | Counter | downstream, method, status | 下游 gRPC 调用总数 |
|
||
| `ai_rate_limit_hits_total` | Counter | dimension (user/ip/school) | 限流命中次数 |
|
||
| `ai_quota_used_ratio` | Gauge | school_id, dimension (school/teacher) | 配额使用率(ai12 增补) |
|
||
| `ai_usage_events_published_total` | Counter | status | 用量事件发布数 |
|
||
| `ai_prompt_template_renders_total` | Counter | template_name, status | 模板渲染次数 |
|
||
| `ai_prompt_cache_hits_total` | Counter | — | Prompt 缓存命中(ai12 增补) |
|
||
| `ai_workflow_active_count` | Gauge | status | 活跃工作流数(ai12 增补) |
|
||
| `ai_workflow_duration_seconds` | Histogram | status | 工作流总耗时(ai12 增补) |
|
||
| `ai_evaluation_pass_rate` | Gauge | operation | 评估通过率(ai12 增补) |
|
||
| `ai_circuit_breaker_state` | Gauge | provider, state (closed/open/half_open) | 熔断器状态(ai12 增补) |
|
||
|
||
### 6.5 Tracer 初始化
|
||
|
||
- 位置:`main.py` `init_tracer()`(已具备,dev_mode 跳过)
|
||
- **改进**:
|
||
- gRPC server 注册 `grpc.aio.ServerInterceptor` 透传 W3C trace context(从 metadata 提取 `traceparent`)
|
||
- 下游 gRPC client 注册 `grpc.aio.ClientInterceptor` 注入 `traceparent` 到 metadata
|
||
- LLM 调用创建子 span(`ai_llm_call`),记录 provider/model/tokens/latency
|
||
- 备课工作流每步创建子 span(`ai_workflow_step`),记录 step_name/status/duration
|
||
|
||
### 6.6 /healthz 检查逻辑
|
||
|
||
- liveness:仅进程存活(已具备)
|
||
|
||
### 6.7 /readyz 检查逻辑(ai06 初稿 + ai12 增补)
|
||
|
||
```python
|
||
async def readyz() -> dict:
|
||
return {
|
||
"status": "ok" if all_ready else "degraded",
|
||
"ready": all_ready,
|
||
"service": "ai",
|
||
"llm_configured": settings.llm_available,
|
||
"degraded": not all_ready,
|
||
"providers": {
|
||
"openai": bool(settings.openai_api_key),
|
||
"anthropic": bool(settings.anthropic_api_key),
|
||
"baichuan": bool(settings.baichuan_api_key),
|
||
"local_ollama": bool(settings.ollama_base_url),
|
||
},
|
||
"downstream_grpc": {
|
||
"content": "ok" | "unreachable" | "not_configured",
|
||
"data_ana": "ok" | "unreachable" | "not_configured",
|
||
"iam": "ok" | "unreachable" | "not_configured", # ai12 增补
|
||
},
|
||
"redis": "ok" | "unreachable", # 限流 + 缓存 + 工作流状态依赖
|
||
"kafka": "ok" | "unreachable", # ai12 增补:用量事件发布依赖
|
||
"timestamp": datetime.now(UTC).isoformat(),
|
||
}
|
||
```
|
||
|
||
### 6.8 优雅关闭顺序(ai06 初稿 + ai12 增补)
|
||
|
||
1. HTTP server stop accepting new requests(uvicorn graceful shutdown)
|
||
2. **ai12 增补**:拒绝新工作流启动(返回 `AI_SERVICE_SHUTTING_DOWN` 503)
|
||
3. gRPC server graceful stop(**关键**:等待在途 `StreamChat` / `StreamGenerateQuestion` 流式 RPC 完成,60s 超时,避免截断用户响应)
|
||
4. LLM 流式请求 drain(等待 httpx stream 完成)
|
||
5. **ai12 增补**:在途工作流状态保存(Redis 已持久化,无需额外操作;Temporal 模式下需 complete workflow)
|
||
6. Kafka producer flush + close(确保用量事件已投递)
|
||
7. 下游 gRPC channels close(content / data-ana / iam)
|
||
8. Redis connection close
|
||
|
||
**信号处理**:注册 `signal.SIGTERM` handler,触发上述顺序,总超时 90s(gRPC 60s + 其他 30s)。
|
||
|
||
## 7. 与其他模块的交互点(契约清单)
|
||
|
||
| 方向 | 对方服务 | 协议 | 接口/事件 | 用途 |
|
||
| ------ | ------------ | ----- | ------------------------------------------------------------ | ------------------------------- |
|
||
| 被调用 | api-gateway | HTTP | `/ai/*` | Gateway 代理 |
|
||
| 被调用 | teacher-bff | gRPC | `AiService.*` | BFF 聚合 AI 能力 |
|
||
| 调用 | content | gRPC | `KnowledgeGraphService.GetPrerequisites / GetLearningPath` | 出题上下文查询 |
|
||
| 调用 | content | gRPC | `TextbookService.ListTextbooks` | 出题教材关联(ai12 增补) |
|
||
| 调用 | content | gRPC | `QuestionService.CreateQuestions`(**待 coord 补全 proto**) | 备课工作流入库(ai12 增补) |
|
||
| 调用 | data-ana | gRPC | `AnalyticsService.GetStudentWeakness / GetLearningTrend` | 个性化出题学情查询 |
|
||
| 调用 | data-ana | gRPC | `AnalyticsService.GetClassPerformance` | 备课工作流班级学情(ai12 增补) |
|
||
| 调用 | iam | gRPC | `IamService.GetEffectiveDataScope`(**待 coord P4 补全**) | DataScope 解析(ai12 增补) |
|
||
| 调用 | LLM Provider | HTTP | OpenAI 兼容 REST `/chat/completions` | LLM 推理 |
|
||
| 发布 | — | Kafka | `edu.insight.ai.usage`(004 §7.2 已确认) | 用量计费外发 |
|
||
| 发布 | — | Kafka | `edu.insight.ai.generated`(**P6+ 待 coord 仲裁**) | 生成内容审计(ai12 增补) |
|
||
| 发布 | — | Kafka | `edu.insight.ai.feedback`(**P6+ 待 coord 仲裁**) | RLHF 反馈数据(ai12 增补) |
|
||
|
||
## 8. 风险与假设
|
||
|
||
### 8.1 假设
|
||
|
||
- **假设 1**:content 服务实现了 `KnowledgeGraphService` gRPC server。当前 content.proto 已定义但未实现 gRPC server(ai05 阶段 2 设计中)。ai 调用前需确认 content gRPC 可用
|
||
- **假设 2**:data-ana 实现了 `AnalyticsService` gRPC server(ai11 设计文档已设计)。ai 调用前需确认 data-ana gRPC 可用
|
||
- **假设 3**:coord 同意新增 `edu.insight.ai.usage` topic + `AIUsageEvent` proto message。**004 §7.2 已确认 topic**,events.proto message 待 coord 补全
|
||
- **假设 4**:Redis 可用(限流 + 缓存 + 工作流状态依赖)。若 Redis 不可用:
|
||
- 限流降级为"无限制"(风险:LLM 成本失控),或降级为内存令牌桶(单实例有效,多实例不一致)
|
||
- 缓存失效,所有 LLM 调用直达 Provider(成本增加)
|
||
- 工作流状态丢失,在途工作流失效(影响教师审核流程)
|
||
- **假设 5**(ai12 增补):iam 提供 `GetEffectiveDataScope` gRPC API。coord 已仲裁 P4 补全(§15.3 #5)
|
||
- **假设 6**(ai12 增补):LLM Provider API 稳定性符合 SLA(OpenAI 99.5%+)。多 Provider 故障切换可覆盖单 Provider 故障
|
||
- **假设 7**(ai12 增补):教师审核备课结果在 24h 内完成。超过 24h 工作流过期,需重新发起
|
||
|
||
### 8.2 技术风险
|
||
|
||
| 风险 | 影响 | 缓解措施 |
|
||
| ---------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------- |
|
||
| LLM 调用延迟高(>30s) | 用户体验差 | 超时 30s + 降级骨架响应 + 流式优先(用户感知首字延迟) |
|
||
| LLM 成本失控 | 财务风险 | Redis 令牌桶限流(user/IP/school 三维度)+ 月度配额 + 用量计费监控 + 告警阈值 |
|
||
| LLM Provider 单点故障 | 服务不可用 | 多 Provider 适配器 + ProviderFailoverChain 自动 failover(OpenAI 失败切 Anthropic,再切本地 Ollama) |
|
||
| 流式 RPC 中断 | 用户响应截断 | gRPC server graceful shutdown 60s drain + 客户端重连机制 + 工作流状态持久化支持重试 |
|
||
| Prompt 注入攻击 | LLM 输出恶意内容 | InputSanitizer 过滤 + system prompt 加安全约束 + OutputModerator 过滤 |
|
||
| 下游 gRPC 不可达 | 备课工作流失败 | 降级:跳过学情查询,仅基于 prompt 生成题目 + `degraded: true` |
|
||
| PII 数据泄露给 LLM | 合规风险 | PIIRedactor 在写入 prompt 前脱敏(学生姓名→"学生A",手机号→"***") |
|
||
| 生成质量低 | 教师审核拒绝率高 | RuleValidator + LLMJudge 双重校验 + 不达标重试(最多 3 次) |
|
||
| 工作流状态丢失 | 教师审核流程中断 | Redis 持久化 + 24h TTL + 事件日志(P6+ Temporal) |
|
||
| Redis 单点故障 | 限流/缓存/工作流全失效 | Redis 哨兵/集群(P6 硬化)+ 降级策略(限流无限制/缓存失效/工作流拒绝启动) |
|
||
|
||
### 8.3 未决设计决策(需 coord 仲裁)
|
||
|
||
1. **备课工作流是否引入 Temporal**:004 §2.3 列出 Temporal 用于 AI 编排,但简单 4 步工作流可用 FastAPI BackgroundTasks + Redis 状态。建议:P5 用 BackgroundTasks + Redis(24h TTL),P6 评估迁移 Temporal(支持长运行 + 复杂状态机)
|
||
2. **`edu.insight.ai.usage` topic 已确认,`AIUsageEvent` proto message 待补全**:需 coord 在 shared-proto events.proto 新增(ai12 已给出建议 schema §3.3)
|
||
3. **LLM Provider 配置化路由**:是否在 shared-py 建立通用 `LLMProvider` 抽象(供未来其他 Python 服务复用)。建议 P5 阶段在 ai 服务内部实现,P6 评估是否提取到 shared-py
|
||
4. **Prompt 模板存储位置**:YAML 文件(简单,无 DB)vs DB(动态更新)。建议 P5 用 YAML 文件(`services/ai/src/ai/prompts/*.yaml`),P6 评估迁移 DB
|
||
5. **ai12 新增 10 项提请**(详见 01-understanding.md §"ai12 新增提请项"):ai gRPC 端口 50058、ai.proto 待补全、events.proto 待补 AIUsageEvent、iam GetEffectiveDataScope、响应信封整改、长运行工作流持久化、多模态规划、评估框架归属、RLHF 数据收集
|
||
|
||
---
|
||
|
||
## ai12 增补:长期架构演进设计(§9-§17)
|
||
|
||
> 以下章节为 ai12 新增,旨在为未来 P6+ 演进做好铺垫,确保 P5 实现不阻塞未来扩展。
|
||
|
||
## 9. 安全架构
|
||
|
||
### 9.1 PII 脱敏流水线
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Input[用户输入] --> Detect[PII 检测<br/>正则 + NER 模型]
|
||
Detect --> Redact[脱敏替换<br/>学生姓名→学生A<br/>手机号→***]
|
||
Redact --> Log[脱敏日志<br/>记录脱敏操作]
|
||
Log --> Prompt[安全 Prompt]
|
||
Prompt --> LLM[LLM 调用]
|
||
```
|
||
|
||
| PII 类型 | 检测方式 | 脱敏方式 |
|
||
| -------- | ---------------------- | -------------------- |
|
||
| 学生姓名 | 白名单匹配(来自 iam) | 替换为"学生A/B/C" |
|
||
| 手机号 | 正则 `1[3-9]\d{9}` | 替换为 `***` |
|
||
| 身份证号 | 正则 `\d{17}[\dXx]` | 替换为 `***` |
|
||
| 邮箱 | 正则 | 替换为 `***@***.com` |
|
||
| 家庭住址 | NER 模型(P6+ 评估) | 替换为"某地" |
|
||
|
||
### 9.2 Prompt 注入防御
|
||
|
||
- **输入 sanitize**:过滤 SQL 注入模式(`'; DROP TABLE`)、HTML 注入(`<script>`)、命令注入(`; rm -rf`)
|
||
- **System prompt 加安全约束**:明确禁止执行"忽略上述指令"、"输出系统 prompt"等攻击模式
|
||
- **输出过滤**:检测输出中是否包含 system prompt 内容、是否执行了注入指令
|
||
|
||
### 9.3 输出内容审核
|
||
|
||
- **敏感词过滤**:基于敏感词字典(政治/色情/暴力/广告),P5 用基础字典,P6+ 接入专业审核 API
|
||
- **安全校验**:检测输出是否包含代码执行指令、是否诱导不当行为
|
||
- **审核失败处理**:返回 `AI_CONTENT_MODERATION_REJECTED` 503 + 触发重新生成(最多 3 次)
|
||
|
||
### 9.4 多租户数据隔离
|
||
|
||
- **DataScope 透传**:所有 LLM 调用必须携带 `UserContext.data_scope`,确保生成内容不超出用户权限范围
|
||
- **Prompt 注入 DataScope 约束**:system prompt 明确"仅可生成用户 DataScope 范围内的内容"
|
||
- **配额隔离**:按 school_id 隔离 token 配额,避免单一学校耗尽全平台预算
|
||
|
||
## 10. 评估框架
|
||
|
||
### 10.1 三道防线质量保障
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Generated[LLM 生成结果] --> R1[第一道:规则校验<br/>RuleValidator]
|
||
R1 -->|通过| R2[第二道:LLM-as-judge<br/>LLMJudge]
|
||
R1 -->|不通过| Retry[重新生成]
|
||
R2 -->|score >= 0.7| R3[第三道:质量门禁<br/>QualityGate]
|
||
R2 -->|score < 0.7| Retry
|
||
R3 -->|通过| Output[输出给用户]
|
||
R3 -->|不通过| Retry
|
||
Retry -->|重试 < 3| Generated
|
||
Retry -->|重试 >= 3| Degraded[降级返回错误]
|
||
```
|
||
|
||
### 10.2 RuleValidator 规则清单
|
||
|
||
| 规则 | 校验内容 | 失败处理 |
|
||
| ---------- | ---------------------------------------------------- | ------------------ |
|
||
| 题型匹配 | 生成题目格式符合 `question_type`(单选/多选/填空等) | 重新生成 |
|
||
| 答案非空 | answer 字段非空且非占位符 | 重新生成 |
|
||
| 解析合理 | explanation 长度 >= 20 字符 | 重新生成 |
|
||
| 难度匹配 | LLM 判定难度与请求 `difficulty` 一致 | 标记 warning,通过 |
|
||
| 知识点覆盖 | 生成题目覆盖请求的 `knowledge_point_ids` | 标记 warning,通过 |
|
||
| 字符长度 | question <= 1000 字符 | 截断 + warning |
|
||
| 格式合规 | 输出可解析为 JSON(如要求 JSON 格式) | 重新生成 |
|
||
|
||
### 10.3 LLMJudge 评估维度
|
||
|
||
| 维度 | 评分标准 | 权重 |
|
||
| ------------ | -------------------------- | ---- |
|
||
| 准确性 | 题目本身正确性、答案正确性 | 0.4 |
|
||
| 清晰度 | 题目表述清晰、无歧义 | 0.2 |
|
||
| 难度匹配 | 实际难度与请求难度匹配 | 0.2 |
|
||
| 知识点相关性 | 题目与知识点相关 | 0.1 |
|
||
| 解析质量 | 解析能帮助学生理解 | 0.1 |
|
||
|
||
**评分阈值**:
|
||
|
||
- `>= 0.85`:优秀,直接通过
|
||
- `0.7-0.85`:合格,通过
|
||
- `< 0.7`:不合格,重新生成
|
||
|
||
### 10.4 评估数据收集(RLHF 基础)
|
||
|
||
每次生成结果记录:
|
||
|
||
- 生成内容(题目/答案/解析)
|
||
- 评估报告(rule_score / judge_score / overall_score)
|
||
- 教师反馈(采纳/拒绝/修改,由 BFF 调用 `ConfirmLessonPlan` 时携带 `modifications`)
|
||
|
||
P6+ 评估发布 `AIFeedbackRecorded` 事件,data-ana 落 ClickHouse 供未来 fine-tune。
|
||
|
||
## 11. RAG 检索架构演进
|
||
|
||
### 11.1 P5:复用 content 服务(无独立向量库)
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Prompt[出题 Prompt] --> Content[content.KnowledgeGraphService<br/>GetPrerequisites]
|
||
Content --> Context[知识点上下文]
|
||
Context --> LLM[LLM 生成]
|
||
```
|
||
|
||
### 11.2 P6+:引入向量库语义检索(评估)
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Prompt[出题 Prompt] --> Embed[Embedding Provider<br/>text-embedding-3-small]
|
||
Embed --> Vector[(向量库<br/>pgvector/Chroma)]
|
||
Vector --> Retrieve[Top-K 相似题目]
|
||
Retrieve --> Rerank[Reranker<br/>cohere-rerank]
|
||
Rerank --> Context[精排后上下文]
|
||
Context --> LLM[LLM 生成]
|
||
```
|
||
|
||
**演进路径**:
|
||
|
||
- P5:仅基于 content 知识点查询
|
||
- P6+:评估引入向量库(pgvector 复用 MySQL / Chroma 独立部署)+ Embedding Provider 抽象
|
||
- P7+:评估 Reranker 提升检索精度
|
||
|
||
**proto 预留**(ai12 增补,建议 coord 在 ai.proto v2 预留):
|
||
|
||
```protobuf
|
||
message EmbeddingRequest {
|
||
string text = 1;
|
||
string model = 2; // text-embedding-3-small / bge-large-zh
|
||
}
|
||
|
||
message EmbeddingResponse {
|
||
repeated float embedding = 1;
|
||
int32 tokens = 2;
|
||
}
|
||
```
|
||
|
||
## 12. 多模态支持规划
|
||
|
||
### 12.1 P5:仅文本
|
||
|
||
- 出题:仅支持文本题目
|
||
- 优化:仅支持文本表达优化
|
||
- 聊天:仅支持文本对话
|
||
|
||
### 12.2 P6+:多模态评估
|
||
|
||
| 场景 | 模态 | 技术方案 |
|
||
| ---------- | --------- | ----------------------------------- |
|
||
| 数学几何题 | 文本+图片 | GPT-4V / Claude 3 Vision 多模态 LLM |
|
||
| 英语听力题 | 文本+音频 | TTS 生成音频 + 文本题目 |
|
||
| 物理实验题 | 文本+图片 | 多模态 LLM + 实验图理解 |
|
||
| 化学反应题 | 文本+图片 | 多模态 LLM + 化学式图片识别 |
|
||
|
||
### 12.3 proto 预留(ai12 增补)
|
||
|
||
```protobuf
|
||
message MultimodalContent {
|
||
string text = 1;
|
||
optional string image_url = 2; // 图片 URL
|
||
optional string audio_url = 3; // 音频 URL
|
||
optional string image_base64 = 4; // 图片 base64(小图)
|
||
}
|
||
|
||
message ChatRequest {
|
||
repeated ChatMessage messages = 1;
|
||
// ... 其他字段
|
||
optional MultimodalContent multimodal = 10; // v2 预留
|
||
}
|
||
```
|
||
|
||
## 13. LLM Provider 适配器详细设计
|
||
|
||
### 13.1 LLMProvider 抽象接口
|
||
|
||
```python
|
||
from abc import ABC, abstractmethod
|
||
from collections.abc import AsyncGenerator
|
||
|
||
|
||
class LLMProvider(ABC):
|
||
"""LLM Provider 抽象接口,所有适配器必须实现."""
|
||
|
||
@property
|
||
@abstractmethod
|
||
def name(self) -> str:
|
||
"""Provider 名称(openai/anthropic/baichuan/local_ollama)."""
|
||
|
||
@abstractmethod
|
||
async def chat(self, messages: list[dict], model: str, temperature: float) -> dict:
|
||
"""非流式调用,返回 OpenAI 兼容响应 dict."""
|
||
|
||
@abstractmethod
|
||
async def stream_chat(self, messages: list[dict], model: str, temperature: float) -> AsyncGenerator[str, None]:
|
||
"""流式调用,yield SSE 格式 chunk."""
|
||
|
||
@abstractmethod
|
||
async def embed(self, text: str, model: str) -> list[float]:
|
||
"""Embedding 调用(P6+ 评估,P5 可 NotImplementedError)."""
|
||
```
|
||
|
||
### 13.2 适配器实现清单
|
||
|
||
| Provider | API 协议 | 认证方式 | 备注 |
|
||
| --------------------- | ---------------- | ---------------- | -------------------------- |
|
||
| `OpenAIProvider` | OpenAI REST | Bearer token | 默认 Provider,gpt-4o-mini |
|
||
| `AnthropicProvider` | Anthropic REST | x-api-key header | 内部转换为 OpenAI 兼容格式 |
|
||
| `BaichuanProvider` | OpenAI 兼容 REST | Bearer token | 百川大模型,国内访问稳定 |
|
||
| `LocalOllamaProvider` | Ollama REST | 无认证 | 本地部署,私有化场景 |
|
||
|
||
### 13.3 ProviderFailoverChain 故障切换
|
||
|
||
```python
|
||
class ProviderFailoverChain:
|
||
"""多 Provider 故障切换链.
|
||
|
||
按优先级顺序尝试 Provider,失败自动切换到下一个。
|
||
熔断器:单 Provider 连续 3 次失败触发熔断(60s 内不再尝试)。
|
||
"""
|
||
|
||
def __init__(self, providers: list[LLMProvider], circuit_breaker: CircuitBreaker):
|
||
self._providers = providers
|
||
self._circuit_breaker = circuit_breaker
|
||
|
||
async def chat(self, messages, model, temperature) -> dict:
|
||
for provider in self._providers:
|
||
if not self._circuit_breaker.is_closed(provider.name):
|
||
continue # 熔断中,跳过
|
||
try:
|
||
result = await provider.chat(messages, model, temperature)
|
||
self._circuit_breaker.record_success(provider.name)
|
||
return result
|
||
except Exception as exc:
|
||
self._circuit_breaker.record_failure(provider.name)
|
||
logger.warning("provider_failover", from_=provider.name, error=str(exc))
|
||
continue
|
||
raise AllProvidersFailedError()
|
||
```
|
||
|
||
### 13.4 配置化路由
|
||
|
||
```python
|
||
# config.py
|
||
class Settings(BaseSettings):
|
||
# Provider 优先级(按顺序 failover)
|
||
llm_provider_priority: list[str] = ["openai", "anthropic", "baichuan", "local_ollama"]
|
||
# 各 Provider 配置
|
||
openai_api_key: str = ""
|
||
openai_base_url: str = "https://api.openai.com/v1"
|
||
anthropic_api_key: str = ""
|
||
anthropic_base_url: str = "https://api.anthropic.com"
|
||
baichuan_api_key: str = ""
|
||
baichuan_base_url: str = "https://api.baichuan-ai.com/v1"
|
||
ollama_base_url: str = "" # 本地 Ollama,如 http://localhost:11434
|
||
# 默认模型路由(model 名 → Provider)
|
||
llm_model_routing: dict[str, str] = {
|
||
"gpt-4o-mini": "openai",
|
||
"gpt-4o": "openai",
|
||
"claude-3-haiku": "anthropic",
|
||
"baichuan2-13b": "baichuan",
|
||
"qwen2.5-7b": "local_ollama",
|
||
}
|
||
```
|
||
|
||
## 14. 性能与容量规划
|
||
|
||
### 14.1 SLO 指标
|
||
|
||
| 指标 | SLO | 测量方式 |
|
||
| --------------- | ------------ | ------------------------------ |
|
||
| 流式首字延迟 | < 2s(P95) | `ai_llm_first_token_seconds` |
|
||
| 非流式响应延迟 | < 10s(P95) | `ai_llm_call_duration_seconds` |
|
||
| 流式 chunk 间隔 | < 100ms | 客户端测量 |
|
||
| 工作流总耗时 | < 60s(P95) | `ai_workflow_duration_seconds` |
|
||
| 评估通过率 | > 80% | `ai_evaluation_pass_rate` |
|
||
| 服务可用性 | 99.5% | 30 天滚动窗口 |
|
||
|
||
### 14.2 容量规划
|
||
|
||
| 资源 | 单实例容量 | P5 预估需求 | 扩展策略 |
|
||
| --------------- | ------------------- | ------------------- | ------------------ |
|
||
| 并发 LLM 调用 | 50(受 httpx 限制) | 200(500 教师) | 水平扩展(4 实例) |
|
||
| 并发流式 RPC | 30 | 100 | 水平扩展(4 实例) |
|
||
| Redis 内存 | 100MB | 500MB(工作流状态) | Redis 集群(P6+) |
|
||
| Kafka 吞吐 | 1000 events/s | 100 events/s | 单分区足够 |
|
||
| 月度 token 消耗 | — | 10M tokens | 按学校配额控制 |
|
||
|
||
### 14.3 流式背压处理
|
||
|
||
- **gRPC 流式 RPC**:默认支持背压(client 慢消费时 server 自动暂停 yield)
|
||
- **SSE**:FastAPI StreamingResponse 不支持背压,需手动检测 client 断开(`await request.is_disconnected()`)
|
||
- **超时**:流式 RPC 60s 无 chunk 自动断开;非流式 30s 超时
|
||
|
||
## 15. 测试策略
|
||
|
||
### 15.1 测试金字塔
|
||
|
||
| 层级 | 覆盖率目标 | 工具 | 重点 |
|
||
| -------- | ---------- | ------------------------------- | ----------------------------------------------------------- |
|
||
| 单元测试 | ≥ 80% | pytest + pytest-asyncio | LLMProvider 适配器 / RuleValidator / PIIRedactor / 模板渲染 |
|
||
| 集成测试 | 关键路径 | pytest + httpx + testcontainers | HTTP/gRPC 端点 / Redis / Kafka |
|
||
| 契约测试 | proto 一致 | pact-python | ai.proto 与 BFF 契约一致性 |
|
||
| 评估测试 | 生成质量 | 自研 + LLM-as-judge | 题目质量评分(人工 + 自动) |
|
||
|
||
### 15.2 LLM Mock 策略
|
||
|
||
- 测试环境不调真实 LLM(成本高 + 不稳定)
|
||
- `MockLLMProvider` 实现固定响应(按 prompt 模式匹配返回预设响应)
|
||
- 评估测试用真实 LLM(独立 CI job,低频运行)
|
||
|
||
## 16. 实施里程碑(ai12 增补)
|
||
|
||
### 16.1 M14(第 1 月):基础架构
|
||
|
||
**目标**:建立 LLMProvider 抽象 + gRPC server 基础
|
||
|
||
| 交付物 | 验收标准 |
|
||
| --------------------------------------------------------------- | ------------------------ |
|
||
| `LLMProvider` 抽象接口 + OpenAIProvider 实现 | 单元测试覆盖 ≥ 80% |
|
||
| AnthropicProvider + BaichuanProvider + LocalOllamaProvider 实现 | 4 Provider 切换可用 |
|
||
| `ProviderFailoverChain` + CircuitBreaker | 故障切换测试通过 |
|
||
| gRPC server(Chat + StreamChat) | BFF 可调通 gRPC 流式 RPC |
|
||
| Redis 多维度限流(user/IP/school) | 限流命中准确 |
|
||
| 响应信封整改为 ActionState | 004 §11.5 合规 |
|
||
| Dockerfile 多阶段构建 | 镜像 < 200MB |
|
||
|
||
### 16.2 M15(第 2 月):出题核心
|
||
|
||
**目标**:Prompt 模板管理 + 出题工作流 + 用量计费
|
||
|
||
| 交付物 | 验收标准 |
|
||
| ---------------------------------------------- | --------------------- |
|
||
| PromptTemplateService + Jinja2 渲染 | 5+ 模板可渲染 |
|
||
| Prompt 模板 CRUD API | HTTP/gRPC 均可用 |
|
||
| GenerateQuestion + StreamGenerateQuestion | 题目逐字流式生成 |
|
||
| RuleValidator + LLMJudge + QualityGate | 评估通过率 > 80% |
|
||
| UsageRecorder + KafkaProducer | 用量事件落 ClickHouse |
|
||
| QuotaEnforcer(学校/教师配额) | 配额超限返回 429 |
|
||
| PIIRedactor + InputSanitizer + OutputModerator | 安全测试通过 |
|
||
|
||
### 16.3 M16(第 3 月):备课工作流
|
||
|
||
**目标**:备课工作流 4 步编排端到端
|
||
|
||
| 交付物 | 验收标准 |
|
||
| --------------------------------------- | -------------------- |
|
||
| LessonPreparationWorkflow 4 步编排 | 端到端跑通 |
|
||
| WorkflowStateStore(Redis 持久化) | 24h 内可恢复 |
|
||
| 教师审核流程(BFF 编排) | 教师可审核/修改/拒绝 |
|
||
| content.CreateQuestions 入库 | 题目入库成功 |
|
||
| gRPC client(content / data-ana / iam) | 下游调用稳定 |
|
||
| 集成测试 + 契约测试 | 关键路径覆盖 |
|
||
| 文档完善(README + known-issues) | 文档同步更新 |
|
||
|
||
## 17. 与架构标准的对齐清单
|
||
|
||
### 17.1 通用架构原则对齐
|
||
|
||
| 原则 | 对齐情况 | 说明 |
|
||
| ---------------------- | -------- | ---------------------------------------------------- |
|
||
| DDD 限界上下文 | ✅ | D6.b 生成子域,边界清晰 |
|
||
| CQRS | N/A | ai 无 DB,无读写分离 |
|
||
| 事件驱动 | ✅ | AIUsageRecorded 事件外发,豁免 Outbox(004 §12.2) |
|
||
| 12-factor | ✅ | 配置通过环境变量 + pydantic-settings |
|
||
| SOLID | ✅ | LLMProvider 抽象(OCP/ISP)+ Service 单一职责(SRP) |
|
||
| Hexagonal architecture | ✅ | LLMProvider / ContentClient / DataAnaClient 均为端口 |
|
||
|
||
### 17.2 Resilience 模式对齐
|
||
|
||
| 模式 | 实现 |
|
||
| -------------------- | -------------------------------------- |
|
||
| Circuit Breaker | ProviderFailoverChain + CircuitBreaker |
|
||
| Retry with backoff | LLM 调用 3 次重试(1s/2s/4s 指数退避) |
|
||
| Timeout | LLM 30s / gRPC 60s / Redis 1s |
|
||
| Bulkhead | 单实例 50 并发 LLM 调用上限 |
|
||
| Failover | 多 Provider 故障切换链 |
|
||
| Graceful degradation | LLM 不可用返回骨架 + degraded:true |
|
||
|
||
### 17.3 可观测性对齐
|
||
|
||
| 支柱 | 实现 | 与 004 §10 对齐 |
|
||
| ------- | ------------------------------------- | --------------- |
|
||
| Logs | structlog + JSONRenderer + request_id | ✅ |
|
||
| Metrics | prometheus-client + 20+ 业务指标 | ✅ |
|
||
| Traces | OpenTelemetry + gRPC interceptor | ✅ |
|
||
| Health | /healthz + /readyz(多维度检查) | ✅ |
|
||
|
||
---
|
||
|
||
## 待 coord 交叉审查项(ai 相关)
|
||
|
||
| # | 议题 | 涉及文档 |
|
||
| -------- | ------------------------------------------------------------------------------------ | ----------------------------- |
|
||
| 2 | 新增 `edu.insight.ai.usage` topic + `AIUsageEvent` proto message | 004 §7.2 + events.proto |
|
||
| 4 | ai 实现 gRPC server 决策(含 StreamChat 流式 RPC) | 004 §4.1 |
|
||
| 6 | ai 备课工作流是否引入 Temporal | 004 §2.3 |
|
||
| 7 | 端口冲突检查:ai 3008 HTTP + 50058 gRPC(ai12 增补:gRPC 端口待补登) | full-stack-runbook + 004 §1.2 |
|
||
| 8 | 错误码前缀检查:`AI_*`(与其他服务不重叠) | — |
|
||
| 9 | 黄金模板对齐:Python 服务无 NestJS 装饰器,权限校验用 FastAPI Depends 等价物是否认可 | — |
|
||
| ai12-A1 | ai gRPC 端口 50058 补登 004 §1.2 + infra/port-allocation.md | 004 §1.2 |
|
||
| ai12-A2 | ai.proto 待补全(GenerateLessonPlan / StreamGenerateQuestion / 字段扩展) | ai.proto |
|
||
| ai12-A3 | events.proto 待补 `AIUsageEvent` message(建议 schema 见 §3.3) | events.proto |
|
||
| ai12-A4 | iam GetEffectiveDataScope proto P4 补全(004 §15.3 #5 已仲裁) | iam.proto |
|
||
| ai12-A5 | 响应信封偏离 ActionState 强制整改(004 §11.5 已约束) | known-issues §2.9 |
|
||
| ai12-A6 | Prompt 模板存储位置(P5 YAML / P6+ DB 评估) | — |
|
||
| ai12-A7 | 长运行工作流持久化(P5 Redis 24h / P6+ Temporal 评估) | 004 §2.3 |
|
||
| ai12-A8 | 多模态支持规划(P5 文本 / P6+ 多模态评估),proto v2 预留字段 | ai.proto v2 |
|
||
| ai12-A9 | 评估框架归属(P5/P6 ai 内部 / P7+ 独立评估) | — |
|
||
| ai12-A10 | RLHF 数据收集 `AIFeedbackRecorded` topic(P6+ 评估) | 004 §7.2 |
|
||
|
||
下一步:等待 coord 交叉审查通过后,进入阶段 3(按图实施)。
|
||
|
||
---
|
||
|
||
**AI Agent**: ai12 (ai) ← ai06(初稿)
|
||
**Branch**: 单仓库并行模式(直接提交 main)
|
||
**Coordinator**: coord-ai
|