2025.11.6
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# pip install --upgrade langchain-openai
|
||||
# pip install --upgrade langchain-huggingface langchain-core langchain-community
|
||||
# pip install --upgrade langchain-core langchain-community
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from langchain_core.runnables import RunnablePassthrough
|
||||
|
||||
load_dotenv()
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
import os
|
||||
from langchain_huggingface import HuggingFaceEmbeddings
|
||||
from langchain_chroma import Chroma
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
|
||||
|
||||
Persist_directory = './chroma_db_war_and_peace_bge_small_en_v1.5'
|
||||
Embedding_model = 'BAAI/bge-small-en-v1.5'
|
||||
|
||||
if not os.path.exists(Persist_directory):
|
||||
print(f"错误: 知识库文件 {Persist_directory} 未找到。")
|
||||
print("请先运行'build_index.py'生成向量数据库,再运行该文件")
|
||||
exit()
|
||||
|
||||
print('---加载本地向量数据库---')
|
||||
|
||||
# 模块A:链接本地Chroma向量数据库
|
||||
# 1. 加载 Embedding 模型
|
||||
embedding_model = HuggingFaceEmbeddings(model_name=Embedding_model)
|
||||
|
||||
# 2. 从本地目录加载Chroma DB
|
||||
db = Chroma(
|
||||
persist_directory=Persist_directory,
|
||||
embedding_function=embedding_model
|
||||
)
|
||||
print(f'Chroma数据库已从本地加载(共{db._collection.count()}条)\n')
|
||||
|
||||
# 模块B:R-A-G Flow
|
||||
# 1. R-检索
|
||||
retriever = db.as_retriever(search_kwargs={"k": 3}) # 召回3条相关数据
|
||||
|
||||
# 2. A-增强
|
||||
sys_prompt = """
|
||||
你是一个博学的历史学家和文学评论家。
|
||||
请根据以下上下文回答问题。如果上下文**强烈暗示**了答案,即使未明说,也可推理回答。
|
||||
如果完全无关,请回答“对不起,根据所提供的上下文我不知道”。
|
||||
|
||||
[上下文]: {context}
|
||||
[问题]: {question}
|
||||
"""
|
||||
prompt = ChatPromptTemplate.from_messages([
|
||||
('system', sys_prompt),
|
||||
('human', '{question}')
|
||||
])
|
||||
|
||||
# 3. G-生成
|
||||
llm = ChatOpenAI(
|
||||
model="deepseek-chat",
|
||||
api_key=api_key,
|
||||
base_url="https://api.deepseek.com"
|
||||
)
|
||||
|
||||
# 4. 辅助函数
|
||||
def format_docs(docs):
|
||||
return "\n".join(doc.page_content for doc in docs)
|
||||
|
||||
|
||||
# 5. 组装RAG链条(LCEL)
|
||||
rag_chain = (
|
||||
{"context":retriever | format_docs, "question": RunnablePassthrough()}
|
||||
| prompt
|
||||
| llm
|
||||
| StrOutputParser()
|
||||
)
|
||||
# 运行RAG链
|
||||
print('---正在运行RAG链条---')
|
||||
question = '莫斯科大火发生在小说的哪一部分?有哪些角色亲历了这场灾难?'
|
||||
response = rag_chain.invoke(question)
|
||||
print(f'提问:{question}')
|
||||
print(f'回答:{response}')
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
from langchain_huggingface import HuggingFaceEmbeddings
|
||||
from langchain_chroma import Chroma
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.runnables import RunnablePassthrough
|
||||
|
||||
# --- Reranker (02) 新增的 import ---
|
||||
from langchain.retrievers import ContextualCompressionRetriever
|
||||
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
|
||||
from langchain.retrievers.document_compressors import CrossEncoderReranker
|
||||
|
||||
Persist_directory = './chroma_db_war_and_peace_bge_small_en_v1.5'
|
||||
Embedding_model = 'BAAI/bge-small-en-v1.5'
|
||||
|
||||
if not os.path.exists(Persist_directory):
|
||||
print(f"错误: 知识库文件 {Persist_directory} 未找到。")
|
||||
print("请先运行'build_index.py'生成向量数据库,再运行该文件")
|
||||
exit()
|
||||
|
||||
print('---加载本地向量数据库---\n')
|
||||
|
||||
# 1. 加载 Embedding 模型
|
||||
embeddings_model = HuggingFaceEmbeddings(model_name=Embedding_model)
|
||||
|
||||
# 2. 加载 Chroma db
|
||||
db = Chroma(
|
||||
persist_directory=Persist_directory,
|
||||
embedding_function=embeddings_model
|
||||
)
|
||||
|
||||
print('---Chroma数据库已加载---\n')
|
||||
|
||||
# --- 模块 B (R-A-G Flow) ---
|
||||
# 1. R-检索--强化版
|
||||
# 1.1 基础检索器(Base Retriever) - '粗召回'
|
||||
base_retriever = db.as_retriever(search_kwargs={"k":5}) # K调大到5
|
||||
# 1.2 Reranker (重排器) - "精排序" -- 首次运行需要耗时下载
|
||||
print('正在加载Reranker模型 (bge-reranker-base)...')
|
||||
encoder = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base") # 加载Ranker模型
|
||||
reranker = CrossEncoderReranker(model=encoder,top_n=2) # 对检索结果进行精排
|
||||
# 1.3 创建管道封装器
|
||||
compression_retriever = ContextualCompressionRetriever(
|
||||
base_retriever=base_retriever, # 用Chroma做 海选
|
||||
base_compressor=reranker # 用Reranker做 精选
|
||||
)
|
||||
retriever = compression_retriever
|
||||
|
||||
print('--检索器已升级为Reranker模式--\n')
|
||||
|
||||
# 2. A-增强
|
||||
sys_prompt = """
|
||||
你是一个博学的历史学家和文学评论家。
|
||||
请根据以下上下文回答问题。如果上下文**强烈暗示**了答案,即使未明说,也可推理回答。
|
||||
如果完全无关,请回答“对不起,根据所提供的上下文我不知道”。
|
||||
|
||||
[上下文]: {context}
|
||||
[问题]: {question}
|
||||
"""
|
||||
prompt = ChatPromptTemplate.from_messages([
|
||||
('system',sys_prompt),
|
||||
('human','{question}')
|
||||
])
|
||||
|
||||
# 3. G-生成
|
||||
llm = ChatOpenAI(
|
||||
model="deepseek-chat",
|
||||
api_key=api_key,
|
||||
base_url="https://api.deepseek.com"
|
||||
)
|
||||
|
||||
# 4. 辅助函数
|
||||
def format_docs(docs):
|
||||
return "\n".join(doc.page_content for doc in docs)
|
||||
|
||||
|
||||
# 5. 组装RAG链 (LCEL)
|
||||
rag_chain = (
|
||||
{"context":retriever | format_docs,"question":RunnablePassthrough()}
|
||||
| prompt
|
||||
| llm
|
||||
| StrOutputParser()
|
||||
)
|
||||
|
||||
# --运行RAG链--
|
||||
print('---正在运行RAG链条---')
|
||||
question = '皮埃尔是共济会成员吗?他在其中扮演什么角色?'
|
||||
response = rag_chain.invoke(question)
|
||||
print(f'提问:{question}')
|
||||
print(f'回答:{response}')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
from langchain_huggingface import HuggingFaceEmbeddings
|
||||
from langchain_chroma import Chroma
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.runnables import RunnablePassthrough
|
||||
from langchain.retrievers import ContextualCompressionRetriever
|
||||
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
|
||||
from langchain.retrievers.document_compressors import CrossEncoderReranker
|
||||
from langchain_core.tools import tool
|
||||
|
||||
|
||||
# 全局 LLM (供Agent和Rag共用)
|
||||
llm = ChatOpenAI(
|
||||
model="deepseek-chat",
|
||||
api_key=api_key,
|
||||
base_url="https://api.deepseek.com"
|
||||
)
|
||||
|
||||
# (1) 构建一个可复用的 RAG链条 (P1+P2)
|
||||
def build_rag_chain(llm_instance):
|
||||
print('---正在构建RAG链条...---\n')
|
||||
|
||||
Persist_directory = './chroma_db_war_and_peace_bge_small_en_v1.5'
|
||||
Embedding_model = 'BAAI/bge-small-en-v1.5'
|
||||
Encoder_model = "BAAI/bge-reranker-base"
|
||||
|
||||
if not os.path.exists(Persist_directory):
|
||||
raise FileNotFoundError(f'索引目录{Persist_directory}未找到,请先运行 build_index.py')
|
||||
|
||||
embeddings_model = HuggingFaceEmbeddings(model_name=Embedding_model)
|
||||
db = Chroma(
|
||||
persist_directory=Persist_directory,
|
||||
embedding_function=embeddings_model
|
||||
)
|
||||
|
||||
# 1. R-检索--强化版
|
||||
base_retriever = db.as_retriever(search_kwargs={"k":5})
|
||||
encoder = HuggingFaceCrossEncoder(model_name=Encoder_model)
|
||||
reranker = CrossEncoderReranker(model=encoder,top_n=2)
|
||||
compression_retriever=ContextualCompressionRetriever(
|
||||
base_retriever=base_retriever,
|
||||
base_compressor=reranker
|
||||
)
|
||||
retriever = compression_retriever
|
||||
|
||||
# 2. A-增强
|
||||
sys_prompt = """
|
||||
你是一个博学的历史学家和文学评论家。
|
||||
请根据以下上下文回答问题。如果上下文**强烈暗示**了答案,即使未明说,也可推理回答。
|
||||
如果完全无关,请回答“对不起,根据所提供的上下文我不知道”。
|
||||
|
||||
[上下文]: {context}
|
||||
[问题]: {question}
|
||||
"""
|
||||
prompt = ChatPromptTemplate.from_messages([
|
||||
('system',sys_prompt),
|
||||
('human','{question}')
|
||||
])
|
||||
# 3.G-生成(llm已在全局生成)
|
||||
|
||||
# 4. 辅助函数
|
||||
def format_docs(docs):
|
||||
return '\n'.join(doc.page_content for doc in docs)
|
||||
|
||||
# 5.组装RAG链条
|
||||
rag_chain = (
|
||||
{'context':retriever | format_docs, 'question': RunnablePassthrough()}
|
||||
| prompt
|
||||
| llm_instance
|
||||
| StrOutputParser()
|
||||
)
|
||||
print('---RAG链条构建完毕!---\n')
|
||||
return rag_chain
|
||||
|
||||
|
||||
# 初始化RAG链
|
||||
rag_chain_instance = build_rag_chain(llm)
|
||||
|
||||
# (2) 封装为标准 Langchain Tool
|
||||
@tool
|
||||
def search_war_and_peace(query):
|
||||
"""查询《战争与和平》小说中的内容,包括人物、情节、历史事件等"""
|
||||
print(f'\n正在检索《战争与和平》:{query}')
|
||||
return rag_chain_instance.invoke(query)
|
||||
|
||||
# 也可以与其他工具并列使用
|
||||
@tool
|
||||
def get_weather(location):
|
||||
"""模拟获得天气信息"""
|
||||
return f"{location}当前天气:23℃,晴,风力2级"
|
||||
|
||||
|
||||
tools = [search_war_and_peace,get_weather]
|
||||
|
||||
|
||||
# 运行
|
||||
if __name__ == '__main__':
|
||||
question = "皮埃尔是共济会成员吗?他在其中扮演什么角色?"
|
||||
res = search_war_and_peace.invoke(question)
|
||||
print(f'问题:{question}')
|
||||
print(f'回答:{res}')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
from langchain_huggingface import HuggingFaceEmbeddings
|
||||
from langchain_chroma import Chroma
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.prompts import ChatPromptTemplate,MessagesPlaceholder
|
||||
from langchain_core.output_parsers import StrOutputParser
|
||||
from langchain_core.runnables import RunnablePassthrough
|
||||
from langchain.retrievers import ContextualCompressionRetriever
|
||||
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
|
||||
from langchain.retrievers.document_compressors import CrossEncoderReranker
|
||||
from langchain_core.tools import tool
|
||||
|
||||
from langchain.agents import AgentExecutor, create_tool_calling_agent
|
||||
from langchain_community.chat_message_histories import ChatMessageHistory
|
||||
from langchain_core.runnables import RunnableWithMessageHistory
|
||||
|
||||
|
||||
|
||||
# 1. 构建一个可复用的 RAG链条
|
||||
def build_rag_chain(llm_instance):
|
||||
print('---正在构建RAG链条---')
|
||||
|
||||
Persist_directory = './chroma_db_war_and_peace_bge_small_en_v1.5'
|
||||
Embedding_model = 'BAAI/bge-small-en-v1.5'
|
||||
Encoder_model = "BAAI/bge-reranker-base"
|
||||
|
||||
if not os.path.exists(Persist_directory):
|
||||
raise FileNotFoundError(f'索引目录{Persist_directory}未找到,请先运行 build_index.py')
|
||||
|
||||
embedding_model = HuggingFaceEmbeddings(model_name=Embedding_model)
|
||||
db = Chroma(
|
||||
persist_directory=Persist_directory,
|
||||
embedding_function=embedding_model
|
||||
)
|
||||
# R
|
||||
base_retriever = db.as_retriever(search_kwargs={'k':5})
|
||||
encoder = HuggingFaceCrossEncoder(model_name=Encoder_model)
|
||||
reranker = CrossEncoderReranker(model=encoder,top_n=2)
|
||||
compression_retriever = ContextualCompressionRetriever(
|
||||
base_retriever=base_retriever,
|
||||
base_compressor=reranker
|
||||
)
|
||||
retriever = compression_retriever
|
||||
# A
|
||||
sys_prompt = """
|
||||
你是一个博学的历史学家和文学评论家。
|
||||
请根据以下上下文回答问题。如果上下文**强烈暗示**了答案,即使未明说,也可推理回答。
|
||||
如果完全无关,请回答“对不起,根据所提供的上下文我不知道”。
|
||||
|
||||
[上下文]: {context}
|
||||
[问题]: {question}
|
||||
"""
|
||||
prompt = ChatPromptTemplate.from_messages([
|
||||
('system',sys_prompt),
|
||||
('human','{question}')
|
||||
])
|
||||
|
||||
def format_docs(docs):
|
||||
return '\n'.join(doc.page_content for doc in docs)
|
||||
|
||||
# R-A-G
|
||||
rag_chain = (
|
||||
{'context':retriever | format_docs,'question':RunnablePassthrough()}
|
||||
| prompt
|
||||
| llm_instance
|
||||
| StrOutputParser()
|
||||
)
|
||||
print('---RAG链条构建完毕!---\n')
|
||||
return rag_chain
|
||||
|
||||
|
||||
|
||||
def create_agent_with_memory():
|
||||
# LLm
|
||||
llm = ChatOpenAI(
|
||||
model="deepseek-chat",
|
||||
api_key=api_key,
|
||||
base_url="https://api.deepseek.com"
|
||||
)
|
||||
# Prompt
|
||||
prompt = ChatPromptTemplate.from_messages([
|
||||
('system','你是一个强大的助手。你能查天气,也能查《战争与和平》。请尽力回答用户所提的所有问题。'),
|
||||
MessagesPlaceholder(variable_name="history"), # 05篇所学:记忆占位符
|
||||
('human','{input}'),
|
||||
MessagesPlaceholder(variable_name="agent_scratchpad") # 05篇所学:ReAct 思考链,使其能够调用工具
|
||||
])
|
||||
|
||||
# Tool
|
||||
rag_chain_instance = build_rag_chain(llm_instance=llm)
|
||||
|
||||
|
||||
@tool
|
||||
def search_war_and_peace(query):
|
||||
"""查询《战争与和平》小说中的内容,包括人物、情节、历史事件等"""
|
||||
print(f'\n正在检索《战争与和平》:{query}')
|
||||
return rag_chain_instance.invoke(query)
|
||||
|
||||
@tool
|
||||
def get_weather(location):
|
||||
"""模拟获得天气信息"""
|
||||
return f"{location}当前天气:23℃,晴,风力2级"
|
||||
|
||||
tools = [get_weather,search_war_and_peace]
|
||||
|
||||
# 创建Agent
|
||||
agent = create_tool_calling_agent(llm=llm,tools=tools,prompt=prompt)
|
||||
agent_executor = AgentExecutor(agent=agent,tools=tools,verbose=False)
|
||||
|
||||
# 封装Memory
|
||||
store = {}
|
||||
|
||||
def get_session_history(session_id:int):
|
||||
if session_id not in store:
|
||||
store[session_id] = ChatMessageHistory()
|
||||
return store[session_id]
|
||||
|
||||
|
||||
# 添加记忆功能
|
||||
agent_with_memory = RunnableWithMessageHistory(
|
||||
runnable=agent_executor,
|
||||
get_session_history=get_session_history,
|
||||
input_messages_key="input",
|
||||
history_messages_key="history"
|
||||
)
|
||||
return agent_with_memory
|
||||
|
||||
|
||||
# 测试
|
||||
|
||||
if __name__ == '__main__':
|
||||
session_id = 'user123'
|
||||
agent = create_agent_with_memory()
|
||||
while 1:
|
||||
user_input = input('\n你:')
|
||||
if user_input=='quit':
|
||||
print('拜拜~')
|
||||
exit()
|
||||
response = agent.invoke(
|
||||
{'input':user_input},
|
||||
config={'configurable':{'session_id':session_id}}
|
||||
)
|
||||
print(f"AI:{response['output']}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# 🧩 模块说明:RAG 进阶 - 从基础到智能 Agent
|
||||
|
||||
> 📌 核心知识点:持久化向量库(Chroma)|精排序(Reranker)|RAG 工具化|Agent 集成|记忆型对话|六大模块融合
|
||||
|
||||
---
|
||||
|
||||
### 1. war_and_peace.txt (知识库源文件)
|
||||
|
||||
托尔斯泰经典小说《战争与和平》的纯文本版本(约3.2MB),作为本篇 RAG 系统的私有知识库。
|
||||
|
||||
- 说明:
|
||||
- 文件需放置在项目根目录。
|
||||
- 若缺失,可从 [Project Gutenberg #2600](https://www.gutenberg.org/ebooks/2600.txt.utf-8) 下载并重命名为 `war_and_peace.txt`。
|
||||
- 所有后续 RAG 功能均基于此文档构建。
|
||||
|
||||
---
|
||||
|
||||
### 2. build_index.py (构建 Chroma 向量数据库)
|
||||
|
||||
将 `war_and_peace.txt` 加载、分块、向量化,并存入 Chroma 持久化向量数据库。
|
||||
|
||||
- ✅ 掌握点:
|
||||
- 使用 RecursiveCharacterTextSplitter 分割长文本(chunk_size=500, overlap=75)。
|
||||
- 调用 HuggingFaceEmbeddings(模型:BAAI/bge-small-en-v1.5)生成语义向量。
|
||||
- 通过 Chroma(persist_directory=...) 创建可持久化、支持增删改的向量库。
|
||||
- 自动分批插入(每批 ≤5000 条),规避 Chroma 单次写入上限限制。
|
||||
|
||||
- 注意事项:
|
||||
- 该脚本仅需运行一次,成功后会生成目录 chroma_db_war_and_peace_bge_small_en_v1.5。
|
||||
- 首次运行需下载 Embedding 模型(约2分钟)+ 向量化全文(约3分钟),总耗时较长。
|
||||
- ✅ 项目已附带预建好的 chroma_db_war_and_peace_bge_small_en_v1.5 文件夹,推荐直接使用,无需重复运行此脚本。
|
||||
- 如需重建,请先手动删除该目录再执行。
|
||||
|
||||
---
|
||||
|
||||
### 3. `01_load_from_chroma.py` (从 Chroma 加载向量库)
|
||||
|
||||
加载已持久化的 Chroma 向量数据库,用于后续检索。
|
||||
|
||||
- ✅ 掌握点:
|
||||
- 使用 `Chroma(persist_directory="...")` 从磁盘加载已保存的向量库。
|
||||
- 无需重新向量化,实现“离线索引”快速启动。
|
||||
- 获取 `db.as_retriever()` 作为后续 RAG 流程的输入。
|
||||
|
||||
- 依赖前提:
|
||||
- 必须存在 `chroma_db_war_and_peace_bge_small_en_v1.5` 目录。
|
||||
|
||||
---
|
||||
|
||||
### 4. `02_reranker.py` (引入精排序器)
|
||||
|
||||
在召回结果上应用 Reranker 模型,提升上下文相关性。
|
||||
|
||||
- ✅ 掌握点:
|
||||
- 使用 `BAAI/bge-reranker-base` Cross-Encoder 对检索结果进行重排序。
|
||||
- 通过 `ContextualCompressionRetriever` 封装为统一检索器。
|
||||
- 仅保留 Top-2 最相关文档,过滤语义噪声。
|
||||
|
||||
- 依赖前提:
|
||||
- 必须存在 `chroma_db_war_and_peace_bge_small_en_v1.5` 目录。
|
||||
|
||||
---
|
||||
|
||||
### 5. `03_rag_as_tool.py` (RAG 工具化封装)
|
||||
|
||||
将 RAG 链封装为 LangChain `@tool`,供 Agent 调用。
|
||||
|
||||
- ✅ 掌握点:
|
||||
- 在启动时一次性构建 RAG 链(避免每次调用重复加载模型/数据库)。
|
||||
- 使用 `@tool` 装饰器注册 `search_war_and_peace(query)` 函数。
|
||||
- 支持与其他工具(如 `get_weather`)并列使用。
|
||||
|
||||
- 依赖前提:
|
||||
- 必须存在 `chroma_db_war_and_peace_bge_small_en_v1.5` 目录。
|
||||
|
||||
---
|
||||
|
||||
### 6. `04_memory_rag_agent.py` (终极集成 Agent)
|
||||
|
||||
首次融合 LangChain 六大核心模块,构建带记忆、能自主决策的智能体。
|
||||
|
||||
- ✅ 掌握点:
|
||||
- LLM:ChatOpenAI 作为推理引擎。
|
||||
- Prompt:含 MessagesPlaceholder("history") 的系统提示。
|
||||
- Chain:LCEL 构建的 RAG 链 + AgentExecutor。
|
||||
- Memory:RunnableWithMessageHistory 实现跨轮次上下文记忆。
|
||||
- Agents:create_tool_calling_agent 驱动 ReAct 循环。
|
||||
- RAG:以 search_war_and_peace 工具形式注入私有知识能力。
|
||||
|
||||
- 效果:
|
||||
- Agent 可结合历史(如“他”指皮埃尔)精准调用 RAG,同时支持查天气等外部工具。
|
||||
|
||||
- 依赖前提:
|
||||
- 必须存在 `chroma_db_war_and_peace_bge_small_en_v1.5` 目录。
|
||||
|
||||
---
|
||||
|
||||
### 🔔 全局注意事项
|
||||
|
||||
- 所有 `.py` 文件(除 `build_index.py` 外)均依赖 `chroma_db_war_and_peace_bge_small_en_v1.5` 目录。
|
||||
- 推荐直接使用已提供的向量数据库文件夹,跳过 `build_index.py` 的长时间构建过程。
|
||||
- 若自行运行 `build_index.py`,请确保 `war_and_peace.txt` 已就位。
|
||||
- 如需更换 Embedding 模型(如升级至 `bge-m3`),需同步更新 `build_index.py` 和 RAG 脚本中的模型名。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 💡 **建议**:
|
||||
|
||||
直接运行 04_memory_rag_agent.py 体验完整 Agent 能力;若想自定义知识库,可替换 war_and_peace.txt 并重新运行 build_index.py(记得先删除旧数据库目录)。
|
||||
@@ -0,0 +1,72 @@
|
||||
# pip install chroma
|
||||
# pip install -U langchain-chroma
|
||||
|
||||
import os
|
||||
from langchain_community.document_loaders import TextLoader
|
||||
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
||||
from langchain_huggingface import HuggingFaceEmbeddings
|
||||
from langchain_chroma import Chroma
|
||||
|
||||
|
||||
knowledge_base_file = "war_and_peace.txt"
|
||||
# 持久化目录: Chroma会把所有数据(向量+文本+元数据)都存到这个文件夹
|
||||
persist_directory = './chroma_db_war_and_peace_bge_small_en_v1.5'
|
||||
embedding_model = 'BAAI/bge-small-en-v1.5' # 如果愿意等待,可以换成模型"BAAI/bge-m3",效果更好更适合长文,但下载时间也更久(2.2G)
|
||||
chunk_size = 500
|
||||
chunk_overlap = 75
|
||||
|
||||
|
||||
# 检查是否已创建
|
||||
if os.path.exists(persist_directory):
|
||||
print(f"检测到已存在的向量数据库: {persist_directory}")
|
||||
print("跳过索引构建。如需重新构建,请手动删除该目录。")
|
||||
exit()
|
||||
|
||||
if not os.path.exists(knowledge_base_file):
|
||||
print(f"错误: 知识库文件 {knowledge_base_file} 未找到。")
|
||||
print("请从 https://www.gutenberg.org/ebooks/2600.txt.utf-8 下载")
|
||||
print("并重命名为 war_and_peace.txt 放在当前目录。")
|
||||
exit()
|
||||
|
||||
print('---正在构建索引---')
|
||||
|
||||
# 1. 加载
|
||||
loader = TextLoader(knowledge_base_file,encoding='utf8')
|
||||
docs = loader.load()
|
||||
print('加载完成...\n')
|
||||
# 2. 分割
|
||||
text_splitter = RecursiveCharacterTextSplitter(
|
||||
chunk_size=chunk_size,
|
||||
chunk_overlap=chunk_overlap
|
||||
)
|
||||
splits = text_splitter.split_documents(docs)
|
||||
print('分割完成...\n')
|
||||
# 3. 向量化 -- 第一次运行会下载模型,预计耗时2分钟
|
||||
embedding_model = HuggingFaceEmbeddings(
|
||||
model_name=embedding_model,
|
||||
model_kwargs={'device':'cpu'}, # 强制模型在cpu上运行
|
||||
encode_kwargs={'batch_size':64} # 每次处理64个文本片段
|
||||
)
|
||||
print('Embedding模型加载完成...\n')
|
||||
# 4. 存储
|
||||
print('正在构建Chroma索引...(注:此步耗时较久,预计要3min)\n')
|
||||
db = Chroma(
|
||||
persist_directory=persist_directory,
|
||||
embedding_function=embedding_model
|
||||
)
|
||||
|
||||
# 分批添加切片chunks(每批不超过 5000)
|
||||
batch_size = 5000 # 必须 < 5461
|
||||
for i in range(0, len(splits), batch_size):
|
||||
batch = splits[i:i + batch_size]
|
||||
db.add_documents(batch)
|
||||
print(f"已插入 {min(i + batch_size, len(splits))} / {len(splits)} 条")
|
||||
|
||||
|
||||
print(f'✅ 索引构建完毕,共 {len(splits)} 条,已保存到 {persist_directory}')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user