flock-core 0.5.4__py3-none-any.whl → 0.5.6__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 flock-core might be problematic. Click here for more details.

flock/agent.py CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import os
7
+ from collections.abc import Sequence
7
8
  from dataclasses import dataclass
8
9
  from typing import TYPE_CHECKING, Any, TypedDict
9
10
 
@@ -118,6 +119,31 @@ class Agent(metaclass=AutoTracedMeta):
118
119
  def identity(self) -> AgentIdentity:
119
120
  return AgentIdentity(name=self.name, labels=self.labels, tenant_id=self.tenant_id)
120
121
 
122
+ @staticmethod
123
+ def _component_display_name(component: AgentComponent) -> str:
124
+ return component.name or component.__class__.__name__
125
+
126
+ def _sorted_utilities(self) -> list[AgentComponent]:
127
+ if not self.utilities:
128
+ return []
129
+ return sorted(self.utilities, key=lambda comp: getattr(comp, "priority", 0))
130
+
131
+ def _add_utilities(self, components: Sequence[AgentComponent]) -> None:
132
+ if not components:
133
+ return
134
+ for component in components:
135
+ self.utilities.append(component)
136
+ comp_name = self._component_display_name(component)
137
+ priority = getattr(component, "priority", 0)
138
+ logger.info(
139
+ "Agent %s: utility added: component=%s, priority=%s, total_utilities=%s",
140
+ self.name,
141
+ comp_name,
142
+ priority,
143
+ len(self.utilities),
144
+ )
145
+ self.utilities.sort(key=lambda comp: getattr(comp, "priority", 0))
146
+
121
147
  def set_max_concurrency(self, value: int) -> None:
122
148
  self.max_concurrency = max(1, value)
123
149
  self._semaphore = asyncio.Semaphore(self.max_concurrency)
@@ -221,21 +247,59 @@ class Agent(metaclass=AutoTracedMeta):
221
247
  return []
222
248
 
223
249
  async def _run_initialize(self, ctx: Context) -> None:
224
- for component in self.utilities:
225
- await component.on_initialize(self, ctx)
250
+ for component in self._sorted_utilities():
251
+ comp_name = self._component_display_name(component)
252
+ priority = getattr(component, "priority", 0)
253
+ logger.debug(
254
+ f"Agent initialize: agent={self.name}, component={comp_name}, priority={priority}"
255
+ )
256
+ try:
257
+ await component.on_initialize(self, ctx)
258
+ except Exception as exc:
259
+ logger.exception(
260
+ f"Agent initialize failed: agent={self.name}, component={comp_name}, "
261
+ f"priority={priority}, error={exc!s}"
262
+ )
263
+ raise
226
264
  for engine in self.engines:
227
265
  await engine.on_initialize(self, ctx)
228
266
 
229
267
  async def _run_pre_consume(self, ctx: Context, inputs: list[Artifact]) -> list[Artifact]:
230
268
  current = inputs
231
- for component in self.utilities:
232
- current = await component.on_pre_consume(self, ctx, current)
269
+ for component in self._sorted_utilities():
270
+ comp_name = self._component_display_name(component)
271
+ priority = getattr(component, "priority", 0)
272
+ logger.debug(
273
+ f"Agent pre_consume: agent={self.name}, component={comp_name}, "
274
+ f"priority={priority}, input_count={len(current)}"
275
+ )
276
+ try:
277
+ current = await component.on_pre_consume(self, ctx, current)
278
+ except Exception as exc:
279
+ logger.exception(
280
+ f"Agent pre_consume failed: agent={self.name}, component={comp_name}, "
281
+ f"priority={priority}, error={exc!s}"
282
+ )
283
+ raise
233
284
  return current
234
285
 
235
286
  async def _run_pre_evaluate(self, ctx: Context, inputs: EvalInputs) -> EvalInputs:
236
287
  current = inputs
237
- for component in self.utilities:
238
- current = await component.on_pre_evaluate(self, ctx, current)
288
+ for component in self._sorted_utilities():
289
+ comp_name = self._component_display_name(component)
290
+ priority = getattr(component, "priority", 0)
291
+ logger.debug(
292
+ f"Agent pre_evaluate: agent={self.name}, component={comp_name}, "
293
+ f"priority={priority}, artifact_count={len(current.artifacts)}"
294
+ )
295
+ try:
296
+ current = await component.on_pre_evaluate(self, ctx, current)
297
+ except Exception as exc:
298
+ logger.exception(
299
+ f"Agent pre_evaluate failed: agent={self.name}, component={comp_name}, "
300
+ f"priority={priority}, error={exc!s}"
301
+ )
302
+ raise
239
303
  return current
240
304
 
241
305
  async def _run_engines(self, ctx: Context, inputs: EvalInputs) -> EvalResult:
@@ -249,7 +313,26 @@ class Agent(metaclass=AutoTracedMeta):
249
313
  accumulated_metrics: dict[str, float] = {}
250
314
  for engine in engines:
251
315
  current_inputs = await engine.on_pre_evaluate(self, ctx, current_inputs)
252
- result = await engine.evaluate(self, ctx, current_inputs)
316
+ use_batch_mode = bool(getattr(ctx, "is_batch", False))
317
+ try:
318
+ if use_batch_mode:
319
+ logger.debug(
320
+ "Agent %s: routing %d artifacts to %s.evaluate_batch",
321
+ self.name,
322
+ len(current_inputs.artifacts),
323
+ engine.__class__.__name__,
324
+ )
325
+ result = await engine.evaluate_batch(self, ctx, current_inputs)
326
+ else:
327
+ result = await engine.evaluate(self, ctx, current_inputs)
328
+ except NotImplementedError:
329
+ if use_batch_mode:
330
+ logger.exception(
331
+ "Agent %s: engine %s does not implement evaluate_batch()",
332
+ self.name,
333
+ engine.__class__.__name__,
334
+ )
335
+ raise
253
336
 
254
337
  # AUTO-WRAP: If engine returns BaseModel instead of EvalResult, wrap it
255
338
  from flock.runtime import EvalResult as ER
@@ -291,8 +374,21 @@ class Agent(metaclass=AutoTracedMeta):
291
374
  self, ctx: Context, inputs: EvalInputs, result: EvalResult
292
375
  ) -> EvalResult:
293
376
  current = result
294
- for component in self.utilities:
295
- current = await component.on_post_evaluate(self, ctx, inputs, current)
377
+ for component in self._sorted_utilities():
378
+ comp_name = self._component_display_name(component)
379
+ priority = getattr(component, "priority", 0)
380
+ logger.debug(
381
+ f"Agent post_evaluate: agent={self.name}, component={comp_name}, "
382
+ f"priority={priority}, artifact_count={len(current.artifacts)}"
383
+ )
384
+ try:
385
+ current = await component.on_post_evaluate(self, ctx, inputs, current)
386
+ except Exception as exc:
387
+ logger.exception(
388
+ f"Agent post_evaluate failed: agent={self.name}, component={comp_name}, "
389
+ f"priority={priority}, error={exc!s}"
390
+ )
391
+ raise
296
392
  return current
297
393
 
298
394
  async def _make_outputs(self, ctx: Context, result: EvalResult) -> list[Artifact]:
@@ -322,9 +418,23 @@ class Agent(metaclass=AutoTracedMeta):
322
418
  return produced
323
419
 
324
420
  async def _run_post_publish(self, ctx: Context, artifacts: Sequence[Artifact]) -> None:
421
+ components = self._sorted_utilities()
325
422
  for artifact in artifacts:
326
- for component in self.utilities:
327
- await component.on_post_publish(self, ctx, artifact)
423
+ for component in components:
424
+ comp_name = self._component_display_name(component)
425
+ priority = getattr(component, "priority", 0)
426
+ logger.debug(
427
+ f"Agent post_publish: agent={self.name}, component={comp_name}, "
428
+ f"priority={priority}, artifact_id={artifact.id}"
429
+ )
430
+ try:
431
+ await component.on_post_publish(self, ctx, artifact)
432
+ except Exception as exc:
433
+ logger.exception(
434
+ f"Agent post_publish failed: agent={self.name}, component={comp_name}, "
435
+ f"priority={priority}, artifact_id={artifact.id}, error={exc!s}"
436
+ )
437
+ raise
328
438
 
329
439
  async def _invoke_call(self, ctx: Context, artifacts: Sequence[Artifact]) -> None:
330
440
  func = self.calls_func
@@ -340,14 +450,39 @@ class Agent(metaclass=AutoTracedMeta):
340
450
  await maybe_coro
341
451
 
342
452
  async def _run_error(self, ctx: Context, error: Exception) -> None:
343
- for component in self.utilities:
344
- await component.on_error(self, ctx, error)
453
+ for component in self._sorted_utilities():
454
+ comp_name = self._component_display_name(component)
455
+ priority = getattr(component, "priority", 0)
456
+ logger.debug(
457
+ f"Agent error hook: agent={self.name}, component={comp_name}, "
458
+ f"priority={priority}, error={error!s}"
459
+ )
460
+ try:
461
+ await component.on_error(self, ctx, error)
462
+ except Exception as exc:
463
+ logger.exception(
464
+ f"Agent error hook failed: agent={self.name}, component={comp_name}, "
465
+ f"priority={priority}, original_error={error!s}, hook_error={exc!s}"
466
+ )
467
+ raise
345
468
  for engine in self.engines:
