Запустите Stanford STORM на кастомном OpenAI-совместимом эндпоинте.

Updated 2026-07-29

STORM строит каждую языковую модель как LitellmModel, а litellm принимает api_base. Поместите https://api.apisrouter.com/v1 в свой общий openai_kwargs, добавьте моделям id префикс openai/ — и все пять слотов LM пайплайна статьи маршрутизируются через один эндпоинт и один ключ.

Короткий ответ: api_base в openai_kwargs, префикс openai/ на id.

LitellmModel в STORM хранит любые kwargs, с которыми вы его конструируете, и вливает их в каждый вызов litellm.completion(). Параметр api_base в litellm — это то, как вы направляете провайдера openai на другой хост, так что добавление api_base в словарь openai_kwargs, который уже используют собственные примеры STORM, — это переопределение целиком. Добавьте каждому id модели префикс openai/, чтобы litellm говорил на протоколе chat-completions с этим base, и строка после слэша передаётся шлюзу без изменений. Поскольку примеры строят один словарь openai_kwargs и переиспользуют его для каждой модели, один добавленный ключ перенаправляет весь пайплайн. Никаких изменений кода STORM, никакого форка; это стандартное поведение knowledge_storm, наложенное на задокументированную маршрутизацию litellm.

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 разбивает статью на пять слотов LM.

STORM (stanford-oval на GitHub, около 30K звёзд) пишет отчёты в стиле Википедии с нуля: она исследует тему через симулированные многосторонние разговоры, строит план на основе изученного, генерирует полную статью раздел за разделом и затем полирует её. STORMWikiLMConfigs выставляет этот пайплайн как пять независимо настраиваемых моделей: conv_simulator_lm и question_asker_lm ведут исследовательские разговоры, outline_gen_lm структурирует статью, article_gen_lm её пишет, а article_polish_lm делает финальный проход. Апстримный README прямо говорит об экономике: симулятор разговора несёт наибольший объём вызовов, поэтому там рекомендуется более быстрая модель, а для генерации статьи — более мощная. Эта рекомендация предполагала выбор между моделями OpenAI; за мультивендорным эндпоинтом она обобщается во что-то более полезное. Каждый слот — это своя собственная LitellmModel со своей собственной строкой модели, так что исследовательская болтовня может работать на быстром id DeepSeek, пока генерация плана и статьи работает на Claude, а полировка — на той модели, которой вы доверяете тон, все аутентифицированы одним и тем же ключом против одного и того же api_base. Сторона retrieval — отдельная машинерия: раннер STORM принимает модуль RM (You.com, Bing и несколько других поисковых бэкендов) с собственным API-ключом. Смена того, куда указывают языковые модели, не касается того, как получаются источники.

Полная настройка: пять слотов, один словарь kwargs.

Рабочий паттерн зеркалит собственные run-скрипты репозитория: постройте общие kwargs один раз, сконструируйте одну LitellmModel на роль и назначьте их через сеттеры STORMWikiLMConfigs. api_key может быть любым именем, каким вы хотите, поскольку вы передаёте его явно; в примере используется своя переменная, чтобы прояснить, что это не credential аккаунта OpenAI. litellm также уважает переменные окружения на уровне провайдера, и провайдер openai читает OPENAI_API_BASE, так что переопределение только через окружение возможно. Явный путь через kwargs всё же предпочтителен: он виден в коде, который произвёл данную статью, он переживает запуск на машине с другим состоянием окружения и делает возможными исключения по каждому слоту, если вы когда-нибудь захотите поставить один этап на другой эндпоинт.

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")

Выбор моделей по этапам пайплайна.

Относитесь к пяти сеттерам как к регулятору бюджета, а не к шаблонному коду. Апстримная рекомендация уже говорит разделять быстрые и сильные модели по этапам; мультивендорный эндпоинт просто расширяет меню по каждому этапу. Меняйте по одному слоту за раз между прогонами на одной и той же теме и сравнивайте результаты, с логом использования по ключу, оценивающим каждую конфигурацию.

  • 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 читает префикс, чтобы выбрать провайдера, и id Claude без префикса выводится как нативный вызов Anthropic, который затем хочет ANTHROPIC_API_KEY и целиком игнорирует ваш шлюз. Каждый id, направляющийся к шлюзу, должен нести префикс openai/; префикс называет протокол, а не вендора. Один забытый слот. Каждая LitellmModel захватывает свои kwargs при конструировании. Если четыре слота делят openai_kwargs, а пятый был построен на скорую руку без api_base, этот слот тихо стучится на умолчание вендора и падает на аутентификации, а трассировка называет этап пайплайна, а не строку конфига. Стройте каждый слот из одного и того же словаря, и этот класс багов исчезает. Сбои retriever, приписанные эндпоинту. Исследовательской фазе нужен работающий поисковый бэкенд; невалидный или исчерпанный ключ retriever (YDC_API_KEY, BING_SEARCH_API_KEY либо какой RM вы выбрали) роняет прогоны во время сбора информации. Эта фаза перемежается с вызовами LM, так что читайте трассировку на предмет того, какой клиент выдал ошибку, прежде чем трогать конфиг LM. secrets.toml демо — это не конфиг вашего скрипта. Демо на Streamlit читает secrets.toml; программные прогоны читают то, что передаёт ваш скрипт. Правка одного во время запуска другого — классическое несовпадение. max_tokens тоже настраивается по слоту. Примеры STORM ставят небольшие лимиты на быстрых слотах (500) и больше на генерации (3000). Направление слота на модель для длинной формы без повышения его max_tokens тихо обрезает разделы, что выглядит как проблема качества модели, но на деле — число в конфиге.

