fix: dedupe agent workflow scene prompts
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-25 21:24:14 +08:00
parent 23933cfb41
commit fb2f3461c2
2 changed files with 174 additions and 10 deletions

View File

@ -2863,7 +2863,108 @@ function normalizeAgentSupplementPrompt(text: string, presetName?: string | null
}
function normalizeAgentWorkflowPrompt(text: string) {
return String(text || "").trim();
return String(text || "")
.split(/\n+/)
.map((line) => line.replace(/\s+/g, " ").trim())
.filter(Boolean)
.join("\n")
.trim();
}
const AGENT_PROMPT_STRUCTURAL_LABELS = new Set([
"角色",
"技能",
"目标",
"约束条件",
"流程",
"输出",
]);
function normalizePromptLineForCompare(line: string) {
return String(line || "")
.replace(/\s+/g, " ")
.replace(/[::]/g, ":")
.replace(/[。;;,,、!!??]+$/g, "")
.trim()
.toLowerCase();
}
function parsePromptLineLabel(line: string) {
const normalized = String(line || "").replace(/\s+/g, " ").trim();
if (!normalized) return { label: "", value: "" };
const colonMatch = normalized.match(/^([^::]{1,20})[::]\s*(.+)$/);
if (colonMatch) {
return {
label: colonMatch[1].trim(),
value: colonMatch[2].trim(),
};
}
const whitespaceMatch = normalized.match(/^([^\s]{1,20})\s+(.+)$/);
if (whitespaceMatch) {
return {
label: whitespaceMatch[1].trim(),
value: whitespaceMatch[2].trim(),
};
}
return { label: "", value: normalized };
}
function dedupeAgentSlotPromptAgainstWorkflow(params: {
workflowPrompt?: string | null;
slotPrompt?: string | null;
}) {
const dedupedSlotLines: string[] = [];
const seenExactLines = new Set<string>();
const workflowStructuralValues = new Map<string, string>();
const rememberLine = (line: string) => {
const normalizedLine = normalizePromptLineForCompare(line);
if (!normalizedLine) {
return;
}
seenExactLines.add(normalizedLine);
};
const normalizedWorkflowLines = normalizeAgentWorkflowPrompt(params.workflowPrompt || "")
.split("\n")
.map((line) => line.trim())
.filter(Boolean);
normalizedWorkflowLines.forEach((line) => {
const parsed = parsePromptLineLabel(line);
if (parsed.label && AGENT_PROMPT_STRUCTURAL_LABELS.has(parsed.label)) {
workflowStructuralValues.set(
parsed.label,
normalizePromptLineForCompare(parsed.value || "")
);
}
rememberLine(line);
});
const normalizedSlotLines = String(params.slotPrompt || "")
.split(/\n+/)
.map((line) => line.replace(/\s+/g, " ").trim())
.filter(Boolean);
normalizedSlotLines.forEach((line) => {
const normalizedLine = normalizePromptLineForCompare(line);
if (!normalizedLine || seenExactLines.has(normalizedLine)) {
return;
}
const parsed = parsePromptLineLabel(line);
if (parsed.label && AGENT_PROMPT_STRUCTURAL_LABELS.has(parsed.label)) {
const normalizedValue = normalizePromptLineForCompare(parsed.value || "");
const workflowValue = workflowStructuralValues.get(parsed.label);
if (workflowValue && workflowValue === normalizedValue) {
return;
}
}
seenExactLines.add(normalizedLine);
dedupedSlotLines.push(line);
});
return dedupedSlotLines.join("\n").trim();
}
function buildAgentSlotPrompt(params: {
@ -2881,9 +2982,12 @@ function buildAgentSlotPrompt(params: {
if (slotTitle) {
lines.push(`【槽位】${slotTitle}`);
}
const slotPrompt = String(params.slotPrompt || "").trim();
if (slotPrompt) {
lines.push(slotPrompt);
const dedupedSlotPrompt = dedupeAgentSlotPromptAgainstWorkflow({
workflowPrompt,
slotPrompt: params.slotPrompt,
});
if (dedupedSlotPrompt) {
lines.push(dedupedSlotPrompt);
}
const supplement = String(params.userSupplementPrompt || "").trim();
if (supplement) {

View File

@ -7,6 +7,7 @@ import {
} from "@/lib/agents/workflow-ui-config";
import type {
AgentWorkflowComposerContext,
AgentWorkflowComposerSelection,
AgentWorkflowMaterialSelections,
AgentWorkflowReferenceImage,
AgentWorkflowSelectedBrandModel,
@ -22,6 +23,57 @@ export interface AgentWorkflowInputsValue {
priceBand: string;
}
function normalizeWorkflowText(value: string) {
return String(value || "")
.replace(/\s+/g, " ")
.trim();
}
function buildGroupedSelectionLines(params: {
selections: AgentWorkflowComposerSelection[];
valueKey: "optionName" | "promptFragment";
separator: string;
}) {
const grouped = new Map<
string,
{
stepTitle: string;
values: string[];
valueSet: Set<string>;
}
>();
params.selections.forEach((item) => {
const rawValue =
params.valueKey === "promptFragment"
? item.promptFragment || item.optionName
: item.optionName;
const normalizedValue = normalizeWorkflowText(rawValue || "");
if (!normalizedValue) {
return;
}
const groupKey = item.stepKey;
const currentGroup = grouped.get(groupKey) || {
stepTitle: item.stepTitle,
values: [],
valueSet: new Set<string>(),
};
const dedupeKey = normalizedValue.toLowerCase();
if (!currentGroup.valueSet.has(dedupeKey)) {
currentGroup.valueSet.add(dedupeKey);
currentGroup.values.push(normalizedValue);
}
grouped.set(groupKey, currentGroup);
});
return Array.from(grouped.values())
.filter((group) => group.values.length > 0)
.map((group) => `${group.stepTitle}:${group.values.join(params.separator)}`);
}
export function buildAgentWorkflowComposerContext(params: {
config?: AgentWorkflowUiConfig | null;
inputs: AgentWorkflowInputsValue;
@ -89,9 +141,13 @@ export function buildAgentWorkflowComposerContext(params: {
`品牌模特图:${referenceImages.filter((item) => item.source === "brandModel").length}张`
);
}
selections.forEach((item) => {
summaryParts.push(`${item.stepTitle}:${item.optionName}`);
});
summaryParts.push(
...buildGroupedSelectionLines({
selections,
valueKey: "optionName",
separator: " / ",
})
);
if (summaryParts.length === 0) {
return null;
@ -149,9 +205,13 @@ export function buildAgentWorkflowComposerContext(params: {
}`
);
}
selections.forEach((item) => {
promptLines.push(`${item.stepTitle}:${item.promptFragment || item.optionName}`);
});
promptLines.push(
...buildGroupedSelectionLines({
selections,
valueKey: "promptFragment",
separator: ";",
})
);
return {
primarySku: params.inputs.primarySku.trim() || undefined,