flock-core 0.4.0b21__py3-none-any.whl → 0.4.0b23__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/core/flock_agent.py CHANGED
@@ -428,81 +428,71 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
428
428
  exclude_none=True, # Exclude None values for cleaner output
429
429
  )
430
430
  logger.debug(f"Base agent data for '{self.name}': {list(data.keys())}")
431
+ serialized_modules = {}
432
+
433
+ def add_serialized_component(component: Any, field_name: str):
434
+ if component:
435
+ comp_type = type(component)
436
+ type_name = FlockRegistry.get_component_type_name(
437
+ comp_type
438
+ ) # Get registered name
439
+ if type_name:
440
+ try:
441
+ serialized_component_data = serialize_item(component)
442
+
443
+ if not isinstance(serialized_component_data, dict):
444
+ logger.error(
445
+ f"Serialization of component {type_name} for field '{field_name}' did not result in a dictionary. Got: {type(serialized_component_data)}"
446
+ )
447
+ serialized_modules[field_name] = {
448
+ "type": type_name,
449
+ "name": getattr(component, "name", "unknown"),
450
+ "error": "serialization_failed_non_dict",
451
+ }
452
+ else:
453
+ serialized_component_data["type"] = type_name
454
+ serialized_modules[field_name] = (
455
+ serialized_component_data
456
+ )
457
+ logger.debug(
458
+ f"Successfully serialized component for field '{field_name}' (type: {type_name})"
459
+ )
431
460
 
432
- # --- Serialize Components using Registry Type Names ---
433
- # Evaluator
434
- if self.evaluator:
435
- logger.debug(f"Serializing evaluator for agent '{self.name}'")
436
- evaluator_type_name = FlockRegistry.get_component_type_name(
437
- type(self.evaluator)
438
- )
439
- if evaluator_type_name:
440
- # Recursively serialize the evaluator's dict representation
441
- evaluator_dict = serialize_item(
442
- self.evaluator.model_dump(mode="json", exclude_none=True)
443
- )
444
- evaluator_dict["type"] = evaluator_type_name # Add type marker
445
- data["evaluator"] = evaluator_dict
446
- logger.debug(
447
- f"Added evaluator of type '{evaluator_type_name}' to agent '{self.name}'"
448
- )
449
- else:
450
- logger.warning(
451
- f"Could not get registered type name for evaluator {type(self.evaluator).__name__} in agent '{self.name}'. Skipping serialization."
452
- )
453
-
454
- # Router
455
- if self.handoff_router:
456
- logger.debug(f"Serializing router for agent '{self.name}'")
457
- router_type_name = FlockRegistry.get_component_type_name(
458
- type(self.handoff_router)
459
- )
460
- if router_type_name:
461
- router_dict = serialize_item(
462
- self.handoff_router.model_dump(
463
- mode="json", exclude_none=True
464
- )
465
- )
466
- router_dict["type"] = router_type_name
467
- data["handoff_router"] = router_dict
468
- logger.debug(
469
- f"Added router of type '{router_type_name}' to agent '{self.name}'"
470
- )
471
- else:
472
- logger.warning(
473
- f"Could not get registered type name for router {type(self.handoff_router).__name__} in agent '{self.name}'. Skipping serialization."
474
- )
475
-
476
- # Modules
477
- if self.modules:
478
- logger.debug(
479
- f"Serializing {len(self.modules)} modules for agent '{self.name}'"
480
- )
481
- serialized_modules = {}
482
- for name, module_instance in self.modules.items():
483
- module_type_name = FlockRegistry.get_component_type_name(
484
- type(module_instance)
485
- )
486
- if module_type_name:
487
- module_dict = serialize_item(
488
- module_instance.model_dump(
489
- mode="json", exclude_none=True
461
+ except Exception as e:
462
+ logger.error(
463
+ f"Failed to serialize component {type_name} for field '{field_name}': {e}",
464
+ exc_info=True,
490
465
  )
491
- )
492
- module_dict["type"] = module_type_name
493
- serialized_modules[name] = module_dict
494
- logger.debug(
495
- f"Added module '{name}' of type '{module_type_name}' to agent '{self.name}'"
496
- )
466
+ serialized_modules[field_name] = {
467
+ "type": type_name,
468
+ "name": getattr(component, "name", "unknown"),
469
+ "error": "serialization_failed",
470
+ }
497
471
  else:
498
472
  logger.warning(
499
- f"Could not get registered type name for module {type(module_instance).__name__} ('{name}') in agent '{self.name}'. Skipping."
473
+ f"Cannot serialize unregistered component {comp_type.__name__} for field '{field_name}'"
500
474
  )
501
- if serialized_modules:
502
- data["modules"] = serialized_modules
503
- logger.debug(
504
- f"Added {len(serialized_modules)} modules to agent '{self.name}'"
505
- )
475
+
476
+ add_serialized_component(self.evaluator, "evaluator")
477
+ if serialized_modules:
478
+ data["evaluator"] = serialized_modules["evaluator"]
479
+ logger.debug(f"Added evaluator to agent '{self.name}'")
480
+
481
+ serialized_modules = {}
482
+ add_serialized_component(self.handoff_router, "handoff_router")
483
+ if serialized_modules:
484
+ data["handoff_router"] = serialized_modules["handoff_router"]
485
+ logger.debug(f"Added handoff_router to agent '{self.name}'")
486
+
487
+ serialized_modules = {}
488
+ for module in self.modules.values():
489
+ add_serialized_component(module, module.name)
490
+
491
+ if serialized_modules:
492
+ data["modules"] = serialized_modules
493
+ logger.debug(
494
+ f"Added {len(serialized_modules)} modules to agent '{self.name}'"
495
+ )
506
496
 
507
497
  # --- Serialize Tools (Callables) ---
508
498
  if self.tools:
@@ -32,7 +32,7 @@ class FlockFactory:
32
32
  output_theme: OutputTheme = OutputTheme.abernathy,
33
33
  wait_for_input: bool = False,
34
34
  temperature: float = 0.0,
35
- max_tokens: int = 4096,
35
+ max_tokens: int = 8192,
36
36
  alert_latency_threshold_ms: int = 30000,
37
37
  no_output: bool = False,
38
38
  print_context: bool = False,
@@ -5,6 +5,7 @@ within the Flock framework to support dynamic lookup and serialization.
5
5
 
6
6
  from __future__ import annotations # Add this at the very top
7
7
 
8
+ import builtins
8
9
  import importlib
9
10
  import inspect
10
11
  import sys
@@ -172,57 +173,88 @@ class FlockRegistry:
172
173
  )
