fix agent video routing failures
This commit is contained in:
parent
b75dfa9295
commit
6f0e49bfb9
@ -9,6 +9,14 @@ import {
|
|||||||
type AgentProjectToolDefinition,
|
type AgentProjectToolDefinition,
|
||||||
type AgentProjectToolName,
|
type AgentProjectToolName,
|
||||||
} from "@/lib/ai/tools-registry";
|
} 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 {
|
import {
|
||||||
getAiModelAgentCodingModelId,
|
getAiModelAgentCodingModelId,
|
||||||
isAiModelMultimodal,
|
isAiModelMultimodal,
|
||||||
@ -140,6 +148,7 @@ export interface AutoAgentInput {
|
|||||||
historyMessages?: HistoryMessage[];
|
historyMessages?: HistoryMessage[];
|
||||||
context?: string;
|
context?: string;
|
||||||
preferredModelId?: string;
|
preferredModelId?: string;
|
||||||
|
threadState?: AgentThreadState | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoAgentResult {
|
export interface AutoAgentResult {
|
||||||
@ -151,6 +160,8 @@ export interface AutoAgentResult {
|
|||||||
recommendedImageParams?: AgentRecommendedImageParams;
|
recommendedImageParams?: AgentRecommendedImageParams;
|
||||||
videoPlan?: AgentVideoPlan;
|
videoPlan?: AgentVideoPlan;
|
||||||
route?: RouteResult;
|
route?: RouteResult;
|
||||||
|
threadState?: AgentThreadState | null;
|
||||||
|
workflowDecision?: HumanConfirmationGateDecision;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AutoAgentState = Annotation.Root({
|
const AutoAgentState = Annotation.Root({
|
||||||
@ -168,6 +179,7 @@ const AutoAgentState = Annotation.Root({
|
|||||||
historyMessages: Annotation<HistoryMessage[]>,
|
historyMessages: Annotation<HistoryMessage[]>,
|
||||||
context: Annotation<string | null>,
|
context: Annotation<string | null>,
|
||||||
preferredModelId: Annotation<string | null>,
|
preferredModelId: Annotation<string | null>,
|
||||||
|
threadState: Annotation<AgentThreadState | null>,
|
||||||
analysis: Annotation<AnalysisResult | null>,
|
analysis: Annotation<AnalysisResult | null>,
|
||||||
availableTools: Annotation<AgentProjectToolDefinition[]>,
|
availableTools: Annotation<AgentProjectToolDefinition[]>,
|
||||||
plan: Annotation<AgentExecutionPlan | null>,
|
plan: Annotation<AgentExecutionPlan | null>,
|
||||||
@ -177,6 +189,7 @@ const AutoAgentState = Annotation.Root({
|
|||||||
recommendedImageParams: Annotation<AgentRecommendedImageParams | null>,
|
recommendedImageParams: Annotation<AgentRecommendedImageParams | null>,
|
||||||
videoPlan: Annotation<AgentVideoPlan | null>,
|
videoPlan: Annotation<AgentVideoPlan | null>,
|
||||||
route: Annotation<RouteResult | null>,
|
route: Annotation<RouteResult | null>,
|
||||||
|
workflowDecision: Annotation<HumanConfirmationGateDecision | null>,
|
||||||
});
|
});
|
||||||
|
|
||||||
type AutoAgentStateType = typeof AutoAgentState.State;
|
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 {
|
function getEnvInt(name: string, fallback: number, min: number, max: number): number {
|
||||||
const raw = process.env[name];
|
const raw = process.env[name];
|
||||||
const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
||||||
@ -1789,6 +1849,25 @@ const analyzeNode = async (state: AutoAgentStateType) => {
|
|||||||
return { analysis };
|
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) => {
|
const planNode = async (state: AutoAgentStateType) => {
|
||||||
if (!state.analysis) return {};
|
if (!state.analysis) return {};
|
||||||
const baseAvailableTools = await getEnabledAgentToolDefinitionsForContext({
|
const baseAvailableTools = await getEnabledAgentToolDefinitionsForContext({
|
||||||
@ -1960,14 +2039,19 @@ let compiledGraph: ReturnType<typeof buildGraph> | null = null;
|
|||||||
|
|
||||||
function buildGraph() {
|
function buildGraph() {
|
||||||
return new StateGraph(AutoAgentState)
|
return new StateGraph(AutoAgentState)
|
||||||
|
.addNode("confirmationGate", confirmationGateNode)
|
||||||
.addNode("analyze", analyzeNode)
|
.addNode("analyze", analyzeNode)
|
||||||
.addNode("plan", planNode)
|
.addNode("planner", planNode)
|
||||||
.addNode("prepareExecution", prepareExecutionNode)
|
.addNode("prepareExecution", prepareExecutionNode)
|
||||||
.addNode("optimize", optimizeNode)
|
.addNode("optimize", optimizeNode)
|
||||||
.addNode("routeModel", routeNode)
|
.addNode("routeModel", routeNode)
|
||||||
.addEdge(START, "analyze")
|
.addEdge(START, "confirmationGate")
|
||||||
.addEdge("analyze", "plan")
|
.addConditionalEdges("confirmationGate", shouldBypassPlanner, {
|
||||||
.addEdge("plan", "prepareExecution")
|
analyze: "analyze",
|
||||||
|
route: "routeModel",
|
||||||
|
})
|
||||||
|
.addEdge("analyze", "planner")
|
||||||
|
.addEdge("planner", "prepareExecution")
|
||||||
.addConditionalEdges("prepareExecution", shouldOptimize, {
|
.addConditionalEdges("prepareExecution", shouldOptimize, {
|
||||||
optimize: "optimize",
|
optimize: "optimize",
|
||||||
route: "routeModel",
|
route: "routeModel",
|
||||||
@ -2001,6 +2085,7 @@ export async function runAutoAgentGraph(params: AutoAgentInput): Promise<AutoAge
|
|||||||
historyMessages: params.historyMessages ?? [],
|
historyMessages: params.historyMessages ?? [],
|
||||||
context: params.context ?? null,
|
context: params.context ?? null,
|
||||||
preferredModelId: params.preferredModelId ?? null,
|
preferredModelId: params.preferredModelId ?? null,
|
||||||
|
threadState: normalizeAgentThreadState(params.threadState),
|
||||||
analysis: null,
|
analysis: null,
|
||||||
availableTools: [],
|
availableTools: [],
|
||||||
plan: null,
|
plan: null,
|
||||||
@ -2010,6 +2095,7 @@ export async function runAutoAgentGraph(params: AutoAgentInput): Promise<AutoAge
|
|||||||
recommendedImageParams: null,
|
recommendedImageParams: null,
|
||||||
videoPlan: null,
|
videoPlan: null,
|
||||||
route: null,
|
route: null,
|
||||||
|
workflowDecision: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -2021,5 +2107,7 @@ export async function runAutoAgentGraph(params: AutoAgentInput): Promise<AutoAge
|
|||||||
recommendedImageParams: result.recommendedImageParams ?? undefined,
|
recommendedImageParams: result.recommendedImageParams ?? undefined,
|
||||||
videoPlan: result.videoPlan ?? undefined,
|
videoPlan: result.videoPlan ?? undefined,
|
||||||
route: result.route ?? undefined,
|
route: result.route ?? undefined,
|
||||||
|
threadState: result.threadState ?? undefined,
|
||||||
|
workflowDecision: result.workflowDecision ?? undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,7 +74,35 @@ async function selectBestSubModel(
|
|||||||
const subModels = await getSubModelsByParentModelId(parentModelId);
|
const subModels = await getSubModelsByParentModelId(parentModelId);
|
||||||
|
|
||||||
if (subModels.length === 0) {
|
if (subModels.length === 0) {
|
||||||
throw new Error("没有可用的视频生成子模型");
|
const allModels = await getEnabledAiModelsWithProvider();
|
||||||
|
const directModel = allModels.find(
|
||||||
|
(item) =>
|
||||||
|
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} 个子模型`);
|
console.log(`[Smart Model Selection] 找到 ${subModels.length} 个子模型`);
|
||||||
@ -263,7 +291,7 @@ async function resolvePreferredVideoParentModelId(preferredModelRef?: string) {
|
|||||||
const models = await getEnabledAiModelsWithProvider();
|
const models = await getEnabledAiModelsWithProvider();
|
||||||
const matched = models.find(
|
const matched = models.find(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.provider.type === "volcano-video" &&
|
(item.provider.type === "volcano-video" || item.provider.type === "doubao-video") &&
|
||||||
(item.model.id === normalizedRef || item.model.modelId === normalizedRef),
|
(item.model.id === normalizedRef || item.model.modelId === normalizedRef),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -289,12 +317,14 @@ export async function executeDirectVideoGeneration(
|
|||||||
const fallbackParentModel =
|
const fallbackParentModel =
|
||||||
allModels.find(
|
allModels.find(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.provider.type === "volcano-video" &&
|
(item.provider.type === "volcano-video" || item.provider.type === "doubao-video") &&
|
||||||
!item.model.isSubModel &&
|
!item.model.isSubModel &&
|
||||||
item.model.modelId === "volcano-video-master",
|
item.model.modelId === "volcano-video-master",
|
||||||
) ||
|
) ||
|
||||||
allModels.find(
|
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;
|
const parentModelId = preferredParentModelId || fallbackParentModel?.model.id;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user