ai-switch

English | 中文
A lightweight local proxy that lets any AI CLI tool (Claude Code, Codex CLI, etc.) use third-party LLM APIs through a unified local endpoint.
One binary, one config, any AI CLI → any LLM API.
Features
- Multi-protocol: Auto-detects client protocol (Responses API, Anthropic Messages, Chat Completions) and converts transparently
- Zero-intrusion: No changes to your CLI config files, just point
base_url to the local proxy
- Scene routing: Route Claude Code requests to different models based on request type (thinking, web search, background tasks)
- Model mapping: Map client model names to upstream model names at the route level
- Cross-provider routing: Route different scenes to different providers (e.g. thinking → DeepSeek, web search → Zhipu)
- Hot reload: Update config without restart (
POST /api/reload or kill -HUP)
- Admin UI: Built-in web dashboard for managing providers and routes
- Lightweight: Pure Go, single binary, no CGO
Installation
git clone https://github.com/keepmind9/ai-switch.git
cd ai-switch
make build-all # build frontend + Go binary (includes Admin UI)
If you don't need the Admin UI, use make build instead (Go only, faster).
Quick Start
1. Create config
cp config.example.yaml config.yaml
Edit config.yaml with your provider credentials:
server:
host: "0.0.0.0"
port: 12345
default_route: "gw-default"
providers:
deepseek:
name: "DeepSeek"
base_url: "https://api.deepseek.com/v1"
api_key: "${DEEPSEEK_API_KEY}"
format: "chat"
routes:
"gw-default":
provider: "deepseek"
default_model: "deepseek-chat"
Note: base_url can include /v1 or not — just copy it from your provider's documentation.
2. Start the server
./bin/server -c config.yaml
Claude Code:
export ANTHROPIC_BASE_URL=http://localhost:12345
export ANTHROPIC_API_KEY=gw-default
Codex CLI:
[model_providers.proxy]
name = "ai-switch"
base_url = "http://localhost:12345/v1"
api_key = "gw-default"
wire_api = "responses"
Any OpenAI-compatible tool:
export OPENAI_BASE_URL=http://localhost:12345/v1
export OPENAI_API_KEY=gw-default
That's it — your CLI tool will now route requests through ai-switch to your configured provider.
How It Works
Claude Code ──→ ai-switch ──→ DeepSeek (chat)
Codex CLI ──→ ──→ Zhipu (anthropic)
Any tool ──→ ──→ MiniMax (chat)
ai-switch sits between your CLI tool and upstream LLM providers. It:
- Detects the client protocol automatically (Anthropic / Responses / Chat)
- Routes requests to the correct provider based on the API key (route key)
- Converts between protocols when needed (e.g. Anthropic → Chat Completions)
- Detects request scenes (thinking, web search, etc.) for smart routing
The route key (gw-default in the example above) serves as both the API key for authentication and the routing identifier.
Configuration
Providers
Define your upstream LLM vendor connections:
providers:
deepseek:
name: "DeepSeek"
base_url: "https://api.deepseek.com/v1"
api_key: "${DEEPSEEK_API_KEY}" # supports ${ENV_VAR} expansion
format: "chat" # chat (default) | responses | anthropic
think_tag: "think" # optional: strip reasoning tags from responses
models: # optional: for validation warnings
- "deepseek-chat"
- "deepseek-reasoner"
Routes
Routes map API keys to providers and models:
routes:
"gw-default":
provider: "deepseek"
default_model: "deepseek-chat"
Scene Map
Route Claude Code requests to different models based on what it's doing:
routes:
"gw-claude":
provider: "zhipu"
default_model: "glm-5.1"
long_context_threshold: 60000
scene_map:
default: "glm-5.1"
think: "glm-5.1"
websearch: "glm-4.7"
background: "glm-4.5-air"
longContext: "glm-5.1"
| Scene |
Key |
Detection |
| Long Context |
longContext |
Token count exceeds long_context_threshold |
| Background |
background |
Model name contains "haiku" |
| Web Search |
websearch |
Tools contain web_search_* type |
| Thinking |
think |
thinking field present |
| Image |
image |
User messages contain image blocks |
| Default |
default |
Fallback |
Priority: longContext > background > websearch > think > image > default
Model Map
Map client model names to upstream models:
routes:
"gw-default":
provider: "deepseek"
default_model: "deepseek-chat"
model_map:
"claude-sonnet-4-5": "deepseek-chat"
"gpt-4o": "deepseek-chat"
Cross-Provider Routing
Use provider:model to route to a different provider within the same route:
routes:
"gw-default":
provider: "minimax"
default_model: "MiniMax-M2.5"
scene_map:
default: "MiniMax-M2.5"
think: "deepseek:deepseek-chat"
websearch: "zhipu:glm-4.7"
Model Resolution Priority
- ModelMap — exact model name match (case-insensitive)
- SceneMap — scene detection (Anthropic protocol only)
- DefaultModel — fallback
CLI
ai-switch serve -c config.yaml # Start the server
ai-switch check -c config.yaml # Validate config without starting
Running without a subcommand defaults to serve:
ai-switch -c config.yaml # Same as: ai-switch serve -c config.yaml
Config validation
$ ai-switch check -c config.yaml
Checking config.yaml ...
Providers: 3
Routes: 3
Default: gw-default
✓ Config is valid.
Exit codes: 0 = valid, 1 = has errors, 2 = warnings only.
Admin UI
Open http://localhost:12345 in your browser for a built-in dashboard to manage providers, routes, and view usage statistics.
Build
make build # fmt + vet + compile
make build-all # build frontend + Go binary
make dev # run in dev mode
make test # run tests
make clean # remove binary
License
MIT