File size: 20,556 Bytes
b2e325f
 
 
 
 
d26c87a
 
3e41ff5
b2e325f
 
 
d26c87a
b2e325f
 
 
 
 
 
 
e41bb17
b2e325f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e41bb17
b2e325f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d26c87a
b2e325f
 
 
 
 
 
 
 
 
d26c87a
 
b2e325f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
"""
1. 完成了用Qwen通义千问作为知识库查询。
1. 总共有三个区块:知识库回答,应用来源,相关问题。
1. 在Huggingface的API上部署了一个在线BGE的模型,用于回答问题。OpenAI的Emebedding或者Langchain的Embedding都不可以用(会报错: self.d)。

注意事项:
1. langchain_KB.py中的代码是用来构建本地知识库的,里面的embeddings需要与rag_response_002.py中的embeddings一致。否则会出错!
1. 如果报错sentence_transformer, 主要原因是与matlabplot等各种package的兼容性冲突。目前几个核心python文件的中的package不会冲突,可以看一下。

"""

##TODO: 

# -*- coding: utf-8 -*-
import streamlit as st
import openai
import os
import numpy as np
import pandas as pd

import csv
import tempfile
from tempfile import NamedTemporaryFile
import pathlib
from pathlib import Path
import re
from re import sub
from itertools import product
import time
from time import sleep
import streamlit_authenticator as stauth
from langchain_community.vectorstores import FAISS
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain.llms.base import LLM
from langchain.llms.utils import enforce_stop_tokens
from typing import Dict, List, Optional, Tuple, Union
import requests
import streamlit as st
import qwen_response
import rag_reponse_002
import dashscope
from dotenv import load_dotenv
from datetime import datetime
import pytz
from pytz import timezone

# def get_current_time():
#     beijing_tz = timezone('Asia/Shanghai')
#     beijing_time = datetime.now(beijing_tz)
#     current_time = beijing_time.strftime('%H:%M:%S')
#     return current_time

load_dotenv()
### 设置openai的API key
os.environ["OPENAI_API_KEY"] = os.environ['user_token']
openai.api_key = os.environ['user_token']
bing_search_api_key = os.environ['bing_api_key']
dashscope.api_key = os.environ['dashscope_api_key']


### Streamlit页面设定。
st.set_page_config(layout="wide")
st.title("本地化国产大模型知识库查询演示")
# st.title("大语言模型智能知识库查询中心")
# st.title("大语言模型本地知识库问答系统")
# st.subheader("Large Language Model-based Knowledge Base QA System")
# st.warning("_声明:内容由人工智能生成,仅供参考。如果您本人使用或对外传播本服务生成的输出,您应当主动核查输出内容的真实性、准确性,避免传播虚假信息。_")
st.caption("_声明:内容由人工智能生成,仅供参考。您应当主动核查输出内容的真实性、准确性,避免传播虚假信息。_")
# st.caption("_声明:内容由人工智能生成,仅供参考。如果您本人使用或对外传播本服务生成的输出,您应当主动核查输出内容的真实性、准确性,避免传播虚假信息。_")
# st.info("_声明:内容由人工智能生成,仅供参考。如果您本人使用或对外传播本服务生成的输出,您应当主动核查输出内容的真实性、准确性,避免传播虚假信息。_")
# st.divider()



### upload file
# username = 'test'
# path = f'./{username}/faiss_index/index.faiss'
# if os.path.exists(path):
#     print(f'{path} local KB exists')
#     database_info = pd.read_csv(f'./{username}/database_name.csv')
#     current_database_name = database_info.iloc[-1][0]
#     current_database_date = database_info.iloc[-1][1]
#     database_claim = f"当前知识库为:{current_database_name},创建于{current_database_date}。可以开始提问!"
#     st.markdown(database_claim)

# uploaded_file = st.file_uploader(
#     "选择上传一个新知识库", type=(["pdf"]))
# # 默认状态下没有上传文件,None,会报错。需要判断。
# if uploaded_file is not None:
#     # uploaded_file_path = upload_file(uploaded_file)
#     upload_file(uploaded_file)


# # ## 创建向量数据库
# from langchain.embeddings.openai import OpenAIEmbeddings
# embeddings = OpenAIEmbeddings(disallowed_special=())  ## 这里是联网情况下,部署在Huggingface上后使用。
# print('embeddings:', embeddings)

