先说明一句:Mythos 5.2 与 Fable 5.2 的具体模型 ID、上下文长度、计费方式、参数支持范围,各平台发布节奏不同,一律以官方文档当前版本为准。本文给的是可迁移的方法和可直接照抄的代码骨架,把占位符换成官方页面上的真实值即可跑通。
这篇能做出什么
按下面的步骤走完,你会得到四样东西:
1. 一条同时能调 Mythos 5.2 和 Fable 5.2 的调用链,带超时、重试和用量统计,出错能定位到具体环节。
2. 一张分工决策表:什么任务交给谁,判断依据是什么,而不是凭感觉。
3. 一个可复用的对照测试脚本,对创意任务和长文任务各跑一轮,输出延迟、token 消耗和人工评分表。
4. 一份提示迁移清单,把原来写给单个模型的提示词改成两个模型都能吃的写法。
最后你会有一个"路由函数":输入任务类型,自动选模型,跑不通时降级到另一个。
前置条件清单
动手前确认这几项:
- 两个模型都已开通调用权限的 API Key,并且知道各自的模型 ID(在控制台或官方文档的模型列表页能找到)。
- 一台能访问外网的开发机,Python 3.9 以上或 Node 18 以上,任选其一,本文用 Python 举例。
- 会设置环境变量,不会把密钥写进代码再提交到仓库。
- 准备 10~20 条你自己的真实任务作为评测集。这一步别省,用别人的样例测出来的结论对你没用。
- 心里有个预算上限。双模型对照测试的调用量大概是单模型的 2~3 倍,先把每日额度看清楚。
第一步:先立假设,再验证
不要一上来就跑分。先写下你对两个模型的分工假设,后面用实测去证实或推翻。一个常见的初始假设是这样:
| 维度 | Mythos 5.2(假设) | Fable 5.2(假设) |
|---|---|---|
| 擅长 | 叙事、风格化表达、发散构思 | 结构稳定、长文一致性、指令跟随 |
| 适合任务 | 文案初稿、故事线、多版本创意 | 报告、白皮书、长文档改写、结构化输出 |
| 交互形态 | 短轮次、多采样、人来挑选 | 单次长输出、少返工 |
| 主要风险 | 事实漂移、篇幅不稳定 | 表达偏平、创意保守 |
把这张表当成"待验证的猜想",不是结论。真正决定分工的是你自己的任务分布,别人的评测结论只能当参考。
判断标准建议固定成三条:能不能直接用(人工改稿时间)、稳定性(同一提示跑 5 次,结果方差大不大)、单位任务成本(不是单 token 价格,是"做完一件事"的总花费,含重试和改稿)。
第二步:配置凭证
用环境变量管理密钥,本地开发放 .env,线上走密钥管理服务。
```bash
.env 示例,值替换成你自己的
export API_BASE_URL="https://你的服务商接口地址"
export API_KEY="sk-你的密钥"
export MODEL_MYTHOS="填官方文档里的模型 ID"
export MODEL_FABLE="填官方文档里的模型 ID"
```
.env 记得加进 .gitignore。代码里只读环境变量,任何情况下不硬编码。
```bash
确认变量已生效
source .env
echo "$MODEL_MYTHOS / $MODEL_FABLE"
```
第三步:用 curl 打通第一条请求
不要跳过这一步直接上 SDK。裸请求能让你看清报文结构、错误码和返回字段,后面出问题才知道是哪一层。
```bash
curl -sS "$API_BASE_URL/v1/messages" \
-H "x-api-key: $API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "'"$MODEL_MYTHOS"'",
"max_tokens": 512,
"messages": [
{"role": "user", "content": "用三句话介绍一个面向运维人员的日志排查工具"}
]
}'
```
请求体里几个字段值得记住:
model:模型 ID,两个模型分别填不同值。max_tokens:输出上限,必填。设小了会被截断,返回里stop_reason会是max_tokens。system:系统提示,和messages分开传,比塞进第一条用户消息更干净。messages:对话数组,角色交替,第一条通常是user。
返回里重点看三处:content 里的文本、stop_reason(是不是正常结束)、usage(输入输出 token 数)。把 usage 记下来,后面算成本全靠它。
如果用的是 OpenAI 兼容风格的接口,路径和字段名会不一样(/v1/chat/completions、messages 结构类似但认证头不同),以服务商文档为准,逻辑是一样的。
第四步:封装一个带重试的调用函数
裸 curl 只用来验证,实际跑测试要封装。下面这个函数把超时、429 退避重试、用量统计都包进去了。
```python
import os, time, json, random
import urllib.request, urllib.error
BASE = os.environ["API_BASE_URL"]
KEY = os.environ["API_KEY"]
def call(model: str, prompt: str, system: str = "", max_tokens: int = 2048,
temperature: float = 0.7, retries: int = 3) -> dict:
body = {
"model": model,
"max_tokens": max_tokens,
"temperature": temperature,
"messages": [{"role": "user", "content": prompt}],
}
if system:
body["system"] = system
data = json.dumps(body).encode("utf-8")
for attempt in range(retries):
req = urllib.request.Request(
f"{BASE}/v1/messages", data=data, method="POST",
headers={
"x-api-key": KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
)
started = time.time()
try:
with urllib.request.urlopen(req, timeout=120) as resp:
payload = json.loads(resp.read().decode("utf-8"))
text = "".join(b.get("text", "") for b in payload.get("content", []))
return {
"model": model,
"text": text,
"stop_reason": payload.get("stop_reason"),
"usage": payload.get("usage", {}),
"latency_s": round(time.time() - started, 2),
}
except urllib.error.HTTPError as e:
code = e.code
detail = e.read().decode("utf-8", "ignore")[:300]
429 限流、5xx 服务端错误可以重试;4xx 参数错误重试没意义
if code in (429, 500, 502, 503, 504) and attempt < retries - 1:
wait = (2 ** attempt) + random.random()
time.sleep(wait)
continue
raise RuntimeError(f"HTTP {code}: {detail}") from e
except TimeoutError:
if attempt < retries - 1:
time.sleep(2 ** attempt)
continue
raise
raise RuntimeError("重试次数用尽")
```
注意 429 和 4xx 的处理区别:限流要等,参数错了再重试一百次也是错。这条规则能省掉大量无效等待。
第五步:提示迁移
把原来给单个模型写的提示词迁到两个模型上,改动集中在四处。
1. 系统提示和用户内容分离。 角色设定、输出规范放 system,具体任务放 messages。这样同一套规范能复用到所有请求。
2. 输出格式写死。 需要结构化结果就直接给 schema,别指望模型猜。
```text
只输出 JSON,不要加解释、不要加 Markdown 代码块标记。字段:
{"title": string, "summary": string, "tags": string[]}
summary 控制在 80 字以内。缺少信息时字段填 null,不要编造。
```
3. 长度用可验证的约束。 "写得详细一点"没用,"分 5 个小节,每节 150~200 字"有用。长文任务尤其要给骨架:先列大纲,确认后再展开。
4. 温度参数">温度参数分开调。 创意任务温度高一些、多采样几个版本让人挑;结构化长文温度压低,减少跑偏。两个模型的温度敏感度可能不同,实测时把它当变量单独测。
迁移后跑一遍回归:拿原来那批提示各跑三次,看有没有出现"格式不对""长度不达标""把 system 里的指令当用户提问回答"这类退化。
第六步:跑对照实测
评测脚本的核心是:同一批提示,两个模型各跑一遍,记录客观指标,然后人工打分。客观指标自动算,主观分一定要盲评——把模型名去掉,只给人看输出。
```python
CASES = [
{"id": "c1", "type": "creative",
"prompt": "为一个面向开发者的笔记工具写三条风格迥异的广告语,分别偏理性、偏幽默、偏诗意。"},
{"id": "c2", "type": "creative",
"prompt": "给一款企业级备份软件起 8 个产品名,每个附一句 20 字内的定位说明。"},
{"id": "l1", "type": "longform",
"prompt": "写一份《新员工入职第一周环境搭建指南》,分准备、安装、验证、排错四部分,"
"每部分不少于 300 字,步骤用有序列表,命令用代码块。"},
{"id": "l2", "type": "longform",
"prompt": "把以下会议记录整理成结构化纪要,包含结论、待办、负责人、截止时间四项……"},
]
def run_suite(models: dict) -> list:
rows = []
for case in CASES:
for label, model_id in models.items():
try:
r = call(model_id, case["prompt"], system="你是严谨的中文技术写作者。",
max_tokens=3000)
rows.append({
"case": case["id"], "type": case["type"], "label": label,
"latency_s": r["latency_s"],
"in_tok": r["usage"].get("input_tokens"),
"out_tok": r["usage"].get("output_tokens"),
"stop": r["stop_reason"], "chars": len(r["text"]),
"text": r["text"],
})
except Exception as e:
rows.append({"case": case["id"], "type": case["type"],
"label": label, "error": str(e)[:200]})
return rows
if __name__ == "__main__":
models = {"mythos": os.environ["MODEL_MYTHOS"], "fable": os.environ["MODEL_FABLE"]}
result = run_suite(models)
with open("bench.jsonl", "w", encoding="utf-8") as f:
for row in result:
f.write(json.dumps(row, ensure_ascii=False) + "\n")
print(f"完成 {len(result)} 条")
```
评分用固定 rubric,别用"感觉哪个好"。创意任务看四项:新颖度(是否给出不落俗套的方案)、可用性(改几个字能上线)、风格贴合(要求的三种风格有没有真的区分开)、多样性(一次给多个方案时是否同质化)。长文任务看四项:结构完整(该有的部分有没有)、事实一致(前后有没有自相矛盾)、长度达成(是否满足字数区间)、格式正确(代码块、列表、标题层级)。
每项 1~5 分,两人独立打分取均值。跑完你会得到一张表,大致会长成这样(示意,非真实数据):
| 用例类型 | 模型 | 平均延迟 | 输出字数 | 结构分 | 可用性分 |
|---|---|---|---|---|---|
| 创意 | Mythos 5.2 | 待测 | 待测 | 待测 | 待测 |
| 创意 | Fable 5.2 | 待测 | 待测 | 待测 | 待测 |
| 长文 | Mythos 5.2 | 待测 | 待测 | 待测 | 待测 |
| 长文 | Fable 5.2 | 待测 | 待测 | 待测 | 待测 |
表里填的是你自己跑出来的数字。任何来源的现成跑分都只能当背景参考,你的任务分布、提示写法、语言习惯都会改变结论。
一个经验:创意任务建议每个提示跑 3~5 次,看最好的那次质量如何(人可以从多个候选里挑);长文任务跑 2~3 次,看最差的那次能不能接受(长文返工成本高,稳定性比峰值更重要)。
第七步:写一个路由函数
测完就该固化了。简单的按类型路由,带降级:
```python
ROUTE = {
"creative": "MODEL_MYTHOS",
"longform": "MODEL_FABLE",
"structured": "MODEL_FABLE",
}
def smart_call(task_type: str, prompt: str, **kw) -> dict:
primary = os.environ[ROUTE.get(task_type, "MODEL_FABLE")]
fallback = os.environ["MODEL_MYTHOS" if primary == os.environ["MODEL_FABLE"] else "MODEL_FABLE"]
try:
return call(primary, prompt, **kw)
except Exception as e:
print(f"[warn] 主模型失败({e}),降级到备用模型")
return call(fallback, prompt, **kw)
```
路由表要能从配置改,不要写死在代码里——模型版本更新后你要能一天内切过去。
常见坑与排错
401 / 403 认证失败。 先确认密钥没多空格、没带引号残留,再确认这个密钥对目标模型有权限。有些平台不同模型的权限是分开开的。
404 模型不存在。 九成是模型 ID 写错了,或者把展示名当成了 ID。控制台的模型列表页复制,别手打。
429 限流。 加指数退避,同时把并发降下来。批量跑测试时用信号量限制并发数,别一次全发出去。
输出被截断。 看 stop_reason 是不是 max_tokens。长文任务把上限调高,或者在提示里要求"先输出大纲,等我确认后再展开",分段生成。
长文前后矛盾。 单次生成越长越容易漂。做法是分段:先出大纲,再逐节生成,每节生成时把前面已确认的内容摘要塞进上下文。这比单纯拉长AI 词典:上下文窗口">上下文窗口更稳。
创意输出同质化。 一次要 8 个方案,模型可能给你 8 个近义词。提示里明确要求"风格必须互斥,不得使用相同句式",并把温度调高。
成本失控。 测试脚本一定要打印累计 token。设一个硬上限,超过就抛异常停下。输入侧的长文档考虑做摘要或分段。
评测结论不可信。 三个原因:样本太小、非盲评、只看一次结果。样本至少 10 条覆盖你的真实场景,盲评,每类跑多轮。
下一步建议
跑通之后按这个顺序往下推:
1. 建回归集。 把这次评测的用例固化成回归集,每次模型版本更新或改提示词都重跑一遍,比对分数。这比上线后才发现退化便宜得多。
2. 加缓存与日志。 相同提示命中缓存能直接砍掉一大块成本;日志里存任务类型、模型、token、耗时、stop_reason,出问题能回溯。
3. 小流量灰度。 新的分工方案先接 10% 流量,观察人工改稿率和报错率,再全量。
4. 定期复核分工表。 模型迭代快,今天的分工结论三个月后可能就不成立。把复核排进季度计划,用回归集重跑一次就行。
5. 关注官方变更日志。 新模型 ID、废弃旧版本、参数调整都会在那里发布,订阅比出事后排查省事。
最后强调一遍:本文所有模型 ID、参数支持范围、计费与配额,以官方文档当前版本为准。方法是通用的,数字得你自己跑出来。
