前言
使用之后需要修改用户目录下.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字段可选,不填则不重写模型名
注意事项
- 服务器默认运行在端口 3000,可以在
router.py中修改PORT变量 - 生产环境建议使用 Gunicorn 或 uWSGI 替代 Flask 内置服务器
- 所有请求和响应都会打印详细的调试日志
- 使用
--no-debug参数可以关闭调试信息输出,减少控制台输出 -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

1万+

被折叠的 条评论
为什么被折叠?



