在喜马拉雅山上用离线 AI 智能体写代码

Latent Space 2026-10-27T15:55:17.642421

聊到远程工作,我们往往默认很多条件都具备,尤其是稳定的网络和电力。但有时候,远程工作意味着要在飞机上、漫长的火车旅途中,或者网络很差甚至没有网络的某个地方完成工作。一年前,我搬到了喜马拉雅山麓。我喜欢这里的宁静,但生活在这里也有它的挑战,比如断网和停电。以前不太在意的事情,现在都成了我安排工作时必须考虑的因素。我在 Thomson Reuters 担任首席工程师(Principal Engineer),大部分时间都在写代码和审查队友的代码。编码代理(coding agent)已经是我日常工作流的重要组成部分,这也让我好奇:离线状态下,我还能保留多少这样的工作流?在喜马拉雅山麓工作。我的工作区照片。如果你和我一样,既喜欢与技术打交道,又能在安静、偏远、网络时好时坏的地方找到平静,这篇文章就是写给你的。它也适合那些想知道编码代理能否完全在笔记本电脑上、在断网状态下运行的人。我试着在笔记本电脑上本地运行一个编码代理,看看离线状态下我究竟能完成多少工作。

离线时我们能做什么?

如果笔记本电脑上已经有一个项目,我们就有很多可做的事。我们可以让代理解释一个不熟悉的函数、排查失败的测试、重构一小段代码,或者帮忙完成某个功能。只要模型、项目依赖和测试工具都在本地,这些任务就可以离线运行。但下载新包或上网查资料仍然需要联网。

OpenCode 的用武之地

OpenCode 是一个开源编码代理,可以与你的项目配合工作。你给它一个任务,它就能读取文件、提出修改建议,并运行测试套件等命令。在我们的配置中,它的权限提示让我们可以在允许命令和更改之前先进行审查。我选择 OpenCode,是因为它让我可以在终端中保持这套工作流,同时连接到运行在自己笔记本电脑上的模型。Ollama 负责运行模型;OpenCode 负责处理与项目的交互。

Mac 上的 OpenCode,本地模型选了 Qwen3.5 9B。能这么玩,靠的是开放权重模型(open-weight model,比如 Qwen):它们的权重——也就是生成回答所依赖的那些学出来的参数——可以直接下载到本地运行。只要 Ollama 在同一台笔记本上跑着 Qwen,OpenCode 就能把请求发给本地服务。我们的做法是先把所有东西下载好,然后断网,再开始写代码任务。如果你更喜欢有引导的编辑流程,Aider 也是备选,它同样支持 Ollama。这篇教程从头到尾都用 OpenCode,这样配置和结果能保持一致。OpenCode 负责处理项目文件,并把模型请求发给同一台笔记本上的 Ollama,Ollama 则在本地运行 Qwen。

选一个开源模型

Qwen 有不同尺寸。我想要的是既能在本地跑、又能调用智能体(agent)工具的模型。先给点参考:下面是 Qwen 模型卡里公布的两档 Qwen 尺寸和另一个开放权重选项的成绩。

LiveCodeBench v6 测的是在编程题上生成代码的能力。BFCL-V4 评估函数调用(function calling)——当模型需要挑一个工具并给出正确参数时,这项能力就很关键。两项都是分数越高越好。

模型 LiveCodeBench v6 BFCL-V4
Qwen3.5 4B 55.8 50.3
Qwen3.5 9B(本文使用) 65.6 66.1
gpt-oss-20b 74.6 未公布

上表分数来自 Qwen 模型卡,不是这台笔记本上的实测。「未公布」表示来源没有给出该项成绩。

这篇教程用的是 Qwen3.5 9B。它公布的编码和工具调用成绩都比 4B 版本强,作为起点比较合适。这张表测不了笔记本上的运行速度,也测不了整套 OpenCode 流程;真正的检验还得靠我们离线的文件编辑和测试。

模型尺寸与内存

模型 大约下载大小
Qwen3.5 4B 3.3–4.0 GB
Qwen3.5 9B(本文使用) 6.6–7.6 GB
Qwen3.5 27B 17–20 GB

