mirror of
https://github.com/TencentCloud/TencentDB-Agent-Memory
synced 2026-07-10 12:34:27 +00:00
234 lines
7.8 KiB
Bash
Executable File
234 lines
7.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# sync-hermes-llm-to-tdai.sh — 读取系统 Hermes 模型配置,同步到 TDAI Gateway
|
||
#
|
||
# 前置条件:
|
||
# 1. memory-tencentdb-ctl.sh 存在
|
||
# 2. ~/.hermes/config.yaml 存在且包含模型配置
|
||
#
|
||
# 用法:
|
||
# bash sync-hermes-llm-to-tdai.sh [--restart] [--dry-run] [--hermes]
|
||
#
|
||
# 参数语义:
|
||
# --restart 仅当 CTL 脚本的 `status` 显示 Gateway 当前为 RUNNING 时,
|
||
# 才透传给 `memory-tencentdb-ctl.sh config llm` 执行重启;
|
||
# 若当前未运行,则本脚本只同步配置,不拉起新进程。
|
||
# --hermes 以 hermes 集成模式调用 CTL(影响 status 判定与配置落点)
|
||
# --standalone 以 standalone 模式调用 CTL
|
||
# --dry-run 透传给 CTL,预演写配置但不真正落盘
|
||
#
|
||
# 可选环境变量:
|
||
# HERMES_HOME hermes 主目录(默认 ~/.hermes)
|
||
# MEMORY_TENCENTDB_ROOT TDAI 统一根目录(默认 ~/.memory-tencentdb)
|
||
#
|
||
|
||
set -euo pipefail
|
||
|
||
SCRIPT_NAME="sync-hermes-llm-to-tdai"
|
||
USER_HOME="${HOME:-$(eval echo "~$(whoami)")}"
|
||
|
||
# ============================================================
|
||
# 路径
|
||
# ============================================================
|
||
|
||
HERMES_HOME="${HERMES_HOME:-$USER_HOME/.hermes}"
|
||
HERMES_CONFIG="$HERMES_HOME/config.yaml"
|
||
|
||
MEMORY_TENCENTDB_ROOT="${MEMORY_TENCENTDB_ROOT:-$USER_HOME/.memory-tencentdb}"
|
||
TDAI_INSTALL_DIR="${TDAI_INSTALL_DIR:-$MEMORY_TENCENTDB_ROOT/tdai-memory-openclaw-plugin}"
|
||
|
||
CTL_SCRIPT="$TDAI_INSTALL_DIR/scripts/memory-tencentdb-ctl.sh"
|
||
|
||
# ============================================================
|
||
# 通用 helpers
|
||
# ============================================================
|
||
|
||
log() { printf '[%s] %s\n' "$SCRIPT_NAME" "$*"; }
|
||
warn() { printf '[%s:warn] %s\n' "$SCRIPT_NAME" "$*" >&2; }
|
||
die() { printf '[%s:error] %s\n' "$SCRIPT_NAME" "$*" >&2; exit "${2:-1}"; }
|
||
|
||
should_restart_gateway() {
|
||
local ctl_mode_args=()
|
||
case "${SYNC_MODE:-standalone}" in
|
||
hermes) ctl_mode_args+=("--hermes") ;;
|
||
*) ctl_mode_args+=("--standalone") ;;
|
||
esac
|
||
|
||
local status_out
|
||
status_out="$(bash "$CTL_SCRIPT" "${ctl_mode_args[@]}" status 2>&1 || true)"
|
||
|
||
if printf '%s\n' "$status_out" | grep -Eq 'state[[:space:]]*:[[:space:]]*RUNNING'; then
|
||
log "通过 ctl status 检测到 Gateway 正在运行,允许执行 --restart"
|
||
return 0
|
||
fi
|
||
|
||
warn "ctl status 未显示 Gateway 处于 RUNNING;本次仅同步配置,不执行 --restart,也不会拉起新进程。"
|
||
return 1
|
||
}
|
||
|
||
# ============================================================
|
||
# Step 1: 检查 memory-tencentdb-ctl.sh 是否存在
|
||
# ============================================================
|
||
|
||
if [[ ! -f "$CTL_SCRIPT" ]]; then
|
||
# 也尝试旧路径
|
||
_LEGACY_CTL="$USER_HOME/tdai-memory-openclaw-plugin/scripts/memory-tencentdb-ctl.sh"
|
||
if [[ -f "$_LEGACY_CTL" ]]; then
|
||
CTL_SCRIPT="$_LEGACY_CTL"
|
||
warn "使用旧路径: $CTL_SCRIPT"
|
||
else
|
||
die "memory-tencentdb-ctl.sh 不存在,TDAI 未安装。退出。" 1
|
||
fi
|
||
fi
|
||
|
||
log "找到 ctl 脚本: $CTL_SCRIPT"
|
||
|
||
# ============================================================
|
||
# Step 2: 检查 hermes config.yaml
|
||
# ============================================================
|
||
|
||
if [[ ! -f "$HERMES_CONFIG" ]]; then
|
||
die "Hermes 配置文件不存在: $HERMES_CONFIG" 1
|
||
fi
|
||
|
||
log "读取 Hermes 配置: $HERMES_CONFIG"
|
||
|
||
# ============================================================
|
||
# Step 3: 从 hermes config.yaml 提取模型配置
|
||
# ============================================================
|
||
|
||
need_cmd() {
|
||
command -v "$1" >/dev/null 2>&1 || die "必需命令未找到: $1" 127
|
||
}
|
||
|
||
need_cmd python3
|
||
|
||
# 使用 python3 安全解析 YAML,提取 model 段的 default / base_url / api_key
|
||
read_hermes_model_config() {
|
||
python3 - "$HERMES_CONFIG" <<'PYEOF'
|
||
import sys, json
|
||
|
||
config_path = sys.argv[1]
|
||
|
||
# 尝试使用 PyYAML;如果不存在则手动逐行解析
|
||
try:
|
||
import yaml
|
||
with open(config_path, "r", encoding="utf-8") as f:
|
||
cfg = yaml.safe_load(f) or {}
|
||
model_section = cfg.get("model", {})
|
||
result = {
|
||
"model": model_section.get("default", ""),
|
||
"base_url": model_section.get("base_url", ""),
|
||
"api_key": model_section.get("api_key", ""),
|
||
}
|
||
print(json.dumps(result))
|
||
except ImportError:
|
||
# PyYAML 不可用,用简单逐行解析提取 model 段
|
||
import re
|
||
with open(config_path, "r", encoding="utf-8") as f:
|
||
lines = f.readlines()
|
||
|
||
in_model = False
|
||
result = {"model": "", "base_url": "", "api_key": ""}
|
||
for line in lines:
|
||
stripped = line.rstrip("\n")
|
||
# 检测 model: 顶层段
|
||
if re.match(r"^model:\s*$", stripped) or re.match(r"^model:\s*#", stripped):
|
||
in_model = True
|
||
continue
|
||
# 进入新的顶层段则退出
|
||
if in_model and re.match(r"^[A-Za-z_]", stripped):
|
||
break
|
||
if in_model:
|
||
m = re.match(r"^\s+default:\s*(.+)", stripped)
|
||
if m:
|
||
result["model"] = m.group(1).strip().strip("'\"")
|
||
m = re.match(r"^\s+base_url:\s*(.+)", stripped)
|
||
if m:
|
||
result["base_url"] = m.group(1).strip().strip("'\"")
|
||
m = re.match(r"^\s+api_key:\s*(.+)", stripped)
|
||
if m:
|
||
result["api_key"] = m.group(1).strip().strip("'\"")
|
||
print(json.dumps(result))
|
||
PYEOF
|
||
}
|
||
|
||
MODEL_JSON="$(read_hermes_model_config)"
|
||
|
||
HERMES_MODEL="$(printf '%s' "$MODEL_JSON" | python3 -c 'import json,sys; print(json.load(sys.stdin)["model"])')"
|
||
HERMES_BASE_URL="$(printf '%s' "$MODEL_JSON" | python3 -c 'import json,sys; print(json.load(sys.stdin)["base_url"])')"
|
||
HERMES_API_KEY="$(printf '%s' "$MODEL_JSON" | python3 -c 'import json,sys; print(json.load(sys.stdin)["api_key"])')"
|
||
|
||
# 校验必需字段
|
||
if [[ -z "$HERMES_MODEL" ]]; then
|
||
die "Hermes 配置中 model.default 为空" 1
|
||
fi
|
||
if [[ -z "$HERMES_BASE_URL" ]]; then
|
||
die "Hermes 配置中 model.base_url 为空" 1
|
||
fi
|
||
if [[ -z "$HERMES_API_KEY" ]]; then
|
||
die "Hermes 配置中 model.api_key 为空" 1
|
||
fi
|
||
|
||
log "Hermes 模型配置:"
|
||
log " model = $HERMES_MODEL"
|
||
log " base_url = $HERMES_BASE_URL"
|
||
log " api_key = <${#HERMES_API_KEY} chars>"
|
||
|
||
# ============================================================
|
||
# Step 4: 透传参数(--restart / --dry-run / --hermes)
|
||
# ============================================================
|
||
|
||
CTL_EXTRA_ARGS=()
|
||
SYNC_MODE="standalone"
|
||
REQUEST_RESTART=0
|
||
for arg in "$@"; do
|
||
case "$arg" in
|
||
--restart)
|
||
REQUEST_RESTART=1
|
||
;;
|
||
--hermes)
|
||
SYNC_MODE="hermes"
|
||
CTL_EXTRA_ARGS+=("$arg")
|
||
;;
|
||
--standalone)
|
||
SYNC_MODE="standalone"
|
||
CTL_EXTRA_ARGS+=("$arg")
|
||
;;
|
||
--dry-run)
|
||
CTL_EXTRA_ARGS+=("$arg")
|
||
;;
|
||
*)
|
||
warn "忽略未知参数: $arg"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
if [[ $REQUEST_RESTART -eq 1 ]]; then
|
||
# 新语义:--restart 只在 Gateway 当前已运行时才生效。
|
||
# 这里复用 CTL 的 status 输出作为唯一事实来源,避免本脚本
|
||
# 自己维护另一套“端口监听/健康检查”判定逻辑。
|
||
if should_restart_gateway; then
|
||
CTL_EXTRA_ARGS+=("--restart")
|
||
else
|
||
log "已按新逻辑跳过 --restart。"
|
||
fi
|
||
fi
|
||
|
||
# ============================================================
|
||
# Step 5: 调用 memory-tencentdb-ctl.sh config llm 同步配置
|
||
# ============================================================
|
||
|
||
log "调用 memory-tencentdb-ctl.sh config llm 同步配置到 TDAI ..."
|
||
|
||
bash "$CTL_SCRIPT" "${CTL_EXTRA_ARGS[@]+"${CTL_EXTRA_ARGS[@]}"}" \
|
||
config llm \
|
||
--api-key "$HERMES_API_KEY" \
|
||
--base-url "$HERMES_BASE_URL" \
|
||
--model "$HERMES_MODEL"
|
||
|
||
log "同步完成。"
|
||
log ""
|
||
log "如需立即重启 Gateway 使配置生效,请重新运行并追加 --restart:"
|
||
log " bash $0 --restart"
|