カタログの実装完了、ドキュメント整理
This commit is contained in:
parent
ce6198102f
commit
89c2c701fd
2
TODO.md
2
TODO.md
|
|
@ -3,8 +3,6 @@
|
|||
- [ ] ツール設計
|
||||
- [ ] Bash ツール (Permission 層と統合) → [tickets/bash-tool.md](tickets/bash-tool.md)
|
||||
- [ ] パーミッション: パターンベースのツール実行制御 → [tickets/permission-extension-point.md](tickets/permission-extension-point.md)
|
||||
- [ ] LLM プロバイダ/モデルカタログ → [tickets/llm-provider-catalog.md](tickets/llm-provider-catalog.md)
|
||||
- [ ] LLM モデルカタログ + マニフェスト ref 解決 → [tickets/llm-model-catalog.md](tickets/llm-model-catalog.md)
|
||||
- [ ] Pod オーケストレーション
|
||||
- [ ] 動的 Scope 変更 → [tickets/dynamic-scope.md](tickets/dynamic-scope.md)
|
||||
- [ ] ネイティブ GUI クライアント MVP → [tickets/native-gui-mvp.md](tickets/native-gui-mvp.md)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
### 宣言した層が解決する
|
||||
|
||||
ある層が構成を宣言として受け取ったなら、その解決もその層の責務。マニフェストに `provider.kind = "anthropic"` と書いた以上、`ProviderConfig` → `LlmClient` の変換は insomnia が行う。逆に llm-worker が `LlmClient` trait だけを受け取るのは正しい — llm-worker はプロバイダの選択を宣言として受け取っていないから。
|
||||
ある層が構成を宣言として受け取ったなら、その解決もその層の責務。マニフェストに `[model] ref = "anthropic/claude-sonnet-4-6"`(あるいは `scheme = "anthropic"` + `model_id = ...` の inline 形式)と書いた以上、`ModelManifest` → `LlmClient` の変換は insomnia 側(`crates/provider`)が行う。逆に llm-worker が `LlmClient` trait だけを受け取るのは正しい — llm-worker はプロバイダの選択を宣言として受け取っていないから。
|
||||
|
||||
### 概念の追加は不在が問題になってから
|
||||
|
||||
|
|
@ -76,8 +76,7 @@ name = "agent"
|
|||
pwd = "/abs/path"
|
||||
|
||||
[model]
|
||||
scheme = "anthropic"
|
||||
model_id = "claude-sonnet-4-20250514"
|
||||
ref = "anthropic/claude-sonnet-4-6"
|
||||
|
||||
[worker]
|
||||
instruction = "$insomnia/default"
|
||||
|
|
@ -88,6 +87,8 @@ target = "/abs/path"
|
|||
permission = "write"
|
||||
```
|
||||
|
||||
`[model]` は `ref = "<provider>/<model_id>"` でプロバイダ / モデルカタログを引く短縮形と、`scheme` / `model_id` / `auth` を直書きする inline 形式の両方を受ける。カタログは `resources/{providers,models}/builtin.toml` を builtin、`$XDG_CONFIG_HOME/insomnia/{providers,models}.toml` を user override として解決する。詳細は `docs/pod-factory.md` と `crates/provider/README.md`。
|
||||
|
||||
### PodFactory: カスケード設定
|
||||
|
||||
マニフェストを手書きせず、4 層のカスケードで `PodManifest` を組み立てる:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ Ollama は独自 scheme を作らず `scheme/anthropic` を base_url 差し替
|
|||
|
||||
### 二次サポート(OpenAI 互換共通枠)
|
||||
|
||||
`provider_kind: openai_compatible` + `base_url` + API key + `available_models[]` の汎用アダプタ 1 本で以下を収容:
|
||||
`scheme = "openai_chat"` + `base_url` + API key を宣言するプロバイダカタログエントリ(`resources/providers/builtin.toml`)1 つで以下を収容。個別モデルはモデルカタログ(`resources/models/builtin.toml`)側で列挙する:
|
||||
|
||||
- OpenRouter
|
||||
- xAI (Grok)
|
||||
|
|
@ -44,7 +44,7 @@ Ollama は独自 scheme を作らず `scheme/anthropic` を base_url 差し替
|
|||
## 実装原則
|
||||
|
||||
- 認証ストアを読むアダプタ(`~/.codex/auth.json` 等)は **llm-worker 直下に置かず上位層に配置**。llm-worker は低レベル基盤に留める方針(`feedback_llm_worker_scope.md`)と整合
|
||||
- モデル列挙は **auto_discover と宣言型の両輪**。Ollama は `/api/tags` で自動、OpenAI 互換枠は `available_models[]` を宣言
|
||||
- モデル列挙は **auto_discover と宣言型の両輪**。Ollama は `/api/tags` で自動、OpenAI 互換枠はモデルカタログ(`resources/models/builtin.toml` + `$XDG_CONFIG_HOME/insomnia/models.toml` の user override)で宣言
|
||||
- UI のプロバイダ選択肢も第一級 → 二次の優先順位で並べる
|
||||
- **`ollama launch insomnia` 対応を視野に**、env 注入(`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等)で起動設定を受け入れる作り
|
||||
|
||||
|
|
|
|||
|
|
@ -80,11 +80,16 @@ resolve 段を取りこぼしている証拠なので `ResolveError::RelativePat
|
|||
|
||||
```toml
|
||||
[model]
|
||||
scheme = "anthropic"
|
||||
model_id = "claude-sonnet-4-20250514"
|
||||
ref = "anthropic/claude-sonnet-4-6"
|
||||
auth = { kind = "api_key", file = "/home/you/.config/insomnia/keys/anthropic" }
|
||||
```
|
||||
|
||||
`ref = "<provider>/<model_id>"` はプロバイダ / モデルカタログを引く短縮形。
|
||||
`scheme` / `base_url` / `model_id` は provider 側の宣言から引かれ、`auth` も
|
||||
カタログの `auth_hint` を起点に解決する。ここでは env 既定(`INSOMNIA_API_KEY_ANTHROPIC`)
|
||||
ではなく file から読みたいので `auth` だけ override している。詳細は
|
||||
`crates/provider/README.md` と `resources/{providers,models}/builtin.toml` を参照。
|
||||
|
||||
### プロジェクト層(最小)
|
||||
|
||||
`<project>/.insomnia/manifest.toml`:
|
||||
|
|
@ -108,9 +113,20 @@ compact_threshold = 80000
|
|||
[pod]
|
||||
name = "reviewer"
|
||||
|
||||
# Form A: ref のみ(カタログから scheme / base_url / auth_hint / capability を全部引く)
|
||||
# [model]
|
||||
# ref = "anthropic/claude-sonnet-4-6"
|
||||
#
|
||||
# Form B: ref + 部分 override(ここで示している形)
|
||||
# カタログ起点に個別フィールドだけ上書き。ref 指定時は scheme / model_id / auth は任意 override。
|
||||
#
|
||||
# Form C: 完全 inline(カタログ無視。実験用 / カタログに無いモデル)
|
||||
# [model]
|
||||
# scheme = "anthropic"
|
||||
# model_id = "claude-sonnet-4-6"
|
||||
# auth = { kind = "api_key", file = "..." }
|
||||
[model]
|
||||
scheme = "anthropic"
|
||||
model_id = "claude-sonnet-4-20250514"
|
||||
ref = "anthropic/claude-sonnet-4-6"
|
||||
base_url = "https://api.anthropic.com"
|
||||
auth = { kind = "api_key", file = "/home/you/.config/insomnia/keys/anthropic" }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,195 +0,0 @@
|
|||
# LLM モデルカタログ + マニフェスト ref 解決
|
||||
|
||||
## 背景
|
||||
|
||||
`llm-provider-catalog` で provider 列挙の宣言化までは入った(`crates/provider/assets/providers.toml`、各エントリは `default_models: Vec<String>`)。しかし:
|
||||
|
||||
- **モデルの capability は依然 `crates/provider/src/capability.rs` の prefix matching ハードコード**で解決している(`model_id.starts_with("claude-")` 等)。`llm-capability-ownership` でハードコードを「scheme 個別判定 → provider 層の prefix 判定」に粗めただけで、宣言的 resource にはなっていない。
|
||||
- **マニフェストは毎回 scheme / model_id / auth を直書き**している。`scheme` は本来 model_id から派生する属性で、マニフェストが知る必要のない wire 知識。
|
||||
- provider カタログの `default_models[]` は ID 文字列だけで、capability・display_name 等のモデル単位メタが乗らない。
|
||||
|
||||
この 3 つを「モデルカタログを resource 化し、マニフェストはそこから ref で引く」に揃えると、`capability.rs` のハードコードが丸ごと不要になり、マニフェストが「`anthropic/claude-sonnet-4-6` を使う」レベルまで簡潔になる。
|
||||
|
||||
## 要件
|
||||
|
||||
### 1. リソース配置
|
||||
|
||||
```
|
||||
resources/
|
||||
providers/builtin.toml ← 既存 crates/provider/assets/providers.toml を移動
|
||||
models/builtin.toml ← 新設
|
||||
```
|
||||
|
||||
両方 `include_dir!` で同梱(`crates/pod/src/prompt_loader.rs:27` の `resources/prompts` と同パターン)。user override は引き続き `$XDG_CONFIG_HOME/insomnia/{providers,models}.toml`、置換採用(マージしない)。
|
||||
|
||||
### 2. provider カタログ形状の変更
|
||||
|
||||
```toml
|
||||
[[provider]]
|
||||
id = "anthropic"
|
||||
display_name = "Anthropic"
|
||||
scheme = "anthropic"
|
||||
base_url = "https://api.anthropic.com"
|
||||
auth_hint = { kind = "api_key", env = "INSOMNIA_API_KEY_ANTHROPIC" }
|
||||
default_capability = { tool_calling = "parallel", structured_output = "json_schema", reasoning = "budget_tokens", vision = true, prompt_caching = { explicit = { max_breakpoints = 4 } } }
|
||||
```
|
||||
|
||||
- `default_models[]` を削除(モデルカタログが真の情報源)
|
||||
- `default_capability` を追加(モデルカタログ未登録時のフォールバック先)
|
||||
- 他フィールドは現行どおり
|
||||
|
||||
### 3. モデルカタログ
|
||||
|
||||
```toml
|
||||
[[model]]
|
||||
id = "claude-sonnet-4-6"
|
||||
provider = "anthropic"
|
||||
capability = { reasoning = "budget_tokens", vision = true, ... }
|
||||
|
||||
[[model]]
|
||||
id = "claude-opus-4-1"
|
||||
provider = "anthropic"
|
||||
# capability 省略可: provider.default_capability にフォールバック
|
||||
|
||||
[[model]]
|
||||
id = "anthropic/claude-sonnet-4"
|
||||
provider = "openrouter"
|
||||
|
||||
[[model]]
|
||||
id = "openai/gpt-5"
|
||||
provider = "openrouter"
|
||||
|
||||
[[model]]
|
||||
id = "gpt-5-codex"
|
||||
provider = "codex-oauth"
|
||||
capability = { reasoning = "effort", ... }
|
||||
```
|
||||
|
||||
- `id` は **provider 内ユニーク**。同じ `gpt-5` が異なる provider に存在するのは OK
|
||||
- `provider` は providers カタログの `id` を参照。解決時 hard error 検証
|
||||
- `capability` 省略時は provider.default_capability にフォールバック
|
||||
|
||||
### 4. マニフェストの 2 形式(untagged)
|
||||
|
||||
```rust
|
||||
// crates/manifest/src/model.rs を置き換え
|
||||
struct ModelManifest {
|
||||
r#ref: Option<String>, // "<provider>/<model_id>"
|
||||
scheme: Option<SchemeKind>,
|
||||
model_id: Option<String>,
|
||||
base_url: Option<String>,
|
||||
auth: Option<AuthRef>,
|
||||
capability: Option<ModelCapability>,
|
||||
}
|
||||
```
|
||||
|
||||
3 つのユースケース:
|
||||
|
||||
```toml
|
||||
# Form A: ref のみ(カタログから全部引く)
|
||||
[model]
|
||||
ref = "anthropic/claude-sonnet-4-6"
|
||||
|
||||
# Form B: ref + override(カタログ起点に部分上書き)
|
||||
[model]
|
||||
ref = "anthropic/claude-sonnet-4-6"
|
||||
auth = { kind = "api_key", file = "./sk-ant.local" }
|
||||
|
||||
# Form C: 完全直書き(カタログ無視。実験用 / カタログに無いモデル)
|
||||
[model]
|
||||
scheme = "anthropic"
|
||||
model_id = "claude-sonnet-4-6"
|
||||
auth = { kind = "api_key", file = "./sk-ant.local" }
|
||||
```
|
||||
|
||||
Validation:
|
||||
- `ref` 無し → `scheme` / `model_id` / `auth` の 3 つが必須
|
||||
- `ref` あり → 他フィールドはすべて任意 override
|
||||
|
||||
### 5. 解決ロジック
|
||||
|
||||
`ref` を first `/` で split → `(provider_id, model_id_in_ref)`(OpenRouter の `openrouter/anthropic/claude-sonnet-4` も provider=`openrouter` / model_id=`anthropic/claude-sonnet-4` で通る)。
|
||||
|
||||
| ステップ | 失敗時 |
|
||||
|---|---|
|
||||
| provider 引き | **hard error**(マニフェストロード失敗) |
|
||||
| model 引き | **warn ログ + provider.default_capability にフォールバック**(API 側で model_id 拒否されたら自然に runtime エラー) |
|
||||
|
||||
最終 `ModelConfig` の各フィールド:
|
||||
|
||||
| フィールド | 解決順 |
|
||||
|---|---|
|
||||
| `scheme` | manifest 明示 > provider.scheme |
|
||||
| `base_url` | manifest 明示 > provider.base_url |
|
||||
| `model_id` | manifest 明示 > ref の `<model_id>` 部分 |
|
||||
| `auth` | manifest 明示 > provider.auth_hint 由来の `AuthRef` |
|
||||
| `capability` | manifest 明示 > model catalog の capability > provider.default_capability > **`Scheme::default_capability`**(ref 無しで capability 省略時の最終セーフティネット) |
|
||||
|
||||
### 6. 削除
|
||||
|
||||
- `crates/provider/src/capability.rs` 丸ごと削除(prefix matching ハードコード)
|
||||
- `crates/provider/assets/providers.toml` 削除(resources/ に移動するため)
|
||||
- provider カタログの `default_models[]` フィールド削除
|
||||
|
||||
### 7. 完了時の動作
|
||||
|
||||
- `[model] ref = "anthropic/claude-sonnet-4-6"` だけ書いた最小マニフェストで Pod が起動できる
|
||||
- ref 無しの完全直書きマニフェスト(既存 test_pod.*.local.toml 形式)も依然動く
|
||||
- 既存 `test_pod.anthropic.local.toml` / `test_pod.codex.local.toml` を ref 形式に書き換え、いずれの形式でも build_client が成功する unit / integration test
|
||||
- builtin の 4 provider と各 default_models 相当のモデルがモデルカタログに登録済み
|
||||
|
||||
## 設計判断
|
||||
|
||||
### prefix matching を消す根拠
|
||||
|
||||
`capability::lookup` は「`claude-` で始まれば全部同じ capability」「`gpt-5` 以降と `o1/o3/o4` は Reasoning family」と判定している。これは:
|
||||
- 新モデルが出るたびに prefix が当たるかコードで検証する必要がある(`gemini-3` を入れる行が `capability.rs:141` に手書きで足された等)
|
||||
- OpenRouter 経由の `anthropic/claude-sonnet-4` は prefix が `anthropic/` で当たらない(router 越しのモデルが構造的にカバー外)
|
||||
- `claude-sonnet-4-6` も `claude-haiku-4-5` も同じ capability にされる(モデル単位の差異が消える)
|
||||
|
||||
モデル単位で宣言する resource があれば、これらは全部「カタログにエントリを足すだけ」で解決する。
|
||||
|
||||
### `ref` 形式を採用しても直書きを残す理由
|
||||
|
||||
カタログに無いモデル(出たての新モデル、ローカル LLM の任意 ID、社内モデル等)を試すときに「カタログに足してから動かす」を強制すると実験コストが上がる。直書き形式は escape hatch として残す。capability も省略可にして scheme default に落とす(wire-level 安全側で動く、推奨ではない)。
|
||||
|
||||
### model_id の重複
|
||||
|
||||
provider 内ユニーク、provider 跨ぎは許容。`openrouter` の `openai/gpt-5` と `codex-oauth` の `gpt-5` が両方カタログにあって構わない。ref が `<provider>/<model_id>` で必ず provider を含むため曖昧性が出ない。
|
||||
|
||||
### model catalog 未登録モデルでも動く扱い
|
||||
|
||||
`ref = "anthropic/some-new-model"` のように **provider はカタログにあるが model は無い** ケースは、warn ログを出して provider.default_capability にフォールバック。「タイポで動いてしまった」を見逃さないよう warn は出すが、新モデルを試す際の摩擦を増やさないため hard error にはしない。本当に存在しないモデル ID なら API 側で `model not found` 系のエラーが返るので、誤魔化しにはならない。
|
||||
|
||||
### user override の挙動
|
||||
|
||||
`llm-provider-catalog` で決めた「user override があれば builtin を置換、マージしない、壊れた TOML はエラー」をモデルカタログにもそのまま適用。providers と models は独立して読む(片方だけ user override も可)。
|
||||
|
||||
### capability 重複の回避
|
||||
|
||||
model catalog の capability と provider.default_capability の両方を持つと「どっちが効くか」が分かりにくいが、解決順は明確に「model 単位 > provider default」と一方向で、両方書いた場合は model 単位が勝つ。「provider レベルで spectrum を決め、モデル単位で必要なときだけ上書き」という運用を想定。
|
||||
|
||||
## Scope 外
|
||||
|
||||
- UI 実装(モデル選択画面・provider 選択画面)
|
||||
- auto_discover(Ollama `/api/tags`、OpenRouter `/models` 動的列挙)
|
||||
- カタログファイル編集 UI
|
||||
- プロファイル(同一 provider で複数の API key を切り替える)
|
||||
- 既存 `Scheme::default_capability` の見直し(残置・現行値そのまま)
|
||||
|
||||
## 依存
|
||||
|
||||
- `llm-provider-catalog` 完了済(本チケットで provider カタログ形状を変更し、`default_models` 削除 + `default_capability` 追加)
|
||||
- `llm-model-config` 完了済(`ModelConfig` / `AuthRef` / `SchemeKind` / `ModelCapability`)
|
||||
- `llm-scheme-openai-responses` / `llm-auth-codex-oauth` 完了済
|
||||
|
||||
## 影響範囲
|
||||
|
||||
- `resources/providers/builtin.toml` 新設(旧 `crates/provider/assets/providers.toml` を移動 + 形状変更)
|
||||
- `resources/models/builtin.toml` 新設
|
||||
- `crates/provider/src/catalog.rs`: provider 形状変更(`default_models` → `default_capability`)+ モデルカタログ追加 + ref 解決関数追加
|
||||
- `crates/provider/src/capability.rs` 削除
|
||||
- `crates/provider/src/lib.rs`: `capability::lookup` 経路を削除し、ref 解決関数経由に置換
|
||||
- `crates/manifest/src/model.rs`: `ModelConfig` を `ModelManifest`(ref / inline 両受け)に置き換え。resolved 形の `ModelConfig` は `crates/provider` 側の内部型に
|
||||
- `crates/pod/src/factory.rs` 等の manifest 経由で `ModelConfig` を組む箇所: `ModelManifest → ModelConfig` 解決関数を呼ぶ形に
|
||||
- `test_pod.anthropic.local.toml` / `test_pod.codex.local.toml`: ref 形式に書き換え(インライン形式の動作確認も別途残す)
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
# LLM プロバイダ/モデルカタログ
|
||||
|
||||
## 背景
|
||||
|
||||
llm-worker の基盤(`scheme` / `ModelCapability` / `AuthRef`)と `crates/provider` の構築ファクトリは `llm-model-config` / `llm-scheme-openai-responses` / `llm-auth-codex-oauth` 完了で揃った。Pod マニフェストは `ModelConfig { scheme, base_url, model_id, auth, capability }` を 1 個宣言する形で機能する。
|
||||
|
||||
一方で UI 層(`native-gui-mvp` / `tui-pod-spawn-ui`)から「使えるプロバイダと代表的なモデル ID を一覧で見せる」ために参照できる候補リストが無い。現状 UI はマニフェストを手書き前提で、プロバイダ追加がコード変更でなく宣言で済むという `docs/plan/llm_providers.md` の狙い(「ルーター系は後追いで数を増やしやすい宣言型設計」)が UI 側に届いていない。
|
||||
|
||||
`docs/plan/llm_providers.md` 本文に書かれていた `available_models[]` 相当は、llm-worker / manifest の基盤に載せるより、このカタログレイヤで宣言するのが素直。基盤は「1 個のモデルを動かす」責務に留め、「選択肢を提供する」責務を分離する。
|
||||
|
||||
## 要件
|
||||
|
||||
1. **カタログファイル形式の定義**: TOML 宣言で以下を表現できる
|
||||
|
||||
```toml
|
||||
[[provider]]
|
||||
id = "anthropic"
|
||||
display_name = "Anthropic"
|
||||
scheme = "anthropic"
|
||||
base_url = "https://api.anthropic.com"
|
||||
auth_hint = { kind = "api_key", env = "INSOMNIA_API_KEY_ANTHROPIC" }
|
||||
default_models = ["claude-sonnet-4-5", "claude-opus-4-1"]
|
||||
|
||||
[[provider]]
|
||||
id = "ollama-local"
|
||||
display_name = "Ollama (local)"
|
||||
scheme = "anthropic"
|
||||
base_url = "http://localhost:11434"
|
||||
auth_hint = { kind = "none" }
|
||||
default_models = ["llama3.1", "qwen2.5-coder"]
|
||||
|
||||
[[provider]]
|
||||
id = "codex-oauth"
|
||||
display_name = "ChatGPT (Codex OAuth)"
|
||||
scheme = "openai_responses"
|
||||
auth_hint = { kind = "codex_oauth" }
|
||||
default_models = ["gpt-5-codex", "gpt-5"]
|
||||
|
||||
[[provider]]
|
||||
id = "openrouter"
|
||||
display_name = "OpenRouter"
|
||||
scheme = "openai_chat"
|
||||
base_url = "https://openrouter.ai/api/v1"
|
||||
auth_hint = { kind = "api_key", env = "INSOMNIA_API_KEY_OPENROUTER" }
|
||||
default_models = ["anthropic/claude-sonnet-4", "openai/gpt-5"]
|
||||
```
|
||||
|
||||
- `id` はカタログ内ユニーク。UI での選択・保存用
|
||||
- `default_models[]` は代表的な ID。UI はこれを候補として出すが、ユーザーが自由入力もできる想定
|
||||
- `auth_hint` は UI 向けメタ(env 名 / 認証不要 / OAuth 利用可 の表示に使う)。実際の認証解決は従来通り `crates/provider` が `AuthRef` から行う
|
||||
- `capability` はここでは宣言しない(scheme の静的テーブルと `ModelConfig.capability` の既存 2 段階で足りる)
|
||||
|
||||
2. **読取 API**: カタログを読み `Vec<ProviderEntry>` として返す関数を 1 本公開する。配置先は `crates/provider`(認証解決と同じ層、plan の「llm-worker は低レベル基盤に留める」原則と整合)。
|
||||
|
||||
3. **配置パス**:
|
||||
- builtin: `crates/provider/assets/providers.toml` を `include_str!` で同梱(代表的な 4 経路 = Anthropic / Ollama / Codex OAuth / OpenRouter を最低限入れる)
|
||||
- user override: `$XDG_CONFIG_HOME/insomnia/providers.toml` があれば **マージではなく置換** で優先採用(マージ規則の複雑さは避ける)
|
||||
- 両方読めない場合は builtin のみ
|
||||
|
||||
4. **ProviderEntry → ModelConfig の変換**: UI が「このプロバイダのこのモデルを使う」を選んだときに、`ProviderEntry` + `model_id` から `ModelConfig` を組める変換関数を提供する。`auth_hint.kind` が `AuthRef` の各バリアントに対応する。
|
||||
|
||||
5. **完了時の動作**:
|
||||
- `crates/provider` の公開 API から builtin プロバイダ一覧が取得できる
|
||||
- user override ファイルが置かれていれば置換採用される
|
||||
- UI 未実装段階でも、unit test で「カタログ読取 → `ProviderEntry` 選択 → `ModelConfig` 生成 → `build_client` が成功する」経路が通る
|
||||
|
||||
## 設計判断
|
||||
|
||||
### builtin と user override の関係
|
||||
|
||||
マージ(builtin に追記する)方式は競合ルール(`id` 衝突時の優先・部分上書き)が必要になり、UI が「どの設定が効いているか」を説明しづらくなる。user override があれば完全置換とし、ユーザーは builtin をコピーして編集するか、自分で最小構成を書くかを選ぶ。
|
||||
|
||||
### `auth_hint` と `AuthRef` の二重定義
|
||||
|
||||
一見冗長だが、`auth_hint` は「UI が何を表示・要求するか」のヒント、`AuthRef` は「ランタイムがどこからトークンを引くか」の宣言で責務が違う。`auth_hint.kind = "api_key"` から `AuthRef::ApiKey { env: auth_hint.env, file: None }` への変換関数 1 本で繋ぐ。
|
||||
|
||||
### capability 宣言をカタログに入れない
|
||||
|
||||
scheme 側の静的テーブルに未登録のモデル ID(OpenRouter 経由の任意モデル等)を使うときは `ModelConfig.capability` で明示指定するという経路が既に存在する。カタログ側で更に capability を持つと 3 箇所で capability が定義できることになり、優先順位が混乱する。カタログは「選択肢の提示」に専念する。
|
||||
|
||||
### auto_discover は別チケット
|
||||
|
||||
Ollama `/api/tags` を叩いてモデル一覧を動的に取る機構は欲しいが、本チケットの静的カタログが動いてから別チケットで足す。`ProviderEntry` に後から `discover: Option<DiscoverMode>` を任意で足せる構造で設計しておく(フィールド拡張予定のコメントだけ残す)。
|
||||
|
||||
## Scope 外
|
||||
|
||||
- UI 実装(GUI / TUI 側のプロバイダ・モデル選択画面は各 UI チケットで)
|
||||
- auto_discover(Ollama `/api/tags`、OpenRouter `/models` 等の動的列挙)
|
||||
- カタログファイル編集 UI
|
||||
- プロファイル(同一プロバイダで複数の API key を切り替える等)
|
||||
|
||||
## 依存
|
||||
|
||||
- `llm-model-config` 完了済(`ModelConfig` / `AuthRef` / `SchemeKind` / `ModelCapability`)
|
||||
- `llm-scheme-openai-responses` 完了済(Responses scheme)
|
||||
- `llm-auth-codex-oauth` 完了済(`AuthRef::CodexOAuth` 解決)
|
||||
|
||||
## 影響範囲
|
||||
|
||||
- `crates/provider/src/`: カタログ読取 + `ProviderEntry` 型 + 変換関数を追加
|
||||
- `crates/provider/assets/providers.toml`: 新設(builtin カタログ)
|
||||
- 他クレートに型は露出するが、既存 API に破壊的変更は入れない
|
||||
|
||||
## Review
|
||||
- 状態: Approve
|
||||
- レビュー詳細: [./llm-provider-catalog.review.md](./llm-provider-catalog.review.md)
|
||||
- 日付: 2026-04-23
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
# Review: LLM プロバイダ/モデルカタログ
|
||||
|
||||
## 前提・要件の確認
|
||||
|
||||
### 要件 1: TOML 形式の定義(`[[provider]]` 配列、`auth_hint = { kind, env? }`)
|
||||
満たされている。
|
||||
- `crates/provider/assets/providers.toml` の 4 エントリがチケット例(`tickets/llm-provider-catalog.md:15-46`)と一字一句合致している(id / display_name / scheme / base_url / auth_hint.kind / env 名 / default_models の順序と値)。
|
||||
- `AuthHint` 側は `#[serde(tag = "kind", rename_all = "snake_case")]` でインラインテーブル `{ kind = "api_key", env = "..." }` を受け付ける(`crates/provider/src/catalog.rs:44-56`)。`kind = "codex_oauth"` はデフォルトの `snake_case` 変換が `codex_o_auth` になってしまう問題を `#[serde(rename = "codex_oauth")]` で明示的に回避しており、既存 `AuthRef::CodexOAuth`(`crates/manifest/src/model.rs:70`)と同じ書き方になっている。整合している。
|
||||
|
||||
### 要件 2: `Vec<ProviderEntry>` を返す読取 API を `crates/provider` に 1 本公開
|
||||
満たされている。
|
||||
- `pub fn load() -> Result<Vec<ProviderEntry>, CatalogError>` が主 API(`catalog.rs:108`)。補助として `load_builtin` / `load_from_path` も公開されており、後者はテストと将来の明示パス指定に合理的。
|
||||
- 配置は `crates/provider` 直下(`lib.rs:14` で `pub mod catalog;`)。`llm-worker` に下りていない。Memory の「llm-worker は低レベル基盤に留める」方針に整合。
|
||||
|
||||
### 要件 3: 配置パス(builtin の `include_str!` / user override 置換 / 両方読めなければ builtin)
|
||||
満たされている。
|
||||
- `BUILTIN_CATALOG: &str = include_str!("../assets/providers.toml")`(`catalog.rs:18`)。
|
||||
- user override は `$XDG_CONFIG_HOME/insomnia/providers.toml`、未設定時は `$HOME/.config/insomnia/providers.toml`(`catalog.rs:137-154`)。チケットでは `$HOME` フォールバックは明示されていないが、XDG Base Directory spec の既定挙動として妥当で、過剰とも言えない。
|
||||
- マージではなく置換(`catalog.rs:108-115`)。`load_prefers_override_over_builtin` / `load_falls_back_to_builtin_when_override_absent` テストで両経路をカバー。
|
||||
|
||||
### 要件 4: `ProviderEntry` + `model_id` → `ModelConfig` の変換
|
||||
満たされている。
|
||||
- `ProviderEntry::to_model_config(&self, model_id: impl Into<String>) -> ModelConfig`(`catalog.rs:82-99`)が `AuthHint` の 3 バリアントを `AuthRef` に 1:1 で写す。`capability` は常に `None` で返し、`build_client` 側の 3 段階 fallback(`ModelConfig.capability` → `capability::lookup` → `scheme default`、`lib.rs:120-124`)に委ねるのはチケット設計判断「`capability` 宣言をカタログに入れない」(`llm-provider-catalog.md:77-79`)と整合。
|
||||
- `to_model_config_maps_auth_hint` テスト(`catalog.rs:208-229`)で 3 バリアント全部を確認。
|
||||
|
||||
### 要件 5: 完了時の動作(builtin 取得 / override 置換 / UI 未実装段階で unit test e2e)
|
||||
満たされている。
|
||||
- `ollama_entry_builds_client`(`catalog.rs:231-239`)が「カタログ読取 → `ProviderEntry` 選択 → `ModelConfig` 生成 → `build_client` が成功」の経路を端から端まで通している。Ollama は `AuthRef::None` なので環境変数依存がなく、常に成功する選択として適切。
|
||||
- `cargo test -p provider` が 43 件全部 pass、ワークスペース `cargo build` もクリーン。
|
||||
|
||||
## アーキテクチャ・スコープ
|
||||
|
||||
- **レイヤ分離**: `crates/provider` 内に閉じ、`llm-worker` には漏れていない。`build_client` / `resolve_auth` の既存シグネチャは一切変更していない(`lib.rs:53-146` は今回の差分外)。非破壊性 OK。
|
||||
- **責務分離**: `AuthHint`(UI メタ)と `AuthRef`(ランタイム解決)を別型として定義し、`to_model_config` で 1:1 変換する設計は、チケット「設計判断: `auth_hint` と `AuthRef` の二重定義」(`llm-provider-catalog.md:73-75`)の意図を正確に反映している。型を流用する代替案もあり得たが、UI ヒントに `file: Option<PathBuf>` が紛れ込むとカタログのスコープが広がるため、別型にしたのは妥当。
|
||||
- **過剰抽象の有無**: `discover: Option<DiscoverMode>` のような先取り実装はされていない(コメントで言及のみ、`catalog.rs:60-61`)。チケットの「auto_discover は別チケット」方針に従っている。
|
||||
- **依存追加**: `toml` を dev-dependencies から regular dependencies に「移動」する形で `cargo add` 相当の編集がされている。手動編集ではあるが、既存行を move しただけなので Memory の `Use cargo add` 方針からの逸脱は軽微。
|
||||
- **モジュール分割**: `pod.rs` 等の既存 primary ファイルに詰め込まず、`catalog.rs` として独立モジュール化している。Memory の「feature モジュール分割」方針に合致。
|
||||
- **Scope 外の侵食**: UI / auto_discover / 編集 UI / プロファイルのどれにも触れていない。境界遵守。
|
||||
|
||||
## 指摘事項
|
||||
|
||||
### Non-blocking / Follow-up
|
||||
- `$HOME` フォールバック(`catalog.rs:143-152`)がチケット本文に明示されていない。妥当な挙動ではあるが、チケットの「配置パス」記述と実装の解像度に乖離があり、ユーザーが後から読んだとき驚きがある。ticket 側に一行追記するか、コード側のドキュメント(モジュール冒頭 doc)に「XDG 既定挙動として `$HOME/.config/...` にフォールバック」を明記しておくと親切。
|
||||
|
||||
### Nits
|
||||
- `catalog.rs:106` の doc コメントに "sigh" というタイポがある("silent" の意図と思われる)。該当箇所:
|
||||
|
||||
```
|
||||
/// 存在すれば builtin を置き換える。存在しなければ builtin のみ。
|
||||
/// user override が存在するが壊れている場合はエラーを返す(silent
|
||||
/// fallback はしない — ユーザーが書いた設定が sigh なく無視されて
|
||||
/// builtin に戻る挙動は気付きにくいため)。
|
||||
```
|
||||
|
||||
`sigh` → `silent` に直すか、日本語化で「気付かれず」等に差し替え。
|
||||
- `builtin_ollama_shape`(`catalog.rs:173-183`)の assert が `scheme == Anthropic` を確認しているが、コメントの「Ollama = Anthropic scheme」の意図は `lib.rs:278-290` の `ollama_succeeds_without_key` と重複する情報。コメントで相互参照しておくと将来の読み手に親切。
|
||||
|
||||
## 判断
|
||||
|
||||
**Approve(完了可)** — チケット要件 1〜5 は全て明確に満たされており、`AuthHint`/`AuthRef` の責務分離・レイヤ境界・既存 API 非破壊といったアーキテクチャ観点も適切。指摘は `sigh` タイポと `$HOME` フォールバックのドキュメント明示の 2 点のみで、どちらも blocking ではない。
|
||||
Loading…
Reference in New Issue
Block a user