ZhouYI преди 2 месеца
родител
ревизия
b4435a5c00
променени са 10 файла, в които са добавени 246 реда и са изтрити 0 реда
  1. 15 0
      02_RAG/agent/chain.py
  2. 22 0
      02_RAG/agent/cli.py
  3. 29 0
      02_RAG/agent/config.py
  4. 12 0
      02_RAG/agent/llm.py
  5. 21 0
      02_RAG/agent/rag/document.py
  6. 46 0
      02_RAG/agent/rag/embedding.py
  7. 48 0
      02_RAG/agent/rag/rag_cli.py
  8. 43 0
      02_RAG/agent/rag/splitter.py
  9. BIN
      02_RAG/car_info.pdf
  10. 10 0
      02_RAG/main.py

+ 15 - 0
02_RAG/agent/chain.py

@@ -0,0 +1,15 @@
+from langchain.agents import create_agent
+from .llm import create_llm
+from .config import AppConfig
+
+
+def create_chat_agent(config:AppConfig):
+    llm = create_llm(config)
+    agent = create_agent(
+        llm=llm,
+        tools=[],
+        system_prompt="""
+        你是一个耐心、清晰的 Python 和 Agent 开发学习助手。
+        """
+    )
+    return agent

+ 22 - 0
02_RAG/agent/cli.py

@@ -0,0 +1,22 @@
+from .chain import create_chat_agent
+from .config import load_config
+
+
+def run_chat() -> None:
+    config = load_config()
+    agent = create_chat_agent(config)
+
+
+    while True:
+        question = input("请输入: ").strip()
+        if not question:
+            continue
+
+        if question.lower() in ["exit", "quit", "q"]:
+            break
+
+        result = agent.invoke(
+            {"messages": [{"role": "user", "content": question}]}
+        )
+        answer = result["messages"][-1].content
+        print(f"AI: {answer}")

+ 29 - 0
02_RAG/agent/config.py

@@ -0,0 +1,29 @@
+import os
+from dataclasses import dataclass
+
+
+@dataclass(frozen=True)
+class AppConfig:
+    api_key: str
+    api_url: str
+    model: str
+    database_url: str
+    history_table_name: str
+    default_session_id: str
+
+def load_config() -> AppConfig:
+    api_key = os.getenv("API_KEY")
+    if not api_key:
+        raise ValueError("API_KEY is not set in environment variables.")
+
+    return AppConfig(
+        api_key=api_key,
+        api_url=os.getenv(
+            "API_URL",
+            "https://dashscope.aliyuncs.com/compatible-mode/v1",
+        ),
+        model=os.getenv("MODEL", "qwen3.6-plus"),
+        database_url=os.getenv("DATABASE_URL", "mysql+pymysql://root:1234@localhost:3306/mydb"),
+        history_table_name=os.getenv("HISTORY_TABLE_NAME", "chat_history"),
+        default_session_id=os.getenv("SESSION_ID", "user_001"),
+    )

+ 12 - 0
02_RAG/agent/llm.py

@@ -0,0 +1,12 @@
+from langchain_openai import ChatOpenAI
+
+from .config import AppConfig
+
+
+def create_llm(config: AppConfig) -> ChatOpenAI:
+    return ChatOpenAI(
+        model_name=config.model,
+        api_key=config.api_key,
+        base_url=config.api_url,
+        temperature=0.7,
+    )

+ 21 - 0
02_RAG/agent/rag/document.py

@@ -0,0 +1,21 @@
+from langchain_community.document_loaders import PyMuPDFLoader
+import re
+
+def load_pdf_document(pdf_path: str)-> list:
+    # 创建加载器实例,传⼊ PDF ⽂件路径
+    pdf_loader = PyMuPDFLoader(pdf_path)
+    # 调⽤ load() ⽅法,返回⼀个 Document 列表(每⻚⼀个 Document)
+    pdf_pages = pdf_loader.load()
+    # 看看加载结果
+    print(f"⽂档类型:{type(pdf_pages)}")
+    print(f"PDF 共 {len(pdf_pages)} ⻚")
+    return pdf_pages
+
+def clean_pdf_text(text: str) -> str:
+    #删除换行符
+    text = text.replace('\n', '')
+    #删除多余空格
+    text = re.sub(r'\s+', ' ', text)
+    #删除连续换行符,只保留一个
+    text = re.sub(r'\n+', '\n', text)
+    return text.strip()

+ 46 - 0
02_RAG/agent/rag/embedding.py

