AI编码助手迁移与Windows自动化打包实践:从ZCode到DeepSeek Harness
2026/9/24 8:31:53 网站建设 项目流程

大概在半年前,我还在用 ZCode 辅助写项目代码。当时的直觉很简单:提示补全够快,上下文理解也在线,日常写合作项目很顺手。直到有一天,我准备把一个内部业务模块的改动提交上去,突然觉得不太对劲——这个模块的行数、注释、异常处理逻辑,好像都被“上报”过。再去查相关讨论,发现 ZCode 的“代码上传”争议已经引发了不小的风波。作为小团队的维护者,我不太可能保证每次都在可控范围内使用一个闭源云服务。

于是我做了一个决定:把这个 AI 编码环节彻底换成 DeepSeek Harness,同时对项目做了一件早该做的事——把 Windows 下的打包流程从“本地碰运气”改为 GitHub Actions 自动完成。这篇文章就是这次迁移的全实录,它会讲清楚 ZCode 为什么会从我的工具箱里消失、DeepSeek Harness 是怎么部署和使用的、以及我最终是如何在 GitHub Actions 上把 Windows 打包这条链路跑通的。

1. 弃用 ZCode 的原因:一次“上传事件”触发的数据安全复盘

1.1 ZCode 用得顺手,但隐患从第一天就存在

ZCode 的定位是 AI 编程助手,它能在编辑器里做行级补全、对话式解释、代码生成,按一下快捷键就能把选中的代码块丢给模型做解释或重构。对写脚本、写接口这类需求,它的速度确实让人上瘾。我前期在个人项目里用它写过不少零零碎碎的工具函数,体验可以用“真香”来形容。

但是痛点也特别明显。ZCode 是云端服务,只要你框选代码点击“发给模型”,这段代码就会离开本地环境。为了让模型更懂上下文,有些功能会自动附带当前文件甚至整个项目的部分内容。个人项目无所谓,但换到公司内部的业务模块,这就是一个很现实的数据安全问题。客户、内部数据、账号逻辑,这些内容如果被作为上下文发送出去,谁也说不清最后会落到哪个模型服务里。

在风波被集中讨论的那些天,我看到很多同行和我有相似的困扰:不是“不信任 AI 工具”,而是“不信任云端的不可见机制”。更关键的是,ZCode 的服务端策略并不透明,它不会告诉你代码在传输过程中被谁看了、存了多久、会不会被用于训练。对于一个有内部工具依赖的团队来说,这种黑盒状态很难接受。

1.2 数据安全要求的现实压力

当时我们团队正在做一个知识库桌面应用,里面有不少内部文档结构、客户名单、检索权重配置。这些数据如果在开发阶段就被“随手”发给云端模型,后面再做数据合规评估就非常被动。我在内部会议上提了一个简单问题:如果客户要求我们展示开发链路中哪些步骤会触达原始数据,我们能不能拍胸脯保证全程都在本地?

答案显然是不能。ZCode 不提供真正的本地模式,也不方便在网络层做白名单限制。为了不过度依赖它,我得在任务层和网络层同时做约束,这反而增加了团队协作成本。与其如此,不如直接用开放模型和本地运行时的组合方案彻底替换掉这个闭环。

1.3 替换前我心里列了一份标准

在动手切换到 DeepSeek Harness 之前,我给“替代工具”列了几个必要条件,避免又掉进同一个坑:

第一,必须支持本地模型接入。内部数据尽量不出内网。就算要用云端 API,也得是那种能明确开关上下文的接口,而不是一不留神就整文件上传。

第二,配置和技能文件必须能放进 Git 仓库。我想把每个 prompt、每个智能体的行为固化下来,团队里任何人 clone 下来都能跑,而不是依赖某个账号的云端同步。

第三,插件系统要够灵活。我需要的不只是“代码补全”,还要能编排“写代码、审代码、生成文档”这一类多角色任务。

第四,至少得有一个活跃的开源社区。出了问题能查到 issue,或者至少能自己改源码。

对照下来,ZCode 在第一、第二、第三条基本都不满足。DeepSeek Harness 则是我在尝试几个本地优先方案之后觉得最符合预期的一个。

2. DeepSeek Harness 的本地化部署:模型接入、技能配置与多智能体编排

2.1 DeepSeek Harness 到底是个什么定位

在正式讲部署之前,我先用自己的话描述一下 DeepSeek Harness:它是一个偏底层的智能体编排框架,你可以把它理解成一个“带控制台和插件的 AI 工作流调度器”。它不像某个云厂商的编辑器插件那样替你完成全部的事情,而是给你一套清晰的目录、配置文件和命令,让你自己决定模型调用谁、每个任务走什么流程、多个智能体之间如何协作。

它的核心优势,恰好是我之前列的需求:

  • 模型端点不绑定特定厂商。你可以连 DeepSeek API,也可以连本地 Ollama,甚至连一个兼容 OpenAI 接口的代理服务。
  • 技能(Skill)以文件形式存在。每个技能对应一个 YAML 配置外加提示词模板,改完丢进 Git,人人都能复用。
  • 支持多智能体编排。比如一个智能体负责生成代码,另一个负责审查,第三个负责把审查意见打回重写,这种“流水线式”的协作可以定义在配置里。

我在 Windows 上实际部署时,并没有使用一键安装包,而是直接由源码启动。这样做的原因很简单:我需要对依赖版本有明确控制,后续打内置包的时候也更好复现。

2.2 在 Windows 上从源码启动 Harness

先做环境准备。我本地用 Python 3.11,外加一个虚拟环境:

git clone https://github.com/<your-fork>/deepseek-harness.git cd deepseek-harness python -m venv .venv .venv\Scripts\activate pip install -r requirements.txt

这里要注意,Windows 下如果直接执行pip install -r requirements.txt有部分项目依赖包含uvloop这类只支持 Linux 的包,会直接报错。我在第一次装的时候就在这里卡住过,后来发现项目里通常会提供requirements-windows.txt或需要你显式跳过某些可选依赖。如果你是拉的主分支代码,一定要先看一下setup.pypyproject.toml里的可选依赖声明。

安装完之后,初始化配置:

harness init

这个命令会在当前用户目录下生成一个配置文件,里面有几个关键选项:模型端点、默认模型名、是否开启思考模式、日志级别、技能目录路径。我的最小化配置长这样:

[harness] model = "deepseek-chat" api_base = "http://127.0.0.1:11434/v1" thinking_mode = true skill_dir = "./skills" agent_dir = "./agents"

如果你用本地 Ollama,只要先把模型拉下来,然后把api_base指向http://127.0.0.1:11434/v1model改成本地模型名,比如deepseek-coder:6.7bqwen2.5-coder:7b,Harness 就能直接调用。这就是我强调的“本地化思路”——大部分数据根本不需要出网。

配置完成后,我习惯用一条命令快速验证连接:

harness run --prompt "你好,请用一句话说明你的运行状态。"

如果配置没问题,你会看到模型返回内容,并且控制台会打印出当前使用的模型端点和耗时信息。要是连本地模型都报超时,优先检查api_base是否写错,以及 Ollama 服务是否真的在监听对应端口。

2.3 技能(Skill)到底怎么配

DeepSeek Harness 的“技能”机制非常有意思。在传统编码助手那里,你只能靠对话框来约束模型行为。而在这里,你可以把一段固定流程做成一个可复用技能,例如“生成单元测试”或“审查代码风格”。

一个技能目录大概长这样:

skills/ code_review/ skill.yaml prompt_template.md run.ps1

skill.yaml里定义元信息:

name: code_review description: 对指定文件进行代码审查,输出问题清单 input: - file_path model: deepseek-chat thinking_mode: true

prompt_template.md是核心提示词模板,里面可以用变量占位:

你现在是资深代码审查员。请阅读文件 {{ file_path }},重点关注: 1. 安全风险(注入、路径穿越、硬编码密钥) 2. 异常处理是否完备 3. 性能瓶颈 4. 可读性 请按严重程度输出问题清单,并给出修改建议。

