搜狐技术产品

LangChain + Easysearch + MiMo 大模型——从零构建企业级 RAG 向量检索系统

不谈概念,只谈落地。一文讲清向量写入、kNN 语义检索、RAG 问答的完整链路,附可跑通代码。


一、背景:为什么是这个组合?

企业做知识库问答,绕不开三个核心问题:向量存哪、怎么检索、谁来回答。

组件
角色
选型理由
Easysearch
向量存储 + 检索
兼容 Elasticsearch API,kNN 插件原生支持向量检索,无需改造现有技术栈
小米 MiMo
文本向量化 + 答案生成
兼容 OpenAI 协议,接入成本极低,国内访问稳定;推理模型语义理解能力强
Python 自研框架
编排层
直接调用 REST API,避免 ES 8.x 客户端兼容性问题,链路透明可控

三者配合,是目前国内开箱即用、成本可控的最优组合之一。

Image
Image

小米赠送的token正好用一下。

10 分钟可见效果。

Image

二、架构:先理解数据流,再看代码

整个 RAG 链路分两个阶段,务必先理解这两条线。

阶段一:离线写入(Indexing)

原始文档(easysearch_docs.txt)
    → RecursiveCharacterTextSplitter 切块(500 字/块,50 字重叠)
    → MiMo LLM 语义编码(每块生成 256 维向量)
    → Easysearch kNN 索引(knn_dense_float_vector)

阶段二:在线检索 + 生成(Retrieval & Generation)

用户提问
    → MiMo LLM 向量化(同模型编码,保证语义空间一致)
    → Easysearch knn_nearest_neighbors 检索 Top-K
    → 拼接检索结果 + 用户问题 → MiMo LLM 生成回答

一条数据走完两条线,写进去的是什么向量,查出来就是同一套语义空间——这是检索质量的生命线。

Image

三、Step 1:环境准备

前置条件

  • Easysearch 集群 已启动,HTTPS 开启,kNN 插件已安装并加载
    Image
  • MiMo API Key:小米开发者平台申请,Base URL 为 https://token-plan-cn.xiaomimimo.com/v1
  • Python ≥ 3.10

项目结构

Image
vectorPrj/
├── .env                  # 配置文件(API Key、连接参数等)
├── config.py             # 配置加载模块
├── es_client.py          # Easysearch REST API 封装(requests 直连)
├── mimo_embeddings.py    # MiMo LLM 文本向量化
├── indexing.py           # 文档切块 + 向量写入
├── retriever.py          # kNN 向量检索
├── rag_qa.py             # RAG 问答链
├── search_test.py        # 检索效果验证
├── main.py               # 命令行入口
├── easysearch_docs.txt   # 知识库文档
└── requirements.txt      # 依赖

安装依赖

pip install -r requirements.txt

核心依赖:requests、python-dotenv、langchain、langchain-community。


四、Step 2:用 MiMo 生成向量(Embedding)

关键认知

MiMo v2.5-pro 是推理模型,没有独立的 Embedding API。但我们可以利用其强大的语义理解能力,通过 Chat API "请"它输出文本的语义向量。

Image

实现思路

文本 → 构造 Prompt:"你是文本语义编码器,请将文本映射为 256 维向量" → MiMo Chat API → 解析 JSON → 得到向量

核心代码 mimo_embeddings.py:

classMiMoLLMEmbeddings(Embeddings):
"""使用 MiMo Chat API 批量生成语义向量"""

def_batch_to_vectors(self, texts: List[str]) -> List[List[float]]:
# 构造 Prompt:要求输出 JSON 格式的向量数组
        prompt = (
f"你是一个文本语义编码器。请将以下 {len(texts)} 段文本分别映射为"
f"{dims} 维向量。向量值在 -1.0 到 1.0 之间。"
f"只输出 JSON 格式,不要任何解释。\n"
f'格式: {{"vectors": [[0.1, -0.2, ...], [0.3, 0.5, ...], ...]}}\n'
        )
# 调用 MiMo Chat API
        msg = self._call_chat(prompt)
# 优先取 content,为空时取 reasoning_content(推理模型特性)
        content = msg.get("content", "") or msg.get("reasoning_content", "")
return self._extract_vectors(content, len(texts), dims)

踩坑与调优

坑 1:max_tokens 不足导致向量被截断

256 维向量约需 3000+ token 输出。初始设置 max_tokens=4096 时,向量常被截断为 0。调大到 8192 后稳定。

坑 2:推理模型的 reasoning_content

MiMo 是推理模型,返回中 reasoning_content 是思考过程,content 是最终答案。向量可能在 content 中,也可能在 reasoning_content 中。两者都解析,取其一即可。

坑 3:批量处理导致向量混乱

初始 BATCH_SIZE=3 时,模型偶尔会输出错位或数量不对。改为 BATCH_SIZE=1 逐条处理,虽然慢但稳定。

向量解析的两套方案

def_extract_vectors(self, text, expected_count, dims):
# 方案1:解析 JSON 格式 {"vectors": [[...], ...]}
# 方案2:正则匹配所有数字数组 [0.1, -0.2, ...]
# 补齐/截断至指定维度

备选方案:本地模型

如果 MiMo 不稳定或网络受限,一行配置切换到本地 BGE 模型:

# .env 中设置
EMBEDDING_BACKEND=bge

自动加载 BAAI/bge-base-zh-v1.5,其余代码完全不用改——这正是抽象层的价值。


五、Step 3:写入文档到 Easysearch 向量索引

为什么不直接用 LangChain 的 Elasticsearch Store?

问题:ES 8.x 官方 Python 客户端会检测服务端是否为 Elasticsearch,非 ES 服务器直接拒绝连接。LangChain 的 ElasticsearchStore 底层依赖该客户端。

方案:直接用 requests 发送原生 ES REST API 请求,绕开客户端校验。

# es_client.py — 封装 HTTP 请求
defes_request(method, path, body=None):
    url = f"{Config.ES_HOST}/{path}"
    resp = session.request(method, url, data=json.dumps(body))
return resp.json()

创建 kNN 索引

# indexing.py
mapping = {
"mappings": {
"properties": {
"content": {"type": "text"},           # 原文,供 BM25 检索
"source": {"type": "keyword"},         # 来源文件名
"content_vector": {                    # 向量字段
"type": "knn_dense_float_vector",
"knn": {"dims": 256}
            }
        }
    }
}
es_request("PUT", "rag-easysearch-docs", body=mapping)

关键参数说明:

  • knn_dense_float_vector:Easysearch kNN 插件要求的向量字段类型
  • dims=256:与 MiMo 输出维度一致,维度不匹配写入直接报错
  • content 字段保留 text 类型:供混合检索中的 BM25 关键词匹配使用

文档切块 + 批量写入

defindex_documents():
# 1. 加载文档
    docs = TextLoader("easysearch_docs.txt").load()

# 2. 切块:500 字一段,50 字重叠保留上下文
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=500, chunk_overlap=50,
        separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""]
    )
    chunks = splitter.split_documents(docs)

# 3. 向量化
    embeddings = MiMoLLMEmbeddings()
    vectors = embeddings.embed_documents([c.page_content for c in chunks])

# 4. 批量写入(bulk API)
for chunk, vec in zip(chunks, vectors):
        actions.append({
"_index": "rag-easysearch-docs",
"_source": {
"content": chunk.page_content,
"source": chunk.metadata["source"],
"content_vector": vec
            }
        })
    es_bulk(actions)

执行:

python main.py index

六、Step 4:验证向量检索效果

写入后必须先验证检索,再接入 LLM 生成。很多人跳过这步,最终回答差却不知道问题出在哪。

kNN 查询语法(Easysearch 原生)

# retriever.py
body = {
"query": {
"knn_nearest_neighbors": {
"field": "content_vector",
"vec": {"values": query_vector},   # 查询向量
"model": "exact",                   # exact=精确检索
"similarity": "cosine",             # 余弦相似度
"candidates": 100,                  # 候选集大小
        }
    }
}

model 参数说明

model
说明
适用场景
exact
精确计算,扫描所有向量
数据量 < 10 万,精度优先
lsh
局部敏感哈希,近似检索
数据量大,速度优先(需对应 mapping)

当前 mapping 使用默认配置,exact 兼容性最好。

混合检索:向量 + BM25

纯向量检索对专有名词(版本号、型号、人名)效果差,关键词检索则无法理解语义。混合检索结合两者:

"bool": {
"should": [
        {"knn_nearest_neighbors": {...}},   # 向量语义匹配(加分)
        {"match": {"content": query}}        # BM25 关键词匹配(加分)
    ]
}

通过 .env 中的 HYBRID_SEARCH=true/false 一键切换。

验证检索

python main.py search "安全功能"

预期输出:

Image
#1  相关度: 5.4621
    来源: easysearch_docs.txt
    内容: Easysearch 的安全功能包括:1. HTTPS 传输加密...

#2  相关度: 4.8903
    来源: easysearch_docs.txt
    内容: Easysearch 的 kNN 插件用于向量相似度检索...

#3  相关度: 4.2156
    来源: easysearch_docs.txt
    内容: Easysearch 默认启用 HTTPS,证书路径配置在...

判断标准:Top1 结果与查询高度相关,说明向量质量合格。如果 Top3 都不相关,通常是 Embedding 模型与文档语言不匹配,需换模型。


七、Step 5:构建 RAG 问答链

完整链路

用户问题 → MiMo 向量化 → Easysearch kNN 检索 Top-K → Prompt 拼接 → MiMo LLM 生成

自定义 Prompt(关键!)

PROMPT = """你是 Easysearch 技术专家。请严格根据以下文档内容回答问题,
若文档中没有答案请直接说"文档中未找到相关信息",不要自行编造。

文档内容:
{context}

用户问题:{question}

回答:"""

为什么这个 Prompt 如此重要?

不约束 LLM,模型会混入训练知识产生"幻觉"——回答听起来流畅但不可信。加上"文档中没有就说不知道",强制模型忠于检索结果。

问答执行

python main.py ask "Easysearch 的安全功能有哪些?"

输出示例:

回答: Easysearch 的安全功能包括以下 5 项:
1. HTTPS 传输加密:默认开启,保护数据传输安全
2. 用户认证:支持 Basic Auth 和 API Key 两种认证方式
3. 角色权限控制(RBAC):可创建不同角色,分配索引级别的读写权限
4. 审计日志:记录所有操作请求,便于安全审计
5. IP 白名单:限制允许访问的 IP 地址范围

来源文档:
  [1] easysearch_docs.txt (相关度: 5.4621)
  [2] easysearch_docs.txt (相关度: 4.8903)
  [3] easysearch_docs.txt (相关度: 4.2156)
Image

回答准确、来源可追溯——这正是 RAG 的核心价值。


八、交互式对话

python main.py chat

进入交互模式后,可以连续提问,每次自动检索 + 生成,体验完整的 RAG 对话流程。

RAG 问答系统已就绪(Easysearch kNN + MiMo)
索引: rag-easysearch-docs
检索模式: Hybrid(向量+BM25)
输入问题开始对话,输入 quit / exit 退出
============================================

你: Easysearch 怎么部署集群?
思考中...
助手: 根据文档,Easysearch 集群部署建议最少 3 个节点...

九、避坑指南:四个最容易踩的坑

坑 01:kNN 插件已安装但未加载

现象:创建索引时 type=knn_dense_float_vector 报 "No handler for type"。

原因:easysearch-plugin list 显示已安装,但 _nodes/plugins 显示未加载。

解决:重启 Easysearch。插件安装后必须重启才能生效。

坑 02:向量维度不匹配

现象:写入时报 mapping 错误。

原因:MiMo 输出 256 维,但索引 mapping 中 dims 设成了其他值。

解决:config.py 中 VECTOR_DIMS 必须与 Embedding 实际输出维度一致。

坑 03:lsh model 与 mapping 不兼容

现象:kNN 查询返回 400 错误 "query is not compatible with mapping"。

原因:model=lsh 需要 mapping 中有对应配置,默认 mapping 不包含。

解决:改用 model=exact,或创建索引时指定 lsh 参数。

坑 04:向量全部为零

现象:检索返回的结果与查询完全不相关。

原因:MiMo 输出被截断(max_tokens 不足),向量解析失败,全部补零。

解决:增大 max_tokens=8192,降低 BATCH_SIZE=1,增加 reasoning_content 备选解析。


十、落地清单

  • [x] Easysearch 安装 kNN 插件并确认已加载(_nodes/plugins)
  • [x] 封装 MiMo LLM 为 LangChain Embeddings 接口
  • [x] 文档切块(500 字/块,50 字重叠)写入 Easysearch
  • [x] 先验证检索相关度,再接入 LLM 生成
  • [x] 自定义 Prompt 约束 LLM 严格基于文档回答
  • [x] 生产环境开启混合检索(向量 + BM25),提升专有名词召回率
  • [x] 返回来源文档,让用户看到回答依据,增加可信度
  • [x] 支持交互式对话,体验完整 RAG 流程

十一、总结

本文从零搭建了一个完整的 RAG 向量检索系统,核心链路:

Easysearch(存储+检索) + MiMo(编码+生成) + 自研编排层

几个关键设计决策:

  1. 放弃 ES 8.x 客户端,改用 requests 直连:
    绕开非 ES 服务器拒绝连接的问题,链路更透明
  2. 用 LLM Chat API 做 Embedding:
    MiMo 没有独立 Embedding API,但推理模型的语义理解能力完全够用
  3. 先验证检索再接入生成:
    分步调试,问题定位清晰,避免"最终效果差但不知道哪环出问题"
  4. 混合检索作为默认策略:
    向量解决语义,BM25 解决专有名词,两者互补

希望这篇文章能帮助你在自己的项目中快速落地 RAG。如果有任何问题,欢迎交流讨论。


Easysearch 集群异常,邮件自动发、根因自动判——AIOps 落地全记录

国产化 Easysearch 性能优化全链路图解

【收藏】图解 Easysearch——从原理到调优

Elasticsearch 国产化替代 ——信创政策到技术选型的全面指南调研报告 V1.0