# embedding_model_name = 'GanymedeNil/text2vec-large-chinese'
# # embeddings = HuggingFaceEmbeddings(model_name=embedding_model_name) ## 这里是联网情况下连接huggingface后使用。
# embeddings = HuggingFaceEmbeddings(model_name='/Users/yunshi/Downloads/360Data/Data Center/Working-On Task/演讲与培训/2023ChatGPT/Coding/RAG/bge-large-zh') ## 切换成BGE的embedding。
# embeddings = HuggingFaceEmbeddings(model_name='/Users/yunshi/Downloads/360Data/Data Center/Working-On Task/演讲与培训/2023ChatGPT/RAG/bge-large-zh/') ## 切换成BGE的embedding。
# embeddings = HuggingFaceEmbeddings(model_name='/Users/yunshi/Downloads/chatGLM/My_LocalKB_Project/GanymedeNil_text2vec-large-chinese/') ## 这里会有个“No sentence-transformers model found with name“的warning,但不是error,不影响使用。



### authentication with a local yaml file.
import yaml
from yaml.loader import SafeLoader
with open('./config.yaml') as file:
    config = yaml.load(file, Loader=SafeLoader)
authenticator = stauth.Authenticate(
    config['credentials'],
    config['cookie']['name'],
    config['cookie']['key'],
    config['cookie']['expiry_days'],
    config['preauthorized']
)

user, authentication_status, username = authenticator.login('用户登录', 'main')

