실제 터미널 캡처: LiteLLM에서 없는 Ollama 모델을 부르면 NotFoundError가 나고 Router fallbacks를 설정하면 로컬 qwen2.5-0.5b-local 모델이 답한 화면과 글 제목

LiteLLM 프록시 NotFoundError, fallbacks로 로컬 모델에 넘기기

애플리케이션은 모델 태그 대신 별칭 main을 부르는데, 그 별칭이 가리키는 태그가 장비에 없으면 NotFoundError가 돌아옵니다. LiteLLM 프록시를 실무에 넣을 수 있는지는 두 가지에 달려 있었습니다. Debian 13의 로컬 Ollama에 없는 모델을 요청하면 오류가 어떤 모양으로 돌아오는지, 그리고 폴백으로 응답을 살려 낼 수 있는지입니다. 두 방식 모두 같은 서버에서 실행했고, 결과는 아래 출력 블록과 캡처에 있습니다. 요청에 쓴 이름과 실제 백엔드가 받은 태그를 끝까지 같이 따라가는 것이 이번 점검의 뼈대였습니다.

Debian에서 그 실패를 그대로 재현하고, router_settings.fallbacks로 이미 받아 둔 qwen2.5-0.5b-local 모델에 요청을 넘기는 설정을 실제로 돌립니다. 같은 파일에서 폴백만 끄고 보내는 요청, 프록시를 거치지 않고 Ollama의 OpenAI 호환 주소를 바로 부르는 호출도 뒤에서 함께 비교합니다.

별칭 main이 없는 태그를 가리킬 때 나오는 NotFoundError

앱이 보내는 이름과 Ollama가 찾는 태그는 설정 파일 안에서 갈라집니다. 표의 환경 값은 재현 단계의 첫 명령 출력에서 옮겼습니다. 실행할 때마다 바뀌는 시각, 헬스 체크 대기 횟수, ollama list의 MODIFIED 열은 본문에 다시 적지 않고 출력 블록에 맡깁니다.

항목값
실행일2026-10-14
OSDebian GNU/Linux 13 (trixie)
아키텍처x86_64
CPU8
RAMRAM_TOTAL_GB 15
LiteLLM1.104.2
프록시 Python3.12.15
Ollama0.40.3
로컬 태그qwen2.5-0.5b-local:latest, ID 9806541f10f4, SIZE 491 MB

첫 명령은 운영체제, CPU 수, 전체 메모리와 실행 파일마다의 버전을 한꺼번에 찍습니다. LiteLLM 버전은 프록시를 띄우는 가상환경의 Python에서 조회해, 실행 환경과 어긋나지 않게 했습니다. ollama list로는 qwen2.5-0.5b-local:latest가 준비돼 있는지 봅니다. 뒤에서 설정에 적는 모델 이름은 모두 이 목록과 맞춰 봐야 합니다.

date '+%Y-%m-%d'
. /etc/os-release && printf '%s\n' "$PRETTY_NAME"
uname -m
nproc
free -g | awk 'NR==2{print "RAM_TOTAL_GB", $2}'
ollama --version
/home/user/litellm/.venv/bin/python -c 'import importlib.metadata; print("LiteLLM: Current Version =", importlib.metadata.version("litellm"))'
/home/user/litellm/.venv/bin/python -V
ollama list

문제 재현용 설정은 별칭 main 하나만 두고, 그 대상을 장비에 없는 ollama_chat/qwen2.5:7b로 둡니다. api_base는 Ollama가 듣고 있는 http://127.0.0.1:11434입니다. general_settings에는 dangerously_permit_weak_or_unset_master_key만 true로 넣었고 master_key는 두지 않았습니다.

model_list:
  - model_name: main
    litellm_params:
      model: ollama_chat/qwen2.5:7b
      api_base: http://127.0.0.1:11434
general_settings:
  dangerously_permit_weak_or_unset_master_key: true

프록시를 127.0.0.1:14041에 띄우고 별칭 main으로 짧은 채팅을 보냈습니다. Authorization 헤더 없이 부를 수 있도록 앞의 설정에 로컬 개발용 기동 옵션을 명시해 둔 상태입니다. max_tokens는 8이고, 응답 헤더와 본문은 파일을 나눠 저장했습니다. 그래야 HTTP 상태와 모델 오류 메시지를 나란히 놓고 볼 수 있습니다.

setsid /home/user/litellm/.venv/bin/litellm --config configs/a.yaml --host 127.0.0.1 --port 14041 > logs/a-proxy.log 2>&1 &
curl -sS -D logs/a.hdr -o logs/a.body -w 'HTTP %{http_code}\n' --max-time 30 http://127.0.0.1:14041/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"main","messages":[{"role":"user","content":"Reply with the single word pong."}],"max_tokens":8}'

환경 조회부터 폴백 없는 main 요청까지 이어진 실행 기록입니다. ollama list에는 qwen2.5-0.5b-local:latest만 있고, 요청 대상인 qwen2.5:7b는 보이지 않습니다. 요청은 HTTP 404로 끝나고, 오류 본문 끝에 폴백을 시도하지 않았다는 안내가 붙습니다. 프록시가 떴다는 것과 대상 모델을 쓸 수 있다는 것은 서로 다른 이야기라는 점이 여기서 드러납니다.

$ export LC_ALL=C PYTHONDONTWRITEBYTECODE=1 TERM=dumb
$ date +%Y-%m-%d
2026-10-14
$ . /etc/os-release && printf %s\n "$PRETTY_NAME"
Debian GNU/Linux 13 (trixie)
$ uname -m
x86_64
$ nproc
8
$ free -g | awk 'NR==2{print "RAM_TOTAL_GB", $2}'
RAM_TOTAL_GB 15
$ ollama --version
ollama version is 0.40.3
$ /home/user/litellm/.venv/bin/python -c 'import importlib.metadata; print("LiteLLM: Current Version =", importlib.metadata.version("litellm"))'
LiteLLM: Current Version = 1.104.2
$ /home/user/litellm/.venv/bin/python -V
Python 3.12.15
$ ollama list
NAME                         ID              SIZE      MODIFIED   
qwen2.5-0.5b-local:latest    9806541f10f4    491 MB    2 days ago    
$ cat > configs/a.yaml
$ cat configs/a.yaml
model_list:
  - model_name: main
    litellm_params:
      model: ollama_chat/qwen2.5:7b
      api_base: http://127.0.0.1:11434
general_settings:
  dangerously_permit_weak_or_unset_master_key: true
$ setsid /home/user/litellm/.venv/bin/litellm --config configs/a.yaml --host 127.0.0.1 --port 14041 > logs/a-proxy.log 2>&1 &
health_ready 12
$ curl -sS -D logs/a.hdr -o logs/a.body -w 'HTTP %{http_code}\n' --max-time 30 http://127.0.0.1:14041/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"main","messages":[{"role":"user","content":"Reply with the single word pong."}],"max_tokens":8}'
HTTP 404
$ awk 'BEGIN{IGNORECASE=1} /^HTTP\// || tolower($0) ~ /^x-litellm-attempted-fallbacks:/ || tolower($0) ~ /^x-litellm-model-group:/ {print}' logs/a.hdr
HTTP/1.1 404 Not Found
$ jq -r 'if .error then "error.type: \(.error.type)\nerror.message: \(.error.message)" else "model: \(.model)\ncontent: \(.choices[0].message.content)" end' logs/a.body
error.type: invalid_request_error
error.message: litellm.NotFoundError: Ollama_chatException - {"error":"model 'qwen2.5:7b' not found"}

LiteLLM: model group 'main' failed with the error above. No fallback was attempted.

재현 쪽 터미널 캡처에는 main 요청의 HTTP 404와, qwen2.5:7b를 찾을 수 없다는 메시지가 같이 잡혀 있습니다. 별칭이 등록돼 있어도 그에 맞는 Ollama 태그가 없으면 요청은 실패합니다. 장애를 재현할 때 별칭 설정과 로컬 모델 목록을 한 실행 기록에 남겨 두면, 나중에 무엇이 어긋났는지 바로 짚어집니다.

LiteLLM 프록시 실행 화면: main 요청이 HTTP 404와 qwen2.5:7b not found를 반환합니다
2026-10-14 14:22:46 KST, Debian 13 — main 요청이 HTTP 404와 qwen2.5:7b not found를 반환합니다.

두 캡처의 명령 줄에는 가상환경이 놓인 실제 폴더가 그대로 보입니다. 본문은 같은 명령을 /home/user/litellm/.venv 기준으로 바꿔 적었고 설정·로그 파일 위치도 같은 식으로 줄였으며, 포트와 응답 결과는 같습니다.

