7.1 为什么数据库要懂向量
RAG(Retrieval-Augmented Generation)的核心:把文档切块 → 每块转成 embedding 向量 → 用户提问 → 把问题也转成向量 → 数据库里找最相似的文档块 → 送给 LLM 生成答案。
以前常见做法:Postgres + pgvector,或者单独用 Pinecone/Qdrant。libSQL 把向量搜索内置进 SQLite,RAG 应用从此不需要多装一个服务。
7.2 向量列类型:F32_BLOB
libSQL 加了一个新列类型:F32_BLOB(N)——定长 32 位浮点数组(blob 底层是 4*N 字节)。
CREATE TABLE documents (
id INTEGER PRIMARY KEY,
title TEXT,
content TEXT,
embedding F32_BLOB(1536) -- OpenAI text-embedding-3-small 的维度
);
7.3 写入向量
libSQL 提供 vector32() 函数把 JSON 数组转成 blob:
INSERT INTO documents (title, content, embedding) VALUES (
'libSQL 简介',
'libSQL 是 Turso fork...',
vector32('[0.01, -0.32, 0.77, ...1536 个浮点]')
);
JS 端写入:
import OpenAI from 'openai';
const openai = new OpenAI();
async function embed(text: string) {
const r = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: text,
});
return r.data[0].embedding; // number[] 长度 1536
}
const vec = await embed('libSQL 简介正文...');
await db.execute({
sql: `INSERT INTO documents (title, content, embedding)
VALUES (?, ?, vector32(?))`,
args: ['libSQL 简介', '正文...', JSON.stringify(vec)],
});
7.4 建索引:libsql_vector_idx
没有索引就是全表扫描——千行以内可接受。规模大要建近似最近邻索引:
CREATE INDEX documents_embedding_idx
ON documents (libsql_vector_idx(embedding));
这会生成一个DiskANN(磁盘友好的 ANN 算法)索引——不是精确,而是 recall > 0.9x 的近似。百万向量下毫秒级查询。
7.5 查询:vector_top_k
SELECT d.id, d.title,
vector_distance_cos(d.embedding, vector32(?)) AS dist
FROM vector_top_k('documents_embedding_idx', vector32(?), 10) AS t
JOIN documents d ON d.rowid = t.id
ORDER BY dist;
vector_top_k(索引名, 查询向量, K) 返回 rowid 与距离,通常 JOIN 回主表取字段。
7.6 完整 RAG 小项目
Step 1 — Schema
-- migrations/001_chunks.sql
CREATE TABLE chunks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT NOT NULL,
content TEXT NOT NULL,
embedding F32_BLOB(1536) NOT NULL
);
CREATE INDEX chunks_emb_idx ON chunks (libsql_vector_idx(embedding));
Step 2 — 灌入
import fs from 'node:fs';
import { db } from './db';
async function ingest(filePath: string) {
const text = fs.readFileSync(filePath, 'utf8');
const chunks = splitIntoChunks(text, 500); // 500 字符一块
for (const c of chunks) {
const vec = await embed(c);
await db.execute({
sql: `INSERT INTO chunks(source, content, embedding)
VALUES(?, ?, vector32(?))`,
args: [filePath, c, JSON.stringify(vec)],
});
}
}
Step 3 — 检索 + 生成
async function ask(question: string) {
const qvec = await embed(question);
const { rows: hits } = await db.execute({
sql: `SELECT c.content, vector_distance_cos(c.embedding, vector32(?1)) d
FROM vector_top_k('chunks_emb_idx', vector32(?1), 5) t
JOIN chunks c ON c.rowid = t.id
ORDER BY d`,
args: [JSON.stringify(qvec)],
});
const context = hits.map((h: any) => h.content).join('\n---\n');
const answer = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: '基于下面上下文作答:\n' + context },
{ role: 'user', content: question },
],
});
return answer.choices[0].message.content;
}
7.7 距离函数
- vector_distance_cos(a, b)
- 余弦距离(1 - cos 相似度)。推荐给 OpenAI embedding——它们已单位归一化。
- vector_distance_l2(a, b)
- L2 欧氏距离。部分模型(BGE、部分 BERT)用这个更合适。
索引构建时参数 metric= 决定用哪种:
CREATE INDEX idx ON t(
libsql_vector_idx(emb, 'metric=cosine', 'max_neighbors=20')
);
7.8 混合检索:向量 + SQL 过滤
SQL 的优势在于:关键词 + 元数据 + 向量可以一条语句混合:
SELECT c.content
FROM vector_top_k('chunks_emb_idx', vector32(?), 20) t
JOIN chunks c ON c.rowid = t.id
WHERE c.source LIKE 'docs/%' -- 只看文档
AND c.created_at > 1700000000 -- 最近的
LIMIT 5;
Pinecone 虽有 metadata filter,但 join 三张表、分组统计这类 SQL 能力是缺失的。Turso 的卖点就是"一条 SQL 搞定 RAG 复合查询"。
7.9 规模建议
| 向量数 | 推荐策略 |
|---|---|
| < 10K | 不建索引,全表扫描最快 |
| 10K – 1M | libsql_vector_idx + 默认参数 |
| 1M – 10M | 参数调优:max_neighbors=30, efConstruction=200 |
| > 10M | 分表 / 分库 / 专业向量库(Qdrant) |
7.10 Drizzle 里用向量
Drizzle 目前把 F32_BLOB 当成普通 blob——可以用 sql 模板访问:
import { sql } from 'drizzle-orm';
const qvec = JSON.stringify(await embed(question));
const hits = await db.all(sql`
SELECT c.content,
vector_distance_cos(c.embedding, vector32(${qvec})) as d
FROM vector_top_k('chunks_emb_idx', vector32(${qvec}), 5) t
JOIN chunks c ON c.rowid = t.id
ORDER BY d
`);
小结
libSQL 把向量搜索做成一等公民——F32_BLOB 列、libsql_vector_idx 索引、vector_top_k 查询。对中小型 RAG(< 1M 向量)来说,Turso 一个库就够,省掉了专门的向量数据库。下一章讲 schema 迁移与版本管理。