This commit is contained in:
musistudio
2026-01-01 22:11:21 +08:00
parent e7608ada4a
commit 4c5a84e028
17 changed files with 2092 additions and 388 deletions

View File

@@ -6,15 +6,29 @@ title: Basic Configuration
CLI uses the same configuration file as Server: `~/.claude-code-router/config.json`
## Configuration File Location
## Configuration Methods
You can configure Claude Code Router in three ways:
### Option 1: Edit Configuration File Directly
Edit `~/.claude-code-router/config.json` with your favorite editor:
```bash
~/.claude-code-router/config.json
nano ~/.claude-code-router/config.json
```
## Quick Configuration
### Option 2: Use Web UI
Use interactive command to configure:
Open the web interface and configure visually:
```bash
ccr ui
```
### Option 3: Interactive Configuration
Use the interactive command-line configuration:
```bash
ccr model
@@ -26,16 +40,23 @@ This will guide you through:
3. Select model
4. Set routing rules
## Manual Configuration
## Restart After Configuration Changes
### Edit Configuration File
After modifying the configuration file or making changes through the Web UI, you must restart the service:
```bash
# Open configuration file
nano ~/.claude-code-router/config.json
ccr restart
```
### Minimal Configuration Example
Or restart directly through the Web UI.
## Configuration File Location
```bash
~/.claude-code-router/config.json
```
## Minimal Configuration Example
```json5
{
@@ -129,14 +150,16 @@ Configuration is automatically backed up on each update:
~/.claude-code-router/config.backup.{timestamp}.json
```
## Reload Configuration
## Apply Configuration Changes
Restart service after modifying configuration:
After modifying the configuration file or making changes through the Web UI, restart the service:
```bash
ccr restart
```
Or restart directly through the Web UI by clicking the "Save and Restart" button.
## View Current Configuration
```bash

View File

@@ -26,27 +26,49 @@ npm install -g @musistudio/claude-code-router
## Basic Usage
### Start Service
### Configuration
Before using Claude Code Router, you need to configure your providers. You can either:
1. **Edit configuration file directly**: Edit `~/.claude-code-router/config.json` manually
2. **Use Web UI**: Run `ccr ui` to open the web interface and configure visually
After making configuration changes, restart the service:
```bash
ccr start
ccr restart
```
### View Status
Or restart directly through the Web UI.
### Start Claude Code
Once configured, you can start Claude Code with:
```bash
ccr status
ccr code
```
### Stop Service
This will launch Claude Code and route your requests through the configured provider.
### Service Management
```bash
ccr stop
ccr start # Start the router service
ccr status # View service status
ccr stop # Stop the router service
ccr restart # Restart the router service
```
### Web UI
```bash
ccr ui # Open Web management interface
```
## Configuration File
`ccr` uses the same configuration file as Server: `~/.claude-code-router/config.json`
`ccr` uses the configuration file at `~/.claude-code-router/config.json`
Configure once, and both CLI and Server will use it.

View File

@@ -6,7 +6,41 @@ sidebar_position: 3
Get up and running with Claude Code Router in 5 minutes.
## 1. Start the Router
## 1. Configure the Router
Before using Claude Code Router, you need to configure your LLM providers. You can either:
### Option A: Edit Configuration File Directly
Edit `~/.claude-code-router/config.json`:
```json
{
"HOST": "0.0.0.0",
"PORT": 8080,
"Providers": [
{
"name": "openai",
"api_base_url": "https://api.openai.com/v1",
"api_key": "your-api-key-here",
"models": ["gpt-4", "gpt-3.5-turbo"]
}
],
"Router": {
"default": "openai,gpt-4"
}
}
```
### Option B: Use Web UI
```bash
ccr ui
```
This will open the web interface where you can configure providers visually.
## 2. Start the Router
```bash
ccr start
@@ -14,7 +48,7 @@ ccr start
The router will start on `http://localhost:8080` by default.
## 2. Use Claude Code
## 3. Use Claude Code
Now you can use Claude Code normally:
@@ -24,8 +58,18 @@ ccr code
Your requests will be routed through Claude Code Router to your configured provider.
## Restart After Configuration Changes
If you modify the configuration file or make changes through the Web UI, restart the service:
```bash
ccr restart
```
Or restart directly through the Web UI.
## What's Next?
- [Basic Configuration](/docs/config/basic) - Learn about configuration options
- [Routing](/docs/config/routing) - Configure smart routing rules
- [CLI Commands](/docs/cli/start) - Explore all CLI commands
- [Basic Configuration](/docs/cli/config/basic) - Learn about configuration options
- [Routing](/docs/cli/config/routing) - Configure smart routing rules
- [CLI Commands](/docs/category/cli-commands) - Explore all CLI commands

View File

@@ -81,6 +81,115 @@ Route image-related tasks:
}
```
## Fallback
When a request fails, you can configure a list of backup models. The system will try each model in sequence until one succeeds:
### Basic Configuration
```json
{
"Router": {
"default": "deepseek,deepseek-chat",
"background": "ollama,qwen2.5-coder:latest",
"think": "deepseek,deepseek-reasoner",
"longContext": "openrouter,google/gemini-2.5-pro-preview",
"longContextThreshold": 60000,
"webSearch": "gemini,gemini-2.5-flash"
},
"fallback": {
"default": [
"aihubmix,Z/glm-4.5",
"openrouter,anthropic/claude-sonnet-4"
],
"background": [
"ollama,qwen2.5-coder:latest"
],
"think": [
"openrouter,anthropic/claude-3.7-sonnet:thinking"
],
"longContext": [
"modelscope,Qwen/Qwen3-Coder-480B-A35B-Instruct"
],
"webSearch": [
"openrouter,anthropic/claude-sonnet-4"
]
}
}
```
### How It Works
1. **Trigger**: When a model request fails for a routing scenario (HTTP error response)
2. **Auto-switch**: The system automatically checks the fallback configuration for that scenario
3. **Sequential retry**: Tries each backup model in order
4. **Success**: Once a model responds successfully, returns immediately
5. **All failed**: If all backup models fail, returns the original error
### Configuration Details
- **Format**: Each backup model format is `provider,model`
- **Validation**: Backup models must exist in the `Providers` configuration
- **Flexibility**: Different scenarios can have different fallback lists
- **Optional**: If a scenario doesn't need fallback, omit it or use an empty array
### Use Cases
#### Scenario 1: Primary Model Quota Exhausted
```json
{
"Router": {
"default": "openrouter,anthropic/claude-sonnet-4"
},
"fallback": {
"default": [
"deepseek,deepseek-chat",
"aihubmix,Z/glm-4.5"
]
}
}
```
Automatically switches to backup models when the primary model quota is exhausted.
#### Scenario 2: Service Reliability
```json
{
"Router": {
"background": "volcengine,deepseek-v3-250324"
},
"fallback": {
"background": [
"modelscope,Qwen/Qwen3-Coder-480B-A35B-Instruct",
"dashscope,qwen3-coder-plus"
]
}
}
```
Automatically switches to other providers when the primary service fails.
### Log Monitoring
The system logs detailed fallback process:
```
[warn] Request failed for default, trying 2 fallback models
[info] Trying fallback model: aihubmix,Z/glm-4.5
[warn] Fallback model aihubmix,Z/glm-4.5 failed: API rate limit exceeded
[info] Trying fallback model: openrouter,anthropic/claude-sonnet-4
[info] Fallback model openrouter,anthropic/claude-sonnet-4 succeeded
```
### Important Notes
1. **Cost consideration**: Backup models may incur different costs, configure appropriately
2. **Performance differences**: Different models may have varying response speeds and quality
3. **Quota management**: Ensure backup models have sufficient quotas
4. **Testing**: Regularly test the availability of backup models
## Project-Level Routing
Configure routing per project in `~/.claude/projects/<project-id>/claude-code-router.json`:

View File

@@ -4,7 +4,152 @@ sidebar_position: 4
# Transformers
Transformers adapt API differences between providers.
Transformers are the core mechanism for adapting API differences between LLM providers. They convert requests and responses between different formats, handle authentication, and manage provider-specific features.
## Understanding Transformers
### What is a Transformer?
A transformer is a plugin that:
- **Transforms requests** from the unified format to provider-specific format
- **Transforms responses** from provider format back to unified format
- **Handles authentication** for provider APIs
- **Modifies requests** to add or adjust parameters
### Data Flow
```
┌─────────────────┐
│ Incoming Request│ (Anthropic format from Claude Code)
└────────┬────────┘
┌─────────────────────────────────┐
│ transformRequestOut │ ← Parse incoming request to unified format
└────────┬────────────────────────┘
┌─────────────────────────────────┐
│ UnifiedChatRequest │
└────────┬────────────────────────┘
┌─────────────────────────────────┐
│ transformRequestIn (optional) │ ← Modify unified request before sending
└────────┬────────────────────────┘
┌─────────────────────────────────┐
│ Provider API Call │
└────────┬────────────────────────┘
┌─────────────────────────────────┐
│ transformResponseIn (optional) │ ← Convert provider response to unified format
└────────┬────────────────────────┘
┌─────────────────────────────────┐
│ transformResponseOut (optional)│ ← Convert unified response to Anthropic format
└────────┬────────────────────────┘
┌─────────────────┐
│ Outgoing Response│ (Anthropic format to Claude Code)
└─────────────────┘
```
### Transformer Interface
All transformers implement the following interface:
```typescript
interface Transformer {
// Convert unified request to provider-specific format
transformRequestIn?: (
request: UnifiedChatRequest,
provider: LLMProvider,
context: TransformerContext
) => Promise<Record<string, any>>;
// Convert provider request to unified format
transformRequestOut?: (
request: any,
context: TransformerContext
) => Promise<UnifiedChatRequest>;
// Convert provider response to unified format
transformResponseIn?: (
response: Response,
context?: TransformerContext
) => Promise<Response>;
// Convert unified response to provider format
transformResponseOut?: (
response: Response,
context: TransformerContext
) => Promise<Response>;
// Custom endpoint path (optional)
endPoint?: string;
// Transformer name (for custom transformers)
name?: string;
// Custom authentication handler (optional)
auth?: (
request: any,
provider: LLMProvider,
context: TransformerContext
) => Promise<any>;
// Logger instance (auto-injected)
logger?: any;
}
```
### Key Types
#### UnifiedChatRequest
```typescript
interface UnifiedChatRequest {
messages: UnifiedMessage[];
model: string;
max_tokens?: number;
temperature?: number;
stream?: boolean;
tools?: UnifiedTool[];
tool_choice?: any;
reasoning?: {
effort?: ThinkLevel; // "none" | "low" | "medium" | "high"
max_tokens?: number;
enabled?: boolean;
};
}
```
#### UnifiedMessage
```typescript
interface UnifiedMessage {
role: "user" | "assistant" | "system" | "tool";
content: string | null | MessageContent[];
tool_calls?: Array<{
id: string;
type: "function";
function: {
name: string;
arguments: string;
};
}>;
tool_call_id?: string;
thinking?: {
content: string;
signature?: string;
};
}
```
## Built-in Transformers
@@ -23,6 +168,12 @@ Transforms requests to be compatible with Anthropic-style APIs:
}
```
**Features:**
- Converts Anthropic message format to/from OpenAI format
- Handles tool calls and tool results
- Supports thinking/reasoning content blocks
- Manages streaming responses
### deepseek
Specialized transformer for DeepSeek API:
@@ -38,6 +189,11 @@ Specialized transformer for DeepSeek API:
}
```
**Features:**
- DeepSeek-specific reasoning format
- Handles `reasoning_content` in responses
- Supports thinking budget tokens
### gemini
Transformer for Google Gemini API:
@@ -53,39 +209,381 @@ Transformer for Google Gemini API:
}
```
### groq
### maxtoken
Transformer for Groq API:
Limits max_tokens in requests:
```json
{
"transformers": [
{
"name": "groq",
"providers": ["groq"]
"name": "maxtoken",
"options": {
"max_tokens": 8192
},
"models": ["deepseek,deepseek-chat"]
}
]
}
```
### openrouter
### customparams
Transformer for OpenRouter API:
Injects custom parameters into requests:
```json
{
"transformers": [
{
"name": "openrouter",
"providers": ["openrouter"]
"name": "customparams",
"options": {
"include_reasoning": true,
"custom_header": "value"
}
}
]
}
```
## Creating Custom Transformers
### Simple Transformer: Modifying Requests
The simplest transformers just modify the request before it's sent to the provider.
**Example: Add a custom header to all requests**
```javascript
// custom-header-transformer.js
module.exports = class CustomHeaderTransformer {
name = 'custom-header';
constructor(options) {
this.headerName = options?.headerName || 'X-Custom-Header';
this.headerValue = options?.headerValue || 'default-value';
}
async transformRequestIn(request, provider, context) {
// Add custom header (will be used by auth method)
request._customHeaders = {
[this.headerName]: this.headerValue
};
return request;
}
async auth(request, provider) {
const headers = {
'authorization': `Bearer ${provider.apiKey}`,
...request._customHeaders
};
return {
body: request,
config: { headers }
};
}
};
```
**Usage in config:**
```json
{
"transformers": [
{
"name": "custom-header",
"path": "/path/to/custom-header-transformer.js",
"options": {
"headerName": "X-My-Header",
"headerValue": "my-value"
}
}
]
}
```
### Intermediate Transformer: Request/Response Conversion
This example shows how to convert between different API formats.
**Example: Mock API format transformer**
```javascript
// mockapi-transformer.js
module.exports = class MockAPITransformer {
name = 'mockapi';
endPoint = '/v1/chat'; // Custom endpoint
// Convert from MockAPI format to unified format
async transformRequestOut(request, context) {
const messages = request.conversation.map(msg => ({
role: msg.sender,
content: msg.text
}));
return {
messages,
model: request.model_id,
max_tokens: request.max_tokens,
temperature: request.temp
};
}
// Convert from unified format to MockAPI format
async transformRequestIn(request, provider, context) {
return {
model_id: request.model,
conversation: request.messages.map(msg => ({
sender: msg.role,
text: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
})),
max_tokens: request.max_tokens || 4096,
temp: request.temperature || 0.7
};
}
// Convert MockAPI response to unified format
async transformResponseIn(response, context) {
const data = await response.json();
const unifiedResponse = {
id: data.request_id,
object: 'chat.completion',
created: data.timestamp,
model: data.model,
choices: [{
index: 0,
message: {
role: 'assistant',
content: data.reply.text
},
finish_reason: data.stop_reason
}],
usage: {
prompt_tokens: data.tokens.input,
completion_tokens: data.tokens.output,
total_tokens: data.tokens.input + data.tokens.output
}
};
return new Response(JSON.stringify(unifiedResponse), {
status: response.status,
statusText: response.statusText,
headers: { 'Content-Type': 'application/json' }
});
}
};
```
### Advanced Transformer: Streaming Response Processing
This example shows how to handle streaming responses.
**Example: Add custom metadata to streaming responses**
```javascript
// streaming-metadata-transformer.js
module.exports = class StreamingMetadataTransformer {
name = 'streaming-metadata';
constructor(options) {
this.metadata = options?.metadata || {};
this.logger = null; // Will be injected by the system
}
async transformResponseOut(response, context) {
const contentType = response.headers.get('Content-Type');
// Handle streaming response
if (contentType?.includes('text/event-stream')) {
return this.transformStream(response, context);
}
// Handle non-streaming response
return response;
}
async transformStream(response, context) {
const decoder = new TextDecoder();
const encoder = new TextEncoder();
const transformedStream = new ReadableStream({
start: async (controller) => {
const reader = response.body.getReader();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.trim() || !line.startsWith('data: ')) {
controller.enqueue(encoder.encode(line + '\n'));
continue;
}
const data = line.slice(6).trim();
if (data === '[DONE]') {
controller.enqueue(encoder.encode(line + '\n'));
continue;
}
try {
const chunk = JSON.parse(data);
// Add custom metadata
if (chunk.choices && chunk.choices[0]) {
chunk.choices[0].metadata = this.metadata;
}
// Log for debugging
this.logger?.debug({
chunk,
context: context.req.id
}, 'Transformed streaming chunk');
const modifiedLine = `data: ${JSON.stringify(chunk)}\n\n`;
controller.enqueue(encoder.encode(modifiedLine));
} catch (parseError) {
// If parsing fails, pass through original line
controller.enqueue(encoder.encode(line + '\n'));
}
}
}
} catch (error) {
this.logger?.error({ error }, 'Stream transformation error');
controller.error(error);
} finally {
controller.close();
reader.releaseLock();
}
}
});
return new Response(transformedStream, {
status: response.status,
statusText: response.statusText,
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
};
```
### Real-World Example: Reasoning Content Transformer
This is based on the actual `reasoning.transformer.ts` from the codebase.
```typescript
// reasoning-transformer.ts
import { Transformer, TransformerOptions } from "@musistudio/llms";
export class ReasoningTransformer implements Transformer {
static TransformerName = "reasoning";
enable: boolean;
constructor(private readonly options?: TransformerOptions) {
this.enable = this.options?.enable ?? true;
}
// Transform request to add reasoning parameters
async transformRequestIn(request: UnifiedChatRequest): Promise<UnifiedChatRequest> {
if (!this.enable) {
request.thinking = {
type: "disabled",
budget_tokens: -1,
};
request.enable_thinking = false;
return request;
}
if (request.reasoning) {
request.thinking = {
type: "enabled",
budget_tokens: request.reasoning.max_tokens,
};
request.enable_thinking = true;
}
return request;
}
// Transform response to convert reasoning_content to thinking format
async transformResponseOut(response: Response): Promise<Response> {
if (!this.enable) return response;
const contentType = response.headers.get("Content-Type");
// Handle non-streaming response
if (contentType?.includes("application/json")) {
const jsonResponse = await response.json();
if (jsonResponse.choices[0]?.message.reasoning_content) {
jsonResponse.thinking = {
content: jsonResponse.choices[0].message.reasoning_content
};
}
return new Response(JSON.stringify(jsonResponse), {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}
// Handle streaming response
if (contentType?.includes("stream")) {
// [Streaming transformation code here]
// See the full implementation in the codebase
}
return response;
}
}
```
## Transformer Registration
### Method 1: Static Name (Class-based)
Use this when creating a transformer in TypeScript/ES6:
```typescript
export class MyTransformer implements Transformer {
static TransformerName = "my-transformer";
async transformRequestIn(request: UnifiedChatRequest): Promise<any> {
// Transformation logic
return request;
}
}
```
### Method 2: Instance Name (Instance-based)
Use this for JavaScript transformers:
```javascript
module.exports = class MyTransformer {
constructor(options) {
this.name = 'my-transformer';
this.options = options;
}
async transformRequestIn(request, provider, context) {
// Transformation logic
return request;
}
};
```
## Applying Transformers
### Global Application
### Global Application (Provider Level)
Apply to all requests for a provider:
@@ -104,7 +602,7 @@ Apply to all requests for a provider:
### Model-Specific Application
Apply to specific models:
Apply to specific models only:
```json
{
@@ -120,9 +618,26 @@ Apply to specific models:
}
```
Note: The model format is `provider,model` (e.g., `deepseek,deepseek-chat`).
### Global Transformers (All Providers)
Apply transformers to all providers:
```json
{
"transformers": [
{
"name": "custom-logger",
"path": "/path/to/custom-logger.js"
}
]
}
```
### Passing Options
Some transformers accept options:
Some transformers accept configuration options:
```json
{
@@ -132,45 +647,144 @@ Some transformers accept options:
"options": {
"max_tokens": 8192
}
},
{
"name": "customparams",
"options": {
"custom_param_1": "value1",
"custom_param_2": 42
}
}
]
}
```
## Custom Transformers
## Best Practices
Create custom transformer plugins:
### 1. Immutability
1. Create a transformer file:
Always create new objects rather than mutating existing ones:
```javascript
module.exports = {
name: 'my-transformer',
transformRequest: async (req, config) => {
// Modify request
return req;
},
transformResponse: async (res, config) => {
// Modify response
return res;
}
};
```
// Bad
async transformRequestIn(request) {
request.max_tokens = 4096;
return request;
}
2. Load in configuration:
```json
{
"transformers": [
{
"name": "my-transformer",
"path": "/path/to/transformer.js"
}
]
// Good
async transformRequestIn(request) {
return {
...request,
max_tokens: request.max_tokens || 4096
};
}
```
### 2. Error Handling
Always handle errors gracefully:
```javascript
async transformResponseIn(response) {
try {
const data = await response.json();
// Process data
return new Response(JSON.stringify(processedData), {
status: response.status,
headers: response.headers
});
} catch (error) {
this.logger?.error({ error }, 'Transformation failed');
// Return original response if transformation fails
return response;
}
}
```
### 3. Logging
Use the injected logger for debugging:
```javascript
async transformRequestIn(request, provider, context) {
this.logger?.debug({
model: request.model,
provider: provider.name
}, 'Transforming request');
// Your transformation logic
return modifiedRequest;
}
```
### 4. Stream Handling
When handling streams, always:
- Use a buffer to handle incomplete chunks
- Properly release the reader lock
- Handle errors in the stream
- Close the controller when done
```javascript
const transformedStream = new ReadableStream({
start: async (controller) => {
const reader = response.body.getReader();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Process stream...
}
} catch (error) {
controller.error(error);
} finally {
controller.close();
reader.releaseLock();
}
}
});
```
### 5. Context Usage
The `context` parameter contains useful information:
```javascript
async transformRequestIn(request, provider, context) {
// Access request ID
const requestId = context.req.id;
// Access original request
const originalRequest = context.req.original;
// Your transformation logic
}
```
## Testing Your Transformer
### Manual Testing
1. Add your transformer to the config
2. Start the server: `ccr restart`
3. Check logs: `tail -f ~/.claude-code-router/logs/ccr-*.log`
4. Make a test request
5. Verify the output
### Debug Tips
- Add logging to track transformation steps
- Test with both streaming and non-streaming requests
- Verify error handling with invalid inputs
- Check that original responses are returned on error
## Next Steps
- [Advanced Topics](/docs/advanced/custom-router) - Advanced routing customization
- [Agents](/docs/advanced/agents) - Extending with agents
- [Advanced Topics](/docs/server/advanced/custom-router) - Advanced routing customization
- [Agents](/docs/server/advanced/agents) - Extending with agents
- [Core Package](/docs/server/intro) - Learn about @musistudio/llms

