zrb 1.5.17__py3-none-any.whl → 1.6.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.
Files changed (50) hide show
  1. zrb/__init__.py +2 -2
  2. zrb/__main__.py +12 -12
  3. zrb/builtin/__init__.py +2 -2
  4. zrb/builtin/llm/chat_session.py +202 -0
  5. zrb/builtin/llm/history.py +6 -6
  6. zrb/builtin/llm/llm_ask.py +142 -0
  7. zrb/builtin/llm/tool/rag.py +39 -23
  8. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/script.js +1 -1
  9. zrb/builtin/todo.py +21 -19
  10. zrb/callback/any_callback.py +1 -1
  11. zrb/callback/callback.py +69 -7
  12. zrb/config.py +261 -91
  13. zrb/context/shared_context.py +4 -2
  14. zrb/input/text_input.py +9 -6
  15. zrb/llm_config.py +73 -79
  16. zrb/runner/cli.py +13 -4
  17. zrb/runner/web_app.py +3 -3
  18. zrb/runner/web_config/config_factory.py +11 -22
  19. zrb/runner/web_route/error_page/show_error_page.py +16 -6
  20. zrb/runner/web_route/home_page/home_page_route.py +23 -7
  21. zrb/runner/web_route/home_page/view.html +19 -33
  22. zrb/runner/web_route/login_page/login_page_route.py +14 -4
  23. zrb/runner/web_route/login_page/view.html +33 -51
  24. zrb/runner/web_route/logout_page/logout_page_route.py +15 -5
  25. zrb/runner/web_route/logout_page/view.html +23 -41
  26. zrb/runner/web_route/node_page/group/show_group_page.py +26 -10
  27. zrb/runner/web_route/node_page/group/view.html +22 -37
  28. zrb/runner/web_route/node_page/task/show_task_page.py +34 -19
  29. zrb/runner/web_route/node_page/task/view.html +74 -88
  30. zrb/runner/web_route/static/global_template.html +27 -0
  31. zrb/runner/web_route/static/resources/common.css +21 -0
  32. zrb/runner/web_route/static/resources/common.js +28 -0
  33. zrb/runner/web_route/task_session_api_route.py +3 -1
  34. zrb/session_state_logger/session_state_logger_factory.py +2 -2
  35. zrb/task/base_task.py +4 -1
  36. zrb/task/base_trigger.py +47 -2
  37. zrb/task/cmd_task.py +3 -3
  38. zrb/task/llm/agent.py +11 -3
  39. zrb/task/llm/context.py +1 -3
  40. zrb/task/llm/context_enrichment.py +21 -15
  41. zrb/task/llm/history.py +32 -63
  42. zrb/task/llm/history_summarization.py +19 -19
  43. zrb/task/llm/print_node.py +5 -6
  44. zrb/task/llm/prompt.py +0 -27
  45. zrb/task/llm_task.py +22 -18
  46. {zrb-1.5.17.dist-info → zrb-1.6.1.dist-info}/METADATA +1 -1
  47. {zrb-1.5.17.dist-info → zrb-1.6.1.dist-info}/RECORD +49 -46
  48. zrb/builtin/llm/llm_chat.py +0 -124
  49. {zrb-1.5.17.dist-info → zrb-1.6.1.dist-info}/WHEEL +0 -0
  50. {zrb-1.5.17.dist-info → zrb-1.6.1.dist-info}/entry_points.txt +0 -0
zrb/task/llm/agent.py CHANGED
@@ -2,14 +2,12 @@ from collections.abc import Callable
2
2
  from typing import TYPE_CHECKING, Any
3
3
 
4
4
  if TYPE_CHECKING:
5
- from openai import APIError
6
5
  from pydantic_ai import Agent, Tool
7
6
  from pydantic_ai.agent import AgentRun
8
7
  from pydantic_ai.mcp import MCPServer
9
8
  from pydantic_ai.models import Model
10
9
  from pydantic_ai.settings import ModelSettings
11
10
  else:
12
- APIError = Any
13
11
  Agent = Any
