#!/usr/bin/env bash # # hermes-setup.sh — 自建 "Portal" 一键配置脚本 # # 把 Hermes Agent 的主模型 + 所有 auxiliary 任务指向你的 LiteLLM 网关, # 并用免费/本地后端填掉工具层。等价于 `hermes setup --portal`,但走的是 # 你自己的 LiteLLM,而不是 Nous 的订阅。 # # 用法: # 交互式: ./hermes-setup.sh # 非交互式: LITELLM_BASE_URL=... LITELLM_API_KEY=... ./hermes-setup.sh --yes # 查看帮助: ./hermes-setup.sh --help # # 所有输入都可以用同名环境变量预先提供(见下方 CONFIG 区)。 # 重复运行是安全的:hermes config set 会覆盖,不会追加。 set -euo pipefail # ------------------------------------------------------------------ 输出helpers c_reset=$'\033[0m'; c_bold=$'\033[1m'; c_dim=$'\033[2m' c_green=$'\033[32m'; c_yellow=$'\033[33m'; c_red=$'\033[31m'; c_cyan=$'\033[36m' info() { printf '%s==>%s %s\n' "$c_cyan" "$c_reset" "$*"; } ok() { printf '%s ✓%s %s\n' "$c_green" "$c_reset" "$*"; } warn() { printf '%s !%s %s\n' "$c_yellow" "$c_reset" "$*" >&2; } die() { printf '%s✗ %s%s\n' "$c_red" "$*" "$c_reset" >&2; exit 1; } # ------------------------------------------------------------------ 参数 ASSUME_YES=0 for arg in "$@"; do case "$arg" in -y|--yes) ASSUME_YES=1 ;; -h|--help) awk 'NR>1 && /^#/ {sub(/^# ?/,""); print; next} NR>1 {exit}' "$0" exit 0 ;; *) die "未知参数: $arg (用 --help 查看用法)" ;; esac done # ------------------------------------------------------------------ 前置检查 command -v hermes >/dev/null 2>&1 || die \ "找不到 hermes 命令。先安装 Hermes Agent 再运行本脚本。" # ------------------------------------------------------------------ 交互读取 # 用法: ask VARNAME "提示语" "默认值" [secret] ask() { local var="$1" prompt="$2" default="${3:-}" secret="${4:-}" local current="${!var:-}" # 已经通过环境变量提供 → 直接用,不追问 if [ -n "$current" ]; then printf -v "$var" '%s' "$current"; return; fi if [ "$ASSUME_YES" = 1 ]; then printf -v "$var" '%s' "$default"; return; fi local shown="$prompt" [ -n "$default" ] && shown="$shown ${c_dim}[$default]${c_reset}" local input if [ "$secret" = secret ]; then read -r -s -p "$shown: " input; echo else read -r -p "$shown: " input fi printf -v "$var" '%s' "${input:-$default}" } echo info "${c_bold}Hermes → LiteLLM 一键配置${c_reset}" echo # ---- CONFIG:LiteLLM 网关 ---- ask LITELLM_BASE_URL "LiteLLM 地址 (含 /v1)" "https://llm.artifacthub.net/v1" ask LITELLM_API_KEY "LiteLLM virtual key" "" secret [ -n "$LITELLM_BASE_URL" ] || die "LiteLLM 地址不能为空" [ -n "$LITELLM_API_KEY" ] || die "LiteLLM key 不能为空" # ---- CONFIG:模型别名(必须是你在 LiteLLM 里定义好的别名)---- ask MAIN_MODEL "主模型别名" "claude-opus-4-8" ask AUX_VISION_MODEL "auxiliary 视觉模型 (需多模态)" "gemini-2.5-flash" ask AUX_TEXT_MODEL "auxiliary 文本模型 (提取/压缩)" "deepseek-v4-flash" # ---- CONFIG:可选的工具 key(留空则跳过)---- ask WEB_SEARCH_BACKEND "网页搜索后端 (tavily/firecrawl/exa/parallel,留空跳过)" "" ask WEB_SEARCH_API_KEY " └ 上面后端对应的 API key" "" secret ask FAL_KEY "图像生成 FAL_KEY (留空跳过)" "" secret ask GROQ_API_KEY "STT 用 Groq 的 key (留空则用本地 whisper)" "" secret # ------------------------------------------------------------------ 应用配置 # 包装:回显 + 执行。secret 变量以 ${VAR} 形式引用,不落明文到 config.yaml。 set_cfg() { info "config set $1 = $2"; hermes config set "$1" "$2" >/dev/null; } set_secret() { hermes config set "$1" "$2" >/dev/null; } # 由 hermes 分流到 .env echo info "写入 LiteLLM 凭据..." set_secret LITELLM_API_KEY "$LITELLM_API_KEY" ok "key 已写入 ~/.hermes/.env (LITELLM_API_KEY)" echo info "配置主模型 (走 LiteLLM)..." set_cfg model.provider custom set_cfg model.base_url "$LITELLM_BASE_URL" hermes config set model.api_key '${LITELLM_API_KEY}' >/dev/null set_cfg model.default "$MAIN_MODEL" echo info "配置 auxiliary 任务 (都走 LiteLLM,用便宜快的模型)..." for task in vision web_extract compression; do case "$task" in vision) m="$AUX_VISION_MODEL" ;; # 视觉任务必须多模态 *) m="$AUX_TEXT_MODEL" ;; # 提取/压缩:纯文本 + 长上下文即可 esac set_cfg "auxiliary.$task.base_url" "$LITELLM_BASE_URL" hermes config set "auxiliary.$task.api_key" '${LITELLM_API_KEY}' >/dev/null set_cfg "auxiliary.$task.model" "$m" done # web_extract 处理长网页,给足超时 set_cfg auxiliary.web_extract.timeout 360 # 压缩阈值:上下文用到 50% 就触发 set_cfg compression.enabled true set_cfg compression.threshold 0.5 echo info "配置工具层 (免费/本地优先)..." # TTS:Edge,免费无 key,322 音色 set_cfg tts.provider edge # STT:有 Groq key 用 Groq,否则本地 faster-whisper if [ -n "$GROQ_API_KEY" ]; then set_secret GROQ_API_KEY "$GROQ_API_KEY" set_cfg stt.provider groq set_cfg stt.groq.model whisper-large-v3-turbo else set_cfg stt.provider local warn "STT 用本地 whisper,需要:pip install faster-whisper" fi # 浏览器:不设 cloud_provider,Hermes 默认走本地(agent-browser / 你的 Chrome CDP) set_cfg browser.auto_local_for_private_urls true ok "浏览器自动化:本地模式 (需要时用 /browser connect 连你的 Chrome)" echo info "配置可选的联网/图像工具..." # 网页搜索:唯一绕不开云服务的能力(需要搜索索引) if [ -n "$WEB_SEARCH_BACKEND" ] && [ -n "$WEB_SEARCH_API_KEY" ]; then case "$WEB_SEARCH_BACKEND" in tavily) set_secret TAVILY_API_KEY "$WEB_SEARCH_API_KEY" ;; firecrawl) set_secret FIRECRAWL_API_KEY "$WEB_SEARCH_API_KEY" ;; exa) set_secret EXA_API_KEY "$WEB_SEARCH_API_KEY" ;; parallel) set_secret PARALLEL_API_KEY "$WEB_SEARCH_API_KEY" ;; *) die "不支持的搜索后端: $WEB_SEARCH_BACKEND" ;; esac set_cfg web.backend "$WEB_SEARCH_BACKEND" ok "网页搜索:$WEB_SEARCH_BACKEND" else warn "跳过网页搜索(未提供后端/key)。之后可运行:hermes config set web.backend tavily" fi # 图像生成:可选 if [ -n "$FAL_KEY" ]; then set_secret FAL_KEY "$FAL_KEY" set_cfg image_gen.provider fal ok "图像生成:FAL" else warn "跳过图像生成(未提供 FAL_KEY)。" fi # ------------------------------------------------------------------ 收尾 echo ok "${c_bold}配置完成${c_reset}" echo cat < 会话内临时切:/model EOF