说明
对AI应用开发所涉及到的流程、工具、技能进行系列介绍,全部文章收录于《AI应用开发》专栏。关注+收藏,不错过后续精彩。
前置文章
AI应用开发01-环境准备 AI应用开发02-从零构建AI聊天机器人 AI应用开发03-RAG增强知识问答
项目源码
gitee仓库:AI应用开发源码
一、项目目标
构建一个基于 Python 的桌面端 AI 聊天应用,具备以下核心能力:
- 调用大模型 API 进行多轮对话(上下文记忆)
- 流式/非流式输出切换
- 对话记录本地保存
- 内置 RAG 知识库(文档上传、向量检索、增强回答)
- 支持嵌入模型与文本切分策略配置
- 简洁实用的 GUI 界面
二、技术选型
| 编程语言 | Python 3.9+ | 生态丰富,AI 开发首选 |
| GUI 框架 | tkinter | Python 自带,轻量无额外依赖 |
| 大模型 API | DeepSeek (兼容 OpenAI) | 国内可直接访问,性价比高 |
| API 客户端 | openai | 通用性最强,未来可无缝切换模型 |
| RAG 引擎 | 自研模块 rag_engine | 封装文档加载、分割、向量库操作 |
| 向量数据库 | ChromaDB | 轻量、支持本地持久化 |
| 嵌入模型 | sentence-transformers | 支持多种预训练模型,可本地运行 |
| 文档解析 | PyPDF2 | 处理 PDF,后续可扩展 docx 等 |
| 环境变量管理 | python-dotenv | 安全存储 API Key |
三、功能需求
3.1 V1 —— 基础对话功能
- ✅ 多轮对话,携带完整上下文
- ✅ 可配置系统提示词(角色设定)
- ✅ 流式 / 非流式输出切换
- ✅ 每次会话自动保存为带时间戳的日志文件
- ✅ 设置面板:API Key、模型名、保存目录等
3.2 V2 —— RAG 知识增强与知识库构建
- ✅ 本地 PDF / TXT 文档上传
- ✅ 文本分割(简单切分 & 语义切分可选)
- ✅ 向量化入库(ChromaDB)
- ✅ 知识库文件管理界面:查看文件名、分块数、删除文件
- ✅ 对话时自动检索相关片段,并显示参考资料来源
- ✅ 嵌入模型可切换(下拉框 + 手动输入)
- ✅ 向量库路径、切分策略等可配置
四、项目结构
F:\\ai-dev\\
├── rag_engine/ # 独立 RAG 模块包
│ ├── __init__.py
│ ├── config.py # 默认配置
│ ├── loader.py # 文档加载器
│ ├── splitter.py # 文本分割器 (simple / semantic)
│ └── vector_store.py # ChromaDB 封装
├── chatbot_gui_modular.py # 主 GUI 程序
├── chatbot_gui.py # 主GUI 程序(简单的对话界面)
├── .env # API 密钥 (不纳入版本控制)
├── config.json # 用户配置 (自动生成)
├── chat_logs/ # 对话记录保存目录
└── chroma_db/ # 向量数据库存储目录
五、关键模块实现
5.1 环境准备与依赖安装
构建虚拟环境agent-demo并激活(如果已经按照前置文章创建,可忽略该步骤)。
python –m venv agent-demo
.\\agent-demo\\Scripts\\Activate.ps1
python –m pip install openai python-dotenv chromadb PyPDF2 sentence-transformers # 安装必要的依赖包,已安装过忽略
对于向量化模型,推荐配置 HuggingFace 国内镜像(在脚本开头添加),避免因网络问题无法启动:
import os
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
5.2 RAG 引擎包 (rag_engine/)
构建一个可独立复用的轻量级 RAG 工具包,采用模块化设计将文档处理全流程拆分为四个职责单一的组件:
- 配置中心(config) 管理默认参数与可用模型列表。
- 文档加载器(loader) 统一处理 PDF/TXT 等多格式文件读取。
- 文本分割器(splitter) 提供简单切分与语义切分两种策略以适配不同场景。
- 向量库管理器(vector_store) 封装 ChromaDB 操作实现文档的向量化存储、检索与统计。 该包通过 __init__.py 暴露统一接口,对外屏蔽内部实现细节,既可直接被桌面 GUI 调用,也可无缝集成到其他 Python 项目中,体现了“高内聚、低耦合”的工程原则,为后续扩展更多文档格式、切分算法或嵌入模型奠定了坚实基础。
5.2.1 config.py – 默认配置
GUI启动时加载默认配置,用户可修改并保存。配置属性主要包含:
- 向量库地址
- 默认向量化模型
- 分块设置
- 可选择的向量化模型列表
DEFAULT_RAG_CONFIG = {
"persist_dir": "chroma_db",
"embedding_model": "shibing624/text2vec-base-chinese",
"chunk_size": 500,
"chunk_overlap": 50,
"split_strategy": "simple",
"available_models": [
"shibing624/text2vec-base-chinese",
"moka-ai/m3e-base",
"BAAI/bge-small-zh-v1.5",
"all-MiniLM-L6-v2",
"intfloat/multilingual-e5-large"
]
}
5.2.2 loader.py – 文档加载器
import os
from PyPDF2 import PdfReader
class DocumentLoader:
@staticmethod
def load(file_path):
ext = os.path.splitext(file_path)[1].lower()
if ext == '.pdf':
return DocumentLoader._load_pdf(file_path)
elif ext == '.txt':
return DocumentLoader._load_txt(file_path)
else:
raise ValueError(f"暂不支持的格式: {ext}")
@staticmethod
def _load_pdf(path):
reader = PdfReader(path)
full_text = ""
for page in reader.pages:
text = page.extract_text()
if text:
full_text += text + "\\n"
return full_text
@staticmethod
def _load_txt(path):
with open(path, 'r', encoding='utf-8') as f:
return f.read()
5.2.3 splitter.py – 文本分割器(支持 simple 和 semantic)
TextSplitter 是整个 RAG 引擎中的文本预处理核心,负责将原始长文本切分成长度适中、语义连贯的文本块(chunk),以便后续进行向量嵌入和检索。其设计遵循“策略可插拔、参数可调节”的原则,对外仅暴露 split(text) 方法,内部则根据初始化时指定的 strategy 参数自动分派到不同的切分逻辑。
一、整体实现思路
二、简单切分(Simple Split)
原理:以自然段落边界(双换行)为第一优先级,段落内部若超出 chunk_size 则按固定窗口滑动截取,确保每个块长度大致均匀。
实现步骤:
适用场景:通用性最强,适合绝大多数格式工整的文档(如报告、手册),计算开销低、速度快。
三、语义切分(Semantic Split)
原理:以句子为最小单位,通过贪婪合并策略将语义上连续的句子聚合成不超过 chunk_size 的块,避免在句子中间截断,从而保持每个块的语义完整性。
实现步骤:
- 若将当前句子追加到 current_chunk 后总长度不超过 chunk_size,则执行追加;
- 否则,先将已有的 current_chunk 作为一个完整块输出,然后处理当前句子:
- 如果当前句子本身长度就超过 chunk_size(极长句),则对该句子按 _simple_split 的窗口滑动方式强制切分;
- 若未超过,则将当前句子作为新 current_chunk 的起始。
适用场景:对话记录、法律条款、学术论文等对语义连续性要求较高的文本,能够显著提升检索结果的连贯性和答案准确性。
代码实现:
import re
class TextSplitter:
def __init__(self, chunk_size=500, overlap=50, strategy="simple"):
self.chunk_size = chunk_size
self.overlap = overlap
self.strategy = strategy
def split(self, text):
if self.strategy == "semantic":
return self._semantic_split(text)
return self._simple_split(text)
def _simple_split(self, text):
paragraphs = [p.strip() for p in text.split("\\n\\n") if len(p.strip()) > 20]
if not paragraphs:
paragraphs = [p.strip() for p in text.split("\\n") if len(p.strip()) > 20]
chunks = []
for para in paragraphs:
if len(para) <= self.chunk_size:
chunks.append(para)
else:
for i in range(0, len(para), self.chunk_size – self.overlap):
chunk = para[i:i + self.chunk_size]
if chunk:
chunks.append(chunk)
return chunks
def _semantic_split(self, text):
sentences = re.split(r'(?<=[。!?.!?])\\s*', text)
sentences = [s.strip() for s in sentences if s.strip()]
chunks = []
current_chunk = ""
for sent in sentences:
if len(current_chunk) + len(sent) <= self.chunk_size:
current_chunk += sent
else:
if current_chunk:
chunks.append(current_chunk)
if len(sent) > self.chunk_size:
for i in range(0, len(sent), self.chunk_size – self.overlap):
chunks.append(sent[i:i + self.chunk_size])
current_chunk = ""
else:
current_chunk = sent
if current_chunk:
chunks.append(current_chunk)
return chunks
5.2.4 vector_store.py – 向量库管理
VectorStore 是 RAG 引擎的存储与检索核心,基于 ChromaDB 封装了文档向量的持久化管理和语义搜索能力,向上层提供简洁的操作接口。
核心职责:
设计特点:
- 持久化本地存储:所有向量数据均保存在指定目录,重启不丢失。
- 模型可插拔:嵌入模型通过参数注入,更换模型只需修改初始化参数,无需改动内部逻辑。
- 异常隔离:通过统一的接口屏蔽底层 ChromaDB 细节,外部调用者无需关心数据库操作,降低了耦合度。
代码实现:
import chromadb
from chromadb.utils import embedding_functions
from .config import DEFAULT_RAG_CONFIG
class VectorStore:
def __init__(self, persist_dir=None, embedding_model=None):
self.persist_dir = persist_dir or DEFAULT_RAG_CONFIG["persist_dir"]
self.embedding_model = embedding_model or DEFAULT_RAG_CONFIG["embedding_model"]
self.embed_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name=self.embedding_model
)
self.client = chromadb.PersistentClient(path=self.persist_dir)
self.collection = self.client.get_or_create_collection(
name="rag_collection",
embedding_function=self.embed_fn
)
def add_document(self, chunks, source_name):
metadatas = [{"source": source_name, "chunk_index": i} for i in range(len(chunks))]
ids = [f"{source_name}_{i}" for i in range(len(chunks))]
existing = self.collection.get(where={"source": source_name})
if existing["ids"]:
self.collection.delete(ids=existing["ids"])
self.collection.add(documents=chunks, metadatas=metadatas, ids=ids)
def search(self, query, top_k=3):
results = self.collection.query(
query_texts=[query],
n_results=top_k,
include=['documents', 'metadatas']
)
if results["documents"] and results["metadatas"]:
docs = results["documents"][0]
metas = results["metadatas"][0]
return [
{"content": doc, "source": meta.get("source", ""), "chunk_index": meta.get("chunk_index", 0)}
for doc, meta in zip(docs, metas)
]
return []
def get_file_stats(self):
all_data = self.collection.get()
stats = {}
if all_data["metadatas"]:
for meta in all_data["metadatas"]:
src = meta["source"]
stats[src] = stats.get(src, 0) + 1
return stats
def remove_file(self, source_name):
self.collection.delete(where={"source": source_name})
5.2.5 __init__.py – 包导出
对外暴露可引用的模块
from .loader import DocumentLoader
from .splitter import TextSplitter
from .vector_store import VectorStore
from .config import DEFAULT_RAG_CONFIG
__all__ = ["DocumentLoader", "TextSplitter", "VectorStore", "DEFAULT_RAG_CONFIG"]
5.3 主 GUI 程序 chatbot_gui_modular.py
完整代码因篇幅原因在此不再展示,可通过gitee仓库获取。核心特点:
- 使用 PanedWindow 实现左右分栏:聊天区 + RAG 面板
- 菜单栏提供“设置”入口,支持动态修改模型、切分策略等
- 发送消息时自动根据 RAG 开关检索上下文,并将参考资料显示在对话框
- 所有耗时操作(上传、API 调用)放入后台线程,避免 UI 冻结
gitee仓库:AI应用开发源码
六、效果演示
6.1 简单对话示例
启动窗口:
设置界面,可修改保存,即时生效。 
6.2 RAG+对话示例

