Skip to content

Commit 76d4993

Browse files
feat(providers): add openrouter and azure env discovery (#161)
* feat(providers): add openrouter and azure env discovery * fix(providers): add distinct azure provider support * feat(providers): add openrouter attribution defaults * fix(providers): correct azure resource routing * fix(providers): harden batch result routing and env overlay * fix(admin): stabilize models dashboard loading
1 parent 980d755 commit 76d4993

30 files changed

Lines changed: 2250 additions & 358 deletions

.env.template

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,17 @@
160160
# Groq
161161
# GROQ_API_KEY=gsk_...
162162

163+
# OpenRouter (default base URL: https://openrouter.ai/api/v1)
164+
# OPENROUTER_API_KEY=sk-or-...
165+
# OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
166+
# OPENROUTER_SITE_URL=https://gomodel.enterpilot.io
167+
# OPENROUTER_APP_NAME=GOModel
168+
169+
# Azure OpenAI
170+
# AZURE_API_KEY=...
171+
# AZURE_API_BASE=https://your-resource.openai.azure.com/openai/deployments/your-deployment
172+
# AZURE_API_VERSION=2024-10-21
173+
163174
# Ollama (local LLM server)
164175
# Note: Ollama doesn't require an API key
165176
# Set base URL to enable (default: http://localhost:11434/v1)

GETTING_STARTED.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,17 @@ Provider credentials:
188188
| `ANTHROPIC_BASE_URL` | Anthropic (custom endpoint) |
189189
| `GEMINI_API_KEY` | Google Gemini |
190190
| `GEMINI_BASE_URL` | Gemini (custom endpoint) |
191+
| `OPENROUTER_API_KEY` | OpenRouter (default base URL: `https://openrouter.ai/api/v1`) |
192+
| `OPENROUTER_BASE_URL` | OpenRouter (custom endpoint override) |
193+
| `OPENROUTER_SITE_URL` | OpenRouter attribution URL override (default: `https://gomodel.enterpilot.io`) |
194+
| `OPENROUTER_APP_NAME` | OpenRouter attribution title override (default: `GOModel`) |
191195
| `XAI_API_KEY` | xAI / Grok |
192196
| `XAI_BASE_URL` | xAI (custom endpoint) |
193197
| `GROQ_API_KEY` | Groq |
194198
| `GROQ_BASE_URL` | Groq (custom endpoint) |
199+
| `AZURE_API_KEY` | Azure OpenAI |
200+
| `AZURE_API_BASE` | Azure OpenAI deployment base URL |
201+
| `AZURE_API_VERSION` | Azure OpenAI API version override (default: `2024-10-21`) |
195202
| `OLLAMA_BASE_URL` | Ollama (default: `http://localhost:11434/v1`) |
196203

197204

@@ -213,6 +220,15 @@ Setting `CIRCUIT_BREAKER_TIMEOUT=60s` in the environment overrides whatever `tim
213220
**Ollama is always active.**
214221
Ollama requires no API key. Even with no YAML and no `OLLAMA_BASE_URL` set, an Ollama provider is registered pointing at `http://localhost:11434/v1`. If you do not want Ollama, make sure no Ollama instance is reachable at that address (the gateway's availability check will remove it from routing if it cannot be reached).
215222

223+
**Azure requires both key and base URL.**
224+
`AZURE_API_KEY` alone is not enough for auto-discovery. Set `AZURE_API_BASE` to the Azure deployment endpoint as well, otherwise the provider is ignored.
225+
226+
**Azure ships with a pinned API version by default.**
227+
If you do not set `AZURE_API_VERSION`, the gateway sends `api-version=2024-10-21`. Override it only when you need a different Azure API version.
228+
229+
**OpenRouter gets GOModel attribution headers by default.**
230+
When the `openrouter` provider is used, the gateway adds `HTTP-Referer` and `X-OpenRouter-Title` unless the request already provides them. Override the defaults with `OPENROUTER_SITE_URL` and `OPENROUTER_APP_NAME`.
231+
216232
**Partial YAML fields leave the rest at defaults.**
217233
YAML is unmarshalled onto the struct that was already populated by built-in defaults. Only fields that appear in the file are written. Omitting `max_backoff` from `resilience.retry` leaves it at `30s`; you do not need to repeat defaults you are happy with.
218234

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ docker run --rm -p 8080:8080 \
3030
-e ANTHROPIC_API_KEY="your-anthropic-key" \
3131
-e GEMINI_API_KEY="your-gemini-key" \
3232
-e GROQ_API_KEY="your-groq-key" \
33+
-e OPENROUTER_API_KEY="your-openrouter-key" \
3334
-e XAI_API_KEY="your-xai-key" \
35+
-e AZURE_API_KEY="your-azure-key" \
36+
-e AZURE_API_BASE="https://your-resource.openai.azure.com/openai/deployments/your-deployment" \
37+
-e AZURE_API_VERSION="2024-10-21" \
3438
-e OLLAMA_BASE_URL="http://host.docker.internal:11434/v1" \
3539
enterpilot/gomodel
3640
```
@@ -60,7 +64,9 @@ Example model identifiers are illustrative and subject to change; consult provid
6064
| Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514` |||||||
6165
| Google Gemini | `GEMINI_API_KEY` | `gemini-2.5-flash` |||||||
6266
| Groq | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |||||||
67+
| OpenRouter | `OPENROUTER_API_KEY` | `google/gemini-2.5-flash` |||||||
6368
| xAI (Grok) | `XAI_API_KEY` | `grok-2` |||||||
69+
| Azure OpenAI | `AZURE_API_KEY` + `AZURE_API_BASE` (`AZURE_API_VERSION` optional) | `gpt-4o` |||||||
6470
| Ollama | `OLLAMA_BASE_URL` | `llama3.2` |||||||
6571

6672
✅ Supported ❌ Unsupported

cmd/gomodel/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ import (
1919
"gomodel/internal/observability"
2020
"gomodel/internal/providers"
2121
"gomodel/internal/providers/anthropic"
22+
"gomodel/internal/providers/azure"
2223
"gomodel/internal/providers/gemini"
2324
"gomodel/internal/providers/groq"
2425
"gomodel/internal/providers/ollama"
2526
"gomodel/internal/providers/openai"
27+
"gomodel/internal/providers/openrouter"
2628
"gomodel/internal/providers/xai"
2729
"gomodel/internal/version"
2830

@@ -118,6 +120,8 @@ func main() {
118120
}
119121

120122
factory.Add(openai.Registration)
123+
factory.Add(openrouter.Registration)
124+
factory.Add(azure.Registration)
121125
factory.Add(anthropic.Registration)
122126
factory.Add(gemini.Registration)
123127
factory.Add(groq.Registration)

config/config.example.yaml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,18 @@ providers:
146146
# base_url: "https://api.groq.com/openai/v1"
147147
# api_key: "${GROQ_API_KEY}"
148148

149+
# Example: OpenRouter
150+
# openrouter:
151+
# type: "openrouter"
152+
# base_url: "https://openrouter.ai/api/v1"
153+
# api_key: "${OPENROUTER_API_KEY}"
154+
149155
# Example: Azure OpenAI
150-
# azure-openai:
151-
# type: "openai"
152-
# base_url: "https://your-resource.openai.azure.com/openai/deployments/your-deployment"
153-
# api_key: "${AZURE_OPENAI_API_KEY}"
156+
# azure:
157+
# type: "azure"
158+
# base_url: "${AZURE_API_BASE}"
159+
# api_key: "${AZURE_API_KEY}"
160+
# api_version: "2024-10-21"
154161

155162
# Example: DeepSeek (OpenAI-compatible)
156163
# deepseek:

config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type RawProviderConfig struct {
5454
Type string `yaml:"type"`
5555
APIKey string `yaml:"api_key"`
5656
BaseURL string `yaml:"base_url"`
57+
APIVersion string `yaml:"api_version"`
5758
Models []string `yaml:"models"`
5859
Resilience *RawResilienceConfig `yaml:"resilience"`
5960
}

config/config_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ func clearProviderEnvVars(t *testing.T) {
1515
"GEMINI_API_KEY", "GEMINI_BASE_URL",
1616
"XAI_API_KEY", "XAI_BASE_URL",
1717
"GROQ_API_KEY", "GROQ_BASE_URL",
18+
"OPENROUTER_API_KEY", "OPENROUTER_BASE_URL", "OPENROUTER_SITE_URL", "OPENROUTER_APP_NAME",
19+
"AZURE_API_KEY", "AZURE_API_BASE", "AZURE_API_VERSION",
1820
"OLLAMA_API_KEY", "OLLAMA_BASE_URL",
1921
} {
2022
t.Setenv(key, "")

docs/advanced/configuration.mdx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,15 @@ Set these to automatically register providers. No YAML configuration required.
128128
| `OPENAI_API_KEY` | OpenAI |
129129
| `ANTHROPIC_API_KEY` | Anthropic |
130130
| `GEMINI_API_KEY` | Google Gemini |
131+
| `OPENROUTER_API_KEY` | OpenRouter |
131132
| `XAI_API_KEY` | xAI (Grok) |
132133
| `GROQ_API_KEY` | Groq |
134+
| `AZURE_API_KEY` | Azure OpenAI (`AZURE_API_BASE` also required) |
133135
| `OLLAMA_BASE_URL` | Ollama (no API key needed) |
134136

135-
You can also set a custom base URL for any provider using `<PROVIDER>_BASE_URL` (e.g., `OPENAI_BASE_URL`).
137+
Most providers can use a custom base URL via `<PROVIDER>_BASE_URL` (for example `OPENAI_BASE_URL`). OpenRouter defaults to `https://openrouter.ai/api/v1` and can be overridden with `OPENROUTER_BASE_URL`. Azure uses `AZURE_API_BASE` for its deployment base URL and accepts an optional `AZURE_API_VERSION` override; otherwise it defaults to `2024-10-21`.
138+
139+
For OpenRouter, GOModel also sends default attribution headers unless the request already sets them. Override those defaults with `OPENROUTER_SITE_URL` and `OPENROUTER_APP_NAME`.
136140

137141
### 2. `.env` File
138142

@@ -240,10 +244,11 @@ providers:
240244
base_url: "https://my-proxy.example.com/v1"
241245
242246
# Add a second OpenAI-compatible endpoint
243-
azure-openai:
244-
type: openai
247+
azure:
248+
type: azure
245249
base_url: "https://my-resource.openai.azure.com/openai/deployments/gpt-4"
246250
api_key: "..."
251+
api_version: "2024-10-21"
247252
248253
# Restrict to specific models
249254
gemini:

internal/admin/dashboard/dashboard_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ func TestIndex_ReturnsHTML(t *testing.T) {
5050
if !strings.Contains(body, "audit logs") {
5151
t.Errorf("expected audit logs navigation item in page HTML")
5252
}
53+
if !strings.Contains(body, `x-data="dashboard()"`) {
54+
t.Errorf("expected alpine dashboard root in page HTML")
55+
}
56+
if strings.Contains(body, `x-init="init()"`) {
57+
t.Errorf("expected dashboard HTML not to call init() explicitly")
58+
}
5359
}
5460

5561
func TestStatic_ServesCSS(t *testing.T) {

internal/admin/dashboard/static/js/dashboard.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,15 @@ function dashboard() {
222222
const res = await fetch(url, { headers: this.headers() });
223223
if (!this.handleFetchResponse(res, 'models')) {
224224
this.models = [];
225+
if (typeof this.syncDisplayModels === 'function') this.syncDisplayModels();
225226
return;
226227
}
227228
this.models = await res.json();
229+
if (typeof this.syncDisplayModels === 'function') this.syncDisplayModels();
228230
} catch (e) {
229231
console.error('Failed to fetch models:', e);
230232
this.models = [];
233+
if (typeof this.syncDisplayModels === 'function') this.syncDisplayModels();
231234
}
232235
},
233236

0 commit comments

Comments
 (0)