2 Commits b777f60891 ... 149d40550a

Auteur SHA1 Bericht Datum
  ex_fangwen.wen 149d40550a Merge remote-tracking branch 'origin/master' 2 maanden geleden
  ex_fangwen.wen eb0d03eb05 day_02 work 2 maanden geleden
3 gewijzigde bestanden met toevoegingen van 205 en 0 verwijderingen
  1. BIN
      resources/TCL空调说明书.pdf
  2. 0 0
      work_01/work_01.py
  3. 205 0
      work_02/rag_practice.py

BIN
resources/TCL空调说明书.pdf


+ 0 - 0
src/work_01.py → work_01/work_01.py


+ 205 - 0
work_02/rag_practice.py

@@ -0,0 +1,205 @@
+"""
+一. 构建知识库
+    1. 收集资料(无优化空间,但为质量的前提)
+    2. 解析并清洗数据
+        ①清洗噪音
+        ②清洗多余无意义字符
+    3. 分块(决定7成左右RAG质量)
+        ①按固定字符切分
+        ②按递归符号切分(递归符号:需人工分析,少量可官网AI总结)
+        ③按文档块标识符切分(一般固定文档格式类型,如markdown)
+        ④按语义模块切分(费钱,使用embadding模型)
+        ⑤按LLM切分(烧钱,不考虑钱直接冲)
+    4. 向量化(智普embadding模型, 余弦相似度)
+    5. 向量数据入库(测试chroma,主流milvus)
+二. 用户基于知识库提问
+    1. 用户提问
+    2. 用户问题向量化,到库中检索,top_k返回前n条完整结果
+    3. 将结果封装进提示词
+    4. 请求LLM返回最终结果
+"""
+
+from dotenv import load_dotenv
+from langchain_openai import ChatOpenAI
+import os, re
+from fastapi import FastAPI, UploadFile, File, HTTPException
+from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
+import tempfile
+from langchain_text_splitters import RecursiveCharacterTextSplitter
+from langchain_community.vectorstores import Chroma
+from langchain_community.embeddings import ZhipuAIEmbeddings
+from langchain_core.prompts import PromptTemplate
+from zai import ZhipuAiClient
+
+
+load_dotenv(override=True)
+deepseek_base_url = os.getenv("DEEPSEEK_BASE_URL")
+deepseek_base_key = os.getenv("DEEPSEEK_BASE_KEY")
+deepseek_base_name = os.getenv("DEEPSEEK_BASE_NAME")
+
+
+# ==============================一. 构建知识库==================================================
+# ==============================1. 收集资料==================================================
+class KnowledgeBaseBuilder:
+    def __init__(self, pdf_dir="./resources", persist_dir="./chroma_db"):
+        self.pdf_dir = pdf_dir
+        self.persist_dir = persist_dir
+        self.embeddings = ZhipuAIEmbeddings(
+            model="embedding-3",  # 或者 "embedding-3"
+            api_key=os.getenv("ZHIPU_API_KEY")
+        )
+        self.documents = []
+        
+    def load_pdfs(self):
+        """加载PDF文件"""
+        loader = DirectoryLoader(
+            self.pdf_dir,
+            glob="**/*.pdf",
+            loader_cls=PyPDFLoader,
+            show_progress=True
+        )
+        self.documents = loader.load()
+        print(f"加载了 {len(self.documents)} 个文档")
+        return self.documents
+
+
+# ==============================2. 解析并清洗数据==================================================
+    def clean_documents(self, docs):
+        """清洗文档数据"""
+        cleaned_docs = []
+        empty_count = 0
+        
+        for i, doc in enumerate(docs):
+            text = doc.page_content
+            
+            # 检查文本是否为空
+            if not text or len(text.strip()) == 0:
+                empty_count += 1
+                print(f"⚠️ 文档 {i+1} 内容为空,跳过")
+                continue
+            
+            # ① 清洗噪音:移除页眉页脚、水印等
+            text = re.sub(r'第\s*\d+\s*页\s*/\s*共\s*\d+\s*页', '', text)
+            text = re.sub(r'Copyright.*?\n', '', text, flags=re.IGNORECASE)
+            text = re.sub(r'机密|内部资料|仅供内部使用', '', text)
+            
+            # ② 去除无意义字符
+            text = re.sub(r'\n\s*\n+', '\n\n', text)  # 合并多个空行
+            text = re.sub(r'[ \t]+', ' ', text)       # 合并多个空格
+            # 保留中英文、数字和常用标点
+            text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\.\,\,\。\!\?\:\;\(\)\n]', ' ', text)
+            text = re.sub(r'\s+', ' ', text).strip()
+            
+            # 清洗后再次检查
+            if not text or len(text) < 10:
+                empty_count += 1
+                print(f"⚠️ 文档 {i+1} 清洗后内容过短,跳过")
+                continue
+            
+            doc.page_content = text
+            cleaned_docs.append(doc)
+        
+        print(f"✅ 清洗完成,有效文档: {len(cleaned_docs)}, 跳过: {empty_count}")
+        
+        if not cleaned_docs:
+            raise ValueError("清洗后没有有效的文档内容")
+        
+        return cleaned_docs
+
+# ==============================3. 分块==================================================
+    def chunk_documents(self, docs, strategy="recursive"):
+        """
+        多种分块策略
+        strategy: fixed, recursive, semantic, markdown
+        当前实现递归分块策略
+        """
+        if strategy == "recursive":
+            splitter = RecursiveCharacterTextSplitter(
+                            chunk_size=500,
+                            chunk_overlap=50,
+                            separators=[
+                                "\n\n",  # 段落
+                                "\n",    # 行
+                                "。",    # 中文句号
+                                "!",    # 感叹号
+                                "?",    # 问号
+                                ";",    # 分号
+                                ",",    # 逗号
+                                ".",     # 英文句号
+                                "!", 
+                                "?",
+                                " "
+                            ],
+                            length_function=len,
+                            keep_separator=True
+                        )
+            chunked_docs = splitter.split_documents(docs)
+            return chunked_docs
+# ==============================4. 向量化==================================================
+# ==============================5. 向量数据入库==================================================
+    def build_vectorstore(self, chunks, collection_name="knowledge_base"):
+        """向量化并存入Chroma数据库"""
+        # 使用智谱Embedding模型
+        vectorstore = Chroma.from_documents(
+            documents=chunks,
+            embedding=self.embeddings,
+            persist_directory=self.persist_dir,
+            collection_name=collection_name
+        )
+        vectorstore.persist()
+        print(f"向量数据库构建完成,存储于: {self.persist_dir}")
+        return vectorstore
+
+
+
+# ==============================二. 用户基于知识库提问==================================================
+# ==============================1. 用户提问==================================================
+# ==============================2. 用户问题向量化,到库中检索,top_k返回前n条完整结果==================================================
+# ==============================3. 将结果封装进提示词==================================================
+# ==============================4. 请求LLM返回最终结果==================================================
+llm = ChatOpenAI(
+    base_url = deepseek_base_url,
+    api_key = deepseek_base_key,
+    model= deepseek_base_name
+)
+
+if __name__ == "__main__":
+    knowlege = KnowledgeBaseBuilder()
+    docs = knowlege.load_pdfs()
+    clean_docs = knowlege.clean_documents(docs)
+    chunks = knowlege.chunk_documents(clean_docs)
+    vectorstore = knowlege.build_vectorstore(chunks)
+
+    # vectorstore = Chroma(
+    #     persist_directory="./chroma_db",  # 持久化目录
+    #     embedding_function=knowlege.embeddings,
+    #     collection_name="knowledge_base"  # 集合名称(与构建时一致)
+    # )
+    user_question = '遥控器如何操作?'
+    retrieved_docs = vectorstore.similarity_search(user_question, k=3)
+    context = "\n\n".join([doc.page_content for doc in retrieved_docs])
+    prompt_template = """
+        你是一个专业的AI助手,请基于以下参考资料回答用户的问题。
+        
+        【参考资料】:
+        {context}
+        
+        【用户问题】:
+        {question}
+        
+        要求:
+        1. 严格基于参考资料回答,不要编造信息
+        2. 如果参考资料中没有相关信息,请明确告知
+        3. 回答要简洁、准确、有条理
+        
+        回答:
+        """
+    prompt = PromptTemplate(
+        template=prompt_template,
+        input_variables=["context", "question"]
+    )
+    
+    # 4. 请求LLM
+    formatted_prompt = prompt.format(context=context, question=user_question)
+    response = llm.invoke(formatted_prompt)
+    print(response)