跳到正文
Latent Space· Rittika Jindal·· 3 小时前AI 评分38

在喜马拉雅山麓用离线 AI 智能体写代码:OpenCode + Ollama + Qwen3.5 9B 实践

Coding From the Himalayas With an Offline AI Agent

AI 导读

一位常驻喜马拉雅山麓的 Thomson Reuters 首席工程师用 OpenCode 1.19.4 搭配 Ollama 本地运行 Qwen3.5 9B,在断网状态下完成代码解释、失败测试排查和小幅重构等任务。

正文 · AI 翻译

当我们谈论远程工作时,很多事情都被我们视为理所当然,尤其是可靠的网络和电力。但有时候,远程工作意味着要在飞机上、长途火车上,或者网络信号很弱甚至没有网络的地方设法完成工作。

一年前我搬到了喜马拉雅山麓。我喜欢住在这里的宁静,但这也带来了它自己的挑战,包括网络和电力中断。以前我不太会考虑的事情,现在成了我规划工作的一部分。

我是Thomson Reuters 的首席工程师,大部分时间都在写代码和审查队友的代码。编码智能体已经是我日常工作流程的重要组成部分,这让我很好奇这个流程中有多少部分我能在离线状态下继续使用。

在喜马拉雅山麓工作。我的工作区照片。

如果你和我一样,既喜欢与技术打交道,又能在网络时好时坏的安静偏远之地找到宁静,那么这篇文章就是为你写的。它同样适合那些想知道编码智能体能否完全在自己的笔记本电脑上、在断网状态下运行的人。我尝试在笔记本电脑上本地运行一个编码智能体,看看离线状态下我究竟能完成多少工作。

离线状态下我们能做什么?

笔记本电脑上已有的项目为我们提供了充足的工作基础。我们可以让智能体解释一个不熟悉的函数、调查一个失败的测试、重构一小段代码,或者帮助完成一个功能。当模型、项目依赖和测试工具都在本地可用时,这些任务可以离线运行。下载新软件包或上网查询资料仍然需要网络连接。

OpenCode 的用武之地

OpenCode 是一个开源编码智能体,可以与你的项目配合工作。你给它一个任务,它可以读取文件、提出修改建议,并运行测试套件等命令。在我们的设置中,它的权限提示让我们可以在允许命令和更改之前进行审查。

我选择 OpenCode,是因为它让我可以在终端中保持这一工作流程,同时连接到运行在自己笔记本电脑上的模型。Ollama 负责运行模型;OpenCode 负责与项目交互。

我 Mac 上的 OpenCode,已选择 Qwen3.5 9B 作为本地模型。

包括 Qwen 在内的开放权重模型让这一切成为可能:它们的权重(用于生成响应的学习参数)可以下载并在本地运行。在同一台笔记本电脑上运行 Ollama 和 Qwen 时,OpenCode 可以将请求发送到本地服务。我们先下载好所有内容,然后断开网络进行编码任务。

如果你更喜欢有引导的编辑工作流程,Aider 是另一个选择;它也支持 Ollama。我们在整个演示过程中都使用 OpenCode,以保持设置和结果的一致性。

OpenCode 处理项目文件,并将模型请求发送到同一台笔记本电脑上的 Ollama。Ollama 在本地运行 Qwen。

选择开源模型

Qwen 有不同的规模。我想要一个可以在本地运行、同时也能使用智能体工具的模型。作为参考,以下是 Qwen 的模型卡中报告的两种 Qwen 规模以及另一个开放权重选项的已发布结果。

LiveCodeBench v6 在编程问题上测试代码生成能力。BFCL-V4 评估函数调用能力,当模型需要选择工具并为其提供正确参数时,这一点很重要。两项测试均为分数越高越好。

模型

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 内存。我们的 64k token OpenCode 配置可能需要更多,因此要为上下文和其他应用留出空间。

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 使用量显著更高:每个已解决任务的 token 约为 Goose 的 40 倍。这些运行使用云端 API 和与我们不同的模型,因此不能预测本地 Qwen3.5 9B 的表现。

初始设置

在仍可访问互联网时,请按照 GitHub README 中的完整设置操作。其中涵盖安装工具、下载项目以及安装 OpenCode 1.19.4。项目包含在本地使用 Qwen 的示例、测试和配置。准备就绪后,让终端 A 保持打开在项目文件夹中。

开始之前,了解 opencode.json 的作用会有所帮助。这是 OpenCode 的项目配置。在我们的文件中,model 和 small_model 都选择 Qwen3.5 9B,baseURL 指向位于 http://127.0.0.1:11434/v1 的 Ollama。仅启用了本地提供程序。它还会在编辑文件或运行 shell 命令前询问,并阻止网络工具。完整配置在仓库中。离线测试时我们仍会断开网络。

