zrb 1.21.17__py3-none-any.whl → 1.21.33__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.
Files changed (41) hide show
  1. zrb/attr/type.py +10 -7
  2. zrb/builtin/git.py +12 -1
  3. zrb/builtin/llm/chat_completion.py +287 -0
  4. zrb/builtin/llm/chat_session_cmd.py +90 -28
  5. zrb/builtin/llm/chat_trigger.py +6 -1
  6. zrb/builtin/llm/tool/cli.py +29 -13
  7. zrb/builtin/llm/tool/code.py +9 -1
  8. zrb/builtin/llm/tool/file.py +32 -6
  9. zrb/builtin/llm/tool/note.py +9 -9
  10. zrb/builtin/llm/tool/search/__init__.py +1 -0
  11. zrb/builtin/llm/tool/search/brave.py +66 -0
  12. zrb/builtin/llm/tool/search/searxng.py +61 -0
  13. zrb/builtin/llm/tool/search/serpapi.py +61 -0
  14. zrb/builtin/llm/tool/sub_agent.py +30 -10
  15. zrb/builtin/llm/tool/web.py +17 -72
  16. zrb/config/config.py +67 -26
  17. zrb/config/default_prompt/interactive_system_prompt.md +16 -13
  18. zrb/config/default_prompt/summarization_prompt.md +54 -8
  19. zrb/config/default_prompt/system_prompt.md +16 -18
  20. zrb/config/llm_rate_limitter.py +15 -6
  21. zrb/input/option_input.py +13 -1
  22. zrb/task/llm/agent.py +42 -143
  23. zrb/task/llm/agent_runner.py +152 -0
  24. zrb/task/llm/conversation_history.py +35 -24
  25. zrb/task/llm/conversation_history_model.py +4 -11
  26. zrb/task/llm/history_processor.py +206 -0
  27. zrb/task/llm/history_summarization.py +2 -179
  28. zrb/task/llm/print_node.py +14 -5
  29. zrb/task/llm/prompt.py +2 -17
  30. zrb/task/llm/subagent_conversation_history.py +41 -0
  31. zrb/task/llm/tool_confirmation_completer.py +41 -0
  32. zrb/task/llm/tool_wrapper.py +15 -11
  33. zrb/task/llm_task.py +41 -40
  34. zrb/util/attr.py +12 -7
  35. zrb/util/git.py +2 -2
  36. zrb/xcom/xcom.py +10 -0
  37. {zrb-1.21.17.dist-info → zrb-1.21.33.dist-info}/METADATA +3 -3
  38. {zrb-1.21.17.dist-info → zrb-1.21.33.dist-info}/RECORD +40 -32
  39. zrb/task/llm/history_summarization_tool.py +0 -24
  40. {zrb-1.21.17.dist-info → zrb-1.21.33.dist-info}/WHEEL +0 -0
  41. {zrb-1.21.17.dist-info → zrb-1.21.33.dist-info}/entry_points.txt +0 -0
@@ -89,6 +89,10 @@ def list_files(
89
89
  """
90
90
  Lists files recursively up to a specified depth.
91
91
 
92
+ **EFFICIENCY TIP:**
93
+ Do NOT use this tool if you already know the file path (e.g., from the user's prompt).
94
+ Use `read_from_file` directly in that case. Only use this to explore directory structures.
95
+
92
96
  Example:
93
97
  list_files(path='src', include_hidden=False, depth=2)
94
98
 
@@ -128,7 +132,6 @@ def list_files(
128
132
  if (include_hidden or not _is_hidden(d))
129
133
  and not is_excluded(d, patterns_to_exclude)
130
134
  ]
131
-
132
135
  for filename in files:
133
136
  if (include_hidden or not _is_hidden(filename)) and not is_excluded(
134
137
  filename, patterns_to_exclude
@@ -180,7 +183,14 @@ def read_from_file(
180
183
  """
181
184
  Reads content from one or more files, optionally specifying line ranges.
182
185
 
186
+ **EFFICIENCY TIP:**
187
+ For source code or configuration files, prefer reading the **entire file** at once
188
+ to ensure you have full context (imports, class definitions, etc.).
189
+ Only use `start_line` and `end_line` for extremely large files (like logs) or
190
+ when you are certain only a specific section is needed.
191
+
183
192
  Examples:
193
+ ```
184
194
  # Read entire content of a single file
185
195
  read_from_file(file={'path': 'path/to/file.txt'})
186
196
 
@@ -193,6 +203,7 @@ def read_from_file(
193
203
  {'path': 'path/to/file1.txt'},
194
204
  {'path': 'path/to/file2.txt', 'start_line': 1, 'end_line': 5}
195
205
  ])
206
+ ```
196
207
 
197
208
  Args:
198
209
  file (FileToRead | list[FileToRead]): A single file configuration or a list of them.
@@ -267,6 +278,7 @@ def write_to_file(
267
278
  - Split larger content into multiple sequential calls (first 'w', then 'a').
268
279
 
269
280
  Examples:
281
+ ```
270
282
  # Overwrite 'file.txt' with initial content
271
283
  write_to_file(file={'path': 'path/to/file.txt', 'content': 'Initial content.'})
272
284
 
@@ -278,6 +290,7 @@ def write_to_file(
278
290
  {'path': 'path/to/file1.txt', 'content': 'Content for file 1'},
279
291
  {'path': 'path/to/file2.txt', 'content': 'Content for file 2', 'mode': 'w'}
280
292
  ])
293
+ ```
281
294
 
282
295
  Args:
283
296
  file (FileToWrite | list[FileToWrite]): A single file configuration or a list of them.
@@ -434,6 +447,7 @@ def replace_in_file(
434
447
  6. **DEFAULT:** Replaces **ALL** occurrences. Set `count=1` for first occurrence only.
435
448
 
436
449
  Examples:
450
+ ```
437
451
  # Replace ALL occurrences
438
452
  replace_in_file(file=[
439
453
  {'path': 'file.txt', 'old_text': 'foo', 'new_text': 'bar'},
@@ -453,6 +467,7 @@ def replace_in_file(
453
467
  'new_text': ' def new_fn():\n pass'
454
468
  }
455
469
  )
470
+ ```
456
471
 
457
472
  Args:
458
473
  file: Single replacement config or list of them.
@@ -506,11 +521,17 @@ def replace_in_file(
506
521
 
507
522
 
508
523
  async def analyze_file(
509
- ctx: AnyContext, path: str, query: str, token_limit: int | None = None
524
+ ctx: AnyContext, path: str, query: str, token_threshold: int | None = None
510
525
  ) -> dict[str, Any]:
511
526
  """
512
527
  Analyzes a file using a sub-agent for complex questions.
513
528
 
529
+ CRITICAL: The query must contain ALL necessary context, instructions, and information.
530
+ The sub-agent performing the analysis does NOT share your current conversation
531
+ history, memory, or global context.
532
+ The quality of analysis depends entirely on the query. Vague queries yield poor
533
+ results.
534
+
514
535
  Example:
515
536
  analyze_file(path='src/main.py', query='Summarize the main function.')
516
537
 
@@ -519,13 +540,13 @@ async def analyze_file(
519
540
  path (str): The path to the file to analyze.
520
541
  query (str): A specific analysis query with clear guidelines and
521
542
  necessary information.
522
- token_limit (int | None): Max tokens.
543
+ token_threshold (int | None): Max tokens.
523
544
 
524
545
  Returns:
525
546
  Analysis results.
526
547
  """
527
- if token_limit is None:
528
- token_limit = CFG.LLM_FILE_ANALYSIS_TOKEN_LIMIT
548
+ if token_threshold is None:
549
+ token_threshold = CFG.LLM_FILE_ANALYSIS_TOKEN_THRESHOLD
529
550
  abs_path = os.path.abspath(os.path.expanduser(path))
530
551
  if not os.path.exists(abs_path):
531
552
  raise FileNotFoundError(f"File not found: {path}")
@@ -540,12 +561,17 @@ async def analyze_file(
540
561
  ),
541
562
  system_prompt=CFG.LLM_FILE_EXTRACTOR_SYSTEM_PROMPT,
542
563
  tools=[read_from_file, search_files],
564
+ auto_summarize=False,
565
+ remember_history=False,
566
+ yolo_mode=True,
543
567
  )
544
568
  payload = json.dumps(
545
569
  {
546
570
  "instruction": query,
547
571
  "file_path": abs_path,
548
- "file_content": llm_rate_limitter.clip_prompt(file_content, token_limit),
572
+ "file_content": llm_rate_limitter.clip_prompt(
573
+ file_content, token_threshold
574
+ ),
549
575
  }
550
576
  )
551
577
  return await _analyze_file(ctx, payload)
@@ -5,9 +5,9 @@ from zrb.config.llm_context.config import llm_context_config
5
5
 
6
6
  def read_long_term_note() -> str:
7
7
  """
8
- Retrieves the GLOBAL long-term memory shared across ALL sessions and projects.
8
+ Retrieves the GLOBAL 🧠 Long Term Note shared across ALL sessions and projects.
9
9
 
10
- CRITICAL: Consult this first for user preferences, facts, and cross-project context.
10
+ Use this to recall user preferences, facts, and cross-project context.
11
11
 
12
12
  Returns:
13
13
  str: The current global note content.
@@ -18,16 +18,16 @@ def read_long_term_note() -> str:
18
18
 
19
19
  def write_long_term_note(content: str) -> str:
20
20
  """
21
- Persists CRITICAL facts to the GLOBAL long-term memory.
21
+ Persists CRITICAL facts to the GLOBAL 🧠 Long Term Note.
22
22
 
23
- USE EAGERLY to save:
23
+ USE EAGERLY to save or update:
24
24
  - User preferences (e.g., "I prefer Python", "No unit tests").
25
25
  - User information (e.g., user name, user email address).
26
26
  - Important facts (e.g., "My API key is in .env").
27
27
  - Cross-project goals.
28
28
  - Anything that will be useful for future interaction across projects.
29
29
 
30
- WARNING: This OVERWRITES the entire global note. Always read first.
30
+ WARNING: This OVERWRITES the entire Long Term Note.
31
31
 
32
32
  Args:
33
33
  content (str): The text to strictly memorize.
@@ -41,7 +41,7 @@ def write_long_term_note(content: str) -> str:
41
41
 
42
42
  def read_contextual_note(path: str | None = None) -> str:
43
43
  """
44
- Retrieves LOCAL memory specific to a file or directory path.
44
+ Retrieves LOCAL 📝 Contextual Note specific to a directory path.
45
45
 
46
46
  Use to recall project-specific architecture, code summaries, or past decisions
47
47
  relevant to the current working location.
@@ -61,15 +61,15 @@ def read_contextual_note(path: str | None = None) -> str:
61
61
 
62
62
  def write_contextual_note(content: str, path: str | None = None) -> str:
63
63
  """
64
- Persists LOCAL facts specific to a file or directory.
64
+ Persists LOCAL facts specific to a directory into 📝 Contextual Note.
65
65
 
66
- USE EAGERLY to save:
66
+ USE EAGERLY to save or update:
67
67
  - Architectural patterns for this project/directory.
68
68
  - Summaries of large files or directories.
69
69
  - Specific guidelines for this project.
70
70
  - Anything related to this directory that will be useful for future interaction.
71
71
 
72
- WARNING: This OVERWRITES the note for the specific path. Always read first.
72
+ WARNING: This OVERWRITES the entire Contextual Note for a directory.
73
73
 
74
74
  Args:
75
75
  content (str): The text to memorize for this location.
@@ -0,0 +1 @@
1
+ # This file makes the directory a Python package
@@ -0,0 +1,66 @@
1
+ from typing import Any
2
+
3
+ import requests
4
+
5
+ from zrb.config.config import CFG
6
+
7
+
8
+ def search_internet(
9
+ query: str,
10
+ page: int = 1,
11
+ safe_search: str | None = None,
12
+ language: str | None = None,
13
+ ) -> dict[str, Any]:
14
+ """
15
+ Performs an internet search using Brave Search.
16
+
17
+ Use this tool to find up-to-date information, answer questions about current events,
18
+ or research topics using a search engine.
19
+
20
+ **EFFICIENCY TIP:**
21
+ Make your `query` specific and keyword-rich to get the best results in a single call.
22
+ Avoid vague queries that require follow-up searches.
23
+ Bad: "new python features"
24
+ Good: "python 3.12 new features list release date"
25
+
26
+ Args:
27
+ query (str): The natural language search query (e.g., 'Soto Madura').
28
+ Do NOT include instructions, meta-talk, or internal reasoning.
29
+ Use concise terms as a human would in a search engine.
30
+ page (int): Search result page number. Defaults to 1.
31
+ safe_search (str | None): Safety setting. 'strict', 'moderate', or 'off'.
32
+ If None, uses the system default configuration.
33
+ language (str | None): Language code (e.g., 'en').
34
+ If None, uses the system default configuration.
35
+
36
+ Returns:
37
+ dict: Summary of search results (titles, links, snippets).
38
+ """
39
+ if safe_search is None:
40
+ safe_search = CFG.BRAVE_API_SAFE
41
+ if language is None:
42
+ language = CFG.BRAVE_API_LANG
43
+
44
+ user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" # noqa
45
+
46
+ response = requests.get(
47
+ "https://api.search.brave.com/res/v1/web/search",
48
+ headers={
49
+ "User-Agent": user_agent,
50
+ "Accept": "application/json",
51
+ "x-subscription-token": CFG.BRAVE_API_KEY,
52
+ },
53
+ params={
54
+ "q": query,
55
+ "count": "10",
56
+ "offset": (page - 1) * 10,
57
+ "safesearch": safe_search,
58
+ "search_lang": language,
59
+ "summary": "true",
60
+ },
61
+ )
62
+ if response.status_code != 200:
63
+ raise Exception(
64
+ f"Error: Unable to retrieve search results (status code: {response.status_code})"
65
+ )
66
+ return response.json()
@@ -0,0 +1,61 @@
1
+ from typing import Any
2
+
3
+ import requests
4
+
5
+ from zrb.config.config import CFG
6
+
7
+
8
+ def search_internet(
9
+ query: str,
10
+ page: int = 1,
11
+ safe_search: int | None = None,
12
+ language: str | None = None,
13
+ ) -> dict[str, Any]:
14
+ """
15
+ Performs an internet search using SearXNG.
16
+
17
+ Use this tool to find up-to-date information, answer questions about current events,
18
+ or research topics using a search engine.
19
+
20
+ **EFFICIENCY TIP:**
21
+ Make your `query` specific and keyword-rich to get the best results in a single call.
22
+ Avoid vague queries that require follow-up searches.
23
+ Bad: "new python features"
24
+ Good: "python 3.12 new features list release date"
25
+
26
+ Args:
27
+ query (str): The natural language search query (e.g., 'Soto Madura').
28
+ Do NOT include instructions, meta-talk, or internal reasoning.
29
+ Use concise terms as a human would in a search engine.
30
+ page (int): Search result page number. Defaults to 1.
31
+ safe_search (int | None): Safety setting. 0 (None), 1 (Moderate), 2 (Strict).
32
+ If None, uses the system default configuration.
33
+ language (str | None): Language code (e.g., 'en').
34
+ If None, uses the system default configuration.
35
+
36
+ Returns:
37
+ dict: Summary of search results (titles, links, snippets).
38
+ """
39
+ if safe_search is None:
40
+ safe_search = CFG.SEARXNG_SAFE
41
+ if language is None:
42
+ language = CFG.SEARXNG_LANG
43
+
44
+ user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" # noqa
45
+
46
+ response = requests.get(
47
+ url=f"{CFG.SEARXNG_BASE_URL}/search",
48
+ headers={"User-Agent": user_agent},
49
+ params={
50
+ "q": query,
51
+ "format": "json",
52
+ "pageno": page,
53
+ "safesearch": safe_search,
54
+ "language": language,
55
+ },
56
+ )
57
+ if response.status_code != 200:
58
+ raise Exception(
59
+ f"Error: Unable to retrieve search results (status code: {response.status_code})"
60
+ )
61
+ return response.json()
@@ -0,0 +1,61 @@
1
+ from typing import Any
2
+
3
+ import requests
4
+
5
+ from zrb.config.config import CFG
6
+
7
+
8
+ def search_internet(
9
+ query: str,
10
+ page: int = 1,
11
+ safe_search: str | None = None,
12
+ language: str | None = None,
13
+ ) -> dict[str, Any]:
14
+ """
15
+ Performs an internet search using SerpApi (Google).
16
+
17
+ Use this tool to find up-to-date information, answer questions about current events,
18
+ or research topics using a search engine.
19
+
20
+ **EFFICIENCY TIP:**
21
+ Make your `query` specific and keyword-rich to get the best results in a single call.
22
+ Avoid vague queries that require follow-up searches.
23
+ Bad: "new python features"
24
+ Good: "python 3.12 new features list release date"
25
+
26
+ Args:
27
+ query (str): The natural language search query (e.g., 'Soto Madura').
28
+ Do NOT include instructions, meta-talk, or internal reasoning.
29
+ Use concise terms as a human would in a search engine.
30
+ page (int): Search result page number. Defaults to 1.
31
+ safe_search (str | None): Safety setting. 'active' or 'off'.
32
+ If None, uses the system default configuration.
33
+ language (str | None): Two-letter language code (e.g., 'en', 'id').
34
+ If None, uses the system default configuration.
35
+
36
+ Returns:
37
+ dict: Summary of search results (titles, links, snippets).
38
+ """
39
+ if safe_search is None:
40
+ safe_search = CFG.SERPAPI_SAFE
41
+ if language is None:
42
+ language = CFG.SERPAPI_LANG
43
+
44
+ user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" # noqa
45
+
46
+ response = requests.get(
47
+ "https://serpapi.com/search",
48
+ headers={"User-Agent": user_agent},
49
+ params={
50
+ "q": query,
51
+ "start": (page - 1) * 10,
52
+ "hl": language,
53
+ "safe": safe_search,
54
+ "api_key": CFG.SERPAPI_KEY,
55
+ },
56
+ )
57
+ if response.status_code != 200:
58
+ raise Exception(
59
+ f"Error: Unable to retrieve search results (status code: {response.status_code})"
60
+ )
61
+ return response.json()
@@ -1,14 +1,21 @@
1
+ import json
1
2
  from collections.abc import Callable
2
3
  from textwrap import dedent
3
4
  from typing import TYPE_CHECKING, Any, Coroutine
4
5
 
5
6
  from zrb.context.any_context import AnyContext
6
- from zrb.task.llm.agent import create_agent_instance, run_agent_iteration
7
+ from zrb.task.llm.agent import create_agent_instance
8
+ from zrb.task.llm.agent_runner import run_agent_iteration
7
9
  from zrb.task.llm.config import get_model, get_model_settings
8
10
  from zrb.task.llm.prompt import get_system_and_user_prompt
11
+ from zrb.task.llm.subagent_conversation_history import (
12
+ get_ctx_subagent_history,
13
+ set_ctx_subagent_history,
14
+ )
9
15
 
10
16
  if TYPE_CHECKING:
11
17
  from pydantic_ai import Tool
18
+ from pydantic_ai._agent_graph import HistoryProcessor
12
19
  from pydantic_ai.models import Model
13
20
  from pydantic_ai.settings import ModelSettings
14
21
  from pydantic_ai.toolsets import AbstractToolset
@@ -25,8 +32,12 @@ def create_sub_agent_tool(
25
32
  tools: "list[ToolOrCallable]" = [],
26
33
  toolsets: list["AbstractToolset[None]"] = [],
27
34
  yolo_mode: bool | list[str] | None = None,
35
+ history_processors: list["HistoryProcessor"] | None = None,
28
36
  log_indent_level: int = 2,
29
- ) -> Callable[[AnyContext, str], Coroutine[Any, Any, dict[str, Any]]]:
37
+ agent_name: str | None = None,
38
+ auto_summarize: bool = True,
39
+ remember_history: bool = True,
40
+ ) -> Callable[[AnyContext, str], Coroutine[Any, Any, Any]]:
30
41
  """
31
42
  Create a tool that is another AI agent, capable of handling complex, multi-step sub-tasks.
32
43
 
@@ -52,8 +63,10 @@ def create_sub_agent_tool(
52
63
  An asynchronous function that serves as the sub-agent tool. When called, it runs the
53
64
  sub-agent with a given query and returns its final result.
54
65
  """
66
+ if agent_name is None:
67
+ agent_name = f"{tool_name}_agent"
55
68
 
56
- async def run_sub_agent(ctx: AnyContext, query: str) -> dict[str, Any]:
69
+ async def run_sub_agent(ctx: AnyContext, query: str) -> Any:
57
70
  """
58
71
  Runs the sub-agent with the given query.
59
72
  """
@@ -72,7 +85,6 @@ def create_sub_agent_tool(
72
85
  ctx=ctx,
73
86
  model_settings_attr=model_settings,
74
87
  )
75
-
76
88
  if system_prompt is None:
77
89
  resolved_system_prompt, query = get_system_and_user_prompt(
78
90
  ctx=ctx,
@@ -92,24 +104,32 @@ def create_sub_agent_tool(
92
104
  tools=tools,
93
105
  toolsets=toolsets,
94
106
  yolo_mode=yolo_mode,
107
+ history_processors=history_processors,
108
+ auto_summarize=auto_summarize,
95
109
  )
96
-
97
110
  sub_agent_run = None
98
111
  # Run the sub-agent iteration
99
- # Start with an empty history for the sub-agent
112
+ history_list = (
113
+ get_ctx_subagent_history(ctx, agent_name) if remember_history else []
114
+ )
100
115
  sub_agent_run = await run_agent_iteration(
101
116
  ctx=ctx,
102
117
  agent=sub_agent_agent,
103
118
  user_prompt=query,
104
119
  attachments=[],
105
- history_list=[],
120
+ history_list=history_list,
106
121
  log_indent_level=log_indent_level,
107
122
  )
108
-
109
123
  # Return the sub-agent's final message content
110
124
  if sub_agent_run and sub_agent_run.result:
111
125
  # Return the final message content
112
- return {"result": sub_agent_run.result.output}
126
+ if remember_history:
127
+ set_ctx_subagent_history(
128
+ ctx,
129
+ agent_name,
130
+ json.loads(sub_agent_run.result.all_messages_json()),
131
+ )
132
+ return sub_agent_run.result.output
113
133
  ctx.log_warning("Sub-agent run did not produce a result.")
114
134
  raise ValueError(f"{tool_name} not returning any result")
115
135
 
@@ -123,7 +143,7 @@ def create_sub_agent_tool(
123
143
  query (str): The query or task for the sub-agent.
124
144
 
125
145
  Returns:
126
- dict[str, Any]: The final response or result from the sub-agent.
146
+ Any: The final response or result from the sub-agent.
127
147
  """
128
148
  ).strip()
129
149
 
@@ -2,6 +2,7 @@ from collections.abc import Callable
2
2
  from typing import Any
3
3
  from urllib.parse import urljoin
4
4
 
5
+ from zrb.builtin.llm.tool.search import brave, searxng, serpapi
5
6
  from zrb.config.config import CFG
6
7
  from zrb.config.llm_config import llm_config
7
8
 
@@ -13,6 +14,11 @@ async def open_web_page(url: str) -> dict[str, Any]:
13
14
  Fetches, parses, and converts a web page to readable Markdown.
14
15
  Preserves semantic structure, removes non-essentials, and extracts all absolute links.
15
16
 
17
+ **EFFICIENCY TIP:**
18
+ Use this tool to read the full content of a specific search result or article.
19
+ It returns clean Markdown and a list of links, which is perfect for deep-diving
20
+ into a topic without navigating a browser UI.
21
+
16
22
  Example:
17
23
  open_web_page(url='https://www.example.com/article')
18
24
 
@@ -30,78 +36,17 @@ async def open_web_page(url: str) -> dict[str, Any]:
30
36
  def create_search_internet_tool() -> Callable:
31
37
  if llm_config.default_search_internet_tool is not None:
32
38
  return llm_config.default_search_internet_tool
33
-
34
- def search_internet(query: str, page: int = 1) -> dict[str, Any]:
35
- """
36
- Performs an internet search using a search engine.
37
- Use to find information, answer general knowledge, or research topics.
38
-
39
- Example:
40
- search_internet(query='latest AI advancements', page=1)
41
-
42
- Args:
43
- query (str): The search query.
44
- page (int, optional): Search result page number. Defaults to 1.
45
-
46
- Returns:
47
- dict: Summary of search results (titles, links, snippets).
48
- """
49
- import requests
50
-
51
- if (
52
- CFG.SEARCH_INTERNET_METHOD.strip().lower() == "serpapi"
53
- and CFG.SERPAPI_KEY != ""
54
- ):
55
- response = requests.get(
56
- "https://serpapi.com/search",
57
- headers={"User-Agent": _DEFAULT_USER_AGENT},
58
- params={
59
- "q": query,
60
- "start": (page - 1) * 10,
61
- "hl": CFG.SERPAPI_LANG,
62
- "safe": CFG.SERPAPI_SAFE,
63
- "api_key": CFG.SERPAPI_KEY,
64
- },
65
- )
66
- elif (
67
- CFG.SEARCH_INTERNET_METHOD.strip().lower() == "brave"
68
- and CFG.BRAVE_API_KEY != ""
69
- ):
70
- response = requests.get(
71
- "https://api.search.brave.com/res/v1/web/search",
72
- headers={
73
- "User-Agent": _DEFAULT_USER_AGENT,
74
- "Accept": "application/json",
75
- "x-subscription-token": CFG.BRAVE_API_KEY,
76
- },
77
- params={
78
- "q": query,
79
- "count": "10",
80
- "offset": (page - 1) * 10,
81
- "safesearch": CFG.BRAVE_API_SAFE,
82
- "search_lang": CFG.BRAVE_API_LANG,
83
- "summary": "true",
84
- },
85
- )
86
- else:
87
- response = requests.get(
88
- url=f"{CFG.SEARXNG_BASE_URL}/search",
89
- headers={"User-Agent": _DEFAULT_USER_AGENT},
90
- params={
91
- "q": query,
92
- "format": "json",
93
- "pageno": page,
94
- "safesearch": CFG.SEARXNG_SAFE,
95
- "language": CFG.SEARXNG_LANG,
96
- },
97
- )
98
- if response.status_code != 200:
99
- raise Exception(
100
- f"Error: Unable to retrieve search results (status code: {response.status_code})" # noqa
101
- )
102
- return response.json()
103
-
104
- return search_internet
39
+ if (
40
+ CFG.SEARCH_INTERNET_METHOD.strip().lower() == "serpapi"
41
+ and CFG.SERPAPI_KEY != ""
42
+ ):
43
+ return serpapi.search_internet
44
+ if (
45
+ CFG.SEARCH_INTERNET_METHOD.strip().lower() == "brave"
46
+ and CFG.BRAVE_API_KEY != ""
47
+ ):
48
+ return brave.search_internet
49
+ return searxng.search_internet
105
50
 
106
51
 
107
52
  async def _fetch_page_content(url: str) -> tuple[str, list[str]]: