Files
Edu/services/ai/docs/02-architecture-design.md
SpecialX faaaf29f67 docs: ai 协作文档体系重构与多 ai 仲裁结果落地
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.设计规格文档
2026-07-10 12:58:22 +08:00

1270 lines
71 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 模块架构设计文档 — ai
> AI 标识ai12复核与长期演进接手← ai06初稿2026-07-09
> 负责模块aiP5
> 阶段:架构设计外包 · 阶段 2模块架构设计ai06 初稿 + ai12 长期演进复核)
> 日期2026-07-09ai06 初稿)/ 2026-07-09ai12 复核与补充)
> 关联文档:[阶段 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 流式 RPCHTTP 保留作 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 50058HTTP+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
- **中间件层**RequestContextMiddlewareX-Request-Id→ AuthDependsJWT claims 解析)→ RateLimitDepends多维度限流→ OTel 自动埋点
- **安全层**ai12 增补PIIRedactor → InputSanitizer → OutputModerator所有进出 LLM 的数据必须经过安全层
- **应用服务层**6 个 Service每个对应一类 AI 能力
- **Provider 适配层**:抽象 `LLMProvider` 接口 + 多适配器实现(策略模式)+ `ProviderFailoverChain` 故障切换resilience 模式)
- **Prompt 模板**:模板注册 + Jinja2 渲染 + 版本化P5 YAMLP6+ DB + A/B
- **评估层**ai12 增补):规则校验 + LLM-as-judge + 质量门禁,构成生成质量保障三道防线
- **工作流持久化**ai12 增补Redis 短期P5+ TemporalP6+ 评估),保证长运行工作流状态不丢失
- **缓存层**ai12 增补Prompt hash 缓存P5+ 语义缓存P6+ 评估)+ 模板缓存
- **下游 client**gRPC 调 content / data-ana / iamDataScope均带 client interceptortrace + 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 | Rediskey: `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 模板 | Redistemplate cache | 1 小时 | 文件/DB 变更时主动失效pub/sub 通知) |
| LLM 响应(相同 prompt hash | Redisprompt cache | 30 分钟 | 短 TTL避免陈旧用户反馈差时主动失效 |
| DataScope避免重复查 iam | Redisdatascope cache | 5 分钟 | 短 TTL权限变更时由 iam 主动失效pub/sub |
| 限流计数 | Redisratelimit hash | 滑动窗口 | 自动过期 |
| 配额计数 | Redisquota hash | 月度重置 | 月初定时任务重置 |
| 工作流状态 | Redisworkflow 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待实现 serverai06 初稿 + 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 requestsuvicorn 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 closecontent / data-ana / iam
8. Redis connection close
**信号处理**:注册 `signal.SIGTERM` handler触发上述顺序总超时 90sgRPC 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 serverai05 阶段 2 设计中。ai 调用前需确认 content gRPC 可用
- **假设 2**data-ana 实现了 `AnalyticsService` gRPC serverai11 设计文档已设计。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 稳定性符合 SLAOpenAI 99.5%+)。多 Provider 故障切换可覆盖单 Provider 故障
- **假设 7**ai12 增补):教师审核备课结果在 24h 内完成。超过 24h 工作流过期,需重新发起
### 8.2 技术风险
| 风险 | 影响 | 缓解措施 |
| ---------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------- |
| LLM 调用延迟高(>30s | 用户体验差 | 超时 30s + 降级骨架响应 + 流式优先(用户感知首字延迟) |
| LLM 成本失控 | 财务风险 | Redis 令牌桶限流user/IP/school 三维度)+ 月度配额 + 用量计费监控 + 告警阈值 |
| LLM Provider 单点故障 | 服务不可用 | 多 Provider 适配器 + ProviderFailoverChain 自动 failoverOpenAI 失败切 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 + Redis24h TTLP6 评估迁移 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 文件(简单,无 DBvs 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 | 默认 Providergpt-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 | 测量方式 |
| --------------- | ------------ | ------------------------------ |
| 流式首字延迟 | < 2sP95 | `ai_llm_first_token_seconds` |
| 非流式响应延迟 | < 10sP95 | `ai_llm_call_duration_seconds` |
| 流式 chunk 间隔 | < 100ms | 客户端测量 |
| 工作流总耗时 | < 60sP95 | `ai_workflow_duration_seconds` |
| 评估通过率 | > 80% | `ai_evaluation_pass_rate` |
| 服务可用性 | 99.5% | 30 天滚动窗口 |
### 14.2 容量规划
| 资源 | 单实例容量 | P5 预估需求 | 扩展策略 |
| --------------- | ------------------- | ------------------- | ------------------ |
| 并发 LLM 调用 | 50受 httpx 限制) | 200500 教师) | 水平扩展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 serverChat + 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 步编排 | 端到端跑通 |
| WorkflowStateStoreRedis 持久化) | 24h 内可恢复 |
| 教师审核流程BFF 编排) | 教师可审核/修改/拒绝 |
| content.CreateQuestions 入库 | 题目入库成功 |
| gRPC clientcontent / data-ana / iam | 下游调用稳定 |
| 集成测试 + 契约测试 | 关键路径覆盖 |
| 文档完善README + known-issues | 文档同步更新 |
## 17. 与架构标准的对齐清单
### 17.1 通用架构原则对齐
| 原则 | 对齐情况 | 说明 |
| ---------------------- | -------- | ---------------------------------------------------- |
| DDD 限界上下文 | ✅ | D6.b 生成子域,边界清晰 |
| CQRS | N/A | ai 无 DB无读写分离 |
| 事件驱动 | ✅ | AIUsageRecorded 事件外发,豁免 Outbox004 §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 gRPCai12 增补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` topicP6+ 评估) | 004 §7.2 |
下一步:等待 coord 交叉审查通过后,进入阶段 3按图实施
---
**AI Agent**: ai12 (ai) ← ai06初稿
**Branch**: 单仓库并行模式(直接提交 main
**Coordinator**: coord-ai