원인은 별칭 main이 가리키는 태그가 장비에 없다는 점입니다. 설정 문서는 model_name을 외부 클라이언트가 LiteLLM에 넘기는 이름으로, litellm_params.model을 completion 호출에 넘기는 문자열로 설명합니다. 요청 본문의 model은 main이어도 Ollama가 받는 태그는 qwen2.5:7b입니다. ollama list의 NAME은 qwen2.5-0.5b-local:latest 하나뿐이라 그 태그는 목록에 없습니다.

재현 스크립트의 채팅 응답은 HTTP 404이고, 상태 줄은 HTTP/1.1 404 Not Found입니다. error.type은 invalid_request_error입니다. error.message의 앞부분은 litellm.NotFoundError: Ollama_chatException - {"error":"model 'qwen2.5:7b' not found"}입니다. 같은 메시지의 다음 문장은 model group main에 대해 폴백을 시도하지 않았다고 말합니다. a.yaml에는 router_settings.fallbacks가 없기 때문입니다.

공급자 문서는 더 나은 응답을 위해 ollama_chat 접두어를 권합니다. 이번 설정의 모델 문자열은 그 접두어를 사용했고, api_base는 문서 예시의 로컬 포트와 같은 11434를 127.0.0.1로 지정했습니다.

router_settings.fallbacks로 받아 둔 로컬 모델에 넘기기

해결은 장비에 있는 태그를 두 번째 별칭으로 등록하고, main이 실패하면 그 별칭을 쓰게 하는 것입니다. local-fast의 litellm_params.model은 ollama_chat/qwen2.5-0.5b-local입니다. router_settings.fallbacks는 main의 다음 후보로 local-fast만 적습니다. 신뢰성 문서는 폴백 목록을 앞에서부터 순서대로 시도한다고 설명합니다. 이번 후보 목록은 한 개입니다.

model_list:
  - model_name: main
    litellm_params:
      model: ollama_chat/qwen2.5:7b
      api_base: http://127.0.0.1:11434
  - model_name: local-fast
    litellm_params:
      model: ollama_chat/qwen2.5-0.5b-local
      api_base: http://127.0.0.1:11434
router_settings:
  fallbacks:
    - main: ["local-fast"]
general_settings:
  dangerously_permit_weak_or_unset_master_key: true

프록시는 127.0.0.1:14042에 띄웠습니다. 첫 요청의 model은 main이고, 두 번째 요청의 model은 local-fast입니다. 본문과 max_tokens는 재현 요청과 같고, Authorization 헤더는 역시 넣지 않았습니다.

setsid /home/user/litellm/.venv/bin/litellm --config configs/b.yaml --host 127.0.0.1 --port 14042 > logs/b-proxy.log 2>&1 &
curl -sS -D logs/b-main.hdr -o logs/b-main.body -w 'HTTP %{http_code}\n' --max-time 40 http://127.0.0.1:14042/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"main","messages":[{"role":"user","content":"Reply with the single word pong."}],"max_tokens":8}'
curl -sS -D logs/b-local-fast.hdr -o logs/b-local-fast.body -w 'HTTP %{http_code}\n' --max-time 40 http://127.0.0.1:14042/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"local-fast","messages":[{"role":"user","content":"Reply with the single word pong."}],"max_tokens":8}'

main 요청과 local-fast 직접 요청을 같은 설정에서 견준 기록입니다. 두 요청 모두 HTTP 200과 Pong!을 돌려주지만 x-litellm-attempted-fallbacks는 각각 1과 0입니다. main이 성공했다는 것은 local-fast가 대신 답했다는 뜻입니다. 운영 로그에도 성공 상태만 남기지 말고 실제로 처리한 모델과 폴백 횟수를 같이 적어 둬야 이 차이가 보입니다.

$ export LC_ALL=C PYTHONDONTWRITEBYTECODE=1
$ cat > configs/b.yaml
$ cat configs/b.yaml
model_list:
  - model_name: main
    litellm_params:
      model: ollama_chat/qwen2.5:7b
      api_base: http://127.0.0.1:11434
  - model_name: local-fast
    litellm_params:
      model: ollama_chat/qwen2.5-0.5b-local
      api_base: http://127.0.0.1:11434
router_settings:
  fallbacks:
    - main: ["local-fast"]
general_settings:
  dangerously_permit_weak_or_unset_master_key: true
