g4f (gpt4free) 多平台发布构建流水线全解:从版本 Tag 到 PyPI、Docker 与原生启动器
本文围绕 gpt4free 仓库的发布构建工作流(docs/build-workflow.md 所描述的完整打包流程)展开,讲清楚“推一个版本 Tag 之后,一条 GitHub Actions 流水线如何并行产出 PyPI 包、多平台可执行启动器与 Docker 镜像,并自动创建 GitHub Release”。读完本文,你将掌握 g4f 各发布产物的触发方式、版本判定逻辑、各构建 Job 的源码级实现,以及本地复现打包时的关键脚本与参数。
一、工作流总览:一条流水线,七类产物
docs/build-workflow.md 指出,仓库通过 .github/workflows/build-packages.yml 工作流在推送版本 Tag 时自动构建多种包格式。原文档列出的产物清单如下:
- PyPI 包 —— Python wheel 与源码分发包(sdist)
- Windows 可执行文件 —— 独立 .exe(文档描述为 Nuitka 时代产物,见第四节)
- Linux 可执行文件 —— Linux 独立二进制
- macOS 可执行文件 —— macOS 独立二进制(x64 与 ARM64)
- Debian 包 —— 面向 Ubuntu/Debian 的 .deb(amd64、arm64、armhf)
- WinGet 包 —— Windows Package Manager 清单
- Docker 镜像 —— 多架构容器镜像
对照当前仓库中 build-packages.yml 的实际内容,可以看到这条流水线由 4 个核心 Job 串联:
| Job | 作用 | 触发条件 |
|---|---|---|
prepare |
解析版本号并判定是否为正式发布(is_release) |
每次运行必执行 |
build-pypi |
构建 wheel + sdist 并上传工件 | 每次运行 |
build-g4f-go |
交叉编译 Go 启动器并打包各平台 zip | 每次运行 |
build-docker |
构建并推送 Docker 镜像(armv7 / slim / 完整版) | 仅正式发布(is_release == 'true') |
create-release |
汇总所有工件,创建 GitHub Release | 仅正式发布 |
触发方式在 build-packages.yml 中定义得很直接:监听所有 Tag(tags: ['*']),同时支持 workflow_dispatch 手动触发,并允许在手动触发时通过 version 输入框指定版本号(留空则自动推导)。
触发构建的标准操作
与原文档一致,最典型的触发方式是推送版本 Tag:
git tag v1.2.3
git push origin v1.2.3
手动触发方式(对应 workflow_dispatch):
- 打开仓库的 "Actions" 页签;
- 选择 "Build All Packages" 工作流;
- 点击 "Run workflow";
- 可选地填写一个版本号。
构建成功后,原文档给出的产物去向与仓库实际配置完全吻合:
- GitHub Releases:所有可执行包与 PyPI 包作为 Release 资源;
- PyPI:
pip install g4f; - Docker Hub:
docker pull hlohaus789/g4f:latest(仓库中的镜像名即hlohaus789/g4f,见 build-packages.yml 的 metadata 配置); - WinGet:
winget install g4f(清单审批通过后生效;Release 正文模板中给出的安装命令是winget install gpt4free,见 build-packages.yml)。
二、版本如何被确定:prepare Job 的三级版本来源
原文档"Version Handling"一节提到工作流支持三种版本来源:Git Tag(发布首选)、环境变量 G4F_VERSION、手动输入。源码中这一逻辑集中在 prepare Job(build-packages.yml):
if [[ "${GIT_REF}" =~ ^refs/tags/ ]]; then
G4F_VERSION="${REF_NAME}" # Tag 名即版本,is_release=true
IS_RELEASE="true"
elif [[ -n "${{ inputs.version }}" ]]; then
G4F_VERSION="${{ inputs.version }}" # 手动输入,is_release=false
IS_RELEASE="false"
else
G4F_VERSION="0.0.0-dev" # 未指定时的开发版兜底
IS_RELEASE="false"
fi
要点有三:
- 只有 Tag 触发才算正式发布:
is_release输出被下游build-docker与create-release用作门禁(if: needs.prepare.outputs.is_release == 'true')。也就是说,手动触发且不带 Tag 的构建只产出工件与 Release 草稿之外的包,不会推 Docker 镜像、也不会创建 Release。 - 版本号向下传递的方式是
needs.prepare.outputs.version,各构建 Job 通过env: G4F_VERSION=...注入(例如 build-pypi 的构建步骤)。 setup.py直接读取该环境变量:setup.py 中version=os.environ.get("G4F_VERSION"),包名固定为g4f。
原文档同时要求版本遵循 PEP 440 规范以保证 PyPI 兼容性。从当前仓库的 Job 名与产物命名(g4f-go-${VERSION}-...、hlohaus789/g4f:${VERSION}-slim)来看,版本号会直接拼接进文件名与镜像 Tag,因此含非法字符的版本号会在发布阶段产生难以检索的资源名。
三、PyPI 包构建与发布:build-pypi Job 和独立发布工作流
build-pypi Job(build-packages.yml)的流程:
actions/setup-python安装 Python3.x;pip install --upgrade pip后安装build与twine;- 在
G4F_VERSION环境变量下执行python -m build,产出 wheel 与 sdist 到dist/; - 用
python -m twine check dist/*校验元数据; - 分别以
pypi-package(合并)、pypi-wheel(dist/*.whl)、pypi-sdist(dist/*.tar.gz)三个工件名上传,供后续 Release Job 下载。
值得注意的细节是:build-packages.yml 末尾的 publish-pypi Job 目前是注释状态(build-packages.yml),即“推 Tag 建 Release 的这条流水线”本身不负责发 PyPI。真正把包发布到 PyPI 的是另一条独立工作流 publish-to-pypi.yml:
- 仅在
github.repository == 'xtekky/gpt4free'且 ref 以refs/tags/开头时运行(第 11 行),避免 fork 仓库误发版; - 用
pypa/build构建 wheel 与源码包,再以pypa/gh-action-pypi-publish基于 OIDC(permissions: id-token: write)发布,无需在密钥中存放 PyPI Token; - 通过
environment: pypi绑定发布环境,属于典型的“构建与发布分离”设计。
再看打包内容本身。setup.py 定义了最小运行依赖与按功能切分的 extras:
- 核心依赖(
INSTALL_REQUIRE):requests、aiohttp、brotli、pycryptodome、nest-asyncio2—— 这也是build-g4f-goJob 中 pip 预装清单(build-packages.yml); - extras 包括
all、slim、api、gui、image、search、webview、files、tray、local等,例如api只需loguru、fastapi、uvicorn、python-multipart、a2wsgi、PyYAML(setup.py); - 命令行入口(setup.py):
g4f=g4f.cli:main、g4f-mcp=g4f.mcp.server:main、g4f-tray=g4f.tray:_tray_main,即安装后同时获得 g4f 客户端、MCP 服务器与托盘程序三个命令。
四、原生可执行产物:从 Nuitka 脚本到 g4f-go 启动器
4.1 文档描述的 Nuitka 方案
原文档指出可执行文件“使用 Nuitka 构建”,并将 scripts/build-nuitka.sh 列为定制关键点。该脚本在仓库中确实存在且完整可用,其设计值得拆解:
- 默认值与架构归一化(build-nuitka.sh):
PLATFORM默认取uname -s,ARCHITECTURE默认取uname -m,VERSION取G4F_VERSION(缺省0.0.0-dev),输出目录OUTPUT_DIR缺省dist;x86_64/amd64 → x64、arm64/aarch64 → arm64、armv7l/armhf → armv7; - 平台差异化参数(build-nuitka.sh):
| 平台 | 产物名 | 关键参数 |
|---|---|---|
| Windows | g4f-windows-${VERSION}-${ARCH}.exe |
--windows-console-mode=attach --onefile |
| macOS | g4f-macos-${VERSION}-${ARCH} |
--macos-create-app-bundle --onefile |
| Linux | g4f-linux-${VERSION}-${ARCH} |
--onefile |
- 通用参数与构建命令(build-nuitka.sh):
--standalone --remove-output --no-pyi-file --include-package=g4f等,最终以python -m nuitka ... g4f_cli.py打包,并存在可选的 Windows 图标参数(projects/windows/icon.ico); - 构建后自检:脚本最后检查产物文件是否存在,失败则以非零码退出(build-nuitka.sh)。
入口文件 g4f_cli.py 极薄:先调用 g4f.debug.enable_logging() 打开日志,再执行 g4f.cli.main(),注释明确说明它是“Nuitka 可执行构建的入口点”。
配套还有 scripts/validate-nuitka.sh 验证脚本,包含 5 项检查:g4f_cli.py --help 可运行、python -m nuitka --version 可用、scripts/build-nuitka.sh 可执行、工作流中包含 Nuitka 字样、工作流中存在 matrix: 架构矩阵。需要说明的是,从当前工作流文本看,可执行文件 Job 已改为 g4f-go 方案,后两项断言与现状存在出入,可以推断该验证脚本属于 Nuitka 时代的产物,本地使用前应先对齐当前流水线。
4.2 当前流水线实际方案:g4f-go 交叉编译
build-packages.yml 第 86 行注释写明 "Executables (built with g4f-go, no Nuitka)":可执行文件 Job 现在改为在 ubuntu-latest 上交叉编译 Go 启动器,覆盖所有 OS/架构,并在各 zip 中携带内嵌的 CPython 运行时。其步骤:
- Go 1.22(
actions/setup-go,依赖g4f-go/go.mod做缓存)+ Python 3.11; - 预装 Python 侧核心依赖与
rsync zip工具; - 执行
./fetch-python.sh获取并合并各平台 Python 运行时; - 执行
./build-all.sh交叉编译并逐平台打 zip; - 上传合并工件
g4f-go与各平台单独工件(g4f-go-windows-amd64、g4f-go-linux-arm64等,均设if-no-files-found: error,缺产物即失败)。
g4f-go/build-all.sh 的默认目标矩阵为:
| OS | Arch | 产物名 |
|---|---|---|
| linux | amd64 | g4f-go |
| linux | arm64 | g4f-go |
| windows | amd64 | g4f-go.exe |
| darwin | arm64 | g4f-go |
| darwin | amd64 | g4f-go |
| android | arm64 | g4f-go |
构建命令形如 CGO_ENABLED=0 GOOS=... GOARCH=... go build -trimpath -ldflags "-s -w -X main.Version=$VERSION" ...,版本号通过 -ldflags -X 注入 main.Version,与 G4F_VERSION 一致(缺省 0.1.0)。支持按 OS 过滤,如 ./build-all.sh linux。
一个值得记录的设计差异来自 build-all.sh 的头部注释:运行时不在构建期嵌入,启动器首次运行时从 python.org / python-build-standalone 下载;./fetch-python.sh 只负责固定尺寸与 SHA 校验值(见 runtime.json),因此 Go 启动器本身可以脱离 Python 工具链独立构建。另外可以注意到,工作流中定义了 windows-386 与 freebsd-amd64 的上传模式(build-packages.yml),而 build-all.sh 的默认目标表未包含这两项,说明平台覆盖以工作流工件名清单为扩展边界,从源码结构看后续可通过 TARGETS 表扩展。
五、Docker 镜像构建:仅正式发布、三类镜像
build-docker Job(build-packages.yml)仅在 is_release == 'true' 时运行,流程为:
docker/setup-qemu-action+docker/setup-buildx-action准备多架构构建环境;docker/metadata-action生成hlohaus789/g4f的元数据 Tag;- 使用仓库 secrets
DOCKER_USERNAME/DOCKER_PASSWORD登录 Docker Hub(这解释了原文档"Troubleshooting"中“Docker push 失败需检查仓库 secrets”一条); - 依次构建并推送三类镜像,均开启
provenance: mode=max与sbom: true:
| 镜像 | Dockerfile | 平台 | Tag |
|---|---|---|---|
| armv7 版 | docker/Dockerfile-armv7 | linux/arm/v7 |
latest-armv7、${VERSION}-armv7 |
| slim 精简版 | docker/Dockerfile-slim | linux/amd64, linux/arm64 |
latest-slim、${VERSION}-slim |
| 完整版 | docker/Dockerfile | linux/amd64 |
由 metadata-action 生成(含 latest) |
三类构建均通过 build-args 注入 G4F_VERSION,与 PyPI/可执行文件共用同一版本来源,保证一次 Tag 内所有产物版本一致。
六、系统级软件包:Debian 构建脚本与 WinGet 现状
原文档将 .deb(amd64/arm64/armhf)与 WinGet 清单列为支持格式,并列出两个定制文件:scripts/build-deb.sh 与 winget/manifests/。
build-deb.sh 在当前仓库中完整可用,其打包流程:
- 以
G4F_VERSION(缺省0.0.0-dev)与ARCH(缺省amd64)为版本与架构变量,清理并重建debian/g4f目录结构(DEBIAN/、usr/bin、usr/lib/python3/dist-packages、usr/share/doc、usr/share/applications); - 生成
control文件:Section: python、Priority: optional、依赖python3 (>= 3.10), python3-pip, python3-aiohttp, python3-requests(build-deb.sh); postinst脚本负责pip3 install五个核心依赖(与setup.py的INSTALL_REQUIRE一致)并建立/usr/local/bin/g4f → /usr/bin/g4f软链;prerm负责卸载时移除软链(build-deb.sh);- 以
python3 setup.py install --root=debian/g4f --prefix=/usr --install-lib=/usr/lib/python3/dist-packages --install-scripts=/usr/bin安装文件,并将 README 压缩、LICENSE 拷为 copyright,还生成桌面.desktop条目。
关于 WinGet:当前仓库快照中未发现 winget/ 目录与 build-packages.yml 中对应的 manifest 生成 Job,可以推断清单目前独立维护在 Windows Package Manager 的生态仓库中,本仓库仅保留文档与 Release 说明(winget install gpt4free)作为用户入口。原文档提到的 winget/manifests/ 模板路径属于规划性描述,使用前建议以 Release 正文中的安装命令为准。
七、Release 资产汇总:create-release Job
正式发布时,create-release Job(build-packages.yml)完成最终汇聚:
- 下载
pypi-package与g4f-go合并工件,再用pattern: g4f-go-*+merge-multiple: true拉取各平台单独工件; - 通过
find/cp将所有文件扁平化到./release/flat/,并打印最终资产清单,便于在日志中核对“缺失工件”(对应原文档 Troubleshooting 第 3 条); - 用
softprops/action-gh-release以GITHUB_TOKEN创建 Release,prerelease: false,正文模板自动写入四个安装入口:pip install g4f==${VERSION};- 各平台
g4f-go-${VERSION}-{os}-{arch}.zip(windows-amd64、linux-amd64、linux-arm64、darwin-amd64/arm64、freebsd-amd64); winget install gpt4free;docker pull hlohaus789/g4f:${VERSION}与-slim变体。
八、构建环境与本地定制
原文档给出的本地构建要求(Python 3.10+、Nuitka、Docker、dpkg-deb)仍然适用于按脚本复现各产物。结合仓库实际,定制构建时的关键文件对照如下:
| 文件 | 职责 |
|---|---|
| g4f_cli.py | 可执行构建入口(Nuitka 方案入口) |
| setup.py | 包名、G4F_VERSION 版本注入、依赖与 console_scripts |
| scripts/build-nuitka.sh | Nuitka 各平台单文件构建脚本 |
| scripts/build-deb.sh | Debian 包构建脚本 |
| scripts/validate-nuitka.sh | Nuitka 构建系统验证(5 项检查) |
| g4f-go/build-all.sh / g4f-go/fetch-python.sh | Go 启动器交叉编译与 Python 运行时获取 |
| .github/workflows/build-packages.yml | 主工作流 |
| .github/workflows/publish-to-pypi.yml | PyPI 发布工作流 |
本地构建示例(均可在当前仓库查看后在自有环境执行):
# Nuitka 本地打包(Linux x64 默认值)
G4F_VERSION=0.0.0-dev PLATFORM=linux ARCHITECTURE=x86_64 OUTPUT_DIR=dist bash scripts/build-nuitka.sh
# Debian 包
G4F_VERSION=0.0.0-dev ARCH=amd64 bash scripts/build-deb.sh
# Go 启动器仅构建 Linux
G4F_VERSION=0.0.0-dev ./g4f-go/build-all.sh linux
九、版本规范、故障排查与安全实践
原文档的 Troubleshooting 与 Security Notes 可以结合源码逐一落地:
- 构建失败:检查 Python 版本与依赖。PyPI 侧工作流使用 Python
3.x,g4f-go 侧固定 3.11,Debian 包声明python3 (>= 3.10)依赖——从源码看 3.10+ 是各产物共同的下限; - 版本错误:确保版本符合 PEP 440。版本号会被
setup.py写进 wheel 文件名、被build-all.sh拼进 zip 名、被 Docker 用作 Tag,任何一处不合法都会产生难以定位的失败; - 缺失工件:
build-g4f-go的各平台上传步骤均设置if-no-files-found: error,缺产物会直接使 Job 失败;create-release也会打印--- Final release assets ---清单便于核对; - Docker push 失败:确认
DOCKER_USERNAME/DOCKER_PASSWORD两个 secrets 已配置(build-packages.yml),且仅在 Tag 触发的正式发布中执行推送。
安全方面,原文档提到工作流采用受信任的 Action 版本、环境隔离与密钥管理。从 build-packages.yml 可以看到具体落实:Docker 系列 Action 均锁定到具体 commit SHA(如 docker/setup-qemu-action@c7c53464...),create-release 仅申请 contents: write 最小权限,publish-to-pypi.yml 通过 OIDC id-token: write 而非明文 Token 发布 PyPI,仓库内没有硬编码凭证。
十、向构建系统贡献修改
原文档给出的贡献流程同样适用于本仓库:
- 先在本地测试:可用
scripts/validate-nuitka.sh做 Nuitka 链路自检,或直接运行scripts/build-nuitka.sh、scripts/build-deb.sh验证本地打包; - 同步更新文档:如本文对应的
docs/build-workflow.md; - 考虑向后兼容:
build-packages.yml的 Job 名称、工件名(pypi-package、g4f-go-*)被create-release依赖,重命名需同步修改下游; - 多 Python 版本测试:当前 Job 覆盖 3.x(PyPI 构建)与 3.11(g4f-go 构建)两条解释器链路,改动依赖清单(如
setup.py的INSTALL_REQUIRE)时应确认两条链路均通过。
小结:gpt4free 的发布体系是“一个 Tag、一条主工作流、四类下游渠道”。prepare Job 用三级来源确定版本并以 is_release 门禁正式发布动作;build-pypi 与独立的 publish-to-pypi.yml 分别负责构建与发布;build-g4f-go 以 Go 交叉编译替代了文档所述 Nuitka 方案(Nuitka 脚本仍保留供本地使用);build-docker 在正式发布时推送 armv7/slim/完整三类多架构镜像;create-release 汇总所有工件并生成带四个安装入口的 Release 说明。理解这套“版本 → 工件 → 渠道”的映射关系,是阅读或维护该仓库发布流程的关键。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00