我们将使用终端 A 运行 OpenCode 和我们的测试。在终端 B 中,启动 Ollama 并保持运行。此命令将模型服务保持在 localhost(同一台 Mac 内的地址),并禁用 Ollama 的云功能。

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

回到终端 A,下载 Qwen 并等待下载完成。我们将在断开互联网之前检查智能体是否能响应。

ollama pull qwen3.5:9b

从费用计算器开始

该示例添加了五笔费用,其中包括一笔退款。正确的总额是 25.50。在终端 A 中运行起始程序。

python3 expense_total.py

起始程序打印出 8.00,即最后一笔费用的值。

该函数在每次迭代时都会替换其总额。在让智能体修复它之前,先运行现有测试。

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 的情况下接收诊断提示。

OpenCode 读取了源代码并搜索了测试。它最初建议使用 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。

这些检查确定了捕获时发生的情况:本地服务有响应,而外部查找失败。在运行编码任务期间,保持所有外部连接断开。

离线修复 bug

在终端 A 中使用相同的直接命令再次启动 OpenCode。

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

让智能体修复这个 bug。

Fix the bug in expense_total.py so the existing tests pass. Don't change the tests or install anything. Run the tests and explain your fix.

录制内容显示 Qwen 提出了一行更改、请求编辑权限并运行测试。更改很小,因此很容易检查。

total = Decimal(amount)   # before
total += Decimal(amount)  # after

录制的编辑和测试循环。Qwen 提出修复、请求批准并成功运行了五个测试。

智能体完成后,在运行你自己的检查之前退出 OpenCode。

/exit

然后我们在终端 A 中自行运行了测试。

python3 -m unittest -v

手动检查显示所有五个测试均通过。

保存的源代码包含 total += Decimal(amount),并且测试文件与起始版本相比没有变化。修复现在会累加每笔金额。运行示例程序应返回 25.50。

再尝试两个编码任务

我在配套项目中又加入了两个练习:一个 CSV 报告和一个日志分析器。我还没有完成它们的离线智能体运行,因此上面显示的结果来自费用示例。这些是使用相同设置接下来要尝试的任务。

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

CSV 报告:代码与操作演示

参照 README 完成 examples/csv-report/csv_report.py。保持测试不变,不要安装任何东西。运行该示例的测试,并说明你做了哪些改动。

日志分析器已经可以运行,但在错误计数和选择最慢请求方面存在缺陷。它的七个测试还覆盖了格式错误的行和空输入。

日志分析器:代码与操作演示

参照 README 修复 examples/log-analyzer/log_analyzer.py。保持测试不变,不要安装任何东西。运行其测试并说明你的修复。

每个操作演示都包含确切的测试命令和预期结果。在相同的本地环境下运行这些练习,然后检查改动和测试输出。

无法上网时什么有帮助

  • 不仅要准备好模型,也要准备好项目。下载其依赖项,并将你需要的文档保存在磁盘上。我们的示例使用 Python 标准库,因此其测试不需要下载软件包。

  • 在线时先按完全相同的配置启动一次。离线尝试时使用相同的命令、模型和配置,这样所需的运行时组件就已经可用。

  • 把项目的测试命令交给智能体。我们的智能体最初建议使用 pytest,尽管这个示例使用的是 unittest。清晰的测试命令可以减少不必要的弯路。

  • 保持任务聚焦,并检查结果。只要求修复一个缺陷或实现一个小功能,审查差异,并自行运行测试。保留原始测试,这样即使测试通过,也仍能验证预期行为。

  • 注意内存压力。模型下载只是内存开销的一部分。较大的上下文和其他打开的应用会减少模型可用的空间。

  • 也要记录需要帮助的尝试。有用的操作演示应展示权限提示、重试和失败,以及最终通过的输出。

我从这次离线尝试中学到了什么

关掉 Wi-Fi 后看到测试通过,回答了我最初的疑问:我可以使用编码智能体读取本地文件、进行修改并运行检查,而无需互联网连接。我仍然审查了修改,并自己运行了原始测试。保持这些测试不变,为我提供了一种检查结果的有用方式。

在停电或旅途中使用这套配置时,我会从小型、自包含的任务开始。项目及其所需的一切都必须在连接消失之前保存在笔记本电脑上。这个单行修复并不能告诉我模型处理大型仓库或不熟悉框架的能力如何。CSV 报告是下一个有用的检查:它要求智能体完成一个函数,因此需要检查的内容比单行修复更多。

来源:Latent Space · latent.space