zrb 1.15.2__py3-none-any.whl → 1.15.4__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.
- zrb/builtin/llm/chat_session.py +8 -18
- zrb/builtin/llm/tool/code.py +4 -3
- zrb/builtin/llm/tool/sub_agent.py +2 -0
- zrb/task/llm/agent.py +6 -1
- zrb/task/llm/history_summarization.py +5 -2
- zrb/task/llm/print_node.py +113 -32
- zrb/task/llm/prompt.py +36 -10
- zrb/task/llm/tool_wrapper.py +44 -14
- zrb/task/llm_task.py +1 -1
- zrb/util/cli/markdown.py +12 -0
- {zrb-1.15.2.dist-info → zrb-1.15.4.dist-info}/METADATA +3 -4
- {zrb-1.15.2.dist-info → zrb-1.15.4.dist-info}/RECORD +14 -13
- {zrb-1.15.2.dist-info → zrb-1.15.4.dist-info}/WHEEL +1 -1
- {zrb-1.15.2.dist-info → zrb-1.15.4.dist-info}/entry_points.txt +0 -0
zrb/builtin/llm/chat_session.py
CHANGED
@@ -10,6 +10,7 @@ import sys
|
|
10
10
|
|
11
11
|
from zrb.config.llm_config import llm_config
|
12
12
|
from zrb.context.any_context import AnyContext
|
13
|
+
from zrb.util.cli.markdown import render_markdown
|
13
14
|
from zrb.util.cli.style import stylize_blue, stylize_bold_yellow, stylize_faint
|
14
15
|
from zrb.util.string.conversion import to_boolean
|
15
16
|
|
@@ -34,7 +35,7 @@ async def read_user_prompt(ctx: AnyContext) -> str:
|
|
34
35
|
# Get user input based on mode
|
35
36
|
if not multiline_mode:
|
36
37
|
ctx.print("💬 >>", plain=True)
|
37
|
-
user_input = await _read_next_line(
|
38
|
+
user_input = await _read_next_line(reader, ctx)
|
38
39
|
if not multiline_mode:
|
39
40
|
ctx.print("", plain=True)
|
40
41
|
# Handle user input
|
@@ -111,6 +112,7 @@ def _show_info(ctx: AnyContext):
|
|
111
112
|
),
|
112
113
|
plain=True,
|
113
114
|
)
|
115
|
+
ctx.print("", plain=True)
|
114
116
|
|
115
117
|
|
116
118
|
def _show_command(command: str, description: str) -> str:
|
@@ -157,9 +159,11 @@ async def _setup_input_reader(is_interactive: bool):
|
|
157
159
|
return reader
|
158
160
|
|
159
161
|
|
160
|
-
async def _read_next_line(
|
162
|
+
async def _read_next_line(reader, ctx: AnyContext) -> str:
|
161
163
|
"""Reads one line of input using the provided reader."""
|
162
|
-
|
164
|
+
from prompt_toolkit import PromptSession
|
165
|
+
|
166
|
+
if isinstance(reader, PromptSession):
|
163
167
|
return await reader.prompt_async()
|
164
168
|
|
165
169
|
line_bytes = await reader.readline()
|
@@ -197,27 +201,13 @@ async def _trigger_ask_and_wait_for_result(
|
|
197
201
|
ctx, user_prompt, modes, yolo_mode, previous_session_name, start_new
|
198
202
|
)
|
199
203
|
result = await _wait_ask_result(ctx)
|
200
|
-
md_result =
|
204
|
+
md_result = render_markdown(result) if result is not None else ""
|
201
205
|
ctx.print("\n🤖 >>", plain=True)
|
202
206
|
ctx.print(md_result, plain=True)
|
203
207
|
ctx.print("", plain=True)
|
204
208
|
return result
|
205
209
|
|
206
210
|
|
207
|
-
def _render_markdown(markdown_text: str) -> str:
|
208
|
-
"""
|
209
|
-
Renders Markdown to a string, ensuring link URLs are visible.
|
210
|
-
"""
|
211
|
-
from rich.console import Console
|
212
|
-
from rich.markdown import Markdown
|
213
|
-
|
214
|
-
console = Console()
|
215
|
-
markdown = Markdown(markdown_text, hyperlinks=False)
|
216
|
-
with console.capture() as capture:
|
217
|
-
console.print(markdown)
|
218
|
-
return capture.get()
|
219
|
-
|
220
|
-
|
221
211
|
def get_llm_ask_input_mapping(callback_ctx: AnyContext):
|
222
212
|
"""
|
223
213
|
Generates the input mapping for the LLM ask task from the callback context.
|
zrb/builtin/llm/tool/code.py
CHANGED
@@ -6,6 +6,7 @@ from zrb.builtin.llm.tool.sub_agent import create_sub_agent_tool
|
|
6
6
|
from zrb.config.config import CFG
|
7
7
|
from zrb.config.llm_rate_limitter import llm_rate_limitter
|
8
8
|
from zrb.context.any_context import AnyContext
|
9
|
+
from zrb.util.cli.style import stylize_faint
|
9
10
|
|
10
11
|
_DEFAULT_EXTENSIONS = [
|
11
12
|
"py",
|
@@ -112,7 +113,7 @@ async def analyze_repo(
|
|
112
113
|
)
|
113
114
|
abs_path = os.path.abspath(os.path.expanduser(path))
|
114
115
|
file_metadatas = _get_file_metadatas(abs_path, extensions, exclude_patterns)
|
115
|
-
ctx.print("Extraction")
|
116
|
+
ctx.print(stylize_faint(" 📝 Extraction"), plain=True)
|
116
117
|
extracted_infos = await _extract_info(
|
117
118
|
ctx,
|
118
119
|
file_metadatas=file_metadatas,
|
@@ -121,10 +122,10 @@ async def analyze_repo(
|
|
121
122
|
)
|
122
123
|
if len(extracted_infos) == 1:
|
123
124
|
return extracted_infos[0]
|
124
|
-
ctx.print("Summarization")
|
125
|
+
ctx.print(stylize_faint(" 📝 Summarization"), plain=True)
|
125
126
|
summarized_infos = extracted_infos
|
126
127
|
while len(summarized_infos) > 1:
|
127
|
-
ctx.print("Summarization")
|
128
|
+
ctx.print(stylize_faint(" 📝 Summarization"), plain=True)
|
128
129
|
summarized_infos = await _summarize_info(
|
129
130
|
ctx,
|
130
131
|
extracted_infos=summarized_infos,
|
@@ -26,6 +26,7 @@ def create_sub_agent_tool(
|
|
26
26
|
tools: "list[ToolOrCallable]" = [],
|
27
27
|
toolsets: list["AbstractToolset[Agent]"] = [],
|
28
28
|
is_yolo_mode: bool | None = None,
|
29
|
+
log_indent_level: int = 2,
|
29
30
|
) -> Callable[[AnyContext, str], Coroutine[Any, Any, str]]:
|
30
31
|
"""
|
31
32
|
Creates a "tool that is another AI agent," capable of handling complex,
|
@@ -110,6 +111,7 @@ def create_sub_agent_tool(
|
|
110
111
|
user_prompt=query,
|
111
112
|
attachments=[],
|
112
113
|
history_list=[],
|
114
|
+
log_indent_level=log_indent_level,
|
113
115
|
)
|
114
116
|
|
115
117
|
# Return the sub-agent's final message content
|
zrb/task/llm/agent.py
CHANGED
@@ -129,6 +129,7 @@ async def run_agent_iteration(
|
|
129
129
|
history_list: ListOfDict | None = None,
|
130
130
|
rate_limitter: LLMRateLimiter | None = None,
|
131
131
|
max_retry: int = 2,
|
132
|
+
log_indent_level: int = 0,
|
132
133
|
) -> "AgentRun":
|
133
134
|
"""
|
134
135
|
Runs a single iteration of the agent execution loop.
|
@@ -159,6 +160,7 @@ async def run_agent_iteration(
|
|
159
160
|
rate_limitter=(
|
160
161
|
llm_rate_limitter if rate_limitter is None else rate_limitter
|
161
162
|
),
|
163
|
+
log_indent_level=log_indent_level,
|
162
164
|
)
|
163
165
|
except BaseException:
|
164
166
|
attempt += 1
|
@@ -174,6 +176,7 @@ async def _run_single_agent_iteration(
|
|
174
176
|
attachments: "list[UserContent]",
|
175
177
|
history_list: ListOfDict,
|
176
178
|
rate_limitter: LLMRateLimiter,
|
179
|
+
log_indent_level: int,
|
177
180
|
) -> "AgentRun":
|
178
181
|
from openai import APIError
|
179
182
|
from pydantic_ai.messages import ModelMessagesTypeAdapter
|
@@ -196,7 +199,9 @@ async def _run_single_agent_iteration(
|
|
196
199
|
# Each node represents a step in the agent's execution
|
197
200
|
# Reference: https://ai.pydantic.dev/agents/#streaming
|
198
201
|
try:
|
199
|
-
await print_node(
|
202
|
+
await print_node(
|
203
|
+
_get_plain_printer(ctx), agent_run, node, log_indent_level
|
204
|
+
)
|
200
205
|
except APIError as e:
|
201
206
|
# Extract detailed error information from the response
|
202
207
|
error_details = extract_api_error_details(e)
|
@@ -152,7 +152,7 @@ async def summarize_history(
|
|
152
152
|
],
|
153
153
|
)
|
154
154
|
try:
|
155
|
-
ctx.print(stylize_faint("📝
|
155
|
+
ctx.print(stylize_faint(" 📝 Rollup Conversation"), plain=True)
|
156
156
|
summary_run = await run_agent_iteration(
|
157
157
|
ctx=ctx,
|
158
158
|
agent=summarization_agent,
|
@@ -160,10 +160,13 @@ async def summarize_history(
|
|
160
160
|
attachments=[],
|
161
161
|
history_list=[],
|
162
162
|
rate_limitter=rate_limitter,
|
163
|
+
log_indent_level=2,
|
163
164
|
)
|
164
165
|
if summary_run and summary_run.result and summary_run.result.output:
|
165
166
|
usage = summary_run.result.usage()
|
166
|
-
ctx.print(
|
167
|
+
ctx.print(
|
168
|
+
stylize_faint(f" 📝 Rollup Conversation Token: {usage}"), plain=True
|
169
|
+
)
|
167
170
|
ctx.print(plain=True)
|
168
171
|
ctx.log_info("History summarized and updated.")
|
169
172
|
else:
|
zrb/task/llm/print_node.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
+
import json
|
1
2
|
from collections.abc import Callable
|
2
3
|
from typing import Any
|
3
4
|
|
4
5
|
from zrb.util.cli.style import stylize_faint
|
5
6
|
|
6
7
|
|
7
|
-
async def print_node(
|
8
|
+
async def print_node(
|
9
|
+
print_func: Callable, agent_run: Any, node: Any, log_indent_level: int = 0
|
10
|
+
):
|
8
11
|
"""Prints the details of an agent execution node using a provided print function."""
|
9
12
|
from pydantic_ai import Agent
|
10
13
|
from pydantic_ai.messages import (
|
@@ -19,10 +22,10 @@ async def print_node(print_func: Callable, agent_run: Any, node: Any):
|
|
19
22
|
)
|
20
23
|
|
21
24
|
if Agent.is_user_prompt_node(node):
|
22
|
-
print_func(
|
25
|
+
print_func(_format_header("🔠 Receiving input...", log_indent_level))
|
23
26
|
elif Agent.is_model_request_node(node):
|
24
27
|
# A model request node => We can stream tokens from the model's request
|
25
|
-
print_func(
|
28
|
+
print_func(_format_header("🧠 Processing...", log_indent_level))
|
26
29
|
# Reference: https://ai.pydantic.dev/agents/#streaming
|
27
30
|
async with node.stream(agent_run.ctx) as request_stream:
|
28
31
|
is_streaming = False
|
@@ -30,65 +33,143 @@ async def print_node(print_func: Callable, agent_run: Any, node: Any):
|
|
30
33
|
if isinstance(event, PartStartEvent) and event.part:
|
31
34
|
if is_streaming:
|
32
35
|
print_func("")
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
is_streaming = False
|
36
|
+
content = _get_event_part_content(event)
|
37
|
+
print_func(_format_content(content, log_indent_level), end="")
|
38
|
+
is_streaming = True
|
37
39
|
elif isinstance(event, PartDeltaEvent):
|
38
40
|
if isinstance(event.delta, TextPartDelta) or isinstance(
|
39
41
|
event.delta, ThinkingPartDelta
|
40
42
|
):
|
43
|
+
content_delta = event.delta.content_delta
|
41
44
|
print_func(
|
42
|
-
|
45
|
+
_format_stream_content(content_delta, log_indent_level),
|
43
46
|
end="",
|
44
47
|
)
|
45
48
|
elif isinstance(event.delta, ToolCallPartDelta):
|
49
|
+
args_delta = event.delta.args_delta
|
46
50
|
print_func(
|
47
|
-
|
48
|
-
end="",
|
51
|
+
_format_stream_content(args_delta, log_indent_level), end=""
|
49
52
|
)
|
50
53
|
is_streaming = True
|
51
54
|
elif isinstance(event, FinalResultEvent) and event.tool_name:
|
52
55
|
if is_streaming:
|
53
56
|
print_func("")
|
57
|
+
tool_name = event.tool_name
|
54
58
|
print_func(
|
55
|
-
|
59
|
+
_format_content(
|
60
|
+
f"Result: tool_name={tool_name}", log_indent_level
|
61
|
+
)
|
56
62
|
)
|
57
63
|
is_streaming = False
|
58
64
|
if is_streaming:
|
59
65
|
print_func("")
|
60
66
|
elif Agent.is_call_tools_node(node):
|
61
67
|
# A handle-response node => The model returned some data, potentially calls a tool
|
62
|
-
print_func(
|
68
|
+
print_func(_format_header("🧰 Calling Tool...", log_indent_level))
|
63
69
|
async with node.stream(agent_run.ctx) as handle_stream:
|
64
70
|
async for event in handle_stream:
|
65
71
|
if isinstance(event, FunctionToolCallEvent):
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
elif isinstance(
|
70
|
-
event.part.args, str
|
71
|
-
) and event.part.args.strip() in ["null", "{}"]:
|
72
|
-
# Some providers might send "null" or "{}" as a string
|
73
|
-
event.part.args = {}
|
74
|
-
# Handle dummy property if present (from our schema sanitization)
|
75
|
-
if (
|
76
|
-
isinstance(event.part.args, dict)
|
77
|
-
and "_dummy" in event.part.args
|
78
|
-
):
|
79
|
-
del event.part.args["_dummy"]
|
72
|
+
args = _get_event_part_args(event)
|
73
|
+
call_id = event.part.tool_call_id
|
74
|
+
tool_name = event.part.tool_name
|
80
75
|
print_func(
|
81
|
-
|
82
|
-
f"
|
83
|
-
f"Call {event.part.tool_name} {event.part.args}"
|
76
|
+
_format_content(
|
77
|
+
f"{call_id} | Call {tool_name} {args}", log_indent_level
|
84
78
|
)
|
85
79
|
)
|
86
80
|
elif isinstance(event, FunctionToolResultEvent):
|
81
|
+
call_id = event.tool_call_id
|
82
|
+
result_content = event.result.content
|
87
83
|
print_func(
|
88
|
-
|
89
|
-
f"
|
84
|
+
_format_content(
|
85
|
+
f"{call_id} | {result_content}", log_indent_level
|
90
86
|
)
|
91
87
|
)
|
92
88
|
elif Agent.is_end_node(node):
|
93
89
|
# Once an End node is reached, the agent run is complete
|
94
|
-
print_func(
|
90
|
+
print_func(_format_header("✅ Completed...", log_indent_level))
|
91
|
+
|
92
|
+
|
93
|
+
def _format_header(text: str, log_indent_level: int = 0) -> str:
|
94
|
+
return _format(
|
95
|
+
text,
|
96
|
+
base_indent=2,
|
97
|
+
first_indent=0,
|
98
|
+
indent=0,
|
99
|
+
log_indent_level=log_indent_level,
|
100
|
+
)
|
101
|
+
|
102
|
+
|
103
|
+
def _format_content(text: str, log_indent_level: int = 0) -> str:
|
104
|
+
return _format(
|
105
|
+
text,
|
106
|
+
base_indent=2,
|
107
|
+
first_indent=3,
|
108
|
+
indent=3,
|
109
|
+
log_indent_level=log_indent_level,
|
110
|
+
)
|
111
|
+
|
112
|
+
|
113
|
+
def _format_stream_content(text: str, log_indent_level: int = 0) -> str:
|
114
|
+
return _format(
|
115
|
+
text,
|
116
|
+
base_indent=2,
|
117
|
+
indent=3,
|
118
|
+
log_indent_level=log_indent_level,
|
119
|
+
is_stream=True,
|
120
|
+
)
|
121
|
+
|
122
|
+
|
123
|
+
def _format(
|
124
|
+
text: str,
|
125
|
+
base_indent: int = 0,
|
126
|
+
first_indent: int = 0,
|
127
|
+
indent: int = 0,
|
128
|
+
log_indent_level: int = 0,
|
129
|
+
is_stream: bool = False,
|
130
|
+
) -> str:
|
131
|
+
line_prefix = (base_indent * (log_indent_level + 1) + indent) * " "
|
132
|
+
processed_text = text.replace("\n", f"\n{line_prefix}")
|
133
|
+
if is_stream:
|
134
|
+
return stylize_faint(processed_text)
|
135
|
+
first_line_prefix = (base_indent * (log_indent_level + 1) + first_indent) * " "
|
136
|
+
return stylize_faint(f"{first_line_prefix}{processed_text}")
|
137
|
+
|
138
|
+
|
139
|
+
def _get_event_part_args(event: Any) -> Any:
|
140
|
+
# Handle empty arguments across different providers
|
141
|
+
if event.part.args == "" or event.part.args is None:
|
142
|
+
return {}
|
143
|
+
if isinstance(event.part.args, str):
|
144
|
+
# Some providers might send "null" or "{}" as a string
|
145
|
+
if event.part.args.strip() in ["null", "{}"]:
|
146
|
+
return {}
|
147
|
+
try:
|
148
|
+
obj = json.loads(event.part.args)
|
149
|
+
if isinstance(obj, dict):
|
150
|
+
return _truncate_kwargs(obj)
|
151
|
+
except json.JSONDecodeError:
|
152
|
+
pass
|
153
|
+
# Handle dummy property if present (from our schema sanitization)
|
154
|
+
if isinstance(event.part.args, dict):
|
155
|
+
return _truncate_kwargs(event.part.args)
|
156
|
+
print(type(event.part.args))
|
157
|
+
return event.part.args
|
158
|
+
|
159
|
+
|
160
|
+
def _truncate_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]:
|
161
|
+
return {key: _truncate_arg(val) for key, val in kwargs.items() if key != "_dummy"}
|
162
|
+
|
163
|
+
|
164
|
+
def _truncate_arg(arg: str, length: int = 19) -> str:
|
165
|
+
if isinstance(arg, str) and len(arg) > length:
|
166
|
+
return f"{arg[:length-4]} ..."
|
167
|
+
return arg
|
168
|
+
|
169
|
+
|
170
|
+
def _get_event_part_content(event: Any) -> str:
|
171
|
+
if not hasattr(event, "part"):
|
172
|
+
return f"{event}"
|
173
|
+
if not hasattr(event.part, "content"):
|
174
|
+
return f"{event.part}"
|
175
|
+
return getattr(event.part, "content")
|
zrb/task/llm/prompt.py
CHANGED
@@ -93,28 +93,54 @@ def get_workflow_prompt(
|
|
93
93
|
modes_attr: StrListAttr | None,
|
94
94
|
render_modes: bool,
|
95
95
|
) -> str:
|
96
|
-
|
96
|
+
builtin_workflow_dir = os.path.join(os.path.dirname(__file__), "default_workflow")
|
97
|
+
modes = set(get_modes(ctx, modes_attr, render_modes))
|
98
|
+
|
97
99
|
# Get user-defined workflows
|
98
100
|
workflows = {
|
99
101
|
workflow_name.strip().lower(): content
|
100
102
|
for workflow_name, content in llm_context_config.get_workflows().items()
|
101
103
|
if workflow_name.strip().lower() in modes
|
102
104
|
}
|
103
|
-
|
105
|
+
|
106
|
+
# Get available builtin workflow names from the file system
|
107
|
+
available_builtin_workflow_names = set()
|
108
|
+
try:
|
109
|
+
for filename in os.listdir(builtin_workflow_dir):
|
110
|
+
if filename.endswith(".md"):
|
111
|
+
available_builtin_workflow_names.add(filename[:-3].lower())
|
112
|
+
except FileNotFoundError:
|
113
|
+
# Handle case where the directory might not exist
|
114
|
+
ctx.log_error(
|
115
|
+
f"Warning: Default workflow directory not found at {builtin_workflow_dir}"
|
116
|
+
)
|
117
|
+
except Exception as e:
|
118
|
+
# Catch other potential errors during directory listing
|
119
|
+
ctx.log_error(f"Error listing default workflows: {e}")
|
120
|
+
|
121
|
+
# Determine which builtin workflows are requested and not already loaded
|
104
122
|
requested_builtin_workflow_names = [
|
105
|
-
workflow_name
|
106
|
-
for workflow_name in
|
107
|
-
if workflow_name
|
123
|
+
workflow_name
|
124
|
+
for workflow_name in available_builtin_workflow_names
|
125
|
+
if workflow_name in modes and workflow_name not in workflows
|
108
126
|
]
|
109
|
-
|
127
|
+
|
128
|
+
# Add builtin-workflows if requested
|
110
129
|
if len(requested_builtin_workflow_names) > 0:
|
111
|
-
dir_path = os.path.dirname(__file__)
|
112
130
|
for workflow_name in requested_builtin_workflow_names:
|
113
131
|
workflow_file_path = os.path.join(
|
114
|
-
|
132
|
+
builtin_workflow_dir, f"{workflow_name}.md"
|
115
133
|
)
|
116
|
-
|
117
|
-
|
134
|
+
try:
|
135
|
+
with open(workflow_file_path, "r") as f:
|
136
|
+
workflows[workflow_name] = f.read()
|
137
|
+
except FileNotFoundError:
|
138
|
+
ctx.log_error(
|
139
|
+
f"Warning: Builtin workflow file not found: {workflow_file_path}"
|
140
|
+
)
|
141
|
+
except Exception as e:
|
142
|
+
ctx.log_error(f"Error reading builtin workflow {workflow_name}: {e}")
|
143
|
+
|
118
144
|
return "\n".join(
|
119
145
|
[
|
120
146
|
make_prompt_section(header.capitalize(), content)
|
zrb/task/llm/tool_wrapper.py
CHANGED
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, Any
|
|
8
8
|
from zrb.context.any_context import AnyContext
|
9
9
|
from zrb.task.llm.error import ToolExecutionError
|
10
10
|
from zrb.util.callable import get_callable_name
|
11
|
+
from zrb.util.cli.markdown import render_markdown
|
11
12
|
from zrb.util.cli.style import (
|
12
13
|
stylize_blue,
|
13
14
|
stylize_error,
|
@@ -123,25 +124,21 @@ def _create_wrapper(
|
|
123
124
|
|
124
125
|
|
125
126
|
async def _ask_for_approval(
|
126
|
-
ctx: AnyContext, func: Callable, args: list[Any], kwargs: dict[Any]
|
127
|
+
ctx: AnyContext, func: Callable, args: list[Any], kwargs: dict[str, Any]
|
127
128
|
) -> tuple[bool, str]:
|
128
129
|
func_name = get_callable_name(func)
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
func_param_str = ",".join(normalized_args + normalized_kwargs)
|
137
|
-
func_call_str = (
|
138
|
-
f"{stylize_blue(func_name + '(')}{func_param_str}{stylize_blue(')')}"
|
130
|
+
func_call_str = _get_func_call_str(func, args, kwargs)
|
131
|
+
func_detail_param = _get_detail_func_param(args, kwargs)
|
132
|
+
confirmation_message = render_markdown(
|
133
|
+
f"Allow to run `{func_name}`? (`Yes` | `No, <reason>`)"
|
134
|
+
)
|
135
|
+
complete_confirmation_message = (
|
136
|
+
f"\n✅ >> {func_call_str}" f"\n{func_detail_param}" f"\n{confirmation_message}"
|
139
137
|
)
|
140
138
|
while True:
|
141
|
-
ctx.print(
|
142
|
-
f"✅ >> Allow to run tool: {func_call_str} (Yes | No, <reason>)", plain=True
|
143
|
-
)
|
139
|
+
ctx.print(complete_confirmation_message, plain=True)
|
144
140
|
user_input = await _read_line()
|
141
|
+
ctx.print("", plain=True)
|
145
142
|
user_responses = [val.strip() for val in user_input.split(",", maxsplit=1)]
|
146
143
|
while len(user_responses) < 2:
|
147
144
|
user_responses.append("")
|
@@ -167,6 +164,39 @@ async def _ask_for_approval(
|
|
167
164
|
continue
|
168
165
|
|
169
166
|
|
167
|
+
def _get_detail_func_param(args: list[Any], kwargs: dict[str, Any]) -> str:
|
168
|
+
markdown = "\n".join(
|
169
|
+
[_get_func_param_item(key, val) for key, val in kwargs.items()]
|
170
|
+
)
|
171
|
+
return render_markdown(markdown)
|
172
|
+
|
173
|
+
|
174
|
+
def _get_func_param_item(key: str, val: Any) -> str:
|
175
|
+
upper_key = key.upper()
|
176
|
+
val_str = f"{val}"
|
177
|
+
val_parts = val_str.split("\n")
|
178
|
+
if len(val_parts) == 1:
|
179
|
+
return f"- {upper_key} `{val}`"
|
180
|
+
lines = [f"- {upper_key}", " ```"]
|
181
|
+
for val_part in val_parts:
|
182
|
+
lines.append(f" {val_part}")
|
183
|
+
lines.append(" ```")
|
184
|
+
return "\n".join(lines)
|
185
|
+
|
186
|
+
|
187
|
+
def _get_func_call_str(func: Callable, args: list[Any], kwargs: dict[str, Any]) -> str:
|
188
|
+
func_name = get_callable_name(func)
|
189
|
+
normalized_args = [stylize_green(_truncate_arg(arg)) for arg in args]
|
190
|
+
normalized_kwargs = []
|
191
|
+
for key, val in kwargs.items():
|
192
|
+
truncated_val = _truncate_arg(f"{val}")
|
193
|
+
normalized_kwargs.append(
|
194
|
+
f"{stylize_yellow(key)}={stylize_green(truncated_val)}"
|
195
|
+
)
|
196
|
+
func_param_str = ", ".join(normalized_args + normalized_kwargs)
|
197
|
+
return f"{stylize_blue(func_name + '(')}{func_param_str}{stylize_blue(')')}"
|
198
|
+
|
199
|
+
|
170
200
|
def _truncate_arg(arg: str, length: int = 19) -> str:
|
171
201
|
if len(arg) > length:
|
172
202
|
return f"{arg[:length-4]} ..."
|
zrb/task/llm_task.py
CHANGED
@@ -331,7 +331,7 @@ class LLMTask(BaseTask):
|
|
331
331
|
ctx.xcom[xcom_usage_key] = Xcom([])
|
332
332
|
usage = agent_run.result.usage()
|
333
333
|
ctx.xcom[xcom_usage_key].push(usage)
|
334
|
-
ctx.print(stylize_faint(f"💸 Token: {usage}"), plain=True)
|
334
|
+
ctx.print(stylize_faint(f" 💸 Token: {usage}"), plain=True)
|
335
335
|
return agent_run.result.output
|
336
336
|
else:
|
337
337
|
ctx.log_warning("Agent run did not produce a result.")
|
zrb/util/cli/markdown.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
def render_markdown(markdown_text: str) -> str:
|
2
|
+
"""
|
3
|
+
Renders Markdown to a string, ensuring link URLs are visible.
|
4
|
+
"""
|
5
|
+
from rich.console import Console
|
6
|
+
from rich.markdown import Markdown
|
7
|
+
|
8
|
+
console = Console()
|
9
|
+
markdown = Markdown(markdown_text, hyperlinks=False)
|
10
|
+
with console.capture() as capture:
|
11
|
+
console.print(markdown)
|
12
|
+
return capture.get()
|
@@ -1,7 +1,8 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.1
|
2
2
|
Name: zrb
|
3
|
-
Version: 1.15.
|
3
|
+
Version: 1.15.4
|
4
4
|
Summary: Your Automation Powerhouse
|
5
|
+
Home-page: https://github.com/state-alchemists/zrb
|
5
6
|
License: AGPL-3.0-or-later
|
6
7
|
Keywords: Automation,Task Runner,Code Generator,Monorepo,Low Code
|
7
8
|
Author: Go Frendi Gunawan
|
@@ -12,7 +13,6 @@ Classifier: Programming Language :: Python :: 3
|
|
12
13
|
Classifier: Programming Language :: Python :: 3.10
|
13
14
|
Classifier: Programming Language :: Python :: 3.11
|
14
15
|
Classifier: Programming Language :: Python :: 3.12
|
15
|
-
Classifier: Programming Language :: Python :: 3.13
|
16
16
|
Provides-Extra: all
|
17
17
|
Provides-Extra: playwright
|
18
18
|
Provides-Extra: rag
|
@@ -35,7 +35,6 @@ Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
35
35
|
Requires-Dist: tiktoken (>=0.8.0,<0.9.0)
|
36
36
|
Requires-Dist: ulid-py (>=1.1.0,<2.0.0)
|
37
37
|
Project-URL: Documentation, https://github.com/state-alchemists/zrb
|
38
|
-
Project-URL: Homepage, https://github.com/state-alchemists/zrb
|
39
38
|
Project-URL: Repository, https://github.com/state-alchemists/zrb
|
40
39
|
Description-Content-Type: text/markdown
|
41
40
|
|
@@ -9,7 +9,7 @@ zrb/builtin/git_subtree.py,sha256=7BKwOkVTWDrR0DXXQ4iJyHqeR6sV5VYRt8y_rEB0EHg,35
|
|
9
9
|
zrb/builtin/group.py,sha256=t008xLM4_fgbjfZrPoi_fQAnSHIo6MOiQSCHBO4GDYU,2379
|
10
10
|
zrb/builtin/http.py,sha256=L6RE73c65wWwG5iHFN-tpOhyh56KsrgVskDd3c3YXtk,4246
|
11
11
|
zrb/builtin/jwt.py,sha256=3M5uaQhJZbKQLjTUft1OwPz_JxtmK-xtkjxWjciOQho,2859
|
12
|
-
zrb/builtin/llm/chat_session.py,sha256=
|
12
|
+
zrb/builtin/llm/chat_session.py,sha256=FnZAhrAHQ8tUgB1ejZby4p_v53c8HwsaMoVvqJWKELw,10191
|
13
13
|
zrb/builtin/llm/history.py,sha256=LDOrL0p7r_AHLa5L8Dp7bHNsOALugmJd7OguXRWGnm4,3087
|
14
14
|
zrb/builtin/llm/input.py,sha256=Nw-26uTWp2QhUgKJcP_IMHmtk-b542CCSQ_vCOjhvhM,877
|
15
15
|
zrb/builtin/llm/llm_ask.py,sha256=03d1-2jh7NS0VP3eNl9wCdrUzud98i6zFUXW5NNu3_o,5597
|
@@ -17,10 +17,10 @@ zrb/builtin/llm/previous-session.js,sha256=xMKZvJoAbrwiyHS0OoPrWuaKxWYLoyR5sgueP
|
|
17
17
|
zrb/builtin/llm/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
18
|
zrb/builtin/llm/tool/api.py,sha256=vMEiZhhTZ3o2jRBxWcJ62b0M85wd_w4W0X4Hx23NXto,2380
|
19
19
|
zrb/builtin/llm/tool/cli.py,sha256=8rugrKaNPEatHjr7nN4OIRLRT2TcF-oylEZGbLI9Brs,1254
|
20
|
-
zrb/builtin/llm/tool/code.py,sha256=
|
20
|
+
zrb/builtin/llm/tool/code.py,sha256=BACeH0tGhTdDo0rZ7sTAP6oRaLi9cS7gdEigXiTd0Jg,8842
|
21
21
|
zrb/builtin/llm/tool/file.py,sha256=eXFGGFxxpdpWGVw0svyQNQc03I5M7wotSsA_HjkXw7c,23670
|
22
22
|
zrb/builtin/llm/tool/rag.py,sha256=Ab8_ZljnG_zfkwxPezImvorshuz3Fi4CmSzNOtU1a-g,9770
|
23
|
-
zrb/builtin/llm/tool/sub_agent.py,sha256=
|
23
|
+
zrb/builtin/llm/tool/sub_agent.py,sha256=hsR-5iozcgE0f-3J7VLP189JqsW6lE2h-x_1YW6pVAo,5086
|
24
24
|
zrb/builtin/llm/tool/web.py,sha256=zNQWDNOz_MyyzhncBeV5E_9XijQxTFVp6BSzz_e5tBI,7261
|
25
25
|
zrb/builtin/md5.py,sha256=690RV2LbW7wQeTFxY-lmmqTSVEEZv3XZbjEUW1Q3XpE,1480
|
26
26
|
zrb/builtin/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -346,7 +346,7 @@ zrb/task/base_trigger.py,sha256=WSGcmBcGAZw8EzUXfmCjqJQkz8GEmi1RzogpF6A1V4s,6902
|
|
346
346
|
zrb/task/cmd_task.py,sha256=myM8WZm6NrUD-Wv0Vb5sTOrutrAVZLt5LVsSBKwX6SM,10860
|
347
347
|
zrb/task/http_check.py,sha256=Gf5rOB2Se2EdizuN9rp65HpGmfZkGc-clIAlHmPVehs,2565
|
348
348
|
zrb/task/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
349
|
-
zrb/task/llm/agent.py,sha256=
|
349
|
+
zrb/task/llm/agent.py,sha256=miQMF7NMFsPEuAp5gTh1kAAEGxgYfLb4uoDU7FL83Go,8645
|
350
350
|
zrb/task/llm/config.py,sha256=n1SPmwab09K2i1sL_OCwrEOWHI0Owx_hvWelg3Dreus,3781
|
351
351
|
zrb/task/llm/conversation_history.py,sha256=B_PDWYL_q66s0xwWBzMSomqPN6u3gkXlIeXBD5A0Apg,4416
|
352
352
|
zrb/task/llm/conversation_history_model.py,sha256=DJ0KDBB0BriQuE5ugC_q0aSHhjNIBcfjUk1f0S_3I9U,9245
|
@@ -354,12 +354,12 @@ zrb/task/llm/default_workflow/coding.md,sha256=2uythvPsnBpYfIhiIH1cCinQXX0i0yUqs
|
|
354
354
|
zrb/task/llm/default_workflow/copywriting.md,sha256=xSO7GeDolwGxiuz6kXsK2GKGpwp8UgtG0yRqTmill_s,1999
|
355
355
|
zrb/task/llm/default_workflow/researching.md,sha256=KD-aYHFHir6Ti-4FsBBtGwiI0seSVgleYbKJZi_POXA,2139
|
356
356
|
zrb/task/llm/error.py,sha256=QR-nIohS6pBpC_16cWR-fw7Mevo1sNYAiXMBsh_CJDE,4157
|
357
|
-
zrb/task/llm/history_summarization.py,sha256=
|
358
|
-
zrb/task/llm/print_node.py,sha256=
|
359
|
-
zrb/task/llm/prompt.py,sha256=
|
360
|
-
zrb/task/llm/tool_wrapper.py,sha256=
|
357
|
+
zrb/task/llm/history_summarization.py,sha256=xSHVoVOJ_4Da2fycWyKm8dBCIbjfAFU_rDC7u6XhKow,8093
|
358
|
+
zrb/task/llm/print_node.py,sha256=Sd8ovTO6KKI9hllZf5TxRXog7QGCne1A1E83jnrH_kg,6526
|
359
|
+
zrb/task/llm/prompt.py,sha256=FGXWYHecWtrNNkPnjg-uhnkqp7fYt8V91-AjFM_5fpA,11550
|
360
|
+
zrb/task/llm/tool_wrapper.py,sha256=4gdBrtjdwBzhBD1y41vzujPGFEY3q5OmHb_E-y62dHI,9393
|
361
361
|
zrb/task/llm/typing.py,sha256=c8VAuPBw_4A3DxfYdydkgedaP-LU61W9_wj3m3CAX1E,58
|
362
|
-
zrb/task/llm_task.py,sha256=
|
362
|
+
zrb/task/llm_task.py,sha256=aweruBmC09RCKNGmiwLo7WVSttCUH2h2RkSfgUGvRFo,14450
|
363
363
|
zrb/task/make_task.py,sha256=PD3b_aYazthS8LHeJsLAhwKDEgdurQZpymJDKeN60u0,2265
|
364
364
|
zrb/task/rsync_task.py,sha256=WfqNSaicJgYWpunNU34eYxXDqHDHOftuDHyWJKjqwg0,6365
|
365
365
|
zrb/task/scaffolder.py,sha256=rME18w1HJUHXgi9eTYXx_T2G4JdqDYzBoNOkdOOo5-o,6806
|
@@ -372,6 +372,7 @@ zrb/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
372
372
|
zrb/util/attr.py,sha256=5GlYSmVAzbcSFjNDXiqqHqNMR6NWjJ6bUHZXdE35mj8,5359
|
373
373
|
zrb/util/callable.py,sha256=b6OFXbCXp2twow3wh2E_h5hNHLs2pXaLfGQz4iVyiQc,771
|
374
374
|
zrb/util/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
375
|
+
zrb/util/cli/markdown.py,sha256=Uhuw8XR-jAG9AG3oNK8VHJpYOdU40Q_8yVN74uu0RJ8,384
|
375
376
|
zrb/util/cli/style.py,sha256=D_548KG1gXEirQGdkAVTc81vBdCeInXtnG1gV1yabBA,6655
|
376
377
|
zrb/util/cli/subcommand.py,sha256=umTZIlrL-9g-qc_eRRgdaQgK-whvXK1roFfvnbuY7NQ,1753
|
377
378
|
zrb/util/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -406,7 +407,7 @@ zrb/util/todo.py,sha256=r9_KYF2-hLKMNjsp6AFK9zivykMrywd-kJ4bCwfdafI,19323
|
|
406
407
|
zrb/util/todo_model.py,sha256=hhzAX-uFl5rsg7iVX1ULlJOfBtblwQ_ieNUxBWfc-Os,1670
|
407
408
|
zrb/xcom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
408
409
|
zrb/xcom/xcom.py,sha256=o79rxR9wphnShrcIushA0Qt71d_p3ZTxjNf7x9hJB78,1571
|
409
|
-
zrb-1.15.
|
410
|
-
zrb-1.15.
|
411
|
-
zrb-1.15.
|
412
|
-
zrb-1.15.
|
410
|
+
zrb-1.15.4.dist-info/METADATA,sha256=LdpREgmZtxq9meZWfPZ6ivABI_531S6agnMVFP7jaME,9744
|
411
|
+
zrb-1.15.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
412
|
+
zrb-1.15.4.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
|
413
|
+
zrb-1.15.4.dist-info/RECORD,,
|
File without changes
|