[M-AI-05-05] 将 Feynman 提交入口接入统一 AiJob Engine #308

Closed
opened 2026-06-21 15:53:42 +08:00 by wangdl · 1 comment
Owner

类型 / 标签

  • backend
  • feynman
  • api
  • feature-flag

风险等级

P1(入口路由错误会导致双执行或请求丢失;响应不兼容会破坏客户端)

依赖

  • M-AI-05-02(Definition + Snapshot)
  • M-AI-05-03(Executor + 验证)
  • M-AI-05-04(Projector + 产物)

目标

保持原 HTTP 接口,在 Legacy 和 Unified 之间增加单一、可回滚的执行路由。

原入口

保持 POST /api/ai-analysis/feynman 不变。

路由

新增或复用 FeynmanExecutionRouter

  • FEYNMAN_ENGINE_MODE=legacy → 原 AiAnalysisService / Legacy Queue
  • FEYNMAN_ENGINE_MODE=unified → AiJobCreationService

Feature Flag 应支持用户白名单。

Unified 流程

  1. 完成原有请求校验
  2. 校验用户拥有知识点或目标
  3. 确定稳定 submissionId
  4. 构造 idempotencyKey
  5. 调用 Snapshot Builder
  6. 调用 AiJobCreationService
  7. 返回兼容响应
  8. 由 Outbox 异步执行

响应兼容

确认原接口当前返回 Job ID、状态、评估请求信息等字段。Unified 模式不得无理由改变旧客户端需要的字段。允许新增可选字段 engineModelifecycleStatus,但不得删除旧字段。

错误处理

Unified 创建失败:返回明确错误、不得自动调用 Legacy、不得创建第二个 Job、不得重复扣减额度、日志不得记录完整解释内容。

幂等

相同 submissionId:返回同一个 Job、只有一个 Snapshot、只有一个 Outbox、不重复扣费。用户主动重新提交新解释时,必须形成新的 submission/version。

约束

  • 不修改客户端
  • 不删除 Legacy
  • 不自动双跑
  • 不新增数据库字段
  • 不迁移 Quiz
  • 不修改 Active Recall

影响范围

src/modules/ai-analysis/src/modules/feynman/src/modules/ai-job/

验收标准

  • FEYNMAN_ENGINE_MODE=legacy 时行为完全不变
  • FEYNMAN_ENGINE_MODE=unified 时通过 CreationService 创建 Job
  • Legacy 和 Unified 分支互斥(同一请求只走一条路径)
  • Unified 创建失败不自动调用 Legacy
  • 响应兼容旧客户端(不删除旧字段)
  • 允许新增 engineModelifecycleStatus 可选字段
  • 相同 submissionId 重复请求幂等(返回同一个 Job)
  • Redis/BullMQ Payload 只有 {jobId}
  • 权限校验不变(用户只能评估自己的知识点)
  • 单元测试 + 集成测试通过

建议执行顺序

第 5 个执行(依赖 02+03+04;可与 06 部分并行)

