0. 前沿

0.1 实验目的

项目名称:“易速鲜花”内部员工知识库问答系统。

项目介绍:“易速鲜花”作为一个大型在线鲜花销售平台,有自己的业务流程和规范,也拥有针对员工的SOP手册。新员工入职培训时,会分享相关的信息。但是,这些信息分散于内部网和HR部门目录各处,有时不便查询;有时因为文档过于冗长,员工无法第一时间找到想要的内容;有时公司政策已更新,但是员工手头的文档还是旧版内容。

基于上述需求,我们将开发一套基于各种内部知识手册的 “Doc-QA” 系统。这个系统将充分利用LangChain框架,处理从员工手册中产生的各种问题。这个问答系统能够理解员工的问题,并基于最新的员工手册,给出精准的答案。

0.2 核心流程

  1. Loading:文档加载器把Documents 加载为以LangChain能够读取的形式。
  2. Splitting:文本分割器把Documents 切分为指定大小的分割,我把它们称为“文档块”或者“文档片”。
  3. Storage:将上一步中分割好的“文档块”以“嵌入”(Embedding)的形式存储到向量数据库(Vector DB)中,形成一个个的“嵌入片”。
  4. Retrieval:应用程序从存储中检索分割后的文档(例如通过比较余弦相似度,找到与输入问题类似的嵌入片)。
  5. Output:把问题和相似的嵌入片传递给语言模型(LLM),使用包含问题和检索到的分割的提示生成答案

1. 效果

1.1 输入问题:

image-20250921221504058

1.2 后台日志:

image-20250921221639567

2. 工程

2.1 依赖

aiofiles==23.2.1
aiohttp==3.8.5
aiosignal==1.3.1
altair==5.1.2
annotated-types==0.7.0
anyio==3.7.1
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.2.3
asttokens==2.4.0
async-lru==2.0.4
async-timeout==4.0.3
attrs==23.1.0
Babel==2.12.1
backcall==0.2.0
backoff==2.2.1
beautifulsoup4==4.12.2
bleach==6.0.0
blinker==1.6.2
blis==0.7.10
cachetools==5.3.1
catalogue==2.0.9
certifi==2023.7.22
cffi==1.15.1
chardet==5.2.0
charset-normalizer==3.2.0
click==8.1.7
colorama==0.4.6
comm==0.1.4
confection==0.1.2
contourpy==1.1.0
cryptography==41.0.3
cycler==0.11.0
cymem==2.0.7
dataclasses-json==0.6.7
debugpy==1.8.0
decorator==5.1.1
defusedxml==0.7.1
Deprecated==1.2.14
dill==0.3.7
distro==1.8.0
docx2txt==0.8
elastic-transport==8.4.0
elasticsearch==8.9.0
emoji==2.8.0
exceptiongroup==1.3.0
executing==1.2.0
faiss-cpu==1.7.4
fastapi==0.103.2
fastjsonschema==2.18.0
ffmpy==0.3.1
filelock==3.12.3
filetype==1.2.0
Flask==2.3.3
fonttools==4.42.1
fqdn==1.5.1
frozenlist==1.4.0
fsspec==2023.9.0
gitdb==4.0.10
GitPython==3.1.37
google-api-core==2.11.1
google-api-python-client==2.98.0
google-auth==2.23.0
google-auth-httplib2==0.1.1
google-auth-oauthlib==1.1.0
google_search_results==2.4.2
googleapis-common-protos==1.60.0
gradio==3.47.1
gradio_client==0.6.0
greenlet==2.0.2
grpcio==1.58.0
grpcio-tools==1.58.0
h11==0.14.0
h2==4.1.0
hpack==4.0.0
httpcore==0.17.3
httplib2==0.22.0
httpx==0.24.1
httpx-sse==0.4.1
huggingface-hub==0.16.4
hyperframe==6.0.1
idna==3.4
importlib-metadata==6.8.0
importlib-resources==6.1.0
ipykernel==6.25.2
ipython==8.15.0
ipython-genutils==0.2.0
ipywidgets==8.1.1
isoduration==20.11.0
itsdangerous==2.1.2
jedi==0.19.0
Jinja2==3.1.2
joblib==1.3.2
json5==0.9.14
jsonpatch==1.33
jsonpointer==2.4
jsonschema==4.19.0
jsonschema-specifications==2023.7.1
jupyter==1.0.0
jupyter-console==6.6.3
jupyter-events==0.7.0
jupyter-lsp==2.2.0
jupyter_client==8.3.1
jupyter_core==5.3.1
jupyter_server==2.7.3
jupyter_server_terminals==0.4.4
jupyterlab==4.0.5
jupyterlab-pygments==0.2.2
jupyterlab-widgets==3.0.9
jupyterlab_server==2.25.0
kiwisolver==1.4.5
langchain==0.3.27
langchain-community==0.3.29
langchain-core==0.3.76
langchain-experimental==0.0.23
langchain-openai==0.0.2
langchain-text-splitters==0.3.11
langchainhub==0.1.14
langcodes==3.3.0
langdetect==1.0.9
langsmith==0.4.29
lxml==4.9.3
manifest-ml==0.0.1
markdown-it-py==3.0.0
MarkupSafe==2.1.3
marshmallow==3.20.1
matplotlib==3.7.3
matplotlib-inline==0.1.6
mdurl==0.1.2
mistune==3.0.1
mpmath==1.3.0
multidict==6.0.4
murmurhash==1.0.9
mypy-extensions==1.0.0
nbclient==0.8.0
nbconvert==7.8.0
nbformat==5.9.2
nest-asyncio==1.5.7
networkx==3.1
nltk==3.8.1
notebook==7.0.3
notebook_shim==0.2.3
numexpr==2.8.5
numpy==2.2.6
oauthlib==3.2.2
openai==1.6.1
orjson==3.11.3
outcome==1.2.0
overrides==7.4.0
packaging==23.2
pandas==2.1.0
pandocfilters==1.5.0
parso==0.8.3
pathy==0.10.2
pickleshare==0.7.5
Pillow==10.0.0
platformdirs==3.10.0
playwright==1.38.0
portalocker==2.7.0
preshed==3.0.8
prometheus-client==0.17.1
prompt-toolkit==3.0.39
protobuf==4.24.3
psutil==5.9.5
pure-eval==0.2.2
pyarrow==13.0.0
pyasn1==0.5.0
pyasn1-modules==0.3.0
pycparser==2.21
pydantic==2.11.9
pydantic-settings==2.10.1
pydantic_core==2.33.2
pydeck==0.8.1b0
pydub==0.25.1
pyee==9.0.4
PyGithub==1.59.1
Pygments==2.16.1
PyJWT==2.8.0
PyNaCl==1.5.0
pyparsing==3.1.1
pypdf==3.15.5
PySocks==1.7.1
python-dateutil==2.8.2
python-dotenv==1.0.0
python-iso639==2023.6.15
python-json-logger==2.0.7
python-magic==0.4.27
python-multipart==0.0.6
pytz==2023.3.post1
pywin32==306
pywinpty==2.0.11
PyYAML==6.0.1
pyzmq==25.1.1
qdrant-client==1.7.0
qtconsole==5.4.4
QtPy==2.4.0
rapidfuzz==3.4.0
redis==5.0.0
referencing==0.30.2
regex==2023.8.8
requests==2.32.5
requests-oauthlib==1.3.1
requests-toolbelt==1.0.0
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rich==13.6.0
rpds-py==0.10.3
rsa==4.9
safetensors==0.3.3
scikit-learn==1.3.0
scipy==1.11.2
selenium==4.13.0
semantic-version==2.10.0
Send2Trash==1.8.2
six==1.16.0
smart-open==6.4.0
smmap==5.0.1
sniffio==1.3.0
sortedcontainers==2.4.0
soupsieve==2.5
spacy==3.6.1
spacy-legacy==3.0.12
spacy-loggers==1.0.4
SQLAlchemy==1.4.49
sqlitedict==2.1.0
srsly==2.4.7
stack-data==0.6.2
starlette==0.27.0
streamlit==1.27.2
sympy==1.12
tabulate==0.9.0
tenacity==8.2.3
terminado==0.17.1
thinc==8.1.12
threadpoolctl==3.2.0
tiktoken==0.5.2
tinycss2==1.2.1
tokenizers==0.13.3
toml==0.10.2
tomli==2.2.1
toolz==0.12.0
torch==2.0.1
torchaudio==2.0.2
torchvision==0.15.2
tornado==6.3.3
tqdm==4.66.1
traitlets==5.9.0
transformers==4.33.1
trio==0.22.2
trio-websocket==0.11.1
typer==0.9.0
types-requests==2.31.0.6
types-urllib3==1.26.25.14
typing-inspect==0.9.0
typing-inspection==0.4.1
typing_extensions==4.15.0
tzdata==2023.3
tzlocal==5.1
unstructured==0.10.22
uri-template==1.3.0
uritemplate==4.1.1
urllib3==1.26.20
uvicorn==0.23.2
validators==0.22.0
wasabi==1.1.2
watchdog==3.0.0
wcwidth==0.2.6
webcolors==1.13
webencodings==0.5.1
websocket-client==1.6.3
websockets==11.0.3
Werkzeug==2.3.7
widgetsnbextension==4.0.9
wikipedia==1.4.0
wrapt==1.15.0
wsproto==1.2.0
yarl==1.9.2
zipp==3.17.0
zstandard==0.25.0

2.2 代码

import os

# 1.Load 导入Document Loaders
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import Docx2txtLoader
from langchain_community.document_loaders import TextLoader