Кто направляет STORM через шлюз.

  • Команды, генерирующие отчёты знаний в объёме (брифы, внутренние документы в стиле вики, вводные по темам), где разделение на пять слотов делает настройку стоимости по этапам стоящей реальных денег.
  • Исследователи, изучающие композицию пайплайна: какой этап выигрывает от более сильной модели — эмпирический вопрос, и один эндпоинт делает перебор сетки комбинаций слот-модель тривиальным.
  • Разработчики, запускающие Claude или Gemini в слотах письма стека в форме OpenAI, без добавления SDK вендора на каждое семейство моделей.
  • Все, кто прогоняет пакетные списки тем, где объём исследовательской фазы умножается по темам, и лог использования становится бухгалтерией стоимости по каждой теме.
  • Разработчики без доступа к биллингу конкретного вендора. Доступ на основе пополнения без требования карты убирает зависимость от регистрации у каждого провайдера.

Проверьте эндпоинт и отладьте первую статью.

Сначала полистайте модели шлюза: строка после openai/ в каждом слоте должна точно совпадать с обслуживаемым id. Сбои первого запуска следуют порядку пайплайна. Ошибка аутентификации, называющая Anthropic или Google, означает, что id без префикса маршрутизировался к нативному провайдеру; добавьте openai/. 401 от шлюза означает, что api_key в ваших kwargs — не ключ шлюза. Ошибка model-not-found называет слот, чей id содержит опечатку. Сбои во время исследовательской фазы, упоминающие ваш поисковый бэкенд, — это credentials retriever, а не маршрутизация LM. А обрезанные или странно короткие разделы статьи — это обычно скупой max_tokens на слоте генерации, а не что-то выше по цепочке. Полный прогон STORM — это большой всплеск: симулированные разговоры с разных точек зрения, затем план, генерация и полировка. Как только один завершается, консоль APIsRouter показывает модель на запрос, счётчики токенов и расходы, что чисто отображается на пять слотов и точно говорит вам, какой этап перенастроить перед следующей партией тем.

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 принимает api_base для провайдера openai. Добавьте api_base в словарь openai_kwargs, и каждый слот, построенный из него, маршрутизируется на шлюз.

Почему id моделей нуждаются в префиксе openai/?

litellm выбирает провайдера по префиксу. openai/claude-sonnet-4-6 означает «говори на протоколе chat-completions OpenAI с моим api_base с моделью claude-sonnet-4-6». Без префикса litellm выводит вендора из имени и маршрутизирует нативно, обходя ваш эндпоинт.

Могут ли разные этапы STORM использовать модели разных вендоров?

Да. Каждый из пяти слотов — независимая LitellmModel, так что симулятор разговора может работать на id DeepSeek, пока генерация статьи работает на Claude, а полировка — на GPT, всё через один и тот же api_base и ключ. Апстрим уже рекомендует разделять быстрые и сильные модели по этапам.

Меняется ли поисковый retriever при смене api_base?

Нет. Retrieval работает через модуль RM, который вы передаёте в STORMWikiRunner (You.com, Bing и другие поддерживаемые бэкенды) с собственным ключом. Маршрутизация LM и извлечение источников — независимые системы, которые падают в разных фазах прогона.

Есть ли путь через переменные окружения вместо kwargs?

litellm уважает переменные на уровне провайдера, и провайдер openai читает OPENAI_API_BASE. Это работает, но явный kwarg api_base более воспроизводим: он путешествует вместе со скриптом, переживает машины с другим состоянием окружения и допускает исключения по каждому слоту.

Сколько токенов потребляет одна статья STORM?

Доминирует исследовательская фаза: симулированные разговоры с нескольких точек зрения умножают вызовы ещё до того, как появится хоть слово статьи, затем генерация и полировка добавляют сверху длинный вывод. Полные прогоны обычно приземляются в сотнях тысяч токенов, и вид использования по ключу показывает точное разделение по каждому этапу.