首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >三位一体的 AI 嵌入式数据库:TriviumDB 入门介绍

三位一体的 AI 嵌入式数据库:TriviumDB 入门介绍

原创
作者头像
晨星成焰
修改2026-08-17 16:24:10
修改2026-08-17 16:24:10
860
举报
文章被收录于专栏:AI开发相关AI开发相关

三位一体的 AI 嵌入式数据库:TriviumDB 入门

cover
cover

通过TDB处理的 .tdb 文件类型,同时解决向量检索、知识图谱和文档查询。这就是 TriviumDB 想做的事——成为 AI 应用领域的 SQLite。本文用当前github版本 TDB 0.7.4 实测的结果带路,30 秒就能上手。

一、为什么 AI 应用会陷入"三库割裂"

做 RAG 或 Agent 记忆系统,大概率踩过这个坑:一套系统里,向量存一个库、关系存一个库、属性又存一个库。

  • 语义向量进 Qdrant 或 Milvus
  • 人物、事件之间的关联进 Neo4j
  • 文本、时间戳、来源等元数据进 SQLite 或 MongoDB

每个库有自己一套 ID、一套查询语法、一套生命周期。删一条记录得操作三个库,任何一步出错都会造成 ID 对不上;想"先向量检索、再沿关系扩散"这种 AI 场景里很自然的组合查询,得跨三个库手工聚合,延迟和复杂度一起往上飙。

TriviumDB 的解法很直接:三个数据模型、一个引擎、一个文件、一套 ID。

二、命名初心

TriviumDB 用纯 Rust 写,把向量检索(Vector)、属性图谱(Graph)和文档型元数据(Document)原生融合在同一个存储内核里。

Trivium 是拉丁语,意为"三条道路的交汇"——正好对应向量、图谱、文档三条数据道路在同一个引擎里交汇。

定位是 AI 应用领域的 SQLite:没有 server、没有端口、没有配置文件,pip installnpm install 之后直接当库用。当前最新版本 0.7.4(2026-08-15 发布),同时提供 Python(PyO3)、Node.js(napi-rs)、Rust 三套绑定,外加一个 CLI 工具 tdb(支持 REPL 和终端 TUI 可视化)。

三、核心模型:一个 node_id,三种身份

这是 TriviumDB 最该先理解的设计。在它里面,一个节点(node)就是一切:

Trinity 模型图
Trinity 模型图

一个 u64 ID 同时映射到向量、Payload 和边表。插入是原子的,删除也是原子的。不会出现"向量库里删了、图库里还留着"这种不一致——因为根本只有一个库。

存储上支持两种布局。单文件模式(Rom)把向量、payload、边全部塞进一个 .tdb,复制走人;Mmap 模式把向量页拆到 .tdb.vec,适合更大的向量池按需零拷贝加载:

存储双布局
存储双布局

四、30 秒上手:一段代码讲完三位一体

npm install triviumdb 之后,这段代码完整演示了"存向量 + 存文档 + 建图关系 + 混合检索":

代码语言:js
复制
const { TriviumDB } = require("triviumdb");

const db = new TriviumDB("memory.tdb", 3, "f32", "normal");

// 两个节点:向量 + 文档 payload 一步写入

const id1 = db.insert([0.12, -0.45, 0.78], { text: "小明喜欢吃苹果" });

const id2 = db.insert([0.08, -0.52, 0.81], { text: "小红送了小明一箱苹果" });

// 一条图关系:id1 与 id2 之间的因果边

db.link(id1, id2, "caused\_by", 0.95);

// 向量检索(expandDepth=0:纯相似度排序)

const hits = db.search([0.10, -0.48, 0.80], 5, 0, 0.1);

for (const h of hits) console.log(`[${h.id}] score=${h.score.toFixed(4)} | ${JSON.stringify(h.payload)}`);

这是我在 0.7.4 上跑出来的结果:

代码语言:ts
复制
===== 纯向量检索 (expandDepth=0) =====

[1] score=0.9995 | {"text":"小明喜欢吃苹果"}

[2] score=0.9993 | {"text":"小红送了小明一箱苹果"}

余弦相似度范围 -1, 1,越接近 1 越像。再看一个更直观的分布(查询向量 [1,0,0]):

代码语言:ts
复制
[1] score=1.0000 | {"tag":"A 完全一致"}

[2] score=0.9899 | {"tag":"B 很接近"}

