惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
Hacker News: Ask HN
Hacker News: Ask HN
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
The Exploit Database - CXSecurity.com
The Hacker News
The Hacker News
Security Latest
Security Latest
Attack and Defense Labs
Attack and Defense Labs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Scott Helme
Scott Helme
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
WordPress大学
WordPress大学
雷峰网
雷峰网
L
LangChain Blog
Y
Y Combinator Blog
I
Intezer
V
Vulnerabilities – Threatpost
Apple Machine Learning Research
Apple Machine Learning Research
The Last Watchdog
The Last Watchdog
Simon Willison's Weblog
Simon Willison's Weblog
W
WeLiveSecurity
P
Privacy International News Feed
V
Visual Studio Blog
T
The Blog of Author Tim Ferriss
A
About on SuperTechFans
AI
AI
博客园 - Franky
Hacker News - Newest:
Hacker News - Newest: "LLM"
Martin Fowler
Martin Fowler
K
Kaspersky official blog
U
Unit 42
S
Schneier on Security
Webroot Blog
Webroot Blog
Know Your Adversary
Know Your Adversary
T
Tor Project blog
aimingoo的专栏
aimingoo的专栏
Engineering at Meta
Engineering at Meta
G
GRAHAM CLULEY
美团技术团队
TaoSecurity Blog
TaoSecurity Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Google DeepMind News
Google DeepMind News
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Jina AI
Jina AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
P
Proofpoint News Feed
爱范儿
爱范儿
F
Fortinet All Blogs
有赞技术团队
有赞技术团队

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角 .NET Core 原生 DI 扩展之基于名称的注入实现 - 元视角
使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角
飞鸿踏雪 · 2024-03-16 · via 元视角

随着 ChatGPT 的兴起及其背后的 AIGC 产业不断升温,向量数据库已成为备受业界瞩目的领域。FAISSMilvusPineconeChroma、Qdrant 等产品层出不穷。市场调研公司 MarketsandMarkets 的数据显示,全球向量数据库市场规模预计将从 2020 年的 3.2 亿美元增长至 2025 年的 10.5 亿美元,年均复合增长率高达 26.8%。这表明向量数据库正从最初的不温不火逐步演变为大模型的 “超级大脑”。向量数据库,不仅解决了大模型在 “事实性” 和 “实时性” 方面的固有缺陷,还为企业重新定义了知识库管理方式。此外,与传统关系型数据库相比,向量数据库在处理大规模高维数据方面具有更高的查询效率和更强的处理能力。因此,向量数据库被认为是未来极具潜力的数据库产品。然而,面对非结构化数据的挑战,传统的关系型/非关系型数据库并未坐以待毙,开始支持向量数据库的特性,PostgrelSQL 就是其中的佼佼者。本文探讨的主题是:如何利用 PostgreSQL 实现向量检索以及全文检索。

从大模型的内卷说起

截止目前,OpenAI 官方支持的上下文长度上限为 128K,即 128000 个 token,这意味着它最多可支持约 64000 个汉字的内容。当然,如果考虑到输入、输出两部分的 token 消耗数量,这 64000 个汉字多少要大打折扣。除此以外,国外的 Claude 2、国内的 Moonshot AI,先后将上下文长度提升到 200K 量级,这似乎预示着大模型正在朝着 “更多参数”“更长上下文” 两个方向“内卷”。众所周知的是,现阶段大模型的训练往往需要成百上千的显卡,不论是“更多参数”还是“更长上下文”,本质上都意味着成本增加,这一点,从 Kimi 近期的宕机事件就可以看出。

AI 眼中的显卡集群 AI 眼中的显卡集群

所以,为什么说 RAG(Retrieval-Augmented Generation) 是目前最为经济的 AI 应用开发方向呢?因为它在通过外挂知识库 “丰富” 大模型的同时,能更好地适应当前 “上下文长度受限” 这一背景。诚然,如果有一天,随着技术的不断发展,芯片的价格可以变得低廉起来,大模型可以天然地支持更长的上下文长度,或许大家就不需要 RAG 了。可至少在 2024 年这个时间节点下,不管是企业还是个人,如果你更看重知识库私有化和数据安全,RAG 始终是绕不过去的一个点。同济大学在 Retrieval-Augmented Generation for Large Language Models: A Survey 这篇论文中提出了 RAG 的三种不同范式,如下图所示:

