zrb 1.21.6__py3-none-any.whl → 1.21.28__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.
Potentially problematic release.
This version of zrb might be problematic. Click here for more details.
- zrb/attr/type.py +10 -7
- zrb/builtin/git.py +12 -1
- zrb/builtin/llm/chat_completion.py +274 -0
- zrb/builtin/llm/chat_session_cmd.py +90 -28
- zrb/builtin/llm/chat_trigger.py +7 -1
- zrb/builtin/llm/history.py +4 -4
- zrb/builtin/llm/tool/api.py +3 -1
- zrb/builtin/llm/tool/cli.py +2 -1
- zrb/builtin/llm/tool/code.py +11 -3
- zrb/builtin/llm/tool/file.py +112 -142
- zrb/builtin/llm/tool/note.py +36 -16
- zrb/builtin/llm/tool/rag.py +17 -8
- zrb/builtin/llm/tool/sub_agent.py +41 -15
- zrb/config/config.py +108 -13
- zrb/config/default_prompt/file_extractor_system_prompt.md +16 -16
- zrb/config/default_prompt/interactive_system_prompt.md +11 -11
- zrb/config/default_prompt/repo_extractor_system_prompt.md +16 -16
- zrb/config/default_prompt/repo_summarizer_system_prompt.md +3 -3
- zrb/config/default_prompt/summarization_prompt.md +54 -8
- zrb/config/default_prompt/system_prompt.md +15 -15
- zrb/config/llm_rate_limitter.py +24 -5
- zrb/input/option_input.py +13 -1
- zrb/task/llm/agent.py +42 -144
- zrb/task/llm/agent_runner.py +152 -0
- zrb/task/llm/config.py +8 -7
- zrb/task/llm/conversation_history.py +35 -24
- zrb/task/llm/conversation_history_model.py +4 -11
- zrb/task/llm/default_workflow/coding/workflow.md +2 -3
- zrb/task/llm/file_replacement.py +206 -0
- zrb/task/llm/file_tool_model.py +57 -0
- zrb/task/llm/history_processor.py +206 -0
- zrb/task/llm/history_summarization.py +2 -179
- zrb/task/llm/print_node.py +14 -5
- zrb/task/llm/prompt.py +8 -19
- zrb/task/llm/subagent_conversation_history.py +41 -0
- zrb/task/llm/tool_wrapper.py +27 -12
- zrb/task/llm_task.py +55 -47
- zrb/util/attr.py +17 -10
- zrb/util/cli/text.py +6 -4
- zrb/util/git.py +2 -2
- zrb/util/yaml.py +1 -0
- zrb/xcom/xcom.py +10 -0
- {zrb-1.21.6.dist-info → zrb-1.21.28.dist-info}/METADATA +5 -5
- {zrb-1.21.6.dist-info → zrb-1.21.28.dist-info}/RECORD +46 -41
- zrb/task/llm/history_summarization_tool.py +0 -24
- {zrb-1.21.6.dist-info → zrb-1.21.28.dist-info}/WHEEL +0 -0
- {zrb-1.21.6.dist-info → zrb-1.21.28.dist-info}/entry_points.txt +0 -0
zrb/task/llm/agent.py
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
import json
|
|
3
2
|
from collections.abc import Callable
|
|
4
3
|
from dataclasses import dataclass
|
|
5
4
|
from typing import TYPE_CHECKING, Any
|
|
6
5
|
|
|
7
|
-
from zrb.config.llm_rate_limitter import
|
|
6
|
+
from zrb.config.llm_rate_limitter import LLMRateLimitter
|
|
8
7
|
from zrb.context.any_context import AnyContext
|
|
9
|
-
from zrb.
|
|
10
|
-
from zrb.task.llm.error import extract_api_error_details
|
|
11
|
-
from zrb.task.llm.print_node import print_node
|
|
8
|
+
from zrb.task.llm.history_processor import create_summarize_history_processor
|
|
12
9
|
from zrb.task.llm.tool_wrapper import wrap_func, wrap_tool
|
|
13
|
-
from zrb.task.llm.typing import ListOfDict
|
|
14
|
-
from zrb.util.cli.style import stylize_faint
|
|
15
10
|
|
|
16
11
|
if TYPE_CHECKING:
|
|
17
12
|
from pydantic_ai import Agent, Tool
|
|
18
|
-
from pydantic_ai.
|
|
19
|
-
from pydantic_ai.messages import UserContent
|
|
13
|
+
from pydantic_ai._agent_graph import HistoryProcessor
|
|
20
14
|
from pydantic_ai.models import Model
|
|
21
15
|
from pydantic_ai.output import OutputDataT, OutputSpec
|
|
22
16
|
from pydantic_ai.settings import ModelSettings
|
|
@@ -28,13 +22,21 @@ if TYPE_CHECKING:
|
|
|
28
22
|
def create_agent_instance(
|
|
29
23
|
ctx: AnyContext,
|
|
30
24
|
model: "str | Model",
|
|
25
|
+
rate_limitter: LLMRateLimitter | None = None,
|
|
31
26
|
output_type: "OutputSpec[OutputDataT]" = str,
|
|
32
27
|
system_prompt: str = "",
|
|
33
28
|
model_settings: "ModelSettings | None" = None,
|
|
34
|
-
tools:
|
|
29
|
+
tools: list["ToolOrCallable"] = [],
|
|
35
30
|
toolsets: list["AbstractToolset[None]"] = [],
|
|
36
31
|
retries: int = 3,
|
|
37
32
|
yolo_mode: bool | list[str] | None = None,
|
|
33
|
+
summarization_model: "Model | str | None" = None,
|
|
34
|
+
summarization_model_settings: "ModelSettings | None" = None,
|
|
35
|
+
summarization_system_prompt: str | None = None,
|
|
36
|
+
summarization_retries: int = 2,
|
|
37
|
+
summarization_token_threshold: int | None = None,
|
|
38
|
+
history_processors: list["HistoryProcessor"] | None = None,
|
|
39
|
+
auto_summarize: bool = True,
|
|
38
40
|
) -> "Agent[None, Any]":
|
|
39
41
|
"""Creates a new Agent instance with configured tools and servers."""
|
|
40
42
|
from pydantic_ai import Agent, RunContext, Tool
|
|
@@ -102,6 +104,21 @@ def create_agent_instance(
|
|
|
102
104
|
ConfirmationWrapperToolset(wrapped=toolset, ctx=ctx, yolo_mode=yolo_mode)
|
|
103
105
|
for toolset in toolsets
|
|
104
106
|
]
|
|
107
|
+
# Create History processor with summarizer
|
|
108
|
+
history_processors = [] if history_processors is None else history_processors
|
|
109
|
+
if auto_summarize:
|
|
110
|
+
history_processors += [
|
|
111
|
+
create_summarize_history_processor(
|
|
112
|
+
ctx=ctx,
|
|
113
|
+
system_prompt=system_prompt,
|
|
114
|
+
rate_limitter=rate_limitter,
|
|
115
|
+
summarization_model=summarization_model,
|
|
116
|
+
summarization_model_settings=summarization_model_settings,
|
|
117
|
+
summarization_system_prompt=summarization_system_prompt,
|
|
118
|
+
summarization_token_threshold=summarization_token_threshold,
|
|
119
|
+
summarization_retries=summarization_retries,
|
|
120
|
+
)
|
|
121
|
+
]
|
|
105
122
|
# Return Agent
|
|
106
123
|
return Agent[None, Any](
|
|
107
124
|
model=model,
|
|
@@ -111,12 +128,14 @@ def create_agent_instance(
|
|
|
111
128
|
toolsets=wrapped_toolsets,
|
|
112
129
|
model_settings=model_settings,
|
|
113
130
|
retries=retries,
|
|
131
|
+
history_processors=history_processors,
|
|
114
132
|
)
|
|
115
133
|
|
|
116
134
|
|
|
117
135
|
def get_agent(
|
|
118
136
|
ctx: AnyContext,
|
|
119
137
|
model: "str | Model",
|
|
138
|
+
rate_limitter: LLMRateLimitter | None = None,
|
|
120
139
|
output_type: "OutputSpec[OutputDataT]" = str,
|
|
121
140
|
system_prompt: str = "",
|
|
122
141
|
model_settings: "ModelSettings | None" = None,
|
|
@@ -128,6 +147,12 @@ def get_agent(
|
|
|
128
147
|
additional_toolsets: "list[AbstractToolset[None] | str]" = [],
|
|
129
148
|
retries: int = 3,
|
|
130
149
|
yolo_mode: bool | list[str] | None = None,
|
|
150
|
+
summarization_model: "Model | str | None" = None,
|
|
151
|
+
summarization_model_settings: "ModelSettings | None" = None,
|
|
152
|
+
summarization_system_prompt: str | None = None,
|
|
153
|
+
summarization_retries: int = 2,
|
|
154
|
+
summarization_token_threshold: int | None = None,
|
|
155
|
+
history_processors: list["HistoryProcessor"] | None = None,
|
|
131
156
|
) -> "Agent":
|
|
132
157
|
"""Retrieves the configured Agent instance or creates one if necessary."""
|
|
133
158
|
# Get tools for agent
|
|
@@ -143,6 +168,7 @@ def get_agent(
|
|
|
143
168
|
return create_agent_instance(
|
|
144
169
|
ctx=ctx,
|
|
145
170
|
model=model,
|
|
171
|
+
rate_limitter=rate_limitter,
|
|
146
172
|
output_type=output_type,
|
|
147
173
|
system_prompt=system_prompt,
|
|
148
174
|
tools=tools,
|
|
@@ -150,6 +176,12 @@ def get_agent(
|
|
|
150
176
|
model_settings=model_settings,
|
|
151
177
|
retries=retries,
|
|
152
178
|
yolo_mode=yolo_mode,
|
|
179
|
+
summarization_model=summarization_model,
|
|
180
|
+
summarization_model_settings=summarization_model_settings,
|
|
181
|
+
summarization_system_prompt=summarization_system_prompt,
|
|
182
|
+
summarization_retries=summarization_retries,
|
|
183
|
+
summarization_token_threshold=summarization_token_threshold,
|
|
184
|
+
history_processors=history_processors,
|
|
153
185
|
)
|
|
154
186
|
|
|
155
187
|
|
|
@@ -170,137 +202,3 @@ def _render_toolset_or_str_list(
|
|
|
170
202
|
continue
|
|
171
203
|
toolsets.append(toolset_or_str)
|
|
172
204
|
return toolsets
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
async def run_agent_iteration(
|
|
176
|
-
ctx: AnyContext,
|
|
177
|
-
agent: "Agent[None, Any]",
|
|
178
|
-
user_prompt: str,
|
|
179
|
-
attachments: "list[UserContent] | None" = None,
|
|
180
|
-
history_list: ListOfDict | None = None,
|
|
181
|
-
rate_limitter: LLMRateLimiter | None = None,
|
|
182
|
-
max_retry: int = 2,
|
|
183
|
-
log_indent_level: int = 0,
|
|
184
|
-
) -> "AgentRun":
|
|
185
|
-
"""
|
|
186
|
-
Runs a single iteration of the agent execution loop.
|
|
187
|
-
|
|
188
|
-
Args:
|
|
189
|
-
ctx: The task context.
|
|
190
|
-
agent: The Pydantic AI agent instance.
|
|
191
|
-
user_prompt: The user's input prompt.
|
|
192
|
-
history_list: The current conversation history.
|
|
193
|
-
|
|
194
|
-
Returns:
|
|
195
|
-
The agent run result object.
|
|
196
|
-
|
|
197
|
-
Raises:
|
|
198
|
-
Exception: If any error occurs during agent execution.
|
|
199
|
-
"""
|
|
200
|
-
if max_retry < 0:
|
|
201
|
-
raise ValueError("Max retry cannot be less than 0")
|
|
202
|
-
attempt = 0
|
|
203
|
-
while attempt < max_retry:
|
|
204
|
-
try:
|
|
205
|
-
return await _run_single_agent_iteration(
|
|
206
|
-
ctx=ctx,
|
|
207
|
-
agent=agent,
|
|
208
|
-
user_prompt=user_prompt,
|
|
209
|
-
attachments=[] if attachments is None else attachments,
|
|
210
|
-
history_list=[] if history_list is None else history_list,
|
|
211
|
-
rate_limitter=(
|
|
212
|
-
llm_rate_limitter if rate_limitter is None else rate_limitter
|
|
213
|
-
),
|
|
214
|
-
log_indent_level=log_indent_level,
|
|
215
|
-
)
|
|
216
|
-
except BaseException:
|
|
217
|
-
attempt += 1
|
|
218
|
-
if attempt == max_retry:
|
|
219
|
-
raise
|
|
220
|
-
raise Exception("Max retry exceeded")
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
async def _run_single_agent_iteration(
|
|
224
|
-
ctx: AnyContext,
|
|
225
|
-
agent: "Agent",
|
|
226
|
-
user_prompt: str,
|
|
227
|
-
attachments: "list[UserContent]",
|
|
228
|
-
history_list: ListOfDict,
|
|
229
|
-
rate_limitter: LLMRateLimiter,
|
|
230
|
-
log_indent_level: int,
|
|
231
|
-
) -> "AgentRun":
|
|
232
|
-
from openai import APIError
|
|
233
|
-
from pydantic_ai.messages import ModelMessagesTypeAdapter
|
|
234
|
-
|
|
235
|
-
agent_payload = _estimate_request_payload(
|
|
236
|
-
agent, user_prompt, attachments, history_list
|
|
237
|
-
)
|
|
238
|
-
callback = _create_print_throttle_notif(ctx)
|
|
239
|
-
if rate_limitter:
|
|
240
|
-
await rate_limitter.throttle(agent_payload, callback)
|
|
241
|
-
else:
|
|
242
|
-
await llm_rate_limitter.throttle(agent_payload, callback)
|
|
243
|
-
|
|
244
|
-
user_prompt_with_attachments = [user_prompt] + attachments
|
|
245
|
-
async with agent:
|
|
246
|
-
async with agent.iter(
|
|
247
|
-
user_prompt=user_prompt_with_attachments,
|
|
248
|
-
message_history=ModelMessagesTypeAdapter.validate_python(history_list),
|
|
249
|
-
) as agent_run:
|
|
250
|
-
async for node in agent_run:
|
|
251
|
-
# Each node represents a step in the agent's execution
|
|
252
|
-
try:
|
|
253
|
-
await print_node(
|
|
254
|
-
_get_plain_printer(ctx), agent_run, node, log_indent_level
|
|
255
|
-
)
|
|
256
|
-
except APIError as e:
|
|
257
|
-
# Extract detailed error information from the response
|
|
258
|
-
error_details = extract_api_error_details(e)
|
|
259
|
-
ctx.log_error(f"API Error: {error_details}")
|
|
260
|
-
raise
|
|
261
|
-
except Exception as e:
|
|
262
|
-
ctx.log_error(f"Error processing node: {str(e)}")
|
|
263
|
-
ctx.log_error(f"Error type: {type(e).__name__}")
|
|
264
|
-
raise
|
|
265
|
-
return agent_run
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def _create_print_throttle_notif(ctx: AnyContext) -> Callable[[], None]:
|
|
269
|
-
def _print_throttle_notif():
|
|
270
|
-
ctx.print(stylize_faint(" ⌛>> Request Throttled"), plain=True)
|
|
271
|
-
|
|
272
|
-
return _print_throttle_notif
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def _estimate_request_payload(
|
|
276
|
-
agent: "Agent",
|
|
277
|
-
user_prompt: str,
|
|
278
|
-
attachments: "list[UserContent]",
|
|
279
|
-
history_list: ListOfDict,
|
|
280
|
-
) -> str:
|
|
281
|
-
system_prompts = agent._system_prompts if hasattr(agent, "_system_prompts") else ()
|
|
282
|
-
return json.dumps(
|
|
283
|
-
[
|
|
284
|
-
{"role": "system", "content": "\n".join(system_prompts)},
|
|
285
|
-
*history_list,
|
|
286
|
-
{"role": "user", "content": user_prompt},
|
|
287
|
-
*[_estimate_attachment_payload(attachment) for attachment in attachments],
|
|
288
|
-
]
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def _estimate_attachment_payload(attachment: "UserContent") -> Any:
|
|
293
|
-
if hasattr(attachment, "url"):
|
|
294
|
-
return {"role": "user", "content": attachment.url}
|
|
295
|
-
if hasattr(attachment, "data"):
|
|
296
|
-
return {"role": "user", "content": "x" * len(attachment.data)}
|
|
297
|
-
return ""
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
def _get_plain_printer(ctx: AnyContext):
|
|
301
|
-
def printer(*args, **kwargs):
|
|
302
|
-
if "plain" not in kwargs:
|
|
303
|
-
kwargs["plain"] = True
|
|
304
|
-
return ctx.print(*args, **kwargs)
|
|
305
|
-
|
|
306
|
-
return printer
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from zrb.config.llm_rate_limitter import LLMRateLimitter, llm_rate_limitter
|
|
6
|
+
from zrb.context.any_context import AnyContext
|
|
7
|
+
from zrb.task.llm.error import extract_api_error_details
|
|
8
|
+
from zrb.task.llm.print_node import print_node
|
|
9
|
+
from zrb.task.llm.typing import ListOfDict
|
|
10
|
+
from zrb.util.cli.style import stylize_faint
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from pydantic_ai import Agent, Tool
|
|
14
|
+
from pydantic_ai.agent import AgentRun
|
|
15
|
+
from pydantic_ai.messages import UserContent
|
|
16
|
+
|
|
17
|
+
ToolOrCallable = Tool | Callable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def run_agent_iteration(
|
|
21
|
+
ctx: AnyContext,
|
|
22
|
+
agent: "Agent[None, Any]",
|
|
23
|
+
user_prompt: str,
|
|
24
|
+
attachments: "list[UserContent] | None" = None,
|
|
25
|
+
history_list: ListOfDict | None = None,
|
|
26
|
+
rate_limitter: LLMRateLimitter | None = None,
|
|
27
|
+
max_retry: int = 2,
|
|
28
|
+
log_indent_level: int = 0,
|
|
29
|
+
) -> "AgentRun":
|
|
30
|
+
"""
|
|
31
|
+
Runs a single iteration of the agent execution loop.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
ctx: The task context.
|
|
35
|
+
agent: The Pydantic AI agent instance.
|
|
36
|
+
user_prompt: The user's input prompt.
|
|
37
|
+
history_list: The current conversation history.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The agent run result object.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
Exception: If any error occurs during agent execution.
|
|
44
|
+
"""
|
|
45
|
+
if max_retry < 0:
|
|
46
|
+
raise ValueError("Max retry cannot be less than 0")
|
|
47
|
+
attempt = 0
|
|
48
|
+
while attempt < max_retry:
|
|
49
|
+
try:
|
|
50
|
+
return await _run_single_agent_iteration(
|
|
51
|
+
ctx=ctx,
|
|
52
|
+
agent=agent,
|
|
53
|
+
user_prompt=user_prompt,
|
|
54
|
+
attachments=[] if attachments is None else attachments,
|
|
55
|
+
history_list=[] if history_list is None else history_list,
|
|
56
|
+
rate_limitter=(
|
|
57
|
+
llm_rate_limitter if rate_limitter is None else rate_limitter
|
|
58
|
+
),
|
|
59
|
+
log_indent_level=log_indent_level,
|
|
60
|
+
)
|
|
61
|
+
except BaseException:
|
|
62
|
+
attempt += 1
|
|
63
|
+
if attempt == max_retry:
|
|
64
|
+
raise
|
|
65
|
+
raise Exception("Max retry exceeded")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def _run_single_agent_iteration(
|
|
69
|
+
ctx: AnyContext,
|
|
70
|
+
agent: "Agent",
|
|
71
|
+
user_prompt: str,
|
|
72
|
+
attachments: "list[UserContent]",
|
|
73
|
+
history_list: ListOfDict,
|
|
74
|
+
rate_limitter: LLMRateLimitter,
|
|
75
|
+
log_indent_level: int,
|
|
76
|
+
) -> "AgentRun":
|
|
77
|
+
from openai import APIError
|
|
78
|
+
from pydantic_ai import UsageLimits
|
|
79
|
+
from pydantic_ai.messages import ModelMessagesTypeAdapter
|
|
80
|
+
|
|
81
|
+
agent_payload = _estimate_request_payload(
|
|
82
|
+
agent, user_prompt, attachments, history_list
|
|
83
|
+
)
|
|
84
|
+
callback = _create_print_throttle_notif(ctx)
|
|
85
|
+
if rate_limitter:
|
|
86
|
+
await rate_limitter.throttle(agent_payload, callback)
|
|
87
|
+
else:
|
|
88
|
+
await llm_rate_limitter.throttle(agent_payload, callback)
|
|
89
|
+
user_prompt_with_attachments = [user_prompt] + attachments
|
|
90
|
+
async with agent:
|
|
91
|
+
async with agent.iter(
|
|
92
|
+
user_prompt=user_prompt_with_attachments,
|
|
93
|
+
message_history=ModelMessagesTypeAdapter.validate_python(history_list),
|
|
94
|
+
usage_limits=UsageLimits(request_limit=None), # We don't want limit
|
|
95
|
+
) as agent_run:
|
|
96
|
+
async for node in agent_run:
|
|
97
|
+
# Each node represents a step in the agent's execution
|
|
98
|
+
try:
|
|
99
|
+
await print_node(
|
|
100
|
+
_get_plain_printer(ctx), agent_run, node, log_indent_level
|
|
101
|
+
)
|
|
102
|
+
except APIError as e:
|
|
103
|
+
# Extract detailed error information from the response
|
|
104
|
+
error_details = extract_api_error_details(e)
|
|
105
|
+
ctx.log_error(f"API Error: {error_details}")
|
|
106
|
+
raise
|
|
107
|
+
except Exception as e:
|
|
108
|
+
ctx.log_error(f"Error processing node: {str(e)}")
|
|
109
|
+
ctx.log_error(f"Error type: {type(e).__name__}")
|
|
110
|
+
raise
|
|
111
|
+
return agent_run
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _create_print_throttle_notif(ctx: AnyContext) -> Callable[[str], None]:
|
|
115
|
+
def _print_throttle_notif(reason: str):
|
|
116
|
+
ctx.print(stylize_faint(f" ⌛>> Request Throttled: {reason}"), plain=True)
|
|
117
|
+
|
|
118
|
+
return _print_throttle_notif
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _estimate_request_payload(
|
|
122
|
+
agent: "Agent",
|
|
123
|
+
user_prompt: str,
|
|
124
|
+
attachments: "list[UserContent]",
|
|
125
|
+
history_list: ListOfDict,
|
|
126
|
+
) -> str:
|
|
127
|
+
system_prompts = agent._system_prompts if hasattr(agent, "_system_prompts") else ()
|
|
128
|
+
return json.dumps(
|
|
129
|
+
[
|
|
130
|
+
{"role": "system", "content": "\n".join(system_prompts)},
|
|
131
|
+
*history_list,
|
|
132
|
+
{"role": "user", "content": user_prompt},
|
|
133
|
+
*[_estimate_attachment_payload(attachment) for attachment in attachments],
|
|
134
|
+
]
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _estimate_attachment_payload(attachment: "UserContent") -> Any:
|
|
139
|
+
if hasattr(attachment, "url"):
|
|
140
|
+
return {"role": "user", "content": attachment.url}
|
|
141
|
+
if hasattr(attachment, "data"):
|
|
142
|
+
return {"role": "user", "content": "x" * len(attachment.data)}
|
|
143
|
+
return ""
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _get_plain_printer(ctx: AnyContext):
|
|
147
|
+
def printer(*args, **kwargs):
|
|
148
|
+
if "plain" not in kwargs:
|
|
149
|
+
kwargs["plain"] = True
|
|
150
|
+
return ctx.print(*args, **kwargs)
|
|
151
|
+
|
|
152
|
+
return printer
|
zrb/task/llm/config.py
CHANGED
|
@@ -4,16 +4,17 @@ if TYPE_CHECKING:
|
|
|
4
4
|
from pydantic_ai.models import Model
|
|
5
5
|
from pydantic_ai.settings import ModelSettings
|
|
6
6
|
|
|
7
|
-
from zrb.attr.type import BoolAttr, StrAttr, StrListAttr
|
|
7
|
+
from zrb.attr.type import BoolAttr, StrAttr, StrListAttr
|
|
8
8
|
from zrb.config.llm_config import LLMConfig, llm_config
|
|
9
9
|
from zrb.context.any_context import AnyContext
|
|
10
|
-
from zrb.context.any_shared_context import AnySharedContext
|
|
11
10
|
from zrb.util.attr import get_attr, get_bool_attr, get_str_list_attr
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
def get_yolo_mode(
|
|
15
14
|
ctx: AnyContext,
|
|
16
|
-
yolo_mode_attr:
|
|
15
|
+
yolo_mode_attr: (
|
|
16
|
+
Callable[[AnyContext], list[str] | bool | None] | StrListAttr | BoolAttr | None
|
|
17
|
+
) = None,
|
|
17
18
|
render_yolo_mode: bool = True,
|
|
18
19
|
) -> bool | list[str]:
|
|
19
20
|
if yolo_mode_attr is None:
|
|
@@ -69,7 +70,7 @@ def get_model_api_key(
|
|
|
69
70
|
) -> str | None:
|
|
70
71
|
"""Gets the model API key, rendering if configured."""
|
|
71
72
|
api_key = get_attr(ctx, model_api_key_attr, None, auto_render=render_model_api_key)
|
|
72
|
-
if api_key is None and llm_config.
|
|
73
|
+
if api_key is None and llm_config.default_model_api_key is not None:
|
|
73
74
|
return llm_config.default_model_api_key
|
|
74
75
|
if isinstance(api_key, str) or api_key is None:
|
|
75
76
|
return api_key
|
|
@@ -78,11 +79,11 @@ def get_model_api_key(
|
|
|
78
79
|
|
|
79
80
|
def get_model(
|
|
80
81
|
ctx: AnyContext,
|
|
81
|
-
model_attr: "Callable[[AnyContext], Model | str |
|
|
82
|
+
model_attr: "Callable[[AnyContext], Model | str | None] | Model | str | None",
|
|
82
83
|
render_model: bool,
|
|
83
|
-
model_base_url_attr:
|
|
84
|
+
model_base_url_attr: "Callable[[AnyContext], Model | str | None] | Model | str | None",
|
|
84
85
|
render_model_base_url: bool = True,
|
|
85
|
-
model_api_key_attr:
|
|
86
|
+
model_api_key_attr: "Callable[[AnyContext], Model | str | None] | Model | str | None" = None,
|
|
86
87
|
render_model_api_key: bool = True,
|
|
87
88
|
is_small_model: bool = False,
|
|
88
89
|
) -> "str | Model":
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
from collections.abc import Callable
|
|
4
|
-
from copy import deepcopy
|
|
5
4
|
from typing import Any
|
|
6
5
|
|
|
7
6
|
from zrb.attr.type import StrAttr
|
|
@@ -13,6 +12,37 @@ from zrb.util.attr import get_str_attr
|
|
|
13
12
|
from zrb.util.file import read_file, write_file
|
|
14
13
|
from zrb.util.markdown import make_markdown_section
|
|
15
14
|
from zrb.util.run import run_async
|
|
15
|
+
from zrb.xcom.xcom import Xcom
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_global_subagent_messages_xcom(ctx: AnyContext) -> Xcom:
|
|
19
|
+
if "_global_subagents" not in ctx.xcom:
|
|
20
|
+
ctx.xcom["_global_subagents"] = Xcom([{}])
|
|
21
|
+
if not isinstance(ctx.xcom["_global_subagents"], Xcom):
|
|
22
|
+
raise ValueError("ctx.xcom._global_subagents must be an Xcom")
|
|
23
|
+
return ctx.xcom["_global_subagents"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def inject_subagent_history_into_ctx(
|
|
27
|
+
ctx: AnyContext, conversation_history: ConversationHistory
|
|
28
|
+
):
|
|
29
|
+
subagent_messages_xcom = _get_global_subagent_messages_xcom(ctx)
|
|
30
|
+
existing_subagent_history = subagent_messages_xcom.get({})
|
|
31
|
+
subagent_messages_xcom.set(
|
|
32
|
+
{**existing_subagent_history, **conversation_history.subagent_history}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def set_ctx_subagent_history(ctx: AnyContext, subagent_name: str, messages: ListOfDict):
|
|
37
|
+
subagent_messages_xcom = _get_global_subagent_messages_xcom(ctx)
|
|
38
|
+
subagent_history = subagent_messages_xcom.get({})
|
|
39
|
+
subagent_history[subagent_name] = messages
|
|
40
|
+
subagent_messages_xcom.set(subagent_history)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_subagent_histories_from_ctx(ctx: AnyContext) -> dict[str, ListOfDict]:
|
|
44
|
+
subagent_messsages_xcom = _get_global_subagent_messages_xcom(ctx)
|
|
45
|
+
return subagent_messsages_xcom.get({})
|
|
16
46
|
|
|
17
47
|
|
|
18
48
|
def inject_conversation_history_notes(conversation_history: ConversationHistory):
|
|
@@ -56,7 +86,10 @@ def get_history_file(
|
|
|
56
86
|
|
|
57
87
|
async def _read_from_source(
|
|
58
88
|
ctx: AnyContext,
|
|
59
|
-
reader:
|
|
89
|
+
reader: (
|
|
90
|
+
Callable[[AnyContext], ConversationHistory | dict[str, Any] | list | None]
|
|
91
|
+
| None
|
|
92
|
+
),
|
|
60
93
|
file_path: str | None,
|
|
61
94
|
) -> "ConversationHistory | None":
|
|
62
95
|
# Priority 1: Reader function
|
|
@@ -165,28 +198,6 @@ async def write_conversation_history(
|
|
|
165
198
|
write_file(history_file, json.dumps(history_data.to_dict(), indent=2))
|
|
166
199
|
|
|
167
200
|
|
|
168
|
-
def replace_system_prompt_in_history(
|
|
169
|
-
history_list: ListOfDict, replacement: str = "<main LLM system prompt>"
|
|
170
|
-
) -> ListOfDict:
|
|
171
|
-
"""
|
|
172
|
-
Returns a new history list where any part with part_kind 'system-prompt'
|
|
173
|
-
has its 'content' replaced with the given replacement string.
|
|
174
|
-
Args:
|
|
175
|
-
history: List of history items (each item is a dict with a 'parts' list).
|
|
176
|
-
replacement: The string to use in place of system-prompt content.
|
|
177
|
-
|
|
178
|
-
Returns:
|
|
179
|
-
A deep-copied list of history items with system-prompt content replaced.
|
|
180
|
-
"""
|
|
181
|
-
new_history = deepcopy(history_list)
|
|
182
|
-
for item in new_history:
|
|
183
|
-
parts = item.get("parts", [])
|
|
184
|
-
for part in parts:
|
|
185
|
-
if part.get("part_kind") == "system-prompt":
|
|
186
|
-
part["content"] = replacement
|
|
187
|
-
return new_history
|
|
188
|
-
|
|
189
|
-
|
|
190
201
|
def count_part_in_history_list(history_list: ListOfDict) -> int:
|
|
191
202
|
"""Calculates the total number of 'parts' in a history list."""
|
|
192
203
|
history_part_len = 0
|
|
@@ -10,27 +10,24 @@ class ConversationHistory:
|
|
|
10
10
|
|
|
11
11
|
def __init__(
|
|
12
12
|
self,
|
|
13
|
-
past_conversation_summary: str = "",
|
|
14
|
-
past_conversation_transcript: str = "",
|
|
15
13
|
history: ListOfDict | None = None,
|
|
16
14
|
contextual_note: str | None = None,
|
|
17
15
|
long_term_note: str | None = None,
|
|
18
16
|
project_path: str | None = None,
|
|
17
|
+
subagent_history: dict[str, ListOfDict] | None = None,
|
|
19
18
|
):
|
|
20
|
-
self.past_conversation_transcript = past_conversation_transcript
|
|
21
|
-
self.past_conversation_summary = past_conversation_summary
|
|
22
19
|
self.history = history if history is not None else []
|
|
23
20
|
self.contextual_note = contextual_note if contextual_note is not None else ""
|
|
24
21
|
self.long_term_note = long_term_note if long_term_note is not None else ""
|
|
25
22
|
self.project_path = project_path if project_path is not None else os.getcwd()
|
|
23
|
+
self.subagent_history = subagent_history if subagent_history is not None else {}
|
|
26
24
|
|
|
27
25
|
def to_dict(self) -> dict[str, Any]:
|
|
28
26
|
return {
|
|
29
|
-
"past_conversation_summary": self.past_conversation_summary,
|
|
30
|
-
"past_conversation_transcript": self.past_conversation_transcript,
|
|
31
27
|
"history": self.history,
|
|
32
28
|
"contextual_note": self.contextual_note,
|
|
33
29
|
"long_term_note": self.long_term_note,
|
|
30
|
+
"subagent_history": self.subagent_history,
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
def model_dump_json(self, indent: int = 2) -> str:
|
|
@@ -44,15 +41,11 @@ class ConversationHistory:
|
|
|
44
41
|
if isinstance(data, cls):
|
|
45
42
|
return data # Already a valid instance
|
|
46
43
|
if isinstance(data, dict):
|
|
47
|
-
# This handles both the new format and the old {'context': ..., 'history': ...}
|
|
48
44
|
return cls(
|
|
49
|
-
past_conversation_summary=data.get("past_conversation_summary", ""),
|
|
50
|
-
past_conversation_transcript=data.get(
|
|
51
|
-
"past_conversation_transcript", ""
|
|
52
|
-
),
|
|
53
45
|
history=data.get("history", data.get("messages", [])),
|
|
54
46
|
contextual_note=data.get("contextual_note", ""),
|
|
55
47
|
long_term_note=data.get("long_term_note", ""),
|
|
48
|
+
subagent_history=data.get("subagent_history", {}),
|
|
56
49
|
)
|
|
57
50
|
elif isinstance(data, list):
|
|
58
51
|
# Handle very old format (just a list) - wrap it
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "A comprehensive workflow for software engineering tasks, including writing, modifying, and debugging code, as well as creating new applications."
|
|
2
|
+
description: "A comprehensive workflow for software engineering tasks, including writing, modifying, and debugging code, as well as creating new applications. ALWAYS activate this workflow whenever you need to deal with software engineering tasks."
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
This workflow provides a structured approach to software engineering tasks. Adhere to these guidelines to deliver high-quality, idiomatic code that respects the project's existing patterns and conventions.
|
|
@@ -35,8 +35,7 @@ Always consider if a more specific workflow is available and appropriate for the
|
|
|
35
35
|
When requested to perform tasks like fixing bugs, adding features, refactoring, or explaining code, follow this sequence:
|
|
36
36
|
1. **Understand & Strategize:** Think about the user's request and the relevant codebase context. When the task involves **complex refactoring, codebase exploration or system-wide analysis**, your **first and primary tool** must be 'codebase_investigator'. Use it to build a comprehensive understanding of the code, its structure, and dependencies. For **simple, targeted searches** (like finding a specific function name, file path, or variable declaration), you should use 'search_file_content' or 'glob' directly.
|
|
37
37
|
2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should use an iterative development process that includes writing unit tests to verify your changes. Use output logs or debug statements as part of this process to arrive at a solution.
|
|
38
|
-
3. **Implement:** Use the available tools (e.g., '
|
|
39
|
-
Mandates').
|
|
38
|
+
3. **Implement:** Use the available tools (e.g., 'replace_in_file', 'write_to_file' 'run_shell_command' ...) to act on the plan, strictly adhering to the project's established conventions (detailed under 'Core Mandates').
|
|
40
39
|
4. **Verify (Tests):** If applicable and feasible, verify the changes using the project's testing procedures. Identify the correct test commands and frameworks by examining 'README' files, build/package configuration (e.g., 'package.json'), or existing test execution patterns. NEVER assume standard test commands.
|
|
41
40
|
5. **Verify (Standards):** VERY IMPORTANT: After making code changes, execute the project-specific build, linting and type-checking commands (e.g., 'tsc', 'npm run lint', 'ruff check .') that you have identified for this project (or obtained from the user). This ensures code quality and adherence to standards. If unsure about these commands, you can ask the user if they'd like you to run them and if so how to.
|
|
42
41
|
6. **Finalize:** After all verification passes, consider the task complete. Do not remove or revert any changes or created files (like tests). Await the user's next instruction.
|