| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- from langchain_community.document_loaders import PyMuPDFLoader
- from langchain_text_splitters import RecursiveCharacterTextSplitter
- from langchain_community.embeddings import DashScopeEmbeddings
- from langchain_community.vectorstores import Chroma
- from langchain_core.prompts import ChatPromptTemplate
- from langchain_community.chat_models import ChatTongyi
- from langchain_core.output_parsers import StrOutputParser
- from dotenv import load_dotenv
- import re
- import os
- load_dotenv()
- api_key = os.getenv('QWEN_API_KEY')
- # ============================================================
- # 1. 加载 PDF 文档
- # ============================================================
- loader = PyMuPDFLoader("D:/code/shuheAI/02_RAG/华为擎云W585X 用户指南-(PGUX,KOS&UOS_02,zh-cn).pdf")
- pdf_pages = loader.load()
- print(f"文档类型:{type(pdf_pages)}")
- print(f"PDF 共{len(pdf_pages)}页")
- # 查看第一页的内容和元数据
- first_page = pdf_pages[0]
- print(f"元数据:{first_page.metadata}")
- print(f"内容预览:{first_page.page_content[:200]}")
- # ============================================================
- # 2. 清洗 PDF 文本
- # ============================================================
- def clean_pdf_text(text: str) -> str:
- """清洗 PDF 解析出的文本,去除常见噪声"""
- # 删除非中文字符之间的换行符
- text = re.sub(r'[^一](\n)[^一]', lambda m: m.group(0).replace('\n', ''), text)
- # 删除项目符号和多余空格
- text = text.replace('•', '').replace(' ', ' ')
- # 删除连续的换行符(保留一个)
- text = re.sub(r'\n{2,}', '\n', text)
- return text.strip()
- # 对所有页面清洗
- for page in pdf_pages:
- page.page_content = clean_pdf_text(page.page_content)
- # ============================================================
- # 3. 文本分块
- # ============================================================
- splitter = RecursiveCharacterTextSplitter(
- separators=["\n\n", "\n", "。", "!", "?", " ", ""],
- chunk_size=50,
- chunk_overlap=10,
- length_function=len
- )
- split_docs = splitter.split_documents(pdf_pages)
- print(f"切分后的文件数量:{len(split_docs)}")
- print(f"切分后的字符数(可以用来大致评估 token 数):{sum([len(doc.page_content) for doc in split_docs])}")
- # 过滤掉 page_content 为空或仅含空白的文档
- valid_docs = [
- doc for doc in split_docs
- if doc.page_content and doc.page_content.strip()
- ]
- print(f"有效块数量:{len(valid_docs)}")
- print(f"总字符数(可大致评估 Token 数):{sum(len(d.page_content) for d in valid_docs)}")
- # ============================================================
- # 4. 初始化 Embedding 模型
- # ============================================================
- embedding_model = DashScopeEmbeddings(
- model="text-embedding-v3",
- dashscope_api_key=api_key
- )
- # 单条文本向量化
- text = "RAG系统搭建实战"
- embedding = embedding_model.embed_query(text)
- print(f"向量维度:{len(embedding)}")
- print(f"前5个值:{embedding[:5]}")
- # ============================================================
- # 5. 向量化 + 存入向量库
- # ============================================================
- persist_dir = "./my_knowledge_db"
- if os.path.exists(persist_dir) and os.listdir(persist_dir):
- print(f"加载已有向量库:{persist_dir}")
- vectordb = Chroma(
- persist_directory=persist_dir,
- embedding_function=embedding_model
- )
- else:
- print(f"创建新向量库:{persist_dir}")
- vectordb = Chroma.from_documents(
- documents=valid_docs,
- embedding=embedding_model,
- collection_metadata={"hnsw:space": "cosine"}, # 余弦相似度
- persist_directory=persist_dir
- )
- # 创建检索器,设置返回 Top-3 最相关文档
- retriever = vectordb.as_retriever(search_kwargs={"k": 3})
- # ============================================================
- # 6. 提问
- # ============================================================
- query = "如何进入BIOS设置?"
- relevant_docs = retriever.invoke(query)
- for i, doc in enumerate(relevant_docs):
- print(f"--- 结果{i+1} ---")
- print(f"内容:{doc.page_content[:100]}...")
- print(f"来源:{doc.metadata}")
- print()
- # 带分数的相似度检索(分数越低越相似,0 表示完全匹配)
- results = vectordb.similarity_search_with_score(query, k=3)
- for doc, score in results:
- print(f"内容:{doc.page_content[:100]}... | 相似度分数:{score:.4f}")
- # ============================================================
- # 7. 生成回答
- # ============================================================
- context = "\n\n---\n\n".join([d.page_content for d in relevant_docs])
- prompt = ChatPromptTemplate.from_template("""
- 你是一个专业的知识库助手。请根据以下上下文回答问题。
- **规则:**
- - 只基于提供的上下文回答,不要编造
- - 如果上下文中没有相关信息,直接说「根据现有资料,我找不到这个问题的答案」
- - 回答要简洁直接,引用原文时用引号
- **上下文:**
- {context}
- **问题:**
- {question}
- """)
- llm = ChatTongyi(model="qwen-plus", dashscope_api_key=api_key)
- chain = prompt | llm | StrOutputParser()
- answer = chain.invoke({"context": context, "question": query})
- print(answer)
|