zrb 1.5.8__py3-none-any.whl → 1.5.10__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.
@@ -132,9 +132,7 @@ def list_files(
132
132
  all_files.append(full_path)
133
133
  # Return paths relative to the original path requested
134
134
  try:
135
- rel_files = [
136
- os.path.relpath(f, os.path.dirname(abs_path)) for f in all_files
137
- ]
135
+ rel_files = [os.path.relpath(f, abs_path) for f in all_files]
138
136
  return json.dumps({"files": sorted(rel_files)})
139
137
  except (
140
138
  ValueError
zrb/llm_config.py CHANGED
@@ -5,62 +5,45 @@ from pydantic_ai.models.openai import OpenAIModel
5
5
  from pydantic_ai.providers import Provider
6
6
  from pydantic_ai.providers.openai import OpenAIProvider
7
7
 
8
+ from zrb.util.string.conversion import to_boolean
9
+
8
10
  DEFAULT_SYSTEM_PROMPT = """
9
- You are a highly capable AI assistant with access to tools. Your primary goal is to
10
- provide accurate, reliable, and helpful responses.
11
-
12
- Key Instructions:
13
- 1. **Prioritize Tool Use:** Always attempt to use available tools to find
14
- information or perform actions before asking the user.
15
- 2. **Correct Tool Invocation:** Use tools precisely as defined. Provide arguments
16
- in valid JSON where required. Do not pass arguments to tools that don't accept
17
- them. Handle tool errors gracefully and retry or adapt your strategy if necessary.
18
- 3. **Accuracy is Paramount:** Ensure all information, code, or outputs provided are
19
- correct and reliable. Verify facts and generate executable code when requested.
20
- 4. **Clarity and Conciseness:** Respond clearly and directly to the user's query
21
- after gathering the necessary information. Avoid unnecessary conversation.
22
- 5. **Context Awareness:** Understand the user's request fully to provide the most
23
- relevant and effective assistance.
11
+ You have access to tools.
12
+ Your goal is to complete the user's task efficiently.
13
+ Analyze the request and use the available tools proactively to achieve the goal.
14
+ Infer parameters and actions from the context.
15
+ Do not ask for confirmation unless strictly necessary due to ambiguity or
16
+ missing critical information.
17
+ Apply relevant domain knowledge and best practices.
18
+ Respond directly and concisely upon task completion or when clarification is essential.
24
19
  """.strip()
25
20
 
26
21
  DEFAULT_PERSONA = """
27
22
  You are an expert in various fields including technology, science, history, and more.
28
23
  """.strip()
29
24
 
25
+ # Concise summarization focused on preserving critical context for continuity.
30
26
  DEFAULT_SUMMARIZATION_PROMPT = """
31
- You are a summarization assistant. Your task is to synthesize the provided
32
- conversation history and the existing context (which might contain a
33
- previous 'history_summary') into a comprehensive, updated summary
34
- Carefully review the '# Current Context' which includes any previous summary
35
- ('history_summary').
36
- Then, review the '# Conversation History to Summarize'.
37
- Combine the information from both the existing context/summary and the new
38
- history. Create a single, coherent summary that captures ALL essential
39
- information, including:
40
- - Key decisions made
41
- - Actions taken (including tool usage and their results)
42
- - Important facts or data points mentioned
43
- - Outcomes of discussions or actions
44
- - Any unresolved questions or pending items
45
- The goal is to provide a complete yet concise background so that the main
46
- assistant can seamlessly continue the conversation without losing critical
47
- context from the summarized parts.
48
- Output *only* the updated summary text."
27
+ You are a summarization assistant. Create an updated, concise summary integrating
28
+ the previous summary (if any) with the new conversation history.
29
+ Your primary goal is to preserve ALL critical context needed for the main assistant
30
+ to continue the task effectively. This includes key facts, decisions, tool usage
31
+ results, and essential background. Do not omit details that would force the main
32
+ assistant to re-gather information.
33
+ Output *only* the updated summary text.
49
34
  """.strip()
50
35
 
51
36
  DEFAULT_CONTEXT_ENRICHMENT_PROMPT = """
52
37
  You are an information extraction assistant.
53
- Analyze the conversation history and current context to extract key facts such as:
54
- - user_name
55
- - user_roles
56
- - preferences
57
- - goals
58
- - etc
59
- Return only a JSON object containing a single key "response",
60
- whose value is another JSON object with these details.
38
+ Analyze the conversation history and current context to extract key facts like
39
+ user_name, user_roles, preferences, goals, etc.
40
+ Return only a JSON object containing a single key "response", whose value is
41
+ another JSON object with these details.
61
42
  If nothing is found, output {"response": {}}.
62
43
  """.strip()
63
44
 
45
+ DEFAULT_SPECIAL_INSTRUCTION_PROMPT = "" # Default to empty
46
+
64
47
 
65
48
  class LLMConfig:
66
49
 
@@ -71,8 +54,12 @@ class LLMConfig:
71
54
  default_api_key: str | None = None,
72
55
  default_persona: str | None = None,
73
56
  default_system_prompt: str | None = None,
57
+ default_special_instruction_prompt: str | None = None,
74
58
  default_summarization_prompt: str | None = None,
75
59
  default_context_enrichment_prompt: str | None = None,
60
+ default_summarize_history: bool | None = None,
61
+ default_history_summarization_threshold: int | None = None,
62
+ default_enrich_context: bool | None = None,
76
63
  ):
77
64
  self._default_model_name = (
78
65
  default_model_name
@@ -99,6 +86,11 @@ class LLMConfig:
99
86
  if default_persona is not None
100
87
  else os.getenv("ZRB_LLM_PERSONA", None)
101
88
  )
89
+ self._default_special_instruction_prompt = (
90
+ default_special_instruction_prompt
91
+ if default_special_instruction_prompt is not None
92
+ else os.getenv("ZRB_LLM_SPECIAL_INSTRUCTION_PROMPT", None)
93
+ )
102
94
  self._default_summarization_prompt = (
103
95
  default_summarization_prompt
104
96
  if default_summarization_prompt is not None
@@ -109,6 +101,21 @@ class LLMConfig:
109
101
  if default_context_enrichment_prompt is not None
110
102
  else os.getenv("ZRB_LLM_CONTEXT_ENRICHMENT_PROMPT", None)
111
103
  )
104
+ self._default_summarize_history = (
105
+ default_summarize_history
106
+ if default_summarize_history is not None
107
+ else os.getenv("ZRB_LLM_SUMMARIZE_HISTORY", "true").lower() == "true"
108
+ )
109
+ self._default_history_summarization_threshold = (
110
+ default_history_summarization_threshold
111
+ if default_history_summarization_threshold is not None
112
+ else int(os.getenv("ZRB_LLM_HISTORY_SUMMARIZATION_THRESHOLD", "5"))
113
+ )
114
+ self._default_enrich_context = (
115
+ default_enrich_context
116
+ if default_enrich_context is not None
117
+ else to_boolean(os.getenv("ZRB_LLM_ENRICH_CONTEXT", "0"))
118
+ )
112
119
  self._default_provider = None
113
120
  self._default_model = None
114
121
 
@@ -127,17 +134,23 @@ class LLMConfig:
127
134
  )
128
135
 
129
136
  def get_default_system_prompt(self) -> str:
130
- system_prompt = (
137
+ return (
131
138
  DEFAULT_SYSTEM_PROMPT
132
139
  if self._default_system_prompt is None
133
140
  else self._default_system_prompt
134
141
  )
135
- persona = (
142
+
143
+ def get_default_persona(self) -> str:
144
+ return (
136
145
  DEFAULT_PERSONA if self._default_persona is None else self._default_persona
137
146
  )
138
- if persona is not None:
139
- return f"{persona}\n{system_prompt}"
140
- return system_prompt
147
+
148
+ def get_default_special_instruction_prompt(self) -> str:
149
+ return (
150
+ DEFAULT_SPECIAL_INSTRUCTION_PROMPT
151
+ if self._default_special_instruction_prompt is None
152
+ else self._default_special_instruction_prompt
153
+ )
141
154
 
142
155
  def get_default_summarization_prompt(self) -> str:
143
156
  return (
@@ -164,12 +177,24 @@ class LLMConfig:
164
177
  provider=self.get_default_model_provider(),
165
178
  )
166
179
 
180
+ def get_default_summarize_history(self) -> bool:
181
+ return self._default_summarize_history
182
+
183
+ def get_default_history_summarization_threshold(self) -> int:
184
+ return self._default_history_summarization_threshold
185
+
186
+ def get_default_enrich_context(self) -> bool:
187
+ return self._default_enrich_context
188
+
167
189
  def set_default_persona(self, persona: str):
168
190
  self._default_persona = persona
169
191
 
170
192
  def set_default_system_prompt(self, system_prompt: str):
171
193
  self._default_system_prompt = system_prompt
172
194
 
195
+ def set_default_special_instruction_prompt(self, special_instruction_prompt: str):
196
+ self._default_special_instruction_prompt = special_instruction_prompt
197
+
173
198
  def set_default_summarization_prompt(self, summarization_prompt: str):
174
199
  self._default_summarization_prompt = summarization_prompt
175
200
 
@@ -191,5 +216,16 @@ class LLMConfig:
191
216
  def set_default_model(self, model: Model | str | None):
192
217
  self._default_model = model
193
218
 
219
+ def set_default_summarize_history(self, summarize_history: bool):
220
+ self._default_summarize_history = summarize_history
221
+
222
+ def set_default_history_summarization_threshold(
223
+ self, history_summarization_threshold: int
224
+ ):
225
+ self._default_history_summarization_threshold = history_summarization_threshold
226
+
227
+ def set_default_enrich_context(self, enrich_context: bool):
228
+ self._default_enrich_context = enrich_context
229
+
194
230
 
195
231
  llm_config = LLMConfig()
zrb/task/any_task.py CHANGED
@@ -12,12 +12,28 @@ if TYPE_CHECKING:
12
12
 
13
13
 
14
14
  class AnyTask(ABC):
15
- """Abstract base class representing a task in the system.
16
-
17
- This class defines the interface for tasks, including methods for retrieving
18
- task metadata (such as name, color, and description), handling inputs and
19
- environment variables, and managing task dependencies (upstreams and fallbacks).
20
- It also includes methods for running tasks synchronously or asynchronously.
15
+ """Abstract base class defining the interface for all executable tasks within Zrb.
16
+
17
+ This interface specifies the essential properties and methods that any concrete
18
+ task implementation must provide. It covers:
19
+
20
+ - **Metadata:** Properties like `name`, `description`, `color`, and `icon` for
21
+ identification and UI representation.
22
+ - **Configuration:** Properties for defining `inputs` (user-provided parameters)
23
+ and `envs` (environment variables).
24
+ - **Execution Control:** Properties like `cli_only` to restrict execution context.
25
+ - **Dependency Management:** Properties for `upstreams` (prerequisites),
26
+ `fallbacks` (error handling), `successors` (post-completion tasks), and
27
+ `readiness_checks` (for long-running tasks). Methods like `append_*` allow
28
+ programmatic modification of these dependencies.
29
+ - **Context Management:** `get_ctx` method to retrieve the task's specific
30
+ execution context.
31
+ - **Execution Methods:** `run` (synchronous) and `async_run` (asynchronous)
32
+ entry points, along with internal execution helpers (`exec_root_tasks`,
33
+ `exec_chain`, `exec`) defining the execution lifecycle.
34
+
35
+ Concrete task classes (like `BaseTask`) inherit from `AnyTask` and provide
36
+ the actual implementation for these abstract members.
21
37
  """
22
38
 
23
39
  @property
File without changes
@@ -0,0 +1,108 @@
1
+ import os
2
+ from typing import TYPE_CHECKING
3
+
4
+ from zrb.context.any_context import AnyContext
5
+ from zrb.context.any_shared_context import AnySharedContext
6
+ from zrb.env.any_env import AnyEnv
7
+ from zrb.input.any_input import AnyInput
8
+ from zrb.session.any_session import AnySession
9
+ from zrb.task.any_task import AnyTask
10
+
11
+ if TYPE_CHECKING:
12
+ # Keep BaseTask under TYPE_CHECKING for functions that need it
13
+ from zrb.task.base_task import BaseTask
14
+
15
+
16
+ def build_task_context(task: AnyTask, session: AnySession) -> AnyContext:
17
+ """
18
+ Retrieves the context for the task from the session and enhances it
19
+ with the task's specific environment variables.
20
+ """
21
+ ctx = session.get_ctx(task)
22
+ # Enhance session ctx with current task env
23
+ for env in task.envs:
24
+ env.update_context(ctx)
25
+ return ctx
26
+
27
+
28
+ def fill_shared_context_inputs(
29
+ task: AnyTask, shared_context: AnySharedContext, str_kwargs: dict[str, str] = {}
30
+ ):
31
+ """
32
+ Populates the shared context with input values provided via kwargs.
33
+ """
34
+ for task_input in task.inputs:
35
+ if task_input.name not in shared_context.input:
36
+ str_value = str_kwargs.get(task_input.name, None)
37
+ task_input.update_shared_context(shared_context, str_value)
38
+
39
+
40
+ def fill_shared_context_envs(shared_context: AnySharedContext):
41
+ """
42
+ Injects OS environment variables into the shared context if they don't already exist.
43
+ """
44
+ os_env_map = {
45
+ key: val for key, val in os.environ.items() if key not in shared_context.env
46
+ }
47
+ shared_context.env.update(os_env_map)
48
+
49
+
50
+ def combine_inputs(
51
+ existing_inputs: list[AnyInput],
52
+ new_inputs: list[AnyInput | None] | AnyInput | None,
53
+ ):
54
+ """
55
+ Combines new inputs into an existing list, avoiding duplicates by name.
56
+ Modifies the existing_inputs list in place.
57
+ """
58
+ input_names = [task_input.name for task_input in existing_inputs]
59
+ if isinstance(new_inputs, AnyInput):
60
+ new_inputs_list = [new_inputs]
61
+ elif new_inputs is None:
62
+ new_inputs_list = []
63
+ else:
64
+ new_inputs_list = new_inputs
65
+
66
+ for task_input in new_inputs_list:
67
+ if task_input is None:
68
+ continue
69
+ if task_input.name not in input_names:
70
+ existing_inputs.append(task_input)
71
+ input_names.append(task_input.name) # Update names list
72
+
73
+
74
+ def get_combined_envs(task: "BaseTask") -> list[AnyEnv]:
75
+ """
76
+ Aggregates environment variables from the task and its upstreams.
77
+ """
78
+ envs = []
79
+ for upstream in task.upstreams:
80
+ envs.extend(upstream.envs) # Use extend for list concatenation
81
+
82
+ # Access _envs directly as task is BaseTask
83
+ task_envs: list[AnyEnv | None] | AnyEnv | None = task._envs
84
+ if isinstance(task_envs, AnyEnv):
85
+ envs.append(task_envs)
86
+ elif isinstance(task_envs, list):
87
+ # Filter out None while extending
88
+ envs.extend(env for env in task_envs if env is not None)
89
+
90
+ # Filter out None values efficiently from the combined list
91
+ return [env for env in envs if env is not None]
92
+
93
+
94
+ def get_combined_inputs(task: "BaseTask") -> list[AnyInput]:
95
+ """
96
+ Aggregates inputs from the task and its upstreams, avoiding duplicates.
97
+ """
98
+ inputs: list[AnyInput] = []
99
+ for upstream in task.upstreams:
100
+ combine_inputs(inputs, upstream.inputs)
101
+
102
+ # Access _inputs directly as task is BaseTask
103
+ task_inputs: list[AnyInput | None] | AnyInput | None = task._inputs
104
+ if task_inputs is not None:
105
+ combine_inputs(inputs, task_inputs)
106
+
107
+ # Filter out None values (although combine_inputs should handle this)
108
+ return [task_input for task_input in inputs if task_input is not None]
@@ -0,0 +1,57 @@
1
+ from typing import TypeVar
2
+
3
+ from zrb.task.any_task import AnyTask
4
+
5
+ T = TypeVar("T", bound="AnyTask")
6
+
7
+
8
+ def get_dependency_list(task: AnyTask, attr_name: str) -> list[AnyTask]:
9
+ """
10
+ Safely retrieves a list of dependencies (upstreams, fallbacks, etc.)
11
+ from a task attribute, handling None or single-task values.
12
+ """
13
+ dependencies = getattr(task, attr_name, None)
14
+ if dependencies is None:
15
+ return []
16
+ elif isinstance(dependencies, list):
17
+ # Ensure all elements are AnyTask (or compatible) if needed,
18
+ # but for now, assume the list contains tasks.
19
+ return dependencies
20
+ # Assume it's a single AnyTask instance
21
+ return [dependencies]
22
+
23
+
24
+ def append_dependency(
25
+ task: T, attr_name: str, dependencies_to_add: AnyTask | list[AnyTask]
26
+ ) -> None:
27
+ """
28
+ Appends one or more dependencies to the specified attribute list of a task,
29
+ ensuring the attribute becomes a list and avoiding duplicates.
30
+
31
+ Modifies the attribute on the task object directly.
32
+ """
33
+ # Retrieve the current list, handling None or single item cases
34
+ current_deps_list = getattr(task, attr_name, None)
35
+ if current_deps_list is None:
36
+ current_deps_list = []
37
+ elif not isinstance(current_deps_list, list):
38
+ current_deps_list = [current_deps_list]
39
+ else:
40
+ # Create a copy to avoid modifying the original list if no changes occur
41
+ current_deps_list = list(current_deps_list)
42
+
43
+ if isinstance(dependencies_to_add, list):
44
+ new_deps = dependencies_to_add
45
+ else:
46
+ new_deps = [dependencies_to_add] # Ensure it's always a list
47
+
48
+ added = False
49
+ for dep in new_deps:
50
+ # Check for existence before appending
51
+ if dep not in current_deps_list:
52
+ current_deps_list.append(dep)
53
+ added = True
54
+
55
+ # Update the attribute on the task object only if changes were made
56
+ if added:
57
+ setattr(task, attr_name, current_deps_list)