[3] score=-1.0000 | {"tag":"C 方向相反"}

区分度比较干脆:相关的拉到 0.99 附近,无关的会掉到 0 上下甚至负分。

让图参与排序

第三步如果把 expandDepth 从 0 改成 2,检索就变成"向量锚定 → Top-K → 图谱扩散 → 最终排序"的混合检索。同一个查询实际输出:

代码语言:txt
复制
===== 图谱扩散检索 (expandDepth=2) =====

[2] score=3.7475 | {"text":"小红送了小明一箱苹果"}

[1] score=2.2489 | {"text":"小明喜欢吃苹果"}

两个变化值得注意。排序翻过来了——id2 因为有出边被扩散能量抬到第一。分数也变了——从余弦值变成融合图传播的"热度"分数,所以会大于 1。这就是图谱扩散在起作用:纯向量只看语义相近,混合检索还会看谁和谁有关联。RAG、推荐、Agent 记忆这类场景里,这往往是决定结果质量的关键。

检索管线
检索管线

五、图查询:内置类 Cypher 引擎 TQL

除了 link()neighbors()getEdges() 这套命令式 API,TriviumDB 还内置了一个类 Cypher 的查询语言 TQL(Trivium Query Language),支持三种入口:MATCH(图遍历)、FIND(文档过滤)、SEARCH(向量检索)。

建一个三人社交小图,查"所有被认识、且年龄大于 26 的人":

代码语言:js
复制
const alice = db.insert([0.50, 0.10, 0.20], { name: "Alice", age: 30 });

const bob   = db.insert([0.45, 0.15, 0.25], { name: "Bob", age: 25 });

const carol = db.insert([0.20, 0.60, 0.10], { name: "Carol", age: 35 });

db.link(alice, bob, "knows", 0.9);

db.link(alice, carol, "knows", 0.7);

db.link(bob, carol, "knows", 0.5);

console.log(db.neighbors(alice, 1));   // [ 2, 3 ]

console.log(db.tql("MATCH (a)-[:knows]->(b) WHERE b.age > 26 RETURN b"));

0.7.4 实测输出:

代码语言:txt
复制
alice 的 1 跳邻居: [ 2, 3 ]

TQL: MATCH (a)-[:knows]->(b) WHERE b.age > 26 RETURN b ->

  b: id=3 payload={"age":35,"name":"Carol"}

  b: id=3 payload={"age":35,"name":"Carol"}

返回了两行 Carol,因为 Alice→Carol、Bob→Carol 两条 knows 边都满足条件——TQL 按匹配路径展开结果,在图数据库里这是标准语义。

文档过滤走类 MongoDB 语法($eq/$ne/$gt/$lt/$in/$and/$or):

代码语言:js
复制
db.tql('FIND {name: "Alice"} RETURN \*')

// → {"\_":{"id":1,"numEdges":2,"payload":{"age":30,"name":"Alice"}}}

六、自动切换:1 万节点以下走精确,以上走 ANN

大多数向量库要你手动决定用什么索引、调什么参数。TriviumDB 的做法是自动路由:

  • 小数据(<1 万节点):BruteForce 精确检索,rayon 多核并行,100% 召回。
  • 大数据(≥1 万节点):自研的 QuIVer ANN 索引自动构建并接管,无需任何配置。

QuIVer 的设计是把 BQ 二进制量化签名 + Vamana 图导航 + f32 原向量按需精排组合在一起(冷热分离)。官网有张热路径示意图 感兴趣可以去官网看看相关内容

官网示意图:QuIVer ANN 路径
官网示意图:QuIVer ANN 路径

*左侧 BQ2 把原始向量压成二进制签名做粗筛,中间 Vamana 剪枝的 ANN 图做候选导航,右侧用存储的 f32 原向量算 cosine 精确重排,最终返回 Top-K 候选。这种"近似找范围 + 精确算分数"的分层设计,是它能以不到 1.3 GB 热内存吃下百万级数据集的关键。*

公开的基准数据是:在 12 个百万级数据集上以 <1.3 GB 热内存实现 ≥88% Recall@10 @ 13-41K QPS,多线程吞吐量比 DiskANN Rust 快 2.5-3.3×,比 hnswlib 快 3.6-4.7×,比 FAISS HNSW 快 3.8-4.9×(论文 arXiv:2605.02171)。

## 七、和主流方案怎么比

