2026-05-28
教程
0

目录

手把手教程:让 Claude Code 使用自建 OpenAI 兼容代理
背景
第一步:跳过官方引导
第二步:配置第三方 API(直接使用现有代理)
第三步:自建代理 – 搭建 OpenAI 兼容网关
测试代理是否正常
第四步:为代理添加 Anthropic /v1/messages 适配
4.1 添加转换函数
4.2 添加 /v1/messages 路由
4.3 重启代理
第五步:配置 Claude Code 使用自建代理
常见问题与解决
1. 报错 selected model (auto) may not exist
2. 流式请求时报 list index out of range
3. 一句话触发多次 API 请求
4. Token 统计异常高
5. Windows 路径注意事项
💡 获取完整项目
最终效果
总结

手把手教程:让 Claude Code 使用自建 OpenAI 兼容代理

从零开始,解决国内无法直连 Anthropic 的问题,并为你的自建代理添加 Claude Code 支持。

背景

Claude Code 是 Anthropic 推出的强大 AI 编程助手,但在国内网络环境下,直接运行 claude 命令会遇到:

Unable to connect to Anthropic services Failed to connect to api.anthropic.com: ERR_BAD_REQUEST

这是因为 Claude Code 启动时需要连接官方 API 进行验证。好在 Claude Code 支持通过环境变量配置第三方 API,我们可以让它在本地或自建代理上运行。

🚀 配套开源项目:本教程使用的自建代理正是 LLM-Proxy-Gateway
一个高性能、多 Key 池、支持优先级与自动故障转移的 LLM 智能代理网关,已内置 Claude Code 适配。

本教程将带你:

  1. 跳过官方引导,进入 Claude Code
  2. 配置第三方 API(包括自建代理)
  3. 为 OpenAI 兼容的代理添加 Anthropic 协议适配(/v1/messages 端点)
  4. 解决流式转换、模型选择等常见问题

最终效果:Claude Code 愉快地跑在你的代理上,就像在使用官方 Claude 一样。


第一步:跳过官方引导

Claude Code 首次启动时会尝试连接 Anthropic 服务器,我们需要让它“误以为”已经完成过引导。

找到配置文件(Windows 路径):
C:\Users\<你的用户名>\.claude.json

用文本编辑器打开,添加或修改:

json
{ "hasCompletedOnboarding": true }

如果没有该文件,可以手动创建。保存后,重新运行 claude,应该会直接进入主题选择界面,而不再报网络错误。


第二步:配置第三方 API(直接使用现有代理)

如果你已经有第三方 OpenAI 兼容的 API(例如 DeepSeek、自建 vLLM 等),只需配置 Claude Code 指向它。

编辑配置文件 ~/.claude/settings.json(Windows 路径 %USERPROFILE%\.claude\settings.json):

json
{ "env": { "ANTHROPIC_BASE_URL": "https://api.deepseek.com", "ANTHROPIC_AUTH_TOKEN": "sk-你的真实密钥", "ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-chat" }, "theme": "dark" }
  • ANTHROPIC_BASE_URL:你的 API 地址,不要加 /v1 后缀
  • ANTHROPIC_AUTH_TOKEN:API 密钥(Bearer token)。
  • ANTHROPIC_DEFAULT_SONNET_MODEL:覆盖 Claude Code 默认的 Sonnet 模型,填入你代理中实际可用的模型名。

配置完成后,重启 Claude Code,应该可以直接对话。


第三步:自建代理 – 搭建 OpenAI 兼容网关

有时我们需要完全自托管代理,比如集成多个 API 源、负载均衡、Token 统计等。本教程使用我开源的 LLM-Proxy-Gateway(项目内 proxy.py),它提供:

  • /v1/chat/completions 标准 OpenAI 端点
  • 多 Key 池、优先级(P0 公益 / P1 免费 / P2 付费)
  • 社区 Key 自动抓取、冷却与删除策略
  • Token 使用统计

该代理默认监听 http://127.0.0.1:8800,返回的模型列表中有一个名为 auto 的模型(代表智能选择)。

测试代理是否正常

bash
curl http://127.0.0.1:8800/v1/models

返回示例:

json
{"object":"list","data":[{"id":"auto","object":"model","created":1779947583,"owned_by":"proxy"}]}

说明代理已运行。


第四步:为代理添加 Anthropic /v1/messages 适配

Claude Code 使用的是 Anthropic 协议(端点 /v1/messages),而我们的代理仅支持 OpenAI 格式。需要增加一个适配层,做请求/响应的双向转换。

