先拿同一段提示词在三个模型上跑一遍——再决定 JellyToken 里用哪个

文章导读
JellyToken 的模型列表拉下来十几个名字,光看描述很难判断哪个适合手上的任务。与其问别人推荐哪个,不如先花半小时做一次横向对照:同一段提示词、同一组参数、同一套评判点,在三个候选模型上各跑一遍,看结果再决定固定用哪个。选型的依据应该是你自己跑出来的记录,而不是印象或者别人的一句推荐。
📋 目录
  1. 壹 写一个把提示词和参数外置的调用脚本
  2. 贰 为三类任务各准备一条真实提示词
  3. 叁 逐条记录结果与耗时,不用主观好坏打分
  4. 肆 对差异最大的那条做重复调用
  5. 伍 把结论写成模型选择记录
A A

JellyToken 的模型列表拉下来十几个名字,光看描述很难判断哪个适合手上的任务。与其问别人推荐哪个,不如先花半小时做一次横向对照:同一段提示词、同一组参数、同一套评判点,在三个候选模型上各跑一遍,看结果再决定固定用哪个。选型的依据应该是你自己跑出来的记录,而不是印象或者别人的一句推荐。

先用同一段提示词、同一组参数、同一套评判点,在三个候选模型上各跑一遍,把是否完整返回、格式是否符合要求、结束原因和耗时逐条记下来;哪一条差异最大,就把它重复调用三次,排除单次采样波动。这套做法适合候选模型只有两三个、任务类型相对固定的接入选型,不适合拿它替代长链路评测或者按线上流量做压测。最终结论写进一份模型选择记录,注明实验时的参数取值和提示词版本,后续换模型才有对照。

写一个把提示词和参数外置的调用脚本

对照实验最容易失控的地方是变量没锁住:换了模型,顺手把温度也调了、提示词也改了,最后结果变好变坏都说不清是哪个改动带来的。所以第一步是把模型标识、提示词、参数三样东西从代码里挪出去,放进配置文件和单独的文本文件,让换模型只改一个字段。

如果 JellyToken 暴露的是常见的 OpenAI 兼容接口,可以用下面这个骨架;如果路径或字段不一样,只需要替换发请求那一段,其余结构不变。运行前把网关地址填进 compare.json,密钥放环境变量,不要写进文件。

# run_compare.py
import json, os, time, hashlib, requests

cfg = json.load(open("compare.json", encoding="utf-8"))
# compare.json 示例:
# {
#   "base_url": "https://<你的网关地址>/v1",
#   "models": ["model-a", "model-b", "model-c"],
#   "prompt_file": "prompts/extract_v1.txt",
#   "params": {"temperature": 0.2, "max_tokens": 512, "top_p": 1.0}
# }
API_KEY = os.environ["JELLY_API_KEY"]
prompt = open(cfg["prompt_file"], encoding="utf-8").read()
prompt_ver = hashlib.md5(prompt.encode()).hexdigest()[:8]

log = open("runs.jsonl", "a", encoding="utf-8")
for model in cfg["models"]:          # 换模型只改这一行数组,或加 `--model` 覆盖
    body = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
        **cfg["params"],
    }
    t0 = time.time()
    r = requests.post(cfg["base_url"] + "/chat/completions",
                      headers={"Authorization": "Bearer " + API_KEY},
                      json=body, timeout=120)
    elapsed_ms = int((time.time() - t0) * 1000)
    data = r.json()
    choice = (data.get("choices") or [{}])[0]
    text = (choice.get("message") or {}).get("content", "")
    log.write(json.dumps({
        "model": model, "prompt_ver": prompt_ver, "params": cfg["params"],
        "http_status": r.status_code, "finish_reason": choice.get("finish_reason"),
        "elapsed_ms": elapsed_ms, "content": text,
    }, ensure_ascii=False) + "\n")
log.close()

变量来源要固定下来:base_url、models、params 读 compare.json,密钥读环境变量 JELLY_API_KEY,提示词读 prompts 目录下的 .txt 文件。验证改动是否真的生效,最省事的办法是在发请求前把 body 打出来看一眼 model 字段,或者在网关侧日志里核对模型标识,别只看返回内容像不像就算换了。params 每次只动一个,动过就重新起一轮对照,否则前面的记录作废。

为三类任务各准备一条真实提示词

用“你好,介绍一下你自己”这类玩具提示词得出的差异,换到真实任务上基本没有参考价值,因为大多数模型都能答得漂亮。建议从你实际要接的三类任务里各挑一条真实输入:结构化抽取、依赖原文的事实问答、有长度上限的改写。每条提示词单独存一个文件,文件名带版本号,例如 prompts/extract_v1.txt。

# 1) 结构化抽取 extract_v1.txt
你是订单信息抽取器。只输出 JSON,不要任何解释和 markdown 代码块。
字段:order_no(字符串)、amount(数字)、currency(三位大写字母)、items(数组,每项含 name 和 qty)。
缺失的字段填 null,不确定的值不要猜测。
文本:7 月 12 日下单,单号 A-3391,两箱 A4 纸,合计 260 元人民币,付款方式未确认。

