cwadayi commited on
Commit
022e486
·
verified ·
1 Parent(s): b84a794

Update ai_service.py

Browse files
Files changed (1) hide show
  1. ai_service.py +67 -91
ai_service.py CHANGED
@@ -1,128 +1,104 @@
1
- # ai_service.py (Instructs AI to assume 'largest' means by magnitude)
2
  import json
3
  import os
4
  import random
5
  from datetime import datetime
6
  import google.generativeai as genai
 
7
  from gradio_client import Client
8
 
9
  # 從設定檔匯入金鑰和 URL
10
  from config import MCP_SERVER_URL
11
 
12
- # --- 1. 設定 Gemini API 金鑰 (金鑰輪替邏輯) ---
 
 
13
  key1 = os.getenv("GEMINI_API_KEY")
14
  key2 = os.getenv("GEMINI_API_KEY2")
15
- valid_keys = []
 
16
  if key1 and "YOUR_GEMINI_API_KEY" not in key1:
17
- valid_keys.append(key1)
18
  if key2 and "YOUR_GEMINI_API_KEY" not in key2:
19
- valid_keys.append(key2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- selected_key = None
22
- if valid_keys:
23
- selected_key = random.choice(valid_keys)
24
- print(f"--- Configuring Gemini. Selected key ending in: ...{selected_key[-4:]} ---")
25
- genai.configure(api_key=selected_key)
26
- else:
27
- print("--- No valid Gemini API Key found. AI Service will be disabled. ---")
28
 
29
- # --- 2. 工具函式 (Tool Functions) ---
30
 
31
- def call_mcp_earthquake_search(
32
- start_date: str,
33
- end_date: str,
34
- min_magnitude: float = 4.0, #降低最小規模門檻以獲取更多資料
35
- max_magnitude: float = 9.0
36
- ) -> str:
37
- """根據指定的條件(時間、規模)從遠端伺服器搜尋地震事件。"""
38
  try:
39
- print(f"--- 正在呼叫遠端地震 MCP 伺服器 (由 Gemini 觸發) ---")
40
  client = Client(src=MCP_SERVER_URL)
41
- result = client.predict(
42
- param_0=start_date, param_1="00:00:00",
43
- param_2=end_date, param_3="23:59:59",
44
- param_4=21.0, param_5=26.0,
45
- param_6=119.0, param_7=123.0,
46
- param_8=0.0, param_9=100.0,
47
- param_10=min_magnitude, param_11=max_magnitude,
48
- api_name="/gradio_fetch_and_plot_data"
49
- )
50
  data = result[0].get('data', [])
51
- if not data:
52
- return "查詢完成,但未找到任何符合條件的地震資料。"
53
  headers = result[0].get('headers', [])
54
  return json.dumps([dict(zip(headers, row)) for row in data], indent=2, ensure_ascii=False)
55
- except Exception as e:
56
- return f"工具執行失敗,錯誤訊息: {e}"
57
 
58
  def call_mcp_pws_search() -> str:
59
- """從遠端伺服器查詢最新的 PWS (Public Weather Service) 發布情形。"""
60
  try:
61
- print(f"--- 正在呼叫遠端 PWS MCP 伺服器 (由 Gemini 觸發) ---")
62
  client = Client("cwadayi/MCP-pws-running")
63
  result = client.predict(None, api_name="/get_disaster_warnings")
64
  return result[0] if isinstance(result, tuple) and len(result) > 0 else str(result)
65
- except Exception as e:
66
- return f"工具執行失敗,錯誤訊息: {e}"
67
-
68
- # --- 3. 向 Gemini 定義工具 (Tool Declarations) ---
69
 
70
- earthquake_search_tool_declaration = {
71
- "name": "call_earthquake_search_tool",
72
- "description": "從台灣中央氣象署的資料庫中搜尋地震事件。",
73
- "parameters": {
74
- "type": "OBJECT",
75
- "properties": {
76
- "start_date": {
77
- "type": "STRING",
78
- "description": "搜尋的開始日期 (格式 'YYYY-MM-DD')。模型應根據使用者問題中的相對時間(例如:昨天、上個月、去年)或絕對時間(例如:2024年)來主動推斷此日期。"
79
- },
80
- "end_date": {
81
- "type": "STRING",
82
- "description": "搜尋的結束日期 (格式 'YYYY-MM-DD')。模型應根據使用者問題中的相對時間或絕對時間來主動推斷此日期。"
83
- },
84
- },
85
- "required": ["start_date", "end_date"]
86
- }
87
- }
88
 
89
- pws_search_tool_declaration = {
90
- "name": "call_mcp_pws_search",
91
- "description": "查詢最新的 PWS (Public Weather Service) 公共天氣服務發布情形。",
92
- "parameters": {"type": "OBJECT", "properties": {}}
93
- }
94
 
95
- available_tools = {
96
- "call_earthquake_search_tool": call_mcp_earthquake_search,
97
- "call_mcp_pws_search": call_mcp_pws_search
98
- }
99
-
100
- # --- 4. 建立 Gemini 模型 ---
101
- model = None
102
- if selected_key:
103
  try:
104
- # [*** 核心修正 ***]
105
- # 新增指令,要求 AI 在遇到「最大」等模糊詞彙時,主動以「規模」作為判斷標準
106
- system_instruction = (
107
- "You are a helpful AI assistant. You must answer in Traditional Chinese."
108
- "You have access to tools. When the user asks for the 'largest' or 'strongest' earthquake, "
109
- "you MUST assume they mean by magnitude. You should then analyze the JSON data returned by the tool, "
110
- "find the single entry with the highest 'magnitude' value, and present that specific earthquake's details as the answer."
111
- "Do not simply show all the data; find the maximum and answer the question directly."
112
- )
113
- model = genai.GenerateContent(
114
  model_name="gemini-1.5-flash",
115
  tools=[earthquake_search_tool_declaration, pws_search_tool_declaration],
116
- system_instruction=system_instruction
 
 
 
 
 
 
117
  )
118
- except Exception as e:
119
- print(f"建立 Gemini 模型失敗: {e}")
120
-
121
- # --- 5. 主要的 AI 文字生成函式 ---
122
- def generate_ai_text(user_prompt: str) -> str:
123
- if not model:
124
- return "🤖 AI (Gemini) 服務尚未設定 API 金鑰,或金鑰無效。"
125
- try:
126
  chat = model.start_chat()
127
  response = chat.send_message(user_prompt)
128
  for part in response.parts:
@@ -140,7 +116,7 @@ def generate_ai_text(user_prompt: str) -> str:
140
  return final_response.text
141
 
142
  return response.text
 
143
  except Exception as e:
144
  print(f"與 Gemini AI 互動時發生錯誤: {e}")
145
  return f"🤖 AI 服務發生錯誤: {e}"
146
-
 
1
+ # ai_service.py (Implements robust API key validation and rotation)
2
  import json
3
  import os
4
  import random
5
  from datetime import datetime
6
  import google.generativeai as genai
7
+ from google.api_core import exceptions as google_exceptions
8
  from gradio_client import Client
9
 
10
  # 從設定檔匯入金鑰和 URL
11
  from config import MCP_SERVER_URL
12
 
13
+ # --- 1. [核心修改] API 金鑰健康檢查與輪替機制 ---
14
+
15
+ # 從環境變數讀取您設定的兩組金鑰
16
  key1 = os.getenv("GEMINI_API_KEY")
17
  key2 = os.getenv("GEMINI_API_KEY2")
18
+
19
+ all_keys = []
20
  if key1 and "YOUR_GEMINI_API_KEY" not in key1:
21
+ all_keys.append(key1)
22
  if key2 and "YOUR_GEMINI_API_KEY" not in key2:
23
+ all_keys.append(key2)
24
+
25
+ # 建立一個存放 "健康" (有效) 金鑰的列表
26
+ healthy_keys = []
27
+ if all_keys:
28
+ print(f"--- Found {len(all_keys)} Gemini API Key(s). Starting validation... ---")
29
+ for key in all_keys:
30
+ try:
31
+ # 嘗試用每個 key 來設定並初始化一個模型,以驗證其有效性
32
+ genai.configure(api_key=key)
33
+ # 嘗試發送一個非常簡單的測試請求
34
+ test_model = genai.GenerativeModel('gemini-1.5-flash')
35
+ test_model.generate_content("test", request_options={'timeout': 10})
36
+
37
+ # 如果成功,將此 key 加入健康列表
38
+ healthy_keys.append(key)
39
+ print(f"--- Key ending in ...{key[-4:]} is VALID and added to the pool. ---")
40
+ except google_exceptions.PermissionDenied as e:
41
+ print(f"--- Key ending in ...{key[-4:]} is INVALID (Permission Denied). Skipping. ---")
42
+ print(f" Error details: {e}")
43
+ except Exception as e:
44
+ # 捕捉其他可能的錯誤,例如超時、額度用盡等
45
+ print(f"--- Key ending in ...{key[-4:]} failed validation. Skipping. ---")
46
+ print(f" Error details: {e}")
47
 
48
+ if not healthy_keys:
49
+ print("--- CRITICAL: No valid Gemini API Keys found. AI Service will be disabled. ---")
 
 
 
 
 
50
 
51
+ # --- 2. 工具函式 (Tool Functions) --- (此區塊維持不變)
52
 
53
+ def call_mcp_earthquake_search(start_date: str, end_date: str, min_magnitude: float = 4.0, max_magnitude: float = 9.0) -> str:
 
 
 
 
 
 
54
  try:
 
55
  client = Client(src=MCP_SERVER_URL)
56
+ 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")
 
 
 
 
 
 
 
 
57
  data = result[0].get('data', [])
58
+ if not data: return "查詢完成,但未找到任何符合條件的地震資料。"
 
59
  headers = result[0].get('headers', [])
60
  return json.dumps([dict(zip(headers, row)) for row in data], indent=2, ensure_ascii=False)
61
+ except Exception as e: return f"工具執行失敗,錯誤訊息: {e}"
 
62
 
63
  def call_mcp_pws_search() -> str:
 
64
  try:
 
65
  client = Client("cwadayi/MCP-pws-running")
66
  result = client.predict(None, api_name="/get_disaster_warnings")
67
  return result[0] if isinstance(result, tuple) and len(result) > 0 else str(result)
68
+ except Exception as e: return f"工具執行失敗,錯誤訊息: {e}"
 
 
 
69
 
70
+ # --- 3. 向 Gemini 定義工具 (Tool Declarations) --- (此區塊維持不變)
71
+ # ... (此處省略與上一版相同的工具定義程式碼)
72
+ earthquake_search_tool_declaration = { "name": "call_earthquake_search_tool", "description": "從台灣中央氣象署的資料庫中搜尋地震事件。", "parameters": { "type": "OBJECT", "properties": { "start_date": { "type": "STRING", "description": "搜尋的開始日期 (格式 'YYYY-MM-DD')。模型應根據使用者問題中的相對時間(例如:昨天、上個月、去年)或絕對時間(例如:2024年)來主動推斷此日期。" }, "end_date": { "type": "STRING", "description": "搜尋的結束日期 (格式 'YYYY-MM-DD')。模型應根據使用者問題中的相對時間或絕對時間來主動推斷此日期。" }, }, "required": ["start_date", "end_date"] } }
73
+ pws_search_tool_declaration = { "name": "call_mcp_pws_search", "description": "查詢最新的 PWS (Public Weather Service) 公共天氣服務發布情形。", "parameters": { "type": "OBJECT", "properties": {} } }
74
+ available_tools = { "call_earthquake_search_tool": call_mcp_earthquake_search, "call_mcp_pws_search": call_mcp_pws_search }
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ # --- 4. 主要的 AI 文字生成函式 ---
 
 
 
 
77
 
78
+ def generate_ai_text(user_prompt: str) -> str:
79
+ # [核心修改] 每次呼叫時,都從健康的金鑰池中隨機選取
80
+ if not healthy_keys:
81
+ return "🤖 AI (Gemini) 服務錯誤:沒有任何有效的 API 金鑰可供使用。"
82
+
 
 
 
83
  try:
84
+ # 從健康池中隨機選擇一個 key
85
+ selected_key = random.choice(healthy_keys)
86
+ print(f"--- Handling request with key ending in: ...{selected_key[-4:]} ---")
87
+
88
+ # 使用選定的 key 建立模型實例
89
+ genai.configure(api_key=selected_key)
90
+ model = genai.GenerativeModel(
 
 
 
91
  model_name="gemini-1.5-flash",
92
  tools=[earthquake_search_tool_declaration, pws_search_tool_declaration],
93
+ system_instruction=(
94
+ "You are a helpful AI assistant. You must answer in Traditional Chinese."
95
+ "When the user asks for the 'largest' or 'strongest' earthquake, "
96
+ "you MUST assume they mean by magnitude. You should then analyze the JSON data returned by the tool, "
97
+ "find the single entry with the highest 'magnitude' value, and present that specific earthquake's details as the answer."
98
+ "Do not simply show all the data; find the maximum and answer the question directly."
99
+ )
100
  )
101
+
 
 
 
 
 
 
 
102
  chat = model.start_chat()
103
  response = chat.send_message(user_prompt)
104
  for part in response.parts:
 
116
  return final_response.text
117
 
118
  return response.text
119
+
120
  except Exception as e:
121
  print(f"與 Gemini AI 互動時發生錯誤: {e}")
122
  return f"🤖 AI 服務發生錯誤: {e}"