From 5a889007d77a76c9004afa61fb1703744449c698 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sat, 26 Sep 2026 14:43:46 +0800 Subject: [PATCH] =?UTF-8?q?docs(kbtree):=20=E8=A1=A5=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E5=B0=81=E8=A3=85=E8=84=9A=E6=9C=AC=20+=20=E6=9C=AC?= =?UTF-8?q?=E6=9C=BA=E9=83=A8=E7=BD=B2=E4=BA=A4=E6=8E=A5=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SKILL.md 里原本只有 curl 示例,agent 用起来要自己拼 URL 与鉴权头。 补 scripts/kb_tree.sh(照 dify-ops 的做法把脚本随 skill 分发): - 子命令 tree/categories/counts/search,选项 -q/-c/-d/-i/-l - token 读取顺序:KB_TOKEN 环境变量 → config/config.json。 **故意不接受命令行传 token**(会进 shell 历史与 ps 输出) - 预检端口:不通时直接说明「服务未上线」并给出上线步骤, 而不是抛 curl: (7) Connection refused 让用户自己猜 - 错误翻译成人话:401 → 令牌无效;404 → 附上服务端返回的现有分类 (用 curl -w 而非 -f,否则 404 响应体里最有用的那份清单会被丢掉) - 退出码:0 成功 / 2 参数 / 3 令牌 / 4 分类不存在 / 5 HTTP / 7 连不上 / 8 请求失败 DEPLOY.md 是给部署方的交接单:本机 kbtree 代码已合入 main 且 skill/token 已就位,但**运行中的二进制里没有 kbtree**(14:35 有人换过一版二进制), 故替换与重启留给部署方。含备份/替换/验证步骤、回滚方式、端口与 token 说明。 token 与 config.json **不入库**(config/ 目录留空),安装时由部署方从 配置库 config_kbtree 表读取后写入本机。 --- assets/skills/knowledge-base/DEPLOY.md | 105 ++++++++++++ assets/skills/knowledge-base/SKILL.md | 17 ++ .../skills/knowledge-base/scripts/kb_tree.sh | 154 ++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 assets/skills/knowledge-base/DEPLOY.md create mode 100755 assets/skills/knowledge-base/scripts/kb_tree.sh diff --git a/assets/skills/knowledge-base/DEPLOY.md b/assets/skills/knowledge-base/DEPLOY.md new file mode 100644 index 0000000..c61ba3b --- /dev/null +++ b/assets/skills/knowledge-base/DEPLOY.md @@ -0,0 +1,105 @@ +# kbtree 本机部署交接(交给部署方执行) + +我负责把 kbtree 的**代码**合入 main,并把 **skill + 调用方法**在本机装好。 +**二进制替换与重启由你做** —— 我不碰运行中的生产守护进程。 + +## 现状(交接时的事实) + +| 项 | 状态 | +|---|---| +| kbtree 代码 | ✅ 已在 `main`(`41d7543`),已推送 | +| skill | ✅ 已装 `/home/newqqagent/skills/knowledge-base/` | +| token | ✅ 已写入 `config_kbtree` 表(固定值,重启不变) | +| 配置库备份 | ✅ `config.db.bak-20260926-143643` | +| **服务** | ❌ **未上线** —— 运行中的二进制里没有 kbtree | +| 我构建的候选二进制 | `/tmp/homed-new`(84.7MB,与 main 同源,`-tags onnxruntime`) | + +**为何未上线**:14:35 有人替换了 `/usr/local/bin/homed`(84,985,720 字节)并重启了 +服务(现 PID 1450152)。那个二进制与 main 构建**不是同一份**,比我的大 2.4MB。 +直接部署会覆盖它 —— 所以交给你决定何时、以及以哪个版本为准。 + +实测确认现役二进制不含 kbtree: + +```bash +strings /usr/local/bin/homed | grep -c kbtree # → 0 +ss -ltn | grep 9892 # → 无监听 +``` + +## 上线步骤 + +**必须按顺序,且第 1 步不能用 `cp`**: + +```bash +DATA=/home/newqqagent + +# 1. 备份配置库(WAL 模式下 cp 会拿到不一致快照,必须用 .backup) +sqlite3 $DATA/config.db ".backup '$DATA/config.db.bak-$(date +%Y%m%d-%H%M%S)'" + +# 2. 确认要部署的二进制 +strings /tmp/homed-new | grep -c kbtree # 期望 ≥ 1;为 0 说明候选不对 + +# 3. 原子替换(install 内部是 rename,不会写坏运行中进程的映像) +install -m 0755 /tmp/homed-new /usr/local/bin/homed + +# 4. 重启 +systemctl restart homeagent + +# 5. 验证 +systemctl is-active homeagent +journalctl -u homeagent --since "-2min" | grep kbtree +ss -ltn | grep 9892 +``` + +### 第 5 步期望看到 + +``` +[kbtree] 已启动 http://127.0.0.1:9892 +[kbtree] 外部 agent 可用:GET /tree、/categories、/counts、/search?q=&category= +``` + +## 部署后的冒烟测试 + +```bash +cd /home/newqqagent/skills/knowledge-base + +./scripts/kb_tree.sh -h # 帮助(不需要 token) +./scripts/kb_tree.sh categories # 分类列表 +./scripts/kb_tree.sh counts # 各分类条目数 +./scripts/kb_tree.sh tree -d 1 -i 0 # 只看第一层结构 +./scripts/kb_tree.sh search -q 知识库 -c tech -l 3 +``` + +token 自动从 `config/config.json` 读(权限 600),无需手动 export。 +覆盖方式:`KB_TOKEN=xxx ./scripts/kb_tree.sh ...` 或改那个 json。 + +## 端口与 token + +- 监听:`kbtree.listen_addr` = `127.0.0.1:9892`(**仅本机**) +- token:`kbtree.token` 已在 `config_kbtree` 表里设为固定值。 + 若要改成随机(每次重启变),把该行 value 置空即可 —— + 但那样每次重启都要重新把 token 告诉所有使用者。 +- 改 token 时**两处一起改**:`config_kbtree` 表 + `skills/knowledge-base/config/config.json`。 + +要对外(别的机器)时改 `listen_addr` 为 `0.0.0.0:9892`, +但**先想清楚 token 怎么分发** —— 它是唯一的屏障。 + +## 退出码约定(脚本) + +| 码 | 含义 | +|---|---| +| 0 | 成功 | +| 2 | 参数错误 / 未知命令 | +| 3 | 令牌无效(401) | +| 4 | 分类不存在(404,stderr 会列出现有分类) | +| 5 | 其它 HTTP 错误 | +| 7 | 连不上(**通常是服务未上线**) | +| 8 | 请求发送失败 | + +## 回滚 + +```bash +install -m 0755 <旧二进制> /usr/local/bin/homed +systemctl restart homeagent +``` + +配置与 skill 都不影响回滚(kbtree 未启动时它们只是闲置文件)。 diff --git a/assets/skills/knowledge-base/SKILL.md b/assets/skills/knowledge-base/SKILL.md index c3e3666..993a29a 100644 --- a/assets/skills/knowledge-base/SKILL.md +++ b/assets/skills/knowledge-base/SKILL.md @@ -34,6 +34,23 @@ AUTH="X-API-Key: $KB_TOKEN" # 或 Authorization: Bearer 或 ?token= curl -s -H "$AUTH" "$BASE/" ``` +### 本机封装脚本(推荐先用它) + +`scripts/kb_tree.sh` 把上面四条命令封好了,省得手拼 URL 与鉴权头: + +```bash +S=~/.claude/skills/knowledge-base/scripts/kb_tree.sh # 按实际安装路径调整 + +$S # 整棵树 +$S tree -c tech -d 1 # tech 子树,只看第一层 +$S categories # 分类列表 +$S counts # 各分类条目数 +$S search -q goroutine -c tech +``` + +token 读取顺序:环境变量 `KB_TOKEN` → 同上��目录的 `../config.json`。 +**不要**把 token 写在命令行上(会进 shell 历史与 `ps` 输出)。 + ### 核心工作流:先看树,再定向检索 **别一上来就全文搜索。** 知识库是分层的,先定位分类能显著提高命中率, diff --git a/assets/skills/knowledge-base/scripts/kb_tree.sh b/assets/skills/knowledge-base/scripts/kb_tree.sh new file mode 100755 index 0000000..2735e93 --- /dev/null +++ b/assets/skills/knowledge-base/scripts/kb_tree.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# kbtree 客户端:按分类树检索 HomeAgent 知识库。 +# +# 由 knowledge-base skill 调用(agent 读 SKILL.md 后按需调本脚本)。 +# 也可人工使用: +# kb_tree.sh # 整棵树 +# kb_tree.sh -c tech -d 1 # tech 子树,只看一层 +# kb_tree.sh -q goroutine -c tech # 在 tech 子树内检索 +# kb_tree.sh -l # 列分类 +# +# token 读取顺序:环境变量 KB_TOKEN > 本目录 config.json > 报错。 +# 故意不放命令行参数:token 会进 shell 历史与 ps 输出。 +set -euo pipefail + +BASE="${KB_BASE:-http://127.0.0.1:9892}" +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# 读 token +# 帮助:-h/--help 必须在解析命令之前判,否则会被当成未知命令 +for a in "$@"; do + case "$a" in + -h|--help) + echo "用法:" + echo " kb_tree.sh [tree|categories|counts|search] [选项]" + echo "选项:" + echo " -q 关键词 检索词(给了 q 就走 search)" + echo " -c 分类 限定分类子树(前缀匹配),如 tech / tech/go" + echo " -d 层数 树展开层数,1=只看第一层(懒加载)" + echo " -i 0|1 是否带条目详情,默认 1" + echo " -l 条数 search 的返回条数,默认 10" + echo "环境变量: KB_TOKEN(也可放同目录 config/config.json)" + exit 0 ;; + esac +done + + +if [[ -z "${KB_TOKEN:-}" ]]; then + CFG="$HERE/../config.json" + if [[ -f "$CFG" ]]; then + KB_TOKEN="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("token",""))' "$CFG" 2>/dev/null || true)" + fi +fi +if [[ -z "${KB_TOKEN:-}" ]]; then + cat >&2 <<'EOF' +kbtree: 未找到访问令牌。 + +取 token 的办法(其一): + 1. 若 homed 启动日志里有「token 未配置,本次随机生成」,那行日志附近有 token; + 2. 若已配置过固定 token,从配置库读: + sqlite3 /config.db "SELECT value FROM config_kbtree WHERE key='token'" + 3. 直接设置:export KB_TOKEN= + +token 未配置时服务会在启动时随机生成,进程重启即失效。 +EOF + exit 2 +fi + +auth=(-H "X-API-Key: ${KB_TOKEN}") + +cmd="${1:-tree}"; shift || true +query=""; category=""; depth=""; items=""; limit="" +while getopts "q:c:d:i:l:h" opt 2>/dev/null; do + case "$opt" in + q) query="$OPTARG" ;; + c) category="$OPTARG" ;; + d) depth="$OPTARG" ;; + i) items="$OPTARG" ;; + l) limit="$OPTARG" ;; + h) echo "用法: kb_tree.sh [tree|categories|counts|search] -q 关键词 -c 分类 -d 层数 -i 0|1 -l 条数"; exit 0 ;; + esac +done + +# 预检:端口通不通。不通就直说"服务未上线", +# 而不是让用户看 curl: (7) Connection refused 后自行猜测。 +HOSTPORT="${BASE#http://}"; HOSTPORT="${HOSTPORT%%/*}" +if ! (exec 3<>"/dev/tcp/${HOSTPORT%%:*}/${HOSTPORT##*:}") 2>/dev/null; then + cat >&2 </config.db ".backup '/config.db.bak-\$(date +%Y%m%d-%H%M%S)'" + install -m 0755 <新二进制> /usr/local/bin/homed # 勿用 cp:会写坏运行中进程的映像 + systemctl restart homeagent + journalctl -u homeagent -f | grep kbtree +EOF + exit 7 +fi + +enc() { python3 -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1]))' "$1"; } + +# kb_call 发请求并把错误翻译成人话。 +# 不用 curl -f:它只吐 "curl: (22) 404",把服务端给的有用信息(404 会附现有分类) +# 全丢了 —— 而那恰恰是排查时最需要的。 +kb_call() { + local url="$1" raw code + raw="$(curl -sS -w $'\n%{http_code}' "${auth[@]}" "$url" 2>&1)" || { + echo "kbtree: 请求失败: ${raw}" >&2; exit 8 + } + code="$(printf '%s' "$raw" | tail -n1)" + body="$(printf '%s' "$raw" | sed '$d')" + case "$code" in + 2*) printf '%s' "$body" | python3 -m json.tool; return 0 ;; + 401) echo "kbtree: 令牌无效(401)。检查 config/config.json 或 KB_TOKEN 是否与 config_kbtree 表一致。" >&2; exit 3 ;; + 404) echo "kbtree: 分类不存在(404)。服务端返回的现有分类:" >&2 + printf '%s' "$body" | KB_BODY="$body" python3 -c ' +import json, os, sys +try: + d = json.loads(os.environ.get("KB_BODY","")) + print(" " + ", ".join(d.get("categories", [])), file=sys.stderr) +except Exception: + print(" (无法解析响应体)", file=sys.stderr) +' + exit 4 ;; + *) echo "kbtree: HTTP $code" >&2; printf '%s\n' "$body" >&2; exit 5 ;; + esac +} + +case "$cmd" in + tree|categories|counts|search) ;; + *) echo "未知命令: $cmd" >&2; exit 2 ;; +esac + +if [[ "$cmd" == "categories" ]]; then + kb_call "$BASE/categories" + exit 0 +fi +if [[ "$cmd" == "counts" ]]; then + kb_call "$BASE/counts" + exit 0 +fi +if [[ "$cmd" == "search" || -n "$query" ]]; then + [[ -n "$query" ]] || { echo "search 需要 -q 关键词" >&2; exit 2; } + url="$BASE/search?q=$(enc "$query")" + [[ -n "$category" ]] && url="$url&category=$(enc "$category")" + [[ -n "$limit" ]] && url="$url&limit=$limit" + kb_call "$url" + exit 0 +fi + +# 默认:树 +url="$BASE/tree" +[[ -n "$category" ]] && url="$url?category=$(enc "$category")" +q="" +[[ -n "$depth" ]] && q="depth=$depth" +[[ -n "$items" ]] && q="${q:+$q&}items=$items" +[[ -n "$q" ]] && url="$url?$q" +kb_call "$url"