$ setsid /home/user/litellm/.venv/bin/litellm --config configs/b.yaml --host 127.0.0.1 --port 14042 > logs/b-proxy.log 2>&1 &
health_ready 15
---- main ----
$ curl -sS -D logs/b-main.hdr -o logs/b-main.body -w 'HTTP %{http_code}\n' --max-time 40 http://127.0.0.1:14042/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"main","messages":[{"role":"user","content":"Reply with the single word pong."}],"max_tokens":8}'
HTTP 200
$ awk 'BEGIN{IGNORECASE=1} /^HTTP\// || tolower($0) ~ /^x-litellm-attempted-fallbacks:/ || tolower($0) ~ /^x-litellm-model-group:/ {print}' logs/b-main.hdr
HTTP/1.1 200 OK
x-litellm-model-group: local-fast
x-litellm-attempted-fallbacks: 1
$ jq -r 'if .error then "error.type: \(.error.type)\nerror.message: \(.error.message)" else "model: \(.model)\ncontent: \(.choices[0].message.content)" end' logs/b-main.body
model: ollama_chat/qwen2.5-0.5b-local
content: Pong!
---- local-fast ----
$ curl -sS -D logs/b-local-fast.hdr -o logs/b-local-fast.body -w 'HTTP %{http_code}\n' --max-time 40 http://127.0.0.1:14042/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"local-fast","messages":[{"role":"user","content":"Reply with the single word pong."}],"max_tokens":8}'
HTTP 200
$ awk 'BEGIN{IGNORECASE=1} /^HTTP\// || tolower($0) ~ /^x-litellm-attempted-fallbacks:/ || tolower($0) ~ /^x-litellm-model-group:/ {print}' logs/b-local-fast.hdr
HTTP/1.1 200 OK
x-litellm-model-group: local-fast
x-litellm-attempted-fallbacks: 0
$ jq -r 'if .error then "error.type: \(.error.type)\nerror.message: \(.error.message)" else "model: \(.model)\ncontent: \(.choices[0].message.content)" end' logs/b-local-fast.body
model: local-fast
content: Pong!

해결 쪽 캡처는 폴백을 거친 호출과 local-fast 직접 호출을 나란히 보여 줍니다. main은 폴백 1회 뒤 HTTP 200, local-fast는 폴백 0회로 HTTP 200입니다. 응답 model도 호출 경로에 따라 공급자 문자열과 별칭으로 다르게 찍힙니다. 클라이언트가 응답 model이 늘 요청 이름과 같다고 가정하고 있다면 여기서 어긋납니다.

LiteLLM 프록시 실행 화면: main은 fallback 1회, local-fast는 0회로 모두 HTTP 200을 받습니다
2026-10-14 14:25:13 KST, Debian 13 — main은 fallback 1회, local-fast는 0회로 모두 HTTP 200을 받습니다.

main 요청의 결과는 HTTP 200이고 상태 줄은 HTTP/1.1 200 OK입니다. 헤더 x-litellm-model-group은 local-fast이고, x-litellm-attempted-fallbacks는 1입니다. 응답 model은 ollama_chat/qwen2.5-0.5b-local입니다. 클라이언트가 보낸 이름은 main인데, 응답의 model 필드는 폴백된 공급자 문자열로 바뀝니다. content 줄의 문장은 출력 블록에 있습니다.

같은 프록시에 local-fast를 직접 보내면 HTTP 200이고, x-litellm-model-group은 local-fast, x-litellm-attempted-fallbacks는 0입니다. 응답 model은 local-fast입니다. 이번 직접 호출에서는 폴백 횟수가 0으로 남고, 응답 model은 별칭을 유지했습니다.

이 구성을 쓰면 클라이언트는 model에 main만 넣어도, 없는 태그에서 실패했을 때 로컬 별칭의 응답을 받습니다. 이번 요청이 폴백을 거쳤는지는 x-litellm-attempted-fallbacks로 가려집니다. 감수할 부분도 있습니다. 응답 model이 요청 별칭 main이 아니라 ollama_chat/qwen2.5-0.5b-local로 바뀌고, 폴백 대상은 ollama list에 실제로 있어야 합니다. 후보가 여러 개라면 문서대로 앞 항목부터 시도합니다.

기동 거부, 요청 단위 폴백 끄기, Ollama 직접 호출

폴백 설정보다 먼저 막히는 지점은 기동입니다. c-boot.yaml은 main과 local-fast, router_settings.fallbacks까지 갖추고도 general_settings를 넣지 않았습니다. 이 파일로 127.0.0.1:14043을 띄우면 프로세스가 요청을 받기 전에 종료됩니다.

