案例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 限制
来源标注
"""

浙公网安备 33010602011771号