| 维度 | SQLite | LevelDB | Neo4j | SurrealDB | **TriviumDB** |

|---|---|---|---|---|---|

| 文档数据 | ✅ SQL | ❌(裸 KV) | ⚠️ 属性 | ✅ SurrealQL | ✅ JSON + $gt/$in |

| 向量检索 | ⚠️ 需外挂 | ❌ | ❌ 需插件 | ✅ DiskANN | ✅ 自研 QuIVer |

| 图谱遍历 | ⚠️ JOIN 模拟 | ❌ | ✅ Cypher | ✅ 图查询 | ✅ 原生邻接表 + TQL |

| 嵌入式 | ✅ | ⚠️ 多文件目录 | ❌ JVM 服务 | ✅ 可切换 | ✅ 单 .tdb |

| 混合检索 | ❌ | ❌ | ❌ | ⚠️ 手动实现 | ✅ 向量 + 图扩散 |

| 键值存储 | ⚠️ | ✅(主场) | ❌ | ⚠️ | ⚠️ |

它的生态位是"嵌入式 × 多模型"——这个象限里目前人很少。LanceDB、Chroma 是纯向量,Kuzu 是纯图,SurrealDB 全能但本质偏服务化。TriviumDB 的差异化在于:原生内核(不是 SQLite 上拼插件)、自研 ANN、向量→图谱扩散的混合检索管线。

在我自己搭建的RAG项目中,AI让我用 LevelDB 起步做嵌入,结果向量、文档过滤、图遍历这些都要自己写胶水,写到最后维护成本和服务型方案差不多。后面我反应过来直接用 TriviumDB 这个三合一引擎不就行了。。。:一套 ID 解决所有事,不用担心三个库之间的状态不一致。

边界也要说清楚。官网描述它定位单机场景,没有分布式能力,社区规模还小(当前 94 个 commit、0.7.x 时代)维护人员也少目前只有我下班空闲时刻做点代码审查和主作者进行主要开发。如果需要支撑千万并发的高可用后端,请选择大型集群化组件避免小作坊悲剧(┬┬﹏┬┬)。它最适合的是个人知识库、Agent 记忆、RAG 原型、中小规模 AI 应用——尤其是希望零运维、单文件交付的场景。

七、现在就能跑起来

代码语言:bash
复制
# Python

pip install triviumdb

# Node.js

npm install triviumdb

# Rust

cargo add triviumdb

不用本地装编译环境,官方已为所有主流平台预编译好二进制。完整 API 参考、TQL 语法、Hook 插件开发指南见git上的项目 docs 目录。想直观了解它的存储与检索管线,可以去 官网 看交互式 3D 模型。

如果也在做 RAG 或 Agent 应用,不妨给它 30 秒——装一个、insert 几条数据、跑一次混合检索,亲手试一下"一个文件搞定三件事"的体验。


_*注:本文代码与输出均基于 TriviumDB 0.7.4 在 Windows x64 上的 napi-rs 绑定实测(Node.js 22),配套演示脚本见如下

代码语言:js
复制
/* triviumdb-intro-demo.js
 * 为《TriviumDB 入门科普》文章准备的演示脚本(v2:每个 Demo 独立文件)
 * 运行环境:本机 0.7.4 (triviumdb.win32-x64-msvc.node, 构建于 2026-08-16)
 * 本机配置: 显卡-4060ti16g 内存-DDR5 32G CPU-I513490f
 * 运行方式:node triviumdb-intro-demo.js
 */
const fs = require("fs");
const os = require("os");
const path = require("path");

const BINDING = "C:/Users/Administrator/Desktop/TriviumDB/triviumdb.win32-x64-msvc.node";
const { TriviumDB } = require(BINDING);

function freshDb(name) {
  const p = path.join(os.tmpdir(), `tdb-article-${name}.tdb`);
  for (const f of [p, p + ".lock", p + ".wal", p + ".quiver"]) {
    try { fs.unlinkSync(f); } catch {}
  }
  return { p, db: new TriviumDB(p, 3, "f32", "normal") };
}
function sep(title) { console.log("\n===== " + title + " ====="); }