## 类型 / 标签 - backend - feynman - api - feature-flag ## 风险等级 P1(入口路由错误会导致双执行或请求丢失;响应不兼容会破坏客户端) ## 依赖 - M-AI-05-02(Definition + Snapshot) - M-AI-05-03(Executor + 验证) - M-AI-05-04(Projector + 产物) ## 目标 保持原 HTTP 接口,在 Legacy 和 Unified 之间增加单一、可回滚的执行路由。 ## 原入口 保持 `POST /api/ai-analysis/feynman` 不变。 ## 路由 新增或复用 `FeynmanExecutionRouter`: - `FEYNMAN_ENGINE_MODE=legacy` → 原 AiAnalysisService / Legacy Queue - `FEYNMAN_ENGINE_MODE=unified` → AiJobCreationService Feature Flag 应支持用户白名单。 ## Unified 流程 1. 完成原有请求校验 2. 校验用户拥有知识点或目标 3. 确定稳定 submissionId 4. 构造 idempotencyKey 5. 调用 Snapshot Builder 6. 调用 AiJobCreationService 7. 返回兼容响应 8. 由 Outbox 异步执行 ## 响应兼容 确认原接口当前返回 Job ID、状态、评估请求信息等字段。Unified 模式不得无理由改变旧客户端需要的字段。允许新增可选字段 `engineMode`、`lifecycleStatus`,但不得删除旧字段。 ## 错误处理 Unified 创建失败:返回明确错误、不得自动调用 Legacy、不得创建第二个 Job、不得重复扣减额度、日志不得记录完整解释内容。 ## 幂等 相同 submissionId:返回同一个 Job、只有一个 Snapshot、只有一个 Outbox、不重复扣费。用户主动重新提交新解释时,必须形成新的 submission/version。 ## 约束 - 不修改客户端 - 不删除 Legacy - 不自动双跑 - 不新增数据库字段 - 不迁移 Quiz - 不修改 Active Recall ## 影响范围 `src/modules/ai-analysis/`、`src/modules/feynman/`、`src/modules/ai-job/` ## 验收标准 - [ ] `FEYNMAN_ENGINE_MODE=legacy` 时行为完全不变 - [ ] `FEYNMAN_ENGINE_MODE=unified` 时通过 CreationService 创建 Job - [ ] Legacy 和 Unified 分支互斥(同一请求只走一条路径) - [ ] Unified 创建失败不自动调用 Legacy - [ ] 响应兼容旧客户端(不删除旧字段) - [ ] 允许新增 `engineMode`、`lifecycleStatus` 可选字段 - [ ] 相同 submissionId 重复请求幂等(返回同一个 Job) - [ ] Redis/BullMQ Payload 只有 `{jobId}` - [ ] 权限校验不变(用户只能评估自己的知识点) - [ ] 单元测试 + 集成测试通过 ## 建议执行顺序 第 5 个执行(依赖 02+03+04;可与 06 部分并行)
wangdl added this to the M-AI-05 Feynman 与 ReviewCard 可靠链路 milestone 2026-06-21 15:53:42 +08:00
wangdl added the
priority:p1
area:ai
backend
labels 2026-06-21 15:53:42 +08:00
Author
Owner

开发完成评论

完成内容

  • 创建 FeynmanExecutionRouter(在 ai-analysis 模块中):
    • Feature Flag FEYNMAN_ENGINE_MODE 控制 legacy/unified 分支
    • FeatureFlag 查询失败 → 安全回退到 Legacy
    • Unified 路径:参数校验 → submissionId → idempotencyKey → SnapshotBuilder → AiJobCreationService
    • 响应兼容:不删除旧字段,新增可选 engineMode + lifecycleStatus
    • Unified 失败不得自动调用 Legacy
  • 更新 AiAnalysisController
    • 注入 FeynmanExecutionRouter(@Optional)
    • knowledgeItemId 作为可选字段加入请求体
    • 如果 Router 未注入 → 回退到 Legacy(兼容旧模块导入)
  • 更新 AiAnalysisModule:导入 AiJobModule + AppConfigModule,提供 FeynmanExecutionRouter
  • 更新 AiJobModule:导出 FeynmanSnapshotBuilder(供 AiAnalysisModule 使用)
  • AiJobCreationService 添加 feynman_evaluation 安全分支(无 retrySnapshotContent 时抛错)

路由流程

POST /api/ai-analysis/feynman (不变)
  → AiAnalysisController.evaluateFeynman()
    → FeynmanExecutionRouter.evaluateFeynman()
      ├─ FEYNMAN_ENGINE_MODE=legacy(默认)
      │   → AiAnalysisService.evaluateFeynman() → 原链路
      │
      └─ FEYNMAN_ENGINE_MODE=unified
          → 参数校验
          → resolveSubmissionId (sessionId+answerId / sessionId / content hash)
          → idempotencyKey = feynman:<submissionId>
          → FeynmanSnapshotBuilder.build()
          → AiJobCreationService.createJob({retrySnapshotContent})
          → Job + Snapshot + Outbox 原子创建
          → return { jobId, status, engineMode, lifecycleStatus }