4.1 添加转换函数

proxy.py 中(handle_chat_request 函数之前)添加以下代码:

python
import json import time def anthropic_to_openai(anthropic_body: dict) -> dict: """将 Anthropic 请求转换为 OpenAI 格式""" openai_messages = [] system_prompt = None # 处理 system 字段 if "system" in anthropic_body: sys_content = anthropic_body["system"] if isinstance(sys_content, list): texts = [item.get("text", "") for item in sys_content if item.get("type") == "text"] system_prompt = "\n".join(texts) else: system_prompt = sys_content # 处理 messages for msg in anthropic_body.get("messages", []): role = msg["role"] content = msg["content"] if isinstance(content, list): # 提取纯文本 text_parts = [block.get("text", "") for block in content if block.get("type") == "text"] content = " ".join(text_parts) if text_parts else "" openai_messages.append({"role": role, "content": content}) # 构建 OpenAI payload openai_payload = { "model": anthropic_body.get("model", "auto"), "messages": openai_messages, "max_tokens": anthropic_body.get("max_tokens", 1024), "temperature": anthropic_body.get("temperature", 1.0), "stream": anthropic_body.get("stream", False), } if system_prompt: openai_payload["system"] = system_prompt # 处理工具调用(可选) if "tools" in anthropic_body: openai_tools = [] for tool in anthropic_body["tools"]: openai_tools.append({ "type": "function", "function": { "name": tool["name"], "description": tool.get("description", ""), "parameters": tool.get("input_schema", {}) } }) openai_payload["tools"] = openai_tools if anthropic_body.get("tool_choice"): openai_payload["tool_choice"] = anthropic_body["tool_choice"] return openai_payload def openai_to_anthropic(openai_response: dict, original_model: str) -> dict: """将 OpenAI 响应转换为 Anthropic 格式""" choice = openai_response.get("choices", [{}])[0] message = choice.get("message", {}) content = message.get("content", "") finish_reason = choice.get("finish_reason", "") stop_reason_map = { "stop": "end_turn", "length": "max_tokens", "tool_calls": "tool_use", "content_filter": "content_filter" } stop_reason = stop_reason_map.get(finish_reason, "end_turn") # 构建 content 块 content_blocks = [] if "tool_calls" in message and message["tool_calls"]: for tc in message["tool_calls"]: content_blocks.append({ "type": "tool_use", "id": tc.get("id", f"toolu_{hash(tc['function']['name'])}"), "name": tc["function"]["name"], "input": json.loads(tc["function"]["arguments"]) }) if content: content_blocks.append({"type": "text", "text": content}) if not content_blocks: content_blocks = [{"type": "text", "text": ""}] usage = openai_response.get("usage", {}) return { "id": openai_response.get("id", f"msg_{int(time.time())}"), "type": "message", "role": "assistant", "model": original_model, "content": content_blocks, "stop_reason": stop_reason, "stop_sequence": None, "usage": { "input_tokens": usage.get("prompt_tokens", 0), "output_tokens": usage.get("completion_tokens", 0) } } def openai_to_anthropic_stream_chunk(openai_chunk: dict, model: str): """将 OpenAI 流式 chunk 转换为 Anthropic SSE 事件""" choices = openai_chunk.get("choices") if not choices or not isinstance(choices, list) or len(choices) == 0: return None choice = choices[0] delta = choice.get("delta", {}) content = delta.get("content", "") finish_reason = choice.get("finish_reason", None) index = choice.get("index", 0) if content: return { "type": "content_block_delta", "index": index, "delta": {"type": "text_delta", "text": content} } if finish_reason: return {"type": "message_stop"} return None

4.2 添加 /v1/messages 路由

app.route 定义区域(例如 /v1/chat/completions 之后)添加:

python
@app.route('/v1/messages', methods=['POST']) def anthropic_messages(): if not check_api_token(): return jsonify({"error": "Unauthorized"}), 401 try: anthropic_body = request.get_json() if not anthropic_body: return jsonify({"error": "Invalid JSON body"}), 400 except: return jsonify({"error": "Invalid JSON body"}), 400 openai_payload = anthropic_to_openai(anthropic_body) is_stream = anthropic_body.get("stream", False) headers = {"Content-Type": "application/json"} auth_header = request.headers.get("Authorization") if auth_header: headers["Authorization"] = auth_header local_url = f"http://127.0.0.1:{PROXY_PORT}/v1/chat/completions" try: upstream_resp = requests.post( local_url, headers=headers, json=openai_payload, timeout=REQUEST_TIMEOUT, stream=is_stream ) if upstream_resp.status_code != 200: return Response(upstream_resp.text, status=upstream_resp.status_code, content_type="application/json") if is_stream: def generate(): for line in upstream_resp.iter_lines(): if not line: continue line = line.decode('utf-8') if line.startswith('data: '): data_str = line[6:] if data_str == '[DONE]': yield 'data: {"type": "message_stop"}\n\n' break try: openai_chunk = json.loads(data_str) anth_chunk = openai_to_anthropic_stream_chunk(openai_chunk, anthropic_body.get("model", "auto")) if anth_chunk: yield f'data: {json.dumps(anth_chunk)}\n\n' except Exception as e: add_log(logging.ERROR, f"Stream conversion error: {e}") continue response = Response(generate(), status=200) response.headers['Content-Type'] = 'text/event-stream' response.headers['Cache-Control'] = 'no-cache' return response else: openai_response = upstream_resp.json() anthropic_response = openai_to_anthropic(openai_response, anthropic_body.get("model", "auto")) return jsonify(anthropic_response) except Exception as e: add_log(logging.ERROR, f"/v1/messages error: {e}") return jsonify({"error": str(e)}), 500

注意:PROXY_PORT 需与你的监听端口一致(本例为 8800)。

4.3 重启代理

bash
python proxy.py

现在代理同时支持:

  • POST /v1/chat/completions(OpenAI 格式)
  • POST /v1/messages(Anthropic 格式)

第五步:配置 Claude Code 使用自建代理

编辑 ~/.claude/settings.json

json
{ "env": { "ANTHROPIC_BASE_URL": "http://127.0.0.1:8800", "ANTHROPIC_AUTH_TOKEN": "123" }, "theme": "dark" }

如果你的代理启用了 API 鉴权(ENABLE_API_AUTH=true),则需要将 ANTHROPIC_AUTH_TOKEN 设置为 API_ACCESS_TOKEN 的值。

启动 Claude Code:

bash
claude

进入后,运行 /model 选择 auto 模型(或你代理中实际支持的模型名),然后随便问一句话。


常见问题与解决

1. 报错 selected model (auto) may not exist

  • 确保你的代理 /v1/models 返回的 idANTHROPIC_DEFAULT_SONNET_MODEL 设置一致。
  • 在 Claude Code 中手动 /model 选择正确的模型。

2. 流式请求时报 list index out of range

  • 某些后端(如 mimo-v2-omni)返回的流式 chunk 可能不标准,更新 openai_to_anthropic_stream_chunk 函数增加防御性判断(上面已提供完整版本)。

3. 一句话触发多次 API 请求

  • Claude Code 默认会进行工具调用探测,如果模型不支持 tool use,可能产生额外请求。可以在 anthropic_to_openai 中注释掉 tools 相关代码,强制禁用工具调用。

4. Token 统计异常高

  • 检查 Claude Code 是否在每次对话中重复发送了长 system prompt,或代理没有正确裁剪上下文。可以添加日志打印请求体内容来分析。

5. Windows 路径注意事项

  • 配置文件位置:C:\Users\<用户名>\.claude\settings.json
  • 跳过引导文件:C:\Users\<用户名>\.claude.json

💡 获取完整项目

本教程的代理代码已全部开源,欢迎使用、反馈和贡献:

👉 GitHub 仓库https://github.com/yhyh0000/LLM-Proxy-Gateway

如果觉得有用,别忘了点亮 ⭐ Star 支持一下!


最终效果

成功运行后,你会看到类似这样的界面:

❯ 你好 ● 你好!有什么我可以帮你的吗?

同时代理日志会显示:

INFO:proxy:流式请求成功 供应商: 小米mimo 模型: mimo-v2-omni

恭喜!你现在已经让 Claude Code 愉快地运行在自建代理上了。


总结

本教程涵盖了:

  1. 解决 Claude Code 国内无法连接的初始问题
  2. 配置第三方 API 的方法(直接或通过自建代理)
  3. 为 OpenAI 兼容代理添加 Anthropic 协议适配层
  4. 处理流式转换、模型选择等实战坑点

通过这种方式,你可以灵活地将 Claude Code 对接任何 OpenAI 兼容的后端(包括本地 vLLM、Ollama、DeepSeek 等),享受 AI 编程助手的便利,同时保持对数据和费用的完全控制。

如果你有其他自建代理或特殊需求,修改适配函数即可。Happy coding!

本文作者:苏皓明

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!