LangChain 实现NaiveRAG朴素向量检索生成

向量检索增强生成(RAG,Retrieval‑Augmented Generation),是弥补大语言模型固有缺陷的关键技术。它可接入私有文档、内部笔记等外部数据源,有效缓解训练数据滞后、知识固化、回答幻觉等问题,输出更为精准可信的内容。Naive RAG 即朴素 RAG,是 RAG 体系中最基础原始的实现方式,也是各类进阶 RAG 技术的底层基石。该方案架构极简、落地门槛低,舍弃复杂优化手段,只保留检索加生成的核心能力。很适合新手学习实践,同时该方式也多用于搭建轻量化知识库问答服务,适配各类简单问答场景。

当前主流 RAG 存在三种典型实现范式,分别为 Naive‑RAG、Advanced‑RAG、Agentic‑RAG。本章将以 Naive‑RAG 为例,介绍其功能实现逻辑,以及调用大模型完成 RAG 检索的完整流程。Naive‑RAG 在处理检索结果时,通常直接复用检索得到的文档片段,不会对召回内容做额外加工与二次处理。

  • 完整流程为:用户输入问题后,直接执行语义相似度检索,通过向量相似度打分筛选出匹配度最高的文档块;随后将检索获取的上下文片段传入 LCEL 执行链,与提示词进行拼接,完成向量检索推理,最终交由大模型生成回答。

该方案仅依靠基础语义相似度完成召回,不具备查询改写、结果重排序、文本块优化等高级能力。但其实现逻辑简洁、开发效率高、调试难度低,是入门学习 RAG 技术的首选方案。

环境依赖安装

1、项目运行需依赖文档解析、文本分割、向量数据库、网络请求等核心库,为保证安装速度和稳定性,统一使用清华PyPI镜像源批量安装所有依赖,避免后续开发过程中反复补装库文件。

bash 复制代码
CMD> pip install -i https://pypi.tuna.tsinghua.edu.cn/simple langchain_text_splitters langchain_chroma chromadb pypdf requests bs4
CMD> 
CMD> pip list
Package                                  Version
---------------------------------------- -----------
beautifulsoup4                           4.15.0
bs4                                      0.0.2
chromadb                                 1.5.9
langchain-chroma                         1.1.0
langchain-text-splitters                 1.1.2
pypdf                                    6.16.1
requests                                 2.34.2

2、大模型知识库检索分为对话大模型和Embedding向量模型两类,二者作用完全不同。对话模型输出自然语言文本(供人阅读),向量模型输出浮点型向量数组(供机器计算相似度),是实现RAG检索生成的核心组件。

本次选用 Qwen3-Embedding-0.6B-GGUF 轻量向量模型,模型存放于阿里魔搭社区,可直接访问链接下载对应文件:

下载完成后,将模型文件直接放入llama.cpp根目录,与后续启动脚本同级,避免因路径错误导致模型加载失败。

3、为避免对话模型和向量模型端口冲突、服务相互干扰,本次采用双端口分离部署方案。11433端口承载对话大模型服务,11434端口承载Embedding向量模型服务,两个服务独立运行、各司其职。

对话大模型服务启动,用于实现自然语言问答、对话交互,加载通用指令微调大模型,上下文窗口设置为4096,适配常规问答场景。

bash 复制代码
CMD> llama-server.exe -m qwen2.5-1.5b-instruct-q4_k_m.gguf --host 127.0.0.1 --port 11433 -c 4096

init: llama threadpool init, n_threads = 12
load_model: initializing, n_slots = 4, n_ctx_slot = 4096, kv_unified = 'true'
llama_server: model loaded
llama_server: listening on http://127.0.0.1:11433

向量Embedding模型服务启动,核心增加 --embeddings 参数,强制模型以向量编码模式启动,不输出文本,仅输出向量数据。

bash 复制代码
CMD> llama-server.exe -m Qwen3-Embedding-0.6B-Q8_0.gguf --host 127.0.0.1 --port 11434 -c 4096 --embeddings -b 512 -ub 512

