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

71 KiB
Raw Blame History

模块架构设计文档 — ai

AI 标识ai12复核与长期演进接手← ai06初稿2026-07-09 负责模块aiP5 阶段:架构设计外包 · 阶段 2模块架构设计ai06 初稿 + ai12 长期演进复核) 日期2026-07-09ai06 初稿)/ 2026-07-09ai12 复核与补充) 关联文档:阶段 1 理解确认书ai-allocation.md004 架构影响地图pending-features.mdcoord 交叉审查报告 审查请求:本设计文档待 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 初稿:分层图

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 增补:完整分层图(含安全 / 评估 / 缓存 / 工作流持久化)

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+ 评估)+ 模板缓存
  • 下游 clientgRPC 调 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 值对象

  • ChatMessagerole + content
  • Usageprompt_tokens + completion_tokens + total_tokens + latency_ms
  • GeneratedQuestionquestion + answer + explanation + question_type + difficulty + knowledge_point_ids
  • PromptTemplatename + system_prompt + user_template + variables + version
  • ai12 增补UserContextuser_id + roles + data_scope + school_id + request_id
  • ai12 增补ProviderConfigprovider_name + model + api_key + base_url + timeout + max_retries
  • ai12 增补EvaluationReportrule_score + judge_score + overall_score + reasons + pass
  • ai12 增补WorkflowStepstep_name + status + started_at + completed_at + result + error
  • ai12 增补QuotaContextschool_id + grade_id + teacher_id + budget + used + remaining

2.4 ai12 增补:备课工作流状态机

备课工作流是 ai 服务最复杂的工作流,明确状态机避免实现混乱:

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 数据收集)

{
  "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

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

// 建议 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 对齐)

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 格式

# 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.usage004 §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 增补)

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_idworkflow_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 调用创建子 spanai_llm_call),记录 provider/model/tokens/latency
    • 备课工作流每步创建子 spanai_workflow_step),记录 step_name/status/duration

6.6 /healthz 检查逻辑

  • liveness仅进程存活已具备

6.7 /readyz 检查逻辑ai06 初稿 + ai12 增补)

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.usage004 §7.2 已确认) 用量计费外发
发布 Kafka edu.insight.ai.generatedP6+ 待 coord 仲裁 生成内容审计ai12 增补)
发布 Kafka edu.insight.ai.feedbackP6+ 待 coord 仲裁 RLHF 反馈数据ai12 增补)

8. 风险与假设

8.1 假设

  • 假设 1content 服务实现了 KnowledgeGraphService gRPC server。当前 content.proto 已定义但未实现 gRPC serverai05 阶段 2 设计中。ai 调用前需确认 content gRPC 可用
  • 假设 2data-ana 实现了 AnalyticsService gRPC serverai11 设计文档已设计。ai 调用前需确认 data-ana gRPC 可用
  • 假设 3coord 同意新增 edu.insight.ai.usage topic + AIUsageEvent proto message。004 §7.2 已确认 topicevents.proto message 待 coord 补全
  • 假设 4Redis 可用(限流 + 缓存 + 工作流状态依赖)。若 Redis 不可用:
    • 限流降级为"无限制"风险LLM 成本失控),或降级为内存令牌桶(单实例有效,多实例不一致)
    • 缓存失效,所有 LLM 调用直达 Provider成本增加
    • 工作流状态丢失,在途工作流失效(影响教师审核流程)
  • 假设 5ai12 增补iam 提供 GetEffectiveDataScope gRPC API。coord 已仲裁 P4 补全§15.3 #5
  • 假设 6ai12 增补LLM Provider API 稳定性符合 SLAOpenAI 99.5%+)。多 Provider 故障切换可覆盖单 Provider 故障
  • 假设 7ai12 增补):教师审核备课结果在 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. 备课工作流是否引入 Temporal004 §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/*.yamlP6 评估迁移 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 脱敏流水线

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 三道防线质量保障

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 服务(无独立向量库)

flowchart LR
    Prompt[出题 Prompt] --> Content[content.KnowledgeGraphService<br/>GetPrerequisites]
    Content --> Context[知识点上下文]
    Context --> LLM[LLM 生成]

11.2 P6+:引入向量库语义检索(评估)

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 预留):

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 增补)

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 抽象接口

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 故障切换

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 配置化路由

# 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
  • SSEFastAPI 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