一、Langchain操作ChromaDB

1、概述

  • 在本项目中,使用 LangChain 作为 RAG 框架核心编排工具,对接 ChromaDB 实现向量检索能力,构建端端的检索增强生成(RAG)流程
  • 具体实现上,通过 LangChain 的 DocumentLoader 模块完成多源数据(PDF、TXT、Markdown)的加载与解析,并使用 TextSplitter 对长文本进行分块处理,以适配向量检索的粒度需求。随后调用 Embeddings 模型(如 OpenAI Embeddings 或 BGE Embedding)将文本块转化为向量表示,并通过 ChromaDB VectorStore 进行持久化存储与索引构建。
  • 在查询阶段,利用 LangChain 的 Chroma.as_retriever() 封装检索器,通过语义相似度计算返回 Top-K 相关文档片段,并将其与用户 Query 一同传入 Prompt Template,由 LLM 生成最终回答,实现“检索 + 生成”的协同增强效果
  • 此外,系统支持通过 LangChain 的 RetrievalQAConversationalRetrievalChain 进行链式调用,实现带历史对话记忆的多轮问答能力,从而提升交互体验与上下文一致性
  • 整体方案实现了模块化、可扩展的 RAG 架构,便于快速替换向量数据库或大模型接口,具备良好的工程落地能力

二、基础操作

1、建立连接

from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings

# Embedding 模型
embedding_model = HuggingFaceEmbeddings(
    # 模型名称
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    # 缓存目录
    cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)

# 连接本地已持久化的 ChromaDB
vectorstore = Chroma(
    persist_directory="./test_db",
    embedding_function=embedding_model
)

print(vectorstore)

2、转为检索器接口

  • 把“向量数据库”转换成“检索器接口(Retriever)”,用于标准化 RAG 检索调用方式
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings

# Embedding 模型
embedding_model = HuggingFaceEmbeddings(
    # 模型名称
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    # 缓存目录
    cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)

# 连接本地已持久化的 ChromaDB
vectorstore = Chroma(
    persist_directory="./test_db",
    embedding_function=embedding_model
)

# 转为检索器接口
retriever = vectorstore.as_retriever()
print(retriever)

3、存入数据

  • 操作的步骤:
    • 文本数据的清洗与分块(Chunking)
    • 中文语义向量化方法
    • ChromaDB 向量数据库的构建与持久化
    • LangChain 构建完整向量检索流程
    • 基于 embedding 的语义检索原理
import os
from langchain_chroma import Chroma
from langchain_core.documents import Document
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

text = """
华清远见2004年成立于北京中关村,12年来始终专注于嵌入式及移动开发专业人才培养,“做良心教育,做专业教育,做受人尊敬的职业教育”是华清远见一直坚持的核心发展理念。培训内容主要包括嵌入式系统开发,Android开发,物联网开发、HTML5开发、UI设计。从短期高端到长期就业课程培训,再到产品研发,凭借专业的课程内容、强大的师资团队以及先进的研发技术受到客户和学员的好评。迄今已有超过10万名学员从华清远见走出。
华清远见研发实力强大。拥有10余种实训套件与智能产品,100余套自主研发实验设备。同时与众多高校进行实验室共建合作,目前已有超过1100所高校1100多个实验室选择了我们。华清远见拥有众多高端培训课程,每类课程均有独特的教学模式。不同于其他机构,华清远见的培训课程不仅限于理论知识,还有丰富多样的实战项目贯穿其中。从研发到培训,全面提升学员技术水平。
华清远见拥有一支非常强大的师资团队。近200余名讲师投身于研发当中。每位讲师均是从事IT行业多年的资深人士,并有着丰富的项目开发经验。在学员培训过程中实时答疑解惑。
华清远见成都中心设有教学部、教务部、市场部等多个部门,其中教学部根据专业方向分为AI教学部和嵌入式教学部,分别负责不同技术领域的人才培养与课程教学工作。AI教学部主要负责人工智能、大数据等相关课程的教学与项目实践,致力于培养具备实际开发能力的AI技术人才。
cc老师是AI教学部的一名讲师,拥有丰富的人工智能领域教学经验,在课程讲授与项目指导过程中深受学员欢迎。
"""

# 分块器
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=100,
    chunk_overlap=20,
    separators=["\n\n", "\n", "。", ",", " "],  # 语义优先级
)

chunks = text_splitter.split_text(text)


# 中文句号归并处理
def merge_chinese_period(chunks):
    merged = []
    for chunk in chunks:
        # 去掉首尾空格
        chunk = chunk.strip()
        if chunk.startswith("。") and merged:
            # 把开头句号加到前一块末尾
            merged[-1] += "。"
            # 当前块去掉句号再加入
            merged.append(chunk[1:].lstrip())
        else:
            merged.append(chunk)
    return merged


chunks = merge_chinese_period(chunks)

# 存入向量数据库
# 把分块后的文本转为Document对象
documents = [Document(page_content=chunk) for chunk in chunks]
# 向量模型
embedding_model = HuggingFaceEmbeddings(
    # 模型名称
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    # 缓存目录
    cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)

# 持久化目录
persist_directory = "./test_db"
# 如果本地已有存储,直接加载;否则生成并保存
if os.path.exists(persist_directory):
    vectordb = Chroma(
        # 持久化目录
        persist_directory=persist_directory,
        # 集合名称
        collection_name="hqyj",
        # 向量模型
        embedding_function=embedding_model
    )
    print("Chroma 向量数据库已加载")
else:
    vectordb = Chroma.from_documents(
        # 文档
        documents=documents,
        # 向量模型
        embedding=embedding_model,
        # 持久化目录
        persist_directory=persist_directory,
        # 集合名称
        collection_name="hqyj",
        # 采用 余弦相似度(cosine)作为向量距离度量方式来进行相似度搜索
        collection_metadata={"hnsw:space": "cosine"}
    )
    print("Chroma 向量数据库已生成并保存")

4、检索

  • 测试是否可以基于向量数据库中的内容回答问题
import os
from langchain_chroma import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI

embedding_model = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)

persist_directory = "./test_db"

vectordb = Chroma(persist_directory=persist_directory,
                  collection_name="hqyj",
                  embedding_function=embedding_model)
# 检索器,检索3个最相关的文档
retriever = vectordb.as_retriever(search_kwargs={"k": 3})

# 提示模板
template = """
你是一个知识问答助手。
根据以下检索到的上下文回答用户问题,
如果答案不在上下文中,请说明“资料中未提及”。
如果答案在上线文中,不需要输出答案来自于上下文内容,只需要输出最终的结果。

上下文:
{context}

问题:
{question}
"""
prompt = PromptTemplate(input_variables=["context", "question"], template=template)

llm = ChatOpenAI(
    model="qwen3.5-35b-a3b",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

rag_chain = (
    RunnableParallel({
        "context": retriever,
        "question": RunnablePassthrough()
    })
    | prompt
    | llm
    | StrOutputParser()
)

query = "华清远见有哪些部门?"
answer = rag_chain.invoke(query)
print("\n最终回答:\n", answer)
  • 检索结果:

image-20260515111353621

5、流程和 API 解读

5.1 执行流程图

用户问题 Query
        ↓
RunnableParallel(并行处理)
        ↓
 ┌────────────────────────────┐
 │                            │
 │ context = retriever       │ → ChromaDB语义检索TopK文档
 │ question = passthrough    │ → 原样传递问题
 │                            │
 └────────────────────────────┘
        ↓
PromptTemplate(拼接上下文 + 问题)
        ↓
ChatOpenAI(大模型生成)
        ↓
StrOutputParser(提取纯文本)
        ↓
     最终答案

5.2 核心模块 API 解读

5.2.1 Chroma 向量数据库

  • 作用:连接本地持久化的向量数据库
vectordb = Chroma(
    persist_directory=persist_directory,
    collection_name="hqyj",
    embedding_function=embedding_model
)
  • 参数解释
参数 作用
persist_directory 本地数据库路径
collection_name 向量集合名称(类似表名)
embedding_function 用于查询时的向量化模型

5.2.2 Retriever(检索器)

  • 作用:把向量库包装成“检索接口”
retriever = vectordb.as_retriever(search_kwargs={"k": 3})
  • k=3:返回最相似的3个文档

  • 自动执行:query → embedding → similarity search

  • 输出:List[Document]

5.2.3 PromptTemplate(提示词模板)

  • 作用:定义 RAG 输入格式
PromptTemplate(input_variables=["context", "question"], template=template)
  • 参数解释
变量 来源
context retriever返回的文档
question 用户输入
  • 提示词模板作用:把“检索内容 + 用户问题”拼成 LLM 输入

5.2.4 ChatOpenAI(大模型)

  • 作用:调用大语言模型生成最终答案
llm = ChatOpenAI(
    model="qwen3.5-35b-a3b",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
  • 参数说明
参数 说明
model 使用的LLM模型
api_key DashScope API密钥
base_url OpenAI兼容接口地址

5.2.5 RunnableParallel(并行执行器)

  • 作用:把输入拆成两路并行处理:

    • 路径1:context
      • 作用:用 query 去 ChromaDB 检索,返回相关文档
    "context": retriever
    
    • 路径2:question
      • 作用:原样传递用户输入,不做任何处理
    "question": RunnablePassthrough()
    
  • 输出结构

{
  "context": [Document, Document, Document],
  "question": "用户问题"
}

5.2.6 Prompt + LLM + Parser(链式调用)

| prompt
| llm
| StrOutputParser()
  • prompt
把 context + question 填入模板,输出:完整提示词字符串
  • llm
输入 prompt → 输出模型回答
  • StrOutputParser
把 AIMessage → 转换为纯字符串

5.2.7 LCEL 管道

  • 本质: LangChain 的函数式流水线
rag_chain = (
    RunnableParallel(...)
    | prompt
    | llm
    | StrOutputParser()
)
  • 等价于
step1 = retriever + passthrough
step2 = prompt.format(step1_output)
step3 = llm(step2)
step4 = parse_output(step3)

5.2.8 调用执行流程

query = "华清远见有哪些部门?"
answer = rag_chain.invoke(query)
  • 执行过程
1. query 输入
2. 并行:
   - retriever → 查知识库
   - passthrough → 原样问题
3. prompt 拼接
4. LLM生成答案
5. 输出字符串

三、法律数据集

1、数据集格式

image-20260515113123624

2、解析并存储

import os

import pandas as pd
from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document
from langchain_huggingface import HuggingFaceEmbeddings


# 读取Excel
df = pd.read_csv(r"F:\workspace\AI\LangChain-1.0\study_rag\法律数据集.csv")
# 数据集提取
data = df['text'].tolist()
# 把分块后的文本转为Document对象
documents = [Document(page_content=item) for item in data]
# 向量模型
embedding_model = HuggingFaceEmbeddings(
    # 模型名称
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    # 缓存目录
    cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)
# 持久化目录
persist_directory = "./chroma_db2"
if os.path.exists(persist_directory):
    vectordb = Chroma(
        # 持久化目录
        persist_directory=persist_directory,
        # 集合名称
        collection_name="hqyj",
        # 向量模型
        embedding_function=embedding_model
    )
    print("Chroma 向量数据库已加载")
else:
    vectordb = Chroma.from_documents(
        # 文档
        documents=documents,
        # 向量模型
        embedding=embedding_model,
        # 持久化目录
        persist_directory=persist_directory,
        # 集合名称
        collection_name="hqyj",
        # 采用 余弦相似度(cosine)作为向量距离度量方式来进行相似度搜索
        collection_metadata={"hnsw:space": "cosine"}
    )
    print("Chroma 向量数据库已生成并保存")

3、检索

import os
from langchain_chroma import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI

embedding_model = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)

persist_directory = "./chroma_db2"

vectordb = Chroma(
    persist_directory=persist_directory,
    collection_name="hqyj",
    embedding_function=embedding_model
)
# 检索器,检索3个最相关的文档
retriever = vectordb.as_retriever(search_kwargs={"k": 3})

# 提示模板
template = """
你是一个知识问答助手。
根据以下检索到的上下文回答用户问题,
如果答案不在上下文中,请说明“资料中未提及”。
如果答案在上线文中,不需要输出答案来自于上下文内容,只需要输出最终的结果。

上下文:
{context}

问题:
{question}
"""
prompt = PromptTemplate(input_variables=["context", "question"], template=template)

llm = ChatOpenAI(
    model="qwen3.5-35b-a3b",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

rag_chain = (
        RunnableParallel({
            "context": retriever,
            "question": RunnablePassthrough()
        })
        | prompt
        | llm
        | StrOutputParser()
)

query = "公安机关接到家庭暴力报案后应当做什么事情?"
answer = rag_chain.invoke(query)
print("\n最终回答:\n", answer)
  • 文件中的源数据:

image-20260515113328229

  • 检索结果:

image-20260515113337939

四、QA 数据集

1、数据集格式

image-20260515113554481

2、解析并存储

import os
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.documents import Document

qa_data = [
    {
        "id": "faq_1",
        "question": "如何修改密码?",
        "answer": "进入个人中心 → 安全设置 → 修改密码"
    },
    {
        "id": "faq_2",
        "question": "如何申请退款?",
        "answer": "在订单页面点击退款按钮申请退款"
    },
    {
        "id": "faq_3",
        "question": "退款多久到账?",
        "answer": "一般3-5个工作日到账"
    }
]

# 向量模型
embedding_model = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)

documents = []

for qa in qa_data:
    question = qa["question"]
    answer = qa["answer"]
    embedding_text = f"问题:{question} 答案:{answer}"
    doc = Document(
        page_content=embedding_text,
        metadata={
            "id": qa["id"],
            "question": question,
            "answer": answer,
        }
    )

    documents.append(doc)

persist_directory = "./chroma_db3"

if os.path.exists(persist_directory):
    vectordb = Chroma(
        persist_directory=persist_directory,
        collection_name="hqyj",
        embedding_function=embedding_model
    )
    print("Chroma 向量数据库已加载")
else:
    vectordb = Chroma.from_documents(
        documents=documents,
        embedding=embedding_model,
        persist_directory=persist_directory,
        collection_name="hqyj",
        collection_metadata={"hnsw:space": "cosine"}
    )
    print("Chroma 向量数据库已生成并保存")

3、检索

import os
from langchain_chroma import Chroma
from langchain_core._api import LangChainDeprecationWarning
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI
import warnings
warnings.filterwarnings("ignore", category=LangChainDeprecationWarning)

embedding_model = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)

persist_directory = "./chroma_db3"

vectordb = Chroma(
    persist_directory=persist_directory,
    collection_name="hqyj",
    embedding_function=embedding_model
)
# 检索器,检索3个最相关的文档
retriever = vectordb.as_retriever(search_kwargs={"k": 3})

# 提示模板
template = """
你是一个知识问答助手。
根据以下检索到的上下文回答用户问题,
如果答案不在上下文中,请说明“资料中未提及”。
如果答案在上线文中,不需要输出答案来自于上下文内容,只需要输出最终的结果。

上下文:
{context}

问题:
{question}
"""
prompt = PromptTemplate(input_variables=["context", "question"], template=template)

llm = ChatOpenAI(
    model="qwen3.5-35b-a3b",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

rag_chain = (
        RunnableParallel({
            "context": retriever,
            "question": RunnablePassthrough()
        })
        | prompt
        | llm
        | StrOutputParser()
)

query = "退款多久到账?"
answer = rag_chain.invoke(query)
print("\n最终回答:\n", answer)
  • 文件中的源数据:

image-20260515114124724

  • 检索结果:

image-20260515114110523

五、客服数据集

1、数据集格式

image-20260515114532247

2、解析并存入

import os

import pandas as pd
import json
from langchain_core.documents import Document
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings

# 读取 CSV
df = pd.read_csv(r"F:\workspace\AI\LangChain-1.0\study_rag\客服数据集.csv")

documents = []

# 解析数据
for idx, row in df.iterrows():
    try:
        messages = json.loads(row["messages"])
        question = None
        answer = None
        for msg in messages:
            if msg["role"] == "user":
                question = msg["content"]
            elif msg["role"] == "assistant":
                answer = msg["content"]
        # 跳过不完整数据
        if not question or not answer:
            continue
        # 构造文本
        content = f"问题:{question} 答案:{answer}"
        doc = Document(
            page_content=content,
            metadata={
                "question": question,
                "answer": answer,
                "source": "ecommerce_chat",
                "id": f"chat_{idx}"
            }
        )
        documents.append(doc)
    except Exception as e:
        print("解析失败:", e)

# 向量模型
embedding_model = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)
persist_directory = "./chroma_db4"

if os.path.exists(persist_directory):
    vectordb = Chroma(
        persist_directory=persist_directory,
        collection_name="hqyj",
        embedding_function=embedding_model
    )
    print("Chroma 向量数据库已加载")
else:
    vectordb = Chroma.from_documents(
        documents=documents,
        embedding=embedding_model,
        persist_directory=persist_directory,
        collection_name="hqyj",
        collection_metadata={"hnsw:space": "cosine"},
    )
    print("Chroma 向量数据库已生成并保存")

3、检索

  • 加入了重排序
import os
from langchain_community.vectorstores import Chroma
from langchain_core._api import LangChainDeprecationWarning
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough, RunnableLambda
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI
import warnings
from sentence_transformers import CrossEncoder

warnings.filterwarnings("ignore", category=LangChainDeprecationWarning)


# 向量模型
embedding_model = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    cache_folder=r"F:\workspace\AI\langchain_project\embedding_model"
)

persist_directory = "./chroma_db4"

vectordb = Chroma(
    persist_directory=persist_directory,
    collection_name="hqyj",
    embedding_function=embedding_model
)

retriever = vectordb.as_retriever(search_kwargs={"k": 30})

# 重排序模型
rerank_model = CrossEncoder(
    model_name_or_path="BAAI/bge-reranker-base",
    cache_folder=r"F:\workspace\AI\LangChain-1.0\models"
)

# 重排序函数
def rerank_documents_cross_encoder(docs, query):
    # 构造 (query, doc) 对
    pairs = [(query, doc.page_content) for doc in docs]
    # 模型预测相关性分数
    scores = rerank_model.predict(pairs)
    print("Cross-Encoder Scores:", scores)
    # 根据分数降序排序
    sorted_docs = [
        doc for _, doc in sorted(zip(scores, docs), key=lambda x: x[0], reverse=True)
    ]
    print("Sorted Docs:", sorted_docs)
    return sorted_docs

# 重排序 + 格式化函数
def rerank_and_format(inputs):
    print("inputs:", inputs)
    docs = inputs["context"]
    query = inputs["question"]
    docs = rerank_documents_cross_encoder(docs, query)
    # 只取 top3,避免上下文太长
    docs = docs[:3]
    print("docs:", docs)
    context = "\n\n".join(doc.page_content for doc in docs)
    return {
        "context": context,
        "question": query
    }

# 提示模板
template = """
你是一个知识问答助手。
根据以下检索到的上下文回答用户问题,
如果答案不在上下文中,请说明“资料中未提及”。
如果答案在上线文中,不需要输出答案来自于上下文内容,只需要输出最终的结果。

上下文:
{context}

问题:
{question}
"""
prompt = PromptTemplate(input_variables=["context", "question"], template=template)

llm = ChatOpenAI(
    model="qwen3.5-35b-a3b",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

rag_chain = (
    RunnableParallel({
        "context": retriever,
        "question": RunnablePassthrough()
    })
    | RunnableLambda(rerank_and_format)
    | prompt
    | llm
    | StrOutputParser()
)

# query = "一般发什么快递?"
query = "刚才下单了麻烦早点发货"
answer = rag_chain.invoke(query)
print("\n最终回答:\n", answer)
  • 文件中的源数据:

image-20260515114612404

  • 检索结果:

image-20260515114650762

六、总结

  • 通过 Langchain 中的 API 完成 RAG 系统的开发需要重点掌握:
    • 执行流程
    • 如何切分数据,使得 RAG 系统性能更好
    • 召回和重排序