entari-plugin-hyw 4.0.0rc5__py3-none-any.whl → 4.0.0rc7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of entari-plugin-hyw might be problematic. Click here for more details.

@@ -6,6 +6,8 @@ Provides page fetching and screenshot capabilities using DrissionPage.
6
6
 
7
7
  import asyncio
8
8
  import base64
9
+ import threading
10
+ import time
9
11
  from concurrent.futures import ThreadPoolExecutor
10
12
  from typing import Optional, Dict, Any, List
11
13
  from loguru import logger
@@ -20,16 +22,182 @@ class ScreenshotService:
20
22
  self.headless = headless
21
23
  self._manager = None
22
24
  self._executor = ThreadPoolExecutor(max_workers=10)
25
+ self._search_tab_pool = [] # List of Tab objects
26
+ self._pool_lock = threading.Lock()
23
27
 
24
28
  if auto_start:
25
29
  self._ensure_ready()
30
+
31
+ def prepare_search_tabs_background(self, count: int, url: str = "https://www.google.com") -> None:
32
+ """
33
+ Pre-launch tabs for search (BACKGROUND - fire and forget).
34
+ Tabs are created in background thread, may not be ready immediately.
35
+ """
36
+ self._executor.submit(self._prepare_search_tabs_sync, count, url)
37
+
38
+ def _prepare_search_tabs_sync(self, count: int, url: str = "https://www.google.com"):
39
+ """Sync implementation of tab preparation - creates tabs in PARALLEL."""
40
+ try:
41
+ self._ensure_ready()
42
+ page = self._manager.page
43
+ if not page: return
44
+
45
+ with self._pool_lock:
46
+ current_count = len(self._search_tab_pool)
47
+ needed = count - current_count
48
+
49
+ if needed <= 0:
50
+ return
51
+
52
+ logger.info(f"ScreenshotService: Pre-launching {needed} search tabs for {url} (parallel)...")
53
+
54
+ # Create tabs in parallel using threads
55
+ created_tabs = [None] * needed
56
+
57
+ def create_single_tab(index):
58
+ try:
59
+ tab = page.new_tab(url)
60
+ created_tabs[index] = tab
61
+ logger.debug(f"ScreenshotService: Tab {index} ready")
62
+ except Exception as e:
63
+ logger.error(f"ScreenshotService: Failed to create tab {index}: {e}")
64
+
65
+ threads = []
66
+ for i in range(needed):
67
+ t = threading.Thread(target=create_single_tab, args=(i,))
68
+ t.start()
69
+ threads.append(t)
70
+
71
+ # Wait for all threads to complete
72
+ for t in threads:
73
+ t.join()
74
+
75
+ # Add successfully created tabs to pool
76
+ with self._pool_lock:
77
+ for tab in created_tabs:
78
+ if tab:
79
+ self._search_tab_pool.append(tab)
80
+ logger.info(f"ScreenshotService: Tab pool ready ({len(self._search_tab_pool)} tabs)")
81
+
82
+ except Exception as e:
83
+ logger.error(f"ScreenshotService: Failed to prepare tabs: {e}")
84
+
85
+ async def search_via_page_input_batch(self, queries: List[str], url: str, selector: str = "#input") -> List[Dict[str, Any]]:
86
+ """
87
+ Execute concurrent searches using page inputs.
88
+ """
89
+ loop = asyncio.get_running_loop()
90
+ return await loop.run_in_executor(
91
+ self._executor,
92
+ self._search_via_page_input_batch_sync,
93
+ queries, url, selector
94
+ )
95
+
96
+ def _search_via_page_input_batch_sync(self, queries: List[str], url: str, selector: str) -> List[Dict[str, Any]]:
97
+ """Sync batch execution - create tabs sequentially, search in parallel."""
98
+ results = [None] * len(queries)
99
+ tabs = []
100
+
101
+ # Phase 1: Get/create tabs SEQUENTIALLY (DrissionPage isn't thread-safe for new_tab)
102
+ target_url = url or "https://www.google.com"
103
+ logger.info(f"ScreenshotService: Acquiring {len(queries)} tabs for parallel search...")
104
+
105
+ for i in range(len(queries)):
106
+ tab = None
107
+ # Try to get from pool first
108
+ with self._pool_lock:
109
+ if self._search_tab_pool:
110
+ tab = self._search_tab_pool.pop(0)
111
+ logger.debug(f"ScreenshotService: Got tab {i} from pool")
112
+
113
+ if not tab:
114
+ # Create new
115
+ self._ensure_ready()
116
+ tab = self._manager.page.new_tab(target_url)
117
+ logger.debug(f"ScreenshotService: Created tab {i} for {target_url}")
118
+
119
+ tabs.append(tab)
120
+
121
+ logger.info(f"ScreenshotService: {len(tabs)} tabs ready, starting parallel searches...")
122
+
123
+ # Phase 2: Execute searches in PARALLEL
124
+ def run_search(index, tab, query):
125
+ try:
126
+ logger.debug(f"Search[{index}]: Starting for '{query}' on {tab.url}")
127
+
128
+ # Wait for page to be ready first
129
+ try:
130
+ tab.wait.doc_loaded(timeout=10)
131
+ except:
132
+ pass
133
+
134
+ # Find input element with wait
135
+ logger.debug(f"Search[{index}]: Looking for input with selector '{selector}'")
136
+ ele = tab.ele(selector, timeout=5)
137
+ if not ele:
138
+ logger.debug(f"Search[{index}]: Primary selector failed, trying fallbacks")
139
+ for fallback in ["textarea[name='q']", "#APjFqb", "input[name='q']", "input[type='text']"]:
140
+ ele = tab.ele(fallback, timeout=2)
141
+ if ele:
142
+ logger.debug(f"Search[{index}]: Found input with fallback '{fallback}'")
143
+ break
144
+
145
+ if not ele:
146
+ logger.error(f"Search[{index}]: No input element found on {tab.url}!")
147
+ results[index] = {"content": "Error: input not found", "title": "Error", "url": tab.url, "html": tab.html[:5000]}
148
+ return
149
+
150
+ logger.debug(f"Search[{index}]: Typing query...")
151
+ ele.input(query)
152
+
153
+ logger.debug(f"Search[{index}]: Pressing Enter...")
154
+ tab.actions.key_down('enter').key_up('enter')
155
+
156
+ logger.debug(f"Search[{index}]: Waiting for search results...")
157
+ tab.wait.doc_loaded(timeout=10)
158
+ time.sleep(0.5)
159
+
160
+ logger.debug(f"Search[{index}]: Extracting content...")
161
+ html = tab.html
162
+ content = trafilatura.extract(
163
+ html, include_links=True, include_images=True, include_comments=False,
164
+ include_tables=True, favor_precision=False, output_format="markdown"
165
+ ) or ""
166
+
167
+ logger.info(f"ScreenshotService: Search '{query}' completed -> {tab.url}")
168
+
169
+ results[index] = {
170
+ "content": content,
171
+ "html": html,
172
+ "title": tab.title,
173
+ "url": tab.url,
174
+ "images": []
175
+ }
176
+
177
+ except Exception as e:
178
+ logger.error(f"ScreenshotService: Search error for '{query}': {e}")
179
+ results[index] = {"content": f"Error: {e}", "title": "Error", "url": "", "html": ""}
180
+ finally:
181
+ try: tab.close()
182
+ except: pass
183
+
184
+ threads = []
185
+ for i, (tab, query) in enumerate(zip(tabs, queries)):
186
+ t = threading.Thread(target=run_search, args=(i, tab, query))
187
+ t.start()
188
+ threads.append(t)
189
+
190
+ for t in threads:
191
+ t.join()
192
+
193
+ return results
26
194
 