三种 RAG 范式的对比 三种 RAG 范式的对比

实现向量检索

PostgreSQL,可以说是目前世界上功能最强大的数据库系统之一。针对这个观点,请你先不要急着反驳我。因为,你可以利用这个时间来阅读下面这篇文章《技术极简主义:一切皆用 Postgres》。更不必说,这篇文章里的内容,对于整个 PostgreSQL 生态而言,不过是沧海一粟。单单是向量检索这个话题,你可以看到诸如 pasepgvectorpg_embeddingpg_vectorize 等解决方案。这里,博主以 pgvector 这个插件为例来进行说明。

pgvector 基本使用

CREATE EXTENSION IF NOT EXISTS vector;

首先,我们使用上面的 SQL 语句来启用 pgvector 插件。此时,我们可以创建一张表来存储向量数据:

CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));

接下来,准备若干条数据进行查询测试,可以注意到,这里的向量为三维向量:

INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]'), ('[7,8,9]');

现在,假设我们有一个向量为:[3,2,1],如何查询距离该向量最近的数据呢?

# L2/欧式距离
SELECT *, embedding <-> '[3,2,1]' AS distance FROM items ORDER BY distance ASC;

# 向量内积
SELECT *, (embedding <#> '[3,2,1]') * -1 AS distance FROM items ORDER BY distance ASC;

# 余弦相似
SELECT *, (1 - (embedding <=> '[3,1,2]')) AS distance FROM items ORDER BY distance ASC;

注意到,这里我们有三种表示距离的方法,即:欧式距离、向量内积和余弦相似,下面是对应的查询结果:

L2/欧式距离 L2/欧式距离

向量内积 向量内积

余弦相似 余弦相似

可以注意到,不管是哪一种方案,距离 [3,2,1] 最近的向量始终都是 [1,2,3],这完全符合我们的预期。在 RAG 的场景中,向量通常由 Embedding 模型来生成,其维度可能会达到 1024 甚至更高。考虑到,pgvector 最多支持 16000 个维度的向量,所以,当你准备开发一款 AI 应用时,PostgreSQL 可以兼顾关系型数据库和向量数据库。对于高维度的向量计算,你可以使用索引来加快查询速度,pgvector 支持 HNSWIVFFlat 两种索引:

索引HNSWIVFFlat
特点查询性能较好;构建时间慢、占用内存多查询性能较差;构建时间快、占用内存少
原理多层图查询将向量划分为列表,搜索距离最近的子列表
支持类型vector, halfvec、bit、sparsevecvector

这里要注意的是,虽然 pgvector 最多支持 16000 个维度的向量,但不管是 HNSW 还是 IVFFlat 索引,它们最多支持 2000 个维度的向量。此外,halfvec、bit、sparsevec 这三种类型目前都还是 unreleased 状态,所以,两种索引算是平分秋色。下面是创建索引的 SQL 语句语法说明:

# HNSW 索引
## L2/欧式距离索引
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
## 向量内积索引
CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);
## 余弦相似索引
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);

# IVFFlat 索引
## L2/欧式距离索引
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
## 向量内积索引
CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);
## 余弦相似索引
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

pgvector 与 EFCore 集成

对于像博主这样使用 C#/.NET 进行开发的朋友,我们可以使用 pgvector-dotnet 这个项目,这里以 Entity Framework Core 为例:

dotnet add package Pgvector.EntityFrameworkCore

为了继续沿用上面的例子,为此,我们定义下面的实体类。一个非常实用的小技巧是:如果你不确定向量的维数,可以不用写这个 [Column] 特性。当然,整张表中的向量维数应该是相同的,就像两个矩阵在相乘时应该满足特定的条件一样,你还记得是什么样的条件吗?:smile:

class Item
{
    public int Id { get; set; }

    [Column(TypeName = "vector(3)")]
    public Vector? Embedding { get; set; }
}

此时,我们就可以像平时一样向数据库中插入一个向量:

ctx.Items.Add(new Item { Embedding = new Vector(new float[] { 1, 2, 3 }) });
ctx.SaveChanges();

当然,查询会稍微不同,因为 LINQ 中没有计算距离相关的表达式:

var embedding = new Vector(new float[] { 1, 1, 1 });
var items = await ctx.Items
    .OrderBy(x => x.Embedding!.L2Distance(embedding))
    .Take(5)
    .ToListAsync();

除了上面的 L2Distance,我们还可以使用 MaxInnerProductCosineDistance 两个函数,它们都属于 Vector 类型的扩展方法,这里不再详细展开说明。

// HNSW 索引
modelBuilder.Entity<Item>()
    .HasIndex(i => i.Embedding)
    .HasMethod("hnsw")
    .HasOperators("vector_l2_ops")
    .HasStorageParameter("m", 16)
    .HasStorageParameter("ef_construction", 64);
    
// IVFFlat 索引
modelBuilder.Entity<Item>()
    .HasIndex(i => i.Embedding)
    .HasMethod("ivfflat")
    .HasOperators("vector_l2_ops")
    .HasStorageParameter("lists", 100);

可以注意到,我们依然可以使用 HNSWIVFFlat 这两种索引,并且其参数与 pgvector 完全一致。实际上,如果你使用过 LangChain 或者 Semantic Kernel 这类 LLM 框架,你就会发现没有银弹,它们正在做的事情,无非就是屏幕前的你和我,想要去努力搞清楚的东西。如下图所示,LangChainSemantic Kernel 均支持使用 PostgreSQL 作为其向量数据库:

PostgreSQL 在 LangChain 中的应用 PostgreSQL 在 LangChain 中的应用

PostgreSQL 在 Semantic Kernel 中的应用 PostgreSQL 在 Semantic Kernel 中的应用

此时此刻,想来你应该明白了 RAG 的工作原理。当我们输入一个问题后,首先会由 Embedding 模型将其转化为一个向量,然后我们从向量数据库中找出距离该向量最近的若干条记录,并将这些记录对应的文本信息作为 LLM 的上下文。此时,LLM 就会整合这些信息并输出最终答案。

利用向量检索查询金庸武侠小说 利用向量检索查询金庸武侠小说

可是这样就足够了吗?我想,或许还不太够。因为如果你按照这个思路实践下来,你会发现通过向量检索出来的内容,其相关性或许并不强,所以,现在业界主要的精力都放在了 Retrieval 上,提出了诸如 RerankRewrite 的方案,例如针对相关性排序、对输入的问题进行重写或者同时生成多个相似问题等。

实现全文检索

在开发基于 Semantic Kernel 的 AI 应用时,其实我对于 PostgreSQL 的认知完全是渐进式的,在熟悉了 pgvector 插件的使用以后,我开始尝试去了解 PostgreSQL 中的类型。最终,它们促使我实现了某些 Semantic Kernel 中没有的功能。此前关注 FastGPT 这个项目的时候,我注意到,它除了支持常规的向量检索外,还支持全文检索。因此,我想知道基于全文检索的检索方案,相比于向量检索的检索方案是否更具有性价比。毕竟,通过 LLM 生成向量需要消耗 token 以及时间,并且当文件内容发生变化时更新向量很麻烦。所以,下面我们来聊一聊如何利用 PostgreSQL 实现全文检索,即:在对输入的问题进行分词处理后,直接去检索含有该关键字的内容。

PostgreSQL 对全文检索的支持

pg_vector 不同,PostgreSQL 天然支持全文检索特性,唯一不同的地方在于分词器。比如,中文和英文的分词规则显然不同。此时,我们就可以使用 zhparserpg_jiebanlpbambooSCWS 等支持中文分词的插件。不用这些插件是否可以呢?经过博主测试,官方自带的分词器,在处理类似 “西施”、“勾践” 的关键字时,无法检索到相关的内容,可见分词器的影响还是很大的。可惜,插件丰富的 PostgreSQL 和 Nginx 一样,安装第三方模块总避免不了折腾一番。下面,博主将以 pg_jieba 为例进行说明:

PostgreSQL 自带的分词器 PostgreSQL 自带的分词器

如图所示,PostgreSQL 自带了一个分词器 default ,你可以使用下面的 SQL 语句进行查询:

select * from pg_ts_parser

在此基础上,PostgreSQL 创建一组用于全文检索的配置,使用下面的 SQL 语句进行查询:

select * from pg_ts_config

此时,你会得到看到下面的结果:

PostgreSQL 自带的全文检索配置 PostgreSQL 自带的全文检索配置

其中:3722default 这个分词器的唯一标识,并且这里面目前没有针对中文的配置。PostgreSQL 主要提供三类函数来支持全文检索,它们分别是文档解析函数查询解析函数以及排序函数

  • 文档解析函数:即 to_tsvector() 函数,负责对指定的字符进行分词。例如,当输入下列命令时,将返回字符串中的每个单词及其序号:
SELECT to_tsvector('simple','nothing is true everything is permitted')

to_tsvector() 函数输出 to_tsvector() 函数输出

  • 查询解析函数:即 to_tsquery()、plainto_tsquery()、phraseto_tsquery() 和 websearch_to_tsquery() 这四个函数,它们负责将指定的字符串转化为表达式。例如,以下命令表示,包含 nothing 或者同时包含 is 和 true:
SELECT to_tsquery('simple','nothing | (is & true)')

to_tsquery() 函数输出 to_tsquery() 函数输出

更多的细节,请参考官方文档:http://www.postgres.cn/docs/12/textsearch-controls.html

  • 排序函数:即 ts_rank() 和 ts_rank_cd(),它们负责计算给定文档与特定查询中的相关性。例如,下面的例子展示了内容与关键词间的相关性:
select
	'nothing is true everything is permitted' as content,
	'nothing | true' as keywords,
	ts_rank_cd(
	    to_tsvector('english','nothing is true everything is permitted'),
	    to_tsquery('english','nothing | true')
  	) as relevance

ts_rank_cd() 函数输出 ts_rank_cd() 函数输出

现在,结合以上知识,假设我们希望从数据中查询符合特定关键字的内容,我们可以像下面这样编写 SQL 语句:

SELECT
  t.*,
  ts_rank_cd(
    to_tsvector('english', t.content),
    to_tsquery('english', '范蠡 & 阿青 | 夫差')
  ) AS relevance
FROM
  "sk-default" t
WHERE
  t.content @@ to_tsquery('english', '范蠡 & 阿青 | 夫差')
ORDER BY
  relevance DESC;

其中, @@ 操作符的作用是将待检索的字段与检索条件连接起来。此时,结果如下图所示:

PostgreSQL 全文检索结果展示 PostgreSQL 全文检索结果展示

结果显示,我们给定的关键词与内容之间的相关性为:0.025,通过这个信息,理论上我们就可以找出我们真正需要的信息。而这便是目前 PostgreSQL 在全文检索方面能达到的程度。对此,大家是否感到满意呢?

使用 pg_jieba 增强中文检索

《越女剑》是金庸先生创作的短篇武侠小说,以春秋末期的吴越争霸作为历史背景,讲述了阿青被范蠡引荐到宫中教授士兵剑术,最终帮助越王勾践复仇雪耻的故事。在教授剑术的过程中,阿青暗自喜欢上了范蠡,而范蠡则喜欢西施,内心深感不忿的阿青找上范蠡和西施,在相互争执的过程中,阿青竹棒上的劲力伤及西施,自此后世留下了 “西子捧心” 的传奇佳话。从这个故事中,我们不难发现,范蠡、阿青、夫差等历史人物悉数登场。但是,我们通过全文检索仅仅得到了一条记录,这是为什么呢?答案便在于上面提到的分词器。所以,下面我们尝试使用 pg_jieba 这个插件来强化检索效果,关于这个插件的安装请参考官方文档,博主这里提供一个开箱即用的方案供大家参考:

link-img