173
174
  return None
174
175
 
175
- def get_callable(self, path_str: str) -> Callable:
176
- """Retrieves a callable by its path string, attempting dynamic import if not found."""
177
- if path_str in self._callables:
178
- logger.debug(f"Found callable '{path_str}' in registry")
179
- return self._callables[path_str]
176
+ def get_callable(self, name_or_path: str) -> Callable:
177
+ """Retrieves a callable by its registered name or full path string.
178
+ Attempts dynamic import if not found directly. Prioritizes exact match,
179
+ then searches for matches ending with '.{name}'.
180
+ """
181
+ # 1. Try exact match first (covers full paths and simple names if registered that way)
182
+ if name_or_path in self._callables:
183
+ logger.debug(
184
+ f"Found callable '{name_or_path}' directly in registry."
185
+ )
186
+ return self._callables[name_or_path]
187
+
188
+ # 2. If not found, and it looks like a simple name, search registered paths
189
+ if "." not in name_or_path:
190
+ matches = []
191
+ for path_str, func in self._callables.items():
192
+ # Check if path ends with ".{simple_name}" or exactly matches simple_name
193
+ if path_str == name_or_path or path_str.endswith(
194
+ f".{name_or_path}"
195
+ ):
196
+ matches.append(func)
180
197
 