View File

@@ -15,17 +15,103 @@ Claude Code Router Server is a core service component responsible for routing Cl
## Architecture Overview
```
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Claude Code │────▶│ CCR Server │────▶│ LLM Provider │
│ Client │ │ (Router + │ │ (OpenAI/ │
└─────────────┘ │ Transformer) │ │ Gemini/etc)│
└──────────────────┘ └──────────────┘
┌─────────────┐ ┌─────────────────────────────┐ ┌──────────────┐
│ Claude Code │────▶│ CCR Server │────▶│ LLM Provider │
│ Client │ │ ┌─────────────────────┐ │ │ (OpenAI/ │
└─────────────┘ │ │ @musistudio/llms │ │ │ Gemini/etc)│
│ │ (Core Package) │ │ └──────────────┘
│ │ - Request Transform │ │
│ │ - Response Transform │ │
│ │ - Auth Handling │ │
│ └─────────────────────┘ │
│ │
│ - Routing Logic │
│ - Agent System │
│ - Configuration │
└─────────────────────────────┘
├─ Web UI
├─ Config API
└─ Logs API
```
## Core Package: @musistudio/llms
The server is built on top of **@musistudio/llms**, a universal LLM API transformation library that provides the core request/response transformation capabilities.
### What is @musistudio/llms?
`@musistudio/llms` is a standalone npm package (`@musistudio/llms`) that handles:
- **API Format Conversion**: Transforms between different LLM provider APIs (Anthropic, OpenAI, Gemini, etc.)
- **Request/Response Transformation**: Converts requests and responses to a unified format
- **Authentication Handling**: Manages different authentication methods across providers
- **Streaming Support**: Handles streaming responses from different providers
- **Transformer System**: Provides an extensible architecture for adding new providers
### Key Concepts
#### 1. Unified Request/Response Format
The core package defines a unified format (`UnifiedChatRequest`, `UnifiedChatResponse`) that abstracts away provider-specific differences:
```typescript
interface UnifiedChatRequest {
messages: UnifiedMessage[];
model: string;
max_tokens?: number;
temperature?: number;
stream?: boolean;
tools?: UnifiedTool[];
tool_choice?: any;
reasoning?: {
effort?: ThinkLevel;
max_tokens?: number;
enabled?: boolean;
};
}
```
#### 2. Transformer Interface
All transformers implement a common interface:
```typescript
interface Transformer {
transformRequestIn?: (request: UnifiedChatRequest, provider: LLMProvider, context: TransformerContext) => Promise<any>;
transformRequestOut?: (request: any, context: TransformerContext) => Promise<UnifiedChatRequest>;
transformResponseIn?: (response: Response, context?: TransformerContext) => Promise<Response>;
transformResponseOut?: (response: Response, context: TransformerContext) => Promise<Response>;
endPoint?: string;
name?: string;
auth?: (request: any, provider: LLMProvider, context: TransformerContext) => Promise<any>;
}
```
#### 3. Built-in Transformers
The core package includes transformers for:
- **anthropic**: Anthropic API format
- **openai**: OpenAI API format
- **gemini**: Google Gemini API format
- **deepseek**: DeepSeek API format
- **groq**: Groq API format
- **openrouter**: OpenRouter API format
- And more...
### Integration with CCR Server
The CCR server integrates `@musistudio/llms` through:
1. **Transformer Service** (`packages/core/src/services/transformer.ts`): Manages transformer registration and instantiation
2. **Provider Configuration**: Maps provider configs to core package's LLMProvider interface
3. **Request Pipeline**: Applies transformers in sequence during request processing
4. **Custom Transformers**: Supports loading external transformer plugins
### Version and Updates
The current version of `@musistudio/llms` is `1.0.51`. It's published as an independent npm package and can be used standalone or as part of CCR Server.
## Core Features
### 1. Request Routing