Ollama 列出了所有可用尺寸;具体下载大小会因运行时格式而不同。下载大小不等于内存需求。Voltti 给 6.6 GB 的 Qwen3.5 9B 版本预留了 12–16 GB 内存。我们这套 OpenCode 配置开了 64k token 上下文,可能还需要更多,所以要给上下文和其他应用留出余量。

OpenCode 在基准测试中的表现

OpenCode 不只在小型演示中测试过。研究预印本《The Scaffold Effect in Coding Agents》用两个云端模型,在 50 个 Terminal-Bench Pro 任务上对比了 OpenCode、Goose 和 OpenHands-SDK。表 2 列出了各家的通过率。

编码智能体 Qwen3.6 Plus MiniMax M2.5
Goose 48% [34–62%] 38% [24–52%]
OpenCode 50% [36–64%] 46% [32–60%]
OpenHands-SDK 50% [36–64%] 46% [32–60%]

方括号内是 95% 置信区间。作者提醒,多数通过率差异在统计上并不显著。研究还指出,OpenCode 的 token 消耗明显更高:每解决一个任务,约是 Goose 的 40 倍。

这些测试用的是云端 API 和不同的模型,和我们本地环境不一样,因此不能据此推断本地 Qwen3.5 9B 的表现。

初始设置

趁着还能联网,先按 GitHub README 里的完整步骤做好配置。其中包含安装工具、下载项目以及安装 OpenCode 1.19.4。项目里已经为本地运行 Qwen 准备好了示例、测试和配置文件。配置完成后,在项目目录里保留 Terminal A 窗口。

动手之前,最好先搞清楚 opencode.json 的作用。它是 OpenCode 的项目配置文件。在我们的文件里,model 和 small_model 都选用了 Qwen3.5 9B,baseURL 指向 Ollama 的 http://127.0.0.1:11434/v1。只启用了本地提供方。此外,它在编辑文件或执行 shell 命令前会先询问,并且屏蔽了网络工具。完整配置见代码仓库。

离线测试时,我们照样会断开网络。Terminal A 用来运行 OpenCode 和我们的测试。在 Terminal B 里启动 Ollama,让它保持运行。这条命令让模型服务只监听 localhost(同一台 Mac 内部的地址),并关闭 Ollama 的云端功能。

OLLAMA_HOST=127.0.0.1:11434 OLLAMA_NO_CLOUD=1 OLLAMA_CONTEXT_LENGTH=65536 ollama serve

回到 Terminal A,下载 Qwen,等下载完成。断网之前,我们先确认智能体能够正常响应。

ollama pull qwen3.5:9b

从一个费用计算器开始

这个例子会加总五笔支出,其中包含一笔退款。正确总额是 25.50。

在终端 A 里运行起始程序 python3 expense_total.py,它输出 8.00,也就是最后一笔开销的金额。这个函数每次迭代都会覆盖掉自己的总额。让智能体修 bug 之前,先跑一下现有的测试:

python3 -m unittest -v

有三个用例失败了:小数运算、退款和示例开销。空输入和单项输入这两个用例已经通过。用现有测试能给智能体一个具体的目标,也能让我们在它的解释之外独立核对结果。

联网试用 OpenCode

联网状态下,直接在项目文件夹里打开 OpenCode。--pure 选项会禁用外部插件。项目配置把主模型和辅助模型都指向 Ollama;之后断网才是让这次运行真正离线的关键。

.tools/node_modules/.bin/opencode --pure

确认选中了 Qwen3.5 9B local。在 OpenCode 里输入这段诊断提示:

读取 expense_total.py 并运行测试。不要修改任何文件,解释这个 bug。

OpenCode 收到诊断提示,此时选中的是 Qwen3.5 9B local。它读完了源码,又去找测试文件。它一开始提议用 pytest,但这个项目用的是 Python 自带的 unittest。权限确认弹窗给了我一个机会,先看清命令再放行。下一个任务,我会把确切的测试命令直接写进提示里:python3 -m unittest -v。

智能体找到了测试,请求权限运行测试命令。本地服务返回结果后,退出 OpenCode。让 Ollama 继续在终端 B 里跑着。

/exit

关掉网络

关掉 Wi-Fi;如果接着网线或手机热点,也一并断开。localhost 依然可用,因为请求根本没离开这台 Mac。

为了做连通性检查,我关掉了 Wi-Fi。在终端 A 里检查 Ollama 的本地 API:

curl --noproxy '*' --fail http://127.0.0.1:11434/api/tags

Ollama 照常响应,列出了已下载的 qwen3.5:9b 模型。接着试一个外部地址:

curl --noproxy '*' --connect-timeout 5 --max-time 10 --head https://example.com

外部请求失败,报错 Could not resolve host。

这些检查确认了当时的情况:本地服务有响应,但外部查询失败。跑编码任务期间,要保持所有外部连接断开。

离线修复缺陷

用同一条直接命令在终端 A 里重新启动 OpenCode。

.tools/node_modules/.bin/opencode --pure

让 agent 修 bug。

修复 expense_total.py 里的 bug,让现有测试通过。不要改动测试,也不要安装任何东西。跑一遍测试,并解释你的修复思路。

录屏显示,Qwen 提出了一行改动,请求编辑权限并运行了测试。改动很小,很容易检查。

total = Decimal(amount)   # 修改前
total += Decimal(amount)  # 修改后

整个编辑与测试流程都被记录了下来:Qwen 提出修复方案,请求批准,然后成功跑完五个测试。

agent 完成后,先退出 OpenCode,再自己检查。

/exit

接着我们在终端 A 里手动跑测试。

python3 -m unittest -v

手动检查显示,五个测试全部通过。保存下来的源码里是 total += Decimal(amount),测试文件与初始版本保持一致。修复后,每个金额都会被累加进去。跑一下示例程序,应该返回 25.50。

还有两个编码任务可以试

我在配套项目里又加了两个练习:一个 CSV 报告,一个日志分析器。它们的离线 agent 运行我还没做完,所以上面展示的结果来自 expense 示例。接下来可以用同样的环境试试这两个任务。

CSV 报告示例从一个未完成的函数开始。让 OpenCode 按产品对行分组,计算数量和收入,输出可读的报告。它带有示例数据,以及七个测试,覆盖分组、十进制运算、排序和空输入。

CSV 报告:代码与操作步骤

参照 README 补全 examples/csv-report/csv_report.py。保持测试不变,不要安装任何东西。跑一遍这个示例的测试,并解释你改了什么。

日志分析器本身能跑,但错误计数和“找出最慢请求”两处有 bug。它的七个测试还覆盖了格式错误的行和空输入。

日志分析器:代码与操作步骤

参照 README 修复 examples/log-analyzer/log_analyzer.py。

保持测试不变,不要安装任何东西。运行它的测试并解释你的修复。

每个演练都包含确切的测试命令和预期结果。用同样的本地环境完成这些练习,然后检查改动和测试输出。

断网时哪些做法有帮助

项目和模型都要提前准备。

下载依赖,把需要的文档存到磁盘上。我们的示例只用 Python 标准库,所以测试不需要下载软件包。

联网时先把完整环境跑一遍。

离线重试时用同样的命令、模型和配置,这样运行时需要的组件已经就位。

把项目的测试命令交给智能体。

我们这个例子用的是 unittest,智能体一开始却建议用 pytest。明确给出测试命令,能少走弯路。

任务保持聚焦,然后检查结果。

只让它修一个 bug 或加一个小功能,看过 diff 之后自己再跑一遍测试。保留原有测试,这样测试通过才能真正说明预期行为没被破坏。

留意内存压力。

下载模型只是内存开销的一部分。上下文开得太大、后台还开着别的应用,都会挤占模型可用的内存。

需要人工介入的尝试也要记录下来。

一份有用的演练应该展示权限提示、重试和失败,而不只是最后测试通过的那段输出。

离线尝试的体会

关掉 Wi-Fi 后看到测试通过,回答了我最初的疑问:不联网也能让编程智能体读取本地文件、做出修改并运行检查。我仍然自己审查了改动、跑了原有测试。保持测试不变,给了我一个检验结果的好办法。

在网络中断或旅途中使用这套方案时,我会从小的、自成一体的任务开始。项目和它需要的一切,都必须在断网前已经存在笔记本上。

这次一行代码的修复,并不能说明模型面对大型代码库或不熟悉的框架时表现如何。下一个有意义的检验是那份 CSV 报告:它要求智能体补全一个函数,需要检查的东西比一行修复多得多。

如何用 OpenCode、Ollama 和 Qwen 搭建并运行一个离线编程智能体。

查看原文