LibreChat 添加自定义模型:把 Anthropic 兼容接口与华为云 MaaS 标准 API 改到 TaoToken
1. LibreChat 自定义模型接入的真实痛点LibreChat 是一个开源的聊天界面支持多模型切换、对话历史、插件调用很多人喜欢它简洁的页面和可自托管的特性。但它的默认模型接入走的是 OpenAI 兼容格式也就是/v1/chat/completions那一套。问题来了当你手里拿到的接口是 Anthropic 兼容格式或者华为云 MaaS 标准 API 时LibreChat 并不能直接调用。我自己就遇到过这个场景一边想用 Anthropic 兼容格式的接口跑 Claude 系列模型另一边又想挂载华为云 MaaS 标准 API 上的 DeepSeek-V3两个通道的请求体结构、鉴权头、响应字段都不一样。LibreChat 的librechat.yaml虽然支持自定义 endpoint但如果你直接把 Anthropic 的 baseURL 填进去启动后发消息大概率会报reading choices或者 401因为 LibreChat 期望返回的是 OpenAI 格式的choices数组而 Anthropic 返回的是content数组。这篇内容就是解决这个问题的。我会给出librechat.yaml中自定义 endpoint 的可复制配置片段说明模型名与 baseURL 的填写位置并演示启动后发一条测试消息验证 Anthropic 兼容通道和华为云 MaaS 标准 API 通道都能返回正常响应。适合需要同时挂载两种格式接口的开发者也适合刚接触 LibreChat 自定义模型配置的小白。核心思路是LibreChat 只认 OpenAI 格式所以我们需要一个中间层做协议转换。这个中间层可以是一个轻量 Flask 服务把 OpenAI 格式请求转成 Anthropic 格式再把 Anthropic 响应转回 OpenAI 格式。TaoToken 在这里的作用是提供统一的 API 入口和 Key 管理让你不用在多个平台之间来回切换配置。2. TaoToken 前置准备与 Key 获取在开始改librechat.yaml之前先把 TaoToken 这边的准备工作做完。TaoToken 是一个 API 聚合与转发平台支持多种模型格式的接入你可以把它理解成一个统一的 API 网关帮你把不同格式的请求路由到对应的后端。首先访问官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册账号。注册完成后进入控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。在控制台里你可以看到自己的账户余额、调用统计和 Key 管理入口。接下来生成 API Key。进入 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 点击创建新 Key复制保存好。这个 Key 后面要填到librechat.yaml的apiKey字段里。注意不要把它提交到公开仓库建议用环境变量引用。TaoToken 的 API 基础地址是 https://taotoken.net/api 注意这个地址不带 UTM 参数直接用于代码里的 baseURL。如果你需要查看接入文档可以访问 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言 SDK 的调用示例和参数说明。这里要区分两个概念TaoToken 的 API 地址是给程序调用的而控制台和文档页面是给人看的。你在librechat.yaml里填的 baseURL 应该是https://taotoken.net/api或者你本地中间层的地址具体取决于你是直连还是走转换层。如果你只是想先验证模型能不能通可以打开模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 在里面选一个模型发一条消息看看返回是否正常。这一步能帮你排除 Key 本身的问题。如果对话页面能通说明 Key 和账户状态没问题接下来就是 LibreChat 配置的事了。对于长期编码和 Agent 场景可以考虑 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它有专门的额度方案适合高频调用。不过这篇主要讲 LibreChat 接入Coding Plan 先了解即可。3. librechat.yaml 可复制配置与中间层代码这一节是核心。LibreChat 的自定义 endpoint 配置写在librechat.yaml里通常放在项目根目录或者config/目录下。你需要先确认 LibreChat 启动时加载了这个文件一般在docker-compose.yml里会挂载./librechat.yaml:/app/librechat.yaml。先看librechat.yaml的配置片段。这里我配置了两个自定义 endpoint一个走 Anthropic 兼容格式一个走华为云 MaaS 标准 API。两者都通过本地中间层转换所以 baseURL 指向本地服务。version: 1.1.5 cache: true endpoints: custom: - name: Anthropic-Compatible apiKey: ${TAOTOKEN_API_KEY} baseURL: http://host.docker.internal:5000/v1 models: default: [claude-3-5-sonnet-20241022] fetch: false titleConvo: true titleModel: claude-3-5-sonnet-20241022 modelDisplayLabel: Anthropic 兼容通道 - name: Huawei-MaaS apiKey: ${TAOTOKEN_API_KEY} baseURL: http://host.docker.internal:5000/v1 models: default: [DeepSeek-V3] fetch: false titleConvo: true titleModel: DeepSeek-V3 modelDisplayLabel: 华为云 MaaS 通道几个关键点说明。baseURL填的是中间层地址不是 TaoToken 的地址因为中间层负责格式转换。如果你 LibreChat 跑在 Windows 的 Docker 里用host.docker.internal访问宿主机如果跑在 Linux 里可以用172.17.0.1或者宿主机的局域网 IP。apiKey用环境变量引用实际值在.env文件里设置TAOTOKEN_API_KEY你的Key。models.default里填模型名这个模型名会传给中间层中间层再决定转发到哪个后端。然后是中间层代码。这是一个 Flask 服务把 OpenAI 格式请求转成 Anthropic 格式再把响应转回来。完整代码如下你可以直接保存为proxy.py运行。import os import json import time import requests from flask import Flask, request, Response, jsonify from flask_cors import CORS import logging app Flask(__name__) CORS(app, resources{r/*: {origins: *}}) logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) TAOTOKEN_API_URL os.getenv(TAOTOKEN_API_URL, https://taotoken.net/api/v1/messages) TAOTOKEN_API_KEY os.getenv(TAOTOKEN_API_KEY, ) DEFAULT_MODEL os.getenv(DEFAULT_MODEL, claude-3-5-sonnet-20241022) def convert_openai_to_anthropic(openai_request): anthropic_request {model: openai_request.get(model, DEFAULT_MODEL), messages: []} messages openai_request.get(messages, []) system_content None anthropic_messages [] for msg in messages: role msg.get(role) content msg.get(content) if role system: if isinstance(content, str): system_content content elif isinstance(content, list): system_content \n.join([i.get(text, ) for i in content if i.get(type) text]) elif role in [user, assistant]: if isinstance(content, str): anthropic_messages.append({role: role, content: content}) elif isinstance(content, list): parts [] for item in content: if item.get(type) text: parts.append({type: text, text: item.get(text, )}) if parts: anthropic_messages.append({role: role, content: parts if len(parts) 1 else parts[0].get(text, )}) anthropic_request[messages] anthropic_messages if system_content: anthropic_request[system] system_content for key in [temperature, max_tokens, top_p, stream]: if openai_request.get(key) is not None: anthropic_request[key] openai_request[key] return anthropic_request def convert_anthropic_to_openai(anthropic_response): content anthropic_response.get(content, []) if isinstance(content, list): text_parts [i.get(text, ) for i in content if isinstance(i, dict) and i.get(type) text] content_text .join(text_parts) else: content_text str(content) usage anthropic_response.get(usage, {}) stop_reason anthropic_response.get(stop_reason, stop) finish_map {end_turn: stop, max_tokens: length, stop_sequence: stop} return { id: anthropic_response.get(id, fchatcmpl-{int(time.time())}), object: chat.completion, created: int(time.time()), model: anthropic_response.get(model, DEFAULT_MODEL), choices: [{index: 0, message: {role: assistant, content: content_text}, finish_reason: finish_map.get(stop_reason, stop)}], usage: {prompt_tokens: usage.get(input_tokens, 0), completion_tokens: usage.get(output_tokens, 0), total_tokens: usage.get(input_tokens, 0) usage.get(output_tokens, 0)} } app.route(/v1/chat/completions, methods[POST, OPTIONS]) def chat_completions(): if request.method OPTIONS: resp jsonify({}) resp.headers.add(Access-Control-Allow-Origin, *) resp.headers.add(Access-Control-Allow-Headers, Content-Type, Authorization) resp.headers.add(Access-Control-Allow-Methods, POST, OPTIONS) return resp openai_request request.get_json() anthropic_request convert_openai_to_anthropic(openai_request) headers { Authorization: fBearer {TAOTOKEN_API_KEY}, Content-Type: application/json, anthropic-version: 2023-06-01 } response requests.post(TAOTOKEN_API_URL, headersheaders, jsonanthropic_request, timeout60) if response.status_code ! 200: return jsonify({error: {message: response.text, type: api_error}}), response.status_code return jsonify(convert_anthropic_to_openai(response.json())) app.route(/v1/models, methods[GET]) def list_models(): return jsonify({object: list, data: [{id: DEFAULT_MODEL, object: model, owned_by: taotoken}]}) if __name__ __main__: app.run(host0.0.0.0, port5000)运行前先装依赖pip install flask flask-cors requests。然后设置环境变量TAOTOKEN_API_KEY为你的 KeyTAOTOKEN_API_URL指向 TaoToken 的 Anthropic 兼容端点。启动命令是python proxy.py服务监听 5000 端口。这里要提醒一点华为云 MaaS 标准 API 的请求体结构和 Anthropic 略有不同如果你的 MaaS 接口需要单独的转换逻辑可以在convert_openai_to_anthropic里根据模型名做分支判断。比如模型名是DeepSeek-V3时走 MaaS 格式是claude-*时走 Anthropic 格式。这样两个通道就能共用一个中间层。4. 启动验证与两条通道测试配置和代码都准备好后按顺序启动。先启动中间层python proxy.py看到Running on http://0.0.0.0:5000说明服务起来了。然后用 curl 测一下中间层是否正常。curl -X POST http://localhost:5000/v1/chat/completions \ -H Content-Type: application/json \ -d {model:claude-3-5-sonnet-20241022,messages:[{role:user,content:你好回复一句话}],max_tokens:100}如果返回的 JSON 里有choices数组且message.content有内容说明中间层转换正常。如果返回 401检查TAOTOKEN_API_KEY是否设置正确如果返回reading choices相关错误说明响应转换有问题检查convert_anthropic_to_openai里的字段映射。中间层通了之后启动 LibreChat。如果你用 Docker执行docker compose up -d然后看日志docker compose logs -f。确认librechat.yaml被加载日志里会显示自定义 endpoint 注册成功。打开 LibreChat 页面在模型选择下拉框里应该能看到「Anthropic 兼容通道」和「华为云 MaaS 通道」两个选项。先测 Anthropic 兼容通道。选「Anthropic 兼容通道」模型选claude-3-5-sonnet-20241022发一条「用一句话介绍你自己」。正常的话几秒内会返回内容。如果报local proxy failed说明 LibreChat 容器访问不到宿主机的 5000 端口检查host.docker.internal是否解析正确或者换成宿主机的局域网 IP。再测华为云 MaaS 通道。选「华为云 MaaS 通道」模型选DeepSeek-V3发一条「11 等于几」。如果返回正常说明两条通道都通了。这时候你可以对比两个通道的响应速度和质量根据实际需求切换使用。实测下来中间层的延迟增加大概在 50 到 150 毫秒之间主要花在请求转发和格式转换上。对于聊天场景来说基本无感。如果你对延迟敏感可以把中间层和 LibreChat 部署在同一台机器上减少网络往返。还有一个细节LibreChat 的titleConvo功能会自动生成对话标题它也会走一次模型调用。如果你发现标题生成失败检查titleModel是否在models.default列表里以及中间层是否支持该模型名。5. 常见报错排查对照这一节列出几个我踩过的坑和对应的排查方法。第一个报错是401 Unauthorized。这个最常见原因是apiKey没填对或者环境变量没生效。检查.env文件里TAOTOKEN_API_KEY的值确认没有多余空格或引号。然后在 LibreChat 容器里执行echo $TAOTOKEN_API_KEY看是否注入成功。如果用的是librechat.yaml里的${TAOTOKEN_API_KEY}引用确保 LibreChat 启动时加载了.env。第二个报错是local proxy failed或者ECONNREFUSED。这是 LibreChat 容器访问不到中间层。Windows Docker 用host.docker.internalLinux Docker 用172.17.0.1或者--network host。如果你中间层跑在另一台机器上填那台机器的 IP。测试方法是在 LibreChat 容器里执行curl http://host.docker.internal:5000/v1/models看能不能通。第三个报错是reading choices或者Cannot read properties of undefined。这是响应格式不对LibreChat 期望 OpenAI 格式但拿到了 Anthropic 原始格式。检查中间层的convert_anthropic_to_openai是否被正确调用以及返回的 JSON 里是否有choices字段。可以在中间层加日志打印转换后的响应。第四个报错是OAuth相关或者invalid api key。如果你在 TaoToken 控制台创建 Key 时选了特定的权限范围确认该 Key 有调用目标模型的权限。另外检查anthropic-version请求头是否填了2023-06-01有些接口对这个头有要求。第五个报错是流式响应中断。如果你在librechat.yaml里开了流式但中间层没处理stream参数会出现只返回一部分内容的情况。检查convert_openai_to_anthropic里是否透传了stream字段以及中间层是否有对应的流式处理逻辑。如果暂时不需要流式可以在 LibreChat 设置里关掉。排查的时候建议按顺序来先测中间层 curl再测 LibreChat 容器到中间层的连通性最后测 LibreChat 页面发消息。这样能快速定位问题出在哪一层。6. 继续接入与 Key 管理两条通道都验证通过后你可以把更多模型加到librechat.yaml的models.default列表里。比如 Anthropic 通道可以加claude-3-opus、claude-3-haikuMaaS 通道可以加其他 DeepSeek 版本。每加一个模型确认中间层能正确路由到对应的后端。Key 管理方面建议在 TaoToken 控制台为不同用途创建不同的 Key。比如一个 Key 专门给 LibreChat 用一个 Key 给 Coding Plan 用。这样如果某个 Key 泄露可以单独吊销不影响其他服务。API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 支持创建多个 Key 并设置备注。如果你后续想接入 Claude Code 或者做 Agent 开发可以参考接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里的示例。文档里有 Base URL、Key、Model ID 三件套的填写说明照着填就行。对于长期编码场景Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 有更划算的额度方案。最后提醒一点中间层代码里的TAOTOKEN_API_URL默认指向 Anthropic 兼容端点。如果你要接华为云 MaaS 标准 API需要确认 TaoToken 是否支持该格式的转发或者在你的中间层里加一个分支把 MaaS 格式的请求单独处理。具体可以看文档里的接口说明或者先在模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 里选对应模型测一下确认能通再写进配置。整套流程跑下来LibreChat 就能同时挂载 Anthropic 兼容格式和华为云 MaaS 标准 API 两个通道了。中间层虽然多了一层转发但换来的是配置灵活性和格式兼容性对于需要多模型切换的场景来说很值得。