代码和配置文件实现claude code多模型路由转发。告别cc switch工具

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

前言

使用之后需要修改用户目录下.claude\settings.json文件。

如我的是C:\Users\用户名\.claude\settings.json

模型和Key的配置请编辑router.py里的MODEL_CONFIG字典

源码和样例在附录

注意:使用claude code的/model 即可切换模型。模型的URL地址不带anthropic,否则会无法访问。若出现其他问题,自行调试

列如
https://dashscope.aliyuncs.com/apps/anthropic 
target要写成
https://dashscope.aliyuncs.com/apps

如果你只是一个模型就没必要转发了。

Anthropic API 路由转发代理

这是一个 Python 实现的 Anthropic API 路由转发代理,可以根据请求的模型名称将请求转发到不同的后端 API。

功能特性

  • ✅ 根据模型名称自动路由到对应的后端 API
  • ✅ 自动替换 API Key(客户端可以使用任意 Key)
  • ✅ 支持流式响应(stream)
  • ✅ 详细的调试日志输出
  • ✅ 支持多个后端 API 配置
  • ✅ 同名模型多 Key 支持(通过 -mm数字 标识区分)

安装依赖

pip install flask requests

启动方式

方式 1: 直接运行 Python

# 默认启动(包含调试信息)
python router.py

# 关闭调试信息
python router.py --no-debug

# 指定端口
python router.py --port 8080

# 组合使用
python router.py --no-debug --port 8080

方式 2: 双击启动脚本 (Windows)

双击 start_router.bat 文件

配置客户端

在 Claude CLI 或其他客户端中配置:

{
  "env": {
    "ANTHROPIC_BASE_URL": "http://127.0.0.1:3000",
    "ANTHROPIC_API_KEY": "any-placeholder",
    "ANTHROPIC_MODEL": "mimo-v2.5-pro",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "qwen3.7-max",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-5.2",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-5.2-mm1"
  },
  "model": "opus",
  "autoUpdatesChannel": "latest",
  "skipDangerousModePermissionPrompt": true
}

同名模型多 Key(标识机制)

同一个模型如果想配置多个不同的 API Key(例如多账号轮换),可以通过在模型名后加 -mm数字 标识来区分,并用 actual_model 字段指定转发到后端时使用的真实模型名(即去掉标识后的名字):

MODEL_CONFIG = {
    'glm-5.2': {                    # 客户端请求 "glm-5.2" 走这个
        'target': 'https://open.bigmodel.cn/api',
        'api_key': 'key-A'
    },
    'glm-5.2-mm1': {                # 客户端请求 "glm-5.2-mm1" 走这个
        'target': 'https://open.bigmodel.cn/api',
        'api_key': 'key-B',
        'actual_model': 'glm-5.2'   # 转发时模型名改写为 "glm-5.2"
    },
    'glm-5.2-mm2': {                # 客户端请求 "glm-5.2-mm2" 走这个
        'target': 'https://open.bigmodel.cn/api',
        'api_key': 'key-C',
        'actual_model': 'glm-5.2'
    },
}

行为说明

  • 客户端请求 model: "glm-5.2-mm1" → 命中 glm-5.2-mm1 配置,使用 key-B,转发给后端的 model 字段被改写为 glm-5.2
  • 客户端请求 model: "glm-5.2" → 命中 glm-5.2 配置,使用 key-A,模型名保持不变
  • 路由匹配采用最长匹配优先,所以 glm-5.2 不会抢匹配到 glm-5.2-mm1
  • actual_model 字段可选,不填则不重写模型名

注意事项

  1. 服务器默认运行在端口 3000,可以在 router.py 中修改 PORT 变量
  2. 生产环境建议使用 Gunicorn 或 uWSGI 替代 Flask 内置服务器
  3. 所有请求和响应都会打印详细的调试日志
  4. 使用 --no-debug 参数可以关闭调试信息输出,减少控制台输出
  5. -mm数字 标识仅用于路由区分,真实模型名通过 actual_model 还原后转发

附录

router.py

"""
Anthropic API 路由转发代理
根据请求的模型名称,将请求转发到不同的后端 API
"""

from flask import Flask, request, jsonify
import requests
import json
from datetime import datetime
import argparse

app = Flask(__name__)

# 调试模式配置
DEBUG_ENABLED = True

