docs/architecture/spo-thin-loop-ui-and-stop-rules.md
本设计解决三个架构问题:
SPO 如何在不复制 compare intelligence 的前提下支持多轮循环SPO 的停止条件如何尽量依赖 compare evaluation 的通用输出SPO 的运行结果如何以最小 UI 复杂度融入现有测试区核心目标是让 SPO 保持为:
而不是新的 judge 层。
截至 2026-03-20,当前代码里与本文直接相关、已经落地的能力包括:
compareModesnapshotRolescompareJudgementscompareStopSignalscompareInsights当前仍未落地:
SPO 按钮SPO 配置弹窗SPO 运行态卡片SPO 多轮状态机SPO 停止规则执行因此本文当前主要是“目标态架构文档”,不应误读为 SPO 运行链路已经上线。
flowchart TD
A["测试执行层\nrun variants / collect snapshots"] --> B["评估层\nGeneric Compare / Structured Compare"]
B --> C["通用重写层\nRewrite From Evaluation"]
C --> D["SPO 编排层\npreset / loop / stop / accept"]
D --> E["SPO 展示层\nbutton / modal / run card / result card / drawer"]
关键原则:
本设计明确不建议增加一个额外的 SPO judge LLM 调用来决定:
原因:
SPOcompare evaluation 和 SPO 之间再次深度耦合推荐方式是:
SPO 只消费这些 signals在保持现有对外主结果结构不变的前提下,compare evaluation 内部或 metadata 层建议补充:
interface CompareStopSignals {
targetVsBaseline: 'improved' | 'flat' | 'regressed'
targetVsReferenceGap: 'none' | 'minor' | 'major'
improvementHeadroom: 'none' | 'low' | 'medium' | 'high'
overfitRisk: 'low' | 'medium' | 'high'
stopRecommendation: 'continue' | 'stop' | 'review'
stopReasons: string[]
}
这些字段不是 SPO 私有的,它们是 structured compare 的通用机器可读信号。
建议 SPO 的主状态机如下:
stateDiagram-v2
[*] --> idle
idle --> configuring
configuring --> running
running --> roundTesting
roundTesting --> roundComparing
roundComparing --> preRewriteStoppingCheck
preRewriteStoppingCheck --> finished: stop
preRewriteStoppingCheck --> roundRewriting: continue
roundRewriting --> roundRetesting
roundRetesting --> roundRetestComparing
roundRetestComparing --> roundAcceptedCheck
roundAcceptedCheck --> running: next round
roundAcceptedCheck --> finished: stop
running --> stopped: user stop
running --> failed: error
configuring
roundTesting
roundComparing
preRewriteStoppingCheck
roundRewriting
workspaceroundRetesting
workspace 再次执行测试roundRetestComparing
roundAcceptedCheck
必须直接结束:
regressed满足以下情况可提前结束:
improvementHeadroom = none | lowtargetVsReferenceGap = none | minor每轮结束后,不应简单地“最后一轮覆盖前一轮”。
建议采用:
accepted best round为保持 SPO 足够薄,acceptedRound 的判定不应由 SPO 自己发明文本规则,而应直接依赖“复测后的 structured compare”。
推荐做法:
roundAcceptedCheck 仅消费这次 post-retest compare 的结果,至少检查:
targetVsBaseline 不是 regressedoverfitRisk 不是 high主界面只新增:
SPO 按钮SPO 运行/结果卡不新增页面,不打断现有测试区结构。
SPO 详情应采用与评估面板类似的右侧抽屉方式:
这样避免引入新的复杂导航结构。
结果展示必须区分:
否则多轮过程中一旦后续轮回归,用户很难理解系统最终保留了什么。
interface SpoConfig {
targetModelKey: string
referenceModelKey: string
maxRounds: number
stopMode: 'round-only' | 'smart' | 'custom'
rewriteModelKey?: string
}
interface SpoRuntimeState {
status: 'idle' | 'running' | 'completed' | 'stopped' | 'failed'
currentRound: number
acceptedRound: number | null
bestScore: number | null
stopReason?: string
rounds: SpoRoundState[]
}
interface SpoRoundState {
round: number
compareScore: number | null
accepted: boolean
stopSignals?: CompareStopSignals
acceptanceSignals?: CompareStopSignals
summary?: string
}
其中:
SpoConfig 属于 SPOCompareStopSignals 属于 compare evaluationSPO 配置弹窗SPO 运行卡 / 结果卡SPO 详情抽屉最合理的架构是:
SPO 保持为极薄的 loop controller + UI wrapper这样可以最大限度复用现有能力,同时把“为什么停、停在哪一轮、最终保留哪一轮”清晰呈现给用户。