// ---------- Demo 1:30 秒入门(insert + link + search) ----------
sep("Demo 1: 30 秒入门");
{
  const { db } = freshDb("demo1");
  const id1 = db.insert([0.12, -0.45, 0.78], { text: "小明喜欢吃苹果" });
  const id2 = db.insert([0.08, -0.52, 0.81], { text: "小红送了小明一箱苹果" });
  db.link(id1, id2, "caused_by", 0.95);

  const pure = db.search([0.10, -0.48, 0.80], 5, 0, 0.1);
  console.log("纯向量检索 (expandDepth=0):");
  for (const h of pure) console.log(`  [${h.id}] score=${h.score.toFixed(4)} | ${JSON.stringify(h.payload)}`);

  const expanded = db.search([0.10, -0.48, 0.80], 5, 2, 0.1);
  console.log("图谱扩散检索 (expandDepth=2):");
  for (const h of expanded) console.log(`  [${h.id}] score=${h.score.toFixed(4)} | ${JSON.stringify(h.payload)}`);

  console.log("nodeCount =", db.nodeCount());
  db.close();
}

// ---------- Demo 2:图谱 + TQL ----------
sep("Demo 2: 图谱操作与 TQL");
{
  const { db } = freshDb("demo2");
  const alice = db.insert([0.50, 0.10, 0.20], { name: "Alice", age: 30 });
  const bob   = db.insert([0.45, 0.15, 0.25], { name: "Bob", age: 25 });
  const carol = db.insert([0.20, 0.60, 0.10], { name: "Carol", age: 35 });
  db.link(alice, bob, "knows", 0.9);
  db.link(alice, carol, "knows", 0.7);
  db.link(bob, carol, "knows", 0.5);

  console.log("alice 的 1 跳邻居:", db.neighbors(alice, 1));
  console.log("alice 的出边:", JSON.stringify(db.getEdges(alice)));

  const rows = db.tql("MATCH (a)-[:knows]->(b) WHERE b.age > 26 RETURN b");
  console.log("TQL: MATCH (a)-[:knows]->(b) WHERE b.age > 26 RETURN b ->");
  for (const r of rows) {
    for (const k of Object.keys(r)) {
      console.log(`  ${k}: id=${r[k].id} payload=${JSON.stringify(r[k].payload)}`);
    }
  }

  const findRows = db.tql('FIND {name: "Alice"} RETURN *');
  console.log("TQL: FIND {name: \"Alice\"} RETURN * ->");
  for (const r of findRows) console.log("  ", JSON.stringify(r));
  db.close();
}

// ---------- Demo 3:余弦相似度分布 ----------
sep("Demo 3: 余弦相似度分布");
{
  const { db } = freshDb("demo3");
  db.insert([1.0, 0.0, 0.0], { tag: "A 完全一致" });
  db.insert([0.98, 0.14, 0.0], { tag: "B 很接近" });
  db.insert([-1.0, 0.0, 0.0], { tag: "C 方向相反" });
  const hits = db.search([1.0, 0.0, 0.0], 5, 0, -1);
  console.log("查询 [1,0,0]:");
  for (const h of hits) console.log(`  [${h.id}] score=${h.score.toFixed(4)} | ${JSON.stringify(h.payload)}`);
  db.close();
}

// ---------- Demo 4:close 后立即重开(0.7.4 B1 修复验证) ----------
sep("Demo 4: close 后可立即重开(0.7.4)");
{
  const p = path.join(os.tmpdir(), "tdb-article-demo4.tdb");
  for (const f of [p, p + ".lock", p + ".wal", p + ".quiver"]) {
    try { fs.unlinkSync(f); } catch {}
  }
  const db1 = new TriviumDB(p, 3, "f32", "normal");
  db1.insert([0.1, 0.2, 0.3], { note: "close 前写入" });
  db1.close();

  const db2 = new TriviumDB(p, 3, "f32", "normal"); // 若失败会抛错
  console.log("重开后 nodeCount =", db2.nodeCount(), "| get(1).payload =", JSON.stringify(db2.get(1).payload));
  db2.close();
  console.log("Demo 4 通过 ✓(0.7.4 起 close 后可立即重开)");
}

console.log("\n全部 Demo 完成 ✓");

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 三位一体的 AI 嵌入式数据库:TriviumDB 入门
    • 一、为什么 AI 应用会陷入"三库割裂"
    • 二、命名初心
    • 三、核心模型:一个 node_id,三种身份
    • 四、30 秒上手:一段代码讲完三位一体
      • 让图参与排序
    • 五、图查询:内置类 Cypher 引擎 TQL
    • 六、自动切换:1 万节点以下走精确,以上走 ANN
    • 七、现在就能跑起来
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档