Rode o Stanford STORM em um endpoint personalizado compatível com OpenAI.

Updated 2026-09-06

O STORM constrói todo modelo de linguagem como um LitellmModel, e o litellm aceita api_base. Coloque https://api.apisrouter.com/v1 no seu openai_kwargs compartilhado, prefixe ids de modelo com openai/, e todos os cinco slots de LM do pipeline de artigo roteiam por um endpoint e uma chave.

Resposta rápida: api_base em openai_kwargs, prefixo openai/ nos ids.

O LitellmModel do STORM armazena quaisquer kwargs com os quais você o constrói e os mescla em toda chamada litellm.completion(). O parâmetro api_base do litellm é como você aponta o provedor openai para um host diferente, então adicionar api_base ao dict openai_kwargs que os próprios exemplos do STORM já usam é a sobrescrita inteira. Prefixe cada id de modelo com openai/ para que o litellm fale o protocolo chat-completions com essa base, e a string depois da barra é passada direto para o gateway.

Como os exemplos montam um dict openai_kwargs e o reutilizam para todo modelo, uma chave adicionada reroteia o pipeline inteiro. Nenhuma mudança de código do STORM, nenhum fork; isso é comportamento padrão do knowledge_storm em camadas sobre o roteamento documentado do 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)

Como o STORM divide um artigo em cinco slots de LM.

O STORM (stanford-oval no GitHub, cerca de 30 mil estrelas) escreve relatórios no estilo Wikipedia do zero: pesquisa um tópico através de conversas simuladas multi-perspectiva, constrói um esboço a partir do que aprendeu, gera o artigo completo seção por seção, e depois polimenta. STORMWikiLMConfigs expõe esse pipeline como cinco modelos configuráveis independentemente: conv_simulator_lm e question_asker_lm comandam as conversas de pesquisa, outline_gen_lm estrutura o artigo, article_gen_lm o escreve, e article_polish_lm faz a passada final.

O README service é explícito sobre a economia: o simulador de conversa roda o maior volume de chamadas, então recomenda um modelo mais rápido ali e um modelo mais poderoso para geração de artigo. Essa orientação assumia escolher entre modelos da OpenAI; atrás de um endpoint multi-fornecedor ela generaliza para algo mais útil. Cada slot é seu próprio LitellmModel com sua própria string de modelo, então a conversa de pesquisa pode rodar em um id DeepSeek rápido enquanto geração de esboço e artigo rodam em Claude, e o polimento em qualquer modelo que você confie para o tom, tudo autenticado pela mesma chave contra o mesmo api_base.

O lado de recuperação é maquinaria separada: o runner do STORM recebe um módulo RM (You.com, Bing, e vários outros backends de busca) com sua própria chave de API. Mudar para onde os modelos de linguagem apontam não toca em como as fontes são buscadas.

Diagrama mostrando os cinco slots de LM do STORM (simulador de conversa, perguntador, esboço, artigo, polimento) cada um construído como um LitellmModel com api_base apontando para o gateway da APIsRouter em api.apisrouter.com/v1, se ramificando para DeepSeek V4 Flash, Claude Sonnet, Claude Opus, GPT-5.5 e Gemini 3.1 Pro.STORM routes through LitellmModel(model='openai/<id>', api_base=...) via shared openai_kwargs to the APIsRouter gateway (api.apisrouter.com/v1), which fans out to: DeepSeek V4 Flash, Claude Sonnet, Claude Opus, GPT-5.5, Gemini 3.1 Pro.STORMviaLitellmModel(model='openai/<id>',api_base=...) viashared openai_kwargsAPIsRouterapi.apisrouter.com/v1DeepSeek V4 FlashClaude SonnetClaude OpusGPT-5.5Gemini 3.1 Pro
Um api_base nos kwargs compartilhados; cada etapa do pipeline escolhe seu próprio modelo do catálogo.

Configuração completa: cinco slots, um dict de kwargs.