27
195
  def _ensure_ready(self):
28
196
  """Ensure shared browser is ready."""
29
197
  from .manager import get_shared_browser_manager
30
198
  self._manager = get_shared_browser_manager(headless=self.headless)
31
199
 
32
- async def fetch_page(self, url: str, timeout: float = 20.0, include_screenshot: bool = True) -> Dict[str, Any]:
200
+ async def fetch_page(self, url: str, timeout: float = 10.0, include_screenshot: bool = True) -> Dict[str, Any]:
33
201
  """
34
202
  Fetch page content (and optionally screenshot).
35
203
  Runs in a thread executor to avoid blocking the async loop.
@@ -43,6 +211,87 @@ class ScreenshotService:
43
211
  include_screenshot
44
212
  )
45
213
 
214
+ async def search_via_address_bar(self, query: str, timeout: float = 20.0) -> Dict[str, Any]:
215
+ """
216
+ Search using browser's address bar (uses browser's default search engine).
217
+ Simulates: Ctrl+L (focus address bar) -> type query -> Enter
218
+ """
219
+ loop = asyncio.get_running_loop()
220
+ return await loop.run_in_executor(
221
+ self._executor,
222
+ self._search_via_address_bar_sync,
223
+ query,
224
+ timeout
225
+ )
226
+
227
+ def _search_via_address_bar_sync(self, query: str, timeout: float) -> Dict[str, Any]:
228
+ """Synchronous address bar search logic."""
229
+ if not query:
230
+ return {"content": "Error: missing query", "title": "Error", "url": "", "html": ""}
231
+
232
+ tab = None
233
+ try:
234
+ self._ensure_ready()
235
+ page = self._manager.page
236
+ if not page:
237
+ return {"content": "Error: Browser not available", "title": "Error", "url": "", "html": ""}
238
+
239
+ # Open new blank tab
240
+ tab = page.new_tab()
241
+
242
+ # Focus address bar with Ctrl+L (or Cmd+L on Mac)
243
+ import platform
244
+ if platform.system() == "Darwin":
245
+ tab.actions.key_down('cmd').key_down('l').key_up('l').key_up('cmd')
246
+ else:
247
+ tab.actions.key_down('ctrl').key_down('l').key_up('l').key_up('ctrl')
248
+
249
+ # Small delay for address bar to focus
250
+ import time as _time
251
+ _time.sleep(0.1)
252
+
253
+ # Type the query
254
+ tab.actions.type(query)
255
+
256
+ # Press Enter to search
257
+ tab.actions.key_down('enter').key_up('enter')
258
+
259
+ # Wait for page to load
260
+ try:
261
+ tab.wait.doc_loaded(timeout=timeout)
262
+ # Additional wait for search results
263
+ _time.sleep(1)
264
+ except:
265
+ pass
266
+
267
+ html = tab.html
268
+ title = tab.title
269
+ final_url = tab.url
270
+
271
+ # Extract content
272
+ content = trafilatura.extract(
273
+ html, include_links=True, include_images=True, include_comments=False,
274
+ include_tables=True, favor_precision=False, output_format="markdown"
275
+ ) or ""
276
+
277
+ logger.info(f"ScreenshotService: Address bar search completed -> {final_url}")
278
+
279
+ return {
280
+ "content": content,
281
+ "html": html,
282
+ "title": title,
283
+ "url": final_url,
284
+ "images": []
285
+ }
286
+
287
+ except Exception as e:
288
+ logger.error(f"ScreenshotService: Address bar search failed: {e}")
289
+ return {"content": f"Error: search failed ({e})", "title": "Error", "url": "", "html": ""}
290
+ finally:
291
+ if tab:
292
+ try: tab.close()
293
+ except: pass
294
+
46
295
  def _fetch_page_sync(self, url: str, timeout: float, include_screenshot: bool) -> Dict[str, Any]:
47
296
  """Synchronous fetch logic."""
48
297
  if not url:
@@ -55,39 +304,31 @@ class ScreenshotService:
55
304
  if not page:
56
305
  return {"content": "Error: Browser not available", "title": "Error", "url": url}
57
306
 
58
- # New Tab
307
+ # New Tab with URL directly
59
308
  tab = page.new_tab(url)
60
309
 
61
- # Wait logic
310
+ # Wait logic - optimized for search pages
62
311
  is_search_page = any(s in url.lower() for s in ['search', 'bing.com', 'duckduckgo', 'google.com/search', 'searx'])
63
312
  if is_search_page:
64
- # Quick check for results
65
- result_selectors = ['#results', '#b_results', '#search', '#links', '.result']
66
- for selector in result_selectors:
67
- if tab.ele(selector, timeout=1):
68
- break
313
+ # Optimized waiting for search engine results
314
+ try:
315
+ # Google uses #search or #rso
316
+ # DuckDuckGo uses #react-layout
317
+ # Bing uses #b_results
318
+ if 'google' in url.lower():
319
+ # Wait for results container (fastest possible return)
320
+ tab.ele('#search', timeout=timeout)
321
+ elif 'bing' in url.lower():
322
+ tab.ele('#b_results', timeout=timeout)
323
+ else:
324
+ # Generic search fallback
325
+ tab.wait.doc_loaded(timeout=timeout)
326
+ except:
327
+ pass
69
328
  else:
70
329
  # 1. Wait for document to settle (Fast Dynamic Wait)
71
330
  try:
72
- tab.wait.doc_loaded(timeout=5)
73
- # Brief check for loading overlays (fast skip if none)
74
- tab.run_js("""
75
- (async () => {
76
- const isVisible = (el) => !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
77
- for (let i = 0; i < 15; i++) {
78
- const indicators = Array.from(document.querySelectorAll('*')).filter(el => {
79
- try {
80
- const text = (el.textContent || '').toLowerCase();
81
- const id = (el.id || '').toLowerCase();
82
- const cls = (el.getAttribute('class') || '').toLowerCase();
83
- return (text.includes('loading') || id.includes('loading') || cls.includes('loading')) && isVisible(el);
84
- } catch(e) { return false; }
85
- });
86
- if (indicators.length === 0) break;
87
- await new Promise(r => setTimeout(r, 100));
88
- }
89
- })()
90
- """, as_expr=True)
331
+ tab.wait.doc_loaded(timeout=timeout)
91
332
  except: pass
92
333
 
93
334
  html = tab.html
@@ -124,42 +365,71 @@ class ScreenshotService:
124
365
  ) or ""
125
366
 
126
367
  # 2. Extract Images via Parallelized JS (Gallery)
368
+ # Strategy: For search pages, use Canvas to grab already loaded images (Instant)
369
+ # For other pages, use fetch (more robust for lazy load)
127
370
  images_b64 = []
128
371
  try:
129
- images_b64 = tab.run_js("""
372
+ js_code = """
130
373
  (async () => {
131
374
  const blocklist = ['logo', 'icon', 'avatar', 'ad', 'pixel', 'tracker', 'button', 'menu', 'nav'];
132
375
  const candidates = Array.from(document.querySelectorAll('img'));
133
- const validCandidates = candidates.filter(img => {
134
- if (!img.src || img.src.startsWith('data:')) return false;
135
- if (img.naturalWidth < 200 || img.naturalHeight < 150) return false;
136
- const alt = (img.alt || '').toLowerCase();
137
- const cls = (typeof img.className === 'string' ? img.className : '').toLowerCase();
138
- const src = img.src.toLowerCase();
139
- if (blocklist.some(b => alt.includes(b) || cls.includes(b) || src.includes(b))) return false;
140
- return true;
141
- }).slice(0, 10);
142
-
143
- const fetchImage = async (url) => {
376
+ const validImages = [];
377
+
378
+ // Helper: Get base64 from loaded image via Canvas
379
+ const getBase64 = (img) => {
144
380
  try {
145
- const controller = new AbortController();
146
- const id = setTimeout(() => controller.abort(), 4000);
147
- const resp = await fetch(url, { signal: controller.signal });
148
- clearTimeout(id);
149
- const blob = await resp.blob();
150
- return new Promise(resolve => {
151
- const reader = new FileReader();
152
- reader.onloadend = () => resolve(reader.result.split(',')[1]);
153
- reader.onerror = () => resolve(null);
154
- reader.readAsDataURL(blob);
155
- });
381
+ const canvas = document.createElement('canvas');
382
+ canvas.width = img.naturalWidth;
383
+ canvas.height = img.naturalHeight;
384
+ const ctx = canvas.getContext('2d');
385
+ ctx.drawImage(img, 0, 0);
386
+ return canvas.toDataURL('image/jpeg').split(',')[1];
156
387
  } catch(e) { return null; }
157
388
  };
158
389
 
159
- const results = await Promise.all(validCandidates.map(img => fetchImage(img.src)));
160
- return results.filter(b64 => !!b64);
390
+ for (const img of candidates) {
391
+ if (validImages.length >= 8) break;
392
+
393
+ if (img.naturalWidth < 100 || img.naturalHeight < 80) continue;
394
+
395
+ const alt = (img.alt || '').toLowerCase();
396
+ const cls = (typeof img.className === 'string' ? img.className : '').toLowerCase();
397
+ const src = (img.src || '').toLowerCase();
398
+
399
+ if (blocklist.some(b => alt.includes(b) || cls.includes(b) || src.includes(b))) continue;
400
+
401
+ // 1. Try Canvas (Instant for loaded images)
402
+ if (img.complete && img.naturalHeight > 0) {
403
+ const b64 = getBase64(img);
404
+ if (b64) {
405
+ validImages.push(b64);
406
+ continue;
407
+ }
408
+ }
409
+
410
+ // 2. Fallback to fetch (only for non-search pages to avoid delay)
411
+ // We skip fetch for search pages to ensure speed
412
+ if (!window.location.href.includes('google') && !window.location.href.includes('search')) {
413
+ try {
414
+ const controller = new AbortController();
415
+ const id = setTimeout(() => controller.abort(), 2000);
416
+ const resp = await fetch(img.src, { signal: controller.signal });
417
+ clearTimeout(id);
418
+ const blob = await resp.blob();
419
+ const b64 = await new Promise(resolve => {
420
+ const reader = new FileReader();
421
+ reader.onloadend = () => resolve(reader.result.split(',')[1]);
422
+ reader.onerror = () => resolve(null);
423
+ reader.readAsDataURL(blob);
424
+ });
425
+ if (b64) validImages.push(b64);
426
+ } catch(e) {}
427
+ }
428
+ }
429
+ return validImages;
161
430
  })()
162
- """, as_expr=True) or []
431
+ """
432
+ images_b64 = tab.run_js(js_code, as_expr=True) or []
163
433
 
164
434
  if images_b64:
165
435
  logger.info(f"ScreenshotService: Extracted {len(images_b64)} images for {url}")
@@ -237,6 +237,8 @@ const instructStage = computed(() => {
237
237
  }
238
238
  })
239
239
 
240
+ const visionStage = computed(() => data.value?.stages?.find(s => s.name === 'Vision'))
241
+
240
242
  const summaryStage = computed(() => data.value?.stages?.find(s => s.name?.toLowerCase() === 'summary' || s.name?.toLowerCase() === 'agent'))
241
243
  // searchStage removed - no longer needed for display
242
244
 
@@ -654,7 +656,7 @@ The system automatically handles citations like [1] and [2], reordering them dyn
654
656
  </div>
655
657
 
656
658
  <!-- Flow: Unified Stage Info Area -->
657
- <div v-if="instructStage || summaryStage" class="relative group/flow">
659
+ <div v-if="instructStage || summaryStage || visionStage" class="relative group/flow">
658
660
  <!-- Corner Badge -->
659
661
  <div
660
662
  class="absolute -top-2 -left-2 h-7 px-2.5 z-10 flex items-center justify-center gap-1.5"
@@ -668,6 +670,35 @@ The system automatically handles citations like [1] and [2], reordering them dyn
668
670
  <div class="shadow-sm shadow-black/5 bg-white pt-8 px-6 pb-8">
669
671
  <div class="space-y-8 relative">
670
672
 
673
+ <!-- Vision Stage -->
674
+ <div v-if="visionStage" class="relative flex items-start gap-4 z-10 w-full">
675
+ <!-- Node: Brand Logo -->
676
+ <div class="shrink-0 w-6 h-6 flex items-center justify-center bg-white">
677
+ <img :src="getIconPath(visionStage)" class="w-5 h-5 object-contain" alt="" />
678
+ </div>
679
+ <!-- Content -->
680
+ <div class="flex-1 min-w-0 pt-1">
681
+ <div class="text-[17px] font-bold uppercase tracking-tight mb-1.5 leading-none" style="color: var(--text-primary)">Vision</div>
682
+ <div class="flex items-center justify-between gap-x-4 text-[13px] font-mono leading-tight w-full" style="color: var(--text-muted)">
683
+ <!-- Model Name (Truncated) -->
684
+ <span class="truncate max-w-[180px]" :title="visionStage.model">{{ visionStage.model }}</span>
685
+
686
+ <!-- Metrics -->
687
+ <div class="flex items-center gap-4 shrink-0">
688
+ <div class="flex items-center gap-1.5 opacity-80">
689
+ <Icon icon="mdi:clock-outline" class="text-[13px]" />
690
+ <span>{{ (visionStage.time || 0).toFixed(2) }}s</span>
691
+ </div>
692
+ <template v-if="visionStage.cost">
693
+ <div class="flex items-center gap-0.5 opacity-80">
694
+ <span>${{ visionStage.cost.toFixed(5) }}</span>
695
+ </div>
696
+ </template>
697
+ </div>
698
+ </div>
699
+ </div>
700
+ </div>
701
+
671
702
  <!-- Instruct Stage -->
672
703
  <div v-if="instructStage" class="relative flex items-start gap-4 z-10 w-full">
673
704
  <!-- Node: Brand Logo -->
@@ -11,10 +11,10 @@ from typing import Dict, Any
11
11
  # =============================================================================
12
12
 
13
13
  INSTRUCT_SP = """你是一个智能 INSTRUCT 助手。
