92 lines
3.9 KiB
TypeScript
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.

import type { AgentRunSnapshot } from "./protocol";
import { videoNodeType, videoNodeUrl, type VideoRuntimeNode } from "./video-protocol";
export async function verifyVideoAgentResults(params: {
snapshot: AgentRunSnapshot;
nodes: VideoRuntimeNode[];
inspect: (
node: VideoRuntimeNode,
requirements: string,
) => Promise<{
passed: boolean;
issues: string[];
duration?: number;
width?: number;
height?: number;
}>;
}) {
const { snapshot } = params;
const requirements = snapshot.videoRequirements;
const produced = snapshot.resultNodeIds.map((id) => params.nodes.find((node) => node.id === id));
const issues: string[] = [];
if (produced.some((node) => !node)) issues.push("部分结果节点已不存在,请核对画布");
const videos = produced.filter((node): node is VideoRuntimeNode =>
Boolean(node && videoNodeType(node) === "video"),
);
const scripts = produced.filter((node) => node && ["script", "text"].includes(node.kind));
const plan = snapshot.calls.find(
(call) => call.name === "planCanvasTask" && call.status === "succeeded",
)?.output;
const expected = requirements?.expectedVideoCount ?? Number(plan?.expectedVideoCount || 1);
if (requirements?.expectsVideo && videos.length !== expected)
issues.push(`需要 ${expected} 条视频,当前仅完成 ${videos.length} 条;脚本和关键帧不计为成片`);
if (requirements?.expectsScript && !scripts.length) issues.push("尚未交付分镜脚本节点");
if (
!produced.length &&
!snapshot.calls.some(
(call) =>
["analyzeVideoMaterials", "editVideoCanvas", "listVideoCanvasCapabilities"].includes(
call.name,
) && call.status === "succeeded",
)
)
issues.push("尚未交付任何创作或分析结果");
for (const video of videos) {
if (!videoNodeUrl(video)) {
issues.push(`${video.title} 缺少可用视频地址`);
continue;
}
if (requirements?.duration && Number(video.draftValues?.duration) !== requirements.duration)
issues.push(`${video.title} 生成时长未采用最新要求 ${requirements.duration} 秒`);
if (requirements?.ratio && video.draftValues?.ratio !== requirements.ratio)
issues.push(`${video.title} 生成比例未采用最新要求 ${requirements.ratio}`);
try {
const result = await params.inspect(
video,
[snapshot.objective, ...snapshot.inputs.map((input) => input.text)].join("\n"),
);
if (!result.duration || !result.width || !result.height)
issues.push(`${video.title} 缺少实际视频时长或尺寸,不能仅按请求参数验收`);
const duration =
requirements?.duration ||
(video.draftValues?.mediaOperation ? undefined : snapshot.videoSettings?.duration);
if (duration && result.duration && Math.abs(result.duration - duration) > 1)
issues.push(
`${video.title} 实际时长 ${result.duration.toFixed(1)} 秒,不符合 ${duration} 秒要求`,
);
const ratio = (
requirements?.ratio ||
(video.draftValues?.mediaOperation ? undefined : snapshot.videoSettings?.ratio) ||
""
)
.split(":")
.map(Number);
if (
result.width &&
result.height &&
ratio.length === 2 &&
Math.abs(result.width / result.height - ratio[0] / ratio[1]) > 0.05
)
issues.push(`${video.title} 实际尺寸 ${result.width}×${result.height} 不符合指定比例`);
if (!result.passed || result.issues.length)
issues.push(
...(result.issues.length ? result.issues : [`${video.title} 的画面内容需要检查`]),
);
} catch {
issues.push(`${video.title} 暂时无法读取或完成画面检查;已保留生成结果,请稍后核对`);
}
}
// A failed visual review must never trigger another paid video automatically.
return { passed: issues.length === 0, issues: issues.slice(0, 12), retryable: false };
}