zrb 1.16.5__py3-none-any.whl → 1.17.1__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.
- zrb/builtin/__init__.py +1 -0
- zrb/builtin/group.py +5 -1
- zrb/builtin/llm/llm_ask.py +7 -10
- zrb/builtin/llm/tool/api.py +85 -71
- zrb/builtin/llm/tool/file.py +35 -100
- zrb/builtin/llm/tool/note.py +9 -12
- zrb/builtin/llm/tool/web.py +31 -87
- zrb/builtin/searxng/config/settings.yml +5671 -0
- zrb/builtin/searxng/start.py +21 -0
- zrb/config/config.py +28 -8
- zrb/config/llm_config.py +100 -4
- zrb/group/any_group.py +1 -1
- zrb/task/llm/agent.py +0 -15
- zrb/task/llm/config.py +12 -3
- zrb/task/llm/prompt.py +49 -1
- zrb/task/llm/tool_wrapper.py +6 -5
- zrb/task/llm_task.py +30 -10
- {zrb-1.16.5.dist-info → zrb-1.17.1.dist-info}/METADATA +1 -2
- {zrb-1.16.5.dist-info → zrb-1.17.1.dist-info}/RECORD +21 -19
- {zrb-1.16.5.dist-info → zrb-1.17.1.dist-info}/WHEEL +0 -0
- {zrb-1.16.5.dist-info → zrb-1.17.1.dist-info}/entry_points.txt +0 -0
zrb/builtin/__init__.py
CHANGED
@@ -16,6 +16,7 @@ from zrb.builtin.project.add.fastapp.fastapp_task import add_fastapp_to_project
|
|
16
16
|
from zrb.builtin.project.create.project_task import create_project
|
17
17
|
from zrb.builtin.python import format_python_code
|
18
18
|
from zrb.builtin.random import shuffle_values, throw_dice
|
19
|
+
from zrb.builtin.searxng.start import start_searxng
|
19
20
|
from zrb.builtin.setup.asdf.asdf import setup_asdf
|
20
21
|
from zrb.builtin.setup.latex.ubuntu import setup_latex_on_ubuntu
|
21
22
|
from zrb.builtin.setup.tmux.tmux import setup_tmux
|
zrb/builtin/group.py
CHANGED
@@ -41,7 +41,7 @@ todo_group = _maybe_add_group(Group(name="todo", description="✅ Todo managemen
|
|
41
41
|
shell_group = _maybe_add_group(
|
42
42
|
Group(name="shell", description="💬 Shell related commands")
|
43
43
|
)
|
44
|
-
shell_autocomplete_group
|
44
|
+
shell_autocomplete_group = shell_group.add_group(
|
45
45
|
Group(name="autocomplete", description="⌨️ Shell autocomplete related commands")
|
46
46
|
)
|
47
47
|
|
@@ -59,3 +59,7 @@ setup_group = _maybe_add_group(Group(name="setup", description="🔧 Setup"))
|
|
59
59
|
setup_latex_group = setup_group.add_group(
|
60
60
|
Group(name="latex", description="✍️ Setup LaTeX")
|
61
61
|
)
|
62
|
+
|
63
|
+
searxng_group = _maybe_add_group(
|
64
|
+
Group(name="searxng", description="🔎 Searxng related command")
|
65
|
+
)
|
zrb/builtin/llm/llm_ask.py
CHANGED
@@ -6,7 +6,10 @@ from zrb.builtin.group import llm_group
|
|
6
6
|
from zrb.builtin.llm.chat_session import get_llm_ask_input_mapping, read_user_prompt
|
7
7
|
from zrb.builtin.llm.history import read_chat_conversation, write_chat_conversation
|
8
8
|
from zrb.builtin.llm.input import PreviousSessionInput
|
9
|
-
from zrb.builtin.llm.tool.api import
|
9
|
+
from zrb.builtin.llm.tool.api import (
|
10
|
+
create_get_current_location,
|
11
|
+
create_get_current_weather,
|
12
|
+
)
|
10
13
|
from zrb.builtin.llm.tool.cli import run_shell_command
|
11
14
|
from zrb.builtin.llm.tool.code import analyze_repo
|
12
15
|
from zrb.builtin.llm.tool.file import (
|
@@ -28,8 +31,6 @@ from zrb.builtin.llm.tool.note import (
|
|
28
31
|
from zrb.builtin.llm.tool.web import (
|
29
32
|
create_search_internet_tool,
|
30
33
|
open_web_page,
|
31
|
-
search_arxiv,
|
32
|
-
search_wikipedia,
|
33
34
|
)
|
34
35
|
from zrb.callback.callback import Callback
|
35
36
|
from zrb.config.config import CFG
|
@@ -85,16 +86,12 @@ def _get_tool(ctx: AnyContext) -> list["ToolOrCallable"]:
|
|
85
86
|
tools.append(run_shell_command)
|
86
87
|
if CFG.LLM_ALLOW_OPEN_WEB_PAGE:
|
87
88
|
tools.append(open_web_page)
|
88
|
-
if CFG.LLM_ALLOW_SEARCH_WIKIPEDIA:
|
89
|
-
tools.append(search_wikipedia)
|
90
|
-
if CFG.LLM_ALLOW_SEARCH_ARXIV:
|
91
|
-
tools.append(search_arxiv)
|
92
89
|
if CFG.LLM_ALLOW_GET_CURRENT_LOCATION:
|
93
|
-
tools.append(
|
90
|
+
tools.append(create_get_current_location())
|
94
91
|
if CFG.LLM_ALLOW_GET_CURRENT_WEATHER:
|
95
|
-
tools.append(
|
92
|
+
tools.append(create_get_current_weather())
|
96
93
|
if CFG.SERPAPI_KEY != "" and CFG.LLM_ALLOW_SEARCH_INTERNET:
|
97
|
-
tools.append(create_search_internet_tool(
|
94
|
+
tools.append(create_search_internet_tool())
|
98
95
|
return tools
|
99
96
|
|
100
97
|
|
zrb/builtin/llm/tool/api.py
CHANGED
@@ -1,71 +1,85 @@
|
|
1
|
-
from typing import Any, Literal
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
latitude
|
44
|
-
longitude
|
45
|
-
temperature_unit
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
1
|
+
from typing import Any, Callable, Literal
|
2
|
+
|
3
|
+
from zrb.config.llm_config import llm_config
|
4
|
+
|
5
|
+
|
6
|
+
def create_get_current_location() -> Callable:
|
7
|
+
if llm_config.default_current_location_tool is not None:
|
8
|
+
return llm_config.default_current_location_tool
|
9
|
+
|
10
|
+
def get_current_location() -> dict[str, float]:
|
11
|
+
"""
|
12
|
+
Fetches the user's current geographical location based on their IP address.
|
13
|
+
|
14
|
+
Use this tool when the user asks "Where am I?", "What is my current
|
15
|
+
location?", or has a query that requires knowing their location to be
|
16
|
+
answered.
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
dict[str, float]: A dictionary containing the 'lat' and 'lon' of the current
|
20
|
+
location.
|
21
|
+
Example: {"lat": 48.8584, "lon": 2.2945}
|
22
|
+
Raises:
|
23
|
+
requests.RequestException: If the API request to the location service
|
24
|
+
fails.
|
25
|
+
"""
|
26
|
+
import requests
|
27
|
+
|
28
|
+
try:
|
29
|
+
response = requests.get("http://ip-api.com/json?fields=lat,lon", timeout=5)
|
30
|
+
response.raise_for_status()
|
31
|
+
return dict(response.json())
|
32
|
+
except requests.RequestException as e:
|
33
|
+
raise requests.RequestException(f"Failed to get location: {e}")
|
34
|
+
|
35
|
+
return get_current_location
|
36
|
+
|
37
|
+
|
38
|
+
def create_get_current_weather() -> Callable:
|
39
|
+
if llm_config.default_current_weather_tool is not None:
|
40
|
+
return llm_config.default_current_weather_tool
|
41
|
+
|
42
|
+
def get_current_weather(
|
43
|
+
latitude: float,
|
44
|
+
longitude: float,
|
45
|
+
temperature_unit: Literal["celsius", "fahrenheit"],
|
46
|
+
) -> dict[str, Any]:
|
47
|
+
"""
|
48
|
+
Retrieves the current weather conditions for a given geographical location.
|
49
|
+
|
50
|
+
Use this tool when the user asks about the weather. If the user does not
|
51
|
+
provide a location, first use the `get_current_location` tool to
|
52
|
+
determine their location.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
latitude (float): The latitude of the location.
|
56
|
+
longitude (float): The longitude of the location.
|
57
|
+
temperature_unit (Literal["celsius", "fahrenheit"]): The desired unit
|
58
|
+
for the temperature reading.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
dict[str, Any]: A dictionary containing detailed weather data, including
|
62
|
+
temperature, wind speed, and weather code.
|
63
|
+
Raises:
|
64
|
+
requests.RequestException: If the API request to the weather service
|
65
|
+
fails.
|
66
|
+
"""
|
67
|
+
import requests
|
68
|
+
|
69
|
+
try:
|
70
|
+
response = requests.get(
|
71
|
+
"https://api.open-meteo.com/v1/forecast",
|
72
|
+
params={
|
73
|
+
"latitude": latitude,
|
74
|
+
"longitude": longitude,
|
75
|
+
"temperature_unit": temperature_unit,
|
76
|
+
"current_weather": True,
|
77
|
+
},
|
78
|
+
timeout=5,
|
79
|
+
)
|
80
|
+
response.raise_for_status()
|
81
|
+
return dict(response.json())
|
82
|
+
except requests.RequestException as e:
|
83
|
+
raise requests.RequestException(f"Failed to get weather data: {e}")
|
84
|
+
|
85
|
+
return get_current_weather
|
zrb/builtin/llm/tool/file.py
CHANGED
@@ -100,34 +100,19 @@ def list_files(
|
|
100
100
|
excluded_patterns: Optional[list[str]] = None,
|
101
101
|
) -> dict[str, list[str]]:
|
102
102
|
"""
|
103
|
-
Lists
|
103
|
+
Lists files and directories within a specified path, providing a map of the filesystem.
|
104
104
|
|
105
|
-
|
106
|
-
discover the structure of a directory, find specific files, or get a
|
107
|
-
general overview of the project layout before performing other operations.
|
108
|
-
**NOTE:**
|
109
|
-
User might provide inaccurate file path, so if you failed to read a file,
|
110
|
-
it is a good idea to list the files to get the accurate file path.
|
105
|
+
Use this tool to explore and understand the directory structure of the project. It's essential for finding files before reading or modifying them. If you receive an error about a file not being found, use this tool to verify the correct path.
|
111
106
|
|
112
107
|
Args:
|
113
|
-
path (str, optional): The directory path to list. Defaults to the
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
Defaults to True.
|
118
|
-
include_hidden (bool, optional): If True, includes hidden files and
|
119
|
-
directories (those starting with a dot). Defaults to False.
|
120
|
-
excluded_patterns (list[str], optional): A list of glob patterns to
|
121
|
-
exclude from the listing. This is useful for ignoring irrelevant
|
122
|
-
files like build artifacts or virtual environments. Defaults to a
|
123
|
-
standard list of common exclusion patterns.
|
108
|
+
path (str, optional): The directory path to list, preferably an absolute path. Defaults to the current directory (".").
|
109
|
+
recursive (bool, optional): If True, lists contents recursively. Defaults to True.
|
110
|
+
include_hidden (bool, optional): If True, includes hidden files (e.g., .gitignore). Defaults to False.
|
111
|
+
excluded_patterns (list[str], optional): A list of glob patterns to ignore (e.g., ["*.log", "node_modules/"]). Defaults to a standard list of ignores.
|
124
112
|
|
125
113
|
Returns:
|
126
|
-
dict[str, list[str]]: A dictionary containing a list of file and directory paths
|
127
|
-
|
128
|
-
Example: {"files": ["src/main.py", "README.md"]}
|
129
|
-
Raises:
|
130
|
-
FileNotFoundError: If the specified path does not exist.
|
114
|
+
dict[str, list[str]]: A dictionary with a single key "files" containing a sorted list of file and directory paths relative to the input path.
|
115
|
+
Example: {"files": ["README.md", "src/main.py", "src/utils/helpers.py"]}
|
131
116
|
"""
|
132
117
|
all_files: list[str] = []
|
133
118
|
abs_path = os.path.abspath(os.path.expanduser(path))
|
@@ -226,59 +211,24 @@ def read_from_file(
|
|
226
211
|
end_line: Optional[int] = None,
|
227
212
|
) -> dict[str, Any]:
|
228
213
|
"""
|
229
|
-
Reads the content of a file
|
230
|
-
end line.
|
231
|
-
|
232
|
-
This tool is essential for inspecting file contents. It can read both text
|
233
|
-
and PDF files. The `content` field in the returned dictionary is **always**
|
234
|
-
prefixed with line numbers (`line_number | `) for each line. These prefixes
|
235
|
-
are added by the tool for internal processing and to provide contextual
|
236
|
-
information to the AI agent (e.g., for accurately applying diffs or for
|
237
|
-
referencing specific lines). They are **not** part of the actual file content.
|
238
|
-
|
239
|
-
When presenting the file content to a user, the AI agent **MUST** omit these
|
240
|
-
line number prefixes by default for better readability. They should only
|
241
|
-
be included if explicitly requested by the user or if the request inherently
|
242
|
-
requires line number context (e.g., 'show me lines 10-20').
|
214
|
+
Reads the content of a single file.
|
243
215
|
|
244
|
-
Use this tool to
|
245
|
-
- Examine the source code of a file.
|
246
|
-
- Read configuration files.
|
247
|
-
- Check the contents of a document.
|
216
|
+
Use this tool to inspect the contents of a specific file. You can read the entire file or specify a range of lines. The content returned will include line numbers, which are useful for other tools like `replace_in_file`.
|
248
217
|
|
249
218
|
Args:
|
250
|
-
path (str): The path to the file to read.
|
251
|
-
start_line (int, optional): The 1-based line number to start reading
|
252
|
-
|
253
|
-
end_line (int, optional): The 1-based line number to stop reading at
|
254
|
-
(inclusive). If omitted, reads to the end of the file.
|
219
|
+
path (str): The absolute path to the file to read.
|
220
|
+
start_line (int, optional): The 1-based line number to start reading from. Defaults to the beginning of the file.
|
221
|
+
end_line (int, optional): The 1-based line number to stop reading at (inclusive). Defaults to the end of the file.
|
255
222
|
|
256
223
|
Returns:
|
257
|
-
dict[str, Any]: A dictionary containing the file path
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
"start_line": 1,
|
266
|
-
"end_line": 3,
|
267
|
-
"total_lines": 3
|
268
|
-
}
|
269
|
-
```
|
270
|
-
If the file 'src/main.py' contains:
|
271
|
-
```
|
272
|
-
import os
|
273
|
-
|
274
|
-
print("Hello, World!")
|
275
|
-
```
|
276
|
-
The 'content' field in the returned dictionary will be:
|
277
|
-
`"1| import os\n2|\n3| print(\"Hello, World!\")"`
|
278
|
-
**IMPORTANT:** The "1|", "2|", "3|" prefixes are NOT part of the
|
279
|
-
original file. They are for internal agent use.
|
280
|
-
Raises:
|
281
|
-
FileNotFoundError: If the specified file does not exist.
|
224
|
+
dict[str, Any]: A dictionary containing the file path and its content with line numbers.
|
225
|
+
Example: {
|
226
|
+
"path": "src/main.py",
|
227
|
+
"content": "1| import os\n2|\n3| print(\"Hello, World!\")",
|
228
|
+
"start_line": 1,
|
229
|
+
"end_line": 3,
|
230
|
+
"total_lines": 3
|
231
|
+
}
|
282
232
|
"""
|
283
233
|
|
284
234
|
abs_path = os.path.abspath(os.path.expanduser(path))
|
@@ -318,24 +268,19 @@ def read_from_file(
|
|
318
268
|
def write_to_file(
|
319
269
|
path: str,
|
320
270
|
content: str,
|
321
|
-
) ->
|
271
|
+
) -> str:
|
322
272
|
"""
|
323
|
-
Writes content to a file,
|
324
|
-
creating it if it doesn't.
|
273
|
+
Writes content to a file, creating it or completely overwriting it.
|
325
274
|
|
326
|
-
Use this tool to create new
|
327
|
-
|
328
|
-
actions. Always read the file first to understand its contents before
|
329
|
-
overwriting it, unless you are creating a new file.
|
275
|
+
Use this tool to create a new file or replace an existing file's entire content.
|
276
|
+
**WARNING:** This is a destructive operation and will overwrite the file if it exists. Use `replace_in_file` for safer, targeted changes.
|
330
277
|
|
331
278
|
Args:
|
332
|
-
path (str): The path
|
333
|
-
content (str): The full
|
334
|
-
Do not use partial content or omit any lines.
|
279
|
+
path (str): The absolute path of the file to write to.
|
280
|
+
content (str): The full content to be written to the file.
|
335
281
|
|
336
282
|
Returns:
|
337
|
-
|
338
|
-
Example: {"success": true, "path": "new_file.txt"}
|
283
|
+
str: A confirmation message indicating the file was written successfully.
|
339
284
|
"""
|
340
285
|
try:
|
341
286
|
abs_path = os.path.abspath(os.path.expanduser(path))
|
@@ -344,7 +289,7 @@ def write_to_file(
|
|
344
289
|
if directory and not os.path.exists(directory):
|
345
290
|
os.makedirs(directory, exist_ok=True)
|
346
291
|
write_file(abs_path, content)
|
347
|
-
return
|
292
|
+
return f"Successfully wrote to file: {path}"
|
348
293
|
except (OSError, IOError) as e:
|
349
294
|
raise OSError(f"Error writing file {path}: {e}")
|
350
295
|
except Exception as e:
|
@@ -470,29 +415,19 @@ def replace_in_file(
|
|
470
415
|
path: str,
|
471
416
|
old_string: str,
|
472
417
|
new_string: str,
|
473
|
-
) ->
|
418
|
+
) -> str:
|
474
419
|
"""
|
475
420
|
Replaces the first occurrence of a string in a file.
|
476
421
|
|
477
|
-
This tool is for making targeted modifications to a file. It is
|
478
|
-
single-step operation that is generally safer and more ergonomic than
|
479
|
-
`write_to_file` for small changes.
|
480
|
-
|
481
|
-
To ensure the replacement is applied correctly and to avoid ambiguity, the
|
482
|
-
`old_string` parameter should be a unique, multi-line string that includes
|
483
|
-
context from before and after the code you want to change.
|
422
|
+
This tool is for making targeted modifications to a file. It is safer than `write_to_file` for small changes. First, use `read_from_file` to get the exact text block you want to change. Then, use that block as the `old_string`.
|
484
423
|
|
485
424
|
Args:
|
486
|
-
path (str): The path of the file to modify.
|
487
|
-
old_string (str): The exact, verbatim string to
|
488
|
-
This should be a unique, multi-line block of text.
|
425
|
+
path (str): The absolute path of the file to modify.
|
426
|
+
old_string (str): The exact, verbatim string to replace. This should be a unique, multi-line block of text copied from the file.
|
489
427
|
new_string (str): The new string that will replace the `old_string`.
|
490
428
|
|
491
429
|
Returns:
|
492
|
-
|
493
|
-
Raises:
|
494
|
-
FileNotFoundError: If the specified file does not exist.
|
495
|
-
ValueError: If the `old_string` is not found in the file.
|
430
|
+
str: A confirmation message on success.
|
496
431
|
"""
|
497
432
|
abs_path = os.path.abspath(os.path.expanduser(path))
|
498
433
|
if not os.path.exists(abs_path):
|
@@ -503,7 +438,7 @@ def replace_in_file(
|
|
503
438
|
raise ValueError(f"old_string not found in file: {path}")
|
504
439
|
new_content = content.replace(old_string, new_string, 1)
|
505
440
|
write_file(abs_path, new_content)
|
506
|
-
return
|
441
|
+
return f"Successfully replaced content in {path}"
|
507
442
|
except ValueError as e:
|
508
443
|
raise e
|
509
444
|
except (OSError, IOError) as e:
|
zrb/builtin/llm/tool/note.py
CHANGED
@@ -17,12 +17,12 @@ def read_long_term_note() -> str:
|
|
17
17
|
|
18
18
|
def write_long_term_note(content: str) -> str:
|
19
19
|
"""
|
20
|
-
Writes
|
21
|
-
|
22
|
-
|
20
|
+
Writes or overwrites the global long-term note for the user.
|
21
|
+
|
22
|
+
Use this to remember key user preferences, goals, or facts that should apply to all future conversations (e.g., "The user prefers TypeScript," "The user's favorite color is read"). This note is persistent across all projects.
|
23
23
|
|
24
24
|
Args:
|
25
|
-
content: The
|
25
|
+
content: The information to save. This will overwrite the entire note.
|
26
26
|
|
27
27
|
Returns:
|
28
28
|
A confirmation message.
|
@@ -52,19 +52,16 @@ def read_contextual_note(path: str | None = None) -> str:
|
|
52
52
|
|
53
53
|
def write_contextual_note(content: str, path: str | None = None) -> str:
|
54
54
|
"""
|
55
|
-
Writes a
|
56
|
-
The content of this note is intended to be read by the AI Agent in the future.
|
57
|
-
The content should contain contextual information.
|
55
|
+
Writes or overwrites a note for a specific file or directory.
|
58
56
|
|
59
|
-
|
57
|
+
Use this to save your findings, summaries, or conclusions about a specific part of the project. This note will be available in the future when you are working within that same context.
|
60
58
|
|
61
59
|
Args:
|
62
|
-
content: The
|
63
|
-
path: The file or directory path to associate the note with.
|
64
|
-
Defaults to the current working directory.
|
60
|
+
content: The information to save about the path. This overwrites any existing note for this path.
|
61
|
+
path (str, optional): The absolute file or directory path to associate the note with. Defaults to the current working directory.
|
65
62
|
|
66
63
|
Returns:
|
67
|
-
A confirmation message
|
64
|
+
A confirmation message.
|
68
65
|
"""
|
69
66
|
if path is None:
|
70
67
|
path = os.getcwd()
|
zrb/builtin/llm/tool/web.py
CHANGED
@@ -2,6 +2,9 @@ from collections.abc import Callable
|
|
2
2
|
from typing import Any
|
3
3
|
from urllib.parse import urljoin
|
4
4
|
|
5
|
+
from zrb.config.config import CFG
|
6
|
+
from zrb.config.llm_config import llm_config
|
7
|
+
|
5
8
|
_DEFAULT_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
|
6
9
|
|
7
10
|
|
@@ -28,26 +31,11 @@ async def open_web_page(url: str) -> dict[str, Any]:
|
|
28
31
|
return {"content": markdown_content, "links_on_page": links}
|
29
32
|
|
30
33
|
|
31
|
-
def create_search_internet_tool(
|
32
|
-
|
33
|
-
|
34
|
-
"""
|
35
|
-
Creates a tool that searches the internet using the SerpAPI Google Search
|
36
|
-
API.
|
37
|
-
|
38
|
-
This factory returns a function that can be used to find information on the
|
39
|
-
web. The generated tool is the primary way to answer general knowledge
|
40
|
-
questions or to find information on topics you are unfamiliar with.
|
41
|
-
|
42
|
-
Args:
|
43
|
-
serp_api_key (str): The API key for SerpAPI.
|
44
|
-
|
45
|
-
Returns:
|
46
|
-
Callable: A function that takes a search query and returns a list of
|
47
|
-
search results.
|
48
|
-
"""
|
34
|
+
def create_search_internet_tool() -> Callable:
|
35
|
+
if llm_config.default_search_internet_tool is not None:
|
36
|
+
return llm_config.default_search_internet_tool
|
49
37
|
|
50
|
-
def search_internet(query: str,
|
38
|
+
def search_internet(query: str, page: int = 1) -> dict[str, Any]:
|
51
39
|
"""
|
52
40
|
Performs an internet search using Google and returns a summary of the results.
|
53
41
|
|
@@ -56,7 +44,7 @@ def create_search_internet_tool(
|
|
56
44
|
|
57
45
|
Args:
|
58
46
|
query (str): The search query.
|
59
|
-
|
47
|
+
Page (int, optional): The search page number, default to 1.
|
60
48
|
|
61
49
|
Returns:
|
62
50
|
dict[str, Any]: A formatted string summarizing the search results,
|
@@ -64,17 +52,29 @@ def create_search_internet_tool(
|
|
64
52
|
"""
|
65
53
|
import requests
|
66
54
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
55
|
+
if CFG.SEARCH_INTERNET_METHOD.strip() == "serpapi" and CFG.SERPAPI_KEY != "":
|
56
|
+
response = requests.get(
|
57
|
+
"https://serpapi.com/search",
|
58
|
+
headers={"User-Agent": _DEFAULT_USER_AGENT},
|
59
|
+
params={
|
60
|
+
"q": query,
|
61
|
+
"start": (page - 1) * 10,
|
62
|
+
"hl": "en",
|
63
|
+
"safe": "off",
|
64
|
+
"api_key": CFG.SERPAPI_KEY,
|
65
|
+
},
|
66
|
+
)
|
67
|
+
else:
|
68
|
+
response = requests.get(
|
69
|
+
url=f"{CFG.SEARXNG_BASE_URL}/search",
|
70
|
+
headers={"User-Agent": _DEFAULT_USER_AGENT},
|
71
|
+
params={
|
72
|
+
"q": query,
|
73
|
+
"format": "json",
|
74
|
+
"pageno": page,
|
75
|
+
"safesearch": 0,
|
76
|
+
},
|
77
|
+
)
|
78
78
|
if response.status_code != 200:
|
79
79
|
raise Exception(
|
80
80
|
f"Error: Unable to retrieve search results (status code: {response.status_code})" # noqa
|
@@ -84,62 +84,6 @@ def create_search_internet_tool(
|
|
84
84
|
return search_internet
|
85
85
|
|
86
86
|
|
87
|
-
def search_wikipedia(query: str) -> dict[str, Any]:
|
88
|
-
"""
|
89
|
-
Searches for articles on Wikipedia.
|
90
|
-
|
91
|
-
This is a specialized search tool for querying Wikipedia. It's best for
|
92
|
-
when the user is asking for definitions, historical information, or
|
93
|
-
biographical details that are likely to be found on an encyclopedia.
|
94
|
-
|
95
|
-
Args:
|
96
|
-
query (str): The search term or question.
|
97
|
-
|
98
|
-
Returns:
|
99
|
-
dict[str, Any]: The raw JSON response from the Wikipedia API, containing a list of
|
100
|
-
search results.
|
101
|
-
"""
|
102
|
-
import requests
|
103
|
-
|
104
|
-
params = {"action": "query", "list": "search", "srsearch": query, "format": "json"}
|
105
|
-
response = requests.get(
|
106
|
-
"https://en.wikipedia.org/w/api.php",
|
107
|
-
headers={"User-Agent": _DEFAULT_USER_AGENT},
|
108
|
-
params=params,
|
109
|
-
)
|
110
|
-
return response.json()
|
111
|
-
|
112
|
-
|
113
|
-
def search_arxiv(query: str, num_results: int = 10) -> dict[str, Any]:
|
114
|
-
"""
|
115
|
-
Searches for academic papers and preprints on ArXiv.
|
116
|
-
|
117
|
-
Use this tool when the user's query is scientific or technical in nature
|
118
|
-
and they are likely looking for research papers, articles, or academic
|
119
|
-
publications.
|
120
|
-
|
121
|
-
Args:
|
122
|
-
query (str): The search query, which can include keywords, author
|
123
|
-
names, or titles.
|
124
|
-
num_results (int, optional): The maximum number of results to return.
|
125
|
-
Defaults to 10.
|
126
|
-
|
127
|
-
Returns:
|
128
|
-
dict[str, Any]: The raw XML response from the ArXiv API, containing a list of
|
129
|
-
matching papers.
|
130
|
-
"""
|
131
|
-
import requests
|
132
|
-
import xmltodict
|
133
|
-
|
134
|
-
params = {"search_query": f"all:{query}", "start": 0, "max_results": num_results}
|
135
|
-
response = requests.get(
|
136
|
-
"http://export.arxiv.org/api/query",
|
137
|
-
headers={"User-Agent": _DEFAULT_USER_AGENT},
|
138
|
-
params=params,
|
139
|
-
)
|
140
|
-
return xmltodict.parse(response.content)
|
141
|
-
|
142
|
-
|
143
87
|
async def _fetch_page_content(url: str) -> tuple[str, list[str]]:
|
144
88
|
"""Fetches the HTML content and all absolute links from a URL."""
|
145
89
|
try:
|