Stanford STORM を、カスタムの OpenAI 互換エンドポイントで動かす。

Updated 2026-07-29

STORM は、すべての言語モデルを LitellmModel として構築し、litellm は api_base を受け付けます。共有する openai_kwargs に https://api.apisrouter.com/v1 を入れ、モデル id の先頭に openai/ を付ければ、記事パイプラインの5つの LM スロットすべてが、1つのエンドポイントと1つのキーを通じて動くようになります。

早わかり: openai_kwargs に api_base、id には openai/ プレフィックス。

STORM の LitellmModel は、構築時に渡した kwargs をそのまま保持し、すべての litellm.completion() の呼び出しにマージします。litellm の api_base パラメータは、openai プロバイダーを別のホストに向ける方法であり、STORM 自身のサンプルがすでに使っている openai_kwargs の辞書に api_base を追加することが、上書きのすべてです。各モデル id の先頭に openai/ を付けて、litellm がそのベースに対してチャット補完プロトコルを話すようにしてください。スラッシュの後ろの文字列は、そのままゲートウェイに渡されます。 サンプルは1つの openai_kwargs 辞書を組み立て、それをすべてのモデルで再利用するため、追加するキー1つがパイプライン全体を再ルーティングします。STORM のコード変更もフォークも不要です。これは、litellm のドキュメント記載済みのルーティングの上に重なる、標準の knowledge_storm の挙動です。

openai_kwargs = {
    "api_key": os.getenv("APISROUTER_API_KEY"),
    "api_base": "https://api.apisrouter.com/v1",
    "temperature": 1.0,
    "top_p": 0.9,
}
fast = LitellmModel(model="openai/deepseek-v4-flash", max_tokens=500, **openai_kwargs)
strong = LitellmModel(model="openai/claude-sonnet-4-6", max_tokens=3000, **openai_kwargs)

STORM が、1つの記事を5つの LM スロットにどう分割するか。

STORM(GitHub 上では stanford-oval、スター数はおよそ30K)は、Wikipedia スタイルのレポートをゼロから書き上げます。複数の視点をシミュレートした会話を通じてトピックをリサーチし、学んだことからアウトラインを構築し、記事全体をセクションごとに生成し、そして磨き上げます。STORMWikiLMConfigs は、そのパイプラインを、独立して設定可能な5つのモデルとして公開します。conv_simulator_lm と question_asker_lm がリサーチの会話を駆動し、outline_gen_lm が記事を構造化し、article_gen_lm がそれを書き、article_polish_lm が最終的な仕上げを行います。 upstream の README は、経済性について明確です。会話シミュレーターが最も多くの呼び出し量を処理するため、そこには速いモデルを、記事生成にはより強力なモデルを推奨しています。そのガイダンスは OpenAI のモデル間で選ぶことを前提としていましたが、マルチベンダーのエンドポイントの裏側では、それはより有用な形に一般化されます。各スロットはそれぞれ独自のモデル文字列を持つ独自の LitellmModel であるため、リサーチの会話は速い DeepSeek の id で走らせつつ、アウトラインと記事生成は Claude で走らせ、仕上げはトーンで信頼するどのモデルでも走らせる、ということが、すべて同じ api_base に対する同じキーで認証されて可能になります。 検索側は別の機構です。STORM のランナーは、独自の API キーを持つ RM モジュール(You.com、Bing、その他いくつかの検索バックエンド)を取ります。言語モデルの向き先を変えても、ソースの取得方法には一切触れません。

フルセットアップ: 5つのスロット、1つの kwargs 辞書。

動作するパターンは、リポジトリ自身の実行スクリプトを反映しています。共有 kwargs を一度組み立て、役割ごとに1つの LitellmModel を構築し、STORMWikiLMConfigs のセッターを通じてそれらを割り当てます。api_key は明示的に渡すため、好きな名前で構いません。サンプルは、これが OpenAI アカウントの認証情報ではないことを明確にするため、独自の変数を使っています。 litellm はプロバイダーレベルの環境変数も尊重し、openai プロバイダーは OPENAI_API_BASE を読むため、環境変数だけでの上書きも可能です。それでも明示的な kwargs の経路の方が好ましいままです。ある記事を生成したコードの中にそれが可視化されており、異なる環境状態のマシンで実行されても生き残り、いつか1つの段階だけ別のエンドポイントにしたくなった場合にスロットごとの例外も可能にするからです。

import os
from knowledge_storm import STORMWikiRunnerArguments, STORMWikiRunner, STORMWikiLMConfigs
from knowledge_storm.lm import LitellmModel
from knowledge_storm.rm import YouRM

openai_kwargs = {
    "api_key": os.getenv("APISROUTER_API_KEY"),
    "api_base": "https://api.apisrouter.com/v1",
    "temperature": 1.0,
    "top_p": 0.9,
}
fast = LitellmModel(model="openai/deepseek-v4-flash", max_tokens=500, **openai_kwargs)
strong = LitellmModel(model="openai/claude-sonnet-4-6", max_tokens=3000, **openai_kwargs)

