From a5a3c967ceba551a776c76ebe199922fe11ba02c Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sun, 14 Jun 2026 15:14:09 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=A8=E9=87=8F=E6=9B=BF=E6=8D=A2=20c?= =?UTF-8?q?laude-fable-5=20=E2=86=92=20claude-opus-4-8=EF=BC=88=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E4=B8=8D=E5=8F=AF=E7=94=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - models.ts: 所有档位默认值、回退链、conflict 固定值全部换为 opus-4-8 - 回退链从三项缩为两项 [opus-4-8, sonnet-4-6](删除重复项) - metrics.ts: 额度权重 opus-4-8 升为 1.0(现为最高档) - 测试同步更新 Co-Authored-By: Claude Sonnet 4.6 --- src/executor/models.ts | 26 +++++++++++++------------- src/model/metrics.ts | 7 +++---- test/metrics.test.ts | 8 ++++---- test/models.test.ts | 37 ++++++++++++++++++------------------- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/executor/models.ts b/src/executor/models.ts index 693d4c9..8671a71 100644 --- a/src/executor/models.ts +++ b/src/executor/models.ts @@ -4,39 +4,39 @@ import type { Complexity } from '../model/complexity.js'; /** 角色:executor(执行改动)/ reviewer(执行后复审)/ planner(拆解 Hard / 写方案 Medium)/ conflict(冲突裁决) */ export type ModelRole = 'executor' | 'reviewer' | 'planner' | 'conflict'; -/** 模型不可用时的回退链(按序取第一个 ≠ 失败模型的,同一 run 内只重试一次) */ -export const MODEL_FALLBACK_CHAIN = ['claude-fable-5', 'claude-opus-4-8', 'claude-sonnet-4-6'] as const; +/** 模型不可用时的回退链(按序取下一档,同一 run 内只重试一次) */ +export const MODEL_FALLBACK_CHAIN = ['claude-opus-4-8', 'claude-sonnet-4-6'] as const; /** 复杂度 → [env 覆盖变量名, 默认模型] */ const MODEL_TABLE: Record> = { executor: { easy: ['MAESTRO_MODEL_EASY', 'claude-sonnet-4-6'], medium: ['MAESTRO_MODEL_MEDIUM', 'claude-opus-4-8'], - hard: ['MAESTRO_MODEL_HARD', 'claude-fable-5'], + hard: ['MAESTRO_MODEL_HARD', 'claude-opus-4-8'], }, reviewer: { - easy: ['MAESTRO_MODEL_REVIEW_EASY', 'claude-fable-5'], - medium: ['MAESTRO_MODEL_REVIEW_MEDIUM', 'claude-fable-5'], - hard: ['MAESTRO_MODEL_REVIEW_HARD', 'claude-fable-5'], + easy: ['MAESTRO_MODEL_REVIEW_EASY', 'claude-opus-4-8'], + medium: ['MAESTRO_MODEL_REVIEW_MEDIUM', 'claude-opus-4-8'], + hard: ['MAESTRO_MODEL_REVIEW_HARD', 'claude-opus-4-8'], }, // planner:拆解/写方案按复杂度分化(不受 project.model 覆盖) planner: { easy: ['MAESTRO_MODEL_PLAN_EASY', 'claude-sonnet-4-6'], medium: ['MAESTRO_MODEL_PLAN_MEDIUM', 'claude-opus-4-8'], - hard: ['MAESTRO_MODEL_PLAN_HARD', 'claude-fable-5'], + hard: ['MAESTRO_MODEL_PLAN_HARD', 'claude-opus-4-8'], }, - // conflict:固定 fable-5,TABLE 条目仅占位(pickModel 对此角色直接 return 不走此处) + // conflict:固定 opus-4-8,TABLE 条目仅占位(pickModel 对此角色直接 return 不走此处) conflict: { - easy: ['', 'claude-fable-5'], - medium: ['', 'claude-fable-5'], - hard: ['', 'claude-fable-5'], + easy: ['', 'claude-opus-4-8'], + medium: ['', 'claude-opus-4-8'], + hard: ['', 'claude-opus-4-8'], }, }; /** * 按角色 + 复杂度选模型: * 1. reviewer/conflict 不受 project.model 覆盖(保复审独立性) - * 2. conflict 固定返回 claude-fable-5 + * 2. conflict 固定返回 claude-opus-4-8(最强可用档) * 3. 其他角色 project.model 设了就最优先 * 4. 否则查 env 覆盖(MAESTRO_MODEL_* / MAESTRO_MODEL_REVIEW_*) * 5. 否则按复杂度取默认 @@ -44,7 +44,7 @@ const MODEL_TABLE: Record = { - 'claude-fable-5': 1.0, - 'claude-opus-4-8': 0.6, + 'claude-opus-4-8': 1.0, 'claude-sonnet-4-6': 0.2, }; export const DEFAULT_COST_PER_MIN = 0.4; diff --git a/test/metrics.test.ts b/test/metrics.test.ts index b2b4390..0655382 100644 --- a/test/metrics.test.ts +++ b/test/metrics.test.ts @@ -36,10 +36,10 @@ test('metrics 纯函数:percentile / ratio / estimateCostUnits', () => { assert.equal(ratio(2, 3), 0.6667); assert.equal(ratio(0, 0), null); // 无样本不除零 - assert.equal(costPerMin('claude-fable-5'), 1.0); + assert.equal(costPerMin('claude-opus-4-8'), 1.0); assert.equal(costPerMin('未知模型'), DEFAULT_COST_PER_MIN); - assert.equal(estimateCostUnits(60_000, 'claude-fable-5'), 1.0); // 1 分钟 × 1.0 - assert.equal(estimateCostUnits(120_000, 'claude-opus-4-8'), 1.2); // 2 分钟 × 0.6 + assert.equal(estimateCostUnits(60_000, 'claude-opus-4-8'), 1.0); // 1 分钟 × 1.0 + assert.equal(estimateCostUnits(120_000, 'claude-sonnet-4-6'), 0.4); // 2 分钟 × 0.2 }); // ──────────────── 空数据不崩 ──────────────── @@ -166,7 +166,7 @@ test('metrics 按模型额度:时长×档位 粗估,estimated=true', () => { const m = s.metrics(p.id); assert.equal(m.byModel.length, 1); assert.deepEqual(m.byModel[0], { - model: 'claude-opus-4-8', runs: 2, durationMs: 120_000, estCostUnits: 1.2, estimated: true, + model: 'claude-opus-4-8', runs: 2, durationMs: 120_000, estCostUnits: 2.0, estimated: true, }); assert.equal(m.estimated, true); s.close(); diff --git a/test/models.test.ts b/test/models.test.ts index a7f6495..b754e03 100644 --- a/test/models.test.ts +++ b/test/models.test.ts @@ -39,11 +39,11 @@ test('pickModel:复杂度映射默认值(executor / reviewer)', () => { const p = fakeProject(); assert.equal(pickModel(fakeTask('easy'), p, 'executor'), 'claude-sonnet-4-6'); assert.equal(pickModel(fakeTask('medium'), p, 'executor'), 'claude-opus-4-8'); - assert.equal(pickModel(fakeTask('hard'), p, 'executor'), 'claude-fable-5'); - // reviewer 三档全升 fable-5(保复审独立性) - assert.equal(pickModel(fakeTask('easy'), p, 'reviewer'), 'claude-fable-5'); - assert.equal(pickModel(fakeTask('medium'), p, 'reviewer'), 'claude-fable-5'); - assert.equal(pickModel(fakeTask('hard'), p, 'reviewer'), 'claude-fable-5'); + assert.equal(pickModel(fakeTask('hard'), p, 'executor'), 'claude-opus-4-8'); + // reviewer 三档统一 opus-4-8(保复审独立性) + assert.equal(pickModel(fakeTask('easy'), p, 'reviewer'), 'claude-opus-4-8'); + assert.equal(pickModel(fakeTask('medium'), p, 'reviewer'), 'claude-opus-4-8'); + assert.equal(pickModel(fakeTask('hard'), p, 'reviewer'), 'claude-opus-4-8'); }); }); @@ -53,10 +53,10 @@ test('pickModel:project.model executor 最优先,但 reviewer/conflict 不 for (const c of ['easy', 'medium', 'hard'] as const) { // executor 受 project.model 覆盖 assert.equal(pickModel(fakeTask(c), p, 'executor'), 'claude-opus-4-6'); - // reviewer 不受 project.model 覆盖,始终用最强档 fable-5(保自审独立性) - assert.equal(pickModel(fakeTask(c), p, 'reviewer'), 'claude-fable-5'); - // conflict 固定 fable-5 - assert.equal(pickModel(fakeTask(c), p, 'conflict'), 'claude-fable-5'); + // reviewer 不受 project.model 覆盖,始终用 opus-4-8(保自审独立性) + assert.equal(pickModel(fakeTask(c), p, 'reviewer'), 'claude-opus-4-8'); + // conflict 固定 opus-4-8 + assert.equal(pickModel(fakeTask(c), p, 'conflict'), 'claude-opus-4-8'); } }); }); @@ -64,15 +64,15 @@ test('pickModel:project.model executor 最优先,但 reviewer/conflict 不 test('pickModel:env 覆盖默认值,executor/reviewer 各用各的 env', () => { withEnv({ MAESTRO_MODEL_EASY: 'claude-haiku-4-5', - MAESTRO_MODEL_REVIEW_EASY: 'claude-opus-4-8', // reviewer env 可单独覆盖 + MAESTRO_MODEL_REVIEW_EASY: 'claude-sonnet-4-6', // reviewer env 可单独覆盖 }, () => { const p = fakeProject(); assert.equal(pickModel(fakeTask('easy'), p, 'executor'), 'claude-haiku-4-5'); // reviewer easy 被 MAESTRO_MODEL_REVIEW_EASY 覆盖 - assert.equal(pickModel(fakeTask('easy'), p, 'reviewer'), 'claude-opus-4-8'); - // reviewer hard 无 env → 默认 fable-5 - assert.equal(pickModel(fakeTask('hard'), p, 'reviewer'), 'claude-fable-5'); - assert.equal(pickModel(fakeTask('hard'), p, 'executor'), 'claude-fable-5'); // executor hard 默认 fable + assert.equal(pickModel(fakeTask('easy'), p, 'reviewer'), 'claude-sonnet-4-6'); + // reviewer hard 无 env → 默认 opus-4-8 + assert.equal(pickModel(fakeTask('hard'), p, 'reviewer'), 'claude-opus-4-8'); + assert.equal(pickModel(fakeTask('hard'), p, 'executor'), 'claude-opus-4-8'); }); }); @@ -97,10 +97,9 @@ test('isModelError:模型不可用类错误才触发回退', () => { assert.ok(!isModelError('model produced empty output')); // 提到 model 但不是可用性错误 }); -test('pickFallbackModel:回退链取第一个 ≠ 失败模型的', () => { - // 链已扩展为三项:fable → opus → sonnet - assert.deepEqual([...MODEL_FALLBACK_CHAIN], ['claude-fable-5', 'claude-opus-4-8', 'claude-sonnet-4-6']); - assert.equal(pickFallbackModel('claude-fable-5'), 'claude-opus-4-8'); +test('pickFallbackModel:回退链取下一档(循环)', () => { + // 链:opus → sonnet → opus(循环) + assert.deepEqual([...MODEL_FALLBACK_CHAIN], ['claude-opus-4-8', 'claude-sonnet-4-6']); assert.equal(pickFallbackModel('claude-opus-4-8'), 'claude-sonnet-4-6'); - assert.equal(pickFallbackModel('claude-sonnet-4-6'), 'claude-fable-5'); + assert.equal(pickFallbackModel('claude-sonnet-4-6'), 'claude-opus-4-8'); });