Spaces:
Running
Running
File size: 8,249 Bytes
b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc d65bc48 b8a4efc |
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 |
import os, re, requests, base64
from langchain_core.documents import Document
from langchain_chroma import Chroma
from openai import OpenAI
from langchain.embeddings.base import Embeddings
from langchain_google_genai import ChatGoogleGenerativeAI
import chromadb
import gradio as gr
from langchain.memory import ConversationBufferMemory
from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate
# =============================================
# 1️⃣ 自訂 LM Studio Embedding 類別
# =============================================
class LmStudioEmbeddings(Embeddings):
def __init__(self, model_name, url):
self.model_name = model_name
self.client = OpenAI(base_url=url, api_key="lm-studio")
def embed_query(self, text: str):
res = self.client.embeddings.create(input=text, model=self.model_name)
return res.data[0].embedding
def embed_documents(self, texts: list[str]):
res = self.client.embeddings.create(input=texts, model=self.model_name)
return [x.embedding for x in res.data]
# =============================================
# 2️⃣ 載入 QA 檔案並分類
# =============================================
path = "/Users/adamlin/Library/CloudStorage/OneDrive-個人/QA/QA_v2.txt"
with open(path, "r", encoding="utf-8") as f:
text = f.read()
pattern = r"(Q[::].*?)(?=Q[::]|$)"
qas = re.findall(pattern, text, flags=re.S)
qa_docs = {"證券": [], "期貨": [], "複委託": []}
for qa in qas:
if "證券" in qa:
qa_docs["證券"].append(Document(page_content=qa.strip(), metadata={"source": path}))
elif "期貨" in qa:
qa_docs["期貨"].append(Document(page_content=qa.strip(), metadata={"source": path}))
elif "複委託" in qa:
qa_docs["複委託"].append(Document(page_content=qa.strip(), metadata={"source": path}))
print("✅ 已成功讀取 QA 並完成分類:")
for k, v in qa_docs.items():
print(f" {k}:{len(v)} 筆")
# =============================================
# 3️⃣ 建立三個獨立向量資料庫
# =============================================
embedding = LmStudioEmbeddings(
model_name="text-embedding-bge-large-zh-v1.5",
url="http://127.0.0.1:1234/v1"
)
client = chromadb.PersistentClient(path="./chroma_db")
collection_names = {"證券": "stocks", "期貨": "futures", "複委託": "overseas"}
vectordbs = {}
for cat, docs in qa_docs.items():
eng_name = collection_names[cat]
vectordbs[cat] = Chroma(
client=client,
collection_name=eng_name,
embedding_function=embedding
)
if len(vectordbs[cat].get()["documents"]) == 0:
vectordbs[cat].add_documents(docs)
print("✅ 各類別向量資料庫建立完成")
# =============================================
# 4️⃣ 初始化 Gemini LLM + 記憶模組
# =============================================
API_KEY = "AIzaSyAxoIHYjStZ5xPe2EoNrOapHhvVmx9QzWs"
llm = ChatGoogleGenerativeAI(model='gemini-2.5-flash', google_api_key=API_KEY)
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# ✅ 只保留一個變數 input,context 會手動插入文字中
prompt = ChatPromptTemplate.from_messages([
("system", "你是一位金融客服人員,請根據下列公司規章內容回答使用者問題。若內容不足,也請根據既有資訊給出合理說明,並建議洽營業員了解詳情。"),
("human", "{input}")
])
chain = LLMChain(
llm=llm,
prompt=prompt,
memory=memory
)
# =============================================
# 5️⃣ 自動分類 + 對話主邏輯
# =============================================
def auto_detect_category(text):
if any(k in text for k in ["股票", "證券", "開戶", "下單", "交割", "現股"]):
return "證券"
elif any(k in text for k in ["期貨", "選擇權", "結算", "保證金", "契約"]):
return "期貨"
elif any(k in text for k in ["複委託", "海外", "美股", "港股", "國外"]):
return "複委託"
else:
return "證券"
def chat_fn(message, history):
print(f"[DEBUG] 問題:{message}")
if "午餐吃什麼" in message:
return "還在盤中交易無法離開,還是我們約下午茶如何?"
category = auto_detect_category(message)
vectordb = vectordbs.get(category)
if not vectordb:
return "目前尚無此類別的知識庫,請洽營業員。"
# 向量檢索
docs = vectordb.similarity_search(message, k=2)
context = "\n\n".join([d.page_content for d in docs]) if docs else "目前查無相關內容。"
# ✅ 將 context 手動整合進輸入文字中(新版 LangChain 安全寫法)
full_input = f"公司規章內容如下:\n{context}\n\n使用者問題:{message}"
try:
response = chain.invoke({"input": full_input})
reply = response["text"].strip()
except Exception as e:
reply = f"⚠️ 生成錯誤:{e}"
return reply or "請洽營業員"
# =============================================
# 6️⃣ Gradio 介面 + 左上角 logo
# =============================================
"""
#要在HF上部署的話需要改ㄧ下api,把它藏起來
import os
from langchain_google_genai import ChatGoogleGenerativeAI
API_KEY = os.getenv("GOOGLE_API_KEY")
if not API_KEY:
raise ValueError("⚠️ 未設定 GOOGLE_API_KEY,請在 Hugging Face Secrets 中新增。")
llm = ChatGoogleGenerativeAI(model='gemini-2.5-flash', google_api_key=API_KEY)
"""
# =============================================
logo_path = r"/Users/adamlin/Library/CloudStorage/OneDrive-個人/QA/mega.png" # ← 改成你的實際路徑
with open(logo_path, "rb") as f:
logo_base64 = base64.b64encode(f.read()).decode("utf-8")
with gr.Blocks(
theme="Taithrah/Minimal",
css="""
/* 固定 logo 在左上角 */
#logo-top {
position: fixed;
top: 12px;
left: 18px;
z-index: 1000;
background-color: white;
border-radius: 10px;
padding: 6px 8px;
box-shadow: 0 0 8px rgba(0,0,0,0.15);
}
#logo-top img {
width: 120px;
height: auto;
display: block;
}
"""
) as demo:
# 插入 logo
gr.HTML(f"""
<div id="logo-top">
<img src="data:image/png;base64,{logo_base64}" alt="logo">
</div>
""")
gr.Markdown("<h1 style='text-align:center'>👨💼 我是小智,您的金融好幫手🫰</h1>")
with gr.Row():
with gr.Column(scale=4):
chatbox = gr.Chatbot(label="💬 對話紀錄", type="messages")
user_input = gr.Textbox(label="輸入訊息", placeholder="請輸入問題...")
def handle_input(message, history):
reply = chat_fn(message, history)
history = history + [
{"role": "user", "content": message},
{"role": "assistant", "content": reply}
]
return history, gr.update(value="")
user_input.submit(handle_input, [user_input, chatbox], [chatbox, user_input])
with gr.Column(scale=1):
gr.Markdown("### 👇 快速提問")
btns = [
("未成年可以開戶嗎?", "未成年可以開戶嗎?"),
("法人開戶要準備什麼?", "法人開戶要準備什麼?"),
("期貨交易保證金是什麼?", "期貨交易保證金是什麼?"),
("複委託要如何下單?", "複委託要如何下單?"),
("美股交易時間?", "美股交易時間?"),
("美股可以定期定額嗎?", "美股可以定期定額嗎?")
]
for label, q in btns:
gr.Button(label).click(lambda h, q=q: handle_input(q, h), [chatbox], [chatbox, user_input])
# ✅ 清除記憶按鈕
def clear_memory():
memory.clear()
return [], gr.update(value="", placeholder="請輸入問題...")
gr.Button("🧹 整理畫面").click(clear_memory, outputs=[chatbox, user_input])
demo.launch() |