스탠퍼드 STORM을 커스텀 OpenAI 호환 엔드포인트에서 실행하기.

Updated 2026-07-29

STORM은 모든 언어 모델을 LitellmModel로 구성하며, litellm은 api_base를 받아들입니다. 공유하는 openai_kwargs에 https://api.apisrouter.com/v1을 넣고, 모델 id 앞에 openai/를 붙이면, 아티클 파이프라인의 다섯 LM 슬롯 모두가 엔드포인트 하나와 키 하나를 통해 라우팅됩니다.

빠른 답: openai_kwargs에 api_base, id에 openai/ 프리픽스.

STORM의 LitellmModel은 여러분이 생성할 때 전달한 kwargs를 저장해 모든 litellm.completion() 호출에 병합합니다. litellm의 api_base 파라미터가 openai 프로바이더를 다른 호스트로 지정하는 방법이므로, STORM 자체 예제가 이미 사용하는 openai_kwargs 딕셔너리에 api_base를 추가하는 것이 오버라이드의 전부입니다. litellm이 그 base에 chat-completions 프로토콜로 말하도록 각 모델 id 앞에 openai/를 붙이면, 슬래시 뒤의 문자열이 그대로 게이트웨이로 전달됩니다. 예제들이 openai_kwargs 딕셔너리 하나를 만들어 모든 모델에 재사용하므로, 추가된 키 하나가 전체 파이프라인을 재라우팅합니다. 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이 아티클을 다섯 개의 LM 슬롯으로 나누는 방식.

STORM(GitHub의 stanford-oval, 약 3만 스타)은 처음부터 위키백과 스타일의 리포트를 씁니다: 시뮬레이션된 다관점 대화를 통해 주제를 리서치하고, 배운 것으로부터 아웃라인을 만들고, 아티클을 섹션별로 생성한 다음 다듬습니다. STORMWikiLMConfigs는 이 파이프라인을 독립적으로 설정 가능한 다섯 개 모델로 노출합니다: conv_simulator_lm과 question_asker_lm이 리서치 대화를 이끌고, outline_gen_lm이 아티클 구조를 잡고, article_gen_lm이 아티클을 쓰고, article_polish_lm이 마지막 손질을 합니다. 업스트림 README는 경제성에 대해 명시적입니다: 대화 시뮬레이터가 가장 많은 호출량을 실행하므로 거기에는 더 빠른 모델을, 아티클 생성에는 더 강력한 모델을 권장합니다. 이 가이드는 OpenAI 모델 중에서 고르는 것을 전제로 했지만, 멀티벤더 엔드포인트 뒤에서는 더 유용한 형태로 일반화됩니다. 각 슬롯은 자신만의 model 문자열을 가진 독립적인 LitellmModel이므로, 리서치 잡담은 빠른 DeepSeek id로 돌리고 아웃라인과 아티클 생성은 Claude로, 다듬기는 톤을 신뢰하는 어떤 모델로든 돌릴 수 있으며, 모두 같은 api_base를 대상으로 같은 키로 인증됩니다. 검색 쪽은 별개의 기계 장치입니다: STORM의 러너는 자체 API 키를 가진 RM 모듈(You.com, Bing, 그 외 여러 검색 백엔드)을 받습니다. 언어 모델이 어디를 향하는지 바꾸는 것은 소스가 어떻게 가져와지는지에는 영향을 주지 않습니다.

전체 설정: 다섯 슬롯, kwargs 딕셔너리 하나.

작동하는 패턴은 저장소 자체의 실행 스크립트를 그대로 따릅니다: 공유 kwargs를 한 번 만들고, 역할별로 LitellmModel 하나씩 만들고, STORMWikiLMConfigs의 setter를 통해 할당하세요. api_key는 명시적으로 전달하므로 원하는 어떤 이름이든 될 수 있습니다; 예제는 이것이 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")

파이프라인 단계별 모델 선택.

다섯 개의 setter를 보일러플레이트가 아니라 예산 다이얼로 다루세요. 업스트림 가이드는 이미 단계마다 빠른 모델과 강한 모델을 나누라고 말합니다; 멀티벤더 엔드포인트는 단계별 메뉴를 넓혀줄 뿐입니다. 같은 주제로 실행 사이에 슬롯 하나씩만 바꿔가며 출력을 비교하고, 키별 사용량 로그가 각 설정에 가격을 매기게 하세요.

  • 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/ 프리픽스를 달아야 합니다; 이 프리픽스는 벤더가 아니라 프로토콜의 이름입니다. 한 슬롯이 뒤처지는 경우. 각 LitellmModel은 생성 시점에 자신의 kwargs를 캡처합니다. 네 개의 슬롯이 openai_kwargs를 공유하는데 다섯 번째가 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을 쓰는가.

  • 대량으로 지식 리포트(브리핑, 위키 스타일 내부 문서, 주제 입문서)를 생성하는 팀 — 다섯 슬롯 분할이 단계별 비용 조정을 실제 돈이 되게 만듭니다.
  • 파이프라인 구성을 연구하는 연구자: 어느 단계가 더 강한 모델에서 이득을 보는지는 실증적인 질문이며, 엔드포인트 하나가 슬롯-모델 조합의 격자를 나열하기 쉽게 만듭니다.
  • OpenAI 형태의 스택에서 글쓰기 슬롯에 Claude나 Gemini를 실행하는 빌더 — 모델 패밀리마다 벤더 SDK를 추가할 필요 없이.
  • 배치 주제 목록을 실행하는 누구나 — 리서치 단계의 볼륨이 주제마다 곱해지고, 사용량 로그가 주제별 비용 장부가 됩니다.
  • 특정 벤더의 결제 수단에 접근할 수 없는 개발자. 카드 없이 충전만으로 사용할 수 있어 프로바이더별 가입 의존성이 사라집니다.

엔드포인트 검증 및 첫 아티클 디버깅.

먼저 게이트웨이의 모델을 나열하세요: 각 슬롯에서 openai/ 뒤의 문자열이 서빙되는 id와 정확히 일치해야 합니다. 첫 실행 실패는 파이프라인 순서를 따릅니다. Anthropic이나 Google을 언급하는 인증 오류는 프리픽스 없는 id가 네이티브 프로바이더로 라우팅됐다는 뜻입니다; openai/를 추가하세요. 게이트웨이에서 나오는 401은 kwargs의 api_key가 게이트웨이 키가 아니라는 뜻입니다. Model-not-found 오류는 id에 오타가 있는 슬롯의 이름을 알려줍니다. 검색 백엔드를 언급하는 리서치 단계 실패는 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은 openai 프로바이더를 위한 api_base를 받아들입니다. openai_kwargs 딕셔너리에 api_base를 추가하면 그것으로 만들어진 모든 슬롯이 게이트웨이로 라우팅됩니다.

모델 id에 왜 openai/ 프리픽스가 필요한가요?

litellm은 프리픽스에서 프로바이더를 고릅니다. openai/claude-sonnet-4-6은 "내 api_base에 model claude-sonnet-4-6으로 OpenAI chat-completions 프로토콜로 말하라"는 뜻입니다. 프리픽스가 없으면 litellm이 이름에서 벤더를 추론해 여러분의 엔드포인트를 우회하고 네이티브로 라우팅합니다.

STORM의 서로 다른 단계가 서로 다른 벤더의 모델을 쓸 수 있나요?

네. 다섯 슬롯 각각은 독립적인 LitellmModel이므로, 대화 시뮬레이터는 DeepSeek id를 실행하는 동안 아티클 생성은 Claude를, 다듬기는 GPT를 실행할 수 있으며, 모두 같은 api_base와 키를 통합니다. 업스트림은 이미 단계별로 빠른 모델과 강한 모델을 나누라고 권장합니다.

api_base를 바꾸면 검색 리트리버도 바뀌나요?

아니요. 검색은 STORMWikiRunner에 전달하는 RM 모듈(You.com, Bing, 그 외 지원되는 백엔드)을 통해 실행되며 자체 키를 가집니다. LM 라우팅과 소스 검색은 실행의 서로 다른 단계에서 실패하는 독립적인 시스템입니다.

kwargs 대신 환경 변수 경로가 있나요?

litellm은 프로바이더 레벨 변수를 존중하며, openai 프로바이더는 OPENAI_API_BASE를 읽습니다. 작동은 하지만, 명시적인 api_base kwarg가 더 재현 가능합니다: 스크립트와 함께 이동하고, 환경 상태가 다른 머신에서도 살아남으며, 슬롯별 예외를 허용합니다.

STORM 아티클 하나가 몇 개의 토큰을 소비하나요?

리서치 단계가 지배적입니다: 다관점 시뮬레이션 대화가 아티클의 단어 하나가 존재하기도 전에 호출을 곱하고, 그 위에 생성과 다듬기가 장문 출력을 더합니다. 전체 실행은 흔히 수십만 토큰에 이르며, 키별 사용량 뷰가 정확한 단계별 분할을 보여줍니다.