【OpenClaw具身硬件】ZeroClaw 源码阅读笔记(3)--- RAG
0x00 概要
本文是 ZeroClaw 的学习笔记。
ZeroClaw 是一个零开销、零妥协、100% Rust实现的AI助手框架,具有以下核心特点:
- 数字-物理桥梁:AI不仅处理数字信息,还能控制物理世界
- 环境感知:通过传感器获取真实环境数据
- 主动交互:能够主动改变物理环境状态
- 极致性能:优化编译配置(opt-level="z",lto="fat")生成最小二进制文件
- 多平台支持:支持CLI、WebGateway、桌面应用、硬件集成
- 模块化设计:高度可扩展的插件式架构
- 安全优先:内置多层安全机制和紧急停止功能
ZeroClaw 的总体如下图所示。

2-总体
ZeroClaw项目采用RAG技术是为了解决硬件控制领域特有的精确性、安全性和易用性挑战。通过将权威的硬件数据手册与强大的LLM能力相结合,RAG实现了:
- 精确控制:确保每个硬件操作都基于准确的规格信息
- 安全保障:防止因知识错误导致的硬件损坏
- 用户体验:让用户能够用自然语言控制复杂的硬件系统
- 灵活扩展:支持无限的硬件类型和用户自定义配置
这种设计完美体现了ZeroClaw"零开销、零妥协"的核心理念,既保持了AI助手的强大能力,又确保了硬件操作的专业性和可靠性。
0x01 两套RAG
ZeroClaw项目里其实是两套独立的RAG。这两套互不调用:HardwareRag是给LLM注入硬件文档的简单流水线,Memory是ZeroClaw的"大脑"。
| 系统 | 用途 | 位置 | 是否带 embedding |
|---|---|---|---|
| HardwareRag | 硬件数据手册检索(GPIO/外设) | src/rag/mod.rs | 不带,纯关键词 |
| Memory 检索管线 | 对话/知识 memory 检索 | src/memory/retrieval.rs + sqlite.rs | 可选(默认不开) |
1.1 关键差异
| 维度 | HardwareRag | Memory 检索 |
|---|---|---|
| 数据源 | 静态数据手册文件 | Agent 运行期累积 |
| 触发 | 每条用户消息自动注入 prompt | LLM 主动调 memory_recall tool |
| 算法 | 纯关键词词频 + board 加分 | cache → FTS5 → vector hybrid |
| embedding | 无 | 可选(默认 Noop) |
| 后端 | 进程内 Vec | SQLite / Qdrant |
| 缓存 | 一次加载到内存 | hot LRU + embedding LRU |
| 写回 | 启动后只读 | 持续写入 |
| 位置 | src/rag/mod.rs | src/memory/ |
1.2 工程取舍
几个值得注意的工程取舍如下:
- HardwareRag 不上 embedding --- 数据手册量小(几十到几百 chunk),关键词 + board 加分够用;省去启动时跑 embedding 的开销,端上启动更快
- Memory 默认 Noop embedding --- 让最小部署完全不依赖外部 API;用户开启 OpenAI 兼容 embedding 后才进 vector 阶段
- 三阶段顺序设计 --- cache 命中 0 ms / FTS 命中 ms 级 / vector 才几十 ----- 几百 ms,先便宜后贵
- FTS 早停门槛
- 当 BM25 已经很高时,跳过向量阶段省 latency 与 embedding API 调用
- chunker 共用 --- src/memory/chunker.rs 的 chunk_markdown 同时被 HardwareRag 和 Memory 用,512 token / 标题分段 / 段落兜底
- SQLite bundled --- 端上零依赖,单二进制即可分发
0x02 HardwarRag 基础
HardwareRag是一个轻量的本地RAG索引,HardwareRag是适用于外设场景的实用离线RAG层,用于把硬件datasheet(.md/.txt,带可选PDF支持)切片、解析pin别名,并基于关键字把相关片段与别名注入到agent/LLM的上下文中,能显著提高L LM在硬件控制与解释上的可靠性。
2.1 基本功能
因为大家会比较熟悉"Memory 检索管线",因此我们本篇主要来看看 HardwarRag,其主要功能如下。
- 索引: 数据手册、参考手册、寄存器映射(PDF → 分块、嵌入向量)。
- 检索: 用户查询("打开 LED")时,获取相关片段(例如目标开发板的 GPIO 部分)。
- 注入: 添加到 LLM 系统提示或上下文。
- 结果: LLM 生成准确的、开发板特定的代码。
其特色如下:
- 没有向量、没有embedding、没有重排一纯关键词
- 文件注释显式写:"Keyword retrieval(default)or semantic search via embeddings(optional)"---但当前代码里embedding路径未实现,就是keyword
- 触发条件:peripherals启用 + 配置了datasheet_dir
- 完全本地、单进程、零网络(PDF提取除外)
2.2 使用原因
ZeroClaw项目使用RAG的核心原因是硬件知识的精确性和实时性需求。
硬件文档的复杂性
- 引脚映射复杂:不同开发板的GPI0引I脚功能各不相同,需要精确的引脚别名映射
- 寄存器配置繁琐:STM32等MCU的寄存器配置需要准确的地址和位域信息
- 协议规范严格:I2C、SPI等通信协议需要精确的时序和数据格式
LLM知识的局限性
- 训练数据滞后:LLM的训练数据可能不包含最新的硬件型号和规格
- 细节精度不足:通用LLM难以记住所有开发板的具体引脚定义和内存映射
- 版本差异问题:同一芯片系列的不同版本可能存在细微但关键的差异
硬编码方案的问题
- 维护困难:每新增一种硬件都需要修改代码
- 扩展性差:无法支持用户自定义的硬件配置
- 灵活性低:难以适应不同版本和变种的硬件
RAG的解决方案
- 实时文档检索:从本地数据手册中检索最新、最准确的硬件信息
- 上下文增强:将精确的硬件规格注入到LLM的上下文中
- 动态知识更新:用户可以随时添加新的数据手册,系统立即生效
- 安全可靠:基于权威数据手册确保操作的正确性
2.3 具体应用场景
常见使用场景举例
- 会话回答:接收到用户问题→memory_recall/content_search拉相关上下文→将top-k摘要拼入prompt→调用.llm_task生成回答。
- 代码辅助:检索项目相关片段/记忆→将检索到的代码片段注入到claude_code或llm_task的prompt→生成补丁或实现。
- 文档问答与知识图谱:FTS/bm25用于高置信关键字匹配,向量用于语义匹配;知识库(knowledge_ graph)提供结构化回溯。
比如,Arduino开发场景如下
- 场景描述:用户连接Arduino Uno,想要控制内置LED
- RAG作用:从arduino-uno.md中获取引脚13对应内置LED的信息
- 用户体验:用户只需说"打开LED 13 引脚",系统自动处理引脚映射
具体数据流如下:
- 用户输入:"打开LED 13 引脚"
- 别名检索:从arduino-uno.md中检索"red_1ed"对应的引脚13
- 上下文注入:将引脚信息注入到LLM的系统提示中
- 代码生成:LLM生成针对引脚13的GPI0控制代码
- 安全验证:验证引脚13确实是输出引脚且安全可用
| 步骤 | 组件 | 说明 |
|---|---|---|
| 1 | User Query | "Turn on LED pin 13" |
| 2 | Datasheet Retrieval | RAG Pipeline 检索数据手册 |
| 3 | LLM Context Enhancement | 增强 LLM 上下文 |
| 4 | Code Generation | 生成代码 |
| 5 | Security Check | 验证引脚13确实是输出引脚且安全可用 |
0x03 HardwarRag 实现
HardwarRag 实际功能是文档检索:RAG(检索增强生成)流水线,将数据手册片段、寄存器映射和引脚定义输入到 LLM 上下文。
3.1 流水线概览
RAG数据流如下:数据手册→别名解析→索引构建→用户输入 →上下文注入→LLM增强→精确控制。
即,把该外设的datasheet放入配置的datasheet目录→agent加载形成HardwareRag→ 用户/流程询问"如何接线/控制X外设"时,agent用RAG拉取相关片段并构造prompt→调用代码生工具→产生代码补丁/脚本→(可选)通过写入/执行工具应用并运行。
数据流向概览如下:

