text-embedding-v4 + LanceDB + OSS:从专业概念到销售知识库案例讲解
这一套组合,本质上是在搭建一个 RAG 知识库检索系统。
所谓 RAG,可以简单理解为:
用户提问时,系统先从企业知识库里找出最相关的资料,再把这些资料交给大模型生成回答。
在这个链路里,text-embedding-v4、LanceDB、OSS 分别负责三件完全不同的事情:
text-embedding-v4:负责把文字变成向量
LanceDB:负责根据向量做相似度检索
OSS:负责存储向量、原文、元数据和索引文件
下面先讲专业概念,再讲销售知识库案例。
一、先理解几个核心专业概念
1. 什么是 Embedding?
Embedding,中文一般叫:
嵌入向量。
它的作用是:
把一段文字转换成一组数字。
例如:
客户觉得我们太贵了,怎么办?
经过 embedding 模型处理后,会变成类似这样的向量:
[0.021, -0.318, 0.772, ..., -0.056]
如果使用 text-embedding-v4 的默认配置,通常可以生成 1024 维向量。
也就是说,一句话会被转换成:
1024 个浮点数
这些数字不是随机的,而是表达了这句话的语义特征。
2. 为什么文字要变成向量?
因为普通数据库只能做关键词匹配。
例如用户问:
客户嫌贵怎么接?
但文档里写的是:
当客户提出价格异议时,应优先强调 ROI 和长期价值,而不是直接降价。
这两句话没有完全相同的关键词,但语义非常接近。
传统搜索可能搜不到。
但是 embedding 可以识别:
客户嫌贵
价格异议
报价太高
竞品更便宜
预算不足
这些表达在语义上是接近的。
所以,向量的价值是:
让系统不再只看“字面是否一样”,而是看“意思是否接近”。
3. 什么是 text-embedding-v4?
text-embedding-v4 是阿里云百炼里的嵌入模型,属于 Qwen3-Embedding 系列。
它在这个系统里的定位是:
语义翻译官。
它不负责保存数据,也不负责搜索数据。
它只负责一件事:
中文 / 英文 / 混合文本
↓
语义向量
例如:
客户说我们比竞品贵 30%,怎么回复?
转换成:
[0.034, -0.217, ..., 0.891]
这个向量后续会交给 LanceDB 检索。
4. text-embedding-v4 常见关键参数
在销售知识库场景里,最重要的参数有几个。
1)dimensions:向量维度
text-embedding-v4 支持多种维度,例如:
2048、1536、1024、768、512、256、128、64
维度越高,表达能力通常越强,但也会带来几个问题:
存储更大
索引更大
检索更慢
成本更高
对于销售知识库这种通用语义检索场景,通常建议从:
1024 维
开始。
原因是:
精度够用
成本可控
索引体积不会过度膨胀
不要一开始就直接上 2048 维。数据量一旦变大,向量文件和索引文件都会明显膨胀。
2)batch size:批量条数
Embedding 接口通常支持批量输入。
你提供的信息里,text-embedding-v4 一次最多处理:
10 条文本
所以批量入库时,应该按 10 条一批调用接口。
3)单行 token 上限
单条文本不能无限长。
你提供的信息里,单行上限是:
8192 token
但在知识库里,不建议直接把一整篇 PDF 扔进去做 embedding。
更合理的做法是:
先切片,再 embedding
例如每段 200 到 500 字左右。
5. 什么是向量数据库?
向量数据库就是专门用来做这类查询的数据库:
给我一个向量
找出数据库里最相似的几个向量
这类查询叫:
向量相似度检索。
例如:
用户问题向量:
[0.034, -0.217, ..., 0.891]
数据库中有几万、几十万、几百万条资料,每条资料也都有一个向量。
向量数据库要做的事情是:
从海量向量中找出最相似的 Top-K 条
比如 Top 5:
第 1 条:客户价格异议处理话术
第 2 条:竞品贵 30% 的应对方式
第 3 条:ROI 价值证明案例
第 4 条:售后服务价值说明
第 5 条:总拥有成本 TCO 对比
6. LanceDB 是什么?
LanceDB 是一个开源向量数据库,底层基于 Lance 数据格式。
在这套系统里,它的定位是:
带索引能力的向量检索引擎。
它负责:
创建表
写入向量、原文、元数据
创建向量索引
执行向量相似度搜索
返回最相关结果
需要注意一个细节:
LanceDB 不是 OSS,OSS 也不是 LanceDB。
LanceDB 负责组织数据、管理索引、执行检索。
OSS 负责保存 LanceDB 产生的数据文件。
也就是说:
LanceDB 是干活的
OSS 是放东西的
7. 什么是 OSS?
OSS 是阿里云对象存储。
你可以把它理解成:
云上的大硬盘。
它适合存:
PDF
图片
视频
日志
数据文件
索引文件
.lance 文件
但是 OSS 不擅长做计算。
它不会帮你做:
向量相似度计算
Top-K 排序
语义搜索
索引召回
所以不要理解成:
把向量放进 OSS,然后 OSS 帮我搜
这是错误理解。
正确理解是:
LanceDB 负责搜索
OSS 负责存储
8. LanceDB 为什么可以接 OSS?
LanceDB 支持使用 S3 兼容存储作为后端。
阿里云 OSS 也提供 S3 兼容接口。
所以可以通过类似下面的方式连接:
s3://your-bucket/sales-kb/lancedb
但实际 endpoint 指向的是阿里云 OSS:
https://oss-cn-hangzhou.aliyuncs.com
这里有一个关键点:
LanceDB 不能直接识别
oss://协议,要通过 S3 兼容方式访问 OSS。
所以路径写的是:
s3://bucket/path
但 endpoint 配的是:
阿里云 OSS 的 S3 兼容 endpoint
9. 什么是索引?
如果没有索引,LanceDB 每次查询都可能需要和所有向量逐个比较。
假设有:
100 万条销售资料片段
用户问一句话,系统就要比较:
1 次问题向量 × 100 万条资料向量
这会很慢。
索引的作用是:
提前把向量组织起来,让查询时不用全量扫描。
可以把索引理解成图书馆目录。
没有目录时,你要一本一本找。
有目录时,你先定位到几个可能相关的书架,再从这些书架中找最相关的书。
10. IVF-PQ 是什么?
LanceDB 可以使用 IVF-PQ 这类向量索引。
不用先被名字吓住。
可以拆开理解。
IVF:先分区
IVF 的思路是:
先把所有向量分成很多个区
比如:
价格异议相关
售后服务相关
产品功能相关
合同条款相关
竞品对比相关
真实系统不是按文字标签这么分,而是按向量空间自动聚类。
查询时,系统先判断用户问题最接近哪些分区。
这样就不用查所有数据。
PQ:压缩向量
PQ 的作用是:
把原始高维向量压缩成更小的表示
为什么要压缩?
因为向量太大。
例如:
100 万条 × 1024 维
数据量会很大。
PQ 可以减少内存和存储压力,加快粗筛速度。
但是压缩会有一定精度损失,所以通常检索过程会分两步:
先用压缩向量快速粗筛
再用原始向量做精确重排
11. 什么是粗筛和精排?
这是向量检索里非常重要的概念。
粗筛
粗筛就是:
先快速找出一批可能相关的候选结果
例如从 100 万条中先筛出 100 条。
这个阶段追求的是:
快
不是绝对精确。
精排
精排就是:
对粗筛出来的候选结果重新精确计算相似度
例如从 100 条里选出最相关的 5 条。
这个阶段追求的是:
准
所以完整流程是:
100 万条
↓ 粗筛
100 条候选
↓ 精排
Top 5
二、三者的准确分工
现在把三个角色钉死。
1. text-embedding-v4:翻译官
它负责:
把人类语言翻译成向量语言
例如:
客户嫌贵怎么办?
变成:
[0.034, -0.217, ..., 0.891]
它不负责:
保存数据
创建索引
查询数据库
返回原文
2. LanceDB:检索管理员
它负责:
保存向量表结构
写入向量、原文、元数据
创建索引
根据 query vector 做相似度搜索
返回 Top-K 结果
它像一个仓库管理员。
它知道:
哪些资料在哪些区域
哪些资料和用户问题最接近
应该先查哪些候选
最后返回哪几条
3. OSS:数据仓库
它负责:
保存 LanceDB 的物理数据文件
例如:
向量数据
原文片段
元数据
索引文件
.lance 文件
它不负责:
语义理解
向量计算
索引调度
Top-K 排序
三、销售知识库案例:从 0 到 1 跑完整链路
1. 业务场景
公司有 5000 份销售资料,包括:
产品手册 PDF
销售 FAQ
竞品对比文档
客户异议处理话术
成功案例
培训 Markdown
现在销售员希望直接提问:
客户说我们比竞品贵 30%,怎么接?
系统返回最相关的 5 段原文,例如:
1. 当客户提出价格异议时,先不要急于降价,应引导客户关注 ROI。
2. 如果客户认为竞品更便宜,可以从售后响应、实施成功率、长期维护成本三个维度对比。
3. 对于预算敏感型客户,可以提供分阶段采购方案。
这就是一个典型销售知识库检索系统。
2. 阶段一:文档抽取
原始资料可能是:
PDF
Word
Markdown
HTML
Excel
第一步要做的是:
把这些文档里的正文抽出来
例如从 PDF 中抽出:
当客户说价格太高时,先不要急着降价,而是引导客户关注 ROI。
从 Markdown 中抽出:
Q:你们比竞品贵 30% 凭什么?
A:我们的售后响应是 2 小时内,竞品通常是 24 小时内。
3. 阶段二:文本切片
不要把一整篇文档直接丢给 embedding 模型。
应该先切片。
比如:
chunks = [
{
"text": "当客户说价格太高时,先不要急着降价,而是引导客户关注 ROI...",
"source": "价格异议处理手册.pdf",
"page": 12,
"category": "销售话术"
},
{
"text": "Q: 你们比竞品贵 30% 凭什么?A: 我们的售后响应是 2 小时...",
"source": "竞品对比FAQ.md",
"page": 3,
"category": "竞品对比"
}
]
每个 chunk 最好保存:
原文 text
来源 source
页码 page
分类 category
更新时间 updated_at
权限信息 permission
不要只存 text。
否则后续返回结果时,你不知道这段话来自哪里,也不好做权限控制。
4. 阶段三:调用 text-embedding-v4 生成向量
每个切片都要转换成向量。
示例代码:
from openai import OpenAI
import os
emb_client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
def embed(texts):
"""
texts: list[str]
建议每批最多 10 条
"""
resp = emb_client.embeddings.create(
model="text-embedding-v4",
input=texts,
dimensions=1024,
encoding_format="float"
)
return [d.embedding for d in resp.data]
然后批量处理:
all_data = []
for i in range(0, len(chunks), 10):
batch = chunks[i:i + 10]
batch_texts = [c["text"] for c in batch]
batch_vectors = embed(batch_texts)
for chunk, vector in zip(batch, batch_vectors):
all_data.append({
"vector": vector,
"text": chunk["text"],
"source": chunk["source"],
"page": chunk["page"],
"category": chunk["category"]
})
此时每条数据长这样:
{
"vector": [0.034, -0.217, ..., 0.891],
"text": "当客户说价格太高时,先不要急着降价...",
"source": "价格异议处理手册.pdf",
"page": 12,
"category": "销售话术"
}
这一步结束后,销售资料已经从“普通文字”变成了“可以被语义检索的数据”。
5. 阶段四:LanceDB 连接 OSS
现在要把这些数据写入 LanceDB。
但我们不想把数据存在本地磁盘,而是存到阿里云 OSS。
所以连接时要使用 S3 兼容方式。
示例:
import lancedb
db = lancedb.connect(
"s3://your-bucket/sales-kb/lancedb",
storage_options={
"region": "oss-cn-hangzhou",
"endpoint": "https://oss-cn-hangzhou.aliyuncs.com",
"aws_access_key_id": "你的OSS_ACCESS_KEY",
"aws_secret_access_key": "你的OSS_SECRET_KEY",
}
)
这里要注意:
s3://your-bucket/sales-kb/lancedb
只是 LanceDB 使用的 S3 兼容路径。
真正访问的是:
阿里云 OSS endpoint
也就是:
https://oss-cn-hangzhou.aliyuncs.com
6. 阶段五:创建 LanceDB 表并写入数据
创建表:
table = db.create_table(
"sales_knowledge",
data=all_data,
mode="overwrite"
)
这一步之后,OSS 中会出现 LanceDB 相关数据文件。
可以理解为:
LanceDB 负责把向量、原文、元数据组织成表
OSS 负责保存这些表对应的物理文件
后续有新资料时,可以增量追加:
table.add(more_data)
7. 阶段六:创建向量索引
数据写入后,如果数据量较大,需要创建索引。
例如:
table.create_index(
vector_column_name="vector",
config=lancedb.Index.ivf_pq(
num_partitions=256,
num_sub_vectors=64,
)
)
这里两个参数先简单理解。
num_partitions
表示把向量空间分成多少个分区。
可以粗略理解为:
仓库分成多少个区域
数据越多,通常分区数可以适当增加。
几万条数据,不需要太大。
几百万条数据,就要认真调参。
num_sub_vectors
这是 PQ 压缩相关参数。
可以粗略理解为:
把一个大向量拆成多少段来压缩
它影响:
索引大小
检索速度
召回效果
不要一开始乱调,建议先用中等配置跑通,再通过测试集评估效果。
8. 阶段七:销售员提问
销售员输入:
客户说我们比竞品贵 30%,怎么接?
系统第一步不是直接查数据库,而是先把这个问题也转换成向量。
注意:
入库文档用什么 embedding 模型,查询问题也要用同一个 embedding 模型。
示例:
def search(question: str, top_k=5):
q_vector = embed([question])[0]
results = (
table
.search(q_vector)
.limit(top_k)
.to_pandas()
)
return results[["text", "source", "page", "category"]]
调用:
hits = search("客户说我们比竞品贵30%,怎么接", top_k=5)
for i, row in hits.iterrows():
print(f"{i + 1}. {row['text']}")
print(f"来源:{row['source']} 第 {row['page']} 页")
四、完整查询链路拆解
现在把整个查询链路拆开。
用户问:
客户说我们比竞品贵 30%,怎么接?
第一步:text-embedding-v4 把问题转成向量
客户说我们比竞品贵 30%,怎么接?
↓
[0.034, -0.217, ..., 0.891]
这一步发生在阿里云百炼 embedding API。
第二步:LanceDB 拿到 query vector
LanceDB 收到的不是文字,而是:
1024 维 query vector
LanceDB 不理解“贵不贵”这个中文意思。
它只知道:
我要找和这个向量最接近的向量
第三步:LanceDB 用索引粗筛
LanceDB 会根据 IVF-PQ 索引快速判断:
这个问题最可能落在哪些向量分区
例如:
256 个分区
↓
挑出最接近的若干个分区
↓
在这些分区里快速扫描候选
这个阶段的目标是:
快点缩小范围
不是马上得到最终答案。
第四步:LanceDB 从 OSS 读取候选数据
粗筛后,LanceDB 可能得到几十条或几百条候选。
然后它会从 OSS 中读取这些候选对应的数据,包括:
原始向量
原文 text
来源 source
页码 page
分类 category
这里一定要理解:
不是 OSS 在做向量搜索,而是 LanceDB 决定要读哪些数据,然后去 OSS 取。
OSS 的角色只是:
你要哪个文件、哪段数据,我给你。
第五步:LanceDB 做全精度重排
候选数据取回来后,LanceDB 会做更精确的相似度计算。
例如:
候选 100 条
↓
精确计算相似度
↓
排序
↓
取 Top 5
最后返回:
最相关的 5 段销售资料
五、用一张图记住完整链路
销售员提问:
“客户说我们比竞品贵 30%,怎么接?”
│
▼
text-embedding-v4
把问题转成 1024 维向量
│
▼
LanceDB
拿 query vector 查 IVF-PQ 索引
│
▼
LanceDB
粗筛出几十条 / 几百条候选
│
▼
OSS
按需读取候选数据
│
▼
LanceDB
对候选做全精度重排
│
▼
返回 Top 5 原文片段
六、入库链路和查询链路要分开理解
很多小白会把这两个流程混在一起。
其实知识库有两条链路。
1. 入库链路
入库链路发生在:
准备知识库数据时
流程是:
PDF / Markdown / Word
↓
抽取正文
↓
文本切片
↓
text-embedding-v4 生成向量
↓
LanceDB 写表
↓
OSS 保存 .lance 数据文件
↓
LanceDB 创建索引
入库不是每次用户提问都做。
通常是:
新增资料
修改资料
定期同步
才做。
2. 查询链路
查询链路发生在:
用户每次提问时
流程是:
用户问题
↓
text-embedding-v4 生成问题向量
↓
LanceDB 查索引
↓
OSS 读取候选
↓
LanceDB 精排
↓
返回 Top-K 原文
用户每问一次,就会走一次查询链路。
七、为什么这套组合适合销售知识库?
1. 中文语义检索能力强
销售话术里经常有大量中文表达:
太贵了
预算不够
竞品便宜
价格异议
ROI 不明显
投入产出比低
这些话字面不同,但业务意思接近。
Embedding 模型可以把它们映射到接近的向量空间。
2. LanceDB 适合轻量级知识库场景
销售知识库一般不是每秒大量写入的交易系统。
它更像:
文档相对稳定
按天或按周更新
查询频繁
LanceDB 这种嵌入式向量数据库比较适合这种场景。
3. OSS 适合存放持续增长的知识库数据
销售资料会不断增加:
新产品资料
新竞品对比
新客户案例
新话术模板
如果全放本地磁盘,后期迁移、扩容、备份都会麻烦。
放到 OSS 后:
存储容量弹性更好
成本更低
多计算节点可以共享同一份数据
4. 存算分离,后期更容易扩展
OSS 负责存储。
LanceDB 运行在应用服务或检索服务里。
当查询并发上升时,可以扩多个检索服务实例:
检索服务 A
检索服务 B
检索服务 C
↓
共同访问同一个 OSS bucket
这就是存算分离的好处。
数据不用搬。
计算节点可以横向扩展。
八、几个容易踩坑的点
1. 不要直接用 oss://
LanceDB 连接 OSS 时,不是直接写:
oss://your-bucket/path
而是使用 S3 兼容方式:
s3://your-bucket/path
然后在 storage_options 里配置 OSS endpoint。
2. 入库和查询必须用同一个 embedding 模型
如果入库时用:
text-embedding-v4,1024 维
查询时也要用:
text-embedding-v4,1024 维
不要入库用 1024 维,查询用 768 维。
也不要入库用一个模型,查询用另一个模型。
否则向量空间不一致,检索效果会很差,甚至直接报错。
3. 文档不要切得太碎,也不要太长
切得太碎:
上下文不完整
答案容易断裂
切得太长:
噪声太多
embedding 表达不聚焦
召回不精准
销售知识库可以先从:
200 到 500 字一段
开始试。
4. 元数据一定要存
不要只存:
vector
text
建议至少存:
source:来源文件
page:页码
category:分类
updated_at:更新时间
permission:权限标识
否则后期会遇到问题:
不知道答案来自哪个文件
无法展示出处
无法按部门做权限隔离
无法做文档更新
无法删除旧版本
5. 不要以为向量检索等于最终答案
LanceDB 返回的是:
相关资料片段
不是完整回答。
如果你要让系统像智能客服一样回答,还需要把 Top-K 片段交给大模型,例如:
请根据以下资料回答用户问题,不要编造。
这一步才是完整 RAG 的生成环节。
九、最终用一句话总结
这套系统可以这样理解:
text-embedding-v4 是翻译官:
把销售问题和销售资料都翻译成向量。
LanceDB 是检索管理员:
根据向量相似度快速找到最相关的资料。
OSS 是仓库:
负责保存 LanceDB 的数据文件、原文、向量、元数据和索引。
销售员问:
客户嫌贵怎么办?
系统实际做的是:
先把问题转成向量
再用 LanceDB 查相似向量
再从 OSS 取回候选资料
最后返回最相关的销售话术
真正要记住的是:
OSS 只负责存储,LanceDB 负责检索,text-embedding-v4 负责把文字变成向量。
这三者合起来,才构成一个可落地的销售知识库语义检索系统。
