这篇能做出什么
做完之后,你会有一个不绑死在任何一家模型上的工作台:
- 同一个入口,能在豆包、Kimi、DeepSeek 之间切换,换模型不用换工具、不用重新贴一遍上下文;
- 一张写死的「任务—模型」分工表,常见任务默认走指定模型,遇到特殊任务临时改一行配置就能改道;
- 一层几十行的统一调用代码,三家模型走同一套参数,谁家接口出问题可以自动降级到另一家;
- 一份调用日志,记录每次用了哪个模型、花了多少 token、耗时多久,一周后你能拿着数据回答「这个任务到底该用谁」。
举个具体场景:你上午要读一份 60 页的合同并出摘要,中午要把会议录音整理成纪要,下午要写一段数据清洗脚本。这三件事的最优解大概率不是同一个模型。有工作台的做法是:合同走长上下文能力强的模型,纪要走中文表达顺手的模型,脚本走代码能力强的模型——你只切换一个任务标签,背后自动改道。
关于「豆包工作」本身:这类成品工作台产品通常在设置里提供「自定义模型 / 接入第三方 API」的入口,具体位置、支持的字段、能配几个模型,以官方页面当前版本为准,产品改版后界面会变。所以本文的做法分两层——能配的地方直接配,配不了的部分用一个本地脚本兜底,两层互不冲突。
前置条件清单
动手前先把这些准备好,缺一项后面就会卡住:
1. 三个平台的账号:豆包(火山方舟开放平台)、Kimi(Moonshot 开放平台)、DeepSeek 开放平台。按各家要求完成实名或企业认证。
2. 三个 API Key:每家控制台里都能创建,创建后只显示一次,记得当场保存。
3. Python 环境:Python 3 即可,不需要特殊版本。
4. 一个能装包的终端:pip install openai python-dotenv。三家平台都提供 OpenAI 兼容接口,所以一个 SDK 通吃。
5. 三份配置信息:每家的 base_url(接口地址)、model(模型名)。这两项在各家控制台的「API 文档 / 接入指南」页能找到,具体取值以官方文档当前版本为准,模型名会随平台迭代新增和下线。
6. 十到二十条你自己的真实任务:用来校准分工表。没有这个,分工表只能照抄别人的经验,大概率不准。
7. 额度与账单提醒:三家控制台都有额度设置或用量告警,开通时顺手设一下,别等月底。
步骤一:把授权做成「可撤销」的
这一步最容易被跳过,但它是整个工作台的地基。三条纪律:
一、一家一个 Key,命名带用途。 创建时备注「个人工作台-2024xx」这种信息,别叫 test。将来要吊销时你知道吊销的是哪一个。
二、Key 只进环境变量,不进代码。 新建项目目录,写一个 .env:
```bash
.env —— 这个文件永远不进 git
DOUBAO_API_KEY=你的豆包key
DOUBAO_BASE_URL=控制台给的兼容地址
DOUBAO_MODEL=控制台给的模型名
KIMI_API_KEY=你的Kimi key
KIMI_BASE_URL=控制台给的兼容地址
KIMI_MODEL=控制台给的模型名
DEEPSEEK_API_KEY=你的DeepSeek key
DEEPSEEK_BASE_URL=控制台给的兼容地址
DEEPSEEK_MODEL=控制台给的模型名
```
配套的 .gitignore:
```bash
.gitignore
.env
*.log
logs/
__pycache__/
```
三、权限最小化 + 额度封顶。 能只开推理权限就别开管理权限;能设月度上限就设。API Key 泄露最常见的路径不是被黑,是自己 push 到了公开仓库。
步骤二:先跑通三条通路,别急着写路由
新手常犯的错是一上来就写路由和分流,结果报错时不知道是路由写错了还是 Key 配错了。先做最笨的事:一个模型一个模型地单独跑通。
```python
smoke_test.py
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
def ping(prefix: str):
key = os.getenv(f"{prefix}_API_KEY")
base = os.getenv(f"{prefix}_BASE_URL")
model = os.getenv(f"{prefix}_MODEL")
client = OpenAI(api_key=key, base_url=base)
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "用一句话说明你是什么模型"}],
max_tokens=100,
)
print(f"[{prefix}] OK -> {resp.choices[0].message.content}")
for p in ["DOUBAO", "KIMI", "DEEPSEEK"]:
try:
ping(p)
except Exception as e:
print(f"[{p}] FAILED -> {type(e).__name__}: {e}")
```
三个都打印 OK,再往下走。任何一个报错,先看后面的「常见坑与排错」,不要继续往下叠代码。
步骤三:写一层统一调用
三家的参数名基本一致(model / messages / temperature / max_tokens),但返回结构和错误码不完全一样。包一层,把差异挡在外面:
```python
workbench.py
import os, time, json
from datetime import datetime
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
PROVIDERS = {
"doubao": {"key": "DOUBAO", "style": "中文写作、纪要润色、生态内文档联动"},
"kimi": {"key": "KIMI", "style": "长文档通读、多文件对照"},
"deepseek": {"key": "DEEPSEEK", "style": "代码、结构化抽取、批量处理"},
}
def _client(name: str):
prefix = PROVIDERS[name]["key"]
return OpenAI(
api_key=os.getenv(f"{prefix}_API_KEY"),
base_url=os.getenv(f"{prefix}_BASE_URL"),
), os.getenv(f"{prefix}_MODEL")
def call(name: str, messages: list, temperature: float = 0.3, max_tokens: int = 2000) -> dict:
client, model = _client(name)
t0 = time.time()
resp = client.chat.completions.create(
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
)
text = resp.choices[0].message.content
usage = resp.usage.model_dump() if resp.usage else {}
return {
"model_name": name,
"text": text,
"latency_s": round(time.time() - t0, 2),
"prompt_tokens": usage.get("prompt_tokens"),
"completion_tokens": usage.get("completion_tokens"),
}
```
注意 temperature 和 max_tokens 各家默认值和取值范围有差异,部分模型对某些参数不敏感甚至忽略。如果你发现调了没反应,去查该模型的参数说明,别怀疑代码。
步骤四:定分工表(这是全篇最值钱的一步)
不要把分工表当成「谁更强」的排行榜,它是你自己任务分布的映射。按任务类型分,不按模型分:
| 任务类型 | 标签 | 默认模型 | 判断依据 |
|---|---|---|---|
| 长文档通读、多文件对照 | long_doc | Kimi | AI 词典:上下文窗口">上下文窗口大,塞整份材料不用切 |
| 代码生成、脚本、正则 | code | DeepSeek | 代码场景表现稳定 |
| 结构化抽取(表格、字段) | extract | DeepSeek | 输出格式较可控,适合批处理 |
| 中文写作、纪要润色 | writing | 豆包 | 中文表达自然,和日常办公场景贴合 |
| 会议纪要、内部问答 | meeting | 豆包 | 同上,且能和其他办公工具联动 |
| 需要先想再答的推理题 | reasoning | DeepSeek | 挑带推理模式的模型 |
| 拿不准的杂活 | default | 豆包 | 日常主入口 |
三条使用心得:
1. 按「输入长度」分流比按「任务名」分流更靠谱。 一句话的活儿谁都行;超过两三万字的材料,先看谁吃得下,再看谁答得好。
2. 批量任务先小样本试。 要处理 500 条数据,先跑 20 条看抽取准确率,别一次性把额度打光。
3. 同一个 prompt 换模型,效果可能差一截。 换模型时提示词要跟着调,尤其是输出格式要求。别用一套提示词打三家。
步骤五:加路由,带手动开关和自动降级
```python
router.py
from workbench import call
ROUTES = {
"long_doc": "kimi",
"code": "deepseek",
"extract": "deepseek",
"reasoning": "deepseek",
"writing": "doubao",
"meeting": "doubao",
"default": "doubao",
}
降级顺序:主模型失败时依次尝试
FALLBACK = ["doubao", "kimi", "deepseek"]
def route(task_type: str, messages: list, override: str | None = None) -> dict:
primary = override or ROUTES.get(task_type, ROUTES["default"])
order = [primary] + [m for m in FALLBACK if m != primary]
last_err = None
for name in order:
try:
result = call(name, messages)
result["task_type"] = task_type
result["fallback_used"] = (name != primary)
return result
except Exception as e:
last_err = e
print(f"[warn] {name} 调用失败,尝试下一个:{type(e).__name__}")
raise RuntimeError(f"全部模型调用失败:{last_err}")
```
override 这个参数就是「自由切换」的入口。命令行里加个 --model kimi,或者在工作台设置里改一个默认值,就能覆盖分工表。默认走规则,特殊任务手动改道,这个组合比纯自动更实用——自动路由猜错的成本,比手动点一下高。
配一个最小命令行入口:
```python
cli.py
import argparse, sys
from router import route
p = argparse.ArgumentParser()
p.add_argument("--task", default="default")
p.add_argument("--model", default=None, help="手动指定模型,覆盖分工表")
p.add_argument("--file", default=None, help="从文件读入提示词")
a = p.parse_args()
prompt = open(a.file, encoding="utf-8").read() if a.file else sys.stdin.read()
res = route(a.task, [{"role": "user", "content": prompt}], override=a.model)
print(res["text"])
print(f"\n--- {res['model_name']} | {res['latency_s']}s | "
f"in={res['prompt_tokens']} out={res['completion_tokens']}", file=sys.stderr)
```
用法:
```bash
echo "把这堆会议记录整理成带负责人和截止日的纪要:..." | python cli.py --task meeting
cat 合同.txt | python cli.py --task long_doc
python cli.py --task code --model doubao --file prompt.txt # 手动改道
```
步骤六:留痕,不然分工表永远校准不了
每次调用追加一行 JSON 到日志文件:
```python
logger.py
import json, os
from datetime import datetime
def log(res: dict, note: str = ""):
os.makedirs("logs", exist_ok=True)
path = f"logs/{datetime.now():%Y-%m}.jsonl"
row = {
"ts": datetime.now().isoformat(timespec="seconds"),
"task": res.get("task_type"),
"model": res["model_name"],
"fallback": res.get("fallback_used", False),
"latency_s": res["latency_s"],
"in": res["prompt_tokens"],
"out": res["completion_tokens"],
"note": note,
}
with open(path, "a", encoding="utf-8") as f:
f.write(json.dumps(row, ensure_ascii=False) + "\n")
```
一周后看三个数:哪类任务的返工率高(你手动改了结果的次数)、哪类任务的耗时集中、哪个模型被降级次数多。返工率高的任务,就换模型试;降级多的模型,去看是不是额度或者限速的问题。
常见坑与排错
401 / 403
Key 错、Key 被吊销、Key 没有该模型的调用权限。先确认 .env 里没有多余空格和引号,再回控制台看这个 Key 是否还活着。
404 model not found
模型名写错,或者豆包这类平台需要用「推理接入点 ID」而不是模型名。两者都去控制台复制粘贴,别手打。模型名随平台迭代会变,以官方文档当前版本为准。
400 上下文超长
输入加输出超过窗口上限。解决办法是切分:按章节、按段落拆,每段单独摘要后再做二次汇总。别指望「超一点没关系」。
429 限流
批量任务必须加重试和退避。简单做法:
```python
import time
def with_retry(fn, times=3):
for i in range(times):
try:
return fn()
except Exception as e:
if "429" in str(e) and i < times - 1:
time.sleep(2 ** i * 2)
continue
raise
```
JSON 解析失败
让模型输出 JSON 时,把字段名和类型写进提示词,并且代码里做兜底:先尝试 json.loads,失败就用正则抠出第一个 {...} 再解析。三家模型对格式的严格遵守程度不一样,兜底不能省。
费用悄悄失控
常见原因有三个:把整份长文档反复贴进上下文(每次都重新计费)、循环里没设退出条件、批量任务没做小样本验证。养成习惯:批量前先跑 20 条,估算总消耗再放开。
Key 提交到 git
如果已经 push 了,第一件事是去控制台吊销重建,不是删 commit。历史记录里的 Key 依然有效。
同一份提示词换个模型就不好使
这不是模型的问题,是提示词过拟合了。把「必须输出三段」「不要用markdown」这类硬性要求保留,把具体的措辞示例按模型微调。
下一步建议
1. 建一个 20 条的小评测集。 把你最常做的任务各挑几条,固定输入,记录三家模型的输出和你的人工评分。以后换模型、平台升级,跑一遍就知道要不要调整分工表。
2. 加上缓存。 同样的输入算过就别再算,尤其是长文档摘要这类贵操作。用输入内容的哈希做键,存到本地 sqlite 或者文件里都行。
3. 把降级链写进规则里。 现在是无脑轮询下一个,可以改成「只降级到同类能力的模型」,避免代码任务被降级到一个不擅长代码的模型,结果更差。
4. 脱敏前置。 如果会处理客户名单、合同金额这类内容,在 call() 之前加一层正则替换,把身份证、手机号、金额替换成占位符,回来再还原。
5. 接进日常入口。 成品工作台里能配自定义模型的地方直接配;配不了的,把 cli.py 包成一个快捷命令或者本地小界面,跟工作台放同一个屏幕。工具换了,习惯不用换。
这套东西的价值不在于「用了三个模型」,而在于你随时可以换掉其中任何一个。哪家涨价、哪家限流、哪家出了更强的版本,你改一行配置就行,工作流不动。