init: llama threadpool init, n_threads = 12
load_model: initializing, n_slots = 4, n_ctx_slot = 4096, kv_unified = 'true'
llama_server: model loaded
llama_server: listening on http://127.0.0.1:11434

文本向量维度测试

Llama.cpp 内置 OpenAI 兼容接口标准,无需部署 OpenAI 服务,本地 GGUF 向量模型即可直接被 langchain_openai.OpenAIEmbeddings 调用,大幅降低本地RAG开发适配成本。

本段代码核心目的:验证向量模型服务可用性、向量维度合法性、文本向量化一致性,是RAG项目前置必测环节。核心区分两个核心向量化方法:

  • embed_query:面向用户提问,单条短句向量化,向量特征适配「检索匹配」场景;
  • embed_documents:面向知识库切片,批量长文本向量化,向量特征适配「入库存储」场景。
python 复制代码
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(
    model="Qwen3-Embedding-0.6B-Q8_0.gguf",
    base_url="http://127.0.0.1:11434/v1",
    api_key="dummy"
)

text = "人工智能是什么"

# 用于查询语句,单条文本,适合用户提问向量化
query_vector = embeddings.embed_query(text)

print(f"向量长度:{len(query_vector)}")
print(f"前5个向量:{query_vector[:5]}")

texts = ["大模型", "RAG检索增强生成", "向量数据库"]

# 用于文档切片列表,批量处理知识库文本,适合文档检索向量化
doc_vectors = embeddings.embed_documents(texts)

print(f"文档向量数量:{len(doc_vectors)}")
print(f"文档向量前5个:{doc_vectors[0][:5]}")
print(f"文档向量前5个:{doc_vectors[1][:5]}")
print(f"文档向量前5个:{doc_vectors[2][:5]}")

模型固定输出1024维浮点向量,所有文本的语义信息都会被压缩为统一维度的数值数组。语义相似度越高的文本,向量数值重合度越高、空间距离越近,这是语义检索的底层原理。

bash 复制代码
CMD> python main.py
向量长度:1024
前5个向量:[-0.021272549405694008, -0.04013322293758392, -0.016011258587241173, -0.1134452372789383, 0.014976831153035164]
文档向量数量:3
文档向量前5个:[0.012190932407975197, -0.04904358834028244, -0.011189550161361694, -0.04492426663637161, -0.02737090364098549]
文档向量前5个:[-0.021237701177597046, -0.0865737572312355, -0.018793350085616112, 0.08651088923215866, 0.02789725735783577]
文档向量前5个:[0.04903004691004753, -0.06314395368099213, -0.010090955533087254, -0.09537919610738754, 0.024128597229719162]

向量数据库存储与语义检索测试

Chroma 是轻量级嵌入式向量数据库,无需独立部署服务、无需额外数据库环境,开箱即用,极其适合本地轻量化RAG项目。核心特性为本地磁盘持久化,通过 persist_directory 参数指定存储目录,向量数据、原始文本、元数据会永久保存,重启项目无需重复向量化。

本段代码实现完整的「文本入库-向量存储-语义召回」流程,不依赖关键词匹配,仅通过向量空间距离计算相似度,支持模糊语义匹配。同时直观暴露 Naive RAG 原生短板:无重排序、无查询优化,单纯相似度检索容易出现匹配偏差。

python 复制代码
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

embeddings = OpenAIEmbeddings(
    model="qwen3-embedding-local.gguf",
    base_url="http://127.0.0.1:11434/v1",
    api_key="dummy"
)

texts = [
    "llama.cpp用来本地运行GGUF大模型,一款轻量级、高性能的本地大模型推理框架,主打低资源消耗,支持CPU、GPU混合推理。",
    "RAG检索增强生成是解决大模型知识滞后、幻觉问题的核心技术,通过检索外部知识库,让大模型基于真实私有数据生成答案。",
    "RAG技术核心流程分为文档加载、文本切片、向量嵌入存储、相似度检索、大模型生成回答五个核心步骤。"
]

# 初始化Chroma向量数据库,持久化存储到本地文件夹
db = Chroma.from_texts(
    texts=texts,
    embedding=embeddings,
    persist_directory="./my_chroma_db"
    )

# 语义相似度检索:查询llama.cpp的功能与用途,返回2条最匹配结果
res = db.similarity_search("llamacpp能干什么?",k=2)

print("相似度检索结果:")
for idx, d in enumerate(res, 1):
    print(f"\n检索结果{idx}:{d.page_content}")

本次检索结果看似不准确,是Naive RAG 典型缺陷:简短口语化问句与正式文档文本向量空间匹配度偏低,单纯向量相似度无法精准匹配业务语义。这也是进阶RAG需要引入重排序、查询改写、上下文优化的核心原因。

运行后项目目录自动生成 my_chroma_db 文件夹,即为持久化向量库,删除文件夹即可重置知识库数据。

bash 复制代码
CMD> python main.py
相似度检索结果:
检索结果1:RAG检索增强生成是解决大模型知识滞后、幻觉问题的核心技术,通过检索外部知识库,让大模型基于真实私有数据生成答案。
检索结果2:RAG技术核心流程分为文档加载、文本切片、向量嵌入存储、相似度检索、大模型生成回答五个核心步骤。

调用LCEL链测试

LCEL(LangChain Expression Language)是LangChain官方链式编程语法,用于极简、标准化组装AI业务流程,替代传统函数嵌套写法,结构清晰、易于调试、支持流式输出。

本段代码实现最简Naive RAG完整链路,覆盖全部核心环节:文档切片、向量库持久化、语义检索、Prompt约束、大模型生成、结果解析。同时引入向量距离分数可视化,分数越小语义相似度越高,可直观调试检索质量。

python 复制代码
import os
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# 全局变量配置
CHUNK_SIZE = 300
CHUNK_OVERLAP = 50
CHROMA_PERSIST_DIR = "./my_chroma_database"

# 大模型调用接口
llm = ChatOpenAI(
    model="qwen2.5-1.5b-instruct-q4_k_m.gguf",
    base_url="http://127.0.0.1:11433/v1",
    api_key="dummy",
    temperature=0.7,
    max_tokens=512,
)

# 向量服务调用接口
embeddings = OpenAIEmbeddings(
    model="qwen3-embedding-local.gguf",
    base_url="http://127.0.0.1:11434/v1",
    api_key="dummy"
)

# 构建向量库 并写入磁盘持久化
def build_vector_store(text_list: list[str]) -> Chroma:
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=CHUNK_SIZE,
        chunk_overlap=CHUNK_OVERLAP,
        separators=["\n\n", "\n", "。", ",", " "]
    )
    all_chunks = []
    for text in text_list:
        chunks = splitter.split_text(text)
        all_chunks.extend(chunks)

    db = Chroma.from_texts(
        texts=all_chunks,
        embedding=embeddings,
        persist_directory=CHROMA_PERSIST_DIR
    )
    print(f"[*] 向量库构建完成,共 {len(all_chunks)} 个文本块,保存到 {CHROMA_PERSIST_DIR}")
    return db

# 加载本地已经存在的向量库
def load_vector_store() -> Chroma:
    return Chroma(
        persist_directory=CHROMA_PERSIST_DIR,
        embedding_function=embeddings
    )

# 将检索到的文档列表拼接成字符串
def format_docs(docs):
    return "\n---\n".join(doc.page_content for doc in docs)

if __name__ == "__main__":
    raw_texts = [
        "llama.cpp用来本地运行GGUF大模型,支持CPU和GPU加速。",
        "RAG检索增强生成用于知识库问答,先检索文档片段,再交给大模型生成答案。",
        "Chroma是轻量级嵌入式向量数据库,可以本地持久化存储向量,不需要额外部署服务。",
        "RecursiveCharacterTextSplitter用于把长文档切分成适合向量化的小块。"
    ]

    if os.path.exists(CHROMA_PERSIST_DIR):
        print("[+] 检测到本地向量库,直接加载...")
        vector_db = load_vector_store()
    else:
        print("[*] 本地向量库不存在,开始构建...")
        vector_db = build_vector_store(raw_texts)

    # 配置检索4级文档片段
    RETRIEVE_TOP_K = 4
    retriever = vector_db.as_retriever(search_kwargs={"k": RETRIEVE_TOP_K})

    # 配置RAG链
    prompt = ChatPromptTemplate.from_messages([
        ("system", """你是知识库问答助手,请严格依据提供的上下文回答用户问题。
        如果上下文中没有相关信息,直接回答"知识库中未找到相关内容",不要编造信息。
        参考上下文:
        {context}"""),
        ("human", "{question}")
    ])

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

    # 测试用户问题
    user_question = "llama.cpp有什么作用?"

    # 打印相似度分数
    docs_score = vector_db.similarity_search_with_score(user_question, k=4)
    print("\n[检索结果 (距离分数 越小越相似)]")
    for doc, score in docs_score:
        print(f"[分数:{score:.4f}] {doc.page_content}")

    answer = rag_chain.invoke(user_question)
    retrieved_docs = retriever.invoke(user_question)
    context_text = format_docs(retrieved_docs)

    print("\n[检索到的上下文]")
    print(context_text)
    print("\n[最终NaiveRAG回答]")
    print(answer)

本次运行充分体现 Naive RAG 的容错能力:虽然第一条检索结果匹配错误,但 Top4 召回集合中包含正确文档,大模型通过整合全部上下文,过滤无效信息、精准输出答案。

核心参数解读:temperature=0.7 平衡回答随机性与准确性,数值越低回答越严谨、越高越灵活;chunk_overlap 文本重叠量,用于避免切片截断丢失上下文信息。

bash 复制代码
CMD> python main.py
[+] 检测到本地向量库,直接加载...

[检索结果 (距离分数 越小越相似)]
[分数:1.1428] RecursiveCharacterTextSplitter用于把长文档切分成适合向量化的小块。
[分数:1.1689] Chroma是轻量级嵌入式向量数据库,可以本地持久化存储向量,不需要额外部署服务。
[分数:1.2348] llama.cpp用来本地运行GGUF大模型,支持CPU和GPU加速。
[分数:1.2493] RAG检索增强生成用于知识库问答,先检索文档片段,再交给大模型生成答案。

[检索到的上下文]
RecursiveCharacterTextSplitter用于把长文档切分成适合向量化的小块。
---
Chroma是轻量级嵌入式向量数据库,可以本地持久化存储向量,不需要额外部署服务。
---
llama.cpp用来本地运行GGUF大模型,支持CPU和GPU加速。
---
RAG检索增强生成用于知识库问答,先检索文档片段,再交给大模型生成答案。

[最终NaiveRAG回答]
llama.cpp用于本地运行GGUF大模型,支持CPU和GPU加速。

调用LCEL增量测试

原生Chroma数据库存在重复添加文档生成冗余数据的问题,容易导致知识库内容重复、检索结果冗余,影响问答精度。因此本节在基础Naive RAG的基础上完成工程化升级,新增三项核心实用能力。

主要包含增量入库与覆盖入库双模式、MMR多样性检索、文档溯源元数据绑定三大能力,同时通过UUID唯一标识解决文档重复问题。MMR多样性检索能够优化基础相似度检索的缺陷,避免召回内容单一、重复,在保障语义匹配度的同时提升检索多样性;为每一个文本切片绑定文档来源元数据,可实现内容快速溯源、权限管控与文档迭代更新,是轻量化RAG项目的核心优化方案。

python 复制代码
import os
import uuid
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.documents import Document

CHUNK_SIZE = 300
CHUNK_OVERLAP = 50
CHROMA_PERSIST_DIR = "./my_chroma_database"
RETRIEVE_TOP_K = 4

llm = ChatOpenAI(
    model="qwen2.5-1.5b-instruct-q4_k_m.gguf",
    base_url="http://127.0.0.1:11433/v1",
    api_key="dummy",
    temperature=0.3,
    max_tokens=800,
)

embeddings = OpenAIEmbeddings(
    model="qwen3-embedding-local.gguf",
    base_url="http://127.0.0.1:11434/v1",
    api_key="dummy"
)

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=CHUNK_SIZE,
    chunk_overlap=CHUNK_OVERLAP,
    separators=["\n\n", "\n", "。", ",", " "]
)

prompt = ChatPromptTemplate.from_messages([
    ("system", """你是知识库问答助手,请严格依据提供的上下文回答用户问题。
    如果上下文中没有相关信息,直接回答"知识库中未找到相关内容",不要编造信息。
    参考上下文:
    {context}"""),
    ("human", "{question}")
])

def format_docs(docs):
    return "\n---\n".join(
        f"[来源:{doc.metadata.get('source','未知')}]\n{doc.page_content}"
        for doc in docs
    )

def build_rag_chain(vector_db: Chroma):
    retriever = vector_db.as_retriever(
        search_type="mmr",        # 开启MMR检索
        search_kwargs={
            "k": RETRIEVE_TOP_K,  # 返回最相关的 4 个文档块
            "fetch_k": 10,        # 从数据库中获取 10 个候选文档
            "lambda_mult":0.3     # 越小越看重相似度,越大看重多样性
        }
    )
    rag_chain = (
        {"context": retriever | format_docs, "question": RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )
    return rag_chain

# 自定义追加文档函数,避免重复添加相同文档
def add_documents_safe(db, docs):
    ids = [str(uuid.uuid4()) for _ in docs]
    db.add_documents(docs, ids=ids)
    print(f"[+] 本次追加 {len(docs)} 个文本块,当前总数量:{db._collection.count()}")

def get_vector_store(documents, incremental: bool = True):
    """
    :param documents: List[Document] 待入库文档
    :param incremental: True=增量追加;False=覆盖重建(删除旧库)
    :return: Chroma db对象
    """
    # 覆盖模式:删除整个库重新构建
    if not incremental:
        if os.path.exists(CHROMA_PERSIST_DIR):
            import shutil
            shutil.rmtree(CHROMA_PERSIST_DIR)
            print("[*] 删除旧向量库,开启覆盖重建模式")

    split_docs = text_splitter.split_documents(documents)

    if os.path.exists(CHROMA_PERSIST_DIR):
        # 库已存在:增量add_documents
        print("[+] 向量库已存在,增量追加新文档")
        db = Chroma(persist_directory=CHROMA_PERSIST_DIR, embedding_function=embeddings)
        #db.add_documents(split_docs)
        #print(f"[+] 本次追加 {len(split_docs)} 个文本块,当前总数量:{db._collection.count()}")
        add_documents_safe(db, split_docs)
    else:
        # 库不存在:初次创建
        print("[*] 新建向量库")
        db = Chroma.from_documents(
            documents=split_docs,
            embedding=embeddings,
            persist_directory=CHROMA_PERSIST_DIR
        )
        print(f"[+] 存入 {len(split_docs)} 个文本块")
    return db

if __name__ == "__main__":
    batch = [
        Document(page_content="llama.cpp用来本地运行GGUF大模型,支持CPU和GPU加速。", metadata={"source":"note1.txt"}),
        Document(page_content="RAG检索增强生成用于知识库问答。", metadata={"source":"note1.txt"}),
        Document(page_content="Chroma是轻量级向量数据库。", metadata={"source":"note2.txt"}),
        Document(page_content="RecursiveCharacterTextSplitter用于文档切分。", metadata={"source":"note2.txt"}),
    ]

    # 构建向量库incremental=True 增量追加文档=False覆盖重建
    db = get_vector_store(batch, incremental=True)
    print(f"\n向量库总文档数:{db._collection.count()}")

    # 打印全部候选+分数
    docs_with_score = db.similarity_search_with_score("llama.cpp则作用是啥?", k=10)
    print("\n[全部检索结果(分数越小越相似)]")
    for doc, score in docs_with_score:
        print(f"score={score:.4f} | source={doc.metadata['source']} | {doc.page_content}")

    # 构建RAG链
    chain = build_rag_chain(db)

    # 调用RAG链
    ans = chain.invoke("llama.cpp则作用是啥?")
    print(f"\n[最终NaiveRAG回答]: {ans}")

MMR检索策略有效平衡相似度与多样性,lambda_mult=0.3 优先保证语义匹配精准度,同时规避结果同质化。输出结果自带文档来源,可快速定位知识库素材出处,适合后续知识库迭代优化。

bash 复制代码
CMD> python main.py
[*] 新建向量库
[+] 存入 4 个文本块

向量库总文档数:4

[全部检索结果(分数越小越相似)]
score=1.0284 | source=note1.txt | RAG检索增强生成用于知识库问答。
score=1.0645 | source=note1.txt | llama.cpp用来本地运行GGUF大模型,支持CPU和GPU加速。
score=1.1095 | source=note2.txt | Chroma是轻量级向量数据库。
score=1.1582 | source=note2.txt | RecursiveCharacterTextSplitter用于文档切分。

[最终NaiveRAG回答]: llama.cpp用来本地运行GGUF大模型,支持CPU和GPU加速。

增量PDF读取测试

原生LangChain PDF加载器依赖冗余第三方库,本案例手写轻量化PDF解析工具,仅依赖 pypdf,简洁高效、无多余依赖。支持读取本地PDF可编辑文本,自动按页封装Document对象,记录页码与文件来源元数据。

适配增量/覆盖入库模式,可直接将PDF文档批量向量化入库,实现本地PDF知识库问答。注意:仅支持可复制文本的PDF,扫描图片型PDF无法提取文本。

python 复制代码
import os
from pypdf import PdfReader
from langchain_core.documents import Document

CHUNK_SIZE = 300
CHUNK_OVERLAP = 50
CHROMA_PERSIST_DIR = "./my_chroma_database"
RETRIEVE_TOP_K = 4

def text_splitter():
    pass

def get_vector_store(documents, incremental: bool = True):
    pass

def build_rag_chain(vector_db):
    pass

def my_pdf_loader(pdf_path):
    reader = PdfReader(pdf_path)
    docs = []
    for page_idx, page in enumerate(reader.pages):
        text = page.extract_text() or ""
        docs.append(Document(
            page_content=text,
            metadata={"source": pdf_path, "page": page_idx}
        ))
    return docs

if __name__ == "__main__":
    pdf_file_path = "./test.pdf"

    if not os.path.exists(pdf_file_path):
        raise FileNotFoundError(f"PDF文件不存在:{pdf_file_path}")

    pdf_docs = my_pdf_loader(pdf_file_path)
    print(f"[*] PDF读取完成,共 {len(pdf_docs)} 页")

    db = get_vector_store(pdf_docs, incremental=False)
    print(f"\n向量库总文档数:{db._collection.count()}")

    test_query = "概括一下文章内容"
    docs_with_score = db.similarity_search_with_score(test_query, k=10)

    chain = build_rag_chain(db)
    ans = chain.invoke(test_query)
    print(f"\n[最终NaiveRAG回答]: {ans}")

PDF每页文本经过切片拆分,2页PDF最终生成8个文本块,保证切片长度均匀、适配向量模型输入。大模型基于全部PDF语义信息完成概括问答,实现私有PDF文档智能解读能力。

bash 复制代码
CMD> python main.py
[*] PDF读取完成,共 2 页
[*] 删除旧向量库,开启覆盖重建模式
[*] 新建向量库
[+] 存入 8 个文本块

向量库总文档数:8

[最终NaiveRAG回答]: RAG是一种结合知识库检索与大模型生成的技术。它的工作流程包括文档加载、文本切分、向量化、存入向量数据库、检索、组装Prompt和输出答案。RAG的主要工作流程
包括文档加载、文本切分、向量化、存入向量数据库、检索、组装Prompt和输出答案。RAG的切分块大小不合适会导致噪声多或丢失上下文。RAG的主要工作流程包括文档加载、文
本切分、向量化、存入向量数据库、检索、组装Prompt和输出答案。

增量文本读取测试

针对本地TXT知识库场景,封装全自动文件夹批量加载工具。解决中文编码乱码问题,自动兼容 utf-8/gbk/gb2312 三大主流编码,无需手动适配。支持单层文件夹批量读取,可快速将大量笔记、文档批量入库构建私有知识库。

python 复制代码
import os
from langchain_core.documents import Document

CHUNK_SIZE = 300
CHUNK_OVERLAP = 50
CHROMA_PERSIST_DIR = "./my_chroma_database"
RETRIEVE_TOP_K = 4

def text_splitter():
    pass

def get_vector_store(documents, incremental: bool = True):
    pass

def build_rag_chain(vector_db):
    pass

def load_txt_file(file_path: str) -> list[Document]:
    """读取单个txt文件,自动尝试utf‑8、gbk编码"""
    if not os.path.exists(file_path):
        raise FileNotFoundError(f"文件不存在:{file_path}")

    text = ""
    for enc in ("utf‑8", "gbk", "gb2312"):
        try:
            with open(file_path, "r", encoding=enc) as f:
                text = f.read()
            break
        except UnicodeDecodeError:
            continue
    else:
        raise RuntimeError(f"{file_path} 编码无法识别,尝试utf‑8/gbk均失败")

    return [Document(page_content=text, metadata={"source": file_path})]

def load_any_file(file_path: str) -> list[Document]:
    """通用加载器,仅支持txt"""
    ext = os.path.splitext(file_path)[1].lower()
    if ext != ".txt":
        raise ValueError(f"暂不支持文件类型 {ext},仅支持txt")
    return load_txt_file(file_path)

class DirectoryLoader:
    """批量加载文件夹下全部txt文档"""
    def __init__(self, directory: str, recursive: bool = False):
        self.directory = directory
        self.recursive = recursive
        self.support_suffix = {".txt"}

    def load(self) -> list[Document]:
        all_docs = []
        if not os.path.isdir(self.directory):
            raise NotADirectoryError(f"目录不存在:{self.directory}")

        for root, _, files in os.walk(self.directory):
            for filename in files:
                ext = os.path.splitext(filename)[1].lower()
                if ext not in self.support_suffix:
                    continue
                full_path = os.path.join(root, filename)
                try:
                    print(f"\n[*] 正在加载:{full_path}")
                    docs = load_any_file(full_path)
                    all_docs.extend(docs)
                    print(f"[+] {filename} 读取成功")
                except Exception as e:
                    print(f"[!] 跳过 {filename},失败:{str(e)}")
            if not self.recursive:
                break
        return all_docs

if __name__ == "__main__":
    doc_dir = "./txt_docs"

    loader = DirectoryLoader(directory=doc_dir, recursive=False)
    all_docs = loader.load()

    if not all_docs:
        print("\n[!] 文件夹没有读取到任何有效txt文档,程序退出")
    else:
        db = get_vector_store(all_docs, incremental=False)
        print(f"\n[+] 向量库总文档数:{db._collection.count()}")

        chain = build_rag_chain(db)
        test_query = "llama.cpp的作用是什么?"
        ans = chain.invoke(test_query)
        print(f"[最终NaiveRAG回答]: {ans}\n")

批量读取文件夹内所有TXT文档,自动切片、入库、构建向量库,全程自动化。适合批量导入笔记、技术文档、学习资料,快速搭建专属文本知识库。

bash 复制代码
CMD> python main.py
[*] 正在加载:./txt_docs\111.txt
[+] 111.txt 读取成功

[*] 正在加载:./txt_docs\222.txt
[+] 222.txt 读取成功
[*] 新建向量库
[+] 存入 8 个文本块

[+] 向量库总文档数:8
[最终NaiveRAG回答]: llama.cpp是GGUF格式模型的本地推理库,主要用于在CPU/GPU上本地运行大模型。它支持GGUF量化模型,例如q4_k_m量化版本,可以降低显存占用,适合个人电脑本地部
署。llama.cpp可以启动OpenAI兼容接口,LangChain可以直接调用该接口,不需要特殊适配。

增量网页读取测试

本模块实现无依赖轻量化网页爬虫,不依赖LangChain社区爬虫组件,仅通过 requests+bs4 完成网页抓取与清洗。自动过滤脚本、样式、空行等无效噪声,提纯网页核心文本,适配网页知识库搭建场景。支持批量URL抓取,可快速将技术博客、文档网站内容入库问答。

python 复制代码
import os
import requests
from bs4 import BeautifulSoup
from langchain_core.documents import Document

CHUNK_SIZE = 300
CHUNK_OVERLAP = 50
CHROMA_PERSIST_DIR = "./my_chroma_database"
RETRIEVE_TOP_K = 4

def text_splitter():
    pass

def get_vector_store(documents, incremental: bool = True):
    pass

def build_rag_chain(vector_db):
    pass

def load_web_urls(url_list: list[str], timeout: int = 10) -> list[Document]:
    """
    手写网页抓取,仅网页来源,无langchain_community依赖
    :param url_list: url列表
    :param timeout: 请求超时秒数
    :return: List[Document]
    """
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
    }
    web_docs = []
    for url in url_list:
        try:
            print(f"\n[*] 正在抓取网页: {url}")
            resp = requests.get(url, headers=headers, timeout=timeout)
            resp.raise_for_status()
            soup = BeautifulSoup(resp.text, "html.parser")

            # 移除脚本、样式标签
            for bad_tag in soup(["script", "style", "noscript"]):
                bad_tag.decompose()

            raw_text = soup.get_text(separator="\n")
            lines = [line.strip() for line in raw_text.splitlines() if line.strip()]
            clean_text = "\n".join(lines)

            doc = Document(
                page_content=clean_text,
                metadata={"source": url}
            )
            web_docs.append(doc)
            print(f"[+] {url} 抓取成功,文本长度:{len(clean_text)}")

        except requests.exceptions.RequestException as e:
            print(f"[!] 请求异常 {url} : {str(e)}")
        except Exception as e:
            print(f"[!] 解析失败 {url} : {str(e)}")
    return web_docs

if __name__ == "__main__":
    all_docs = []

    url_list = [
        "https://www.lyshark.com"
    ]
    web_docs = load_web_urls(url_list)
    all_docs.extend(web_docs)

    if not all_docs:
        print("\n[!] 没有加载到任何文档,程序退出")
    else:
        db = get_vector_store(all_docs, incremental=False)
        print(f"\n[+] 向量库总文档数:{db._collection.count()}")

        chain = build_rag_chain(db)
        test_query = "请概括网页内容"
        ans = chain.invoke(test_query)
        print(f"[最终NaiveRAG回答]: {ans}\n")

网页原始文本经过降噪清洗、切片拆分,长文本切割为32个适配向量化的文本块,成功构建网页知识库。可实现对任意公开技术网页的内容总结、问答、检索,适合学习资料快速沉淀。

bash 复制代码
CMD> python main.py
[*] 正在抓取网页: https://www.lyshark.com
[+] https://www.lyshark.com 抓取成功,文本长度:7706
[*] 删除旧向量库,开启覆盖重建模式
[*] 新建向量库
[+] 存入 32 个文本块

[+] 向量库总文档数:32
[最终NaiveRAG回答]: 一个致力于信息安全知识分享的专业平台,平台高度关注前沿攻防技术研究。
CMP扫描。
相关推荐
lyshark17 小时前
基于 vLLM+Nginx 构建负载均衡推理集群
大模型应用技术实践
lyshark2 天前
LLama-Factory 实现大模型LoRA-SFT微调指南
大模型应用技术实践
lyshark4 天前
千问大模型完整RLHF全参数微调指南
大模型应用技术实践
lyshark5 天前
千问大模型二次LoRA‑SFT指令微调指南
大模型应用技术实践
lyshark11 天前
轻量化小模型MiniMind从训练到落地指南
大模型应用技术实践
lyshark15 天前
Ubuntu 大模型HF转GGUF全流程实践指南
大模型应用技术实践·linux 系统运维技术实践
lyshark16 天前
LangChain 消息流输出与结构化处理
大模型应用技术实践
lyshark17 天前
Python 原生封装 Llama.cpp 大模型推理接口
大模型应用技术实践
lyshark18 天前
LangGraph Server Agent 框架本地部署指南
大模型应用技术实践
lyshark19 天前
LangChain 实现AdvancedRAG增强向量检索生成
大模型应用技术实践