181
- logger.debug(
182
- f"Callable '{path_str}' not in registry, attempting dynamic import."
183
- )
184
- try:
185
- if "." not in path_str: # Built-ins
186
- logger.debug(f"Trying to import built-in callable '{path_str}'")
187
- builtins_module = importlib.import_module("builtins")
188
- if hasattr(builtins_module, path_str):
189
- func = getattr(builtins_module, path_str)
190
- if callable(func):
191
- self.register_callable(func, path_str) # Cache it
192
- logger.info(
193
- f"Successfully imported built-in callable '{path_str}'"
194
- )
195
- return func
196
- logger.error(f"Built-in callable '{path_str}' not found.")
197
- raise KeyError(f"Built-in callable '{path_str}' not found.")
198
-
199
- logger.debug(f"Trying to import module callable '{path_str}'")
200
- module_name, func_name = path_str.rsplit(".", 1)
201
- module = importlib.import_module(module_name)
202
- func = getattr(module, func_name)
203
- if callable(func):
204
- self.register_callable(
205
- func, path_str
206
- ) # Cache dynamically imported
207
- logger.info(
208
- f"Successfully imported module callable '{path_str}'"
198
+ if len(matches) == 1:
199
+ logger.debug(
200
+ f"Found unique callable for simple name '{name_or_path}' via path '{self.get_callable_path_string(matches[0])}'."
209
201
  )
210
- return func
211
- else:
202
+ return matches[0]
203
+ elif len(matches) > 1:
204
+ # Ambiguous simple name - require full path
205
+ found_paths = [
206
+ self.get_callable_path_string(f) for f in matches
207
+ ]
212
208
  logger.error(
213
- f"Dynamically imported object '{path_str}' is not callable."
209
+ f"Ambiguous callable name '{name_or_path}'. Found matches: {found_paths}. Use full path string for lookup."
214
210
  )
215
- raise TypeError(
216
- f"Dynamically imported object '{path_str}' is not callable."
211
+ raise KeyError(
212
+ f"Ambiguous callable name '{name_or_path}'. Use full path string."
217
213
  )
218
- except (ImportError, AttributeError, KeyError, TypeError) as e:
219
- logger.error(
220
- f"Failed to dynamically load/find callable '{path_str}': {e}",
221
- exc_info=True,
214
+ # else: Not found by simple name search in registry, proceed to dynamic import
215
+
216
+ # 3. Attempt dynamic import if it looks like a full path
217
+ if "." in name_or_path:
218
+ logger.debug(
219
+ f"Callable '{name_or_path}' not in registry cache, attempting dynamic import."
222
220
  )
223
- raise KeyError(
224
- f"Callable '{path_str}' not found or failed to load: {e}"
225
- ) from e
221
+ try:
222
+ module_name, func_name = name_or_path.rsplit(".", 1)
223
+ module = importlib.import_module(module_name)
224
+ func = getattr(module, func_name)
225
+ if callable(func):
226
+ self.register_callable(
227
+ func, name_or_path
228
+ ) # Cache dynamically imported
229
+ logger.info(
230
+ f"Successfully imported and registered module callable '{name_or_path}'"
231
+ )
232
+ return func
233
+ else:
234
+ raise TypeError(
235
+ f"Dynamically imported object '{name_or_path}' is not callable."
236
+ )
237
+ except (ImportError, AttributeError, TypeError) as e:
238
+ logger.error(
239
+ f"Failed to dynamically load/find callable '{name_or_path}': {e}",
240
+ exc_info=False,
241
+ )
242
+ # Fall through to raise KeyError
243
+ # 4. Handle built-ins if not found yet (might be redundant if simple name check worked)
244
+ elif name_or_path in builtins.__dict__:
245
+ func = builtins.__dict__[name_or_path]
246
+ if callable(func):
247
+ self.register_callable(func, name_or_path) # Cache it
248
+ logger.info(
249
+ f"Found and registered built-in callable '{name_or_path}'"
250
+ )
251
+ return func
252
+
253
+ # 5. Final failure
254
+ logger.error(
255
+ f"Callable '{name_or_path}' not found in registry or via import."
256
+ )
257
+ raise KeyError(f"Callable '{name_or_path}' not found.")
226
258
 
227
259
  def get_callable_path_string(self, func: Callable) -> str | None:
228
260
  """Gets the path string for a callable, registering it if necessary."""
@@ -14,13 +14,26 @@ class HandOffRequest(BaseModel):
14
14
  next_agent: str = Field(default="", description="Next agent to invoke")
15
15
  # match = use the output fields of the current agent that also exists as input field of the next agent
16
16
  # add = add the output of the current agent to the input of the next agent
17
- hand_off_mode: Literal["match", "add"] = Field(default="match")
17
+ output_to_input_merge_strategy: Literal["match", "add"] = Field(
18
+ default="match"
19
+ )
20
+ add_input_fields: list[str] | None = Field(
21
+ default=None,
22
+ description="List of input fields to add to the next agent",
23
+ )
24
+ add_output_fields: list[str] | None = Field(
25
+ default=None,
26
+ description="List of output fields to add to the next agent",
27
+ )
28
+ add_description: str | None = Field(
29
+ default=None, description="Add this description to the next agent"
30
+ )
18
31
  override_next_agent: Any | None = Field(
19
32
  default=None,
20
33
  description="Override the next agent to hand off to",
21
34
  )
22
35
  override_context: FlockContext | None = Field(
23
- default=None, descrio="Override context parameters"
36
+ default=None, description="Override context parameters"
24
37
  )
25
38
 
26
39
 
@@ -34,10 +47,10 @@ class FlockRouterConfig(BaseModel):
34
47
  enabled: bool = Field(
35
48
  default=True, description="Whether the router is enabled"
36
49
  )
37
- agents: list[str] | None = Field(
38
- default=None,
39
- description="List of agents to choose from",
40
- )
50
+ # agents: list[str] | None = Field(
51
+ # default=None,
52
+ # description="List of agents to choose from",
53
+ # )
41
54
 
42
55
 
43
56
  class FlockRouter(BaseModel, ABC):
@@ -408,7 +408,7 @@ class DSPyIntegrationMixin:
408
408
 
409
409
  lm = dspy.settings.get("lm")
410
410
  cost = sum([x["cost"] for x in lm.history if x["cost"] is not None])
411
- lm_history = lm.inspect_history()
411
+ lm_history = lm.history
412
412
 
413
413
  return final_result, cost, lm_history
414
414
 
@@ -8,6 +8,7 @@ import sys
8
8
  import types
9
9
  import typing
10
10
  from collections.abc import Mapping, Sequence
11
+ from enum import Enum
11
12
  from typing import (
12
13
  TYPE_CHECKING,
13
14
  Any,
@@ -57,7 +58,9 @@ def _format_type_to_string(type_hint: type) -> str:
57
58
  inner_type = next(t for t in args if t is not type(None))
58
59
  return _format_type_to_string(inner_type)
59
60
  # return f"Optional[{_format_type_to_string(inner_type)}]"
60
- return f"Union[{', '.join(_format_type_to_string(arg) for arg in args)}]"
61
+ return (
62
+ f"Union[{', '.join(_format_type_to_string(arg) for arg in args)}]"
63
+ )
61
64
  elif origin is Literal:
62
65
  formatted_args = []
63
66
  for arg in args:
@@ -66,7 +69,9 @@ def _format_type_to_string(type_hint: type) -> str:
66
69
  else:
67
70
  formatted_args.append(str(arg))
68
71
  return f"Literal[{', '.join(formatted_args)}]"
69
- elif hasattr(type_hint, "__forward_arg__"): # Handle ForwardRefs if necessary
72
+ elif hasattr(
73
+ type_hint, "__forward_arg__"
74
+ ): # Handle ForwardRefs if necessary
70
75
  return type_hint.__forward_arg__
71
76
  elif hasattr(type_hint, "__name__"):
72
77
  # Handle custom types registered in registry (get preferred name)
@@ -83,7 +88,9 @@ def _format_type_to_string(type_hint: type) -> str:
83
88
  type_repr = str(type_hint).replace("typing.", "") # Basic cleanup
84
89
  type_repr = str(type_hint).replace("| None", "")
85
90
  type_repr = type_repr.strip()
86
- logger.debug(f"Using fallback string representation for type: {type_repr}")
91
+ logger.debug(
92
+ f"Using fallback string representation for type: {type_repr}"
93
+ )
87
94
  return type_repr
88
95
 
89
96
 
@@ -189,18 +196,51 @@ def serialize_item(item: Any) -> Any:
189
196
 
190
197
  FlockRegistry = get_registry()
191
198
 
192
- if isinstance(item, BaseModel):
193
- dumped = item.model_dump(mode="json", exclude_none=True)
194
- return serialize_item(dumped)
195
- elif callable(item) and not isinstance(item, type):
196
- path_str = FlockRegistry.get_callable_path_string(item) # Use registry helper
199
+ if callable(item) and not isinstance(item, type):
200
+ path_str = FlockRegistry.get_callable_path_string(
201
+ item
202
+ ) # Use registry helper
197
203
  if path_str:
198
- return {"__callable_ref__": path_str}
204
+ # Store the simple name (last part of the path) for cleaner YAML/JSON
205
+ simple_name = path_str.split(".")[-1]
206
+ logger.debug(
207
+ f"Serializing callable '{getattr(item, '__name__', 'unknown')}' as reference: '{simple_name}' (from path '{path_str}')"
208
+ )
209
+ return {
210
+ "__callable_ref__": simple_name
211
+ } # Use simple name convention
199
212
  else:
213
+ # Handle unregistered callables (e.g., lambdas defined inline)
214
+ # Option 1: Raise error (stricter)
215
+ # raise ValueError(f"Cannot serialize unregistered callable: {getattr(item, '__name__', item)}")
216
+ # Option 2: Store string representation with warning (more lenient)
200
217
  logger.warning(
201
- f"Could not get path string for callable {item}, storing as string."
218
+ f"Cannot serialize unregistered callable {getattr(item, '__name__', item)}, storing as string."
202
219
  )
203
220
  return str(item)
221
+ elif isinstance(item, BaseModel):
222
+ logger.debug(
223
+ f"Serializing Pydantic model instance: {item.__class__.__name__}"
224
+ )
225
+ serialized_dict = {}
226
+ # Iterate through defined fields in the model
227
+ fields_to_iterate = {}
228
+ if hasattr(item, "model_fields"): # Pydantic v2
229
+ fields_to_iterate = item.model_fields
230
+
231
+ for field_name in fields_to_iterate:
232
+ # Get the value *from the instance*
233
+ try:
234
+ value = getattr(item, field_name)
235
+ if value is not None: # Exclude None values
236
+ # Recursively serialize the field's value
237
+ serialized_dict[field_name] = serialize_item(value)
238
+ except AttributeError:
239
+ # Should not happen if iterating model_fields/__fields__ but handle defensively
240
+ logger.warning(
241
+ f"Attribute '{field_name}' not found on instance of {item.__class__.__name__} during serialization."
242
+ )
243
+ return serialized_dict
204
244
  elif isinstance(item, Mapping):
205
245
  return {key: serialize_item(value) for key, value in item.items()}
206
246
  elif isinstance(item, Sequence) and not isinstance(item, str):
@@ -218,8 +258,12 @@ def serialize_item(item: Any) -> Any:
218
258
  ) # Check regular types/classes by path
219
259
  if type_name:
220
260
  return {"__type_ref__": type_name}
221
- logger.warning(f"Could not serialize type object {item}, storing as string.")
261
+ logger.warning(
262
+ f"Could not serialize type object {item}, storing as string."
263
+ )
222
264
  return str(item)
265
+ elif isinstance(item, Enum):
266
+ return item.value
223
267
  else:
224
268
  # Return basic types as is
225
269
  return item
@@ -240,12 +284,24 @@ def deserialize_item(item: Any) -> Any:
240
284
 
241
285
  if isinstance(item, Mapping):
242
286
  if "__callable_ref__" in item and len(item) == 1:
243
- path_str = item["__callable_ref__"]
287
+ ref_name = item["__callable_ref__"]
244
288
  try:
245
- return FlockRegistry.get_callable(path_str)
289
+ # The registry's get_callable needs to handle lookup by simple name OR full path
290
+ # Or we assume get_callable handles finding the right function from the simple name
291
+ resolved_callable = FlockRegistry.get_callable(ref_name)
292
+ logger.debug(
293
+ f"Deserialized callable reference '{ref_name}' to {resolved_callable}"
294
+ )
295
+ return resolved_callable
246
296
  except KeyError:
247
297
  logger.error(
248
- f"Callable reference '{path_str}' not found during deserialization."
298
+ f"Callable reference '{ref_name}' not found during deserialization."
299
+ )
300
+ return None # Or raise?
301
+ except Exception as e:
302
+ logger.error(
303
+ f"Error resolving callable reference '{ref_name}': {e}",
304
+ exc_info=True,
249
305
  )
250
306
  return None
251
307
  elif "__component_ref__" in item and len(item) == 1:
@@ -273,7 +329,9 @@ def deserialize_item(item: Any) -> Any:
273
329
  mod = importlib.import_module(module_name)
274
330
  type_obj = getattr(mod, class_name)
275
331
  if isinstance(type_obj, type):
276
- FlockRegistry.register_type(type_obj, type_name) # Cache it
332
+ FlockRegistry.register_type(
333
+ type_obj, type_name
334
+ ) # Cache it
277
335
  return type_obj
278
336
  else:
279
337
  raise TypeError()
@@ -294,7 +352,9 @@ def deserialize_item(item: Any) -> Any:
294
352
 
295
353
 
296
354
  # --- Component Deserialization Helper ---
297
- def deserialize_component(data: dict | None, expected_base_type: type) -> Any | None:
355
+ def deserialize_component(
356
+ data: dict | None, expected_base_type: type
357
+ ) -> Any | None:
298
358
  """Deserializes a component (Module, Evaluator, Router) from its dict representation.
299
359
  Uses the 'type' field to find the correct class via FlockRegistry.
300
360
  """
@@ -306,10 +366,14 @@ def deserialize_component(data: dict | None, expected_base_type: type) -> Any |
306
366
  if data is None:
307
367
  return None
308
368
  if not isinstance(data, dict):
309
- logger.error(f"Expected dict for component deserialization, got {type(data)}")
369
+ logger.error(
370
+ f"Expected dict for component deserialization, got {type(data)}"
371
+ )
310
372
  return None
311
373
 
312
- type_name = data.get("type") # Assuming 'type' key holds the class name string
374
+ type_name = data.get(
375
+ "type"
376
+ ) # Assuming 'type' key holds the class name string
313
377
  if not type_name:
314
378
  logger.error(f"Component data missing 'type' field: {data}")
315
379
  return None
@@ -317,7 +381,9 @@ def deserialize_component(data: dict | None, expected_base_type: type) -> Any |
317
381
  try:
318
382
  ComponentClass = FlockRegistry.get_component(type_name) # Use registry
319
383
  # Optional: Keep the base type check
320
- if COMPONENT_BASE_TYPES and not issubclass(ComponentClass, expected_base_type):
384
+ if COMPONENT_BASE_TYPES and not issubclass(
385
+ ComponentClass, expected_base_type
386
+ ):
321
387
  raise TypeError(
322
388
  f"Deserialized class {type_name} is not a subclass of {expected_base_type.__name__}"
323
389
  )
@@ -152,6 +152,11 @@ def resolve_inputs(
152
152
  continue
153
153
 
154
154
  # Fallback to the initial input
155
+ var_value = context.get_variable(key)
156
+ if var_value is not None:
157
+ inputs[key] = var_value
158
+ continue
159
+
155
160
  inputs[key] = context.get_variable("flock." + key)
156
161
 
157
162
  # Case 2: A compound key (e.g., "agent_name.property" or "context.property")
@@ -2,7 +2,7 @@ from collections.abc import Generator
2
2
  from typing import Any
3
3
 
4
4
  import dspy
5
- from pydantic import Field
5
+ from pydantic import Field, PrivateAttr
6
6
  from rich.console import Console
7
7
 
8
8
  from flock.core.flock_agent import FlockAgent
@@ -35,7 +35,9 @@ class DeclarativeEvaluatorConfig(FlockEvaluatorConfig):
35
35
  kwargs: dict[str, Any] = Field(default_factory=dict)
36
36
 
37
37
 
38
- class DeclarativeEvaluator(FlockEvaluator, DSPyIntegrationMixin, PromptParserMixin):
38
+ class DeclarativeEvaluator(
39
+ FlockEvaluator, DSPyIntegrationMixin, PromptParserMixin
40
+ ):
39
41
  """Evaluator that uses DSPy for generation."""
40
42
 
41
43
  config: DeclarativeEvaluatorConfig = Field(
@@ -43,8 +45,8 @@ class DeclarativeEvaluator(FlockEvaluator, DSPyIntegrationMixin, PromptParserMix
43
45
  description="Evaluator configuration",
44
46
  )
45
47
 
46
- cost: float = 0.0
47
- lm_history: list = Field(default_factory=list)
48
+ _cost: float = PrivateAttr(default=0.0)
49
+ _lm_history: list = PrivateAttr(default_factory=list)
48
50
 
49
51
  async def evaluate(
50
52
  self, agent: FlockAgent, inputs: dict[str, Any], tools: list[Any]
@@ -89,10 +91,14 @@ class DeclarativeEvaluator(FlockEvaluator, DSPyIntegrationMixin, PromptParserMix
89
91
 
90
92
  # --- Conditional Evaluation (Stream vs No Stream) ---
91
93
  if self.config.stream:
92
- logger.info(f"Evaluating agent '{agent.name}' with async streaming.")
94
+ logger.info(
95
+ f"Evaluating agent '{agent.name}' with async streaming."
96
+ )
93
97
  if not callable(agent_task):
94
98
  logger.error("agent_task is not callable, cannot stream.")
95
- raise TypeError("DSPy task could not be created or is not callable.")
99
+ raise TypeError(
100
+ "DSPy task could not be created or is not callable."
101
+ )
96
102
 
97
103
  streaming_task = dspy.streamify(agent_task)
98
104
  stream_generator: Generator = streaming_task(**inputs)
@@ -112,9 +118,11 @@ class DeclarativeEvaluator(FlockEvaluator, DSPyIntegrationMixin, PromptParserMix
112
118
  if delta_content:
113
119
  console.print(delta_content, end="")
114
120
 
115
- result_dict, cost, lm_history = self._process_result(chunk, inputs)
116
- self.cost = cost
117
- self.lm_history = lm_history
121
+ result_dict, cost, lm_history = self._process_result(
122
+ chunk, inputs
123
+ )
124
+ self._cost = cost
125
+ self._lm_history = lm_history
118
126
 
119
127
  console.print("\n")
120
128
  return self.filter_thought_process(
@@ -126,9 +134,11 @@ class DeclarativeEvaluator(FlockEvaluator, DSPyIntegrationMixin, PromptParserMix
126
134
  try:
127
135
  # Ensure the call is awaited if the underlying task is async
128
136
  result_obj = agent_task(**inputs)
129
- result_dict, cost, lm_history = self._process_result(result_obj, inputs)
130
- self.cost = cost
131
- self.lm_history = lm_history
137
+ result_dict, cost, lm_history = self._process_result(
138
+ result_obj, inputs
139
+ )
140
+ self._cost = cost
141
+ self._lm_history = lm_history
132
142
  return self.filter_thought_process(
133
143
  result_dict, self.config.include_thought_process
134
144
  )
@@ -140,7 +150,7 @@ class DeclarativeEvaluator(FlockEvaluator, DSPyIntegrationMixin, PromptParserMix
140
150
  raise RuntimeError(f"Evaluation failed: {e}") from e
141
151
 
142
152
  def filter_thought_process(
143
- result_dict: dict[str, Any], include_thought_process: bool
153
+ self, result_dict: dict[str, Any], include_thought_process: bool
144
154
  ) -> dict[str, Any]:
145
155
  """Filter out thought process from the result dictionary."""
146
156
  if include_thought_process: