案例1-知识库Demo

 Loader:把外部文件读进来,变成 Document
 RecursiveCharacterTextSplitter:把大的 Document 切成小的 Document
  Embedding:把小 Document 变成向量
  VectorStore:保存向量,支持相似度检索
  Retriever:根据用户问题找相关 Document
  LLM:根据相关 Document 生成回答

api_key以及Redis_url全部存在.env文件中,通过os.getevn()来获取

依赖:

import os
import socket
from urllib.parse import urlparse
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.documents import Document
from langchain_redis import RedisConfig, RedisVectorStore
from langchain_community.document_loaders import TextLoader
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

load_dotenv(encoding="utf-8")

加载知识库:

"""
  每个 Document 通常有两个关键部分:
        document.page_content  正文内容。
        document.metadata  元数据,比如来源文件路径
"""
# 加载知识库
def load_knowledge_base(file_path: str) -> list[Document]:
    """读取知识库文件,并返回 Document 列表。"""
    loader = TextLoader(file_path, encoding="utf-8")
    documents = loader.load()
    print(f"已加载知识库,共 {len(documents)} 个文档")
    return documents

分割内容为Document列表

def split_text(
    documents: list[Document],
    chunk_size: int = 100,
    chunk_overlap: int = 0,
) -> list[Document]:
    """使用 LangChain 文本切分器切分文档。"""
    text_splitter = RecursiveCharacterTextSplitter(
          chunk_size=chunk_size,
          chunk_overlap=chunk_overlap,
          separators=["\n## ", "\n\n", "\n", "。", ",", " ", ""],
      )
    texts = text_splitter.split_documents(documents)
    print(f"分割文档数量:{len(texts)}")
    return texts

创建Embedding模型

# 将切片文本转为向量数据 ,或者向量数据转为文本
def create_embeddings():
    """初始化通义千问 embedding 模型。"""
    return DashScopeEmbeddings(
        model="text-embedding-v3",
        dashscope_api_key=os.getenv("aliQwen-api"),
    )
将切片文本向量化并写入向量数据库中
# 将切片文本向量化并写入向量数据库中
def build_retriever_from_documents(texts):
    if not texts:
        raise ValueError("文本切片为空,无法写入向量数据库")

    # 设置embedding模型
    embeddings = create_embeddings()
    config = RedisConfig(
        redis_url=os.getenv("REDIS_URL"),
        index_name="my_index3",
    )
    vector_store = RedisVectorStore(
        embeddings=embeddings,
        config=config,
    )
    vector_store.add_texts(
        texts=[document.page_content for document in texts],
        metadatas=[document.metadata for document in texts],
    )

    # 生成检索器,相似度按前K条作为context
    retriever = vector_store.as_retriever(
         # 过滤器会拦截掉相似度评分低于 0.8 的文档,最后只返回那些通过阈值筛选的文档
        search_kwargs={"k": 2},
        search_type= "similarity",
    )
    return retriever

初始化LLM模型

# 初始化LLM模型
def create_llm() -> ChatOpenAI:
    """初始化 LLM,只需调用一次。"""
    return ChatOpenAI(
        model="deepseek-chat",
        openai_api_key=os.getenv("deepseek-api"),
        openai_api_base="https://api.deepseek.com",
        temperature=0.3,
    )

构建prompt模板,组装chain

SYSTEM_MESSAGE = """
你是一个轻松的朋友。
请严格根据下面提供的知识库内容来回答用户的问题。
如果知识库中没有相关信息,请直接说"我不知道", 
检索结果为空时不调用模型,
相似度低于阈值时直接返回“知识库没有相关内容,
不要编造答案。
回答中附带来源片段
知识库内容{context}
"""

def build_chain(llm: ChatOpenAI):
    """构建问答 chain。"""
    prompt = ChatPromptTemplate.from_messages([
        ("system", SYSTEM_MESSAGE),
        ("human", "{question}"),
    ])
    return prompt | llm

格式化context

def format_documents(documents: list[Document]) -> str:
    """把检索到的文档拼成 prompt 中的 context。"""
    formatted = []
    for index, document in enumerate(documents, start=1):
          source = document.metadata.get("source", "未知来源")
          formatted.append(
              f"[片段 {index} | 来源: {source}]\n{document.page_content}"
          )
    return "\n\n".join(formatted)

构建主函数mian()

def main():
    # 文档路径
    knowledge_base_path = "knowledge_base.txt"
    # 错误排查
    if not os.path.exists(knowledge_base_path):
        print(f"错误:找不到知识库文件 {knowledge_base_path}")
        return
    if not os.getenv("deepseek-api"):
        print("错误:未找到 deepseek-api 环境变量")
        return
    if not os.getenv("aliQwen-api"):
        print("错误:未找到 aliQwen-api 环境变量")
        return
    if not os.getenv("REDIS_URL"):
        print("错误:未找到 REDIS_URL 环境变量")
        return
    
    print("正在加载知识库...")
    documents = load_knowledge_base(knowledge_base_path)
    print(f"知识库加载完成")
    
    texts = split_text(documents)
    retriever = build_retriever_from_documents(texts)
    llm = create_llm()
    chain = build_chain(llm)
    print("初始化完成!输入问题开始对话,输入退出结束。\n")
    
        while True:
        question = input("你:").strip()
        if question == "退出":
            print("再见!")
            break
        if not question:
            continue
        # retriever检索器,从知识库里找出k条和问题最相关的文档片段
        # 返回一个 list[Document];每个Document中有page_content 和 metadata
        #  page_content:原始的文本内容(就是你希望索引、检索、或者输入给 LLM 的那段文字)
        #  metadata: 可选的附加信息,比如来源文件、页码、时间戳、文档标题等
        # 对应RAG的【检索】
        relevant_documents = retriever.invoke(question)
        if not relevant_documents:
              print("助手:知识库没有相关内容。")
                continue
        # 把文档格式化成上下文; 把上一行检索到的多个 Document 对象,转换成一个普通字符串
        #  把检索器返回的 Document 列表,整理成可以放进 prompt 的上下文文本
        # 可以把它理解成:把图书馆找到的资料复印出来,整理成一份给大模型看的参考资料
        # 对应RAG的【增强】
        context = format_documents(relevant_documents)
        #  把 context 填进 SYSTEM_MESSAGE 里的 {context}
        #  把 question 填进 human 消息里的 {question}
        #  然后调用大模型生成回答
        response = chain.invoke({"context": context, "question": question})

        print(f"\n助手:{response.content}\n")


if __name__ == "__main__":
    main()

"""
RAG 里很重要的能力是控制 context:
  chunk_size
  chunk_overlap
  top_k
  去重
  重排序
  最大 token 限制
  来源标注
"""
    

 

posted @ 2026-05-12 11:51  幻影之舞  阅读(13)  评论(0)    收藏  举报