14
- 你的目标是为用户的查询制定调研计划并执行。
14
+ 你的目标是立即, 为用户的查询制定调研计划并执行
15
15
  > 尽量完成所有搜索和获取, 需要的时候可以并发执行多个同种或不同种工具
16
16
 
17
- - 用户消息包含典型名词、可能的专有名词组合: 调用 `web_search` 工具(最大3)。
17
+ - 用户消息包含典型名词、可能的专有名词组合: 调用 `web_search` 工具(最大3, 推荐1)。
18
18
  - 提炼消息中出独立的关键词搜索关键词本身, 不添加任何其他辅助搜索词, 搜索效果最好
19
19
  - 不混合搜索词, 每次只针对搜索一个关键词, 获取其目的方向的最新数据
20
20
  - 用户消息中出现链接时, 调用 `crawl_page`(最大3) 获取网页全文
@@ -22,25 +22,31 @@ INSTRUCT_SP = """你是一个智能 INSTRUCT 助手。
22
22
  - 明显的鉴证、国家领导人、历史敏感事件隐喻
23
23
  - 过于露骨的r18+、r18g内容(但不包括 galgame等acg向, 除非其过于违规)
24
24
 
25
+ ## 内容审查
26
+ 以下情况调用 `refuse_answer`:
27
+ - 内容涉及隐喻政治事件任务
28
+ - r18+、r18g(但不包含正常galgame、科普等)
29
+
30
+ ## 决定流程
31
+ 调用 `set_mode` 决定使用 `fast` 还是 `deepsearch`:
32
+ - 只要用户不明确研究就用 `fast`
33
+ - 用户提到: 深度搜索、深度研究、deepsearch、整理关于...的资料, 这几种情况时, 选择 `deepsearch`
25
34
 
26
35
  ## 重要规则 (CRITICAL RULES):
27
36
  - 禁止输出任何文本回复:你必须且只能通过工具调用来行动。
28
- - 禁止回答用户的问题:你的任务仅仅是收集信息,回答将由后续阶段完成。
29
- - 禁止解释你的行为:不要输出思考过程或计划描述,直接调用工具。
30
37
  - 如果没有工具调用,流程将自动结束。
31
- """
32
38
 
