File size: 13,347 Bytes
a0fe41e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ccda82a
 
a0fe41e
 
 
 
ccda82a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a0fe41e
16bcae7
 
a0fe41e
ccda82a
 
a0fe41e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16bcae7
a0fe41e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python
# coding: utf-8

import gradio as gr
from llama_cpp import Llama
from langchain_community.llms import LlamaCpp
from langchain.prompts import PromptTemplate
import llama_cpp
from langchain.callbacks.manager import CallbackManager
from sentence_transformers import SentenceTransformer
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
import numpy as np
import pandas as pd
import re
import os
from sklearn.metrics.pairwise import cosine_similarity
from transformers import AutoTokenizer, AutoModelForCausalLM


model = SentenceTransformer('sentence-transformers/paraphrase-multilingual-mpnet-base-v2',device='cpu')


model1 = AutoModelForCausalLM.from_pretrained("MediaTek-Research/Breeze-7B-Instruct-v0_1")

def invoke_with_temperature(prompt, temperature=0.4):
    # 將 prompt 編碼為模型的輸入格式
    inputs = tokenizer(prompt, return_tensors="pt")
    
    # 使用生成方法生成文本,設置溫度參數
    output = model1.generate(inputs["input_ids"], max_length=200, temperature=temperature, num_return_sequences=1)

    # 解碼並返回生成的文本
    return tokenizer.decode(output[0], skip_special_tokens=True)
    
# llm = LlamaCpp(
#     model_path=r"MediaTek-Research/Breeze-7B-Instruct-v0_1",
#     n_gpu_layers=100,
#     n_batch=512,
#     n_ctx=3000,
#     f16_kv=True,
#     callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),
#     verbose=False,
# )

embedd_bk=pd.read_pickle(r".\bk_description1_角色形容詞_677.pkl")
df_bk=pd.read_excel(r".\bk_description1_角色形容詞短文.xlsx")

# def invoke_with_temperature(prompt, temperature=0.4):
#     return llm.invoke(prompt, temperature=temperature)

def process_user_input(message):
    user_mental_state4= PromptTemplate(
        input_variables=["input"],
        template="""[INST]<<SYS>>你是一位具有同理心的專業心理諮商師,沒有性別歧視,你可以客觀的根據談話內容的描述,判斷說話的人的心理困擾<</SYS>> 
        請根據{input}描述三個最有可能心理困擾,輸出只包含三個心理困擾,回答格式只採用CSV格式,分隔符號使用逗號,參考以下範例:名詞1,名詞2,名詞3。[/INST]"""
    )
    
    user_character= PromptTemplate(
        input_variables=["input"],
        template="""[INST]<<SYS>>你是一位具有同理心的專業心理諮商師,沒有性別歧視,你可以客觀的根據談話內容的描述,判斷說話的大學生,在生活中的多重角色身分<</SYS>> 
        請你根據談話內容{input},客觀的判斷說話的大學生,在談話內容中的角色,以及他生活中其他角色的身分,提供三個最有可能的角色身分名詞,
        輸出只包含三個身分名詞,回答格式只採用CSV格式,分隔符號使用逗號,參考以下範例:名詞1,名詞2,名詞3。[/INST]"""
    )
    

    df_user=pd.DataFrame(columns=["輸入內容","形容詞1", "形容詞2", "形容詞3", "角色1", "角色2", "角色3"])
    #df_user_record=pd.read_excel(r"C:\Users\Cora\推薦系統實作\gradio系統歷史紀錄.xlsx")
    

    prompt_value1=user_mental_state4.invoke({"input":message})
    string=invoke_with_temperature(prompt_value1)
    #print("\n")

    # 將字符串分割為名詞
    adjectives = [adj.strip() for adj in re.split('[,、,]', string)]
    
    index=len(df_user)
    df_user.loc[index, '輸入內容'] = message

    # 確保形容詞數量符合欄位數量
    if len(adjectives) == 3:
        df_user.loc[index, '形容詞1'] = adjectives[0]
        df_user.loc[index, '形容詞2'] = adjectives[1]
        df_user.loc[index, '形容詞3'] = adjectives[2]

    prompt_value2=user_character.invoke({"input":message})
    string=invoke_with_temperature(prompt_value2)
    #print("\n")

    # 將字符串分割為名詞
    character = [adj.strip() for adj in re.split('[,、,]', string)]
    for i in range(min(len(character), 3)):
        df_user.loc[index, f'角色{i+1}'] = character[i]
    # if len(character) == 3:
    #     df_user.loc[index, '角色1'] = character[0]
    #     df_user.loc[index, '角色2'] = character[1]
    #     df_user.loc[index, '角色3'] = character[2]
    df_user.to_excel("user_gradio系統.xlsx")
    return df_user
    #return message

def embedd_df_user(df_user):
    
    columns_to_encode=df_user.loc[:,["形容詞1", "形容詞2", "形容詞3"]]

    # 初始化一個空的 DataFrame,用來存儲向量化結果
    embedd_user=df_user[["輸入內容"]]
    #user_em= user_em.assign(形容詞1=None, 形容詞2=None, 形容詞3=None,角色1=None,角色2=None,角色3=None)
    embedd_user= embedd_user.assign(形容詞1=None, 形容詞2=None, 形容詞3=None)
    

    # 遍歷每一個單元格,將結果存入新的 DataFrame 中
    i=len(df_user)-1
    for col in columns_to_encode:
        #print(i,col)
        # 將每個單元格的內容進行向量化
        embedd_user.at[i, col] = model.encode(df_user.at[i, col])           
    
    #embedd_user.to_pickle(r"C:\Users\user\推薦系統實作\user_gradio系統.pkl")
    
    return embedd_user
    #word="happy"
    #return word

def top_n_books_by_average(df, n=3):
    
    # 根据 `average` 列降序排序
    sorted_df = df.sort_values(by='average', ascending=False)
    
    # 选择前 N 行
    top_n_df = sorted_df.head(n)
    
    # 提取书名列
    top_books = top_n_df['書名'].tolist()
    
    return top_books,sorted_df

def similarity(embedd_user,embedd_bk,df_bk):
    df_similarity= pd.DataFrame(df_bk[['書名','短文','URL',"形容詞1", "形容詞2", "形容詞3", '角色1', '角色2', '角色3']])
    df_similarity['average'] = np.nan
    #for p in range(len(embedd_user)): 
    index=len(embedd_user)-1               
    for k in range(len(embedd_bk)):
            list=[]
            for i in range(1,4):
                for j in range(3,6):
                    vec1=embedd_user.iloc[index,i]#i是第i個形容詞,index是第幾個是使用者輸入
                    vec2=embedd_bk.iloc[k,j]
                    similarity = cosine_similarity([vec1], [vec2])
                    list.append(similarity[0][0])
            # 计算总和
            total_sum = sum(list)
            # 计算数量
            count = len(list)
            # 计算平均值
            average = total_sum / count
            df_similarity.loc[k,'average']=average

    top_books,sorted_df = top_n_books_by_average(df_similarity)
    return sorted_df     

def filter(sorted_df,df_user):
    filter_prompt4 = PromptTemplate(
        input_variables=["mental_issue", "user_identity"," book","book_reader", "book_description"],
        template="""[INST]<<SYS>>你是專業的心理諮商師和書籍推薦專家,擅長根據使用者的心理問題、身份特質,以及書名、書籍針對的主題和適合的讀者,判斷書籍是否適合推薦給使用者。

        你的目的是幫助讀者找到可以緩解心理問題的書籍。請注意:
        1. 若書籍針對的問題與使用者的心理問題有關聯,即使書籍適合的讀者群與使用者身份沒有直接關聯,應偏向推薦。
        2. 若使用者身份的需求與書籍針對的問題有潛在關聯,應偏向推薦。
        3. 若書籍適合的讀者與使用者身份特質有任何關聯,應傾向推薦。
        4. 若書名跟使用者的心理問題或身分特質有任何關聯,應偏向推薦<</SYS>>

        使用者提供的資訊如下:
        使用者身份是「{user_identity}」,其心理問題是「{mental_issue}」。書名是{book},書籍適合的讀者群為「{book_reader}」,書籍針對的問題是「{book_description}」。

        請根據以上資訊判斷這本書是否適合推薦給該使用者。
        僅輸出「是」或「否」,輸出後即停止。[/INST]"""
     )
    df_filter=sorted_df.iloc[:20,:]
    df_filter = df_filter.reset_index(drop=True)
    df_filter=df_filter.assign(推薦=None)

    
    p=len(df_user)-1
    sum_for_bk=0
    # 提取角色內容
    role1 = df_user["角色1"].iloc[p] if pd.notnull(df_user["角色1"].iloc[p]) else ""
    role2 = df_user["角色2"].iloc[p] if pd.notnull(df_user["角色2"].iloc[p]) else ""
    role3 = df_user["角色3"].iloc[p] if pd.notnull(df_user["角色3"].iloc[p]) else ""
    
    # 用"、"連接不為空的角色
    user_identity = "、".join([role for role in [role1, role2, role3] if role])  # 只加入有內容的角色
    
    #user_identity = df_user["角色1"].iloc[p]+"、"+df_user["角色2"].iloc[p]+"、"+df_user["角色3"].iloc[p]
    mental_issue=df_user["形容詞1"].iloc[p]+"、"+df_user["形容詞2"].iloc[p]+"、"+df_user["形容詞3"].iloc[p]
    for k in range(len(df_filter)):    
        #word=df_user["輸入內容"].iloc[p]
        #book_reader = df_filter["角色1"].iloc[p] + "or" + df_filter["角色2"].iloc[p] + "or" + df_filter["角色3"].iloc[p]
        book=df_filter["書名"].iloc[k] 
        book_reader = df_filter["角色1"].iloc[k] 
        # user_identity = df_user["角色1"].iloc[p]+"、"+df_user["角色2"].iloc[p]+"、"+df_user["角色3"].iloc[p]
        # mental_issue=df_user["形容詞1"].iloc[p]+"、"+df_user["形容詞2"].iloc[p]+"、"+df_user["形容詞3"].iloc[p]
        book_description=df_filter["形容詞1"].iloc[k]+"、"+df_filter["形容詞2"].iloc[k]+"、"+df_filter["形容詞3"].iloc[k]
        print(book_reader)
        print(user_identity)
        #output = filter_prompt1.invoke({"user_identity": user_identity, "book_reader": book_reader})
        output = filter_prompt4.invoke({"mental_issue":mental_issue,"user_identity": user_identity, "book":book,"book_description":book_description,"book_reader": book_reader})
        string2=invoke_with_temperature(output)
        df_filter.loc[k, '推薦'] =string2
        if string2.strip()=="是":
            sum_for_bk+=1
            if(sum_for_bk==3):
                break   
    df_recommend=df_filter[df_filter["推薦"].str.strip() == "是"]
        
    return df_recommend
    
