zrb 1.9.4__py3-none-any.whl → 1.9.6__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 (54) hide show
  1. zrb/__init__.py +8 -8
  2. zrb/__main__.py +1 -1
  3. zrb/builtin/__init__.py +0 -50
  4. zrb/builtin/llm/chat_session.py +1 -1
  5. zrb/builtin/llm/history.py +1 -1
  6. zrb/builtin/llm/llm_ask.py +3 -3
  7. zrb/builtin/llm/tool/api.py +19 -9
  8. zrb/builtin/llm/tool/cli.py +11 -5
  9. zrb/builtin/llm/tool/code.py +20 -20
  10. zrb/builtin/llm/tool/file.py +107 -155
  11. zrb/builtin/llm/tool/rag.py +28 -5
  12. zrb/builtin/llm/tool/sub_agent.py +12 -14
  13. zrb/builtin/llm/tool/web.py +46 -14
  14. zrb/builtin/todo.py +1 -1
  15. zrb/{llm_config.py → config/llm_config.py} +143 -158
  16. zrb/{llm_rate_limitter.py → config/llm_rate_limitter.py} +1 -1
  17. zrb/{runner → config}/web_auth_config.py +1 -1
  18. zrb/context/shared_context.py +1 -1
  19. zrb/input/text_input.py +1 -1
  20. zrb/runner/cli.py +2 -2
  21. zrb/runner/web_app.py +2 -2
  22. zrb/runner/web_route/docs_route.py +1 -1
  23. zrb/runner/web_route/error_page/serve_default_404.py +1 -1
  24. zrb/runner/web_route/error_page/show_error_page.py +1 -1
  25. zrb/runner/web_route/home_page/home_page_route.py +2 -2
  26. zrb/runner/web_route/login_api_route.py +1 -1
  27. zrb/runner/web_route/login_page/login_page_route.py +2 -2
  28. zrb/runner/web_route/logout_api_route.py +1 -1
  29. zrb/runner/web_route/logout_page/logout_page_route.py +2 -2
  30. zrb/runner/web_route/node_page/group/show_group_page.py +1 -1
  31. zrb/runner/web_route/node_page/node_page_route.py +1 -1
  32. zrb/runner/web_route/node_page/task/show_task_page.py +1 -1
  33. zrb/runner/web_route/refresh_token_api_route.py +1 -1
  34. zrb/runner/web_route/static/static_route.py +1 -1
  35. zrb/runner/web_route/task_input_api_route.py +1 -1
  36. zrb/runner/web_route/task_session_api_route.py +1 -1
  37. zrb/runner/web_util/cookie.py +1 -1
  38. zrb/runner/web_util/token.py +1 -1
  39. zrb/runner/web_util/user.py +1 -1
  40. zrb/session_state_logger/session_state_logger_factory.py +1 -1
  41. zrb/task/cmd_task.py +1 -1
  42. zrb/task/llm/agent.py +1 -1
  43. zrb/task/llm/config.py +1 -1
  44. zrb/task/llm/context_enrichment.py +2 -2
  45. zrb/task/llm/history_summarization.py +2 -2
  46. zrb/task/llm/prompt.py +1 -1
  47. zrb/task/llm_task.py +1 -1
  48. zrb/util/init_path.py +1 -1
  49. zrb-1.9.6.dist-info/METADATA +250 -0
  50. {zrb-1.9.4.dist-info → zrb-1.9.6.dist-info}/RECORD +53 -53
  51. zrb-1.9.4.dist-info/METADATA +0 -245
  52. /zrb/{config.py → config/config.py} +0 -0
  53. {zrb-1.9.4.dist-info → zrb-1.9.6.dist-info}/WHEEL +0 -0
  54. {zrb-1.9.4.dist-info → zrb-1.9.6.dist-info}/entry_points.txt +0 -0
@@ -5,8 +5,8 @@ import re
5
5
  from typing import Any, Dict, List, Optional
6
6
 
7
7
  from zrb.builtin.llm.tool.sub_agent import create_sub_agent_tool
