zrb 1.9.0__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.
@@ -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:
@@ -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 = 30000,
86
- summarization_token_limit: int = 30000,
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.
@@ -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 = 30000
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})
@@ -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 using SerpApi (Google Search) and return parsed results.
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
- """Maximum number of LLM requests allowed per minute."""
247
- return int(os.getenv("LLM_MAX_REQUESTS_PER_MINUTE", "60"))
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
- """Maximum number of LLM tokens allowed per minute."""
252
- return int(os.getenv("ZRB_LLM_MAX_TOKENS_PER_MINUTE", "200000"))
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 LLM_HISTORY_SUMMARIZATION_THRESHOLD(self) -> int:
274
- return int(os.getenv("ZRB_LLM_HISTORY_SUMMARIZATION_THRESHOLD", "5"))
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 LLM_CONTEXT_ENRICHMENT_THRESHOLD(self) -> int:
282
- return int(os.getenv("ZRB_LLM_CONTEXT_ENRICHMENT_THRESHOLD", "5"))
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:
zrb/llm_config.py CHANGED
@@ -178,9 +178,9 @@ class LLMConfig:
178
178
  default_summarization_prompt: str | None = None,
179
179
  default_context_enrichment_prompt: str | None = None,
180
180
  default_summarize_history: bool | None = None,
181
- default_history_summarization_threshold: int | None = None,
181
+ default_history_summarization_token_threshold: int | None = None,
182
182
  default_enrich_context: bool | None = None,
183
- default_context_enrichment_threshold: int | None = None,
183
+ default_context_enrichment_token_threshold: int | None = None,
184
184
  default_model: "Model | None" = None,
185
185
  default_model_settings: "ModelSettings | None" = None,
186
186
  default_model_provider: "Provider | None" = None,
@@ -195,12 +195,12 @@ class LLMConfig:
195
195
  self._default_summarization_prompt = default_summarization_prompt
196
196
  self._default_context_enrichment_prompt = default_context_enrichment_prompt
197
197
  self._default_summarize_history = default_summarize_history
