在 NixOS 系统中尝试部署使用大模型编程代理
having a taste of AI coding, trying opencode, crush, glm-5 and other products. all with NixOS native containers
zping
2371 字
2026-03-09 05:14 +0000
现在才开始尝试使用大模型编程也许已经晚了一点,不过这也是没有办法,像往年一样,去年也是无所作为的一年。一年前,我尝试过一些。但那个时间点,比较多的是 Cursor AI 这类项目,不仅昂贵,而且对于我来说仍是黑箱。但现在情况显然不同了。
现在大模型编程的技术方案中 ClaudeCode 是标准。不过像我这种死硬“免费”分子自然选择 OpenCode ,对于 OpenCode 还有需要提及一些历史。就是今年的 OpenCode 并不是去年的 OpenCode 。原来的 OpenCode 也就是 opencode-ai/opencode ,是一个使用编程语言 golang 开发的命令行接口代理。这个旧 opencode 的代码仓库已经是存档状态。当然,这个项目目前的开发,但改了一个名字 charmbracelet/crush 。现在的 OC 是 anomalyco/opencode ,这是一个 TypeScript 项目。两者实现路径完全不同,其实是没有关系的。
因为 charmbracelet/crush 是一个 GO 语言项目,至少我相对熟悉一点,配置的逻辑这些都比较明白,所以专门跑了一下。但很不幸的是,这东西用了一个很奇怪的许可 fsl11Mit ,许可条款不具体讨论,总之是“不免费”,这样只能是看一下。老实说它的 TUI 写得还是很不错的。这也许是一个可以八卦的点,但我无意八卦。
还有一点值得提及的是 charmbracelet/crush 也是有 nix 支持的,具体的可以去查看 charmbracelet/nur 。不过它的 overlays 并不是自动的,直接写 charmbracelet-nur.overlays.default 这样会报错。要像下面那样做一个手动导入:
overlays = [
(import "${charmbracelet-nur}/overlay.nix") # 手动导入 overlay.nix
];
作为一个命令行工具,而且本来就是以开源为发端的工具,在未达成领域内垄断,同时具有高可替代的情形下,改换一个不兼容许可属于极具勇气的行为。当然,作为局外人,多嘴是不好的。
回到正题,考虑到这类开发工具自主化程度相当高,对其进行隔离显然是必要的,这两年因为沉醉于 NixOS ,这样决定采用 NixOS 原生容器化方案,而不是烂大街通用的 Docker 容器化方案。当然,因为是第一次尝试,最终的方案仍会和多数 NixOS 原生容器化不同,这里先提示一下,本文所述的方案属于少数中的少数。也是因为这个关系,有必要对目标进行简化。
首先是没有必要弄成全局配置,一般来说,这类容器会配置在 configuration.nix 文件中,不过还有 extra-container 可以回避全局配置的问题。这样可以为一个 OpenCode 实例指定一个目录,然后再目录中写入一个独立的 flake.nix 文件即可。
其次,文件系统和进程空间能被孤立出来已经满足需求,而网络栈个人觉得还是先和宿主机共享,不然就要考虑 macvlan ,还有各种桥接,还有 IPv4 和 IPv6 诸如此类,对于几个在单机上的容器来说,这个复杂度有点过了。
最后是如何传入 api key ,个人还是考虑最简单的以环境变量传入。一般来说 NixOS 会采用 sops ,阅读了文档等等之后,感觉牵扯太多,这次只是在单机上,而且只有一个变量。
因为 api key 是以环境变量形式传入,那就有必要确定对应环境变量的名称。这里要提及 OpenCode 的另一个项目 anomalyco/models.dev 。这个项目中的供应商目录下的 provider.toml 文件中就是 OpenCode 启动时会查找的环境变量的名称。还有一个前端项目,也应当注意, OpenCode 直接使用了这个项目所定义的模型标识字串。
最后部署时, OpenCode 被做成了一个用户服务(systemd service)。后面需要的 Mariadb 也被我写成了服务,只是 mariadb 数据库的初始化我是手动跑的 mariadb-install-db ,和标准镜像里的脚本相比不免有些粗鄙,当然,反正能用就行。未来如果增加类似的环境依赖估计也会采用差不多的方式。
使用过程中最让人惊讶的地方在于它系统指令实际上是不会添加任何注释的:
# Code style
IMPORTANT: DO NOT ADD ANY COMMENTS unless asked to
这涉及到“自解释代码”的概念,不过没有想到,现在的注释可能确实无关紧要了。早年间开源社区还很流行通过注释来自动生成文档。现在想来,连文档也可以直接通过代码生成。
当然,如果进一步研究提示词的加载(源码:packages/opencode/src/session/system.ts),默认使用的提示词文件是 qwen.txt ,然后针对 gemeni, claude, trinity, gpt 有特异的优化。更有意思的是, OpenCode 并没有提供一个接口让用户,完全替换这些最基础的提示词文件。当然,可以编写一个 agent 配置,然后在启动时配置 --agent 参数。但这样做就比较绕了。
另一方面,如果查看用于上面完全禁注释的情况,只存在于 qwen.txt 和 trinity.txt 中。在 anthropic.txt 和 gemini.txt 中,也会限制注释,但仍被允许有条件使用。原文如下:
**Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
可以看出,后两者还是写一些注释的。但是前者产生的代码则必然一句注释也不会有。
回到 opencode 的功能上。如果项目本身有版本控制(我只用过 git) ,那么会有工作区(workspace)和沙盒环境(sandbox)支持。工作区和沙盒环境对应的是 ~/.local/share/opencode/worktree/ 目录。
部署技巧上,对于本地开发环境中的服务,使用套接字,而非端口监听,其实会省不少事。我一直不太明白为什么很多应用都默认使用端口监听。当然, opencode 本身对这个提议也是置若罔闻。不过我作为用户,对这个事情向来是只能持宽容的态度。
至于 opencode 对于供应商的支持,可以参照另外一个项目 model.dev。当然,这个项目本身是个网站,你可以在这里查确项目所支持的供应商,确认传入的 api key 的环境变量名,或是 yaml 配置文件中 key 的参数。这些小细节在实际部署中会让人非常头疼。
最后,我们来到大模型本身的选择,我只有三个指标,一是生产代码确实可以跑通,质量可以接受;二是令牌(token)用量和价格可以接受;三是部署和调整方案方便。提示词这些我自己可以慢慢优化。
这和雇佣一个人或者选择乙方本质上也没有什么区别。正像乙方也可能因为不相干的事情在不同的时间有不同的表现这类大模型也可能因为各种运营因素有各种变化。而人际交往中对不同的人,需要准备不同的沟通方式,也是显然的。
反之,我倒是对那些横向测评,通过同一套提示词来比较整出所谓的“最好”的模型,这种东西非常的不感冒。更深层次的,就像我上面指出的,同一个 AI 编程工具,它针对不同大模型的底层提示词是可以不一样的。而这些底层提示词,虽然现在还是某种玄学,但确实是决定最终结果的关键因素。更为重要的是,所谓的“通用”提示词,很多测评者的语言能力其本质是对人类交流能力的一种亵渎。引用原神最近的台词“派蒙,你可曾学习过语文?”。如果是给大活人的乙方下达这种指令,那人家肯定是要口吐芬芳的。