346
469
  await engine.on_error(self, ctx, error)
347
470
 
348
471
  async def _run_terminate(self, ctx: Context) -> None:
349
- for component in self.utilities:
350
- await component.on_terminate(self, ctx)
472
+ for component in self._sorted_utilities():
473
+ comp_name = self._component_display_name(component)
474
+ priority = getattr(component, "priority", 0)
475
+ logger.debug(
476
+ f"Agent terminate: agent={self.name}, component={comp_name}, priority={priority}"
477
+ )
478
+ try:
479
+ await component.on_terminate(self, ctx)
480
+ except Exception as exc:
481
+ logger.exception(
482
+ f"Agent terminate failed: agent={self.name}, component={comp_name}, "
483
+ f"priority={priority}, error={exc!s}"
484
+ )
485
+ raise
351
486
  for engine in self.engines:
352
487
  await engine.on_terminate(self, ctx)
353
488
 
@@ -377,7 +512,7 @@ class Agent(metaclass=AutoTracedMeta):
377
512
  return []
378
513
 
379
514
  default_component = OutputUtilityComponent()
380
- self.utilities = [default_component]
515
+ self._add_utilities([default_component])
381
516
  return self.utilities
382
517
 
383
518
  def _find_matching_artifact(
@@ -653,7 +788,8 @@ class AgentBuilder:
653
788
  - AgentComponent: Base class for custom components
654
789
  - Lifecycle hooks: on_initialize, on_pre_consume, on_post_publish, etc.
655
790
  """
656
- self._agent.utilities.extend(components)
791
+ if components:
792
+ self._agent._add_utilities(list(components))
657
793
  return self
658
794
 
659
795
  def with_engines(self, *engines: EngineComponent) -> AgentBuilder:
flock/components.py CHANGED
@@ -56,6 +56,10 @@ class AgentComponent(BaseModel, metaclass=TracedModelMeta):
56
56
 
57
57
  name: str | None = None
58
58
  config: AgentComponentConfig = Field(default_factory=AgentComponentConfig)
59
+ priority: int = Field(
60
+ default=0,
61
+ description="Execution priority (lower numbers run earlier; default preserves add order).",
62
+ )
59
63
 
60
64
  async def on_initialize(
61
65
  self, agent: Agent, ctx: Context
@@ -109,6 +113,38 @@ class EngineComponent(AgentComponent):
109
113
  """Override this method in your engine implementation."""
110
114
  raise NotImplementedError
111
115
 
116
+ async def evaluate_batch(self, agent: Agent, ctx: Context, inputs: EvalInputs) -> EvalResult:
117
+ """Process batch of accumulated artifacts (BatchSpec).
118
+
119
+ Override this method if your engine supports batch processing.
120
+
121
+ Args:
122
+ agent: Agent instance executing this engine
123
+ ctx: Execution context (ctx.is_batch will be True)
124
+ inputs: EvalInputs with inputs.artifacts containing batch items
125
+
126
+ Returns:
127
+ EvalResult with processed artifacts
128
+
129
+ Raises:
130
+ NotImplementedError: If engine doesn't support batching
131
+
132
+ Example:
133
+ >>> async def evaluate_batch(self, agent, ctx, inputs):
134
+ ... events = inputs.all_as(Event) # Get ALL items
135
+ ... results = await bulk_process(events)
136
+ ... return EvalResult.from_objects(*results, agent=agent)
137
+ """
138
+ raise NotImplementedError(
139
+ f"{self.__class__.__name__} does not support batch processing.\n\n"
140
+ f"To fix this:\n"
141
+ f"1. Remove BatchSpec from agent subscription, OR\n"
142
+ f"2. Implement evaluate_batch() in {self.__class__.__name__}, OR\n"
143
+ f"3. Use a batch-aware engine (e.g., CustomBatchEngine)\n\n"
144
+ f"Agent: {agent.name}\n"
145
+ f"Engine: {self.__class__.__name__}"
146
+ )
147
+
112
148
  async def fetch_conversation_context(
113
149
  self,
114
150
  ctx: Context,
@@ -98,6 +98,8 @@ class DashboardEventCollector(AgentComponent):
98
98
  Phase 3: Emits events via WebSocket using WebSocketManager.
99
99
  """
100
100
 
101
+ priority: int = -100 # Run before other agent utilities for event capture
102
+
101
103
  # Use PrivateAttr for non-Pydantic fields (AgentComponent extends BaseModel)
102
104
  _events: deque[
103
105
  AgentActivatedEvent | MessagePublishedEvent | AgentCompletedEvent | AgentErrorEvent