Skip to content

fix: 로컬 모델(Ollama/LM Studio) 자동 감지 및 라우팅 수정#1

Open
naegeon wants to merge 2 commits into
reallygood83:mainfrom
naegeon:fix/local-model-discovery-routing
Open

fix: 로컬 모델(Ollama/LM Studio) 자동 감지 및 라우팅 수정#1
naegeon wants to merge 2 commits into
reallygood83:mainfrom
naegeon:fix/local-model-discovery-routing

Conversation

@naegeon
Copy link
Copy Markdown

@naegeon naegeon commented Apr 15, 2026

요약

WSL2 환경에서 hermes-for-web 을 설치하고 Ollama 로컬 Gemma4 모델 + OpenAI Codex OAuth 를 함께 쓰려다가 발견한 버그 4종을 정리한 PR 입니다. 모두 api/config.py 한 파일에서 발생합니다.

재현

  1. Hermes Agent 설치 후 hermes auth add openai-codex --type oauth 로 Codex OAuth 완료
  2. Windows 호스트에 Ollama 구동, gemma4:e4b 모델 pull
  3. ~/.hermes/config.yaml:
    ```yaml
    model:
    provider: openai-codex
    default: gpt-5.4
    base_url: http://localhost:11434/v1
    ```
  4. WebUI 기동 후 모델 피커 확인

증상

  • 모델 피커에 Ollama 그룹이 뜨지 않음
  • GitHub Copilot 그룹에 엉뚱한 모델(Ollama 자동감지 결과)이 들어감
  • 그룹 라벨이 'Ollama' 가 아니라 'Custom' 으로 표시
  • 로컬 모델을 골라도 실제 호출은 Codex 로 가서 실패

원인 4가지

1. /v1/models 경로 오류 (치명)

```python
if base_url.endswith('/v1'):
endpoint_url = base_url[:-3] + '/models' # → http://host:11434/models (404)
```

OpenAI 호환 규격은 /v1/models 가 정답. Ollama, LM Studio, llama.cpp(OpenAI 모드) 모두 동일 경로.

2. localhost hostname 이 loopback 으로 인식 안 됨

`ipaddress.ip_address('localhost')` 는 ValueError. 127.0.0.1 은 처리되지만 WSL/Docker 에서 자주 쓰는 localhosthost.docker.internal 은 전부 'custom' 으로 떨어져서 Ollama 그룹 라벨이 붙지 않음.

3. copilot / github-copilot 모델 리스트 부재

_PROVIDER_MODELS 에 copilot 항목이 없어 unknown fallback 으로 떨어지고, 그 결과 auto_detected_models (Ollama 자동감지 리스트) 가 Copilot 그룹에 오염되어 들어감.

4. 로컬 모델 선택 시 pinned provider 로 잘못 라우팅

피커는 bare id (예: gemma4:e4b) 를 보내는데, resolve_model_provider 는 prefix 가 없으면 config 의 pinned provider (예: openai-codex) 로 라우팅. 결과적으로 Ollama 모델을 고르면 Codex 엔드포인트로 요청이 감.

수정 내역

  • 버그 1: base_url[:-3] 제거, base_url + '/models' 로 변경
  • 버그 2: hostname in ('localhost', 'host.docker.internal') 분기 추가 → 포트 11434 면 'ollama', 'lmstudio'/'lm-studio' 이면 'lmstudio', 그 외는 'local'
  • 버그 3: _PROVIDER_MODELScopilotgithub-copilot 기본 리스트 추가 (gpt-5.4 / gpt-5.4-mini / claude-sonnet-4.6 / o4-mini)
  • 버그 4:
    • get_available_models() 가 auto_detected_models 반환 시 id 에 {pid}/ prefix 부여 (예: ollama/gemma4:e4b)
    • resolve_model_provider()prefix in ('ollama', 'lmstudio', 'local', 'custom') 분기 추가 → provider='custom' + config_base_url 로 라우팅

테스트 결과

```
$ curl -s http://127.0.0.1:8788/api/models | jq '.groups[] | {provider, models: [.models[].id]}'
{ "provider": "GitHub Copilot", "models": ["gpt-5.4", "gpt-5.4-mini", "claude-sonnet-4.6", "o4-mini"] }
{ "provider": "Ollama", "models": ["ollama/gemma4:e4b"] }
{ "provider": "OpenAI", "models": ["gpt-5.4", "gpt-5.4-mini", "gpt-4o", "o3", "o4-mini"] }
{ "provider": "OpenAI Codex", "models": ["gpt-5.4", "gpt-5.4-mini", "codex-mini-latest"] }
```

  • Ollama 그룹 정상 노출
  • Copilot 그룹 오염 해소
  • ollama/gemma4:e4b 선택 시 로컬 엔드포인트로 정상 라우팅
  • openai-codex/gpt-5.4 선택 시 Codex 로 정상 라우팅

환경

  • WSL2 Ubuntu 24.04, Python 3.11.15 (Hermes Agent venv)
  • Hermes Agent (최신 main)
  • hermes-for-web @ b46f64b

🤖 Generated with Claude Code

