Skip to content

Commit d98895a

Browse files
committed
docs(ai): 增加长名词规范化检索方案分享文档
- 详细介绍基于 pg_trgm、intarray、rjieba 和 pgvector 的混合检索方案 - 讲解中文地点长名词分词与词典维护策略 - 展示检索体系架构、SQL策略与性能优化要点 - 附加示例代码与数据库索引建议 - 提供质量评估、监控指标及降级处理方案 - 包含案例走查与工程实践细节,加强实用指导
1 parent 9445486 commit d98895a

File tree

4 files changed

+286
-0
lines changed

4 files changed

+286
-0
lines changed
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
title: 使用 pg_trgm、intarray、rjieba、pgvector 打造“长名词”规范化检索:地点识别 + 语义向量的混合方案
2+
date: 2025-09-02 20:56:25
3+
category:[ai]
4+
tags:[pg_trgm, rjieba, pgvector, intarray, postgresql]
5+
6+
---
7+
8+
本文分享一个在生产中可落地的“长名词”规范化检索方案:以地点识别为核心,将中文分词与词典增强([rjieba](https://github.com/messense/rjieba-py))、PostgreSQL 的模糊匹配([pg_trgm](https://www.postgresql.org/docs/17/pgtrgm.html))、数组过滤([intarray](https://www.postgresql.org/docs/17/intarray.html))与语义向量([pgvector](https://github.com/pgvector/pgvector))进行混合召回与融合排序,兼顾召回率、可解释性与低延迟。
9+
10+
- 代码参考:
11+
- 地点级联模糊搜索函数:[aiguide-funcs.sql](../scripts/software/postgres/sqls/aiguide-funcs.sql)
12+
- 混合检索服务:[material_search_svc.py](../aiguide/domain/material/material_search_svc.py)
13+
- 地点模型与向量缓存序列化:[location_model.py](../aiguide/domain/location/location_model.py)
14+
15+
## 背景与问题定义
16+
17+
“长名词”常见于中文口语/社交文本,如“北京市圆明园遗址公园景区碧桐书院”和“北京圆明园碧桐书院”,“北京市故宫博物院坤宁宫”和“沈阳市沈阳故宫坤宁宫”这样的地点位置长名词。挑战在于:
18+
19+
- 别称/简称、行政区冗余、错别字、空格与标点差异;
20+
- 用户意图中既有地点链路(省市区/园区/场馆),也有自由描述;
21+
- 需要在模糊容错与结构化过滤之间取得平衡,并保证低延迟与可解释性;
22+
- 相同的地名存在于不同的城市
23+
24+
## 技术选型概览
25+
26+
- `rjieba`:中文分词与自定义词典,优先长词和地点词,支撑“长名词”的切分与归一化。
27+
- `pg_trgm`:三元组相似度(`%``<->`)提供容错匹配,支持别名、错别字与顺序扰动。
28+
- `intarray`:基于 `int[]` 的交集过滤(`location_ids && ...`),将“识别出的地点链路”用于结构化剪枝。
29+
- `pgvector``embedding <=> query_embedding` 进行语义相似度排序,兜底非精确文本匹配与跨表达召回。
30+
31+
## 系统架构(数据层—预处理—检索—融合)
32+
33+
- 数据层
34+
- `Location`: 存储树型结构的“地点”
35+
- `LocationAlias`: 存储“地点”的别名
36+
- `Material`(含 embedding 向量): 存储属于“长名词”的素材的语义向量
37+
- 预处理:并行执行“分词+地点识别”和“向量计算”,产出 location_ids 与 query_embedding。
38+
- 检索:首先通过 rjieba 对长名词进行分词,然后通过 pg-trgm 进行模糊匹配,最后通过 intarray 进行结构化剪枝。获得标准化的 `location_ids`
39+
-`location_ids` 时:地点过滤 + 语义向量排序;
40+
-`location_ids` 时:纯向量降级策略。
41+
- 融合:以 1 - 距离 作为基础分,可按需引入 trigram/别名命中加权与去重。
42+
43+
## rjieba 词典维护实践
44+
45+
- 词典结构与权重策略
46+
47+
- 自定义词典采用“词条 词频 词性”格式(空格分隔),词频用于调控分词偏好,词性建议用“nz/nt/ns”等以突出专名;
48+
- 长词优先:对“园区/场馆/商圈/景区全称”设置更高词频,降低被切碎或被短词覆盖的概率;
49+
- 别名等价:将常见别称、简称、口语化写法全部入库,词频略低于标准名,避免别名在无必要时压制标准名。
50+
51+
- 数据来源与建库流程
52+
53+
- 基础词库:来源于 Location 与 LocationAlias 表的 name/alias 字段,周期性全量导出;
54+
- 增量沉淀:采集线上查询日志与召回失败样本,挖掘 OOV(未登录词)并做人工校验后入库;
55+
- 行政区规范化:省/市/区/街道等层级名称统一规范(简繁、空格、标点),形成映射表供分词后归一。
56+
57+
- 热更新与版本管理
58+
59+
- 词典按日期/版本号落盘,应用侧维护“当前版本号”;
60+
- 热更新流程:生成新词典文件 → 校验(格式、重复、冲突)→ 原子替换 → 加载到分词器(失败回退旧版本);
61+
- 监控加载耗时与内存占用,避免在高峰期进行大体量重载。
62+
63+
- 消歧与冲突处理
64+
65+
- 最长优先 + 业务白名单:对易混淆短词(如“中关村”、“天安门”)通过白名单强制长词优先;
66+
- 别名归一:分词后将别名映射为标准名的 canonical form,便于与 search_location 的候选比对;
67+
- 上下文约束:结合上位行政区(如“北京”→“朝阳区”)进行候选过滤,减少跨城误配。
68+
69+
- 与 search_location 的协同
70+
71+
- 应用层将 rjieba 的 tokens(经归一化/去噪)以 text[] 传入 search_location(query_tokens);
72+
- search_location 在每一层用 trigram(%/<->)对 location.name 与 location_alias.alias 排序,配合 k_window 做滑动窗口扩展;
73+
- 实践建议:
74+
- tokens 中优先保留“地点长名词”,去掉无信息助词;
75+
- 针对层级词(市/区/园区)保留原顺序,有助于递归链路拼接;
76+
- 对明显错别字的 token 进行简单归一化(同音/近形),降低 trigram 的噪声成本。
77+
78+
- 评估与监控指标
79+
80+
- 词典覆盖率(对标标准名与别名)、OOV 比例、误切/漏切率;
81+
- 查询侧指标:地点识别成功率、最优链路深度分布、search_location 降级比例;
82+
- A/B:对比“仅向量”与“分词+地点识别+向量”的 nDCG、Hit@K 与人为可解释性。
83+
84+
- 性能优化要点
85+
86+
- 词典按行政区/业务域分片,按需加载核心子集;
87+
- 缓存高频查询的分词结果与标准化链路;
88+
- 限制超长输入的 token 数量与长度,保护下游 SQL 的窗口枚举;
89+
- 定期清理低价值别名,避免词典“膨胀”影响分词速度。
90+
91+
- 示例:在应用层加载与使用 rjieba(简化示意)
92+
93+
```python
94+
import rjieba
95+
from typing import List
96+
97+
# 启动时加载主词典与自定义词典
98+
rjieba.initialize()
99+
rjieba.load_userdict('/data/dicts/location_userdict.txt') # 行内:词条 词频 词性
100+
101+
def normalize_tokens(tokens: List[str]) -> List[str]:
102+
# 简单归一化示意:去空白/标点,别名到标准名映射(可查表)
103+
return [t.strip() for t in tokens if t.strip()]
104+
105+
def tokenize_query(q: str) -> List[str]:
106+
# 长词优先,保留地点相关词
107+
words = [w for w, tag, freq in rjieba.tokenize(q, withFlag=True, HMM=False)]
108+
return normalize_tokens(words)
109+
110+
# 传给 PostgreSQL 的 search_location(query_tokens) 作为 text[]
111+
# 例如:query_tokens := tokenize_query('去北京环球影城哈利波特魔法世界玩一天')
112+
```
113+
114+
- 预处理流水线(并行)
115+
116+
- 分词与地点识别:
117+
- 先用 rjieba 切分并做“长词优先”的词典匹配;
118+
- 结合 pg_trgm 产生候选,弥补分词/拼写噪声;
119+
- 通过多级匹配+最长链路选择生成 location_ids(省 → 市 → 区 → 点)。
120+
- 语义向量:并行计算 query 的 embedding,降低整体端到端延迟。
121+
- 两路结果共同驱动后续 SQL 策略选择(见下文)。
122+
123+
## 检索阶段与 SQL 策略
124+
125+
混合检索的核心在 [material_search_svc.py](../aiguide/domain/material/material_search_svc.py)
126+
127+
- 当识别到地点:
128+
- 使用 intarray 做结构化剪枝:location_ids && CAST(:location_ids AS int[])
129+
- 使用 pgvector 排序:embedding <=> CAST(:query_embedding AS vector)
130+
- 基础分为 (1 - distance)
131+
- 无地点或低置信度:
132+
- 退化为“纯向量检索”,在不牺牲鲁棒性的前提下降级。
133+
134+
示例(节选,两类 SQL 思路):
135+
136+
```sql
137+
-- 带地点过滤(简化示意)
138+
select id, (1 - (embedding <=> :query_embedding)) as score
139+
from material
140+
where status = 100
141+
and embedding is not null
142+
and location_ids && cast(:location_ids as int[])
143+
order by (embedding <=> :query_embedding)
144+
limit :limit;
145+
146+
-- 纯向量(降级)
147+
select id, (1 - (embedding <=> :query_embedding)) as score
148+
from material
149+
where status = 100 and embedding is not null
150+
order by (embedding <=> :query_embedding)
151+
limit :limit;
152+
```
153+
154+
此外,地点链路识别使用 SQL 存储函数进行“多级逐段匹配与扩展”,函数定义见:[aiguide-funcs.sql](../scripts/software/postgres/sqls/aiguide-funcs.sql) 的 search_location。其核心做法:按 token 窗口生成候选,对 location.name 与 location_alias.alias 分别用 % 过滤与 <-> 排序,逐级扩展形成最优链路。
155+
156+
## 数据库扩展与索引建议
157+
158+
```sql
159+
-- 启用扩展
160+
create extension if not exists pg_trgm;
161+
create extension if not exists intarray;
162+
create extension if not exists vector;
163+
164+
-- 示例索引(按需调整字段名/策略)
165+
-- 1) trigram:地点名与别名
166+
create index if not exists idx_location_name_trgm on location using gin (name gin_trgm_ops);
167+
create index if not exists idx_location_alias_trgm on location_alias using gin (alias gin_trgm_ops);
168+
169+
-- 2) intarray:素材表地点过滤
170+
create index if not exists idx_material_location_ids on material using gin (location_ids);
171+
172+
-- 3) pgvector:语义向量近似检索
173+
create index if not exists idx_material_emb2 on material using ivfflat (embedding vector_cosine_ops) with (lists = 100);
174+
```
175+
176+
实践要点:向量索引建议结合 probes 参数与定期重建;trigram 索引适合配合别名回表;数组索引能极大降低候选量。
177+
178+
## 性能优化与容量规划
179+
180+
- 并行化:分词/地点识别与向量计算并行,缩短 P95/P99。
181+
- 剪枝:先地点过滤再向量排序;阈值提前过滤(score_threshold)。
182+
- 批处理:DB 往返合并;必要时使用 pipeline 式多语句。
183+
- 缓存:rjieba 词典、别名映射、热门地点 embedding 预热。
184+
185+
## 质量评估与监控
186+
187+
- 标注集:覆盖别称、错别字、行政区冗余与顺序扰动的查询。
188+
- 指标:Recall/Precision、nDCG、Hit@K、空检率、降级比例。
189+
- 监控:慢查询、相似度分布、错误日志与样本采样回放。
190+
191+
## 失败与降级策略
192+
193+
- 无地点命中:纯向量方案保证兜底召回。
194+
- 向量服务不可用:trigram-only + 规则排序(别名优先、行政区近邻)。
195+
- 超时:熔断与重试,返回可解释的兜底结果。
196+
197+
## 工程实践要点
198+
199+
- 模块边界清晰:预处理(分词/识别)→ SQL 组装 → 执行与解析 → 融合与日志。
200+
- 可配置与热更新:词典、阈值、权重与索引参数。
201+
- 一致性:别名变更引起的缓存失效策略;定期 Vacuum/Analyze。
202+
203+
## 案例走查(简)
204+
205+
- 输入:"去北京环球影城哈利波特魔法世界玩一天";
206+
- 预处理:rjieba 产出长词候选,search_location(query_tokens:=['北京','环球影城','哈利波特','魔法世界']) 给出 location_ids 多级链路;
207+
- 检索:优先使用地点过滤 + 向量排序;若地点未命中则走纯向量;
208+
- 融合与输出:以 (1 - 距离) 为主分,并返回可解释的地点链路与命中依据。
209+
210+
## 附录
211+
212+
- 函数定义:[aiguide-funcs.sql](../scripts/software/postgres/sqls/aiguide-funcs.sql)
213+
- 检索服务实现:[material_search_svc.py](../aiguide/domain/material/material_search_svc.py)
214+
- 运行环境建议:PostgreSQL(pg_trgm、intarray、pgvector 已启用),Python 3.12+,rjieba 自定义词典按需加载。

knowledges/cloud/nvidia/ngc.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Nvidia 云服务
2+
3+
## FourCastNet
4+
5+
安装部署 FourCastNet
6+
7+
```shell
8+
docker pull nvcr.io/nim/nvidia/fourcastnet:1.0.0
9+
10+
export NGC_CLI_API_KEY=<替换为正式 key>
11+
export LOCAL_NIM_CACHE=~/.cache/nim
12+
mkdir -p $LOCAL_NIM_CACHE
13+
14+
docker run -d --name=fourcastnet \
15+
--runtime=nvidia --gpus all --shm-size 8g \
16+
-p 8000:8000 \
17+
-e NGC_CLI_API_KEY \
18+
-v $LOCAL_NIM_CACHE:/opt/nim/.cache \
19+
-u $(id -u) \
20+
nvcr.io/nim/nvidia/fourcastnet:1.0.0
21+
```
22+
23+
## Brev 使用
24+
25+
```shell
26+
# 使用 SSH 直接登录
27+
brev shell stable-diffusion
28+
29+
# 使用 VSCode 打开
30+
brev open fourcastnet-server
31+
32+
# print workspaces within active organization
33+
brev list
34+
35+
brev list --org nca-18846
36+
```
37+
38+
端口转发
39+
40+
```shell
41+
# 重定向网络端口到本地 <本地>:<远程>
42+
brev port-forward fourcastnet-server --port 8000:8000
43+
44+
# 该命令底层采用 SSH 端口转发技术。如需手动设置端口转发,可使用以下 SSH 命令格式:
45+
ssh -i ~/.brev/brev.pem -p 22 -L LOCAL_PORT:localhost:REMOTE_PORT ubuntu@INSTANCE_IP
46+
```
47+
48+
- ~/.brev/brev.pem 是您的 Brev 私钥
49+
- LOCAL_PORT 是本地机器上的端口号
50+
- REMOTE_PORT 是 Brev 实例上的端口号
51+
- INSTANCE_IP 是您实例的 IP 地址
52+
- ubuntu 是 Brev 实例的默认用户名
53+
File renamed without changes.

knowledges/技术图谱.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Rust 技术图谱
2+
3+
## LLM 客户端
4+
5+
- [RIG](https://github.com/0xPlaygrounds/rig): ⚙️🦀 在 Rust 中构建模块化且可扩展的 LLM 应用程序
6+
- [Graph LLM](https://github.com/a-agmon/rs-graph-llm): 用于在 Rust 中构建交互式多代理工作流系统的高性能框架。(基于 rig)
7+
8+
## 并发/并行
9+
10+
- [kanal](https://crates.io/crates/kanal): Kanal库是基于CSP(通信顺序进程)模型、用Rust实现的通道机制。它旨在通过提供支持多生产者和多消费者、并具备高效通信高级特性的通道,帮助程序员轻松构建高效的并发程序。该库专注于统一Rust代码中同步与异步部分之间的消息传递,同时兼顾高性能需求,为开发者提供了同步与异步API的灵活组合。
11+
12+
## WEB
13+
14+
- [axum](https://docs.rs/axum)
15+
16+
## 数据库
17+
18+
- [sqlx](https://github.com/launchbadge/sqlx)
19+
- [sea-query](https://github.com/SeaQL/sea-query/)

0 commit comments

Comments
 (0)