zrb 1.15.3__py3-none-any.whl → 2.0.0a4__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.
Potentially problematic release.
This version of zrb might be problematic. Click here for more details.
- zrb/__init__.py +118 -133
- zrb/attr/type.py +10 -7
- zrb/builtin/__init__.py +55 -1
- zrb/builtin/git.py +12 -1
- zrb/builtin/group.py +31 -15
- zrb/builtin/llm/chat.py +147 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
- zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
- zrb/builtin/searxng/config/settings.yml +5671 -0
- zrb/builtin/searxng/start.py +21 -0
- zrb/builtin/shell/autocomplete/bash.py +4 -3
- zrb/builtin/shell/autocomplete/zsh.py +4 -3
- zrb/callback/callback.py +8 -1
- zrb/cmd/cmd_result.py +2 -1
- zrb/config/config.py +555 -169
- zrb/config/helper.py +84 -0
- zrb/config/web_auth_config.py +50 -35
- zrb/context/any_shared_context.py +20 -3
- zrb/context/context.py +39 -5
- zrb/context/print_fn.py +13 -0
- zrb/context/shared_context.py +17 -8
- zrb/group/any_group.py +3 -3
- zrb/group/group.py +3 -3
- zrb/input/any_input.py +5 -1
- zrb/input/base_input.py +18 -6
- zrb/input/option_input.py +41 -1
- zrb/input/text_input.py +7 -24
- zrb/llm/agent/__init__.py +9 -0
- zrb/llm/agent/agent.py +215 -0
- zrb/llm/agent/summarizer.py +20 -0
- zrb/llm/app/__init__.py +10 -0
- zrb/llm/app/completion.py +281 -0
- zrb/llm/app/confirmation/allow_tool.py +66 -0
- zrb/llm/app/confirmation/handler.py +178 -0
- zrb/llm/app/confirmation/replace_confirmation.py +77 -0
- zrb/llm/app/keybinding.py +34 -0
- zrb/llm/app/layout.py +117 -0
- zrb/llm/app/lexer.py +155 -0
- zrb/llm/app/redirection.py +28 -0
- zrb/llm/app/style.py +16 -0
- zrb/llm/app/ui.py +733 -0
- zrb/llm/config/__init__.py +4 -0
- zrb/llm/config/config.py +122 -0
- zrb/llm/config/limiter.py +247 -0
- zrb/llm/history_manager/__init__.py +4 -0
- zrb/llm/history_manager/any_history_manager.py +23 -0
- zrb/llm/history_manager/file_history_manager.py +91 -0
- zrb/llm/history_processor/summarizer.py +108 -0
- zrb/llm/note/__init__.py +3 -0
- zrb/llm/note/manager.py +122 -0
- zrb/llm/prompt/__init__.py +29 -0
- zrb/llm/prompt/claude_compatibility.py +92 -0
- zrb/llm/prompt/compose.py +55 -0
- zrb/llm/prompt/default.py +51 -0
- zrb/llm/prompt/markdown/file_extractor.md +112 -0
- zrb/llm/prompt/markdown/mandate.md +23 -0
- zrb/llm/prompt/markdown/persona.md +3 -0
- zrb/llm/prompt/markdown/repo_extractor.md +112 -0
- zrb/llm/prompt/markdown/repo_summarizer.md +29 -0
- zrb/llm/prompt/markdown/summarizer.md +21 -0
- zrb/llm/prompt/note.py +41 -0
- zrb/llm/prompt/system_context.py +46 -0
- zrb/llm/prompt/zrb.py +41 -0
- zrb/llm/skill/__init__.py +3 -0
- zrb/llm/skill/manager.py +86 -0
- zrb/llm/task/__init__.py +4 -0
- zrb/llm/task/llm_chat_task.py +316 -0
- zrb/llm/task/llm_task.py +245 -0
- zrb/llm/tool/__init__.py +39 -0
- zrb/llm/tool/bash.py +75 -0
- zrb/llm/tool/code.py +266 -0
- zrb/llm/tool/file.py +419 -0
- zrb/llm/tool/note.py +70 -0
- zrb/{builtin/llm → llm}/tool/rag.py +33 -37
- zrb/llm/tool/search/brave.py +53 -0
- zrb/llm/tool/search/searxng.py +47 -0
- zrb/llm/tool/search/serpapi.py +47 -0
- zrb/llm/tool/skill.py +19 -0
- zrb/llm/tool/sub_agent.py +70 -0
- zrb/llm/tool/web.py +97 -0
- zrb/llm/tool/zrb_task.py +66 -0
- zrb/llm/util/attachment.py +101 -0
- zrb/llm/util/prompt.py +104 -0
- zrb/llm/util/stream_response.py +178 -0
- zrb/runner/cli.py +21 -20
- zrb/runner/common_util.py +24 -19
- zrb/runner/web_route/task_input_api_route.py +5 -5
- zrb/runner/web_util/user.py +7 -3
- zrb/session/any_session.py +12 -9
- zrb/session/session.py +38 -17
- zrb/task/any_task.py +24 -3
- zrb/task/base/context.py +42 -22
- zrb/task/base/execution.py +67 -55
- zrb/task/base/lifecycle.py +14 -7
- zrb/task/base/monitoring.py +12 -7
- zrb/task/base_task.py +113 -50
- zrb/task/base_trigger.py +16 -6
- zrb/task/cmd_task.py +6 -0
- zrb/task/http_check.py +11 -5
- zrb/task/make_task.py +5 -3
- zrb/task/rsync_task.py +30 -10
- zrb/task/scaffolder.py +7 -4
- zrb/task/scheduler.py +7 -4
- zrb/task/tcp_check.py +6 -4
- zrb/util/ascii_art/art/bee.txt +17 -0
- zrb/util/ascii_art/art/cat.txt +9 -0
- zrb/util/ascii_art/art/ghost.txt +16 -0
- zrb/util/ascii_art/art/panda.txt +17 -0
- zrb/util/ascii_art/art/rose.txt +14 -0
- zrb/util/ascii_art/art/unicorn.txt +15 -0
- zrb/util/ascii_art/banner.py +92 -0
- zrb/util/attr.py +54 -39
- zrb/util/cli/markdown.py +32 -0
- zrb/util/cli/text.py +30 -0
- zrb/util/cmd/command.py +33 -10
- zrb/util/file.py +61 -33
- zrb/util/git.py +2 -2
- zrb/util/{llm/prompt.py → markdown.py} +2 -3
- zrb/util/match.py +78 -0
- zrb/util/run.py +3 -3
- zrb/util/string/conversion.py +1 -1
- zrb/util/truncate.py +23 -0
- zrb/util/yaml.py +204 -0
- zrb/xcom/xcom.py +10 -0
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/METADATA +41 -27
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/RECORD +129 -131
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/WHEEL +1 -1
- zrb/attr/__init__.py +0 -0
- zrb/builtin/llm/chat_session.py +0 -311
- zrb/builtin/llm/history.py +0 -71
- zrb/builtin/llm/input.py +0 -27
- zrb/builtin/llm/llm_ask.py +0 -187
- zrb/builtin/llm/previous-session.js +0 -21
- zrb/builtin/llm/tool/__init__.py +0 -0
- zrb/builtin/llm/tool/api.py +0 -71
- zrb/builtin/llm/tool/cli.py +0 -38
- zrb/builtin/llm/tool/code.py +0 -254
- zrb/builtin/llm/tool/file.py +0 -626
- zrb/builtin/llm/tool/sub_agent.py +0 -137
- zrb/builtin/llm/tool/web.py +0 -195
- zrb/builtin/project/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/__init__.py +0 -0
- zrb/builtin/project/create/__init__.py +0 -0
- zrb/builtin/shell/__init__.py +0 -0
- zrb/builtin/shell/autocomplete/__init__.py +0 -0
- zrb/callback/__init__.py +0 -0
- zrb/cmd/__init__.py +0 -0
- zrb/config/default_prompt/file_extractor_system_prompt.md +0 -12
- zrb/config/default_prompt/interactive_system_prompt.md +0 -35
- zrb/config/default_prompt/persona.md +0 -1
- zrb/config/default_prompt/repo_extractor_system_prompt.md +0 -112
- zrb/config/default_prompt/repo_summarizer_system_prompt.md +0 -10
- zrb/config/default_prompt/summarization_prompt.md +0 -16
- zrb/config/default_prompt/system_prompt.md +0 -32
- zrb/config/llm_config.py +0 -243
- zrb/config/llm_context/config.py +0 -129
- zrb/config/llm_context/config_parser.py +0 -46
- zrb/config/llm_rate_limitter.py +0 -137
- zrb/content_transformer/__init__.py +0 -0
- zrb/context/__init__.py +0 -0
- zrb/dot_dict/__init__.py +0 -0
- zrb/env/__init__.py +0 -0
- zrb/group/__init__.py +0 -0
- zrb/input/__init__.py +0 -0
- zrb/runner/__init__.py +0 -0
- zrb/runner/web_route/__init__.py +0 -0
- zrb/runner/web_route/home_page/__init__.py +0 -0
- zrb/session/__init__.py +0 -0
- zrb/session_state_log/__init__.py +0 -0
- zrb/session_state_logger/__init__.py +0 -0
- zrb/task/__init__.py +0 -0
- zrb/task/base/__init__.py +0 -0
- zrb/task/llm/__init__.py +0 -0
- zrb/task/llm/agent.py +0 -243
- zrb/task/llm/config.py +0 -103
- zrb/task/llm/conversation_history.py +0 -128
- zrb/task/llm/conversation_history_model.py +0 -242
- zrb/task/llm/default_workflow/coding.md +0 -24
- zrb/task/llm/default_workflow/copywriting.md +0 -17
- zrb/task/llm/default_workflow/researching.md +0 -18
- zrb/task/llm/error.py +0 -95
- zrb/task/llm/history_summarization.py +0 -216
- zrb/task/llm/print_node.py +0 -101
- zrb/task/llm/prompt.py +0 -325
- zrb/task/llm/tool_wrapper.py +0 -220
- zrb/task/llm/typing.py +0 -3
- zrb/task/llm_task.py +0 -341
- zrb/task_status/__init__.py +0 -0
- zrb/util/__init__.py +0 -0
- zrb/util/cli/__init__.py +0 -0
- zrb/util/cmd/__init__.py +0 -0
- zrb/util/codemod/__init__.py +0 -0
- zrb/util/string/__init__.py +0 -0
- zrb/xcom/__init__.py +0 -0
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/entry_points.txt +0 -0
zrb/llm/task/llm_task.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
|
+
|
|
4
|
+
from zrb.attr.type import BoolAttr, StrAttr, fstring
|
|
5
|
+
from zrb.config.config import CFG
|
|
6
|
+
from zrb.context.any_context import AnyContext
|
|
7
|
+
from zrb.context.print_fn import PrintFn
|
|
8
|
+
from zrb.env.any_env import AnyEnv
|
|
9
|
+
from zrb.input.any_input import AnyInput
|
|
10
|
+
from zrb.llm.agent.agent import create_agent, run_agent
|
|
11
|
+
from zrb.llm.config.config import LLMConfig
|
|
12
|
+
from zrb.llm.config.config import llm_config as default_llm_config
|
|
13
|
+
from zrb.llm.config.limiter import LLMLimiter
|
|
14
|
+
from zrb.llm.config.limiter import llm_limiter as default_llm_limitter
|
|
15
|
+
from zrb.llm.history_manager.any_history_manager import AnyHistoryManager
|
|
16
|
+
from zrb.llm.history_manager.file_history_manager import FileHistoryManager
|
|
17
|
+
from zrb.llm.history_processor.summarizer import (
|
|
18
|
+
summarize_history,
|
|
19
|
+
)
|
|
20
|
+
from zrb.llm.prompt.compose import PromptManager
|
|
21
|
+
from zrb.llm.util.attachment import get_attachments
|
|
22
|
+
from zrb.llm.util.stream_response import (
|
|
23
|
+
create_event_handler,
|
|
24
|
+
create_faint_printer,
|
|
25
|
+
)
|
|
26
|
+
from zrb.task.any_task import AnyTask
|
|
27
|
+
from zrb.task.base_task import BaseTask
|
|
28
|
+
from zrb.util.attr import get_attr, get_bool_attr
|
|
29
|
+
from zrb.util.string.name import get_random_name
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from pydantic_ai import Tool, UserContent
|
|
33
|
+
from pydantic_ai._agent_graph import HistoryProcessor
|
|
34
|
+
from pydantic_ai.models import Model
|
|
35
|
+
from pydantic_ai.settings import ModelSettings
|
|
36
|
+
from pydantic_ai.tools import ToolFuncEither
|
|
37
|
+
from pydantic_ai.toolsets import AbstractToolset
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class LLMTask(BaseTask):
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
name: str,
|
|
45
|
+
color: int | None = None,
|
|
46
|
+
icon: str | None = None,
|
|
47
|
+
description: str | None = None,
|
|
48
|
+
cli_only: bool = False,
|
|
49
|
+
input: list[AnyInput | None] | AnyInput | None = None,
|
|
50
|
+
env: list[AnyEnv | None] | AnyEnv | None = None,
|
|
51
|
+
system_prompt: (
|
|
52
|
+
"Callable[[AnyContext], str | fstring | None] | str | None"
|
|
53
|
+
) = None,
|
|
54
|
+
render_system_prompt: bool = False,
|
|
55
|
+
prompt_manager: PromptManager | None = None,
|
|
56
|
+
tools: list["Tool | ToolFuncEither"] = [],
|
|
57
|
+
toolsets: list["AbstractToolset[None]"] = [],
|
|
58
|
+
message: StrAttr | None = None,
|
|
59
|
+
render_message: bool = True,
|
|
60
|
+
attachment: "UserContent | list[UserContent] | Callable[[AnyContext], UserContent | list[UserContent]] | None" = None, # noqa
|
|
61
|
+
history_processors: list["HistoryProcessor"] = [],
|
|
62
|
+
llm_config: LLMConfig | None = None,
|
|
63
|
+
llm_limitter: LLMLimiter | None = None,
|
|
64
|
+
model: (
|
|
65
|
+
"Callable[[AnyContext], Model | str | fstring | None] | Model | None"
|
|
66
|
+
) = None,
|
|
67
|
+
render_model: bool = True,
|
|
68
|
+
model_settings: (
|
|
69
|
+
"ModelSettings | Callable[[AnyContext], ModelSettings] | None"
|
|
70
|
+
) = None,
|
|
71
|
+
conversation_name: StrAttr | None = None,
|
|
72
|
+
render_conversation_name: bool = True,
|
|
73
|
+
history_manager: AnyHistoryManager | None = None,
|
|
74
|
+
tool_confirmation: Callable[[Any], Any] | None = None,
|
|
75
|
+
yolo: BoolAttr = False,
|
|
76
|
+
summarize_command: list[str] = [],
|
|
77
|
+
execute_condition: bool | str | Callable[[AnyContext], bool] = True,
|
|
78
|
+
retries: int = 2,
|
|
79
|
+
retry_period: float = 0,
|
|
80
|
+
readiness_check: list[AnyTask] | AnyTask | None = None,
|
|
81
|
+
readiness_check_delay: float = 0.5,
|
|
82
|
+
readiness_check_period: float = 5,
|
|
83
|
+
readiness_failure_threshold: int = 1,
|
|
84
|
+
readiness_timeout: int = 60,
|
|
85
|
+
monitor_readiness: bool = False,
|
|
86
|
+
upstream: list[AnyTask] | AnyTask | None = None,
|
|
87
|
+
fallback: list[AnyTask] | AnyTask | None = None,
|
|
88
|
+
successor: list[AnyTask] | AnyTask | None = None,
|
|
89
|
+
print_fn: PrintFn | None = None,
|
|
90
|
+
):
|
|
91
|
+
super().__init__(
|
|
92
|
+
name=name,
|
|
93
|
+
color=color,
|
|
94
|
+
icon=icon,
|
|
95
|
+
description=description,
|
|
96
|
+
cli_only=cli_only,
|
|
97
|
+
input=input,
|
|
98
|
+
env=env,
|
|
99
|
+
execute_condition=execute_condition,
|
|
100
|
+
retries=retries,
|
|
101
|
+
retry_period=retry_period,
|
|
102
|
+
readiness_check=readiness_check,
|
|
103
|
+
readiness_check_delay=readiness_check_delay,
|
|
104
|
+
readiness_check_period=readiness_check_period,
|
|
105
|
+
readiness_failure_threshold=readiness_failure_threshold,
|
|
106
|
+
readiness_timeout=readiness_timeout,
|
|
107
|
+
monitor_readiness=monitor_readiness,
|
|
108
|
+
upstream=upstream,
|
|
109
|
+
fallback=fallback,
|
|
110
|
+
successor=successor,
|
|
111
|
+
print_fn=print_fn,
|
|
112
|
+
)
|
|
113
|
+
self._llm_config = default_llm_config if llm_config is None else llm_config
|
|
114
|
+
self._llm_limitter = (
|
|
115
|
+
default_llm_limitter if llm_limitter is None else llm_limitter
|
|
116
|
+
)
|
|
117
|
+
self._system_prompt = system_prompt
|
|
118
|
+
self._render_system_prompt = render_system_prompt
|
|
119
|
+
self._prompt_manager = prompt_manager
|
|
120
|
+
self._tools = tools
|
|
121
|
+
self._toolsets = toolsets
|
|
122
|
+
self._message = message
|
|
123
|
+
self._render_message = render_message
|
|
124
|
+
self._attachment = attachment
|
|
125
|
+
self._history_processors = history_processors
|
|
126
|
+
self._model = model
|
|
127
|
+
self._render_model = render_model
|
|
128
|
+
self._model_settings = model_settings
|
|
129
|
+
self._conversation_name = conversation_name
|
|
130
|
+
self._render_conversation_name = render_conversation_name
|
|
131
|
+
self._history_manager = (
|
|
132
|
+
FileHistoryManager(history_dir=CFG.LLM_HISTORY_DIR)
|
|
133
|
+
if history_manager is None
|
|
134
|
+
else history_manager
|
|
135
|
+
)
|
|
136
|
+
self._tool_confirmation = tool_confirmation
|
|
137
|
+
self._yolo = yolo
|
|
138
|
+
self._summarize_command = summarize_command
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def prompt_manager(self) -> PromptManager:
|
|
142
|
+
if self._prompt_manager is None:
|
|
143
|
+
raise ValueError(f"Task {self.name} doesn't have prompt_manager")
|
|
144
|
+
return self._prompt_manager
|
|
145
|
+
|
|
146
|
+
def add_toolset(self, *toolset: "AbstractToolset"):
|
|
147
|
+
self.append_toolset(*toolset)
|
|
148
|
+
|
|
149
|
+
def append_toolset(self, *toolset: "AbstractToolset"):
|
|
150
|
+
self._toolsets += list(toolset)
|
|
151
|
+
|
|
152
|
+
def add_tool(self, *tool: "Tool | ToolFuncEither"):
|
|
153
|
+
self.append_tool(*tool)
|
|
154
|
+
|
|
155
|
+
def append_tool(self, *tool: "Tool | ToolFuncEither"):
|
|
156
|
+
self._tools += list(tool)
|
|
157
|
+
|
|
158
|
+
def add_history_processor(self, *processor: "HistoryProcessor"):
|
|
159
|
+
self.append_history_processor(*processor)
|
|
160
|
+
|
|
161
|
+
def append_history_processor(self, *processor: "HistoryProcessor"):
|
|
162
|
+
self._history_processors += list(processor)
|
|
163
|
+
|
|
164
|
+
async def _exec_action(self, ctx: AnyContext) -> Any:
|
|
165
|
+
conversation_name = self._get_conversation_name(ctx)
|
|
166
|
+
message_history = self._history_manager.load(conversation_name)
|
|
167
|
+
user_message = get_attr(ctx, self._message, "", self._render_message)
|
|
168
|
+
user_attachments = get_attachments(ctx, self._attachment)
|
|
169
|
+
|
|
170
|
+
if (
|
|
171
|
+
isinstance(user_message, str)
|
|
172
|
+
and user_message.strip() in self._summarize_command
|
|
173
|
+
):
|
|
174
|
+
ctx.print("Compressing conversation history...", plain=True)
|
|
175
|
+
new_history = await summarize_history(message_history)
|
|
176
|
+
self._history_manager.update(conversation_name, new_history)
|
|
177
|
+
self._history_manager.save(conversation_name)
|
|
178
|
+
return "Conversation history compressed."
|
|
179
|
+
|
|
180
|
+
yolo = get_bool_attr(ctx, self._yolo, False)
|
|
181
|
+
system_prompt = self._get_system_prompt(ctx)
|
|
182
|
+
ctx.log_debug(f"SYSTEM PROMPT: {system_prompt}")
|
|
183
|
+
agent = create_agent(
|
|
184
|
+
model=self._get_model(ctx),
|
|
185
|
+
system_prompt=self._get_system_prompt(ctx),
|
|
186
|
+
tools=self._tools,
|
|
187
|
+
toolsets=self._toolsets,
|
|
188
|
+
model_settings=self._get_model_settings(ctx),
|
|
189
|
+
history_processors=self._history_processors,
|
|
190
|
+
yolo=yolo,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
print_event = create_faint_printer(ctx)
|
|
194
|
+
handle_event = create_event_handler(
|
|
195
|
+
print_event,
|
|
196
|
+
show_tool_call_detail=CFG.LLM_SHOW_TOOL_CALL_PREPARATION,
|
|
197
|
+
show_tool_result=CFG.LLM_SHOW_TOOL_CALL_RESULT,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
output, new_history = await run_agent(
|
|
201
|
+
agent=agent,
|
|
202
|
+
message=user_message,
|
|
203
|
+
message_history=message_history,
|
|
204
|
+
limiter=self._llm_limitter,
|
|
205
|
+
attachments=user_attachments,
|
|
206
|
+
print_fn=ctx.print,
|
|
207
|
+
event_handler=handle_event,
|
|
208
|
+
tool_confirmation=self._tool_confirmation,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
self._history_manager.update(conversation_name, new_history)
|
|
212
|
+
self._history_manager.save(conversation_name)
|
|
213
|
+
ctx.log_debug(f"All messages: {new_history}")
|
|
214
|
+
|
|
215
|
+
return output
|
|
216
|
+
|
|
217
|
+
def _get_system_prompt(self, ctx: AnyContext) -> str:
|
|
218
|
+
if self._prompt_manager is None:
|
|
219
|
+
return str(
|
|
220
|
+
get_attr(ctx, self._system_prompt, "", self._render_system_prompt)
|
|
221
|
+
)
|
|
222
|
+
compose_prompt = self._prompt_manager.compose_prompt()
|
|
223
|
+
return compose_prompt(ctx)
|
|
224
|
+
|
|
225
|
+
def _get_conversation_name(self, ctx: AnyContext) -> str:
|
|
226
|
+
conversation_name = str(
|
|
227
|
+
get_attr(ctx, self._conversation_name, "", self._render_conversation_name)
|
|
228
|
+
)
|
|
229
|
+
if conversation_name.strip() == "":
|
|
230
|
+
conversation_name = get_random_name()
|
|
231
|
+
return conversation_name
|
|
232
|
+
|
|
233
|
+
def _get_model_settings(self, ctx: AnyContext) -> "ModelSettings | None":
|
|
234
|
+
model_settings = self._model_settings
|
|
235
|
+
rendered_model_settings = get_attr(ctx, model_settings, None)
|
|
236
|
+
if rendered_model_settings is not None:
|
|
237
|
+
return rendered_model_settings
|
|
238
|
+
return self._llm_config.model_settings
|
|
239
|
+
|
|
240
|
+
def _get_model(self, ctx: AnyContext) -> "str | Model":
|
|
241
|
+
model = self._model
|
|
242
|
+
rendered_model = get_attr(ctx, model, None, auto_render=self._render_model)
|
|
243
|
+
if rendered_model is not None:
|
|
244
|
+
return rendered_model
|
|
245
|
+
return self._llm_config.model
|
zrb/llm/tool/__init__.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from zrb.llm.tool.bash import run_shell_command
|
|
2
|
+
from zrb.llm.tool.code import analyze_code
|
|
3
|
+
from zrb.llm.tool.file import (
|
|
4
|
+
analyze_file,
|
|
5
|
+
list_files,
|
|
6
|
+
read_file,
|
|
7
|
+
read_files,
|
|
8
|
+
replace_in_file,
|
|
9
|
+
search_files,
|
|
10
|
+
write_file,
|
|
11
|
+
write_files,
|
|
12
|
+
)
|
|
13
|
+
from zrb.llm.tool.note import create_note_tools
|
|
14
|
+
from zrb.llm.tool.rag import create_rag_from_directory
|
|
15
|
+
from zrb.llm.tool.skill import create_activate_skill_tool
|
|
16
|
+
from zrb.llm.tool.sub_agent import create_sub_agent_tool
|
|
17
|
+
from zrb.llm.tool.web import open_web_page, search_internet
|
|
18
|
+
from zrb.llm.tool.zrb_task import create_list_zrb_task_tool, create_run_zrb_task_tool
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"run_shell_command",
|
|
22
|
+
"analyze_code",
|
|
23
|
+
"list_files",
|
|
24
|
+
"read_file",
|
|
25
|
+
"read_files",
|
|
26
|
+
"write_file",
|
|
27
|
+
"write_files",
|
|
28
|
+
"replace_in_file",
|
|
29
|
+
"search_files",
|
|
30
|
+
"analyze_file",
|
|
31
|
+
"create_note_tools",
|
|
32
|
+
"create_rag_from_directory",
|
|
33
|
+
"create_activate_skill_tool",
|
|
34
|
+
"create_sub_agent_tool",
|
|
35
|
+
"open_web_page",
|
|
36
|
+
"search_internet",
|
|
37
|
+
"create_list_zrb_task_tool",
|
|
38
|
+
"create_run_zrb_task_tool",
|
|
39
|
+
]
|
zrb/llm/tool/bash.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from zrb.util.cli.style import stylize_faint
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def run_shell_command(command: str, timeout: int = 30) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Executes a shell command on the host system and returns its combined stdout and stderr.
|
|
10
|
+
This is a powerful tool for running builds, tests, or system utilities.
|
|
11
|
+
|
|
12
|
+
**CRITICAL SAFETY:**
|
|
13
|
+
- DO NOT run destructive commands (e.g., `rm -rf /`) without absolute certainty.
|
|
14
|
+
- Prefer specialized tools (like `read_file` or `write_file`) for file operations.
|
|
15
|
+
|
|
16
|
+
**USAGE GUIDELINES:**
|
|
17
|
+
- Use non-interactive commands.
|
|
18
|
+
- If a command is expected to produce massive output, use `timeout` or pipe to a file.
|
|
19
|
+
- The output is streamed to the console in real-time.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
command (str): The full shell command to execute.
|
|
23
|
+
timeout (int): Maximum wait time in seconds before terminating the process. Defaults to 30.
|
|
24
|
+
"""
|
|
25
|
+
ANSI_ESCAPE = re.compile(
|
|
26
|
+
r"(?:\x1B\[[0-?]*[ -/]*[@-~])|" # CSI (Control Sequence Introducer)
|
|
27
|
+
r"(?:\x1B\][^\a\x1b]*[\a\x1b])|" # OSC (Operating System Command)
|
|
28
|
+
r"(?:\x1B[0-9=>])" # Simple 2-byte (DECSC, DECRC, etc.)
|
|
29
|
+
)
|
|
30
|
+
try:
|
|
31
|
+
process = await asyncio.create_subprocess_shell(
|
|
32
|
+
command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
stdout_lines = []
|
|
36
|
+
stderr_lines = []
|
|
37
|
+
|
|
38
|
+
async def read_stream(stream, lines_list, prefix=""):
|
|
39
|
+
while True:
|
|
40
|
+
line = await stream.readline()
|
|
41
|
+
if not line:
|
|
42
|
+
break
|
|
43
|
+
decoded = line.decode()
|
|
44
|
+
if decoded:
|
|
45
|
+
shown = ANSI_ESCAPE.sub("", decoded)
|
|
46
|
+
shown = stylize_faint(shown)
|
|
47
|
+
print(f"{prefix} {shown}", end="") # Stream to console
|
|
48
|
+
lines_list.append(decoded)
|
|
49
|
+
|
|
50
|
+
# Wait for the process to complete or timeout
|
|
51
|
+
try:
|
|
52
|
+
await asyncio.wait_for(
|
|
53
|
+
asyncio.gather(
|
|
54
|
+
read_stream(process.stdout, stdout_lines, ""),
|
|
55
|
+
read_stream(process.stderr, stderr_lines, "[stderr] "),
|
|
56
|
+
process.wait(),
|
|
57
|
+
),
|
|
58
|
+
timeout=timeout,
|
|
59
|
+
)
|
|
60
|
+
except asyncio.TimeoutError:
|
|
61
|
+
if process.returncode is None:
|
|
62
|
+
try:
|
|
63
|
+
process.terminate()
|
|
64
|
+
await process.wait()
|
|
65
|
+
except ProcessLookupError:
|
|
66
|
+
pass
|
|
67
|
+
return f"Error: Command timed out after {timeout} seconds."
|
|
68
|
+
|
|
69
|
+
output = "\n".join(stdout_lines)
|
|
70
|
+
error = "\n".join(stderr_lines)
|
|
71
|
+
|
|
72
|
+
return f"Exit Code: {process.returncode}\nStdout:\n{output}\nStderr:\n{error}"
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
return f"Error executing command: {e}"
|
zrb/llm/tool/code.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import fnmatch
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from zrb.config.config import CFG
|
|
6
|
+
from zrb.llm.agent.agent import create_agent, run_agent
|
|
7
|
+
from zrb.llm.config.config import llm_config
|
|
8
|
+
from zrb.llm.config.limiter import llm_limiter
|
|
9
|
+
from zrb.llm.prompt.default import (
|
|
10
|
+
get_repo_extractor_system_prompt,
|
|
11
|
+
get_repo_summarizer_system_prompt,
|
|
12
|
+
)
|
|
13
|
+
from zrb.llm.tool.file import DEFAULT_EXCLUDED_PATTERNS
|
|
14
|
+
|
|
15
|
+
_DEFAULT_EXTENSIONS = [
|
|
16
|
+
"py",
|
|
17
|
+
"go",
|
|
18
|
+
"java",
|
|
19
|
+
"ts",
|
|
20
|
+
"js",
|
|
21
|
+
"rs",
|
|
22
|
+
"rb",
|
|
23
|
+
"php",
|
|
24
|
+
"sh",
|
|
25
|
+
"bash",
|
|
26
|
+
"c",
|
|
27
|
+
"cpp",
|
|
28
|
+
"h",
|
|
29
|
+
"hpp",
|
|
30
|
+
"cs",
|
|
31
|
+
"swift",
|
|
32
|
+
"kt",
|
|
33
|
+
"scala",
|
|
34
|
+
"m",
|
|
35
|
+
"pl",
|
|
36
|
+
"lua",
|
|
37
|
+
"sql",
|
|
38
|
+
"html",
|
|
39
|
+
"css",
|
|
40
|
+
"scss",
|
|
41
|
+
"less",
|
|
42
|
+
"json",
|
|
43
|
+
"yaml",
|
|
44
|
+
"yml",
|
|
45
|
+
"toml",
|
|
46
|
+
"ini",
|
|
47
|
+
"xml",
|
|
48
|
+
"md",
|
|
49
|
+
"rst",
|
|
50
|
+
"txt",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def analyze_code(
|
|
55
|
+
path: str,
|
|
56
|
+
query: str,
|
|
57
|
+
extensions: list[str] | None = None,
|
|
58
|
+
exclude_patterns: list[str] | None = None,
|
|
59
|
+
) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Performs a deep, semantic analysis of an entire codebase or directory to answer complex architectural or logic-based questions.
|
|
62
|
+
This tool uses a 'map-reduce' strategy to handle large repositories that exceed single-prompt limits.
|
|
63
|
+
|
|
64
|
+
**WHEN TO USE:**
|
|
65
|
+
- To understand system-wide flows, architectural patterns, or cross-file dependencies.
|
|
66
|
+
- When you need a summary of how a feature is implemented across multiple files.
|
|
67
|
+
- To identify potential refactoring opportunities or technical debt.
|
|
68
|
+
|
|
69
|
+
**LIMITATIONS:**
|
|
70
|
+
- It extracts and summarizes information; it does not read every single byte if the repo is massive.
|
|
71
|
+
- For precise line-by-line reading of a known file, use `read_file` instead.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
path (str): Path to the directory or repository.
|
|
75
|
+
query (str): A clear, specific question or analysis goal (e.g., "How is authentication handled?").
|
|
76
|
+
extensions (list[str], optional): File extensions to include (e.g., ["py", "ts"]).
|
|
77
|
+
exclude_patterns (list[str], optional): Glob patterns to ignore (e.g., ["tests/*"]).
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
str: A comprehensive analytical report.
|
|
81
|
+
"""
|
|
82
|
+
if extensions is None:
|
|
83
|
+
extensions = _DEFAULT_EXTENSIONS
|
|
84
|
+
if exclude_patterns is None:
|
|
85
|
+
exclude_patterns = DEFAULT_EXCLUDED_PATTERNS
|
|
86
|
+
|
|
87
|
+
abs_path = os.path.abspath(os.path.expanduser(path))
|
|
88
|
+
if not os.path.exists(abs_path):
|
|
89
|
+
return f"Error: Path not found: {path}"
|
|
90
|
+
|
|
91
|
+
# 1. Gather files
|
|
92
|
+
file_metadatas = _get_file_metadatas(abs_path, extensions, exclude_patterns)
|
|
93
|
+
if not file_metadatas:
|
|
94
|
+
return "No files found matching the criteria."
|
|
95
|
+
|
|
96
|
+
print(f" 📝 Extraction ({len(file_metadatas)} files)")
|
|
97
|
+
|
|
98
|
+
# 2. Extract Info
|
|
99
|
+
extraction_token_threshold = CFG.LLM_REPO_ANALYSIS_EXTRACTION_TOKEN_THRESHOLD
|
|
100
|
+
extracted_infos = await _extract_info(
|
|
101
|
+
file_metadatas=file_metadatas,
|
|
102
|
+
query=query,
|
|
103
|
+
token_limit=extraction_token_threshold,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if not extracted_infos:
|
|
107
|
+
return "No information could be extracted from the files."
|
|
108
|
+
|
|
109
|
+
if len(extracted_infos) == 1:
|
|
110
|
+
return extracted_infos[0]
|
|
111
|
+
|
|
112
|
+
# 3. Summarize Info (Reduce)
|
|
113
|
+
summarization_token_threshold = CFG.LLM_REPO_ANALYSIS_SUMMARIZATION_TOKEN_THRESHOLD
|
|
114
|
+
summarized_infos = extracted_infos
|
|
115
|
+
|
|
116
|
+
while len(summarized_infos) > 1:
|
|
117
|
+
print(f" 📝 Summarization ({len(summarized_infos)} chunks)")
|
|
118
|
+
summarized_infos = await _summarize_info(
|
|
119
|
+
extracted_infos=summarized_infos,
|
|
120
|
+
query=query,
|
|
121
|
+
token_limit=summarization_token_threshold,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return summarized_infos[0]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _get_file_metadatas(
|
|
128
|
+
dir_path: str,
|
|
129
|
+
extensions: list[str],
|
|
130
|
+
exclude_patterns: list[str],
|
|
131
|
+
) -> list[dict[str, str]]:
|
|
132
|
+
metadata_list = []
|
|
133
|
+
for root, _, files in os.walk(dir_path):
|
|
134
|
+
files.sort()
|
|
135
|
+
for file in files:
|
|
136
|
+
if not any(file.endswith(f".{ext}") for ext in extensions):
|
|
137
|
+
continue
|
|
138
|
+
file_path = os.path.join(root, file)
|
|
139
|
+
try:
|
|
140
|
+
rel_path = os.path.relpath(file_path, dir_path)
|
|
141
|
+
if _is_excluded(rel_path, exclude_patterns):
|
|
142
|
+
continue
|
|
143
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
144
|
+
metadata_list.append({"path": rel_path, "content": f.read()})
|
|
145
|
+
except Exception as e:
|
|
146
|
+
print(f"Error reading file {file_path}: {e}")
|
|
147
|
+
metadata_list.sort(key=lambda m: m["path"])
|
|
148
|
+
return metadata_list
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _is_excluded(name: str, patterns: list[str]) -> bool:
|
|
152
|
+
for pattern in patterns:
|
|
153
|
+
if fnmatch.fnmatch(name, pattern):
|
|
154
|
+
return True
|
|
155
|
+
parts = name.split(os.path.sep)
|
|
156
|
+
for part in parts:
|
|
157
|
+
if fnmatch.fnmatch(part, pattern):
|
|
158
|
+
return True
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def _extract_info(
|
|
163
|
+
file_metadatas: list[dict[str, str]],
|
|
164
|
+
query: str,
|
|
165
|
+
token_limit: int,
|
|
166
|
+
) -> list[str]:
|
|
167
|
+
agent = create_agent(
|
|
168
|
+
model=llm_config.model,
|
|
169
|
+
system_prompt=get_repo_extractor_system_prompt(),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
extracted_infos = []
|
|
173
|
+
content_buffer = []
|
|
174
|
+
current_token_count = 0
|
|
175
|
+
|
|
176
|
+
# We estimate token count of the prompt template overhead
|
|
177
|
+
base_overhead = 100
|
|
178
|
+
|
|
179
|
+
for metadata in file_metadatas:
|
|
180
|
+
path = metadata.get("path", "")
|
|
181
|
+
content = metadata.get("content", "")
|
|
182
|
+
file_obj = {"path": path, "content": content}
|
|
183
|
+
file_str = json.dumps(file_obj)
|
|
184
|
+
file_tokens = llm_limiter.count_tokens(file_str)
|
|
185
|
+
|
|
186
|
+
if current_token_count + file_tokens + base_overhead > token_limit:
|
|
187
|
+
if content_buffer:
|
|
188
|
+
await _run_extraction(agent, query, content_buffer, extracted_infos)
|
|
189
|
+
|
|
190
|
+
content_buffer = [file_obj]
|
|
191
|
+
current_token_count = file_tokens
|
|
192
|
+
else:
|
|
193
|
+
content_buffer.append(file_obj)
|
|
194
|
+
current_token_count += file_tokens
|
|
195
|
+
|
|
196
|
+
# Process remaining buffer
|
|
197
|
+
if content_buffer:
|
|
198
|
+
await _run_extraction(agent, query, content_buffer, extracted_infos)
|
|
199
|
+
|
|
200
|
+
return extracted_infos
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
async def _run_extraction(agent, query, content_buffer, extracted_infos):
|
|
204
|
+
prompt_data = {
|
|
205
|
+
"main_assistant_query": query,
|
|
206
|
+
"files": content_buffer,
|
|
207
|
+
}
|
|
208
|
+
# We serialize to JSON for the prompt
|
|
209
|
+
message = json.dumps(prompt_data)
|
|
210
|
+
|
|
211
|
+
result, _ = await run_agent(
|
|
212
|
+
agent=agent,
|
|
213
|
+
message=message,
|
|
214
|
+
message_history=[], # Stateless
|
|
215
|
+
limiter=llm_limiter,
|
|
216
|
+
)
|
|
217
|
+
extracted_infos.append(str(result))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
async def _summarize_info(
|
|
221
|
+
extracted_infos: list[str],
|
|
222
|
+
query: str,
|
|
223
|
+
token_limit: int,
|
|
224
|
+
) -> list[str]:
|
|
225
|
+
agent = create_agent(
|
|
226
|
+
model=llm_config.model,
|
|
227
|
+
system_prompt=get_repo_summarizer_system_prompt(),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
summarized_infos = []
|
|
231
|
+
content_buffer = ""
|
|
232
|
+
# Overhead for prompt structure
|
|
233
|
+
base_overhead = 100
|
|
234
|
+
|
|
235
|
+
for info in extracted_infos:
|
|
236
|
+
# Check if adding this info exceeds limit
|
|
237
|
+
if (
|
|
238
|
+
llm_limiter.count_tokens(content_buffer + info) + base_overhead
|
|
239
|
+
> token_limit
|
|
240
|
+
):
|
|
241
|
+
if content_buffer:
|
|
242
|
+
await _run_summarization(agent, query, content_buffer, summarized_infos)
|
|
243
|
+
content_buffer = info
|
|
244
|
+
else:
|
|
245
|
+
content_buffer += info + "\n"
|
|
246
|
+
|
|
247
|
+
if content_buffer:
|
|
248
|
+
await _run_summarization(agent, query, content_buffer, summarized_infos)
|
|
249
|
+
|
|
250
|
+
return summarized_infos
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
async def _run_summarization(agent, query, content_buffer, summarized_infos):
|
|
254
|
+
prompt_data = {
|
|
255
|
+
"main_assistant_query": query,
|
|
256
|
+
"extracted_info": content_buffer,
|
|
257
|
+
}
|
|
258
|
+
message = json.dumps(prompt_data)
|
|
259
|
+
|
|
260
|
+
result, _ = await run_agent(
|
|
261
|
+
agent=agent,
|
|
262
|
+
message=message,
|
|
263
|
+
message_history=[], # Stateless
|
|
264
|
+
limiter=llm_limiter,
|
|
265
|
+
)
|
|
266
|
+
summarized_infos.append(str(result))
|