yoi/tickets/llm-provider-catalog.review.md
2026-04-23 15:37:51 +09:00

6.9 KiB
Raw Blame History

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::CodexOAuthcrates/manifest/src/model.rs:70)と同じ書き方になっている。整合している。

要件 2: Vec<ProviderEntry> を返す読取 API を crates/provider に 1 本公開

満たされている。

  • pub fn load() -> Result<Vec<ProviderEntry>, CatalogError> が主 APIcatalog.rs:108)。補助として load_builtin / load_from_path も公開されており、後者はテストと将来の明示パス指定に合理的。
  • 配置は crates/provider 直下(lib.rs:14pub 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.tomlcatalog.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_idModelConfig の変換

満たされている。

  • ProviderEntry::to_model_config(&self, model_id: impl Into<String>) -> ModelConfigcatalog.rs:82-99)が AuthHint の 3 バリアントを AuthRef に 1:1 で写す。capability は常に None で返し、build_client 側の 3 段階 fallbackModelConfig.capabilitycapability::lookupscheme defaultlib.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_clientcatalog.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。
  • 責務分離: AuthHintUI メタ)と AuthRef(ランタイム解決)を別型として定義し、to_model_config で 1:1 変換する設計は、チケット「設計判断: auth_hintAuthRef の二重定義」(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 に戻る挙動は気付きにくいため)。
    

    sighsilent に直すか、日本語化で「気付かれず」等に差し替え。

  • builtin_ollama_shapecatalog.rs:173-183)の assert が scheme == Anthropic を確認しているが、コメントの「Ollama = Anthropic scheme」の意図は lib.rs:278-290ollama_succeeds_without_key と重複する情報。コメントで相互参照しておくと将来の読み手に親切。

判断

Approve完了可 — チケット要件 1〜5 は全て明確に満たされており、AuthHint/AuthRef の責務分離・レイヤ境界・既存 API 非破壊といったアーキテクチャ観点も適切。指摘は sigh タイポと $HOME フォールバックのドキュメント明示の 2 点のみで、どちらも blocking ではない。