@@ -86,6 +86,16 @@ async def set_system_instructions(self, system_prompt: str, check_client_disconn
8686 await sys_prompt_textarea .fill (system_prompt , timeout = 5000 )
8787 await expect_async (sys_prompt_textarea ).to_have_value (system_prompt , timeout = 5000 )
8888 self .logger .info (f'[{ self .req_id } ] 系统指令已成功填充并验证。' )
89+ for close_attempt in range (1 , 4 ):
90+ try :
91+ await self .page .keyboard .press ("Escape" )
92+ await asyncio .sleep (0.2 )
93+ if not await sys_prompt_textarea .is_visible ():
94+ self .logger .info (f'[{ self .req_id } ] ✅ 系统指令面板已关闭。' )
95+ break
96+ self .logger .warning (f"[{ self .req_id } ] 系统指令面板关闭验证失败 (嘗試 { close_attempt } )" )
97+ except Exception :
98+ pass
8999 except Exception as e :
90100 self .logger .error (f'[{ self .req_id } ] 设置系统指令时出错: { e } ' )
91101 if isinstance (e , ClientDisconnectedError ):
@@ -136,10 +146,6 @@ def _is_gemini3_pro_series(self, model_id: Optional[str]) -> bool:
136146 mid = (model_id or "" ).lower ()
137147 return ("gemini-3" in mid ) and ("pro" in mid )
138148
139- def _has_main_reasoning_switch (self , model_id : Optional [str ]) -> bool :
140- """判斷模型是否擁有主開關(Flash 系列)"""
141- mid = (model_id or "" ).lower ()
142- return "flash" in mid
143149
144150 async def _check_level_dropdown_exists (self ) -> bool :
145151 """檢查等級下拉選單是否存在"""
@@ -150,12 +156,11 @@ async def _check_level_dropdown_exists(self) -> bool:
150156 return False
151157
152158 def _determine_level_from_effort (self , reasoning_effort : Any ) -> Optional [str ]:
153- """根據 reasoning_effort 決定等級(high/low)"""
154159 if isinstance (reasoning_effort , str ):
155160 rs = reasoning_effort .strip ().lower ()
156161 if rs == "low" :
157162 return "low"
158- if rs in ["high" , "none" , "-1" ]:
163+ if rs in ["high" , "medium" , " none" , "-1" ]:
159164 return "high"
160165 try :
161166 return "high" if int (rs ) >= 8000 else "low"
@@ -177,7 +182,6 @@ def _apply_model_budget_cap(self, value: int, model_id: Optional[str]) -> int:
177182 return value
178183
179184 async def _select_thinking_level (self , level : str , check_client_disconnected : Callable ):
180- """設定推理等級(Gemini 3 Pro 專用),包含重試邏輯"""
181185 target_selector = (
182186 THINKING_LEVEL_OPTION_HIGH_SELECTOR if level == "high"
183187 else THINKING_LEVEL_OPTION_LOW_SELECTOR
@@ -202,40 +206,51 @@ async def _select_thinking_level(self, level: str, check_client_disconnected: Ca
202206 pass
203207 raise Exception (f"等級選項 { level } 不存在" )
204208 await click_element (self .page , option , f"Thinking Level { level } " , self .req_id )
205- await asyncio .sleep (0.2 )
206- self .logger .info (f"[{ self .req_id } ] ✓ 推理等級已設定為 { level } " )
207- return
209+ await asyncio .sleep (0.3 )
210+ current_text = await trigger .inner_text (timeout = 2000 )
211+ if level .lower () in current_text .lower ():
212+ self .logger .info (f"[{ self .req_id } ] ✓ 推理等級已設定為 { level } " )
213+ return
214+ self .logger .warning (f"[{ self .req_id } ] 等級驗證失敗 (嘗試 { attempt } ): 當前顯示 '{ current_text } '" )
215+ if attempt < max_retries :
216+ await asyncio .sleep (0.3 )
208217 except Exception as e :
209218 if isinstance (e , ClientDisconnectedError ):
210219 raise
211220 self .logger .warning (f"[{ self .req_id } ] 設定等級失敗 (嘗試 { attempt } ): { e } " )
212221 if attempt < max_retries :
213222 await asyncio .sleep (0.5 )
214- else :
215- raise
223+ self . logger . error ( f"[ { self . req_id } ] ❌ 推理等級設定失敗,已重試 { max_retries } 次" )
224+ raise Exception ( f"推理等級 { level } 設定失敗" )
216225
217226 async def _set_budget_value (self , token_budget : int , check_client_disconnected : Callable ):
218- """設定預算數值"""
219227 budget_input = self .page .locator (THINKING_BUDGET_INPUT_SELECTOR )
220- try :
221- await expect_async (budget_input ).to_be_visible (timeout = 5000 )
222- await self ._check_disconnect (check_client_disconnected , '預算輸入框可見後' )
223- self .logger .info (f"[{ self .req_id } ] 設定推理預算為: { token_budget } tokens" )
224- await budget_input .fill (str (token_budget ), timeout = 5000 )
225- await self ._check_disconnect (check_client_disconnected , '預算填充後' )
226- await asyncio .sleep (0.1 )
227- actual_val = await budget_input .input_value (timeout = 3000 )
228- if int (actual_val ) == token_budget :
229- self .logger .info (f"[{ self .req_id } ] ✓ 預算已更新為 { actual_val } " )
230- else :
231- self .logger .warning (f"[{ self .req_id } ] 預算驗證失敗,實際: { actual_val } ,預期: { token_budget } " )
232- except Exception as e :
233- self .logger .error (f"[{ self .req_id } ] 設定預算時發生錯誤: { e } " )
234- if isinstance (e , ClientDisconnectedError ):
235- raise
228+ max_retries = 3
229+ for attempt in range (1 , max_retries + 1 ):
230+ try :
231+ self .logger .info (f"[{ self .req_id } ] (嘗試 { attempt } /{ max_retries } ) 設定推理預算為: { token_budget } tokens" )
232+ await expect_async (budget_input ).to_be_visible (timeout = 5000 )
233+ await self ._check_disconnect (check_client_disconnected , '預算輸入框可見後' )
234+ await budget_input .fill (str (token_budget ), timeout = 5000 )
235+ await self ._check_disconnect (check_client_disconnected , '預算填充後' )
236+ await asyncio .sleep (0.2 )
237+ actual_val = await budget_input .input_value (timeout = 3000 )
238+ if int (actual_val ) == token_budget :
239+ self .logger .info (f"[{ self .req_id } ] ✓ 預算已更新為 { actual_val } " )
240+ return True
241+ self .logger .warning (f"[{ self .req_id } ] 預算驗證失敗 (嘗試 { attempt } ): 實際 { actual_val } , 預期 { token_budget } " )
242+ if attempt < max_retries :
243+ await asyncio .sleep (0.3 )
244+ except Exception as e :
245+ if isinstance (e , ClientDisconnectedError ):
246+ raise
247+ self .logger .warning (f"[{ self .req_id } ] 設定預算失敗 (嘗試 { attempt } ): { e } " )
248+ if attempt < max_retries :
249+ await asyncio .sleep (0.3 )
250+ self .logger .error (f"[{ self .req_id } ] ❌ 預算設定失敗,已重試 { max_retries } 次" )
251+ return False
236252
237253 async def _handle_thinking_budget (self , request_params : Dict [str , Any ], model_id_to_use : Optional [str ], check_client_disconnected : Callable ):
238- """處理推理模式與預算的完整邏輯"""
239254 reasoning_effort = request_params .get ('reasoning_effort' )
240255 cfg = parse_reasoning_param (reasoning_effort )
241256 self .logger .info (f"[{ self .req_id } ] 推理配置: { describe_config (cfg )} " )
@@ -245,12 +260,9 @@ async def _handle_thinking_budget(self, request_params: Dict[str, Any], model_id
245260 return
246261
247262 try :
248- uses_level = self ._is_gemini3_pro_series (model_id_to_use ) and await self ._check_level_dropdown_exists ()
249- has_switch = self ._has_main_reasoning_switch (model_id_to_use )
263+ await self ._control_thinking_mode_toggle (should_be_checked = True , check_client_disconnected = check_client_disconnected )
250264
251- if has_switch :
252- self .logger .info (f"[{ self .req_id } ] 控制主開關: 啟用" )
253- await self ._control_thinking_mode_toggle (should_be_checked = True , check_client_disconnected = check_client_disconnected )
265+ uses_level = self ._is_gemini3_pro_series (model_id_to_use ) and await self ._check_level_dropdown_exists ()
254266
255267 if uses_level :
256268 level = self ._determine_level_from_effort (reasoning_effort ) or DEFAULT_THINKING_LEVEL
@@ -332,41 +344,63 @@ async def _adjust_google_search(self, request_params: Dict[str, Any], check_clie
332344 self .logger .error (f"[{ self .req_id } ] ❌ Google Search 設定失敗,已重試 { max_retries } 次" )
333345
334346 async def _ensure_tools_panel_expanded (self , check_client_disconnected : Callable ):
335- try :
336- collapse_tools_locator = self .page .locator ('button[aria-label="Expand or collapse tools"]' )
337- await expect_async (collapse_tools_locator ).to_be_visible (timeout = 5000 )
338- grandparent_locator = collapse_tools_locator .locator ('xpath=../..' )
339- class_string = await grandparent_locator .get_attribute ('class' , timeout = 3000 )
340- if class_string and 'expanded' not in class_string .split ():
341- self .logger .info (f'[{ self .req_id } ] 🔧 正在展开工具面板...' )
347+ max_retries = 3
348+ for attempt in range (1 , max_retries + 1 ):
349+ try :
350+ collapse_tools_locator = self .page .locator ('button[aria-label="Expand or collapse tools"]' )
351+ await expect_async (collapse_tools_locator ).to_be_visible (timeout = 5000 )
352+ grandparent_locator = collapse_tools_locator .locator ('xpath=../..' )
353+ class_string = await grandparent_locator .get_attribute ('class' , timeout = 3000 )
354+ if class_string and 'expanded' in class_string .split ():
355+ self .logger .info (f'[{ self .req_id } ] ✅ 工具面板已展开。' )
356+ return
357+ self .logger .info (f'[{ self .req_id } ] 🔧 (嘗試 { attempt } /{ max_retries } ) 正在展开工具面板...' )
342358 await click_element (self .page , collapse_tools_locator , 'Expand/Collapse Tools Button' , self .req_id )
343359 await self ._check_disconnect (check_client_disconnected , '展开工具面板后' )
344- await expect_async (grandparent_locator ).to_have_class (re .compile ('.*expanded.*' ), timeout = 5000 )
345- self .logger .info (f'[{ self .req_id } ] ✅ 工具面板已展开。' )
346- else :
347- self .logger .info (f'[{ self .req_id } ] ✅ 工具面板已展开。' )
348- except Exception as e :
349- self .logger .error (f'[{ self .req_id } ] 展开工具面板时发生错误: { e } ' )
350- if isinstance (e , ClientDisconnectedError ):
351- raise
360+ await asyncio .sleep (0.3 )
361+ new_class = await grandparent_locator .get_attribute ('class' , timeout = 3000 )
362+ if new_class and 'expanded' in new_class .split ():
363+ self .logger .info (f'[{ self .req_id } ] ✅ 工具面板已展开。' )
364+ return
365+ self .logger .warning (f"[{ self .req_id } ] 工具面板展开验证失败 (嘗試 { attempt } )" )
366+ if attempt < max_retries :
367+ await asyncio .sleep (0.3 )
368+ except Exception as e :
369+ if isinstance (e , ClientDisconnectedError ):
370+ raise
371+ self .logger .warning (f'[{ self .req_id } ] 展开工具面板失败 (嘗試 { attempt } ): { e } ' )
372+ if attempt < max_retries :
373+ await asyncio .sleep (0.3 )
374+ self .logger .error (f'[{ self .req_id } ] ❌ 工具面板展开失败,已重试 { max_retries } 次' )
352375
353376 async def _open_url_content (self , check_client_disconnected : Callable ):
354- try :
355- self .logger .info (f'[{ self .req_id } ] 检查并启用 URL Context 开关...' )
356- use_url_content_selector = self .page .locator (USE_URL_CONTEXT_SELECTOR )
357- await expect_async (use_url_content_selector ).to_be_visible (timeout = 5000 )
358- is_checked = await use_url_content_selector .get_attribute ('aria-checked' )
359- if 'false' == is_checked :
360- self .logger .info (f'[{ self .req_id } ] URL Context 开关未开启,正在点击以开启...' )
377+ max_retries = 3
378+ for attempt in range (1 , max_retries + 1 ):
379+ try :
380+ self .logger .info (f'[{ self .req_id } ] (嘗試 { attempt } /{ max_retries } ) 检查并启用 URL Context 开关...' )
381+ use_url_content_selector = self .page .locator (USE_URL_CONTEXT_SELECTOR )
382+ await expect_async (use_url_content_selector ).to_be_visible (timeout = 5000 )
383+ is_checked = await use_url_content_selector .get_attribute ('aria-checked' )
384+ if is_checked == 'true' :
385+ self .logger .info (f'[{ self .req_id } ] ✅ URL Context 开关已处于开启状态。' )
386+ return
361387 await click_element (self .page , use_url_content_selector , 'URL Context Toggle' , self .req_id )
362388 await self ._check_disconnect (check_client_disconnected , '点击URLCONTEXT后' )
363- self .logger .info (f'[{ self .req_id } ] URL Context 开关已点击。' )
364- else :
365- self .logger .info (f'[{ self .req_id } ] URL Context 开关已处于开启状态。' )
366- except Exception as e :
367- self .logger .error (f'[{ self .req_id } ] 操作 USE_URL_CONTEXT_SELECTOR 时发生错误:{ e } 。' )
368- if isinstance (e , ClientDisconnectedError ):
369- raise
389+ await asyncio .sleep (0.3 )
390+ new_state = await use_url_content_selector .get_attribute ('aria-checked' )
391+ if new_state == 'true' :
392+ self .logger .info (f'[{ self .req_id } ] ✅ URL Context 开关已开启。' )
393+ return
394+ self .logger .warning (f"[{ self .req_id } ] URL Context 验证失败 (嘗試 { attempt } ): '{ new_state } '" )
395+ if attempt < max_retries :
396+ await asyncio .sleep (0.3 )
397+ except Exception as e :
398+ if isinstance (e , ClientDisconnectedError ):
399+ raise
400+ self .logger .warning (f'[{ self .req_id } ] URL Context 操作失败 (嘗試 { attempt } ): { e } ' )
401+ if attempt < max_retries :
402+ await asyncio .sleep (0.3 )
403+ self .logger .error (f'[{ self .req_id } ] ❌ URL Context 设定失败,已重试 { max_retries } 次' )
370404
371405 async def _control_thinking_budget_toggle (self , should_be_checked : bool , check_client_disconnected : Callable ) -> bool :
372406 toggle_selector = SET_THINKING_BUDGET_TOGGLE_SELECTOR
0 commit comments