Amity123's picture
Update app.py
a38aea5 verified
import os
import re
import traceback
import numpy as np
import gradio as gr
from openai import OpenAI
# ======== 1. HF Secret 讀取與安全檢查 ========
secret_key = os.environ.get("OPENAIAPIKEY")
if not secret_key:
raise ValueError(
"⚠️ HF Space Secret 'OPENAIAPIKEY' 未設定或名稱錯誤!"
"請先在 HF Space Secrets 裡建立此 Secret 並重新部署。"
)
os.environ["OPENAI_API_KEY"] = secret_key
# ======== 2. 初始化 OpenAI SDK ========
client = OpenAI()
# ======== 3. 專業領域定義 ========
PROFESSIONS = {
"程式設計": "你是一位資深程式設計師,回答必須專業、詳細,附上程式範例與步驟。",
"行銷": "你是一位行銷專家,回答必須專業、詳細,提供可執行行銷策略與步驟。",
"法律": "你是一位律師,回答必須專業、法律依據明確、可操作建議清楚。",
"醫療": "你是一位醫師,回答必須專業、以健康與安全為前提,提供可操作建議。",
"財務": "你是一位財務顧問,回答必須專業、符合台灣會計與稅務法規,提供可執行建議。",
"設計": "你是一位設計師,回答必須專業、詳細,提供設計步驟與案例。"
}
# ======== 4. UTF-8 安全函數 ========
def safe_utf8(text):
if not isinstance(text, str):
text = str(text)
return text.encode("utf-8", errors="ignore").decode("utf-8")
# ======== 5. Embedding 初始化 ========
profession_embeddings = {}
def ensure_embeddings():
global profession_embeddings
if not profession_embeddings:
for field in PROFESSIONS.keys():
try:
emb = client.embeddings.create(
model="text-embedding-3-small",
input=safe_utf8(field)
)
profession_embeddings[field] = np.array(emb.data[0].embedding)
except Exception as e:
print(f"Embedding 初始化失敗 ({field}):", e)
profession_embeddings[field] = np.zeros(1536)
# ======== 6. 自動判斷職業 ========
def detect_profession(user_input: str) -> str:
ensure_embeddings()
try:
text_emb = client.embeddings.create(
model="text-embedding-3-small",
input=safe_utf8(user_input)
)
text_vector = np.array(text_emb.data[0].embedding)
except Exception as e:
print("Embedding 呼叫錯誤:", traceback.format_exc())
return "你是一個專業顧問,回答必須專業、詳細、可操作。"
scores = {}
for field, emb in profession_embeddings.items():
if np.linalg.norm(text_vector) == 0 or np.linalg.norm(emb) == 0:
scores[field] = -1
else:
scores[field] = np.dot(text_vector, emb) / (np.linalg.norm(text_vector)*np.linalg.norm(emb))
best_field = max(scores, key=scores.get)
return PROFESSIONS[best_field]
# ======== 7. AI Agent 回答 ========
def professional_agent(user_input, state):
user_input = safe_utf8(user_input)
if state.get("profession_prompt") is None:
profession_prompt = detect_profession(user_input)
state["profession_prompt"] = profession_prompt
question = re.sub(r"我是.*?(,|,)", "", user_input)
if not question.strip():
answer = f"✅ 已設定你的專業領域:\n{profession_prompt}\n請提出問題。"
state["chat_history"].append({"role":"user","content":user_input})
state["chat_history"].append({"role":"assistant","content":answer})
return state["chat_history"], state
else:
user_input = question
messages = [{"role": "system", "content": state["profession_prompt"]}]
for msg in state["chat_history"]:
messages.append({"role": msg["role"], "content": msg["content"]})
messages.append({"role": "user", "content": user_input})
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
temperature=0.2
)
answer = safe_utf8(response.choices[0].message.content)
except Exception as e:
tb = traceback.format_exc()
print(tb) # 後端完整 log
answer = f"⚠️ 發生錯誤,請查看後端 logs: {str(e)}"
# Append using dict 格式
state["chat_history"].append({"role":"user","content":user_input})
state["chat_history"].append({"role":"assistant","content":answer})
# 保留最近 10 條訊息
if len(state["chat_history"]) > 20:
state["chat_history"] = state["chat_history"][-20:]
return state["chat_history"], state
# ======== 8. Gradio 介面 ========
with gr.Blocks() as demo:
gr.Markdown("## 🧑‍💼AI 專業領域顧問")
gr.Markdown("第一次輸入可以同時輸入職業 + 問題,例如:我是會計師,我想知道台灣稅務分析")
chatbot = gr.Chatbot(type="messages")
msg = gr.Textbox(label="輸入訊息")
state = gr.State({"chat_history": [], "profession_prompt": None})
msg.submit(professional_agent, [msg, state], [chatbot, state])
demo.launch(server_name="0.0.0.0", server_port=7860)