198
- self._default_history_summarization_threshold = (
199
- default_history_summarization_threshold
198
+ self._default_history_summarization_token_threshold = (
199
+ default_history_summarization_token_threshold
200
200
  )
201
201
  self._default_enrich_context = default_enrich_context
202
- self._default_context_enrichment_threshold = (
203
- default_context_enrichment_threshold
202
+ self._default_context_enrichment_token_threshold = (
203
+ default_context_enrichment_token_threshold
204
204
  )
205
205
  self._default_model_settings = default_model_settings
206
206
  self._default_model_provider = default_model_provider
@@ -316,10 +316,10 @@ class LLMConfig:
316
316
  return CFG.LLM_SUMMARIZE_HISTORY
317
317
 
318
318
  @property
319
- def default_history_summarization_threshold(self) -> int:
320
- if self._default_history_summarization_threshold is not None:
321
- return self._default_history_summarization_threshold
322
- return CFG.LLM_HISTORY_SUMMARIZATION_THRESHOLD
319
+ def default_history_summarization_token_threshold(self) -> int:
320
+ if self._default_history_summarization_token_threshold is not None:
321
+ return self._default_history_summarization_token_threshold
322
+ return CFG.LLM_HISTORY_SUMMARIZATION_TOKEN_THRESHOLD
323
323
 
324
324
  @property
325
325
  def default_enrich_context(self) -> bool:
@@ -328,10 +328,10 @@ class LLMConfig:
328
328
  return CFG.LLM_ENRICH_CONTEXT
329
329
 
330
330
  @property
331
- def default_context_enrichment_threshold(self) -> int:
332
- if self._default_context_enrichment_threshold is not None:
333
- return self._default_context_enrichment_threshold
334
- return CFG.LLM_CONTEXT_ENRICHMENT_THRESHOLD
331
+ def default_context_enrichment_token_threshold(self) -> int:
332
+ if self._default_context_enrichment_token_threshold is not None:
333
+ return self._default_context_enrichment_token_threshold
334
+ return CFG.LLM_CONTEXT_ENRICHMENT_TOKEN_THRESHOLD
335
335
 
336
336
  def set_default_persona(self, persona: str):
337
337
  self._default_persona = persona
@@ -369,18 +369,22 @@ class LLMConfig:
369
369
  def set_default_summarize_history(self, summarize_history: bool):
370
370
  self._default_summarize_history = summarize_history
371
371
 
372
- def set_default_history_summarization_threshold(
373
- self, history_summarization_threshold: int
372
+ def set_default_history_summarization_token_threshold(
373
+ self, history_summarization_token_threshold: int
374
374
  ):
375
- self._default_history_summarization_threshold = history_summarization_threshold
375
+ self._default_history_summarization_token_threshold = (
376
+ history_summarization_token_threshold
377
+ )
376
378
 
377
379
  def set_default_enrich_context(self, enrich_context: bool):
378
380
  self._default_enrich_context = enrich_context
379
381
 
380
- def set_default_context_enrichment_threshold(
381
- self, context_enrichment_threshold: int
382
+ def set_default_context_enrichment_token_threshold(
383
+ self, context_enrichment_token_threshold: int
382
384
  ):
383
- self._default_context_enrichment_threshold = context_enrichment_threshold
385
+ self._default_context_enrichment_token_threshold = (
386
+ context_enrichment_token_threshold
387
+ )
384
388
 
385
389
  def set_default_model_settings(self, model_settings: "ModelSettings"):
386
390
  self._default_model_settings = model_settings
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
5
5
  from zrb.attr.type import BoolAttr, IntAttr
6
6
  from zrb.context.any_context import AnyContext
7
7
  from zrb.llm_config import llm_config
8
- from zrb.llm_rate_limitter import LLMRateLimiter
8
+ from zrb.llm_rate_limitter import LLMRateLimiter, llm_rate_limitter
9
9
  from zrb.task.llm.agent import run_agent_iteration
10
10
  from zrb.task.llm.history import (
11
11
  count_part_in_history_list,
@@ -20,6 +20,12 @@ if TYPE_CHECKING:
20
20
  from pydantic_ai.settings import ModelSettings
21
21
 
22
22
 
23
+ def _count_token_in_history(history_list: ListOfDict) -> int:
24
+ """Counts the total number of tokens in a conversation history list."""
25
+ text_to_count = json.dumps(history_list)
26
+ return llm_rate_limitter.count_token(text_to_count)
27
+
28
+
23
29
  async def enrich_context(
24
30
  ctx: AnyContext,
25
31
  model: "Model | str | None",
@@ -77,22 +83,22 @@ async def enrich_context(
77
83
  return previous_long_term_context
78
84
 
79
85
 
80
- def get_context_enrichment_threshold(
86
+ def get_context_enrichment_token_threshold(
81
87
  ctx: AnyContext,
82
- context_enrichment_threshold_attr: IntAttr | None,
83
- render_context_enrichment_threshold: bool,
88
+ context_enrichment_token_threshold_attr: IntAttr | None,
89
+ render_context_enrichment_token_threshold: bool,
84
90
  ) -> int:
85
- """Gets the context enrichment threshold, handling defaults and errors."""
91
+ """Gets the context enrichment token threshold, handling defaults and errors."""
86
92
  try:
87
93
  return get_int_attr(
88
94
  ctx,
89
- context_enrichment_threshold_attr,
90
- llm_config.default_context_enrichment_threshold,
91
- auto_render=render_context_enrichment_threshold,
95
+ context_enrichment_token_threshold_attr,
96
+ llm_config.default_context_enrichment_token_threshold,
97
+ auto_render=render_context_enrichment_token_threshold,
92
98
  )
93
99
  except ValueError as e:
94
100
  ctx.log_warning(
95
- f"Could not convert context_enrichment_threshold to int: {e}. "
101
+ f"Could not convert context_enrichment_token_threshold to int: {e}. "
96
102
  "Defaulting to -1 (no threshold)."
97
103
  )
98
104
  return -1
@@ -103,21 +109,25 @@ def should_enrich_context(
103
109
  history_list: ListOfDict,
104
110
  should_enrich_context_attr: BoolAttr | None,
105
111
  render_enrich_context: bool,
106
- context_enrichment_threshold_attr: IntAttr | None,
107
- render_context_enrichment_threshold: bool,
112
+ context_enrichment_token_threshold_attr: IntAttr | None,
113
+ render_context_enrichment_token_threshold: bool,
108
114
  ) -> bool:
109
115
  """
110
- Determines if context enrichment should occur based on history, threshold, and config.
116
+ Determines if context enrichment should occur based on history, token threshold, and config.
111
117
  """
112
118
  history_part_count = count_part_in_history_list(history_list)
113
119
  if history_part_count == 0:
114
120
  return False
115
- enrichment_threshold = get_context_enrichment_threshold(
121
+ enrichment_token_threshold = get_context_enrichment_token_threshold(
116
122
  ctx,
117
- context_enrichment_threshold_attr,
118
- render_context_enrichment_threshold,
123
+ context_enrichment_token_threshold_attr,
124
+ render_context_enrichment_token_threshold,
119
125
  )
120
- if enrichment_threshold == -1 or enrichment_threshold > history_part_count:
126
+ history_token_count = _count_token_in_history(history_list)
127
+ if (
128
+ enrichment_token_threshold == -1
129
+ or enrichment_token_threshold > history_token_count
130
+ ):
121
131
  return False
122
132
  return get_bool_attr(
123
133
  ctx,
@@ -133,22 +143,22 @@ async def maybe_enrich_context(
133
143
  long_term_context: str,
134
144
  should_enrich_context_attr: BoolAttr | None,
135
145
  render_enrich_context: bool,
136
- context_enrichment_threshold_attr: IntAttr | None,
137
- render_context_enrichment_threshold: bool,
146
+ context_enrichment_token_threshold_attr: IntAttr | None,
147
+ render_context_enrichment_token_threshold: bool,
138
148
  model: "str | Model | None",
139
149
  model_settings: "ModelSettings | None",
140
150
  context_enrichment_prompt: str,
141
151
  rate_limitter: LLMRateLimiter | None = None,
142
152
  ) -> str:
143
- """Enriches context based on history if enabled and threshold met."""
153
+ """Enriches context based on history if enabled and token threshold met."""
144
154
  shorten_history_list = replace_system_prompt_in_history_list(history_list)
145
155
  if should_enrich_context(
146
156
  ctx,
147
157
  shorten_history_list,
148
158
  should_enrich_context_attr,
149
159
  render_enrich_context,
150
- context_enrichment_threshold_attr,
151
- render_context_enrichment_threshold,
160
+ context_enrichment_token_threshold_attr,
161
+ render_context_enrichment_token_threshold,
152
162
  ):
153
163
  return await enrich_context(
154
164
  ctx=ctx,
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
5
5
  from zrb.attr.type import BoolAttr, IntAttr
6
6
  from zrb.context.any_context import AnyContext
7
7
  from zrb.llm_config import llm_config
8
- from zrb.llm_rate_limitter import LLMRateLimiter
8
+ from zrb.llm_rate_limitter import LLMRateLimiter, llm_rate_limitter
9
9
  from zrb.task.llm.agent import run_agent_iteration
10
10
  from zrb.task.llm.history import (
11
11
  count_part_in_history_list,
@@ -20,22 +20,28 @@ if TYPE_CHECKING:
20
20
  from pydantic_ai.settings import ModelSettings
21
21
 
22
22
 
23
- def get_history_summarization_threshold(
23
+ def _count_token_in_history(history_list: ListOfDict) -> int:
24
+ """Counts the total number of tokens in a conversation history list."""
25
+ text_to_count = json.dumps(history_list)
26
+ return llm_rate_limitter.count_token(text_to_count)
27
+
28
+
29
+ def get_history_summarization_token_threshold(
24
30
  ctx: AnyContext,
25
- history_summarization_threshold_attr: IntAttr | None,
26
- render_history_summarization_threshold: bool,
31
+ history_summarization_token_threshold_attr: IntAttr | None,
32
+ render_history_summarization_token_threshold: bool,
27
33
  ) -> int:
28
- """Gets the history summarization threshold, handling defaults and errors."""
34
+ """Gets the history summarization token threshold, handling defaults and errors."""
29
35
  try:
30
36
  return get_int_attr(
31
37
  ctx,
32
- history_summarization_threshold_attr,
33
- llm_config.default_history_summarization_threshold,
34
- auto_render=render_history_summarization_threshold,
38
+ history_summarization_token_threshold_attr,
39
+ llm_config.default_history_summarization_token_threshold,
40
+ auto_render=render_history_summarization_token_threshold,
35
41
  )
36
42
  except ValueError as e:
37
43
  ctx.log_warning(
38
- f"Could not convert history_summarization_threshold to int: {e}. "
44
+ f"Could not convert history_summarization_token_threshold to int: {e}. "
39
45
  "Defaulting to -1 (no threshold)."
40
46
  )
41
47
  return -1
@@ -46,19 +52,23 @@ def should_summarize_history(
46
52
  history_list: ListOfDict,
47
53
  should_summarize_history_attr: BoolAttr | None,
48
54
  render_summarize_history: bool,
49
- history_summarization_threshold_attr: IntAttr | None,
50
- render_history_summarization_threshold: bool,
55
+ history_summarization_token_threshold_attr: IntAttr | None,
56
+ render_history_summarization_token_threshold: bool,
51
57
  ) -> bool:
52
- """Determines if history summarization should occur based on length and config."""
58
+ """Determines if history summarization should occur based on token length and config."""
53
59
  history_part_count = count_part_in_history_list(history_list)
54
60
  if history_part_count == 0:
55
61
  return False
56
- summarization_threshold = get_history_summarization_threshold(
62
+ summarization_token_threshold = get_history_summarization_token_threshold(
57
63
  ctx,
58
- history_summarization_threshold_attr,
59
- render_history_summarization_threshold,
64
+ history_summarization_token_threshold_attr,
65
+ render_history_summarization_token_threshold,
60
66
  )
61
- if summarization_threshold == -1 or summarization_threshold > history_part_count:
67
+ history_token_count = _count_token_in_history(history_list)
68
+ if (
69
+ summarization_token_threshold == -1
70
+ or summarization_token_threshold > history_token_count
71
+ ):
62
72
  return False
63
73
  return get_bool_attr(
64
74
  ctx,
@@ -102,7 +112,7 @@ async def summarize_history(
102
112
  history_list=[],
103
113
  rate_limitter=rate_limitter,
104
114
  )
105
- if summary_run and summary_run.result.output:
115
+ if summary_run and summary_run.result and summary_run.result.output:
106
116
  new_summary = str(summary_run.result.output)
107
117
  usage = summary_run.result.usage()
108
118
  ctx.print(stylize_faint(f"📝 Summarization Token: {usage}"), plain=True)
@@ -126,8 +136,8 @@ async def maybe_summarize_history(
126
136
  conversation_summary: str,
127
137
  should_summarize_history_attr: BoolAttr | None,
128
138
  render_summarize_history: bool,
129
- history_summarization_threshold_attr: IntAttr | None,
130
- render_history_summarization_threshold: bool,
139
+ history_summarization_token_threshold_attr: IntAttr | None,
140
+ render_history_summarization_token_threshold: bool,
131
141
  model: "str | Model | None",
132
142
  model_settings: "ModelSettings | None",
133
143
  summarization_prompt: str,
@@ -140,8 +150,8 @@ async def maybe_summarize_history(
140
150
  shorten_history_list,
141
151
  should_summarize_history_attr,
142
152
  render_summarize_history,
143
- history_summarization_threshold_attr,
144
- render_history_summarization_threshold,
153
+ history_summarization_token_threshold_attr,
154
+ render_history_summarization_token_threshold,
145
155
  ):
146
156
  new_summary = await summarize_history(
147
157
  ctx=ctx,
zrb/task/llm_task.py CHANGED
@@ -75,8 +75,8 @@ class LLMTask(BaseTask):
75
75
  render_enrich_context: bool = True,
76
76
  context_enrichment_prompt: StrAttr | None = None,
77
77
  render_context_enrichment_prompt: bool = True,
78
- context_enrichment_threshold: IntAttr | None = None,
79
- render_context_enrichment_threshold: bool = True,
78
+ context_enrichment_token_threshold: IntAttr | None = None,
79
+ render_context_enrichment_token_threshold: bool = True,
80
80
  tools: (
81
81
  list["ToolOrCallable"]
82
82
  | Callable[[AnySharedContext], list["ToolOrCallable"]]
@@ -102,8 +102,8 @@ class LLMTask(BaseTask):
102
102
  summarize_history: BoolAttr | None = None,
103
103
  render_summarize_history: bool = True,
104
104
  summarization_prompt: StrAttr | None = None,
105
- history_summarization_threshold: IntAttr | None = None,
106
- render_history_summarization_threshold: bool = True,
105
+ history_summarization_token_threshold: IntAttr | None = None,
106
+ render_history_summarization_token_threshold: bool = True,
107
107
  rate_limitter: LLMRateLimiter | None = None,
108
108
  execute_condition: bool | str | Callable[[AnySharedContext], bool] = True,
109
109
  retries: int = 2,
@@ -161,8 +161,10 @@ class LLMTask(BaseTask):
161
161
  self._render_enrich_context = render_enrich_context
162
162
  self._context_enrichment_prompt = context_enrichment_prompt
163
163
  self._render_context_enrichment_prompt = render_context_enrichment_prompt
164
- self._context_enrichment_threshold = context_enrichment_threshold
165
- self._render_context_enrichment_threshold = render_context_enrichment_threshold
164
+ self._context_enrichment_token_threshold = context_enrichment_token_threshold
165
+ self._render_context_enrichment_token_threshold = (
166
+ render_context_enrichment_token_threshold
167
+ )
166
168
  self._tools = tools
167
169
  self._rate_limitter = rate_limitter
168
170
  self._additional_tools: list["ToolOrCallable"] = []
@@ -175,9 +177,11 @@ class LLMTask(BaseTask):
175
177
  self._render_history_file = render_history_file
176
178
  self._should_summarize_history = summarize_history
177
179
  self._render_summarize_history = render_summarize_history
178
- self._history_summarization_threshold = history_summarization_threshold
179
- self._render_history_summarization_threshold = (
180
- render_history_summarization_threshold
180
+ self._history_summarization_token_threshold = (
181
+ history_summarization_token_threshold
182
+ )
183
+ self._render_history_summarization_token_threshold = (
184
+ render_history_summarization_token_threshold
181
185
  )
182
186
  self._max_call_iteration = max_call_iteration
183
187
  self._conversation_context = conversation_context
@@ -199,14 +203,16 @@ class LLMTask(BaseTask):
199
203
  def set_should_enrich_context(self, enrich_context: bool):
200
204
  self._should_enrich_context = enrich_context
201
205
 
202
- def set_context_enrichment_threshold(self, enrichment_threshold: int):
203
- self._context_enrichment_threshold = enrichment_threshold
206
+ def set_context_enrichment_token_threshold(self, enrichment_token_threshold: int):
207
+ self._context_enrichment_token_threshold = enrichment_token_threshold
204
208
 
205
209
  def set_should_summarize_history(self, summarize_history: bool):
206
210
  self._should_summarize_history = summarize_history
207
211
 
208
- def set_history_summarization_threshold(self, summarization_threshold: int):
209
- self._history_summarization_threshold = summarization_threshold
212
+ def set_history_summarization_token_threshold(
213
+ self, summarization_token_threshold: int
214
+ ):
215
+ self._history_summarization_token_threshold = summarization_token_threshold
210
216
 
211
217
  async def _exec_action(self, ctx: AnyContext) -> Any:
212
218
  # Get dependent configurations first
@@ -255,8 +261,8 @@ class LLMTask(BaseTask):
255
261
  long_term_context=long_term_context,
256
262
  should_enrich_context_attr=self._should_enrich_context,
257
263
  render_enrich_context=self._render_enrich_context,
258
- context_enrichment_threshold_attr=self._context_enrichment_threshold,
259
- render_context_enrichment_threshold=self._render_context_enrichment_threshold,
264
+ context_enrichment_token_threshold_attr=self._context_enrichment_token_threshold,
265
+ render_context_enrichment_token_threshold=self._render_context_enrichment_token_threshold,
260
266
  model=model,
261
267
  model_settings=model_settings,
262
268
  context_enrichment_prompt=context_enrichment_prompt,
@@ -268,9 +274,9 @@ class LLMTask(BaseTask):
268
274
  conversation_summary=conversation_summary,
269
275
  should_summarize_history_attr=self._should_summarize_history,
270
276
  render_summarize_history=self._render_summarize_history,
271
- history_summarization_threshold_attr=self._history_summarization_threshold,
272
- render_history_summarization_threshold=(
273
- self._render_history_summarization_threshold
277
+ history_summarization_token_threshold_attr=self._history_summarization_token_threshold,
278
+ render_history_summarization_token_threshold=(
279
+ self._render_history_summarization_token_threshold
274
280
  ),
275
281
  model=model,
276
282
  model_settings=model_settings,
@@ -358,3 +364,134 @@ class LLMTask(BaseTask):
358
364
  except Exception as e:
359
365
  ctx.log_error(f"Error during agent execution or history saving: {str(e)}")
360
366
  raise # Re-raise the exception after logging
367
+
368
+
369
+ def llm_task(
370
+ name: str,
371
+ color: int | None = None,
372
+ icon: str | None = None,
373
+ description: str | None = None,
374
+ cli_only: bool = False,
375
+ input: list[AnyInput | None] | AnyInput | None = None,
376
+ env: list[AnyEnv | None] | AnyEnv | None = None,
377
+ model: "Callable[[AnySharedContext], Model | str | fstring] | Model | None" = None,
378
+ render_model: bool = True,
379
+ model_base_url: StrAttr | None = None,
380
+ render_model_base_url: bool = True,
381
+ model_api_key: StrAttr | None = None,
382
+ render_model_api_key: bool = True,
383
+ model_settings: "ModelSettings | Callable[[AnySharedContext], ModelSettings] | None" = None,
384
+ agent: "Agent | Callable[[AnySharedContext], Agent] | None" = None,
385
+ persona: StrAttr | None = None,
386
+ system_prompt: StrAttr | None = None,
387
+ special_instruction_prompt: StrAttr | None = None,
388
+ message: StrAttr | None = None,
389
+ render_message: bool = True,
390
+ enrich_context: BoolAttr | None = None,
391
+ render_enrich_context: bool = True,
392
+ context_enrichment_prompt: StrAttr | None = None,
393
+ render_context_enrichment_prompt: bool = True,
394
+ context_enrichment_token_threshold: IntAttr | None = None,
395
+ render_context_enrichment_token_threshold: bool = True,
396
+ tools: (
397
+ list["ToolOrCallable"] | Callable[[AnySharedContext], list["ToolOrCallable"]]
398
+ ) = [],
399
+ mcp_servers: (
400
+ list["MCPServer"] | Callable[[AnySharedContext], list["MCPServer"]]
401
+ ) = [],
402
+ conversation_history: (
403
+ ConversationHistoryData
404
+ | Callable[[AnySharedContext], ConversationHistoryData | dict | list]
405
+ | dict
406
+ | list
407
+ ) = ConversationHistoryData(),
408
+ conversation_history_reader: (
409
+ Callable[[AnySharedContext], ConversationHistoryData | dict | list | None]
410
+ | None
411
+ ) = None,
412
+ conversation_history_writer: (
413
+ Callable[[AnySharedContext, ConversationHistoryData], None] | None
414
+ ) = None,
415
+ conversation_history_file: StrAttr | None = None,
416
+ render_history_file: bool = True,
417
+ summarize_history: BoolAttr | None = None,
418
+ render_summarize_history: bool = True,
419
+ summarization_prompt: StrAttr | None = None,
420
+ history_summarization_token_threshold: IntAttr | None = None,
421
+ render_history_summarization_token_threshold: bool = True,
422
+ rate_limitter: LLMRateLimiter | None = None,
423
+ execute_condition: bool | str | Callable[[AnySharedContext], bool] = True,
424
+ retries: int = 2,
425
+ retry_period: float = 0,
426
+ readiness_check: list[AnyTask] | AnyTask | None = None,
427
+ readiness_check_delay: float = 0.5,
428
+ readiness_check_period: float = 5,
429
+ readiness_failure_threshold: int = 1,
430
+ readiness_timeout: int = 60,
431
+ monitor_readiness: bool = False,
432
+ max_call_iteration: int = 20,
433
+ upstream: list[AnyTask] | AnyTask | None = None,
434
+ fallback: list[AnyTask] | AnyTask | None = None,
435
+ successor: list[AnyTask] | AnyTask | None = None,
436
+ conversation_context: (
437
+ dict[str, Any] | Callable[[AnySharedContext], dict[str, Any]] | None
438
+ ) = None,
439
+ ) -> LLMTask:
440
+ """
441
+ Create a new LLM task.
442
+ """
443
+ return LLMTask(
444
+ name=name,
445
+ color=color,
446
+ icon=icon,
447
+ description=description,
448
+ cli_only=cli_only,
449
+ input=input,
450
+ env=env,
451
+ model=model,
452
+ render_model=render_model,
453
+ model_base_url=model_base_url,
454
+ render_model_base_url=render_model_base_url,
455
+ model_api_key=model_api_key,
456
+ render_model_api_key=render_model_api_key,
457
+ model_settings=model_settings,
458
+ agent=agent,
459
+ persona=persona,
460
+ system_prompt=system_prompt,
461
+ special_instruction_prompt=special_instruction_prompt,
462
+ message=message,
463
+ render_message=render_message,
464
+ enrich_context=enrich_context,
465
+ render_enrich_context=render_enrich_context,
466
+ context_enrichment_prompt=context_enrichment_prompt,
467
+ render_context_enrichment_prompt=render_context_enrichment_prompt,
468
+ context_enrichment_token_threshold=context_enrichment_token_threshold,
469
+ render_context_enrichment_token_threshold=render_context_enrichment_token_threshold,
470
+ tools=tools,
471
+ mcp_servers=mcp_servers,
472
+ conversation_history=conversation_history,
473
+ conversation_history_reader=conversation_history_reader,
474
+ conversation_history_writer=conversation_history_writer,
475
+ conversation_history_file=conversation_history_file,
476
+ render_history_file=render_history_file,
477
+ summarize_history=summarize_history,
478
+ render_summarize_history=render_summarize_history,
479
+ summarization_prompt=summarization_prompt,
480
+ history_summarization_token_threshold=history_summarization_token_threshold,
481
+ render_history_summarization_token_threshold=render_history_summarization_token_threshold,
482
+ rate_limitter=rate_limitter,
483
+ execute_condition=execute_condition,
484
+ retries=retries,
485
+ retry_period=retry_period,
486
+ readiness_check=readiness_check,
487
+ readiness_check_delay=readiness_check_delay,
488
+ readiness_check_period=readiness_check_period,
489
+ readiness_failure_threshold=readiness_failure_threshold,
490
+ readiness_timeout=readiness_timeout,
491
+ monitor_readiness=monitor_readiness,
492
+ max_call_iteration=max_call_iteration,
493
+ upstream=upstream,
494
+ fallback=fallback,
495
+ successor=successor,
496
+ conversation_context=conversation_context,
497
+ )
zrb/util/file.py CHANGED
@@ -31,9 +31,10 @@ def _read_text_file_content(file_path: str) -> str:
31
31
 
32
32
 
33
33
  def _read_pdf_file_content(file_path: str) -> str:
34
- from pdfplumber.pdf import PDF, open
34
+ import pdfplumber
35
+ from pdfplumber.pdf import PDF
35
36
 
36
- with open(file_path) as pdf:
37
+ with pdfplumber.open(file_path) as pdf:
37
38
  pdf: PDF
38
39
  return "\n".join(
39
40
  page.extract_text() for page in pdf.pages if page.extract_text()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zrb
3
- Version: 1.9.0
3
+ Version: 1.9.1
4
4
  Summary: Your Automation Powerhouse
5
5
  Home-page: https://github.com/state-alchemists/zrb
6
6
  License: AGPL-3.0-or-later
@@ -12,16 +12,16 @@ zrb/builtin/jwt.py,sha256=3M5uaQhJZbKQLjTUft1OwPz_JxtmK-xtkjxWjciOQho,2859
12
12
  zrb/builtin/llm/chat_session.py,sha256=iDNHbNX268NWl0uQmjecD3Bn686-mByqqXuMc6IKjOE,7146
13
13
  zrb/builtin/llm/history.py,sha256=cnkOyO43uiMQ9cEvmqk-pPoCk1zCAH_fwAqSgBtsjzY,3079
14
14
  zrb/builtin/llm/input.py,sha256=Nw-26uTWp2QhUgKJcP_IMHmtk-b542CCSQ_vCOjhvhM,877
15
- zrb/builtin/llm/llm_ask.py,sha256=QUV29gOAFKiMfJlAKbY9YfGPoxYv-4RPv6p7cWogK4U,4438
15
+ zrb/builtin/llm/llm_ask.py,sha256=A7MmDybKSdlO0T-Wted9t1tEtw6ULjV5aG4SAwpkt-w,4532
16
16
  zrb/builtin/llm/previous-session.js,sha256=xMKZvJoAbrwiyHS0OoPrWuaKxWYLoyR5sguePIoCjTY,816
17
17
  zrb/builtin/llm/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  zrb/builtin/llm/tool/api.py,sha256=yR9I0ZsI96OeQl9pgwORMASVuXsAL0a89D_iPS4C8Dc,1699
19
19
  zrb/builtin/llm/tool/cli.py,sha256=_CNEmEc6K2Z0i9ppYeM7jGpqaEdT3uxaWQatmxP3jKE,858
20
- zrb/builtin/llm/tool/code.py,sha256=q6YrVJkRJg4AQpnK2KHE6AEMo8nMbRN4XUZ3QtMI_Og,8090
21
- zrb/builtin/llm/tool/file.py,sha256=ufLCAaHB0JkEAqQS4fbM9OaTfLluqlCuSyMmnYhI0rY,18491
20
+ zrb/builtin/llm/tool/code.py,sha256=t_0-D1ToSm3ATYGiHfkHBpvkRGMnrmwWSYk_M73AVXs,8090
21
+ zrb/builtin/llm/tool/file.py,sha256=z0_RihenF09WtJqAoKLMeVmkqTS7Z5W04lwkKbyfESo,22683
22
22
  zrb/builtin/llm/tool/rag.py,sha256=yqx7vXXyrOCJjhQJl4s0TnLL-2uQUTuKRnkWlSQBW0M,7883
23
23
  zrb/builtin/llm/tool/sub_agent.py,sha256=Xz_nwNA8hW52gCeNdNBT7TXDUU3x7Ube-t17FYxx0-E,4671
24
- zrb/builtin/llm/tool/web.py,sha256=GYp6e_eaw-dj7MDpB4CP1fplUbfguuJawem9lPJM9TY,5481
24
+ zrb/builtin/llm/tool/web.py,sha256=SeTT59yRaBJQILi4TETaLDMna2ooeYStYMkAnn0uTZc,5465
25
25
  zrb/builtin/md5.py,sha256=690RV2LbW7wQeTFxY-lmmqTSVEEZv3XZbjEUW1Q3XpE,1480
26
26
  zrb/builtin/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  zrb/builtin/project/add/fastapp/fastapp_input.py,sha256=MKlWR_LxWhM_DcULCtLfL_IjTxpDnDBkn9KIqNmajFs,310
@@ -217,7 +217,7 @@ zrb/callback/callback.py,sha256=PFhCqzfxdk6IAthmXcZ13DokT62xtBzJr_ciLw6I8Zg,4030
217
217
  zrb/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
218
  zrb/cmd/cmd_result.py,sha256=L8bQJzWCpcYexIxHBNsXj2pT3BtLmWex0iJSMkvimOA,597
219
219
  zrb/cmd/cmd_val.py,sha256=7Doowyg6BK3ISSGBLt-PmlhzaEkBjWWm51cED6fAUOQ,1014
220
- zrb/config.py,sha256=L4nrdO8uK1MNOJiTgywkjlYn5kX1dOsq48b5mPZ2004,10364
220
+ zrb/config.py,sha256=TRVQgUgX9yIso0Z6GioX8HASX_tITv5m_AP0Z4UIWb4,10574
221
221
  zrb/content_transformer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
222
222
  zrb/content_transformer/any_content_transformer.py,sha256=v8ZUbcix1GGeDQwB6OKX_1TjpY__ksxWVeqibwa_iZA,850
223
223
  zrb/content_transformer/content_transformer.py,sha256=STl77wW-I69QaGzCXjvkppngYFLufow8ybPLSyAvlHs,2404
@@ -246,7 +246,7 @@ zrb/input/option_input.py,sha256=TQB82ko5odgzkULEizBZi0e9TIHEbIgvdP0AR3RhA74,213
246
246
  zrb/input/password_input.py,sha256=szBojWxSP9QJecgsgA87OIYwQrY2AQ3USIKdDZY6snU,1465
247
247
  zrb/input/str_input.py,sha256=NevZHX9rf1g8eMatPyy-kUX3DglrVAQpzvVpKAzf7bA,81
248
248
  zrb/input/text_input.py,sha256=6T3MngWdUs0u0ZVs5Dl11w5KS7nN1RkgrIR_zKumzPM,3695
249
- zrb/llm_config.py,sha256=s-8stT_9lsH35IQuE6YBdLbi2actYgwLDFqEqgedtCE,16964
249
+ zrb/llm_config.py,sha256=FaX_v_EeZoXoZtwFpjqpvPHkxyaeqAziwcu-zb8lGXM,17144
250
250
  zrb/llm_rate_limitter.py,sha256=uM9zmSgV10fQq1dlaDGLDrv72uLj6ldBxMoGjO2Az14,4429
251
251
  zrb/runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
252
252
  zrb/runner/cli.py,sha256=y_n7O4kQKXpiLAsBsf1WnHNfWgKlrxE9TU0MU1ICUMg,6989
@@ -340,15 +340,15 @@ zrb/task/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
340
340
  zrb/task/llm/agent.py,sha256=IUGhU7vSfGVpVbd0rGLJ9kxIpt04Fm6UBjBWCVBVaZs,6695
341
341
  zrb/task/llm/config.py,sha256=WsO_kLXi0JXB6SPNL_dWm1Izu04KRrzmuX6DSrRYeBE,3483
342
342
  zrb/task/llm/context.py,sha256=LGGQ_mb1dWorfshHjGgXEW_pRweGj-6MZcIUFq3AHy4,2213
343
- zrb/task/llm/context_enrichment.py,sha256=NrRsKLY2Kojx2lejdEJxORgz-7uiQ1_A_IAyHJ3WVPQ,5552
343
+ zrb/task/llm/context_enrichment.py,sha256=MBs5lkI1GFZwtW_P7fJfIc6CWcAS0UWQeBO6ggSBSk0,6033
344
344
  zrb/task/llm/error.py,sha256=s5PSK3ibGupMzOM0P81nstHWrMr3205H0FSDwfhWUDA,3662
345
345
  zrb/task/llm/history.py,sha256=cpaNqoEzsNAgzZGPPdohA8U5nTrVWmZ3P1Y5RTtXDgc,7986
346
- zrb/task/llm/history_summarization.py,sha256=-A7oLLHYYxlYL6RQYhaL_yu2yT1qlB9s8kAWGNqPWfU,5534
346
+ zrb/task/llm/history_summarization.py,sha256=TejPLktJmlxvhM-UmC29m_g6U9bC5e4fxRSEwt22GN0,6032
347
347
  zrb/task/llm/print_node.py,sha256=VB_ZXD3iQPZcaNWvIMNRxOhmXIJp9__5utRrSbiIRXo,4457
348
348
  zrb/task/llm/prompt.py,sha256=obSsLHF4AHlwMIEQ8XTJmtEnkLlD5XTEkk0RJUmIz4Y,3410
349
349
  zrb/task/llm/tool_wrapper.py,sha256=8_bL8m_WpRf-pVKSrvQIVqT-m2sUA87a1RBQG13lhp4,6457
350
350
  zrb/task/llm/typing.py,sha256=c8VAuPBw_4A3DxfYdydkgedaP-LU61W9_wj3m3CAX1E,58
351
- zrb/task/llm_task.py,sha256=-1-QV3wN70CgGSdtAj4F5QR9wMQSLsFJcUcFkeiCoWs,15362
351
+ zrb/task/llm_task.py,sha256=qNXS078y2s6iF50OoVH3-7UWmV5phgfY2_NiZ9KxGSU,21055
352
352
  zrb/task/make_task.py,sha256=PD3b_aYazthS8LHeJsLAhwKDEgdurQZpymJDKeN60u0,2265
353
353
  zrb/task/rsync_task.py,sha256=GSL9144bmp6F0EckT6m-2a1xG25AzrrWYzH4k3SVUKM,6370
354
354
  zrb/task/scaffolder.py,sha256=rME18w1HJUHXgi9eTYXx_T2G4JdqDYzBoNOkdOOo5-o,6806
@@ -376,7 +376,7 @@ zrb/util/codemod/modify_function_call.py,sha256=wbyoRRsM4V9fPYkT5kHN0zpBetpRDq2S
376
376
  zrb/util/codemod/modify_method.py,sha256=5fioXjqNQmrf4CV2qlTZHpViF9PMnNer4FvuKam7byo,6344
377
377
  zrb/util/codemod/modify_module.py,sha256=2mzi_NxJ-kNFo5G0U_Rqb3JoYQl1s6Izt7b_wAl10F0,715
378
378
  zrb/util/cron.py,sha256=UWqqhhM7DDTPx6wjCIdBndnVh3NRu-sdKazp8aZkXh8,3833
379
- zrb/util/file.py,sha256=RJosRqX63iwJ-TLx0yzJ4pJ97evKR-oxxs4r-v5chq0,2779
379
+ zrb/util/file.py,sha256=tm_8qn0vEM8Hz46yXUcvFHfsLtQNqidQaEuB85xqhFE,2806
380
380
  zrb/util/git.py,sha256=gS_Y9sQgJbY0PfgSQiowLvV3Nf0y9C8nT3j6z6oEsG8,8186
381
381
  zrb/util/git_subtree.py,sha256=E_UB5OIgm8WkHL9beifRxpZ25_BB9p1H578OhLZTgRU,4611
382
382
  zrb/util/group.py,sha256=T82yr3qg9I5k10VPXkMyrIRIqyfzadSH813bqzwKEPI,4718
@@ -391,7 +391,7 @@ zrb/util/todo.py,sha256=r9_KYF2-hLKMNjsp6AFK9zivykMrywd-kJ4bCwfdafI,19323
391
391
  zrb/util/todo_model.py,sha256=0SJ8aLYfJAscDOk5JsH7pXP3h1rAG91VMCS20-c2Y6A,1576
392
392
  zrb/xcom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
393
393
  zrb/xcom/xcom.py,sha256=o79rxR9wphnShrcIushA0Qt71d_p3ZTxjNf7x9hJB78,1571
394
- zrb-1.9.0.dist-info/METADATA,sha256=2j4pOOx_l_wmHxFKn4gVX6wCeI9AOqmKszZ7PASZzSI,9321
395
- zrb-1.9.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
396
- zrb-1.9.0.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
397
- zrb-1.9.0.dist-info/RECORD,,
394
+ zrb-1.9.1.dist-info/METADATA,sha256=ohOmILh0PYQYiYzfuNy3dfuqNT7UorikVrHQO5jIE8I,9321
395
+ zrb-1.9.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
396
+ zrb-1.9.1.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
397
+ zrb-1.9.1.dist-info/RECORD,,
File without changes