O padrão que funciona espelha os próprios scripts de execução do repositório: monte os kwargs compartilhados uma vez, construa um LitellmModel por papel, e atribua-os pelos setters de STORMWikiLMConfigs. O api_key pode ter qualquer nome que você quiser já que você o passa explicitamente; o exemplo usa sua própria variável para deixar claro que essa não é uma credencial de conta OpenAI.

O litellm também honra variáveis de ambiente no nível de provedor, e o provedor openai lê OPENAI_API_BASE, então uma sobrescrita só por ambiente é possível. O caminho explícito de kwargs ainda é o preferível: ele fica visível no código que produziu um artigo específico, sobrevive a ser rodado em uma máquina com estado de ambiente diferente, e torna possíveis exceções por slot se você algum dia quiser uma etapa em um endpoint diferente.

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

Escolhendo modelos por etapa do pipeline.

Trate os cinco setters como um dial de orçamento, não boilerplate. A orientação service já diz para dividir modelos rápidos e fortes entre etapas; um endpoint multi-fornecedor só amplia o cardápio por etapa. Mude um slot de cada vez entre execuções sobre o mesmo tópico e compare as saídas, com o log de uso por chave precificando cada configuração.

  • conv_simulator_lm e question_asker_lm são as etapas de volume: entrevistas simuladas multi-turno através de várias perspectivas por tópico. deepseek-v4-flash ou outro id rápido evita que a fase de pesquisa domine o gasto, e conversa imperfeita é tolerável porque alimenta anotações, não prosa.
  • article_gen_lm é o slot carro-chefe. Ele escreve seções longas, estruturadas, e citadas a partir da pesquisa acumulada, o que é trabalho de geração sustentada onde claude-sonnet-4-6 ou gpt-5.5 supera visivelmente ids menores.
  • outline_gen_lm são poucas chamadas com influência desproporcional, o mesmo formato de um slot de planejamento: um esboço fraco limita o artigo não importa quão bom seja o escritor. É o lugar natural para testar claude-opus-4-7.
  • article_polish_lm reescreve para fluidez e remove duplicação através do artigo montado, o que se beneficia de um id de contexto longo; gemini-3.1-pro-preview vale a pena testar aqui.

Pague pelo uso · abaixo do preço oficial

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

ModeloPreço oficialNosso preço
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

Os modos de falha específicos do STORM.

Um id de modelo nu roteia por inferência, não pelo seu api_base. O litellm lê o prefixo para escolher um provedor, e um id Claude sem prefixo é inferido como uma chamada nativa Anthropic, que então quer ANTHROPIC_API_KEY e ignora seu gateway inteiramente. Todo id destinado ao gateway precisa carregar o prefixo openai/; o prefixo nomeia o protocolo, não o fornecedor.

Um slot deixado para trás. Cada LitellmModel captura seus kwargs na construção. Se quatro slots compartilham openai_kwargs e um quinto foi construído ad hoc sem api_base, esse slot silenciosamente publica para o padrão do fornecedor e falha na autenticação, e o traceback nomeia uma etapa do pipeline em vez de uma linha de configuração. Construa todo slot a partir do mesmo dict e essa classe de bug desaparece.

Falhas de retriever culpadas no endpoint. A fase de pesquisa precisa de um backend de busca funcionando; uma chave de retriever inválida ou esgotada (YDC_API_KEY, BING_SEARCH_API_KEY, ou qualquer RM que você escolheu) falha execuções durante a coleta de informação. Essa fase se intercala com chamadas de LM, então leia o traceback para ver qual cliente disparou o erro antes de mexer na configuração de LM.

O secrets.toml da demo não é a configuração do seu script. A demo Streamlit lê secrets.toml; execuções programáticas leem o que quer que seu script passe. Editar um enquanto roda o outro é um descompasso clássico.

max_tokens também é por slot. Os exemplos do STORM definem limites pequenos nos slots rápidos (500) e maiores na geração (3000). Apontar um slot para um modelo de forma longa sem elevar seu max_tokens trunca seções silenciosamente, o que parece um problema de qualidade de modelo mas é um número de configuração.

