fix: limit kimi planning to reference image and extend timeout
This commit is contained in:
parent
d2aa27b1c2
commit
ed6ebbf80a
@ -2908,8 +2908,6 @@ type ReplacementReferenceDecision = {
|
||||
};
|
||||
|
||||
type ReplacementScenePlan = {
|
||||
selectedProductImageIndexes: number[];
|
||||
productSelectionReason: string;
|
||||
useModelImage: boolean;
|
||||
modelImageReason: string;
|
||||
sceneSummary: string;
|
||||
@ -2919,6 +2917,8 @@ type ReplacementScenePlan = {
|
||||
styleSummary: string;
|
||||
avoidSummary: string;
|
||||
generationPrompt: string;
|
||||
selectedProductImageIndexes: number[];
|
||||
productSelectionReason: string;
|
||||
analysisModelName?: string | null;
|
||||
analysisSource: "local_moonshot" | "configured_vision" | "heuristic";
|
||||
};
|
||||
@ -2960,22 +2960,6 @@ function parseReplacementScenePlan(
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(jsonText) as Record<string, unknown>;
|
||||
const rawIndexes = Array.isArray(parsed.selectedProductImageIndexes)
|
||||
? parsed.selectedProductImageIndexes
|
||||
: Array.isArray(parsed.productImageIndexes)
|
||||
? parsed.productImageIndexes
|
||||
: [];
|
||||
const selectedProductImageIndexes = Array.from(
|
||||
new Set(
|
||||
rawIndexes
|
||||
.map((value) => Number(value))
|
||||
.filter((value) => Number.isFinite(value))
|
||||
.map((value) =>
|
||||
Math.max(1, Math.min(params.productImageCount, Math.round(value))),
|
||||
),
|
||||
),
|
||||
).slice(0, 3);
|
||||
|
||||
const useModelImageRaw =
|
||||
normalizeBooleanLike(parsed.useModelImage) ??
|
||||
normalizeBooleanLike(parsed.needModelImage) ??
|
||||
@ -2988,14 +2972,6 @@ function parseReplacementScenePlan(
|
||||
);
|
||||
|
||||
const scenePlan: ReplacementScenePlan = {
|
||||
selectedProductImageIndexes:
|
||||
selectedProductImageIndexes.length > 0
|
||||
? selectedProductImageIndexes
|
||||
: [1].filter((value) => value <= params.productImageCount),
|
||||
productSelectionReason: truncateReplacementAnalysisText(
|
||||
parsed.productSelectionReason || parsed.selectionReason || parsed.productReason,
|
||||
160,
|
||||
),
|
||||
useModelImage: params.hasModelImage ? Boolean(useModelImageRaw) : false,
|
||||
modelImageReason: truncateReplacementAnalysisText(
|
||||
parsed.modelImageReason || parsed.modelReason,
|
||||
@ -3026,6 +3002,14 @@ function parseReplacementScenePlan(
|
||||
220,
|
||||
),
|
||||
generationPrompt,
|
||||
selectedProductImageIndexes: Array.from(
|
||||
{ length: Math.min(Math.max(params.productImageCount, 1), 2) },
|
||||
(_, index) => index + 1,
|
||||
),
|
||||
productSelectionReason:
|
||||
params.productImageCount > 1
|
||||
? "默认优先使用前两张产品图作为主视角与补充细节,其余角度不强制同时融入。"
|
||||
: "默认使用当前唯一产品图作为主体参考。",
|
||||
analysisModelName: null,
|
||||
analysisSource: "heuristic",
|
||||
};
|
||||
@ -3064,9 +3048,9 @@ function buildHeuristicReplacementScenePlan(params: {
|
||||
params.productImageCount > 1
|
||||
? "默认优先使用前两张产品图作为主视角与补充细节,其余角度不强制同时融入。"
|
||||
: "默认使用当前唯一产品图作为主体参考。",
|
||||
useModelImage: params.hasModelImage,
|
||||
useModelImage: false,
|
||||
modelImageReason: params.hasModelImage
|
||||
? "未完成场景级人物需求分析,先保留模特图作为可选补充参考。"
|
||||
? "前置分析未明确当前参考图存在人物展示需求,兜底阶段默认不使用模特图。"
|
||||
: "当前未提供模特图。",
|
||||
sceneSummary: params.shouldReplace
|
||||
? "保持当前参考图版式与场景结构,只替换参考图中的产品主体。"
|
||||
@ -3386,13 +3370,11 @@ async function analyzeReplacementExecutionPlan(params: {
|
||||
}): Promise<ReplacementExecutionPlan> {
|
||||
if (params.planningRuntime) {
|
||||
try {
|
||||
const analysisImageUrls = [
|
||||
...params.productImageUrls,
|
||||
...(params.modelImageUrl ? [params.modelImageUrl] : []),
|
||||
params.referenceImageUrl,
|
||||
].filter(Boolean) as string[];
|
||||
const replacementScenePlanningTimeoutMs =
|
||||
params.planningRuntime.kind === "local_moonshot" ? 90_000 : 45_000;
|
||||
const analysisImageUrls = [params.referenceImageUrl];
|
||||
const { inlineDataUrls } = await inlineVisionImages(analysisImageUrls, {
|
||||
maxImages: 8,
|
||||
maxImages: 1,
|
||||
maxSide: 900,
|
||||
timeoutMs: 12000,
|
||||
retries: 2,
|
||||
@ -3410,25 +3392,23 @@ async function analyzeReplacementExecutionPlan(params: {
|
||||
const analysisPrompt = [
|
||||
"你是电商详情替换流程中的“场景理解规划器”。",
|
||||
`当前要分析的参考图:${params.referenceLabel}`,
|
||||
`产品图数量:${params.productImageUrls.length} 张。产品图全部属于同一产品的多角度/多细节白底图。`,
|
||||
"这些产品图只是帮助你理解同一产品的结构、材质、颜色、比例和细节,不代表必须把所有角度都同时体现在这一张结果图里。",
|
||||
"你必须根据最后一张参考图底板当前这一屏的机位、主体位置和露出范围,只选择最需要的 1-2 张产品图作为本屏主参考;只有在确实必要时才允许选择 3 张,禁止无理由把所有产品图都选上。",
|
||||
"你现在只分析这一张参考图本身,不分析产品图,不分析模特图。",
|
||||
"产品图和模特图会在后续生图阶段单独提供给生图模型使用,你这里不要尝试理解它们的具体外观,也不要要求必须使用所有输入图。",
|
||||
params.modelImageUrl
|
||||
? "还提供了 1 张模特图。只有当最后一张参考图底板当前这一屏本身存在人物展示需求时,才允许使用模特图;如果这一屏不需要人物,必须明确 useModelImage=false。"
|
||||
? "你只需要根据当前参考图判断:这一屏是否存在真实的人物展示需求;如果没有人物需求,useModelImage 必须为 false。"
|
||||
: "本轮没有模特图,useModelImage 必须为 false。",
|
||||
"最后一张图片是唯一的详情参考图底板,它决定当前这一屏的版式、文字排版、机位、构图、留白和信息层级。",
|
||||
"这张图片是唯一的详情参考图底板,它决定当前这一屏的版式、文字排版、机位、构图、留白和信息层级。",
|
||||
"请完成以下任务:",
|
||||
"1. 判断 shouldReplace:当前参考图里是否存在清晰、主要、适合被替换的家具商品主体。",
|
||||
"2. 给出 selectedProductImageIndexes:从产品图中挑出最适合当前这一屏的产品角度索引(从1开始计数)。",
|
||||
"3. 判断 useModelImage,并说明原因。",
|
||||
"4. 提炼 sceneSummary、cameraSummary、placementSummary、preserveSummary、styleSummary、avoidSummary。",
|
||||
"5. 输出 generationPrompt:这是一段给后续生图模型的本屏生成指令,必须明确“只按当前参考图这一屏需要使用产品角度图,不需要把所有产品图全部融进结果”。",
|
||||
"2. 判断 useModelImage,并说明原因。",
|
||||
"3. 提炼 sceneSummary、cameraSummary、placementSummary、preserveSummary、styleSummary、avoidSummary。",
|
||||
"4. 输出 generationPrompt:这是一段给后续生图模型的本屏生成指令,描述这一屏需要的场景、机位、主体位置和必须保留的内容。",
|
||||
workflowPromptExcerpt
|
||||
? `当前风格与场景限制(来自前置工作流与模式模板):\n${workflowPromptExcerpt}`
|
||||
: "",
|
||||
"只返回严格 JSON,不要任何额外解释。",
|
||||
`JSON 格式:
|
||||
{"shouldReplace":true,"containsFurnitureProduct":true,"reason":"不超过30字","selectedProductImageIndexes":[1,2],"productSelectionReason":"不超过50字","useModelImage":false,"modelImageReason":"不超过40字","sceneSummary":"不超过80字","cameraSummary":"不超过60字","placementSummary":"不超过60字","preserveSummary":"不超过80字","styleSummary":"不超过80字","avoidSummary":"不超过80字","generationPrompt":"不超过300字"}`,
|
||||
{"shouldReplace":true,"containsFurnitureProduct":true,"reason":"不超过30字","useModelImage":false,"modelImageReason":"不超过40字","sceneSummary":"不超过80字","cameraSummary":"不超过60字","placementSummary":"不超过60字","preserveSummary":"不超过80字","styleSummary":"不超过80字","avoidSummary":"不超过80字","generationPrompt":"不超过300字"}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@ -3451,10 +3431,10 @@ async function analyzeReplacementExecutionPlan(params: {
|
||||
},
|
||||
],
|
||||
system:
|
||||
"你是高精度商品详情图场景规划器。重点是:多张产品图只是同一产品的多视角信息源,不是必须全部同时使用。你必须根据参考图当前这一屏的真实需求筛选少量最合适的产品图。",
|
||||
"你是高精度商品详情图场景规划器。你只分析当前参考图本身,负责理解这一屏的场景、机位、主体位置和人物需求,不要臆测或依赖其他输入图片。",
|
||||
temperature: params.planningRuntime.kind === "local_moonshot" ? 1 : 0.1,
|
||||
maxTokens: 1200,
|
||||
abortSignal: AbortSignal.timeout(45_000),
|
||||
abortSignal: AbortSignal.timeout(replacementScenePlanningTimeoutMs),
|
||||
} as any);
|
||||
|
||||
const parsedDecision = parseReplacementReferenceDecision(text);
|
||||
@ -6564,9 +6544,14 @@ export async function POST(request: Request) {
|
||||
await resolveReplacementScenePlanningRuntime({
|
||||
models: replacementVisionModels,
|
||||
});
|
||||
const replacementScenePlanningTimeoutMs =
|
||||
replacementScenePlanningRuntime?.kind === "local_moonshot"
|
||||
? 90_000
|
||||
: 45_000;
|
||||
console.info("[Chat API] Replacement scene planning runtime:", {
|
||||
kind: replacementScenePlanningRuntime?.kind || "none",
|
||||
modelName: replacementScenePlanningRuntime?.modelName || null,
|
||||
timeoutMs: replacementScenePlanningTimeoutMs,
|
||||
});
|
||||
const stopKeepalive = startStreamKeepalive(
|
||||
writer,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user