contextagent 0.1.0__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.
- agentz/agent/base.py +262 -0
- agentz/artifacts/__init__.py +5 -0
- agentz/artifacts/artifact_writer.py +538 -0
- agentz/artifacts/reporter.py +235 -0
- agentz/artifacts/terminal_writer.py +100 -0
- agentz/context/__init__.py +6 -0
- agentz/context/context.py +91 -0
- agentz/context/conversation.py +205 -0
- agentz/context/data_store.py +208 -0
- agentz/llm/llm_setup.py +156 -0
- agentz/mcp/manager.py +142 -0
- agentz/mcp/patches.py +88 -0
- agentz/mcp/servers/chrome_devtools/server.py +14 -0
- agentz/profiles/base.py +108 -0
- agentz/profiles/data/data_analysis.py +38 -0
- agentz/profiles/data/data_loader.py +35 -0
- agentz/profiles/data/evaluation.py +43 -0
- agentz/profiles/data/model_training.py +47 -0
- agentz/profiles/data/preprocessing.py +47 -0
- agentz/profiles/data/visualization.py +47 -0
- agentz/profiles/manager/evaluate.py +51 -0
- agentz/profiles/manager/memory.py +62 -0
- agentz/profiles/manager/observe.py +48 -0
- agentz/profiles/manager/routing.py +66 -0
- agentz/profiles/manager/writer.py +51 -0
- agentz/profiles/mcp/browser.py +21 -0
- agentz/profiles/mcp/chrome.py +21 -0
- agentz/profiles/mcp/notion.py +21 -0
- agentz/runner/__init__.py +74 -0
- agentz/runner/base.py +28 -0
- agentz/runner/executor.py +320 -0
- agentz/runner/hooks.py +110 -0
- agentz/runner/iteration.py +142 -0
- agentz/runner/patterns.py +215 -0
- agentz/runner/tracker.py +188 -0
- agentz/runner/utils.py +45 -0
- agentz/runner/workflow.py +250 -0
- agentz/tools/__init__.py +20 -0
- agentz/tools/data_tools/__init__.py +17 -0
- agentz/tools/data_tools/data_analysis.py +152 -0
- agentz/tools/data_tools/data_loading.py +92 -0
- agentz/tools/data_tools/evaluation.py +175 -0
- agentz/tools/data_tools/helpers.py +120 -0
- agentz/tools/data_tools/model_training.py +192 -0
- agentz/tools/data_tools/preprocessing.py +229 -0
- agentz/tools/data_tools/visualization.py +281 -0
- agentz/utils/__init__.py +69 -0
- agentz/utils/config.py +708 -0
- agentz/utils/helpers.py +10 -0
- agentz/utils/parsers.py +142 -0
- agentz/utils/printer.py +539 -0
- contextagent-0.1.0.dist-info/METADATA +269 -0
- contextagent-0.1.0.dist-info/RECORD +66 -0
- contextagent-0.1.0.dist-info/WHEEL +5 -0
- contextagent-0.1.0.dist-info/licenses/LICENSE +21 -0
- contextagent-0.1.0.dist-info/top_level.txt +2 -0
- pipelines/base.py +972 -0
- pipelines/data_scientist.py +97 -0
- pipelines/data_scientist_memory.py +151 -0
- pipelines/experience_learner.py +0 -0
- pipelines/prompt_generator.py +0 -0
- pipelines/simple.py +78 -0
- pipelines/simple_browser.py +145 -0
- pipelines/simple_chrome.py +75 -0
- pipelines/simple_notion.py +103 -0
- pipelines/tool_builder.py +0 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from pydantic import BaseModel, Field
|
4
|
+
|
5
|
+
from agentz.profiles.base import Profile
|
6
|
+
|
7
|
+
|
8
|
+
class InstructionsInput(BaseModel):
|
9
|
+
"""Input schema for instructions-based runtime template."""
|
10
|
+
instructions: str = Field(description="The instructions to follow")
|
11
|
+
|
12
|
+
|
13
|
+
# Profile instance for chrome agent
|
14
|
+
chrome_profile = Profile(
|
15
|
+
instructions="You are a chrome agent. Your task is to interact with the chrome browser following the instructions provided.",
|
16
|
+
runtime_template="[[INSTRUCTIONS]]",
|
17
|
+
output_schema=None,
|
18
|
+
input_schema=InstructionsInput,
|
19
|
+
tools=None,
|
20
|
+
model=None
|
21
|
+
)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from pydantic import BaseModel, Field
|
4
|
+
|
5
|
+
from agentz.profiles.base import Profile
|
6
|
+
|
7
|
+
|
8
|
+
class InstructionsInput(BaseModel):
|
9
|
+
"""Input schema for instructions-based runtime template."""
|
10
|
+
instructions: str = Field(description="The instructions to follow")
|
11
|
+
|
12
|
+
|
13
|
+
# Profile instance for notion agent
|
14
|
+
notion_profile = Profile(
|
15
|
+
instructions="You are a notion agent. Your task is to interact with the notion MCP server following the instructions provided.",
|
16
|
+
runtime_template="[[INSTRUCTIONS]]",
|
17
|
+
output_schema=None,
|
18
|
+
input_schema=InstructionsInput,
|
19
|
+
tools=None,
|
20
|
+
model=None
|
21
|
+
)
|
@@ -0,0 +1,74 @@
|
|
1
|
+
"""Agent runtime execution module.
|
2
|
+
|
3
|
+
This module provides the complete agent runtime infrastructure:
|
4
|
+
|
5
|
+
Base Runners:
|
6
|
+
- Runner: Base runner from agents library (re-exported)
|
7
|
+
- ContextRunner: Context-aware runner with output parsing
|
8
|
+
|
9
|
+
Runtime Infrastructure:
|
10
|
+
- RuntimeTracker: Manages runtime state (tracing, printing, reporting, iteration tracking, data store)
|
11
|
+
- AgentExecutor: High-level agent execution with full pipeline infrastructure
|
12
|
+
- AgentStep: Abstraction for a single agent execution step
|
13
|
+
|
14
|
+
Pipeline Infrastructure:
|
15
|
+
- HookRegistry: Event-driven hook system for pipeline lifecycle
|
16
|
+
- IterationManager: Iteration management for iterative workflows
|
17
|
+
- WorkflowHelpers: Control flow helpers (loops, conditionals, parallel execution)
|
18
|
+
|
19
|
+
High-Level Patterns:
|
20
|
+
- execute_tool_plan: Execute tool agents from routing plans
|
21
|
+
- execute_tools: Execute tools based on routing plan
|
22
|
+
- run_manager_tool_loop: Standard manager-tool iterative pattern
|
23
|
+
|
24
|
+
Utilities:
|
25
|
+
- record_structured_payload: Record payloads to state
|
26
|
+
- serialize_output: Serialize agent outputs
|
27
|
+
|
28
|
+
Runtime Access:
|
29
|
+
- get_current_tracker: Access the current RuntimeTracker from anywhere
|
30
|
+
- get_current_data_store: Convenience for accessing the data store
|
31
|
+
|
32
|
+
"""
|
33
|
+
|
34
|
+
from agentz.runner.base import Runner, ContextRunner
|
35
|
+
from agentz.runner.tracker import (
|
36
|
+
RuntimeTracker,
|
37
|
+
get_current_tracker,
|
38
|
+
get_current_data_store,
|
39
|
+
)
|
40
|
+
from agentz.runner.executor import AgentExecutor, AgentStep, PrinterConfig
|
41
|
+
from agentz.runner.hooks import HookRegistry
|
42
|
+
from agentz.runner.iteration import IterationManager
|
43
|
+
from agentz.runner.workflow import WorkflowHelpers
|
44
|
+
from agentz.runner.patterns import (
|
45
|
+
execute_tool_plan,
|
46
|
+
execute_tools,
|
47
|
+
run_manager_tool_loop,
|
48
|
+
)
|
49
|
+
from agentz.runner.utils import record_structured_payload, serialize_output
|
50
|
+
|
51
|
+
__all__ = [
|
52
|
+
# Base runners
|
53
|
+
"Runner",
|
54
|
+
"ContextRunner",
|
55
|
+
# Runtime infrastructure
|
56
|
+
"RuntimeTracker",
|
57
|
+
"AgentExecutor",
|
58
|
+
"AgentStep",
|
59
|
+
"PrinterConfig",
|
60
|
+
# Pipeline infrastructure
|
61
|
+
"HookRegistry",
|
62
|
+
"IterationManager",
|
63
|
+
"WorkflowHelpers",
|
64
|
+
# High-level patterns
|
65
|
+
"execute_tool_plan",
|
66
|
+
"execute_tools",
|
67
|
+
"run_manager_tool_loop",
|
68
|
+
# Utilities
|
69
|
+
"record_structured_payload",
|
70
|
+
"serialize_output",
|
71
|
+
# Runtime access
|
72
|
+
"get_current_tracker",
|
73
|
+
"get_current_data_store",
|
74
|
+
]
|
agentz/runner/base.py
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
"""Base runner classes for agent execution.
|
2
|
+
|
3
|
+
This module provides the foundational runner classes:
|
4
|
+
- Runner: Base runner from agents library (re-exported for convenience)
|
5
|
+
- ContextRunner: Context-aware runner that applies output parsing
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any
|
9
|
+
|
10
|
+
from agents import Runner, RunResult
|
11
|
+
|
12
|
+
__all__ = ["Runner", "ContextRunner"]
|
13
|
+
|
14
|
+
|
15
|
+
class ContextRunner(Runner):
|
16
|
+
"""Runner shim that invokes ContextAgent.parse_output after execution."""
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
async def run(cls, *args: Any, **kwargs: Any) -> RunResult:
|
20
|
+
result = await Runner.run(*args, **kwargs)
|
21
|
+
starting_agent = kwargs.get("starting_agent") or (args[0] if args else None)
|
22
|
+
|
23
|
+
# Import here to avoid circular dependency
|
24
|
+
from agentz.agent.base import ContextAgent
|
25
|
+
|
26
|
+
if isinstance(starting_agent, ContextAgent):
|
27
|
+
return await starting_agent.parse_output(result)
|
28
|
+
return result
|
@@ -0,0 +1,320 @@
|
|
1
|
+
"""Core agent execution primitives."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import json
|
5
|
+
import time
|
6
|
+
from dataclasses import dataclass, field
|
7
|
+
from typing import Any, Callable, Dict, Optional, Union
|
8
|
+
|
9
|
+
from agentz.runner.base import ContextRunner as Runner
|
10
|
+
from agents.tracing.create import agent_span, function_span
|
11
|
+
from pydantic import BaseModel
|
12
|
+
|
13
|
+
from agentz.runner.tracker import RuntimeTracker
|
14
|
+
|
15
|
+
|
16
|
+
@dataclass
|
17
|
+
class PrinterConfig:
|
18
|
+
"""Configuration for printer updates during step execution."""
|
19
|
+
key: Optional[str] = None
|
20
|
+
title: Optional[str] = None
|
21
|
+
start_message: Optional[str] = None
|
22
|
+
done_message: Optional[str] = None
|
23
|
+
|
24
|
+
|
25
|
+
@dataclass
|
26
|
+
class AgentStep:
|
27
|
+
"""Represents a single agent execution step.
|
28
|
+
|
29
|
+
This encapsulates all the information needed to execute an agent:
|
30
|
+
- The agent instance
|
31
|
+
- Instructions (static or dynamic via callable)
|
32
|
+
- Span configuration for tracing
|
33
|
+
- Output model for parsing
|
34
|
+
- Printer configuration for status updates
|
35
|
+
"""
|
36
|
+
|
37
|
+
agent: Any
|
38
|
+
instructions: Union[str, Callable[[], str]]
|
39
|
+
span_name: str
|
40
|
+
span_type: str = "agent"
|
41
|
+
output_model: Optional[type[BaseModel]] = None
|
42
|
+
sync: bool = False
|
43
|
+
printer_config: Optional[PrinterConfig] = None
|
44
|
+
span_kwargs: Dict[str, Any] = field(default_factory=dict)
|
45
|
+
|
46
|
+
def get_instructions(self) -> str:
|
47
|
+
"""Get instructions, evaluating callable if needed.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
Instructions string
|
51
|
+
"""
|
52
|
+
if callable(self.instructions):
|
53
|
+
return self.instructions()
|
54
|
+
return self.instructions
|
55
|
+
|
56
|
+
def get_printer_key(self, iteration: int = 0) -> Optional[str]:
|
57
|
+
"""Get the printer key, adding iteration prefix if configured.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
iteration: Current iteration number
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
Printer key with iteration prefix, or None if not configured
|
64
|
+
"""
|
65
|
+
if not self.printer_config or not self.printer_config.key:
|
66
|
+
return None
|
67
|
+
return f"iter:{iteration}:{self.printer_config.key}"
|
68
|
+
|
69
|
+
|
70
|
+
class AgentExecutor:
|
71
|
+
"""Core agent execution primitive.
|
72
|
+
|
73
|
+
Handles the low-level mechanics of running agents with:
|
74
|
+
- Span tracking and output capture
|
75
|
+
- Optional output model parsing
|
76
|
+
- Printer status updates
|
77
|
+
- Sync/async execution
|
78
|
+
"""
|
79
|
+
|
80
|
+
def __init__(self, context: RuntimeTracker):
|
81
|
+
"""Initialize executor with runtime tracker.
|
82
|
+
|
83
|
+
Args:
|
84
|
+
context: RuntimeTracker for tracing, printing, etc.
|
85
|
+
"""
|
86
|
+
self.context = context
|
87
|
+
|
88
|
+
async def execute_step(self, step: AgentStep) -> Any:
|
89
|
+
"""Execute an AgentStep.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
step: AgentStep to execute
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
Parsed output if output_model provided, otherwise Runner result
|
96
|
+
"""
|
97
|
+
instructions = step.get_instructions()
|
98
|
+
|
99
|
+
return await self.agent_step(
|
100
|
+
agent=step.agent,
|
101
|
+
instructions=instructions,
|
102
|
+
span_name=step.span_name,
|
103
|
+
span_type=step.span_type,
|
104
|
+
output_model=step.output_model,
|
105
|
+
sync=step.sync,
|
106
|
+
printer_key=step.printer_config.key if step.printer_config else None,
|
107
|
+
printer_title=step.printer_config.title if step.printer_config else None,
|
108
|
+
**step.span_kwargs
|
109
|
+
)
|
110
|
+
|
111
|
+
async def agent_step(
|
112
|
+
self,
|
113
|
+
agent,
|
114
|
+
instructions: str,
|
115
|
+
span_name: Optional[str] = None,
|
116
|
+
span_type: str = "agent",
|
117
|
+
output_model: Optional[type[BaseModel]] = None,
|
118
|
+
sync: bool = False,
|
119
|
+
printer_key: Optional[str] = None,
|
120
|
+
printer_title: Optional[str] = None,
|
121
|
+
group_id: Optional[str] = None,
|
122
|
+
printer_group_id: Optional[str] = None,
|
123
|
+
printer_border_style: Optional[str] = None,
|
124
|
+
**span_kwargs
|
125
|
+
) -> Any:
|
126
|
+
"""Run an agent with span tracking and optional output parsing.
|
127
|
+
|
128
|
+
Args:
|
129
|
+
agent: The agent to run
|
130
|
+
instructions: Instructions/prompt for the agent
|
131
|
+
span_name: Name for the span (auto-detected from agent.name if not provided)
|
132
|
+
span_type: Type of span - "agent" or "function"
|
133
|
+
output_model: Optional pydantic model to parse output
|
134
|
+
sync: Whether to run synchronously
|
135
|
+
printer_key: Optional key for printer updates (auto-detected from agent.name if not provided)
|
136
|
+
printer_title: Optional title for printer display (auto-detected from agent.name if not provided)
|
137
|
+
group_id: Optional group to nest this item in (alias for printer_group_id)
|
138
|
+
printer_group_id: Optional group to nest this item in (deprecated, use group_id)
|
139
|
+
printer_border_style: Optional border color
|
140
|
+
**span_kwargs: Additional kwargs for span (e.g., tools, input)
|
141
|
+
|
142
|
+
Returns:
|
143
|
+
Parsed output if output_model provided, otherwise Runner result
|
144
|
+
"""
|
145
|
+
# Extract agent name for auto-detection
|
146
|
+
agent_name = getattr(agent, "name", getattr(agent, "__class__", type("obj", (), {})).__name__)
|
147
|
+
|
148
|
+
# Auto-detect span_name from agent if not provided
|
149
|
+
if span_name is None:
|
150
|
+
span_name = str(agent_name)
|
151
|
+
|
152
|
+
# Auto-detect printer_key from agent if not provided
|
153
|
+
if printer_key is None:
|
154
|
+
printer_key = str(agent_name)
|
155
|
+
|
156
|
+
# Auto-detect printer_title from agent if not provided
|
157
|
+
if printer_title is None:
|
158
|
+
# Capitalize first letter and add "ing" suffix
|
159
|
+
title_base = str(agent_name).capitalize()
|
160
|
+
printer_title = f"{title_base}"
|
161
|
+
|
162
|
+
# Support both group_id and printer_group_id (group_id takes precedence)
|
163
|
+
if group_id is not None:
|
164
|
+
printer_group_id = group_id
|
165
|
+
|
166
|
+
span_factory = agent_span if span_type == "agent" else function_span
|
167
|
+
|
168
|
+
reporter = self.context.reporter
|
169
|
+
step_id: Optional[str] = None
|
170
|
+
if reporter:
|
171
|
+
step_id = f"{self.context.iteration}-{span_name}-{time.time_ns()}"
|
172
|
+
reporter.record_agent_step_start(
|
173
|
+
step_id=step_id,
|
174
|
+
agent_name=str(agent_name),
|
175
|
+
span_name=span_name,
|
176
|
+
iteration=self.context.iteration,
|
177
|
+
group_id=printer_group_id,
|
178
|
+
printer_title=printer_title,
|
179
|
+
)
|
180
|
+
|
181
|
+
full_printer_key: Optional[str] = None
|
182
|
+
if printer_key:
|
183
|
+
full_printer_key = f"iter:{self.context.iteration}:{printer_key}"
|
184
|
+
self.context.update_printer(
|
185
|
+
full_printer_key,
|
186
|
+
"Working...",
|
187
|
+
title=printer_title or printer_key,
|
188
|
+
border_style=printer_border_style,
|
189
|
+
group_id=printer_group_id,
|
190
|
+
)
|
191
|
+
|
192
|
+
status = "success"
|
193
|
+
error_message: Optional[str] = None
|
194
|
+
start_time = time.perf_counter()
|
195
|
+
|
196
|
+
try:
|
197
|
+
with self.context.span_context(span_factory, name=span_name, **span_kwargs) as span:
|
198
|
+
# Activate context so tools can access it
|
199
|
+
with self.context.activate():
|
200
|
+
if sync:
|
201
|
+
result = Runner.run_sync(agent, instructions, context=self.context.data_store)
|
202
|
+
else:
|
203
|
+
result = await Runner.run(agent, instructions, context=self.context.data_store)
|
204
|
+
|
205
|
+
raw_output = getattr(result, "final_output", result)
|
206
|
+
|
207
|
+
# Update printer status and emit detailed output as a standalone panel
|
208
|
+
if full_printer_key:
|
209
|
+
self.context.update_printer(
|
210
|
+
full_printer_key,
|
211
|
+
"Completed",
|
212
|
+
is_done=True,
|
213
|
+
title=printer_title or printer_key,
|
214
|
+
group_id=printer_group_id,
|
215
|
+
border_style=printer_border_style,
|
216
|
+
)
|
217
|
+
|
218
|
+
# Extract content from raw_output
|
219
|
+
if hasattr(raw_output, 'output'):
|
220
|
+
panel_content = str(raw_output.output)
|
221
|
+
elif isinstance(raw_output, BaseModel):
|
222
|
+
panel_content = raw_output.model_dump_json(indent=2)
|
223
|
+
elif isinstance(raw_output, dict):
|
224
|
+
panel_content = json.dumps(raw_output, indent=2)
|
225
|
+
else:
|
226
|
+
panel_content = str(raw_output)
|
227
|
+
|
228
|
+
if panel_content.strip():
|
229
|
+
self.context.log_panel(
|
230
|
+
printer_title or printer_key,
|
231
|
+
panel_content,
|
232
|
+
border_style=printer_border_style,
|
233
|
+
iteration=self.context.iteration,
|
234
|
+
group_id=printer_group_id,
|
235
|
+
)
|
236
|
+
|
237
|
+
if output_model:
|
238
|
+
if isinstance(raw_output, output_model):
|
239
|
+
output = raw_output
|
240
|
+
elif isinstance(raw_output, BaseModel):
|
241
|
+
output = output_model.model_validate(raw_output.model_dump())
|
242
|
+
elif isinstance(raw_output, (dict, list)):
|
243
|
+
output = output_model.model_validate(raw_output)
|
244
|
+
elif isinstance(raw_output, (str, bytes, bytearray)):
|
245
|
+
output = output_model.model_validate_json(raw_output)
|
246
|
+
else:
|
247
|
+
output = output_model.model_validate(raw_output)
|
248
|
+
if span and hasattr(span, "set_output"):
|
249
|
+
span.set_output(output.model_dump())
|
250
|
+
return output
|
251
|
+
else:
|
252
|
+
if span and hasattr(span, "set_output"):
|
253
|
+
span.set_output({"output_preview": str(getattr(result, "final_output", result))[:200]})
|
254
|
+
return result
|
255
|
+
except Exception as exc: # noqa: BLE001 - propagate after logging
|
256
|
+
status = "error"
|
257
|
+
error_message = str(exc)
|
258
|
+
raise
|
259
|
+
finally:
|
260
|
+
if reporter and step_id is not None:
|
261
|
+
reporter.record_agent_step_end(
|
262
|
+
step_id=step_id,
|
263
|
+
status=status,
|
264
|
+
duration_seconds=time.perf_counter() - start_time,
|
265
|
+
error=error_message,
|
266
|
+
)
|
267
|
+
|
268
|
+
async def run_span_step(
|
269
|
+
self,
|
270
|
+
step_key: str,
|
271
|
+
callable_or_coro: Union[Callable, Any],
|
272
|
+
span_name: str,
|
273
|
+
span_type: str = "function",
|
274
|
+
start_message: Optional[str] = None,
|
275
|
+
done_message: Optional[str] = None,
|
276
|
+
**span_kwargs
|
277
|
+
) -> Any:
|
278
|
+
"""Execute a step with span context and printer updates.
|
279
|
+
|
280
|
+
Args:
|
281
|
+
step_key: Printer status key
|
282
|
+
callable_or_coro: Callable or coroutine to execute
|
283
|
+
span_name: Name for the span
|
284
|
+
span_type: Type of span - "agent" or "function"
|
285
|
+
start_message: Optional start message for printer
|
286
|
+
done_message: Optional completion message for printer
|
287
|
+
**span_kwargs: Additional kwargs for span (e.g., tools, input)
|
288
|
+
|
289
|
+
Returns:
|
290
|
+
Result from callable_or_coro
|
291
|
+
"""
|
292
|
+
span_factory = agent_span if span_type == "agent" else function_span
|
293
|
+
|
294
|
+
if start_message:
|
295
|
+
self.context.update_printer(step_key, start_message)
|
296
|
+
|
297
|
+
with self.context.span_context(span_factory, name=span_name, **span_kwargs) as span:
|
298
|
+
# Execute callable or await coroutine
|
299
|
+
if asyncio.iscoroutine(callable_or_coro):
|
300
|
+
result = await callable_or_coro
|
301
|
+
elif callable(callable_or_coro):
|
302
|
+
result = callable_or_coro()
|
303
|
+
else:
|
304
|
+
result = callable_or_coro
|
305
|
+
|
306
|
+
# Set span output if available
|
307
|
+
if span and hasattr(span, "set_output"):
|
308
|
+
if isinstance(result, dict):
|
309
|
+
span.set_output(result)
|
310
|
+
elif isinstance(result, str):
|
311
|
+
span.set_output({"output_preview": result[:200]})
|
312
|
+
elif hasattr(result, "model_dump"):
|
313
|
+
span.set_output(result.model_dump())
|
314
|
+
else:
|
315
|
+
span.set_output({"result": str(result)[:200]})
|
316
|
+
|
317
|
+
if done_message:
|
318
|
+
self.context.update_printer(step_key, done_message, is_done=True)
|
319
|
+
|
320
|
+
return result
|
agentz/runner/hooks.py
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
"""Hook registry system for event-driven pipeline extensibility."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
from typing import Any, Callable, Dict, List, Tuple
|
5
|
+
|
6
|
+
from loguru import logger
|
7
|
+
|
8
|
+
|
9
|
+
class HookRegistry:
|
10
|
+
"""Registry for managing pipeline event hooks.
|
11
|
+
|
12
|
+
Supports registering callbacks for various lifecycle events and triggering
|
13
|
+
them with priority ordering.
|
14
|
+
"""
|
15
|
+
|
16
|
+
DEFAULT_EVENTS = [
|
17
|
+
"before_execution",
|
18
|
+
"after_execution",
|
19
|
+
"before_iteration",
|
20
|
+
"after_iteration",
|
21
|
+
"before_agent_step",
|
22
|
+
"after_agent_step",
|
23
|
+
]
|
24
|
+
|
25
|
+
def __init__(self, events: List[str] = None):
|
26
|
+
"""Initialize hook registry.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
events: List of event names to support (defaults to DEFAULT_EVENTS)
|
30
|
+
"""
|
31
|
+
event_list = events or self.DEFAULT_EVENTS
|
32
|
+
self._hooks: Dict[str, List[Tuple[int, Callable]]] = {
|
33
|
+
event: [] for event in event_list
|
34
|
+
}
|
35
|
+
|
36
|
+
def register(
|
37
|
+
self,
|
38
|
+
event: str,
|
39
|
+
callback: Callable,
|
40
|
+
priority: int = 0
|
41
|
+
) -> None:
|
42
|
+
"""Register a hook callback for an event.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
event: Event name
|
46
|
+
callback: Callable or async callable
|
47
|
+
priority: Execution priority (higher = earlier)
|
48
|
+
|
49
|
+
Raises:
|
50
|
+
ValueError: If event is not recognized
|
51
|
+
|
52
|
+
Example:
|
53
|
+
def log_iteration(pipeline, iteration, group_id):
|
54
|
+
logger.info(f"Starting iteration {iteration.index}")
|
55
|
+
|
56
|
+
registry.register("before_iteration", log_iteration)
|
57
|
+
"""
|
58
|
+
if event not in self._hooks:
|
59
|
+
raise ValueError(
|
60
|
+
f"Unknown hook event: {event}. Valid events: {list(self._hooks.keys())}"
|
61
|
+
)
|
62
|
+
|
63
|
+
self._hooks[event].append((priority, callback))
|
64
|
+
# Sort by priority (descending)
|
65
|
+
self._hooks[event].sort(key=lambda x: -x[0])
|
66
|
+
|
67
|
+
async def trigger(self, event: str, context: Any, **kwargs) -> None:
|
68
|
+
"""Trigger all registered hooks for an event.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
event: Event name
|
72
|
+
context: Context object to pass to callbacks (typically the pipeline)
|
73
|
+
**kwargs: Additional arguments to pass to hook callbacks
|
74
|
+
"""
|
75
|
+
if event not in self._hooks:
|
76
|
+
return
|
77
|
+
|
78
|
+
for priority, callback in self._hooks[event]:
|
79
|
+
try:
|
80
|
+
if asyncio.iscoroutinefunction(callback):
|
81
|
+
await callback(context, **kwargs)
|
82
|
+
else:
|
83
|
+
callback(context, **kwargs)
|
84
|
+
except Exception as e:
|
85
|
+
logger.warning(f"Hook {callback.__name__} for {event} failed: {e}")
|
86
|
+
|
87
|
+
def has_hooks(self, event: str) -> bool:
|
88
|
+
"""Check if any hooks are registered for an event.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
event: Event name
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
True if hooks are registered, False otherwise
|
95
|
+
"""
|
96
|
+
return event in self._hooks and len(self._hooks[event]) > 0
|
97
|
+
|
98
|
+
def clear_event(self, event: str) -> None:
|
99
|
+
"""Clear all hooks for a specific event.
|
100
|
+
|
101
|
+
Args:
|
102
|
+
event: Event name
|
103
|
+
"""
|
104
|
+
if event in self._hooks:
|
105
|
+
self._hooks[event].clear()
|
106
|
+
|
107
|
+
def clear_all(self) -> None:
|
108
|
+
"""Clear all registered hooks."""
|
109
|
+
for event in self._hooks:
|
110
|
+
self._hooks[event].clear()
|