@@ -0,0 +1,46 @@
+from langchain_community.embeddings import DashScopeEmbeddings
+from agent.config import load_config
+from langchain_community.vectorstores import Chroma
+import os
+def get_embedding_model():
+    """
+    获取 Embedding 模型。
+
+    """
+    config = load_config()
+    return DashScopeEmbeddings(
+        model="text-embedding-v4",
+        dashscope_api_key=config.api_key,  # 替换为你的 API Key
+    )
+
+def get_vectorstore(embedding_model, documents: list):
+    if "./chroma_db" in os.listdir():
+        db = Chroma(
+            persist_directory="./chroma_db",
+            embedding_function=embedding_model,
+        )
+    else:
+        db = Chroma.from_documents(
+            documents=documents,
+            embedding=embedding_model,
+            collection_metadata={"hnsw:space": "cosine"},
+            persist_directory="./chroma_db"
+        )
+        db.persist()
+    return db
+
+def query_vectorstore(query: str)-> list:
+    """
+    查询向量数据库。
+
+    参数:
+        query (str): 用户的查询问题。
+
+    返回:
+        list: 与查询最相关的文档列表。
+    """
+    embedding_model = get_embedding_model()
+    db = get_vectorstore(embedding_model, [])
+    retriever = db.as_retriever(search_kwargs={"k": 3})
+    return retriever.get_relevant_documents(query)
+

+ 48 - 0
02_RAG/agent/rag/rag_cli.py

@@ -0,0 +1,48 @@
+from agent.rag.document import load_pdf_document, clean_pdf_text
+from agent.rag import splitter
+from agent.rag.embedding import get_embedding_model, get_vectorstore
+from agent.llm import create_llm
+from agent.config import load_config
+
+from langchain_core.prompts import ChatPromptTemplate
+
+#加载文档
+pages = load_pdf_document("car_info.pdf")
+#清洗
+#documents = [clean_pdf_text(page.page_content) for page in pages]
+
+#分块
+#chunks=[splitter.embedding_split(document)for document in documents]
+docs=splitter.recursive_split(pages)
+#向量化+构建向量数据库
+
+embedding_model = get_embedding_model()
+db = get_vectorstore(embedding_model, docs)
+#创建检索器
+retriever = db.as_retriever(search_kwargs={"k": 3})
+
+
+
+context=""
+config=load_config()
+llm = create_llm(config)
+prompt=ChatPromptTemplate.from_messages([
+    ("system", "你是⼀个专业的知识库助⼿。请根据以下上下⽂回答问题。"),
+    ("user", "根据以下内容回答用户问题,如果无法从中获取答案,请说“抱歉,我无法回答这个问题。”\n\n{context}\n\n用户问题: {question}")
+])
+while True:
+    question = input("请输入: ").strip()
+    if not question:
+        continue
+
+    if question.lower() in ["exit", "quit", "q"]:
+        break
+    #查询向量数据库
+    relevant_docs = retriever.invoke(question)
+    for doc in relevant_docs:
+        context += doc.page_content + "\n"
+
+    #生成回答
+    chain=prompt|llm
+    answer = chain.invoke({"context": context, "question": question})
+    print("AI回答:", answer)

+ 43 - 0
02_RAG/agent/rag/splitter.py

@@ -0,0 +1,43 @@
+from langchain_text_splitters import RecursiveCharacterTextSplitter  
+from langchain_experimental.text_splitter import SemanticChunker
+from langchain_community.embeddings import DashScopeEmbeddings
+from agent.config import load_config
+def recursive_split(documents:list, chunk_size:int=500, chunk_overlap:int=100) -> list:
+    """
+    使用递归字符分割器将文本分割为较小的块。
+    
+    参数:
+        documents (list): 要分割的文档列表。
+        chunk_size (int): 每个块的最大字符数。
+        chunk_overlap (int): 块之间的重叠字符数。
+    """
+    text_splitter = RecursiveCharacterTextSplitter(
+        separators=["\n\n", "\n", "。", "!", "?", ".", " ",""],
+        chunk_size=chunk_size,
+        chunk_overlap=chunk_overlap,
+        length_function=len,
+    )
+    chunks = text_splitter.split_documents(documents)
+    return chunks
+
+
+def embedding_split(text:str) -> list:
+    """
+    使用 Embedding 模型计算相邻句⼦的语义相似度,当相似度「断崖式下降」时,就在那⾥切⼀⼑
+    
+    """
+    config=load_config()
+    # 使⽤通义的 Embedding 模型做语义分块
+    emmbedding_model=DashScopeEmbeddings(
+        model="text-embedding-v4",
+        dashscope_api_key=config.api_key,
+    )
+    # 创建语义分块器
+    semantic_splitter=SemanticChunker(
+        emmbedding_model=emmbedding_model,
+        breakpoint_threshold_type="percentile",
+        breakpoint_threshold_amount=85
+    )
+    #分块
+    chunks=semantic_splitter.split_text(text)
+    return chunks

BIN
02_RAG/car_info.pdf


+ 10 - 0
02_RAG/main.py

@@ -0,0 +1,10 @@
+from agent.config import load_config
+from agent.rag import rag_cli
+import os
+def main():
+    rag_cli.main()
+    print("02")  # 应输出你的AccessKeyID:AccessKeySecret
+
+
+if __name__ == "__main__":
+    main()