Skip to content

Commit 400e321

Browse files
committed
feat: 添加思考预算和Google搜索功能的默认配置选项
1 parent faa9e21 commit 400e321

3 files changed

Lines changed: 128 additions & 92 deletions

File tree

.env.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ DEFAULT_STOP_SEQUENCES=["用户:"]
8888
# 是否在处理请求时自动打开并使用 "URL Context" 功能,此工具功能详情可参考:https://ai.google.dev/gemini-api/docs/url-context
8989
ENABLE_URL_CONTEXT=true
9090

91+
# 是否默认启用 "指定思考预算" 功能 (true/false),不启用时模型一般将自行决定思考预算
92+
# 当 API 请求中未提供 reasoning_effort 参数时将使用此值。
93+
ENABLE_THINKING_BUDGET=false
94+
95+
# "指定思考预算量" 的默认值 (token)
96+
# 当 API 请求中未提供 reasoning_effort 参数时,将使用此值。
97+
DEFAULT_THINKING_BUDGET=8192
98+
99+
# 是否默认启用 "Google Search" 功能 (true/false)
100+
# 当 API 请求中未提供 tools 参数时,将使用此设置作为 Google Search 的默认开关状态。
101+
ENABLE_GOOGLE_SEARCH=false
102+
91103
# =============================================================================
92104
# 超时配置 (毫秒)
93105
# =============================================================================

browser_utils/page_controller.py

Lines changed: 113 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from config import (
2020
CLICK_TIMEOUT_MS, WAIT_FOR_ELEMENT_TIMEOUT_MS, CLEAR_CHAT_VERIFY_TIMEOUT_MS,
2121
DEFAULT_TEMPERATURE, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_STOP_SEQUENCES, DEFAULT_TOP_P,
22-
ENABLE_URL_CONTEXT
22+
ENABLE_URL_CONTEXT, ENABLE_THINKING_BUDGET, DEFAULT_THINKING_BUDGET, ENABLE_GOOGLE_SEARCH
2323
)
2424
from models import ClientDisconnectedError
2525
from .operations import save_error_snapshot, _wait_for_response_completion, _get_final_response_content
@@ -68,34 +68,65 @@ async def adjust_parameters(self, request_params: Dict[str, Any], page_params_ca
6868
else:
6969
self.logger.info(f"[{self.req_id}] URL Context 功能已禁用,跳过调整。")
7070

71-
# 调整“思考预算”开关
72-
await self._set_thinking_budget_toggle_checked(check_client_disconnected)
73-
74-
# 在函数末尾,await self._set_thinking_budget_toggle_checked(...) 之后添加:
75-
self.logger.info(f"[{self.req_id}] 检查预算...{request_params.get('reasoning_effort')}")
76-
if 'reasoning_effort' in request_params:
77-
await self._adjust_thinking_budget(request_params.get('reasoning_effort'), check_client_disconnected)
71+
# 调整“思考预算”
72+
await self._handle_thinking_budget(request_params, check_client_disconnected)
7873

7974
# 调整 Google Search 开关
8075
await self._adjust_google_search(request_params, check_client_disconnected)
8176

82-
async def _adjust_thinking_budget(self, reasoning_effort: Optional[str], check_client_disconnected: Callable):
77+
async def _handle_thinking_budget(self, request_params: Dict[str, Any], check_client_disconnected: Callable):
78+
"""处理思考预算的调整逻辑。"""
79+
reasoning_effort = request_params.get('reasoning_effort')
80+
if reasoning_effort is not None:
81+
# 用户指定了,则开启并设置
82+
self.logger.info(f"[{self.req_id}] 用户指定了 reasoning_effort: {reasoning_effort}。")
83+
await self._control_thinking_budget_toggle(should_be_checked=True, check_client_disconnected=check_client_disconnected)
84+
await self._adjust_thinking_budget(reasoning_effort, check_client_disconnected)
85+
else:
86+
# 用户未指定,根据默认配置
87+
self.logger.info(f"[{self.req_id}] 用户未指定 reasoning_effort,根据默认配置 ENABLE_THINKING_BUDGET: {ENABLE_THINKING_BUDGET}。")
88+
await self._control_thinking_budget_toggle(should_be_checked=ENABLE_THINKING_BUDGET, check_client_disconnected=check_client_disconnected)
89+
if ENABLE_THINKING_BUDGET:
90+
# 如果默认开启,则使用默认值
91+
await self._adjust_thinking_budget(None, check_client_disconnected)
92+
93+
def _parse_thinking_budget(self, reasoning_effort: Optional[Any]) -> Optional[int]:
94+
"""从 reasoning_effort 解析出 token_budget。"""
95+
token_budget = None
96+
if reasoning_effort is None:
97+
token_budget = DEFAULT_THINKING_BUDGET
98+
self.logger.info(f"[{self.req_id}] 'reasoning_effort' 为空,使用默认思考预算: {token_budget}")
99+
elif isinstance(reasoning_effort, int):
100+
token_budget = reasoning_effort
101+
elif isinstance(reasoning_effort, str):
102+
if reasoning_effort.lower() == 'none':
103+
token_budget = DEFAULT_THINKING_BUDGET
104+
self.logger.info(f"[{self.req_id}] 'reasoning_effort' 为 'none' 字符串,使用默认思考预算: {token_budget}")
105+
else:
106+
effort_map = {
107+
"low": 1000,
108+
"medium": 8000,
109+
"high": 24000
110+
}
111+
token_budget = effort_map.get(reasoning_effort.lower())
112+
if token_budget is None:
113+
try:
114+
token_budget = int(reasoning_effort)
115+
except (ValueError, TypeError):
116+
pass # token_budget remains None
117+
118+
if token_budget is None:
119+
self.logger.warning(f"[{self.req_id}] 无法从 '{reasoning_effort}' (类型: {type(reasoning_effort)}) 解析出有效的 token_budget。")
120+
121+
return token_budget
122+
123+
async def _adjust_thinking_budget(self, reasoning_effort: Optional[Any], check_client_disconnected: Callable):
83124
"""根据 reasoning_effort 调整思考预算。"""
84-
self.logger.info(f"[{self.req_id}] 检查并调整思考预算...")
125+
self.logger.info(f"[{self.req_id}] 检查并调整思考预算,输入值: {reasoning_effort}")
85126

86-
token_budget = None
87-
if reasoning_effort is None or reasoning_effort.lower() == 'none':
88-
token_budget = 8192
89-
self.logger.info(f"[{self.req_id}] 'reasoning_effort' 为空或 'none',使用默认思考预算: {token_budget}")
90-
else:
91-
effort_map = {
92-
"low": 1000,
93-
"medium": 8000,
94-
"high": 24000
95-
}
96-
token_budget = effort_map.get(reasoning_effort.lower())
97-
98-
if not token_budget:
127+
token_budget = self._parse_thinking_budget(reasoning_effort)
128+
129+
if token_budget is None:
99130
self.logger.warning(f"[{self.req_id}] 无效的 reasoning_effort 值: '{reasoning_effort}'。跳过调整。")
100131
return
101132

@@ -122,18 +153,32 @@ async def _adjust_thinking_budget(self, reasoning_effort: Optional[str], check_c
122153
if isinstance(e, ClientDisconnectedError):
123154
raise
124155

156+
def _should_enable_google_search(self, request_params: Dict[str, Any]) -> bool:
157+
"""根据请求参数或默认配置决定是否应启用 Google Search。"""
158+
if 'tools' in request_params and request_params.get('tools') is not None:
159+
tools = request_params.get('tools')
160+
has_google_search_tool = False
161+
if isinstance(tools, list):
162+
for tool in tools:
163+
if isinstance(tool, dict):
164+
if tool.get('google_search_retrieval') is not None:
165+
has_google_search_tool = True
166+
break
167+
if tool.get('function', {}).get('name') == 'googleSearch':
168+
has_google_search_tool = True
169+
break
170+
self.logger.info(f"[{self.req_id}] 请求中包含 'tools' 参数。检测到 Google Search 工具: {has_google_search_tool}。")
171+
return has_google_search_tool
172+
else:
173+
self.logger.info(f"[{self.req_id}] 请求中不包含 'tools' 参数。使用默认配置 ENABLE_GOOGLE_SEARCH: {ENABLE_GOOGLE_SEARCH}。")
174+
return ENABLE_GOOGLE_SEARCH
175+
125176
async def _adjust_google_search(self, request_params: Dict[str, Any], check_client_disconnected: Callable):
126-
"""根据请求参数中的 'googleSearch' 工具存在与否,双向控制 Google Search 开关。"""
177+
"""根据请求参数或默认配置,双向控制 Google Search 开关。"""
127178
self.logger.info(f"[{self.req_id}] 检查并调整 Google Search 开关...")
128179

129-
tools = request_params.get('tools')
130-
has_google_search = False
131-
if isinstance(tools, list):
132-
for tool in tools:
133-
if isinstance(tool, dict) and tool.get('function', {}).get('name') == 'googleSearch':
134-
has_google_search = True
135-
break
136-
180+
should_enable_search = self._should_enable_google_search(request_params)
181+
137182
toggle_selector = GROUNDING_WITH_GOOGLE_SEARCH_TOGGLE_SELECTOR
138183

139184
try:
@@ -142,39 +187,25 @@ async def _adjust_google_search(self, request_params: Dict[str, Any], check_clie
142187
await self._check_disconnect(check_client_disconnected, "Google Search 开关 - 元素可见后")
143188

144189
is_checked_str = await toggle_locator.get_attribute("aria-checked")
145-
self.logger.info(f"[{self.req_id}] Google Search 开关 'aria-checked' 状态: '{is_checked_str}'。是否需要搜索: {has_google_search}")
146-
147-
if has_google_search:
148-
# 需要搜索,但开关是关闭的,则打开它
149-
if is_checked_str == "false":
150-
self.logger.info(f"[{self.req_id}] 'googleSearch' 工具存在,但开关关闭。正在点击以打开...")
151-
await toggle_locator.click(timeout=CLICK_TIMEOUT_MS)
152-
await self._check_disconnect(check_client_disconnected, "Google Search 开关 - 点击打开后")
153-
await asyncio.sleep(0.5) # 等待UI更新
154-
new_state = await toggle_locator.get_attribute("aria-checked")
155-
if new_state == "true":
156-
self.logger.info(f"[{self.req_id}] ✅ Google Search 开关已成功打开。")
157-
else:
158-
self.logger.warning(f"[{self.req_id}] ⚠️ Google Search 开关打开失败。当前状态: '{new_state}'")
190+
is_currently_checked = is_checked_str == "true"
191+
self.logger.info(f"[{self.req_id}] Google Search 开关当前状态: '{is_checked_str}'。期望状态: {should_enable_search}")
192+
193+
if should_enable_search != is_currently_checked:
194+
action = "打开" if should_enable_search else "关闭"
195+
self.logger.info(f"[{self.req_id}] Google Search 开关状态与期望不符。正在点击以{action}...")
196+
await toggle_locator.click(timeout=CLICK_TIMEOUT_MS)
197+
await self._check_disconnect(check_client_disconnected, f"Google Search 开关 - 点击{action}后")
198+
await asyncio.sleep(0.5) # 等待UI更新
199+
new_state = await toggle_locator.get_attribute("aria-checked")
200+
if (new_state == "true") == should_enable_search:
201+
self.logger.info(f"[{self.req_id}] ✅ Google Search 开关已成功{action}。")
159202
else:
160-
self.logger.info(f"[{self.req_id}] 'googleSearch' 工具存在,且开关已打开,无需操作。")
203+
self.logger.warning(f"[{self.req_id}] ⚠️ Google Search 开关{action}失败。当前状态: '{new_state}'")
161204
else:
162-
# 不需要搜索,但开关是打开的,则关闭它
163-
if is_checked_str == "true":
164-
self.logger.info(f"[{self.req_id}] 'googleSearch' 工具不存在,但开关打开。正在点击以关闭...")
165-
await toggle_locator.click(timeout=CLICK_TIMEOUT_MS)
166-
await self._check_disconnect(check_client_disconnected, "Google Search 开关 - 点击关闭后")
167-
await asyncio.sleep(0.5) # 等待UI更新
168-
new_state = await toggle_locator.get_attribute("aria-checked")
169-
if new_state == "false":
170-
self.logger.info(f"[{self.req_id}] ✅ Google Search 开关已成功关闭。")
171-
else:
172-
self.logger.warning(f"[{self.req_id}] ⚠️ Google Search 开关关闭失败。当前状态: '{new_state}'")
173-
else:
174-
self.logger.info(f"[{self.req_id}] 'googleSearch' 工具不存在,且开关已关闭,无需操作。")
205+
self.logger.info(f"[{self.req_id}] Google Search 开关已处于期望状态,无需操作。")
175206

176207
except Exception as e:
177-
self.logger.error(f"[{self.req_id}] ❌ 操作 'Google Search toggle' 开关 '{toggle_selector}' 时发生错误: {e}")
208+
self.logger.error(f"[{self.req_id}] ❌ 操作 'Google Search toggle' 开关时发生错误: {e}")
178209
if isinstance(e, ClientDisconnectedError):
179210
raise
180211

@@ -200,53 +231,43 @@ async def _open_url_content(self,check_client_disconnected: Callable):
200231
except Exception as e:
201232
self.logger.error(f"[{self.req_id}] ❌ 操作USE_URL_CONTEXT_SELECTOR时发生错误:{e}。")
202233

203-
async def _set_thinking_budget_toggle_checked(self, check_client_disconnected: Callable):
234+
async def _control_thinking_budget_toggle(self, should_be_checked: bool, check_client_disconnected: Callable):
204235
"""
205-
确保 "Thinking Budget" 滑块开关处于选中状态
236+
根据 should_be_checked 的值,控制 "Thinking Budget" 滑块开关的状态
206237
"""
207238
toggle_selector = SET_THINKING_BUDGET_TOGGLE_SELECTOR
208-
self.logger.info(f"[{self.req_id}] 检查并设置 'Thinking Budget toggle' 滑块开关 '{toggle_selector}' 的状态...")
239+
self.logger.info(f"[{self.req_id}] 控制 'Thinking Budget' 开关,期望状态: {'选中' if should_be_checked else '未选中'}...")
209240

210241
try:
211242
toggle_locator = self.page.locator(toggle_selector)
212-
213-
# 等待元素在 DOM 中可见
214243
await expect_async(toggle_locator).to_be_visible(timeout=5000)
215244
await self._check_disconnect(check_client_disconnected, "思考预算开关 - 元素可见后")
216245

217-
# 1. 检查当前 'aria-checked' 属性
218-
is_checked = await toggle_locator.get_attribute("aria-checked")
219-
self.logger.info(f"[{self.req_id}] 思考预算开关当前 'aria-checked' 状态: {is_checked}")
246+
is_checked_str = await toggle_locator.get_attribute("aria-checked")
247+
current_state_is_checked = is_checked_str == "true"
248+
self.logger.info(f"[{self.req_id}] 思考预算开关当前 'aria-checked' 状态: {is_checked_str} (当前是否选中: {current_state_is_checked})")
220249

221-
# 2. 如果开关未选中 (aria-checked="false"),则点击它
222-
if is_checked == "false":
223-
self.logger.info(f"[{self.req_id}] 思考预算开关当前未选中,正在点击以启用...")
250+
if current_state_is_checked != should_be_checked:
251+
action = "启用" if should_be_checked else "禁用"
252+
self.logger.info(f"[{self.req_id}] 思考预算开关当前状态与期望不符,正在点击以{action}...")
224253
await toggle_locator.click(timeout=CLICK_TIMEOUT_MS)
225-
await self._check_disconnect(check_client_disconnected, "思考预算开关 - 点击后")
226-
227-
# 3. 验证操作是否成功
228-
await asyncio.sleep(0.5) # 短暂等待,让 UI 状态更新
229-
new_state = await toggle_locator.get_attribute("aria-checked")
230-
if new_state == "true":
231-
self.logger.info(f"[{self.req_id}] ✅ 'Thinking Budget toggle' 已成功启用。新状态: {new_state}")
232-
else:
233-
self.logger.warning(f"[{self.req_id}] ⚠️ 'Thinking Budget toggle' 点击后验证失败。期望状态: 'true', 实际状态: '{new_state}'")
234-
# 如果需要,可以在此处添加错误快照
235-
# await save_error_snapshot(f"thinking_budget_toggle_verify_fail_{self.req_id}")
254+
await self._check_disconnect(check_client_disconnected, f"思考预算开关 - 点击{action}后")
255+
256+
await asyncio.sleep(0.5)
257+
new_state_str = await toggle_locator.get_attribute("aria-checked")
258+
new_state_is_checked = new_state_str == "true"
236259

237-
elif is_checked == "true":
238-
self.logger.info(f"[{self.req_id}] 'Thinking Budget toggle' 已处于选中状态,无需操作。")
260+
if new_state_is_checked == should_be_checked:
261+
self.logger.info(f"[{self.req_id}] ✅ 'Thinking Budget' 开关已成功{action}。新状态: {new_state_str}")
262+
else:
263+
self.logger.warning(f"[{self.req_id}] ⚠️ 'Thinking Budget' 开关{action}后验证失败。期望状态: '{should_be_checked}', 实际状态: '{new_state_str}'")
239264
else:
240-
# 处理 'aria-checked' 属性不存在或值无效的情况
241-
self.logger.warning(f"[{self.req_id}] 无法确定 'Thinking Budget toggle' 的状态,'aria-checked' 值为: '{is_checked}'。")
265+
self.logger.info(f"[{self.req_id}] 'Thinking Budget' 开关已处于期望状态,无需操作。")
242266

243267
except Exception as e:
244-
self.logger.error(f"[{self.req_id}] ❌ 操作 'Thinking Budget toggle' 滑块开关 '{toggle_selector}' 时发生错误: {e}")
245-
# 如果需要,可以在此处添加错误快照
246-
# await save_error_snapshot(f"thinking_budget_toggle_error_{self.req_id}")
247-
# 如果是客户端断开连接的特定错误,则重新抛出
268+
self.logger.error(f"[{self.req_id}] ❌ 操作 'Thinking Budget toggle' 开关时发生错误: {e}")
248269
if isinstance(e, ClientDisconnectedError):
249-
raise
270+
raise
250271
async def _adjust_temperature(self, temperature: float, page_params_cache: dict, params_cache_lock: asyncio.Lock, check_client_disconnected: Callable):
251272
"""调整温度参数。"""
252273
async with params_cache_lock:

config/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
DEFAULT_TOP_P = float(os.environ.get('DEFAULT_TOP_P', '0.95'))
2222
# --- 默认功能开关 ---
2323
ENABLE_URL_CONTEXT = os.environ.get('ENABLE_URL_CONTEXT', 'true').lower() in ('true', '1', 'yes')
24+
ENABLE_THINKING_BUDGET = os.environ.get('ENABLE_THINKING_BUDGET', 'false').lower() in ('true', '1', 'yes')
25+
DEFAULT_THINKING_BUDGET = int(os.environ.get('DEFAULT_THINKING_BUDGET', '8192'))
26+
ENABLE_GOOGLE_SEARCH = os.environ.get('ENABLE_GOOGLE_SEARCH', 'false').lower() in ('true', '1', 'yes')
2427

2528
# 默认停止序列 - 支持 JSON 格式配置
2629
try:

0 commit comments

Comments
 (0)