这条的评判点是:输出能不能直接 json.loads 通过、字段名和类型是否与约定一致(amount 是数字而不是字符串)、缺失的付款方式有没有被编出来。

# 2) 事实问答 qa_v1.txt
根据下面提供的原文回答问题。答案必须能在原文中找到依据,并引用原文中的那句话。
如果原文没有提到,直接回答“原文未提及”,不要补充你自己的知识。
原文:本次维护窗口为周六 02:00 至 04:00,期间只读接口可用,写入接口暂停。
问题:维护期间写入接口是否可用?

评判点是:有没有给出可核对的原文引用、答案与原文是否一致、在原文没有覆盖的问题上会不会老实说未提及。

# 3) 长度受控改写 rewrite_v1.txt
把下面的活动说明改写成不超过 120 字的客服口径,保留时间、参与方式、限制条件三项信息,
不要增加原文中没有的承诺,也不要省略限制条件。
原文:活动自 8 月 1 日起,用户可在客户端首页入口参与,每人限参与一次,
名额共 5000 个,先到先得,活动解释权归平台所有。

评判点是:字数是否不超过 120、三项信息是否齐全、有没有新增原文没有的承诺。这三类任务分别对应格式约束、事实约束和长度约束,能把模型之间的差别暴露得比闲聊明显得多。

先拿同一段提示词在三个模型上跑一遍——再决定 JellyToken 里用哪个

逐条记录结果与耗时,不用主观好坏打分

“感觉 B 写得更好”这种判断没法复现,也没法在同事之间对齐。把比较落到脚本能判定的字段上:记录表至少包含模型标识、是否完整返回、格式是否符合要求、结束原因、耗时五项。其中格式是否符合要求由脚本判定,例如抽取任务用 json.loads 试一次,通过写 true,不通过写 false,不要靠人眼看。

{"model":"model-b","prompt_ver":"a1b2c3d4","params":{"temperature":0.2,"max_tokens":512},
 "http_status":200,"finish_reason":"stop","elapsed_ms":2140,"format_ok":true}

看记录的时候重点在三个地方:finish_reason 是不是 stop,如果是 length 说明输出被截断,格式判定失败可能只是预算不够,先把 max_tokens 调大再判断;format_ok 为 false 的模型,在需要程序解析的场景里要先扣分;耗时只做同机、相近时间段的横向比较,网络波动就会影响结果,单次耗时不建议当成定论。

对差异最大的那条做重复调用

三条结果里差异最大的那条,往往正好是关键判断点,比如某个模型格式不符或者直接编了事实。这时候不能只看一次,因为采样本身就有波动。做法是保持模型、提示词、参数完全不变,把同一个请求重复三次,在日志里加一个 run_index 字段区分第几次,输出分别落到 run_1.json、run_2.json、run_3.json,或者在同一份 jsonl 里按 run_index 过滤。

三次结果比较三件事:结构是否一致(抽取任务比字段和值,改写任务比字数),finish_reason 是否都是 stop,耗时是否在同一量级。如果三次不一致,按顺序排查:先看是不是 length 截断,把 max_tokens 调大再跑;温度大于 0 的先把温度降到 0 再跑一轮,看输出是否稳定;降完仍不稳定,就在选择记录里标注“该任务下输出不稳定”,考虑给解析失败加一次有限重试,或者换另一个候选模型。不要把三次里最好看的那一次当成结论写进记录。

把结论写成模型选择记录

实验做完,把结论落成一个文件放进仓库,例如 docs/model-selection.md,按任务类型写清选定模型、当时的参数取值和提示词版本号。这样半年后有人问“为什么用这个模型”,翻文件就有答案;换模型时拿同一份提示词和参数重跑对应那一行,比较新旧记录就知道该不该改。

| 任务类型   | 选定模型 | temperature | max_tokens | 提示词文件与版本 | 备注 |
|------------|----------|-------------|------------|------------------|------|
| 结构化抽取 | model-b  | 0.2         | 512        | extract_v1 (a1b2c3d4) | 三次重复输出字段一致 |
| 事实问答   | model-a  | 0.2         | 512        | qa_v1 (e5f6a7b8)      | 有一处未按原文引用,待观察 |
| 长度受控改写 | model-c | 0.2        | 512        | rewrite_v1 (c9d0e1f2) | 字数稳定在限值内 |

记录里的模型标识要写实际调用时用的那个字段值,不要写展示名。参数和提示词只要改动其中任意一项,原来那一行记录就不再适用,需要重跑后再更新,不要在原行上直接改数值。若某个任务类型长期只有单一模型表现稳定,可以先固定它,其余模型留作备选;这个判断建议每隔一段时间用同一套提示词复核一次,确认结论没有因为版本更新而失效。