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 +153 -17
- flock/components.py +36 -0
- flock/dashboard/collector.py +2 -0
- flock/dashboard/static_v2/assets/index-DFRnI_mt.js +1 -1
- flock/dashboard/static_v2/index.html +3 -3
- flock/engines/dspy_engine.py +41 -3
- flock/engines/examples/__init__.py +6 -0
- flock/engines/examples/simple_batch_engine.py +61 -0
- flock/frontend/README.md +4 -4
- flock/frontend/docs/DESIGN_SYSTEM.md +1 -1
- flock/frontend/package-lock.json +2 -2
- flock/frontend/package.json +1 -1
- flock/frontend/src/components/settings/SettingsPanel.css +1 -1
- flock/frontend/src/components/settings/ThemeSelector.tsx +2 -2
- flock/frontend/src/services/indexeddb.ts +1 -1
- flock/frontend/src/styles/variables.css +1 -1
- flock/orchestrator.py +500 -140
- flock/orchestrator_component.py +686 -0
- flock/runtime.py +3 -0
- {flock_core-0.5.4.dist-info → flock_core-0.5.6.dist-info}/METADATA +69 -3
- {flock_core-0.5.4.dist-info → flock_core-0.5.6.dist-info}/RECORD +24 -21
- {flock_core-0.5.4.dist-info → flock_core-0.5.6.dist-info}/WHEEL +0 -0
- {flock_core-0.5.4.dist-info → flock_core-0.5.6.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.4.dist-info → flock_core-0.5.6.dist-info}/licenses/LICENSE +0 -0
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.
|
|
225
|
-
|
|
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.
|
|
232
|
-
|
|
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.
|
|
238
|
-
|
|
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
|
-
|
|
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.
|
|
295
|
-
|
|
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
|
|
327
|
-
|
|
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.
|
|
344
|
-
|
|
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.
|
|
350
|
-
|
|
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.
|
|
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
|
-
|
|
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,
|
flock/dashboard/collector.py
CHANGED
|
@@ -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
|