8
+ from zrb.config.llm_rate_limitter import llm_rate_limitter
8
9
  from zrb.context.any_context import AnyContext
9
- from zrb.llm_rate_limitter import llm_rate_limitter
10
10
  from zrb.util.file import read_file, read_file_with_line_numbers, write_file
11
11
 
12
12
  _EXTRACT_INFO_FROM_FILE_SYSTEM_PROMPT = """
@@ -106,17 +106,22 @@ def list_files(
106
106
  include_hidden: bool = False,
107
107
  excluded_patterns: Optional[list[str]] = None,
108
108
  ) -> str:
109
- """List files/directories in a path, excluding specified patterns.
109
+ """
110
+ Lists the files and directories within a specified path.
111
+
112
+ This is a fundamental tool for exploring the file system. Use it to discover the structure of a directory, find specific files, or get a general overview of the project layout before performing other operations.
113
+
110
114
  Args:
111
- path (str): Path to list. Pass exactly as provided, including '~'. Defaults to ".".
112
- recursive (bool): List recursively. Defaults to True.
113
- include_hidden (bool): Include hidden files/dirs. Defaults to False.
114
- excluded_patterns (Optional[List[str]]): List of glob patterns to exclude.
115
- Defaults to a comprehensive list of common temporary/artifact patterns.
115
+ path (str, optional): The directory path to list. Defaults to the current directory (".").
116
+ recursive (bool, optional): If True, lists files and directories recursively. If False, lists only the top-level contents. Defaults to True.
117
+ include_hidden (bool, optional): If True, includes hidden files and directories (those starting with a dot). Defaults to False.
118
+ excluded_patterns (list[str], optional): A list of glob patterns to exclude from the listing. This is useful for ignoring irrelevant files like build artifacts or virtual environments. Defaults to a standard list of common exclusion patterns.
119
+
116
120
  Returns:
117
- str: JSON string: {"files": ["file1.txt", ...]} or {"error": "..."}
121
+ str: A JSON string containing a list of file and directory paths relative to the input path.
122
+ Example: '{"files": ["src/main.py", "README.md"]}'
118
123
  Raises:
119
- Exception: If an error occurs.
124
+ FileNotFoundError: If the specified path does not exist.
120
125
  """
121
126
  all_files: list[str] = []
122
127
  abs_path = os.path.abspath(os.path.expanduser(path))
@@ -214,19 +219,26 @@ def read_from_file(
214
219
  start_line: Optional[int] = None,
215
220
  end_line: Optional[int] = None,
216
221
  ) -> str:
217
- """Read file content (or specific lines) at a path, including line numbers.
218
- This tool can read both, text and pdf file.
222
+ """
223
+ Reads the content of a file, optionally from a specific start line to an end line.
224
+
225
+ This tool is essential for inspecting file contents. It can read both text and PDF files. The returned content is prefixed with line numbers, which is crucial for providing context when you need to modify the file later with the `apply_diff` tool.
226
+
227
+ Use this tool to:
228
+ - Examine the source code of a file.
229
+ - Read configuration files.
230
+ - Check the contents of a document.
231
+
219
232
  Args:
220
- path (str): Path to read. Pass exactly as provided, including '~'.
221
- start_line (Optional[int]): Starting line number (1-based).
222
- Defaults to None (start of file).
223
- end_line (Optional[int]): Ending line number (1-based, inclusive).
224
- Defaults to None (end of file).
233
+ path (str): The path to the file to read.
234
+ start_line (int, optional): The 1-based line number to start reading from. If omitted, reading starts from the beginning of the file.
235
+ end_line (int, optional): The 1-based line number to stop reading at (inclusive). If omitted, reads to the end of the file.
236
+
225
237
  Returns:
226
- str: JSON: {"path": "...", "content": "...", "start_line": N, ...} or {"error": "..."}
227
- The content includes line numbers.
238
+ str: A JSON object containing the file path, the requested content with line numbers, the start and end lines, and the total number of lines in the file.
239
+ Example: '{"path": "src/main.py", "content": "1: import os\n2: \n3: print(\"Hello, World!\")", "start_line": 1, "end_line": 3, "total_lines": 3}'
228
240
  Raises:
229
- Exception: If an error occurs.
241
+ FileNotFoundError: If the specified file does not exist.
230
242
  """
231
243
  abs_path = os.path.abspath(os.path.expanduser(path))
232
244
  # Check if file exists
@@ -267,26 +279,20 @@ def read_from_file(
267
279
  def write_to_file(
268
280
  path: str,
269
281
  content: str,
270
- line_count: int,
271
282
  ) -> str:
272
- """Write full content to a file. Creates/overwrites file.
283
+ """
284
+ Writes content to a file, completely overwriting it if it exists or creating it if it doesn't.
285
+
286
+ Use this tool to create new files or to replace the entire content of existing files. This is a destructive operation, so be certain of your actions. Always read the file first to understand its contents before overwriting it, unless you are creating a new file.
287
+
273
288
  Args:
274
- path (str): Path to write. Pass exactly as provided, including '~'.
275
- content (str): Full file content.
276
- MUST be complete, no truncation/omissions. Exclude line numbers.
277
- line_count (int): Number of lines in the provided content.
289
+ path (str): The path to the file to write to.
290
+ content (str): The full, complete content to be written to the file. Do not use partial content or omit any lines.
291
+
278
292
  Returns:
279
- str: JSON: {"success": true, "path": "f.txt", "warning": "..."} or {"error": "..."}
280
- Raises:
281
- Exception: If an error occurs.
293
+ str: A JSON object indicating success or failure.
294
+ Example: '{"success": true, "path": "new_file.txt"}'
282
295
  """
283
- actual_lines = len(content.splitlines())
284
- warning = None
285
- if actual_lines != line_count:
286
- warning = (
287
- f"Provided line_count ({line_count}) does not match actual "
288
- f"content lines ({actual_lines}) for file {path}"
289
- )
290
296
  try:
291
297
  abs_path = os.path.abspath(os.path.expanduser(path))
292
298
  # Ensure directory exists