七、设计权衡与优化方向
关键设计权衡(TOP3)
1. 轻量GUI vs 现代体验
- 权衡:选择Python自带的tkinter,牺牲界面美观度,换取零额外依赖、极低上手成本。
- 指导:初期学习重心应在AI逻辑而非前端。后续可无缝迁移至customtkinter或全栈Web框架,核心业务代码不受影响。
2. 通用API接口 vs 专用SDK
- 权衡:用openai库调用DeepSeek,而不用其官方SDK,保持与OpenAI协议兼容。
- 指导:实现模型无关性,未来切换任何兼容OpenAI的模型只需改base_url和key,降低锁定风险,便于学习和对比不同模型。
3. 简单切分 vs 语义切分
- 权衡:默认采用快速、通用的简单切分,同时提供基于正则分句的语义切分作为选项,不引入沉重模型依赖。
- 指导:理解“够用即可”的工程思维;当需要更高检索质量时,再升级为基于embedding相似度的动态切分,避免过早优化。
#关键优化方向(TOP3)
1. 混合检索(向量 + 关键词)
- 问题:纯向量检索可能遗漏精确关键词匹配。
- 行动:引入BM25等稀疏检索,与向量检索结果融合(混合召回),显著提升RAG回答的准确率和覆盖面。
2. Agent化与工具调用(MCP集成)
- 问题:当前仅是被动问答,无法主动执行操作。
- 行动:接入MCP(Model Context Protocol),让模型能调用搜索、计算器、文件操作等外部工具,从“聊天助手”升级为“自主代理”。
3. 产品化部署与多模态扩展
- 问题:桌面应用局限于单机,无法协作。
- 行动:将后端改造为FastAPI,前端用现代Web技术,实现多用户、云端部署;同时拓展图片、语音等多模态交互能力。



