ollama-proxy/AGENTS.md
Atte149 4fe15324c8 feat: multi-provider support — Ollama Cloud + OpenCode Go
- Account.Provider field (ollama-cloud | opencode-go), backward compatible
- Model-based routing: common models served by combined pool, unique models
  routed to their provider only
- /go/v1/* path forces OpenCode Go provider (prefix stripped upstream)
- Merged /v1/models endpoint returns union of both catalogs (44 models)
- Failover: 429/402 → cooldown + failover; 5xx → retry without cooldown
- CLI: accounts add --provider flag, list shows provider column
- Body buffering: request body buffered (8 MiB cap) for failover replay
- opencode integration: unified provider 'oc' with all merged models
- 64 tests pass (unit + integration)
- Verified: glm-5 → ollama, mimo-v2.5 → go, gpt-oss:20b → ollama
2026-06-24 15:37:28 +03:00

113 lines
No EOL
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AGENTS.md — ollama-proxy
## Контекст
`ollama-proxy` — Go-прокси для Ollama Cloud (`https://ollama.com`) и OpenCode Go (`https://opencode.ai/zen/go/v1`). Балансирует запросы по нескольким API-ключам с round-robin и failover на 429/402/5xx. Модели общие для обоих провайдеров идут одним пулом, уникальные — маршрутизируются к нужному провайдеру. Слушает `127.0.0.1:11435`, экспонирует OpenAI-совместимый (`/v1/*`) и нативный Ollama (`/api/*`) API. Деплой — systemd unit `ollama-proxy.service`. Используется opencode через кастомный провайдер `oc`.
## Стек
- Go 1.22 (используется `log/slog`, `net/http`, `net/http/httputil`)
- Единственная внешняя зависимость: `golang.org/x/term` (prompt без echo в `accounts add`)
- Go 1.18 из apt НЕ подходит — нужен `~/.local/go/bin/go` (Go 1.22.10). Перед сборкой: `export PATH="$HOME/.local/go/bin:$PATH"`
- Сборка: `make build``./ollama-proxy`
- Тесты: `go test ./...` (43 теста, включая integration-тесты с `httptest`)
- Линт: `go vet ./...`, `gofmt -l .`
## Структура
```
main.go — точка входа, делегирует в cli.Run
internal/config/
accounts.go — Account, AccountsFile, JSON load/save (chmod 0600, atomic)
server.go — ServerConfig (addr, base_url, cooldown, retries, log_level)
internal/log/logger.go — slog JSON logger с configurable level
internal/proxy/
balancer.go — round-robin + per-account cooldown (RWMutex)
handler.go — HTTP handler: reverse-proxy, Authorization подмена, SSE flush
retry.go — документация стриминг-инварианта
internal/cli/
root.go — dispatch подкоманд (serve / accounts / version)
accounts.go — add / list / remove / set-base-url
serve.go — запуск HTTP-сервера с signal handling
systemd/ollama-proxy.service — systemd unit
```
## Конфигурация
- Аккаунты: `~/.config/ollama-proxy/accounts.json` (XDG_CONFIG_HOME, chmod 0600, atomic write)
- Флаги `serve`: `--addr`, `--base-url`, `--cooldown`, `--retries`, `--log-level`
- Env vars: `OLLAMA_PROXY_ADDR`, `OLLAMA_PROXY_BASE_URL`, `OLLAMA_PROXY_COOLDOWN`, `OLLAMA_PROXY_RETRIES`, `OLLAMA_PROXY_LOG_LEVEL`
## Типичные задачи
- **Добавить аккаунт:** `ollama-proxy accounts add --name <alias> --provider ollama-cloud|opencode-go` (промпт) или `... add <key> --name <alias> --provider <P>`
- **Перезапустить после правки аккаунтов:** `sudo systemctl restart ollama-proxy` (прокси читает accounts.json при старте; runtime-обновления не поддерживаются)
- **Посмотреть логи:** `journalctl -u ollama-proxy -f` — JSON-структурированные (account, account_id, provider, path, status, latency_ms, stream)
- **Проверить, что прокси жив:** `curl http://127.0.0.1:11435/api/version` → должен вернуть JSON от ollama.com
- **Проверить список моделей:** `curl http://127.0.0.1:11435/v1/models | jq '.data | length'` (~44 модели, merged из Ollama Cloud + OpenCode Go)
- **Запустить opencode с пулом:** `openlama` (обёртка в `~/.local/bin/openlama`, выставляет NO_PROXY и запускает opencode)
- **Обновить список моделей в opencode-конфиге:** `curl -sS http://127.0.0.1:11435/v1/models | jq -r '.data[].id'` → обновить `models` блок в `~/.config/opencode/openlama.json`
## Интеграция с opencode
**Два отдельных конфига:** `opencode` видит только marg/fireworks, `openlama` видит только `oc` (объединённый пул Ollama Cloud + OpenCode Go).
- `~/.config/opencode/opencode.json` — глобальный конфиг с `marg` + `fireworks` (без `oc`)
- `~/.config/opencode/openlama.json` — отдельный конфиг с `oc` + `"enabled_providers": ["oc"]`. Содержит те же instructions/mcp/plugins/agent что глобальный, но только провайдер `oc`.
- `~/.local/bin/openlama` — обёртка, выставляет `OPENCODE_CONFIG=~/.config/opencode/openlama.json` + `NO_PROXY=127.0.0.1,localhost,::1` + `exec opencode "$@"`
openlama.json (ключевая часть):
```json
{
"enabled_providers": ["oc"],
"provider": {
"oc": {
"npm": "@ai-sdk/openai-compatible",
"name": "Ollama + OpenCode Go (pool)",
"options": {
"baseURL": "http://127.0.0.1:11435/v1",
"apiKey": "proxy"
},
"models": {
"glm-5": { "name": "glm-5" },
"gpt-oss:20b": { "name": "gpt-oss:20b (ollama)" },
"mimo-v2.5": { "name": "mimo-v2.5 (go)" }
}
}
}
}
```
`OPENCODE_CONFIG` мерджится с глобальным конфигом, но `enabled_providers: ["oc"]` перекрывает глобальный — `marg`/`fireworks` скрыты.
**Маршрутизация моделей:** прокси парсит `model` из тела запроса и выбирает eligible провайдеров:
- Общие модели (glm-5, kimi-k2.6, deepseek-v4-flash и др.) → пул всех аккаунтов обоих провайдеров
- Уникальные Ollama (gpt-oss:20b, qwen3-coder, gemma, nemotron) → только ollama-cloud аккаунты
- Уникальные Go (mimo-v2.5, qwen3.5-plus, qwen3.7-max) → только opencode-go аккаунты
- Также: `/go/v1/*` path force-рует opencode-go провайдер
**Модель не сделана дефолтной** — выбирается через `/models` в TUI.
**ВАЖНО: NO_PROXY.** Если в окружении установлен `HTTP_PROXY`/`HTTPS_PROXY` (например `http://127.0.0.1:2080`), opencode (Bun runtime) направит запросы к прокси через HTTP-прокси → 502 Bad Gateway. Нужно явно исключить localhost:
```sh
# В ~/.config/environment.d/no-proxy-local.conf (systemd/desktop sessions):
NO_PROXY=127.0.0.1,localhost,::1
no_proxy=127.0.0.1,localhost,::1
```
Или запускать opencode с `NO_PROXY=127.0.0.1,localhost opencode ...`.
**Модели в конфиге:** opencode НЕ умеет динамически резолвить модели через `/v1/models` для `@ai-sdk/openai-compatible``models: {}` даёт `ProviderModelNotFoundError`. Нужно явно перечислить модели. Обновить список: `curl -sS http://127.0.0.1:11435/v1/models | jq -r '.data[].id'` → вставить в `models` блок.
## WARN
- **Никогда** не запускай прокси на `0.0.0.0` без auth — он не имеет аутентификации, любой может использовать твои ключи
- `accounts.json` содержит ключи в plaintext — файл chmod 0600, не коммить в git
- После правки `accounts.json` нужен `sudo systemctl restart ollama-proxy` (прокси не перечитывает файл в рантайме)
- Если локальная `ollama` запущена на 11434 — это НЕ конфликтует (прокси на 11435), но `OLLAMA_HOST=127.0.0.1:11435` нужно явно указывать
- Стриминг-инвариант: после первого flush-а 2xx ответа переключение аккаунта НЕ происходит. Mid-stream ошибки → truncated response клиенту. Это by design (нельзя дублировать вывод)
- 5xx НЕ вызывает cooldown аккаунта (только 429). 5xx → failover на следующий аккаунт, но без penalty
## План отката
- `sudo systemctl stop ollama-proxy && sudo systemctl disable ollama-proxy`
- `sudo rm /usr/local/bin/ollama-proxy /etc/systemd/system/ollama-proxy.service && sudo systemctl daemon-reload`
- Убрать блок `ocp` из `~/.config/opencode/opencode.json`