接入 JellyToken 时,先别把业务提示词和一堆参数一起塞进去。用一条只有单轮 user 消息的请求,把地址、鉴权头、模型名、消息体这几项固定下来跑一次,只要这次返回能按预期读到正文字段,密钥、模型名、返回格式三件事就算对上了。之后再把业务逻辑叠上去,排查范围才收得住。
判断依据很简单:最小请求就失败,问题在配置或链路;最小请求通过而业务请求失败,问题多半在提示词或参数。适用场景是首次接入、更换密钥或切换模型;操作动作是先跑单轮请求,再看返回体里的正文、用量和结束原因;验证方式看 HTTP 状态码与这三个字段是否读得出来;风险边界是这只证明链路和格式对得上,不替代业务侧的内容质量校验。
写一条只有单轮用户消息的请求
请求骨架只保留四项:地址、鉴权头、模型标识、单条消息。地址通常来自控制台或接入说明里给的 base url,先确认末尾是否已经带版本段(例如 /v1),拼接时不要重复;鉴权头按说明放进 Authorization,常见形式是 Bearer 加密钥;模型标识要从控制台可用的模型列表里原样复制,不要凭记忆写;消息只留一条 role 为 user 的内容,写 ping 这类短文本就够。密钥和模型名建议从环境变量取,别写死在代码里。
import os, requests
base = os.environ['JELLYTOKEN_BASE_URL'].rstrip('/')
key = os.environ['JELLYTOKEN_API_KEY']
model = os.environ['JELLYTOKEN_MODEL']
resp = requests.post(
base + '/chat/completions',
headers={'Authorization': 'Bearer ' + key, 'Content-Type': 'application/json'},
json={'model': model,
'messages': [{'role': 'user', 'content': 'ping'}],
'max_tokens': 16,
'stream': False},
timeout=30,
)
print(resp.status_code)
print(resp.text)
运行方式:先把三个环境变量导出,再执行这段代码。首次故意用 resp.text 打印原始返回,是让你先看清返回体长什么样,再决定怎么取值。如果你的接入路径或字段名与这里不同,以控制台给的说明为准,只替换地址和字段名,不要顺手改请求结构。
读取返回体里的关键字段
确认返回体后,要确保程序取的是正文字段,而不是整包打印。也不要写 data['choices'][0]['message']['content'] 这种深链,任何一层缺失都会直接抛异常,报错信息还指向不到真正原因。
data = resp.json()
choices = data.get('choices') or []
choice = choices[0] if choices else {}
message = choice.get('message') or {}
text = message.get('content')
finish = choice.get('finish_reason')
usage = data.get('usage') or {}
p_tok = usage.get('prompt_tokens')
c_tok = usage.get('completion_tokens')
if not isinstance(text, str) or text == '':
print('未取到正文, 顶层字段:', list(data.keys()))
print('choice 字段:', list(choice.keys()))
else:
print(text)
print('finish_reason=', finish, 'prompt=', p_tok, 'completion=', c_tok)
正文用来判断返回格式是否对得上;用量字段(输入、输出 token)用来确认返回是否完整、计费口径是否和自己预期一致;结束原因用来区分正常结束还是被长度截断。字段缺失时不要直接崩溃,先把顶层字段名和 choice 字段名打出来,通常就能看出是返回结构和预期不一致,还是这一路返回的其实是错误体。具体字段命名需要结合你实际拿到的返回确认,不要硬套模板。
把异常分支分成鉴权、参数、服务三类
把失败分成三类,报错就能直接指向下一步动作,而不是反复试。判断顺序建议先看状态码,再看返回体里的错误描述;两者指向不一致时,以返回体描述为准。
- 鉴权类:状态码 401 或 403,或返回体错误信息指向密钥无效、无权限、额度不足。检查动作是密钥是否原样使用、有没有多余空格或换行、Authorization 前缀是否写对、密钥是否属于当前环境、请求头有没有被中间层改写。这一类不要先动模型名。
- 参数类:状态码 400、404 或 422,返回体提示模型不存在或字段不合法。检查动作是模型标识是否与控制台完全一致(大小写、连字符)、messages 是否为数组且每条都有 role 和 content、max_tokens 等数值是否在允许区间、路径有没有多写或少写版本段。
- 服务类:状态码 429 或 5xx,以及连接超时、地址无法解析。检查动作是地址是否可达、是否触发限流需要退避后重试、是否为服务端临时故障。这一类不要反复改请求体。
这三类之外的情况,先把原始返回保留下来再处理,不要凭状态码之外的现象猜原因。
把这条请求固化成可重复运行的自检脚本
把三个变量做成环境变量或命令行参数,改配置后先跑自检再接业务。输出固定成一行,方便贴到工单或群里对比。退出码 0 表示通过,非 0 表示失败,便于手动或流水线判断;密钥只打印前后各四位。
import os, sys, requests
def mask(s):
return s[:4] + '****' + s[-4:] if len(s) > 8 else '****'
def main():
base = os.environ['JELLYTOKEN_BASE_URL'].rstrip('/')
key = os.environ['JELLYTOKEN_API_KEY']
model = os.environ['JELLYTOKEN_MODEL']
r = requests.post(base + '/chat/completions',
headers={'Authorization': 'Bearer ' + key, 'Content-Type': 'application/json'},
json={'model': model,
'messages': [{'role': 'user', 'content': 'ping'}],
'max_tokens': 16, 'stream': False},
timeout=30)
if r.status_code in (401, 403):
print('[FAIL] kind=auth status=%s msg=%s' % (r.status_code, r.text[:200]))
sys.exit(2)
if r.status_code in (400, 404, 422):
print('[FAIL] kind=param status=%s msg=%s' % (r.status_code, r.text[:200]))
sys.exit(3)
if r.status_code != 200:
print('[FAIL] kind=service status=%s msg=%s' % (r.status_code, r.text[:200]))
sys.exit(4)
d = r.json()
c = (d.get('choices') or [{}])[0]
text = ((c.get('message') or {}).get('content')) or ''
print('[PASS] model=%s status=200 content_len=%d finish=%s key=%s' %
(model, len(text), c.get('finish_reason'), mask(key)))
sys.exit(0)
if __name__ == '__main__':
main()
通过时应该是一行 PASS:模型名是你填的那个,content_len 大于 0,finish 通常是 stop,密钥只显示前后四位。如果 finish 是 length,说明被 max_tokens 截断,链路是通的但不适合当通过样本,把 max_tokens 调大一点再跑一次。
在自检通过后再接业务提示词
自检通过前不要改业务提示词,否则链路问题和内容问题会混在一起。扩展时按下面的清单逐项加,每加一步重跑一次自检脚本。
- messages 从单条扩成系统提示词加多轮对话:system 消息放在数组最前,role 名拼写要准。最容易改错的是把业务说明塞进 user 而漏掉 system,或者把整段对话拼成一个字符串。
- 增加采样参数,如 temperature、top_p、max_tokens:一次只加一个,确认返回正常再加下一个。最容易改错的是把数值写成字符串。
- 换真实业务输入:把 ping 换成实际内容,先保持单轮,确认返回仍能读出正文,再考虑多轮和上下文拼接。
- 考虑流式:stream 打开后返回形态会变,正文不再是单个 JSON 字段,而是一行行增量片段,解析方式完全不同。建议非流式跑通后再接流式,避免两类问题混在一起。
把每次自检输出的 PASS 或 FAIL 行留下来,改配置后前后对比,比反复猜故障点可靠得多。