lm_configs = STORMWikiLMConfigs()
lm_configs.set_conv_simulator_lm(fast)
lm_configs.set_question_asker_lm(fast)
lm_configs.set_outline_gen_lm(strong)
lm_configs.set_article_gen_lm(strong)
lm_configs.set_article_polish_lm(strong)

engine_args = STORMWikiRunnerArguments(output_dir="./results")
rm = YouRM(ydc_api_key=os.getenv("YDC_API_KEY"), k=engine_args.search_top_k)
runner = STORMWikiRunner(engine_args, lm_configs, rm)
runner.run(topic="Small modular reactors")

パイプラインの段階ごとのモデル選び。

5つのセッターを、定型作業ではなく予算のダイヤルとして扱ってください。upstream のガイダンスはすでに、段階ごとに速いモデルと強力なモデルを分けることを推奨しています。マルチベンダーのエンドポイントは、段階ごとのメニューを広げるだけです。同じトピックでの実行間で一度に1つのスロットを変更し、出力を diff してください。キーごとの利用ログが各構成に価格をつけてくれます。

  • conv_simulator_lm と question_asker_lm は、ボリュームの段階です。トピックごとに複数の視点にまたがる、複数ターンのシミュレートされたインタビューです。deepseek-v4-flash や他の速い id が、リサーチのフェーズが支出を支配してしまうのを防ぎます。不完全な会話も許容範囲です。それが供給するのは文章ではなくメモだからです。
  • article_gen_lm は旗艦のスロットです。蓄積されたリサーチから、長く構造化された引用付きのセクションを書きます。これは持続的な生成作業であり、claude-sonnet-4-6 や gpt-5.5 が、より小さな id を明らかに上回ります。
  • outline_gen_lm は、呼び出し回数は少ないものの、影響が大きい、計画スロットと同じ形です。弱いアウトラインは、書き手がどれだけ優れていても記事の上限を決めてしまいます。claude-opus-4-7 を試すのに自然な場所です。
  • article_polish_lm は、文章の流れのために書き直し、組み立てられた記事全体の重複を取り除きます。これは長文脈対応の id の恩恵を受けます。ここでは gemini-3.1-pro-preview をベンチマークする価値があります。

従量課金 · 公式価格より安い

Selected models are priced below official list prices. Exact input, output, cache, and per-request prices are shown for each model.

モデル公式価格当社価格
DeepSeek V4 Flash$0.14 / $0.28 per M$0.10 / $0.30 per M
Claude Sonnet 4.6$3.00 / $15.00 per M$2.40 / $12.00 per M
Claude Opus 4.7$5.00 / $25.00 per M$4.00 / $20.00 per M
GPT-5.5$5.00 / $30.00 per M$4.00 / $24.00 per M
Gemini 3.1 Pro Preview$2.00 / $12.00 per M$1.60 / $9.60 per M

STORM に特有の失敗パターン。

生のモデル id は、あなたの api_base によってではなく、推測によってルーティングされます。litellm はプレフィックスを読んでプロバイダーを選び、プレフィックスなしの Claude の id は Anthropic ネイティブの呼び出しとして推測され、それは ANTHROPIC_API_KEY を求め、あなたのゲートウェイを完全に無視します。ゲートウェイ向けのすべての id は、openai/ プレフィックスを持たなければなりません。このプレフィックスが指すのはベンダーではなくプロトコルです。 1つのスロットが置き去りにされる。各 LitellmModel は、構築時に自身の kwargs を捕捉します。4つのスロットが openai_kwargs を共有し、5つ目が api_base なしでその場しのぎに構築された場合、そのスロットは静かにベンダーのデフォルトに投稿し、認証で失敗します。そしてトレースバックが指すのは設定行ではなくパイプラインの段階です。すべてのスロットを同じ辞書から構築すれば、この種のバグは消えます。 エンドポイントのせいにされるリトリーバーの失敗。リサーチのフェーズには、機能する検索バックエンドが必要です。無効な、あるいは使い果たされたリトリーバーのキー(YDC_API_KEY、BING_SEARCH_API_KEY、あるいは選んだどの RM でも)は、情報収集の間に実行を失敗させます。このフェーズは LM の呼び出しと絡み合っているため、LM の設定に触れる前に、どのクライアントが例外を発生させたか、トレースバックを読んでください。 デモの secrets.toml は、あなたのスクリプトの設定ではありません。Streamlit のデモは secrets.toml を読みますが、プログラムによる実行は、あなたのスクリプトが渡すものを読みます。一方を編集しながらもう一方を実行するのは、典型的な不一致です。 max_tokens もスロットごとです。STORM のサンプルは、速いスロットには小さな上限(500)を、生成にはより大きな上限(3000)を設定しています。max_tokens を引き上げずに、あるスロットを長文生成向けのモデルに向けると、セクションが静かに切り詰められます。これはモデルの品質の問題に見えますが、実際は設定の数字の問題です。

