(Python 3.10 + PyCharm 环境 · 无需显卡 · 支持 PDF/Word/Excel/图片 · 含图形界面、windows10 TLSC)
第一章:环境准备(30分钟)
步骤 1:安装 Tesseract OCR(10分钟)
• 下载安装包
• 打开浏览器访问:https://github.com/UB-Mannheim/tesseract/wiki
• 点击 tesseract-ocr-w64-setup-5.3.0.20221222.exe 下载(64位系统)
• 运行安装
• 双击下载的安装包 •
在 "Select Additional Language Data" 界面:
• 勾选 Chinese (Simplified)
• 勾选 English
• 其他选项保持默认,点击 "Next" 直到完成
• 配置环境变量
• 按 Win + R 输入 sysdm.cpl 回车
• 切换到 "高级" 选项卡 → 点击 "环境变量"
• 在 "系统变量" 区域找到 Path → 点击 "编辑"
• 点击 "新建" → 输入:
C:\Program Files\Tesseract-OCR(按照实际路径修改)
• 点击 "确定" 保存所有窗口
验证安装
• 按 Win + R 输入 cmd 回车
• 在命令行输入:
bash
tesseract --list-langs
·确认输出中包含 chi_sim 和 eng
步骤 2:创建项目虚拟环境(5分钟)
• 打开 PyCharm
• 启动 PyCharm
• 顶部菜单:File → New Project
• 位置(Location):G:\PythonProject\spark_local_rag(建议修改为你自己的路径)
• Python 解释器:选择已安装的 Python 3.10
• 勾选 ✔️ Create a virtual environment
• 虚拟环境名称:spark_env • 点击 "Create"
• 激活虚拟环境
• 在 PyCharm 底部找到 Terminal 标签页(或按 Alt + F12)
• 输入命令验证:
bash
pip --version
确认输出路径包含 spark_env(例如:G:\PythonProject\spark_local_rag\spark_env\Scripts\pip.exe)
步骤 3:安装依赖包(15分钟)
在 Terminal 中逐行执行(复制粘贴每行后按回车):
bash
pip install langchain==0.1.17
pip install langchain-community
pip install chromadb==0.4.24
pip install pypdf
pip install python-docx
pip install pillow pytesseract
pip install gradio==4.20.0
pip install websocket-client
pip install pandas openpyxl xlrd
第二章:创建项目结构(5分钟)
在 PyCharm 左侧 Project 面板中:
• 右键点击项目根目录 → New → Directory
• 名称:knowledge_base(存放知识文件)
• 右键点击项目根目录 → New → Python File
创建以下 4 个文件:
• xinghuo_llm.py
• document_loader.py
• rag_pipeline.py
• main.py
第三章:编写核心代码(逐行操作)
文件 1:xinghuo_llm.py(讯飞星火集成)
- 在 PyCharm 中打开
xinghuo_llm.py - 复制以下完整代码到文件中:
python
# xinghuo_llm.py
import base64
import datetime
import hashlib
import hmac
import json
import threading
from urllib.parse import urlencode
from langchain_core.language_models.llms import LLM
from websocket import create_connection
# ====== 讯飞星火认证配置 ======
APPID = "你自己的"
APIKey = "你自己的"
APISecret = "你自己的"
SPARK_URL = "wss://你自己的"
def create_url():
"""生成带鉴权信息的WebSocket URL"""
host = "spark-api.xf-yun.com"
path = "/v1/x1"
now = datetime.datetime.now(datetime.timezone.utc)
date = now.strftime("%a, %d %b %Y %H:%M:%S GMT")
# 构造签名原文
signature_origin = f"host: {host}\ndate: {date}\nx-date: {date}\nGET {path} HTTP/1.1"
# 生成HMAC-SHA256签名
signature_sha = hmac.new(
APISecret.encode("utf-8"),
signature_origin.encode("utf-8"),
digestmod=hashlib.sha256,
).digest()
signature_sha_base64 = base64.b64encode(signature_sha).decode("utf-8")
# 生成authorization头
authorization_origin = (
f'api_key="{APIKey}", algorithm="hmac-sha256", '
f'headers="host date x-date request-line", signature="{signature_sha_base64}"'
)
authorization = base64.b64encode(authorization_origin.encode("utf-8")).decode("utf-8")
# 拼接请求参数
params = {
"authorization": authorization,
"date": date,
"x-date": date,
"host": host
}
return SPARK_URL + "?" + urlencode(params)
class SparkX1LLM(LLM):
"""讯飞星火X1-32K模型的LangChain封装"""
def _call(self, prompt: str, stop=None, **kwargs) -> str:
"""同步调用模型"""
url = create_url()
result = []
try:
# 创建WebSocket连接
ws = create_connection(url)
# 构造请求payload
payload = {
"header": {"app_id": APPID},
"parameter": {
"chat": {
"domain": "x1", # 必须是x1(对应/v1/x1接口)
"temperature": 0.5,
"max_tokens": 2048
}
},
"payload": {
"message": {
"text": [{"role": "user", "content": prompt}]
}
}
}
# 发送请求
ws.send(json.dumps(payload))
# 接收响应
while True:
message = ws.recv()
data = json.loads(message)
# 检查错误
header = data.get("header", {})
code = header.get("code")
if code != 0:
error_msg = data.get("message", "未知错误")
raise RuntimeError(f"讯飞API错误[{code}]: {error_msg}")
# 提取文本内容
choices = data.get("payload", {}).get("choices", {})
text_content = choices.get("text", [{}])[0].get("content", "")
status = choices.get("status")
result.append(text_content)
# 检查是否结束
if status == 2:
break
# 关闭连接
ws.close()
return "".join(result)
except Exception as e:
raise RuntimeError(f"WebSocket调用失败: {str(e)}")
@property
def _llm_type(self) -> str:
return "spark-x1"
文件 2:document_loader.py(多格式文件加载)
• 打开 document_loader.py
• 复制以下完整代码:
python
# document_loader.py
import os
import pandas as pd
from langchain_core.documents import Document
from langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader
from PIL import Image
import pytesseract
def excel_to_text(filepath: str) -> str:
"""将Excel文件转换为结构化文本"""
try:
xls = pd.ExcelFile(filepath)
full_text = f"文件: {os.path.basename(filepath)}\n\n"
for sheet_name in xls.sheet_names:
# 读取工作表,空值转为空字符串
df = pd.read_excel(filepath, sheet_name=sheet_name, dtype=str).fillna("")
if df.empty:
continue
full_text += f"工作表: {sheet_name}\n"
# 获取列名(假设第一行为标题)
headers = df.columns.tolist()
# 遍历每一行
for idx, row in df.iterrows():
row_items = []
for col in headers:
val = str(row[col]).strip()
if val: # 只添加非空值
row_items.append(f"{col}={val}")
if row_items: # 只添加非空行
full_text += f"第{idx+1}行: " + "; ".join(row_items) + "\n"
full_text += "\n"
return full_text.strip()
except Exception as e:
return f"Excel文件解析失败 ({os.path.basename(filepath)}): {str(e)}"
def load_documents_from_folder(folder_path: str):
"""从文件夹加载所有支持类型的文档"""
documents = []
# 遍历文件夹中所有文件
for filename in os.listdir(folder_path):
filepath = os.path.join(folder_path, filename)
try:
# PDF文件处理
if filename.lower().endswith(".pdf"):
loader = PyPDFLoader(filepath)
docs = loader.load()
for doc in docs:
doc.metadata["source"] = filename
documents.extend(docs)
# Word文件处理
elif filename.lower().endswith((".docx", ".doc")):
loader = Docx2txtLoader(filepath)
docs = loader.load()
for doc in docs:
doc.metadata["source"] = filename
documents.extend(docs)
# 图片文件处理
elif filename.lower().endswith((".png", ".jpg", ".jpeg", ".bmp")):
text = pytesseract.image_to_string(
Image.open(filepath),
lang="chi_sim+eng"
)
doc = Document(
page_content=text,
metadata={"source": filename}
)
documents.append(doc)
# Excel文件处理
elif filename.lower().endswith((".xlsx", ".xls")):
text = excel_to_text(filepath)
doc = Document(
page_content=text,
metadata={"source": filename}
)
documents.append(doc)
except Exception as e:
print(f"⚠️ 加载 {filename} 失败: {str(e)}")
print(f"✅ 共加载 {len(documents)} 个文档片段")
return documents
文件 3:rag_pipeline.py(RAG核心逻辑)
- 打开
rag_pipeline.py - 复制以下完整代码:
python
# rag_pipeline.py
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from .xinghuo_llm import SparkX1LLM
from .document_loader import load_documents_from_folder
import os
class LocalRAG:
def __init__(self, knowledge_dir="knowledge_base", persist_dir="vector_db"):
self.knowledge_dir = knowledge_dir
self.persist_dir = persist_dir
self.vector_db = None
self.qa_chain = None
# 创建知识库目录(如果不存在)
os.makedirs(knowledge_dir, exist_ok=True)
# 初始化嵌入模型(完全离线)
self.embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2",
model_kwargs={'device': 'cpu'}
)
def rebuild_knowledge_base(self):
"""重建知识库(加载文档 + 创建向量库)"""
print("🔄 正在加载文档...")
documents = load_documents_from_folder(self.knowledge_dir)
if not documents:
raise ValueError(f"知识库目录 {self.knowledge_dir} 中没有找到有效文档!")
print("✂️ 正在分割文本...")
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""]
)
splits = text_splitter.split_documents(documents)
print("🧠 正在创建向量数据库...")
self.vector_db = Chroma.from_documents(
documents=splits,
embedding=self.embeddings,
persist_directory=self.persist_dir
)
self.vector_db.persist() # 保存到磁盘
print("🔗 正在初始化问答链...")
llm = SparkX1LLM() # 使用讯飞星火
self.qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=self.vector_db.as_retriever(
search_type="similarity",
search_kwargs={"k": 3}
),
return_source_documents=True
)
print("✅ 知识库重建完成!")
def query(self, question: str):
"""执行问答"""
if not self.qa_chain:
raise RuntimeError("请先重建知识库!")
print(f"\n❓ 用户问题: {question}")
response = self.qa_chain.invoke({"query": question})
# 提取引用来源
sources = []
for doc in response["source_documents"]:
source = doc.metadata["source"]
if source not in sources:
sources.append(source)
return {
"answer": response["result"],
"sources": sources
}
文件 4:main.py(图形界面)
- 打开
main.py - 复制以下完整代码:
python
# main.py
import gradio as gr
from rag_pipeline import LocalRAG
import os
import shutil
# 初始化RAG系统
rag_system = LocalRAG()
def upload_files(files):
"""处理上传的文件"""
saved_files = []
for file in files:
dest_path = os.path.join(rag_system.knowledge_dir, os.path.basename(file.name))
shutil.copy(file.name, dest_path)
saved_files.append(os.path.basename(file.name))
return f"✅ 已上传 {len(saved_files)} 个文件: {', '.join(saved_files)}"
def rebuild_knowledge_base():
"""重建知识库"""
try:
rag_system.rebuild_knowledge_base()
return "✅ 知识库重建成功!现在可以提问了。"
except Exception as e:
return f"❌ 重建失败: {str(e)}"
def answer_question(question):
"""回答问题"""
try:
if not rag_system.qa_chain:
return "请先重建知识库!", []
result = rag_system.query(question)
sources = "\n".join([f"• {source}" for source in result["sources"]])
return result["answer"], sources
except Exception as e:
return f"生成回答时出错: {str(e)}", ""
# 创建Gradio界面
with gr.Blocks(title="本地知识助手 - 讯飞星火版") as demo:
gr.Markdown("# 🧠 本地知识助手 (讯飞星火版)")
gr.Markdown("### 支持 PDF / Word / Excel / 图片文件 · 完全离线运行")
with gr.Tab("上传文件"):
file_upload = gr.Files(label="选择文件(支持 PDF/Word/Excel/图片)")
upload_btn = gr.Button("上传文件")
upload_status = gr.Textbox(label="上传状态", interactive=False)
upload_btn.click(upload_files, inputs=file_upload, outputs=upload_status)
rebuild_btn = gr.Button("重建知识库", variant="primary")
rebuild_status = gr.Textbox(label="重建状态", interactive=False)
rebuild_btn.click(rebuild_knowledge_base, outputs=rebuild_status)
with gr.Tab("问答"):
question = gr.Textbox(label="输入你的问题", placeholder="例如:文档中提到的关键技术是什么?")
ask_btn = gr.Button("获取答案", variant="primary")
with gr.Row():
answer = gr.Textbox(label="回答", lines=10)
sources = gr.Textbox(label="参考来源", lines=5)
ask_btn.click(answer_question, inputs=question, outputs=[answer, sources])
# 启动应用
if __name__ == "__main__":
demo.launch(server_name="127.0.0.1", server_port=7860)
项目结构

第四章:测试运行(10分钟)
步骤 1:准备测试文件
- 在项目目录中找到
knowledge_base文件夹 - 放入以下测试文件(可自行创建):
-
测试文档.pdf(含一段中文文字) -
员工信息.xlsx(内容如下):姓名 部门 工龄 张三 技术部 3年 李四 市场部 5年
-
步骤 2:启动应用
• 在 PyCharm 中右键点击 main.py
• 选择 Run 'main'
• 等待控制台出现:
Running on local URL: http://127.0.0.1:7860
步骤 3:使用图形界面
- 自动打开浏览器(或手动访问 http://127.0.0.1:7860)
- 切换到 "上传文件" 标签页 :
- 点击 "选择文件" 按钮
- 选择
测试文档.pdf和员工信息.xlsx - 点击 "上传文件" → 显示上传成功
- 点击 "重建知识库" → 等待 1-2 分钟(首次较慢)
- 切换到 "问答" 标签页 :
- 输入问题:
张三在哪个部门? - 点击 "获取答案"
- 预期结果:
- 回答区域:
张三在技术部工作。 - 参考来源:
• 员工信息.xlsx
- 回答区域:
- 输入问题:
最终界面


不过经本人亲测,回答的结果五花八门,距离理想目标还差太远,不过权当是练手了,了解以下过程。