@@ -295,8 +301,6 @@ def write_to_file(
295
301
  os.makedirs(directory, exist_ok=True)
296
302
  write_file(abs_path, content)
297
303
  result_data = {"success": True, "path": path}
298
- if warning:
299
- result_data["warning"] = warning
300
304
  return json.dumps(result_data)
301
305
  except (OSError, IOError) as e:
302
306
  raise OSError(f"Error writing file {path}: {e}")
@@ -310,17 +314,21 @@ def search_files(
310
314
  file_pattern: Optional[str] = None,
311
315
  include_hidden: bool = True,
312
316
  ) -> str:
313
- """Search files in a directory using regex, showing context.
317
+ """
318
+ Searches for a regular expression (regex) pattern within files in a specified directory.
319
+
320
+ This tool is invaluable for finding specific code, configuration, or text across multiple files. Use it to locate function definitions, variable assignments, error messages, or any other text pattern.
321
+
314
322
  Args:
315
- path (str): Path to search. Pass exactly as provided, including '~'.
316
- regex (str): Python regex pattern to search for.
317
- file_pattern (Optional[str]): Glob pattern to filter files
318
- (e.g., '*.py'). Defaults to None.
319
- include_hidden (bool): Include hidden files/dirs. Defaults to True.
323
+ path (str): The directory path to start the search from.
324
+ regex (str): The Python-compatible regular expression pattern to search for.
325
+ file_pattern (str, optional): A glob pattern to filter which files get searched (e.g., "*.py", "*.md"). If omitted, all files are searched.
326
+ include_hidden (bool, optional): If True, the search will include hidden files and directories. Defaults to True.
327
+
320
328
  Returns:
321
- str: JSON: {"summary": "...", "results": [{"file":"f.py", ...}]} or {"error": "..."}
329
+ str: A JSON object containing a summary of the search and a list of results. Each result includes the file path and a list of matches, with each match showing the line number, line content, and a few lines of context from before and after the match.
322
330
  Raises:
323
- Exception: If error occurs or regex is invalid.
331
+ ValueError: If the provided `regex` pattern is invalid.
324
332
  """
325
333
  try:
326
334
  pattern = re.compile(regex)
@@ -407,83 +415,70 @@ def _get_file_matches(
407
415
  raise RuntimeError(f"Unexpected error processing {file_path}: {e}")
408
416
 
409
417
 
410
- def apply_diff(
418
+ def replace_in_file(
411
419
  path: str,
412
- start_line: int,
413
- end_line: int,
414
- search_content: str,
415
- replace_content: str,
420
+ old_string: str,
421
+ new_string: str,
416
422
  ) -> str:
417
- """Apply a precise search/replace to a file based on line numbers and content.
418
- This tool enables you to update certain part of the file efficiently.
423
+ """
424
+ Replaces the first occurrence of a string in a file.
425
+
426
+ This tool is for making targeted modifications to a file. It is a single-step operation that is generally safer and more ergonomic than `write_to_file` for small changes.
427
+
428
+ To ensure the replacement is applied correctly and to avoid ambiguity, the `old_string` parameter should be a unique, multi-line string that includes context from before and after the code you want to change.
429
+
419
430
  Args:
420
- path (str): Path to modify. Pass exactly as provided, including '~'.
421
- start_line (int): The 1-based starting line number of the content to replace.
422
- end_line (int): The 1-based ending line number (inclusive) of the content to replace.
423
- search_content (str): The exact content expected to be found in the specified
424
- line range. Must exactly match file content including whitespace/indentation,
425
- excluding line numbers.
426
- replace_content (str): The new content to replace the search_content with.
427
- Excluding line numbers.
431
+ path (str): The path of the file to modify.
432
+ old_string (str): The exact, verbatim string to search for and replace. This should be a unique, multi-line block of text.
433
+ new_string (str): The new string that will replace the `old_string`.
434
+
428
435
  Returns:
429
- str: JSON: {"success": true, "path": "f.py"} or {"success": false, "error": "..."}
436
+ str: A JSON object indicating the success or failure of the operation.
430
437
  Raises:
431
- Exception: If an error occurs.
438
+ FileNotFoundError: If the specified file does not exist.
439
+ ValueError: If the `old_string` is not found in the file.
432
440
  """
433
441
  abs_path = os.path.abspath(os.path.expanduser(path))
434
442
  if not os.path.exists(abs_path):
435
443
  raise FileNotFoundError(f"File not found: {path}")
436
444
  try:
437
445
  content = read_file(abs_path)
438
- lines = content.splitlines()
439
- if start_line < 1 or end_line > len(lines) or start_line > end_line:
440
- raise ValueError(
441
- f"Invalid line range {start_line}-{end_line} for file with {len(lines)} lines"
442
- )
443
- original_content = "\n".join(lines[start_line - 1 : end_line])
444
- if original_content != search_content:
445
- error_message = (
446
- f"Search content does not match file content at "
447
- f"lines {start_line}-{end_line}.\n"
448
- f"Expected ({len(search_content.splitlines())} lines):\n"
449
- f"---\n{search_content}\n---\n"
450
- f"Actual ({len(lines[start_line-1:end_line])} lines):\n"
451
- f"---\n{original_content}\n---"
452
- )
453
- return json.dumps({"success": False, "path": path, "error": error_message})
454
- new_lines = (
455
- lines[: start_line - 1] + replace_content.splitlines() + lines[end_line:]
456
- )
457
- new_content = "\n".join(new_lines)
458
- if content.endswith("\n"):
459
- new_content += "\n"
446
+ if old_string not in content:
447
+ raise ValueError(f"old_string not found in file: {path}")
448
+ new_content = content.replace(old_string, new_string, 1)
460
449
  write_file(abs_path, new_content)
461
450
  return json.dumps({"success": True, "path": path})
462
451
  except ValueError as e:
463
- raise ValueError(f"Error parsing diff: {e}")
452
+ raise e
464
453
  except (OSError, IOError) as e:
465
- raise OSError(f"Error applying diff to {path}: {e}")
454
+ raise OSError(f"Error applying replacement to {path}: {e}")
466
455
  except Exception as e:
467
- raise RuntimeError(f"Unexpected error applying diff to {path}: {e}")
456
+ raise RuntimeError(f"Unexpected error applying replacement to {path}: {e}")
468
457
 
469
458
 
470
459
  async def analyze_file(
471
460
  ctx: AnyContext, path: str, query: str, token_limit: int = 40000
472
461
  ) -> str:
473
- """Analyze file using LLM capability to reduce context usage.
474
- Use this tool for:
475
- - summarization
476
- - outline/structure extraction
477
- - code review
478
- - other tasks
462
+ """
463
+ Performs a deep, goal-oriented analysis of a single file using a sub-agent.
464
+
465
+ This tool is ideal for complex questions about a single file that go beyond simple reading or searching. It uses a specialized sub-agent to analyze the file's content in relation to a specific query.
466
+
467
+ Use this tool to:
468
+ - Summarize the purpose and functionality of a script or configuration file.
469
+ - Extract the structure of a file (e.g., "List all the function names in this Python file").
470
+ - Perform a detailed code review of a specific file.
471
+ - Answer complex questions like, "How is the 'User' class used in this file?".
472
+
479
473
  Args:
480
- path (str): File path to be analyze. Pass exactly as provided, including '~'.
481
- query(str): Instruction to analyze the file
482
- token_limit(Optional[int]): Max token length to be taken from file
474
+ path (str): The path to the file to be analyzed.
475
+ query (str): A clear and specific question or instruction about what to analyze in the file.
476
+ token_limit (int, optional): The maximum token length of the file content to be passed to the analysis sub-agent.
477
+
483
478
  Returns:
484
- str: The analysis result
479
+ str: A detailed, markdown-formatted analysis of the file, tailored to the specified query.
485
480
  Raises:
486
- Exception: If an error occurs.
481
+ FileNotFoundError: If the specified file does not exist.
487
482
  """
488
483
  abs_path = os.path.abspath(os.path.expanduser(path))
489
484
  if not os.path.exists(abs_path):
@@ -504,38 +499,16 @@ async def analyze_file(
504
499
 
505
500
  def read_many_files(paths: List[str]) -> str:
506
501
  """
507
- Read and return the content of multiple files.
502
+ Reads and returns the full content of multiple files at once.
508
503
 
509
- This function is ideal for when you need to inspect the contents of
510
- several files at once. For each file path provided in the input list,
511
- it reads the entire file content. The result is a JSON string
512
- containing a dictionary where keys are the file paths and values are
513
- the corresponding file contents.
514
-
515
- Use this tool when you need a comprehensive view of multiple files,
516
- for example, to understand how different parts of a module interact,
517
- to check configurations across various files, or to gather context
518
- before making widespread changes.
504
+ This tool is highly efficient for gathering context from several files simultaneously. Use it when you need to understand how different files in a project relate to each other, or when you need to inspect a set of related configuration or source code files.
519
505
 
520
506
  Args:
521
- paths (List[str]): A list of absolute or relative paths to the
522
- files you want to read. It is crucial to
523
- provide accurate paths. Use the `list_files`
524
- tool if you are unsure about the exact file
525
- locations.
507
+ paths (List[str]): A list of paths to the files you want to read. It is crucial to provide accurate paths. Use the `list_files` tool first if you are unsure about the exact file locations.
526
508
 
527
509
  Returns:
528
- str: A JSON string representing a dictionary where each key is a
529
- file path and the corresponding value is the content of that
530
- file. If a file cannot be read, its entry in the dictionary
531
- will contain an error message.
532
- Example:
533
- {
534
- "results": {
535
- "path/to/file1.py": "...",
536
- "path/to/file2.txt": "..."
537
- }
538
- }
510
+ str: A JSON object where keys are the file paths and values are their corresponding contents, prefixed with line numbers. If a file cannot be read, its value will be an error message.
511
+ Example: '{"results": {"src/api.py": "1: import ...", "config.yaml": "1: key: value"}}'
539
512
  """
540
513
  results = {}
541
514
  for path in paths:
@@ -552,42 +525,18 @@ def read_many_files(paths: List[str]) -> str:
552
525
 
553
526
  def write_many_files(files: Dict[str, str]) -> str:
554
527
  """
555
- Write content to multiple files simultaneously.
556
-
557
- This function allows you to create, overwrite, or update multiple
558
- files in a single operation. You provide a dictionary where each
559
- key is a file path and the corresponding value is the content to be
560
- written to that file. This is particularly useful for applying
561
- changes across a project, such as refactoring code, updating
562
- configuration files, or creating a set of new files from a template.
528
+ Writes content to multiple files in a single, atomic operation.
563
529
 
564
- Each file is handled as a complete replacement of its content. If the
565
- file does not exist, it will be created. If it already exists, its
530
+ This tool is for applying widespread changes to a project, such as creating a set of new files from a template, updating multiple configuration files, or performing a large-scale refactoring.
566
531
 
567
- entire content will be overwritten with the new content you provide.
568
- Therefore, it is essential to provide the full, intended content for
569
- each file.
532
+ Each file's content is completely replaced. If a file does not exist, it will be created. If it exists, its current content will be entirely overwritten. Therefore, you must provide the full, intended content for each file.
570
533
 
571
534
  Args:
572
- files (Dict[str, str]): A dictionary where keys are the file paths
573
- (absolute or relative) and values are the
574
- complete contents to be written to those
575
- files.
535
+ files (Dict[str, str]): A dictionary where keys are the file paths and values are the complete contents to be written to those files.
576
536
 
577
537
  Returns:
578
- str: A JSON string summarizing the operation. It includes a list
579
- of successfully written files and a list of files that
580
- failed, along with the corresponding error messages.
581
- Example:
582
- {
583
- "success": [
584
- "path/to/file1.py",
585
- "path/to/file2.txt"
586
- ],
587
- "errors": {
588
- "path/to/problematic/file.py": "Error message"
589
- }
590
- }
538
+ str: A JSON object summarizing the operation, listing successfully written files and any files that failed, along with corresponding error messages.
539
+ Example: '{"success": ["file1.py", "file2.txt"], "errors": {}}'
591
540
  """
592
541
  success = []
593
542
  errors = {}
@@ -602,3 +551,6 @@ def write_many_files(files: Dict[str, str]) -> str:
602
551
  except Exception as e:
603
552
  errors[path] = f"Error writing file: {e}"
604
553
  return json.dumps({"success": success, "errors": errors})
554
+
555
+
556
+ apply_diff = replace_in_file
@@ -8,7 +8,7 @@ from textwrap import dedent
8
8
 
9
9
  import ulid
10
10
 
11
- from zrb.config import CFG
11
+ from zrb.config.config import CFG
12
12
  from zrb.util.cli.style import stylize_error, stylize_faint
13
13
  from zrb.util.file import read_file
14
14
 
@@ -43,10 +43,33 @@ def create_rag_from_directory(
43
43
  openai_base_url: str | None = None,
44
44
  openai_embedding_model: str | None = None,
45
45
  ):
46
- """Create a RAG retrieval tool function for LLM use.
47
- This factory configures and returns an async function that takes a query,
48
- updates a vector database if needed, performs a similarity search,
49
- and returns relevant document chunks.
46
+ """
47
+ Creates a powerful Retrieval-Augmented Generation (RAG) tool for querying a local knowledge base.
48
+
49
+ This factory function generates a tool that can perform semantic searches over a directory of documents. It automatically indexes the documents into a vector database, keeping it updated as files change. The generated tool is ideal for answering questions based on a specific set of documents, such as project documentation, research papers, or internal wikis.
50
+
51
+ The created tool will:
52
+ 1. Monitor a specified directory for file changes.
53
+ 2. Automatically update a vector database (ChromaDB) with the latest content.
54
+ 3. Accept a user query, embed it, and perform a similarity search against the document vectors.
55
+ 4. Return the most relevant document chunks that match the query.
56
+
57
+ Args:
58
+ tool_name (str): The name for the generated RAG tool (e.g., "search_project_docs").
59
+ tool_description (str): A clear description of what the generated tool does and when to use it (e.g., "Searches the project's technical documentation to answer questions.").
60
+ document_dir_path (str, optional): The path to the directory containing the documents to be indexed.
61
+ vector_db_path (str, optional): The path to store the ChromaDB vector database.
62
+ vector_db_collection (str, optional): The name of the collection within the vector database.
63
+ chunk_size (int, optional): The size of text chunks for embedding.
64
+ overlap (int, optional): The overlap between text chunks.
65
+ max_result_count (int, optional): The maximum number of search results to return.
66
+ file_reader (list[RAGFileReader], optional): Custom file readers for specific file types.
67
+ openai_api_key (str, optional): OpenAI API key for embeddings.
68
+ openai_base_url (str, optional): OpenAI base URL for embeddings.
69
+ openai_embedding_model (str, optional): The embedding model to use.
70
+
71
+ Returns:
72
+ Callable: An asynchronous function that serves as the RAG tool.
50
73
  """
51
74
 
52
75
  async def retrieve(query: str) -> str:
@@ -29,25 +29,23 @@ def create_sub_agent_tool(
29
29
  mcp_servers: list["MCPServer"] = [],
30
30
  ) -> Callable[[AnyContext, str], Coroutine[Any, Any, str]]:
31
31
  """
32
- Create an LLM "sub-agent" tool function for use by a main LLM agent.
32
+ Creates a "tool that is another AI agent," capable of handling complex, multi-step sub-tasks.
33
33
 
34
- This factory configures and returns an async function that, when called
35
- by the main agent, instantiates and runs a sub-agent (the sub-agent)
36
- with a given query and returns the sub-agent's final response.
34
+ This powerful factory function generates a tool that, when used, spins up a temporary, specialized AI agent. This "sub-agent" has its own system prompt, tools, and context, allowing it to focus exclusively on accomplishing the task it's given without being distracted by the main conversation.
35
+
36
+ This is ideal for delegating complex tasks like analyzing a file or a repository.
37
37
 
38
38
  Args:
39
- tool_name: The name of the tool for the main agent.
40
- tool_description: The description of the tool for the main agent.
41
- sub_agent_system_prompt: The system prompt for the sub-agent.
42
- sub_agent_model: The model for the sub-agent (optional).
43
- sub_agent_model_settings: Model settings for the sub-agent (optional).
44
- sub_agent_tools: A list of tools (Tool instances or callables) for the
45
- sub-agent (optional).
46
- sub_agent_mcp_servers: A list of MCP servers for the sub-agent (optional).
39
+ tool_name (str): The name for the generated sub-agent tool.
40
+ tool_description (str): A clear description of the sub-agent's purpose and when to use it.
41
+ system_prompt (str, optional): The system prompt that will guide the sub-agent's behavior.
42
+ model (str | Model, optional): The language model the sub-agent will use.
43
+ model_settings (ModelSettings, optional): Specific settings for the sub-agent's model.
44
+ tools (list, optional): A list of tools that will be exclusively available to the sub-agent.
45
+ mcp_servers (list, optional): A list of MCP servers for the sub-agent.
47
46
 
48
47
  Returns:
49
- An async callable function that takes a context and a query string,
50
- runs the sub-agent, and returns the sub-agent's final message content.
48
+ Callable: An asynchronous function that serves as the sub-agent tool. When called, it runs the sub-agent with a given query and returns its final result.
51
49
  """
52
50
 
53
51
  async def run_sub_agent(ctx: AnyContext, query: str) -> str:
@@ -3,11 +3,16 @@ from collections.abc import Callable
3
3
 
4
4
 
5
5
  async def open_web_page(url: str) -> str:
6
- """Get parsed text content and links from a web page URL.
6
+ """
7
+ Fetches and parses the textual content of a given web page URL.
8
+
9
+ Use this tool to "read" a web page. It strips away HTML tags, scripts, and other non-textual elements to provide the clean text content. It also extracts any hyperlinks found on the page. This is useful when you need to understand the content of a specific URL that you have discovered through a search or from another source.
10
+
7
11
  Args:
8
- url (str): The URL of the web page to open.
12
+ url (str): The full URL of the web page to open (e.g., "https://example.com/article").
13
+
9
14
  Returns:
10
- str: JSON: {"content": "parsed text content", "links_on_page": ["url1", ...]}
15
+ str: A JSON object containing the cleaned text `content` of the page and a list of `links_on_page`.
11
16
  """
12
17
 
13
18
  async def get_page_content(page_url: str):
@@ -57,13 +62,30 @@ async def open_web_page(url: str) -> str:
57
62
 
58
63
 
59
64
  def create_search_internet_tool(serp_api_key: str) -> Callable[[str, int], str]:
65
+ """
66
+ Creates a tool that searches the internet using the SerpAPI Google Search API.
67
+
68
+ This factory returns a function that can be used to find information on the web. The generated tool is the primary way to answer general knowledge questions or to find information on topics you are unfamiliar with.
69
+
70
+ Args:
71
+ serp_api_key (str): The API key for SerpAPI.
72
+
73
+ Returns:
74
+ Callable: A function that takes a search query and returns a list of search results.
75
+ """
76
+
60
77
  def search_internet(query: str, num_results: int = 10) -> str:
61
- """Search the internet Google Search and return parsed results.
78
+ """
79
+ Performs an internet search using Google and returns a summary of the results.
80
+
81
+ Use this tool to find information on the web, answer general knowledge questions, or research topics.
82
+
62
83
  Args:
63
- query (str): Search query.
64
- num_results (int): Search result count. Defaults to 10.
84
+ query (str): The search query.
85
+ num_results (int, optional): The desired number of search results. Defaults to 10.
86
+
65
87
  Returns:
66
- str: JSON: {"content": "parsed text content", "links_on_page": ["url1", ...]}
88
+ str: A JSON object containing the parsed text content from the search results page.
67
89
  """
68
90
  import requests
69
91
 
@@ -90,11 +112,16 @@ def create_search_internet_tool(serp_api_key: str) -> Callable[[str, int], str]:
90
112
 
91
113
 
92
114
  def search_wikipedia(query: str) -> str:
93
- """Search Wikipedia using its API.
115
+ """
116
+ Searches for articles on Wikipedia.
117
+
118
+ This is a specialized search tool for querying Wikipedia. It's best for when the user is asking for definitions, historical information, or biographical details that are likely to be found on an encyclopedia.
119
+
94
120
  Args:
95
- query (str): Search query.
121
+ query (str): The search term or question.
122
+
96
123
  Returns:
97
- str: JSON from Wikipedia API: {"batchcomplete": ..., "query": {"search": [...]}}
124
+ str: The raw JSON response from the Wikipedia API, containing a list of search results.
98
125
  """
99
126
  import requests
100
127
 
@@ -104,12 +131,17 @@ def search_wikipedia(query: str) -> str:
104
131
 
105
132
 
106
133
  def search_arxiv(query: str, num_results: int = 10) -> str:
107
- """Search ArXiv for papers using its API.
134
+ """
135
+ Searches for academic papers and preprints on ArXiv.
136
+
137
+ Use this tool when the user's query is scientific or technical in nature and they are likely looking for research papers, articles, or academic publications.
138
+
108
139
  Args:
109
- query (str): Search query.
110
- num_results (int): Search result count. Defaults to 10.
140
+ query (str): The search query, which can include keywords, author names, or titles.
141
+ num_results (int, optional): The maximum number of results to return. Defaults to 10.
142
+
111
143
  Returns:
112
- str: XML string from ArXiv API containing search results.
144
+ str: The raw XML response from the ArXiv API, containing a list of matching papers.
113
145
  """
114
146
  import requests
115
147
 
zrb/builtin/todo.py CHANGED
@@ -4,7 +4,7 @@ import os
4
4
  from typing import Any
5
5
 
6
6
  from zrb.builtin.group import todo_group
7
- from zrb.config import CFG
7
+ from zrb.config.config import CFG
8
8
  from zrb.context.any_context import AnyContext
9
9
  from zrb.input.str_input import StrInput
10
10
  from zrb.input.text_input import TextInput