ゲートウェイ経由で STORM を使うのは誰か。

  • ボリュームで知識レポートを生成するチーム(ブリーフ、Wiki スタイルの社内ドキュメント、トピック入門書)で、5スロットの分割が、段階ごとのコストチューニングを実際のお金の節約に値するものにする人。
  • パイプラインの構成を研究する研究者で、どの段階がより強力なモデルの恩恵を受けるかは実証的な問いであり、1つのエンドポイントが、スロットとモデルの組み合わせのグリッドを列挙するのを些細なことにしてくれる人。
  • OpenAI の形をしたスタックの執筆スロットで Claude や Gemini を走らせるビルダーで、モデルファミリーごとにベンダーの SDK を追加する必要のない人。
  • バッチのトピックリストを実行する人で、リサーチフェーズのボリュームがトピック横断で増幅し、利用ログがトピックごとのコスト台帳になる人。
  • 特定ベンダーの請求手段にアクセスできない開発者。チャージ制でカード不要のアクセスなら、プロバイダーごとのサインアップという依存を取り除けます。

エンドポイントを検証し、最初の記事をデバッグする。

まずゲートウェイのモデルを一覧してください。各スロットの openai/ の後ろの文字列は、提供されている id と正確に一致していなければなりません。 初回実行の失敗は、パイプラインの順序に従います。Anthropic や Google の名前を挙げる認証エラーは、プレフィックスなしの id がネイティブなプロバイダーにルーティングされたということです。openai/ を追加してください。ゲートウェイからの 401 は、kwargs の api_key がゲートウェイのキーではないということです。model-not-found エラーは、タイプミスのある id を持つスロットを指し示します。あなたの検索バックエンドに言及するリサーチフェーズ中の失敗は、LM のルーティングではなくリトリーバーの認証情報の問題です。そして、切り詰められた、あるいは妙に短い記事のセクションは、たいてい upstream の何かではなく、生成スロットのけちな max_tokens が原因です。 完全な STORM の実行は、大きなバーストです。複数の視点にまたがるシミュレートされた会話、続いてアウトライン、生成、仕上げです。1回完了すると、APIsRouter コンソールがリクエストごとのモデル、トークン数、支出を表示し、それは5つのスロットにきれいに対応し、次のトピックのバッチの前に、どの段階を調整し直すべきか正確に教えてくれます。

curl -s https://api.apisrouter.com/v1/models \
  -H "Authorization: Bearer $APISROUTER_API_KEY" | head -50

よくある質問

STORM はカスタムの OpenAI 互換エンドポイントにどう対応していますか?

litellm を通じてです。STORM はすべての LM を LitellmModel として構築し、それはコンストラクタの kwargs を各 litellm.completion() の呼び出しにマージします。litellm は openai プロバイダー向けに api_base を受け付けます。openai_kwargs 辞書に api_base を追加すれば、そこから構築されたすべてのスロットがゲートウェイにルーティングされます。

モデル id に openai/ プレフィックスが必要なのはなぜですか?

litellm はプレフィックスからプロバイダーを選びます。openai/claude-sonnet-4-6 は、「自分の api_base に対して、モデル claude-sonnet-4-6 で OpenAI のチャット補完プロトコルを話せ」を意味します。プレフィックスがないと、litellm は名前からベンダーを推測してネイティブにルーティングし、あなたのエンドポイントを迂回します。

異なる STORM の段階で、異なるベンダーのモデルを使えますか?

はい。5つのスロットはそれぞれ独立した LitellmModel であるため、会話シミュレーターが DeepSeek の id を走らせつつ、記事生成は Claude、仕上げは GPT を走らせる、ということが、すべて同じ api_base とキーを通じて可能です。upstream はすでに、段階ごとに速いモデルと強力なモデルを分けることを推奨しています。

api_base を変更すると、検索リトリーバーも変わりますか?

いいえ。検索は、独自のキーを持つ、STORMWikiRunner に渡す RM モジュール(You.com、Bing、その他対応するバックエンド)を通じて実行されます。LM のルーティングとソースの検索は独立したシステムであり、実行の異なるフェーズで失敗します。

kwargs の代わりに環境変数の経路はありますか?

litellm はプロバイダーレベルの変数を尊重し、openai プロバイダーは OPENAI_API_BASE を読みます。それは機能しますが、明示的な api_base の kwarg の方が再現性が高いです。スクリプトと一緒に移動し、異なる環境状態のマシンでも生き残り、スロットごとの例外を許容します。

STORM の記事1つは、どれくらいのトークンを消費しますか?

リサーチフェーズが支配的です。複数視点のシミュレートされた会話が、記事の1語も存在しない段階で呼び出しを増幅させ、そのあとに生成と仕上げが長文の出力を上乗せします。完全な実行は、一般的に数十万トークンに達し、キーごとの利用状況ビューが、段階ごとの正確な内訳を示します。