adamtobegreat commited on
Commit
b8a4efc
·
verified ·
1 Parent(s): 8ffc316

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +210 -56
app.py CHANGED
@@ -1,70 +1,224 @@
 
 
 
 
 
 
 
1
  import gradio as gr
2
- from huggingface_hub import InferenceClient
3
-
4
-
5
- def respond(
6
- message,
7
- history: list[dict[str, str]],
8
- system_message,
9
- max_tokens,
10
- temperature,
11
- top_p,
12
- hf_token: gr.OAuthToken,
13
- ):
14
- """
15
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
16
- """
17
- client = InferenceClient(token=hf_token.token, model="openai/gpt-oss-20b")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- messages = [{"role": "system", "content": system_message}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- messages.extend(history)
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- messages.append({"role": "user", "content": message})
 
24
 
25
- response = ""
 
26
 
27
- for message in client.chat_completion(
28
- messages,
29
- max_tokens=max_tokens,
30
- stream=True,
31
- temperature=temperature,
32
- top_p=top_p,
33
- ):
34
- choices = message.choices
35
- token = ""
36
- if len(choices) and choices[0].delta.content:
37
- token = choices[0].delta.content
38
 
39
- response += token
40
- yield response
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  """
44
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
 
 
 
 
 
 
 
 
 
45
  """
46
- chatbot = gr.ChatInterface(
47
- respond,
48
- type="messages",
49
- additional_inputs=[
50
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
51
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
52
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
53
- gr.Slider(
54
- minimum=0.1,
55
- maximum=1.0,
56
- value=0.95,
57
- step=0.05,
58
- label="Top-p (nucleus sampling)",
59
- ),
60
- ],
61
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
- with gr.Blocks() as demo:
64
- with gr.Sidebar():
65
- gr.LoginButton()
66
- chatbot.render()
 
 
 
 
 
 
 
 
67
 
 
 
 
 
 
68
 
69
- if __name__ == "__main__":
70
- demo.launch()
 
1
+ import os, re, requests, base64
2
+ from langchain_core.documents import Document
3
+ from langchain_chroma import Chroma
4
+ from openai import OpenAI
5
+ from langchain.embeddings.base import Embeddings
6
+ from langchain_google_genai import ChatGoogleGenerativeAI
7
+ import chromadb
8
  import gradio as gr
9
+ from langchain.memory import ConversationBufferMemory
10
+ from langchain.chains import LLMChain
11
+ from langchain.prompts import ChatPromptTemplate
12
+
13
+ # =============================================
14
+ # 1️⃣ 自訂 LM Studio Embedding 類別
15
+ # =============================================
16
+ class LmStudioEmbeddings(Embeddings):
17
+ def __init__(self, model_name, url):
18
+ self.model_name = model_name
19
+ self.client = OpenAI(base_url=url, api_key="lm-studio")
20
+
21
+ def embed_query(self, text: str):
22
+ res = self.client.embeddings.create(input=text, model=self.model_name)
23
+ return res.data[0].embedding
24
+
25
+ def embed_documents(self, texts: list[str]):
26
+ res = self.client.embeddings.create(input=texts, model=self.model_name)
27
+ return [x.embedding for x in res.data]
28
+
29
+ # =============================================
30
+ # 2️⃣ 載入 QA 檔案並分類
31
+ # =============================================
32
+ path = "/Users/adamlin/Library/CloudStorage/OneDrive-個人/QA/QA_v2.txt"
33
+ with open(path, "r", encoding="utf-8") as f:
34
+ text = f.read()
35
+
36
+ pattern = r"(Q[::].*?)(?=Q[::]|$)"
37
+ qas = re.findall(pattern, text, flags=re.S)
38
+
39
+ qa_docs = {"證券": [], "期貨": [], "複委託": []}
40
+ for qa in qas:
41
+ if "證券" in qa:
42
+ qa_docs["證券"].append(Document(page_content=qa.strip(), metadata={"source": path}))
43
+ elif "期貨" in qa:
44
+ qa_docs["期貨"].append(Document(page_content=qa.strip(), metadata={"source": path}))
45
+ elif "複委託" in qa:
46
+ qa_docs["複委託"].append(Document(page_content=qa.strip(), metadata={"source": path}))
47
+
48
+ print("✅ 已成功讀取 QA 並完成分類:")
49
+ for k, v in qa_docs.items():
50
+ print(f" {k}:{len(v)} 筆")
51
 
52
+ # =============================================
53
+ # 3️⃣ 建立三個獨立向量資料庫
54
+ # =============================================
55
+ embedding = LmStudioEmbeddings(
56
+ model_name="text-embedding-bge-large-zh-v1.5",
57
+ url="http://127.0.0.1:1234/v1"
58
+ )
59
+
60
+ client = chromadb.PersistentClient(path="./chroma_db")
61
+ collection_names = {"證券": "stocks", "期貨": "futures", "複委託": "overseas"}
62
+
63
+ vectordbs = {}
64
+ for cat, docs in qa_docs.items():
65
+ eng_name = collection_names[cat]
66
+ vectordbs[cat] = Chroma(
67
+ client=client,
68
+ collection_name=eng_name,
69
+ embedding_function=embedding
70
+ )
71
+ if len(vectordbs[cat].get()["documents"]) == 0:
72
+ vectordbs[cat].add_documents(docs)
73
+
74
+ print("✅ 各類別向量資料庫建立完成")
75
+
76
+ # =============================================
77
+ # 4️⃣ 初始化 Gemini LLM + 記憶模組
78
+ # =============================================
79
+ API_KEY = "AIzaSyAxoIHYjStZ5xPe2EoNrOapHhvVmx9QzWs"
80
+ llm = ChatGoogleGenerativeAI(model='gemini-2.5-flash', google_api_key=API_KEY)
81
+
82
+ memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
83
+
84
+ # ✅ 只保留一個變數 input,context 會手動插入文字中
85
+ prompt = ChatPromptTemplate.from_messages([
86
+ ("system", "你是一位金融客服人員,請根據下列公司規章內容回答使用者問題。若內容不足,也請根據既有資訊給出合理說明,並建議洽營業員了解詳情。"),
87
+ ("human", "{input}")
88
+ ])
89
+
90
+ chain = LLMChain(
91
+ llm=llm,
92
+ prompt=prompt,
93
+ memory=memory
94
+ )
95
 
96
+ # =============================================
97
+ # 5️⃣ 自動分類 + 對話主邏輯
98
+ # =============================================
99
+ def auto_detect_category(text):
100
+ if any(k in text for k in ["股票", "證券", "開戶", "下單", "交割", "現股"]):
101
+ return "證券"
102
+ elif any(k in text for k in ["期貨", "選擇權", "結算", "保證金", "契約"]):
103
+ return "期貨"
104
+ elif any(k in text for k in ["複委託", "海外", "美股", "港股", "國外"]):
105
+ return "複委託"
106
+ else:
107
+ return "證券"
108
 
109
+ def chat_fn(message, history):
110
+ print(f"[DEBUG] 問題:{message}")
111
 
112
+ if "午餐吃什麼" in message:
113
+ return "還在盤中交易無法離開,還是我們約下午茶如何?"
114
 
115
+ category = auto_detect_category(message)
116
+ vectordb = vectordbs.get(category)
117
+ if not vectordb:
118
+ return "目前尚無此類別的知識庫,請洽營業員。"
 
 
 
 
 
 
 
119
 
120
+ # 向量檢索
121
+ docs = vectordb.similarity_search(message, k=2)
122
+ context = "\n\n".join([d.page_content for d in docs]) if docs else "目前查無相關內容。"
123
 
124
+ # ✅ 將 context 手動整合進輸入文字中(新版 LangChain 安全寫法)
125
+ full_input = f"公司規章內容如下:\n{context}\n\n使用者問題:{message}"
126
+
127
+ try:
128
+ response = chain.invoke({"input": full_input})
129
+ reply = response["text"].strip()
130
+ except Exception as e:
131
+ reply = f"⚠️ 生成錯誤:{e}"
132
+
133
+ return reply or "請洽營業員"
134
+
135
+ # =============================================
136
+ # 6️⃣ Gradio 介面 + 左上角 logo
137
+ # =============================================
138
 
139
  """
140
+ #要在HF上部署的話需要改ㄧ下api,把它藏起來
141
+
142
+ import os
143
+ from langchain_google_genai import ChatGoogleGenerativeAI
144
+
145
+ API_KEY = os.getenv("GOOGLE_API_KEY")
146
+ if not API_KEY:
147
+ raise ValueError("⚠️ 未設定 GOOGLE_API_KEY,請在 Hugging Face Secrets 中新增。")
148
+
149
+ llm = ChatGoogleGenerativeAI(model='gemini-2.5-flash', google_api_key=API_KEY)
150
  """
151
+ # =============================================
152
+
153
+
154
+
155
+ logo_path = r"/Users/adamlin/Library/CloudStorage/OneDrive-個人/QA/mega.png" # 改成你的實際路徑
156
+ with open(logo_path, "rb") as f:
157
+ logo_base64 = base64.b64encode(f.read()).decode("utf-8")
158
+
159
+ with gr.Blocks(
160
+ theme="Taithrah/Minimal",
161
+ css="""
162
+ /* 固定 logo 在左上角 */
163
+ #logo-top {
164
+ position: fixed;
165
+ top: 12px;
166
+ left: 18px;
167
+ z-index: 1000;
168
+ background-color: white;
169
+ border-radius: 10px;
170
+ padding: 6px 8px;
171
+ box-shadow: 0 0 8px rgba(0,0,0,0.15);
172
+ }
173
+ #logo-top img {
174
+ width: 120px;
175
+ height: auto;
176
+ display: block;
177
+ }
178
+ """
179
+ ) as demo:
180
+
181
+ # 插入 logo
182
+ gr.HTML(f"""
183
+ <div id="logo-top">
184
+ <img src="data:image/png;base64,{logo_base64}" alt="logo">
185
+ </div>
186
+ """)
187
+
188
+ gr.Markdown("<h1 style='text-align:center'>👨‍💼 我是小智,您的金融好幫手🫰</h1>")
189
+
190
+ with gr.Row():
191
+ with gr.Column(scale=4):
192
+ chatbox = gr.Chatbot(label="💬 對話紀錄", type="messages")
193
+ user_input = gr.Textbox(label="輸入訊息", placeholder="請輸入問題...")
194
+
195
+ def handle_input(message, history):
196
+ reply = chat_fn(message, history)
197
+ history = history + [
198
+ {"role": "user", "content": message},
199
+ {"role": "assistant", "content": reply}
200
+ ]
201
+ return history, gr.update(value="")
202
+
203
+ user_input.submit(handle_input, [user_input, chatbox], [chatbox, user_input])
204
 
205
+ with gr.Column(scale=1):
206
+ gr.Markdown("### 👇 快速提問")
207
+ btns = [
208
+ ("未成年可以開戶嗎?", "未成年可以開戶嗎?"),
209
+ ("法人開戶要準備什麼?", "法人開戶要準備什麼?"),
210
+ ("期貨交易保證金是什麼?", "期貨交易保證金是什麼?"),
211
+ ("複委託要如何下單?", "複委託要如何下單?"),
212
+ ("美股交易時間?", "美股交易時間?"),
213
+ ("美股可以定期定額嗎?", "美股可以定期定額嗎?")
214
+ ]
215
+ for label, q in btns:
216
+ gr.Button(label).click(lambda h, q=q: handle_input(q, h), [chatbox], [chatbox, user_input])
217
 
218
+ # ✅ 清除記憶按鈕
219
+ def clear_memory():
220
+ memory.clear()
221
+ return [], gr.update(value="", placeholder="請輸入問題...")
222
+ gr.Button("🧹 整理畫面").click(clear_memory, outputs=[chatbox, user_input])
223
 
224
+ demo.launch()