cwadayi commited on
Commit
fe4a3b8
·
verified ·
1 Parent(s): 13b5faf

Update ai_service.py

Browse files
Files changed (1) hide show
  1. ai_service.py +23 -70
ai_service.py CHANGED
@@ -1,53 +1,20 @@
1
- # ai_service.py (修正與優化版本)
2
  import json
3
- from datetime import datetime, timedelta, date
4
  import google.generativeai as genai
5
  from gradio_client import Client
6
 
7
- # [修正] 引入 google.ai.generativelanguage 來處理工具回傳,這是官方建議的標準做法
8
- from google.ai import generativelanguage as glm
9
 
10
  # 從設定檔匯入金鑰和 URL
11
  from config import GEMINI_API_KEY, MCP_SERVER_URL
12
 
13
- # --- 1. 設定 Gemini API 金鑰 ---
14
- # 這個設定只會在程式啟動時執行一次
15
  if GEMINI_API_KEY and "YOUR_GEMINI_API_KEY" not in GEMINI_API_KEY:
16
  genai.configure(api_key=GEMINI_API_KEY)
17
 
18
- # --- 2. 內部輔助函式 (處理相對日期) ---
19
- def _resolve_relative_date(date_str: str) -> str:
20
- """
21
- 將 "今天", "昨天", "上週" 等相對日期字串轉換為 'YYYY-MM-DD' 格式。
22
- 如果無法識別,則返回今天的日期。
23
- """
24
- today = date.today()
25
- date_str = date_str.lower()
26
-
27
- if "今天" in date_str or "today" in date_str:
28
- return today.strftime('%Y-%m-%d')
29
- if "昨天" in date_str or "yesterday" in date_str:
30
- return (today - timedelta(days=1)).strftime('%Y-%m-%d')
31
- if "前天" in date_str:
32
- return (today - timedelta(days=2)).strftime('%Y-%m-%d')
33
- if "上週" in date_str or "last week" in date_str:
34
- return (today - timedelta(weeks=1)).strftime('%Y-%m-%d')
35
- if "上個月" in date_str or "last month" in date_str:
36
- # 簡單計算,回到上個月的同一天,若不存在則為月底
37
- last_month_day = today.replace(day=1) - timedelta(days=1)
38
- return last_month_day.replace(day=today.day if today.day <= last_month_day.day else last_month_day.day).strftime('%Y-%m-%d')
39
-
40
- # 如果傳入的已經是 'YYYY-MM-DD' 格式,直接返回
41
- try:
42
- datetime.strptime(date_str, '%Y-%m-%d')
43
- return date_str
44
- except ValueError:
45
- # 對於無法識別的格式 (例如 "去年"),給予一個合理的預設值 (今天)
46
- print(f"[警告] 無法解析日期 '{date_str}',將使用今天作為預設值。")
47
- return today.strftime('%Y-%m-%d')
48
-
49
-
50
- # --- 3. 工具函式 (用於地震查詢) ---
51
  def call_mcp_earthquake_search(
52
  start_date: str,
53
  end_date: str,
@@ -56,18 +23,13 @@ def call_mcp_earthquake_search(
56
  ) -> str:
57
  """根據指定的條件(時間、規模)從遠端伺服器搜尋地震事件。"""
58
  try:
59
- # [優化] 使用輔助函式處理相對日期
60
- resolved_start_date = _resolve_relative_date(start_date)
61
- resolved_end_date = _resolve_relative_date(end_date)
62
-
63
  print(f"--- 正在呼叫遠端地震 MCP 伺服器 (由 Gemini 觸發) ---")
64
- print(f" 原始查詢: start='{start_date}', end='{end_date}'")
65
- print(f" 解析後查詢: start='{resolved_start_date}', end='{resolved_end_date}', 規模 {min_magnitude} 以上")
66
 
67
  client = Client(src=MCP_SERVER_URL)
68
  result = client.predict(
69
- param_0=resolved_start_date, param_1="00:00:00",
70
- param_2=resolved_end_date, param_3="23:59:59",
71
  param_4=21.0, param_5=26.0, # 預設台灣緯度
72
  param_6=119.0, param_7=123.0, # 預設台灣經度
73
  param_8=0.0, param_9=100.0,
@@ -89,14 +51,14 @@ def call_mcp_earthquake_search(
89
  print(f"呼叫 MCP 伺服器失敗: {e}")
90
  return f"工具執行失敗,錯誤訊息: {e}"
91
 
92
- # --- 4. 向 Gemini 定義工具 ---
93
  earthquake_search_tool_declaration = {
94
  "name": "call_earthquake_search_tool",
95
  "description": "根據指定的條件(時間、地點、規模等)從台灣中央氣象署的資料庫中搜尋地震事件。預設搜尋台灣周邊地區。",
96
  "parameters": {
97
  "type": "OBJECT", "properties": {
98
- "start_date": {"type": "STRING", "description": "搜尋的開始日期 (格式 'YYYY-MM-DD')。模型應根據使用者問題推斷此日期,例如從『去年』、『上個月』、『昨天』等推斷出對應日期。"},
99
- "end_date": {"type": "STRING", "description": "搜尋的結束日期 (格式 'YYYY-MM-DD')。模型應根據使用者問題推斷此日期。"},
100
  "min_magnitude": {"type": "NUMBER", "description": "要搜尋的最小地震規模。如果使用者未指定,請使用預設值 4.5。"},
101
  "max_magnitude": {"type": "NUMBER", "description": "要搜尋的最大地震規模。預設為 8.0。"},
102
  }, "required": ["start_date", "end_date"]
@@ -105,16 +67,16 @@ earthquake_search_tool_declaration = {
105
 
106
  available_tools = {"call_earthquake_search_tool": call_mcp_earthquake_search}
107
 
108
- # --- 5. 建立 Gemini 模型 ---
109
  model = None
110
  if GEMINI_API_KEY and "YOUR_GEMINI_API_KEY" not in GEMINI_API_KEY:
111
  try:
112
  system_instruction = (
113
- "你是個樂於助人的 AI 助理,你必須使用繁體中文回答。"
114
- "你可以取用工具來查詢資訊。當工具以 JSON 格式回傳資料時,"
115
- "你必須分析 JSON 資料來完整回答使用者的問題。"
116
- "例如,如果使用者問『去年最大的地震是哪個?』,你應該先使用搜尋工具查詢去年的日期範圍,"
117
- "然後從回傳的 JSON 結果中找出規模(magnitude)最大的那筆紀錄,最後再回答。"
118
  )
119
  model = genai.GenerativeModel(
120
  model_name="gemini-1.5-flash",
@@ -124,7 +86,7 @@ if GEMINI_API_KEY and "YOUR_GEMINI_API_KEY" not in GEMINI_API_KEY:
124
  except Exception as e:
125
  print(f"建立 Gemini 模型失敗: {e}")
126
 
127
- # --- 6. 主要的 AI 文字生成函式 ---
128
  def generate_ai_text(user_prompt: str) -> str:
129
  if not model:
130
  return "🤖 AI (Gemini) 服務尚未設定 API 金鑰,或金鑰無效。"
@@ -132,14 +94,10 @@ def generate_ai_text(user_prompt: str) -> str:
132
  print(f"--- 開始 Gemini 對話,使用者輸入: '{user_prompt}' ---")
133
  chat = model.start_chat()
134
  response = chat.send_message(user_prompt)
135
-
136
- # 檢查模型是否要求呼叫工具
137
  try:
138
  function_call = response.candidates[0].content.parts[0].function_call
139
  except (IndexError, AttributeError):
140
  function_call = None
141
-
142
- # 如果模型沒有要求呼叫工具,直接回傳文字結果
143
  if not function_call:
144
  print("--- Gemini 直接回覆文字 ---")
145
  return response.text
@@ -149,22 +107,17 @@ def generate_ai_text(user_prompt: str) -> str:
149
  if not tool_function:
150
  return f"錯誤:模型嘗試呼叫一個不存在的工具 '{function_call.name}'。"
151
 
152
- # 執行工具函式
153
  tool_result = tool_function(**dict(function_call.args))
154
  print("--- 將工具結果回傳給 Gemini ---")
155
 
156
- # [修正] 使用官方建議的 glm.Part 物件來回傳工具執行結果
157
  response = chat.send_message(
158
- glm.Part(
159
- function_response=glm.FunctionResponse(
160
- name=function_call.name,
161
- response={'result': tool_result}
162
- )
163
- )
164
  )
165
 
166
  print("--- Gemini 根據工具結果生成最終回覆 ---")
167
  return response.text
168
  except Exception as e:
169
  print(f"與 Gemini AI 互動時發生錯誤: {e}")
170
- return f"🤖 AI 服務發生錯誤: {e}"
 
 
1
+ # ai_service.py (Definitive fix for the ImportError)
2
  import json
3
+ from datetime import datetime
4
  import google.generativeai as genai
5
  from gradio_client import Client
6
 
7
+ # [修正] 移除 'Part' 的 import,因為它導致了錯誤
8
+ # from google.generativeai.types import Part
9
 
10
  # 從設定檔匯入金鑰和 URL
11
  from config import GEMINI_API_KEY, MCP_SERVER_URL
12
 
13
+ # --- 1. 設定 Gemini API 金鑰 (一次性設定) ---
 
14
  if GEMINI_API_KEY and "YOUR_GEMINI_API_KEY" not in GEMINI_API_KEY:
15
  genai.configure(api_key=GEMINI_API_KEY)
16
 
17
+ # --- 2. 工具函式 (用於地震查詢) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  def call_mcp_earthquake_search(
19
  start_date: str,
20
  end_date: str,
 
23
  ) -> str:
24
  """根據指定的條件(時間、規模)從遠端伺服器搜尋地震事件。"""
25
  try:
 
 
 
 
26
  print(f"--- 正在呼叫遠端地震 MCP 伺服器 (由 Gemini 觸發) ---")
27
+ print(f" 查詢條件: {start_date} {end_date}, 規模 {min_magnitude} 以上")
 
28
 
29
  client = Client(src=MCP_SERVER_URL)
30
  result = client.predict(
31
+ param_0=start_date, param_1="00:00:00",
32
+ param_2=end_date, param_3="23:59:59",
33
  param_4=21.0, param_5=26.0, # 預設台灣緯度
34
  param_6=119.0, param_7=123.0, # 預設台灣經度
35
  param_8=0.0, param_9=100.0,
 
51
  print(f"呼叫 MCP 伺服器失敗: {e}")
52
  return f"工具執行失敗,錯誤訊息: {e}"
53
 
54
+ # --- 3. 向 Gemini 定義工具 ---
55
  earthquake_search_tool_declaration = {
56
  "name": "call_earthquake_search_tool",
57
  "description": "根據指定的條件(時間、地點、規模等)從台灣中央氣象署的資料庫中搜尋地震事件。預設搜尋台灣周邊地區。",
58
  "parameters": {
59
  "type": "OBJECT", "properties": {
60
+ "start_date": {"type": "STRING", "description": "搜尋的開始日期 (格式 'YYYY-MM-DD')。模型應根據使用者問題推斷此日期,例如從『去年』或『2024年』推斷出 '2024-01-01'。"},
61
+ "end_date": {"type": "STRING", "description": "搜尋的結束日期 (格式 'YYYY-MM-DD')。模型應根據使用者問題推斷此日期,例如從『昨天』或『2024年』推斷出 '2024-12-31'。"},
62
  "min_magnitude": {"type": "NUMBER", "description": "要搜尋的最小地震規模。如果使用者未指定,請使用預設值 4.5。"},
63
  "max_magnitude": {"type": "NUMBER", "description": "要搜尋的最大地震規模。預設為 8.0。"},
64
  }, "required": ["start_date", "end_date"]
 
67
 
68
  available_tools = {"call_earthquake_search_tool": call_mcp_earthquake_search}
69
 
70
+ # --- 4. 建立 Gemini 模型 ---
71
  model = None
72
  if GEMINI_API_KEY and "YOUR_GEMINI_API_KEY" not in GEMINI_API_KEY:
73
  try:
74
  system_instruction = (
75
+ "You are a helpful AI assistant. You must answer in Traditional Chinese."
76
+ "You have access to tools. When a tool returns data in JSON format, "
77
+ "you must analyze the JSON data to fully answer the user's question. "
78
+ "For example, if the user asks for the largest earthquake, use the search tool for the relevant date range "
79
+ "and then find the entry with the highest magnitude from the JSON results before answering."
80
  )
81
  model = genai.GenerativeModel(
82
  model_name="gemini-1.5-flash",
 
86
  except Exception as e:
87
  print(f"建立 Gemini 模型失敗: {e}")
88
 
89
+ # --- 5. 主要的 AI 文字生成函式 ---
90
  def generate_ai_text(user_prompt: str) -> str:
91
  if not model:
92
  return "🤖 AI (Gemini) 服務尚未設定 API 金鑰,或金鑰無效。"
 
94
  print(f"--- 開始 Gemini 對話,使用者輸入: '{user_prompt}' ---")
95
  chat = model.start_chat()
96
  response = chat.send_message(user_prompt)
 
 
97
  try:
98
  function_call = response.candidates[0].content.parts[0].function_call
99
  except (IndexError, AttributeError):
100
  function_call = None
 
 
101
  if not function_call:
102
  print("--- Gemini 直接回覆文字 ---")
103
  return response.text
 
107
  if not tool_function:
108
  return f"錯誤:模型嘗試呼叫一個不存在的工具 '{function_call.name}'。"
109
 
 
110
  tool_result = tool_function(**dict(function_call.args))
111
  print("--- 將工具結果回傳給 Gemini ---")
112
 
113
+ # [修正] 直接傳送包含 function_response 的字典,不再使用 Part 類別
114
  response = chat.send_message(
115
+ {"function_response": {"name": function_call.name, "response": {"result": tool_result}}}
 
 
 
 
 
116
  )
117
 
118
  print("--- Gemini 根據工具結果生成最終回覆 ---")
119
  return response.text
120
  except Exception as e:
121
  print(f"與 Gemini AI 互動時發生錯誤: {e}")
122
+ return f"🤖 AI 服務發生錯誤: {e}"
123
+