Repository navigation
Expand file tree
/
Copy pathconfig.example.yaml
More file actions
933 lines (864 loc) · 44 KB
/
Copy pathconfig.example.yaml
File metadata and controls
933 lines (864 loc) · 44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
# GoModel Configuration (optional)
# Copy to config/config.yaml to customize.
# Environment variables always override values in this file.
# All settings have sensible defaults — no config file is required.
#
# Any string value can be a secret reference, resolved after the file is read:
# ${env:NAME} the variable's value; unset or empty stops startup
# ${file:/abs/path} the file's contents minus one trailing newline (Docker/Kubernetes secrets)
# References may sit inside a value ("Bearer ${env:TOKEN}"); write $${ for a literal ${.
# The plain ${NAME} / ${NAME:-default} form still works and leaves an unset NAME in place.
server:
port: "8080"
base_path: "/" # env: BASE_PATH; set to "/g" to serve the gateway under https://example.com/g/
master_key: "your-secret-key"
master_key_disabled: false # env: MASTER_KEY_DISABLED; true forgets the master key, leaving managed gateway API keys and any configured authentication extension as the only credentials. The gateway then requires a credential on every request and keeps the admin API gated, so issue one with dashboard access first
body_size_limit: "10M"
stream_stall_timeout: 60 # env: STREAM_STALL_TIMEOUT; seconds one response write on a model route may wait for the client to read it. Fires only when a client stops reading, never while the provider is slow; 0 disables
swagger_enabled: false # env: SWAGGER_ENABLED; requires a binary built with -tags=swagger
pprof_enabled: false # expose /debug/pprof/* for local profiling only
enable_passthrough_routes: true # expose /p/{provider}/{endpoint} passthrough routes
allow_passthrough_v1_alias: true # allow /p/{provider}/v1/... while keeping /p/{provider}/... canonical
user_path_header: "X-GoModel-User-Path" # env: USER_PATH_HEADER; inbound header used for user_path scoping
enabled_passthrough_providers: ["openai", "anthropic", "cohere", "openrouter", "kilo", "zai", "sglang", "vllm", "llmd", "deepseek", "bailian"] # providers enabled on /p/{provider}/...
auth_verify_enabled: false # env: AUTH_VERIFY_ENABLED; expose GET /v1/auth/verify, which reports whether the API key a request carries authenticates against this gateway (401 when it does not; 200 with {"valid":false,"method":"none"} when the gateway has no authentication configured). Outside /admin, so it works with the admin API disabled
realtime_enabled: true # env: REALTIME_ENABLED; expose the /v1/realtime and /v1/realtime/translations websockets, their calls/client_secrets siblings, and /p/{provider}/v1/realtime upgrades
# Client address resolution. Leave trusted_proxies empty (default) and every
# client address GoModel reports is the socket peer, with forwarding headers
# ignored. Entries are CIDR networks, bare addresses, or the presets
# "loopback" and "private" (RFC 1918 + ULA); prefer the one subnet your proxy
# sits on over "private", which trusts every private client to set its own
# forwarded address.
# trusted_proxies: ["10.42.0.0/16", "loopback"]
# client_ip_header: "CF-Connecting-IP" # env: SERVER_CLIENT_IP_HEADER; default X-Forwarded-For, resolved hop by hop. Any other header carries one address and is taken verbatim, which is correct behind an edge like Cloudflare. Requires trusted_proxies; RFC 7239 Forwarded is not supported
# trusted_hops: 1 # env: SERVER_TRUSTED_HOPS; proxies between the client and the gateway. Takes the X-Forwarded-For entry at that depth instead of scanning the chain, for an edge at a fixed depth with addresses you cannot enumerate. Requires trusted_proxies
pid_file: "data/gomodel.pid" # env: PID_FILE; where the running gateway records its process id so `gomodel --reload` can find it. Set per instance when several gateways share a host; empty writes no pid file and disables --reload; changing it needs a restart, not a reload
models:
enabled_by_default: true # env: MODELS_ENABLED_BY_DEFAULT; when false, models stay unavailable until an access override allows one or more user paths
keep_only_aliases_at_models_endpoint: false # env: KEEP_ONLY_ALIASES_AT_MODELS_ENDPOINT; hide provider models from GET /v1/models and expose only enabled virtual models
unqualified_model_ids_at_models_endpoint: false # env: UNQUALIFIED_MODEL_IDS_AT_MODELS_ENDPOINT; list bare model IDs (gpt-5) instead of provider-qualified ones (openai/gpt-5). Caveat: when two providers expose the same model ID only the provider an unqualified request routes to (the first registered one) is listed; pin a name with a virtual model (source gpt-5 -> target azure/gpt-5)
configured_provider_models_mode: "fallback" # env: CONFIGURED_PROVIDER_MODELS_MODE; "fallback" uses configured lists only when upstream /models is unavailable/empty, "allowlist" exposes only configured models and skips upstream /models for configured lists, "merge" adds configured models on top of the upstream inventory (for models a provider serves but does not list)
# Tagging based on headers: label every request from the listed headers. Labels
# are recorded in usage tracking and audit logs. A header value can carry several
# labels split by `delimiter` (default: ","). `prefix` is trimmed from each
# extracted label only — the header itself is forwarded unchanged unless
# `do_not_pass: true`. An env entry replaces the whole YAML entry with the same
# header name (unset companion vars reset fields to defaults, they do not
# inherit the YAML values); other env entries are appended:
# TAGGING_HEADER_1=X-My-Tags
# TAGGING_HEADER_1_PREFIX=tag- # optional
# TAGGING_HEADER_1_DONOTPASS=true # optional, default false (headers are passed as-is)
# TAGGING_HEADER_1_DELIMITER=";" # optional, default ","
# Entries declared here or via env are read-only in the dashboard; omit this
# section to manage tagging headers only from the UI (Settings → Tagging based on headers).
# tagging:
# headers:
# - header: X-My-Tags
# prefix: "tag-" # optional: "tag-alpha, beta" → labels "alpha", "beta"
# - header: X-Internal-Routing
# do_not_pass: true # stripped before forwarding to the provider
# delimiter: ";"
# # LiteLLM tags (x-litellm-tags header, body "tags", list metadata.tags) become
# # labels and are stripped before the provider; at most 32 per request, tags
# # over 128 characters are dropped. Clients choose them, so they can charge any
# # label budget they name: set false when label budgets must hold per team.
# litellm_tags_enabled: true # env: LITELLM_TAGS_ENABLED
# Session keeping: identify which requests belong to one client session, for
# sticky virtual-model load balancing and audit-log session grouping. Everything
# below is the default — omit the section entirely for the same behavior. Extra
# headers are merged over the built-in known-tools registry; the optional
# transform "session-uuid" extracts a session_<uuid> value.
# session:
# enabled: true
# auto_detect: true
# builtin_rules: true
# headers:
# - header: X-My-Session
# Virtual models as infrastructure-as-code: redirects, load balancers, and access
# policies. These override admin-store rows with the same source and are read-only
# in the dashboard. The VIRTUAL_MODELS env var (a JSON array) merges over this list
# and wins per source. Omit this section to manage virtual models only from the UI.
# virtual_models:
# - source: regular # a plain alias
# target: anthropic/claude-sonnet-4-6
# slowdown: 0.5 # add 50%; use 0 to disable inherited slowdown (active range: 0.1-10)
# # reasoning_effort: medium # force this effort upstream (none | minimal | low | medium | high | xhigh | max); empty keeps the client's value
# - source: smart # weighted round-robin load balancer
# strategy: round_robin # round_robin (default) | cost | failover | adaptive (uses a routing extension when registered; otherwise falls back to round_robin) | plugin (see smart-router below)
# targets:
# - { model: openai/gpt-4o, weight: 2 }
# - { model: anthropic/claude-sonnet-4-6 }
# # session_affinity: true # keep a detected client session on the target that served it
# # failover: true # retry a failed request on the remaining targets; false returns the error instead
# - source: cheap # always route to the cheapest available target
# strategy: cost
# targets:
# - { model: openai/gpt-4o }
# - { model: groq/llama-3.3-70b }
# - source: production # chain: a target may name another virtual model (acyclic, max 8 deep)
# targets:
# - { model: cheap, weight: 3 }
# - { model: anthropic/claude-sonnet-4-6 }
# - source: gpt-4o # failover: shadow a real model, try it first, then the fallbacks in order
# strategy: failover
# targets:
# - { model: gpt-4o }
# - { model: azure/gpt-4o }
# - { model: gemini/gemini-2.5-pro }
# - source: smart-router # plugin: a routing-strategy plugin picks the target; weights are ignored
# strategy: plugin
# strategy_plugin: cheapest_healthy # built in; GET /admin/plugins lists loaded plugins with kind "route"
# strategy_config: # the plugin's route_fields, validated at startup; defaults apply when omitted
# prefer: cheapest # cheapest | fastest
# max_error_rate: 0.2
# targets:
# - { model: openai/gpt-4o }
# - { model: anthropic/claude-sonnet-4-6 }
# - { model: groq/llama-3.3-70b }
# Users as infrastructure-as-code: per user-path model allowlists (groups and
# users are the user paths behind your API keys). A node with no allowed_models
# keeps every model available; once a node lists models, requests under it are
# limited to that list and to the lists of every parent. These shadow dashboard
# rows with the same path and are read-only in the dashboard. The USERS env var
# (a JSON array) merges over this list and wins per path.
# users:
# - path: /acme
# allowed_models: [openai/*, anthropic/*]
# - path: /acme/eng
# allowed_models: [anthropic/*] # provider wildcard, provider/model, or bare model id; virtual models resolve to their targets first, so list the targets, not the virtual model
# description: Engineering only ships on Claude
# MCP gateway: aggregate upstream MCP (Model Context Protocol) servers behind the
# authenticated /mcp endpoint. Tools/prompts are namespaced as {server}_{name};
# /mcp/{server} exposes one upstream with original names. Servers declared here or
# via the MCP_SERVERS env var (a JSON object merged over this map, winning per name)
# are read-only in the dashboard; the dashboard manages its own additional servers.
# The gateway is the credential boundary: client API keys never reach an upstream,
# and upstream headers support ${ENV} references. `user_paths` limits which callers
# can even discover a server (subtree match, like virtual models).
# mcp:
# enabled: true # env: MCP_ENABLED (default: true; a no-op with no servers)
# # Browser origins allowed to call /mcp. Default: none — MCP clients are not
# # web pages, and refusing browsers is what blocks DNS rebinding (a rebound
# # page's Origin and Host agree with each other, so comparing them proves
# # nothing). Add an origin only if you serve an MCP web client from it.
# allowed_origins: [] # env: MCP_ALLOWED_ORIGINS; "*" disables the check
# # off (default) lists every tool; search lists only search_tools and call_tool,
# # so large catalogs stop costing model context. Clients override it per session
# # with the X-MCP-Tool-Discovery header.
# tool_discovery: off # env: MCP_TOOL_DISCOVERY
# servers:
# github:
# url: https://api.githubcopilot.com/mcp
# transport: http # http (streamable HTTP, default) | sse (legacy) | stdio
# headers:
# Authorization: "Bearer ${GITHUB_PAT}"
# description: GitHub tools
# allowed_tools: [] # allowlist of upstream tool names; empty = all (new upstream tools stay hidden when set)
# disallowed_tools: [] # tools to exclude, applied after the allowlist; edits apply without a reconnect
# user_paths: [] # restrict visibility to these user-path subtrees; empty = everyone
# disallowed_user_paths: [] # hide from these subtrees, even inside user_paths
# tool_timeout: 30s # per tools/call upper bound
# local-files: # stdio servers spawn a subprocess and are declarative-only:
# transport: stdio # the admin API and dashboard reject them by design
# command: npx
# args: ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
# env: # subprocesses inherit only PATH/HOME/TMPDIR/USER/LANG — never the
# SOME_TOKEN: "${SOME_TOKEN}" # gateway's API keys; pass extras here explicitly
# # env: MCP_VIRTUAL_SERVERS. Each virtual server serves a subset of the servers
# # above at /mcp/{name}, with namespaced tool names. Names must not match a
# # server slug. It only narrows: every member keeps its user-path and tool rules.
# virtual_servers:
# coding:
# description: Code review tools
# servers: [github, linear]
# tool_discovery: search # optional; default: mcp.tool_discovery
cache:
model:
refresh_interval: 3600 # how often to refresh the model registry (seconds, default: 3600)
recheck_interval: 60 # env: PROVIDER_RECHECK_INTERVAL; how often providers whose last refresh failed are re-probed for recovery (seconds, default: 60; 0 disables)
# model_list:
# # env: MODEL_LIST_URL. Pricing, context windows, and capabilities. An HTTP
# # URL (default: the public catalog on GitHub), a local file path for
# # air-gapped installs (re-read on each refresh), or "off".
# url: /etc/gomodel/models.json
local:
cache_dir: ".cache" # local cache directory; kept as fallback if redis is configured but unreachable
# Redis is preferred when set. Leave `local` in place to start in degraded mode if Redis is down:
# redis:
# url: "redis://localhost:6379"
# key: "gomodel:models"
# ttl: 86400 # 24 hours in seconds
# response:
# simple: # omit the whole `simple` key to disable exact-match caching (unless RESPONSE_CACHE_SIMPLE_ENABLED=true)
# enabled: true # default when `simple` is present; set false to disable while keeping the block
# redis:
# url: "redis://localhost:6379"
# key: "gomodel:response:"
# ttl: 3600
# semantic: # omit the whole `semantic` key to disable semantic caching (unless SEMANTIC_CACHE_ENABLED=true)
# enabled: true
# embedder:
# provider: openai # must match a key under `providers` (openai, gemini, groq, …)
# model: text-embedding-3-small # optional; provider-specific defaults apply if omitted
# vector_store:
# type: qdrant # required: qdrant | pgvector | pinecone | weaviate (pick one block below)
# qdrant:
# url: "http://localhost:6333"
# collection: "gomodel_semantic"
# api_key: "" # optional for local Qdrant
# # pgvector:
# # url: "postgres://user:pass@localhost:5432/gomodel"
# # table: "gomodel_semantic_cache" # optional; default if omitted
# # dimension: 1536 # must match embedding model output
# # pinecone:
# # host: "https://your-index.svc.region.pinecone.io" # data-plane host
# # api_key: "..."
# # namespace: "" # optional
# # dimension: 1536 # must match your Pinecone index
# # weaviate:
# # url: "http://localhost:8080"
# # class: "GomodelSemanticCache" # PascalCase recommended (GraphQL)
# # api_key: "" # optional
storage:
type: "sqlite" # "sqlite", "postgresql", or "mongodb"
sqlite:
path: "data/gomodel.db"
postgresql:
url: "postgres://user:pass@localhost/gomodel"
max_conns: 10
mongodb:
url: "mongodb://localhost:27017"
database: "gomodel"
# Encrypts dashboard-managed secrets at rest. Prefer GOMODEL_ENCRYPTION_KEY
# from a secret manager over writing the key here.
# encryption_key: "${GOMODEL_ENCRYPTION_KEY}"
# encryption_key_previous: "${GOMODEL_ENCRYPTION_KEY_PREVIOUS}" # only during a key rotation
# Where media files go: audio and images captured for the audit log (see
# logging.log_audio_bodies / log_image_bodies). Metadata stays in storage
# above; only the bytes are written here. Mount a volume at the path in
# containers, or a shared mount (EFS, S3 FUSE) when running several replicas.
media:
storage:
type: "filesystem" # "filesystem" (default) or "memory" (lost on restart)
path: "data/media" # default: data/media when ./data exists, else the OS data dir
logging:
enabled: true
log_bodies: true # WARNING: may contain sensitive data
log_revision_bodies: true # store rewritten bodies from request rewriters and prompt guardrails (needs log_bodies)
# log_guardrail_steps: true # one audit revision per editing prompt guardrail; false records the chain's edits as one revision
# log_audio_bodies: false # embed audio bytes for /v1/audio/* entries (needs log_bodies)
# log_image_bodies: false # embed image bytes for /v1/images/* entries (needs log_bodies)
# log_image_bodies_scope: all # all | input | output
log_headers: true
buffer_size: 1000
flush_interval: 5 # seconds
retention_days: 30 # 0 = keep forever
only_model_interactions: true
usage:
# Usage actions require USAGE_ENABLED=true (or usage.enabled: true) and a supported
# storage backend; pricing recalculation appears only when both usage tracking and
# pricing_recalculation_enabled are enabled.
enabled: true
pricing_recalculation_enabled: true
enforce_returning_usage_data: true
buffer_size: 1000
flush_interval: 5
retention_days: 90
# Billed cost = provider cost + markup, recorded per usage row. Provider cost,
# budgets, and dashboard totals are unaffected. Markups are YAML-only.
pricing:
markups_enabled: false # env: PRICING_MARKUPS_ENABLED; true records billed cost and shows it in the dashboard
bill_cache_hits: true # env: PRICING_BILL_CACHE_HITS; false bills response-cache hits at $0
markups: {} # selector -> percent; most specific wins: "provider/model", "model", "provider/", "/"
# markups:
# "/": 15
# "openai/": 10
# "anthropic/claude-opus-4-1": 25
# GoModel Pro (billing entitlement): per-customer markups by user path.
# The deepest covering path with a matching selector wins, then pricing.markups.
user_path_markups: {}
# user_path_markups:
# /acme:
# "/": 30
# "openai/gpt-5": 15
# /acme/internal:
# "/": 0
budgets:
enabled: true # env: BUDGETS_ENABLED; with no configured budgets this has no effect
# GoModel Pro: provider budgets with cost_basis "billed" count billed
# response-cache hits; false limits only requests the provider served.
provider_billed_cache_hits: true # env: BUDGETS_PROVIDER_BILLED_CACHE_HITS
user_paths:
# Env equivalent:
# SET_BUDGET_USER__PATH__EXAMPLE="daily=10,weekly=50"
- path: "/user/path/example"
per_child: false # GoModel Pro: true gives each direct child an independent budget
limits:
- period: "daily" # hourly, daily, weekly, monthly; stored in DB as period_seconds
amount: 10.00
- period: "weekly"
amount: 50.00
labels:
# Request labels are matched verbatim, so they have no env-var form:
# declare label budgets here or in the dashboard.
- label: "Mobile-App-iOS"
limits:
- period: "monthly"
amount: 500.00
cost_basis: "provider" # GoModel Pro: "billed" limits billed cost (needs pricing.markups_enabled)
providers:
# Caps one provider instance's spend across every user path and label;
# failover skips a provider whose budget is spent.
# Env equivalent: SET_PROVIDER_BUDGET_OPENAI="daily=100"
- name: "openai"
limits:
- period: "daily"
amount: 100.00
rate_limits:
enabled: true # env: RATE_LIMITS_ENABLED; with no configured rules this has no effect
flush_interval: 1 # seconds; env: RATE_LIMITS_FLUSH_INTERVAL; 0 = no periodic snapshot (still load + shutdown flush)
user_paths:
# Env equivalent:
# SET_RATE_LIMIT_USER__PATH__EXAMPLE="rpm=100,tpm=50000,rpd=10000,concurrent=10"
- path: "/user/path/example"
per_child: false # GoModel Pro: true gives each direct child independent counters
limits:
- period: "minute" # minute, hour, day, concurrent; stored in DB as period_seconds
max_requests: 100
max_tokens: 50000 # requires usage tracking (USAGE_ENABLED=true)
- period: "day"
max_requests: 10000
- period: "concurrent" # max_requests caps in-flight requests
max_requests: 10
# Provider rules cap one configured provider across all consumers and
# models. Virtual-model load balancing and failover skip a saturated
# provider while another target has capacity; when no target can take the
# request it gets 429. Env equivalent: SET_PROVIDER_RATE_LIMIT_OPENAI="rpm=500"
providers:
- name: "openai"
limits:
- period: "minute"
max_requests: 500
max_tokens: 200000
- period: "concurrent"
max_requests: 50
# Model rules cap one model: "openai/gpt-4o" pins one provider's model,
# a bare id like "gpt-4o" caps the model across every provider. No env
# form; use YAML or the admin API/dashboard.
models:
- model: "openai/gpt-4o"
limits:
- period: "minute"
max_tokens: 90000
metrics:
enabled: false
endpoint: "/metrics"
# OpenTelemetry export (traces and metrics over OTLP). The settings below
# mirror the standard OTEL_* environment variables; a variable set in the
# environment wins over the YAML value. Less common settings (per-signal
# endpoints, timeouts, compression, …) are available through their OTEL_* variable.
opentelemetry:
enabled: false
service_name: "gomodel"
# resource_attributes:
# deployment.environment: production
endpoint: "http://localhost:4318"
protocol: "http/protobuf" # or "grpc" (default endpoint localhost:4317)
# headers:
# authorization: "Bearer ${OTEL_BACKEND_TOKEN}"
traces_exporter: "otlp" # or "none"
metrics_exporter: "otlp" # or "none"
sampler: "parentbased_always_on" # e.g. "parentbased_traceidratio" with sampler_arg "0.1"
# sampler_arg: "0.1"
propagators: "tracecontext,baggage" # also b3, b3multi, jaeger, ottrace, none
capture_message_content: false # record prompts and completions on provider call spans
http:
timeout: 600 # seconds (10 minutes)
response_header_timeout: 600
stream_idle_timeout: 300 # seconds of silence allowed mid-stream before a stalled stream fails; 0 disables
# Client TLS trust for every outbound HTTPS connection (providers, model
# catalog, update check, MCP upstreams, vector stores). All optional; the
# default trusts the operating system certificate store.
tls:
# ca_file: /etc/gomodel/ca.pem # private CA bundle (PEM), appended to the system store
# client_cert_file: /etc/gomodel/tls.crt # mTLS client certificate; set both files together
# client_key_file: /etc/gomodel/tls.key
# insecure_skip_verify: false # lab use only; logs a warning
workflows:
refresh_interval: 1m
# Global resilience settings (applied to all providers by default)
# Individual providers can override any of these values.
resilience:
retry:
retry_on_statuses: [429, 502, 503, 504, 522, 524] # env: RETRY_ON_STATUSES; [] disables status-based retries
max_retries: 3
initial_backoff: 1s
max_backoff: 30s
backoff_factor: 2.0
jitter_factor: 0.1
circuit_breaker:
failure_on_statuses: [429, 5xx] # env: CIRCUIT_BREAKER_FAILURE_ON_STATUSES; [] disables status-based failures
scope: provider # env: CIRCUIT_BREAKER_SCOPE; model isolates each model within a provider
# Set false to never fail fast on a struggling provider (default: true).
enabled: true
failure_threshold: 5
success_threshold: 2
timeout: 30s
# Count a call answered slower than this as a failure, so sustained latency
# opens the circuit (and fails over) like sustained errors. 0 disables it.
slow_call_threshold: 0s # env: CIRCUIT_BREAKER_SLOW_CALL_THRESHOLD; e.g. 60s
guardrails:
enabled: false
enable_for_batch_processing: false
rules:
# Each entry is a guardrail instance built from a plugin (`type`). Built-in
# plugins: system_prompt, llm_based_altering, string_replace, tag_replace,
# header_edit, llm_judge, presidio; loaded .so plugins are referenced by their manifest
# name.
# Instances are seeded into the guardrails store and referenced by the
# default workflow at `order` in `phase` (prompt | response | stream,
# default prompt). Guardrails with the same "order" run in parallel;
# different orders run sequentially. `config` is validated against the
# plugin's schema (GET /admin/guardrails/types). `fail_mode` (closed |
# open, default closed) and `timeout_ms` (default 0 = none) bound failures.
- name: "safety-prompt"
type: "system_prompt"
order: 0
system_prompt: # typed shorthand; equivalent to config: {mode: ..., content: ...}
mode: "decorator" # "inject", "override", or "decorator"
content: "Always be safe and respectful."
# Example: redact a pattern from responses (response phase, generic config).
# - name: "mask-keys"
# type: "string_replace"
# phase: "response"
# order: 10
# fail_mode: "open"
# config:
# mode: "regex" # literal (default) | regex
# rules: | # one "find => replace" per line (a YAML list also works)
# sk-[A-Za-z0-9]{20,} => [redacted]
# Example: anonymize personal data with a Presidio analyzer sidecar
# (docker run -p 5002:3000 ghcr.io/data-privacy-stack/presidio-analyzer).
# - name: "pii"
# type: "presidio"
# order: 1
# timeout_ms: 5000
# config:
# analyzer_url: "http://localhost:5002"
# entities: ["PERSON", "EMAIL_ADDRESS", "PHONE_NUMBER"]
# block_entities: ["CREDIT_CARD"]
# Example: a second system_prompt instance running at the same order (parallel)
# - name: "compliance-prompt"
# type: "system_prompt"
# order: 0
# system_prompt:
# mode: "inject"
# content: "Follow all compliance rules."
# Names support spaces and unicode characters:
# - name: "проверка безопасности"
# type: "system_prompt"
# order: 0
# system_prompt:
# mode: "decorator"
# content: "Соблюдайте все правила безопасности."
# Example: rewrite user messages with an auxiliary model before the main call.
# The default prompt is a LiteLLM-derived anonymization prompt; override it to
# implement other LLM-based rewrites.
# - name: "privacy-rewrite"
# type: "llm_based_altering"
# user_path: "/team/privacy" # optional base path for internal rewrite calls and audit logs
# order: 1
# timeout_ms: 20000
# llm_based_altering:
# model: "gpt-4o-mini"
# roles: ["user"]
# max_tokens: 4096
# skip_content_prefix: "### safe"
# # prompt: "Custom rewrite instructions here."
# The plugin system: built-in and loaded plugin types, the guardrails built
# from them, routing-strategy plugins for virtual models, and the
# /admin/plugins and /admin/guardrails endpoints. Off by default;
# guardrails.enabled: true turns it on implicitly.
# Shared-object plugins (.so) need a cgo build of GoModel (make build-plugins
# or the gomodel:<version>-plugins image). A .so is trusted code: keep
# search_paths root-owned and pin sha256 in production.
plugins:
enabled: false # PLUGINS_ENABLED. Default: false
search_paths: [] # PLUGINS_SEARCH_PATHS (comma-separated). Default: empty (no .so loading)
load: []
# load:
# - file: keyword_block.so # relative to search_paths, or absolute
# sha256: "" # optional pin; mismatch fails startup
# Failover is a load-balancing behaviour of virtual models: when the target a
# request was sent to fails, the redirect's remaining targets are tried in
# order. Declare a virtual model with `strategy: failover` (see virtual_models
# above) to make a target list a priority list. `enabled: false` switches the
# failover sweep off for every request.
failover:
enabled: true # env: FAILOVER_ENABLED; default true
# How many failover targets one request may try after its first attempt
# fails. 0 (default) sweeps every remaining target in order.
# max_attempts: 0 # env: FAILOVER_MAX_ATTEMPTS
# Upstream HTTP statuses that trigger failover: exact codes or a class such
# as 5xx. A non-empty list replaces the default [429, 5xx] entirely; an
# empty list keeps the default.
# retry_on_statuses: [429, 5xx] # env: FAILOVER_RETRY_ON_STATUSES=429,5xx
# Error phrases that trigger failover whatever the status: every word must
# appear in the error code or message (case-insensitive); a numeric word
# such as 404 or 4xx constrains the status instead. A non-empty list replaces
# the default (an empty one keeps it), which covers "model not found / unsupported / deprecated" style
# errors, aggregator "upstream failed" 4xx relays, and retired-model 404s.
# retry_on_errors: ["model not found", "upstream failed", "404 deprecated"] # env: FAILOVER_RETRY_ON_ERRORS (comma-separated)
# Deprecated: `rules`, `manual_rules_path`, `rules_json`, and `disabled_models`
# still load and are translated into managed failover-strategy virtual models
# that shadow the primary model. Move them under virtual_models.
# rules:
# "gpt-4o":
# - "azure/gpt-4o"
# - "gemini/gemini-2.5-pro"
# Bare <PROVIDER>_* env vars (OPENAI_API_KEY, OPENAI_BASE_URL, ...) override the
# provider that carries the type's name ("openai"). For a provider you renamed
# (alpha: {type: openai}) they only fill fields left empty here; values set
# explicitly are kept and the ignored env vars are logged as a warning at
# startup. To run a second instance of a type, use <PROVIDER>_<SUFFIX>_* env
# vars or another named entry here.
providers:
openai:
type: openai
api_key: "sk-..." # or a secret reference: ${file:/run/secrets/openai}
# Identified sessions stay on one key by default, preserving provider prompt
# cache affinity while still spreading different sessions across keys.
# Equivalent env vars: OPENAI_API_KEY_2, OPENAI_API_KEY_3, ...
# api_keys:
# - "${OPENAI_API_KEY_2}"
# - "${OPENAI_API_KEY_3}"
# Set false for strict per-request round robin (usually worse for prompt caching).
# session_sticky_keys: false
# Send only this provider's traffic through an HTTP(S) or SOCKS5 proxy.
# Providers without one keep the process-wide HTTP_PROXY / HTTPS_PROXY / NO_PROXY.
# Equivalent env var: OPENAI_PROXY_URL
# proxy_url: "socks5://user:pass@10.0.0.1:1080"
# Per-provider resilience overrides (optional).
# Only specified fields override the global defaults above.
# resilience:
# retry:
# max_retries: 5
# circuit_breaker:
# enabled: false
anthropic:
type: anthropic
api_key: "sk-ant-..."
# Workload identity federation instead of api_key: exchange the platform's
# identity token for short-lived access tokens (openai and anthropic only).
# An api_key, when set, wins. Equivalent env vars: ANTHROPIC_FEDERATION_RULE_ID,
# ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID,
# ANTHROPIC_IDENTITY_TOKEN_FILE, ANTHROPIC_WORKSPACE_ID.
# For openai, set identity_provider_id and service_account_id instead.
# workload_identity:
# identity_token_file: /var/run/secrets/anthropic.com/token # re-read on each exchange
# federation_rule_id: fdrl_...
# organization_id: 00000000-0000-0000-0000-000000000000
# service_account_id: svac_...
# workspace_id: wrkspc_...
# ChatGPT subscription (Codex backend). Serves /v1/responses only and is
# billed against your ChatGPT plan. The key is the access token from
# `codex login`: jq -r .tokens.access_token ~/.codex/auth.json
chatgpt:
type: chatgpt
api_key: "${CHATGPT_API_KEY}"
# models: [gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5]
# GitHub Copilot subscription. The key is a GitHub OAuth token for the
# account, e.g. `gh auth token` (see docs/providers/github-copilot.mdx).
github-copilot:
type: github-copilot
api_key: "${GITHUB_COPILOT_API_KEY}"
cohere:
type: cohere
api_key: "${COHERE_API_KEY}"
bailian:
type: bailian
api_key: "${BAILIAN_API_KEY}"
# base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
# Alternative regions (replace {workspace-id} with your workspace):
# Singapore: "https://{workspace-id}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1"
# Frankfurt: "https://{workspace-id}.eu-central-1.maas.aliyuncs.com/compatible-mode/v1"
# Hong Kong: "https://{workspace-id}.cn-hongkong.maas.aliyuncs.com/compatible-mode/v1"
gemini:
type: gemini
api_key: "..."
# Chat/responses, embeddings, and images use Gemini's native APIs by default.
# Set GEMINI_API_MODE=openai_compatible or api_mode: openai_compatible to use
# Gemini's OpenAI-compatible API instead (image edits require native mode).
# Native mode accepts image data URLs but does not fetch remote image URLs yet.
vertex:
type: vertex
auth_type: gcp_adc # or gcp_service_account
vertex_project: "my-gcp-project"
vertex_location: "us-central1"
# service_account_file: "/var/run/secrets/google/service-account.json"
# service_account_json: "${VERTEX_SERVICE_ACCOUNT_JSON}"
# service_account_json_base64: "${VERTEX_SERVICE_ACCOUNT_JSON_BASE64}"
# api_mode: native # or openai_compatible, or anthropic for Claude models
# models:
# - id: "google/gemini-2.5-flash"
xai:
type: xai
api_key: "..."
groq:
type: groq
api_key: "gsk_..."
fireworks:
type: fireworks
api_key: "fw_..."
# models:
# - id: "accounts/fireworks/models/gpt-oss-120b"
deepinfra:
type: deepinfra
api_key: "${DEEPINFRA_API_KEY}"
# base_url defaults to "https://api.deepinfra.com/v1/openai".
together:
type: together
api_key: "${TOGETHER_API_KEY}"
# base_url defaults to "https://api.together.ai/v1".
chutes:
type: chutes
api_key: "${CHUTES_API_KEY}"
# base_url defaults to "https://llm.chutes.ai/v1".
# Set base_url when using a different compatible endpoint.
edenai:
type: edenai
api_key: "${EDENAI_API_KEY}"
# base_url defaults to "https://api.edenai.run/v3".
# Multi-provider gateway: model IDs use provider/model notation and pass
# through unchanged. Models, context windows, capabilities, and per-token
# pricing are discovered from Eden's own /v3/models, and per-request cost
# comes from the exact USD "cost" Eden returns on each response, so no
# pricing metadata needs declaring here.
# GoModel's /v1/responses is served by translating to Eden's chat
# completions; Eden's own /v3/responses is a different API and is never used.
# models:
# - id: "openai/gpt-4"
# - id: "anthropic/claude-sonnet-latest"
elevenlabs:
type: elevenlabs
api_key: "${ELEVENLABS_API_KEY}"
# base_url defaults to "https://api.elevenlabs.io".
# Voice-only provider: text-to-speech and speech-to-text, no chat. The
# OpenAI "voice" field must be an ElevenLabs voice_id.
audiocpp:
type: audiocpp
base_url: "http://localhost:8099"
# Audio-only local server: text-to-speech and speech-to-text, no chat.
# base_url is required (audiocpp_server's default port collides with the
# gateway's own) and a trailing /v1 is accepted. api_key is optional and
# usually unset, since audio.cpp has no authentication of its own.
jev:
type: jev
api_key: "${JEV_API_KEY}"
# base_url defaults to "https://api.typesafe.ai". TypeSafe's System One
# API is a decision API: configuring this provider enables
# POST /v1/decisions, which answers OpenAI Decisions requests by
# translating them to System One, and POST /v1/systemone (also enabled
# by openrouter, which serves Jev natively), which forwards System One
# requests natively (point the TypeSafe SDK at the gateway root). A self-hosted Kev
# server speaks the same API without authentication: set base_url
# (e.g. "http://localhost:8009") and omit api_key. Name it "kev" to see
# that name in logs and usage; no separate provider type is needed.
# Pinned versions such as "jev-1.13.0" route here without being listed.
# Jev is priced per input token and is not in the upstream model catalog;
# declare its pricing here to have the gateway cost System One requests.
# models:
# - id: "jev-1.13.0"
# metadata:
# pricing:
# currency: USD
# input_per_mtok: 0.042
# output_per_mtok: 0
meta:
type: meta
api_key: "..."
# Muse Spark models are not in the upstream model catalog yet; supply
# metadata here to advertise context window and pricing to the gateway.
# models:
# - id: "muse-spark-1.1"
# metadata:
# context_window: 1048576
# pricing:
# currency: USD
# input_per_mtok: 1.25
# output_per_mtok: 4.25
zai:
type: zai
api_key: "..."
# Optional: use GLM Coding Plan endpoint instead of the general endpoint.
# base_url: "https://api.z.ai/api/coding/paas/v4"
xiaomi:
type: xiaomi
api_key: "..."
opencode_go:
type: opencode_go
api_key: "${OPENCODE_GO_API_KEY}"
ollama:
type: ollama
base_url: "http://localhost:11434/v1"
vllm:
type: vllm
base_url: "http://localhost:8000/v1"
# Optional: set this only when vllm serve was started with --api-key.
# api_key: "token-abc123"
# llm-d Router / Endpoint Picker (EPP)
llmd:
type: llmd
base_url: "http://quickstart-epp.llm-d.svc.cluster.local/v1"
# Optional trusted request class injected as llm-d inference headers.
# inference_objective: "standard-traffic"
# Derive the llm-d fairness ID from GoModel's effective user path (default).
fairness_from_user_path: true
# Declare models when the route does not forward GET /v1/models.
models:
- id: "Qwen/Qwen2.5-0.5B-Instruct"
sglang:
type: sglang
base_url: "http://localhost:30000/v1"
# Optional: set this only when launch_server uses --api-key.
# api_key: "token-abc123"
# Responses API path: auto (default) sends requests with function tools
# through chat completions; native always uses /v1/responses;
# chat_compatible always uses chat completions.
# api_mode: auto
# Custom OpenAI-compatible provider
# my-provider:
# type: openai
# base_url: "https://api.example.com/v1"
# api_key: "..."
# Example: LM Studio. Despite being "Ollama-like", LM Studio speaks the
# OpenAI-compatible API (/v1/chat/completions, /v1/embeddings) and has NO
# native Ollama API. Configure it as "openai" (or "vllm"), NOT "ollama" —
# the ollama type sends embeddings to Ollama's native /api/embed, which LM
# Studio does not implement.
# lmstudio:
# type: openai
# base_url: "http://localhost:1234/v1"
# api_key: "lm-studio" # any non-empty value; LM Studio ignores it
# Example: Groq (OpenAI-compatible)
# groq:
# type: "openai"
# base_url: "https://api.groq.com/openai/v1"
# api_key: "${GROQ_API_KEY}"
# Example: OpenRouter with an explicit configured model list.
# In fallback mode (default), this list is used only if upstream /models is
# unavailable or empty. In allowlist mode, only these models are exposed and
# upstream /models is skipped for this provider.
# You can also set OPENROUTER_MODELS="openai/gpt-oss-120b,anthropic/claude-sonnet-4".
# openrouter:
# type: "openrouter"
# base_url: "https://openrouter.ai/api/v1"
# api_key: "${OPENROUTER_API_KEY}"
# models:
# - openai/gpt-oss-120b
# - anthropic/claude-sonnet-4
# # Narrow the inventory to what should be routable. Patterns are globs
# # matched case-insensitively against the raw model ID; `*` also matches `/`,
# # so `*:free` matches `deepseek/deepseek-r1:free`.
# model_filter:
# include: ["*:free"] # keep only these models (empty keeps all)
# exclude: ["*-preview:free"] # then drop these
# # Cap the highest per-million-token rate (the larger of input and output).
# # A model with no known price is dropped while a cap is set. Must be a
# # finite number of 0 or more.
# max_price_per_mtok: 0
# Example: Kilo AI Gateway. Model IDs use provider/model and are forwarded
# unchanged. You can also set KILO_MODELS as a comma-separated env var.
# kilo:
# type: "kilo"
# base_url: "https://api.kilo.ai/api/gateway"
# api_key: "${KILO_API_KEY}"
# models:
# - anthropic/claude-sonnet-4.5
# - openai/gpt-5.5
# Example: Azure OpenAI
# azure:
# type: "azure"
# base_url: "${AZURE_BASE_URL}"
# api_key: "${AZURE_API_KEY}"
# api_version: "2024-10-21"
# Example: Oracle OpenAI-compatible endpoint
# oracle:
# type: "oracle"
# base_url: "${ORACLE_BASE_URL}"
# api_key: "${ORACLE_API_KEY}"
# # Optional when ORACLE_MODELS is set as a comma-separated env var instead.
# models:
# - openai.gpt-oss-120b
# - xai.grok-3
# Example: DeepSeek. GoModel translates /v1/responses requests to DeepSeek
# chat completions because DeepSeek does not expose a native Responses API.
# deepseek:
# type: "deepseek"
# base_url: "https://api.deepseek.com"
# api_key: "${DEEPSEEK_API_KEY}"
# Example: Amazon Bedrock. The provider has no API key — auth is taken from
# the standard AWS credential chain (env vars, AWS_PROFILE, IAM Identity
# Center, container/instance roles). Set base_url to either an AWS region
# ("us-east-1") or a full https endpoint to enable the provider; without it
# gomodel falls back to AWS_REGION / AWS_DEFAULT_REGION.
# bedrock:
# type: "bedrock"
# base_url: "us-east-1"
# # Optional configured model allowlist (model IDs or inference profiles)
# # models:
# # - anthropic.claude-3-5-haiku-20241022-v1:0
# # - amazon.nova-lite-v1:0
# Example: Amazon Bedrock Mantle. This is the OpenAI-compatible Bedrock
# endpoint and is required for Responses-only models such as GPT-5.6.
# Authenticate with api_key or omit it to use the AWS credential chain.
# bedrock-mantle:
# type: "bedrock-mantle"
# base_url: "us-east-1"
# api_key: "${BEDROCK_MANTLE_API_KEY}"
# api_mode: "auto" # auto, openai, or standard
# models:
# - openai.gpt-5.6-sol
# - openai.gpt-5.6-terra
# - openai.gpt-5.6-luna
# Example: local Ollama server with explicit per-model metadata.
# Use the rich entry form to declare context_window, pricing, capabilities,
# etc. for models that aren't in the upstream ai-model-list registry. Plain
# string entries still work and produce unenriched models as before.
# Declared fields are merged onto any remote-registry entry; operator values
# win per-field.
# nippur:
# type: "ollama"
# base_url: "http://127.0.0.1:8080/v1"
# models:
# - id: GLM-4.7-Flash
# metadata:
# display_name: "GLM 4.7 Flash (local)"
# context_window: 131072
# max_output_tokens: 8192
# modes: ["chat"]
# capabilities:
# tools: true
# pricing:
# currency: USD
# input_per_mtok: 0
# output_per_mtok: 0
# - Gemma4-31B
# Extensions are absent by default. Add a named section only when a custom
# distribution requires it. Core preserves these values without depending on
# the extension's schema; the owning extension strictly validates its section.
# extensions:
# example:
# enabled: true
# Version awareness. Checks https://gomodel.enterpilot.io/version once a day —
# and again on the first dashboard visit of each day — so operators learn that a
# newer release exists. Sends the running version, the distribution name, a random
# per-deployment install id, and the visiting browser's user agent and language.
# Never sends API keys, model names, prompts, usage data, client addresses, or
# the dashboard hostname. Set enabled: false to stop all outbound traffic.
# Air-gapped installs: disables the update check and drops HTTP model catalog
# URLs in one step. Configured providers, declared endpoints, and a catalog
# given as a local file path are unaffected. Default: false
# offline: false
# version_check:
# enabled: true