在 SER8 上新建一个独立的 Hermes 实例(容器名 hermesII),对接国内飞书,主模型用 Gemini,配额用完时自动切换到本地 Ollama 兜底。这份教程是把实际部署中踩过的所有坑(反复重启、配置报错、fallback不生效、响应慢等)全部填平后的最终版本,照着做可以少走弯路。
架构总览
飞书(国内) ──WebSocket──> hermesII 容器(独立实例)
│
├─ 主模型: Gemini (远程API)
└─ Fallback: Ollama mistral-nemo:12b (本地,127.0.0.1:11434)
与你原有的 Lark(国际版)Hermes 实例完全隔离,容器名、目录、配置互不干扰。
network_mode: host,方便直接访问宿主机上的 Ollama 端口,不用处理容器间网络。
第一步:飞书开放平台创建自建应用
打开 https://open.feishu.cn/ ,登录后进入"开发者后台"→"创建企业自建应用"
应用名随意(比如
Hermes-Gemini),创建后进入应用详情页在"凭证与基础信息"里记下 App ID 和 App Secret
左侧菜单"应用能力"→ 开启"机器人"能力
左侧"权限管理"→ 添加以下权限:
im:messageim:message.p2p_msgim:chat(如需群聊)
在“事件与回调”中订阅方式选长连接(WebSocket),不用 Webhook——不需要公网 IP,不用做端口转发
在“事件与回调”中添加事件选“接收消息v2.0”
去"版本管理与发布"发布新版本。
第二步:拿 Gemini API Key
打开 https://aistudio.google.com/apikey ,登录 Google 账号,创建一个 API Key,记下来。
重要坑点:Google 从 2026年6月19日起开始拒绝旧版"Standard"类型的 Google Cloud API Key 访问 Gemini API,这类旧 key 将在 2026年9月完全失效,报错是
HTTP 401 UNAUTHENTICATED。创建 Key 时确认走的是 https://aistudio.google.com/api-keys 这个页面生成的标准 Gemini API Key,如果已经有旧 key 遇到 401 报错,去这个页面检查类型并重新生成一个。
建议给对应的 Google Cloud 项目开启计费,免费额度(15 RPM / 1500 RPD)在 Agent 场景下(一次对话可能触发多次模型调用)容易很快打满,开计费后自动升到 Tier 1(150-300 RPM,几乎无日限),按量付费,平时用量小基本花不了多少钱。
第三步:SER8 上建独立目录
mkdir -p /vol3/1000/docker/hermesII/data
cd /vol3/1000/docker/hermesII
第四步:写 .env
cat > .env << 'EOF'
# 飞书应用凭证
FEISHU_APP_ID=cli_你的appid
FEISHU_APP_SECRET=你的appsecret
# Gemini
GOOGLE_API_KEY=你的gemini_key
# 不启动Dashboard面板,只跑网关,省资源
HERMES_DASHBOARD=0
# 个人自用场景,放开允许名单(默认拒绝所有陌生人,包括你自己第一次发消息)
GATEWAY_ALLOW_ALL_USERS=true
EOF
chmod 600 .env
如果不放心
GATEWAY_ALLOW_ALL_USERS=true这么开放,可以不加,改走 config.yaml 里的 pairing 配对流程,第一次跟机器人说话时会给配对提示。
第五步:写 config.yaml
mkdir -p data
cat > data/config.yaml << 'EOF'
_config_version: 12
model:
default: gemini-3.5-flash
provider: gemini
base_url: https://generativelanguage.googleapis.com/v1beta
fallback_providers:
- provider: custom
model: mistral-nemo:12b
base_url: http://127.0.0.1:11434/v1
api_key: no-key-required
context_length: 65536
gateway:
enabled: true
platforms:
feishu:
enabled: true
extra:
connection_mode: "websocket"
dm_policy: "open"
group_policy: "allowlist"
allow_bots: "mentions"
EOF
几个必须注意的点(都是踩过的坑,原因见后面"坑点合集"):
_config_version: 12必须加,不加会报"配置过旧无法自动迁移"警告(实测容器首次启动后会自动把它迁移到更新的版本号,属于正常现象,不用管)。fallback 的
provider必须写custom,不能写ollama——Hermes 没有单独的ollamaprovider,本地 Ollama 走 OpenAI 兼容接口要用通用的custom。fallback 的
model用mistral-nemo:12b,不要用 qwen2.5:14b——qwen2.5:14b 原生上下文只有 32,768,低于 Hermes 要求的最低 64,000,启动时会直接报错拒绝。
第六步:写 docker-compose.yml
services:
hermesII:
image: nousresearch/hermes-agent:latest # 建议后续锁定具体版本号而非latest
container_name: hermesII
restart: unless-stopped
network_mode: host
command: ["gateway", "run"] # 必须加,否则会陷入反复重启死循环
env_file:
- .env
environment:
- HERMES_DASHBOARD=0
volumes:
- ./data:/opt/data
mem_limit: 1.5g # 不要设太低,512m会被OOM杀掉导致反复重启
给数据目录放开权限(fnOS 下容器内是 uid 10000):
chown -R 10000:10000 /vol3/1000/docker/hermesII/data
第七步:给现有 Ollama 服务加两行环境变量
编辑你已有的 Ollama docker-compose.yml(和 open-webui 在同一份里),加 OLLAMA_CONTEXT_LENGTH 和 OLLAMA_KEEP_ALIVE:
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
restart: always
ports:
- "11434:11434"
volumes:
- /vol3/1000/docker/ollama/models:/root/.ollama
devices:
- /dev/dri
environment:
- OLLAMA_VULKAN=1
- OLLAMA_IGPU_ENABLE=1
- OLLAMA_CONTEXT_LENGTH=65536 # 新增,满足Hermes最低64K要求
- OLLAMA_KEEP_ALIVE=5m # 新增,闲置5分钟自动卸载释放共享内存
group_add:
- "44"
- "105"
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: always
ports:
- "8081:8080"
volumes:
- /vol3/1000/docker/ollama/webui:/app/backend/data
environment:
- OLLAMA_BASE_URL=http://ollama:11434
depends_on:
- ollama
docker compose up -d --force-recreate ollama
只重建 ollama 这一个服务,open-webui 不受影响。
第八步:拉取 mistral-nemo:12b
docker exec -it ollama ollama pull mistral-nemo:12b
约 7GB,视网速需要几分钟。
第九步:启动 Hermes 并验证
cd /vol3/1000/docker/hermesII
docker compose up -d
docker compose logs -f
正常应该看到,没有反复重启、没有 Goodbye 退出:
→ gateway is now running under s6 supervision (auto-restart on crash, ...)
┌─────────────────────────────────────────────────────────┐
│ ⚕ Hermes Gateway Starting... │
└─────────────────────────────────────────────────────────┘
[Lark] [...] connected to wss://msg-frontier.feishu.cn/ws/v2?...
再确认 fallback 配置对不对:
docker exec -it hermesII hermes fallback list
应该显示:
Primary: gemini-3.5-flash (via gemini)
Fallback chain (1 entry):
1. mistral-nemo:12b (via custom) [http://127.0.0.1:11434/v1]
第十步:飞书里测试对话
搜索你创建的机器人名字,发消息测试。群聊要 @机器人,单聊直接发即可。
第十一步(可选):验证 fallback 真的能切换
临时把 .env 里 GOOGLE_API_KEY 改错(比如末尾加个字符)触发认证失败:
docker compose restart hermesII
飞书里发条消息,看日志:
docker compose logs -f hermesII
正常流程是:Gemini 报 401 → 重试 3 次都失败 → 提示 "switching to fallback provider" → 本地 mistral-nemo:12b 接管并回复。
测完务必把 .env 里的 Key 改回正确值。
要让新的 .env 生效,得让容器重新创建,而不是单纯重启:
cd /vol3/1000/docker/hermesII
docker compose up -d --force-recreate
或者更彻底一点(等效,更保险):
docker compose down
docker compose up -d
验证 Key 是不是真的进去了
推荐先直接进容器里确认环境变量的值,而不是靠猜:
docker exec -it hermesII env | grep -E "GOOGLE_API_KEY|GEMINI_API_KEY"
对照一下显示出来的值,是不是跟你 .env 文件里现在写的一致(不是那个末尾多字符的错误版本)。
重新测试
docker compose logs -f hermesII
飞书里发条消息,这次应该能看到直接走 gemini-3.5-flash 成功,不再触发 fallback 那一串 401 报错和切换日志了。
顺带记一下这个坑,以后每次改 .env 或者 docker-compose.yml 里的环境变量,记得用 docker compose up -d(会自动识别配置变化决定要不要重建)而不是 docker compose restart,后者只适合"进程卡住了想让它重新跑一遍"这种不涉及配置改动的场景。我之前几轮教程里如果写的是 restart,建议你之后统一换成 up -d,更保险。
坑点合集(实际部署中遇到的问题及根因)
排错速查表
内存监控
free -h
docker stats --no-stream
hermesII 本身(不触发 fallback 时)占用不大,平时观察整体基线是否稳定即可;fallback 真正触发、本地模型被加载的那一刻会有一次性内存峰值,属于正常现象。
评论区