model_list:
  - model_name: main
    litellm_params:
      model: ollama_chat/qwen2.5:7b
      api_base: http://127.0.0.1:11434
  - model_name: local-fast
    litellm_params:
      model: ollama_chat/qwen2.5-0.5b-local
      api_base: http://127.0.0.1:11434
router_settings:
  fallbacks:
    - main: ["local-fast"]

c-boot.yaml로 프록시를 띄우고 마스터 키 관련 로그만 추려 봤습니다. 이 설정에는 general_settings.master_key도, 로컬 개발용 예외 옵션도 없습니다. 출력에는 UnsafeMasterKeyError가 남고, 마스터 키가 없어 시작을 거부한다는 안내가 따라옵니다. 모델 라우팅을 들여다보기 전에, 프로세스가 요청을 받을 수 있는 상태인지부터 갈라 봐야 하는 이유입니다.

setsid /home/user/litellm/.venv/bin/litellm --config configs/c-boot.yaml --host 127.0.0.1 --port 14043 > logs/c-boot.log 2>&1 &
grep -E 'refused to start|Neither general_settings|Local development only|dangerously_permit_weak_or_unset_master_key' logs/c-boot.log

로그의 예외는 litellm.proxy.auth.master_key_boot_check.UnsafeMasterKeyError입니다. 문장은 master key가 없으면 모든 요청을 인증 없이 받게 되므로 시작을 거부한다는 내용입니다. 이어서 general_settings.master_key와 LITELLM_MASTER_KEY가 둘 다 없다고 적고, 로컬에서만 환경 변수 LITELLM_DANGEROUSLY_PERMIT_WEAK_OR_UNSET_MASTER_KEY 또는 general_settings.dangerously_permit_weak_or_unset_master_key를 true로 두라고 안내합니다. 이번 채팅에 쓴 파일은 후자를 true로 넣었고, 키 값은 넣지 않았습니다. 마스터 키 회전 문서도 해석된 키가 비어 있으면 프록시가 부팅 때 종료된다고 적습니다.

폴백이 들어 있는 파일에서도 요청 단위로 폴백을 끌 수 있습니다. c.yaml은 b.yaml과 같은 설정이고, 포트는 14043입니다. 본문에 disable_fallbacks를 true로 넣고 model은 main으로 보냈습니다. 신뢰성 문서는 이 필드를 요청 본문에 넣어 그 요청의 폴백을 끈다고 설명합니다.

model_list:
  - model_name: main
    litellm_params:
      model: ollama_chat/qwen2.5:7b
      api_base: http://127.0.0.1:11434
  - model_name: local-fast
    litellm_params:
      model: ollama_chat/qwen2.5-0.5b-local
      api_base: http://127.0.0.1:11434
router_settings:
  fallbacks:
    - main: ["local-fast"]
general_settings:
  dangerously_permit_weak_or_unset_master_key: true

이번에는 폴백 설정이 살아 있는 프록시에 disable_fallbacks를 true로 실어 보냅니다. 요청 모델은 main, max_tokens는 앞과 같은 8입니다. 응답이 HTTP 404이니, 폴백을 켠 main 요청의 HTTP 200과 확연히 갈립니다. 설정 파일을 건드리지 않고 원래 모델의 실패를 다시 보고 싶을 때 쓰기 좋은 방법입니다.

setsid /home/user/litellm/.venv/bin/litellm --config configs/c.yaml --host 127.0.0.1 --port 14043 > logs/c-proxy.log 2>&1 &
curl -sS -D logs/c-disable.hdr -o logs/c-disable.body -w 'HTTP %{http_code}\n' --max-time 30 http://127.0.0.1:14043/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"main","messages":[{"role":"user","content":"Reply with the single word pong."}],"max_tokens":8,"disable_fallbacks":true}'

이 요청은 HTTP 404이고 error.type은 invalid_request_error입니다. error.message는 NotFoundError와 model ‘qwen2.5:7b’ not found까지입니다. 재현 스크립트의 메시지와 달리, 폴백을 시도하지 않았다는 이어지는 문장은 이 출력에 없습니다. 설정 파일의 폴백은 그대로 두고 원래 오류만 보고 싶을 때 이 필드를 씁니다.

별칭과 폴백이 필요 없으면 Ollama의 OpenAI 호환 경로를 직접 부릅니다. 주소는 http://127.0.0.1:11434/v1/chat/completions이고, model은 목록에 있는 qwen2.5-0.5b-local입니다. 이번 curl에는 Content-Type과 JSON 본문만 있었습니다.

