我们进入第四天。根据之前的计划,第四天的目标是:让 RAG 支持多文档(上传 PDF、Word)和更好的文本切分(按段落/语义)。
不过为了适应你目前每天1小时的学习节奏,我会把任务精简成两个核心部分:
支持从多个文本文件加载知识(而不是单一的 my_knowledge.txt)。
改进文本切分方式(从简单的固定字符数改为按段落切分,保留语义完整性)。
你可以先完成这些,后续如果有时间再扩展为支持 PDF/Word。
第四天任务概览(约 60 分钟)
步骤 内容 预计时间
1 准备多个知识文件(例如关于你项目的不同方面) 10分钟
2 修改代码:批量加载文件夹下所有 .txt 文件 15分钟
3 改进文本切分:按段落(双换行)切分,并过滤短文本 15分钟
4 测试多文档问答 10分钟
5 (可选)将 ChromaDB 持久化,避免每次重启都重新加载 10分钟
详细操作指南
第 1 步:准备多个知识文件(10 分钟)
在 app_rag.py 同级目录下,新建一个文件夹 knowledge_base,在里面创建几个 .txt 文件,例如:
文件1:project_overview.txt
text
我的后端项目是一个电商平台的后台管理系统。项目使用 Spring Boot 3.0 作为主框架,采用微服务架构。
文件2:tech_stack.txt
text
技术栈包括:Java 17, Spring Boot 3.0, MySQL 8.0, Redis 7.0, RabbitMQ, Docker, Kubernetes。
部署环境为阿里云 ACK。
文件3:features.txt
text
核心功能:用户认证、商品管理、订单处理、库存管理、支付对接(支付宝+微信)、数据报表。
这样你就有了三个不同主题的知识文件。
第 2 步:修改代码,批量加载文件夹下所有 .txt 文件(15 分钟)
你需要修改 app_rag.py 中加载文档的部分。原来的代码只读取单一的 my_knowledge.txt,现在改成遍历 knowledge_base 文件夹下的所有 .txt 文件。
在 app_rag.py 中找到原来的 load_and_chunk 和加载逻辑,替换为以下代码:
python
import glob
定义知识库文件夹路径
KNOWLEDGE_DIR = "knowledge_base"
def load_all_txt_files(directory):
"""读取目录下所有 .txt 文件,返回一个列表,每个元素是 (文件名, 文件内容)"""
all_files = glob.glob(os.path.join(directory, "*.txt"))
documents = \[\]
for file_path in all_files:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
保存文件名和内容,便于调试
documents.append((os.path.basename(file_path), content))
return documents
改进的文本切分函数:按段落切分(双换行分隔)
def chunk_by_paragraphs(text, min_chunk_size=50):
"""按段落切分,过滤掉太短的段落(如空行或只有几个字符)"""
paragraphs = text.split('\n\n') # 双换行作为段落分隔符
进一步按单换行分割长段落(可选)
chunks = \[\]
for para in paragraphs:
para = para.strip()
if len(para) < min_chunk_size:
continue
如果段落仍然很长(比如超过500字),可以再按句子切分,这里先简单处理
if len(para) > 1000:
按句号分割
sentences = para.replace('。', '。\n').split('\n')
for sent in sentences:
sent = sent.strip()
if len(sent) > min_chunk_size:
chunks.append(sent)
else:
chunks.append(para)
return chunks
加载并处理所有知识文件
if os.path.exists(KNOWLEDGE_DIR):
all_docs = load_all_txt_files(KNOWLEDGE_DIR)
total_chunks = 0
for filename, content in all_docs:
chunks = chunk_by_paragraphs(content)
for i, chunk in enumerate(chunks):
embedding = embedder.encode(chunk).tolist()
collection.add(
embeddings=embedding,
documents=chunk,
metadatas={"source": filename, "chunk_index": i}, # 添加元数据,便于追溯来源
ids=f"{filename}_{i}"
)
total_chunks += len(chunks)
print(f"Loaded {len(all_docs)} files, {total_chunks} chunks into ChromaDB.")
else:
print(f"Warning: {KNOWLEDGE_DIR} directory not found. Please create it and add .txt files.")
注意:确保 KNOWLEDGE_DIR 文件夹存在,否则会打印警告。
第 3 步:测试多文档问答(10 分钟)
保存修改后的 app_rag.py。
在终端中重启服务(如果已经在运行,按 Ctrl+C 停止,再重新运行):
bash
uvicorn app_rag:app --reload --port 8000
在另一个终端或 Jupyter 中测试:
python
import requests
测试问题1
resp = requests.post("http://127.0.0.1:8000/rag", json={"question": "我的项目用了哪些技术?"})
print(resp.json()"answer")
测试问题2
resp2 = requests.post("http://127.0.0.1:8000/rag", json={"question": "项目有哪些核心功能?"})
print(resp2.json()"answer")
你应该会看到模型分别从 tech_stack.txt 和 features.txt 中提取信息来回答。
第 4 步(可选):将 ChromaDB 持久化(10 分钟)
当前服务重启后,之前加载的文档会丢失(因为使用了 chromadb.Client() 内存模式)。为了让知识库持久保存,改用持久化客户端。
修改代码中初始化 ChromaDB 的部分:
原来:
python
chroma_client = chromadb.Client()
collection = chroma_client.create_collection(name="docs")
改为:
python
chroma_client = chromadb.PersistentClient(path="./chroma_db") # 数据会保存在当前目录下的 chroma_db 文件夹
如果集合已存在,则获取;否则创建
try:
collection = chroma_client.get_collection(name="docs")
except:
collection = chroma_client.create_collection(name="docs")
注意:如果集合已存在,再次添加文档时可能会重复。你可以在每次启动时先清空集合,或者判断文档是否已存在。简单起见,可以每次启动时删除旧的集合再创建,但会丢失之前的增量。一个折中方案是:启动时检测集合是否为空,若为空则加载文件;若不为空则跳过加载,直接使用已有数据。
今日产出总结
✅ 支持从文件夹加载多个 .txt 文档。
✅ 改进了文本切分(按段落、过滤短文本)。
✅ (可选)实现了 ChromaDB 持久化。
✅ 能够回答跨文件的问题,并且答案有据可循。