利用 LangChain 进行多向量索引(Multi-representation Indexing),通过从网页爬取文章,生成摘要,并使用向量数据库(Chroma)存储摘要,同时维护原始文档的存储,以便实现高效检索。

完整示例代码如下:

from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())

import os
os.environ['LANGCHAIN_TRACING_V2'] = 'true'
os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'
os.environ['LANGCHAIN_PROJECT'] = 'advanced-rag'
os.environ['LANGCHAIN_API_KEY'] = os.getenv("LANGCHAIN_API_KEY")
os.environ['GROQ_API_KEY'] = os.getenv("GROQQ_API_KEY")

from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
import os

# 设置一个常见的浏览器 User-Agent 字符串
os.environ["USER_AGENT"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"

loader = WebBaseLoader("https://www.36kr.com/p/3175118078316933")
docs = loader.load()

loader = WebBaseLoader("https://www.36kr.com/p/3174699772215686")
docs.extend(loader.load())

import uuid

from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_groq import ChatGroq

chain = (
    {"doc": lambda x: x.page_content}
    | ChatPromptTemplate.from_template("总结以下文档:\n\n{doc}")
    | ChatGroq()
    | StrOutputParser()
)

summaries = chain.batch(docs, {"max_concurrency": 1})

from langchain.storage import InMemoryByteStore
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceBgeEmbeddings

model_name = "BAAI/bge-small-zh-v1.5"
model_kwargs = {"device": "cpu"}
encode_kwargs = {"normalize_embeddings": True}
hf_embeddings = HuggingFaceBgeEmbeddings(
    model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs
)

# The vectorstore to use to index the child chunks
vectorstore = Chroma(collection_name="summaries",
                     embedding_function=hf_embeddings)

# The storage layer for the parent documents
store = InMemoryByteStore()
id_key = "doc_id"

# The retriever
retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    byte_store=store,
    id_key=id_key,
)
doc_ids = [str(uuid.uuid4()) for _ in docs]

# Docs linked to summaries
summary_docs = [
    Document(page_content=s, metadata={id_key: doc_ids[i]})
    for i, s in enumerate(summaries)
]

# Add
retriever.vectorstore.add_documents(summary_docs)
retriever.docstore.mset(list(zip(doc_ids, docs)))

retriever.docstore.mget(doc_ids)

query = "恒宇医疗近年来在IVUS和OCT产品的商业化驱动"
sub_docs = vectorstore.similarity_search(query,k=1)
sub_docs[0]

retrieved_docs = retriever.get_relevant_documents(query,n_results=1)
retrieved_docs[0].page_content[0:500]

代码详细解析(按模块拆解)

1. 加载环境变量

from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())

作用:

  • find_dotenv() 用于找到 .env 文件的路径(如果存在)。
  • load_dotenv() 读取 .env 文件中的环境变量,比如 API Key。

举例:
假设 .env 文件里有:

LANGCHAIN_API_KEY=my-secret-key
GROQQ_API_KEY=my-groq-key

那么,执行 load_dotenv() 后,代码可以通过 os.getenv("LANGCHAIN_API_KEY") 访问这个 Key,而不用直接在代码里写死(提高安全性)。


2. 设置环境变量

import os
os.environ['LANGCHAIN_TRACING_V2'] = 'true'
os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com'
os.environ['LANGCHAIN_PROJECT'] = 'advanced-rag'
os.environ['LANGCHAIN_API_KEY'] = os.getenv("LANGCHAIN_API_KEY")
os.environ['GROQ_API_KEY'] = os.getenv("GROQQ_API_KEY")

作用:

  • 这些 os.environ 变量主要是给 LangChain 设定 API 相关的配置
    • LANGCHAIN_TRACING_V2 = 'true':开启 LangChain 运行跟踪(方便调试)。
    • LANGCHAIN_PROJECT = 'advanced-rag':指定项目名称。
    • LANGCHAIN_API_KEYGROQ_API_KEY:分别设置 LangChain 和 Groq 的 API Key(用于大模型调用)。

举例:

print(os.environ["LANGCHAIN_PROJECT"])  # 输出 "advanced-rag"

这说明你已经成功设置了项目名称。


3. 爬取网页数据

from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 设置一个常见的浏览器 User-Agent 字符串
os.environ["USER_AGENT"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"

loader = WebBaseLoader("https://www.36kr.com/p/3175118078316933")
docs = loader.load()

loader = WebBaseLoader("https://www.36kr.com/p/3174699772215686")
docs.extend(loader.load())

print(docs)

作用:

  • WebBaseLoader 用于从网页爬取文章
  • docs = loader.load() 抓取第一篇文章(存成 LangChain 文档对象)。
  • docs.extend(loader.load()) 追加第二篇文章docs 里。

为什么要设定 User-Agent
有些网站会禁止爬虫访问,设置 User-Agent 让代码看起来像是正常的用户访问。

举例:
如果你打印 docs,它会是一个包含两篇文章的列表,docs的值是完整的文章:

[Document(page_content="文章1内容...", metadata={"source": "https://www.36kr.com/p/3175118078316933"}),
 Document(page_content="文章2内容...", metadata={"source": "https://www.36kr.com/p/3174699772215686"})]

在这里插入图片描述


4. 生成摘要

import uuid

from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_groq import ChatGroq

chain = (
    {"doc": lambda x: x.page_content}
    | ChatPromptTemplate.from_template("总结以下文档:\n\n{doc}")
    | ChatGroq()
    | StrOutputParser()
)

summaries = chain.batch(docs, {"max_concurrency": 1})

作用:

  • 构建 LangChain 处理链(chain)

    1. {"doc": lambda x: x.page_content}:提取 docs 中的正文内容。
    2. ChatPromptTemplate.from_template("总结以下文档:\n\n{doc}"):使用 ChatGPT 生成摘要的提示词。
    3. ChatGroq():调用 Groq API 让大模型生成摘要。
    4. StrOutputParser():解析模型返回的文本输出。
  • 并行处理 docs

    • chain.batch(docs, {"max_concurrency": 1}):逐个处理文档(这里 max_concurrency=1 限制为单线程处理)。

举例:
假设 docs 中某篇文章的内容是:

"恒宇医疗在IVUS和OCT领域发展迅速,市场份额逐步扩大..."

那么 summaries 可能会返回:

["恒宇医疗近年来在IVUS和OCT产品的商业化驱动方面取得了显著进展..."]

5. 构建多向量索引

在这里插入图片描述

from langchain.storage import InMemoryByteStore
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceBgeEmbeddings

model_name = "BAAI/bge-small-zh-v1.5"
model_kwargs = {"device": "cpu"}
encode_kwargs = {"normalize_embeddings": True}
hf_embeddings = HuggingFaceBgeEmbeddings(
    model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs
)

# The vectorstore to use to index the child chunks
vectorstore = Chroma(collection_name="summaries",
                     embedding_function=hf_embeddings)

# The storage layer for the parent documents
store = InMemoryByteStore()
id_key = "doc_id"

作用:

  • 使用 HuggingFacebge-small-zh-v1.5 模型来生成向量(适用于中文)。
  • Chroma 作为向量数据库,用来存储摘要的向量表示。
  • InMemoryByteStore 作为存储层,用于存储原始文档。

6. 存储摘要和原始文档

doc_ids = [str(uuid.uuid4()) for _ in docs]

summary_docs = [
    Document(page_content=s, metadata={id_key: doc_ids[i]})
    for i, s in enumerate(summaries)
]
#存储摘要的向量表示
retriever.vectorstore.add_documents(summary_docs)

#存储原始文档
retriever.docstore.mset(list(zip(doc_ids, docs)))

作用:

  1. uuid.uuid4() 生成唯一 ID,保证每篇文档都有唯一标识
  2. 摘要存入 vectorstore(向量数据库)。
  3. 原文档存入 docstore,用于后续检索时关联原文。

7. 检索

query = "恒宇医疗近年来在IVUS和OCT产品的商业化驱动"

#搜索最相关的摘要
sub_docs = vectorstore.similarity_search(query,k=1)
sub_docs[0]

#完整原文
retrieved_docs = retriever.get_relevant_documents(query,n_results=1)
retrieved_docs[0].page_content[0:500]

作用:

  1. vectorstore.similarity_search(query, k=1):用向量搜索最相关的摘要
  2. retriever.get_relevant_documents(query, n_results=1):找到摘要后,返回完整原文

retriever.get_relevant_documents(query, n_results=1)的内部执行流程请点击学习


总结

这段代码的完整流程:

  1. 从网页爬取文章
  2. 用大模型生成摘要
  3. 摘要存入向量数据库(Chroma),原文存入存储层
  4. 基于查询进行向量相似性搜索,最终返回原文档

这样可以高效检索和关联长文档,特别适用于**RAG(检索增强生成)**场景。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