33
- INSTRUCT_REVIEW_SP = """你是一个智能 INSTRUCT 审查助手, 你需要对 INSTRUCT 的输出进行二次信息补充或直接通过(不进行操作)
39
+ ## now
40
+ 请快速给出回复.
41
+ """
34
42
 
43
+ INSTRUCT_DEEPSEARCH_SP = """你是一个智能 INSTRUCT_DEEPSEARCH 审查助手, 你需要对 INSTRUCT 的输出进行多次信息补充直到信息足够、或达到次数上限(3次)
35
44
 
36
- - 你已经使用过 `web_search` 工具, 不推荐再次使用, 即便你微调搜索词也只能获取重复信息
37
45
  - 推荐使用 `crawl_page` 工具查看官方网站、wiki网站(但不推荐维基百科)、权威网站
38
46
  - crawl_page 永远不使用国内垃圾网站例如 csdn、知乎、等重复搬运二手信息的网站
39
47
 
40
48
  ## 重要规则 (CRITICAL RULES):
41
49
  - 禁止输出任何文本回复:你必须且只能通过工具调用来行动。
42
- - 禁止回答用户的问题:你的任务仅仅是收集信息。
43
- - 禁止解释你的行为:直接调用所需工具。
44
50
  - 如果没有必要进一步操作,请不要输出任何内容(空回复),流程将自动进入下一阶段。
45
51
  """
