This guide is for OpenHands Enterprise operators who want to chain the
built-in LiteLLM to an external gateway. If you are using OpenHands Cloud or
the OSS build and want to point OpenHands at your own LiteLLM proxy directly,
see LiteLLM Proxy instead. That path
does not involve the built-in LiteLLM.
Overview
OHE does not point the OpenHands runtime directly at an external gateway. Instead, the built-in LiteLLM forwards requests to the external gateway, which in turn forwards to the actual LLM provider:- OHE never needs credentials for the underlying LLM providers.
- Your gateway keeps full control of provider keys, routing rules, cost tracking, and audit logs.
- Only one secret is exchanged: an API key or virtual key for your gateway, which the built-in LiteLLM uses to authenticate.
What you need from the gateway owner
For each model you want to expose to OHE, you need three pieces of information from whoever administers the external gateway:
No provider credentials, AWS keys, or Azure endpoints are needed on the OHE
side. Those all stay on the external gateway.
Prerequisites
Before you start, confirm:-
OHE is installed and reachable. You can sign in at
https://app.<your-base-domain>. -
The external gateway is reachable from the OHE cluster. The built-in
LiteLLM pod makes outbound HTTP/S calls to the gateway, so DNS and network
paths must resolve from inside the
openhandsnamespace. -
You have the built-in LiteLLM master key. This is needed for the admin
API path (testing only) and for verifying the config. Retrieve it with:
- You have cluster access to edit Helm values or apply config changes, and can restart the LiteLLM pod.
Configure the built-in LiteLLM
There are two ways to add gateway-forwarding models to the built-in LiteLLM. For production, use the Helm values. Use the admin API only for light testing. It does not survive pod restarts or upgrades and is not recommended for regular use.Option 1: Admin API (testing only)
GET /v1/models and are usable
right away. No pod restart is needed.
Option 2: Helm values (production)
For production, add model entries to the OpenHands Helm chart’sproxy_config.model_list. These survive pod restarts and cluster upgrades.
- Replicated (VM/embedded cluster)
- Standalone Helm
- Open the Replicated admin console at
https://<admin-console-host>:30000. - Navigate to the LiteLLM config section and edit the
model_listYAML. - Add one entry per model (see the config snippets in Gateway-specific configuration below).
- Save and deploy. Replicated will roll the LiteLLM pod with the new config.
Gateway-specific configuration
Themodel and api_base fields differ depending on whether the external
gateway is LiteLLM or Bifrost.
LiteLLM as the external gateway
Use thelitellm_proxy/ model prefix. This tells the built-in LiteLLM to
forward to another LiteLLM instance and preserve LiteLLM-specific features
(virtual key headers, spend tracking, team/org metadata).
The
api_base should not include /v1. LiteLLM appends the
/v1/chat/completions path automatically.Bifrost as the external gateway
Use theopenai/ model prefix. Bifrost is OpenAI-compatible, so the built-in
LiteLLM treats it as an OpenAI-compatible endpoint.
api_basemust include/v1. Bifrost does not auto-append it.- The model name on Bifrost uses the
provider/modelconvention (for example,anthropic/claude-sonnet-4-5-20250929), so the fullmodelfield becomesopenai/anthropic/claude-sonnet-4-5-20250929.
Multi-model gateways
Gateways typically host many models across different providers, sizes, and routing rules. There are two patterns for exposing them to OHE.Pattern A: Explicit per-model entries (recommended)
Add onemodel_list entry per model you want to expose. Each entry maps a
friendly name (what OHE users see in the dropdown) to a model on the external
gateway. This works identically for LiteLLM and Bifrost gateways.
api_base and use the same api_key.
Only the upstream model name differs. OHE users see three models in the
dropdown: claude-sonnet-4-5, claude-haiku-4-5, gpt-4o.
This pattern is explicit, easy to audit, and gives you control over which
models are exposed and what they are named.
Pattern B: Wildcard passthrough (not recommended)
LiteLLM supports a wildcard model entry that forwards any model name to the upstream gateway without pre-declaring each one:- The OHE model dropdown becomes unusable.
GET /v1/modelson the built-in LiteLLM returns 200+ entries: the explicitly configured models, a literal*, and the entire LiteLLM internal OpenAI model registry (models likeopenai/gpt-4o,openai/gpt-5, and so on). These OpenAI models do not exist on the external gateway. They are LiteLLM’s known model names, auto-populated because of theopenai/*prefix. Users see a flooded dropdown where most entries fail when selected. - Users must type the exact
provider/modelformat. A call toclaude-opus-4-8fails with a 400 error. A call toanthropic/claude-opus-4-8succeeds and is forwarded to the gateway. The user must know the gateway’s model naming convention in advance. - Typo protection moves to the gateway. Unknown model names are forwarded verbatim and rejected by the external gateway, not by the built-in LiteLLM.
Model discovery
OHE discovers available models by callingGET /v1/models on the built-in
LiteLLM. This endpoint returns every model in the model_list, both those in
the Helm config and any added via the admin API for testing.
Verified capabilities
The following OHE agent capabilities have been tested and confirmed working through both LiteLLM and Bifrost external gateways:Identity and cost attribution
A common reason to chain through an external gateway is cost attribution and audit: the gateway owner needs to know which OpenHands user, team, or project generated each LLM call so they can route spend to the right cost center. This section is a set of recipes. Pick the one that matches your scenario.What the OpenHands runtime sends by default
The runtime calls the built-in LiteLLM using the OpenAI Python SDK. By default the request carries:- Standard OpenAI SDK headers (
x-stainless-*,authorization). - An OpenAI
userfield in the request body, set to the OpenHands user identifier. The built-in LiteLLM records this in its own spend logs but does not forward it to the upstream gateway in the request body.
X-OpenHands-User-Id or similar identity header is attached
automatically. Everything below adds attribution to that baseline.
Recipe 1: Per-team attribution with per-key model entries
Use when you have a small number of teams or projects and want the external gateway to attribute spend by API key. How. Create one API key per team on the external gateway. Add one model entry per key in the built-in LiteLLM config:Authorization: Bearer <team_key> header. Standard gateway spend reporting by key.
Limits.
- No header forwarding or runtime changes needed.
- Does not scale to many users because each user needs their own entry and key. Best for a small number of teams or projects.
Recipe 2: Per-user or per-profile attribution with extra_headers
Use when you want each LLM call from a specific OpenHands user
or team to carry identity headers the gateway can read. Works for
both web UI and API conversations.
How. Two steps.
-
Enable header forwarding on the built-in LiteLLM. In your Helm
values or Replicated config:
In the Replicated admin console this is the Enable Forwarding Client Headers Through LiteLLM to LLM Providers checkbox under Advanced Options.
-
Set
extra_headerson the LLM profile. In the OpenHands web UI, open Settings, LLM, Advanced Options, and edit the Extra Headers field. Or POST to the profile API:
extra_headers. Users
select their own profile from the profile dropdown.
What appears at the gateway. Every LLM call from a conversation
using this profile arrives with the headers you set. The gateway
reads them and attributes spend accordingly.
Verified.
- The
extra_headersfield is exposed on the LLM profile schema in the OHE app and persists through the profile API round-trip. - The SDK forwards
llm.extra_headersto LiteLLM on every call. - The built-in LiteLLM forwards headers starting with
x-(andanthropic-*, excludingx-stainless-*) to the upstream gateway whenforward_client_headers_to_llm_api: true. Tested end-to-end with a capture service standing in for the upstream gateway.
- Headers are static per profile, not per user, so per-user attribution scales with the number of profiles.
- The header name
x-litellm-session-idis reserved by the SDK for conversation tracing (see Trace calls back to a conversation). Setting that key inextra_headersis overwritten at call time.
Recipe 3: Static gateway auth headers with custom_llm_extra_headers
Use when the external gateway requires a static auth or routing
header on every request, and your LLM provider setting is Custom LLM.
How.
- In the Replicated admin console, set LLM Provider to Custom LLM.
- Under Advanced Options, enable Custom LLM Extra HTTP Headers.
-
Enter a JSON object mapping header names to values:
- Deploy. The built-in LiteLLM injects these headers on every outbound request to the gateway.
- Gated on the Custom LLM provider. Not available for Anthropic, OpenAI, Bedrock, Azure, or Vertex provider settings.
- Static values, same for every user. Not a per-user attribution mechanism.
- Values are rendered as plaintext in the LiteLLM ConfigMap.
Recipe 4: LiteLLM spend log metadata
Use when the external gateway is also LiteLLM and you want structured metadata (user, project, cost center) captured on both the built-in and upstream LiteLLM spend logs, so you can query and join them. How. Enable header forwarding as in Recipe 2. Then set thex-litellm-spend-logs-metadata header on the LLM profile’s
extra_headers. LiteLLM parses this header as a JSON string and
stores it in the spend log row:
- Only LiteLLM gateways interpret the JSON natively. Bifrost sees the header but does not parse it.
- The value is a JSON string, not a nested object. Serialize before
putting it in
extra_headers.
Recipe 5: Batch reconciliation with conversation tags
Use when you can reconcile gateway spend with OpenHands conversations after the fact and do not need per-call attribution visible at the gateway. How. Tag conversations with your external identifiers when you start them via the API. Tag keys must be lowercase alphanumeric (no underscores or hyphens); values are strings up to 256 characters:Choosing a recipe
Recipes are not mutually exclusive. A common combination is Recipe 1
(per-team keys) plus Recipe 2 (per-user headers within a team).
Trace calls back to a conversation
Independent of attribution, the SDK stamps every LLM request withx-litellm-session-id: <conversation_id>. When
forward_client_headers_to_llm_api: true, this header reaches the
external gateway. It is useful for:
- Correlating a spend log row on the gateway to the OpenHands conversation that produced it.
- Joining logs across the built-in and external LiteLLM instances.
- Debugging which conversation is generating traffic.
Security notes
- The external gateway key is stored as a Kubernetes secret in the OHE cluster. Limit access to that secret to the LiteLLM pod’s service account.
- The built-in LiteLLM logs request and response metadata (model, token counts, latency) but not prompt or response content by default. The external gateway is the place to enforce content-level audit logging if needed.
- If the external gateway is outside the OHE cluster, use HTTPS and ensure the LiteLLM pod can resolve and reach the gateway’s DNS name.
Troubleshooting
Model not found (404 from built-in LiteLLM)
Model not found (404 from built-in LiteLLM)
- Verify the model appears in
GET /v1/modelson the built-in LiteLLM. - If added via admin API, check the response from
/model/newfor errors. - If added via Helm values, verify the pod restarted after the values change.
401 from external gateway
401 from external gateway
- Verify the
api_keyinlitellm_paramsis a valid key on the external gateway. - For Bifrost, check that
enforceAuthOnInferenceis eitherfalse(for testing) or that a valid virtual key is configured.
400 model not found from external gateway
400 model not found from external gateway
The
model field in litellm_params must match what the external gateway
expects:- For LiteLLM gateways: use the
model_namefrom the gateway’s config, for examplelitellm_proxy/claude-sonnet-4-5-20250929. - For Bifrost: use
provider/model, for exampleopenai/anthropic/claude-sonnet-4-5-20250929.
Tool calls not working
Tool calls not working
- Verify the model supports tool/function calling (some smaller models do not).
- Test directly against the external gateway (bypass the built-in LiteLLM) to isolate whether the issue is in the gateway or the chaining.
Model dropdown shows hundreds of OpenAI models I did not configure
Model dropdown shows hundreds of OpenAI models I did not configure
This means a wildcard (
model_name: "*") entry is in the model_list.
The openai/* prefix causes LiteLLM to auto-populate its internal OpenAI
model registry into /v1/models. Remove the wildcard entry and use
explicit per-model entries (Pattern A) instead.Reference
- OpenHands LLM configuration overview: LLM Configuration
- LiteLLM proxy (OSS/Cloud path, no built-in LiteLLM): LiteLLM Proxy
- LiteLLM model config reference: LiteLLM docs
- Bifrost configuration reference: Bifrost docs

