23 files (+4676/-10): - Contract: m-ai-05-feynman-migration-contract.md (737 lines) - Gate audit: m-ai-05-gate-audit.md (318 lines) - Job Definition + Snapshot Builder + Registration - Executor + BusinessValidator + ReferenceValidator - Projector (atomic transaction + 3-layer idempotency) - ExecutionRouter (FeatureFlag + idempotencyKey) - ObservabilityService (structured logging + counters) - Engine: feynman_evaluation execution branch - AiJobCreationService: feynman_evaluation safety branch - Controller/Module: Router injection - CI: path detection for m-ai-05 - E2E: 8 HTTP-layer scenarios (14 total) - Unit tests: 104 new tests (5 spec files) Co-Authored-By: Claude <noreply@anthropic.com>
100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
||
import { AiGatewayService } from '../ai/gateway/ai-gateway.service';
|
||
import { FeynmanEvaluationResultSchema } from '../ai/prompts/schemas/feynman-evaluation.schema';
|
||
import type { FeynmanEvaluationResult } from '../ai/prompts/schemas/feynman-evaluation.schema';
|
||
import type { FeynmanSnapshot } from './feynman-snapshot-builder';
|
||
|
||
/**
|
||
* M-AI-05-03: Feynman Executor
|
||
*
|
||
* 将 Feynman 评估输入快照适配到统一 Job Engine 的 EXECUTE 阶段。
|
||
*
|
||
* 职责:
|
||
* 1. 从 Snapshot 构造模型请求消息(复用现有 Feynman prompt 模板逻辑)
|
||
* 2. 通过 AiGatewayService 调用模型(不直接导入 Provider SDK)
|
||
* 3. 接收 timeout → 委托给 AiGatewayService 内部的 AbortController
|
||
* 4. 返回 AiGatewayService 的原始响应(parsed output)
|
||
*
|
||
* 不负责(由 Engine 统一处理):
|
||
* - 写数据库(无副作用)
|
||
* - 写 Job 状态
|
||
* - 重试逻辑
|
||
* - 写 Artifact
|
||
* - 解析 Credential
|
||
*
|
||
* 兼容性:
|
||
* - 使用与旧链路相同的 promptKey(feynman-evaluation)和 outputSchema
|
||
* - 消息构造逻辑与 FeynmanEvaluationWorkflow.execute() 一致
|
||
* (src/modules/ai/workflows/feynman-evaluation.workflow.ts:18-29)
|
||
*/
|
||
|
||
@Injectable()
|
||
export class FeynmanExecutor {
|
||
private readonly logger = new Logger(FeynmanExecutor.name);
|
||
|
||
constructor(private readonly aiGateway: AiGatewayService) {}
|
||
|
||
/**
|
||
* 执行 Feynman 评估 AI 分析。
|
||
*
|
||
* @param snapshot - FeynmanSnapshot(由 FeynmanSnapshotBuilder 产出)
|
||
* @param timeoutMs - 超时毫秒数(来自 Definition.execution.timeoutMs)
|
||
* @returns AiGateway 响应(含 parsed + usage)
|
||
*/
|
||
async execute(
|
||
snapshot: FeynmanSnapshot,
|
||
timeoutMs: number,
|
||
) {
|
||
const s = snapshot.snapshot;
|
||
|
||
// 构造用户消息(与旧链路 FeynmanEvaluationWorkflow.execute() 一致)
|
||
// workflow.ts:18-29 的消息格式:
|
||
// 【知识点标题】+ title + 【知识点原文】+ content + 【用户的费曼解释】+ explanation
|
||
const userMessage = [
|
||
`【知识点标题】`,
|
||
s.knowledgeItemTitle,
|
||
'',
|
||
`【知识点原文】`,
|
||
s.knowledgeItemContent,
|
||
'',
|
||
`【用户的费曼解释】`,
|
||
s.userExplanation,
|
||
'',
|
||
`请评估以上费曼解释的质量,严格按照 JSON Schema 输出。`,
|
||
].join('\n');
|
||
|
||
this.logger.log(
|
||
`Feynman Executor calling AI: userId=${s.userId} ` +
|
||
`knowledgeItemId=${s.knowledgeItemId} ` +
|
||
`submissionId=${s.submissionId} ` +
|
||
`promptKey=${s.promptKey} promptVersion=${s.promptVersion} ` +
|
||
`modelTier=${s.modelTier} timeoutMs=${timeoutMs}`,
|
||
);
|
||
|
||
const response = await this.aiGateway.generate(
|
||
{
|
||
feature: 'feynman-evaluation',
|
||
userId: s.userId,
|
||
tier: s.modelTier as any,
|
||
promptKey: s.promptKey,
|
||
promptVersion: s.promptVersion,
|
||
messages: [
|
||
{ role: 'user' as const, content: userMessage },
|
||
],
|
||
outputSchema: FeynmanEvaluationResultSchema,
|
||
maxTokens: 4096,
|
||
},
|
||
timeoutMs,
|
||
);
|
||
|
||
this.logger.log(
|
||
`Feynman Executor completed: userId=${s.userId} ` +
|
||
`knowledgeItemId=${s.knowledgeItemId} ` +
|
||
`score=${(response.parsed as any)?.score} ` +
|
||
`tokens=${response.usage.inputTokens}/${response.usage.outputTokens}`,
|
||
);
|
||
|
||
return response;
|
||
}
|
||
}
|