fix agent video routing failures
This commit is contained in:
parent
b75dfa9295
commit
6f0e49bfb9
@ -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<HistoryMessage[]>,
|
||||
context: Annotation<string | null>,
|
||||
preferredModelId: Annotation<string | null>,
|
||||
threadState: Annotation<AgentThreadState | null>,
|
||||
analysis: Annotation<AnalysisResult | null>,
|
||||
availableTools: Annotation<AgentProjectToolDefinition[]>,
|
||||
plan: Annotation<AgentExecutionPlan | null>,
|
||||
@ -177,6 +189,7 @@ const AutoAgentState = Annotation.Root({
|
||||
recommendedImageParams: Annotation<AgentRecommendedImageParams | null>,
|
||||
videoPlan: Annotation<AgentVideoPlan | null>,
|
||||
route: Annotation<RouteResult | null>,
|
||||
workflowDecision: Annotation<HumanConfirmationGateDecision | null>,
|
||||
});
|
||||
|
||||
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<typeof buildGraph> | 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<AutoAge
|
||||
historyMessages: params.historyMessages ?? [],
|
||||
context: params.context ?? null,
|
||||
preferredModelId: params.preferredModelId ?? null,
|
||||
threadState: normalizeAgentThreadState(params.threadState),
|
||||
analysis: null,
|
||||
availableTools: [],
|
||||
plan: null,
|
||||
@ -2010,6 +2095,7 @@ export async function runAutoAgentGraph(params: AutoAgentInput): Promise<AutoAge
|
||||
recommendedImageParams: null,
|
||||
videoPlan: null,
|
||||
route: null,
|
||||
workflowDecision: null,
|
||||
});
|
||||
|
||||
return {
|
||||
@ -2021,5 +2107,7 @@ export async function runAutoAgentGraph(params: AutoAgentInput): Promise<AutoAge
|
||||
recommendedImageParams: result.recommendedImageParams ?? undefined,
|
||||
videoPlan: result.videoPlan ?? 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);
|
||||
|
||||
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} 个子模型`);
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user