Spaces:
Sleeping
Sleeping
| # ai_service.py (Instructs AI to execute tool calls without asking for confirmation) | |
| import json | |
| import os | |
| import random | |
| from datetime import datetime | |
| import google.generativeai as genai | |
| from google.api_core import exceptions as google_exceptions | |
| from gradio_client import Client | |
| # 從設定檔匯入金鑰和 URL | |
| from config import MCP_SERVER_URL | |
| # --- 1. API 金鑰健康檢查與輪替機制 --- | |
| key1 = os.getenv("GEMINI_API_KEY") | |
| key2 = os.getenv("GEMINI_API_KEY2") | |
| all_keys = [] | |
| if key1 and "YOUR_GEMINI_API_KEY" not in key1: all_keys.append(key1) | |
| if key2 and "YOUR_GEMINI_API_KEY" not in key2: all_keys.append(key2) | |
| healthy_keys = [] | |
| if all_keys: | |
| print(f"--- Found {len(all_keys)} Gemini API Key(s). Starting validation... ---") | |
| for key in all_keys: | |
| try: | |
| genai.configure(api_key=key) | |
| test_model = genai.GenerativeModel('gemini-1.5-flash') | |
| test_model.generate_content("test", request_options={'timeout': 10}) | |
| healthy_keys.append(key) | |
| print(f"--- Key ending in ...{key[-4:]} is VALID and added to the pool. ---") | |
| except Exception as e: | |
| print(f"--- Key ending in ...{key[-4:]} failed validation. Skipping. Error: {e} ---") | |
| if not healthy_keys: | |
| print("--- CRITICAL: No valid Gemini API Keys found. AI Service will be disabled. ---") | |
| # --- 2. 工具函式 (Tool Functions) --- | |
| def call_mcp_earthquake_search(start_date: str, end_date: str, min_magnitude: float = 4.0, max_magnitude: float = 9.0) -> str: | |
| try: | |
| 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") | |
| data = result[0].get('data', []) | |
| if not data: return "查詢完成,但未找到任何符合條件的地震資料。" | |
| headers = result[0].get('headers', []) | |
| return json.dumps([dict(zip(headers, row)) for row in data], indent=2, ensure_ascii=False) | |
| except Exception as e: return f"工具執行失敗,錯誤訊息: {e}" | |
| def call_mcp_pws_search() -> str: | |
| try: | |
| client = Client("cwadayi/MCP-pws-running") | |
| result = client.predict(None, api_name="/get_disaster_warnings") | |
| return result[0] if isinstance(result, tuple) and len(result) > 0 else str(result) | |
| except Exception as e: return f"工具執行失敗,錯誤訊息: {e}" | |
| # --- 3. 向 Gemini 定義工具 (Tool Declarations) --- | |
| 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"] } } | |
| pws_search_tool_declaration = { "name": "call_mcp_pws_search", "description": "查詢最新的 PWS (Public Weather Service) 公共天氣服務發布情形。", "parameters": { "type": "OBJECT", "properties": {} } } | |
| available_tools = { "call_earthquake_search_tool": call_mcp_earthquake_search, "call_mcp_pws_search": call_mcp_pws_search } | |
| # --- 4. 主要的 AI 文字生成函式 --- | |
| def generate_ai_text(user_prompt: str) -> str: | |
| if not healthy_keys: | |
| return "🤖 AI (Gemini) 服務錯誤:沒有任何有效的 API 金鑰可供使用。" | |
| try: | |
| selected_key = random.choice(healthy_keys) | |
| print(f"--- Handling request with key ending in: ...{selected_key[-4:]} ---") | |
| genai.configure(api_key=selected_key) | |
| # [*** 核心修正 ***] | |
| # 在系統指令中,明確要求 AI 在推斷出參數後「不要提問,直接執行」 | |
| system_instruction = ( | |
| "You are a helpful AI assistant. You must answer in Traditional Chinese." | |
| "When a user's query can be answered by a tool, you MUST infer the parameters from the query and call the tool immediately without asking for confirmation. Do not ask the user to confirm the parameters you have inferred." | |
| "When the user asks for the 'largest' or 'strongest' earthquake, you MUST assume they mean by magnitude. You should then analyze the JSON data returned by the tool, " | |
| "find the single entry with the highest 'magnitude' value, and present that specific earthquake's details as the answer." | |
| ) | |
| model = genai.GenerativeModel( | |
| model_name="gemini-1.5-flash", | |
| tools=[earthquake_search_tool_declaration, pws_search_tool_declaration], | |
| system_instruction=system_instruction | |
| ) | |
| chat = model.start_chat() | |
| response = chat.send_message(user_prompt) | |
| for part in response.parts: | |
| if part.function_call: | |
| function_call = part.function_call | |
| 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)) | |
| final_response = chat.send_message( | |
| {"function_response": {"name": function_call.name, "response": {"result": tool_result}}} | |
| ) | |
| return final_response.text | |
| return response.text | |
| except Exception as e: | |
| print(f"與 Gemini AI 互動時發生錯誤: {e}") | |
| return f"🤖 AI 服務發生錯誤: {e}" | |