View File

@@ -81,35 +81,7 @@ const config: Config = {
},
footer: {
style: 'light',
links: [
{
title: 'Docs',
items: [
{
label: 'Tutorial',
to: '/docs/intro',
},
],
},
{
title: 'Community',
items: [
{
label: 'GitHub',
href: 'https://github.com/musistudio/claude-code-router',
},
],
},
{
title: 'More',
items: [
{
label: 'Blog',
to: '/blog',
},
],
},
],
links: [],
copyright: `Copyright © ${new Date().getFullYear()} Claude Code Router. Built with Docusaurus.`,
},
prism: {

View File

@@ -27,15 +27,15 @@
"message": "服务器 API 接口文档",
"description": "The generated-index page description for category 'API Reference' in sidebar 'tutorialSidebar'"
},
"sidebar.tutorialSidebar.category.Configuration": {
"sidebar.tutorialSidebar.category.server-configuration-category": {
"message": "配置",
"description": "The label for category 'Configuration' in sidebar 'tutorialSidebar'"
},
"sidebar.tutorialSidebar.category.Configuration.link.generated-index.title": {
"sidebar.tutorialSidebar.category.server-configuration-category.link.generated-index.title": {
"message": "服务器配置",
"description": "The generated-index page title for category 'Configuration' in sidebar 'tutorialSidebar'"
},
"sidebar.tutorialSidebar.category.Configuration.link.generated-index.description": {
"sidebar.tutorialSidebar.category.server-configuration-category.link.generated-index.description": {
"message": "服务器配置说明",
"description": "The generated-index page description for category 'Configuration' in sidebar 'tutorialSidebar'"
},
@@ -74,5 +74,17 @@
"sidebar.tutorialSidebar.category.Commands.link.generated-index.description": {
"message": "完整的命令参考",
"description": "The generated-index page description for category 'Commands' in sidebar 'tutorialSidebar'"
},
"sidebar.tutorialSidebar.category.cli-configuration-category": {
"message": "配置",
"description": "The label for category 'Configuration' in sidebar 'tutorialSidebar'"
},
"sidebar.tutorialSidebar.category.cli-configuration-category.link.generated-index.title": {
"message": "CLI 配置",
"description": "The generated-index page title for category 'Configuration' in sidebar 'tutorialSidebar'"
},
"sidebar.tutorialSidebar.category.cli-configuration-category.link.generated-index.description": {
"message": "CLI 配置指南",
"description": "The generated-index page description for category 'Configuration' in sidebar 'tutorialSidebar'"
}
}

View File

@@ -82,6 +82,115 @@ sidebar_position: 3
}
```
## 故障转移Fallback
当请求失败时,可以配置备用模型列表。系统会按顺序尝试每个模型,直到请求成功:
### 基本配置
```json
{
"Router": {
"default": "deepseek,deepseek-chat",
"background": "ollama,qwen2.5-coder:latest",
"think": "deepseek,deepseek-reasoner",
"longContext": "openrouter,google/gemini-2.5-pro-preview",
"longContextThreshold": 60000,
"webSearch": "gemini,gemini-2.5-flash"
},
"fallback": {
"default": [
"aihubmix,Z/glm-4.5",
"openrouter,anthropic/claude-sonnet-4"
],
"background": [
"ollama,qwen2.5-coder:latest"
],
"think": [
"openrouter,anthropic/claude-3.7-sonnet:thinking"
],
"longContext": [
"modelscope,Qwen/Qwen3-Coder-480B-A35B-Instruct"
],
"webSearch": [
"openrouter,anthropic/claude-sonnet-4"
]
}
}
```
### 工作原理
1. **触发条件**当某个路由场景的模型请求失败时HTTP 错误响应)
2. **自动切换**:系统自动检查该场景的 fallback 配置
3. **顺序尝试**:按照列表顺序依次尝试每个备用模型
4. **成功返回**:一旦某个模型成功响应,立即返回结果
5. **全部失败**:如果所有备用模型都失败,返回原始错误
### 配置说明
- **格式**:每个备用模型格式为 `provider,model`
- **验证**:备用模型必须在 `Providers` 配置中存在
- **灵活性**:可以为不同场景配置不同的备用列表
- **可选性**:如果某个场景不需要备用,可以不配置或使用空数组
### 使用场景
#### 场景一:主模型配额不足
```json
{
"Router": {
"default": "openrouter,anthropic/claude-sonnet-4"
},
"fallback": {
"default": [
"deepseek,deepseek-chat",
"aihubmix,Z/glm-4.5"
]
}
}
```
当主模型配额用完时,自动切换到备用模型。
#### 场景二:服务稳定性保障
```json
{
"Router": {
"background": "volcengine,deepseek-v3-250324"
},
"fallback": {
"background": [
"modelscope,Qwen/Qwen3-Coder-480B-A35B-Instruct",
"dashscope,qwen3-coder-plus"
]
}
}
```
当主服务商出现故障时,自动切换到其他服务商。
### 日志监控
系统会记录详细的 fallback 过程:
```
[warn] Request failed for default, trying 2 fallback models
[info] Trying fallback model: aihubmix,Z/glm-4.5
[warn] Fallback model aihubmix,Z/glm-4.5 failed: API rate limit exceeded
[info] Trying fallback model: openrouter,anthropic/claude-sonnet-4
[info] Fallback model openrouter,anthropic/claude-sonnet-4 succeeded
```
### 注意事项
1. **成本考虑**:备用模型可能产生不同的费用,请合理配置
2. **性能差异**:不同模型的响应速度和质量可能有差异
3. **配额管理**:确保备用模型有足够的配额
4. **测试验证**:定期测试备用模型的可用性
## 项目级路由
`~/.claude/projects/<project-id>/claude-code-router.json` 中为每个项目配置路由:

View File

@@ -5,7 +5,152 @@ sidebar_position: 4
# 转换器
转换器用于适配不同提供商之间的 API 差异
转换器适配不同 LLM 提供商 API 差异的核心机制。它们在不同格式之间转换请求和响应,处理认证,并管理提供商特定的功能
## 理解转换器
### 什么是转换器?
转换器是一个插件,它可以:
- **转换请求**:从统一格式转换为提供商特定格式
- **转换响应**:从提供商格式转换回统一格式
- **处理认证**:为提供商 API 处理认证
- **修改请求**:添加或调整参数
### 数据流
```
┌─────────────────┐
│ 传入请求 │ (来自 Claude Code 的 Anthropic 格式)
└────────┬────────┘
┌─────────────────────────────────┐
│ transformRequestOut │ ← 将传入请求解析为统一格式
└────────┬────────────────────────┘
┌─────────────────────────────────┐
│ UnifiedChatRequest │
└────────┬────────────────────────┘
┌─────────────────────────────────┐
│ transformRequestIn (可选) │ ← 在发送前修改统一请求
└────────┬────────────────────────┘
┌─────────────────────────────────┐
│ 提供商 API 调用 │
└────────┬────────────────────────┘
┌─────────────────────────────────┐
│ transformResponseIn (可选) │ ← 将提供商响应转换为统一格式
└────────┬────────────────────────┘
┌─────────────────────────────────┐
│ transformResponseOut (可选) │ ← 将统一响应转换为 Anthropic 格式
└────────┬────────────────────────┘
┌─────────────────┐
│ 传出响应 │ (返回给 Claude Code 的 Anthropic 格式)
└─────────────────┘
```
### 转换器接口
所有转换器都实现以下接口:
```typescript
interface Transformer {
// 将统一请求转换为提供商特定格式
transformRequestIn?: (
request: UnifiedChatRequest,
provider: LLMProvider,
context: TransformerContext
) => Promise<Record<string, any>>;
// 将提供商请求转换为统一格式
transformRequestOut?: (
request: any,
context: TransformerContext
) => Promise<UnifiedChatRequest>;
// 将提供商响应转换为统一格式
transformResponseIn?: (
response: Response,
context?: TransformerContext
) => Promise<Response>;
// 将统一响应转换为提供商格式
transformResponseOut?: (
response: Response,
context: TransformerContext
) => Promise<Response>;
// 自定义端点路径(可选)
endPoint?: string;
// 转换器名称(用于自定义转换器)
name?: string;
// 自定义认证处理器(可选)
auth?: (
request: any,
provider: LLMProvider,
context: TransformerContext
) => Promise<any>;
// Logger 实例(自动注入)
logger?: any;
}
```
### 关键类型
#### UnifiedChatRequest
```typescript
interface UnifiedChatRequest {
messages: UnifiedMessage[];
model: string;
max_tokens?: number;
temperature?: number;
stream?: boolean;
tools?: UnifiedTool[];
tool_choice?: any;
reasoning?: {
effort?: ThinkLevel; // "none" | "low" | "medium" | "high"
max_tokens?: number;
enabled?: boolean;
};
}
```
#### UnifiedMessage
```typescript
interface UnifiedMessage {
role: "user" | "assistant" | "system" | "tool";
content: string | null | MessageContent[];
tool_calls?: Array<{
id: string;
type: "function";
function: {
name: string;
arguments: string;
};
}>;
tool_call_id?: string;
thinking?: {
content: string;
signature?: string;
};
}
```
## 内置转换器
@@ -15,13 +160,20 @@ sidebar_position: 4
```json
{
"transformer": {
"use": ["anthropic"]
}
"transformers": [
{
"name": "anthropic",
"providers": ["deepseek", "groq"]
}
]
}
```
如果只使用这一个转换器,它将直接透传请求和响应(您可以用来接入其他支持 Anthropic 端点的服务商)。
**功能:**
- 在 Anthropic 消息格式和 OpenAI 格式之间转换
- 处理工具调用和工具结果
- 支持思考/推理内容块
- 管理流式响应
### deepseek
@@ -29,169 +181,421 @@ sidebar_position: 4
```json
{
"transformer": {
"use": ["deepseek"]
}
"transformers": [
{
"name": "deepseek",
"providers": ["deepseek"]
}
]
}
```
**功能:**
- DeepSeek 特定的推理格式
- 处理响应中的 `reasoning_content`
- 支持思考预算令牌
### gemini
用于 Google Gemini API 的转换器:
```json
{
"transformer": {
"use": ["gemini"]
}
}
```
### groq
用于 Groq API 的转换器:
```json
{
"transformer": {
"use": ["groq"]
}
}
```
### openrouter
用于 OpenRouter API 的转换器:
```json
{
"transformer": {
"use": ["openrouter"]
}
}
```
OpenRouter 转换器还支持 `provider` 路由参数,以指定 OpenRouter 应使用哪些底层提供商:
```json
{
"transformer": {
"use": ["openrouter"],
"moonshotai/kimi-k2": {
"use": [
["openrouter", {
"provider": {
"only": ["moonshotai/fp8"]
}
}]
]
"transformers": [
{
"name": "gemini",
"providers": ["gemini"]
}
}
]
}
```
### maxtoken
设置特定的 `max_tokens`
限制请求中的 max_tokens
```json
{
"transformer": {
"use": [
["maxtoken", { "max_tokens": 65536 }]
]
"transformers": [
{
"name": "maxtoken",
"options": {
"max_tokens": 8192
},
"models": ["deepseek,deepseek-chat"]
}
]
}
```
### customparams
向请求中注入自定义参数:
```json
{
"transformers": [
{
"name": "customparams",
"options": {
"include_reasoning": true,
"custom_header": "value"
}
}
]
}
```
## 创建自定义转换器
### 简单转换器:修改请求
最简单的转换器只修改发送到提供商之前的请求。
**示例:为所有请求添加自定义头**
```javascript
// custom-header-transformer.js
module.exports = class CustomHeaderTransformer {
name = 'custom-header';
constructor(options) {
this.headerName = options?.headerName || 'X-Custom-Header';
this.headerValue = options?.headerValue || 'default-value';
}
async transformRequestIn(request, provider, context) {
// 添加自定义头(将被 auth 方法使用)
request._customHeaders = {
[this.headerName]: this.headerValue
};
return request;
}
async auth(request, provider) {
const headers = {
'authorization': `Bearer ${provider.apiKey}`,
...request._customHeaders
};
return {
body: request,
config: { headers }
};
}
};
```
**在配置中使用:**
```json
{
"transformers": [
{
"name": "custom-header",
"path": "/path/to/custom-header-transformer.js",
"options": {
"headerName": "X-My-Header",
"headerValue": "my-value"
}
}
]
}
```
### 中级转换器:请求/响应转换
此示例展示如何在不同 API 格式之间转换。
**示例Mock API 格式转换器**
```javascript
// mockapi-transformer.js
module.exports = class MockAPITransformer {
name = 'mockapi';
endPoint = '/v1/chat'; // 自定义端点
// 从 MockAPI 格式转换为统一格式
async transformRequestOut(request, context) {
const messages = request.conversation.map(msg => ({
role: msg.sender,
content: msg.text
}));
return {
messages,
model: request.model_id,
max_tokens: request.max_tokens,
temperature: request.temp
};
}
// 从统一格式转换为 MockAPI 格式
async transformRequestIn(request, provider, context) {
return {
model_id: request.model,
conversation: request.messages.map(msg => ({
sender: msg.role,
text: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
})),
max_tokens: request.max_tokens || 4096,
temp: request.temperature || 0.7
};
}
// 将 MockAPI 响应转换为统一格式
async transformResponseIn(response, context) {
const data = await response.json();
const unifiedResponse = {
id: data.request_id,
object: 'chat.completion',
created: data.timestamp,
model: data.model,
choices: [{
index: 0,
message: {
role: 'assistant',
content: data.reply.text
},
finish_reason: data.stop_reason
}],
usage: {
prompt_tokens: data.tokens.input,
completion_tokens: data.tokens.output,
total_tokens: data.tokens.input + data.tokens.output
}
};
return new Response(JSON.stringify(unifiedResponse), {
status: response.status,
statusText: response.statusText,
headers: { 'Content-Type': 'application/json' }
});
}
};
```
### 高级转换器:流式响应处理
此示例展示如何处理流式响应。
**示例:向流式响应添加自定义元数据**
```javascript
// streaming-metadata-transformer.js
module.exports = class StreamingMetadataTransformer {
name = 'streaming-metadata';
constructor(options) {
this.metadata = options?.metadata || {};
this.logger = null; // 将由系统注入
}
async transformResponseOut(response, context) {
const contentType = response.headers.get('Content-Type');
// 处理流式响应
if (contentType?.includes('text/event-stream')) {
return this.transformStream(response, context);
}
// 处理非流式响应
return response;
}
async transformStream(response, context) {
const decoder = new TextDecoder();
const encoder = new TextEncoder();
const transformedStream = new ReadableStream({
start: async (controller) => {
const reader = response.body.getReader();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.trim() || !line.startsWith('data: ')) {
controller.enqueue(encoder.encode(line + '\n'));
continue;
}
const data = line.slice(6).trim();
if (data === '[DONE]') {
controller.enqueue(encoder.encode(line + '\n'));
continue;
}
try {
const chunk = JSON.parse(data);
// 添加自定义元数据
if (chunk.choices && chunk.choices[0]) {
chunk.choices[0].metadata = this.metadata;
}
// 记录日志以便调试
this.logger?.debug({
chunk,
context: context.req.id
}, '转换流式数据块');
const modifiedLine = `data: ${JSON.stringify(chunk)}\n\n`;
controller.enqueue(encoder.encode(modifiedLine));
} catch (parseError) {
// 如果解析失败,透传原始行
controller.enqueue(encoder.encode(line + '\n'));
}
}
}
} catch (error) {
this.logger?.error({ error }, '流式转换错误');
controller.error(error);
} finally {
controller.close();
reader.releaseLock();
}
}
});
return new Response(transformedStream, {
status: response.status,
statusText: response.statusText,
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
};
```
### 真实示例:推理内容转换器
这是基于代码库中实际的 `reasoning.transformer.ts`
```typescript
// reasoning-transformer.ts
import { Transformer, TransformerOptions } from "@musistudio/llms";
export class ReasoningTransformer implements Transformer {
static TransformerName = "reasoning";
enable: boolean;
constructor(private readonly options?: TransformerOptions) {
this.enable = this.options?.enable ?? true;
}
// 转换请求以添加推理参数
async transformRequestIn(request: UnifiedChatRequest): Promise<UnifiedChatRequest> {
if (!this.enable) {
request.thinking = {
type: "disabled",
budget_tokens: -1,
};
request.enable_thinking = false;
return request;
}
if (request.reasoning) {
request.thinking = {
type: "enabled",
budget_tokens: request.reasoning.max_tokens,
};
request.enable_thinking = true;
}
return request;
}
// 转换响应以将 reasoning_content 转换为 thinking 格式
async transformResponseOut(response: Response): Promise<Response> {
if (!this.enable) return response;
const contentType = response.headers.get("Content-Type");
// 处理非流式响应
if (contentType?.includes("application/json")) {
const jsonResponse = await response.json();
if (jsonResponse.choices[0]?.message.reasoning_content) {
jsonResponse.thinking = {
content: jsonResponse.choices[0].message.reasoning_content
};
}
return new Response(JSON.stringify(jsonResponse), {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}
// 处理流式响应
if (contentType?.includes("stream")) {
// [流式转换代码在这里]
// 参见代码库中的完整实现
}
return response;
}
}
```
### tooluse
## 转换器注册
通过 `tool_choice` 参数优化某些模型的工具使用:
### 方法 1静态名称基于类
```json
{
"transformer": {
"use": ["tooluse"]
在 TypeScript/ES6 中创建转换器时使用:
```typescript
export class MyTransformer implements Transformer {
static TransformerName = "my-transformer";
async transformRequestIn(request: UnifiedChatRequest): Promise<any> {
// 转换逻辑
return request;
}
}
```
### reasoning
### 方法 2实例名称基于实例
用于处理 `reasoning_content` 字段
用于 JavaScript 转换器
```json
{
"transformer": {
"use": ["reasoning"]
```javascript
module.exports = class MyTransformer {
constructor(options) {
this.name = 'my-transformer';
this.options = options;
}
}
```
### sampling
用于处理采样信息字段,如 `temperature``top_p``top_k``repetition_penalty`
```json
{
"transformer": {
"use": ["sampling"]
async transformRequestIn(request, provider, context) {
// 转换逻辑
return request;
}
}
```
### enhancetool
对 LLM 返回的工具调用参数增加一层容错处理(注意:这会导致不再流式返回工具调用信息):
```json
{
"transformer": {
"use": ["enhancetool"]
}
}
```
### cleancache
清除请求中的 `cache_control` 字段:
```json
{
"transformer": {
"use": ["cleancache"]
}
}
```
### vertex-gemini
处理使用 Vertex 鉴权的 Gemini API
```json
{
"transformer": {
"use": ["vertex-gemini"]
}
}
};
```
## 应用转换器
### 全局应用
### 全局应用(提供商级别)
应用于提供商的所有请求:
提供商的所有请求应用
```json
{
"Providers": [
{
"name": "deepseek",
"api_base_url": "https://api.deepseek.com/chat/completions",
"api_key": "your-api-key",
"transformer": {
"use": ["deepseek"]
}
"NAME": "deepseek",
"HOST": "https://api.deepseek.com",
"APIKEY": "your-api-key",
"transformers": ["anthropic"]
}
]
}
@@ -199,84 +603,189 @@ OpenRouter 转换器还支持 `provider` 路由参数,以指定 OpenRouter 应
### 模型特定应用
应用于特定模型:
```json
{
"name": "deepseek",
"transformer": {
"use": ["deepseek"],
"deepseek-chat": {
"use": ["tooluse"]
}
}
}
```
### 传递选项
某些转换器接受选项:
```json
{
"transformer": {
"use": [
["maxtoken", { "max_tokens": 8192 }]
]
}
}
```
## 自定义转换器
创建自定义转换器插件:
1. 创建转换器文件:
```javascript
module.exports = {
name: 'my-transformer',
transformRequest: async (req, config) => {
// 修改请求
return req;
},
transformResponse: async (res, config) => {
// 修改响应
return res;
}
};
```
2. 在配置中加载:
应用于特定模型:
```json
{
"transformers": [
{
"path": "/path/to/transformer.js",
"name": "maxtoken",
"options": {
"key": "value"
"max_tokens": 8192
},
"models": ["deepseek,deepseek-chat"]
}
]
}
```
注意:模型格式为 `provider,model`(例如 `deepseek,deepseek-chat`)。
### 全局转换器(所有提供商)
将转换器应用于所有提供商:
```json
{
"transformers": [
{
"name": "custom-logger",
"path": "/path/to/custom-logger.js"
}
]
}
```
### 传递选项
某些转换器接受配置选项:
```json
{
"transformers": [
{
"name": "maxtoken",
"options": {
"max_tokens": 8192
}
},
{
"name": "customparams",
"options": {
"custom_param_1": "value1",
"custom_param_2": 42
}
}
]
}
```
## 实验性转换器
## 最佳实践
### gemini-cli实验性
### 1. 不可变性
通过 Gemini CLI 对 Gemini 的非官方支持。
始终创建新对象而不是修改现有对象:
### qwen-cli实验性
```javascript
// 不好的做法
async transformRequestIn(request) {
request.max_tokens = 4096;
return request;
}
通过 Qwen CLI 对 qwen3-coder-plus 的非官方支持。
// 好的做法
async transformRequestIn(request) {
return {
...request,
max_tokens: request.max_tokens || 4096
};
}
```
### rovo-cli实验性
### 2. 错误处理
通过 Atlassian Rovo Dev CLI 对 GPT-5 的非官方支持。
始终优雅地处理错误:
```javascript
async transformResponseIn(response) {
try {
const data = await response.json();
// 处理数据
return new Response(JSON.stringify(processedData), {
status: response.status,
headers: response.headers
});
} catch (error) {
this.logger?.error({ error }, '转换失败');
// 如果转换失败,返回原始响应
return response;
}
}
```
### 3. 日志记录
使用注入的 logger 进行调试:
```javascript
async transformRequestIn(request, provider, context) {
this.logger?.debug({
model: request.model,
provider: provider.name
}, '转换请求');
// 转换逻辑
return modifiedRequest;
}
```
### 4. 流处理
处理流式响应时,始终:
- 使用缓冲区处理不完整的数据块
- 正确释放 reader 锁
- 处理流中的错误
- 完成时关闭 controller
```javascript
const transformedStream = new ReadableStream({
start: async (controller) => {
const reader = response.body.getReader();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 处理流...
}
} catch (error) {
controller.error(error);
} finally {
controller.close();
reader.releaseLock();
}
}
});
```
### 5. 上下文使用
`context` 参数包含有用信息:
```javascript
async transformRequestIn(request, provider, context) {
// 访问请求 ID
const requestId = context.req.id;
// 访问原始请求
const originalRequest = context.req.original;
// 转换逻辑
}
```
## 测试转换器
### 手动测试
1. 将转换器添加到配置
2. 启动服务器:`ccr restart`
3. 检查日志:`tail -f ~/.claude-code-router/logs/ccr-*.log`
4. 发出测试请求
5. 验证输出
### 调试技巧
- 添加日志记录以跟踪转换步骤
- 使用流式和非流式请求进行测试
- 使用无效输入验证错误处理
- 检查错误时是否返回原始响应
## 下一步
- [高级主题](/zh/docs/advanced/custom-router) - 高级路由自定义
- [Agent](/zh/docs/advanced/agents) - 使用 Agent 扩展功能
- [高级主题](/docs/server/advanced/custom-router) - 高级路由自定义
- [Agents](/docs/server/advanced/agents) - 使用 agents 扩展
- [核心包](/docs/server/intro) - 了解 @musistudio/llms

View File

@@ -11,17 +11,103 @@ Claude Code Router Server 是一个核心服务组件,负责将 Claude Code
## 架构概述
```
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Claude Code │────▶│ CCR Server │────▶│ LLM Provider │
│ Client │ │ (Router + │ │ (OpenAI/ │
└─────────────┘ │ Transformer) │ │ Gemini/etc)│
└──────────────────┘ └──────────────┘
┌─────────────┐ ┌─────────────────────────────┐ ┌──────────────┐
│ Claude Code │────▶│ CCR Server │────▶│ LLM Provider │
│ Client │ │ ┌─────────────────────┐ │ │ (OpenAI/ │
└─────────────┘ │ │ @musistudio/llms │ │ │ Gemini/etc)│
│ │ (核心包) │ │ └──────────────┘
│ │ - 请求转换 │ │
│ │ - 响应转换 │ │
│ │ - 认证处理 │ │
│ └─────────────────────┘ │
│ │
│ - 路由逻辑 │
│ - Agent 系统 │
│ - 配置管理 │
└─────────────────────────────┘
├─ Web UI
├─ Config API
└─ Logs API
```
## 核心包:@musistudio/llms
服务器构建于 **@musistudio/llms** 之上,这是一个通用的 LLM API 转换库,提供了核心的请求/响应转换能力。
### 什么是 @musistudio/llms
`@musistudio/llms` 是一个独立的 npm 包(`@musistudio/llms`),负责处理:
- **API 格式转换**:在不同的 LLM 提供商 API 之间转换Anthropic、OpenAI、Gemini 等)
- **请求/响应转换**:将请求和响应转换为统一格式
- **认证处理**:管理不同提供商的认证方法
- **流式响应支持**:处理来自不同提供商的流式响应
- **转换器系统**:提供可扩展的架构来添加新的提供商
### 核心概念
#### 1. 统一请求/响应格式
核心包定义了统一格式(`UnifiedChatRequest``UnifiedChatResponse`),抽象了提供商特定的差异:
```typescript
interface UnifiedChatRequest {
messages: UnifiedMessage[];
model: string;
max_tokens?: number;
temperature?: number;
stream?: boolean;
tools?: UnifiedTool[];
tool_choice?: any;
reasoning?: {
effort?: ThinkLevel;
max_tokens?: number;
enabled?: boolean;
};
}
```
#### 2. 转换器接口
所有转换器都实现一个通用接口:
```typescript
interface Transformer {
transformRequestIn?: (request: UnifiedChatRequest, provider: LLMProvider, context: TransformerContext) => Promise<any>;
transformRequestOut?: (request: any, context: TransformerContext) => Promise<UnifiedChatRequest>;
transformResponseIn?: (response: Response, context?: TransformerContext) => Promise<Response>;
transformResponseOut?: (response: Response, context: TransformerContext) => Promise<Response>;
endPoint?: string;
name?: string;
auth?: (request: any, provider: LLMProvider, context: TransformerContext) => Promise<any>;
}
```
#### 3. 内置转换器
核心包包含以下转换器:
- **anthropic**Anthropic API 格式
- **openai**OpenAI API 格式
- **gemini**Google Gemini API 格式
- **deepseek**DeepSeek API 格式
- **groq**Groq API 格式
- **openrouter**OpenRouter API 格式
- 等等...
### 与 CCR Server 的集成
CCR server 通过以下方式集成 `@musistudio/llms`
1. **转换器服务**`packages/core/src/services/transformer.ts`):管理转换器的注册和实例化
2. **提供商配置**:将提供商配置映射到核心包的 LLMProvider 接口
3. **请求管道**:在请求处理过程中按顺序应用转换器
4. **自定义转换器**:支持加载外部转换器插件
### 版本和更新
`@musistudio/llms` 的当前版本是 `1.0.51`。它作为独立的 npm 包发布,可以独立使用或作为 CCR Server 的一部分使用。
## 核心功能
### 1. 请求路由

View File

@@ -8,11 +8,11 @@
"description": "The alt text of navbar logo"
},
"item.label.Documentation": {
"message": "Documentation",
"message": "文档",
"description": "Navbar item with label Documentation"
},
"item.label.Blog": {
"message": "Blog",
"message": "博客",
"description": "Navbar item with label Blog"
},
"item.label.GitHub": {

View File

@@ -2,6 +2,54 @@ import type { SidebarsConfig } from '@docusaurus/plugin-content-docs';
const sidebars: SidebarsConfig = {
tutorialSidebar: [
{
type: 'category',
label: 'CLI',
link: {
type: 'generated-index',
title: 'Claude Code Router CLI',
description: 'Command-line tool usage guide',
slug: 'category/cli',
},
items: [
'cli/intro',
'cli/installation',
'cli/quick-start',
{
type: 'category',
label: 'Commands',
link: {
type: 'generated-index',
title: 'CLI Commands',
description: 'Complete command reference',
slug: 'category/cli-commands',
},
items: [
'cli/commands/start',
'cli/commands/model',
'cli/commands/status',
'cli/commands/statusline',
'cli/commands/preset',
'cli/commands/other',
],
},
{
type: 'category',
label: 'Configuration',
key: 'cli-configuration-category',
link: {
type: 'generated-index',
title: 'CLI Configuration',
description: 'CLI configuration guide',
slug: 'category/cli-config',
},
items: [
'cli/config/basic',
'cli/config/project-level',
],
},
],
},
{
type: 'category',
label: 'Server',
@@ -63,54 +111,6 @@ const sidebars: SidebarsConfig = {
},
],
},
{
type: 'category',
label: 'CLI',
link: {
type: 'generated-index',
title: 'Claude Code Router CLI',
description: 'Command-line tool usage guide',
slug: 'category/cli',
},
items: [
'cli/intro',
'cli/installation',
'cli/quick-start',
{
type: 'category',
label: 'Commands',
link: {
type: 'generated-index',
title: 'CLI Commands',
description: 'Complete command reference',
slug: 'category/cli-commands',
},
items: [
'cli/commands/start',
'cli/commands/model',
'cli/commands/status',
'cli/commands/statusline',
'cli/commands/preset',
'cli/commands/other',
],
},
{
type: 'category',
label: 'Configuration',
key: 'cli-configuration-category',
link: {
type: 'generated-index',
title: 'CLI Configuration',
description: 'CLI configuration guide',
slug: 'category/cli-config',
},
items: [
'cli/config/basic',
'cli/config/project-level',
],
},
],
},
],
};