3-数据流
我们再进一步可以展开为两阶段:入库 和 检索。
3.1.1 入库流程
入库流程(启动时一次完成,全在内存)

3-入库流程
3.1.2 检索流程
检索流程(每次用户消息一次)的总体流程如下,此处涵盖了上下文注入阶段。
bash
用户消息 + 配置的 boards 列表 + chunk_limit
|
|
▼
引脚别名注入
rag.pin_alias_context(query, boards)
→ 词法匹配 "red led" → "red_led: 13"
|
|
▼
关键词检索
rag.retrieve(query, boards, limit)
- 把 query 拆成 token(去掉 ≤2 字符)
- 对每个 chunk:每命中一个 token +1 分;
chunk.board ∈ boards 再 +2 分
- 排序 → 截断 limit
|
|
▼
拼接 prompt 上下文
"[Pin aliases for query]\n..."
"+ "[Hardware documentation]\n--- src (board) ---\n""
|
|
▼
注入到下一次 LLM 调用的 system/user prompt
3.1.3 新增外设流程
在前两个阶段基础上,我们推断出,如果新接了一个外设之后,ZeroClaw会把PDF/MD型datasheet转为可检索片段(HardwareRag),并把这些片段注入LLM上下文;再由生成工具(例如claude_code/llm_task/codex_cli)根据上下文生成驱动/控制代码,最终写入或执行需要额外的写入/执行流程与安全批准。
关键实现点如下:
- 读取与索引:HardwareRag::load会扫描datasheet目录并切片/解析(支持PDF需开启rag-pdf)。
- 上下文注入:agent在生成调用前通过build_hardware_context把pin-alias与检索到的片段入prompt。
- 生成与执行链:生成代码由工具负责(示例:claude_code/claude_code_runner/llm_task odex_cli),生成后可通过file_write/file_edit写入并用shell执行(或在tmux runner中交互)。
3.2 实现类
HardwareRag 是具体实现类。
rust
/// Hardware RAG index --- loads and retrieves datasheet chunks.
pub struct HardwareRag {
chunks: Vec<DatasheetChunk>,
/// Per-board pin aliases (board -> alias -> pin).
pin_aliases: HashMap<String, PinAliases>,
}
公开接口(主要方法/结构):
- DatasheetChunk: 包含 board:Option,source: String,content:String。
- HardwareRag::load(workspace_dir,datasheet_dir):扫描dir,读取文件(可选PDF),解析别名,ch unk文档并建立索引。
- HardwareRag::pin_aliases_for_board(board):返回某板的别名映射。
- HardwareRag::pin_alias_context(query,boards):当查询匹配别名时,生成类似 Pin aliases for q uery\nboard:alias=pin"的上下文文本片段。
- HardwareRag::retrieve(query,boards,limit):按关键字检索并返回最相关的 DatasheetChunk引用。 len()/is_empty():索引元信息。
关键组件与流程
- 文档切分与摄取:chunker.rs与各类memory后端的写入路径(memory::sqlite、memory::qdrant、memory::postgres、memory::markdown)负责把文本/文件分片并入库(支持PDF文本提取,见file_r ead.rs的PDF支持)
- 向量化(Embeddings):embeddings.rs定义EmbeddingProvider,支持openai/openrouter/ custom;配置可通过memory与embedding_routes路由覆盖(见memory::resolve_embedding_config
- 存储层(Vectors+FTS):可选后端包括SqliteMemory(FTS5+向量融合)、QdrantMemory(外部向量DB)、以及Postgres(pgvector)(见qdrant.rs、sqlite.rs、knowledge_graph_pg.rs)
- 多阶段检索管道:retrieval.rs实现RetrievalPipeline(默认stages:cache>fts→vector>),支持hot cache、FTS 早返回值(fts_early_return_score)与hybrid(tuning via vector_weigh t/keyword_weight).
- 检索接口/工具:memory_recall.rs、content_search.rs、knowledge_tool.rs等工具暴露检索能力给agent/LLM;工具返回带score的条目并可应用时间/limit/namespace过滤。
- 生成与注入:检索结果被注入到LLM提示或llm_task/claude_code的上下文中,形成RAG:检索 (符号/语义)>拼接提示>模型生成响应或代码。
数据摄取与解析细节:
- 文件收集:递归收集.md/.txt(与rag-pdf feature下的.pdf) 1.3.2切片:使用chunker::chunk_markdown(. ,max_tokens=512)将长文切成可注入片段。
- Pin别名解析:parse_pin_aliases 支持两种格式---alias:pin/ alias=pin 和 Markdown 表格行(alias丨pin);结果规范化为小写并以
- 板卡识别:infer_board_from_path 以文件 stem作为 board,并处理generic命名约定。
检索算法(实现与评分):
- 关键字检索:把查询拆词(丢弃短词len<=2),对每个chunk的小写内容做字符串contains> 检测,score=匹配词数。
- 板卡偏好加权:若chunk 的 board在请求的boards列表中,额外+2分。
- 排序与截断:按分降序排序、截断到1imit并返回chunk引l用(不返回显式score元数据)
在agent中的使用点:
- build_hardware_context会调用pin_alias_context 与 retrieve,把pin-alias+datasheet片段拼成一个额外上下文注入到LLM提示中,用于把自然语言映射为硬件命令/解释>
- 配置/加载:agent启动时可从配置指定datasheet目录并调用HardwareRag::load;onboard向> 导提示与datasheet配置相关选项。
配置点(在哪里控制RAG行为)
- memory: backend, retrieval_stages, embedding_provider, embedding_model, vector_weight/keyword_weight,embedding_dimensions,fts_early_return_score 等。
- embedding_routes:按hint路由到不同embedding 提供商与模型(见memory::resolve_embeddi ng_config)
- 后端专有设置:memory.qdrant(URL、collection、API key)、memory.postgres(pgvector)等。
3.3 详细流程
详细逻辑流程如下。
3.3.1 添加数据手册(RAG)
数据来源
- 配置项datasheet_dir(如datasheets/)
- -文件类型:.md、.txt、可选.pdf(feature rag-pdf,靠pdf-extract)
- -文件名=board tag(nucleo-f401re.md →board nucleo-f401re)
- generic/子目录或generic.md→不绑定board
因此,如果要添加数据,我们可以将 .md 或 .txt 文件放入 docs/datasheets/(或你的 datasheet_dir)。按开发板命名文件:nucleo-f401re.md、arduino-uno.md。
引脚别名(推荐)
添加 ## Pin Aliases 部分,以便代理可以将"红色 LED"映射到引脚 13:
shell
# 我的开发板
## 引脚别名
| 别名 | 引脚 |
|-------------|-----|
| red_led | 13 |
| builtin_led | 13 |
| user_led | 5 |
或使用键值格式:
makefile
## 引脚别名
red_led: 13
builtin_led: 13
PDF 数据手册
使用 rag-pdf 特性时,ZeroClaw 可以索引 PDF 文件:
css
cargo build --features hardware,rag-pdf
将 PDF 放入数据手册目录。它们会被提取和分块用于 RAG(检索增强生成)。
3.3.2 初始化阶段 (Indexing Phase)
数据源加载

3-数据源加载
数据流向概览
| 步骤 | 组件 | 输入/输出 |
|---|---|---|
| 1 | User Query | "turn on red led" |
| 2 | Hardware RAG Index & Retrieval | 索引与检索 |
| 3 | LLM Context Enhancement | 上下文增强 |
| 4 | Code Generation & Execution | 代码生成与执行 |
数据源类型
| 文件类型 | 扩展名 | 示例 |
|---|---|---|
| Markdown Files | .md | nucleo-f401re.md |
| Text Files | .txt | generic.txt |
| PDF Files | stm32f401.pdf |
File System Scanner 方法
| 方法 | 功能 |
|---|---|
collect_md_txt_paths() |
收集 Markdown 和 Text 文件路径 |
collect_pdf_paths() |
收集 PDF 文件路径 (with rag-pdf feature) |
Content Extraction 方法
| 文件类型 | 提取方法 |
|---|---|
| .md / .txt | std::fs::read_to_string() |
pdf_extract::extract_text_from_mem() |
引脚别名解析
parse_pin_aliases() 函数完成了引脚别名解析。

3-引脚别名解析
输入格式
| 格式 | 示例 | ||
|---|---|---|---|
| Markdown Table | ` | red_led | 13 |` | |
| Key-Value | red_led: 13 |
输出格式
| 类型 | 结构 |
|---|---|
| HashMap | "red_led" → 13 |
"builtin_led" → 13 |
引脚别名映射
| 别名 (Alias) | 引脚号 (Pin) |
|---|---|
| red_led | 13 |
| builtin_led | 13 |
功能说明
parse_pin_aliases() 函数用于解析 Markdown 文档中的 Pin Aliases 部分,支持两种格式:
- Markdown 表格格式 - 标准的管道符表格
- 键值对格式 -
alias: pin的简单文本格式
输出为一个 HashMap,将别名字符串映射到引脚编号(u32 类型)。
文档分块处理

3-文档分块处理
分块流程
| 步骤 | 组件 | 说明 |
|---|---|---|
| 1 | Full Document Content | 完整文档内容 |
| 2 | chunk_markdown() |
分块处理 |
| 最大 token 数: 512 | ||
| 保留语义边界 | ||
| 3 | Vec |
分块结果向量 |
DatasheetChunk 结构
| 字段 | 示例值 |
|---|---|
board: Some("nucleo-f401re") |
特定板卡内容 |
board: None |
通用内容 (generic content) |
分块特点
| 特性 | 说明 |
|---|---|
| max_tokens | 512 tokens |
| 语义边界保留 | Preserves semantic boundaries |
| 板卡关联 | 每个 chunk 可关联特定板卡或通用 |
3.3.3 查询处理阶段
引脚别名上下文生成
pin_alias_context() 函数

3-pin_alias_context
输入参数
| 参数 | 类型 | 示例值 |
|---|---|---|
| query | String | "red led" |
| boards | Vec | "nucleo-f401re", "arduino-uno" |
| pin_aliases | HashMap | {"nucleo-f401re": {"red_led": 13, "user_led": 5}} |
处理流程
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | Tokenize query | "red", "led" |
| 2 | Match against alias keys | "red_led" 包含 "red" 和 "led" |
| 3 | Generate context lines | 生成上下文行 |
输出格式
ini
[Pin aliases for query]
nucleo-f401re: red_led = pin 13
匹配逻辑
| 查询词 | 别名 | 匹配结果 |
|---|---|---|
| "red" + "led" | "red_led" | ✅ 匹配成功 |
| "user_led" | ❌ 不匹配 |
文档片段检索
retrieve() 函数

3-retrieve
输入参数
| 参数 | 类型 | 示例值 |
|---|---|---|
| query | String | "led" |
| boards | Vec | "nucleo-f401re" |
| limit | usize | 5 |
处理步骤
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | Tokenize query | 提取长度 > 2 的词 → "led" |
| 2 | Score each chunk | 计算每个 chunk 的分数 |
| - Base score | 内容中匹配词的数量 | |
| - Board boost | chunk.board 匹配 query boards 时 +2 | |
| 3 | Sort | 按分数降序排序 |
| 4 | Return | 返回前 limit 个 chunks |
输出格式
| 类型 | 结构 |
|---|---|
| Vec<&DatasheetChunk> | 引用向量 |
board: Some("nucleo-f401re") |
|
content: "Pin 13: LED" |
评分机制
| 评分项 | 计算方式 |
|---|---|
| Base score | 匹配词在内容中出现的次数 |
| Board boost | +2 (如果 chunk.board 匹配查询 boards) |
示例匹配
| 查询 | 匹配内容 | Board |
|---|---|---|
| "led" | "Pin 13: LED" | nucleo-f401re |
3.3.4 上下文注入阶段
系统提示构建
Agent Loop Integration 中,会调用load_hardware_context_prompt()来插入硬件信息。
完整流程
sql
hardware::boot() ──→ load_hardware_context_prompt() ──→ Final System Prompt
│ │ │
▼ ▼ ▼
Loads HardwareRag Reads HARDWARE.md [Pin aliases]
Retrieves aliases Reads devices/<alias>.md [Hardware docs]
Reads skills/*.md
展开如下:

3-上下文注入阶段
启动流程
| 步骤 | 函数 | 操作 |
|---|---|---|
| 1 | crate::hardware::boot() |
加载 HardwareRag,获取设备别名 |
| 2 | load_hardware_context_prompt() |
读取硬件上下文文件 |
| 3 | 生成 | Final System Prompt |
加载的文件
| 文件路径 | 说明 |
|---|---|
~/.zeroclaw/hardware/HARDWARE.md |
硬件主文档 |
~/.zeroclaw/hardware/devices/.md |
设备别名文档 |
~/.zeroclaw/hardware/skills/*.md |
技能文档 |
Final System Prompt 结构
| 部分 | 内容 |
|---|---|
| Pin aliases for query | nucleo-f401re: red_led = pin 13 |
| Hardware documentation | docs/datasheets/nucleo-f401re.md |
Pin 13: LED |
|
GPIO configuration details... |
动态上下文更新

3-动态上下文更新
运行时上下文更新流程
| 步骤 | 组件 | 操作 |
|---|---|---|
| 1 | POST /api/hardware/pin | API 请求 |
| 2 | Gateway Hardware Context Endpoints | 网关硬件上下文端点 |
| 3 | File System | 写入 ~/.zeroclaw/hardware/devices/.md |
| 4 | Next Agent Request | 下一次代理请求 |
| - boot() re-reads files from disk | 从磁盘重新读取文件 | |
| - New context injected into LLM prompt | 新上下文注入 LLM 提示 |
数据流向
sql
POST /api/hardware/pin ──→ Gateway Hardware Context Endpoints ──→ File System
│
▼
Next Agent Request
(boot() re-reads + context injection)
关键操作
| 操作 | 说明 |
|---|---|
| API 调用 | POST /api/hardware/pin |
| 文件更新 | ~/.zeroclaw/hardware/devices/ |
| 上下文刷新 | boot() 重新读取文件 |
| 提示注入 | 新上下文注入 LLM prompt |
完整端到端的用户交互流程
完整数据流如下
scss
User Query ──→ Agent (识别硬件) ──→ HardwareRag (处理)
│
▼
Context Injection ──→ LLM (生成代码) ──→ Tool Execution ──→ Response
展开如下:

3-完整端到端的用户交互流程
端到端流程步骤
| 步骤 | 阶段 | 操作 |
|---|---|---|
| 1 | 用户输入 | "Turn on the red LED on my Nucleo board" |
| 2 | 硬件查询识别 | 检测连接的外设: "nucleo-f401re-0" |
| 3 | HardwareRag 处理 | a) 加载数据手册 b) 解析引脚别名 c) 检索相关 chunks d) 生成引脚别名上下文 |
| 4 | 上下文注入 | 将引脚别名和硬件文档注入系统提示 |
| 5 | LLM 生成代码 | 生成 gpio_write(13, state) 代码 |
| 6 | 工具执行 | 调用 gpio_write 工具,执行硬件操作 |
| 7 | 响应用户 | "Red LED turned on successfully!" |
关键数据
| 项目 | 值 |
|---|---|
| 检测到的外设 | nucleo-f401re-0 |
| 引脚别名 | red_led → 13 |
| 生成的函数 | set_red_led(state: bool) |
| 工具调用 | gpio_write(13, true) |

TransFormer-封面
0xFF 参考
本文使用 markdown.com.cn 排版