curl -sS -D logs/c-ollama.hdr -o logs/c-ollama.body -w 'HTTP %{http_code}\n' --max-time 40 http://127.0.0.1:11434/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"qwen2.5-0.5b-local","messages":[{"role":"user","content":"Reply with the single word pong."}],"max_tokens":8}'

마스터 키 없는 기동 실패, 폴백을 끈 요청, Ollama 직접 호출이 차례로 이어지는 출력입니다. 기동 단계에는 UnsafeMasterKeyError가, 폴백을 끈 요청에는 HTTP 404가 남습니다. 준비된 모델을 Ollama 호환 경로로 바로 요청하면 HTTP 200과 PONG!이 돌아옵니다. 맨 끝의 문서 URL 조회 결과는 주소에 닿았는지를 본 것이라 모델 호출 성공과는 상관이 없습니다.

$ export LC_ALL=C PYTHONDONTWRITEBYTECODE=1
---- boot without master_key ----
$ cat > configs/c-boot.yaml
$ cat configs/c-boot.yaml
model_list:
  - model_name: main
    litellm_params:
      model: ollama_chat/qwen2.5:7b
      api_base: http://127.0.0.1:11434
  - model_name: local-fast
    litellm_params:
      model: ollama_chat/qwen2.5-0.5b-local
      api_base: http://127.0.0.1:11434
router_settings:
  fallbacks:
    - main: ["local-fast"]
$ setsid /home/user/litellm/.venv/bin/litellm --config configs/c-boot.yaml --host 127.0.0.1 --port 14043 > logs/c-boot.log 2>&1 &
boot_refused 12
$ grep -E 'refused to start|Neither general_settings|Local development only|dangerously_permit_weak_or_unset_master_key' logs/c-boot.log
litellm.proxy.auth.master_key_boot_check.UnsafeMasterKeyError: LiteLLM proxy refused to start: no master key is set, so every request would be accepted without authentication. The fix is printed once the server exits.
LiteLLM proxy refused to start: no master key is set, so every request would be accepted without authentication.
Neither general_settings.master_key nor the LITELLM_MASTER_KEY environment variable is set.
Local development only: set LITELLM_DANGEROUSLY_PERMIT_WEAK_OR_UNSET_MASTER_KEY=true, or
general_settings.dangerously_permit_weak_or_unset_master_key: true, to start anyway.
---- disable_fallbacks ----
$ cat > configs/c.yaml
$ cat configs/c.yaml
model_list:
  - model_name: main
    litellm_params:
      model: ollama_chat/qwen2.5:7b
      api_base: http://127.0.0.1:11434
  - model_name: local-fast
    litellm_params:
      model: ollama_chat/qwen2.5-0.5b-local
      api_base: http://127.0.0.1:11434
router_settings:
  fallbacks:
    - main: ["local-fast"]
general_settings:
  dangerously_permit_weak_or_unset_master_key: true