# 配置不同模型的真实后端地址以及各自需要的 API Key
# - 同名模型可通过 '-mm数字' 加标识来区分不同 key,例如 'glm-5.2-mm1'
# - actual_model: 转发到后端时使用的真实模型名(去掉标识);不填则不重写
MODEL_CONFIG = {
    'mimo-v2.5-pro': {
        'target': 'https://api.xiaomimimo.com',
        'api_key': '你的秘钥'
    },
    'glm-4.7': {
        'target': 'https://open.bigmodel.cn/api',
         'api_key': '你的秘钥'
    },
    'glm-5.2': {
        'target': 'https://open.bigmodel.cn/api',
          'api_key': '你的秘钥'
    },
    'glm-5.2-mm1': {
        'target': 'https://open.bigmodel.cn/api',
         'api_key': '你的秘钥',
        'actual_model': 'glm-5.2',
    },
    'qwen3.7-max': {
        'target': 'https://dashscope.aliyuncs.com/apps',
         'api_key': '你的秘钥'
    },
}

# 默认配置
DEFAULT_CONFIG = {
    'target': 'https://api.xiaomimimo.com',
    'api_key': '你的秘钥'
}


def match_config(requested_model: str) -> dict:
    """根据请求的模型名称匹配配置(取最长匹配,最具体的优先)"""
    requested_model = requested_model.lower()
    best_name = None
    best_config = None
    for name, config in MODEL_CONFIG.items():
        if name in requested_model:
            if best_name is None or len(name) > len(best_name):
                best_name = name
                best_config = config
    return best_config if best_config is not None else DEFAULT_CONFIG


def log_debug(title: str, content: any):
    """打印调试信息(仅在 DEBUG_ENABLED 为 True 时输出)"""
    if not DEBUG_ENABLED:
        return

    try:
        print(f"\n{'=' * 80}")
        print(f"[DEBUG] ==================== {title} ====================")
        if isinstance(content, (dict, list)):
            print(json.dumps(content, indent=2, ensure_ascii=False))
        else:
            print(content)
        print(f"{'=' * 80}\n")
    except UnicodeEncodeError:
        # 如果遇到编码错误,使用 ASCII 模式输出
        print(f"\n{'=' * 80}")
        print(f"[DEBUG] ==================== {title} ====================")
        if isinstance(content, (dict, list)):
            print(json.dumps(content, indent=2, ensure_ascii=True))
        else:
            print(str(content).encode('ascii', 'replace').decode('ascii'))
        print(f"{'=' * 80}\n")


@app.route('/v1/messages', methods=['POST'])
def proxy_messages():
    """代理 Anthropic Messages API 请求"""

    # 获取请求数据
    request_data = request.get_json(force=True)
    requested_model = request_data.get('model', '')

    # 匹配配置
    config = match_config(requested_model)
    target_url = f"{config['target']}/anthropic/v1/messages"

    # 如果配置了 actual_model,把请求体里的模型名重写为真实模型名(去掉标识)
    actual_model = config.get('actual_model')
    if actual_model and actual_model != requested_model:
        request_data = dict(request_data)
        request_data['model'] = actual_model

    # 打印请求信息
    print(f"\n{'#' * 80}")
    print(f"[{datetime.now().isoformat()}] 收到请求")
    log_debug("请求信息", {
        '时间': datetime.now().isoformat(),
        '请求方法': request.method,
        '请求路径': request.url,
        '客户端 IP': request.remote_addr,
        'User-Agent': request.headers.get('User-Agent', ''),
    })

    log_debug("请求头", dict(request.headers))

    log_debug("请求体", request_data)

    log_debug("模型路由信息", {
        '客户端请求模型': requested_model,
        '转发目标': target_url,
        'API Key (前10位)': config['api_key'][:10] + '...' if config['api_key'] else '无',
    })

    # 构建转发请求头
    proxy_headers = {
        'Content-Type': 'application/json',
        'x-api-key': config['api_key'],
        'anthropic-version': request.headers.get('anthropic-version', '2023-06-01'),
    }

    # 保留其他可能需要的请求头
    for header_name in ['anthropic-beta', 'user-agent']:
        if header_name in request.headers:
            proxy_headers[header_name] = request.headers[header_name]

    log_debug("代理请求头 (已修改)", {
        '完整请求 URL': target_url,
        'host': target_url.split('//')[1].split('/')[0],
        'x-api-key': proxy_headers['x-api-key'][:10] + '...',
        'content-type': 'application/json',
        '所有代理请求头': proxy_headers,
    })

    # 发送请求到目标 API
    try:
        is_stream = request_data.get('stream', False)

        response = requests.post(
            target_url,
            json=request_data,
            headers=proxy_headers,
            timeout=300,  # 5 分钟超时
            stream=is_stream
        )

        # 打印响应信息
        log_debug("响应信息", {
            '响应时间': datetime.now().isoformat(),
            '响应状态码': response.status_code,
            '响应状态消息': response.reason,
        })

        log_debug("响应头", dict(response.headers))

        # 流式响应处理
        if is_stream:
            log_debug("响应体 (流式)", f"流式响应,状态码: {response.status_code}")

            def generate():
                try:
                    for chunk in response.iter_content(chunk_size=None):
                        yield chunk
                except Exception as e:
                    print(f"[ERROR] 流式响应错误: {e}")
                    yield f"data: {json.dumps({'error': str(e)})}\n\n"

            return app.response_class(
                generate(),
                status=response.status_code,
                headers={
                    'Content-Type': response.headers.get('Content-Type', 'text/event-stream'),
                    'Cache-Control': 'no-cache',
                    'Connection': 'keep-alive',
                }
            )
        else:
            # 非流式响应
            try:
                response_data = response.json()
            except json.JSONDecodeError:
                response_data = {'raw': response.text}

            log_debug("响应体", response_data)

            log_debug("请求摘要", {
                '原始请求模型': requested_model,
                '请求方法': request.method,
                '请求路径': request.url,
                '是否流式': '是' if is_stream else '否',
            })

            return jsonify(response_data), response.status_code

    except requests.exceptions.Timeout:
        error_msg = {
            '错误时间': datetime.now().isoformat(),
            '错误消息': '请求超时 (300秒)',
            '错误类型': 'Timeout',
        }
        log_debug("代理错误", error_msg)
        return jsonify({
            'error': {
                'type': 'proxy_error',
                'message': 'Request timeout'
            }
        }), 504

    except requests.exceptions.ConnectionError as e:
        error_msg = {
            '错误时间': datetime.now().isoformat(),
            '错误消息': f'连接失败: {str(e)}',
            '错误类型': 'ConnectionError',
        }
        log_debug("代理错误", error_msg)
        return jsonify({
            'error': {
                'type': 'proxy_error',
                'message': f'Connection failed: {str(e)}'
            }
        }), 502

    except requests.exceptions.RequestException as e:
        error_msg = {
            '错误时间': datetime.now().isoformat(),
            '错误消息': str(e),
            '错误类型': type(e).__name__,
        }
        log_debug("代理错误", error_msg)
        return jsonify({
            'error': {
                'type': 'proxy_error',
                'message': str(e)
            }
        }), 502


@app.route('/health', methods=['GET'])
def health_check():
    """健康检查端点"""
    return jsonify({'status': 'ok', 'message': 'Anthropic API 路由代理运行中'})


if __name__ == '__main__':
    # 解析命令行参数
    parser = argparse.ArgumentParser(description='Anthropic API 路由转发代理')
    parser.add_argument('--no-debug', action='store_true',
                        help='关闭调试信息输出')
    parser.add_argument('--port', type=int, default=3000,
                        help='服务端口 (默认: 3000)')
    args = parser.parse_args()

    # 根据命令行参数设置调试模式
    if args.no_debug:
        DEBUG_ENABLED = False
        print("[INFO] 调试信息已关闭")
    else:
        print("[INFO] 调试信息已开启 (使用 --no-debug 参数可关闭)")

    PORT = args.port
    print(f"[START] Anthropic 标准中转网关已启动 (无鉴权模式)")
    print(f"[INFO] 请将客户端 Base URL 设为: http://127.0.0.1:{PORT}")
    print(f"[INFO] 客户端的 API Key 可以随便填写,中转会自动替换为下游所需 Key")
    app.run(host='0.0.0.0', port=PORT, debug=True)

settings.json

配置样例。最多4个模型。

{
  "env": {
    "ANTHROPIC_BASE_URL": "http://127.0.0.1:3000",
    "ANTHROPIC_API_KEY": "any-placeholder",
    "ANTHROPIC_MODEL": "mimo-v2.5-pro",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "qwen3.7-max",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-5.2",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-5.2-mm1"
  },
  "model": "mimo-v2.5-pro",
  "autoUpdatesChannel": "latest",
  "skipDangerousModePermissionPrompt": true
}

start_router.bat

便捷启动脚本

@echo off
chcp 65001 >nul
echo ========================================
echo   Anthropic API 路由转发代理
echo ========================================
echo.
echo [INFO] 正在启动路由代理...
echo [INFO] 请将客户端 Base URL 设为: http://127.0.0.1:3000
echo [INFO] 客户端的 API Key 可以随便填写,中转会自动替换为下游所需 Key
echo.
echo [INFO] 命令行参数:
echo   --no-debug    关闭调试信息输出
echo   --port PORT   指定服务端口 (默认: 3000)
echo.
echo [INFO] 按 Ctrl+C 停止服务器
echo ========================================
echo.

python router.py
pause

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值