Quem roteia o STORM por um gateway.

  • Times gerando relatórios de conhecimento em volume (briefings, documentos internos no estilo wiki, primers de tópico), onde a divisão de cinco slots torna o ajuste de custo por etapa dinheiro de verdade.
  • Pesquisadores estudando composição de pipeline: qual etapa se beneficia de um modelo mais forte é uma pergunta empírica, e um endpoint torna trivial enumerar a grade de combinações slot-modelo.
  • Criadores rodando Claude ou Gemini nos slots de escrita de uma stack no formato OpenAI, sem adicionar um SDK de fornecedor por família de modelo.
  • Qualquer um rodando listas de tópicos em lote, onde o volume da fase de pesquisa se multiplica pelos tópicos e o log de uso vira o livro-razão de custo por tópico.
  • Desenvolvedores sem acesso ao faturamento de um determinado fornecedor. O acesso baseado em recarga sem exigência de cartão remove a dependência de cadastro por provedor.

Verifique o endpoint e depure o primeiro artigo.

Liste os modelos do gateway primeiro: a string depois de openai/ em cada slot precisa corresponder exatamente a um id servido.

Falhas na primeira execução seguem a ordem do pipeline. Um erro de autenticação nomeando Anthropic ou Google significa que um id sem prefixo roteou para um provedor nativo; adicione openai/. Um 401 do gateway significa que a api_key nos seus kwargs não é a chave do gateway. Um erro model-not-found nomeia o slot cujo id tem um erro de digitação. Falhas durante a fase de pesquisa que mencionam seu backend de busca são credenciais de retriever, não roteamento de LM. E seções de artigo truncadas ou estranhamente curtas geralmente são um max_tokens apertado demais no slot de geração em vez de qualquer coisa service.

Uma execução completa do STORM é uma grande explosão: conversas simuladas através de perspectivas, depois esboço, geração, e polimento. Assim que uma completa, o console da APIsRouter mostra modelo, contagens de token e gasto por requisição, que mapeia de forma limpa nos cinco slots e te diz exatamente qual etapa reajustar antes do próximo lote de tópicos.

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

Perguntas frequentes

Como o STORM suporta um endpoint personalizado compatível com OpenAI?

Através do litellm. O STORM constrói todo LM como um LitellmModel, que mescla seus kwargs de construtor em toda chamada litellm.completion(), e o litellm aceita api_base para o provedor openai. Adicione api_base ao dict openai_kwargs e todo slot construído a partir dele roteia para o gateway.

Por que ids de modelo precisam do prefixo openai/?

O litellm escolhe o provedor pelo prefixo. openai/claude-sonnet-4-6 significa "fale o protocolo chat-completions da OpenAI com meu api_base usando o modelo claude-sonnet-4-6". Sem o prefixo, o litellm infere o fornecedor pelo nome e roteia nativamente, ignorando seu endpoint.

Etapas diferentes do STORM podem usar modelos de fornecedores diferentes?

Sim. Cada um dos cinco slots é um LitellmModel independente, então o simulador de conversa pode rodar um id DeepSeek enquanto a geração de artigo roda Claude e o polimento roda GPT, tudo através do mesmo api_base e chave. O service já recomenda dividir modelos rápidos e fortes entre etapas.

O retriever de busca muda quando eu mudo api_base?

Não. A recuperação roda através do módulo RM que você passa para STORMWikiRunner (You.com, Bing, e outros backends suportados) com sua própria chave. Roteamento de LM e recuperação de fonte são sistemas independentes que falham em fases diferentes de uma execução.

Existe um caminho de variável de ambiente em vez de kwargs?

O litellm honra variáveis no nível de provedor, e o provedor openai lê OPENAI_API_BASE. Funciona, mas o kwarg explícito api_base é mais reproduzível: ele viaja com o script, sobrevive a máquinas com estado de ambiente diferente, e permite exceções por slot.

Quantos tokens um artigo do STORM consome?

A fase de pesquisa domina: conversas simuladas multi-perspectiva multiplicam chamadas antes de uma palavra do artigo existir, depois geração e polimento adicionam saída de forma longa em cima. Execuções completas comumente chegam a centenas de milhares de tokens, e a visão de uso por chave mostra a divisão exata por etapa.