本页目录

本地大模型能帮上什么忙 —— 边界、场景与自动化

把模型跑起来并常驻只是起点。真正的问题是:一张消费级显卡上的 35B 小模型,到底该拿它干嘛?本文的答案很克制——它干不了生产端的复杂逻辑(那是 Claude Code + Opus 的活),它的位置很窄:⁠日常的、非复杂逻辑的、可容错的杂活⁠。但在这个窄位置里,它有云替代不了的两个底色:⁠离线自足、隐私不出机⁠。想清楚这条边界,才知道哪些活值得为它折腾。

一、先划边界:本地模型不干什么

装完本地模型,第一反应往往是"拿它替代 ChatGPT / Copilot"——然后失望。因为在纯能力上,消费级显卡跑得动的 35B 模型打不过前沿云模型⁠,尤其在多步推理、长链路 agent、精确代码这些"生产端"的活上。

一句话定位:

复杂逻辑、生产交付、要质量的活 → Claude Code + Opus;本地模型只接日常、非复杂逻辑、可容错的杂活。

谁干什么:按「逻辑复杂度 + 交付要求」分 Claude Code + Opus 复杂逻辑 · 生产交付 · 要质量 · 写功能代码 / 重构 / 调试 · 架构设计 / 深度分析 · 多步推理 / 长链路 agent 翻车代价高的活,都在这一边 本地 35B(小模型) 日常 · 非复杂逻辑 · 可容错 · 整理 / 总结 / 归类 / 提取 · 日报合成 / 会话回捞 · 日常问答 / 查信息 / 排日程 错了无所谓、能重跑的活,放这边 别拿「省钱 / 隐私」当借口把复杂活塞给本地——省下的钱不够填翻车的坑。

为什么不能让本地干生产:小参数模型在需要精确和多步推理的地方会翻车⁠,而生产端翻车的代价很高(错误代码、跑偏的 agent)。本地的省钱和隐私,不足以抵消这个代价。⁠把复杂活留给 Opus,是给本地模型划清边界的第一条,也是最重要的一条。

二、本地的立身之本:离线 + 隐私

既然能力上打不过云,为什么还要本地?因为有两件事云再强也做不到⁠:

  • 离线自足⁠:断网、上不了云、云被墙、云涨价或限流——本地照跑。它不依赖任何外部服务在线。
  • 隐私不出机⁠:私人数据喂给它不上传、不进任何第三方的日志和训练集。

这两条,才是"有些活就该在本地"的真正理由。

关于云端 fallback 的矛盾

一个流行做法是"本地优先、云端兜底":本地停了(如打游戏时 gamemode 停服)自动回退云端,使用不中断。听起来很美,但要清醒——

一旦配了云端 fallback,本地就不再离线、也不再纯隐私。 只要发生过一次回退,那一次的数据就上了云。fallback 和本地的两个底色天然矛盾⁠。

所以不能无脑给所有本地任务都挂 fallback。诚实的做法是按场景分两类⁠:

场景性质本地为什么被选中配 fallback?例子
可用性优先本地只是"省钱的默认",离线/隐私不是硬需求✅ 可以openclaw 日常助手:查信息、排日程,本地停了回退云端,保证助手不掉线
纯粹性优先本地是"因为离线/隐私才选的"❌ 绝不daily-harvest 扫含私人代码和密钥的会话,回退云端 = 把这些数据传出去

这条线正是本机配置里划出来的:openclaw 的 primary = 本地 / fallbacks = deepseek(可用性优先),而 llm-jobs 的 harvest 本地不可用时直接 exit 3 等待补跑、⁠绝不上云⁠(纯粹性优先)。判断标准很简单:⁠如果这个任务"上一次云"就违背了你用本地的初衷,那它就不该有 fallback。

三、那它到底能帮上什么

在"日常杂活 + 离线/隐私"这个窄位置里,本地模型的三个属性各解锁一类场景。下面 ✅ 是本机真实在跑的,💡 是可延伸的设想。

零边际成本 → 敢让日常杂活常态化

按量计费的云模型,你会下意识"算着用";本地零成本,才敢让低价值但高频的杂活天天跑。

  • openclaw 日常问答⁠:查信息、排日程、随手一问,问多少次都不心疼。这类"单次没多大价值、但架不住频繁"的活,只有零成本养得起。
  • 日报每天合成⁠:daily-report 每天 09:00 把昨天的碎条目去重、合并成人话,连同系统巡检、待接续、周五的「本周视角」拼成四节推微信。每天必跑、一次多调,换云端按次计费就显得肉疼。
  • 本周视角⁠:周五日报多出一节——模型读过去 7 天的完成条目,综述精力去向、点名停滞的事、给下周一条建议。跨文档综合这种活,人工做很烦,模型零成本周周做。
  • 💡 延伸⁠:给每篇收藏的文章 / RSS 自动写一句摘要;月报滚动汇总。

隐私不出机 → 敢把私人数据喂进去

有一类数据"很想用 AI 处理,但不敢交给云"。本地是唯一敢喂的地方。

  • daily-harvest:扫当天所有 Claude Code 项目会话(含私人项目、代码、可能的密钥)+ openclaw 记忆,提炼"实际干了什么"回填日报。这些数据绝不能上云⁠,只有本地敢这么喂。
  • 💡 延伸⁠:私人日记 / 聊天记录 / 财务票据做本地 RAG 检索;照片 OCR 归档。

常驻可嵌入 → 敢把它焊进自动化

模型常驻在 localhost:18080,任何脚本、定时器随时能调,无需唤醒任何云服务、无网络往返顾虑。

  • llm-jobs:systemd timer 到点就调本地模型,采集→提炼→投递,全程本地闭环(下一节详解)。
  • openclaw 默认模型⁠:日常 agent 的 primary 指向本地。
  • 系统巡检降噪⁠:health-snapshot 只读采集原始信号(failed units、备份结果、ZFS 水位、24h 上万条 err 级日志按来源聚合、SSH 认证失败),模型负责降噪和轻重排序,产出日报的「🩺 系统」一节。"radicale 把 INFO 打成 err 属长期噪声""SSH 有 2FA、爆破未遂不必报"这类判断,规则写不出来,模型一句话搞定;全绿时只输出「✅ 一切正常」。
  • 💡 延伸⁠:记忆的周期性蒸馏与维护。

四、把想法落成常驻任务:llm-jobs 框架

上面那些自动化,落地都长一个样:⁠采集数据 → 调一次本地模型 → 写文件 / 投递⁠。与其每个任务各写一遍(各自 hand-roll curl 打模型、各自发微信),不如抽成统一框架。~/.local/lib/llm-jobs/ 就是这个框架——「周期性调用本地模型」这一类会不断长出来的任务(日报、harvest、将来的周报/月报/记忆维护)的统一落点,加新任务只写一个采集脚本。

为什么要这层抽象

日报原本是个孤立脚本,唯一数据源 ~/doc/daily/,而这个目录靠 Claude Code 和 openclaw(Miko)两个 agent「想起来才调 doc-daily-log」的尽力而为——谁忘了记,完成的活就整块从日报消失(例:2026-07-03 日报原本是空的)。问题拆两块:

  • 网罗性⁠:干的活没可靠地进 daily。→ 用 daily-harvest真实作业痕迹⁠(会话记录)里捞。
  • 质量⁠:纯字符串解析,重复项、粒度不齐原样透传。→ 用本地模型合成。

而这俩本质都是「定时调本地模型」,于是抽成统一框架。

架构决策:systemd vs openclaw cron

同样是"定时",两条路差别很大:

systemd timer + 脚本openclaw cron
任务模型确定性 pipeline(采集→调模型→投递)唤醒一个 agent 会话⁠(带工具/记忆/channel 上下文)
可观测性journald → Loki,可 Grafana 告警openclaw 内部日志,游离监控栈外
依赖只依赖 llama-server(也是 systemd)依赖 openclaw gateway 在线
调试--dry-runsystemctl start、纯脚本可版本化需在 agent 语境复现,非确定

规矩:pipeline 类走 systemd,agent 类才用 openclaw cron。 日报和 harvest 是「读文件→调一次模型→写文件/发消息」的管道,不需要工具循环,走 systemd。openclaw cron 只留给「Miko 真的要动脑动手」的定时任务(目前无)。

结构:三层解耦

~/.local/lib/llm-jobs/
  bin/llm-call         统一模型入口:POST localhost:18080;默认按请求关 thinking(--think 开),
                       空 content 视为失败并升 token 重试,本地挂了 exit 3
  bin/notify-weixin    统一投递:发微信 bot;--spool 把 token 失效(ret=-2)的消息落盘排队
  bin/health-snapshot  只读采集巡检信号(failed units/备份结果/ZFS/journal err 聚合/SSH 认证),
                       供 daily-report 的「系统」一节喂模型降噪
  jobs/daily-harvest   凌晨:扫昨天的会话 → map-reduce 提炼完成项 + 待接续 → 补进 daily
  jobs/daily-report    晨间:读 daily + 实时巡检 → 合成四节 → 投递
  jobs/notify-flush    每小时:补发 spool 里排队的微信消息
  README.md            约定 + 任务登记表
  • bin/llm-call 是唯一模型入口 —— 「定时调本地模型」只有一处经测试的实现,模型名/端点/回退策略集中管理。换模型只改这一处。
  • bin/notify-weixin 投递独立 —— 将来加 bark/邮件/多渠道只动这一层。
  • 模板单元 llm-job@.service —— ExecStart=%h/.local/lib/llm-jobs/jobs/%i,加任务不用新建 service 文件。

