zrb 1.13.1__py3-none-any.whl → 1.21.17__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 (105) hide show
  1. zrb/__init__.py +2 -6
  2. zrb/attr/type.py +8 -8
  3. zrb/builtin/__init__.py +2 -0
  4. zrb/builtin/group.py +31 -15
  5. zrb/builtin/http.py +7 -8
  6. zrb/builtin/llm/attachment.py +40 -0
  7. zrb/builtin/llm/chat_session.py +130 -144
  8. zrb/builtin/llm/chat_session_cmd.py +226 -0
  9. zrb/builtin/llm/chat_trigger.py +73 -0
  10. zrb/builtin/llm/history.py +4 -4
  11. zrb/builtin/llm/llm_ask.py +218 -110
  12. zrb/builtin/llm/tool/api.py +74 -62
  13. zrb/builtin/llm/tool/cli.py +35 -16
  14. zrb/builtin/llm/tool/code.py +49 -47
  15. zrb/builtin/llm/tool/file.py +262 -251
  16. zrb/builtin/llm/tool/note.py +84 -0
  17. zrb/builtin/llm/tool/rag.py +25 -18
  18. zrb/builtin/llm/tool/sub_agent.py +29 -22
  19. zrb/builtin/llm/tool/web.py +135 -143
  20. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
  21. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
  22. zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
  23. zrb/builtin/searxng/config/settings.yml +5671 -0
  24. zrb/builtin/searxng/start.py +21 -0
  25. zrb/builtin/setup/latex/ubuntu.py +1 -0
  26. zrb/builtin/setup/ubuntu.py +1 -1
  27. zrb/builtin/shell/autocomplete/bash.py +4 -3
  28. zrb/builtin/shell/autocomplete/zsh.py +4 -3
  29. zrb/config/config.py +255 -78
  30. zrb/config/default_prompt/file_extractor_system_prompt.md +109 -9
  31. zrb/config/default_prompt/interactive_system_prompt.md +24 -30
  32. zrb/config/default_prompt/persona.md +1 -1
  33. zrb/config/default_prompt/repo_extractor_system_prompt.md +31 -31
  34. zrb/config/default_prompt/repo_summarizer_system_prompt.md +27 -8
  35. zrb/config/default_prompt/summarization_prompt.md +8 -13
  36. zrb/config/default_prompt/system_prompt.md +36 -30
  37. zrb/config/llm_config.py +129 -24
  38. zrb/config/llm_context/config.py +127 -90
  39. zrb/config/llm_context/config_parser.py +1 -7
  40. zrb/config/llm_context/workflow.py +81 -0
  41. zrb/config/llm_rate_limitter.py +89 -45
  42. zrb/context/any_shared_context.py +7 -1
  43. zrb/context/context.py +8 -2
  44. zrb/context/shared_context.py +6 -8
  45. zrb/group/any_group.py +12 -5
  46. zrb/group/group.py +67 -3
  47. zrb/input/any_input.py +5 -1
  48. zrb/input/base_input.py +18 -6
  49. zrb/input/text_input.py +7 -24
  50. zrb/runner/cli.py +21 -20
  51. zrb/runner/common_util.py +24 -19
  52. zrb/runner/web_route/task_input_api_route.py +5 -5
  53. zrb/runner/web_route/task_session_api_route.py +1 -4
  54. zrb/runner/web_util/user.py +7 -3
  55. zrb/session/any_session.py +12 -6
  56. zrb/session/session.py +39 -18
  57. zrb/task/any_task.py +24 -3
  58. zrb/task/base/context.py +17 -9
  59. zrb/task/base/execution.py +15 -8
  60. zrb/task/base/lifecycle.py +8 -4
  61. zrb/task/base/monitoring.py +12 -7
  62. zrb/task/base_task.py +69 -5
  63. zrb/task/base_trigger.py +12 -5
  64. zrb/task/llm/agent.py +138 -52
  65. zrb/task/llm/config.py +45 -13
  66. zrb/task/llm/conversation_history.py +76 -6
  67. zrb/task/llm/conversation_history_model.py +0 -168
  68. zrb/task/llm/default_workflow/coding/workflow.md +41 -0
  69. zrb/task/llm/default_workflow/copywriting/workflow.md +68 -0
  70. zrb/task/llm/default_workflow/git/workflow.md +118 -0
  71. zrb/task/llm/default_workflow/golang/workflow.md +128 -0
  72. zrb/task/llm/default_workflow/html-css/workflow.md +135 -0
  73. zrb/task/llm/default_workflow/java/workflow.md +146 -0
  74. zrb/task/llm/default_workflow/javascript/workflow.md +158 -0
  75. zrb/task/llm/default_workflow/python/workflow.md +160 -0
  76. zrb/task/llm/default_workflow/researching/workflow.md +153 -0
  77. zrb/task/llm/default_workflow/rust/workflow.md +162 -0
  78. zrb/task/llm/default_workflow/shell/workflow.md +299 -0
  79. zrb/task/llm/file_replacement.py +206 -0
  80. zrb/task/llm/file_tool_model.py +57 -0
  81. zrb/task/llm/history_summarization.py +22 -35
  82. zrb/task/llm/history_summarization_tool.py +24 -0
  83. zrb/task/llm/print_node.py +182 -63
  84. zrb/task/llm/prompt.py +213 -153
  85. zrb/task/llm/tool_wrapper.py +210 -53
  86. zrb/task/llm/workflow.py +76 -0
  87. zrb/task/llm_task.py +98 -47
  88. zrb/task/make_task.py +2 -3
  89. zrb/task/rsync_task.py +25 -10
  90. zrb/task/scheduler.py +4 -4
  91. zrb/util/attr.py +50 -40
  92. zrb/util/cli/markdown.py +12 -0
  93. zrb/util/cli/text.py +30 -0
  94. zrb/util/file.py +27 -11
  95. zrb/util/{llm/prompt.py → markdown.py} +2 -3
  96. zrb/util/string/conversion.py +1 -1
  97. zrb/util/truncate.py +23 -0
  98. zrb/util/yaml.py +204 -0
  99. {zrb-1.13.1.dist-info → zrb-1.21.17.dist-info}/METADATA +40 -20
  100. {zrb-1.13.1.dist-info → zrb-1.21.17.dist-info}/RECORD +102 -79
  101. {zrb-1.13.1.dist-info → zrb-1.21.17.dist-info}/WHEEL +1 -1
  102. zrb/task/llm/default_workflow/coding.md +0 -24
  103. zrb/task/llm/default_workflow/copywriting.md +0 -17
  104. zrb/task/llm/default_workflow/researching.md +0 -18
  105. {zrb-1.13.1.dist-info → zrb-1.21.17.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,226 @@
1
+ import os
2
+ import subprocess
3
+
4
+ from zrb.context.any_context import AnyContext
5
+ from zrb.task.llm.workflow import get_available_workflows
6
+ from zrb.util.cli.markdown import render_markdown
7
+ from zrb.util.cli.style import (
8
+ stylize_blue,
9
+ stylize_bold_yellow,
10
+ stylize_error,
11
+ stylize_faint,
12
+ )
13
+ from zrb.util.file import write_file
14
+ from zrb.util.markdown import make_markdown_section
15
+
16
+ MULTILINE_START_CMD = ["/multi", "/multiline"]
17
+ MULTILINE_END_CMD = ["/end"]
18
+ QUIT_CMD = ["/bye", "/quit", "/q", "/exit"]
19
+ WORKFLOW_CMD = ["/workflow", "/workflows", "/skill", "/skills", "/w"]
20
+ SAVE_CMD = ["/save", "/s"]
21
+ ATTACHMENT_CMD = ["/attach", "/attachment", "/attachments"]
22
+ YOLO_CMD = ["/yolo"]
23
+ HELP_CMD = ["/help", "/info"]
24
+ ADD_SUB_CMD = ["add"]
25
+ SET_SUB_CMD = ["set"]
26
+ CLEAR_SUB_CMD = ["clear"]
27
+ RUN_CLI_CMD = ["/run", "/exec", "/execute", "/cmd", "/cli", "!"]
28
+
29
+
30
+ def print_current_yolo_mode(
31
+ ctx: AnyContext, current_yolo_mode_value: str | bool
32
+ ) -> None:
33
+ yolo_mode_str = (
34
+ current_yolo_mode_value if current_yolo_mode_value != "" else "*Not Set*"
35
+ )
36
+ ctx.print(render_markdown(f"🎲 Current YOLO mode: {yolo_mode_str}"), plain=True)
37
+ ctx.print("", plain=True)
38
+
39
+
40
+ def print_current_attachments(ctx: AnyContext, current_attachments_value: str) -> None:
41
+ attachments_str = (
42
+ current_attachments_value if current_attachments_value != "" else "*Not Set*"
43
+ )
44
+ ctx.print(render_markdown(f"📎 Current attachments: {attachments_str}"), plain=True)
45
+ ctx.print("", plain=True)
46
+
47
+
48
+ def print_current_workflows(ctx: AnyContext, current_workflows_value: str) -> None:
49
+ available_workflows = get_available_workflows()
50
+ available_workflows_str = (
51
+ ", ".join(sorted([workflow_name for workflow_name in available_workflows]))
52
+ if len(available_workflows) > 0
53
+ else "*No Available Workflow*"
54
+ )
55
+ current_workflows_str = (
56
+ current_workflows_value
57
+ if current_workflows_value != ""
58
+ else "*No Active Workflow*"
59
+ )
60
+ ctx.print(
61
+ render_markdown(
62
+ "\n".join(
63
+ [
64
+ f"- 🔄 Current workflows : {current_workflows_str}",
65
+ f"- 📚 Available workflows : {available_workflows_str}",
66
+ ]
67
+ )
68
+ ),
69
+ plain=True,
70
+ )
71
+ ctx.print("", plain=True)
72
+
73
+
74
+ def save_final_result(ctx: AnyContext, user_input: str, final_result: str) -> None:
75
+ save_path = get_command_param(user_input, SAVE_CMD)
76
+ save_path = os.path.expanduser(save_path)
77
+ if os.path.exists(save_path):
78
+ ctx.print(
79
+ stylize_error(f"Cannot save to existing file: {save_path}"),
80
+ plain=True,
81
+ )
82
+ return
83
+ write_file(save_path, final_result)
84
+ ctx.print(f"Response saved to {save_path}", plain=True)
85
+
86
+
87
+ def run_cli_command(ctx: AnyContext, user_input: str) -> None:
88
+ command = get_command_param(user_input, RUN_CLI_CMD)
89
+ result = subprocess.run(
90
+ command,
91
+ shell=True,
92
+ capture_output=True,
93
+ text=True,
94
+ )
95
+ ctx.print(
96
+ render_markdown(
97
+ make_markdown_section(
98
+ f"`{command}`",
99
+ "\n".join(
100
+ [
101
+ make_markdown_section("📤 Stdout", result.stdout, as_code=True),
102
+ make_markdown_section("🚫 Stderr", result.stderr, as_code=True),
103
+ make_markdown_section(
104
+ "🎯 Return code", f"Return Code: {result.returncode}"
105
+ ),
106
+ ]
107
+ ),
108
+ )
109
+ ),
110
+ plain=True,
111
+ )
112
+ ctx.print("", plain=True)
113
+
114
+
115
+ def get_new_yolo_mode(old_yolo_mode: str | bool, user_input: str) -> str | bool:
116
+ new_yolo_mode = get_command_param(user_input, YOLO_CMD)
117
+ if new_yolo_mode != "":
118
+ return new_yolo_mode
119
+ return old_yolo_mode
120
+
121
+
122
+ def get_new_attachments(old_attachment: str, user_input: str) -> str:
123
+ if not is_command_match(user_input, ATTACHMENT_CMD):
124
+ return old_attachment
125
+ if is_command_match(user_input, ATTACHMENT_CMD, SET_SUB_CMD):
126
+ return get_command_param(user_input, ATTACHMENT_CMD, SET_SUB_CMD)
127
+ if is_command_match(user_input, ATTACHMENT_CMD, CLEAR_SUB_CMD):
128
+ return ""
129
+ if is_command_match(user_input, ATTACHMENT_CMD, ADD_SUB_CMD):
130
+ new_attachment = get_command_param(user_input, ATTACHMENT_CMD, ADD_SUB_CMD)
131
+ return ",".join([old_attachment, new_attachment])
132
+ return old_attachment
133
+
134
+
135
+ def get_new_workflows(old_workflow: str, user_input: str) -> str:
136
+ if not is_command_match(user_input, WORKFLOW_CMD):
137
+ return old_workflow
138
+ if is_command_match(user_input, WORKFLOW_CMD, SET_SUB_CMD):
139
+ return get_command_param(user_input, WORKFLOW_CMD, SET_SUB_CMD)
140
+ if is_command_match(user_input, WORKFLOW_CMD, CLEAR_SUB_CMD):
141
+ return ""
142
+ if is_command_match(user_input, WORKFLOW_CMD, ADD_SUB_CMD):
143
+ new_workflow = get_command_param(user_input, WORKFLOW_CMD, ADD_SUB_CMD)
144
+ return ",".join([old_workflow, new_workflow])
145
+ return old_workflow
146
+
147
+
148
+ def get_command_param(user_input: str, *cmd_patterns: list[str]) -> str:
149
+ if not is_command_match(user_input, *cmd_patterns):
150
+ return ""
151
+ parts = [part for part in user_input.split(" ") if part.strip() != ""]
152
+ if len(parts) <= len(cmd_patterns):
153
+ return ""
154
+ params = parts[len(cmd_patterns) :]
155
+ return " ".join(params)
156
+
157
+
158
+ def is_command_match(user_input: str, *cmd_patterns: list[str]) -> bool:
159
+ parts = [part for part in user_input.split(" ") if part.strip() != ""]
160
+ if len(cmd_patterns) > len(parts):
161
+ return False
162
+ for index, cmd_pattern in enumerate(cmd_patterns):
163
+ part = parts[index]
164
+ if part.lower() not in cmd_pattern:
165
+ return False
166
+ return True
167
+
168
+
169
+ def print_commands(ctx: AnyContext):
170
+ """
171
+ Displays the available chat session commands to the user.
172
+ Args:
173
+ ctx: The context object for the task.
174
+ """
175
+ ctx.print(
176
+ "\n".join(
177
+ [
178
+ _show_command("/bye", "Quit from chat session"),
179
+ _show_command("/multi", "Start multiline input"),
180
+ _show_command("/end", "End multiline input"),
181
+ _show_command("/attachment", "Show current attachment"),
182
+ _show_subcommand("add", "<new-attachment>", "Attach a file"),
183
+ _show_subcommand(
184
+ "set", "<attachment1,attachment2,...>", "Attach a file"
185
+ ),
186
+ _show_subcommand("clear", "", "Clear attachment"),
187
+ _show_command("/workflow", "Show active workflows"),
188
+ _show_subcommand("add", "<workflow>", "Add active workflow"),
189
+ _show_subcommand(
190
+ "set", "<workflow1,workflow2,..>", "Set active workflows"
191
+ ),
192
+ _show_subcommand("clear", "", "Deactivate all workflows"),
193
+ _show_command("/save <file-path>", "Save last response to a file"),
194
+ _show_command("/yolo", "Show current YOLO mode"),
195
+ _show_command_param(
196
+ "<true | false | tool1,tool2,...>", "Set YOLO mode"
197
+ ),
198
+ _show_command("/run", ""),
199
+ _show_command_param(
200
+ "<cli-command>", "Run a non-interactive CLI command"
201
+ ),
202
+ _show_command("/help", "Show this message"),
203
+ ]
204
+ ),
205
+ plain=True,
206
+ )
207
+ ctx.print("", plain=True)
208
+
209
+
210
+ def _show_command(command: str, description: str) -> str:
211
+ styled_command = stylize_bold_yellow(command.ljust(37))
212
+ styled_description = stylize_faint(description)
213
+ return f" {styled_command} {styled_description}"
214
+
215
+
216
+ def _show_subcommand(subcommand: str, param: str, description: str) -> str:
217
+ styled_subcommand = stylize_bold_yellow(f" {subcommand}")
218
+ styled_param = stylize_blue(param.ljust(32 - len(subcommand)))
219
+ styled_description = stylize_faint(description)
220
+ return f" {styled_subcommand} {styled_param} {styled_description}"
221
+
222
+
223
+ def _show_command_param(param: str, description: str) -> str:
224
+ styled_param = stylize_blue(f" {param}".ljust(37))
225
+ styled_description = stylize_faint(description)
226
+ return f" {styled_param} {styled_description}"
@@ -0,0 +1,73 @@
1
+ import asyncio
2
+ from asyncio import StreamReader
3
+ from typing import TYPE_CHECKING, Any, Callable, Coroutine
4
+
5
+ from zrb.context.any_context import AnyContext
6
+ from zrb.util.run import run_async
7
+
8
+ if TYPE_CHECKING:
9
+ from prompt_toolkit import PromptSession
10
+
11
+
12
+ ChatTrigger = Callable[[AnyContext], Coroutine[Any, Any, str] | str]
13
+
14
+
15
+ class LLMChatTrigger:
16
+
17
+ def __init__(self):
18
+ self._triggers: list[ChatTrigger] = []
19
+
20
+ def add_trigger(self, *trigger: ChatTrigger):
21
+ self.append_trigger(*trigger)
22
+
23
+ def append_trigger(self, *trigger: ChatTrigger):
24
+ for single_trigger in trigger:
25
+ self._triggers.append(single_trigger)
26
+
27
+ async def wait(
28
+ self, reader: "PromptSession[Any] | StreamReader", ctx: AnyContext
29
+ ) -> str:
30
+ trigger_tasks = [
31
+ asyncio.create_task(run_async(self._read_next_line(reader, ctx)))
32
+ ] + [asyncio.create_task(run_async(trigger(ctx))) for trigger in self._triggers]
33
+ final_result: str = ""
34
+ try:
35
+ done, pending = await asyncio.wait(
36
+ trigger_tasks, return_when=asyncio.FIRST_COMPLETED
37
+ )
38
+ for task in done:
39
+ final_result = await task
40
+ if pending:
41
+ for task in pending:
42
+ task.cancel()
43
+ for task in done:
44
+ break
45
+ except asyncio.CancelledError:
46
+ ctx.print("Task cancelled.", plain=True)
47
+ final_result = "/bye"
48
+ except KeyboardInterrupt:
49
+ ctx.print("KeyboardInterrupt detected. Exiting...", plain=True)
50
+ final_result = "/bye"
51
+ return final_result
52
+
53
+ async def _read_next_line(
54
+ self, reader: "PromptSession[Any] | StreamReader", ctx: AnyContext
55
+ ) -> str:
56
+ """Reads one line of input using the provided reader."""
57
+ from prompt_toolkit import PromptSession
58
+
59
+ try:
60
+ if isinstance(reader, PromptSession):
61
+ return await reader.prompt_async()
62
+ line_bytes = await reader.readline()
63
+ if not line_bytes:
64
+ return "/bye" # Signal to exit
65
+ user_input = line_bytes.decode().strip()
66
+ ctx.print(user_input, plain=True)
67
+ return user_input
68
+ except KeyboardInterrupt:
69
+ ctx.print("KeyboardInterrupt detected. Exiting...", plain=True)
70
+ return "/bye"
71
+
72
+
73
+ llm_chat_trigger = LLMChatTrigger()
@@ -3,12 +3,12 @@ import os
3
3
  from typing import Any
4
4
 
5
5
  from zrb.config.config import CFG
6
- from zrb.context.any_shared_context import AnySharedContext
6
+ from zrb.context.any_context import AnyContext
7
7
  from zrb.task.llm.conversation_history_model import ConversationHistory
8
8
  from zrb.util.file import read_file, write_file
9
9
 
10
10
 
11
- def read_chat_conversation(ctx: AnySharedContext) -> dict[str, Any] | list | None:
11
+ def read_chat_conversation(ctx: AnyContext) -> dict[str, Any] | list | None:
12
12
  """Reads conversation history from the session file.
13
13
  Returns the raw dictionary or list loaded from JSON, or None if not found/empty.
14
14
  The LLMTask will handle parsing this into ConversationHistory.
@@ -51,10 +51,10 @@ def read_chat_conversation(ctx: AnySharedContext) -> dict[str, Any] | list | Non
51
51
  return None
52
52
 
53
53
 
54
- def write_chat_conversation(ctx: AnySharedContext, history_data: ConversationHistory):
54
+ def write_chat_conversation(ctx: AnyContext, history_data: ConversationHistory):
55
55
  """Writes the conversation history data (including context) to a session file."""
56
56
  os.makedirs(CFG.LLM_HISTORY_DIR, exist_ok=True)
57
- current_session_name = ctx.session.name
57
+ current_session_name = ctx.session.name if ctx.session is not None else None
58
58
  if not current_session_name:
59
59
  ctx.log_warning("Cannot write history: Session name is empty.")
60
60
  return