AI编排实践:DeepSeek Harness+GitHub Actions实现Windows打包流水线
2026/9/23 11:09:47 网站建设 项目流程

那次给客户发新版,我照旧手动开虚拟机出 Windows 安装包,装完才发现忘了把 VC 运行库打进去。客户现场装不上,运维和技术支持在群里轮番@我,那个下午我基本是在导日志和重新打包里度过的。当天晚上我就下了决心,出包这件事必须自动化,而且要让 AI 参与编排。

之前我一直在用 ZCode 这类 AI 编程工具写辅助脚本。说实话,它帮我写过不少打包脚本和配置文件,但我的真实需求不只是“生成代码”,而是一条能复用的、可审计的构建流程:从代码提交到跑测试,再到出 Windows 包、生成更新说明。ZCode 管不到这一步,我需要的其实是一个可以编排 Agent 的“工头”,而不是一个聊天窗口。后面我切到了 DeepSeek Harness,用 GitHub Actions 把 Windows 打包整个搬到了云端流水线。

现在这套链路已经稳定跑了几个月。流程变成了:推送一个 tag,GitHub Actions 在 Windows runner 上拉代码、装依赖、跑测试、打 exe、上传 artifact,我再下载做签名和分发。这篇不是复刻官方文档,我会把换工具的决策过程、Harness 的部署细节、Actions 流水线的排坑经历都摊开写。如果你也被“手动出包”折磨过,或者想让 AI 真正参与构建流程而不是停留在聊天框里,这篇应该对你有用。

1. 离开 ZCode 的真实原因:不是它不够好,而是它管不到流程

1.1 ZCode 解决过的问题,我不否认

说句公道话,ZCode 最早确实解决了我一部分问题。它是面向编程场景的 AI 工具,直接对接模型来生成和修改代码。最常用的场景是让它帮我写 PyInstaller 的 spec 文件、补 requirements.txt、生成 gitignore、甚至解释某些报错。对一个经常跟多环境打交道的开发者来说,这比反复查文档快得多。

但用着用着你会发现,这类工具的核心交互模式是“对话”。你问一句,它答一句;你让它改一处,它生成一段 diff。适合“点状”任务,不适合“链式”任务。我要的 Windows 打包流程是线性的:确定版本号 → 更新依赖 → 跑单测 → 编译 exe → 收集产物 → 生成更新日志。每一步之间有依赖关系,需要按顺序执行,还要能重复跑。对话式工具每次都要重新交代上下文,天生就做不好这件事。

1.2 三个让我最终决定下车的点

第一个是黑盒顾虑。当时社区里有一些关于 ZCode 数据传输行为的讨论,我没有实锤,也不会去下“它一定偷代码”这种结论。但这里有个很现实的问题:我手上有企业内部项目的源码,公司安全红线是代码不允许未经审批外发。一个闭源的、数据上报策略无法审计的工具,我没办法在项目里长期使用。哪怕你只有 1% 的怀疑,面对核心代码库,这 1% 都赌不起。

第二个是流程颗粒度不对。ZCode 的定位是“写代码”,可我缺的不是代码,是“代码写完之后的事情”。举个例子,我有个工具打包时需要把版本号写进 exe 的属性页、同时更新一个 version.json 给自动更新服务用。这个逻辑不复杂,但需要稳定重复执行。我更希望有一个配置化的方式,把这类操作固定下来,而不是每次让 AI 重新生成一遍逻辑,偶尔还会生成出参数对不上的版本。

第三个是多任务并行。我希望“构建助手”“代码审查助手”“更新日志助手”能同时跑,或者按 DAG 编排顺序执行。ZCode 的对话形态撑不起这种多智能体场景。DeepSeek Harness 正好是开源的多智能体编排框架,主 Agent 下面可以挂不同的 Skill 和 Plugin,模型后端也能切换,适合我把整个构建流程抽象成可执行的技能。

1.3 拆掉重来时我给自己定的标准

决定切换工具前,我列了一个需求清单,拿来衡量新方案是否合格:

  • 可审计:配置全文本化,数据流向明确,能私有化部署模型。
  • 可编排:能把代码检查、构建、打版本、生成日志串成一条流水线。
  • 可编程控制:能通过 CLI 或 API 触发,能被 CI 调用。
  • 可回退:升级新版本后能方便退回旧版本,不被工具绑架。

