zrb 1.13.1__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 (117) hide show
  1. zrb/__init__.py +2 -6
  2. zrb/attr/type.py +10 -7
  3. zrb/builtin/__init__.py +2 -0
  4. zrb/builtin/git.py +12 -1
  5. zrb/builtin/group.py +31 -15
  6. zrb/builtin/http.py +7 -8
  7. zrb/builtin/llm/attachment.py +40 -0
  8. zrb/builtin/llm/chat_completion.py +287 -0
  9. zrb/builtin/llm/chat_session.py +130 -144
  10. zrb/builtin/llm/chat_session_cmd.py +288 -0
  11. zrb/builtin/llm/chat_trigger.py +78 -0
  12. zrb/builtin/llm/history.py +4 -4
  13. zrb/builtin/llm/llm_ask.py +218 -110
  14. zrb/builtin/llm/tool/api.py +74 -62
  15. zrb/builtin/llm/tool/cli.py +56 -21
  16. zrb/builtin/llm/tool/code.py +57 -47
  17. zrb/builtin/llm/tool/file.py +292 -255
  18. zrb/builtin/llm/tool/note.py +84 -0
  19. zrb/builtin/llm/tool/rag.py +25 -18
  20. zrb/builtin/llm/tool/search/__init__.py +1 -0
  21. zrb/builtin/llm/tool/search/brave.py +66 -0
  22. zrb/builtin/llm/tool/search/searxng.py +61 -0
  23. zrb/builtin/llm/tool/search/serpapi.py +61 -0
  24. zrb/builtin/llm/tool/sub_agent.py +53 -26
  25. zrb/builtin/llm/tool/web.py +94 -157
  26. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
  27. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
  28. zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
  29. zrb/builtin/searxng/config/settings.yml +5671 -0
  30. zrb/builtin/searxng/start.py +21 -0
  31. zrb/builtin/setup/latex/ubuntu.py +1 -0
  32. zrb/builtin/setup/ubuntu.py +1 -1
  33. zrb/builtin/shell/autocomplete/bash.py +4 -3
  34. zrb/builtin/shell/autocomplete/zsh.py +4 -3
  35. zrb/config/config.py +297 -79
  36. zrb/config/default_prompt/file_extractor_system_prompt.md +109 -9
  37. zrb/config/default_prompt/interactive_system_prompt.md +25 -28
  38. zrb/config/default_prompt/persona.md +1 -1
  39. zrb/config/default_prompt/repo_extractor_system_prompt.md +31 -31
  40. zrb/config/default_prompt/repo_summarizer_system_prompt.md +27 -8
  41. zrb/config/default_prompt/summarization_prompt.md +57 -16
  42. zrb/config/default_prompt/system_prompt.md +29 -25
  43. zrb/config/llm_config.py +129 -24
  44. zrb/config/llm_context/config.py +127 -90
  45. zrb/config/llm_context/config_parser.py +1 -7
  46. zrb/config/llm_context/workflow.py +81 -0
  47. zrb/config/llm_rate_limitter.py +100 -47
  48. zrb/context/any_shared_context.py +7 -1
  49. zrb/context/context.py +8 -2
  50. zrb/context/shared_context.py +6 -8
  51. zrb/group/any_group.py +12 -5
  52. zrb/group/group.py +67 -3
  53. zrb/input/any_input.py +5 -1
  54. zrb/input/base_input.py +18 -6
  55. zrb/input/option_input.py +13 -1
  56. zrb/input/text_input.py +7 -24
  57. zrb/runner/cli.py +21 -20
  58. zrb/runner/common_util.py +24 -19
  59. zrb/runner/web_route/task_input_api_route.py +5 -5
  60. zrb/runner/web_route/task_session_api_route.py +1 -4
  61. zrb/runner/web_util/user.py +7 -3
  62. zrb/session/any_session.py +12 -6
  63. zrb/session/session.py +39 -18
  64. zrb/task/any_task.py +24 -3
  65. zrb/task/base/context.py +17 -9
  66. zrb/task/base/execution.py +15 -8
  67. zrb/task/base/lifecycle.py +8 -4
  68. zrb/task/base/monitoring.py +12 -7
  69. zrb/task/base_task.py +69 -5
  70. zrb/task/base_trigger.py +12 -5
  71. zrb/task/llm/agent.py +130 -145
  72. zrb/task/llm/agent_runner.py +152 -0
  73. zrb/task/llm/config.py +45 -13
  74. zrb/task/llm/conversation_history.py +110 -29
  75. zrb/task/llm/conversation_history_model.py +4 -179
  76. zrb/task/llm/default_workflow/coding/workflow.md +41 -0
  77. zrb/task/llm/default_workflow/copywriting/workflow.md +68 -0
  78. zrb/task/llm/default_workflow/git/workflow.md +118 -0
  79. zrb/task/llm/default_workflow/golang/workflow.md +128 -0
  80. zrb/task/llm/default_workflow/html-css/workflow.md +135 -0
  81. zrb/task/llm/default_workflow/java/workflow.md +146 -0
  82. zrb/task/llm/default_workflow/javascript/workflow.md +158 -0
  83. zrb/task/llm/default_workflow/python/workflow.md +160 -0
  84. zrb/task/llm/default_workflow/researching/workflow.md +153 -0
  85. zrb/task/llm/default_workflow/rust/workflow.md +162 -0
  86. zrb/task/llm/default_workflow/shell/workflow.md +299 -0
  87. zrb/task/llm/file_replacement.py +206 -0
  88. zrb/task/llm/file_tool_model.py +57 -0
  89. zrb/task/llm/history_processor.py +206 -0
  90. zrb/task/llm/history_summarization.py +2 -192
  91. zrb/task/llm/print_node.py +192 -64
  92. zrb/task/llm/prompt.py +198 -153
  93. zrb/task/llm/subagent_conversation_history.py +41 -0
  94. zrb/task/llm/tool_confirmation_completer.py +41 -0
  95. zrb/task/llm/tool_wrapper.py +216 -55
  96. zrb/task/llm/workflow.py +76 -0
  97. zrb/task/llm_task.py +122 -70
  98. zrb/task/make_task.py +2 -3
  99. zrb/task/rsync_task.py +25 -10
  100. zrb/task/scheduler.py +4 -4
  101. zrb/util/attr.py +54 -39
  102. zrb/util/cli/markdown.py +12 -0
  103. zrb/util/cli/text.py +30 -0
  104. zrb/util/file.py +27 -11
  105. zrb/util/git.py +2 -2
  106. zrb/util/{llm/prompt.py → markdown.py} +2 -3
  107. zrb/util/string/conversion.py +1 -1
  108. zrb/util/truncate.py +23 -0
  109. zrb/util/yaml.py +204 -0
  110. zrb/xcom/xcom.py +10 -0
  111. {zrb-1.13.1.dist-info → zrb-1.21.33.dist-info}/METADATA +40 -20
  112. {zrb-1.13.1.dist-info → zrb-1.21.33.dist-info}/RECORD +114 -83
  113. {zrb-1.13.1.dist-info → zrb-1.21.33.dist-info}/WHEEL +1 -1
  114. zrb/task/llm/default_workflow/coding.md +0 -24
  115. zrb/task/llm/default_workflow/copywriting.md +0 -17
  116. zrb/task/llm/default_workflow/researching.md +0 -18
  117. {zrb-1.13.1.dist-info → zrb-1.21.33.dist-info}/entry_points.txt +0 -0