14
12
  Tool = Any
15
13
  AgentRun = Any
@@ -130,6 +128,7 @@ async def run_agent_iteration(
130
128
  Raises:
131
129
  Exception: If any error occurs during agent execution.
132
130
  """
131
+ from openai import APIError
133
132
  from pydantic_ai.messages import ModelMessagesTypeAdapter
134
133
 
135
134
  async with agent.run_mcp_servers():
@@ -141,7 +140,7 @@ async def run_agent_iteration(
141
140
  # Each node represents a step in the agent's execution
142
141
  # Reference: https://ai.pydantic.dev/agents/#streaming
143
142
  try:
144
- await print_node(ctx.print, agent_run, node)
143
+ await print_node(_get_plain_printer(ctx), agent_run, node)
145
144
  except APIError as e:
146
145
  # Extract detailed error information from the response
147
146
  error_details = extract_api_error_details(e)
@@ -152,3 +151,12 @@ async def run_agent_iteration(
152
151
  ctx.log_error(f"Error type: {type(e).__name__}")
153
152
  raise
154
153
  return agent_run
154
+
155
+
156
+ def _get_plain_printer(ctx: AnyContext):
157
+ def printer(*args, **kwargs):
158
+ if "plain" not in kwargs:
159
+ kwargs["plain"] = True
160
+ return ctx.print(*args, **kwargs)
161
+
162
+ return printer
zrb/task/llm/context.py CHANGED
@@ -73,9 +73,7 @@ def get_conversation_context(
73
73
  Retrieves the conversation context.
74
74
  If a value in the context dict is callable, it executes it with ctx.
75
75
  """
76
- raw_context = get_attr(
77
- ctx, conversation_context_attr, {}, auto_render=False
78
- ) # Context usually shouldn't be rendered
76
+ raw_context = get_attr(ctx, conversation_context_attr, {}, auto_render=False)
79
77
  if not isinstance(raw_context, dict):
80
78
  ctx.log_warning(
81
79
  f"Conversation context resolved to type {type(raw_context)}, "
@@ -1,6 +1,5 @@
1
1
  import json
2
2
  import traceback
3
- from textwrap import dedent
4
3
  from typing import TYPE_CHECKING, Any
5
4
 
6
5
  from pydantic import BaseModel
@@ -9,8 +8,13 @@ from zrb.attr.type import BoolAttr, IntAttr
9
8
  from zrb.context.any_context import AnyContext
10
9
  from zrb.llm_config import llm_config
11
10
  from zrb.task.llm.agent import run_agent_iteration
11
+ from zrb.task.llm.history import (
12
+ count_part_in_history_list,
13
+ replace_system_prompt_in_history_list,
14
+ )
12
15
  from zrb.task.llm.typing import ListOfDict
13
16
  from zrb.util.attr import get_bool_attr, get_int_attr
17
+ from zrb.util.cli.style import stylize_faint
14
18
 
15
19
  if TYPE_CHECKING:
16
20
  from pydantic_ai.models import Model
@@ -47,15 +51,13 @@ async def enrich_context(
47
51
  context_json = json.dumps(conversation_context)
48
52
  history_json = json.dumps(history_list)
49
53
  # The user prompt will now contain the dynamic data
50
- user_prompt_data = dedent(
51
- f"""
52
- Analyze the following
53
- # Current Context
54
- {context_json}
55
- # Conversation History
56
- {history_json}
57
- """
58
- ).strip()
54
+ user_prompt_data = "\n".join(
55
+ [
56
+ "Extract context from the following conversation info",
57
+ f"Existing Context: {context_json}",
58
+ f"Conversation History: {history_json}",
59
+ ]
60
+ )
59
61
  except Exception as e:
60
62
  ctx.log_warning(f"Error formatting context/history for enrichment: {e}")
61
63
  return conversation_context # Return original context if formatting fails
@@ -72,6 +74,7 @@ async def enrich_context(
72
74
  )
73
75
 
74
76
  try:
77
+ ctx.print(stylize_faint("[Context Enrichment Triggered]"), plain=True)
75
78
  enrichment_run = await run_agent_iteration(
76
79
  ctx=ctx,
77
80
  agent=enrichment_agent,
@@ -80,6 +83,8 @@ async def enrich_context(
80
83
  )
81
84
  if enrichment_run and enrichment_run.result.output:
82
85
  response = enrichment_run.result.output.response
86
+ usage = enrichment_run.result.usage()
87
+ ctx.print(stylize_faint(f"[Token Usage] {usage}"), plain=True)
83
88
  if response:
84
89
  conversation_context.update(response)
85
90
  ctx.log_info("Context enriched based on history.")
@@ -127,15 +132,15 @@ def should_enrich_context(
127
132
  """
128
133
  Determines if context enrichment should occur based on history, threshold, and config.
129
134
  """
130
- history_len = len(history_list)
131
- if history_len == 0:
135
+ history_part_count = count_part_in_history_list(history_list)
136
+ if history_part_count == 0:
132
137
  return False
133
138
  enrichment_threshold = get_context_enrichment_threshold(
134
139
  ctx,
135
140
  context_enrichment_threshold_attr,
136
141
  render_context_enrichment_threshold,
137
142
  )
138
- if enrichment_threshold == -1 or enrichment_threshold > history_len:
143
+ if enrichment_threshold == -1 or enrichment_threshold > history_part_count:
139
144
  return False
140
145
  return get_bool_attr(
141
146
  ctx,
@@ -158,9 +163,10 @@ async def maybe_enrich_context(
158
163
  context_enrichment_prompt: str,
159
164
  ) -> dict[str, Any]:
160
165
  """Enriches context based on history if enabled and threshold met."""
166
+ shorten_history_list = replace_system_prompt_in_history_list(history_list)
161
167
  if should_enrich_context(
162
168
  ctx,
163
- history_list,
169
+ shorten_history_list,
164
170
  should_enrich_context_attr,
165
171
  render_enrich_context,
166
172
  context_enrichment_threshold_attr,
@@ -174,6 +180,6 @@ async def maybe_enrich_context(
174
180
  prompt=context_enrichment_prompt,
175
181
  ),
176
182
  conversation_context=conversation_context,
177
- history_list=history_list,
183
+ history_list=shorten_history_list,
178
184
  )
179
185
  return conversation_context
zrb/task/llm/history.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import os
3
3
  from collections.abc import Callable
4
+ from copy import deepcopy
4
5
  from typing import Any, Optional
5
6
 
6
7
  from pydantic import BaseModel
@@ -177,66 +178,34 @@ async def write_conversation_history(
177
178
  write_file(history_file, history_data.model_dump_json(indent=2))
178
179
 
179
180
 
180
- async def prepare_initial_state(
181
- ctx: AnyContext,
182
- conversation_history_reader: (
183
- Callable[[AnySharedContext], ConversationHistoryData | dict | list | None]
184
- | None
185
- ),
186
- conversation_history_file_attr: StrAttr | None,
187
- render_history_file: bool,
188
- conversation_history_attr: (
189
- ConversationHistoryData
190
- | Callable[[AnySharedContext], ConversationHistoryData | dict | list]
191
- | dict
192
- | list
193
- ),
194
- conversation_context_getter: Callable[[AnyContext], dict[str, Any]],
195
- ) -> tuple[ListOfDict, dict[str, Any]]:
196
- """Reads history and prepares the initial conversation context."""
197
- history_data: ConversationHistoryData = await read_conversation_history(
198
- ctx,
199
- conversation_history_reader,
200
- conversation_history_file_attr,
201
- render_history_file,
202
- conversation_history_attr,
203
- )
204
- # Clean the history list to remove context from historical user prompts
205
- cleaned_history_list = []
206
- for interaction in history_data.history:
207
- cleaned_history_list.append(
208
- remove_context_from_interaction_history(interaction)
209
- )
210
- conversation_context = conversation_context_getter(ctx)
211
- # Merge history context from loaded data without overwriting existing keys
212
- for key, value in history_data.context.items():
213
- if key not in conversation_context:
214
- conversation_context[key] = value
215
- # Return the CLEANED history list
216
- return cleaned_history_list, conversation_context
217
-
218
-
219
- def remove_context_from_interaction_history(
220
- interaction: dict[str, Any],
221
- ) -> dict[str, Any]:
222
- try:
223
- cleaned_interaction = json.loads(json.dumps(interaction))
224
- except Exception:
225
- # Fallback to shallow copy if not JSON serializable (less safe)
226
- cleaned_interaction = interaction.copy()
227
- if "parts" in cleaned_interaction and isinstance(
228
- cleaned_interaction["parts"], list
229
- ):
230
- for part in cleaned_interaction["parts"]:
231
- is_user_prompt = part.get("part_kind") == "user-prompt"
232
- has_str_content = isinstance(part.get("content"), str)
233
- if is_user_prompt and has_str_content:
234
- content = part["content"]
235
- user_message_marker = "# User Message\n"
236
- marker_index = content.find(user_message_marker)
237
- if marker_index != -1:
238
- # Extract message after the marker and strip whitespace
239
- start_index = marker_index + len(user_message_marker)
240
- part["content"] = content[start_index:].strip()
241
- # else: If marker not found, leave content as is (old format/error)
242
- return cleaned_interaction
181
+ def replace_system_prompt_in_history_list(
182
+ history_list: ListOfDict, replacement: str = "<main LLM system prompt>"
183
+ ) -> ListOfDict:
184
+ """
185
+ Returns a new history list where any part with part_kind 'system-prompt'
186
+ has its 'content' replaced with the given replacement string.
187
+ Args:
188
+ history: List of history items (each item is a dict with a 'parts' list).
189
+ replacement: The string to use in place of system-prompt content.
190
+
191
+ Returns:
192
+ A deep-copied list of history items with system-prompt content replaced.
193
+ """
194
+ new_history = deepcopy(history_list)
195
+ for item in new_history:
196
+ parts = item.get("parts", [])
197
+ for part in parts:
198
+ if part.get("part_kind") == "system-prompt":
199
+ part["content"] = replacement
200
+ return new_history
201
+
202
+
203
+ def count_part_in_history_list(history_list: ListOfDict) -> int:
204
+ """Calculates the total number of 'parts' in a history list."""
205
+ history_part_len = 0
206
+ for history in history_list:
207
+ if "parts" in history:
208
+ history_part_len += len(history["parts"])
209
+ else:
210
+ history_part_len += 1
211
+ return history_part_len
@@ -7,8 +7,13 @@ from zrb.attr.type import BoolAttr, IntAttr
7
7
  from zrb.context.any_context import AnyContext
8
8
  from zrb.llm_config import llm_config
9
9
  from zrb.task.llm.agent import run_agent_iteration
10
+ from zrb.task.llm.history import (
11
+ count_part_in_history_list,
12
+ replace_system_prompt_in_history_list,
13
+ )
10
14
  from zrb.task.llm.typing import ListOfDict
11
15
  from zrb.util.attr import get_bool_attr, get_int_attr
16
+ from zrb.util.cli.style import stylize_faint
12
17
 
13
18
  if TYPE_CHECKING:
14
19
  from pydantic_ai.models import Model
@@ -18,17 +23,6 @@ else:
18
23
  ModelSettings = Any
19
24
 
20
25
 
21
- def get_history_part_len(history_list: ListOfDict) -> int:
22
- """Calculates the total number of 'parts' in a history list."""
23
- history_part_len = 0
24
- for history in history_list:
25
- if "parts" in history:
26
- history_part_len += len(history["parts"])
27
- else:
28
- history_part_len += 1
29
- return history_part_len
30
-
31
-
32
26
  def get_history_summarization_threshold(
33
27
  ctx: AnyContext,
34
28
  history_summarization_threshold_attr: IntAttr | None,
@@ -60,15 +54,15 @@ def should_summarize_history(
60
54
  render_history_summarization_threshold: bool,
61
55
  ) -> bool:
62
56
  """Determines if history summarization should occur based on length and config."""
63
- history_part_len = get_history_part_len(history_list)
64
- if history_part_len == 0:
57
+ history_part_count = count_part_in_history_list(history_list)
58
+ if history_part_count == 0:
65
59
  return False
66
60
  summarization_threshold = get_history_summarization_threshold(
67
61
  ctx,
68
62
  history_summarization_threshold_attr,
69
63
  render_history_summarization_threshold,
70
64
  )
71
- if summarization_threshold == -1 or summarization_threshold > history_part_len:
65
+ if summarization_threshold == -1 or summarization_threshold > history_part_count:
72
66
  return False
73
67
  return get_bool_attr(
74
68
  ctx,
@@ -111,15 +105,18 @@ async def summarize_history(
111
105
  try:
112
106
  context_json = json.dumps(conversation_context)
113
107
  history_to_summarize_json = json.dumps(history_list)
114
- summarization_user_prompt = (
115
- f"# Current Context\n{context_json}\n\n"
116
- f"# Conversation History to Summarize\n{history_to_summarize_json}"
108
+ summarization_user_prompt = "\n".join(
109
+ [
110
+ f"Current Context: {context_json}",
111
+ f"Conversation History to Summarize: {history_to_summarize_json}",
112
+ ]
117
113
  )
118
114
  except Exception as e:
119
115
  ctx.log_warning(f"Error formatting context/history for summarization: {e}")
120
116
  return conversation_context # Return original context if formatting fails
121
117
 
122
118
  try:
119
+ ctx.print(stylize_faint("[Summarization Triggered]"), plain=True)
123
120
  summary_run = await run_agent_iteration(
124
121
  ctx=ctx,
125
122
  agent=summarization_agent,
@@ -128,6 +125,8 @@ async def summarize_history(
128
125
  )
129
126
  if summary_run and summary_run.result.output:
130
127
  summary_text = str(summary_run.result.output)
128
+ usage = summary_run.result.usage()
129
+ ctx.print(stylize_faint(f"[Token Usage] {usage}"), plain=True)
131
130
  # Update context with the new summary
132
131
  conversation_context["history_summary"] = summary_text
133
132
  ctx.log_info("History summarized and added/updated in context.")
@@ -153,9 +152,10 @@ async def maybe_summarize_history(
153
152
  summarization_prompt: str,
154
153
  ) -> tuple[ListOfDict, dict[str, Any]]:
155
154
  """Summarizes history and updates context if enabled and threshold met."""
155
+ shorten_history_list = replace_system_prompt_in_history_list(history_list)
156
156
  if should_summarize_history(
157
157
  ctx,
158
- history_list,
158
+ shorten_history_list,
159
159
  should_summarize_history_attr,
160
160
  render_summarize_history,
161
161
  history_summarization_threshold_attr,
@@ -170,7 +170,7 @@ async def maybe_summarize_history(
170
170
  prompt=summarization_prompt,
171
171
  ),
172
172
  conversation_context=conversation_context,
173
- history_list=history_list, # Pass the full list for context
173
+ history_list=shorten_history_list, # Pass the full list for context
174
174
  )
175
175
  # Truncate the history list after summarization
176
176
  return [], updated_context
@@ -30,7 +30,7 @@ async def print_node(print_func: Callable, agent_run: Any, node: Any):
30
30
  async for event in request_stream:
31
31
  if isinstance(event, PartStartEvent):
32
32
  if is_streaming:
33
- print_func("", plain=True)
33
+ print_func("")
34
34
  print_func(
35
35
  stylize_faint(
36
36
  f"[Request] Starting part {event.index}: {event.part!r}"
@@ -42,24 +42,22 @@ async def print_node(print_func: Callable, agent_run: Any, node: Any):
42
42
  print_func(
43
43
  stylize_faint(f"{event.delta.content_delta}"),
44
44
  end="",
45
- plain=is_streaming,
46
45
  )
47
46
  elif isinstance(event.delta, ToolCallPartDelta):
48
47
  print_func(
49
48
  stylize_faint(f"{event.delta.args_delta}"),
50
49
  end="",
51
- plain=is_streaming,
52
50
  )
53
51
  is_streaming = True
54
52
  elif isinstance(event, FinalResultEvent):
55
53
  if is_streaming:
56
- print_func("", plain=True)
54
+ print_func("")
57
55
  print_func(
58
56
  stylize_faint(f"[Result] tool_name={event.tool_name}"),
59
57
  )
60
58
  is_streaming = False
61
59
  if is_streaming:
62
- print_func("", plain=True)
60
+ print_func("")
63
61
  elif Agent.is_call_tools_node(node):
64
62
  # A handle-response node => The model returned some data, potentially calls a tool
65
63
  print_func(
@@ -95,4 +93,5 @@ async def print_node(print_func: Callable, agent_run: Any, node: Any):
95
93
  )
96
94
  elif Agent.is_end_node(node):
97
95
  # Once an End node is reached, the agent run is complete
98
- print_func(stylize_faint(f"{agent_run.result.data}"))
96
+ print_func(stylize_faint("[End of Response]"))
97
+ # print_func(stylize_faint(f"{agent_run.result.data}"))
zrb/task/llm/prompt.py CHANGED
@@ -1,11 +1,6 @@
1
- import json
2
- from textwrap import dedent
3
- from typing import Any
4
-
5
1
  from zrb.attr.type import StrAttr
6
2
  from zrb.context.any_context import AnyContext
7
3
  from zrb.llm_config import llm_config as default_llm_config
8
- from zrb.task.llm.context import extract_default_context
9
4
  from zrb.util.attr import get_attr, get_str_attr
10
5
 
11
6
 
@@ -127,25 +122,3 @@ def get_context_enrichment_prompt(
127
122
  if context_enrichment_prompt is not None:
128
123
  return context_enrichment_prompt
129
124
  return default_llm_config.get_default_context_enrichment_prompt()
130
-
131
-
132
- def build_user_prompt(
133
- ctx: AnyContext,
134
- message_attr: StrAttr | None,
135
- conversation_context: dict[str, Any],
136
- ) -> str:
137
- """Constructs the final user prompt including context."""
138
- original_user_message = get_user_message(ctx, message_attr)
139
- # Combine default context, conversation context (potentially enriched/summarized)
140
- modified_user_message, default_context = extract_default_context(
141
- original_user_message
142
- )
143
- enriched_context = {**default_context, **conversation_context}
144
- return dedent(
145
- f"""
146
- # Context
147
- {json.dumps(enriched_context)}
148
- # User Message
149
- {modified_user_message}
150
- """
151
- ).strip()
zrb/task/llm_task.py CHANGED
@@ -22,26 +22,24 @@ from zrb.input.any_input import AnyInput
22
22
  from zrb.task.any_task import AnyTask
23
23
  from zrb.task.base_task import BaseTask
24
24
  from zrb.task.llm.agent import get_agent, run_agent_iteration
25
-
26
- # No longer need llm_config here
27
25
  from zrb.task.llm.config import (
28
26
  get_model,
29
27
  get_model_settings,
30
28
  )
31
- from zrb.task.llm.context import get_conversation_context
29
+ from zrb.task.llm.context import extract_default_context, get_conversation_context
32
30
  from zrb.task.llm.context_enrichment import maybe_enrich_context
33
31
  from zrb.task.llm.history import (
34
32
  ConversationHistoryData,
35
33
  ListOfDict,
36
- prepare_initial_state,
34
+ read_conversation_history,
37
35
  write_conversation_history,
38
36
  )
39
37
  from zrb.task.llm.history_summarization import maybe_summarize_history
40
38
  from zrb.task.llm.prompt import (
41
- build_user_prompt,
42
39
  get_combined_system_prompt,
43
40
  get_context_enrichment_prompt,
44
41
  get_summarization_prompt,
42
+ get_user_message,
45
43
  )
46
44
  from zrb.util.cli.style import stylize_faint
47
45
  from zrb.xcom.xcom import Xcom
@@ -240,6 +238,7 @@ class LLMTask(BaseTask):
240
238
  summarization_prompt_attr=self._summarization_prompt,
241
239
  render_summarization_prompt=self._render_summarization_prompt,
242
240
  )
241
+ user_message = get_user_message(ctx, self._message)
243
242
  # Get the combined system prompt using the new getter
244
243
  system_prompt = get_combined_system_prompt(
245
244
  ctx=ctx,
@@ -250,17 +249,19 @@ class LLMTask(BaseTask):
250
249
  special_instruction_prompt_attr=self._special_instruction_prompt,
251
250
  render_special_instruction_prompt=self._render_special_instruction_prompt,
252
251
  )
253
- # 1. Prepare initial state (read history, get initial context)
254
- history_list, conversation_context = await prepare_initial_state(
252
+ # 1. Prepare initial state (read history from previous session)
253
+ conversation_history = await read_conversation_history(
255
254
  ctx=ctx,
256
255
  conversation_history_reader=self._conversation_history_reader,
257
256
  conversation_history_file_attr=self._conversation_history_file,
258
257
  render_history_file=self._render_history_file,
259
258
  conversation_history_attr=self._conversation_history,
260
- conversation_context_getter=lambda c: get_conversation_context(
261
- c, self._conversation_context
262
- ),
263
259
  )
260
+ history_list = conversation_history.history
261
+ conversation_context = {
262
+ **conversation_history.context,
263
+ **get_conversation_context(ctx, self._conversation_context),
264
+ }
264
265
  # 2. Enrich context (optional)
265
266
  conversation_context = await maybe_enrich_context(
266
267
  ctx=ctx,
@@ -289,18 +290,21 @@ class LLMTask(BaseTask):
289
290
  model_settings=model_settings,
290
291
  summarization_prompt=summarization_prompt,
291
292
  )
292
- # 4. Build the final user prompt
293
- user_prompt = build_user_prompt(
294
- ctx=ctx,
295
- message_attr=self._message,
296
- conversation_context=conversation_context,
293
+ # 4. Build the final user prompt and system prompt
294
+ final_user_prompt, default_context = extract_default_context(user_message)
295
+ final_system_prompt = "\n".join(
296
+ [
297
+ system_prompt,
298
+ "# Context",
299
+ json.dumps({**default_context, **conversation_context}),
300
+ ]
297
301
  )
298
302
  # 5. Get the agent instance
299
303
  agent = get_agent(
300
304
  ctx=ctx,
301
305
  agent_attr=self._agent,
302
306
  model=model,
303
- system_prompt=system_prompt,
307
+ system_prompt=final_system_prompt,
304
308
  model_settings=model_settings,
305
309
  tools_attr=self._tools,
306
310
  additional_tools=self._additional_tools,
@@ -309,7 +313,7 @@ class LLMTask(BaseTask):
309
313
  )
310
314
  # 6. Run the agent iteration and save the results/history
311
315
  return await self._run_agent_and_save_history(
312
- ctx, agent, user_prompt, history_list, conversation_context
316
+ ctx, agent, final_user_prompt, history_list, conversation_context
313
317
  )
314
318
 
315
319
  async def _run_agent_and_save_history(
@@ -346,7 +350,7 @@ class LLMTask(BaseTask):
346
350
  ctx.xcom[xcom_usage_key] = Xcom([])
347
351
  usage = agent_run.result.usage()
348
352
  ctx.xcom.get(xcom_usage_key).push(usage)
349
- ctx.print(stylize_faint(f"[USAGE] {usage}"))
353
+ ctx.print(stylize_faint(f"[Token Usage] {usage}"), plain=True)
350
354
  return agent_run.result.output
351
355
  else:
352
356
  ctx.log_warning("Agent run did not produce a result.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zrb
3
- Version: 1.5.17
3
+ Version: 1.6.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