在常见框架里配置中转 API:从 SDK 到 Dify

接入中转 API 的核心只有三个值:接口地址、密钥、模型名。难的是每个框架给这三个值起的名字不一样,有的叫 base_url,有的叫 api_base,Dify 里干脆是一个表单。这篇文章把它们对照着列出来,每段代码都能直接运行,最后附一份按顺序排查的清单。

更新于

要点

  1. 统一把地址和密钥放进环境变量 API_BASE 与 API_KEY,代码里不出现明文。
  2. 接口地址要带 /v1 后缀,模型名固定写 uncensored。
  3. LangChain 用 base_url,LlamaIndex 的 OpenAILike 用 api_base,名字不同含义相同。
  4. Dify 里选“OpenAI-API-compatible”供应商,手工填写模型名、地址和上下文长度即可。

先备齐三个值

不管用哪个框架,先确认手里有这三样东西,后面的配置都是在填空。

  • 接口地址:https://api.llmzhongzhuan.com/v1。注意末尾的 /v1 要保留,但不要再加 /chat/completions,SDK 会自己拼接。
  • 密钥:在 获取密钥页 用邮箱和密码注册后立即显示,每个账号一把,可以重置,重置后旧的立即作废。
  • 模型名:只有一个,uncensored。你可以用 GET /v1/models 自己确认。

另外记住几个边界:上下文总长 100,000 token,单次 max_tokens 默认 2048、最大 16,000,每个密钥每分钟 300 次请求。这些数字在后面的参数设置里会反复用到。

环境变量的写法

把密钥写死在代码里是最常见的事故源头。推荐的做法是只读环境变量,部署时由容器或密钥管理系统注入。下面用 API_KEY 和 API_BASE 两个变量,各个框架的示例都沿用这两个名字。

# Linux / macOS:写进 ~/.zshrc 或 .env 加载脚本
export API_KEY="替换为你的密钥"
export API_BASE="https://api.llmzhongzhuan.com/v1"

# Windows PowerShell(仅当前会话)
$env:API_KEY = "替换为你的密钥"
$env:API_BASE = "https://api.llmzhongzhuan.com/v1"

如果你用 .env 文件,别忘了把它加进 .gitignore。另外,官方 Python SDK 默认会读取 OPENAI_API_KEY 和 OPENAI_BASE_URL,你既可以沿用这两个变量名,也可以像下面那样在构造函数里显式传参。显式传参的好处是不会被机器上残留的旧变量干扰,排错时少一个变量。

OpenAI Python SDK 与 Node SDK

Python 需要 v1 以上的 openai 包,Node 需要 v4 以上。两者的差别只有语法,构造客户端时传入地址和密钥即可。先看 Python 的非流式调用,同时打印 usage,方便核对扣费:

import os
from openai import OpenAI

client = OpenAI(
    base_url=os.environ.get("API_BASE", "https://api.llmzhongzhuan.com/v1"),
    api_key=os.environ["API_KEY"],
)

resp = client.chat.completions.create(
    model="uncensored",
    messages=[{"role": "user", "content": "写一条 20 字以内的发布公告标题。"}],
    max_tokens=100,
)
print(resp.choices[0].message.content)
print(resp.usage)

Node 示例改用流式输出,这是前端打字机效果最常用的写法。流式请求结束时服务端会自动补一个带 usage 的分片,不需要额外参数:

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: process.env.API_BASE ?? "https://api.llmzhongzhuan.com/v1",
  apiKey: process.env.API_KEY,
});