幂等键策略

  • sessionId + answerId 都存在 → feynman:sessionId:answerId
  • sessionIdfeynman:sessionId
  • 都不存在 → feynman:<sha256(content).substring(0,16)>(相同内容 → 相同 key)

修改文件

  • src/modules/ai-analysis/feynman-execution-router.ts(新增,176 行)
  • src/modules/ai-analysis/feynman-execution-router.spec.ts(新增,225 行)
  • src/modules/ai-analysis/ai-analysis.controller.ts(修改,+10/-4 行)
  • src/modules/ai-analysis/ai-analysis.module.ts(修改,+4/-1 行)
  • src/modules/ai-job/ai-job-creation.service.ts(修改,+6 行:feynman_evaluation 安全分支)
  • src/modules/ai-job/ai-job.module.ts(修改,+1 行:export FeynmanSnapshotBuilder)

代码证据

  • feynman-execution-router.ts:71-145 — evaluateFeynman() 主路由逻辑
  • feynman-execution-router.ts:151-162 — shouldUseUnified()(FeatureFlag + 安全回退)
  • feynman-execution-router.ts:170-183 — resolveSubmissionId()(3 层回退策略)
  • ai-analysis.controller.ts:45-57 — Controller 注入 Router + 回退兼容
  • ai-job-creation.service.ts:81-96 — feynman_evaluation 安全分支(无 retrySnapshotContent → 抛错)

测试情况

  • 已运行命令:
    • npx jest --testPathPatterns="feynman-execution-router"
    • npx jest --testPathPatterns="ai-job|ai-analysis"
  • 结果:
    • 新增 14 tests:参数校验(3) + Legacy(2) + Unified(3) + 幂等键(4) + 响应兼容(2)
    • 全部通过:17 suites, 340 passed, 18 skipped, 0 failed

Feature Flag 配置

-- 启用 Feynman Unified(全局)
INSERT INTO FeatureFlag (name, enabled) VALUES ('FEYNMAN_ENGINE_MODE', true);

-- 仅白名单用户
INSERT INTO FeatureFlag (name, enabled, whitelist) 
VALUES ('FEYNMAN_ENGINE_MODE', true, 'user-001,user-002');

-- 回滚到 Legacy
UPDATE FeatureFlag SET enabled = false WHERE name = 'FEYNMAN_ENGINE_MODE';

未完成 / 风险

  • knowledgeItemId 未在请求体中(当前客户端不传),使用 unknown 占位或需客户端升级
  • FeynmanSnapshotBuilder.build() 在 Router 中调用(需要 knowledgeItemId 进行所有权验证),当前传入 unknown 会导致 SnapshotBuilder 查找失败 → 在 M-AI-05-07 E2E 中需要 Mock 或真实 Fixture
  • Engine 中尚未增加 feynman_evaluation jobType 的执行分派(当前 Engine 只处理 active_recall + 默认路径,feynman_evaluation 会走默认路径)

是否建议进入 Review

  • 是 — 入口路由已完成,Legacy/Unified 双分支 + 幂等 + 响应兼容,建议审核后启动 M-AI-05-06
