gomodel migrate litellm does most of it.
1. Convert the config
Run a dry run first. It prints the migration report and the generated config, and writes nothing:--out writes three files and refuses to overwrite them unless you pass
--force:
os.environ/NAME references become ${NAME}, so the environment you already
run LiteLLM with keeps working. include: files are followed.
2. Review the report
ReadMIGRATION_REPORT.md before sending traffic. It has three lists:
- Review before switching traffic: things you must finish, such as guardrails, model access groups, or a Langfuse callback. GoModel guardrails are off until you configure them.
- Not migrated: settings with no GoModel equivalent, named one by one. Nothing is dropped silently.
- Behavior changes: settings that were converted but behave a little differently, such as routing strategies.
3. Run GoModel next to LiteLLM
Start GoModel with the generated files on another port. Add the variables listed under Environment in the report (the ones LiteLLM already read withos.environ/) to .env, creating it if the converter wrote none. GoModel
refuses to start while the variable server.master_key reads is unset. Then
send a few test requests with the models your clients use:
compose.yaml
.env with Compose env_file (or GoModel’s own .env loader when you
run the binary from that directory). docker run --env-file reads values
literally, so it would keep the quotes .env puts around values such as
inline JSON credentials. When you run the binary directly, a variable already
exported in your shell, such as OPENAI_API_KEY, wins over the same name in
.env.
GET /v1/models lists the same model names LiteLLM did. Then move clients
over one team at a time.
4. Import virtual keys
LiteLLM stores each virtual key as a SHA-256 hash in its Postgres database. GoModel imports those hashes, so clients keep theirsk-... keys and GoModel
never handles the keys themselves. This loop imports every key that is not
blocked or expired, with its alias, expiry, and the models LiteLLM let it call.
A team key becomes user path /<team alias>:
LITELLM_DATABASE_URL set to the
DATABASE_URL LiteLLM uses. It is safe to re-run: keys already imported answer
409 auth_key_exists.
- Model access follows LiteLLM’s rules: a team key gets its own model list, else its team’s; a personal key gets its own list narrowed by its user’s. The result becomes the key’s allowed models. A personal key that may call no model is skipped.
- GoModel checks the model a virtual model resolves to, so the import stores
the provider models behind each name, such as
openai/gpt-4oforgpt-4o. Import keys after GoModel runs with the converted config. If you later point a virtual model at other models, update the allowed models of the keys that use it; until then they cannot reach the new models. - Keys blocked or expired in LiteLLM are not imported. Block a key in GoModel by deactivating it on the API Keys page.
POST /admin/auth-keys/import
describes the request. Tokens beginning with sk- are matched only against
imported keys.
5. Recreate budgets and rate limits
LiteLLM’s budgets, rate limits, and spend stay in its database. GoModel models the same ideas on a user path hierarchy:
Create them in the dashboard or with the admin API.
What changes for clients
How settings map
Provider names follow GoModel’s environment conventions: a deployment using
LiteLLM’s default key variable (
OPENAI_API_KEY) becomes provider openai,
one reading OPENAI_EU_API_KEY becomes openai-eu, and each Azure deployment
becomes its own provider, such as azure-gpt-4o-prod.
Providers GoModel does not support yet, such as Replicate or SageMaker, are
listed under Not migrated in the report.