def output_content(df_recommend):
    
    title = {}
    URL = {}
    summary = {}
    
    for i in range(3):
        title[f'title_{i}'] = df_recommend.iloc[i, 0]  # Using iloc instead of loc
        URL[f'URL_{i}'] = df_recommend.iloc[i, 2]
        summary[f'summary_{i}'] = df_recommend.iloc[i, 1]
    
    output = f"""根據您的狀態,這裡提供三本書供您參考\n 
               <第一本>
               書名:{title['title_0']}\n        
               本書介紹:{summary['summary_0']}\n       
               購書網址:{URL['URL_0']}\n 
               <第二本>
               書名:{title['title_1']}\n        
               本書介紹:{summary['summary_1']}\n       
               購書網址:{URL['URL_1']}\n   
               <第三本>
               書名:{title['title_2']}\n        
               本書介紹:{summary['summary_2']}\n       
               購書網址:{URL['URL_2']}\n   
               希望對您有所幫助"""
    return output                   
                    
def main_pipeline(message,history):
                
    df_user=process_user_input(message)
    embedd_user=embedd_df_user(df_user)
    sorted_df=similarity(embedd_user,embedd_bk,df_bk)
    df_filter=filter(sorted_df,df_user)
    final=output_content(df_filter)
    return final

css = """
.chatbox .message-box {
    height: 500px !important;  # 設定訊息框的高度
    width: 100% 
    overflow-y: auto;  # 如果內容超出高度則顯示滾動條
    text-rendering: optimizeLegibility;  # 啟用抗鋸齒渲染
}


"""

theme=gr.themes.Default(primary_hue=gr.themes.colors.red, secondary_hue=gr.themes.colors.pink,font=[gr.themes.GoogleFont("LXGW WenKai Mono TC")]).set(
  body_background_fill='#FFF5EE'
)

with gr.Blocks(theme=theme) as demo:
    with gr.Row():
        with gr.Column():
            gr.Markdown("""
            <div style="text-align: center;">
                <h1 style="display: inline; vertical-align: middle;">
                    <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR_Rj6Add1OjrIeVXL4z84YzG4QIEuM4ptvvQ&s" 
                         width="100" height="100" style="display: inline; vertical-align: middle; margin-right: 10px;">
                    心理書籍推薦系統
                    <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR_Rj6Add1OjrIeVXL4z84YzG4QIEuM4ptvvQ&s" 
                         width="100" height="100" style="display: inline; vertical-align: middle; margin-left: 10px;">
                </h1>
            </div>
            """)

            gr.ChatInterface(
                main_pipeline,
                type="messages",
                title="",  # title 設為空,使用自定義 Markdown 標題
                description='<div style="text-align: center;font-size:16px">這是個讓人放鬆的網站,希望透過讓人抒發心情表達現在面臨的狀況與挑戰,從書裡獲得解答。</div><div style="text-align: center;font-size: 16px;">-你可以告訴我們最近的心情和想法,放心我們不會儲存任何紀錄-</div>',
                css=css
            )

if __name__ == "__main__":
    demo.launch(share=True)


# In[ ]: