Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LangChainEmbedding

一个 Embedding embedding 与向量数据库 的渐进式学习项目:从「怎么把文本变成向量」开始,到「向量存哪、怎么查」,最终落到 * 图文跨模态检索*(用 Qwen3-VL-Embedding 把图片和文本映射到同一语义空间,实现以文搜图、以图搜文、图文混合检索)。

所有模型全部本地离线运行,不依赖任何在线 Embedding API。


技术栈

层面 选型
框架 LangChain 1.3.18 / langchain-core 1.6.1
文本 Embedding Qwen3-Embedding-0.6B、BAAI/bge-small-zh-v1.5
多模态 Embedding Qwen3-VL-Embedding-2B(2048 维,图文同空间)
推理 PyTorch 2.14.0 · transformers 5.16.1 · Apple Silicon 走 MPS + fp16
向量库 Milvus(pymilvus 3.0.1)· FAISS · Chroma
语言 / 环境 Python 3.12.14(conda 环境 langchain-embedding)

目录结构

LangChainEmbedding/
├── env_utils.py                    # 统一读取 .env(系统环境变量优先)
├── models/
│   └── init_chat_model_llm.py      # 共享的 DeepSeek / GLM 聊天模型实例
│
├── embedding_demo/                 # ① Embedding 基础
│   ├── download_model_embedding.py #   模型下载(hf-mirror 镜像 + 断点重试)
│   ├── 01langchain本地Qwen3的嵌入模型.py  # LangChain 封装 Qwen3,含 Query 指令前缀
│   ├── 02私有化的Qwen3的嵌入模型.py       # 纯 SentenceTransformer 离线加载
│   ├── 03加载bge的嵌入模型.py             # BGE 系列 + 归一化检索
│   ├── 04Qwen3的嵌入模型和Langchain整合.py # 手写 Embeddings 子类
│   ├── 05Embedding案例.py                # 完整案例:CSV 批量向量化 + 语义搜索
│   └── custom_embedding.py         #   自定义 Embeddings 类(被 chroma/faiss 复用)
│
├── vector_db/                      # ② 向量数据库
│   ├── chroma/01Chroma案例.py       #   Chroma 持久化 + 元数据过滤
│   ├── faiss/                       #   FAISS 三连:建库 / 落盘 / 加载删除
│   │   ├── 01faiss_案例.py
│   │   ├── 02faiss_案例.py
│   │   └── 03faiss_案例.py
│   └── milvus/
│       ├── milvus_basics/          #   Milvus 原生 API 三步走
│       │   ├── collection_create.py
│       │   ├── collection_insert.py
│       │   └── collection_search.py
│       └── multi_model/            #   ③ 多模态检索(本项目重点)
│           ├── qwen3_vl_embedding.py    # Qwen3-VL 推理封装(含 chat template 补齐)
│           ├── multimodel_embeddings.py # LangChain Embeddings 适配层
│           ├── milvus_manager.py        # 双向量 Collection 管理 + 三种检索
│           ├── multimodel_search_demo.py# 可运行入口
│           └── images/                  # 5 张示例图片
│
├── datas/                          # 06 案例的输入 / 输出 CSV
├── chroma_db/  faiss_db/           # 落盘数据(运行时生成,不入库)
├── requirements.txt                # 精简依赖清单(只列直接依赖)
├── .env.example                    # 环境变量模板(复制为 .env 后填值)
└── README.md

环境准备

1. 创建环境并安装依赖

conda create -n langchain-embedding python=3.12 -y
conda activate langchain-embedding
pip install -r requirements.txt

⚠️ 解释器别选错:项目根目录有个 .venv,里面只装了一个 pip(空壳)。 在 IDE 里请把解释器指向 /opt/miniconda3/envs/langchain-embedding/bin/python, 否则会报 ModuleNotFoundError: No module named 'pymilvus'。

磁盘空间:PyTorch + transformers + chromadb 全套约 3~4 GB。

2. 配置 .env

在项目根目录创建 .env(已被 .gitignore 忽略):

DEEPSEEK_API_KEY=sk-xxx
DEEPSEEK_BASE_URL=https://api.deepseek.com
GLM_API_KEY=xxx
GLM_BASE_URL=https://open.bigmodel.cn/api/paas/v4/
LANGSMITH_API_KEY=              # 可选,留空即可

只有 models/init_chat_model_llm.py 和 embedding_demo/01openai.py 需要 API Key, 向量库和多模态部分完全离线,不配也能跑。

配了 LANGSMITH_API_KEY 会自动开启链路追踪上报。不想上报就加一行 LANGCHAIN_TRACING_V2=false。

3. 下载模型

本项目共用到 3 个模型,本地均已缓存:

模型 用途 大小
Qwen/Qwen3-Embedding-0.6B 文本向量(02/03/05、chroma、faiss、milvus_basics) ~1.2 GB
BAAI/bge-small-zh-v1.5 文本向量(04/06) ~90 MB
Qwen/Qwen3-VL-Embedding-2B 图文多模态向量(multi_model) ~4 GB

下载脚本默认下载 Qwen3-VL-Embedding-2B。要下其他模型,改 download_model_embedding.py 顶部的 REPO_ID 再运行:

python embedding_demo/download_model_embedding.py

脚本已做国内适配:走 hf-mirror.com 镜像、关闭 Xet 协议(否则镜像返回 401)、 带 3 次重试、只下推理必需文件(跳过 *.bin 原始权重)。

4. 启动 Milvus

只有 vector_db/milvus/ 下的代码需要。用 Docker 起一个单机版:

curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
bash standalone_embed.sh start

默认连 http://127.0.0.1:19530,可用环境变量覆盖:

export MILVUS_URI="http://127.0.0.1:19530"
export MILVUS_COLLECTION="multi_model_search_demo"

运行指南

按下面的顺序跑,就是一条完整的学习路径。

① Embedding 基础

python embedding_demo/02langchain本地Qwen3的嵌入模型.py   # LangChain 封装 + Query 指令前缀
python embedding_demo/03私有化的Qwen3的嵌入模型.py        # 纯 sentence-transformers
python embedding_demo/04加载bge的嵌入模型.py              # BGE + 归一化
python embedding_demo/05Qwen3的嵌入模型和Langchain整合.py  # 手写 Embeddings 子类
python embedding_demo/06Embedding案例.py                  # CSV 批量向量化 + 语义搜索

02 里有个容易忽略的细节:Qwen3 是指令感知模型,查询侧必须拼 Instruct: 前缀, 否则检索效果会明显下降。代码里通过 query_encode_kwargs={"prompt": QUERY_PROMPT} 处理。

② 向量数据库

# Chroma:在 vector_db/ 目录下执行(脚本里 persist_directory='../chroma_db')
cd vector_db && python chroma/01Chroma案例.py

# FAISS:先建库检索,再落盘
cd vector_db/faiss && python 01faiss_案例.py
cd vector_db && python faiss/02faiss_案例.py          # 落盘到根目录 faiss_db/

# 03 用的是 '../../faiss_db',需在 faiss/ 目录下执行
cd vector_db/faiss && python 03faiss_案例.py          # 加载、删除、带过滤检索

⚠️ FAISS 02 和 03 的相对路径基准不一致('../faiss_db' vs '../../faiss_db'), 必须按上面各自的 cwd 执行。这属于历史遗留,建议后续统一改成基于 Path(__file__).resolve().parents[2] 的绝对路径。详见下方踩坑记录。

Milvus 三步走,必须按顺序执行:

python vector_db/milvus/milvus_basics/collection_create.py   # 建 Collection(dim=1024)
python vector_db/milvus/milvus_basics/collection_insert.py   # 插入 8 条文档
python vector_db/milvus/milvus_basics/collection_search.py   # ANN 搜索 / 标量过滤 / 标量查询

③ 多模态检索(重点)

cd vector_db/milvus/multi_model
python multimodel_search_demo.py

首次运行会加载 2B 参数模型(约 4 GB 权重),约 40 秒 (Apple Silicon 自动走 MPS + fp16;无 MPS 时回退 CPU,会慢很多)。 输出三个场景的结果:

======================== 以文搜图:猫在窗边晒太阳 ========================
id=1, score=0.6630   图片: cat_sunbath.jpg      描述: 一只橘色的猫在窗台上晒太阳...

======================== 以图搜文:coffee_latte.jpg ========================
id=3, score=0.5420   图片: coffee_latte.jpg     描述: 一杯拿铁咖啡,表面有精致的拉花...

======================== 图文混合检索 ========================
id=4, rrf_score=0.0328   图片: milvus_architecture.jpg
id=5, rrf_score=0.0323   图片: qwen_multimodal.jpg

多模态模块设计

三层结构,职责清晰:

multimodel_search_demo.py  →  编排:建表 / 插数据 / 跑三个场景
        ↓
milvus_manager.py          →  Milvus:双向量 Collection + 三种检索实现
        ↓
multimodel_embeddings.py   →  LangChain Embeddings 适配层(设备选择、图片解码)
        ↓
qwen3_vl_embedding.py      →  Qwen3-VL 推理:processor + last-token pooling + L2 归一化

核心思路:双向量字段

每条数据在 Milvus 里存两个向量,都在同一个 2048 维语义空间:

字段 来源
image_vector 图片过模型得到的向量
text_vector 描述文本过模型得到的向量

因为图文同空间,所以可以交叉检索:

检索方式 查询向量 搜索字段 实现
以文搜图 文本 image_vector search_by_text()
以图搜文 图片 text_vector search_by_image()
图文混合 文本 + 图片 两路召回 hybrid_search_multimodal()

图文混合为什么用 RRF

两路召回得到的 COSINE 分数量纲不可比(文本↔文本 和 图片↔图片 的分数分布不同), 直接加权相加没有意义。所以用 RRF(Reciprocal Rank Fusion) 按名次融合:

score = Σ
1 / (rrf_k + rank)  # rrf_k 默认 60

只关心排名、不关心原始分数,规避了量纲问题。rrf_k 越大,排名靠前的优势越小。

排序在哪控制

三个位置,各管一段:

位置 控制范围 能否改
Milvus COSINE 检索 天然降序(分数越大越相似) 不能翻转
milvus_manager.py RRF sorted(..., reverse=True) 可改 reverse
multimodel_search_demo.py 最终输出顺序 改 RESULT_SORT_DESCENDING

想让结果从低分到高分打印,把 RESULT_SORT_DESCENDING 改成 False 即可 (只影响已召回的 top_k 条,不改变召回集合)。

图片输入支持三种形式

multimodel_embeddings.py 的 _load_image() 统一处理:

  • http:// / https:// URL
  • 本地文件路径
  • Base64(data:image/jpeg;base64,... 或纯 Base64)

踩坑记录

这些都是实际跑通过程中遇到并已修复的。

现象 原因 解决
ModuleNotFoundError: No module named 'pymilvus' 解释器选了空壳 .venv 切到 conda 环境解释器
ModuleNotFoundError: No module named 'env_utils' 脚本运行时 sys.path[0] 是脚本所在目录 01openai.py 里显式补了项目根路径
Cannot use apply_chat_template because this processor does not have a chat template 模型快照缺 .jinja 模板文件 qwen3_vl_embedding.py 内置 CHAT_TEMPLATE 并传入 processor
插入后首次搜索偶发返回空 Milvus 最终一致性,数据未落盘 insert_data() 里加了 client.flush()
torch_dtype 弃用警告 transformers 新版 API 变更 改用 dtype=torch.float16
HF 下载走镜像报 401 Xet 协议会请求 xethub.hf.co 设 HF_HUB_DISABLE_XET=1 降级为 HTTP
03faiss_案例.py 加载失败 相对路径相对当前工作目录,且 02/03 基准不一致:02 用 '../faiss_db'(cwd 需为 vector_db/),03 用 '../../faiss_db'(cwd 需为 vector_db/faiss/) 按各自 cwd 执行;或把两处都改成 Path(__file__).resolve().parents[2] / "faiss_db"
Chroma 数据落在奇怪位置 persist_directory='../chroma_db' 同样相对 cwd 在 vector_db/ 下执行,数据落到根目录 chroma_db/

依赖维护

requirements.txt 只列直接依赖(项目代码真正 import 的包),传递依赖交给 pip 解析。 审计方式:AST 扫描全项目 import + importlib.metadata 反向依赖闭包复核。

可删除 4 个(代码零引用,且无其他包依赖)

conda activate langchain-embedding
pip uninstall -y dashscope zhipuai langchain-faiss pandas-stubs
包 说明
dashscope 通义 SDK。所有 Qwen 都走本地 transformers,删它收益最大(会带走 openai/uvicorn/tiktoken 一串)
zhipuai 智谱 SDK。GLM 走的是 OpenAI 兼容接口
langchain-faiss 代码用的是 langchain_community.vectorstores.FAISS
pandas-stubs 纯类型存根,项目没配 mypy

千万别删 3 个("伪顶层"陷阱)

它们在 pip list --not-required 里看着像孤儿,实际删了直接崩:

包 真实身份 谁需要
langchain-deepseek langchain[deepseek] 的 extra init_chat_model(model_provider="deepseek")
torchvision transformers[vision] 的 extra Qwen3VLProcessor 图像预处理
av qwen-vl-utils 的硬性依赖 import qwen_vl_utils 就会失败

httptools / uvloop / watchfiles 是 uvicorn[standard] 的可选件(chromadb 引入), 删了不报错但会让 Chroma server 模式降级,体积极小,建议留着。

按目录的依赖对照

想删掉某个目录时,照这张表卸载:

目录 专属依赖
embedding_demo/ langchain-huggingface、sentence-transformers、huggingface-hub、openai、pandas、numpy
models/ langchain、langchain-deepseek、python-dotenv
vector_db/chroma/ langchain-chroma(→ chromadb,本机最大的间接依赖源)
vector_db/faiss/ faiss-cpu、langchain-community
vector_db/milvus/milvus_basics/ pymilvus、sentence-transformers
vector_db/milvus/multi_model/ pymilvus、torch、transformers、torchvision、qwen-vl-utils、pillow

扩展方向

  • milvus_basics 加上混合检索(BM25 + 向量)
  • 多模态 demo 支持视频输入(Qwen3VLVideoProcessor 已就绪)
  • 接 RAG:把检索结果喂给 models/init_chat_model_llm.py 的 LLM 生成答案
  • 换成更大的 Qwen3-VL-Embedding-8B 对比效果
  • 尝试向量降维(MRL 截断 / PCA)在召回率与速度之间找平衡点

参考

About

LangChain 嵌入与向量数据库渐进式实践:文本向量化 → Milvus/FAISS/Chroma → Qwen3-VL 图文跨模态检索 → 历史感知检索的会话式 RAG,全模型本地离线运行。

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages