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/task/llm/tool_wrapper.py
DELETED
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
import functools
|
|
2
|
-
import inspect
|
|
3
|
-
import signal
|
|
4
|
-
import traceback
|
|
5
|
-
import typing
|
|
6
|
-
from collections.abc import Callable
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
8
|
-
|
|
9
|
-
from zrb.config.config import CFG
|
|
10
|
-
from zrb.config.llm_rate_limitter import llm_rate_limitter
|
|
11
|
-
from zrb.context.any_context import AnyContext
|
|
12
|
-
from zrb.task.llm.error import ToolExecutionError
|
|
13
|
-
from zrb.task.llm.file_replacement import edit_replacement, is_single_path_replacement
|
|
14
|
-
from zrb.util.callable import get_callable_name
|
|
15
|
-
from zrb.util.cli.markdown import render_markdown
|
|
16
|
-
from zrb.util.cli.style import (
|
|
17
|
-
stylize_blue,
|
|
18
|
-
stylize_error,
|
|
19
|
-
stylize_faint,
|
|
20
|
-
stylize_green,
|
|
21
|
-
stylize_yellow,
|
|
22
|
-
)
|
|
23
|
-
from zrb.util.cli.text import edit_text
|
|
24
|
-
from zrb.util.run import run_async
|
|
25
|
-
from zrb.util.string.conversion import to_boolean
|
|
26
|
-
from zrb.util.yaml import edit_obj, yaml_dump
|
|
27
|
-
|
|
28
|
-
if TYPE_CHECKING:
|
|
29
|
-
from pydantic_ai import Tool
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class ToolExecutionCancelled(ValueError):
|
|
33
|
-
pass
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def wrap_tool(func: Callable, ctx: AnyContext, yolo_mode: bool | list[str]) -> "Tool":
|
|
37
|
-
"""Wraps a tool function to handle exceptions and context propagation."""
|
|
38
|
-
from pydantic_ai import RunContext, Tool
|
|
39
|
-
|
|
40
|
-
original_sig = inspect.signature(func)
|
|
41
|
-
needs_run_context_for_pydantic = _has_context_parameter(original_sig, RunContext)
|
|
42
|
-
wrapper = wrap_func(func, ctx, yolo_mode)
|
|
43
|
-
return Tool(wrapper, takes_ctx=needs_run_context_for_pydantic)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def wrap_func(func: Callable, ctx: AnyContext, yolo_mode: bool | list[str]) -> Callable:
|
|
47
|
-
original_sig = inspect.signature(func)
|
|
48
|
-
needs_any_context_for_injection = _has_context_parameter(original_sig, AnyContext)
|
|
49
|
-
# Pass individual flags to the wrapper creator
|
|
50
|
-
wrapper = _create_wrapper(
|
|
51
|
-
func=func,
|
|
52
|
-
original_signature=original_sig,
|
|
53
|
-
ctx=ctx,
|
|
54
|
-
needs_any_context_for_injection=needs_any_context_for_injection,
|
|
55
|
-
yolo_mode=yolo_mode,
|
|
56
|
-
)
|
|
57
|
-
_adjust_signature(wrapper, original_sig)
|
|
58
|
-
return wrapper
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _has_context_parameter(original_sig: inspect.Signature, context_type: type) -> bool:
|
|
62
|
-
"""
|
|
63
|
-
Checks if the function signature includes a parameter with the specified
|
|
64
|
-
context type annotation.
|
|
65
|
-
"""
|
|
66
|
-
return any(
|
|
67
|
-
_is_annotated_with_context(param.annotation, context_type)
|
|
68
|
-
for param in original_sig.parameters.values()
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _is_annotated_with_context(param_annotation, context_type):
|
|
73
|
-
"""
|
|
74
|
-
Checks if the parameter annotation is the specified context type
|
|
75
|
-
or a generic type containing it (e.g., Optional[ContextType]).
|
|
76
|
-
"""
|
|
77
|
-
if param_annotation is inspect.Parameter.empty:
|
|
78
|
-
return False
|
|
79
|
-
if param_annotation is context_type:
|
|
80
|
-
return True
|
|
81
|
-
# Check for generic types like Optional[ContextType] or Union[ContextType, ...]
|
|
82
|
-
origin = typing.get_origin(param_annotation)
|
|
83
|
-
args = typing.get_args(param_annotation)
|
|
84
|
-
if origin is not None and args:
|
|
85
|
-
# Check if context_type is one of the arguments of the generic type
|
|
86
|
-
return any(arg is context_type for arg in args)
|
|
87
|
-
return False
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _create_wrapper(
|
|
91
|
-
func: Callable,
|
|
92
|
-
original_signature: inspect.Signature,
|
|
93
|
-
ctx: AnyContext,
|
|
94
|
-
needs_any_context_for_injection: bool,
|
|
95
|
-
yolo_mode: bool | list[str],
|
|
96
|
-
) -> Callable:
|
|
97
|
-
"""Creates the core wrapper function."""
|
|
98
|
-
|
|
99
|
-
@functools.wraps(func)
|
|
100
|
-
async def wrapper(*args, **kwargs):
|
|
101
|
-
# Identify AnyContext parameter name from the original signature if needed
|
|
102
|
-
any_context_param_name = None
|
|
103
|
-
if needs_any_context_for_injection:
|
|
104
|
-
for param in original_signature.parameters.values():
|
|
105
|
-
if _is_annotated_with_context(param.annotation, AnyContext):
|
|
106
|
-
any_context_param_name = param.name
|
|
107
|
-
break # Found it, no need to continue
|
|
108
|
-
if any_context_param_name is None:
|
|
109
|
-
# This should not happen if needs_any_context_for_injection is True,
|
|
110
|
-
# but check for safety
|
|
111
|
-
raise ValueError(
|
|
112
|
-
"AnyContext parameter name not found in function signature."
|
|
113
|
-
)
|
|
114
|
-
# Inject the captured ctx into kwargs. This will overwrite if the LLM
|
|
115
|
-
# somehow provided it.
|
|
116
|
-
kwargs[any_context_param_name] = ctx
|
|
117
|
-
# We will need to overwrite SIGINT handler, so that when user press ctrl + c,
|
|
118
|
-
# the program won't immediately exit
|
|
119
|
-
original_sigint_handler = signal.getsignal(signal.SIGINT)
|
|
120
|
-
tool_name = get_callable_name(func)
|
|
121
|
-
try:
|
|
122
|
-
has_ever_edited = False
|
|
123
|
-
if not ctx.is_web_mode and ctx.is_tty:
|
|
124
|
-
if (
|
|
125
|
-
isinstance(yolo_mode, list) and func.__name__ not in yolo_mode
|
|
126
|
-
) or not yolo_mode:
|
|
127
|
-
approval, reason, kwargs, has_ever_edited = (
|
|
128
|
-
await _handle_user_response(ctx, func, args, kwargs)
|
|
129
|
-
)
|
|
130
|
-
if not approval:
|
|
131
|
-
raise ToolExecutionCancelled(
|
|
132
|
-
f"Tool execution cancelled. User disapproving: {reason}"
|
|
133
|
-
)
|
|
134
|
-
signal.signal(signal.SIGINT, _tool_wrapper_sigint_handler)
|
|
135
|
-
ctx.print(stylize_faint(f"Run {tool_name}"), plain=True)
|
|
136
|
-
result = await run_async(func(*args, **kwargs))
|
|
137
|
-
_check_tool_call_result_limit(result)
|
|
138
|
-
if has_ever_edited:
|
|
139
|
-
return {
|
|
140
|
-
"tool_call_result": result,
|
|
141
|
-
"new_tool_parameters": kwargs,
|
|
142
|
-
"message": "User correction: Tool was called with user's parameters",
|
|
143
|
-
}
|
|
144
|
-
return result
|
|
145
|
-
except BaseException as e:
|
|
146
|
-
error_model = ToolExecutionError(
|
|
147
|
-
tool_name=tool_name,
|
|
148
|
-
error_type=type(e).__name__,
|
|
149
|
-
message=str(e),
|
|
150
|
-
details=traceback.format_exc(),
|
|
151
|
-
)
|
|
152
|
-
return error_model.model_dump_json()
|
|
153
|
-
finally:
|
|
154
|
-
signal.signal(signal.SIGINT, original_sigint_handler)
|
|
155
|
-
|
|
156
|
-
return wrapper
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def _tool_wrapper_sigint_handler(signum, frame):
|
|
160
|
-
raise KeyboardInterrupt("SIGINT detected while running tool")
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def _check_tool_call_result_limit(result: Any):
|
|
164
|
-
if (
|
|
165
|
-
llm_rate_limitter.count_token(result)
|
|
166
|
-
> llm_rate_limitter.max_tokens_per_tool_call_result
|
|
167
|
-
):
|
|
168
|
-
raise ValueError("Result value is too large, please adjust the parameter")
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
async def _handle_user_response(
|
|
172
|
-
ctx: AnyContext,
|
|
173
|
-
func: Callable,
|
|
174
|
-
args: list[Any] | tuple[Any],
|
|
175
|
-
kwargs: dict[str, Any],
|
|
176
|
-
) -> tuple[bool, str, dict[str, Any], bool]:
|
|
177
|
-
has_ever_edited = False
|
|
178
|
-
while True:
|
|
179
|
-
func_call_str = _get_func_call_str(func, args, kwargs)
|
|
180
|
-
complete_confirmation_message = "\n".join(
|
|
181
|
-
[
|
|
182
|
-
f"\n🎰 >> {func_call_str}",
|
|
183
|
-
_get_detail_func_param(args, kwargs),
|
|
184
|
-
f"🎰 >> {_get_run_func_confirmation(func)}",
|
|
185
|
-
]
|
|
186
|
-
)
|
|
187
|
-
ctx.print(complete_confirmation_message, plain=True)
|
|
188
|
-
user_response = await _read_line(args, kwargs)
|
|
189
|
-
ctx.print("", plain=True)
|
|
190
|
-
new_kwargs, is_edited = _get_edited_kwargs(ctx, user_response, kwargs)
|
|
191
|
-
if is_edited:
|
|
192
|
-
kwargs = new_kwargs
|
|
193
|
-
has_ever_edited = True
|
|
194
|
-
continue
|
|
195
|
-
approval_and_reason = _get_user_approval_and_reason(
|
|
196
|
-
ctx, user_response, func_call_str
|
|
197
|
-
)
|
|
198
|
-
if approval_and_reason is None:
|
|
199
|
-
continue
|
|
200
|
-
approval, reason = approval_and_reason
|
|
201
|
-
return approval, reason, kwargs, has_ever_edited
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def _get_edited_kwargs(
|
|
205
|
-
ctx: AnyContext, user_response: str, kwargs: dict[str, Any]
|
|
206
|
-
) -> tuple[dict[str, Any], bool]:
|
|
207
|
-
user_edit_responses = [val for val in user_response.split(" ", maxsplit=2)]
|
|
208
|
-
if len(user_edit_responses) >= 1 and user_edit_responses[0].lower() != "edit":
|
|
209
|
-
return kwargs, False
|
|
210
|
-
while len(user_edit_responses) < 3:
|
|
211
|
-
user_edit_responses.append("")
|
|
212
|
-
key, val_str = user_edit_responses[1:]
|
|
213
|
-
# Make sure first segment of the key is in kwargs
|
|
214
|
-
if key != "":
|
|
215
|
-
key_parts = key.split(".")
|
|
216
|
-
if len(key_parts) > 0 and key_parts[0] not in kwargs:
|
|
217
|
-
return kwargs, True
|
|
218
|
-
# Handle replacement edit
|
|
219
|
-
if len(kwargs) == 1:
|
|
220
|
-
kwarg_key = list(kwargs.keys())[0]
|
|
221
|
-
if is_single_path_replacement(kwargs[kwarg_key]) and (
|
|
222
|
-
key == "" or key == kwarg_key
|
|
223
|
-
):
|
|
224
|
-
kwargs[kwarg_key], edited = edit_replacement(kwargs[kwarg_key])
|
|
225
|
-
return kwargs, True
|
|
226
|
-
# Handle other kind of edit
|
|
227
|
-
old_val_str = yaml_dump(kwargs, key)
|
|
228
|
-
if val_str == "":
|
|
229
|
-
val_str = edit_text(
|
|
230
|
-
prompt_message=f"# {key}" if key != "" else "",
|
|
231
|
-
value=old_val_str,
|
|
232
|
-
editor=CFG.DEFAULT_EDITOR,
|
|
233
|
-
extension=".yaml",
|
|
234
|
-
)
|
|
235
|
-
if old_val_str == val_str:
|
|
236
|
-
return kwargs, True
|
|
237
|
-
edited_kwargs = edit_obj(kwargs, key, val_str)
|
|
238
|
-
return edited_kwargs, True
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def _get_user_approval_and_reason(
|
|
242
|
-
ctx: AnyContext, user_response: str, func_call_str: str
|
|
243
|
-
) -> tuple[bool, str] | None:
|
|
244
|
-
user_approval_responses = [
|
|
245
|
-
val.strip() for val in user_response.split(",", maxsplit=1)
|
|
246
|
-
]
|
|
247
|
-
while len(user_approval_responses) < 2:
|
|
248
|
-
user_approval_responses.append("")
|
|
249
|
-
approval_str, reason = user_approval_responses
|
|
250
|
-
try:
|
|
251
|
-
approved = True if approval_str.strip() == "" else to_boolean(approval_str)
|
|
252
|
-
if not approved and reason == "":
|
|
253
|
-
reason = "User disapproving the tool execution"
|
|
254
|
-
return approved, reason
|
|
255
|
-
except Exception:
|
|
256
|
-
return False, user_response
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def _get_run_func_confirmation(func: Callable) -> str:
|
|
260
|
-
func_name = get_callable_name(func)
|
|
261
|
-
return render_markdown(
|
|
262
|
-
f"Allow to run `{func_name}`? (✅ `Yes` | ⛔ `No, <reason>` | 📝 `Edit <param> <value>`)"
|
|
263
|
-
).strip()
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def _get_detail_func_param(args: list[Any] | tuple[Any], kwargs: dict[str, Any]) -> str:
|
|
267
|
-
if not kwargs:
|
|
268
|
-
return ""
|
|
269
|
-
yaml_str = yaml_dump(kwargs)
|
|
270
|
-
# Create the final markdown string
|
|
271
|
-
markdown = f"```yaml\n{yaml_str}\n```"
|
|
272
|
-
return render_markdown(markdown)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def _get_func_call_str(
|
|
276
|
-
func: Callable, args: list[Any] | tuple[Any], kwargs: dict[str, Any]
|
|
277
|
-
) -> str:
|
|
278
|
-
func_name = get_callable_name(func)
|
|
279
|
-
normalized_args = [stylize_green(_truncate_arg(arg)) for arg in args]
|
|
280
|
-
normalized_kwargs = []
|
|
281
|
-
for key, val in kwargs.items():
|
|
282
|
-
truncated_val = _truncate_arg(f"{val}")
|
|
283
|
-
normalized_kwargs.append(
|
|
284
|
-
f"{stylize_yellow(key)}={stylize_green(truncated_val)}"
|
|
285
|
-
)
|
|
286
|
-
func_param_str = ", ".join(normalized_args + normalized_kwargs)
|
|
287
|
-
return f"{stylize_blue(func_name + '(')}{func_param_str}{stylize_blue(')')}"
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
def _truncate_arg(arg: str, length: int = 19) -> str:
|
|
291
|
-
normalized_arg = arg.replace("\n", "\\n")
|
|
292
|
-
if len(normalized_arg) > length:
|
|
293
|
-
return f"{normalized_arg[:length-4]} ..."
|
|
294
|
-
return normalized_arg
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
async def _read_line(args: list[Any] | tuple[Any], kwargs: dict[str, Any]):
|
|
298
|
-
from prompt_toolkit import PromptSession
|
|
299
|
-
from prompt_toolkit.completion import Completer, Completion
|
|
300
|
-
|
|
301
|
-
class ToolConfirmationCompleter(Completer):
|
|
302
|
-
"""Custom completer for tool confirmation that doesn't auto-complete partial words."""
|
|
303
|
-
|
|
304
|
-
def __init__(self, options, meta_dict):
|
|
305
|
-
self.options = options
|
|
306
|
-
self.meta_dict = meta_dict
|
|
307
|
-
|
|
308
|
-
def get_completions(self, document, complete_event):
|
|
309
|
-
text = document.text.strip()
|
|
310
|
-
|
|
311
|
-
# Only provide completions if:
|
|
312
|
-
# 1. Input is empty, OR
|
|
313
|
-
# 2. Input exactly matches the beginning of an option
|
|
314
|
-
if text == "":
|
|
315
|
-
# Show all options when nothing is typed
|
|
316
|
-
for option in self.options:
|
|
317
|
-
yield Completion(
|
|
318
|
-
option,
|
|
319
|
-
start_position=0,
|
|
320
|
-
display_meta=self.meta_dict.get(option, ""),
|
|
321
|
-
)
|
|
322
|
-
else:
|
|
323
|
-
# Only complete if text exactly matches the beginning of an option
|
|
324
|
-
for option in self.options:
|
|
325
|
-
if option.startswith(text):
|
|
326
|
-
yield Completion(
|
|
327
|
-
option,
|
|
328
|
-
start_position=-len(text),
|
|
329
|
-
display_meta=self.meta_dict.get(option, ""),
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
options = ["yes", "no", "edit"]
|
|
333
|
-
meta_dict = {
|
|
334
|
-
"yes": "Approve the execution",
|
|
335
|
-
"no": "Disapprove the execution",
|
|
336
|
-
"edit": "Edit tool execution parameters",
|
|
337
|
-
}
|
|
338
|
-
for key in kwargs:
|
|
339
|
-
options.append(f"edit {key}")
|
|
340
|
-
meta_dict[f"edit {key}"] = f"Edit tool execution parameter: {key}"
|
|
341
|
-
completer = ToolConfirmationCompleter(options, meta_dict)
|
|
342
|
-
reader = PromptSession()
|
|
343
|
-
return await reader.prompt_async(completer=completer)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
def _adjust_signature(wrapper: Callable, original_sig: inspect.Signature):
|
|
347
|
-
"""Adjusts the wrapper function's signature for schema generation."""
|
|
348
|
-
# The wrapper's signature should represent the arguments the *LLM* needs to provide.
|
|
349
|
-
# The LLM does not provide RunContext (pydantic-ai injects it) or AnyContext
|
|
350
|
-
# (we inject it). So, the wrapper's signature should be the original signature,
|
|
351
|
-
# minus any parameters annotated with RunContext or AnyContext.
|
|
352
|
-
|
|
353
|
-
from pydantic_ai import RunContext
|
|
354
|
-
|
|
355
|
-
params_for_schema = [
|
|
356
|
-
param
|
|
357
|
-
for param in original_sig.parameters.values()
|
|
358
|
-
if not _is_annotated_with_context(param.annotation, RunContext)
|
|
359
|
-
and not _is_annotated_with_context(param.annotation, AnyContext)
|
|
360
|
-
]
|
|
361
|
-
wrapper.__signature__ = inspect.Signature(parameters=params_for_schema)
|
zrb/task/llm/typing.py
DELETED
zrb/task/llm/workflow.py
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
from zrb.config.config import CFG
|
|
4
|
-
from zrb.config.llm_context.config import llm_context_config
|
|
5
|
-
from zrb.config.llm_context.workflow import LLMWorkflow
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def load_workflow(workflow_name: str | list[str]) -> str:
|
|
9
|
-
"""
|
|
10
|
-
Loads and formats one or more workflow documents for LLM consumption.
|
|
11
|
-
|
|
12
|
-
Retrieves workflows by name, formats with descriptive headers for LLM context injection.
|
|
13
|
-
|
|
14
|
-
Args:
|
|
15
|
-
workflow_name: Name or list of names of the workflow(s) to load
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
Formatted workflow content as a string with headers
|
|
19
|
-
|
|
20
|
-
Raises:
|
|
21
|
-
ValueError: If any specified workflow name is not found
|
|
22
|
-
"""
|
|
23
|
-
names = [workflow_name] if isinstance(workflow_name, str) else workflow_name
|
|
24
|
-
available_workflows = get_available_workflows()
|
|
25
|
-
contents = []
|
|
26
|
-
for name in names:
|
|
27
|
-
workflow = available_workflows.get(name.strip().lower())
|
|
28
|
-
if workflow is None:
|
|
29
|
-
raise ValueError(f"Workflow not found: {name}")
|
|
30
|
-
contents.append(
|
|
31
|
-
"\n".join(
|
|
32
|
-
[
|
|
33
|
-
f"# {workflow.name}",
|
|
34
|
-
f"> Workflow Location: `{workflow.path}`",
|
|
35
|
-
workflow.content,
|
|
36
|
-
]
|
|
37
|
-
)
|
|
38
|
-
)
|
|
39
|
-
return "\n".join(contents)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def get_available_workflows() -> dict[str, LLMWorkflow]:
|
|
43
|
-
available_workflows = {
|
|
44
|
-
workflow_name.strip().lower(): workflow
|
|
45
|
-
for workflow_name, workflow in llm_context_config.get_workflows().items()
|
|
46
|
-
}
|
|
47
|
-
# Define builtin workflow locations in order of precedence
|
|
48
|
-
builtin_workflow_locations = [
|
|
49
|
-
os.path.expanduser(additional_builtin_workflow_path)
|
|
50
|
-
for additional_builtin_workflow_path in CFG.LLM_BUILTIN_WORKFLOW_PATHS
|
|
51
|
-
if os.path.isdir(os.path.expanduser(additional_builtin_workflow_path))
|
|
52
|
-
]
|
|
53
|
-
builtin_workflow_locations.append(
|
|
54
|
-
os.path.join(os.path.dirname(__file__), "default_workflow")
|
|
55
|
-
)
|
|
56
|
-
# Load workflows from all locations
|
|
57
|
-
for workflow_location in builtin_workflow_locations:
|
|
58
|
-
if not os.path.isdir(workflow_location):
|
|
59
|
-
continue
|
|
60
|
-
for workflow_name in os.listdir(workflow_location):
|
|
61
|
-
workflow_dir = os.path.join(workflow_location, workflow_name)
|
|
62
|
-
workflow_file = os.path.join(workflow_dir, "workflow.md")
|
|
63
|
-
if not os.path.isfile(workflow_file):
|
|
64
|
-
workflow_file = os.path.join(workflow_dir, "SKILL.md")
|
|
65
|
-
if not os.path.isfile(path=workflow_file):
|
|
66
|
-
continue
|
|
67
|
-
# Only add if not already defined (earlier locations have precedence)
|
|
68
|
-
if workflow_name not in available_workflows:
|
|
69
|
-
with open(workflow_file, "r") as f:
|
|
70
|
-
workflow_content = f.read()
|
|
71
|
-
available_workflows[workflow_name] = LLMWorkflow(
|
|
72
|
-
name=workflow_name,
|
|
73
|
-
path=workflow_dir,
|
|
74
|
-
content=workflow_content,
|
|
75
|
-
)
|
|
76
|
-
return available_workflows
|