把一个聚合了多个模型的平台当成统一入口时,先确认三件事:账号下有哪些模型可选、每个模型用哪种调用方式、每个模型能接收多长的输入。这三项通常都能在控制台页面或说明文档里查到,也能用一条最小请求加调用日志交叉验证。在确认之前,模型名不要写死在代码里,长文本也不要假定所有模型都能完整接收。
接入 JellyToken 这类多模型入口,先做三件可验证的事:在控制台把模型名称、模型标识、流式支持、输入长度抄成对照表并标注来源;用一条只含单条用户消息的最小请求确认密钥与模型标识匹配;再按各模型里最小的输入长度上限,给长提示词定裁切或分段规则。判断依据只看页面显示、返回状态码和调用日志,未确认前不要写死模型名,也不要把某个模型的专属标记带进通用提示词。
在平台页面上把可用模型列成一张对照表
对照表的作用是防止凭记忆写模型名。模型名称是给人看的展示名,模型标识才是请求里 model 字段要填的值,两者经常不一致;漏掉这一条,最容易出现“页面上看得到、请求却报模型不存在”的情况。建议打开控制台逐个模型抄录,四列都要,再加一列写明这条信息是从控制台页面还是从说明文档读到的,后面排查时有据可查。
| 模型名称 | 模型标识 | 是否支持流式 | 输入长度上限 | 来源(控制台页面/说明文档) |
|---|---|---|---|---|
| 控制台上的展示名 | 请求里 model 字段要填的值 | 是 / 否 / 未标注 | 文档写明的数值与单位(token 或字符) | 控制台页面 或 说明文档 |
| 第二个模型的展示名 | 对应模型标识 | 是 / 否 / 未标注 | 写明数值与单位 | 控制台页面 或 说明文档 |
| (继续按账号实际可用的模型补齐) |
表里填不出来的格子不要猜,直接写“文档未标注”。输入长度上限没写的模型,先按第三小节的保守办法处理;控制台显示支持流式、文档却没提的,先用非流式跑通再试流式,这样出错时只需要判断一个变量。
用一条最小请求验证密钥与模型标识是否匹配
这一步只排除第一层错误:密钥环境不对、鉴权头写法不对、模型标识写错。请求体里只留模型标识和一条用户消息,不要顺手带上温度、工具、系统提示等额外参数,否则参数错误会被误读成鉴权或模型错误。下面是一段通用 HTTP 骨架,路径和字段以平台文档为准,不确定时照抄文档给出的完整地址,不要自己拼接。
POST {BASE_URL}/chat/completions
Authorization: Bearer {API_KEY}
Content-Type: application/json
{
"model": "{MODEL_ID}",
"messages": [{"role": "user", "content": "ping"}],
"stream": false
}
用 SDK 时同样只保留这两项,替换项只有三处:地址前缀取控制台或文档给出的值,密钥从环境变量读取而不是粘进代码,模型标识直接用上一张表里的值。
import os
from openai import OpenAI # 仅当平台兼容该客户端时;否则换成文档给出的客户端
client = OpenAI(
base_url="{BASE_URL}",
api_key=os.environ["JELLY_API_KEY"],
)
resp = client.chat.completions.create(
model="{MODEL_ID}",
messages=[{"role": "user", "content": "ping"}],
)
print(resp.choices[0].message.content)
返回结果按状态码读,不同返回说明的问题不一样:
- 200 且返回内容非空:密钥与模型标识这一层对得上,可以继续往下测;
- 401 或 403:先确认环境变量是否真的取到(打印长度,不打印内容)、鉴权头前缀是否写对、密钥是否属于当前环境;
- 404 或提示模型不存在:多半是模型标识写错,或该模型没有在当前账号下开通;
- 400:模型标识能找到,但请求字段不被接受,回头核对文档里的参数名和消息结构。
平台侧一般有调用记录,用请求发出的时间对上一条日志,可以确认请求究竟打到了哪个模型。这一步完成后,才值得去调提示词内容。
对比不同模型的输入长度,给超长提示词定处理规则
同一份长文本在一个模型上通过、换到另一个模型被截断,是换模型时最常见的翻车方式。把对照表里的输入长度上限横向排一遍,取其中最小的值作为通用阈值,超过阈值的长文本走裁切或分段,而不是每次换模型再改一遍代码。
没有精确 token 计数器时,可以先按字符数估:中文大致接近一字一 token 的量级,英文大致四字符一 token,不同分词器的差异明显,只能当作粗略估计。平台或所用库提供计数能力时以计数结果为准;两处都没有,就把字符数阈值压在页面标注值的七成左右,再留余量。
MAX_CHARS = 3000 # 填入对照表里最小的输入长度上限,再留出余量
def split_text(text, max_chars=MAX_CHARS):
if len(text) <= max_chars:
return [text]
return [text[i:i + max_chars] for i in range(0, len(text), max_chars)]
parts = split_text(long_doc)
partials = [call(model_id, part) for part in parts]
final = call(model_id, "\n".join(partials))
选择规则看内容性质:裁切适合丢掉的只是冗余部分,比如日志尾部、重复段落;分段适合信息都重要的长文档,先分块处理再合并,代价是多花几次调用。无论哪种,超限时都不建议静默截断后继续跑,在被截断的位置打一条日志(记录原始字符数、模型标识、阈值),事后排查比靠猜省事得多。
把提示词里的模型专属写法抽出来
提示词里最容易变成“只能在一个模型上跑”的部分,通常是特殊分隔标记、系统消息的摆放位置、结构化输出的约束方式。这些写法在单一模型下很好用,换到聚合入口里的另一个模型,就可能被当作正文原样输出,或者被直接忽略。先列出当前提示词里用了哪些专属写法,再逐条改写成通用表达。
- 特殊分隔标记:形如 <|im_start|>、<|im_end|> 这类对话分隔符,多数模型只把它当普通文本;
- 系统消息位置:有的写法把规则塞进第一条用户消息,有的依赖单独的 system 角色,跨模型时应统一用 messages 里的 role 表达;
- 结构化输出:依赖某家 JSON 模式、函数调用或特定参数名,换模型后不生效,需要在提示词里自带字段示例;
- 停止词与模板尾巴:某些结束标记用在别的模型上不会触发停止,会持续生成多余内容。
改写前后的对照可以这样写。改写前是拼接字符串加专属标记:
# 改写前:依赖特定分隔符
prompt = (
"<|im_start|>system\n只输出 JSON<|im_end|>\n"
"<|im_start|>user\n改写下面这句话:...<|im_end|>"
)
# 改写后:只用通用的角色与消息结构
messages = [
{"role": "system", "content": "只输出一个 JSON 对象,字段为 text,不要任何额外文字"},
{"role": "user", "content": "改写下面这句话:..."},
]
# 返回后再用 json.loads 校验一次,解析失败就重试或提示模型
改写之后要回到第二小节的骨架,给每个模型各跑一条最小请求,确认同一段解析代码都能接住返回结果。验证方式是看日志里的解析报错类型和失败次数,而不是靠肉眼看返回像不像 JSON。提示词里保留了模型专属写法的地方,建议在对照表里单独备注,换模型时优先检查这几处。