@@ -1,33 +1,68 @@
1
- import json
2
1
  import subprocess
2
+ import sys
3
3
 
4
+ if sys.version_info >= (3, 12):
5
+ from typing import TypedDict
6
+ else:
7
+ from typing_extensions import TypedDict
4
8
 
5
- def run_shell_command(command: str) -> str:
9
+
10
+ class ShellCommandResult(TypedDict):
11
+ """
12
+ Result of shell command execution
13
+
14
+ Attributes:
15
+ return_code: The return code, 0 indicating no error
16
+ stdout: Standard output
17
+ stderr: Standard error
6
18
  """
7
- Executes a shell command on the user's local machine and returns the output.
8
19
 
9
- This tool is powerful and should be used for tasks that require interacting with the command line, such as running scripts, managing system processes, or using command-line tools.
20
+ return_code: int
21
+ stdout: str
22
+ stderr: str
10
23
 
11
- **Security Warning:** This tool executes commands with the same permissions as the user running the assistant. Before executing any command that could modify files or system state (e.g., `git`, `npm`, `pip`, `docker`), you MUST explain what the command does and ask the user for confirmation.
24
+
25
+ def run_shell_command(command: str, timeout: int = 30) -> ShellCommandResult:
26
+ """
27
+ Executes a non-interactive shell command on the user's machine.
28
+
29
+ **EFFICIENCY TIP:**
30
+ Combine multiple shell commands into a single call using `&&` or `;` to save steps.
31
+ Example: `mkdir new_dir && cd new_dir && touch file.txt`
32
+
33
+ CRITICAL: This tool runs with user-level permissions. Explain commands that modify
34
+ the system (e.g., `git`, `pip`) and ask for confirmation.
35
+ IMPORTANT: Long-running processes should be run in the background (e.g., `command &`).
36
+
37
+ Example:
38
+ run_shell_command(command='ls -l', timeout=30)
12
39
 
13
40
  Args:
14
- command (str): The exact shell command to execute.
41
+ command (str): The exact shell command to be executed.
42
+ timeout (int): The maximum time in seconds to wait for the command to finish.
43
+ Defaults to 30.
15
44
 
16
45
  Returns:
17
- str: A JSON string containing return code, standard output (stdout),
18
- and standard error (stderr) from the command.
19
- Example: {"return_code": 0, "stdout": "ok", "stderr": ""}
46
+ dict: return_code, stdout, and stderr.
20
47
  """