按这个清单筛选下来,DeepSeek Harness 最合适的点在于它是开源的,Skill 机制能把“Windows 打包”定义成一个流程模板,而且它支持接入本地模型。GitHub Actions 则负责解决“在哪里跑构建”的问题。两者配合,才算是把我要的闭环补齐了。

2. DeepSeek Harness 部署要点:从安装到第一个 Skill

2.1 Harness 的架构认知:它不是一个聊天框,是一个调度系统

开始装之前,建议你先建立一个整体认知。DeepSeek Harness 本质上是多智能体的运行与编排环境,你在里面定义“谁在什么时候调用什么工具,用哪个模型回答问题”。我习惯用一个类比来解释:它像一条生产流水线,模型是工人,Skill 是工序卡,Plugin 是外挂设备,而 Harness 本体是那个安排先后顺序的工头。工头不亲手拧螺丝,但它知道先拧哪颗、后拧哪颗,流程不对还能停下来求助。

我跑起来之后发现这套抽象对工程场景特别受用。因为 Windows 打包本身就是一个流程,你可以在 Harness 里显式定义“先提取代码变更列表,再跑测试,最后执行打包脚本”,每一步的输入输出都能追踪。这比在对话窗口里让 AI 凭感觉生成一段脚本要可靠得多。

2.2 安装与初始化:动手前先看这两条

安装没什么神秘的,官方仓库 README 里写得很清楚。大致是拉取代码、创建虚拟环境、安装依赖。我个人习惯把仓库克隆到/opt/harness(Linux 上)或D:\Tools\harness(Windows 上),然后用虚拟环境隔离依赖,避免污染全局 Python。

初始化时它会在用户目录下生成配置目录,里面包含模型配置、Skill 目录、Plugin 目录和日志文件。第一次跑会让你选择模型后端。这里有两个方向:连云端 API,或者接本地模型。我的建议是前期先用 API 把流程跑通,后面再根据隐私需求切本地模型。不要一上来就在本地部署大模型,先把编排逻辑跑通了,模型后端随时可以换。

2.3 配置模型:密钥不进仓库

配置模型时我只强调一点:API Key 不要写进任何 YAML 或配置文件再提交到 Git。Harness 支持从环境变量读取密钥,我会在配置里写成api_key: ${DEEPSEEK_API_KEY},然后在本地或 CI 的环境变量里注入真实值。这不是小题大做,我见过太多人把 key 提交进仓库,几小时内就被爬虫扫走。

如果你要接本地模型,路径也不复杂。Harness 的模型后端支持 OpenAI 兼容协议,所以 Ollama、vLLM、LocalAI 这类服务都可以。我本地试过用 Ollama 跑 Qwen 系列模型,配置 base_url 指向http://127.0.0.1:11434/v1就能接通。这样在需要处理高敏感代码摘要时,可以一键切到本地方案,代码完全不出内网。

2.4 写一个 Windows 打包 Skill:把流程固化下来

Skill 是 Harness 里最核心的扩展单位。一个 Skill 本质上是一个定义文件加若干可执行脚本。定义文件里写清楚这个技能是干嘛的、支持哪些指令、允许调用哪些命令、输出什么结果。下面的示例是我的win_buildSkill 的核心配置骨架:

name: win_build description: 在 Windows 环境下执行桌面工具打包,包括版本号注入和产物收集 allowed_tools: - git - python - pip - pyinstaller steps: - name: ensure_deps command: pip install -r requirements.txt - name: inject_version command: python scripts/inject_version.py --version {{ version }} - name: build_exe command: pyinstaller --clean --onefile --name app windows_main.py - name: collect_output command: python scripts/collect_output.py

重点在于allowed_tools这一项。它相当于给 AI 划了一条命令边界:你只能调用这几个可执行文件,不在列表里的命令一律拒绝。我踩过坑之后才意识到,这一步不是可选项,是必选项。第一次没配边界,AI 分析打包产物体积时居然想直接执行Remove-Item来清理“无用文件”,幸好 Harness 会弹确认,我及时拦住了。给 AI 限定工具边界,跟给外包员工开权限账号是一个道理:最小权限,才能不闯祸。

2.5 关于版本回退:清理缓存比换代码更重要

DeepSeek Harness 迭代很快,我也遇到过新版本行为变化导致 Skill 跑不通的情况。搜索时看到不少人问“怎么退回到 v0.1.5-rc.2”,这个我熟。除了在 Git 里切回对应 tag,还要注意清掉 Harness 的缓存目录,尤其是 Skill 编译缓存和对话历史缓存。只回退代码不清理缓存,你切回去会发现新版本的残留配置还在,问题依旧。正确的操作顺序是:先进仓库执行git checkout v0.1.5-rc.2,再删掉用户目录下 Harness 的缓存目录,最后重新初始化配置。退版本之前,最好把当前版本里的 Skill 配置备份一份,回退之后直接恢复,省得重新配。

3. 用 GitHub Actions 把 Windows 打包变成流水线

3.1 为什么我选了 GitHub Actions,而不是 Jenkins 或商业打包服务

选择 GitHub Actions 之前,我对比过几个方案。Jenkins 我熟,功能也强大,但需要在公司维护一台常驻服务器。团队里没人愿意当这个“Jenkins 保姆”,插件升级、构建节点维护、权限管理,都是持续成本。商业云打包服务我也看过,但有几个痛点:一是打包过程在别人服务器上,需要上传源码,敏感的内部项目不敢传;二是定制能力受平台限制,很多冷门依赖装不上。

GitHub Actions 的好处是 runner 可以托管也可以自建,工作流描述是纯文本 YAML,天然适合代码审查。Windows runner 官方直接提供 Windows Server 环境,Python、PowerShell、常见构建工具都是现成的,不需要自己初始化系统。而且 Actions 的计费对私有仓库也有免费额度,小项目基本够用。对个人开发者和中小团队来说,它是目前综合成本最低的方案。

3.2 完整 Workflow 配置:从拉代码到传产物

下面这份 YAML 是我实际在用的简化版,去掉了内部通知部分,只保留核心流程:

name: build-windows on: workflow_dispatch: push: tags: - 'v*' jobs: build: runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Cache pip uses: actions/cache@v3 with: path: ~\AppData\Local\pip\Cache key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pyinstaller - name: Inject version run: python scripts/inject_version.py --version ${{ github.ref_name }} - name: Build exe run: pyinstaller --clean --onefile --name app windows_main.py - name: Upload artifact uses: actions/upload-artifact@v4 with: name: app-windows-x64 path: dist/app.exe

触发条件我设置了两条:workflow_dispatch支持手动触发,方便临时出包;push带标签自动触发,比如推送v1.4.0这个 tag 就自动开始构建。这样版本发布和出包天然绑定,不会出现“代码改了但忘打包”的尴尬。

Setup Python一步我固定用 3.11,不是因为最新版不好,而是老项目里有几个依赖库在 Python 3.12 上还有兼容问题。如果你的项目是新的,建议直接在测试环境试跑 3.12 或 3.13,能用就用新版。跑在旧版本上只是求稳,不代表新版本不行。

3.3 版本号注入:tag 不是摆设,要真正写进产物

很多人打出来的 exe 在“文件属性 → 详细信息”里看不到版本号,就是因为构建时没有注入版本信息。我把github.ref_name(也就是你推送的 tag,比如v1.4.0)作为参数传给inject_version.py,脚本里会做两件事:把版本号写入version.txt供程序读取,同时更新 PyInstaller 的版本资源文件。这样打出来的 exe,属性页里能正确显示产品版本,自动更新服务也能通过接口拿到版本号做比对。

你可能会问,tag 名是v1.4.0,直接拿来用不会带上v吗?对,所以脚本里要处理一下,去掉前导的v

import argparse parser = argparse.ArgumentParser() parser.add_argument("--version", required=True) args = parser.parse_args() version = args.version.lstrip("v") print(f"version={version}") # 示例:写入 version.txt with open("version.txt", "w", encoding="utf-8") as f: f.write(version)

这个细节看着小,实际影响很大。版本号写不进去,后续的自动更新和问题追溯全都会乱套。

3.4 自建 Runner 还是用托管的 windows-latest

如果你只是打包公开项目,直接用 GitHub 托管的windows-latest就够了,省心。但像我兼顾公司内部项目,就不得不考虑代码不出内网、依赖私有源、需要挂内部数字证书这几个因素。这种情况下,自建 Runner 反而更合适。

我列过一张对比表,你可以直接参考:

对比维度托管 Runner(windows-latest)自建 Runner
运维成本零,官方维护需要自己装环境、打补丁
源码安全代码会上到 GitHub 服务器代码留在内网或指定机器
依赖访问只能访问公网依赖源可访问内网私有源
证书安装无法长期保存内部证书可以预装企业证书
网络策略受限于 GitHub 服务可配合企业网络策略

