一、前置环境准备
1.1 基础环境要求
- Python 3.8 及以上版本
- 操作系统:Windows/MacOS/Linux 均可适配
- 内存:最低 4G,推荐 8G 及以上(处理大文档时更流畅)
1.2 依赖库安装
本方案使用的所有依赖库均为开源轻量化工具,一行命令即可完成全部安装:
bash
运行
# 核心依赖:OpenAI SDK、文档解析、向量数据库、文本处理
pip install openai pypdf python-docx python-pptx openpyxl chromadb nltk
1.3 API 访问凭证准备
- 登录 4SAPI 控制台,进入「API 令牌管理」模块,点击「创建令牌」生成专属 API Key,全程无需官方繁琐审核,注册即享初始体验额度;
- 安全配置:将 API Key 与网关地址通过环境变量管理,避免硬编码泄露,Windows/MacOS/Linux 均可通过系统环境变量配置,示例:
bash
运行
# Linux/MacOS 环境变量配置
export GEMINI_API_KEY="你的4SAPI专属令牌"
export GEMINI_BASE_URL="https://4sapi.ai/v1"
# Windows 环境变量配置
set GEMINI_API_KEY=你的4SAPI专属令牌
set GEMINI_BASE_URL=https://4sapi.ai/v1
二、分步实战:从零搭建智能文档问答系统
2.1 第一步:多格式文档解析与文本预处理
首先实现多格式文档的统一解析与文本清洗,支持 PDF、Word、PPT、Excel 四大主流企业文档格式,同时完成智能文本切片,为后续向量化检索做准备。
新建document_processor.py文件,实现完整的文档处理逻辑,代码可直接复制运行:
python
运行
import os
import re
from pypdf import PdfReader
from docx import Document
from pptx import Presentation
from openpyxl import load_workbook
class DocumentProcessor:
def __init__(self, chunk_size=1000, chunk_overlap=200):
"""
初始化文档处理器
:param chunk_size: 文本切片大小,单位:字符
:param chunk_overlap: 切片重叠大小,避免上下文断裂
"""
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
def clean_text(self, text):
"""文本清洗:去除多余空格、换行符、特殊字符"""
# 去除多余换行和空格
text = re.sub(r'\n+', '\n', text)
text = re.sub(r'\s+', ' ', text)
# 去除无意义的特殊字符
text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9.,。,、??!!;;::""''()()\n\s]', '', text)
return text.strip()
def split_text(self, text):
"""智能文本切片,保证上下文连贯性"""
chunks = []
start = 0
text_length = len(text)
while start < text_length:
# 计算当前切片的结束位置
end = min(start + self.chunk_size, text_length)
# 调整切片结束位置到最近的句号/换行,避免切断句子
if end < text_length:
# 寻找最近的句子结束符
for split_char in ['。', '!', '?', '.', '!', '?', '\n']:
split_pos = text.rfind(split_char, start, end)
if split_pos != -1:
end = split_pos + 1
break
# 截取切片
chunk = text[start:end].strip()
if chunk:
chunks.append(chunk)
# 移动起始位置,保留重叠部分
start = end - self.chunk_overlap
# 防止死循环
if start >= text_length:
break
return chunks
def parse_pdf(self, file_path):
"""解析PDF文档"""
text = ""
reader = PdfReader(file_path)
for page in reader.pages:
text += page.extract_text() + "\n"
return self.clean_text(text)
def parse_docx(self, file_path):
"""解析Word文档"""
text = ""
doc = Document(file_path)
for para in doc.paragraphs:
text += para.text + "\n"
# 解析表格内容
for table in doc.tables:
for row in table.rows:
row_text = " | ".join([cell.text.strip() for cell in row.cells])
text += row_text + "\n"
return self.clean_text(text)
def parse_pptx(self, file_path):
"""解析PPT文档"""
text = ""
prs = Presentation(file_path)
for slide in prs.slides:
for shape in slide.shapes:
if hasattr(shape, "text"):
text += shape.text + "\n"
return self.clean_text(text)
def parse_excel(self, file_path):
"""解析Excel文档"""
text = ""
wb = load_workbook(file_path, read_only=True, data_only=True)
for sheet_name in wb.sheetnames:
text += f"===== 工作表:{sheet_name} =====\n"
sheet = wb[sheet_name]
for row in sheet.iter_rows(values_only=True):
row_text = " | ".join([str(cell).strip() if cell is not None else "" for cell in row])
text += row_text + "\n"
return self.clean_text(text)
def parse_document(self, file_path):
"""统一文档解析入口,自动识别文件格式"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"文档不存在:{file_path}")
file_ext = os.path.splitext(file_path)[1].lower()
parse_func_map = {
'.pdf': self.parse_pdf,
'.docx': self.parse_docx,
'.doc': self.parse_docx,
'.pptx': self.parse_pptx,
'.ppt': self.parse_pptx,
'.xlsx': self.parse_excel,
'.xls': self.parse_excel
}
if file_ext not in parse_func_map:
raise ValueError(f"不支持的文档格式:{file_ext}")
# 解析文档并切片
raw_text = parse_func_map[file_ext](file_path)
text_chunks = self.split_text(raw_text)
return text_chunks
# 测试文档解析功能
if __name__ == "__main__":
processor = DocumentProcessor()
# 替换为你的本地文档路径
test_file = "企业产品手册.pdf"
try:
chunks = processor.parse_document(test_file)
print(f"文档解析完成,共生成{len(chunks)}个文本切片")
print("首个切片预览:")
print(chunks[0][:200])
except Exception as e:
print(f"文档解析失败:{e}")
2.2 第二步:基于 Gemini 的文本向量化与检索逻辑实现
基于 Chroma 搭建轻量化向量数据库,通过 4SAPI 网关调用 Gemini 3.0 实现文本向量化,同时实现语义相似度检索,精准匹配用户问题与知识库内容。
新建vector_store.py文件,实现向量存储与检索全逻辑,代码可直接复制运行:
python
运行
import os
import chromadb
from chromadb.utils import embedding_functions
from openai import OpenAI
from document_processor import DocumentProcessor
class VectorStoreManager:
def __init__(self, collection_name="enterprise_knowledge_base"):
"""
初始化向量存储管理器
:param collection_name: 知识库集合名称
"""
# 初始化OpenAI客户端,对接4SAPI网关
self.client = OpenAI(
api_key=os.getenv("GEMINI_API_KEY"),
base_url=os.getenv("GEMINI_BASE_URL")
)
# 初始化Chroma向量数据库,数据持久化存储在本地chroma_db目录
self.chroma_client = chromadb.PersistentClient(path="./chroma_db")
# 初始化嵌入函数,使用text-embedding-ada-002标准,Gemini完全兼容
self.embedding_func = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.getenv("GEMINI_API_KEY"),
api_base=os.getenv("GEMINI_BASE_URL"),
model_name="text-embedding-ada-002"
)
# 获取/创建知识库集合
self.collection = self.chroma_client.get_or_create_collection(
name=collection_name,
embedding_function=self.embedding_func,
metadata={"description": "企业级智能文档知识库"}
)
# 初始化文档处理器
self.doc_processor = DocumentProcessor()
def add_document_to_kb(self, file_path, document_id=None):
"""
将文档添加到知识库
:param file_path: 本地文档路径
:param document_id: 文档唯一标识,默认使用文件名
"""
# 解析文档,获取文本切片
text_chunks = self.doc_processor.parse_document(file_path)
if not text_chunks:
raise ValueError("文档解析后无有效内容")
# 生成文档唯一标识和切片ID
file_name = os.path.basename(file_path)
document_id = document_id if document_id else file_name
chunk_ids = [f"{document_id}_chunk_{i}" for i in range(len(text_chunks))]
# 生成切片元数据
metadatas = [
{"document_id": document_id, "file_name": file_name, "chunk_index": i}
for i in range(len(text_chunks))
]
# 将切片添加到向量数据库
self.collection.add(
documents=text_chunks,
metadatas=metadatas,
ids=chunk_ids
)
print(f"文档【{file_name}】添加成功,共入库{len(text_chunks)}个切片")
return True
def search_related_chunks(self, query, top_k=3):
"""
根据用户问题检索相关的文本切片
:param query: 用户问题
:param top_k: 返回最相关的切片数量
:return: 相关切片列表
"""
results = self.collection.query(
query_texts=[query],
n_results=top_k
)
# 提取检索到的文本切片
related_chunks = results["documents"][0]
metadatas = results["metadatas"][0]
# 拼接检索结果与元数据
result_list = []
for chunk, metadata in zip(related_chunks, metadatas):
result_list.append({
"content": chunk,
"document_id": metadata["document_id"],
"file_name": metadata["file_name"]
})
return result_list
def delete_document(self, document_id):
"""从知识库中删除指定文档"""
self.collection.delete(where={"document_id": document_id})
print(f"文档【{document_id}】已从知识库中删除")
return True
def list_all_documents(self):
"""列出知识库中所有的文档"""
all_metadata = self.collection.get()["metadatas"]
document_set = set([item["document_id"] for item in all_metadata])
return list(document_set)
# 测试向量存储与检索功能
if __name__ == "__main__":
vs_manager = VectorStoreManager()
# 测试添加文档到知识库
test_file = "企业产品手册.pdf"
try:
vs_manager.add_document_to_kb(test_file)
# 测试检索功能
test_query = "产品的核心功能有哪些?"
results = vs_manager.search_related_chunks(test_query, top_k=3)
print(f"\n检索到{len(results)}条相关内容:")
for i, res in enumerate(results):
print(f"\n{i+1}. 来自文档【{res['file_name']}】:")
print(res['content'][:300])
except Exception as e:
print(f"操作失败:{e}")
2.3 第三步:Gemini 大模型问答能力整合
将文档检索能力与 Gemini 大模型的生成能力结合,实现基于知识库内容的精准问答,避免大模型 “幻觉”,保证回答内容完全来自企业知识库,同时支持多轮对话上下文记忆。
新建knowledge_qa.py文件,实现核心问答逻辑,代码可直接复制运行:
python
运行
import os
from openai import OpenAI
from vector_store import VectorStoreManager
class KnowledgeQASystem:
def __init__(self):
# 初始化OpenAI客户端,对接4SAPI网关
self.client = OpenAI(
api_key=os.getenv("GEMINI_API_KEY"),
base_url=os.getenv("GEMINI_BASE_URL")
)
# 初始化向量存储管理器
self.vs_manager = VectorStoreManager()
# 多轮对话上下文存储
self.dialog_history = []
def build_prompt(self, query, related_chunks):
"""构建RAG提示词,严格限定回答基于知识库内容"""
# 拼接检索到的知识库内容
knowledge_context = "\n\n".join([f"知识库内容{i+1}:\n{chunk['content']}" for i, chunk in enumerate(related_chunks)])
# 构建系统提示词,限定回答规则,避免幻觉
system_prompt = f"""
你是企业专属的智能知识库助手,必须严格遵循以下规则进行回答:
1. 所有回答必须完全基于下方提供的【知识库内容】,禁止编造知识库中不存在的信息、数据、功能;
2. 如果知识库内容中没有用户问题的相关答案,必须明确告知用户:"抱歉,知识库中暂未收录该问题的相关内容,请补充相关文档后再次提问",禁止编造答案;
3. 回答需逻辑清晰、条理分明、重点突出,专业内容表述准确,避免冗余内容;
4. 回答需使用中文,保持专业、严谨的语气,符合企业办公场景的使用需求。
【知识库内容】:
{knowledge_context}
"""
# 构建对话消息,包含历史上下文
messages = [{"role": "system", "content": system_prompt}]
# 添加历史对话上下文
messages.extend(self.dialog_history)
# 添加当前用户问题
messages.append({"role": "user", "content": query})
return messages
def chat(self, query, top_k=3, stream=True):
"""
核心问答入口
:param query: 用户问题
:param top_k: 检索相关切片数量
:param stream: 是否开启流式输出
:return: 模型回答结果
"""
# 1. 检索知识库相关内容
related_chunks = self.vs_manager.search_related_chunks(query, top_k=top_k)
if not related_chunks:
return "抱歉,知识库中暂未收录相关内容,请先上传对应文档。"
# 2. 构建提示词
messages = self.build_prompt(query, related_chunks)
try:
# 3. 调用Gemini 3.0模型生成回答
response = self.client.chat.completions.create(
model="gemini-3.0-pro",
messages=messages,
stream=stream,
temperature=0.2, # 知识库场景调低温度,保证回答严谨准确
top_p=0.7,
max_tokens=2048
)
# 处理流式输出
if stream:
full_response = ""
print("智能助手回答:\n")
for chunk in response:
if chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
full_response += content
print(content, end="", flush=True)
print("\n")
# 保存对话历史
self.dialog_history.append({"role": "user", "content": query})
self.dialog_history.append({"role": "assistant", "content": full_response})
# 限制历史对话长度,避免超出上下文窗口
if len(self.dialog_history) > 10:
self.dialog_history = self.dialog_history[-10:]
return full_response
# 处理非流式输出
else:
full_response = response.choices[0].message.content
# 保存对话历史
self.dialog_history.append({"role": "user", "content": query})
self.dialog_history.append({"role": "assistant", "content": full_response})
if len(self.dialog_history) > 10:
self.dialog_history = self.dialog_history[-10:]
return full_response
except Exception as e:
return f"问答调用异常:{e}"
def clear_dialog_history(self):
"""清空对话历史,开启新的会话"""
self.dialog_history = []
print("对话历史已清空,已开启新会话")
return True
# 系统完整功能测试
if __name__ == "__main__":
qa_system = KnowledgeQASystem()
# 第一步:添加企业文档到知识库
print("===== 知识库文档入库 =====")
document_list = ["企业产品手册.pdf", "公司制度规范.docx", "2025财务报表.xlsx"]
for doc_path in document_list:
if os.path.exists(doc_path):
qa_system.vs_manager.add_document_to_kb(doc_path)
else:
print(f"文档【{doc_path}】不存在,跳过")
# 第二步:交互式问答测试
print("\n===== 智能知识库问答系统 =====")
print("输入问题即可提问,输入【clear】清空对话历史,输入【exit】退出系统\n")
while True:
user_query = input("请输入你的问题:")
if user_query.lower() == "exit":
print("感谢使用,系统已退出")
break
elif user_query.lower() == "clear":
qa_system.clear_dialog_history()
continue
elif not user_query.strip():
print("请输入有效问题")
continue
# 发起问答
qa_system.chat(user_query, stream=True)
2.4 第四步:系统部署与接口封装
完成上述三步后,直接运行knowledge_qa.py即可实现本地交互式问答。如需对接企业系统、前端页面,可通过 FastAPI 快速封装统一的 HTTP 接口,实现跨平台调用,核心接口封装示例如下:
python
运行
from fastapi import FastAPI, UploadFile, File, Form
from fastapi.responses import StreamingResponse
from knowledge_qa import KnowledgeQASystem
import os
import shutil
app = FastAPI(title="企业级智能知识库问答系统API")
qa_system = KnowledgeQASystem()
# 文档上传接口
@app.post("/api/document/upload")
async def upload_document(file: UploadFile = File(...)):
"""上传文档并添加到知识库"""
save_path = f"./upload_files/{file.filename}"
os.makedirs("./upload_files", exist_ok=True)
# 保存上传的文件
with open(save_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
# 添加到知识库
try:
qa_system.vs_manager.add_document_to_kb(save_path)
return {"code": 200, "message": f"文档【{file.filename}】上传并入库成功"}
except Exception as e:
return {"code": 500, "message": f"文档处理失败:{str(e)}"}
# 问答接口-流式输出
@app.post("/api/qa/stream")
async def qa_stream(query: str = Form(...), top_k: int = Form(3)):
"""流式问答接口,适配前端页面实时输出"""
async def generate():
related_chunks = qa_system.vs_manager.search_related_chunks(query, top_k=top_k)
messages = qa_system.build_prompt(query, related_chunks)
response = qa_system.client.chat.completions.create(
model="gemini-3.0-pro",
messages=messages,
stream=True,
temperature=0.2
)
for chunk in response:
if chunk.choices[0].delta.content:
yield chunk.choices[0].delta.content
return StreamingResponse(generate(), media_type="text/plain")
# 问答接口-非流式输出
@app.post("/api/qa/nostream")
async def qa_nostream(query: str = Form(...), top_k: int = Form(3)):
"""非流式问答接口,适配系统自动化调用"""
result = qa_system.chat(query, top_k=top_k, stream=False)
return {"code": 200, "data": result}
# 清空对话历史接口
@app.post("/api/dialog/clear")
async def clear_dialog():
qa_system.clear_dialog_history()
return {"code": 200, "message": "对话历史已清空"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
接口封装完成后,运行该文件即可启动 API 服务,通过 8000 端口即可调用所有接口,完美对接企业官网、办公系统、钉钉 / 企业微信机器人、前端管理页面等各类场景。
三、进阶优化:生产级能力增强
上述基础版本已可直接落地使用,如需适配企业级生产环境,可通过以下 4 个方向进行优化,进一步提升系统稳定性与可用性:
- 批量文档增量更新:新增文档批量上传、增量更新功能,定时扫描企业文档库,自动同步新增 / 修改的文档,无需手动上传;
- 检索精度优化:引入重排序(Rerank)模型,对初次检索结果进行二次排序,进一步提升语义匹配精度,同时优化切片规则,针对表格、图片内容单独处理;
- 权限管控体系:新增用户管理、文档权限分级功能,不同部门、不同角色仅能访问对应权限的知识库内容,适配企业分级管理需求;
- 高并发部署:将向量数据库替换为企业级分布式向量库(Milvus/Pinecone),接入 Redis 缓存高频问题,通过 K8s 实现服务弹性扩缩容,支撑万级并发访问。
四、常见问题与排查方案
结合实际落地经验,整理了开发过程中高频问题的排查方案,可按优先级逐一验证:
- 文档解析失败优先检查:文档格式是否在支持范围内,文件是否损坏、加密;PDF 文档如果是扫描件,需先通过 OCR 工具提取文本,本方案暂不支持纯图片扫描件。
- 模型调用返回 401 报错核心检查:环境变量是否正确配置,API Key 是否正确填写,无前后空格、大小写错误;账户可用额度是否充足,令牌是否处于有效期内。
- 回答出现幻觉,编造知识库外的内容优化方案:调低
temperature数值至 0.1-0.2,加强系统提示词的约束规则;增加检索的top_k数值,补充更多的知识库上下文;优化文档切片规则,保证切片内容的完整性。 - 检索结果不匹配,找不到相关内容优化方案:调整文档切片的
chunk_size与chunk_overlap,避免切片内容过短导致上下文丢失;优化检索的top_k数值,扩大检索范围;针对专业术语,在系统提示词中补充专业词汇说明。 - 多轮对话上下文丢失排查方案:检查对话历史的长度限制,避免超出模型上下文窗口;优化历史对话存储逻辑,仅保留关键对话信息,过滤冗余内容。