跳转到内容

06. 自定义发行版构建系统

学习目标

  • 理解 Goose 自定义发行版的架构层次与可定制点
  • 掌握通过配置文件(无代码改动)创建发行版的方法
  • 学习品牌定制(图标、名称、颜色)的具体操作
  • 了解 Justfile + cargo 构建流程
  • 分析自定义发行版 vs 单一产品的设计取舍

项目实践

架构总览

Goose 的自定义发行版(Custom Distribution)能力允许组织基于同一套代码库,构建面向不同场景的预配置变体。整个架构分为三层:

每一层都是定制化的入口:

定制目标修改位置复杂度
预配置模型/提供商config.yamlinit-config.yaml、环境变量
添加自定义提供商crates/goose/src/providers/declarative/
捆绑自定义 MCP 扩展config.yamlbuilt-in-extensions.json
修改系统提示词crates/goose/src/prompts/
定制桌面品牌ui/desktop/(图标、名称、颜色)
构建全新 UI对接 goose-server REST API
创建引导式工作流Recipe YAML 定义
复杂多步骤工作流带子配方的 Recipe

纯配置发行版

最简单的发行版无需任何代码改动,仅通过配置文件实现。

场景:预配置本地模型

创建一个开箱即用的本地模型发行版,用户无需输入 API Key:

# init-config.yaml — 首次运行时若无配置则自动应用
GOOSE_PROVIDER: ollama
GOOSE_MODEL: qwen3-coder:latest

启动脚本中设置环境变量默认值:

Terminal window
export GOOSE_PROVIDER=ollama
export GOOSE_MODEL=qwen3-coder:latest
export OLLAMA_HOST=http://localhost:11434

场景:企业托管 API Key

企业内部发行版预配 API Key,锁定提供商选择:

config.yaml
GOOSE_PROVIDER: anthropic
GOOSE_MODEL: claude-sonnet-4-20250514

通过系统 Keyring 注入密钥:

Terminal window
# 密钥存储在系统 keyring 或 ~/.config/goose/secrets.yaml
goose 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
}

支持的引擎包括 openaianthropicollama。对于有独特 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 为不同职业角色创建专用发行版:

legal-assistant.yaml
version: 1.0.0
title: Legal Research Assistant
description: 面向法律专业人士的 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

品牌定制

桌面端品牌化

替换视觉资产:

  1. 替换 ui/desktop/src/images/ 下的 icon.pngicon.icoicon.icns
  2. 修改 ui/desktop/forge.config.ts 中的元数据
  3. 更新系统提示词 crates/goose/src/prompts/system.md 中的 AI 助手名称
  4. 自定义 ui/desktop/src/ 中的 React 组件(配色、文字标签、功能可见性)
  5. 统一打包与更新器名称:
    • 设置 GITHUB_OWNERGITHUB_REPO 用于更新器仓库查找
    • 设置 GOOSE_BUNDLE_NAME 用于打包名称和调试脚本

品牌一致性检查清单

在发布前确认:

  • 应用元数据(forge.config.tspackage.jsonindex.html)使用发行版名称
  • 发布产物名称与更新器查找名称一致
  • Linux .desktop 模板指向与打包生成的可执行文件同名

构建流程

Justfile 与 cargo 构建

Goose 使用 Justfile 封装常用的构建与发布任务:

# 核心构建命令
cargo build # Debug 构建
cargo build --release # Release 构建
just release-binary # Release 构建 + OpenAPI 生成
# 代码质量
cargo fmt
cargo clippy --all-targets -- -D warnings
# UI 相关
just generate-openapi # 服务器变更后生成 OpenAPI
just run-ui # 启动桌面端
# 测试
cargo test # 全部测试
cargo test -p goose # 指定 crate 测试

构建流程概览:

环境变量控制

构建时通过环境变量控制发行版行为:

Terminal window
# 禁用遥测
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.tsexecutableName.desktop 模板中的 Exec 一致
自动更新器找不到正确的 Release 资产统一设置 GOOSE_BUNDLE_NAME,确保与 Release 资产名匹配
私有 Fork 错过安全更新定期同步上游,订阅 Release 公告,将通用改动向上游提交
遥测数据泄露到外部企业发行版设置 GOOSE_DISABLE_TELEMETRY=1 或指向自有 PostHog 实例
声明式提供商的 base_url 格式不正确确保 URL 以 /v1/chat/completions 结尾(OpenAI 兼容端点)

设计取舍

自定义发行版 vs 单一产品

Goose 选择支持自定义发行版而非强制单一产品,原因有三:

  1. 企业部署需求差异巨大:金融、法律、医疗等行业对 AI 助手的提供商、工具、合规要求截然不同,无法通过配置项穷尽所有组合。

  2. 降低 Fork 维护成本:Apache 2.0 许可证允许 Fork,但深度定制的 Fork 会随着上游迭代变得越来越难以维护。Goose 通过清晰的定制点(配置、扩展、Recipe)让 Fork 只需要修改少量文件。

  3. 生态扩展的自然路径:组织可以从纯配置发行版起步(零代码改动),逐步过渡到扩展定制,最终实现深度定制。这为不同成熟度的用户提供了渐进式路径。

配置优先 vs 代码定制

Goose 优先使用配置文件实现定制化(config.yamlinit-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 — 构建任务封装