Просмотр исходного кода

feat: add CLI commands for multi-model routing

Add inkos config set-model / remove-model / show-models commands for
per-agent model overrides. Uses existing modelOverrides field in
inkos.json. Update CLI reference in both READMEs, mark roadmap item done.
Ma 7 месяцев назад
Родитель
Сommit
91c5f7045b
3 измененных файлов с 102 добавлено и 2 удалено
  1. 4 1
      README.en.md
  2. 4 1
      README.md
  3. 94 0
      packages/cli/src/commands/config.ts

+ 4 - 1
README.en.md

@@ -392,6 +392,9 @@ inkos up                          # Daemon mode
 | `inkos config set-global` | Set global LLM config (~/.inkos/.env) |
 | `inkos config show-global` | Show global config |
 | `inkos config set/show` | View/update project config |
+| `inkos config set-model <agent> <model>` | Set model override for a specific agent |
+| `inkos config remove-model <agent>` | Remove agent model override (fall back to default) |
+| `inkos config show-models` | Show current model routing |
 | `inkos doctor` | Diagnose setup issues (includes API connectivity test) |
 | `inkos detect [id] [n]` | AIGC detection (`--all` for all chapters, `--stats` for statistics) |
 | `inkos style analyze <file>` | Analyze reference text to extract style fingerprint |
@@ -476,7 +479,7 @@ TypeScript monorepo managed with pnpm workspaces.
 - [x] Webhook notifications + smart scheduler (quality gates)
 - [x] Cross-chapter coherence (chapter summaries + subplot/emotion/character matrices)
 - [ ] `packages/studio` Web UI for review and editing
-- [ ] Multi-model routing (different models for different agents)
+- [x] Multi-model routing (different models for different agents, `inkos config set-model`)
 - [ ] Custom agent plugin system
 - [ ] Platform-specific export (Qidian, Tomato, etc.)
 

+ 4 - 1
README.md

@@ -392,6 +392,9 @@ inkos up                       # 守护进程模式
 | `inkos config set-global` | 设置全局 LLM 配置(~/.inkos/.env) |
 | `inkos config show-global` | 查看全局配置 |
 | `inkos config set/show` | 查看/更新项目配置 |
+| `inkos config set-model <agent> <model>` | 为指定 agent 设置模型覆盖 |
+| `inkos config remove-model <agent>` | 移除 agent 模型覆盖(回退到默认) |
+| `inkos config show-models` | 查看当前模型路由 |
 | `inkos doctor` | 诊断配置问题(含 API 连通性测试) |
 | `inkos detect [id] [n]` | AIGC 检测(`--all` 全部章节,`--stats` 统计) |
 | `inkos style analyze <file>` | 分析参考文本提取文风指纹 |
@@ -494,7 +497,7 @@ TypeScript 单仓库,pnpm workspaces 管理。
 - [x] 写后验证器(11 条硬规则 + 自动 spot-fix)
 - [x] 审计-修订闭环加固(AI 标记守卫 + 温度锁)
 - [ ] `packages/studio` Web UI 审阅编辑界面
-- [ ] 多模型路由(不同 agent 用不同模型)
+- [x] 多模型路由(不同 agent 用不同模型,`inkos config set-model`)
 - [ ] 自定义 agent 插件系统
 - [ ] 平台格式导出(起点、番茄等)
 

+ 94 - 0
packages/cli/src/commands/config.ts

@@ -111,3 +111,97 @@ configCommand
       process.exit(1);
     }
   });
+
+const KNOWN_AGENTS = ["writer", "auditor", "reviser", "architect", "radar", "chapter-analyzer"] as const;
+
+configCommand
+  .command("set-model")
+  .description("Set model override for a specific agent")
+  .argument("<agent>", `Agent name (${KNOWN_AGENTS.join(", ")})`)
+  .argument("<model>", "Model name (e.g., gpt-4o, claude-sonnet-4-20250514)")
+  .action(async (agent: string, model: string) => {
+    if (!KNOWN_AGENTS.includes(agent as typeof KNOWN_AGENTS[number])) {
+      logError(`Unknown agent "${agent}". Valid agents: ${KNOWN_AGENTS.join(", ")}`);
+      process.exit(1);
+    }
+
+    const root = findProjectRoot();
+    const configPath = join(root, "inkos.json");
+
+    try {
+      const raw = await readFile(configPath, "utf-8");
+      const config = JSON.parse(raw);
+      const overrides = config.modelOverrides ?? {};
+      config.modelOverrides = { ...overrides, [agent]: model };
+      await writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
+      log(`Model override: ${agent} → ${model}`);
+    } catch (e) {
+      logError(`Failed to update config: ${e}`);
+      process.exit(1);
+    }
+  });
+
+configCommand
+  .command("remove-model")
+  .description("Remove model override for a specific agent (falls back to default)")
+  .argument("<agent>", "Agent name")
+  .action(async (agent: string) => {
+    const root = findProjectRoot();
+    const configPath = join(root, "inkos.json");
+
+    try {
+      const raw = await readFile(configPath, "utf-8");
+      const config = JSON.parse(raw);
+      const overrides = config.modelOverrides;
+      if (!overrides || !(agent in overrides)) {
+        log(`No model override for "${agent}".`);
+        return;
+      }
+      const { [agent]: _, ...rest } = overrides;
+      config.modelOverrides = Object.keys(rest).length > 0 ? rest : undefined;
+      await writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
+      log(`Removed model override for ${agent}. Will use default model.`);
+    } catch (e) {
+      logError(`Failed to update config: ${e}`);
+      process.exit(1);
+    }
+  });
+
+configCommand
+  .command("show-models")
+  .description("Show model routing for all agents")
+  .option("--json", "Output JSON")
+  .action(async (opts) => {
+    const root = findProjectRoot();
+    const configPath = join(root, "inkos.json");
+
+    try {
+      const raw = await readFile(configPath, "utf-8");
+      const config = JSON.parse(raw);
+      const defaultModel = config.llm?.model ?? "(not set)";
+      const overrides: Record<string, string> = config.modelOverrides ?? {};
+
+      if (opts.json) {
+        log(JSON.stringify({ defaultModel, overrides }, null, 2));
+        return;
+      }
+
+      log(`Default model: ${defaultModel}\n`);
+      if (Object.keys(overrides).length === 0) {
+        log("No agent-specific overrides. All agents use the default model.");
+        return;
+      }
+      log("Agent overrides:");
+      for (const [agent, model] of Object.entries(overrides)) {
+        log(`  ${agent} → ${model}`);
+      }
+      log("");
+      const usingDefault = KNOWN_AGENTS.filter((a) => !(a in overrides));
+      if (usingDefault.length > 0) {
+        log(`Using default: ${usingDefault.join(", ")}`);
+      }
+    } catch (e) {
+      logError(`Failed to read config: ${e}`);
+      process.exit(1);
+    }
+  });