$ setsid /home/user/litellm/.venv/bin/litellm --config configs/c.yaml --host 127.0.0.1 --port 14043 > logs/c-proxy.log 2>&1 &
health_ready 12
$ curl -sS -D logs/c-disable.hdr -o logs/c-disable.body -w 'HTTP %{http_code}\n' --max-time 30 http://127.0.0.1:14043/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"main","messages":[{"role":"user","content":"Reply with the single word pong."}],"max_tokens":8,"disable_fallbacks":true}'
HTTP 404
$ awk 'BEGIN{IGNORECASE=1} /^HTTP\// || tolower($0) ~ /^x-litellm-attempted-fallbacks:/ || tolower($0) ~ /^x-litellm-model-group:/ {print}' logs/c-disable.hdr
HTTP/1.1 404 Not Found
$ jq -r 'if .error then "error.type: \(.error.type)\nerror.message: \(.error.message)" else "model: \(.model)\ncontent: \(.choices[0].message.content)" end' logs/c-disable.body
error.type: invalid_request_error
error.message: litellm.NotFoundError: Ollama_chatException - {"error":"model 'qwen2.5:7b' not found"}
---- ollama openai compatibility ----
$ curl -sS -D logs/c-ollama.hdr -o logs/c-ollama.body -w 'HTTP %{http_code}\n' --max-time 40 http://127.0.0.1:11434/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"qwen2.5-0.5b-local","messages":[{"role":"user","content":"Reply with the single word pong."}],"max_tokens":8}'
HTTP 200
$ awk 'BEGIN{IGNORECASE=1} /^HTTP\// {print}' logs/c-ollama.hdr
HTTP/1.1 200 OK
$ jq -r 'if .error then "error.type: \(.error.type)\nerror.message: \(.error.message)" else "model: \(.model)\ncontent: \(.choices[0].message.content)" end' logs/c-ollama.body
model: qwen2.5-0.5b-local
content: PONG!
---- official URL HEAD ----
$ curl -sI -o /dev/null -w '%{http_code}' --max-time 20 https://docs.litellm.ai/docs/proxy/reliability
200 https://docs.litellm.ai/docs/proxy/reliability
$ curl -sI -o /dev/null -w '%{http_code}' --max-time 20 https://docs.litellm.ai/docs/proxy/configs
200 https://docs.litellm.ai/docs/proxy/configs
$ curl -sI -o /dev/null -w '%{http_code}' --max-time 20 https://docs.litellm.ai/docs/providers/ollama
200 https://docs.litellm.ai/docs/providers/ollama
$ curl -sI -o /dev/null -w '%{http_code}' --max-time 20 https://docs.litellm.ai/docs/proxy/master_key_rotations
200 https://docs.litellm.ai/docs/proxy/master_key_rotations
$ curl -sI -o /dev/null -w '%{http_code}' --max-time 20 https://docs.ollama.com/api/openai-compatibility
200 https://docs.ollama.com/api/openai-compatibility
$ curl -sI -o /dev/null -w '%{http_code}' --max-time 20 https://github.com/BerriAI/litellm/releases/tag/v1.104.2
200 https://github.com/BerriAI/litellm/releases/tag/v1.104.2
$ curl -sI -o /dev/null -w '%{http_code}' --max-time 20 https://github.com/ollama/ollama/releases/tag/v0.40.3
200 https://github.com/ollama/ollama/releases/tag/v0.40.3
$ curl -sI -o /dev/null -w '%{http_code}' --max-time 20 https://pypi.org/project/litellm/
200 https://pypi.org/project/litellm/

점검 캡처는 기동 조건과 요청 경로를 하나씩 떼어 확인한 화면입니다. 폴백을 끈 main 요청은 HTTP 404, 준비된 태그를 지정한 Ollama 직접 호출은 HTTP 200이고, 직접 호출의 내용은 PONG!으로 찍힙니다. 같은 오류가 운영 중에 나면 기동 로그와 원래 모델의 오류, 실제 태그의 응답을 차례로 대조하면 됩니다.

LiteLLM 프록시 실행 화면: Ollama 호환 API에서 HTTP 200·PONG! 응답을 확인합니다
2026-10-14 14:25:55 KST, Debian 13 — Ollama 호환 API에서 HTTP 200·PONG! 응답을 확인합니다.

직접 호출의 결과는 HTTP 200이고 상태 줄은 HTTP/1.1 200 OK입니다. 응답 model은 qwen2.5-0.5b-local입니다. content 줄은 출력 블록에 있습니다. 호환 문서는 로컬 서버의 /v1/chat/completions 요청을 안내합니다.

직접 호출은 프록시와 별칭 없이 장비에 있는 태그만으로 HTTP 200을 받는 가장 짧은 길입니다. disable_fallbacks는 폴백 설정을 그대로 둔 채 원래 NotFoundError만 돌려줍니다. 다만 Ollama 직접 호출이든 local-fast 직접 호출이든, 클라이언트가 요청할 모델 이름은 바꿔야 합니다.

이번 프록시의 바인드 주소는 모두 127.0.0.1입니다. Ollama도 127.0.0.1:11434에서 응답했습니다. 짧은 max_tokens 요청은 491 MB 로컬 태그로 처리되었습니다.

기동 인증부터 채팅 별칭까지, 배포 때 다시 볼 것

이번 출력의 LiteLLM은 1.104.2이고, 마스터 키도 로컬 개발용 예외 옵션도 없는 설정은 기동을 거부합니다. 배포 설정을 갱신할 때는 general_settings.master_key나 LITELLM_MASTER_KEY가 어떤 경로로 주입되는지부터 확인합니다. 로그가 로컬 개발용이라고 못 박은 dangerously_permit_weak_or_unset_master_key를 운영 인증 설정 대신 쓰면 안 됩니다. 버전을 올릴 때도 기동 성공과 실제 채팅 응답을 따로 확인하도록 헬스체크 절차를 맞춰 둡니다.