21
- result = subprocess.run(
22
- command,
23
- shell=True,
24
- capture_output=True,
25
- text=True,
26
- )
27
- return json.dumps(
28
- {
29
- "return_code": result.returncode,
30
- "stdout": result.stdout,
31
- "stderr": result.stderr,
48
+ try:
49
+ result = subprocess.run(
50
+ command,
51
+ shell=True,
52
+ capture_output=True,
53
+ text=True,
54
+ timeout=timeout,
55
+ )
56
+ return {
57
+ "return_code": int(result.returncode),
58
+ "stdout": str(result.stdout or ""),
59
+ "stderr": str(result.stderr or ""),
60
+ }
61
+ except subprocess.TimeoutExpired as e:
62
+ stdout = e.stdout.decode() if isinstance(e.stdout, bytes) else (e.stdout or "")
63
+ stderr = e.stderr.decode() if isinstance(e.stderr, bytes) else (e.stderr or "")
64
+ return {
65
+ "return_code": 124,
66
+ "stdout": str(stdout),
67
+ "stderr": f"{stderr}\nError: Command timed out after {timeout} seconds".strip(),
32
68
  }
33
- )
@@ -6,6 +6,7 @@ from zrb.builtin.llm.tool.sub_agent import create_sub_agent_tool
6
6
  from zrb.config.config import CFG
7
7
  from zrb.config.llm_rate_limitter import llm_rate_limitter
8
8
  from zrb.context.any_context import AnyContext
9
+ from zrb.util.cli.style import stylize_faint
9
10
 
10
11
  _DEFAULT_EXTENSIONS = [
11
12
  "py",
@@ -49,36 +50,41 @@ _DEFAULT_EXTENSIONS = [
49
50
  async def analyze_repo(
50
51
  ctx: AnyContext,
51
52
  path: str,
52
- goal: str,
53
+ query: str,
53
54
  extensions: list[str] = _DEFAULT_EXTENSIONS,
54
55
  exclude_patterns: list[str] = DEFAULT_EXCLUDED_PATTERNS,
55
56
  extraction_token_threshold: int | None = None,
56
57
  summarization_token_threshold: int | None = None,
57
58
  ) -> str:
58
59
  """
59
- Performs a deep, goal-oriented analysis of a code repository or directory.
60
+ Analyzes a code repository or directory to answer a specific query.
60
61
 
61
- This powerful tool recursively reads all relevant files in a directory, extracts key information, and then summarizes that information in relation to a specific goal. It uses intelligent sub-agents for extraction and summarization, making it ideal for complex tasks that require a holistic understanding of a codebase.
62
+ CRITICAL: The query must contain ALL necessary context, instructions, and information.
63
+ The sub-agent performing the analysis does NOT share your current conversation
64
+ history, memory, or global context.
65
+ The quality of analysis depends entirely on the query. Vague queries yield poor
66
+ results.
62
67
 
63
- Use this tool for:
64
- - Understanding a large or unfamiliar codebase.
65
- - Generating high-level summaries of a project's architecture.
66
- - Performing a preliminary code review.
67
- - Creating documentation or diagrams (e.g., "Generate a Mermaid C4 diagram for this service").
68
- - Answering broad questions like "How does the authentication in this project work?".
68
+ IMPORTANT: This tool can be slow and expensive on large repositories. Use judiciously.
69
+
70
+ Example:
71
+ analyze_repo(
72
+ path='src/my_project',
73
+ query='Summarize the main functionalities by analyzing Python files.',
74
+ extensions=['py']
75
+ )
69
76
 
70
77
  Args:
71
- path (str): The path to the directory or repository to analyze.
72
- goal (str): A clear and specific description of what you want to achieve. A good goal is critical for getting a useful result. For example: "Understand the database schema by analyzing all the .sql files" or "Create a summary of all the API endpoints defined in the 'api' directory".
73
- extensions (list[str], optional): A list of file extensions to include in the analysis. Defaults to a comprehensive list of common code and configuration files.
74
- exclude_patterns (list[str], optional): A list of glob patterns for files and directories to exclude from the analysis. Defaults to common patterns like '.git', 'node_modules', and '.venv'.
75
- extraction_token_threshold (int, optional): The maximum token threshold for the extraction sub-agent.
76
- summarization_token_threshold (int, optional): The maximum token threshold for the summarization sub-agent.
78
+ ctx (AnyContext): The execution context.
79
+ path (str): Path to the directory or repository.
80
+ query (str): Clear and specific analysis question or goal.
81
+ extensions (list[str], optional): File extensions to include.
82
+ exclude_patterns (list[str], optional): Glob patterns to exclude.
83
+ extraction_token_threshold (int, optional): Token limit for extraction sub-agent.
84
+ summarization_token_threshold (int, optional): Token limit for summarization sub-agent.
77
85
 
78
86
  Returns:
79
- str: A detailed, markdown-formatted analysis and summary of the repository, tailored to the specified goal.
80
- Raises:
81
- Exception: If an error occurs during the analysis.
87
+ str: Detailed, markdown-formatted analysis and summary.
82
88
  """
83
89
  if extraction_token_threshold is None:
84
90
  extraction_token_threshold = CFG.LLM_REPO_ANALYSIS_EXTRACTION_TOKEN_THRESHOLD
@@ -88,23 +94,26 @@ async def analyze_repo(
88
94
  )
89
95
  abs_path = os.path.abspath(os.path.expanduser(path))
90
96
  file_metadatas = _get_file_metadatas(abs_path, extensions, exclude_patterns)
91
- ctx.print("Extraction")
97
+ ctx.print(stylize_faint(" 📝 Extraction"), plain=True)
92
98
  extracted_infos = await _extract_info(
93
99
  ctx,
94
100
  file_metadatas=file_metadatas,
95
- goal=goal,
101
+ query=query,
96
102
  token_limit=extraction_token_threshold,
97
103
  )
104
+ if len(extracted_infos) == 0:
105
+ raise RuntimeError(
106
+ "No info can be extracted, adjust extensions or exclude_patterns."
107
+ )
98
108
  if len(extracted_infos) == 1:
99
109
  return extracted_infos[0]
100
- ctx.print("Summarization")
101
110
  summarized_infos = extracted_infos
102
111
  while len(summarized_infos) > 1:
103
- ctx.print("Summarization")
112
+ ctx.print(stylize_faint(" 📝 Summarization"), plain=True)
104
113
  summarized_infos = await _summarize_info(
105
114
  ctx,
106
115
  extracted_infos=summarized_infos,
107
- goal=goal,
116
+ query=query,
108
117
  token_limit=summarization_token_threshold,
109
118
  )
110
119
  return summarized_infos[0]
@@ -122,11 +131,11 @@ def _get_file_metadatas(
122
131
  if not any(file.endswith(f".{ext}") for ext in extensions):
123
132
  continue
124
133
  file_path = os.path.join(root, file)
125
- if is_excluded(file_path, exclude_patterns):
126
- continue
127
134
  try:
135
+ rel_path = os.path.relpath(file_path, dir_path)
136
+ if is_excluded(rel_path, exclude_patterns):
137
+ continue
128
138
  with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
129
- rel_path = os.path.relpath(file_path, dir_path)
130
139
  metadata_list.append({"path": rel_path, "content": f.read()})
131
140
  except Exception as e:
132
141
  print(f"Error reading file {file_path}: {e}")
@@ -137,13 +146,15 @@ def _get_file_metadatas(
137
146
  async def _extract_info(
138
147
  ctx: AnyContext,
139
148
  file_metadatas: list[dict[str, str]],
140
- goal: str,
149
+ query: str,
141
150
  token_limit: int,
142
151
  ) -> list[str]:
143
152
  extract = create_sub_agent_tool(
144
153
  tool_name="extract",
145
154
  tool_description="extract",
146
155
  system_prompt=CFG.LLM_REPO_EXTRACTOR_SYSTEM_PROMPT,
156
+ auto_summarize=False,
157
+ remember_history=False,
147
158
  )
148
159
  extracted_infos = []
149
160
  content_buffer = []
@@ -155,7 +166,7 @@ async def _extract_info(
155
166
  file_str = json.dumps(file_obj)
156
167
  if current_token_count + llm_rate_limitter.count_token(file_str) > token_limit:
157
168
  if content_buffer:
158
- prompt = _create_extract_info_prompt(goal, content_buffer)
169
+ prompt = json.dumps(_create_extract_info_prompt(query, content_buffer))
159
170
  extracted_info = await extract(
160
171
  ctx, llm_rate_limitter.clip_prompt(prompt, token_limit)
161
172
  )
@@ -165,10 +176,9 @@ async def _extract_info(
165
176
  else:
166
177
  content_buffer.append(file_obj)
167
178
  current_token_count += llm_rate_limitter.count_token(file_str)
168
-
169
179
  # Process any remaining content in the buffer
170
180
  if content_buffer:
171
- prompt = _create_extract_info_prompt(goal, content_buffer)
181
+ prompt = json.dumps(_create_extract_info_prompt(query, content_buffer))
172
182
  extracted_info = await extract(
173
183
  ctx, llm_rate_limitter.clip_prompt(prompt, token_limit)
174
184
  )
@@ -176,25 +186,25 @@ async def _extract_info(
176
186
  return extracted_infos
177
187
 
178
188
 
179
- def _create_extract_info_prompt(goal: str, content_buffer: list[dict]) -> str:
180
- return json.dumps(
181
- {
182
- "main_assistant_goal": goal,
183
- "files": content_buffer,
184
- }
185
- )
189
+ def _create_extract_info_prompt(query: str, content_buffer: list[dict]) -> dict:
190
+ return {
191
+ "main_assistant_query": query,
192
+ "files": content_buffer,
193
+ }
186
194
 
187
195
 
188
196
  async def _summarize_info(
189
197
  ctx: AnyContext,
190
198
  extracted_infos: list[str],
191
- goal: str,
199
+ query: str,
192
200
  token_limit: int,
193
201
  ) -> list[str]:
194
202
  summarize = create_sub_agent_tool(
195
203
  tool_name="extract",
196
204
  tool_description="extract",
197
205
  system_prompt=CFG.LLM_REPO_SUMMARIZER_SYSTEM_PROMPT,
206
+ auto_summarize=False,
207
+ remember_history=False,
198
208
  )
199
209
  summarized_infos = []
200
210
  content_buffer = ""
@@ -202,7 +212,9 @@ async def _summarize_info(
202
212
  new_prompt = content_buffer + extracted_info
203
213
  if llm_rate_limitter.count_token(new_prompt) > token_limit:
204
214
  if content_buffer:
205
- prompt = _create_summarize_info_prompt(goal, content_buffer)
215
+ prompt = json.dumps(
216
+ _create_summarize_info_prompt(query, content_buffer)
217
+ )
206
218
  summarized_info = await summarize(
207
219
  ctx, llm_rate_limitter.clip_prompt(prompt, token_limit)
208
220
  )
@@ -213,7 +225,7 @@ async def _summarize_info(
213
225
 
214
226
  # Process any remaining content in the buffer
215
227
  if content_buffer:
216
- prompt = _create_summarize_info_prompt(goal, content_buffer)
228
+ prompt = json.dumps(_create_summarize_info_prompt(query, content_buffer))
217
229
  summarized_info = await summarize(
218
230
  ctx, llm_rate_limitter.clip_prompt(prompt, token_limit)
219
231
  )
@@ -221,10 +233,8 @@ async def _summarize_info(
221
233
  return summarized_infos
222
234
 
223
235
 
224
- def _create_summarize_info_prompt(goal: str, content_buffer: str) -> str:
225
- return json.dumps(
226
- {
227
- "main_assistant_goal": goal,
228
- "extracted_info": content_buffer,
229
- }
230
- )
236
+ def _create_summarize_info_prompt(query: str, content_buffer: str) -> dict:
237
+ return {
238
+ "main_assistant_query": query,
239
+ "extracted_info": content_buffer,
240
+ }