06. 自定义发行版构建系统
学习目标
- 理解 Goose 自定义发行版的架构层次与可定制点
- 掌握通过配置文件(无代码改动)创建发行版的方法
- 学习品牌定制(图标、名称、颜色)的具体操作
- 了解 Justfile + cargo 构建流程
- 分析自定义发行版 vs 单一产品的设计取舍
项目实践
架构总览
Goose 的自定义发行版(Custom Distribution)能力允许组织基于同一套代码库,构建面向不同场景的预配置变体。整个架构分为三层:
每一层都是定制化的入口:
| 定制目标 | 修改位置 | 复杂度 |
|---|---|---|
| 预配置模型/提供商 | config.yaml、init-config.yaml、环境变量 | 低 |
| 添加自定义提供商 | crates/goose/src/providers/declarative/ | 低 |
| 捆绑自定义 MCP 扩展 | config.yaml、built-in-extensions.json | 中 |
| 修改系统提示词 | crates/goose/src/prompts/ | 低 |
| 定制桌面品牌 | ui/desktop/(图标、名称、颜色) | 中 |
| 构建全新 UI | 对接 goose-server REST API | 高 |
| 创建引导式工作流 | Recipe YAML 定义 | 低 |
| 复杂多步骤工作流 | 带子配方的 Recipe | 中 |
纯配置发行版
最简单的发行版无需任何代码改动,仅通过配置文件实现。
场景:预配置本地模型
创建一个开箱即用的本地模型发行版,用户无需输入 API Key:
# init-config.yaml — 首次运行时若无配置则自动应用GOOSE_PROVIDER: ollamaGOOSE_MODEL: qwen3-coder:latest启动脚本中设置环境变量默认值:
export GOOSE_PROVIDER=ollamaexport GOOSE_MODEL=qwen3-coder:latestexport OLLAMA_HOST=http://localhost:11434场景:企业托管 API Key
企业内部发行版预配 API Key,锁定提供商选择:
GOOSE_PROVIDER: anthropicGOOSE_MODEL: claude-sonnet-4-20250514通过系统 Keyring 注入密钥:
# 密钥存储在系统 keyring 或 ~/.config/goose/secrets.yamlgoose configure set-secret ANTHROPIC_API_KEY "企业密钥"声明式自定义提供商
无需编写 Rust 代码即可添加新的 AI 提供商。在 ~/.config/goose/custom_providers/ 中创建 JSON 文件:
{ "name": "my_provider", "engine": "openai", "display_name": "My Custom Provider", "description": "内部 LLM 端点", "api_key_env": "MY_PROVIDER_API_KEY", "base_url": "https://llm.internal.company.com/v1/chat/completions", "models": [ { "name": "company-llm-v1", "context_limit": 32768 } ], "supports_streaming": true, "requires_auth": true}支持的引擎包括 openai、anthropic、ollama。对于有独特 API 的提供商,可以实现 Provider trait 并注册到 factory.rs。
捆绑自定义 MCP 扩展
将内部数据源作为内置扩展捆绑到发行版中:
{ "id": "internal-data", "name": "Internal Data Lake", "description": "查询企业数据源", "enabled": true, "type": "stdio", "cmd": "python", "args": ["/path/to/internal_data_mcp.py"], "env_keys": ["INTERNAL_DATA_API_KEY"], "timeout": 300}添加到 ui/desktop/src/built-in-extensions.json 后,该扩展会出现在扩展管理 UI 中。也可以添加到 bundled-extensions.json 放入设置目录。
面向特定受众的发行版
通过 Recipe YAML 为不同职业角色创建专用发行版:
version: 1.0.0title: Legal Research Assistantdescription: 面向法律专业人士的 AI 助手
instructions: | 你是法律研究助手,帮助律师和法务助理完成: - 判例法研究 - 文档审查与摘要 - 合同分析 - 法律写作辅助
始终引用来源。不确定时明确标注。不提供实际法律建议。
extensions: - type: builtin name: developer description: 文件和文档工具 - type: stdio name: legal-database cmd: python args: ["/opt/legal-goose/legal_db_mcp.py"] description: 法律数据库搜索
activities: - "研究关于...的判例法" - "总结这份合同..." - "查找...的先例"
settings: goose_provider: anthropic goose_model: claude-sonnet-4-20250514品牌定制
桌面端品牌化
替换视觉资产:
- 替换
ui/desktop/src/images/下的icon.png、icon.ico、icon.icns - 修改
ui/desktop/forge.config.ts中的元数据 - 更新系统提示词
crates/goose/src/prompts/system.md中的 AI 助手名称 - 自定义
ui/desktop/src/中的 React 组件(配色、文字标签、功能可见性) - 统一打包与更新器名称:
- 设置
GITHUB_OWNER、GITHUB_REPO用于更新器仓库查找 - 设置
GOOSE_BUNDLE_NAME用于打包名称和调试脚本
- 设置
品牌一致性检查清单
在发布前确认:
- 应用元数据(
forge.config.ts、package.json、index.html)使用发行版名称 - 发布产物名称与更新器查找名称一致
- Linux
.desktop模板指向与打包生成的可执行文件同名
构建流程
Justfile 与 cargo 构建
Goose 使用 Justfile 封装常用的构建与发布任务:
# 核心构建命令cargo build # Debug 构建cargo build --release # Release 构建just release-binary # Release 构建 + OpenAPI 生成
# 代码质量cargo fmtcargo clippy --all-targets -- -D warnings
# UI 相关just generate-openapi # 服务器变更后生成 OpenAPIjust run-ui # 启动桌面端
# 测试cargo test # 全部测试cargo test -p goose # 指定 crate 测试构建流程概览:
环境变量控制
构建时通过环境变量控制发行版行为:
# 禁用遥测export GOOSE_DISABLE_TELEMETRY=1
# 控制打包名称export GOOSE_BUNDLE_NAME="InsightStream-goose"
# 指定 GitHub 仓库(用于自动更新)export GITHUB_OWNER="your-org"export GITHUB_REPO="your-goose-fork"ACP 与 REST API 集成
自定义 UI 可以通过两种协议与 Goose 后端集成:
REST API(goose-server):基于 HTTP 的简单集成,适合 Web 应用和轻量客户端。启动 goosed 后在 http://localhost:3000 提供 REST API,OpenAPI 规范在 ui/desktop/openapi.json。
Agent Client Protocol(ACP):基于 JSON-RPC 的双向通信协议,适合 IDE 集成和桌面应用。通过 goose acp --with-builtin developer,memory 在 stdio 上运行。
| ACP 方法 | 说明 |
|---|---|
initialize | 建立连接,交换能力 |
session/new | 创建新会话,可选附加 MCP 服务器 |
session/load | 按 ID 恢复已有会话 |
session/prompt | 发送提示,接收流式响应 |
session/cancel | 取消进行中的提示 |
Recipe 参数化发行版
Recipe 支持多种参数类型,使发行版可以在运行时定制:
| 类型 | 说明 | 用例 |
|---|---|---|
string | 自由文本输入 | 名称、路径、查询 |
number | 数值输入 | 计数、限制 |
boolean | 真/假开关 | 功能标志 |
date | 日期选择器 | 时间过滤器 |
file | 文件路径(内容导入) | 文档处理 |
select | 从选项下拉选择 | 预定义选择 |
参数通过 Jinja2 模板语法在 Recipe 中引用:{{ github_repo }}。
问题与规避
| 问题 | 规避方法 |
|---|---|
| Fork 后难以合并上游更新 | 将定制隔离在配置文件和独立扩展仓库中,避免修改核心代码 |
| 桌面端品牌修改遗漏某些 UI 组件 | 使用品牌一致性检查清单逐项确认 |
Linux 打包后 .desktop 文件指向错误的可执行名 | 确保 forge.config.ts 的 executableName 与 .desktop 模板中的 Exec 一致 |
| 自动更新器找不到正确的 Release 资产 | 统一设置 GOOSE_BUNDLE_NAME,确保与 Release 资产名匹配 |
| 私有 Fork 错过安全更新 | 定期同步上游,订阅 Release 公告,将通用改动向上游提交 |
| 遥测数据泄露到外部 | 企业发行版设置 GOOSE_DISABLE_TELEMETRY=1 或指向自有 PostHog 实例 |
| 声明式提供商的 base_url 格式不正确 | 确保 URL 以 /v1/chat/completions 结尾(OpenAI 兼容端点) |
设计取舍
自定义发行版 vs 单一产品
Goose 选择支持自定义发行版而非强制单一产品,原因有三:
-
企业部署需求差异巨大:金融、法律、医疗等行业对 AI 助手的提供商、工具、合规要求截然不同,无法通过配置项穷尽所有组合。
-
降低 Fork 维护成本:Apache 2.0 许可证允许 Fork,但深度定制的 Fork 会随着上游迭代变得越来越难以维护。Goose 通过清晰的定制点(配置、扩展、Recipe)让 Fork 只需要修改少量文件。
-
生态扩展的自然路径:组织可以从纯配置发行版起步(零代码改动),逐步过渡到扩展定制,最终实现深度定制。这为不同成熟度的用户提供了渐进式路径。
配置优先 vs 代码定制
Goose 优先使用配置文件实现定制化(config.yaml、init-config.yaml、声明式提供商 JSON、Recipe YAML),而不是要求修改 Rust 代码。这带来的好处是:发行版维护者不需要 Rust 技能;配置文件的 diff 比代码 diff 更易于审查;上游合并时冲突更少。只有当配置无法覆盖时(如新的传输协议、独特的 AI 模型 API),才需要代码层面的定制。
参考来源
CUSTOM_DISTROS.md— 自定义发行版官方指南crates/goose/src/config/base.rs— 配置管理与密钥存储crates/goose/src/config/declarative_providers.rs— 声明式提供商实现crates/goose/src/providers/factory.rs— 提供商注册crates/goose/src/recipe/mod.rs— Recipe 格式定义crates/goose/src/prompts/— 系统提示词模板ui/desktop/forge.config.ts— Electron 打包配置Justfile— 构建任务封装