fix: improve agent image intent handling
This commit is contained in:
parent
3ebe7db37d
commit
dd4426ded7
@ -6463,6 +6463,7 @@ export async function POST(request: Request) {
|
||||
})
|
||||
.filter(Boolean) as Array<{ role: "user" | "assistant"; content: string }>,
|
||||
context: effectiveAutoContextText || undefined,
|
||||
preferredModelId: preferredAgentModelId,
|
||||
}).catch(() =>
|
||||
optimizePrompt({
|
||||
originalPrompt: promptSeed,
|
||||
|
||||
@ -284,6 +284,30 @@ function defaultToolForAction(action: AgentPlanNextAction): AgentProjectToolName
|
||||
}
|
||||
}
|
||||
|
||||
function candidateToolsForAction(action: AgentPlanNextAction): AgentProjectToolName[] {
|
||||
switch (action) {
|
||||
case "analyze_image":
|
||||
return ["analyzeTargetImage", "analyzeImage"];
|
||||
case "edit_image":
|
||||
return ["editTargetImage", "editImage"];
|
||||
case "generate_image":
|
||||
return ["generateNewImage", "generateImage"];
|
||||
case "generate_video":
|
||||
return ["generateVideo"];
|
||||
case "clarify":
|
||||
case "respond":
|
||||
default:
|
||||
return ["respondDirect"];
|
||||
}
|
||||
}
|
||||
|
||||
function pickAvailableToolForAction(
|
||||
action: AgentPlanNextAction,
|
||||
availableNames: Set<AgentProjectToolName>,
|
||||
): AgentProjectToolName | undefined {
|
||||
return candidateToolsForAction(action).find((name) => availableNames.has(name));
|
||||
}
|
||||
|
||||
function mapIntentToAction(intent: AnalysisResult["intent"]): AgentPlanNextAction {
|
||||
switch (intent) {
|
||||
case "vision":
|
||||
@ -395,9 +419,26 @@ function sanitizePlannedToolCalls(
|
||||
});
|
||||
}
|
||||
|
||||
if (sanitized.length > 0) return sanitized;
|
||||
if (sanitized.length > 0) {
|
||||
const hasPrimaryActionTool = sanitized.some((tool) =>
|
||||
candidateToolsForAction(fallbackAction).includes(tool.name),
|
||||
);
|
||||
const fallbackTool = pickAvailableToolForAction(fallbackAction, availableNames);
|
||||
if (
|
||||
!hasPrimaryActionTool &&
|
||||
fallbackTool &&
|
||||
fallbackAction !== "respond" &&
|
||||
fallbackAction !== "clarify"
|
||||
) {
|
||||
sanitized.push({
|
||||
name: fallbackTool,
|
||||
reason: "补齐本轮主执行工具,避免只解析目标图后停住。",
|
||||
});
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
const fallbackTool = defaultToolForAction(fallbackAction);
|
||||
const fallbackTool = pickAvailableToolForAction(fallbackAction, availableNames) || defaultToolForAction(fallbackAction);
|
||||
if (
|
||||
(fallbackAction === "analyze_image" || fallbackAction === "edit_image") &&
|
||||
options?.targetSource &&
|
||||
@ -698,6 +739,59 @@ function normalizeAspectRatio(value?: string): string | undefined {
|
||||
return allowed.has(raw) ? raw : undefined;
|
||||
}
|
||||
|
||||
function parseChineseCountToken(token?: string): number | undefined {
|
||||
const normalized = String(token || "").trim().toLowerCase();
|
||||
if (!normalized) return undefined;
|
||||
const numeric = Number(normalized);
|
||||
if (Number.isFinite(numeric) && numeric >= 1) return Math.round(numeric);
|
||||
const map: Record<string, number> = {
|
||||
一: 1,
|
||||
二: 2,
|
||||
两: 2,
|
||||
三: 3,
|
||||
四: 4,
|
||||
五: 5,
|
||||
六: 6,
|
||||
七: 7,
|
||||
八: 8,
|
||||
九: 9,
|
||||
十: 10,
|
||||
};
|
||||
return map[normalized];
|
||||
}
|
||||
|
||||
function inferExplicitImageCountFromText(text: string): number | undefined {
|
||||
const normalized = String(text || "").toLowerCase().replace(/\s+/g, "");
|
||||
const match =
|
||||
normalized.match(/([1-9]\d?|一|二|两|三|四|五|六|七|八|九|十)张/) ||
|
||||
normalized.match(/([1-9]\d?|一|二|两|三|四|五|六|七|八|九|十)(?:个|组)?(?:版本|方案|变体)/);
|
||||
const parsed = parseChineseCountToken(match?.[1]);
|
||||
if (parsed && parsed >= 1) return Math.min(parsed, 12);
|
||||
if (/多张/.test(normalized)) return 3;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function inferExplicitAspectRatioFromText(text: string): string | undefined {
|
||||
const normalized = String(text || "").toLowerCase().replace(/\s+/g, "");
|
||||
const explicitRatio = normalized.match(/(21:9|16:9|9:16|5:4|4:5|4:3|3:4|3:2|2:3|1:1)/)?.[1];
|
||||
if (explicitRatio) return explicitRatio;
|
||||
if (/竖图|竖版|竖屏|手机壁纸|9比16/.test(normalized)) return "9:16";
|
||||
if (/横图|横版|横屏|宽屏|banner|16比9/.test(normalized)) return "16:9";
|
||||
if (/方图|方形|头像|logo|电商主图|1比1/.test(normalized)) return "1:1";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractExplicitImageParamsFromText(
|
||||
text: string,
|
||||
): Pick<AgentRecommendedImageParams, "aspectRatio" | "imageCount"> {
|
||||
const output: Pick<AgentRecommendedImageParams, "aspectRatio" | "imageCount"> = {};
|
||||
const aspectRatio = inferExplicitAspectRatioFromText(text);
|
||||
const imageCount = inferExplicitImageCountFromText(text);
|
||||
if (aspectRatio) output.aspectRatio = aspectRatio;
|
||||
if (imageCount) output.imageCount = imageCount;
|
||||
return output;
|
||||
}
|
||||
|
||||
function normalizeImageSize(value?: string): string | undefined {
|
||||
if (!value) return undefined;
|
||||
const normalized = value.trim().toUpperCase();
|
||||
@ -964,9 +1058,8 @@ function heuristicImageParams(
|
||||
if (analysis.intent === "image_edit") {
|
||||
params.imageCount = 1;
|
||||
} else {
|
||||
const explicitCountMatch = normalized.match(/([1-9]\d?)张/);
|
||||
const explicitCount = explicitCountMatch ? Number(explicitCountMatch[1]) : 0;
|
||||
if (explicitCount >= 1) {
|
||||
const explicitCount = inferExplicitImageCountFromText(text);
|
||||
if (explicitCount && explicitCount >= 1) {
|
||||
params.imageCount = Math.min(explicitCount, 12);
|
||||
} else if (/多张|四张|4张|三张|3张/.test(normalized)) {
|
||||
params.imageCount = /四张|4张/.test(normalized) ? 4 : 3;
|
||||
@ -977,7 +1070,10 @@ function heuristicImageParams(
|
||||
}
|
||||
}
|
||||
|
||||
if (/横图|横版|宽屏|封面|banner|海报/.test(normalized)) {
|
||||
const explicitAspectRatio = inferExplicitAspectRatioFromText(text);
|
||||
if (explicitAspectRatio) {
|
||||
params.aspectRatio = explicitAspectRatio;
|
||||
} else if (/横图|横版|宽屏|封面|banner|海报/.test(normalized)) {
|
||||
params.aspectRatio = "16:9";
|
||||
} else if (/竖图|竖版|手机壁纸|全身|海报竖版/.test(normalized)) {
|
||||
params.aspectRatio = "9:16";
|
||||
@ -1313,6 +1409,59 @@ function sanitizeAgentPlan(
|
||||
};
|
||||
}
|
||||
|
||||
async function refinePlanWithSkillRoute(params: {
|
||||
text: string;
|
||||
analysis: AnalysisResult;
|
||||
plan: AgentExecutionPlan;
|
||||
availableTools: AgentProjectToolDefinition[];
|
||||
skillRoute?: AgentSkillRouteResult;
|
||||
hasImage: boolean;
|
||||
hasCanvasSelection: boolean;
|
||||
targetResolutionMode: AgentTargetResolutionMode;
|
||||
mediaSource: "current_message" | "canvas_selection" | "history" | "none";
|
||||
selectedImageCount: number;
|
||||
threadState: AgentThreadState | null;
|
||||
}): Promise<AgentExecutionPlan> {
|
||||
if (!params.skillRoute) return params.plan;
|
||||
|
||||
const { refinePlanWithSpecialist } = await import("./specialists");
|
||||
let refined = params.plan;
|
||||
for (const specialist of [params.skillRoute.primary, ...params.skillRoute.overlays]) {
|
||||
refined = refinePlanWithSpecialist(specialist, {
|
||||
text: params.text,
|
||||
analysis: params.analysis,
|
||||
plan: refined,
|
||||
availableTools: params.availableTools,
|
||||
hasImage: params.hasImage,
|
||||
hasCanvasSelection: params.hasCanvasSelection,
|
||||
targetResolutionMode: params.targetResolutionMode,
|
||||
mediaSource: params.mediaSource,
|
||||
selectedImageCount: params.selectedImageCount,
|
||||
threadState: params.threadState,
|
||||
});
|
||||
}
|
||||
return refined;
|
||||
}
|
||||
|
||||
function shouldUseLightweightPlanner(
|
||||
state: AutoAgentStateType,
|
||||
analysis: AnalysisResult,
|
||||
): boolean {
|
||||
if (process.env.AGENT_FORCE_LLM_PLANNER === "true") return false;
|
||||
if (analysis.needsClarification) return false;
|
||||
if (analysis.confidence < 0.9) return false;
|
||||
if (analysis.intent === "image_gen") return true;
|
||||
if (analysis.intent === "video_gen") return true;
|
||||
if (
|
||||
analysis.intent === "image_edit" &&
|
||||
state.targetResolutionMode !== "clarification" &&
|
||||
(state.hasImage || state.imageFromHistory || state.historyImageCount > 0 || state.selectedImageCount > 0)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function buildExecutionPlanWithAgent(
|
||||
state: AutoAgentStateType,
|
||||
analysis: AnalysisResult,
|
||||
@ -1328,6 +1477,28 @@ async function buildExecutionPlanWithAgent(
|
||||
availableTools,
|
||||
});
|
||||
|
||||
if (shouldUseLightweightPlanner(state, analysis)) {
|
||||
const refinedFallbackPlan = await refinePlanWithSkillRoute({
|
||||
text: state.text,
|
||||
analysis,
|
||||
plan: fallbackPlan,
|
||||
availableTools,
|
||||
skillRoute,
|
||||
hasImage: state.hasImage,
|
||||
hasCanvasSelection: state.hasCanvasSelection || state.selectedImageCount > 0,
|
||||
targetResolutionMode: state.targetResolutionMode,
|
||||
mediaSource: state.mediaSource,
|
||||
selectedImageCount: state.selectedImageCount,
|
||||
threadState: state.threadState,
|
||||
});
|
||||
console.log("[Auto Agent] Lightweight planner used:", {
|
||||
intent: analysis.intent,
|
||||
confidence: analysis.confidence,
|
||||
nextAction: refinedFallbackPlan.nextAction,
|
||||
});
|
||||
return refinedFallbackPlan;
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
getEnabledAiModelsWithProvider,
|
||||
@ -1445,23 +1616,19 @@ async function buildExecutionPlanWithAgent(
|
||||
|
||||
const parsed = parseJsonBlock<Record<string, unknown>>(text);
|
||||
let planned = sanitizeAgentPlan(parsed, fallbackPlan, availableTools);
|
||||
if (skillRoute) {
|
||||
const { refinePlanWithSpecialist } = await import("./specialists");
|
||||
for (const specialist of [skillRoute.primary, ...skillRoute.overlays]) {
|
||||
planned = refinePlanWithSpecialist(specialist, {
|
||||
text: state.text,
|
||||
analysis,
|
||||
plan: planned,
|
||||
availableTools,
|
||||
hasImage: state.hasImage,
|
||||
hasCanvasSelection: state.hasCanvasSelection || state.selectedImageCount > 0,
|
||||
targetResolutionMode: state.targetResolutionMode,
|
||||
mediaSource: state.mediaSource,
|
||||
selectedImageCount: state.selectedImageCount,
|
||||
threadState: state.threadState,
|
||||
});
|
||||
}
|
||||
}
|
||||
planned = await refinePlanWithSkillRoute({
|
||||
text: state.text,
|
||||
analysis,
|
||||
plan: planned,
|
||||
availableTools,
|
||||
skillRoute,
|
||||
hasImage: state.hasImage,
|
||||
hasCanvasSelection: state.hasCanvasSelection || state.selectedImageCount > 0,
|
||||
targetResolutionMode: state.targetResolutionMode,
|
||||
mediaSource: state.mediaSource,
|
||||
selectedImageCount: state.selectedImageCount,
|
||||
threadState: state.threadState,
|
||||
});
|
||||
console.log(
|
||||
`[Auto Agent] Planner model: ${plannerModelConfig.model.displayName} (${plannerModelConfig.provider.type})`,
|
||||
);
|
||||
@ -2017,6 +2184,18 @@ const shouldOptimize = (state: AutoAgentStateType) => {
|
||||
return state.execution.shouldOptimizePrompt ? "optimize" : "route";
|
||||
};
|
||||
|
||||
function shouldUseLlmPromptOptimizer(state: AutoAgentStateType): boolean {
|
||||
if (!state.analysis || !state.execution?.shouldApplyImagePipeline) return false;
|
||||
if (process.env.AGENT_LLM_PROMPT_OPTIMIZER_ENABLED === "false") return false;
|
||||
if (
|
||||
state.analysis.intent === "image_gen" &&
|
||||
process.env.AGENT_IMAGE_GEN_LLM_OPTIMIZER_ENABLED !== "true"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const optimizeNode = async (state: AutoAgentStateType) => {
|
||||
if (!state.analysis || !state.execution) {
|
||||
return {};
|
||||
@ -2053,19 +2232,20 @@ const optimizeNode = async (state: AutoAgentStateType) => {
|
||||
return {};
|
||||
}
|
||||
const { optimizePrompt, optimizePromptWithLLM } = await import("./prompt-optimizer");
|
||||
const optimizedFromLlm = await withTimeoutFallback<
|
||||
Awaited<ReturnType<typeof optimizePromptWithLLM>> | null
|
||||
>(
|
||||
optimizePromptWithLLM({
|
||||
originalPrompt: state.text,
|
||||
intent: state.analysis.intent,
|
||||
subIntent: state.analysis.subIntent,
|
||||
historyMessages: state.historyMessages,
|
||||
context: state.context || undefined,
|
||||
}),
|
||||
AGENT_OPERATION_TIMEOUT_MS,
|
||||
null,
|
||||
);
|
||||
const optimizedFromLlm = shouldUseLlmPromptOptimizer(state)
|
||||
? await withTimeoutFallback<Awaited<ReturnType<typeof optimizePromptWithLLM>> | null>(
|
||||
optimizePromptWithLLM({
|
||||
originalPrompt: state.text,
|
||||
intent: state.analysis.intent,
|
||||
subIntent: state.analysis.subIntent,
|
||||
historyMessages: state.historyMessages,
|
||||
context: state.context || undefined,
|
||||
preferredModelId: state.preferredModelId || undefined,
|
||||
}),
|
||||
AGENT_OPERATION_TIMEOUT_MS,
|
||||
null,
|
||||
)
|
||||
: null;
|
||||
const optimized =
|
||||
optimizedFromLlm ||
|
||||
optimizePrompt({
|
||||
@ -2073,16 +2253,20 @@ const optimizeNode = async (state: AutoAgentStateType) => {
|
||||
intent: state.analysis.intent,
|
||||
subIntent: state.analysis.subIntent,
|
||||
});
|
||||
const heuristicParams = heuristicImageParams(state.text, state.analysis);
|
||||
const explicitTextParams = extractExplicitImageParamsFromText(state.text);
|
||||
const mergedParams = sanitizeRecommendedImageParams(
|
||||
{
|
||||
...heuristicParams,
|
||||
...optimized.recommendedImageParams,
|
||||
...explicitTextParams,
|
||||
negativePrompt:
|
||||
optimized.negativePrompt && optimized.negativePrompt.trim().length > 0
|
||||
? optimized.negativePrompt
|
||||
: optimized.recommendedImageParams?.negativePrompt,
|
||||
},
|
||||
state.analysis.intent,
|
||||
) ?? heuristicImageParams(state.text, state.analysis);
|
||||
) ?? heuristicParams;
|
||||
|
||||
return {
|
||||
optimizedPrompt: optimized.optimizedPrompt,
|
||||
|
||||
@ -139,6 +139,7 @@ export function resolveConversationAction(params: {
|
||||
confirmedPrompt?: string;
|
||||
hasImageInputForPromptCarryForward: boolean;
|
||||
}) {
|
||||
const normalizedText = String(params.text || "").toLowerCase().replace(/\s+/g, "");
|
||||
const requestedGrid = detectRequestedStoryboardGrid(params.text);
|
||||
const shouldContinuePendingGrid =
|
||||
Boolean(params.pendingGridConfirmation) &&
|
||||
@ -163,8 +164,16 @@ export function resolveConversationAction(params: {
|
||||
} satisfies ConversationActionResolution;
|
||||
}
|
||||
|
||||
const mentionsGenerationFollowup = mentionsImageGenerationFollowup(params.text);
|
||||
const explicitFreshImageGeneration =
|
||||
!/不要生图|先别生图|不需要生图|不要生成|别生成/.test(normalizedText) &&
|
||||
/生图|出图|生成.*(?:图|图片|海报|头像|插画|场景|主图|产品图)|画.*(?:图|图片|海报|头像|插画|小猫|猫咪|人物|角色)|做.*(?:图|图片|海报|头像|主图|产品图)/.test(
|
||||
normalizedText,
|
||||
);
|
||||
const hasImageEditFollowup =
|
||||
params.imageWorkflowMemory?.lastIntent === "image_edit" &&
|
||||
!mentionsGenerationFollowup &&
|
||||
!explicitFreshImageGeneration &&
|
||||
(mentionsImageEditFollowup(params.text) ||
|
||||
Boolean(params.imageWorkflowMemory?.lastResultImageUrls?.length));
|
||||
|
||||
@ -187,7 +196,7 @@ export function resolveConversationAction(params: {
|
||||
|
||||
const hasImageGenerationFollowup =
|
||||
params.imageWorkflowMemory?.lastIntent === "image_gen" &&
|
||||
(mentionsImageGenerationFollowup(params.text) || promptCarryForwardRequested);
|
||||
(mentionsGenerationFollowup || promptCarryForwardRequested);
|
||||
|
||||
if (hasImageGenerationFollowup) {
|
||||
return {
|
||||
|
||||
@ -62,6 +62,8 @@ export function buildImageEditSpecialistRoute(params: {
|
||||
planningHints,
|
||||
toolAllowlist: [
|
||||
"respondDirect",
|
||||
"resolveTargetImage",
|
||||
"editTargetImage",
|
||||
"editImage",
|
||||
"removeBackground",
|
||||
"enhanceImage",
|
||||
@ -71,6 +73,7 @@ export function buildImageEditSpecialistRoute(params: {
|
||||
"useSelectedCanvasImage",
|
||||
"useLatestResultImage",
|
||||
"useOriginalUploadImage",
|
||||
"publishResultToCanvas",
|
||||
"publishToCanvas",
|
||||
],
|
||||
};
|
||||
@ -111,7 +114,14 @@ export function refinePlanWithImageEditSubagent(params: {
|
||||
}
|
||||
|
||||
let toolCalls = [...params.plan.toolCalls];
|
||||
if (hasTool(params.availableTools, "useLatestResultImage")) {
|
||||
if (hasTool(params.availableTools, "resolveTargetImage")) {
|
||||
toolCalls = upsertTool(
|
||||
toolCalls,
|
||||
"resolveTargetImage",
|
||||
"image-edit 子代理优先绑定上一轮结果图作为连续修图目标",
|
||||
{ prepend: true }
|
||||
);
|
||||
} else if (hasTool(params.availableTools, "useLatestResultImage")) {
|
||||
toolCalls = upsertTool(
|
||||
toolCalls,
|
||||
"useLatestResultImage",
|
||||
@ -119,15 +129,26 @@ export function refinePlanWithImageEditSubagent(params: {
|
||||
{ prepend: true }
|
||||
);
|
||||
}
|
||||
if (hasTool(params.availableTools, "editImage")) {
|
||||
if (hasTool(params.availableTools, "editTargetImage")) {
|
||||
toolCalls = upsertTool(
|
||||
toolCalls,
|
||||
"editTargetImage",
|
||||
"image-edit 子代理要求优先走连续修图执行链路"
|
||||
);
|
||||
} else if (hasTool(params.availableTools, "editImage")) {
|
||||
toolCalls = upsertTool(
|
||||
toolCalls,
|
||||
"editImage",
|
||||
"image-edit 子代理要求优先走连续修图执行链路",
|
||||
{ prepend: true }
|
||||
"image-edit 子代理要求优先走连续修图执行链路"
|
||||
);
|
||||
}
|
||||
if (hasTool(params.availableTools, "publishToCanvas")) {
|
||||
if (hasTool(params.availableTools, "publishResultToCanvas")) {
|
||||
toolCalls = upsertTool(
|
||||
toolCalls,
|
||||
"publishResultToCanvas",
|
||||
"image-edit 子代理建议把修图结果同步回画布"
|
||||
);
|
||||
} else if (hasTool(params.availableTools, "publishToCanvas")) {
|
||||
toolCalls = upsertTool(
|
||||
toolCalls,
|
||||
"publishToCanvas",
|
||||
|
||||
@ -85,10 +85,13 @@ export function buildImageGenerationSpecialistRoute(params: {
|
||||
planningHints,
|
||||
toolAllowlist: [
|
||||
"respondDirect",
|
||||
"resolveTargetImage",
|
||||
"generateNewImage",
|
||||
"generateImage",
|
||||
"useSelectedCanvasImage",
|
||||
"useLatestResultImage",
|
||||
"useOriginalUploadImage",
|
||||
"publishResultToCanvas",
|
||||
"publishToCanvas",
|
||||
],
|
||||
};
|
||||
@ -137,10 +140,29 @@ export function refinePlanWithImageGenerationSubagent(params: {
|
||||
}
|
||||
|
||||
let toolCalls = [...params.plan.toolCalls];
|
||||
if (hasTool(params.availableTools, "generateImage")) {
|
||||
toolCalls = [{ name: "generateImage", reason: "image-generation 子代理要求优先走连续生图执行链路" }];
|
||||
const targetResolverTools = toolCalls.filter((tool) =>
|
||||
["resolveTargetImage", "useSelectedCanvasImage", "useLatestResultImage", "useOriginalUploadImage"].includes(
|
||||
tool.name,
|
||||
),
|
||||
);
|
||||
if (hasTool(params.availableTools, "generateNewImage")) {
|
||||
toolCalls = [
|
||||
...targetResolverTools,
|
||||
{ name: "generateNewImage", reason: "image-generation 子代理要求优先走连续生图执行链路" },
|
||||
];
|
||||
} else if (hasTool(params.availableTools, "generateImage")) {
|
||||
toolCalls = [
|
||||
...targetResolverTools,
|
||||
{ name: "generateImage", reason: "image-generation 子代理要求优先走连续生图执行链路" },
|
||||
];
|
||||
}
|
||||
if (hasTool(params.availableTools, "publishToCanvas")) {
|
||||
if (hasTool(params.availableTools, "publishResultToCanvas")) {
|
||||
toolCalls = upsertTool(
|
||||
toolCalls,
|
||||
"publishResultToCanvas",
|
||||
"image-generation 子代理建议把连续生图结果同步到画布"
|
||||
);
|
||||
} else if (hasTool(params.availableTools, "publishToCanvas")) {
|
||||
toolCalls = upsertTool(
|
||||
toolCalls,
|
||||
"publishToCanvas",
|
||||
|
||||
@ -579,8 +579,9 @@ export async function optimizePromptWithLLM(params: {
|
||||
subIntent?: string;
|
||||
historyMessages?: HistoryMessage[];
|
||||
context?: string;
|
||||
preferredModelId?: string;
|
||||
}): Promise<OptimizeResult> {
|
||||
const { originalPrompt, intent, subIntent, historyMessages, context } = params;
|
||||
const { originalPrompt, intent, subIntent, historyMessages, context, preferredModelId } = params;
|
||||
|
||||
console.log("[Prompt Optimizer LLM] Input:", {
|
||||
originalPrompt,
|
||||
@ -611,6 +612,16 @@ export async function optimizePromptWithLLM(params: {
|
||||
const agentModel = await getAgentAiModelWithProvider();
|
||||
const isReasoningCompatibleProvider = (providerType: string) =>
|
||||
["zhipu", "alibaba", "openai", "custom", "local", "volcano-multimodal", "volcano-vision"].includes(providerType);
|
||||
const normalizedPreferredModelId = String(preferredModelId || "").trim();
|
||||
const preferredOptimizerModel = normalizedPreferredModelId
|
||||
? allModels.find(
|
||||
(m) =>
|
||||
(m.model.id === normalizedPreferredModelId || m.model.modelId === normalizedPreferredModelId) &&
|
||||
m.model.isEnabled &&
|
||||
!m.model.isSubModel &&
|
||||
isReasoningCompatibleProvider(m.provider.type)
|
||||
)
|
||||
: undefined;
|
||||
const usableAgentModel =
|
||||
agentModel && isReasoningCompatibleProvider(agentModel.provider.type)
|
||||
? agentModel
|
||||
@ -621,7 +632,7 @@ export async function optimizePromptWithLLM(params: {
|
||||
const qwenModel = allModels.find(
|
||||
(m) => m.provider.type === "alibaba" && m.model.supportsVision
|
||||
);
|
||||
const selectedOptimizerModel = usableAgentModel || zhipuModel || qwenModel;
|
||||
const selectedOptimizerModel = preferredOptimizerModel || usableAgentModel || zhipuModel || qwenModel;
|
||||
const agentCodingModelId =
|
||||
selectedOptimizerModel &&
|
||||
usableAgentModel &&
|
||||
@ -677,6 +688,7 @@ export async function optimizePromptWithLLM(params: {
|
||||
prompt: finalUserMessage,
|
||||
temperature: getCompatibleTextGenerationTemperature(selectedOptimizerModel, 0.2),
|
||||
maxTokens: 200,
|
||||
abortSignal: AbortSignal.timeout(12_000),
|
||||
} as any);
|
||||
|
||||
// 解析 JSON 响应
|
||||
|
||||
@ -78,14 +78,42 @@ function isExplicitVideoGenerationRequest(text: string): boolean {
|
||||
function isExplicitImageGenerationRequest(text: string): boolean {
|
||||
const normalizedText = normalizeCompactText(text);
|
||||
if (!normalizedText || isLikelyAdvisoryOrPlanningRequest(text)) return false;
|
||||
return /重新生成|再来一张|生成新图|新建图片|创作一张|生成一张|画一张|给我画|帮我画|出一张|做一张图|做一张海报|创建一张图片|参考.*生成|按这个风格生成|以这张图为灵感生成|仿照.*生成|生成一只|生成一个|生成一幅|生成一版|生成一套|生成一组|帮我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)|给我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)/.test(
|
||||
normalizedText,
|
||||
if (/不要生图|先别生图|不需要生图|不要生成|别生成|只要提示词|仅给提示词|先给提示词/.test(normalizedText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const countToken = "(?:一|两|二|三|四|五|六|七|八|九|十|[1-9]\\d?)";
|
||||
const visualTarget =
|
||||
"(?:图|图片|海报|头像|插画|场景|主图|产品图|商品图|电商图|壁纸|封面|logo|图标)";
|
||||
const createVisualTarget = new RegExp(
|
||||
`(?:生成|创作|创建|制作|设计|做|出|画|绘制)${countToken}?张?.{0,32}${visualTarget}`,
|
||||
);
|
||||
const drawSubject = new RegExp(
|
||||
`(?:画|绘制|生成|创作|创建)${countToken}?(?:只|个|位|幅|张)?.{0,24}(?:小猫|猫咪|猫|狗|人物|角色|ip|头像|logo|图标|插画)`,
|
||||
);
|
||||
const continueVariants = new RegExp(
|
||||
`(?:再来|再出|再做|再给我|补|多来|继续生成|继续出)${countToken}?张`,
|
||||
);
|
||||
|
||||
return (
|
||||
/重新生成|生成新图|新建图片|创作一张|生成一张|画一张|给我画|帮我画|出一张|做一张图|做一张海报|创建一张图片|生成一只|生成一个|生成一幅|生成一版|生成一套|生成一组|生图|出图/.test(
|
||||
normalizedText,
|
||||
) ||
|
||||
createVisualTarget.test(normalizedText) ||
|
||||
drawSubject.test(normalizedText) ||
|
||||
continueVariants.test(normalizedText) ||
|
||||
/参考.*生成|按这个风格生成|以这张图为灵感生成|仿照.*生成|同风格再来|同样风格再来/.test(
|
||||
normalizedText,
|
||||
) ||
|
||||
/帮我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)|给我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)/.test(
|
||||
normalizedText,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function isLikelyImageEditRequest(text: string): boolean {
|
||||
const normalizedText = String(text || "").toLowerCase();
|
||||
return /抠图|去背景|透明背景|去背|扣图|移除背景|换背景|改背景|背景换成|背景改成|白底|纯色背景|替换.*背景|变清晰|高清|锐化|降噪|提升质量|修复|清晰化|扩展|拓展|延伸|扩图|outpainting|拓宽|扩大|去除|消除|擦掉|删除|移除|p掉|重绘|风格化|卡通化|动漫风|油画风|转.*风格|变.*风格|改.*风格|调色|调整颜色|亮度|对比度|饱和度|色调|曝光|滤镜|改成.*色|换成.*色|变成.*色|颜色改成|颜色换成|颜色变成|缩放|放大|缩小|调整尺寸|裁剪|resize|crop|修图|p图|改图|调图|编辑.*图|优化.*图|重做|再调整|继续改|继续调/.test(
|
||||
return /抠图|去背景|透明背景|去背|扣图|移除背景|换背景|改背景|背景换成|背景改成|白底|纯色背景|替换.*背景|变清晰|高清|锐化|降噪|提升质量|修复|清晰化|扩展|拓展|延伸|扩图|outpainting|拓宽|扩大|换成.*(?:比例|竖版|横版|竖图|横图|9:16|16:9|1:1)|改成.*(?:比例|竖版|横版|竖图|横图|9:16|16:9|1:1)|比例|画幅|竖版|横版|去除|消除|擦掉|删除|移除|p掉|重绘|风格化|卡通化|动漫风|油画风|转.*风格|变.*风格|改.*风格|调色|调整颜色|亮度|对比度|饱和度|色调|曝光|滤镜|改成.*色|换成.*色|变成.*色|颜色改成|颜色换成|颜色变成|缩放|放大|缩小|调整尺寸|裁剪|resize|crop|修图|p图|改图|调图|编辑.*图|优化.*图|重做|再调整|继续改|继续调/.test(
|
||||
normalizedText,
|
||||
);
|
||||
}
|
||||
@ -94,6 +122,7 @@ function inferDefaultImageEditSubIntent(text: string): string {
|
||||
const lowerText = String(text || "").toLowerCase();
|
||||
if (/抠图|去背景|透明背景|去背|扣图|移除背景/.test(lowerText)) return "remove_background";
|
||||
if (/换背景|改背景|背景换成|背景改成|白底|透明底/.test(lowerText)) return "replace_background";
|
||||
if (/扩展|拓展|延伸|扩图|outpainting|拓宽|扩大|比例|画幅|竖版|横版|竖图|横图|9:16|16:9|1:1/.test(lowerText)) return "extend";
|
||||
if (/风格|卡通|动漫|油画|像素风|写实|插画/.test(lowerText)) return "style_transfer";
|
||||
if (/颜色|调色|亮度|对比度|饱和度|改成.*色|换成.*色/.test(lowerText)) return "color_adjust";
|
||||
if (/去除|消除|擦掉|删除|移除|p掉|重绘/.test(lowerText)) return "inpaint";
|
||||
|
||||
@ -54,9 +54,33 @@ function isLikelyVideoExecutionRequest(text: string): boolean {
|
||||
function isLikelyImageExecutionRequest(text: string): boolean {
|
||||
const normalized = normalizeCompactText(text);
|
||||
if (!normalized) return false;
|
||||
if (/不要生图|先别生图|不需要生图|不要生成|别生成|只要提示词|仅给提示词|先给提示词/.test(normalized)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /生图|生成图片|出图|画一张|帮我画|做图|做一张图|重新生成|再来一张|执行生成|开始生成|生成出来|生成一只|生成一个|生成一张|生成一幅|生成一版|帮我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)|给我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)/.test(
|
||||
normalized,
|
||||
const countToken = "(?:一|两|二|三|四|五|六|七|八|九|十|[1-9]\\d?)";
|
||||
const visualTarget =
|
||||
"(?:图|图片|海报|头像|插画|场景|主图|产品图|商品图|电商图|壁纸|封面|logo|图标)";
|
||||
const createVisualTarget = new RegExp(
|
||||
`(?:生成|创作|创建|制作|设计|做|出|画|绘制)${countToken}?张?.{0,32}${visualTarget}`,
|
||||
);
|
||||
const drawSubject = new RegExp(
|
||||
`(?:画|绘制|生成|创作|创建)${countToken}?(?:只|个|位|幅|张)?.{0,24}(?:小猫|猫咪|猫|狗|人物|角色|ip|头像|logo|图标|插画)`,
|
||||
);
|
||||
const continueVariants = new RegExp(
|
||||
`(?:再来|再出|再做|再给我|补|多来|继续生成|继续出)${countToken}?张`,
|
||||
);
|
||||
|
||||
return (
|
||||
/生图|生成图片|出图|画一张|帮我画|做图|做一张图|重新生成|再来一张|执行生成|开始生成|生成出来|生成一只|生成一个|生成一张|生成一幅|生成一版|帮我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)|给我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)/.test(
|
||||
normalized,
|
||||
) ||
|
||||
createVisualTarget.test(normalized) ||
|
||||
drawSubject.test(normalized) ||
|
||||
continueVariants.test(normalized) ||
|
||||
/参考.*生成|按这个风格生成|以这张图为灵感生成|仿照.*生成|同风格再来|同样风格再来/.test(
|
||||
normalized,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ function inferDefaultImageEditSubIntent(text: string): AnalysisResult["subIntent
|
||||
const lowerText = text.toLowerCase();
|
||||
if (/抠图|去背景|透明背景|去背|扣图|移除背景/.test(lowerText)) return "remove_background";
|
||||
if (/换背景|改背景|背景换成|背景改成|白底|透明底/.test(lowerText)) return "replace_background";
|
||||
if (/扩展|拓展|延伸|扩图|outpainting|拓宽|扩大|比例|画幅|竖版|横版|竖图|横图|9:16|16:9|1:1/.test(lowerText)) return "extend";
|
||||
if (/风格|卡通|动漫|油画|像素风|写实|插画/.test(lowerText)) return "style_transfer";
|
||||
if (/颜色|调色|亮度|对比度|饱和度|改成.*色|换成.*色/.test(lowerText)) return "color_adjust";
|
||||
if (/去除|消除|擦掉|删除|移除|p掉|重绘/.test(lowerText)) return "inpaint";
|
||||
@ -65,11 +66,11 @@ export function isExplicitPromptOptimizationRequest(text: string): boolean {
|
||||
if (alsoRequestsExecution) return false;
|
||||
|
||||
const asksAnalysis =
|
||||
/分析|看图|看看|识别|拆解|提炼|整理|归纳|总结|描述|写出|给我|输出|优化|改写/.test(
|
||||
/分析|看图|看看|识别|拆解|提炼|整理|归纳|总结|描述|写|撰写|写出|给我|输出|优化|改写/.test(
|
||||
normalizedText,
|
||||
);
|
||||
const asksPromptQuestion =
|
||||
/提示词应该是什么|什么提示词|提示词是什么|怎么写提示词|如何写提示词|怎么描述|如何描述|怎么表达|如何表达|给我一版提示词|给我一套提示词|适合.*提示词|用于生图的提示词|生成这样的图片.*提示词/.test(
|
||||
/提示词应该是什么|什么提示词|提示词是什么|怎么写提示词|如何写提示词|怎么描述|如何描述|怎么表达|如何表达|给我一版提示词|给我一套提示词|帮我写.*提示词|写一版.*提示词|适合.*提示词|用于生图.*提示词|生成这样的图片.*提示词/.test(
|
||||
normalizedText,
|
||||
);
|
||||
const blocksExecution =
|
||||
@ -95,17 +96,48 @@ function isExplicitVideoGenerationRequest(text: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isExplicitImageGenerationRequest(text: string): boolean {
|
||||
export function isExplicitImageGenerationRequest(text: string): boolean {
|
||||
const normalizedText = normalizeCompactText(text);
|
||||
if (isLikelyAdvisoryOrPlanningRequest(text)) return false;
|
||||
return /重新生成|再来一张|生成新图|新建图片|创作一张|生成一张|画一张|给我画|帮我画|出一张|做一张图|做一张海报|创建一张图片|参考.*生成|按这个风格生成|以这张图为灵感生成|仿照.*生成|生成一只|生成一个|生成一幅|生成一版|生成一套|生成一组|帮我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)|给我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)/.test(
|
||||
normalizedText,
|
||||
if (isExplicitPromptOptimizationRequest(text)) return false;
|
||||
if (/不要生图|先别生图|不需要生图|不要生成|别生成|只要提示词|仅给提示词|先给提示词/.test(normalizedText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const countToken = "(?:一|两|二|三|四|五|六|七|八|九|十|[1-9]\\d?)";
|
||||
const visualTarget =
|
||||
"(?:图|图片|海报|头像|插画|场景|主图|产品图|商品图|电商图|壁纸|封面|logo|图标)";
|
||||
const createVisualTarget = new RegExp(
|
||||
`(?:生成|创作|创建|制作|设计|做|出|画|绘制)${countToken}?张?.{0,32}${visualTarget}`,
|
||||
);
|
||||
const drawSubject = new RegExp(
|
||||
`(?:画|绘制|生成|创作|创建)${countToken}?(?:只|个|位|幅|张)?.{0,24}(?:小猫|猫咪|猫|狗|人物|角色|ip|头像|logo|图标|插画)`,
|
||||
);
|
||||
const continueVariants = new RegExp(
|
||||
`(?:再来|再出|再做|再给我|补|多来|继续生成|继续出)${countToken}?张`,
|
||||
);
|
||||
const referenceGeneration =
|
||||
/参考.*生成|按这个风格生成|以这张图为灵感生成|仿照.*生成|同风格再来|同样风格再来/.test(
|
||||
normalizedText,
|
||||
);
|
||||
|
||||
return (
|
||||
/重新生成|生成新图|新建图片|创作一张|生成一张|画一张|给我画|帮我画|出一张|做一张图|做一张海报|创建一张图片|生成一只|生成一个|生成一幅|生成一版|生成一套|生成一组|生图|出图/.test(
|
||||
normalizedText,
|
||||
) ||
|
||||
createVisualTarget.test(normalizedText) ||
|
||||
drawSubject.test(normalizedText) ||
|
||||
continueVariants.test(normalizedText) ||
|
||||
referenceGeneration ||
|
||||
/帮我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)|给我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)/.test(
|
||||
normalizedText,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function isLikelyImageEditRequest(text: string): boolean {
|
||||
const normalizedText = text.toLowerCase();
|
||||
return /抠图|去背景|透明背景|去背|扣图|移除背景|换背景|改背景|背景换成|背景改成|白底|纯色背景|替换.*背景|变清晰|高清|锐化|降噪|提升质量|修复|清晰化|扩展|拓展|延伸|扩图|outpainting|拓宽|扩大|去除|消除|擦掉|删除|移除|p掉|重绘|风格化|卡通化|动漫风|油画风|转.*风格|变.*风格|改.*风格|调色|调整颜色|亮度|对比度|饱和度|色调|曝光|滤镜|改成.*色|换成.*色|变成.*色|颜色改成|颜色换成|颜色变成|缩放|放大|缩小|调整尺寸|裁剪|resize|crop|修图|p图|改图|调图|编辑.*图|优化.*图|重做|再调整|继续改|继续调/.test(
|
||||
return /抠图|去背景|透明背景|去背|扣图|移除背景|换背景|改背景|背景换成|背景改成|白底|纯色背景|替换.*背景|变清晰|高清|锐化|降噪|提升质量|修复|清晰化|扩展|拓展|延伸|扩图|outpainting|拓宽|扩大|换成.*(?:比例|竖版|横版|竖图|横图|9:16|16:9|1:1)|改成.*(?:比例|竖版|横版|竖图|横图|9:16|16:9|1:1)|比例|画幅|竖版|横版|去除|消除|擦掉|删除|移除|p掉|重绘|风格化|卡通化|动漫风|油画风|转.*风格|变.*风格|改.*风格|调色|调整颜色|亮度|对比度|饱和度|色调|曝光|滤镜|改成.*色|换成.*色|变成.*色|颜色改成|颜色换成|颜色变成|缩放|放大|缩小|调整尺寸|裁剪|resize|crop|修图|p图|改图|调图|编辑.*图|优化.*图|重做|再调整|继续改|继续调/.test(
|
||||
normalizedText,
|
||||
);
|
||||
}
|
||||
@ -1859,6 +1891,16 @@ export async function analyzeIntent(params: {
|
||||
* 🆕 现在返回低置信度,强制走LLM分析以获取自然对话回复
|
||||
*/
|
||||
function quickKeywordMatch(text: string, hasImage: boolean): AnalysisResult {
|
||||
if (isExplicitPromptOptimizationRequest(text)) {
|
||||
return {
|
||||
intent: hasImage ? "vision" : "chat",
|
||||
confidence: 0.95,
|
||||
conversationalReply: hasImage
|
||||
? "收到,我先分析这张图并整理可直接使用的优化提示词,不会直接生图。"
|
||||
: "收到,我先按你的要求整理提示词,不直接执行生成。",
|
||||
};
|
||||
}
|
||||
|
||||
if (isExplicitVideoGenerationRequest(text)) {
|
||||
return {
|
||||
intent: "video_gen",
|
||||
@ -1876,6 +1918,15 @@ function quickKeywordMatch(text: string, hasImage: boolean): AnalysisResult {
|
||||
conversationalReply: hasImage
|
||||
? "收到,我会结合参考图直接帮你生成。"
|
||||
: "好的,我来直接帮你生成这张图。",
|
||||
};
|
||||
}
|
||||
|
||||
if (hasImage && isLikelyImageEditRequest(text) && !isLikelyAdvisoryOrPlanningRequest(text)) {
|
||||
return {
|
||||
intent: "image_edit",
|
||||
subIntent: inferDefaultImageEditSubIntent(text),
|
||||
confidence: 0.92,
|
||||
conversationalReply: "收到,我会按你的目标直接处理这张图。",
|
||||
};
|
||||
}
|
||||
|
||||
@ -2306,7 +2357,7 @@ export function fallbackKeywordAnalysis(
|
||||
}
|
||||
|
||||
// 图像拓展
|
||||
if (/扩展|拓展|延伸|扩图|outpainting|拓宽|扩大/.test(lowerText)) {
|
||||
if (/扩展|拓展|延伸|扩图|outpainting|拓宽|扩大|换成.*(?:比例|竖版|横版|竖图|横图|9:16|16:9|1:1)|改成.*(?:比例|竖版|横版|竖图|横图|9:16|16:9|1:1)|比例|画幅|竖版|横版/.test(lowerText)) {
|
||||
return {
|
||||
intent: "image_edit",
|
||||
subIntent: "extend",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user