From 6f0e49bfb92f80de0a717054ab9bce224bcbbd49 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Mar 2026 10:28:00 +0800 Subject: [PATCH] fix agent video routing failures --- lib/ai/auto-agent-graph.ts | 96 ++++++++++++++++++++++++++++++++-- lib/ai/tools/generate-video.ts | 38 ++++++++++++-- 2 files changed, 126 insertions(+), 8 deletions(-) diff --git a/lib/ai/auto-agent-graph.ts b/lib/ai/auto-agent-graph.ts index 0ebf6a0b1..4eb19bdfb 100644 --- a/lib/ai/auto-agent-graph.ts +++ b/lib/ai/auto-agent-graph.ts @@ -9,6 +9,14 @@ import { type AgentProjectToolDefinition, type AgentProjectToolName, } from "@/lib/ai/tools-registry"; +import { + runHumanConfirmationGate, + type HumanConfirmationGateDecision, +} from "@/lib/ai/runtime/middlewares/human-confirmation-gate"; +import { + normalizeAgentThreadState, + type AgentThreadState, +} from "@/lib/ai/runtime/agent-thread-state"; import { getAiModelAgentCodingModelId, isAiModelMultimodal, @@ -140,6 +148,7 @@ export interface AutoAgentInput { historyMessages?: HistoryMessage[]; context?: string; preferredModelId?: string; + threadState?: AgentThreadState | null; } export interface AutoAgentResult { @@ -151,6 +160,8 @@ export interface AutoAgentResult { recommendedImageParams?: AgentRecommendedImageParams; videoPlan?: AgentVideoPlan; route?: RouteResult; + threadState?: AgentThreadState | null; + workflowDecision?: HumanConfirmationGateDecision; } const AutoAgentState = Annotation.Root({ @@ -168,6 +179,7 @@ const AutoAgentState = Annotation.Root({ historyMessages: Annotation, context: Annotation, preferredModelId: Annotation, + threadState: Annotation, analysis: Annotation, availableTools: Annotation, plan: Annotation, @@ -177,6 +189,7 @@ const AutoAgentState = Annotation.Root({ recommendedImageParams: Annotation, videoPlan: Annotation, route: Annotation, + workflowDecision: Annotation, }); type AutoAgentStateType = typeof AutoAgentState.State; @@ -425,6 +438,53 @@ function prepareExecutionFromPlan(params: { }; } +function buildWorkflowGateBypassPayload( + state: AutoAgentStateType, + decision: HumanConfirmationGateDecision, +) { + const analysis: AnalysisResult = { + intent: "chat", + confidence: 0.98, + needsClarification: decision.action === "awaiting_confirmation", + clarifyingQuestions: + decision.action === "awaiting_confirmation" + ? ["请先确认或修改当前待确认流程,我再继续执行。"] + : [], + conversationalReply: decision.friendlyMessage, + }; + + const plan: AgentExecutionPlan = { + goal: createPlanGoalSummary(state.text, state.hasImage), + nextAction: "respond", + intent: "chat", + targetSource: state.targetResolutionMode, + preferredCapability: "respond", + responseMode: "direct_answer", + shouldUseHistoryImage: false, + shouldUseStructuredReply: false, + confidence: 0.98, + toolCalls: [ + { + name: "respondDirect", + reason: "当前工作流处于人工确认门控阶段,先处理用户确认或修改意图。", + }, + ], + }; + + const execution = prepareExecutionFromPlan({ + plan, + availableTools: [], + }); + + return { + analysis, + plan, + execution, + friendlyMessage: decision.friendlyMessage || null, + workflowDecision: decision, + }; +} + function getEnvInt(name: string, fallback: number, min: number, max: number): number { const raw = process.env[name]; const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN; @@ -1789,6 +1849,25 @@ const analyzeNode = async (state: AutoAgentStateType) => { return { analysis }; }; +const confirmationGateNode = async (state: AutoAgentStateType) => { + const workflowDecision = runHumanConfirmationGate({ + text: state.text, + threadState: state.threadState, + }); + + if (!workflowDecision.shouldBypassPlanner) { + return { + workflowDecision, + }; + } + + return buildWorkflowGateBypassPayload(state, workflowDecision); +}; + +const shouldBypassPlanner = (state: AutoAgentStateType) => { + return state.workflowDecision?.shouldBypassPlanner ? "route" : "analyze"; +}; + const planNode = async (state: AutoAgentStateType) => { if (!state.analysis) return {}; const baseAvailableTools = await getEnabledAgentToolDefinitionsForContext({ @@ -1960,14 +2039,19 @@ let compiledGraph: ReturnType | null = null; function buildGraph() { return new StateGraph(AutoAgentState) + .addNode("confirmationGate", confirmationGateNode) .addNode("analyze", analyzeNode) - .addNode("plan", planNode) + .addNode("planner", planNode) .addNode("prepareExecution", prepareExecutionNode) .addNode("optimize", optimizeNode) .addNode("routeModel", routeNode) - .addEdge(START, "analyze") - .addEdge("analyze", "plan") - .addEdge("plan", "prepareExecution") + .addEdge(START, "confirmationGate") + .addConditionalEdges("confirmationGate", shouldBypassPlanner, { + analyze: "analyze", + route: "routeModel", + }) + .addEdge("analyze", "planner") + .addEdge("planner", "prepareExecution") .addConditionalEdges("prepareExecution", shouldOptimize, { optimize: "optimize", route: "routeModel", @@ -2001,6 +2085,7 @@ export async function runAutoAgentGraph(params: AutoAgentInput): Promise + item.model.id === parentModelId && + (item.provider.type === "volcano-video" || item.provider.type === "doubao-video"), + ); + + if (!directModel) { + throw new Error("没有可用的视频生成子模型"); + } + + const directCapabilities = directModel.model.capabilities as VideoModelCapabilities | undefined; + const supportedModes = getSupportedModes(directCapabilities, directModel.model.modelId); + if (!supportedModes.includes(mode)) { + throw new Error(`模型 ${directModel.model.displayName} 不支持 ${mode} 模式`); + } + + return { + model: directModel, + capabilities: + directCapabilities || + ({ + supportedModes, + supportedResolutions: ["480p", "720p", "1080p"], + supportedRatios: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive"], + durationRange: { min: 2, max: 12 }, + maxReferenceImages: 4, + } as VideoModelCapabilities), + }; } console.log(`[Smart Model Selection] 找到 ${subModels.length} 个子模型`); @@ -263,7 +291,7 @@ async function resolvePreferredVideoParentModelId(preferredModelRef?: string) { const models = await getEnabledAiModelsWithProvider(); const matched = models.find( (item) => - item.provider.type === "volcano-video" && + (item.provider.type === "volcano-video" || item.provider.type === "doubao-video") && (item.model.id === normalizedRef || item.model.modelId === normalizedRef), ); @@ -289,12 +317,14 @@ export async function executeDirectVideoGeneration( const fallbackParentModel = allModels.find( (item) => - item.provider.type === "volcano-video" && + (item.provider.type === "volcano-video" || item.provider.type === "doubao-video") && !item.model.isSubModel && item.model.modelId === "volcano-video-master", ) || allModels.find( - (item) => item.provider.type === "volcano-video" && !item.model.isSubModel, + (item) => + (item.provider.type === "volcano-video" || item.provider.type === "doubao-video") && + !item.model.isSubModel, ); const parentModelId = preferredParentModelId || fallbackParentModel?.model.id;