Files
maestro/src/executor/models.ts
T
wangjia 13572a5416 feat(complexity): 新增 AUTO·由模型判断 复杂度档位 (tsk_zacmwH3chZpG)
新建任务时除手选 hard/medium/easy 外,新增 AUTO 档:先以 medium 落库
占位,建任务后异步用 sonnet 起一次轻量分类调用回填三档结果并记录理由,
不阻塞建任务返回;模型不可用/解析失败一律兜底 medium,绝不报错。

- web 看板:复杂度 seg 增加第四档 AUTO(默认选中,cyan 样式),提交 complexity='auto'
- src/api/server.ts:POST tasks 接受 'auto' → medium 占位 + 异步 classify 回填(可注入 classify 测试)
- src/executor/classify.ts(新增):buildClassifyPrompt/parseClassification/classifyComplexity
  复用 cc.ts/models.ts,sonnet 省额度,无工具、限轮限时
- src/executor/models.ts:pickClassifierModel(复用 executor easy 档 + MAESTRO_MODEL_EASY 覆盖)
- src/store/store.ts:applyAutoComplexity 回填复杂度+重置状态+理由入产出字段+广播 task.updated
- test/classify.test.ts:解析映射/兜底、prompt、回填、建任务集成(13 例)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 10:46:37 +08:00

70 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Project, Task } from '../model/types.js';
import type { Complexity } from '../model/complexity.js';
/** 角色:executor(执行改动)/ reviewer(执行后复审) */
export type ModelRole = 'executor' | 'reviewer';
/** 模型不可用时的回退链(按序取第一个 ≠ 失败模型的,同一 run 内只重试一次) */
export const MODEL_FALLBACK_CHAIN = ['claude-opus-4-8', 'claude-sonnet-4-6'] as const;
/** 复杂度 → [env 覆盖变量名, 默认模型] */
const MODEL_TABLE: Record<ModelRole, Record<Complexity, [env: string, fallback: string]>> = {
executor: {
easy: ['MAESTRO_MODEL_EASY', 'claude-sonnet-4-6'],
medium: ['MAESTRO_MODEL_MEDIUM', 'claude-opus-4-8'],
hard: ['MAESTRO_MODEL_HARD', 'claude-fable-5'],
},
reviewer: {
easy: ['MAESTRO_MODEL_REVIEW_EASY', 'claude-sonnet-4-6'],
medium: ['MAESTRO_MODEL_REVIEW_MEDIUM', 'claude-opus-4-8'],
hard: ['MAESTRO_MODEL_REVIEW_HARD', 'claude-opus-4-8'],
},
};
/**
* 按角色 + 复杂度选模型:
* 1. project.model 设了就最优先(executor/reviewer 都用它)
* 2. 否则查 env 覆盖(MAESTRO_MODEL_* / MAESTRO_MODEL_REVIEW_*
* 3. 否则按复杂度取默认
*/
export function pickModel(task: Task, project: Project, role: ModelRole): string {
if (project.model) return project.model;
const [env, fallback] = MODEL_TABLE[role][task.complexity];
const override = process.env[env]?.trim();
return override || fallback;
}
/** 看板展示用:当前项目 executor 各复杂度实际会用的模型(含 project.model / env 覆盖解析) */
export function resolvedExecutorModels(project: Project): Record<Complexity, string> {
const out = {} as Record<Complexity, string>;
for (const c of ['easy', 'medium', 'hard'] as Complexity[]) {
if (project.model) { out[c] = project.model; continue; }
const [env, fallback] = MODEL_TABLE.executor[c];
out[c] = process.env[env]?.trim() || fallback;
}
return out;
}
/**
* AUTO 复杂度分类器用的模型:复用 executor easy 档(claude-sonnet,省额度),
* env MAESTRO_MODEL_EASY 可覆盖。单次轻量调用,不走 project.model。
*/
export function pickClassifierModel(): string {
const [env, fallback] = MODEL_TABLE.executor.easy;
return process.env[env]?.trim() || fallback;
}
/** 错误信息是否像“模型不可用”(not_found / invalid / permission 等模式 + 提到 model */
export function isModelError(msg: string): boolean {
if (!/model/i.test(msg)) return false;
return /not[_\s-]?found|invalid|permission|forbidden|unauthorized|unavailable|unknown|unsupported|does not exist|no access|404|403/i.test(msg);
}
/** 回退链里取第一个与失败模型不同的;链上全相同(不可能两项都等)则 null */
export function pickFallbackModel(failedModel: string): string | null {
for (const m of MODEL_FALLBACK_CHAIN) {
if (m !== failedModel) return m;
}
return null;
}