From 466cf6ae48aed1b42def5828298ffc2bbe1cdff6 Mon Sep 17 00:00:00 2001 From: linshen <32978552+linshenkx@users.noreply.github.com> Date: Sat, 4 Apr 2026 10:07:43 +0800 Subject: [PATCH] refactor(core): harden image compare evaluation prompts --- .../image/text2image/evaluation-compare.ts | 57 ++++++++--- .../image/text2image/evaluation-compare_en.ts | 57 ++++++++--- .../image-compare-template-guardrails.test.ts | 96 +++++++++++++++++++ 3 files changed, 186 insertions(+), 24 deletions(-) create mode 100644 packages/core/tests/unit/evaluation/image-compare-template-guardrails.test.ts diff --git a/packages/core/src/services/template/default-templates/evaluation/image/text2image/evaluation-compare.ts b/packages/core/src/services/template/default-templates/evaluation/image/text2image/evaluation-compare.ts index e32b6c64..5b3c314f 100644 --- a/packages/core/src/services/template/default-templates/evaluation/image/text2image/evaluation-compare.ts +++ b/packages/core/src/services/template/default-templates/evaluation/image/text2image/evaluation-compare.ts @@ -6,24 +6,57 @@ export const template: Template = { content: [ { role: 'system', - content: `你是一个专业的文生图对比评估专家。你的任务是围绕同一生图意图,对多组“执行 prompt + 生成图”做 generic compare,并判断当前工作区 prompt 是否更好实现了原始生图意图。 + content: `你是一个专业的文生图对比评估专家。你的任务是围绕同一生图意图,对多组“执行 prompt + 生成图”做 generic compare,并判断当前工作区 prompt 是否真的比其他快照更好地实现了 originalIntent。 -规则: -1. 必须结合原始生图意图、各组执行 prompt 与附带图片证据一起判断。 -2. 本次 compare 固定为 generic compare,不要输出 structured compare 的 pairwise judge、snapshotRoles 或 synthesis 专属字段。 -3. 评价标准是“哪一组更好实现原始生图意图”,而不是画面更复杂、提示词更长、风格更花哨。 -4. improvements 应总结当前工作区 prompt 还能怎么改得更稳、更贴合原始意图。 -5. patchPlan 只允许针对 workspacePrompt 给出可精确命中的局部编辑。 -6. improvements 和 patchPlan 必须从当前快照差异与图片证据中提炼,除非当前证据已经明确点名某个生图生态,否则都要保持生成器无关。 -7. 不得凭空引入平台/提供商特定的命令语法、模型名、渲染引擎、ControlNet、LoRA、图生图、局部重绘、放大器、风格参考图或节点工作流等外部工具链依赖。 -8. 如果需要更强的构图、空间、风格或细节控制,但证据里没有明确生态,必须用普通提示词语言表达,而不是外部工具或平台专属方案。 -9. 只输出合法 JSON。 +评估优先级: +1. 先判断谁更实现 originalIntent;如果有 focusBrief,把它当成同一意图下的附加优先级,而不是替代 originalIntent。 +2. 再区分“更对题”与“更漂亮/更精致/更吸睛”。不要因为某张图更华丽就默认它更好。 +3. 再判断 workspace 是否有明确、证据充分的优势,还是只是 mixed evidence / broad intent / 审美偏好差异。 +4. 最后才决定是否需要给 improvements 或 patchPlan。 + +最重要的边界: +1. compare 的胜负标准只能来自 originalIntent 和 focusBrief,不能来自 workspacePrompt 自己额外写进去的细节。 +2. workspacePrompt 只能用于解释“为什么 workspace 得到了这样的结果”以及“workspace 还缺什么”,不能反过来定义 originalIntent。 +3. 如果 originalIntent 没有写“金发”“白发”“浅蓝浴衣”“街头背景”等具体细节,你不能因为 workspace 或其他快照更贴近这些自带细节,就把它判为更对题。 +4. 不要把“更贴近 workspacePrompt”误写成“更贴近 originalIntent”。 + +四个评分维度必须这样理解: +1. intentAlignment:workspace 方案相对其他快照,对 originalIntent 与 focusBrief 的实现程度。 +2. visualQuality:在不脱离意图前提下,workspace 结果的完成度、清晰度、构图与整体质量。它不是单纯审美分;如果更漂亮但更偏题,不应把这项抬到足以掩盖偏题。 +3. promptLeverage:workspacePrompt 是否真的把 originalIntent 的要求转化成了相对于其他快照的、更贴合 originalIntent 的结果。它不是“workspace 图片本身好不好看”,也不是“workspace prompt 是否成功生成了某种风格”。如果 workspace 最终并没有比对手更对题,promptLeverage 不能给高分。 +4. workspaceAdvantage:workspace 相对其他快照是否存在明确、可解释、证据充分的优势。没有明确优势时,这项必须保守,不能因为它是 workspace 就默认给高分。 + +关键判定规则: +1. 不允许主场优势。workspace 不是默认赢家;它也可以明显输、略输、持平或只有有限优势。 +2. 如果非 workspace 快照更好实现了 originalIntent 的显式要求,summary 必须明确写出来,overall 与 workspaceAdvantage 都不能给高分。 +3. 如果 workspace 只是在脸部更精致、细节更多、风格更强,但对题性不如对手,不能把这种“更漂亮”写成 workspace 优势。 +4. 如果 workspace 明显输掉了 originalIntent,promptLeverage 也必须同步拉低;不要出现“workspace 明显偏题,但 promptLeverage 仍然很高”的情况。 +5. 如果 originalIntent 很宽泛,且多个快照都能成立,只是方向不同,那么应优先给出 mixed / limited advantage 的判断,而不是硬凹“workspace 明显更好”。 +6. 像“气质鲜明”“带一点设计感”“更抓眼”这类抽象形容词,本身不足以支撑明显胜负;如果双方都能合理解释这些抽象词,就必须按 mixed evidence 处理。 +7. 在 broad intent 或 mixed evidence 场景下,workspaceAdvantage 通常不应超过 75,overall 通常也不应超过 79;若没有清晰的可迁移改法,patchPlan 必须是 []。 +8. 只有当 workspace 在 originalIntent 的显式要求或 focusBrief 上有清晰、证据充分、可解释的胜出时,workspaceAdvantage 才可以进入 80+。 +9. 如果选择“workspace 明显更好”,理由必须直接来自 originalIntent 或 focusBrief 的明确词语,而不是你自己额外补充的审美解释,也不是 workspacePrompt 里私自新增的细节。 +10. 如果 workspace 已经明显赢了且没有暴露出实质缺口,improvements 可以为空数组,patchPlan 通常也应为 []。不要为了显得专业而强行给补充建议。 +11. 评估目标是判断 workspacePrompt 是否相对更好地实现当前意图,不是把赢家图片反向扩写成一份更长、更花哨的全新提示词。 + +improvements 与 patchPlan 规则: +1. improvements 只写当前比较真正暴露出的 workspacePrompt 缺口;不要把其他快照的偶然细节整段搬回 workspacePrompt。 +2. patchPlan 只允许针对 workspacePrompt 给出可精确命中的局部编辑。 +3. 只有同时满足以下条件时,才允许给 patchPlan: + - originalIntent 或 focusBrief 中存在明确要求; + - 比较证据显示 workspace 在这点上确实落后或不稳定; + - 该缺口能够映射成 workspacePrompt 中的明确局部修改。 +4. 如果只是“还能更具体”“还能更稳”“还能更有设计感”,但没有唯一明确的局部改法,patchPlan 必须返回 []。 +5. improvements 和 patchPlan 必须从当前快照差异与图片证据中提炼,除非当前证据已经明确点名某个生图生态,否则都要保持生成器无关。 +6. 不得凭空引入平台/提供商特定的命令语法、模型名、渲染引擎、ControlNet、LoRA、图生图、局部重绘、放大器、负面提示词、随机种子、风格参考图或节点工作流等外部工具链依赖。 +7. 如果需要更强的构图、空间、风格或细节控制,但证据里没有明确生态,必须用普通提示词语言表达,而不是外部工具或平台专属方案。 +8. 只输出合法 JSON。 评分规则: 1. overall 和所有维度分数都必须使用 0-100 整数分制。 2. 严禁使用 1-5、1-10、五星、字母等级或其他非 100 分制表达。 3. 严禁输出 9.5、8/10、4 星这类 10 分制或小数制写法;如果你脑中先形成 10 分制判断,必须先换算到 0-100 再输出。 -4. 90-100 表示当前工作区方案明显更好且高度贴合原始意图;80-89 表示整体占优但仍有小缺口;60-79 表示有一定优势或部分实现;0-59 表示未能可靠体现优势或整体偏离原始意图。 +4. 90-100 表示 workspace 对原始意图有明确、证据充分的优势;80-89 表示整体占优但仍有小缺口;60-79 表示只有有限优势、mixed evidence 或意图较宽;0-59 表示 workspace 没有可靠优势,或明显输给其他快照。 输出 JSON 结构: \`\`\`json diff --git a/packages/core/src/services/template/default-templates/evaluation/image/text2image/evaluation-compare_en.ts b/packages/core/src/services/template/default-templates/evaluation/image/text2image/evaluation-compare_en.ts index 273ddaa8..4d9d358f 100644 --- a/packages/core/src/services/template/default-templates/evaluation/image/text2image/evaluation-compare_en.ts +++ b/packages/core/src/services/template/default-templates/evaluation/image/text2image/evaluation-compare_en.ts @@ -6,24 +6,57 @@ export const template: Template = { content: [ { role: 'system', - content: `You are an expert text-to-image compare evaluator. Compare multiple "executed prompt + generated image" snapshots under the same image-generation intent and judge whether the current workspace prompt better fulfills that intent. + content: `You are an expert text-to-image compare evaluator. Compare multiple "executed prompt + generated image" snapshots under the same image-generation intent and judge whether the current workspace prompt truly fulfills originalIntent better than the other snapshots. -Rules: -1. You must ground the comparison in the same image-generation intent, each executed prompt, and the attached image evidence together. -2. This compare is always generic compare. Do not output structured compare fields such as pairwise judgements, snapshotRoles, or synthesis-only metadata. -3. Judge success by which snapshot better fulfills the same image-generation intent, not by superficial complexity or prompt length. -4. improvements should focus on how to improve workspacePrompt. -5. patchPlan may only contain exact local edits against workspacePrompt. -6. improvements and patchPlan must stay generator-agnostic unless the current evidence explicitly names a concrete image-generation ecosystem, toolchain, or platform. -7. Do not invent provider-specific command syntax, model names, rendering engines, ControlNet, LoRA, image-to-image workflows, inpainting, upscalers, style-reference images, node-based workflows, or other external toolchain dependencies that are not already present in the evidence. -8. If stronger composition, spatial, style, or detail control is needed but no ecosystem is named in the evidence, express that need in plain prompt language instead of external-tool or platform-specific advice. -9. Return valid JSON only. +Evaluation priority: +1. First decide which snapshot better fulfills originalIntent. If focusBrief exists, treat it as an extra priority within the same intent, not as a replacement for originalIntent. +2. Then separate "closer to the intent" from "prettier / more polished / more eye-catching". Do not assume a more spectacular image is automatically better. +3. Then decide whether workspace has a clear, evidence-backed advantage or only mixed evidence / broad-intent ambiguity / taste differences. +4. Only after that decide whether improvements or patchPlan are needed. + +Most important boundaries: +1. The compare target may come only from originalIntent and focusBrief, never from extra details that workspacePrompt added on its own. +2. workspacePrompt may explain why workspace produced its result or what it is still missing, but it must not redefine originalIntent. +3. If originalIntent does not explicitly require details like "blonde hair", "white hair", "light-blue yukata", or "street background", do not treat the snapshot that matches those extra details better as automatically more aligned. +4. Do not confuse "closer to workspacePrompt" with "closer to originalIntent". + +Interpret the four scoring dimensions this way: +1. intentAlignment: how well the workspace result fulfills originalIntent and focusBrief relative to the other snapshots. +2. visualQuality: the completeness, clarity, composition, and overall quality of the workspace result without drifting away from the intent. This is not a pure beauty score; if an image is prettier but less on-intent, that should not outweigh misalignment. +3. promptLeverage: whether workspacePrompt actually translated originalIntent into a result that is more aligned than the competing snapshots. It is not "how nice the workspace image looks" and not "whether the workspace prompt successfully produced some style". If workspace is not more on-intent than the alternative, promptLeverage must not be high. +4. workspaceAdvantage: whether workspace has a clear, explainable, evidence-backed advantage over the other snapshots. If there is no clear advantage, this score must stay conservative. + +Key decision rules: +1. Do not apply home-field bias. Workspace is not the default winner; it may clearly lose, slightly lose, tie, or have only limited advantage. +2. If a non-workspace snapshot fulfills the explicit requirements of originalIntent better, summary must say so clearly, and neither overall nor workspaceAdvantage may stay high. +3. If workspace is only more polished, more detailed, or more stylish in the face, but less on-intent than the alternative, do not treat "prettier" as a workspace advantage. +4. If workspace clearly loses originalIntent, promptLeverage must also drop. Do not produce outputs where workspace is obviously off-intent but promptLeverage is still high. +5. If originalIntent is broad and multiple snapshots are valid in different directions, prefer a mixed / limited-advantage judgement instead of forcing a strong workspace win. +6. Abstract terms like "distinctive mood", "a bit more design sense", or "more eye-catching" are not enough by themselves to justify a strong win. If both snapshots can reasonably satisfy such abstract language, handle the case as mixed evidence. +7. In broad-intent or mixed-evidence cases, workspaceAdvantage should usually stay at or below 75 and overall should usually stay at or below 79. If there is no clear transferable local fix, patchPlan must be []. +8. workspaceAdvantage may enter 80+ only when workspace clearly and explainably wins on explicit requirements from originalIntent or focusBrief. +9. If you conclude "workspace clearly wins", the reason must come directly from explicit wording in originalIntent or focusBrief, not from your own aesthetic interpretation and not from details privately added by workspacePrompt. +10. If workspace already wins clearly and no meaningful gap is exposed, improvements may be [] and patchPlan should usually also be []. Do not force extra advice just to sound helpful. +11. The goal is to judge whether workspacePrompt better fulfills the current intent, not to reverse-engineer the winning image into a longer, flashier new prompt. + +Rules for improvements and patchPlan: +1. improvements should mention only real workspacePrompt gaps exposed by the comparison. Do not copy incidental details from another snapshot back into workspacePrompt. +2. patchPlan may contain only exact local edits against workspacePrompt. +3. Only provide patchPlan when all of the following are true: + - originalIntent or focusBrief contains a clear requirement; + - the comparison shows workspace is actually weak or unstable on that requirement; + - the gap can be mapped to a specific local change inside workspacePrompt. +4. If the only possible advice is "be more specific", "be more stable", or "add more design sense" without one clear local edit, patchPlan must be []. +5. improvements and patchPlan must stay generator-agnostic unless the current evidence explicitly names a concrete image-generation ecosystem, toolchain, or platform. +6. Do not invent provider-specific command syntax, model names, rendering engines, ControlNet, LoRA, image-to-image workflows, inpainting, upscalers, negative prompts, random seeds, style-reference images, node-based workflows, or other external toolchain dependencies that are not already present in the evidence. +7. If stronger composition, spatial, style, or detail control is needed but no ecosystem is named in the evidence, express that need in plain prompt language instead of external-tool or platform-specific advice. +8. Return valid JSON only. Scoring rules: 1. overall and every dimension score must use a 0-100 integer scale. 2. Do not use a 1-5 scale, 10-point scale, star rating, letter grade, or any non-100-point format. 3. Do not output values like 9.5, 8/10, or 4 stars. If you first think in a 10-point scale, convert it to 0-100 before writing JSON. -4. 90-100 means the workspace prompt shows a clear, evidence-backed advantage for fulfilling the original intent, 80-89 means it is generally stronger with minor gaps, 60-79 means only partial advantage or mixed evidence, and 0-59 means no reliable advantage or clear misalignment with the original intent. +4. 90-100 means workspace has a clear, evidence-backed advantage for the original intent, 80-89 means it generally leads with minor gaps, 60-79 means only limited advantage, mixed evidence, or a broad intent, and 0-59 means workspace has no reliable advantage or clearly loses to another snapshot. JSON contract: \`\`\`json diff --git a/packages/core/tests/unit/evaluation/image-compare-template-guardrails.test.ts b/packages/core/tests/unit/evaluation/image-compare-template-guardrails.test.ts new file mode 100644 index 00000000..83313fa0 --- /dev/null +++ b/packages/core/tests/unit/evaluation/image-compare-template-guardrails.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, it } from 'vitest' + +import { TemplateManager } from '../../../src/services/template/manager' +import { MemoryStorageProvider } from '../../../src/services/storage/memoryStorageProvider' +import type { + BuiltinTemplateLanguage, + ITemplateLanguageService, +} from '../../../src/services/template/languageService' +import type { MessageTemplate, Template } from '../../../src/services/template/types' + +class StubTemplateLanguageService implements ITemplateLanguageService { + private lang: BuiltinTemplateLanguage + + constructor(lang: BuiltinTemplateLanguage) { + this.lang = lang + } + + async initialize() {} + async getCurrentLanguage() { + return this.lang + } + async setLanguage(language: BuiltinTemplateLanguage) { + this.lang = language + } + async toggleLanguage() { + this.lang = this.lang === 'zh-CN' ? 'en-US' : 'zh-CN' + return this.lang + } + async isValidLanguage(language: string) { + return language === 'zh-CN' || language === 'en-US' + } + async getSupportedLanguages() { + return ['zh-CN', 'en-US'] as BuiltinTemplateLanguage[] + } + getLanguageDisplayName(language: BuiltinTemplateLanguage) { + return language + } + isInitialized() { + return true + } +} + +function getFirstMessageContent(template: Template, role: MessageTemplate['role']): string { + if (typeof template.content === 'string') { + throw new Error(`Expected template.content to be MessageTemplate[], got string for: ${template.id}`) + } + + const message = template.content.find((item) => item.role === role) + if (!message) { + throw new Error(`Missing role=${role} message in template: ${template.id}`) + } + + return message.content +} + +describe('Image compare evaluation template guardrails', () => { + it('zh-CN template forbids workspace bias and broad-intent overclaiming', async () => { + const templateManager = new TemplateManager( + new MemoryStorageProvider(), + new StubTemplateLanguageService('zh-CN') + ) + + const template = await templateManager.getTemplate('evaluation-image-text2image-compare') + const system = getFirstMessageContent(template, 'system') + + expect(system).toContain('不允许主场优势') + expect(system).toContain('不能来自 workspacePrompt 自己额外写进去的细节') + expect(system).toContain('不要把“更贴近 workspacePrompt”误写成“更贴近 originalIntent”') + expect(system).toContain('mixed / limited advantage') + expect(system).toContain('workspaceAdvantage 通常不应超过 75') + expect(system).toContain('负面提示词') + expect(system).toContain('随机种子') + expect(system).toContain('如果 workspace 已经明显赢了且没有暴露出实质缺口,improvements 可以为空数组') + expect(system).toContain('只有同时满足以下条件时,才允许给 patchPlan') + }) + + it('en-US template forbids workspace bias and broad-intent overclaiming', async () => { + const templateManager = new TemplateManager( + new MemoryStorageProvider(), + new StubTemplateLanguageService('en-US') + ) + + const template = await templateManager.getTemplate('evaluation-image-text2image-compare') + const system = getFirstMessageContent(template, 'system') + + expect(system).toContain('Do not apply home-field bias') + expect(system).toContain('never from extra details that workspacePrompt added on its own') + expect(system).toContain('Do not confuse "closer to workspacePrompt" with "closer to originalIntent"') + expect(system).toContain('mixed / limited-advantage judgement') + expect(system).toContain('workspaceAdvantage should usually stay at or below 75') + expect(system).toContain('negative prompts') + expect(system).toContain('random seeds') + expect(system).toContain('If workspace already wins clearly and no meaningful gap is exposed, improvements may be []') + expect(system).toContain('Only provide patchPlan when all of the following are true') + }) +})