46
52
 
@@ -74,6 +80,22 @@ SUMMARY_REPORT_SP = """# 你是一个信息整合专家 (Summary Agent).
74
80
  """
75
81
 
76
82
 
83
+ # =============================================================================
84
+ # VISION DESCRIPTION PROMPT
85
+ # =============================================================================
86
+
87
+ VISION_DESCRIPTION_SP = """你是一个图像描述专家。
88
+ 根据用户发送的图片和文字,快速描述图片中的内容。
89
+
90
+ 要求:
91
+ - 客观描述图片中的主要元素、场景、人物、文字等
92
+ - 如果图片包含文字,请完整转录
93
+ - 如果用户有具体问题,围绕问题描述相关细节
94
+ - 描述应该简洁但信息丰富,控制在 300 字以内
95
+ - 使用用户的语言回复
96
+ """
97
+
98
+
77
99
  # =============================================================================
78
100
  # TOOL DEFINITIONS
79
101
  # =============================================================================
@@ -101,8 +123,8 @@ def get_web_search_tool() -> Dict[str, Any]:
101
123
  return {
102
124
  "type": "function",
103
125
  "function": {
104
- "name": "internal_web_search",
105
- "description": "网络搜索",
126
+ "name": "web_search",
127
+ "description": "网络搜索, 只容许输入正常的字符串查询, 禁止高级搜索",
106
128
  "parameters": {
107
129
  "type": "object",
108
130
  "properties": {"query": {"type": "string"}},
@@ -128,3 +150,25 @@ def get_crawl_page_tool() -> Dict[str, Any]:
128
150
  },
129
151
  },
130
152
  }
153
+
154
+
155
+ def get_set_mode_tool() -> Dict[str, Any]:
156
+ """Tool for setting the pipeline mode (fast or deepsearch)."""
157
+ return {
158
+ "type": "function",
159
+ "function": {
160
+ "name": "set_mode",
161
+ "description": "设置本次查询的处理模式",
162
+ "parameters": {
163
+ "type": "object",
164
+ "properties": {
165
+ "mode": {
166
+ "type": "string",
167
+ "enum": ["fast", "deepsearch"],
168
+ "description": "fast=快速回答 / deepsearch=深度研究"
169
+ },
170
+ },
171
+ "required": ["mode"],
172
+ },
173
+ },
174
+ }