ollama-proxy/AGENTS.md
Atte149 3963eede70 feat: ollama-proxy — multi-account Ollama Cloud reverse proxy
Go reverse proxy for ollama.com that balances requests across multiple
API keys with round-robin and failover on 429/5xx. Exposes both native
Ollama API (/api/*) and OpenAI-compatible (/v1/*) passthrough.

- Round-robin balancer with per-account cooldown (60s default)
- Pre-stream failover: 429 → cooldown + next account; 5xx → next account
- Streaming invariant: once 2xx starts streaming, no account switch
- SSE (text/event-stream) and NDJSON passthrough with http.Flusher
- CLI: accounts add/list/remove/set-base-url, serve, version
- Accounts stored in ~/.config/ollama-proxy/accounts.json (chmod 0600)
- systemd unit (User=dueattendant149, 127.0.0.1:11435, Restart=always)
- 43 tests (unit + integration with httptest upstream)
- opencode integration: custom provider 'ocp' with explicit model list
- Requires NO_PROXY=127.0.0.1,localhost when HTTP_PROXY is set
2026-06-19 13:58:13 +03:00

91 lines
No EOL
6.1 KiB
Markdown
Raw 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`). Балансирует запросы по нескольким API-ключам с round-robin и failover на 429/5xx. Слушает `127.0.0.1:11435`, экспонирует нативный Ollama API (`/api/*`) и OpenAI-совместимый (`/v1/*`) passthrough. Деплой — systemd unit `ollama-proxy.service`. Используется opencode через кастомный провайдер `ocp`.
## Стек
- 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>` (промпт) или `... add <key> --name <alias>`
- **Перезапустить после правки аккаунтов:** `sudo systemctl restart ollama-proxy` (прокси читает accounts.json при старте; runtime-обновления не поддерживаются)
- **Посмотреть логи:** `journalctl -u ollama-proxy -f` — JSON-структурированные (account, account_id, 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'` (~36 моделей)
## Интеграция с opencode
В `~/.config/opencode/opencode.json` в блоке `provider`:
```json
"ocp": {
"npm": "@ai-sdk/openai-compatible",
"name": "Ollama Cloud (pool)",
"options": {
"baseURL": "http://127.0.0.1:11435/v1",
"apiKey": "proxy"
},
"models": {
"gpt-oss:20b": { "name": "gpt-oss:20b" },
"...": { "name": "..." }
}
}
```
Модель выбирается через `/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`