约定:任务脚本只调 bin/ 里的共享原语⁠,不要各自 hand-roll curl 打模型或发微信。

完整闭环

两个定时任务共享同一份 daily:先补全,后报告 04:00 · daily-harvest 扫【昨天】的 CC 会话 + openclaw 记忆 提炼完成项 + 待接续 补进昨天的 daily 09:00 · daily-report 读昨天的 daily(已被 harvest 补全)+ 实时巡检 合成四节 推微信 两个任务共享同一份 daily:harvest 先补全,report 才读得到完整数据。 机器夜里睡眠错过触发也没关系,Persistent=true 会在唤醒后补跑。

harvest 的定时实例经 systemd drop-in 传 --date 昨天(手动跑仍默认今天);04:00 时昨天已彻底结束,补全的正好是 09:00 要报的。机器夜里睡眠错过触发也没关系,Persistent=true 会在唤醒后补跑。

daily-harvest(04:00):找昨天所有 Claude Code 会话 ~/.claude/projects/*/*.jsonl,每个压成 USER:/AI: 纯文本(去思考和工具噪声、截断到限额)喂模型 map 提炼两类东西——「实际完成的有意义工作」和「明确说要做但没做完的」,外加 openclaw 自己蒸馏过的记忆。完成项候选 + 当天 daily 已有的人工条目一起 reduce,只挑真正完成、且人工未记的,输出 JSON,用 hv_* tag 逐条写回,每次先物理删旧 hv_ 块再重算(⁠幂等⁠,可反复跑不叠加)。待接续候选参照完成项去重后写 daily/<日期>.pending(整条链路 best-effort,失败只丢这一节)。全程本地模型 + 本地写。

daily-report(09:00):合成四节推微信——①「🩺 系统」:health-snapshot 采实时巡检信号,模型降噪成简报(模型不可用降级为只列 failed units,巡检永不阻塞发报);②「📝 昨日完成」:读昨天 daily 全部条目,模型去重合并写成人话,⁠模型不可用时回退「原始分组」(不上云,只是不做 LLM 合成),日报一定发得出去;③「🔜 待接续」:直接读 .pending 文件,没有则整节不出现;④「📊 本周视角」:仅周五,模型综述近 7 天条目。拼上日期/天气(wttr.in),所有节经统一的确定性排版后处理⁠(条目间空行、要点全角缩进——不指望模型排版稳定),notify-weixin --spool 投递。

兜底(纯粹性优先的体现):harvest 完成后写 ~/.local/state/llm-jobs/harvest-<日期>.ok 戳记;若本地模型不可用(被 gamemode 停了),写 .deferredexit 3——⁠不写假的空结果、也不上云⁠,systemd 标记失败进 Loki。09:00 report 出报前若发现昨日没有 .ok 戳记,就先补跑一次 daily-harvest --date <昨天>(此时模型已恢复)再读 daily。覆盖"凌晨机器在睡眠 / 模型被 gamemode 停了"全场景。注意这条兜底链的前提是 .ok 戳记必须诚实⁠——2026-07-10 的事故(见下一节"血泪")正是空结果被误写成 .ok,把整条兜底链短路了。

加一个新任务

  1. ~/.local/lib/llm-jobs/jobs/<name>(可执行,支持 --dry-run,只调 bin/ 里的共享原语)
  2. ~/.config/systemd/user/llm-job@<name>.timer(照抄 daily-report,改 OnCalendar)
  3. systemctl --user daemon-reload && systemctl --user enable --now llm-job@<name>.timer

运维

# 手动跑(不发送/不写入)
~/.local/lib/llm-jobs/jobs/daily-report  --dry-run
~/.local/lib/llm-jobs/jobs/daily-harvest --dry-run [--date YYYY-MM-DD]

# 手动触发真实任务
systemctl --user start llm-job@daily-report.service

# 看日志(journald → 也进 Loki)
journalctl --user -u llm-job@daily-report.service -n 50
systemctl --user list-timers | grep llm-job

微信 notify-weixin 若返回 ret=-2(context token 过期),--spool 调用方的消息会落盘排队,notify-flush 每小时自动补发;在微信给 bot 发一条消息刷新 token 可加速送达。

五、把本地模型用好的几条实操

都是从上面这些真实任务里踩出来的,可迁移到任何"调本地模型"的场景:

  • 日常任务关掉 thinking,且要在请求侧关⁠:整理、总结、归类这类活不需要思维链,关掉省一大截时延。关键是用请求级 chat_template_kwargs: {"enable_thinking": false}(可覆盖服务端预设),⁠不要依赖服务端预设⁠——预设是共享的,可能哪天为别的场景改掉(见下条血泪)。bin/llm-call 现在默认请求侧关思考,需要时 --think 显式开。
  • 善用长上下文,别自己切碎⁠:本地能开到很长的窗口(本机 64K–192K),喂整个会话、整份文档即可。harvest 曾把单会话截断上限从 10k 提到 40k——截断会切掉会话中段的完成小结,导致漏掉。
  • 结构化输出要校验 + 重试⁠:让模型出 JSON 就要容错解析、失败重试(bin/llm-call 内置重试 2 次)。小模型在 temp 下偶尔会跑格式。
  • 判断类提示词用正向抽取,别给"没有就输出无"的轻易出口⁠(血泪):harvest 的 map 早期用否定框架("不算讨论就输出无"),在 temp 下会随机把有产出的会话整段判成「无」,丢掉一整天的活(同输入第 1 次出 6 条、第 2 次「无」)。改成"编程会话通常有产出,通读全文再抽取,仅纯咨询才输出无"后 5/5 稳定。LLM 判断类提示词别给"什么都不输出"的便捷出口,它会在采样里不稳定地丢数据。
  • 别过度合并⁠:reduce 阶段"合并同类项"太激进,会让同输入时而 19 条、时而塌成 4 条空泛大标题。⁠保粒度⁠,合并留给下游(日报生成)那一步。
  • HTTP 200 也可能是假成功——空 content 必须当失败⁠(血泪,2026-07-10):为修 codex 卡顿把服务端预设 qwen3.6-mtp-thinkingenable_thinking:false 改成 reasoning = on 后,思维链被分离进 reasoning_content,批任务的小 max_tokens(512)全烧在思考上——返回 200 但 content=""finish_reason=length。harvest 把空输出当"该会话没活",17 个有真实工作的会话全判「无」,还写了 .ok 成功戳记,晨间兜底被短路,日报发出「昨日无记录」。而且给思考加预算赢不了⁠:实测 512→2048 它思考得更久照样烧光。修法双管齐下:①llm-call 把空 content 视为失败(length 时翻倍 max_tokens 立即重试,耗尽 exit 3 走 deferred);②批任务默认请求侧关思考(上一条)。更深一层的教训:⁠共享推理实例的服务端预设是所有调用方的隐式契约⁠——为某一个场景改预设,要把其它调用方都过一遍。
  • 别指望模型排版稳定,展示层用确定性后处理⁠:prompt 里写"每条空一行"这类排版要求,小模型执行不稳定。日报的做法是模型只管内容,脚本统一重排(条目间空行、要点全角空格缩进),模型输出格式再怎么飘,读者看到的排版恒定。
  • 结构化输出的 token 要给足,且失败绝不能伪装成功⁠(血泪):忙碌的一天候选可达上百条(54 会话→163 候选),reduce 输出 JSON 很长,max_tokens=1500 直接截断→ JSON 非法 → 解析失败。早期把解析失败静默当成"无新增"、还写了 .ok 成功戳记⁠,于是晨间兜底也不触发,整天工作静默蒸发(2026-07-06)。三条铁律:①输出 token 给足并升档重试(6000→9000);②判空要区分 None(失败)与 [](真无新增);③失败要有降级而非丢弃⁠——本机 harvest 在 reduce 反复失败时降级为"按项目分组直写原始候选"(下游日报再合并),只有模型真不可用才 .deferred+exit 3。⁠任何"失败被当成空结果"的路径,都会在自动化里悄悄吞掉数据。
  • agent / 工具调用要开 --jinja:否则模型"能吐 toolcall 标签却不执行"——llama-server 的原生工具解析(把输出解析成结构化 tool_calls)只在 --jinja 开启时生效。纯对话不受影响,但当 agent 用必开。

相关文档

  • local-llm.md — 部署:显存预算、量化选型、MTP、常驻潮汐调度、完整 systemd 配置
  • timers-and-crons.md — 全部 systemd timer 总览(含 llm-job@ 两个)
  • monitoring.md — journald → Loki 日志聚合(llm-jobs 失败可在此告警)