zrb 1.8.15__py3-none-any.whl → 1.9.1__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 +16 -11
- zrb/builtin/llm/llm_ask.py +4 -0
- zrb/builtin/llm/tool/code.py +2 -2
- zrb/builtin/llm/tool/file.py +105 -2
- zrb/builtin/llm/tool/web.py +1 -1
- zrb/config.py +14 -8
- zrb/llm_config.py +156 -185
- zrb/task/any_task.py +6 -9
- zrb/task/llm/agent.py +26 -33
- zrb/task/llm/config.py +4 -7
- zrb/task/llm/context.py +0 -44
- zrb/task/llm/context_enrichment.py +73 -100
- zrb/task/llm/error.py +2 -4
- zrb/task/llm/history.py +19 -11
- zrb/task/llm/history_summarization.py +75 -88
- zrb/task/llm/print_node.py +10 -8
- zrb/task/llm/prompt.py +12 -19
- zrb/task/llm/tool_wrapper.py +2 -4
- zrb/task/llm_task.py +207 -78
- zrb/util/file.py +3 -2
- {zrb-1.8.15.dist-info → zrb-1.9.1.dist-info}/METADATA +1 -1
- {zrb-1.8.15.dist-info → zrb-1.9.1.dist-info}/RECORD +24 -24
- {zrb-1.8.15.dist-info → zrb-1.9.1.dist-info}/WHEEL +0 -0
- {zrb-1.8.15.dist-info → zrb-1.9.1.dist-info}/entry_points.txt +0 -0
zrb/builtin/llm/chat_session.py
CHANGED
@@ -29,7 +29,9 @@ async def read_user_prompt(ctx: AnyContext) -> str:
|
|
29
29
|
|
30
30
|
_show_info(ctx)
|
31
31
|
final_result = ""
|
32
|
-
ctx.print(stylize_faint("
|
32
|
+
ctx.print(stylize_faint("💬"), plain=True)
|
33
|
+
ctx.print(ctx.input.message, plain=True)
|
34
|
+
ctx.print("", plain=True)
|
33
35
|
result = await _trigger_ask_and_wait_for_result(
|
34
36
|
ctx,
|
35
37
|
user_prompt=ctx.input.message,
|
@@ -46,9 +48,11 @@ async def read_user_prompt(ctx: AnyContext) -> str:
|
|
46
48
|
user_input_session = PromptSession()
|
47
49
|
while True:
|
48
50
|
await asyncio.sleep(0.01)
|
49
|
-
|
51
|
+
if not multiline_mode:
|
52
|
+
ctx.print(stylize_faint("💬"), plain=True)
|
50
53
|
user_input = await user_input_session.prompt_async()
|
51
|
-
|
54
|
+
if not multiline_mode:
|
55
|
+
ctx.print("", plain=True)
|
52
56
|
# Handle special input
|
53
57
|
if user_input.strip().lower() in ("/bye", "/quit"):
|
54
58
|
user_prompt = "\n".join(user_inputs)
|
@@ -60,6 +64,7 @@ async def read_user_prompt(ctx: AnyContext) -> str:
|
|
60
64
|
elif user_input.strip().lower() in ("/multi",):
|
61
65
|
multiline_mode = True
|
62
66
|
elif user_input.strip().lower() in ("/end",):
|
67
|
+
ctx.print("", plain=True)
|
63
68
|
multiline_mode = False
|
64
69
|
user_prompt = "\n".join(user_inputs)
|
65
70
|
user_inputs = []
|
@@ -103,7 +108,9 @@ async def _trigger_ask_and_wait_for_result(
|
|
103
108
|
return None
|
104
109
|
await _trigger_ask(ctx, user_prompt, previous_session_name, start_new)
|
105
110
|
result = await _wait_ask_result(ctx)
|
106
|
-
ctx.print(
|
111
|
+
ctx.print(stylize_faint("\n🤖"), plain=True)
|
112
|
+
ctx.print(result, plain=True)
|
113
|
+
ctx.print("", plain=True)
|
107
114
|
return result
|
108
115
|
|
109
116
|
|
@@ -199,13 +206,11 @@ def _show_info(ctx: AnyContext):
|
|
199
206
|
ctx: The context object for the task.
|
200
207
|
"""
|
201
208
|
ctx.print(
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
f"{stylize_bold_yellow('/help')} {stylize_faint('Show this message')}",
|
208
|
-
]
|
209
|
+
(
|
210
|
+
f" {stylize_bold_yellow('/bye')} {stylize_faint('Quit from chat session')}\n"
|
211
|
+
f" {stylize_bold_yellow('/multi')} {stylize_faint('Start multiline input')}\n"
|
212
|
+
f" {stylize_bold_yellow('/end')} {stylize_faint('End multiline input')}\n"
|
213
|
+
f" {stylize_bold_yellow('/help')} {stylize_faint('Show this message')}\n"
|
209
214
|
),
|
210
215
|
plain=True,
|
211
216
|
)
|
zrb/builtin/llm/llm_ask.py
CHANGED
@@ -10,7 +10,9 @@ from zrb.builtin.llm.tool.file import (
|
|
10
10
|
apply_diff,
|
11
11
|
list_files,
|
12
12
|
read_from_file,
|
13
|
+
read_many_files,
|
13
14
|
search_files,
|
15
|
+
write_many_files,
|
14
16
|
write_to_file,
|
15
17
|
)
|
16
18
|
from zrb.builtin.llm.tool.web import (
|
@@ -132,8 +134,10 @@ if CFG.LLM_ALLOW_ACCESS_LOCAL_FILE:
|
|
132
134
|
search_files,
|
133
135
|
list_files,
|
134
136
|
read_from_file,
|
137
|
+
read_many_files,
|
135
138
|
apply_diff,
|
136
139
|
write_to_file,
|
140
|
+
write_many_files,
|
137
141
|
)
|
138
142
|
|
139
143
|
if CFG.LLM_ALLOW_ACCESS_SHELL:
|
zrb/builtin/llm/tool/code.py
CHANGED
@@ -82,8 +82,8 @@ async def analyze_repo(
|
|
82
82
|
goal: str,
|
83
83
|
extensions: list[str] = _DEFAULT_EXTENSIONS,
|
84
84
|
exclude_patterns: list[str] = DEFAULT_EXCLUDED_PATTERNS,
|
85
|
-
extraction_token_limit: int =
|
86
|
-
summarization_token_limit: int =
|
85
|
+
extraction_token_limit: int = 40000,
|
86
|
+
summarization_token_limit: int = 40000,
|
87
87
|
) -> str:
|
88
88
|
"""
|
89
89
|
Extract and summarize information from any directory.
|
zrb/builtin/llm/tool/file.py
CHANGED
@@ -2,7 +2,7 @@ import fnmatch
|
|
2
2
|
import json
|
3
3
|
import os
|
4
4
|
import re
|
5
|
-
from typing import Any, Optional
|
5
|
+
from typing import Any, Dict, List, Optional
|
6
6
|
|
7
7
|
from zrb.builtin.llm.tool.sub_agent import create_sub_agent_tool
|
8
8
|
from zrb.context.any_context import AnyContext
|
@@ -215,6 +215,7 @@ def read_from_file(
|
|
215
215
|
end_line: Optional[int] = None,
|
216
216
|
) -> str:
|
217
217
|
"""Read file content (or specific lines) at a path, including line numbers.
|
218
|
+
This tool can read both, text and pdf file.
|
218
219
|
Args:
|
219
220
|
path (str): Path to read. Pass exactly as provided, including '~'.
|
220
221
|
start_line (Optional[int]): Starting line number (1-based).
|
@@ -466,7 +467,7 @@ def apply_diff(
|
|
466
467
|
|
467
468
|
|
468
469
|
async def analyze_file(
|
469
|
-
ctx: AnyContext, path: str, query: str, token_limit: int =
|
470
|
+
ctx: AnyContext, path: str, query: str, token_limit: int = 40000
|
470
471
|
) -> str:
|
471
472
|
"""Analyze file using LLM capability to reduce context usage.
|
472
473
|
Use this tool for:
|
@@ -498,3 +499,105 @@ async def analyze_file(
|
|
498
499
|
)
|
499
500
|
clipped_payload = llm_rate_limitter.clip_prompt(payload, token_limit)
|
500
501
|
return await _analyze_file(ctx, clipped_payload)
|
502
|
+
|
503
|
+
|
504
|
+
def read_many_files(paths: List[str]) -> str:
|
505
|
+
"""
|
506
|
+
Read and return the content of multiple files.
|
507
|
+
|
508
|
+
This function is ideal for when you need to inspect the contents of
|
509
|
+
several files at once. For each file path provided in the input list,
|
510
|
+
it reads the entire file content. The result is a JSON string
|
511
|
+
containing a dictionary where keys are the file paths and values are
|
512
|
+
the corresponding file contents.
|
513
|
+
|
514
|
+
Use this tool when you need a comprehensive view of multiple files,
|
515
|
+
for example, to understand how different parts of a module interact,
|
516
|
+
to check configurations across various files, or to gather context
|
517
|
+
before making widespread changes.
|
518
|
+
|
519
|
+
Args:
|
520
|
+
paths (List[str]): A list of absolute or relative paths to the
|
521
|
+
files you want to read. It is crucial to
|
522
|
+
provide accurate paths. Use the `list_files`
|
523
|
+
tool if you are unsure about the exact file
|
524
|
+
locations.
|
525
|
+
|
526
|
+
Returns:
|
527
|
+
str: A JSON string representing a dictionary where each key is a
|
528
|
+
file path and the corresponding value is the content of that
|
529
|
+
file. If a file cannot be read, its entry in the dictionary
|
530
|
+
will contain an error message.
|
531
|
+
Example:
|
532
|
+
{
|
533
|
+
"results": {
|
534
|
+
"path/to/file1.py": "...",
|
535
|
+
"path/to/file2.txt": "..."
|
536
|
+
}
|
537
|
+
}
|
538
|
+
"""
|
539
|
+
results = {}
|
540
|
+
for path in paths:
|
541
|
+
try:
|
542
|
+
abs_path = os.path.abspath(os.path.expanduser(path))
|
543
|
+
if not os.path.exists(abs_path):
|
544
|
+
raise FileNotFoundError(f"File not found: {path}")
|
545
|
+
content = read_file_with_line_numbers(abs_path)
|
546
|
+
results[path] = content
|
547
|
+
except Exception as e:
|
548
|
+
results[path] = f"Error reading file: {e}"
|
549
|
+
return json.dumps({"results": results})
|
550
|
+
|
551
|
+
|
552
|
+
def write_many_files(files: Dict[str, str]) -> str:
|
553
|
+
"""
|
554
|
+
Write content to multiple files simultaneously.
|
555
|
+
|
556
|
+
This function allows you to create, overwrite, or update multiple
|
557
|
+
files in a single operation. You provide a dictionary where each
|
558
|
+
key is a file path and the corresponding value is the content to be
|
559
|
+
written to that file. This is particularly useful for applying
|
560
|
+
changes across a project, such as refactoring code, updating
|
561
|
+
configuration files, or creating a set of new files from a template.
|
562
|
+
|
563
|
+
Each file is handled as a complete replacement of its content. If the
|
564
|
+
file does not exist, it will be created. If it already exists, its
|
565
|
+
|
566
|
+
entire content will be overwritten with the new content you provide.
|
567
|
+
Therefore, it is essential to provide the full, intended content for
|
568
|
+
each file.
|
569
|
+
|
570
|
+
Args:
|
571
|
+
files (Dict[str, str]): A dictionary where keys are the file paths
|
572
|
+
(absolute or relative) and values are the
|
573
|
+
complete contents to be written to those
|
574
|
+
files.
|
575
|
+
|
576
|
+
Returns:
|
577
|
+
str: A JSON string summarizing the operation. It includes a list
|
578
|
+
of successfully written files and a list of files that
|
579
|
+
failed, along with the corresponding error messages.
|
580
|
+
Example:
|
581
|
+
{
|
582
|
+
"success": [
|
583
|
+
"path/to/file1.py",
|
584
|
+
"path/to/file2.txt"
|
585
|
+
],
|
586
|
+
"errors": {
|
587
|
+
"path/to/problematic/file.py": "Error message"
|
588
|
+
}
|
589
|
+
}
|
590
|
+
"""
|
591
|
+
success = []
|
592
|
+
errors = {}
|
593
|
+
for path, content in files.items():
|
594
|
+
try:
|
595
|
+
abs_path = os.path.abspath(os.path.expanduser(path))
|
596
|
+
directory = os.path.dirname(abs_path)
|
597
|
+
if directory and not os.path.exists(directory):
|
598
|
+
os.makedirs(directory, exist_ok=True)
|
599
|
+
write_file(abs_path, content)
|
600
|
+
success.append(path)
|
601
|
+
except Exception as e:
|
602
|
+
errors[path] = f"Error writing file: {e}"
|
603
|
+
return json.dumps({"success": success, "errors": errors})
|
zrb/builtin/llm/tool/web.py
CHANGED
@@ -58,7 +58,7 @@ async def open_web_page(url: str) -> str:
|
|
58
58
|
|
59
59
|
def create_search_internet_tool(serp_api_key: str) -> Callable[[str, int], str]:
|
60
60
|
def search_internet(query: str, num_results: int = 10) -> str:
|
61
|
-
"""Search the internet
|
61
|
+
"""Search the internet Google Search and return parsed results.
|
62
62
|
Args:
|
63
63
|
query (str): Search query.
|
64
64
|
num_results (int): Search result count. Defaults to 10.
|
zrb/config.py
CHANGED
@@ -243,13 +243,19 @@ class Config:
|
|
243
243
|
|
244
244
|
@property
|
245
245
|
def LLM_MAX_REQUESTS_PER_MINUTE(self) -> int:
|
246
|
-
"""
|
247
|
-
|
246
|
+
"""
|
247
|
+
Maximum number of LLM requests allowed per minute.
|
248
|
+
Default is conservative to accommodate free-tier LLM providers.
|
249
|
+
"""
|
250
|
+
return int(os.getenv("LLM_MAX_REQUESTS_PER_MINUTE", "15"))
|
248
251
|
|
249
252
|
@property
|
250
253
|
def LLM_MAX_TOKENS_PER_MINUTE(self) -> int:
|
251
|
-
"""
|
252
|
-
|
254
|
+
"""
|
255
|
+
Maximum number of LLM tokens allowed per minute.
|
256
|
+
Default is conservative to accommodate free-tier LLM providers.
|
257
|
+
"""
|
258
|
+
return int(os.getenv("ZRB_LLM_MAX_TOKENS_PER_MINUTE", "100000"))
|
253
259
|
|
254
260
|
@property
|
255
261
|
def LLM_MAX_TOKENS_PER_REQUEST(self) -> int:
|
@@ -270,16 +276,16 @@ class Config:
|
|
270
276
|
return to_boolean(os.getenv("ZRB_LLM_SUMMARIZE_HISTORY", "true"))
|
271
277
|
|
272
278
|
@property
|
273
|
-
def
|
274
|
-
return int(os.getenv("
|
279
|
+
def LLM_HISTORY_SUMMARIZATION_TOKEN_THRESHOLD(self) -> int:
|
280
|
+
return int(os.getenv("ZRB_LLM_HISTORY_SUMMARIZATION_TOKEN_THRESHOLD", "3000"))
|
275
281
|
|
276
282
|
@property
|
277
283
|
def LLM_ENRICH_CONTEXT(self) -> bool:
|
278
284
|
return to_boolean(os.getenv("ZRB_LLM_ENRICH_CONTEXT", "true"))
|
279
285
|
|
280
286
|
@property
|
281
|
-
def
|
282
|
-
return int(os.getenv("
|
287
|
+
def LLM_CONTEXT_ENRICHMENT_TOKEN_THRESHOLD(self) -> int:
|
288
|
+
return int(os.getenv("ZRB_LLM_CONTEXT_ENRICHMENT_TOKEN_THRESHOLD", "3000"))
|
283
289
|
|
284
290
|
@property
|
285
291
|
def LLM_HISTORY_DIR(self) -> str:
|