2025最新AI大模型应用开发,AI大模型项目实战,RAG项目实战,全网稀缺实战教程,99%都在讲理论,直接带你手写代码实战完成项目
·
图书智能检索系统
前言
基于检索增强生成(RAG)技术的智能检索系统完整实现。这个项目展示了如何将传统文学作品与现代AI技术相结合,为用户提供智能化的文学问答体验。
项目效果


技术栈
- 核心框架: Python 3.13
- 编辑器: PyCharm
- AI模型: DeepSeek API
- 向量化: TF-IDF + scikit-learn
- 中文分词: jieba
- 文档处理: 自定义文本分块算法
- 缓存机制: pickle序列化
- 可视化界面:streamlit
sklearn中文社区:https://scikit-learn.org.cn/
numpy中文文档:https://numpy.com.cn/
streamlit : https://docs.streamlit.io/
系统架构
用户问题 → 文本预处理 → 向量化 → 相似度检索 → 上下文构建 → AI生成答案
核心类设计SmartLibRAG
初始化设计思路
def __init__(self, api_key: str, docs_dir: str = "docs"):
设计这个类的主要目标是提供一个可配置、可扩展的框架,用于加载文档、预处理文本、构建向量索引以及执行问答任务。
- 依赖注入: API密钥通过参数传入,便于测试和部署
- 路径抽象: 使用
pathlib.Path而非字符串处理路径 - 缓存优先: 预设缓存目录,避免重复计算
- 组件分离: 将OpenAI客户端、向量化器、文档存储等分别管理
文档加载策略
def load_documents(self) -> None:
设计亮点:
- 使用生成器模式遍历文件
- 异常处理确保单个文件错误不影响整体加载
- 结构化存储文档元信息(文件名、路径、内容)
实际应用建议:
在生产环境中,建议添加文件格式验证和编码检测。
智能文本分块算法
def split_text_into_chunks(self, text: str, chunk_size: int = 300, overlap: int = 50):
这是整个系统的核心算法之一。我采用了基于语义边界的分块策略:
算法特点:
- 语义完整性: 按句号、问号、感叹号分句,保持语义完整
- 动态调整: 根据句子长度动态调整块大小
- 重叠处理: 虽然参数中有overlap,但当前实现更注重语义边界
优化建议:
# 可以考虑添加重叠逻辑
if len(chunks) > 1 and overlap > 0:
# 添加重叠文本逻辑
pass
缓存机制设计
def preprocess_documents(self) -> None:
缓存策略:
-
文档块缓存:避免重复分词和分块
-
向量缓存:避免重复向量化计算
-
增量更新:检测文件变化(可扩展)
这种设计在处理大型文档集合时能显著提升性能。
中文NLP处理
def chinese_tokenizer(self, text):
中文处理的挑战:
- 分词准确性: 使用jieba处理中文分词
- 停用词过滤: 自定义停用词库,提升检索精度
- 标点符号处理: 智能过滤标点,保留有意义的词汇,例如:,。!?;:“”()【】《》、
- 单字符过滤: 避免无意义的单字符干扰
jieba文档:https://github.com/fxsjy/jieba
常见停用词:
['的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这']
实现细节
if (word and
len(word) > 1 and # 长度过滤
word not in self.stopwords and # 停用词过滤
not word.isspace() and # 空白字符过滤
not all(char in punctuation for char in word)): # 标点过滤
向量索引构建

示例代码
tf = TfidfVectorizer(norm='l2')
train = ["Chinese Beijing Chinese", "Chinese Chinese shanghai", "Chinese Macao", "Tokyo Japan, Chinese"]
# 训练得到语料库
tf.fit(train)
print(tf.get_feature_names_out()) # ['beijing' 'chinese' 'japan' 'macao' 'shanghai' 'tokyo']
test = ["Chinese Chinese Chinese Tokyo Japan"]
tf_fit = tf.transform(test)
print(tf_fit.toarray()) # [[0. 0.7420575 0.4739993 0. 0. 0.4739993]]
公式计算
"""
TF-IDE = 词频(TF) × 逆文档频率(IDF)
TF = 某个词在文章中的出现次数 / 文章的总次数
IDF = log(语料库的文档总数 / 包含该词的文档数 + 1)
"""
def build_vector_index(self) -> None:
技术选择说明:
- TF-IDF vs 深度学习嵌入: 考虑到部署成本和效果平衡,选择TF-IDF
- 参数调优:
max_features=5000: 控制词汇表大小ngram_range=(1, 2): 包含单词和双词组合min_df=1, max_df=0.95: 过滤极端频率词汇
缓存恢复机制
# 巧妙的缓存恢复设计
self.vectorizer = TfidfVectorizer(
tokenizer=self.chinese_tokenizer,
vocabulary=cache_data['vocabulary']
)
self.vectorizer.idf_ = cache_data['idf']
检索算法优化
def search_relevant_chunks(self, query: str, top_k: int = 10, similarity_threshold: float = 0.01):
检索策略
- 阈值过滤: 先过滤低相似度结果
- 排序优化: 只对有效结果排序
- 数量控制: top_k限制返回数量
性能优化点
# 避免对所有结果排序,只处理有效结果
valid_indices = np.where(similarities > similarity_threshold)[0]
sorted_indices = valid_indices[np.argsort(similarities[valid_indices])[::-1]]
AI生成模块
def generate_answer(self, query: str, context_chunks: List[Dict]) -> str:
Prompt工程
- 系统角色定义: 明确AI作为红楼梦专家的身份
- 回答规范: 详细的回答要求和格式规范
- 上下文构建: 结构化的文档片段组织
deepseek提示词库:https://api-docs.deepseek.com/zh-cn/prompt-library/
错误处理策略
try:
# API调用
except Exception as e:
error_type = type(e).__name__
error_details = str(e) if str(e) else "未知错误"
return f"生成答案时出错: {error_type} - {error_details}"
系统提示词
"""
# Role: 红楼梦研究专家
## Profile
- language: 中文
- description: 精通《红楼梦》文本及红学研究,能够深入解析作品的人物、情节、诗词及文化内涵
- background: 多年从事《红楼梦》研究与教学,参与过多项红学课题研究
- personality: 严谨细致,富有文人气质,善于引经据典
- expertise: 文本分析、人物研究、诗词鉴赏、文化解读
- target_audience: 红学爱好者、文学研究者、学生群体
## Skills
1. 文本解析能力
- 情节梳理: 能准确还原小说情节脉络
- 细节把握: 对文本细节有敏锐洞察力
- 人物分析: 深入剖析人物性格与命运
- 诗词解读: 精准解析书中诗词内涵
2. 学术研究能力
- 文献考证: 熟悉各类红学研究成果
- 文化阐释: 揭示作品背后的文化内涵
- 比较研究: 能与其他文学作品进行对比
- 版本鉴别: 了解不同版本差异
## Rules
1. 回答原则:
- 基于文本: 所有回答必须严格依据原著文本
- 严谨准确: 不妄加猜测,不传播未经考证的观点
- 深度解析: 透过表面现象揭示深层含义
- 客观公正: 避免个人主观臆断
2. 行为准则:
- 引经据典: 重要观点需引用原文佐证
- 语言典雅: 保持与原著相称的文雅风格
- 层次分明: 回答要有逻辑性和条理性
- 深入浅出: 复杂问题要解释得通俗易懂
3. 限制条件:
- 不涉争议: 避免介入红学争议性话题
- 不妄评续作: 对后四十回保持审慎态度
- 不越文本: 不脱离文本过度解读
- 不代作者: 不以作者口吻发表观点
## Workflows
- 目标: 提供专业准确的红楼梦解读
- 步骤 1: 仔细理解用户问题,明确询问重点
- 步骤 2: 检索相关文本段落,确认信息准确性
- 步骤 3: 组织回答内容,适当引用原文
- 步骤 4: 以典雅文风呈现完整解答
- 预期结果: 用户获得权威、深入的红楼梦知识
## Initialization
作为红楼梦研究专家,你必须遵守上述Rules,按照Workflows执行任务。
"""
系统集成与接口设计
统一问答接口
def ask(self, question: str, top_k: int = 10, similarity_threshold: float = 0.01) -> Dict:
接口设计原则
-
参数可配置: 允许调整检索参数
-
结构化返回: 统一的返回格式
-
来源追踪: 提供答案来源信息
返回格式设计
return {
'question': question,
'answer': answer,
'sources': sources # 包含来源文件、相似度、内容预览
}
部署和使用
环境配置
根目录下创建.env文件
DEEPSEEK_API_KEY=your_api_key_here
初始化流程
rag = SmartLibRAG(api_key=api_key)
rag.initialize() # 一键初始化所有组件
性能优化建议
1. 缓存策略优化
- 实现文件变更检测
- 添加内存缓存层
- 考虑Redis等外部缓存
2. 检索算法优化
- 考虑使用Faiss等专业向量检索库
- 实现多路召回策略
- 添加查询扩展机制
3. 并发处理
- 添加异步处理支持
- 实现请求队列管理
- 考虑多进程部署
扩展方向
1. 多模态支持
- 图片内容理解
- 音频问答支持
2. 知识图谱集成
- 人物关系图谱
- 情节时间线
3. 个性化推荐
- 用户兴趣建模
- 智能问题推荐
- streamlit页面配置
# 页面配置
st.set_page_config(
page_title="图书智能检索系统",
page_icon="📚",
layout="wide",
initial_sidebar_state="expanded"
)
- 自定义样式
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&display=swap');
.main-header {
font-size: 3rem;
background: linear-gradient(135deg, #8B4513, #CD853F);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-align: center;
margin-bottom: 2rem;
font-weight: 700;
font-family: 'Noto Serif SC', serif;
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
}
.subtitle {
text-align: center;
color: #666;
font-style: italic;
margin-bottom: 2rem;
font-family: 'Noto Serif SC', serif;
}
.chat-message {
padding: 1.2rem;
border-radius: 1rem;
margin-bottom: 1rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: all 0.3s ease;
animation: fadeIn 0.5s ease-in;
}
.chat-message:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.user-message {
background: linear-gradient(135deg, #e3f2fd, #bbdefb);
border-left: 4px solid #2196f3;
margin-left: 2rem;
}
.assistant-message {
background: linear-gradient(135deg, #fff8e1, #ffecb3);
border-left: 4px solid #8B4513;
margin-right: 2rem;
}
.source-info {
background: linear-gradient(135deg, #f3e5f5, #e1bee7);
padding: 0.8rem;
border-radius: 0.8rem;
margin-top: 0.8rem;
font-size: 0.9rem;
border: 1px solid #ce93d8;
transition: all 0.3s ease;
}
.source-info:hover {
background: linear-gradient(135deg, #e8eaf6, #c5cae9);
border-color: #9c27b0;
}
.status-success {
color: #2e7d32;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
}
.status-error {
color: #d32f2f;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
}
.status-warning {
color: #f57c00;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
}
.example-button {
background: linear-gradient(135deg, #fff3e0, #ffe0b2);
border: 1px solid #ffb74d;
border-radius: 0.5rem;
padding: 0.5rem;
margin: 0.2rem 0;
transition: all 0.3s ease;
cursor: pointer;
}
.example-button:hover {
background: linear-gradient(135deg, #ffe0b2, #ffcc80);
transform: translateX(5px);
}
.sidebar-section {
background: linear-gradient(135deg, #fafafa, #f5f5f5);
padding: 1rem;
border-radius: 0.8rem;
margin-bottom: 1rem;
border: 1px solid #e0e0e0;
}
.typing-indicator {
display: inline-block;
animation: typing 1.5s infinite;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes typing {
0%, 60%, 100% { opacity: 1; }
30% { opacity: 0.5; }
}
.metric-card {
background: linear-gradient(135deg, #e8f5e8, #c8e6c9);
padding: 1rem;
border-radius: 0.8rem;
text-align: center;
margin: 0.5rem 0;
border: 1px solid #81c784;
}
.progress-bar {
background: linear-gradient(90deg, #4caf50, #8bc34a);
height: 4px;
border-radius: 2px;
animation: progress 2s ease-in-out;
}
@keyframes progress {
from { width: 0%; }
to { width: 100%; }
}
</style>
""", unsafe_allow_html=True)
- 显示用户消息
def display_chat_message(role, content, sources=None, typing=False):
"""显示聊天消息"""
if role == "user":
st.markdown(f"""
<div class="chat-message user-message">
<strong>🙋♀️ 您:</strong> {content}
</div>
""", unsafe_allow_html=True)
else:
typing_indicator = '<span class="typing-indicator">💭</span>' if typing else '🤖'
st.markdown(f"""
<div class="chat-message assistant-message">
<strong>{typing_indicator} 红楼梦助手:</strong> {content}
</div>
""", unsafe_allow_html=True)
if sources:
with st.expander(f"📖 参考文档片段 ({len(sources)}个)", expanded=False):
for i, source in enumerate(sources, 1):
similarity_color = "#4caf50" if source['similarity'] > 0.5 else "#ff9800" if source[
'similarity'] > 0.3 else "#f44336"
st.markdown(f"""
<div class="source-info">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
<strong>📄 片段 {i}: {source['source']}</strong>
<span style="background: {similarity_color}; color: white; padding: 0.2rem 0.5rem; border-radius: 1rem; font-size: 0.8rem;">
相似度: {source['similarity']:.3f}
</span>
</div>
<div style="background: rgba(255,255,255,0.8); padding: 0.5rem; border-radius: 0.5rem; border-left: 3px solid {similarity_color};">
<em>📝 内容预览:</em><br>
{source['content_preview']}
</div>
</div>
""", unsafe_allow_html=True)
更多推荐

所有评论(0)