https://github.com/Regularly-Archive/2024/tree/main/PostgreSQL.Embedding/Docker

接下来,我们首先需要启用 pg_jieba 插件:

CREATE EXTENSION IF NOT EXISTS pg_jieba;

现在,如果我们再次查询分词器信息:

select * from pg_ts_parser

你会注意到,这里多了几个分词器,这些便是 pg_jieba 插件提供的分词器:

pg_jieba 插件提供的分词器 pg_jieba 插件提供的分词器

如果你继续查询全文检索的配置信息,你将会由类似的发现:

select * from pg_ts_config

此时,你会看到下面的结果:

pg_jieba 插件提供的全文检索配置 pg_jieba 插件提供的全文检索配置

现在,我们就可以直接使用 jiebacfg 这个配置来进行查询:

SELECT
  t.*,
  ts_rank_cd(
    to_tsvector('jiebacfg', t.content),
    to_tsquery('jiebacfg', '范蠡 & 阿青 | 夫差')
  ) AS relevance
FROM
  "sk-default" t
WHERE
  t.content @@ to_tsquery('jiebacfg', '范蠡 & 阿青 | 夫差')
ORDER BY
  relevance DESC;

有时候,你可能希望创建自己的全文检索配置,此时,你可以使用下面的 SQL 语句:

# 拷贝一个现有配置
CREATE TEXT SEARCH CONFIGURATION my_english (copy = english);
# 创建一个全新的配置
 CREATE TEXT SEARCH CONFIGURATION my_jieba (parser = 'jieba');

当然,你还可以通过创建索引来提升全文检索的效率,使用下面的 SQL 语句即可:

CREATE INDEX idx_full_text_search ON "sk-default" USING GIN(to_tsvector('jiebacfg', content));

最终,博主采用全文检索 + 模糊匹配(LIKE) 的方式完成了整个设想,因为其中包含相关性及检索数目的控制,所以,我个人感觉这个方案比单纯的向量检索要更为经济一点,毕竟,通过 Embedding 模型生成向量需要时间和金钱,除非你使用一个本地的离线模型来完成这个工作。到目前为止,知识库可以使用向量检索或者全文检索,我认为,在实际场景中完全可以将这两种方案结合起来使用。如下图所示,当博主向知识库提问的时候,它已经可以给出非常接近事实的答案,虽然这个答案依然令人捧腹,哈哈!

使用全文检索后的知识库输出展示 使用全文检索后的知识库输出展示

至此,基于 PostgreSQL 的全文检索方案完成落地,大家对这个方案的表现还满意吗?

本文小结

或许,我每次写博客的时候,都没有办法做到 100% 的完全遵从写作计划。就像这篇文章,我一开始的规划仅仅是写 pgvector,但我总感觉这些零零散散的内容,不值得专门去写一篇文章。直到拖延了将近半个月以后,我发觉基于 PostgreSQL 的全文检索方案可以作为 Semantic Kernel 的一个改进点,经过一番权衡和上下求索,总算是完成了这篇文章的写作。本文的核心要点其实只有两个,其一是选择 PostgreSQL 作为当下 AI 应用开发中的向量数据库其二是利用 PostgreSQL 的全文搜索特性强化知识库检索。其实,采用向量还是文本进行检索,本质上只是一种查询媒介的选择,真正有价值的是在检索过程中的种种思考。比如,如何对搜索结果进行排序、如何根据用户的输入产生相似的问题等等,从表面上看,这是一个 AI 应用开发的问题,然而,从更深刻地角度来看,这其实是一个信息检索的问题。作为李彦宏先生曾经的粉丝,我非常希望百度可以在搜索引擎中引入 AI 技术,毕竟,RAG 这个方向的 AI 应用,其着眼点还是在信息的检索上。虽然大家都在质疑 Kimi 的 200K 上下文到底是不是真的 200K,可如果一件事情能通过 RAG 这种更经济、更环保的方式实现,还能让用户感到心悦诚服的话,何乐而不为呢?最后,请允许我推荐一个最近正在使用的 AI 搜索引擎:秘塔AI搜索,真的比百度好用! 🫡