## 开发完成评论 ### 完成内容 - 创建 `FeynmanExecutionRouter`(在 ai-analysis 模块中): - Feature Flag `FEYNMAN_ENGINE_MODE` 控制 legacy/unified 分支 - FeatureFlag 查询失败 → 安全回退到 Legacy - Unified 路径:参数校验 → submissionId → idempotencyKey → SnapshotBuilder → AiJobCreationService - 响应兼容:不删除旧字段,新增可选 `engineMode` + `lifecycleStatus` - Unified 失败不得自动调用 Legacy - 更新 `AiAnalysisController`: - 注入 `FeynmanExecutionRouter`(@Optional) - `knowledgeItemId` 作为可选字段加入请求体 - 如果 Router 未注入 → 回退到 Legacy(兼容旧模块导入) - 更新 `AiAnalysisModule`:导入 `AiJobModule` + `AppConfigModule`,提供 `FeynmanExecutionRouter` - 更新 `AiJobModule`:导出 `FeynmanSnapshotBuilder`(供 AiAnalysisModule 使用) - 在 `AiJobCreationService` 添加 `feynman_evaluation` 安全分支(无 retrySnapshotContent 时抛错) ### 路由流程 ``` POST /api/ai-analysis/feynman (不变) → AiAnalysisController.evaluateFeynman() → FeynmanExecutionRouter.evaluateFeynman() ├─ FEYNMAN_ENGINE_MODE=legacy(默认) │ → AiAnalysisService.evaluateFeynman() → 原链路 │ └─ FEYNMAN_ENGINE_MODE=unified → 参数校验 → resolveSubmissionId (sessionId+answerId / sessionId / content hash) → idempotencyKey = feynman:<submissionId> → FeynmanSnapshotBuilder.build() → AiJobCreationService.createJob({retrySnapshotContent}) → Job + Snapshot + Outbox 原子创建 → return { jobId, status, engineMode, lifecycleStatus } ``` ### 幂等键策略 - `sessionId + answerId` 都存在 → `feynman:sessionId:answerId` - 仅 `sessionId` → `feynman:sessionId` - 都不存在 → `feynman:<sha256(content).substring(0,16)>`(相同内容 → 相同 key) ### 修改文件 - `src/modules/ai-analysis/feynman-execution-router.ts`(新增,176 行) - `src/modules/ai-analysis/feynman-execution-router.spec.ts`(新增,225 行) - `src/modules/ai-analysis/ai-analysis.controller.ts`(修改,+10/-4 行) - `src/modules/ai-analysis/ai-analysis.module.ts`(修改,+4/-1 行) - `src/modules/ai-job/ai-job-creation.service.ts`(修改,+6 行:feynman_evaluation 安全分支) - `src/modules/ai-job/ai-job.module.ts`(修改,+1 行:export FeynmanSnapshotBuilder) ### 代码证据 - `feynman-execution-router.ts:71-145` — evaluateFeynman() 主路由逻辑 - `feynman-execution-router.ts:151-162` — shouldUseUnified()(FeatureFlag + 安全回退) - `feynman-execution-router.ts:170-183` — resolveSubmissionId()(3 层回退策略) - `ai-analysis.controller.ts:45-57` — Controller 注入 Router + 回退兼容 - `ai-job-creation.service.ts:81-96` — feynman_evaluation 安全分支(无 retrySnapshotContent → 抛错) ### 测试情况 - 已运行命令: - `npx jest --testPathPatterns="feynman-execution-router"` - `npx jest --testPathPatterns="ai-job|ai-analysis"` - 结果: - 新增 14 tests:参数校验(3) + Legacy(2) + Unified(3) + 幂等键(4) + 响应兼容(2) - 全部通过:17 suites, 340 passed, 18 skipped, 0 failed ### Feature Flag 配置 ```sql -- 启用 Feynman Unified(全局) INSERT INTO FeatureFlag (name, enabled) VALUES ('FEYNMAN_ENGINE_MODE', true); -- 仅白名单用户 INSERT INTO FeatureFlag (name, enabled, whitelist) VALUES ('FEYNMAN_ENGINE_MODE', true, 'user-001,user-002'); -- 回滚到 Legacy UPDATE FeatureFlag SET enabled = false WHERE name = 'FEYNMAN_ENGINE_MODE'; ``` ### 未完成 / 风险 - `knowledgeItemId` 未在请求体中(当前客户端不传),使用 `unknown` 占位或需客户端升级 - `FeynmanSnapshotBuilder.build()` 在 Router 中调用(需要 knowledgeItemId 进行所有权验证),当前传入 `unknown` 会导致 SnapshotBuilder 查找失败 → 在 M-AI-05-07 E2E 中需要 Mock 或真实 Fixture - Engine 中尚未增加 `feynman_evaluation` jobType 的执行分派(当前 Engine 只处理 `active_recall` + 默认路径,feynman_evaluation 会走默认路径) ### 是否建议进入 Review - 是 — 入口路由已完成,Legacy/Unified 双分支 + 幂等 + 响应兼容,建议审核后启动 M-AI-05-06
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wangdl/api-server#308
No description provided.