Skip to content

Commit f9c689c

Browse files
feat(oracle): support configured models via env (#246)
* feat(oracle): support configured models via env * docs(roadmap): restore oracle env item
1 parent 5e1c9d2 commit f9c689c

12 files changed

Lines changed: 108 additions & 25 deletions

File tree

.env.template

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@
273273
# Oracle
274274
# ORACLE_API_KEY=...
275275
# ORACLE_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1
276+
# Optional fallback model inventory when Oracle's /models endpoint is unavailable
277+
# Comma-separated; whitespace around entries is ignored
278+
# ORACLE_MODELS=openai.gpt-oss-120b,xai.grok-3
276279

277280
# Ollama (local LLM server)
278281
# Note: Ollama doesn't require an API key, but one can be sent for secured deployments

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,4 @@ Full reference: `.env.template` and `config/config.yaml`
115115
- **Resilience:** Configured via `config/config.yaml` — global `resilience.retry.*` and `resilience.circuit_breaker.*` defaults with optional per-provider overrides under `providers.<name>.resilience.retry.*` and `providers.<name>.resilience.circuit_breaker.*`. Retry defaults: `max_retries` (3), `initial_backoff` (1s), `max_backoff` (30s), `backoff_factor` (2.0), `jitter_factor` (0.1). Circuit breaker defaults: `failure_threshold` (5), `success_threshold` (2), `timeout` (30s)
116116
- **Metrics:** `METRICS_ENABLED` (false), `METRICS_ENDPOINT` (/metrics)
117117
- **Guardrails:** Configured via `config/config.yaml` only (except `GUARDRAILS_ENABLED` env var)
118-
- **Providers:** `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `GROQ_API_KEY`, `OPENROUTER_API_KEY`, `ZAI_API_KEY`, `ZAI_BASE_URL` (optional Z.ai endpoint override), `AZURE_API_KEY`, `AZURE_BASE_URL` (Azure OpenAI deployment base URL), `AZURE_API_VERSION` (optional Azure API version), `ORACLE_API_KEY` (Oracle API key), `ORACLE_BASE_URL` (Oracle OpenAI-compatible base URL), `OLLAMA_BASE_URL`
118+
- **Providers:** `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `GROQ_API_KEY`, `OPENROUTER_API_KEY`, `ZAI_API_KEY`, `ZAI_BASE_URL` (optional Z.ai endpoint override), `AZURE_API_KEY`, `AZURE_BASE_URL` (Azure OpenAI deployment base URL), `AZURE_API_VERSION` (optional Azure API version), `ORACLE_API_KEY` (Oracle API key), `ORACLE_BASE_URL` (Oracle OpenAI-compatible base URL), `ORACLE_MODELS` (comma-separated Oracle fallback model inventory), `OLLAMA_BASE_URL`

GETTING_STARTED.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ Provider credentials:
199199
| `AZURE_API_VERSION` | Azure OpenAI API version override (default: `2024-10-21`) |
200200
| `ORACLE_API_KEY` | Oracle |
201201
| `ORACLE_BASE_URL` | Oracle OpenAI-compatible base URL |
202+
| `ORACLE_MODELS` | Oracle fallback model inventory (comma-separated, used when `/models` is unavailable) |
202203
| `OLLAMA_BASE_URL` | Ollama (default: `http://localhost:11434/v1`) |
203204

204205
See `.env.template` for the full list of all configurable environment variables.
@@ -224,7 +225,7 @@ Ollama requires no API key. Even with no YAML and no `OLLAMA_BASE_URL` set, an O
224225

225226
**Oracle requires both key and base URL.**
226227
`ORACLE_API_KEY` alone is not enough for auto-discovery. Set `ORACLE_BASE_URL` to the Oracle OpenAI-compatible endpoint, otherwise the provider is ignored.
227-
If your Oracle endpoint does not return a usable model list, configure `providers.<name>.models` in YAML to seed the router with explicit model IDs.
228+
If your Oracle endpoint does not return a usable model list, set `ORACLE_MODELS` or configure `providers.<name>.models` in YAML to seed the router with explicit model IDs.
228229

229230
**Azure ships with a pinned API version by default.**
230231
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.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ docker run --rm -p 8080:8080 \
4242
-e AZURE_API_VERSION="2024-10-21" \
4343
-e ORACLE_API_KEY="your-oracle-key" \
4444
-e ORACLE_BASE_URL="https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1" \
45+
-e ORACLE_MODELS="openai.gpt-oss-120b,xai.grok-3" \
4546
-e OLLAMA_BASE_URL="http://host.docker.internal:11434/v1" \
4647
enterpilot/gomodel
4748
```
@@ -81,6 +82,8 @@ Example model identifiers are illustrative and subject to change; consult provid
8182
✅ Supported ❌ Unsupported
8283

8384
For Z.ai's GLM Coding Plan, set `ZAI_BASE_URL=https://api.z.ai/api/coding/paas/v4`.
85+
For Oracle, set `ORACLE_MODELS=openai.gpt-oss-120b,xai.grok-3` when the
86+
upstream `/models` endpoint is unavailable.
8487

8588
---
8689

config/config.example.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ providers:
231231
# type: "oracle"
232232
# base_url: "${ORACLE_BASE_URL}"
233233
# api_key: "${ORACLE_API_KEY}"
234+
# # Optional when ORACLE_MODELS is set as a comma-separated env var instead.
234235
# models:
235236
# - openai.gpt-oss-120b
236237
# - xai.grok-3

docs/advanced/config-yaml.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ especially:
1515
- custom provider instance names such as `ollama-a`, `ollama-b`, or `my-openai`
1616
- larger nested config that is easier to review in one file
1717

18+
For Oracle specifically, a single fallback model list can now stay in env via
19+
`ORACLE_MODELS`. Use YAML when you need custom Oracle provider names or more
20+
than one Oracle provider instance.
21+
1822
## Priority Order
1923

2024
Effective precedence is:

docs/advanced/configuration.mdx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ Set these to automatically register providers. No YAML configuration required.
143143
| `OLLAMA_BASE_URL` | Ollama (no API key needed) |
144144

145145
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`. Z.ai defaults to `https://api.z.ai/api/paas/v4`; set `ZAI_BASE_URL=https://api.z.ai/api/coding/paas/v4` for the GLM Coding Plan endpoint. Azure uses `AZURE_BASE_URL` for its deployment base URL and accepts an optional `AZURE_API_VERSION` override; otherwise it defaults to `2024-10-21`. Oracle requires `ORACLE_BASE_URL` because its OpenAI-compatible endpoint is region-specific.
146-
When using Oracle, `models:` also acts as a fallback inventory if the upstream endpoint does not expose a usable `/models` response.
146+
When using Oracle, set `ORACLE_MODELS` to a comma-separated list such as `openai.gpt-oss-120b,xai.grok-3` if the upstream endpoint does not expose a usable `/models` response. YAML `models:` remains available for custom provider names and multi-provider Oracle setups.
147147

148148
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`.
149149

@@ -250,6 +250,7 @@ export AZURE_API_KEY="..." # Registers "azure" provider when paired wi
250250
export AZURE_BASE_URL="https://your-resource.openai.azure.com/openai/deployments/your-deployment"
251251
export ORACLE_API_KEY="..." # Registers "oracle" provider when paired with ORACLE_BASE_URL
252252
export ORACLE_BASE_URL="https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1"
253+
export ORACLE_MODELS="openai.gpt-oss-120b,xai.grok-3" # Optional fallback inventory for Oracle
253254
export OLLAMA_BASE_URL="http://localhost:11434/v1" # Registers "ollama" provider
254255
```
255256

@@ -294,14 +295,14 @@ providers:
294295
```
295296

296297
<Note>
297-
For Oracle, prefer YAML configuration over env-only auto-discovery. Oracle
298-
inference can work even when the upstream `/models` endpoint is unavailable,
299-
so setting `models:` gives GoModel a reliable fallback inventory. See the
300-
[Oracle guide](/guides/oracle) for the required OCI policy and a tested
301-
configuration. Automatic model discovery is not yet a reliable, validated
302-
path for this provider: GoModel can try Oracle's OpenAI-compatible `/models`
303-
endpoint, but Oracle may not return a usable inventory there. OCI-native
304-
Oracle model discovery is not integrated yet.
298+
For Oracle, give GoModel a fallback inventory with `ORACLE_MODELS` or
299+
`models:`. `ORACLE_MODELS` is enough for the default single-provider setup;
300+
use YAML when you need custom provider names or multiple Oracle providers.
301+
See the [Oracle guide](/guides/oracle) for the required OCI policy and a
302+
tested configuration. Automatic model discovery is not yet a reliable,
303+
validated path for this provider: GoModel can try Oracle's OpenAI-compatible
304+
`/models` endpoint, but Oracle may not return a usable inventory there.
305+
OCI-native Oracle model discovery is not integrated yet.
305306
</Note>
306307

307308
### Ollama (Local Models)

docs/guides/oracle.mdx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Flow:
1616
- Create an Oracle Generative AI API key.
1717
- Add an OCI IAM policy for `generativeaiapikey`.
1818
- Choose a supported Oracle region and model.
19-
- Prefer YAML configuration for Oracle so you can set `models:`.
19+
- Decide whether you want env-only `ORACLE_MODELS` or YAML `models:`.
2020

2121
## 1. Add the OCI policy
2222

@@ -42,7 +42,18 @@ export ORACLE_API_KEY="..."
4242

4343
## 3. Configure Oracle in GoModel
4444

45-
Use a YAML provider block and set `models:` explicitly:
45+
For the default single `oracle` provider, env-only configuration is enough:
46+
47+
```bash
48+
export ORACLE_MODELS="openai.gpt-oss-120b,xai.grok-3"
49+
```
50+
51+
`ORACLE_MODELS` is a comma-separated list. GoModel trims whitespace around each
52+
entry and uses the list as the fallback inventory when Oracle's `/models`
53+
endpoint is unavailable.
54+
55+
Use a YAML provider block when you want a custom provider name, multiple Oracle
56+
providers, or prefer to keep the model list in `config.yaml`:
4657

4758
```yaml
4859
providers:
@@ -61,15 +72,15 @@ Why `models:` matters:
6172
- GoModel can fall back to the configured model list when `/models` is
6273
unavailable
6374

64-
For Oracle, YAML is the recommended path. Environment-only auto-discovery is
65-
not enough if you need the configured-model fallback.
75+
If both are set, `ORACLE_MODELS` overrides YAML `models:` for the default
76+
`oracle` provider.
6677

6778
## Current status
6879

6980
What is integrated today:
7081

7182
- Oracle's OpenAI-compatible inference endpoints
72-
- manual model configuration through `models:`
83+
- manual model configuration through `ORACLE_MODELS` or `models:`
7384
- GoModel `/v1/models` from the configured-model fallback
7485

7586
What is not yet validated as reliable:
@@ -79,11 +90,12 @@ What is not yet validated as reliable:
7990
What is not integrated yet:
8091

8192
- native Oracle model auto-discovery through OCI APIs
82-
- automatic population of the Oracle model inventory without `models:`
93+
- automatic population of the Oracle model inventory without `ORACLE_MODELS` or
94+
`models:`
8395

8496
If Oracle later exposes a reliable `/models` endpoint for this API-key flow, or
85-
GoModel adds a separate OCI-native discovery path, this manual `models:`
86-
requirement can be relaxed.
97+
GoModel adds a separate OCI-native discovery path, this manual fallback
98+
configuration requirement can be relaxed.
8799

88100
## 4. Start GoModel
89101

@@ -100,7 +112,7 @@ curl -s http://localhost:8080/v1/models
100112
Expected result:
101113

102114
- a `200 OK`
103-
- an Oracle-owned model such as `oracle/openai.gpt-oss-120b`
115+
- a model such as `openai.gpt-oss-120b` with `owned_by: "oracle"`
104116
105117
## 6. Verify Responses
106118
@@ -140,7 +152,8 @@ text.
140152
Usually means the Generative AI API key policy is missing, the region is
141153
wrong, or the model is not available to the account.
142154
- `model registry has no models`
143-
Add `models:` to the Oracle provider config so GoModel can use the fallback.
155+
Set `ORACLE_MODELS` or add `models:` to the Oracle provider config so GoModel
156+
can use the fallback.
144157
- OCI CLI works but Oracle bearer requests fail
145158
These are different auth flows. OCI CLI uses API signing keys; Oracle
146159
Generative AI inference uses the Generative AI bearer API key.

internal/providers/config.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func applyProviderEnvVars(raw map[string]config.RawProviderConfig, discovery map
4444

4545
apiKey := os.Getenv(envNames.APIKey)
4646
explicitBaseURL := normalizeResolvedBaseURL(os.Getenv(envNames.BaseURL))
47+
models := parseCSVEnvList(os.Getenv(envNames.Models))
4748
apiVersion := ""
4849
if spec.SupportsAPIVersion {
4950
apiVersion = os.Getenv(envNames.APIVersion)
@@ -53,7 +54,7 @@ func applyProviderEnvVars(raw map[string]config.RawProviderConfig, discovery map
5354
baseURL = spec.DefaultBaseURL
5455
}
5556

56-
if apiKey == "" && baseURL == "" && apiVersion == "" {
57+
if apiKey == "" && baseURL == "" && apiVersion == "" && len(models) == 0 {
5758
continue
5859
}
5960

@@ -71,6 +72,9 @@ func applyProviderEnvVars(raw map[string]config.RawProviderConfig, discovery map
7172
if apiVersion != "" {
7273
existing.APIVersion = apiVersion
7374
}
75+
if len(models) > 0 {
76+
existing.Models = models
77+
}
7478
result[targetKey] = existing
7579
} else if ambiguous {
7680
continue
@@ -83,6 +87,7 @@ func applyProviderEnvVars(raw map[string]config.RawProviderConfig, discovery map
8387
APIKey: apiKey,
8488
BaseURL: baseURL,
8589
APIVersion: apiVersion,
90+
Models: models,
8691
}
8792
}
8893
}
@@ -122,6 +127,7 @@ type providerEnvNames struct {
122127
APIKey string
123128
BaseURL string
124129
APIVersion string
130+
Models string
125131
}
126132

127133
func derivedEnvNames(providerType string) providerEnvNames {
@@ -130,6 +136,7 @@ func derivedEnvNames(providerType string) providerEnvNames {
130136
APIKey: prefix + "_API_KEY",
131137
BaseURL: prefix + "_BASE_URL",
132138
APIVersion: prefix + "_API_VERSION",
139+
Models: prefix + "_MODELS",
133140
}
134141
}
135142

@@ -167,6 +174,26 @@ func normalizeResolvedBaseURL(value string) string {
167174
return trimmed
168175
}
169176

177+
func parseCSVEnvList(value string) []string {
178+
if strings.TrimSpace(value) == "" {
179+
return nil
180+
}
181+
182+
items := strings.Split(value, ",")
183+
values := make([]string, 0, len(items))
184+
for _, item := range items {
185+
trimmed := strings.TrimSpace(item)
186+
if trimmed == "" {
187+
continue
188+
}
189+
values = append(values, trimmed)
190+
}
191+
if len(values) == 0 {
192+
return nil
193+
}
194+
return values
195+
}
196+
170197
func isUnresolvedEnvPlaceholder(value string) bool {
171198
if !strings.HasPrefix(value, "${") || !strings.HasSuffix(value, "}") || len(value) <= 3 {
172199
return false

internal/providers/config_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ func TestApplyProviderEnvVars_DoesNotDiscoverAzureWithoutBaseURL(t *testing.T) {
471471
func TestApplyProviderEnvVars_DiscoversOracleFromExplicitEnvVars(t *testing.T) {
472472
t.Setenv("ORACLE_API_KEY", "oracle-key")
473473
t.Setenv("ORACLE_BASE_URL", "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1")
474+
t.Setenv("ORACLE_MODELS", " openai.gpt-oss-120b, xai.grok-3 ,, ")
474475

475476
got := applyProviderEnvVars(map[string]config.RawProviderConfig{}, testDiscoveryConfigs)
476477

@@ -487,6 +488,9 @@ func TestApplyProviderEnvVars_DiscoversOracleFromExplicitEnvVars(t *testing.T) {
487488
if p.BaseURL != "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1" {
488489
t.Errorf("BaseURL = %q, want Oracle base URL", p.BaseURL)
489490
}
491+
if len(p.Models) != 2 || p.Models[0] != "openai.gpt-oss-120b" || p.Models[1] != "xai.grok-3" {
492+
t.Errorf("Models = %v, want [openai.gpt-oss-120b xai.grok-3]", p.Models)
493+
}
490494
}
491495

492496
func TestApplyProviderEnvVars_DoesNotDiscoverOracleWithoutBaseURL(t *testing.T) {
@@ -499,6 +503,31 @@ func TestApplyProviderEnvVars_DoesNotDiscoverOracleWithoutBaseURL(t *testing.T)
499503
}
500504
}
501505

506+
func TestApplyProviderEnvVars_OracleModelsEnvWinsOverYAMLWithoutOtherOracleEnvVars(t *testing.T) {
507+
raw := map[string]config.RawProviderConfig{
508+
"oracle": {
509+
Type: "oracle",
510+
APIKey: "oracle-key",
511+
BaseURL: "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1",
512+
Models: []string{"yaml-model"},
513+
},
514+
}
515+
t.Setenv("ORACLE_MODELS", "openai.gpt-oss-120b, xai.grok-3")
516+
517+
got := applyProviderEnvVars(raw, testDiscoveryConfigs)
518+
519+
p := got["oracle"]
520+
if p.APIKey != "oracle-key" {
521+
t.Fatalf("APIKey = %q, want oracle-key", p.APIKey)
522+
}
523+
if p.BaseURL != "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1" {
524+
t.Fatalf("BaseURL = %q, want Oracle base URL", p.BaseURL)
525+
}
526+
if len(p.Models) != 2 || p.Models[0] != "openai.gpt-oss-120b" || p.Models[1] != "xai.grok-3" {
527+
t.Fatalf("Models = %v, want [openai.gpt-oss-120b xai.grok-3]", p.Models)
528+
}
529+
}
530+
502531
func TestApplyProviderEnvVars_EnvWinsOverYAML(t *testing.T) {
503532
t.Setenv("OPENAI_API_KEY", "sk-env-key")
504533

@@ -636,6 +665,7 @@ func TestApplyProviderEnvVars_SkipsWhenNoEnvVars(t *testing.T) {
636665
envNames := derivedEnvNames(providerType)
637666
t.Setenv(envNames.APIKey, "")
638667
t.Setenv(envNames.BaseURL, "")
668+
t.Setenv(envNames.Models, "")
639669
if spec.SupportsAPIVersion {
640670
t.Setenv(envNames.APIVersion, "")
641671
}

0 commit comments

Comments
 (0)