-
Notifications
You must be signed in to change notification settings - Fork 14
Home
基于章节树(Chapter Tree)的个人/团队知识库 RAG 检索系统 + Agent Harness
端到端流水线:Markdown 知识库 → 章节树解析 → 向量 + BM25 混合检索 → Rerank → LLM 回答
适用版本:v0.1.0 | 最近更新:2026-06-13
- 1. 项目概览
- 2. RAG —— 检索增强生成引擎
- 3. Wiki —— Markdown 知识库
- 4. Harness —— Agent 执行回路
- 5. Skills —— 工具与技能体系
- 6. 四维度协作流(端到端示例)
- 7. 常见问题 FAQ
- 8. 路线图
he-wiki-rag 不是一个单纯的 RAG Demo,而是一个端到端的"知识 → 检索 → Agent 回答"工程样板。它把整个系统拆成四个相互独立、又可独立升级的维度:
| 维度 | 角色 | 技术栈 | 入口路径 |
|---|---|---|---|
| RAG | 检索后端:把 Wiki 知识库变成可被语义查询的 API | Python 3.11+ / FastAPI / ChromaDB / BGE-M3 | he-rag-engine/ |
| Wiki | 内容来源:以 Markdown + 章节树组织的可检索知识库 | Markdown / YAML frontmatter | wiki/ |
| Harness | 智能体执行回路:流式对话、工具调用、状态持久化、Trace | Node.js 18+ / SQLite | he-harness/ |
| Skills | Agent 可调用的"技能" = 工具集合 | OpenAI 兼容 tool calling | he-harness/src/tools.js |
四者通过 HTTP + tool calling 解耦:
┌──────────────────┐ ┌──────────────────────┐
│ he-harness │ HTTP │ he-rag-engine │
│ (Node.js CLI) │ ──────▶ │ (FastAPI + Chroma) │
│ Harness + Skills│ │ RAG │
└──────────────────┘ └──────────┬───────────┘
│
▼
┌──────────────────┐
│ wiki/ │
│ Markdown 知识库 │
│ + chapter_trees │
└──────────────────┘
flowchart LR
subgraph Wiki[Wiki 维度]
MD["Markdown 文档<br/>+ frontmatter"]
CT["chapter_trees.json<br/>章节树索引"]
end
subgraph RAG[RAG 维度]
EMB["BGE-M3 Embedding"]
BM["BM25 (jieba)"]
QE["自动查询扩展"]
RR["Qwen3-Reranker"]
end
subgraph Harness[Harness 维度]
LOOP["Agent Loop"]
STATE["State Store<br/>(SQLite)"]
TRACE["Tracer + Replay"]
end
subgraph Skills[Skills 维度]
TOOL1["knowledge_search"]
TOOL2["chapter_detail"]
TOOL3["calculator / file..."]
end
MD --> CT --> EMB
MD --> BM
EMB --> QE
LOOP --> Skills
TOOL1 --> RAG
RAG --> LOOP
LOOP --> STATE
LOOP --> TRACE
he-wiki-rag/
├── he-rag-engine/ # Python FastAPI 检索后端(向量 + BM25 + Rerank)
│ ├── app/ # FastAPI 入口、配置、模型、索引器、检索引擎
│ ├── scripts/ # 摘要增强脚本
│ ├── evaluate.py # 内置评测脚本
│ └── pyproject.toml
├── he-harness/ # Node.js Agent Harness(CLI 交互 + 工具调用)
│ ├── src/ # Agent Loop、State、Tracer、Tools
│ ├── cli/ # 交互式 CLI
│ ├── examples/ # 使用示例
│ └── package.json
├── wiki/ # 示例知识库(Markdown + 章节树索引)
│ ├── 03-knowledge-and-rag/ # ~76 篇 RAG 主题 Markdown
│ └── indices/chapter_trees.json
├── assets/ # 演示截图
├── start.sh # 一键启动脚本
└── README.md
# 1. 克隆 & 进入
git clone <repo-url> && cd he-wiki-rag
# 2. 配置环境变量
cp he-rag-engine/.env.example he-rag-engine/.env # 填 RAG_SILICONFLOW_API_KEY
cp he-harness/.env.example he-harness/.env # 可选:填 LLM Key,或保留 Mock
# 3. 一键启动
./start.sh启动后即可在 CLI 中提问:
你: RAG 的切块策略有哪些?
🔎 正在检索知识库...
✅ 知识库助手:……(引用了 3 个章节,附带面包屑路径)
详细步骤、配置项、二次开发模式见 根 README。
路径:
he-rag-engine/,技术栈 Python 3.11 + FastAPI + ChromaDB + BGE-M3 + jieba + Qwen3-Reranker。
RAG 维度解决的核心问题:把 Markdown 知识库变成可被语义查询的 API,并保证结果可溯源(带面包屑、源文件、原文片段)。
flowchart TB
subgraph API["FastAPI Server"]
direction LR
EP1["/search"]
EP2["/chapter/{id}"]
EP3["/health"]
EP4["/stats"]
EP5["/admin/rebuild-index"]
end
API --> SE
subgraph SE["Search Engine"]
direction TB
QE["Query Expand<br/>Embedding 自动扩展 + Fallback 词典"]
QE --> VS & BS
VS["Vector Search<br/>ChromaDB + BGE-M3"]
BS["BM25 Search<br/>rank_bm25 + jieba"]
VS --> RRF["RRF Fusion<br/>加权倒数排名融合<br/>k=60, Vector:2.0 / BM25:1.0"]
BS --> RRF
RRF --> RR["Rerank<br/>Qwen3-Reranker-8B<br/>SiliconFlow API"]
RR --> PCI["Parent-Child Injection<br/>父子节点注入 (保守策略)<br/>MIN_SCORE=0.8, DISCOUNT=0.5"]
PCI --> RES["Results"]
end
RES --> OUTPUT["Search Results"]
subgraph DS["Data Layer"]
direction LR
IDX["Indexer<br/>ChromaDB + BM25 索引"]
TS["Tree Store<br/>内存章节树"]
SUM["Summary Enhancer<br/>LLM 摘要增强 (Qwen2.5-7B)"]
end
IDX -.->|"索引构建"| SE
TS -.->|"章节树查询"| SE
SUM -.->|"增强摘要"| IDX
系统的核心创新是基于 Markdown 标题层级构建的章节树,而非传统的固定 token 切块。
每篇文档被解析为:
| 概念 | 含义 | 关键字段 |
|---|---|---|
| Document | 一个 .md 文件 |
docId, filePath, frontmatter
|
| Chapter / Node | 按 H1~H6 切出的章节段 |
node_id, title, breadcrumb, summary, parent_id, children_ids, line, anchor
|
与"傻切块"相比,章节树保留了:
-
层级关系:父子兄弟节点可用于上下文扩展(
expand=children/parent/siblings) -
面包屑路径:检索结果直接告诉用户"这条来自
RAG > 关键设计点 > 切块策略" -
精确溯源:
line+anchor直接定位回源文件段落
| 通道 | 模型 / 算法 | 输入 | 输出 |
|---|---|---|---|
| 向量检索 | ChromaDB + BGE-M3(1024 维) | 原始查询 | 语义 Top-N 候选 |
| BM25 检索 | rank_bm25(BM25Okapi) + jieba 分词 | 查询扩展后的查询 | 关键词 Top-N 候选 |
两路用 ThreadPoolExecutor 并行执行降低延迟。
RRF_score(d) = Σ weight_i / (k + rank_i(d))
-
k = 60(平滑常数) - 向量权重
2.0(语义优先),BM25 权重1.0(关键词补充) - 候选集大小:
top_k × candidate_multiplier(rerank 时为 4 倍,否则 3 倍)
使用 Qwen3-Reranker-8B(SiliconFlow API)对候选精排:
-
主方案:SiliconFlow
/v1/rerankAPI,单次批量请求,输出连续相关性分数 -
备选方案:本地 Ollama 部署
Qwen3-Reranker-4B,逐文档 yes/no 判定 - Reranker 输入 = 标题 + 面包屑 + 关键词 + 摘要 + 原文前 8 行(≤ 300 字)
- 最大候选数 35 篇,输出
top_n=30
为什么用 Rerank API 而非本地模型? Reranker 8B 参数量大,本地部署要大量 GPU 显存;API 方案低延迟、可批量;保留 Ollama 作为离线备选。
问题:BM25 是纯词频匹配,"切块" 永远匹配不到 "chunking"。
方案:用 Embedding 相似度自动发现同义词,零维护词典。
flowchart TD
Q["查询: 切块策略"] --> T["jieba 分词"]
T --> Terms["术语: 切块, 策略"]
Terms --> Emb["BGE-M3 Embedding"]
Emb --> Sim["余弦相似度匹配"]
Sim --> Match["匹配结果<br/>chunking (0.89)<br/>分块 (0.85)<br/>切分 (0.82)"]
Match --> Exp["扩展后查询"]
Exp --> Final["切块策略 chunking 分块 切分"]
关键特性:
-
零维护:换知识库后
rebuild-index即可,自动从新知识库提取术语 - 多语言:BGE-M3 天然支持中英双语,自动发现"切块 ↔ chunking"
- 优雅降级:扩展器未加载时回退到精简的 Fallback 手工词典
-
阈值可调:
RAG_EXPAND_SIMILARITY_THRESHOLD(默认 0.75) - 性能:词汇表 Embedding 仅索引构建时执行一次;查询时相似度计算 = numpy 矩阵乘法(< 1ms)
Rerank 后,对高分结果的子节点进行保守注入,扩展检索上下文:
| 参数 | 值 | 说明 |
|---|---|---|
MIN_PARENT_SCORE |
0.8 |
父节点 rerank 分数阈值 |
MAX_CHILDREN_PER_PARENT |
2 |
每个父节点最多注入子节点数 |
CHILD_SCORE_DISCOUNT |
0.5 |
子节点分数 = 父节点分数 × 0.5 |
设计原则:不污染顶部结果,只为高度相关的父节点补充子章节信息。
用 LLM 为每个章节生成 50-100 字的中文摘要,提升 Embedding 质量:
- 模型:Qwen2.5-7B-Instruct(SiliconFlow API)
- 输入:章节路径 + 标题 + 正文片段(≤ 500 字)
-
输出:中文摘要,存于
data/enhanced_summaries.json - 支持增量:已生成摘要的章节自动跳过
Embedding 文档格式:
路径: RAG > 关键设计点 > 切块策略
标题: 切块策略
摘要: 讨论了知识库文档的切块方法,包括按 token 数切分和重叠窗口策略...
内容: <原文内容,最多 2048 字符>
| Method | Path | 说明 |
|---|---|---|
POST |
/search |
知识库搜索主接口 |
GET |
/chapter/{node_id} |
获取章节详情(含原文 + 子章节) |
GET |
/chapter/{node_id}/children |
子章节列表 |
GET |
/chapter/{node_id}/siblings |
兄弟章节列表 |
GET |
/health |
健康检查 |
GET |
/stats |
索引 + 章节树统计 |
POST |
/admin/rebuild-index |
触发全量索引重建(热更新,不中断服务) |
POST /search 请求体:
{
"query": "什么是 RAG 的切块策略?",
"top_k": 10,
"search_mode": "hybrid", // hybrid | vector | keyword
"rerank": true,
"expand": "none", // none | children | siblings | parent
"filters": {
"category": ["rag"],
"priority": ["high"],
"doc_type": ["concept"],
"keywords": ["切块"]
}
}响应示例:
{
"results": [
{
"node_id": "doc-001__ch-003",
"score": 0.92,
"rank_source": { "vector": 1, "bm25": 3, "rerank": 1 },
"title": "切块策略",
"breadcrumb": ["RAG", "关键设计点", "切块策略"],
"content": "## 切块策略\n...",
"summary": "讨论了知识库文档的切块方法...",
"metadata": {
"file_path": "wiki/rag/chunking-strategy.md",
"line": 42,
"anchor": "切块策略",
"level": 2,
"keywords": ["切块", "chunking"],
"doc_type": "concept",
"priority": "high",
"category": "rag"
},
"expanded": null
}
],
"total": 10,
"took_ms": 826.3,
"index_stats": { "total_chunks": 1234, "total_documents": 56, "last_sync": "..." }
}- 加载
chapter_trees.json到TreeStore - 预加载文件内容缓存
- 加载增强摘要
- 加载查询扩展词汇表
- 尝试加载已有索引;空索引 → 自动
build()
| 维度 | 说明 |
|---|---|
| 触发时机 | 知识库内容变更、章节树重新生成、摘要增量更新后 |
| 是否中断 | ❌ 不中断(用旧索引继续服务) |
| 耗时 | 1000 文档 ~ 30-60s;5000 文档 ~ 3-5min(瓶颈 = Embedding API) |
| 与重启区别 | Rebuild = 热更新;重启 = 冷启动(适合配置变更 / 部署) |
Rebuild 流程:
- 重新加载
chapter_trees.json - 加载增强摘要
- 重建 ChromaDB 向量索引
- 重建 BM25 关键词索引
- 重建查询扩展词汇表
cd he-rag-engine
python evaluate.py # 默认评测(hybrid 模式)
python evaluate.py --modes hybrid vector keyword
python evaluate.py --rerank # 开启 rerank 评测
python evaluate.py --hierarchical # 父子节点算部分命中
python evaluate.py --output results.json -v评测指标:
- Recall@K(K=5,10,20):相关文档的召回率
- Precision@K:返回结果的精确率
- MRR:首个相关文档的排名倒数
- NDCG@K:归一化折损累积增益
- Hit Rate@K:至少命中一个相关文档的查询比例
- 层级匹配:子节点(权重 0.5)和父节点(权重 0.3)算部分命中
支持按难度(easy/medium/hard)和分类(category)细分分析。
| 变量 | 默认 | 说明 |
|---|---|---|
RAG_SILICONFLOW_API_KEY |
必填 | SiliconFlow API Key |
RAG_EMBEDDING_PROVIDER |
siliconflow |
siliconflow / ollama
|
RAG_RERANK_PROVIDER |
siliconflow |
siliconflow / ollama
|
RAG_RERANK_MODEL |
Qwen/Qwen3-Reranker-8B |
Rerank 模型名 |
RAG_KB_ROOT |
./wiki |
知识库根目录 |
RAG_EXPAND_SIMILARITY_THRESHOLD |
0.75 |
自动查询扩展相似度阈值 |
RAG_EXPAND_MAX_TERMS |
6 |
最多扩展术语数 |
完整配置见 he-rag-engine/.env.example。
路径:
wiki/,被 RAG 引擎索引的内容来源。
wiki/
├── README.md # 仓库元信息(项目说明,不参与检索)
├── 03-knowledge-and-rag/ # 示例知识库正文(~76 篇 Markdown)
│ ├── README.md # ⚠️ 领域根节点(带 frontmatter,参与检索,请勿随意改)
│ ├── rag-system-design.md
│ ├── chunking-strategies.md
│ ├── retrieval-*.md
│ └── ...(约 70+ 主题页 + ~30 routing 治理页)
└── indices/
└── chapter_trees.json # 章节树索引(由 he-rag-engine 构建阶段生成)
⚠️ 重要:wiki/03-knowledge-and-rag/README.md的 frontmatter 是type: domain-root,它本身会被检索系统当作一篇文档。修改它会污染检索结果。
每篇 Markdown 顶部加 YAML frontmatter:
---
title: 文章标题
type: concept # concept / pattern / architecture / faq
priority: P0 # P0 / P1 / P2
category: rag # 用于检索过滤
keywords: 切块, chunking
---字段含义:
| 字段 | 类型 | 用途 |
|---|---|---|
title |
string | 文档标题(显示 + 检索) |
type |
enum | 文档类型(concept/pattern/architecture/faq/domain-root) |
priority |
enum | 重要程度(P0/P1/P2) |
category |
string | 主题分类(rag/agent/harness 等),用于检索过滤 |
keywords |
list | 关键词列表(用于检索过滤 + Embedding 增强) |
chapter_trees.json 是 RAG 引擎的核心输入,由知识库构建流程生成,存放于 wiki/indices/。
关键结构:
{
"generatedAt": "2026-06-05T17:01:02",
"version": "1.0.0",
"statistics": {
"totalDocuments": 772,
"totalChapters": 8337,
"maxDepth": 4
},
"documents": [
{
"docId": "e63bc74b-...",
"filePath": "wiki/00-home/agent-orchestration.md",
"frontmatter": { "title": "Agent 编排", "type": "concept", ... },
"chapters": [
{
"id": "30d9aeff-...",
"node_id": "30d9aeff-...",
"title": "编排架构",
"level": 1,
"ordinal": 1,
"parentId": "e63bc74b-...",
"line": 11,
"anchor": "编排架构",
"summary": "编排架构",
"breadcrumb": ["编排架构"]
}
]
}
]
}| 字段 | 说明 |
|---|---|
filePath |
Markdown 文件相对 kb_root 的路径 |
chapters[].level |
Markdown 标题层级(1=H1, 2=H2, ...) |
chapters[].parentId |
父节点 ID(指向 doc_id 或上级 chapter.id) |
chapters[].breadcrumb |
面包屑路径(数组),提供定位语境 |
chapters[].line |
标题在源文件中的行号(1-based) |
chapters[].anchor |
URL 锚点标识符 |
-
用标题层级组织内容:
######划分章节,章节树解析依赖标题层级 - 单篇 200~500 行:过长会被自动切块并破坏结构语义
-
稳定结构:
概念 / 模式 / 架构 / 示例 / 延伸阅读是建议的章节顺序 - 一个文档一个核心主题:降低正文混杂度
-
来源材料放
raw/:正式知识页只保留必要的来源引用
# 1. 替换内容
rm -rf wiki/03-knowledge-and-rag
mkdir -p wiki/your-topic
# ... 放入你的 .md 文件
# 2. 重新生成章节树索引
python scripts/build_chapter_trees.py --kb-root ./wiki
# 3. 触发索引热更新(不需要重启服务)
curl -X POST http://localhost:8000/admin/rebuild-indexflowchart TD
A["修改/新增/删除<br/>Markdown 文件"] --> B["重新生成 chapter_trees.json"]
B --> C["增量生成摘要<br/>python scripts/enhance_summaries.py"]
C --> D["POST /admin/rebuild-index"]
D --> E["索引热更新完成<br/>(服务不中断)"]
日常开发循环:
# 1. 编写/修改 Markdown
# 2. 重新生成章节树
python scripts/build_chapter_trees.py --kb-root ./wiki
# 3. 增量生成摘要(已生成则自动跳过)
python scripts/enhance_summaries.py
# 4. 热更新索引
curl -X POST http://localhost:8000/admin/rebuild-index路径:
he-harness/,技术栈 Node.js 18+ / ES Modules / SQLite(better-sqlite3)。
Harness 维度解决的核心问题:让 LLM 在"可控、有边界、有可观测、可回放"的执行回路里工作,而不是一次性问答。
项目根植于以下五条工程原则:
-
Agent 应被理解为执行回路,而非单次问答 ——
AgentLoop -
没有 Trace 的 Harness,很难调试;没有 Replay 的 Harness,很难进化 ——
Tracer -
Harness 管的不是功能多少,而是运行边界 ——
ToolGuardrail -
状态是一等资产 ——
StateStore(SQLite 持久化) -
压缩解决的是扩展,重置解决的是漂移 ——
ContextCompressor
| 层级 | 名称 | 实现文件 | 职责 |
|---|---|---|---|
| 1 | Task Input Layer | cli/index.js |
接收用户任务、CLI 交互 |
| 2 | Agent Loop | src/agent-loop.js |
主执行回路(LLM ↔ Tools) |
| 3 | Tool & Permission Layer | src/tool-guardrail.js |
工具白名单、循环检测、渐进干预 |
| 4 | State Layer | src/state-store.js |
会话/消息持久化、压缩续传链 |
| 5 | Observation Layer | src/tracer.js |
Trace 记录、Replay、Bad Case 下沉 |
| 6 | Governance Layer | (可扩展) | 跨 Harness 的策略、限流、配额 |
flowchart TB
U["用户"] --> L1["1. Task Input<br/>CLI 交互"]
L1 --> L2["2. Agent Loop<br/>主执行回路"]
L2 --> L3["3. Tool & Permission<br/>工具白名单 + 防护栏"]
L2 --> L4["4. State<br/>SQLite 持久化"]
L2 --> L5["5. Observation<br/>Trace + Replay"]
L2 --> L6["6. Governance<br/>策略 / 限流"]
L3 --> Tools["Skills<br/>(tools)"]
import { AgentLoop, createHarness } from './src/index.js';
const harness = createHarness({
llmClient, // LLM 客户端
tools, // 工具定义数组
validToolNames: new Set(['knowledge_search', 'chapter_detail']),
maxTurns: 30,
dbPath: './harness.db',
tracesDir: './traces',
});
const result = await harness.agentLoop.run(initialMessages, taskId);主循环(每轮):
- 上下文压缩(如启用)→
ContextCompressor.compress(messages) - 调用 LLM →
llmClient.chatComplete({ messages, tools }) - 提取推理内容(多提供商:
reasoning_content/reasoning/thinking/<think>标签) - 无工具调用 → 任务自然结束,保存状态 + Trace
- 有工具调用 → 逐个执行
_executeToolCall(含 4 层防御) - 保存中间状态到 StateStore
- 达到
maxTurns→ 强制结束
import { StateStore } from './src/index.js';
const store = new StateStore({ dbPath: './harness.db' });
store.createSession(sessionId);
store.saveState(sessionId, messages);
const { session, messages } = store.loadSession(sessionId);特性:
- 基于
better-sqlite3,WAL 模式并发优化 - 完整消息历史(含
tool_calls、tool_call_id、reasoning、token_count、finish_reason) - 支持会话恢复、压缩续传链
- 声明式 Schema(
SCHEMA_DEFINITION),可平滑迁移 - 索引:
started_at、parent_session_id、title
import { ToolGuardrailController, ToolGuardrailConfig } from './src/index.js';
const guardrail = new ToolGuardrailController(new ToolGuardrailConfig({
warningsEnabled: true,
hardStopEnabled: true,
idempotentTools: new Set(['knowledge_search', 'chapter_detail', 'read_file']),
}));职责:
- 幂等工具 vs 修改工具分类:防止修改类工具被滥用
- 循环检测:相同失败 / 同工具失败 / 无进展
- 渐进式干预:警告 → 阻止
import { ContextCompressor, CompressorConfig } from './src/index.js';
const compressor = new ContextCompressor(new CompressorConfig({
protectFirstN: 4, // 保护前 N 条消息
targetTotalMessages: 24, // 目标总消息数
}), llmClient); // 可选:传 LLM 做真实摘要策略:
-
头部保护:
protectFirstN永远不压缩(保 system prompt + 关键设定) - 尾部保护:保留最近 N 条(最相关上下文)
- 中间区域压缩:用 LLM 摘要旧工具结果
- 预压缩:旧工具结果先被摘要再参与压缩
import { Tracer, TracerConfig } from './src/index.js';
const tracer = new Tracer(new TracerConfig({
tracesDir: './traces',
saveTraces: true,
verbose: true,
}));
await tracer.recordStart(taskId, messages);
await tracer.recordLLMCall(taskId, turn, assistantMsg);
await tracer.recordToolCall(taskId, turn, toolCall, result);
await tracer.recordEnd(taskId, success, finalMessages);特性:
- 完整记录执行资产(LLM 调用、工具调用、推理、错误)
- JSONL 格式导出
- 支持 Replay
-
Bad Case 自动下沉:
failed/目录留作离线分析 - 调试 → 复现 → 修复 → 上线的标准工作流
import { createLLMClient } from './src/index.js';
// OpenAI 官方
const client = createLLMClient('openai', {
apiKey: 'sk-...', model: 'gpt-4o', baseURL: 'https://api.openai.com/v1',
});
// OpenAI 兼容(DeepSeek / vLLM / Azure / Ollama 都可以)
const client = createLLMClient('openai-compatible', {
apiKey: '...', model: '...', baseURL: 'https://your-api/v1',
});
// Mock(无 Key 也能跑)
const client = createLLMClient('mock');支持多提供商的工具调用规范化(normalizeToolCall)和推理内容提取(reasoning_content / <think> / OpenRouter reasoning_details 等)。
AgentLoop._executeToolCall 内置 4 层防御:
| 层级 | 防御 | 失败行为 |
|---|---|---|
| 1 |
工具名验证(白名单 validToolNames) |
返回 { error: "Unknown tool..." }
|
| 2 |
工具防护栏检查(ToolGuardrail.beforeCall) |
返回 { error: "<guardrail msg>" }
|
| 3 |
JSON 参数解析(JSON.parse 容错) |
返回 { error: "Invalid JSON..." }
|
| 4 | 工具执行异常捕获(try-catch) | 返回 { error: "Tool execution failed..." }
|
关键设计:所有失败都"以工具结果的形式"返回给 LLM,让模型有机会理解错误并自我修正,而不是直接抛异常中断回路。
cd he-harness
npm install
npm run rag-chat # 启动 RAG 问答 CLI
# 或
node cli/index.js内置命令:
-
/help—— 查看帮助 -
/tools—— 查看当前可用工具 -
/exit或/quit—— 退出
示例对话:
你: RAG 的切块策略有哪些?
🔎 正在检索知识库...
✅ 知识库助手:……(引用了 3 个章节,附带面包屑路径)
| 变量 | 默认 | 说明 |
|---|---|---|
LLM_PROVIDER |
mock |
openai / openai-compatible / mock / ollama
|
OPENAI_API_KEY |
- | OpenAI API Key |
OPENAI_MODEL |
gpt-4o |
OpenAI 模型 |
OPENAI_BASE_URL |
https://api.openai.com/v1 |
OpenAI API 地址 |
OPENAI_COMPATIBLE_* |
- | 兼容 API 配置(Key/Model/BaseURL) |
MAX_TURNS |
20 |
最大对话轮数 |
SYSTEM_PROMPT |
You are a helpful AI assistant... | 系统提示词 |
RAG_API_BASE_URL |
http://localhost:8000 |
RAG 引擎地址(被 knowledge_search 工具使用) |
路径:
he-harness/src/tools.js
Skills 维度解决的核心问题:让 LLM 拥有"动手能力"。Skills = OpenAI 风格的 tool calling 工具集合,由 Harness 维度的 Agent Loop 调度。
getAllTools(includeRAG=true) 返回 6 个工具:
| 工具名 | 类型 | 用途 | RAG 工具? |
|---|---|---|---|
read_file |
幂等 | 读文件(限 2000 字符) | 否 |
write_file |
修改 | 写文件(自动建目录) | 否 |
calculator |
幂等 | 四则运算 | 否 |
search |
幂等 | 模拟搜索(演示用) | 否 |
knowledge_search |
幂等 | 调用 RAG /search 检索知识库 |
✅ |
chapter_detail |
幂等 | 调用 RAG /chapter/{id} 取章节全文 |
✅ |
{
"name": "knowledge_search",
"description": "Search the knowledge base for relevant information. Use this when you need to find documents, guidelines, or answers from the internal knowledge base.",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "The search query in natural language" },
"top_k": { "type": "integer", "default": 5, "description": "Number of results (max 20)" },
"search_mode": { "type": "string", "enum": ["hybrid", "vector", "keyword"], "default": "hybrid" },
"rerank": { "type": "boolean", "default": false, "description": "Enable rerank (slower, ~3-8s)" }
},
"required": ["query"]
}
}行为:
- 调用
POST {RAG_API_BASE_URL}/search - 把结果格式化为 LLM 易消费的结构(rank / score / summary / content / node_id)
-
无结果时返回
found: false+ 友好提示,让 Agent 主动告知用户"超出知识库范围"
{
"name": "chapter_detail",
"description": "Get full content of a specific chapter by node_id. Use this after knowledge_search to read the complete document.",
"inputSchema": {
"type": "object",
"properties": {
"node_id": { "type": "string", "description": "The node_id (from knowledge_search results)" },
"include_children": { "type": "boolean", "default": true, "description": "Include child chapters" }
},
"required": ["node_id"]
}
}典型协作模式:
sequenceDiagram
participant U as User
participant A as Agent (LLM)
participant K as knowledge_search
participant C as chapter_detail
participant R as RAG Engine
U->>A: "RAG 切块策略有哪些?"
A->>K: knowledge_search(query="RAG 切块策略", top_k=5)
K->>R: POST /search
R-->>K: 5 条候选(含 node_id)
K-->>A: 5 条带摘要的结果
A->>C: chapter_detail(node_id="doc-001__ch-003")
C->>R: GET /chapter/{id}
R-->>C: 完整章节内容
C-->>A: 原文 + 子章节
A-->>U: 整合答案 + 引用
每个工具是 ES Module Class,包含 3 部分:
export class MyTool {
static name = 'my_tool'; // 工具标识(Agent 调用时使用)
static schema = { // OpenAI 风格 tool schema
name: 'my_tool',
description: '一句话说清用途和调用时机', // ⚠️ 决定 LLM 是否会选它
inputSchema: {
type: 'object',
properties: { /* ... */ },
required: ['...'],
}
};
async execute(args) { // 实际执行逻辑
// 返回 JSON.stringify(...) 的字符串
return JSON.stringify({ success: true, result: '...' });
}
}关键设计原则:
-
description写完整且具体(LLM 不会调用描述模糊的工具) -
execute必须始终返回 JSON 字符串(成功或错误) - 错误也要结构化:
{ error: "..." }而不是抛异常 - 长结果要截断(如
read_file限 2000 字符、knowledge_search限 1500)
// 在 src/tools.js 中添加
export class MyTool {
static name = 'my_tool';
static schema = {
name: 'my_tool',
description: 'Description of when to use this tool',
inputSchema: {
type: 'object',
properties: {
param1: { type: 'string', description: '...' },
param2: { type: 'integer', default: 10 }
},
required: ['param1']
}
};
async execute(args) {
// 实现工具逻辑
return JSON.stringify({ success: true, result: '...' });
}
}export function getAllTools(includeRAG = true) {
const tools = [
// ... 现有工具
{ name: MyTool.name, schema: MyTool.schema, execute: (args) => new MyTool().execute(args) }
];
// ...
}// cli/index.js 中指定 validToolNames
const validToolNames = new Set(['knowledge_search', 'chapter_detail', 'my_tool']);const guardrail = new ToolGuardrailController(new ToolGuardrailConfig({
idempotentTools: new Set(['knowledge_search', 'my_tool']), // 幂等工具
// 修改工具不加入 idempotentTools,自动按修改类处理
}));最重要的安全边界:在 AgentLoop 初始化时声明 validToolNames: Set<string>,LLM 即便输出了不在白名单中的工具名,也会被第一层防御拦截。
// ✅ 推荐:最小权限原则
const validToolNames = new Set(['knowledge_search', 'chapter_detail']);
// ❌ 不推荐:把全部工具都给 LLM(容易误调)
const validToolNames = new Set([...所有工具名]);下面是一个用户问 "RAG 的切块策略有哪些?" 时,四个维度协同的完整流程:
sequenceDiagram
autonumber
participant U as User
participant CLI as Harness CLI
participant Loop as AgentLoop
participant Skill as knowledge_search
participant API as RAG /search
participant Wiki as wiki/*.md
participant State as StateStore
participant Trace as Tracer
U->>CLI: 输入问题
CLI->>Loop: run(messages, taskId)
Loop->>Trace: recordStart(taskId, messages)
Loop->>Loop: ContextCompressor.compress()
Loop->>Loop: llmClient.chatComplete()
Note over Loop: LLM 决定调用 knowledge_search
Loop->>Skill: execute({query, top_k: 5})
Skill->>API: POST /search
API->>Wiki: 加载 chapter_trees.json
API->>API: QueryExpand → VectorSearch ∥ BM25Search
API->>API: RRF Fusion → Rerank → Parent-Child Injection
API-->>Skill: 5 条候选
Skill-->>Loop: 格式化结果
Loop->>Trace: recordToolCall(turn, toolCall, result)
Loop->>State: saveState(taskId, messages)
Loop->>Loop: LLM 整合答案(可能再调用 chapter_detail)
Loop->>Trace: recordEnd(success, messages)
Loop-->>CLI: AgentResult
CLI-->>U: 渲染回答 + 引用
关键产物:
- 📄
harness.db:完整会话状态(可恢复) - 📄
traces/{taskId}.jsonl:每一步执行资产(可 Replay) - 📄
wiki/indices/chapter_trees.json:知识库结构(可重建) - 📄
he-rag-engine/data/:向量 + BM25 + 词汇表(可热更新)
A:首次启动会调用 Embedding API 构建向量索引。如果你的知识库较大(> 1000 文档),可能需要几分钟。后续启动从磁盘加载已有索引,秒级。
A:可以。he-harness 通过 OpenAI 兼容协议对接 LLM,只需修改 .env 中的 OPENAI_BASE_URL 和 OPENAI_API_KEY 即可指向任何 OpenAI 兼容服务(Azure OpenAI、vLLM、Ollama、DeepSeek 等)。
A:不是。RAG_EMBEDDING_PROVIDER=ollama + RAG_RERANK_PROVIDER=ollama 可以完全本地化部署(需要 16GB+ 显存跑 Qwen3-Reranker-4B)。
A:是仓库自带的一份示例知识库(约 76 篇 RAG / 知识工程相关的 Markdown),用于让你 clone 下来就能直接跑通。你也可以清空 wiki/03-knowledge-and-rag/ 然后放入自己的内容。
# 1. 替换内容
rm -rf wiki/03-knowledge-and-rag && mkdir -p wiki/your-topic
# 2. 重新生成章节树
python scripts/build_chapter_trees.py --kb-root ./wiki
# 3. 增量生成摘要
python scripts/enhance_summaries.py
# 4. 触发索引热更新
curl -X POST http://localhost:8000/admin/rebuild-indexA:检查日志,确认 chapter_trees.json 存在;然后调用 POST /admin/rebuild-index。
A:索引可能为空。先调用 POST /admin/rebuild-index;如仍 0 结果,检查 data/vocab.json 和 data/chroma/ 是否生成。
A:编辑 src/tool-guardrail.js 的 ToolGuardrailConfig:
new ToolGuardrailConfig({
warningsEnabled: true,
hardStopEnabled: true,
idempotentTools: new Set([...]),
maxConsecutiveFailures: 3,
// ...
})A:参考 §5.4 自定义工具开发 的 4 步流程。
- Wiki 前端编辑器(所见即所得)
- 多租户 / 多人协作
- Rerank 蒸馏到本地小模型
- 支持 PDF / Notion 导入
- 技能市场(Skills Marketplace)
- 跨会话记忆与长期知识沉淀
- Eval 集自动从 Bad Case 沉淀
文档版本:v0.1.0 | 最近更新:2026-06-13 任何问题请提交 GitHub Issue 反馈。