zrb 1.21.29__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 -129
- zrb/builtin/__init__.py +54 -2
- zrb/builtin/llm/chat.py +147 -0
- zrb/callback/callback.py +8 -1
- zrb/cmd/cmd_result.py +2 -1
- zrb/config/config.py +491 -280
- zrb/config/helper.py +84 -0
- zrb/config/web_auth_config.py +50 -35
- zrb/context/any_shared_context.py +13 -2
- zrb/context/context.py +31 -3
- zrb/context/print_fn.py +13 -0
- zrb/context/shared_context.py +14 -1
- zrb/input/option_input.py +30 -2
- 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/mandate.md +23 -0
- zrb/llm/prompt/markdown/persona.md +3 -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 +8 -5
- 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/session/any_session.py +0 -3
- zrb/session/session.py +1 -1
- zrb/task/base/context.py +25 -13
- zrb/task/base/execution.py +52 -47
- zrb/task/base/lifecycle.py +7 -4
- zrb/task/base_task.py +48 -49
- zrb/task/base_trigger.py +4 -1
- zrb/task/cmd_task.py +6 -0
- zrb/task/http_check.py +11 -5
- zrb/task/make_task.py +3 -0
- zrb/task/rsync_task.py +5 -0
- zrb/task/scaffolder.py +7 -4
- zrb/task/scheduler.py +3 -0
- 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/cli/markdown.py +22 -2
- zrb/util/cmd/command.py +33 -10
- zrb/util/file.py +51 -32
- zrb/util/match.py +78 -0
- zrb/util/run.py +3 -3
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/METADATA +9 -15
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/RECORD +100 -128
- zrb/attr/__init__.py +0 -0
- zrb/builtin/llm/attachment.py +0 -40
- zrb/builtin/llm/chat_completion.py +0 -274
- zrb/builtin/llm/chat_session.py +0 -270
- zrb/builtin/llm/chat_session_cmd.py +0 -288
- zrb/builtin/llm/chat_trigger.py +0 -79
- zrb/builtin/llm/history.py +0 -71
- zrb/builtin/llm/input.py +0 -27
- zrb/builtin/llm/llm_ask.py +0 -269
- zrb/builtin/llm/previous-session.js +0 -21
- zrb/builtin/llm/tool/__init__.py +0 -0
- zrb/builtin/llm/tool/api.py +0 -75
- zrb/builtin/llm/tool/cli.py +0 -52
- zrb/builtin/llm/tool/code.py +0 -236
- zrb/builtin/llm/tool/file.py +0 -560
- zrb/builtin/llm/tool/note.py +0 -84
- zrb/builtin/llm/tool/sub_agent.py +0 -150
- zrb/builtin/llm/tool/web.py +0 -171
- 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/interactive_system_prompt.md +0 -29
- zrb/config/default_prompt/persona.md +0 -1
- zrb/config/default_prompt/summarization_prompt.md +0 -57
- zrb/config/default_prompt/system_prompt.md +0 -38
- zrb/config/llm_config.py +0 -339
- zrb/config/llm_context/config.py +0 -166
- zrb/config/llm_context/config_parser.py +0 -40
- zrb/config/llm_context/workflow.py +0 -81
- zrb/config/llm_rate_limitter.py +0 -190
- 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 -204
- zrb/task/llm/agent_runner.py +0 -152
- zrb/task/llm/config.py +0 -122
- zrb/task/llm/conversation_history.py +0 -209
- zrb/task/llm/conversation_history_model.py +0 -67
- zrb/task/llm/default_workflow/coding/workflow.md +0 -41
- zrb/task/llm/default_workflow/copywriting/workflow.md +0 -68
- zrb/task/llm/default_workflow/git/workflow.md +0 -118
- zrb/task/llm/default_workflow/golang/workflow.md +0 -128
- zrb/task/llm/default_workflow/html-css/workflow.md +0 -135
- zrb/task/llm/default_workflow/java/workflow.md +0 -146
- zrb/task/llm/default_workflow/javascript/workflow.md +0 -158
- zrb/task/llm/default_workflow/python/workflow.md +0 -160
- zrb/task/llm/default_workflow/researching/workflow.md +0 -153
- zrb/task/llm/default_workflow/rust/workflow.md +0 -162
- zrb/task/llm/default_workflow/shell/workflow.md +0 -299
- zrb/task/llm/error.py +0 -95
- zrb/task/llm/file_replacement.py +0 -206
- zrb/task/llm/file_tool_model.py +0 -57
- zrb/task/llm/history_processor.py +0 -206
- zrb/task/llm/history_summarization.py +0 -25
- zrb/task/llm/print_node.py +0 -221
- zrb/task/llm/prompt.py +0 -321
- zrb/task/llm/subagent_conversation_history.py +0 -41
- zrb/task/llm/tool_wrapper.py +0 -361
- zrb/task/llm/typing.py +0 -3
- zrb/task/llm/workflow.py +0 -76
- zrb/task/llm_task.py +0 -379
- 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/{config/default_prompt/file_extractor_system_prompt.md → llm/prompt/markdown/file_extractor.md} +0 -0
- /zrb/{config/default_prompt/repo_extractor_system_prompt.md → llm/prompt/markdown/repo_extractor.md} +0 -0
- /zrb/{config/default_prompt/repo_summarizer_system_prompt.md → llm/prompt/markdown/repo_summarizer.md} +0 -0
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/WHEEL +0 -0
- {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/entry_points.txt +0 -0
zrb/config/helper.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_env(env_name: str | list[str], default: str = "", prefix: str = "ZRB") -> str:
|
|
7
|
+
env_name_list = env_name if isinstance(env_name, list) else [env_name]
|
|
8
|
+
for name in env_name_list:
|
|
9
|
+
value = os.getenv(f"{prefix}_{name}", None)
|
|
10
|
+
if value is not None:
|
|
11
|
+
return value
|
|
12
|
+
return default
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_current_shell() -> str:
|
|
16
|
+
if platform.system() == "Windows":
|
|
17
|
+
return "PowerShell"
|
|
18
|
+
current_shell = os.getenv("SHELL", "")
|
|
19
|
+
if current_shell.endswith("zsh"):
|
|
20
|
+
return "zsh"
|
|
21
|
+
return "bash"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_default_diff_edit_command(editor: str) -> str:
|
|
25
|
+
if editor in [
|
|
26
|
+
"code",
|
|
27
|
+
"vscode",
|
|
28
|
+
"vscodium",
|
|
29
|
+
"windsurf",
|
|
30
|
+
"cursor",
|
|
31
|
+
"zed",
|
|
32
|
+
"zeditor",
|
|
33
|
+
"agy",
|
|
34
|
+
]:
|
|
35
|
+
return f"{editor} --wait --diff {{old}} {{new}}"
|
|
36
|
+
if editor == "emacs":
|
|
37
|
+
return 'emacs --eval \'(ediff-files "{old}" "{new}")\''
|
|
38
|
+
if editor in ["nvim", "vim"]:
|
|
39
|
+
return (
|
|
40
|
+
f"{editor} -d {{old}} {{new}} "
|
|
41
|
+
"-i NONE "
|
|
42
|
+
'-c "wincmd h | set readonly | wincmd l" '
|
|
43
|
+
'-c "highlight DiffAdd cterm=bold ctermbg=22 guibg=#005f00 | highlight DiffChange cterm=bold ctermbg=24 guibg=#005f87 | highlight DiffText ctermbg=21 guibg=#0000af | highlight DiffDelete ctermbg=52 guibg=#5f0000" ' # noqa
|
|
44
|
+
'-c "set showtabline=2 | set tabline=[Instructions]\\ :wqa(save\\ &\\ quit)\\ \\|\\ i/esc(toggle\\ edit\\ mode)" ' # noqa
|
|
45
|
+
'-c "wincmd h | setlocal statusline=OLD\\ FILE" '
|
|
46
|
+
'-c "wincmd l | setlocal statusline=%#StatusBold#NEW\\ FILE\\ :wqa(save\\ &\\ quit)\\ \\|\\ i/esc(toggle\\ edit\\ mode)" ' # noqa
|
|
47
|
+
'-c "autocmd BufWritePost * wqa"'
|
|
48
|
+
)
|
|
49
|
+
return 'vimdiff {old} {new} +"setlocal ro" +"wincmd l" +"autocmd BufWritePost <buffer> qa"' # noqa
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_log_level(level: str) -> int:
|
|
53
|
+
level = level.upper()
|
|
54
|
+
log_levels = {
|
|
55
|
+
"CRITICAL": logging.CRITICAL, # 50
|
|
56
|
+
"FATAL": logging.CRITICAL, # 50
|
|
57
|
+
"ERROR": logging.ERROR, # 40
|
|
58
|
+
"WARN": logging.WARNING, # 30
|
|
59
|
+
"WARNING": logging.WARNING, # 30
|
|
60
|
+
"INFO": logging.INFO, # 20
|
|
61
|
+
"DEBUG": logging.DEBUG, # 10
|
|
62
|
+
"NOTSET": logging.NOTSET, # 0
|
|
63
|
+
}
|
|
64
|
+
if level in log_levels:
|
|
65
|
+
return log_levels[level]
|
|
66
|
+
return logging.WARNING
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_max_token_threshold(
|
|
70
|
+
factor: float, max_tokens_per_minute: int, max_tokens_per_request: int
|
|
71
|
+
) -> int:
|
|
72
|
+
return round(factor * min(max_tokens_per_minute, max_tokens_per_request))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def limit_token_threshold(
|
|
76
|
+
threshold: int,
|
|
77
|
+
factor: float,
|
|
78
|
+
max_tokens_per_minute: int,
|
|
79
|
+
max_tokens_per_request: int,
|
|
80
|
+
) -> int:
|
|
81
|
+
return min(
|
|
82
|
+
threshold,
|
|
83
|
+
get_max_token_threshold(factor, max_tokens_per_minute, max_tokens_per_request),
|
|
84
|
+
)
|
zrb/config/web_auth_config.py
CHANGED
|
@@ -41,58 +41,108 @@ class WebAuthConfig:
|
|
|
41
41
|
return self._secret_key
|
|
42
42
|
return CFG.WEB_SECRET_KEY
|
|
43
43
|
|
|
44
|
+
@secret_key.setter
|
|
45
|
+
def secret_key(self, secret_key: str):
|
|
46
|
+
self._secret_key = secret_key
|
|
47
|
+
|
|
44
48
|
@property
|
|
45
49
|
def access_token_expire_minutes(self) -> int:
|
|
46
50
|
if self._access_token_expire_minutes is not None:
|
|
47
51
|
return self._access_token_expire_minutes
|
|
48
52
|
return CFG.WEB_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES
|
|
49
53
|
|
|
54
|
+
@access_token_expire_minutes.setter
|
|
55
|
+
def access_token_expire_minutes(self, minutes: int):
|
|
56
|
+
self._access_token_expire_minutes = minutes
|
|
57
|
+
|
|
50
58
|
@property
|
|
51
59
|
def refresh_token_expire_minutes(self) -> int:
|
|
52
60
|
if self._refresh_token_expire_minutes is not None:
|
|
53
61
|
return self._refresh_token_expire_minutes
|
|
54
62
|
return CFG.WEB_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES
|
|
55
63
|
|
|
64
|
+
@refresh_token_expire_minutes.setter
|
|
65
|
+
def refresh_token_expire_minutes(self, minutes: int):
|
|
66
|
+
self._refresh_token_expire_minutes = minutes
|
|
67
|
+
|
|
56
68
|
@property
|
|
57
69
|
def access_token_cookie_name(self) -> str:
|
|
58
70
|
if self._access_token_cookie_name is not None:
|
|
59
71
|
return self._access_token_cookie_name
|
|
60
72
|
return CFG.WEB_ACCESS_TOKEN_COOKIE_NAME
|
|
61
73
|
|
|
74
|
+
@access_token_cookie_name.setter
|
|
75
|
+
def access_token_cookie_name(self, name: str):
|
|
76
|
+
self._access_token_cookie_name = name
|
|
77
|
+
|
|
62
78
|
@property
|
|
63
79
|
def refresh_token_cookie_name(self) -> str:
|
|
64
80
|
if self._refresh_token_cookie_name is not None:
|
|
65
81
|
return self._refresh_token_cookie_name
|
|
66
82
|
return CFG.WEB_REFRESH_TOKEN_COOKIE_NAME
|
|
67
83
|
|
|
84
|
+
@refresh_token_cookie_name.setter
|
|
85
|
+
def refresh_token_cookie_name(self, name: str):
|
|
86
|
+
self._refresh_token_cookie_name = name
|
|
87
|
+
|
|
68
88
|
@property
|
|
69
89
|
def enable_auth(self) -> bool:
|
|
70
90
|
if self._enable_auth is not None:
|
|
71
91
|
return self._enable_auth
|
|
72
92
|
return CFG.WEB_ENABLE_AUTH
|
|
73
93
|
|
|
94
|
+
@enable_auth.setter
|
|
95
|
+
def enable_auth(self, enable: bool):
|
|
96
|
+
self._enable_auth = enable
|
|
97
|
+
|
|
74
98
|
@property
|
|
75
99
|
def super_admin_username(self) -> str:
|
|
76
100
|
if self._super_admin_username is not None:
|
|
77
101
|
return self._super_admin_username
|
|
78
102
|
return CFG.WEB_SUPER_ADMIN_USERNAME
|
|
79
103
|
|
|
104
|
+
@super_admin_username.setter
|
|
105
|
+
def super_admin_username(self, username: str):
|
|
106
|
+
self._super_admin_username = username
|
|
107
|
+
|
|
80
108
|
@property
|
|
81
109
|
def super_admin_password(self) -> str:
|
|
82
110
|
if self._super_admin_password is not None:
|
|
83
111
|
return self._super_admin_password
|
|
84
112
|
return CFG.WEB_SUPER_ADMIN_PASSWORD
|
|
85
113
|
|
|
114
|
+
@super_admin_password.setter
|
|
115
|
+
def super_admin_password(self, password: str):
|
|
116
|
+
self._super_admin_password = password
|
|
117
|
+
|
|
86
118
|
@property
|
|
87
119
|
def guest_username(self) -> str:
|
|
88
120
|
if self._guest_username is not None:
|
|
89
121
|
return self._guest_username
|
|
90
122
|
return CFG.WEB_GUEST_USERNAME
|
|
91
123
|
|
|
124
|
+
@guest_username.setter
|
|
125
|
+
def guest_username(self, username: str):
|
|
126
|
+
self._guest_username = username
|
|
127
|
+
|
|
92
128
|
@property
|
|
93
129
|
def guest_accessible_tasks(self) -> list[AnyTask | str]:
|
|
94
130
|
return self._guest_accessible_tasks
|
|
95
131
|
|
|
132
|
+
@guest_accessible_tasks.setter
|
|
133
|
+
def guest_accessible_tasks(self, tasks: list[AnyTask | str]):
|
|
134
|
+
self._guest_accessible_tasks = tasks
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def find_user_by_username_callback(self) -> Callable[[str], "User | None"] | None:
|
|
138
|
+
return self._find_user_by_username
|
|
139
|
+
|
|
140
|
+
@find_user_by_username_callback.setter
|
|
141
|
+
def find_user_by_username_callback(
|
|
142
|
+
self, find_user_by_username: Callable[[str], "User | None"]
|
|
143
|
+
):
|
|
144
|
+
self._find_user_by_username = find_user_by_username
|
|
145
|
+
|
|
96
146
|
@property
|
|
97
147
|
def default_user(self) -> "User":
|
|
98
148
|
from zrb.runner.web_schema.user import User
|
|
@@ -127,41 +177,6 @@ class WebAuthConfig:
|
|
|
127
177
|
return [self.default_user]
|
|
128
178
|
return self._user_list + [self.super_admin, self.default_user]
|
|
129
179
|
|
|
130
|
-
def set_secret_key(self, secret_key: str):
|
|
131
|
-
self._secret_key = secret_key
|
|
132
|
-
|
|
133
|
-
def set_access_token_expire_minutes(self, minutes: int):
|
|
134
|
-
self._access_token_expire_minutes = minutes
|
|
135
|
-
|
|
136
|
-
def set_refresh_token_expire_minutes(self, minutes: int):
|
|
137
|
-
self._refresh_token_expire_minutes = minutes
|
|
138
|
-
|
|
139
|
-
def set_access_token_cookie_name(self, name: str):
|
|
140
|
-
self._access_token_cookie_name = name
|
|
141
|
-
|
|
142
|
-
def set_refresh_token_cookie_name(self, name: str):
|
|
143
|
-
self._refresh_token_cookie_name = name
|
|
144
|
-
|
|
145
|
-
def set_enable_auth(self, enable: bool):
|
|
146
|
-
self._enable_auth = enable
|
|
147
|
-
|
|
148
|
-
def set_super_admin_username(self, username: str):
|
|
149
|
-
self._super_admin_username = username
|
|
150
|
-
|
|
151
|
-
def set_super_admin_password(self, password: str):
|
|
152
|
-
self._super_admin_password = password
|
|
153
|
-
|
|
154
|
-
def set_guest_username(self, username: str):
|
|
155
|
-
self._guest_username = username
|
|
156
|
-
|
|
157
|
-
def set_guest_accessible_tasks(self, tasks: list[AnyTask | str]):
|
|
158
|
-
self._guest_accessible_tasks = tasks
|
|
159
|
-
|
|
160
|
-
def set_find_user_by_username(
|
|
161
|
-
self, find_user_by_username: Callable[[str], "User | None"]
|
|
162
|
-
):
|
|
163
|
-
self._find_user_by_username = find_user_by_username
|
|
164
|
-
|
|
165
180
|
def append_user(self, user: "User"):
|
|
166
181
|
duplicates = [
|
|
167
182
|
existing_user
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations # Enables forward references
|
|
2
2
|
|
|
3
|
+
import sys
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TextIO
|
|
5
6
|
|
|
6
7
|
from zrb.dot_dict.dot_dict import DotDict
|
|
7
|
-
from zrb.xcom.xcom import Xcom
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from zrb.session import any_session
|
|
@@ -87,3 +87,14 @@ class AnySharedContext(ABC):
|
|
|
87
87
|
str: The rendered template as a string.
|
|
88
88
|
"""
|
|
89
89
|
pass
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def shared_print(
|
|
93
|
+
self,
|
|
94
|
+
*values: object,
|
|
95
|
+
sep: str = " ",
|
|
96
|
+
end: str = "\n",
|
|
97
|
+
file: TextIO | None = sys.stderr,
|
|
98
|
+
flush: bool = True,
|
|
99
|
+
):
|
|
100
|
+
pass
|
zrb/context/context.py
CHANGED
|
@@ -101,6 +101,18 @@ class Context(AnyContext):
|
|
|
101
101
|
return template
|
|
102
102
|
return float(self.render(template))
|
|
103
103
|
|
|
104
|
+
def shared_print(
|
|
105
|
+
self,
|
|
106
|
+
*values: object,
|
|
107
|
+
sep: str = " ",
|
|
108
|
+
end: str = "\n",
|
|
109
|
+
file: TextIO | None = sys.stderr,
|
|
110
|
+
flush: bool = True,
|
|
111
|
+
):
|
|
112
|
+
return self._shared_ctx.shared_print(
|
|
113
|
+
*values, sep=sep, end=end, file=file, flush=flush
|
|
114
|
+
)
|
|
115
|
+
|
|
104
116
|
def print(
|
|
105
117
|
self,
|
|
106
118
|
*values: object,
|
|
@@ -110,11 +122,14 @@ class Context(AnyContext):
|
|
|
110
122
|
flush: bool = True,
|
|
111
123
|
plain: bool = False,
|
|
112
124
|
):
|
|
113
|
-
|
|
125
|
+
if sep is None:
|
|
126
|
+
sep = " "
|
|
127
|
+
if end is None:
|
|
128
|
+
end = "\n"
|
|
114
129
|
message = sep.join([f"{value}" for value in values])
|
|
115
130
|
if plain:
|
|
116
131
|
# self.append_to_shared_log(remove_style(message))
|
|
117
|
-
|
|
132
|
+
self.shared_print(message, sep=sep, end=end, file=file, flush=flush)
|
|
118
133
|
self.append_to_shared_log(remove_style(f"{message}{end}"))
|
|
119
134
|
return
|
|
120
135
|
color = self._color
|
|
@@ -137,7 +152,20 @@ class Context(AnyContext):
|
|
|
137
152
|
prefix = f"{formatted_time}{attempt_status} {padded_styled_task_name} ⬤ "
|
|
138
153
|
self.append_to_shared_log(remove_style(f"{prefix} {message}{end}"))
|
|
139
154
|
stylized_prefix = stylize(prefix, color=color)
|
|
140
|
-
|
|
155
|
+
self.shared_print(
|
|
156
|
+
f"{stylized_prefix} {message}", sep=sep, end=end, file=file, flush=flush
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def print_err(
|
|
160
|
+
self,
|
|
161
|
+
*values: object,
|
|
162
|
+
sep: str | None = " ",
|
|
163
|
+
end: str | None = "\n",
|
|
164
|
+
file: TextIO | None = sys.stderr,
|
|
165
|
+
flush: bool = True,
|
|
166
|
+
plain: bool = False,
|
|
167
|
+
):
|
|
168
|
+
self.print(*values, sep=sep, end=end, file=file, flush=flush, plain=plain)
|
|
141
169
|
|
|
142
170
|
def log_debug(
|
|
143
171
|
self,
|
zrb/context/print_fn.py
ADDED
zrb/context/shared_context.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import sys
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, TextIO
|
|
4
4
|
|
|
5
5
|
from zrb.config.config import CFG
|
|
6
6
|
from zrb.context.any_shared_context import AnySharedContext
|
|
7
|
+
from zrb.context.print_fn import PrintFn
|
|
7
8
|
from zrb.dot_dict.dot_dict import DotDict
|
|
8
9
|
from zrb.session.any_session import AnySession
|
|
9
10
|
from zrb.util.string.conversion import (
|
|
@@ -28,6 +29,7 @@ class SharedContext(AnySharedContext):
|
|
|
28
29
|
xcom: dict[str, Xcom] = {},
|
|
29
30
|
logging_level: int | None = None,
|
|
30
31
|
is_web_mode: bool = False,
|
|
32
|
+
print_fn: PrintFn | None = None,
|
|
31
33
|
):
|
|
32
34
|
self.__logging_level = logging_level
|
|
33
35
|
self._input = DotDict(input)
|
|
@@ -37,6 +39,7 @@ class SharedContext(AnySharedContext):
|
|
|
37
39
|
self._session: AnySession | None = None
|
|
38
40
|
self._log = []
|
|
39
41
|
self._is_web_mode = is_web_mode
|
|
42
|
+
self._print_fn = print_fn if print_fn is not None else print
|
|
40
43
|
|
|
41
44
|
def __repr__(self):
|
|
42
45
|
class_name = self.__class__.__name__
|
|
@@ -108,3 +111,13 @@ class SharedContext(AnySharedContext):
|
|
|
108
111
|
"double_quote": double_quote,
|
|
109
112
|
},
|
|
110
113
|
)
|
|
114
|
+
|
|
115
|
+
def shared_print(
|
|
116
|
+
self,
|
|
117
|
+
*values: object,
|
|
118
|
+
sep: str = " ",
|
|
119
|
+
end: str = "\n",
|
|
120
|
+
file: TextIO | None = sys.stderr,
|
|
121
|
+
flush: bool = True,
|
|
122
|
+
):
|
|
123
|
+
return self._print_fn(*values, sep=sep, end=end, file=file, flush=flush)
|
zrb/input/option_input.py
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
1
3
|
from zrb.attr.type import StrAttr, StrListAttr
|
|
2
4
|
from zrb.context.any_shared_context import AnySharedContext
|
|
3
5
|
from zrb.input.base_input import BaseInput
|
|
4
6
|
from zrb.util.attr import get_str_list_attr
|
|
7
|
+
from zrb.util.match import fuzzy_match
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from prompt_toolkit.completion import Completer
|
|
5
11
|
|
|
6
12
|
|
|
7
13
|
class OptionInput(BaseInput):
|
|
@@ -58,10 +64,32 @@ class OptionInput(BaseInput):
|
|
|
58
64
|
self, shared_ctx: AnySharedContext, prompt_message: str, options: list[str]
|
|
59
65
|
) -> str:
|
|
60
66
|
from prompt_toolkit import PromptSession
|
|
61
|
-
from prompt_toolkit.completion import WordCompleter
|
|
62
67
|
|
|
63
68
|
if shared_ctx.is_tty:
|
|
64
69
|
reader = PromptSession()
|
|
65
|
-
option_completer =
|
|
70
|
+
option_completer = self._get_option_completer(options)
|
|
66
71
|
return reader.prompt(f"{prompt_message}: ", completer=option_completer)
|
|
67
72
|
return input(f"{prompt_message}: ")
|
|
73
|
+
|
|
74
|
+
def _get_option_completer(self, options: list[str]) -> "Completer":
|
|
75
|
+
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
|
|
76
|
+
from prompt_toolkit.document import Document
|
|
77
|
+
|
|
78
|
+
class OptionCompleter(Completer):
|
|
79
|
+
def __init__(self, options: list[str]):
|
|
80
|
+
self._options = options
|
|
81
|
+
|
|
82
|
+
def get_completions(
|
|
83
|
+
self, document: Document, complete_event: CompleteEvent
|
|
84
|
+
):
|
|
85
|
+
search_pattern = document.get_word_before_cursor(WORD=True)
|
|
86
|
+
candidates = []
|
|
87
|
+
for option in self._options:
|
|
88
|
+
matched, score = fuzzy_match(option, search_pattern)
|
|
89
|
+
if matched:
|
|
90
|
+
candidates.append((score, option))
|
|
91
|
+
candidates.sort(key=lambda x: (x[0], x[1]))
|
|
92
|
+
for _, option in candidates:
|
|
93
|
+
yield Completion(option, start_position=-len(search_pattern))
|
|
94
|
+
|
|
95
|
+
return OptionCompleter(options)
|
zrb/llm/agent/agent.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from contextvars import ContextVar
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
3
|
+
|
|
4
|
+
from zrb.llm.config.config import llm_config as default_llm_config
|
|
5
|
+
from zrb.llm.config.limiter import LLMLimiter
|
|
6
|
+
from zrb.llm.util.attachment import normalize_attachments
|
|
7
|
+
from zrb.llm.util.prompt import expand_prompt
|
|
8
|
+
|
|
9
|
+
# Context variable to propagate tool confirmation callback to sub-agents
|
|
10
|
+
tool_confirmation_var: ContextVar[Callable[[Any], Any] | None] = ContextVar(
|
|
11
|
+
"tool_confirmation", default=None
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from pydantic_ai import Agent, DeferredToolRequests, DeferredToolResults, Tool
|
|
16
|
+
from pydantic_ai._agent_graph import HistoryProcessor
|
|
17
|
+
from pydantic_ai.messages import UserPromptPart
|
|
18
|
+
from pydantic_ai.models import Model
|
|
19
|
+
from pydantic_ai.output import OutputDataT, OutputSpec
|
|
20
|
+
from pydantic_ai.settings import ModelSettings
|
|
21
|
+
from pydantic_ai.tools import ToolFuncEither
|
|
22
|
+
from pydantic_ai.toolsets import AbstractToolset
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_agent(
|
|
26
|
+
model: "Model | str | None" = None,
|
|
27
|
+
system_prompt: str = "",
|
|
28
|
+
tools: list["Tool | ToolFuncEither"] = [],
|
|
29
|
+
toolsets: list["AbstractToolset[None]"] = [],
|
|
30
|
+
model_settings: "ModelSettings | None" = None,
|
|
31
|
+
history_processors: list["HistoryProcessor"] | None = None,
|
|
32
|
+
output_type: "OutputSpec[OutputDataT]" = str,
|
|
33
|
+
retries: int = 1,
|
|
34
|
+
yolo: bool = False,
|
|
35
|
+
) -> "Agent[None, Any]":
|
|
36
|
+
from pydantic_ai import Agent, DeferredToolRequests
|
|
37
|
+
from pydantic_ai.toolsets import FunctionToolset
|
|
38
|
+
|
|
39
|
+
# Expand system prompt with references
|
|
40
|
+
effective_system_prompt = expand_prompt(system_prompt)
|
|
41
|
+
|
|
42
|
+
final_output_type = output_type
|
|
43
|
+
effective_toolsets = list(toolsets)
|
|
44
|
+
if tools:
|
|
45
|
+
effective_toolsets.append(FunctionToolset(tools=tools))
|
|
46
|
+
|
|
47
|
+
if not yolo:
|
|
48
|
+
final_output_type = output_type | DeferredToolRequests
|
|
49
|
+
effective_toolsets = [ts.approval_required() for ts in effective_toolsets]
|
|
50
|
+
|
|
51
|
+
if model is None:
|
|
52
|
+
model = default_llm_config.model
|
|
53
|
+
|
|
54
|
+
return Agent(
|
|
55
|
+
model=model,
|
|
56
|
+
output_type=final_output_type,
|
|
57
|
+
instructions=effective_system_prompt,
|
|
58
|
+
toolsets=effective_toolsets,
|
|
59
|
+
model_settings=model_settings,
|
|
60
|
+
history_processors=history_processors,
|
|
61
|
+
retries=retries,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def run_agent(
|
|
66
|
+
agent: "Agent[None, Any]",
|
|
67
|
+
message: str | None,
|
|
68
|
+
message_history: list[Any],
|
|
69
|
+
limiter: LLMLimiter,
|
|
70
|
+
attachments: list[Any] | None = None,
|
|
71
|
+
print_fn: Callable[[str], Any] = print,
|
|
72
|
+
event_handler: Callable[[Any], Any] | None = None,
|
|
73
|
+
tool_confirmation: Callable[[Any], Any] | None = None,
|
|
74
|
+
) -> tuple[Any, list[Any]]:
|
|
75
|
+
"""
|
|
76
|
+
Runs the agent with rate limiting, history management, and optional CLI confirmation loop.
|
|
77
|
+
Returns (result_output, new_message_history).
|
|
78
|
+
"""
|
|
79
|
+
import asyncio
|
|
80
|
+
|
|
81
|
+
from pydantic_ai import AgentRunResultEvent, DeferredToolRequests
|
|
82
|
+
|
|
83
|
+
# Resolve tool confirmation callback (Arg > Context > None)
|
|
84
|
+
effective_tool_confirmation = tool_confirmation
|
|
85
|
+
if effective_tool_confirmation is None:
|
|
86
|
+
effective_tool_confirmation = tool_confirmation_var.get()
|
|
87
|
+
|
|
88
|
+
# Set context var for sub-agents
|
|
89
|
+
token = tool_confirmation_var.set(effective_tool_confirmation)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
# Expand user message with references
|
|
93
|
+
effective_message = expand_prompt(message) if message else message
|
|
94
|
+
|
|
95
|
+
# Prepare Prompt Content
|
|
96
|
+
prompt_content = _get_prompt_content(effective_message, attachments, print_fn)
|
|
97
|
+
|
|
98
|
+
# 1. Prune & Throttle
|
|
99
|
+
current_history = await _acquire_rate_limit(
|
|
100
|
+
limiter, prompt_content, message_history, print_fn
|
|
101
|
+
)
|
|
102
|
+
current_message = prompt_content
|
|
103
|
+
current_results = None
|
|
104
|
+
|
|
105
|
+
# 2. Execution Loop
|
|
106
|
+
while True:
|
|
107
|
+
result_output = None
|
|
108
|
+
run_history = []
|
|
109
|
+
|
|
110
|
+
async for event in agent.run_stream_events(
|
|
111
|
+
current_message,
|
|
112
|
+
message_history=current_history,
|
|
113
|
+
deferred_tool_results=current_results,
|
|
114
|
+
):
|
|
115
|
+
await asyncio.sleep(0)
|
|
116
|
+
if isinstance(event, AgentRunResultEvent):
|
|
117
|
+
result = event.result
|
|
118
|
+
result_output = result.output
|
|
119
|
+
run_history = result.all_messages()
|
|
120
|
+
if event_handler:
|
|
121
|
+
await event_handler(event)
|
|
122
|
+
|
|
123
|
+
# Handle Deferred Calls
|
|
124
|
+
if isinstance(result_output, DeferredToolRequests):
|
|
125
|
+
current_results = await _process_deferred_requests(
|
|
126
|
+
result_output, effective_tool_confirmation
|
|
127
|
+
)
|
|
128
|
+
if current_results is None:
|
|
129
|
+
return result_output, run_history
|
|
130
|
+
# Prepare next iteration
|
|
131
|
+
current_message = None
|
|
132
|
+
current_history = run_history
|
|
133
|
+
continue
|
|
134
|
+
return result_output, run_history
|
|
135
|
+
finally:
|
|
136
|
+
tool_confirmation_var.reset(token)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _get_prompt_content(
|
|
140
|
+
message: str | None, attachments: list[Any] | None, print_fn: Callable[[str], Any]
|
|
141
|
+
) -> "list[UserPromptPart] | str | None":
|
|
142
|
+
from pydantic_ai.messages import UserPromptPart
|
|
143
|
+
|
|
144
|
+
prompt_content = message
|
|
145
|
+
if attachments:
|
|
146
|
+
attachments = normalize_attachments(attachments, print_fn)
|
|
147
|
+
parts: list[UserPromptPart] = []
|
|
148
|
+
if message:
|
|
149
|
+
parts.append(UserPromptPart(content=message))
|
|
150
|
+
parts.extend(attachments)
|
|
151
|
+
prompt_content = parts
|
|
152
|
+
return prompt_content
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def _acquire_rate_limit(
|
|
156
|
+
limiter: LLMLimiter,
|
|
157
|
+
message: str | None,
|
|
158
|
+
message_history: list[Any],
|
|
159
|
+
print_fn: Callable[[str], Any],
|
|
160
|
+
) -> list[Any]:
|
|
161
|
+
"""Prunes history and waits if rate limits are exceeded."""
|
|
162
|
+
if not message:
|
|
163
|
+
return message_history
|
|
164
|
+
|
|
165
|
+
# Prune
|
|
166
|
+
pruned_history = limiter.fit_context_window(message_history, message)
|
|
167
|
+
|
|
168
|
+
# Throttle
|
|
169
|
+
est_tokens = limiter.count_tokens(pruned_history) + limiter.count_tokens(message)
|
|
170
|
+
await limiter.acquire(
|
|
171
|
+
est_tokens, notifier=lambda msg: print_fn(msg) if msg else None
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return pruned_history
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def _process_deferred_requests(
|
|
178
|
+
result_output: "DeferredToolRequests",
|
|
179
|
+
effective_tool_confirmation: Callable[[Any], Any] | None,
|
|
180
|
+
) -> "DeferredToolResults | None":
|
|
181
|
+
"""Handles tool approvals/denials via callback or CLI fallback."""
|
|
182
|
+
import asyncio
|
|
183
|
+
import inspect
|
|
184
|
+
|
|
185
|
+
from pydantic_ai import DeferredToolResults, ToolApproved, ToolDenied
|
|
186
|
+
|
|
187
|
+
all_requests = (result_output.calls or []) + (result_output.approvals or [])
|
|
188
|
+
if not all_requests:
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
current_results = DeferredToolResults()
|
|
192
|
+
|
|
193
|
+
for call in all_requests:
|
|
194
|
+
if effective_tool_confirmation:
|
|
195
|
+
res = effective_tool_confirmation(call)
|
|
196
|
+
if inspect.isawaitable(res):
|
|
197
|
+
result = await res
|
|
198
|
+
else:
|
|
199
|
+
result = res
|
|
200
|
+
current_results.approvals[call.tool_call_id] = result
|
|
201
|
+
else:
|
|
202
|
+
# CLI Fallback
|
|
203
|
+
prompt_text = f"Execute tool '{call.tool_name}' with args {call.args}?"
|
|
204
|
+
prompt_cli = f"\n[?] {prompt_text} (y/N) "
|
|
205
|
+
|
|
206
|
+
# We use asyncio.to_thread(input, ...) to avoid blocking the loop
|
|
207
|
+
user_input = await asyncio.to_thread(input, prompt_cli)
|
|
208
|
+
answer = user_input.strip().lower() in ("y", "yes")
|
|
209
|
+
|
|
210
|
+
if answer:
|
|
211
|
+
current_results.approvals[call.tool_call_id] = ToolApproved()
|
|
212
|
+
else:
|
|
213
|
+
current_results.approvals[call.tool_call_id] = ToolDenied("User denied")
|
|
214
|
+
|
|
215
|
+
return current_results
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from zrb.llm.agent.agent import create_agent
|
|
4
|
+
from zrb.llm.prompt.default import get_summarizer_system_prompt
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from pydantic_ai import Agent
|
|
8
|
+
from pydantic_ai.models import Model
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_summarizer_agent(
|
|
12
|
+
model: "str | None | Model" = None,
|
|
13
|
+
system_prompt: str | None = None,
|
|
14
|
+
) -> "Agent[None, str]":
|
|
15
|
+
effective_system_prompt = system_prompt or get_summarizer_system_prompt()
|
|
16
|
+
|
|
17
|
+
return create_agent(
|
|
18
|
+
model=model,
|
|
19
|
+
system_prompt=effective_system_prompt,
|
|
20
|
+
)
|
zrb/llm/app/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from zrb.llm.app.confirmation.allow_tool import allow_tool_usage
|
|
2
|
+
from zrb.llm.app.confirmation.handler import ConfirmationMiddleware, last_confirmation
|
|
3
|
+
from zrb.llm.app.confirmation.replace_confirmation import replace_confirmation
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"allow_tool_usage",
|
|
7
|
+
"ConfirmationMiddleware",
|
|
8
|
+
"last_confirmation",
|
|
9
|
+
"replace_confirmation",
|
|
10
|
+
]
|