我现在的方案是混合使用:公开项目走托管 Runner,内部项目走自建 Runner。这样既省了公共项目的运维成本,又保住了内部项目的安全红线。

4. AI 编排 + 云端构建的完整链路:从 PR 到发布包

4.1 用 Harness 做“PR 体检”,把无效构建提前拦下来

流水线跑顺之后,新的问题是:每次 PR 都触发构建,但有些改动只是文档说明或注释调整,根本不需要出包。构建本身要花费几分钟,跑多了浪费额度。

我在 Harness 里做了一个pr_reviewSkill,让它充当“体检医生”。PR 创建或更新时,它会把变更文件列表和 git diff 的统计信息交给模型,判断这次变更是否影响最终产物。如果只是README.md.github/workflows下的文档变动,它直接评论一个“无需打包”;如果涉及核心代码或依赖文件,它会评论“建议出包,请维护者确认”。

这一步最开始我只让它在本地输出结论,后来接了 GitHub CLI 让它把结果写成 PR 评论,团队其他人也能看到。效率提升很明显,无效构建减少了一半以上。需要说明的是,这里我只把文件列表和 diff 统计发给模型,不会把完整源码外发,隐私方面是安全的。

4.2 自动生成 Changelog:让 AI 只读提交历史,不直接动仓库

以前每个版本发布,我都要翻一遍 git log,手动整理哪些是新增、哪些是修复。现在这个活也交给 DeepSeek Harness 了。在 Actions 里增加一个步骤,读取git log中从上一个 tag 到当前 tag 的提交信息,通过 DeepSeek API 分类汇总,生成结构化的更新说明。我画个最简单的实现思路,用 Python 调用 API:

import json import os import requests commits = os.popen("git log --oneline v1.3.0..v1.4.0").read().strip() prompt = f""" 根据以下 git 提交记录,生成中文更新日志。 要求按「新增」「修复」「优化」三类分组,每条不超过30字。 提交记录: {commits} """ resp = requests.post( "https://api.deepseek.com/chat/completions", headers={ "Authorization": f"Bearer {os.environ['DEEPSEEK_API_KEY']}", "Content-Type": "application/json", }, json={ "model": "deepseek-chat", "messages": [{"role": "user", "content": prompt}], "temperature": 0.3, }, timeout=60, ) data = resp.json() changelog = data["choices"][0]["message"]["content"] print(changelog)

我特意强调一点:这个脚本只是“生成”更新日志,不会把日志直接推回仓库。Changelog 推到发布描述里还是需要人工确认的。因为 AI 偶尔会漏掉破坏性变更,或者把同一件事拆成两条写。自动化能做到“帮你起草”,但“确认发布”这个动作还是要留给人。这是我在踩过几次坑之后坚持的原则。

4.3 签名和分发:自动化止步于“签名”是正确的

GitHub Actions 可以自动构建,但不能替你完成真正意义上的代码签名。注意区别:生成一个自签名证书谁都能做,但用自签名证书签出来的 exe,在 Windows 上依然会触发 SmartScreen 警告,用户要额外点几步才能运行。企业正式分发必须用受信任的 EV 证书,这类证书通常要求硬件密钥存储、严格的私钥管理。我不会把 EV 证书的私钥放进 CI 服务器,风险太大了。

所以我的流程是:Actions 把打好的 exe 和版本文件上传为 artifact,我下载之后,在本地或专用签名机上用硬件令牌完成签名,再推到内部更新服务器或发给客户。自动化完成了 90% 的重复劳动,签名这 10% 保留人工兜底,是安全和效率的平衡点。

4.4 跑通后的时间账

整套链路跑通后,我算过一笔时间账。以前手动走一遍 Windows 打包流程,包括开虚拟机、装依赖、跑测试、打 exe、整理产物,大约需要 1.5 到 2 小时。现在从推送 tag 到 artifact 就绪,流水线自动跑完大约 10 到 15 分钟,这段时间我可以去干别的。更重要的是,以前“上次能成,这次不能成”的玄学问题基本消失了,因为每次构建都从同一套干净的 Windows 环境开始,环境差异导致的怪问题被直接消灭掉了。

5. 排坑实录:Windows 打包流水线常见故障速查

5.1 Windows runner 环境三大坑

第一个坑是执行策略。GitHub Actions 的 Windows runner 默认用的是 PowerShell,但 PowerShell 脚本默认执行策略是 Restricted,直接跑.ps1脚本会报错。解决办法是在脚本前加powershell -ExecutionPolicy Bypass -File,或者直接改用shell: bash来执行命令,Actions 的 Windows runner 自带 Git Bash,很多命令在 bash 里跑反而省心。

第二个坑是路径分隔符。GITHUB_WORKSPACE在 Windows runner 上指向D:\a\repo\repo,bash 脚本里要用正斜杠才能正常拼接。我经常看到有人混合使用$GITHUB_WORKSPACE\scripts\foo.pypython $GITHUB_WORKSPACE/scripts/foo.py,因为转义问题报“路径不存在”。建议全程统一用正斜杠,Windows 的 API 是能认正斜杠的。

第三个坑是杀毒软件扫描。Windows runner 上跑着 Windows Defender,构建完成后它可能会对新生成的 exe 做扫描,这本身不会报错,但如果你的构建脚本紧接着就要读取这个 exe,偶尔会碰上文件被占用的报错。遇到这种情况,可以在构建步骤后加一个短暂的等待重试逻辑,或者用sleep 5缓解。当然,如果你打包的工具本来就容易被误报,那是另一个要单独处理的话题。

5.2 DeepSeek Harness 配置类问题

先说“本地模型思考模式回空”的问题。我有段时间给 Harness 接本地模型,发现设置成深度思考模式后,模型偶尔会返回空内容。排查下来是参数的问题:本地小模型对temperaturetop_p的敏感度很高,某些采样参数组合下,模型会陷入低置信度循环,最终产出空回复。把temperature调低到 0.3 左右,同时把max_tokens设置得足够大,情况就缓解了。如果还是空,就把流式输出关掉再试。

再说“Skill 执行越界”的问题。我在前面提到,Skill 必须配置allowed_tools白名单。但白名单不是万能的,AI 仍然会尝试用允许的命令做不允许的事,比如用 Python 的os.remove删文件,而 Python 本身在allowed_tools里。解决办法有两个层面:一是在 Skill 的指令提示里写明“禁止删除构建目录以外的文件”;二是在 Harness 的执行确认机制上,保留高危操作的人工审批。这两个我都在用,缺一不可。

还有一个很细节的问题:API 超时。当提交记录特别多时,请求 DeepSeek API 生成 changelog 可能会超过默认超时时间。我的脚本里把timeout设成了 60 秒,并且加了重试逻辑。第一次超时后等几秒再试基本都能成功。如果你的提交量很大,建议分批处理,比如按目录或者按时间切块,一次传的提交记录控制在合理范围内。

5.3 超实用速查表

现象常见原因解决办法
PowerShell 脚本不能执行执行策略限制-ExecutionPolicy Bypass
构建脚本报“路径不存在”路径分隔符混用统一用正斜杠
exe 生成后被占用/杀毒误报Defender 扫描构建后加等待或重试逻辑
版本号没有写进 exe版本资源文件未注入构建前执行inject_version.py
本地模型返回空内容采样参数不匹配调低temperature,关闭流式
AI 尝试执行计划外命令Skill 边界没划清配置allowed_tools+ 高险操作人工确认
API 调用超时上下文过长加长 timeout,加重试,分批请求
回退 Harness 版本后行为异常缓存未清理退 tag 后清缓存再初始化
artifact 名称显示异常名称含中文或特殊字符artifact 名称只用英文、数字、连字符
依赖安装缓慢缓存未生效配置 actions/cache 缓存 pip

我个人的体会是,这套链路跑通之后,最大的收获不是省了多少时间,而是出包结果的一致性。以前手动出包,能不能成功一半看机器状态,一半看运气;现在每次构建都从同一个干净环境开始,流程固化,参数沉淀成代码,产品的发布动作真正变得可控了。

如果你也想往这个方向改造,我建议别一上来就追求“AI 全自动出包”。先做最小闭环:用一个简单的 GitHub Actions workflow 把一次 Windows 打包跑通,哪怕只是打一个 hello world 的 exe。跑通之后再加版本号注入、缓存优化。最后再接 DeepSeek Harness 的智能体检和 changelog 生成。这样每一步都有明确的验证点,出问题也知道是哪个环节的锅。

最后再分享一个小技巧:把 Harness 生成的构建建议直接写成 GitHub PR 评论,比任何 IM 通知都管用。因为评论和代码变更绑定在一起,点开 PR 就能看到来龙去脉,事后追溯也方便。就用这个习惯收尾,希望你下次出包,也能安心地把时间花在真正需要人的事情上。

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

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

立即咨询