if authentication_status:
    with st.sidebar:
        st.markdown(
            """
            <style>
            [data-testid="stSidebar"][aria-expanded="true"]{
                min-width: 450px;
                max-width: 450px;
            }
            """,
            unsafe_allow_html=True,
        )
        ### siderbar的题目。
        ### siderbar的题目。
        # st.header(f'**大语言模型专家系统工作设定区**')
        st.header(f'**欢迎 **{username}** 使用本系统** ')
        st.write(f'_Large Language Model Expert System Working Environment_')
        # st.write(f'_Welcome and Hope U Enjoy Staying Here_')
        authenticator.logout('登出', 'sidebar')
        
        ### upload模块
        def upload_file(uploaded_file):
            if uploaded_file is not None:
                # filename = uploaded_file.name
                # st.write(filename)  # print out the whole file name to validate. not to show in the final version. 
                try:
                    # if '.pdf' in filename: ### original code here.
                    if '.pdf' in uploaded_file.name:
                        pdf_filename = uploaded_file.name ### original code here.
                        filename = uploaded_file.name
                        # print('PDF file:', pdf_filename)
                        # with st.status('正在为您解析新知识库...', expanded=False, state='running') as status:
                        spinner = st.spinner('正在为您解析新知识库...请耐心等待')
                        with spinner:
                            ### 以下是langchain方案。
                            import langchain_KB
                            import save_database_info

                            uploaded_file_name = "File_provided"
                            temp_dir = tempfile.TemporaryDirectory()
                            # ! working.
                            uploaded_file_path = pathlib.Path(temp_dir.name) / uploaded_file_name
                            with open(pdf_filename, 'wb') as output_temporary_file:
                            # with open(f'./{username}_upload.pdf', 'wb') as output_temporary_file: ### original code here. 可能会造成在引用信息来源时文件名不对的问题。
                                # ! 必须用这种格式读入内容,然后才可以写入temporary文件夹中。
                                # output_temporary_file.write(uploaded_file.getvalue())
                                output_temporary_file.write(uploaded_file.getvalue())

                            langchain_KB.langchain_localKB_construct(output_temporary_file, username)
                            ## 在屏幕上展示当前知识库的信息,包括名字和加载日期。
                            save_database_info.save_database_info(f'./{username}/database_name.csv', pdf_filename, str(datetime.now(pytz.timezone('Asia/Shanghai')).strftime("%Y-%m-%d %H:%M")))
                            st.markdown('新知识库解析成功,请务必刷新页面,然后开启对话 🔃')
                            
                            return pdf_filename

                    else:
                        # if '.csv' in filename: ### original code here.
                        if '.csv' in uploaded_file.name:
                            print('start the csv file processing...')
                            csv_filename = uploaded_file.name
                            filename = uploaded_file.name
                            
                            csv_file = pd.read_csv(uploaded_file)
                            csv_file.to_csv(f'./{username}/{username}_upload.csv', encoding='utf-8', index=False)
                            st.write(csv_file[:3])  # 这里只是显示文件,后面需要定位文件所在的绝对路径。
                        else:
                            xls_file = pd.read_excel(uploaded_file)
                            xls_file.to_csv(f'./{username}_upload.csv', index=False)
                            st.write(xls_file[:3])

                        print('end the csv file processing...')
                        
                        # uploaded_file_name = "File_provided"
                        # temp_dir = tempfile.TemporaryDirectory()
                        # ! working.
                        # uploaded_file_path = pathlib.Path(temp_dir.name) / uploaded_file_name
                        # with open('./upload.csv', 'wb') as output_temporary_file:
                        # with open(f'./{username}_upload.csv', 'wb') as output_temporary_file:
                            # print(f'./{name}_upload.csv')
                            # ! 必须用这种格式读入内容,然后才可以写入temporary文件夹中。
                            # output_temporary_file.write(uploaded_file.getvalue())
                            # st.write(uploaded_file_path)  #* 可以查看文件是否真实存在,然后是否可以
                            
                except Exception as e:
                    st.write(e)

                ## 以下代码是为了解决上传文件后,文件路径和文件名不对的问题。
                # uploaded_file_name = "File_provided"
                # temp_dir = tempfile.TemporaryDirectory()
                # # ! working.
                # uploaded_file_path = pathlib.Path(temp_dir.name) / uploaded_file_name
                # # with open('./upload.csv', 'wb') as output_temporary_file:
                # with open(f'./{name}_upload.csv', 'wb') as output_temporary_file:
                #     # print(f'./{name}_upload.csv')
                #     # ! 必须用这种格式读入内容,然后才可以写入temporary文件夹中。
                #     # output_temporary_file.write(uploaded_file.getvalue())
                #     output_temporary_file.write(uploaded_file.getvalue())
                #     # st.write(uploaded_file_path)  # * 可以查看文件是否真实存在,然后是否可以
                # # st.write('Now file saved successfully.')

                # return pdf_filename, csv_filename
            return filename

        path = f'./{username}/faiss_index/index.faiss'
        if os.path.exists(path):
            print(f'{path} local KB exists')
            database_info = pd.read_csv(f'./{username}/database_name.csv', encoding='utf-8', header=None) ## 不加encoding的话,中文名字的PDF会报错。
            print(database_info)
            current_database_name = database_info.iloc[-1][0]
            current_database_date = database_info.iloc[-1][1]
            database_claim = f"当前知识库为:{current_database_name},创建于{current_database_date}。可以开始提问!"
            st.warning(database_claim)
            # st.markdown(database_claim)

        try:
            uploaded_file = st.file_uploader(
                "选择上传一个新知识库", type=(["pdf"]))
            # 默认状态下没有上传文件,None,会报错。需要判断。
            if uploaded_file is not None:
                # uploaded_file_path = upload_file(uploaded_file)
                upload_file(uploaded_file)
        except Exception as e:
            print(e)
            pass

        ## 在sidebar上的三个分页显示,用st.tabs实现。
        tab_1, tab_2, tab_3, tab_4 = st.tabs(['使用须知', '模型参数', '提示词模板', '系统角色设定'])

        # with st.expander(label='**使用须知**', expanded=False):
        with tab_1:
            # st.markdown("#### 快速上手指南")
            # with st.text(body="说明"):
            #     st.markdown("* 重启一轮新对话时,只需要刷新页面(按Ctrl/Command + R)即可。")
            with st.text(body="说明"):
                st.markdown("* 为了保护数据与隐私,所有对话均不会被保存,刷新页面立即删除。敬请放心。")
            # with st.text(body="说明"):
            #     st.markdown("* “GPT-4”回答质量极佳,但速度缓慢,建议适当使用。")
            with st.text(body="说明"):
                st.markdown("* 查询知识库模式与所有的搜索引擎或者数据库检索方式一样,仅限一轮对话,将不会保持之前的会话记录。")
            with st.text(body="说明"):
                st.markdown("""* 系统的工作流程如下:
    1. 用户输入问题。
    1. 系统将问题转换为机器可理解的格式。
    1. 系统使用大语言模型来生成与问题相关的候选答案。
    1. 系统使用本地知识库来评估候选答案的准确性。
    1.  系统返回最准确的答案。""")

        ## 大模型参数
        # with st.expander(label='**大语言模型参数**', expanded=True):
        with tab_2:
            max_tokens = st.slider(label='Max_Token(生成结果时最大字数)', min_value=100, max_value=8096, value=4096,step=100)
            temperature = st.slider(label='Temperature (温度)', min_value=0.0, max_value=1.0, value=0.8, step=0.1)
            top_p = st.slider(label='Top_P (核采样)', min_value=0.0, max_value=1.0, value=0.6, step=0.1)
            frequency_penalty = st.slider(label='Frequency Penalty (重复度惩罚因子)', min_value=-2.0, max_value=2.0, value=1.0, step=0.1)
            presence_penalty = st.slider(label='Presence Penalty (控制主题的重复度)', min_value=-2.0, max_value=2.0, value=1.0, step=0.1)

        with tab_3:
            # st.markdown("#### Prompt提示词参考资料")
            # with st.expander(label="**大语言模型基础提示词Prompt示例**", expanded=False):
            st.code(
                body="我是一个企业主,我需要关注哪些“存货”相关的数据资源规则?", language='plaintext')
            st.code(
                body="作为零售商,了解哪些关键的库存管理指标对我至关重要?", language='plaintext')
            st.code(body="企业主在监控库存时,应如何确保遵守行业法规和最佳实践?",
                    language='plaintext')
            st.code(body="在数字化时代,我应该关注哪些技术工具或平台来优化我的库存数据流程?", language='plaintext')
            st.code(body="我应该如何定期审查和分析这些库存数据以提高运营效率?", language='plaintext')
            st.code(body="如何设置预警系统来避免过度库存或缺货情况?", language='plaintext')

        with tab_4:
            st.text_area(label='系统角色设定', value='你是一个人工智能,你需要回答我提出的问题,或者完成我交代的任务。你需要使用我提问的语言(如中文、英文)来回答。', height=200, label_visibility='hidden')


