Administrator
发布于 2026-09-07 / 1 阅读
0
0

大模型按时间段自动切换(零 token 定时方案)

大模型 API 按时间段自动切换(零 token 定时方案)

需求:DeepSeek 有空闲时段半价,GLM 有白天的活动价——想让 AI 助手"晚上自动用 DeepSeek、白天自动用 GLM",且切换过程不消耗任何模型调用。本文记录一套脚本 + 定时任务的零成本方案,含完整的模拟测试方法。

一、方案总览

两个供应商(智谱 glm-5.3-flash 和 deepseek deepseek-v4-flash)的密钥都在配置里。方案 = 1 个零 token 切换脚本 + 2 个 no_agent 定时任务

时间

动作

每天 19:00

切到 deepseek-v4-flash(空闲半价时段)

每天 8:00

切回 glm-5.3-flash

定时任务用 no_agent 模式(纯脚本执行,不启动 LLM,零成本),切换脚本本身只是改配置文件。

二、脚本的关键实现细节

1. 直接编辑 YAML,不要用 yaml 库重写

用 PyYAML dump() 重写整个配置文件会丢注释、丢格式、甚至改变键顺序。正确做法是按行定位顶层 model:

lines = text.splitlines(keepends=True)
# 找到行首 "model:" 的行,块终点 = 下一个行首非缩进非注释键
for j in range(start + 1, len(lines)):
    stripped = lines[j].strip()
    if stripped and not stripped.startswith("#") and not lines[j][:1].isspace():
        end = j
        break
new_text = "".join(lines[:start]) + new_block + "\n" + "".join(lines[end:])

不要用正则 (?ms)^model:\n(?:[ \t]+.*\n)+? ——DOTALL/贪婪模式下 .* 会吞掉整个文件,实测不可靠。

2. 密钥不硬编码

模型名、base_url、api_key 全部从配置的 providers 段动态读取,脚本里不含任何密钥,泄漏面为零。

3. 安全措施三件套

# 改前备份(只备份一次,首次的原始配置永远保留)
if not BACKUP.exists():
    shutil.copy2(CONFIG, BACKUP)
# 写后校验
try:
    yaml.safe_load(new_text)
except yaml.YAMLError:
    print("校验失败,放弃写入")   # 绝不写入坏配置
    return 1
# 幂等:已是目标模型则静默跳过(cron 重复触发无害)

4. 日期门控

脚本内置生效日判断(如 START_DATE = "2026-09-10"),生效日前运行直接空转退出——提前部署,到点自动生效。

5. 切换即生效

每次模型请求都会重新读配置,不需要重启 gateway。注意:切的是全局默认模型,若某个定时任务单独指定了 model/provider 则以任务自己的为准。

三、定时任务(no_agent,零成本)

任务A「模型切换-夜间DeepSeek」:0 19 * * *   script=model_time_switch.py  no_agent=true
任务B「模型切换-日间GLM」:     0 8 * * *    script=model_time_switch.py  no_agent=true

日志写入专用文件,每次切换留痕可审计。

四、不碰真实配置的模拟测试法

上线前必须实测("先验证再给结论")。技巧:把模块的 CONFIG/LOG 指向临时目录 + 注入假时间

import importlib.util, shutil, yaml, datetime as real_dt

spec = importlib.util.spec_from_file_location("mts", "model_time_switch.py")
mts = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mts)

tmp = Path("/tmp/mts_test"); tmp.mkdir()
mts.CONFIG = tmp/"config.yaml"; mts.BACKUP = tmp/"config.yaml.bak"; mts.LOG = tmp/"switch.log"
shutil.copy("/real/path/config.yaml", mts.CONFIG)   # 真实配置拷贝到临时区

class FakeDateTime(real_dt.datetime):
    now_inst = None
    @classmethod
    def now(cls, tz=None): return cls.now_inst
mts.datetime = FakeDateTime                          # 时间注入

def run(h, label):
    FakeDateTime.now_inst = FakeDateTime(2026, 9, 10, h, 0)
    mts.main()
    c = yaml.safe_load(mts.CONFIG.read_text())["model"]
    print(f"{label}: {c['provider']}/{c['default']}")

实测通过的用例:

用例

结果

22:00 夜间 → deepseek

✓ deepseek-v4-flash

10:00 日间 → zhipu

✓ glm-5.3-flash

19:00 整点边界 / 8:00 整点边界

✓ 正确切换

重复执行(10:30 再跑日间)

✓ 幂等跳过

除 model 块外其余配置

✓ 逐键比对完全无损

最后一条很重要:把临时配置全文与原配置比对(model 块除外),确认切换脚本对文件其余部分零损伤。

五、踩坑记录

  1. GLM-5.3 系列的 reasoning_effort 只接受 low/high/max(强制思考模型,medium 等值直接 400 code 1210)。多模型混用时在配置里用 per-model override:reasoning_overrides: {glm-5.3-flash: high},其他模型不受影响。

  2. 不要用 hermes config set 写嵌套 key——会把 YAML 结构写坏,嵌套配置直接编辑文件。

  3. 正则改 YAML 不可靠:贪婪匹配吞全文,按行解析是唯一稳妥路线。

  4. 定时任务用 no_agent 模式跑纯脚本,模型调用次数为 0,切换本身不产生任何 API 成本。

  5. 切换粒度提醒:按时段切价只在"任务对模型价格不敏感/可预测用量"时有意义;如果任务有单独指定的模型,时段切换对它无效,需要在任务层面单独规划。


评论