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 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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.29.1主题创建

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