最后是一个run.ps1,它负责接收参数并调用 API 入口。这样团队里任何人都可以通过harness run --skill code_review --param file_path=xxx.py来执行统一标准审查。审查结果会稳定地按模板输出,不会再出现“有时候让模型看,有时候没让模型看”的不确定性。

2.4 多智能体协作的编排方式

除了单技能调用,我还用 Harness 配了一条简单的“编码—审查—修改”流水线。

定义两个智能体,一个是coder,一个是reviewercoder负责根据需求生成代码,reviewer负责检查产物并打回或通过。两者共享一个工作目录,通过 JSON 文件传递消息。配置上并不复杂,主要是在agents/目录下给每个智能体单独写一个 YAML,里面写明它依赖哪些技能、使用哪个模型、最大轮次是多少。

我的实际体感是:多智能体的价值不在“模拟几个人开会”,而在于把不同职责的提示词隔离在不同的上下文中。比如写代码时不需要背着一大堆审查规则,审查时也不需要关心功能实现的细节。上下文变短之后,模型输出的稳定性会好很多,特别是在 deepseek-chat 这类长上下文模型上,至少不会出现写到一半开始自言自语的怪事。

到这里,项目的 AI 辅助链路已经全部迁移到了本地可控的 DeepSeek Harness 上。接下来要解决的就是那个更机械的问题:怎么把应用稳定地打包成 Windows 产物,并且不需要每次都在本地开命令行。

3. Windows 打包为什么要搬到 GitHub Actions:目标不是“能出 exe”这么简单

3.1 本地打包的真正痛点

在使用 GitHub Actions 之前,我也尝试过在本地用 PyInstaller 打包。坦白说,小项目打包一次确实很快,但当你需要每周出一个候选版本时,问题就会逐渐暴露:

  • 环境漂移。今天在 A 机器上打出来的包和明天在 B 机器上打出来的包可能因为补丁版本、环境变量、SDK 路径不一样而产生差异。用户反馈“这里报错”时,你无法快速重建出当时的打包环境。
  • 依赖不可复现。本地环境可能装了 A 依赖的 1.1 版本,但 requirements.txt 写的是>=1.0,下次重新装可能就变成了 1.2,然后某个 C 扩展库在 Windows 下又出兼容问题。
  • 分发路径低效。打包出来后,如果走微信小文件传输或者内网盘发给人,既没有版本记录,也没有校验信息,出了问题很难追溯。
  • 资源占用。打包时 PyInstaller 会把所有依赖扫描一遍,IO 和 CPU 占用都比较高,经常打扰我正在本地调试的进程。

所以,把 Windows 打包搬到 CI,对我来说不是“为了显得很工程化”,而是实打实地把“发布”这个动作从个人电脑里解放出来。

3.2 为什么选择 GitHub Actions 而不是自建 Jenkins

我评估过自建 Jenkins,也看过其他 CI 方案。最后选择 GitHub Actions 的原因很朴素:

  • 项目代码本来就在 GitHub 上,不需要额外维护一套共享存储和构建节点。
  • 公共仓库使用 GitHub 托管的 Windows runner 是免费的,即使配置只能跑在自己仓库,成本也远低于自建服务器。
  • 生态成熟。actions/checkoutactions/setup-pythonactions/upload-artifact这些官方动作已经帮我把环境准备和产物保存链路解决了大半。
  • 工作流文件用 YAML 写,能放进仓库,符合我前面说的“配置要在 Git 里可审计”。

当然,GitHub Actions 也有它的限制,比如 runner 的 IP 是动态的、Windows 虚拟机实例的临时性很强。但对于 Windows 应用打包来说,这些限制基本上不影响,因为我们本来就需要一个干净的临时环境,打包完成后立刻丢弃。

3.3 我重新定义的“打包完成”标准

在设计工作流之前,我给自己列了一份“完成”的定义,而不是简单一句“生成了 exe”。

  • 触发必须是确定性的。打正式包只发生在 push tag 时,比如v1.2.0
  • 环境必须是可复现的。Python 版本锁定,依赖使用锁定文件,构建工具版本固定。
  • 产物必须可追溯。每次构建输出下来,都要带上 commit SHA 和构建时间。
  • 必须有人工可执行的回滚方式。保留历史 artifact,不一定每次都发布到 release 页面。

有了这个标准,后面的工作流设计就变得很具体了。我不需要在一个 YAML 里堆砌各种炫技操作,而是要按“可复现、可审计、可回滚”这三个原则慢慢拆解。

4. 核心工作流:Windows runner 上的构建、缓存、产物流转

4.1 触发方式和分级配置

我在项目里使用的是workflow_dispatch加 tag 触发的方式。workflow_dispatch允许我手动触发一次构建,适合日常验证;tag 触发则留给正式发布。这样就避免了每次 push 代码都跑一次完整打包,节省大量排队时间。

name: build-windows on: workflow_dispatch: push: tags: - 'v*' permissions: contents: write

需要提醒的是,permissions: contents: write是为了后面能自动上传 release 资产。如果你不打算自动发 release,只是把 artifact 留在 Actions 页面,那么可以不给这个权限,遵循最小权限原则。

4.2 环境准备、依赖安装和缓存

装上actions/setup-python之后,它会自动读取项目的requirements.txtpyproject.toml做缓存。不过我建议在项目根目录放一个锁定文件,比如requirements-lock.txt,这样打包环境不会因为某个间接依赖的小版本升级而出现意外。

jobs: build-windows: runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' cache: 'pip' cache-dependency-path: requirements-lock.txt - name: Install dependencies shell: pwsh run: | python -m pip install --upgrade pip pip install -r requirements-lock.txt pip install pyinstaller==6.6.0

在 Windows runner 上,我一般用pwsh作为默认 shell,因为 PowerShell 对路径和错误处理更友好。如果你用默认的cmd,遇到路径带空格、循环变量等问题会非常痛苦。

4.3 PyInstaller 打包配置和 spec 文件管理

我建议把 PyInstaller 的编译选项沉淀成一个build_win.spec文件提交到仓库,而不是在命令行里写一堆--hidden-import。这样别人改的时候能清楚地看到隐藏依赖、数据文件、图标都配在哪里。

一个精简的 spec 文件大概长这样:

# build_win.spec # -*- mode: python ; coding: utf-8 -*- a = Analysis( ['app_main.py'], pathex=['.'], binaries=[], datas=[('assets/', 'assets/'), ('skills/', 'skills/')], hiddenimports=['pydantic_core._pydantic_core'], hookspath=[], runtime_hooks=[], excludes=['tkinter', 'unittest'], noarchive=False, ) pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, a.binaries, a.datas, [], name='knowledge-assistant', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=False, icon='assets/app.ico' )

注意这里我用了console=False,因为它是桌面应用,不应该弹出黑色命令行窗口。但如果你还在调试阶段console=False会隐藏掉错误信息,打包出来跑不起来又看不到提示,建议调试期先改成console=True

然后在工作流里调用它:

- name: Build with PyInstaller shell: pwsh run: | pyinstaller --clean --noconfirm build_win.spec

4.4 从产物到 Release:上传 artifact 与自动发布

打包完成后,第一步是把产物保存为 GitHub Actions artifact,这样就算没有打 tag,团队成员也能在 Actions 页面下载到当前 commit 对应的构建产物。

- name: Upload Windows artifact uses: actions/upload-artifact@v4 with: name: knowledge-assistant-win-x64 path: dist/knowledge-assistant/ if-no-files-found: error

等验证没问题,再补一个“打 tag 后自动发 release”的步骤。我用的是softprops/action-gh-release

- name: Upload release asset if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: files: | dist/knowledge-assistant/*.exe dist/knowledge-assistant/*.dll env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

这里的小技巧是:GITHUB_TOKEN不需要手动建 secret,它由 Actions 运行时自动注入。你只要在 job 的permissions里给了contents: write,它就有权限往 release 里上传文件。

4.5 为什么我最终放弃 onefile,改用 onedir

这是一个很典型的打包决策。最开始我图省事,直接用--onefile打单一 exe。优点确实是分发方便,但坏处也很明显:

  • 启动时 PyInstaller 需要把文件解压到临时目录,会带来肉眼可见的延迟。
  • 很多杀毒软件对每次释放临时文件的单一 exe 更加敏感,误报率远高于目录结构的程序。
  • 如果某个 DLL 或资源文件被误删,排查非常困难。

所以我切换到onedir模式,把整个knowledge-assistant/目录压缩成 zip,通过 release 页面分发给内部用户。实测下来启动速度提升了一半,安全误报率也明显下降。

5. 第一次跑 CI 就翻车:四个问题的定位与修正

5.1 “Microsoft Visual C++ 14.0 or greater is required”的误导性提示

我第一次把编译步骤推到 GitHub Actions,没过多久就看到依赖安装阶段红了一大片,报错内容是经典的Microsoft Visual C++ 14.0 or greater is required。我当时第一反应是让 runner 安装 Visual Studio Build Tools,差点走上一条给每个 job 安装 2GB SDK 的笨路。

后来冷静下来发现,之所以出现这个错误,是因为一个 Python 包的 C 扩展没有对应的 Windows wheel。pip install在找不到预编译包时,会选择从源码构建,然后源码构建需要本机 C 编译器,于是报了这个错。这其实不是 CI 环境缺编译器,而是依赖版本解析把没有 wheel 的包拉了进来。

解决办法很简单:在requirements-lock.txt里把相关包固定到有 Windows wheel 的版本,同时在setup-python里指定一个足够新的 Python 小版本,比如 3.11.x。遇到类似报错,不要急着一通安装编译器,先去 PyPI 查一下这个包是否存在win_amd64.whl

5.2 PowerShell 环境下引用的路径问题

第二个问题发生在 PyInstaller 阶段。警告信息显示找不到assets/目录。我一开始以为路径写错了,直到在本地 PowerShell 里手动执行了一遍才发现,是run块里我把路径写成了相对路径,而 GitHub Actions 的工作目录有时并不如你预期。

在 GitHub 托管的 Windows runner 上,仓库会被 checkout 到D:\a\<仓库名>\<仓库名>。如果某个步骤之前切换过目录,后面run里的相对路径就会跑偏。

我的修正方式是:在关键的 shell 命令前先显式切回工作目录:

cd $env:GITHUB_WORKSPACE pyinstaller --clean --noconfirm build_win.spec

不要相信 “当前目录应该是仓库根目录” 这种默认假设。Actions 某些缓存动作和第三方 action 可能会改变当前工作目录,最稳的方式就是每次都显式 cd。

5.3 PyInstaller 隐藏导入导致启动崩溃

第三轮构建很顺利,exe 也出来了,但在我本地双击运行的时候直接闪退。这种问题是最难查的,因为它不是“构建时错误”,而是“运行时错误”。

我查了事件查看器,发现是pydantic_core._pydantic_core这个模块没被正确打进包里。原因是我依赖的 FastAPI 在加载时会动态引入这个模块,PyInstaller 静态分析时没有完全捕捉到。

解决办法就是在 spec 文件的hiddenimports里显式声明:

hiddenimports=['pydantic_core._pydantic_core']

这一点也提醒我:不管 AI 辅助工具多智能,最后能验证产物真的能跑的人只有你自己。我后来专门在 CI 里加了一个简单的“启动冒烟测试”,用subprocess启动 exe 并等待几秒,检查进程是否存活,这样能把一部分运行时问题拦截在 CI 阶段。

5.4 Release 上传失败:权限配置遗漏

最后一个问题出在上传 release 资产时。提示Resource not accessible by integration。这个问题的原因非常明确:我没有给 job 授予对 release 的写权限。

permissions块必须同时具备contents: write,仅放在 workflow 文件末尾是没用的,因为它定义在每个 job 的顶层。调整之后,上传立即成功。

这一轮排查下来,我的感受是:CI 报错并不可怕,可怕的是看到报错就立刻在 Windows runner 上装各种编译工具。先检查依赖有没有 wheel,再检查路径是否戴好变量,最后再考虑权限问题,这个顺序能省下很多时间。

6. 搭建跑通后的经验沉淀:这几件事千万别省

6.1 锁定依赖版本,别信“>=”

在打包问题上,我最大的教训就是依赖版本必须锁定。requirements.txt里写fastapi>=0.100这种宽松范围,对于开发是友好的,但对于打包就是灾难。因为你不知道哪次重装会拉到一个新版,然后某个传递依赖在 Windows 下不再提供 wheel。

我现在使用pip-toolspip freeze生成requirements-lock.txt,提交到仓库。CI 构建时直接安装锁定文件,保证每次构建的依赖完全一样。这对 Debug 线上问题尤其重要——用户报错时,我能准确知道打包用的 pydantic 版本是 2.6.4,而不是一个模糊的“2.x”。

6.2 签名不是可选项,是必经之路

Windows 桌面应用如果不做代码签名,用户首次运行时大概率会遇到 SmartScreen 的蓝色警告。对内部工具来说,你可以让同事点“更多信息”再“仍要运行”,但团队规模稍大一点,这种操作就会变成混乱的源头。

理想的方案是购买 OV 或 EV 代码签名证书,在 GitHub Actions 里用Azure Trusted Signing或者导入 PFX 证书的 action 完成签名。如果你只是个人项目或内部小范围使用,可以先用自签名证书顶一顶,同时留下明文说明。但别把签名步骤删掉,否则后面会有更麻烦的信任问题。

6.3 保留历史 artifacts,做版本回滚

很多团队习惯把构建产物传到 release 页面,然后手动删除旧版本。我强烈建议保留最近的 5 到 10 个历史 artifacts,尤其是在没有自动回滚系统的时候。GitHub Actions 的 artifact 会对 commit SHA 和构建时间做标注,配合 release 页面,你能比较清晰地定位出“哪个构建时间点开始出现回归”。

如果哪天某个用户反馈版本行为异常,我可以直接回到上一版 artifact,快速验证是代码改动还是打包环境变动引入的问题,而不是逼用户重新整理日志。

6.4 DeepSeek Harness 的配置也要进版本库

最后再说回到 DeepSeek Harness。很多人部署完 Harness 后,只把提示词写在本地调试记事本里,这是非常可惜的。我的习惯是:

  • skills/agents/目录全部纳入 Git 管理。
  • 每个 skill 的 prompt 模板必须写明适用场景和依赖模型。
  • 模型端点在配置文件中通过环境变量引用,不要把本地 Ollama 的地址硬编码到共享配置里。

这样,如果团队里来了新人,他只需要 clone 仓库,运行harness init,把环境变量指向他的本地模型端点,就能拿到和我完全一致的智能体流程。不同人之间的差异只剩下本地模型版本,而不是“提示词写法不同导致的结果漂移”。

我还踩过一个小坑:Harness 迭代迅速,某次升级后我的多个 skill 配置全部失效,后来发现是配置格式变了。所以我干脆把harness --version写入docs/目录,如果有人升级,会先被提醒检查配置文件兼容性。虽然听起来繁琐,但真的是避免“莫名其妙地坏掉”的高效办法。

跑完这次迁移之后,我个人的体会是:真正影响开发效率的,往往不是代码生成速度,而是工具链的确定性和安全感。ZCode 的弃用与其说是“某个功能让我失望”,不如说是我对整个工作流的数据控制能力提出了更高要求。DeepSeek Harness 帮我解决了 AI 编排和本地化的问题,GitHub Actions 则把 Windows 打包从一个“本地黑盒”变成了透明、可重放的流水线。如果你也在用类似的云编码助手,且恰好需要面对 Windows 分发问题,不妨也按这个思路认真做一个迁移。打包自动化这件事,早做永远比晚做省心。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询