const stream = await client.chat.completions.create({
  model: "uncensored",
  messages: [{ role: "user", content: "列出三个命名服务端日志字段的好习惯。" }],
  stream: true,
  max_tokens: 300,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
process.stdout.write("\n");

Node 示例使用顶层 await,需要 .mjs 文件或在 package.json 里设置 "type": "module"。如果你的运行时不支持,把它包进 async 函数即可。

LangChain:ChatOpenAI 的 base_url

LangChain 里不需要任何专门的适配器,直接用 langchain_openai 包里的 ChatOpenAI,把 base_url 指过去就行。

import os
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="uncensored",
    base_url=os.environ.get("API_BASE", "https://api.llmzhongzhuan.com/v1"),
    api_key=os.environ["API_KEY"],
    temperature=0.7,
    max_tokens=500,
    timeout=60,
)

print(llm.invoke("把“服务已降级”改写成对用户友好的一句话。").content)

两个小提醒。第一,max_tokens 和 timeout 最好显式设置,默认值未必符合你的业务。第二,如果你在链里用工具调用,本站支持 OpenAI 格式的 tools,LangChain 的 bind_tools 可以正常使用;流式时在 stream() 里按块消费即可。

LlamaIndex:OpenAILike

LlamaIndex 自带的 OpenAI 类会校验模型名是否在官方清单内,遇到自定义模型名会报错。这时应该改用 llama-index-llms-openai-like 里的 OpenAILike,它不做这种校验,参数名也不同,地址叫 api_base。

import os
from llama_index.llms.openai_like import OpenAILike

llm = OpenAILike(
    model="uncensored",
    api_base=os.environ.get("API_BASE", "https://api.llmzhongzhuan.com/v1"),
    api_key=os.environ["API_KEY"],
    is_chat_model=True,
    context_window=100000,
    max_tokens=500,
)

print(llm.complete("用一句话解释什么是幂等请求。"))

这里的 is_chat_model=True 很关键,它让 LlamaIndex 走对话接口而不是旧的补全接口。context_window 设为 100000,LlamaIndex 在切分上下文时就不会误按默认的几千 token 截断你的文档。

Dify:OpenAI-API-compatible 供应商

Dify 这类可视化平台没有代码,而是通过表单配置。不同版本的界面文案略有出入,但步骤基本一致:

  1. 进入“设置”,找到“模型供应商”页面,在列表里选择“OpenAI-API-compatible”,点击添加模型。
  2. 模型类型选“LLM”,模型名称填 uncensored。
  3. API Key 填你的密钥,API endpoint URL 填 https://api.llmzhongzhuan.com/v1。
  4. 模型上下文长度填 100000,最大 token 上限填 16000。
  5. 如果要在工作流里使用工具调用,把函数调用支持选项打开;流式输出保持开启。
  6. 保存后新建一个简单的聊天应用,选中刚添加的模型发一句话,确认能正常返回。

平台有时会在保存时发一次探测请求,如果这一步失败,通常是地址多写了路径,或者密钥前后多了空格。如果你的 Dify 部署在容器里,还要确认容器能访问外网域名。

上线前要核对的参数与部署写法

框架能跑通只是第一步,下面几个参数在上线前最好逐一核对,它们决定了成本和失败率。

  • max_tokens:默认 2048,需要长文输出就显式调大,上限 16,000。注意输入和输出加起来不能超过 100,000,否则会收到 400。
  • timeout:长输出的请求耗时更久,流式场景建议把读超时设到 60 秒以上,非流式则按最长输出估算。
  • temperature / top_p / stop:这些标准采样参数会被透传,框架里设置的值会直接生效,不需要额外开关。
  • 并发:每个密钥每分钟 300 次请求,多个服务实例共用同一把密钥时,限流是合并计算的,别让每个实例都按 300 次来规划。
  • 重试:框架自带的重试往往只针对网络错误,对 429 和 503 要自己加退避,写法见稳定性那一篇。

部署到容器时,思路同样是“镜像里不放密钥”,由编排工具在启动时注入。下面是 compose 里的最小写法:

# docker-compose.yml 片段
services:
  app:
    image: your-app:latest
    environment:
      API_BASE: https://api.llmzhongzhuan.com/v1
      API_KEY: ${API_KEY}   # 从宿主机环境或 .env 读取,不写进镜像

在 Kubernetes 里就换成 Secret 引用,原则不变。另外建议给不同环境准备不同的账号或密钥,例如开发、预发和生产各一套,这样某个环境的密钥泄露或余额耗尽,不会连带影响线上。因为每个账号只有一把密钥,所以多环境最好对应多个账号,并分别预充值适量余额。

最后是日志。很多框架在调试模式下会打印完整的请求头,里面就包含 Authorization,务必在生产环境关闭这类调试输出,或者在日志过滤器里把密钥脱敏。把 usage 字段记录下来则是好习惯,它能帮你对照账单,也能提前发现某个功能的提示词突然变长。

报错时的排查顺序

接入阶段的问题 90% 集中在几处,按下面的顺序查最快:

  1. 401:密钥为空、复制时带了空格,或者重置后仍在用旧密钥。
  2. 404:地址写成了不带 /v1 的根路径,或者多拼了一次 /chat/completions。
  3. 402:错误码为 no_credit,说明余额用完或试用已过期,需要充值预付费余额。
  4. 400:常见原因是输入加 max_tokens 超过了 100,000,或请求体超过 8 MB。
  5. 429 / 503:前者是每分钟 300 次的限流,后者是 upstream_busy,稍等几秒重试,具体做法见 稳定性实践。

还有一类容易被忽略的问题是网络环境。公司内网、代理软件或者安全组规则,可能让域名解析成功却无法建立 HTTPS 连接,表现为长时间卡住后超时,而不是明确的错误码。遇到这种情况,先在同一台机器上用 curl 访问 /v1/models;能通就说明问题在应用层,不通则去查代理和防火墙。把排查过程记在团队文档里,下次新同事接入时就能少走一遍弯路。

三个值都正确却仍然失败时,先用 curl 直接请求一次,排除框架自身的问题。如果想先了解中转到底做了什么,可以回看 中转原理 这一篇。

常见问题

base_url 到底要不要带 /v1?

要带。写成 https://api.llmzhongzhuan.com/v1 即可,SDK 会在后面自动拼上 /chat/completions,不要重复添加。

LlamaIndex 为什么要用 OpenAILike 而不是 OpenAI?

OpenAI 类会检查模型名是否属于官方清单,自定义模型名会报错。OpenAILike 不做校验,更适合接入兼容接口。

Dify 里填的模型名可以随便起吗?

不可以,模型名必须是 uncensored,因为请求会原样带上这个 id。显示名称可以另外起,但模型字段要对。

环境变量改了为什么没生效?

多数是终端或进程没有重启。建议在代码里显式传 base_url 和 api_key,并打印一次实际使用的地址来确认。

只需填写表单即可获取密钥

创建账户,复制密钥,修改 Base URL。配置就是这么简单。

获取 API 密钥