# 加载Documents
base_dir = '.\OneFlower' # 文档的存放目录
documents = []
for file in os.listdir(base_dir):
# 构建完整的文件路径
file_path = os.path.join(base_dir, file)
if file.endswith('.pdf'):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
elif file.endswith('.docx'):
loader = Docx2txtLoader(file_path)
documents.extend(loader.load())
elif file.endswith('.txt'):
loader = TextLoader(file_path)
documents.extend(loader.load())

# 2.Split 将Documents切分成块以便后续进行嵌入和向量存储
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 单条分块字符数(≤8192即可)
chunk_overlap=50 # 重叠字符数(确保上下文连贯)
)
chunked_documents = text_splitter.split_documents(documents)
print(f"优化后文档分块数量:{len(chunked_documents)}") # 查看分块数量


# 3.Store 将分割嵌入并存储在矢量数据库Qdrant中
import os
import requests
from typing import List
from langchain.embeddings.base import Embeddings
from langchain_community.vectorstores import Qdrant


# 最终完整版:支持批量拆分(适配阿里25条/批的限制)
class QwenEmbeddings(Embeddings):
def __init__(self, api_key: str, base_url: str):
self.api_key = api_key
self.base_url = base_url
self.model = "text-embedding-v1"
self.max_batch_size = 25 # 阿里接口单次最大批量(固定为25,不可超)

def embed_documents(self, texts: List[str]) -> List[List[float]]:
"""嵌入文档:过滤空文本 + 批量拆分(每批≤25条)"""
# 1. 过滤空文本(避免无效请求)
valid_texts = [text.strip() for text in texts if text.strip()]
if not valid_texts:
return []

all_embeddings = [] # 存储所有批量的嵌入结果
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}

# 2. 拆分批量(每批不超过 max_batch_size 条)
for i in range(0, len(valid_texts), self.max_batch_size):
batch_texts = valid_texts[i:i + self.max_batch_size] # 取当前批次文本
payload = {
"model": self.model,
"input": batch_texts
}

# 3. 逐批调用接口
try:
response = requests.post(
f"{self.base_url}/embeddings",
headers=headers,
json=payload,
timeout=30
)
response.raise_for_status()
# 提取当前批次的嵌入结果
batch_embeddings = [item["embedding"] for item in response.json()["data"]]
all_embeddings.extend(batch_embeddings) # 合并到总结果
print(f"成功处理第 {i // self.max_batch_size + 1} 批嵌入(共 {len(batch_texts)} 条)")
except Exception as e:
print(f"处理第 {i // self.max_batch_size + 1} 批嵌入失败:{str(e)}")
print(f"当前批次文本(前300字符):{str(batch_texts[:2])[:300]}...") # 打印部分文本排查
raise

# 4. 确保结果长度与有效文本长度一致(避免遗漏)
assert len(all_embeddings) == len(valid_texts), "嵌入结果数量与文本数量不匹配!"
return all_embeddings

def embed_query(self, text: str) -> List[List[float]]:
"""嵌入查询:单条文本,无需拆分"""
return self.embed_documents([text])[0] if text.strip() else []

embedding = QwenEmbeddings(
api_key=os.environ.get("DASHSCOPE_API_KEY"), # 确保环境变量已配置
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

vectorstore = Qdrant.from_documents(
documents=chunked_documents,
embedding=embedding,
location=":memory:", # 内存存储(测试用)
collection_name="my_documents",
)

# 4. Retrieval 准备模型和Retrieval链
import logging # 导入Logging工具
from langchain_community.chat_models import ChatOpenAI # ChatOpenAI模型
from langchain.retrievers.multi_query import MultiQueryRetriever # MultiQueryRetriever工具
from langchain.chains import RetrievalQA # RetrievalQA链

# 设置Logging
logging.basicConfig()
logging.getLogger('langchain.retrievers.multi_query').setLevel(logging.INFO)

# 实例化一个大模型工具
llm = ChatOpenAI(
api_key=os.environ.get("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model_name="qwen-flash",
)

# 实例化一个MultiQueryRetriever
retriever_from_llm = MultiQueryRetriever.from_llm(retriever=vectorstore.as_retriever(), llm=llm)

# 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm,retriever=retriever_from_llm)

# 5. Output 问答系统的UI实现
from flask import Flask, request, render_template
app = Flask(__name__) # Flask APP

@app.route('/', methods=['GET', 'POST'])
def home():
if request.method == 'POST':

# 接收用户输入作为问题
question = request.form.get('question')

# RetrievalQA链 - 读入问题,生成答案
result = qa_chain({"query": question})

# 把大模型的回答结果返回网页进行渲染
return render_template('index.html', result=result)

return render_template('index.html')

if __name__ == "__main__":
app.run(host='0.0.0.0',debug=True,port=5000)

2.3 运行

提前设置好环境变量

ec0f3567-71cb-4888-b15f-dbb9b6da5a6c


本站由 卡卡龙 使用 Stellar 1.33.1主题创建

本站访问量 次. 本文阅读量 次.