WebUI 에서 로컬 OpenAI 호환 엔드포인트가 다음 네 가지 이유로 제대로
발견되거나 호출되지 않던 것을 수정:

1. /v1/models 경로 오류
   base_url 이 '/v1' 로 끝날 때 base_url[:-3] + '/models' 로 잘라서
   http://host:11434/models (404) 을 쳤음. OpenAI 호환 규격은 /v1/models 가
   정답. base_url + '/models' 로 유지.

2. 'localhost' hostname 이 loopback 으로 인식되지 않음
   ipaddress.ip_address('localhost') 는 ValueError. 127.0.0.1 은 되는데
   localhost 는 전부 'custom' 그룹으로 떨어져 Ollama 라벨이 안 붙음.
   hostname in ('localhost', 'host.docker.internal') 분기 추가.

3. copilot / github-copilot 모델 리스트 부재
   auth store 에서 gh CLI 토큰은 감지되지만 _PROVIDER_MODELS 에 copilot
   항목이 없어서 unknown-fallback 으로 떨어지고, 그 결과 auto_detected_models
   (Ollama 자동감지 리스트) 가 Copilot 그룹에 오염되어 들어감.
   gpt-5.4 / gpt-5.4-mini / claude-sonnet-4.6 / o4-mini 기본 리스트 추가.

4. 로컬 모델 선택 시 pinned provider 로 잘못 라우팅
   피커는 bare id (예: 'gemma4:e4b') 로 요청을 보내고,
   resolve_model_provider 는 prefix 없으면 config.yaml 의 pinned provider
   (openai-codex 등) 로 보냄. 결과적으로 Ollama 모델을 고르면 Codex 로
   요청 → 실패. 해결:
   - get_available_models 가 auto_detected_models 에 'ollama/' 등의
     provider prefix 를 붙여서 반환
   - resolve_model_provider 에 prefix in (ollama, lmstudio, local, custom)
     분기 추가 → provider='custom' + config_base_url 로 라우팅
1차 PR 적용 후 실제 환경 테스트에서 드러난 추가 결함 3건을 수정.

## 문제

로컬 Ollama + 클라우드 Codex 를 혼합 사용할 때, Codex 모델을 피커에서
고르면 호출이 로컬 Ollama 포트(11434) 로 가서 HTTP 404 "model 'gpt-5.4'
not found" 로 실패.

진단 로그로 확인한 실제 흐름:

  input_model='gpt-5.4'   ← 브라우저가 prefix 없이 bare id 전송
  resolve_model_provider 반환: ('gpt-5.4', 'openai-codex', 'http://localhost:11434/v1')
  streaming.py 최종: resolved_base_url='http://localhost:11434/v1'  ← Codex 인데 Ollama URL

## 원인 3건

### (a) resolve_model_provider 최종 fallback 이 무조건 config.base_url 반환

\`\`\`python
# before
return model_id, config_provider, config_base_url
\`\`\`

사용자가 config.yaml 에 \`model.base_url\` 을 설정하는 경우는
보통 로컬 엔드포인트 자동발견 목적(/api/models 드롭다운 populate)인데,
bare cloud 모델 id 로 cloud provider 호출할 때도 그 URL 을 그대로 상속해서
cloud provider 가 로컬 포트를 호출. provider 가 custom/local/ollama/lmstudio
일 때만 전달하도록 수정.

### (b) prefix 매칭 분기도 동일 문제

\`openai-codex/gpt-5.4\` 처럼 prefix 가 config_provider 와 일치하는 경우도
\`return bare, config_provider, config_base_url\` 로 로컬 URL 을 상속시킴.
(a) 와 같은 조건부 처리로 변경.

### (c) streaming.py override 조건이 이미 유효한 URL 을 덮어씀

\`\`\`python
# before
if not resolved_base_url or str(resolved_provider).startswith('custom'):
    resolved_base_url = rt_base_url
\`\`\`

resolve_runtime_provider(requested='custom') 은 Hermes 의 custom 폴백
(https://openrouter.ai/api/v1) 을 반환. provider='custom' 이면 이미 유효한
로컬 URL 을 가지고 있어도 OpenRouter 로 덮어써서 로컬 Ollama 호출이
OpenRouter 401 로 실패. \`if not resolved_base_url\` 만 남김.

## 추가: HERMES_WEBUI_HIDE_PROVIDERS 환경변수

\`gh auth token\` 이 자동 감지되어 사용하지 않는 GitHub Copilot 프로바이더가
피커에 계속 뜨는 문제. credential 레벨에서 끊는 건 gh CLI 를 건드려야 해서
부작용이 크니, UI 레벨에서 숨길 수 있는 옵트인 env var 추가.

  Environment=HERMES_WEBUI_HIDE_PROVIDERS=copilot,github-copilot

## 테스트

- openai-codex/gpt-5.4 → https://chatgpt.com/backend-api/codex 로 정상 라우팅
- openai/gpt-5.4 → 같은 경로 (openai credential 없으면 codex 로 폴백)
- ollama/gemma4:e4b-128k → http://localhost:11434/v1 로 정상 라우팅
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant