Loading

LangChain搭建简单本地知识库

LangChain 是一个开源的大语言模型(LLM)应用开发框架,核心是把大模型与外部数据、工具、工作流连接起来,解决大模型 “知识过时、无法联网、不会用工具” 的问题,让你快速搭建出上下文感知、能推理、可调用工具的 AI 应用,LangChain = LLM + 外部数据 + 工具 + 工作流编排,是开发 LLM 应用的事实标准框架

  • 定位:LLM 应用的 “乐高积木” 与 “操作系统”,不是替代大模型,而是增强与编排大模型。

  • 解决三大痛点

    1. 知识边界:让模型能读你的文档、数据库、实时数据(RAG)。
    2. 工具缺失:让模型会用计算器、搜索引擎、API、代码解释器。
    3. 流程复杂:把多步骤任务(提问→检索→计算→回答)串成可复用的 “链”。
  • 语言支持Python(主流) + JavaScript/TypeScript

搭建流程

  1. 文档加载,并按一定条件切割成片段
  2. 将切割的文本片段灌入检索引擎
  3. 封装检索接口
  4. 构建调用流程:Query -> 检索 -> Prompt -> LLM -> 回复
环境准备
# 1. 处理PDF文件的核心库:用于读取PDF内容、提取文本(LangChain的PDF加载器底层常依赖它)
pip install pypdf2

# 2. 阿里云百炼大模型的Python SDK:用于调用阿里云的通义千问等大模型(替代OpenAI)
pip install dashscope

# 3. LangChain核心库:提供LLM应用开发的核心组件(链、代理、记忆、提示模板等)
pip install langchain

# 4. LangChain对接OpenAI生态的专用库:封装OpenAI/兼容OpenAI接口的模型调用逻辑(如通义千问也可适配)
pip install langchain-openai

# 5. LangChain社区生态库:包含第三方集成(如PDF加载器、向量数据库对接、本地模型适配等)
pip install langchain-community

# 6. FAISS向量数据库(CPU版):Facebook开源的高效向量检索库,用于RAG场景的本地向量存储与匹配
pip install faiss-cpu
创建知识库
# -*- coding: utf-8 -*-


import os
import logging
import dashscope
import pickle
import config
from PyPDF2 import PdfReader
from langchain.chains.question_answering import load_qa_chain
from langchain_openai import OpenAI, ChatOpenAI
from langchain_openai import OpenAIEmbeddings
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.callbacks.manager import get_openai_callback
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from typing import List, Tuple


# 提取pdf文件每页的内容和对应的页码
def extract_text_with_page_numbers(file) -> Tuple[str, List[int]]:
    """
    :param file:  pdf文件
    :return: 提取的文本内容,每行文本对应的页面列表
    """
    text = ""
    page_numbers = []
    for page_number, page in enumerate(file.pages, start=1):
        extracted_text = page.extract_text()
        if extracted_text:
            text += extracted_text
            page_numbers.extend([page_number] * len(extracted_text.split("\n")))
        else:
            logging.warning(f"Page {page_number} has no text")
    return text,page_numbers



# 处理文本创建向量存储
def process_text_with_splitter(text:str,page_numbers:List[int],save_path:str = None) -> FAISS:
    """
    :param text:  提取的文本内容
    :param page_numbers: 每行文本对应的页面列表
    :param save_path: 保存向量数据库的路径
    :return: 基于FAISS的向量存储对象
    """

    # 创建文本分割器材递归文本分割,长文本分割成小块
    text_splitter = RecursiveCharacterTextSplitter(
        # 分割器会优先用第一个分隔符(\n\n,空两行)拆分文本
        # 如果拆分后的块仍超过chunk_size,再用下一个分隔符(\n,换行),依此类推,直到最后用空字符串("")强制拆分字符。
        # 优先按段落(\n\n)切 → 再按行(\n)→ 再按句子(.)→ 再按空格()→ 最后按字符切,符合中文 / 英文文本的自然分割逻辑
        separators=["\n\n","\n","."," ",""], # 分割符优先级(从高到低)
        chunk_size=512, # 每个文本块的最大长度(按 length_function 计算,这里是字符数)
        chunk_overlap=128, # 相邻文本块的重叠长度(避免切割后语义断裂),比如第一个块是 1-512 字符,第二个块是 385-896 字符(512-128=385),保证语义连贯
        length_function=len # 计算文本长度的函数(默认就是 len,按字符数算),如果是处理嵌入模型(如 OpenAI Embedding),也可以换成按「token 数」计算(比如 tiktoken 库)
    )

    # 分割文本
    chunks = text_splitter.split_text(text)

    # 调用阿里百练炼型,用于后续分割的文本向量
    embeddings = DashScopeEmbeddings(
        model = "text-embedding-v4",
        dashscope_api_key=config.API_KEY
    )

    # 从文本块创建知识库
    knowledgeBase = FAISS.from_texts(chunks,embeddings)

    # 存储每个文本块对应的页码信息
    page_info = { chunk:page_numbers[i] for i,chunk in enumerate(chunks)}
    knowledgeBase.page_info = page_info
    if save_path:
        os.makedirs(save_path,exist_ok=True)
        # 保存FAISS向量数据库
        knowledgeBase.save_local(save_path)

        # 保存页码信息到同一目录
        with open(os.path.join(save_path,"page_info.pkl"),"wb") as f:
            pickle.dump(page_info, f)

    return knowledgeBase

#     从磁盘加载向量数据库和页码信息
def load_knowledge_base(load_path: str, embeddings=None) -> FAISS:

    # 如果没有提供嵌入模型,则创建一个新的
    if embeddings is None:
        embeddings = DashScopeEmbeddings(
            model="text-embedding-v4",
            dashscope_api_key=config.API_KEY
        )

    # 加载FAISS向量数据库,添加allow_dangerous_deserialization=True参数以允许反序列化
    knowledgeBase = FAISS.load_local(load_path, embeddings, allow_dangerous_deserialization=True)
    print(f"向量数据库已从 {load_path} 加载。")

    # 加载页码信息
    page_info_path = os.path.join(load_path, "page_info.pkl")
    if os.path.exists(page_info_path):
        with open(page_info_path, "rb") as f:
            page_info = pickle.load(f)
        knowledgeBase.page_info = page_info
        print("页码信息已加载。")
    else:
        print("警告: 未找到页码信息文件。")

    return knowledgeBase

if __name__ == '__main__':
    pdf_reader = PdfReader("人工智能全景科普:从基础认知到未来展望(10页完整版).pdf")
    text,page_numbers = extract_text_with_page_numbers(pdf_reader)
    print(text)
    print(page_numbers)

    # 处理文本并创建知识库,同时保存到磁盘
    save_dir = "./vector_db"
    knowledgeBase = process_text_with_splitter(text, page_numbers, save_path=save_dir)
    # 处理文本并创建知识库
    knowledgeBase = process_text_with_splitter(text, page_numbers)
    print( knowledgeBase)
查询知识库
# 从内存中加载查询
def query_chat(query,knowledgeBase):
    """
    :param query: 提问的问题
    :param knowledgeBase:  使用内存中已经存在的向量存储对象
    :return: 
    """
    # 执行相似度搜索,找到与查询相关的文档
    docs = knowledgeBase.similarity_search(query)

    # 初始化对话大模型
    chatLLM  = ChatOpenAI(
         api_key = config.API_KEY,
        base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1",
        model = "deepseek-v3"
    )

    # 加载问答链
    chain = load_qa_chain(chatLLM, chain_type="stuff")

    # 准备输入数据
    input_data = {"input_documents": docs, "question": query}

    # 使用回调函数跟踪API调用成本
    with get_openai_callback() as cost:
        # 执行问答链
        response = chain.invoke(input=input_data)
        print(f"查询已处理。成本: {cost}")
        print(response["output_text"])
        print("来源:")

    # 记录唯一的页码
    unique_pages = set()

    # 显示每个文档块的来源页码
    for doc in docs:
        text_content = getattr(doc, "page_content", "")
        source_page = knowledgeBase.page_info.get(
            text_content.strip(), "未知"
        )

        if source_page not in unique_pages:
            unique_pages.add(source_page)
            print(f"文本块页码: {source_page}")

image-20260319143249762

    # 从硬盘加载向量数据库(替换原内存直接使用逻辑)
    knowledgeBase = FAISS.load_local(
        vector_db_path,
        embeddings,
        allow_dangerous_deserialization=True
    )
 

posted @ 2026-03-19 14:50  木子七  阅读(1)  评论(0)    收藏  举报