elif authentication_status == False:
    st.error('⛔ 用户名或密码错误!')
elif authentication_status == None:
    st.warning('⬆ 请先登录!')
    

### 上传文件的模块




#### start: 主程序
## 清楚所有对话记录。
def clear_all():
    st.session_state.conversation = None
    st.session_state.chat_history = None
    st.session_state.messages = []
    message_placeholder = st.empty()
    
    ## 只用这一个就可以了。
    st.rerun()
    
    return None

if "copied" not in st.session_state: 
    st.session_state.copied = []
    
if "llm_response" not in st.session_state: 
    st.session_state.llm_response = ""

## copy to clipboard function with a button.
def copy_to_clipboard(text):
    st.session_state.copied.append(text)
    clipboard.copy(text)


def main():
    # llm = ChatGLM() ## 启动一个实例。
    col1, col2 = st.columns([2, 1])
    # st.markdown('### 数据库查询区')
    # with st.expander(label='**查询企业内部知识库**', expanded=True):
    with col1:
        KB_mode = True
        user_input = st.text_input(label='**📶 大模型数据库对话区**', placeholder='请输入您的问题', label_visibility='visible')
        if user_input:
            ## 非stream输出,原始状态,不需要改变api.py中的内容。
            # with st.status('检索中...', expanded=True, state='running') as status:
            spinner = st.spinner('思考中...请耐心等待')
            with spinner:
                if KB_mode == True:
                    # import rag_reponse_001
                    # clear_all()
                    # response = rag_reponse_001.rag_response(user_input=user_input, k=5) ## working. 
                    # print('user_input:', user_input)
                    response, source = rag_reponse_002.rag_response(username=username, user_input=user_input, k=3)
                    print('llm response:', response)
                    sim_prompt = f"""你需要根据以下的问题来提出5个可能的后续问题{user_input}
                        """
                    # sim_questions = chatgpt.chatgpt(user_prompt=sim_prompt) ## chatgpt to get similar questions.
                    sim_questions = qwen_response.call_with_messages(sim_prompt)
                    if len(user_input) != 0:
                        sim_prompt = f"""你需要根据以下的初始问题来提出3个相似的问题和3个后续问题。
                        初始问题是:{user_input}
                        --------------------
                        你回答的时候,需要使用如下格式:
                        **相似问题:**
                        **后续问题:**
                        
                        """
                        # sim_prompt = f"""你需要根据以下的问题来提出5个可能的后续问题{user_input}"""
                        
                        ### 这里用chatgpt来生成相似问题。
                        # sim_questions = chatgpt.chatgpt(user_prompt=sim_prompt)
                        
                        ### 这里用Qwen来生成相似问题。
                        sim_questions = qwen_response.call_with_messages(sim_prompt)
                    
                    
                    st.markdown(response)
                    # st_copy_to_clipboard(text=str(response), show_text=True, before_copy_label="📋", after_copy_label="✅")
                    
                    ## 如果这样使用,每次按button都会重新提交问题。
                    # st.button(label="📃", on_click=copy_to_clipboard, args=(response,))
                    
                    st.divider()
                    st.caption(source)
                    st.divider()
                    
    ## 初始状态下response未被定义。
    try:    
        if response:
            with col2:
                with st.expander(label='## **您可能还会关注以下内容**', expanded=True):
                                st.info(sim_questions)
    except:
        pass                    
                        
        # st.stop()

    return None

#### End: 主程序

if __name__ == '__main__':
    main()