출력의 health_ready는 스크립트가 남긴 준비 표시일 뿐, 헬스체크 API 경로를 보여 주는 값이 아닙니다. 이 표시만 보고 점검 경로를 정하지 말고 배포 환경의 설정에 맞춰 관리합니다. 채팅 확인에는 출력에서 쓴 /v1/chat/completions와 실제로 등록된 별칭을, Ollama를 직접 점검할 때는 로컬 태그를 지정합니다. 이 기록 하나로 과거 버전과의 API 차이를 단정하기보다는, 현재 실행에서 드러난 기동 조건과 요청 필드가 배포 설정에 들어가 있는지 확인하는 데 쓰십시오.

fallback으로 별칭을 지킬지, 있는 모델만 부를지

표에는 이번 실행에서 응답이 남은 선택만 모았습니다. main은 폴백이 없으면 HTTP 404, 폴백을 구성하면 HTTP 200입니다. local-fast 직접 호출과 Ollama 호환 경로 직접 호출도 HTTP 200이었으니, 고르는 기준은 별칭을 유지해야 하는가와 대체 경로가 필요한가 두 가지로 좁혀집니다. 응답 문장과 폴백 횟수는 출력 블록의 본문과 헤더에서 맞춰 보시면 됩니다.

선택실행결과
main, 폴백 없음재현 스크립트, 127.0.0.1:14041, model mainHTTP 404, invalid_request_error, model ‘qwen2.5:7b’ not found, 폴백 시도 없음
main, router_settings.fallbacks해결 스크립트, 127.0.0.1:14042, model mainHTTP 200, model ollama_chat/qwen2.5-0.5b-local, x-litellm-model-group local-fast, x-litellm-attempted-fallbacks 1
local-fast 직접해결 스크립트, model local-fastHTTP 200, model local-fast, x-litellm-attempted-fallbacks 0
disable_fallbacks true점검 스크립트, 폴백이 있는 설정, model mainHTTP 404, 같은 NotFoundError
Ollama 호환 경로 직접점검 스크립트, 127.0.0.1:11434/v1/chat/completions, model qwen2.5-0.5b-localHTTP 200, model qwen2.5-0.5b-local

앱이 별칭 main만 알고, 장비에 없는 태그가 실패하면 받아 둔 로컬 태그로 넘겨야 하는 상황이라면 router_settings.fallbacks가 답입니다. 이때 폴백 응답의 model은 공급자 문자열로 바뀐다는 점만 기억해 두십시오.

이미 있는 모델만 부른다면 local-fast 별칭이나 Ollama의 /v1/chat/completions 쪽이 더 단순합니다. 폴백 설정은 그대로 두고 원래 오류만 보고 싶을 때는 요청 본문에 disable_fallbacks true를 넣습니다. 이 버전을 로컬에서 키 없이 띄우려면 general_settings.dangerously_permit_weak_or_unset_master_key를 true로 둡니다.

설정을 맞추면서 기준으로 삼은 공식 문서와 릴리스 페이지는 다음과 같습니다.

출력 블록을 보면, 준비된 로컬 모델을 대체 대상으로 등록한 폴백은 없는 태그의 오류를 정상 응답으로 바꿔 놓았습니다. 애플리케이션이 main 별칭을 지켜야 한다면 router_settings.fallbacks를 구성하고, 배포 전에 대체 모델이 실제로 답하는지까지 확인하는 구성을 권합니다. 이미 있는 모델만 부른다면 local-fast를 지정하거나 Ollama 호환 경로를 바로 쓰는 편이 간단합니다. 운영 로그에는 HTTP 상태와 함께 처리 모델, 폴백 횟수를 남겨 대체 응답이 늘어나는 시점을 잡아냅니다. CPU 8, RAM_TOTAL_GB 15 환경에서 짧은 요청이 성공했다고 실시간 서비스의 처리 용량으로 읽어서는 안 되고, 실제 요청 길이와 동시 요청 수에 맞춰 사양을 정해야 합니다. 실시간 서비스라면 기본 경로에서 준비된 모델을 부르고 폴백은 실패를 메우는 경로로만 두는 쪽이 요청 흐름을 다루기 쉽습니다.

로컬 모델 설치와 메모리 계산까지 이어서 보시려면 로컬 LLM 실행 허브를, 모델 호출과 프롬프트 관리로 넘어가시려면 RAG·AI API 활용 허브를 열어 보십시오.

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다