File size: 5,821 Bytes
fe4a3b8
84b321a
fe4a3b8
84b321a
 
04a59fe
fe4a3b8
 
dfdd885
84b321a
 
dfdd885
fe4a3b8
84b321a
 
dfdd885
fe4a3b8
84b321a
 
 
 
 
 
 
dfdd885
84b321a
fe4a3b8
84b321a
 
 
fe4a3b8
 
84b321a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfdd885
84b321a
 
dfdd885
fe4a3b8
84b321a
 
 
 
b0d9c94
fe4a3b8
 
b0d9c94
 
 
84b321a
 
dfdd885
84b321a
58fd6fa
fe4a3b8
84b321a
 
 
a7dcc8d
fe4a3b8
 
 
 
 
a7dcc8d
84b321a
 
a7dcc8d
 
84b321a
 
 
dfdd885
fe4a3b8
84b321a
 
 
dfdd885
84b321a
 
 
 
 
 
 
 
 
 
b0d9c94
84b321a
 
 
 
b0d9c94
84b321a
 
b0d9c94
fe4a3b8
84b321a
fe4a3b8
84b321a
b0d9c94
84b321a
 
dfdd885
84b321a
fe4a3b8
 
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
# ai_service.py (Definitive fix for the ImportError)
import json
from datetime import datetime
import google.generativeai as genai
from gradio_client import Client

# [修正] 移除 'Part' 的 import,因為它導致了錯誤
# from google.generativeai.types import Part

# 從設定檔匯入金鑰和 URL
from config import GEMINI_API_KEY, MCP_SERVER_URL

# --- 1. 設定 Gemini API 金鑰 (一次性設定) ---
if GEMINI_API_KEY and "YOUR_GEMINI_API_KEY" not in GEMINI_API_KEY:
    genai.configure(api_key=GEMINI_API_KEY)

# --- 2. 工具函式 (用於地震查詢) ---
def call_mcp_earthquake_search(
    start_date: str,
    end_date: str,
    min_magnitude: float = 4.5,
    max_magnitude: float = 8.0
) -> str:
    """根據指定的條件(時間、規模)從遠端伺服器搜尋地震事件。"""
    try:
        print(f"--- 正在呼叫遠端地震 MCP 伺服器 (由 Gemini 觸發) ---")
        print(f"    查詢條件: {start_date}{end_date}, 規模 {min_magnitude} 以上")

        client = Client(src=MCP_SERVER_URL)
        result = client.predict(
            param_0=start_date, param_1="00:00:00",
            param_2=end_date, param_3="23:59:59",
            param_4=21.0, param_5=26.0, # 預設台灣緯度
            param_6=119.0, param_7=123.0, # 預設台灣經度
            param_8=0.0, param_9=100.0,
            param_10=min_magnitude, param_11=max_magnitude,
            api_name="/gradio_fetch_and_plot_data"
        )
        dataframe_dict = result[0]
        data = dataframe_dict.get('data', [])

        if not data:
            print("--- MCP 伺服器回傳:未找到符合條件的地震 ---")
            return "查詢完成,但未找到任何符合條件的地震資料。"

        headers = dataframe_dict.get('headers', [])
        formatted_results = [dict(zip(headers, row)) for row in data]
        print(f"--- MCP 伺服器成功回傳 {len(data)} 筆資料 ---")
        return json.dumps(formatted_results, indent=2, ensure_ascii=False)
    except Exception as e:
        print(f"呼叫 MCP 伺服器失敗: {e}")
        return f"工具執行失敗,錯誤訊息: {e}"

# --- 3. 向 Gemini 定義工具 ---
earthquake_search_tool_declaration = {
    "name": "call_earthquake_search_tool",
    "description": "根據指定的條件(時間、地點、規模等)從台灣中央氣象署的資料庫中搜尋地震事件。預設搜尋台灣周邊地區。",
    "parameters": {
        "type": "OBJECT", "properties": {
            "start_date": {"type": "STRING", "description": "搜尋的開始日期 (格式 'YYYY-MM-DD')。模型應根據使用者問題推斷此日期,例如從『去年』或『2024年』推斷出 '2024-01-01'。"},
            "end_date": {"type": "STRING", "description": "搜尋的結束日期 (格式 'YYYY-MM-DD')。模型應根據使用者問題推斷此日期,例如從『昨天』或『2024年』推斷出 '2024-12-31'。"},
            "min_magnitude": {"type": "NUMBER", "description": "要搜尋的最小地震規模。如果使用者未指定,請使用預設值 4.5。"},
            "max_magnitude": {"type": "NUMBER", "description": "要搜尋的最大地震規模。預設為 8.0。"},
        }, "required": ["start_date", "end_date"]
    }
}

available_tools = {"call_earthquake_search_tool": call_mcp_earthquake_search}

# --- 4. 建立 Gemini 模型 ---
model = None
if GEMINI_API_KEY and "YOUR_GEMINI_API_KEY" not in GEMINI_API_KEY:
    try:
        system_instruction = (
            "You are a helpful AI assistant. You must answer in Traditional Chinese."
            "You have access to tools. When a tool returns data in JSON format, "
            "you must analyze the JSON data to fully answer the user's question. "
            "For example, if the user asks for the largest earthquake, use the search tool for the relevant date range "
            "and then find the entry with the highest magnitude from the JSON results before answering."
        )
        model = genai.GenerativeModel(
            model_name="gemini-1.5-flash",
            tools=[earthquake_search_tool_declaration],
            system_instruction=system_instruction
        )
    except Exception as e:
        print(f"建立 Gemini 模型失敗: {e}")

# --- 5. 主要的 AI 文字生成函式 ---
def generate_ai_text(user_prompt: str) -> str:
    if not model:
        return "🤖 AI (Gemini) 服務尚未設定 API 金鑰,或金鑰無效。"
    try:
        print(f"--- 開始 Gemini 對話,使用者輸入: '{user_prompt}' ---")
        chat = model.start_chat()
        response = chat.send_message(user_prompt)
        try:
            function_call = response.candidates[0].content.parts[0].function_call
        except (IndexError, AttributeError):
            function_call = None
        if not function_call:
            print("--- Gemini 直接回覆文字 ---")
            return response.text
        
        print(f"--- Gemini 要求呼叫工具: {function_call.name} ---")
        tool_function = available_tools.get(function_call.name)
        if not tool_function:
            return f"錯誤:模型嘗試呼叫一個不存在的工具 '{function_call.name}'。"
        
        tool_result = tool_function(**dict(function_call.args))
        print("--- 將工具結果回傳給 Gemini ---")
        
        # [修正] 直接傳送包含 function_response 的字典,不再使用 Part 類別
        response = chat.send_message(
            {"function_response": {"name": function_call.name, "response": {"result": tool_result}}}
        )
        
        print("--- Gemini 根據工具結果生成最終回覆 ---")
        return response.text
    except Exception as e:
        print(f"與 Gemini AI 互動時發生錯誤: {e}")
        return f"🤖 AI 服務發生錯誤: {e}"