文档 / 搜索 / LingCode Cloud / 向量检索
📘 参考 ● AI 更新于 2026-06-11

向量检索

一句话:向量检索按含义而非精确关键词来查找行——这是语义检索和 RAG 的基础。你在每一行旁边存一个向量(embedding)(一串数字),然后按它与查询向量的接近程度对行排序。LingCode Cloud 运行的 Postgres 带有 pgvector 扩展,并且可以替你生成这些向量(托管向量化),所以你永远不必把向量化模型的密钥塞进应用里。加一列 vector(N),用 lingcode.vector.embed() 把文本向量化,再用 lingcode.vector.search() 排序。

如果你只用过 LIKE '%term%' 或全文检索,那么向量检索是一种不同的思路,值得在动手用它之前先理解清楚。关键词检索问的是「哪些行包含这个词?」向量检索问的是「哪些行表达的大致是同一个意思?」正是这个差别,让「找相似」「从我的文档里给出答案」以及「能扛住同义词和拼写错误的检索」成为可能。本页讲清楚向量是什么、它为什么有效,如何配置一个列,你真正要写的两个 SDK 调用,以及一个端到端的小型 RAG 示例。

为什么含义胜过关键词

一个向量(embedding)是一串数字——一个向量——由 AI 模型从一段文本生成。模型经过训练,使得含义相近的文本在这个向量空间里落到彼此靠近的位置,而不相关的文本则相距很远。「我怎么重置密码?」和「我忘了我的登录凭证」几乎没有共同的词,但它们的向量却挨得很近,因为它们表达的是同一个意思。

这就是全部诀窍。一旦每一行都带上了向量,「找出和这个查询相似的行」就变成了「找出向量与查询向量最接近的那些行」。关键词检索做不到这一点——它只能匹配你输入的那些词。向量检索匹配的是意思,所以它能扛住同义词、换种说法和拼写错误,这也正是语义检索和检索增强生成(RAG)背后的原理:先取出最相关的行,再交给模型来撰写答案。

托管向量化:应用里不放模型密钥

生成一个向量通常意味着调用一个向量化模型,也就意味着要有一个 API 密钥。有了 LingCode Cloud 的托管向量化lingcode.vector.embed() 会在服务端替你生成向量——你的应用永远不持有向量化模型的密钥,也永远不直接和模型厂商对话。你发文本进去,拿向量出来。(想自带模型?你仍然可以把预先算好的向量直接传给 search。)

配置一个向量列

向量存在类型为 vector(N) 的列里,其中 N 是向量的维度——模型生成的数字数组的长度(例如 1536)。你在迁移(migration)里添加这个列,方式和添加任何其他列一样。运行迁移的方法见数据库指南

create table docs (
  id         bigint generated always as identity primary key,
  title      text not null,
  text       text not null,
  embedding  vector(1536)
);

对于大数据集,加一个合适的向量索引,让相似度查询在表增大时仍然保持快速——没有它,检索就得扫描每一行。

维度必须处处一致。vector(N) 里的 N、你存进去的向量,以及你用来检索的查询向量,三者长度必须相同。混用维度(存 1536 维向量却用 768 维向量来查)是向量检索里最常见的一个 bug。用同一个模型给你存储的文本和查询做向量化,数字才能对得上。

两个 SDK 调用

你要做的一切都在 lingcode.vector.* 之下。和每个 SDK 调用一样,这两个都返回 { data, error }

vector.embed(input) —— 把文本变成向量

input 是一个字符串或一个字符串数组。它用在两个地方:在写入时填充你的列,以及在检索时把用户的查询向量化。

const { data, error } = await lingcode.vector.embed('How do I reset my password?');
// data = {
//   embedding:   number[],     // 单个字符串输入对应的向量
//   embeddings:  number[][],   // 数组输入时每项一个向量
//   model:       string,       // 所用的向量化模型
//   dimensions:  number        // 每个向量的长度
// }

vector.search({ table, column, embedding, limit?, metric? }) —— 按接近程度排序

传入要检索的表和向量列,再加上查询向量。结果以行的形式返回,按距离排序(最近的在前)。

const { data, error } = await lingcode.vector.search({
  table:     'docs',
  column:    'embedding',
  embedding: data.embedding,   // 来自 vector.embed() 的查询向量
  limit:     5,                // 1–200
  metric:    'cosine'          // 'cosine'(默认)| 'l2' | 'ip'
});
// data = rows[],按距离排序

结果会和普通查询一样经过行级安全过滤——用户永远只在自己有权看到的行上排序。

典型流程

两个阶段:写入时向量化一次,查询时再向量化并检索。把向量化放在写入时做,这样检索本身就只是一次快速查找。

写入时——向量化并存储

当一篇文档到来时,把它的文本向量化,并把向量存进这一行的 embedding 列。

const { data } = await lingcode.vector.embed(doc.text);

await lingcode.from('docs').insert({
  title:     doc.title,
  text:      doc.text,
  embedding: data.embedding
});

查询时——把问题向量化,然后检索

用同样的方式把用户的查询向量化,然后让你的行对它进行排序。

const { data: q } = await lingcode.vector.embed(userQuery);

const { data: hits } = await lingcode.vector.search({
  table:     'docs',
  column:    'embedding',
  embedding: q.embedding,
  limit:     5
});

一个小型 RAG 示例

检索增强生成无非就是:把问题向量化 → 检索出最相关的行 → 把这些行喂给模型来撰写答案。检索这一步正是本页所讲的;撰写答案那一步是一次普通的模型调用。

async function answer(userQuery) {
  // 1. 把问题向量化。
  const { data: q } = await lingcode.vector.embed(userQuery);

  // 2. 按含义取出最接近的行。
  const { data: hits } = await lingcode.vector.search({
    table:     'docs',
    column:    'embedding',
    embedding: q.embedding,
    limit:     5
  });

  // 3. 把最靠前的行作为上下文交给模型来撰写答案。
  const context = hits.map(r => r.text).join('\n\n');
  // ...用 `context` + `userQuery` 调用你的答案模型...
  return context;
}

因为检索基于含义,用户可以用自己的话来提问,仍然能拉到正确的行——这正是 RAG 答案区别于关键词查找的地方。

选择距离度量

metric 决定了两个向量之间的「接近程度」如何衡量。选你的向量化模型推荐的那个;如果拿不准,cosine 是稳妥的默认值。

何时该用混合检索

语义检索擅长含义,但可能会漏掉精确的词元——一个产品 SKU、一个用户名、一个特定的错误码。关键词(全文)检索正相反:对精确词项很准,但对同义词视而不见。混合检索用倒数排名融合(RRF)把两者合在一起,于是像「Aurora Pro 蓝牙连不上」这样的查询,既能匹配字面上的产品名,能匹配这条抱怨的含义。只要查询里既有一个具体词项又有一段模糊描述,就该用它。

底层原理(REST)

这些 SDK 调用映射到你后端基础 URL https://lingcode.dev/api/cloud/be/<backend-id> 下的这些端点。用你的 anon key 或一个已登录的用户令牌认证;结果会和任何其他查询一样经过行级安全过滤。

向量化与向量检索

POST /vector/embed
  { input }
  → { embedding, embeddings, model, dimensions }

POST /vector/search
  { table, column, embedding, limit?, metric? }
  → ranked rows

关键词(全文)检索

POST /search/text
  { table, column, query, is_tsvector?, limit? }
  → matching rows

混合检索(全文 + 语义)

用倒数排名融合(RRF)把关键词排名和含义排名融合起来。你既要传原始的 query 文本(用于全文),也要传它的 embedding(用于语义);权重用来调节每一边各贡献多少。

POST /search/hybrid
  {
    table,
    text_column,
    vector_column,
    query,
    embedding,
    id_column?,
    text_is_tsvector?,
    metric?,
    limit?,
    full_text_weight?,
    semantic_weight?,
    rrf_k?
  }
  → fused, ranked rows

快速检查清单

  • 让存储向量和查询向量的维度保持一致——同一个模型、同一个 N
  • 在写入时向量化,这样检索就只是一次查询,而不是每行一次模型往返。
  • 表变大后就加一个向量索引
  • 除非你的模型另有说明,否则默认用 cosine
  • 当查询里既带一个精确词项又带一段描述时,使用混合检索