flock-core 0.4.0b22__py3-none-any.whl → 0.4.0b24__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.

Files changed (32) hide show
  1. flock/core/flock.py +24 -7
  2. flock/core/flock_agent.py +219 -75
  3. flock/core/flock_factory.py +8 -6
  4. flock/core/flock_registry.py +140 -60
  5. flock/core/flock_router.py +19 -6
  6. flock/core/serialization/serialization_utils.py +85 -19
  7. flock/core/util/input_resolver.py +5 -0
  8. flock/evaluators/declarative/declarative_evaluator.py +18 -10
  9. flock/evaluators/memory/memory_evaluator.py +2 -0
  10. flock/evaluators/test/test_case_evaluator.py +2 -0
  11. flock/evaluators/zep/zep_evaluator.py +2 -0
  12. flock/modules/assertion/assertion_module.py +286 -0
  13. flock/modules/callback/callback_module.py +2 -0
  14. flock/modules/memory/memory_module.py +2 -0
  15. flock/modules/output/output_module.py +2 -0
  16. flock/modules/performance/metrics_module.py +2 -0
  17. flock/modules/zep/zep_module.py +2 -0
  18. flock/routers/agent/agent_router.py +7 -5
  19. flock/routers/conditional/conditional_router.py +482 -0
  20. flock/routers/default/default_router.py +5 -1
  21. flock/routers/feedback/feedback_router.py +114 -0
  22. flock/routers/list_generator/list_generator_router.py +166 -0
  23. flock/routers/llm/llm_router.py +3 -1
  24. flock/workflow/activities.py +20 -1
  25. {flock_core-0.4.0b22.dist-info → flock_core-0.4.0b24.dist-info}/METADATA +2 -1
  26. {flock_core-0.4.0b22.dist-info → flock_core-0.4.0b24.dist-info}/RECORD +29 -28
  27. flock/evaluators/memory/azure_search_evaluator.py +0 -0
  28. flock/evaluators/natural_language/natural_language_evaluator.py +0 -66
  29. flock/modules/azure-search/azure_search_module.py +0 -0
  30. {flock_core-0.4.0b22.dist-info → flock_core-0.4.0b24.dist-info}/WHEEL +0 -0
  31. {flock_core-0.4.0b22.dist-info → flock_core-0.4.0b24.dist-info}/entry_points.txt +0 -0
  32. {flock_core-0.4.0b22.dist-info → flock_core-0.4.0b24.dist-info}/licenses/LICENSE +0 -0
flock/core/flock.py CHANGED
@@ -59,6 +59,10 @@ FlockRegistry = get_registry() # Get the registry instance
59
59
  # Define TypeVar for generic class methods like from_dict
60
60
  T = TypeVar("T", bound="Flock")
61
61
 
62
+ from rich.traceback import install
63
+
64
+ install(show_locals=True)
65
+
62
66
 
63
67
  class Flock(BaseModel, Serializable):
64
68
  """Orchestrator for managing and executing agent systems.
@@ -350,9 +354,13 @@ class Flock(BaseModel, Serializable):
350
354
  try:
351
355
  resolved_start_agent = self._agents.get(start_agent_name)
352
356
  if not resolved_start_agent:
353
- resolved_start_agent = FlockRegistry.get_agent(start_agent_name)
357
+ resolved_start_agent = FlockRegistry.get_agent(
358
+ start_agent_name
359
+ )
354
360
  if not resolved_start_agent:
355
- raise ValueError(f"Start agent '{start_agent_name}' not found.")
361
+ raise ValueError(
362
+ f"Start agent '{start_agent_name}' not found."
363
+ )
356
364
  self.add_agent(resolved_start_agent)
357
365
 
358
366
  run_context = context if context else FlockContext()
@@ -384,15 +392,20 @@ class Flock(BaseModel, Serializable):
384
392
 
385
393
  # Execute workflow
386
394
  if not self.enable_temporal:
387
- result = await run_local_workflow(run_context, box_result=False)
395
+ result = await run_local_workflow(
396
+ run_context, box_result=False
397
+ )
388
398
  else:
389
- result = await run_temporal_workflow(run_context, box_result=False)
399
+ result = await run_temporal_workflow(
400
+ run_context, box_result=False
401
+ )
390
402
 
391
403
  span.set_attribute("result.type", str(type(result)))
392
404
  result_str = str(result)
393
405
  span.set_attribute(
394
406
  "result.preview",
395
- result_str[:1000] + ("..." if len(result_str) > 1000 else ""),
407
+ result_str[:1000]
408
+ + ("..." if len(result_str) > 1000 else ""),
396
409
  )
397
410
 
398
411
  if box_result:
@@ -400,13 +413,17 @@ class Flock(BaseModel, Serializable):
400
413
  logger.debug("Boxing final result.")
401
414
  return Box(result)
402
415
  except ImportError:
403
- logger.warning("Box library not installed, returning raw dict.")
416
+ logger.warning(
417
+ "Box library not installed, returning raw dict."
418
+ )
404
419
  return result
405
420
  else:
406
421
  return result
407
422
 
408
423
  except Exception as e:
409
- logger.error(f"Flock run '{self.name}' failed: {e}", exc_info=True)
424
+ logger.error(
425
+ f"Flock run '{self.name}' failed: {e}", exc_info=True
426
+ )
410
427
  span.record_exception(e)
411
428
  span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))
412
429
  return {
flock/core/flock_agent.py CHANGED
@@ -23,9 +23,9 @@ from rich.console import Console
23
23
 
24
24
  # Core Flock components (ensure these are importable)
25
25
  from flock.core.context.context import FlockContext
26
- from flock.core.flock_evaluator import FlockEvaluator
27
- from flock.core.flock_module import FlockModule
28
- from flock.core.flock_router import FlockRouter
26
+ from flock.core.flock_evaluator import FlockEvaluator, FlockEvaluatorConfig
27
+ from flock.core.flock_module import FlockModule, FlockModuleConfig
28
+ from flock.core.flock_router import FlockRouter, FlockRouterConfig
29
29
  from flock.core.logging.logging import get_logger
30
30
 
31
31
  # Mixins and Serialization components
@@ -45,6 +45,15 @@ tracer = trace.get_tracer(__name__)
45
45
  T = TypeVar("T", bound="FlockAgent")
46
46
 
47
47
 
48
+ SignatureType = (
49
+ str
50
+ | Callable[..., str]
51
+ | type[BaseModel]
52
+ | Callable[..., type[BaseModel]]
53
+ | None
54
+ )
55
+
56
+
48
57
  # Make FlockAgent inherit from Serializable
49
58
  class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
50
59
  """Core, declarative base class for Flock agents, enabling serialization,
@@ -61,14 +70,14 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
61
70
  "",
62
71
  description="A human-readable description or a callable returning one.",
63
72
  )
64
- input: str | Callable[..., str] | None = Field(
73
+ input: SignatureType = Field(
65
74
  None,
66
75
  description=(
67
76
  "Signature for input keys. Supports type hints (:) and descriptions (|). "
68
77
  "E.g., 'query: str | Search query, context: dict | Conversation context'. Can be a callable."
69
78
  ),
70
79
  )
71
- output: str | Callable[..., str] | None = Field(
80
+ output: SignatureType = Field(
72
81
  None,
73
82
  description=(
74
83
  "Signature for output keys. Supports type hints (:) and descriptions (|). "
@@ -111,6 +120,43 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
111
120
  description="Runtime context associated with the flock execution.",
112
121
  )
113
122
 
123
+ def __init__(
124
+ self,
125
+ name: str,
126
+ model: str | None = None,
127
+ description: str | Callable[..., str] | None = "",
128
+ input: SignatureType = None,
129
+ output: SignatureType = None,
130
+ tools: list[Callable[..., Any]] | None = None,
131
+ evaluator: "FlockEvaluator | None" = None,
132
+ handoff_router: "FlockRouter | None" = None,
133
+ modules: dict[str, "FlockModule"] | None = None, # Use dict for modules
134
+ write_to_file: bool = False,
135
+ wait_for_input: bool = False,
136
+ **kwargs,
137
+ ):
138
+ super().__init__(
139
+ name=name,
140
+ model=model,
141
+ description=description,
142
+ input=input, # Store the raw input spec
143
+ output=output, # Store the raw output spec
144
+ tools=tools,
145
+ write_to_file=write_to_file,
146
+ wait_for_input=wait_for_input,
147
+ evaluator=evaluator,
148
+ handoff_router=handoff_router,
149
+ modules=modules
150
+ if modules is not None
151
+ else {}, # Ensure modules is a dict
152
+ **kwargs,
153
+ )
154
+
155
+ if isinstance(self.input, type) and issubclass(self.input, BaseModel):
156
+ self._input_model = self.input
157
+ if isinstance(self.output, type) and issubclass(self.output, BaseModel):
158
+ self._output_model = self.output
159
+
114
160
  # --- Existing Methods (add_module, remove_module, etc.) ---
115
161
  # (Keep these methods as they were, adding type hints where useful)
116
162
  def add_module(self, module: FlockModule) -> None:
@@ -359,6 +405,114 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
359
405
  span.record_exception(temporal_error)
360
406
  raise
361
407
 
408
+ def add_component(
409
+ self,
410
+ config_instance: FlockModuleConfig
411
+ | FlockRouterConfig
412
+ | FlockEvaluatorConfig,
413
+ component_name: str | None = None,
414
+ ) -> "FlockAgent":
415
+ """Adds or replaces a component (Evaluator, Router, Module) based on its configuration object.
416
+
417
+ Args:
418
+ config_instance: An instance of a config class inheriting from
419
+ FlockModuleConfig, FlockRouterConfig, or FlockEvaluatorConfig.
420
+ component_name: Explicit name for the component (required for Modules if not in config).
421
+
422
+ Returns:
423
+ self for potential chaining.
424
+ """
425
+ from flock.core.flock_registry import get_registry
426
+
427
+ config_type = type(config_instance)
428
+ registry = get_registry() # Get registry instance
429
+ logger.debug(
430
+ f"Attempting to add component via config: {config_type.__name__}"
431
+ )
432
+
433
+ # --- 1. Find Component Class using Registry Map ---
434
+ ComponentClass = registry.get_component_class_for_config(config_type)
435
+
436
+ if not ComponentClass:
437
+ logger.error(
438
+ f"No component class registered for config type {config_type.__name__}. Use @flock_component(config_class=...) on the component."
439
+ )
440
+ raise TypeError(
441
+ f"Cannot find component class for config {config_type.__name__}"
442
+ )
443
+
444
+ component_class_name = ComponentClass.__name__
445
+ logger.debug(
446
+ f"Found component class '{component_class_name}' mapped to config '{config_type.__name__}'"
447
+ )
448
+
449
+ # --- 2. Determine Assignment Target and Name (Same as before) ---
450
+ instance_name = component_name
451
+ attribute_name: str = ""
452
+
453
+ if issubclass(ComponentClass, FlockEvaluator):
454
+ attribute_name = "evaluator"
455
+ if not instance_name:
456
+ instance_name = getattr(
457
+ config_instance, "name", component_class_name.lower()
458
+ )
459
+
460
+ elif issubclass(ComponentClass, FlockRouter):
461
+ attribute_name = "handoff_router"
462
+ if not instance_name:
463
+ instance_name = getattr(
464
+ config_instance, "name", component_class_name.lower()
465
+ )
466
+
467
+ elif issubclass(ComponentClass, FlockModule):
468
+ attribute_name = "modules"
469
+ if not instance_name:
470
+ instance_name = getattr(
471
+ config_instance, "name", component_class_name.lower()
472
+ )
473
+ if not instance_name:
474
+ raise ValueError(
475
+ "Module name must be provided either in config or as component_name argument."
476
+ )
477
+ # Ensure config has name if module expects it
478
+ if hasattr(config_instance, "name") and not getattr(
479
+ config_instance, "name", None
480
+ ):
481
+ setattr(config_instance, "name", instance_name)
482
+
483
+ else: # Should be caught by registry map logic ideally
484
+ raise TypeError(
485
+ f"Class '{component_class_name}' mapped from config is not a valid Flock component."
486
+ )
487
+
488
+ # --- 3. Instantiate the Component (Same as before) ---
489
+ try:
490
+ init_args = {"config": config_instance, "name": instance_name}
491
+
492
+ component_instance = ComponentClass(**init_args)
493
+ except Exception as e:
494
+ logger.error(
495
+ f"Failed to instantiate {ComponentClass.__name__} with config {config_type.__name__}: {e}",
496
+ exc_info=True,
497
+ )
498
+ raise RuntimeError(f"Component instantiation failed: {e}") from e
499
+
500
+ # --- 4. Assign to the Agent (Same as before) ---
501
+ if attribute_name == "modules":
502
+ if not isinstance(self.modules, dict):
503
+ self.modules = {}
504
+ self.modules[instance_name] = component_instance
505
+ logger.info(
506
+ f"Added/Updated module '{instance_name}' (type: {ComponentClass.__name__}) to agent '{self.name}'"
507
+ )
508
+ else:
509
+ setattr(self, attribute_name, component_instance)
510
+ logger.info(
511
+ f"Set {attribute_name} to {ComponentClass.__name__} (instance name: '{instance_name}') for agent '{self.name}'"
512
+ )
513
+
514
+ return self
515
+
362
516
  # resolve_callables remains useful for dynamic definitions
363
517
  def resolve_callables(self, context: FlockContext | None = None) -> None:
364
518
  """Resolves callable fields (description, input, output) using context."""
@@ -428,81 +582,71 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
428
582
  exclude_none=True, # Exclude None values for cleaner output
429
583
  )
430
584
  logger.debug(f"Base agent data for '{self.name}': {list(data.keys())}")
585
+ serialized_modules = {}
586
+
587
+ def add_serialized_component(component: Any, field_name: str):
588
+ if component:
589
+ comp_type = type(component)
590
+ type_name = FlockRegistry.get_component_type_name(
591
+ comp_type
592
+ ) # Get registered name
593
+ if type_name:
594
+ try:
595
+ serialized_component_data = serialize_item(component)
596
+
597
+ if not isinstance(serialized_component_data, dict):
598
+ logger.error(
599
+ f"Serialization of component {type_name} for field '{field_name}' did not result in a dictionary. Got: {type(serialized_component_data)}"
600
+ )
601
+ serialized_modules[field_name] = {
602
+ "type": type_name,
603
+ "name": getattr(component, "name", "unknown"),
604
+ "error": "serialization_failed_non_dict",
605
+ }
606
+ else:
607
+ serialized_component_data["type"] = type_name
608
+ serialized_modules[field_name] = (
609
+ serialized_component_data
610
+ )
611
+ logger.debug(
612
+ f"Successfully serialized component for field '{field_name}' (type: {type_name})"
613
+ )
431
614
 
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
615
+ except Exception as e:
616
+ logger.error(
617
+ f"Failed to serialize component {type_name} for field '{field_name}': {e}",
618
+ exc_info=True,
490
619
  )
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
- )
620
+ serialized_modules[field_name] = {
621
+ "type": type_name,
622
+ "name": getattr(component, "name", "unknown"),
623
+ "error": "serialization_failed",
624
+ }
497
625
  else:
498
626
  logger.warning(
499
- f"Could not get registered type name for module {type(module_instance).__name__} ('{name}') in agent '{self.name}'. Skipping."
627
+ f"Cannot serialize unregistered component {comp_type.__name__} for field '{field_name}'"
500
628
  )
501
- if serialized_modules:
502
- data["modules"] = serialized_modules
503
- logger.debug(
504
- f"Added {len(serialized_modules)} modules to agent '{self.name}'"
505
- )
629
+
630
+ add_serialized_component(self.evaluator, "evaluator")
631
+ if serialized_modules:
632
+ data["evaluator"] = serialized_modules["evaluator"]
633
+ logger.debug(f"Added evaluator to agent '{self.name}'")
634
+
635
+ serialized_modules = {}
636
+ add_serialized_component(self.handoff_router, "handoff_router")
637
+ if serialized_modules:
638
+ data["handoff_router"] = serialized_modules["handoff_router"]
639
+ logger.debug(f"Added handoff_router to agent '{self.name}'")
640
+
641
+ serialized_modules = {}
642
+ for module in self.modules.values():
643
+ add_serialized_component(module, module.name)
644
+
645
+ if serialized_modules:
646
+ data["modules"] = serialized_modules
647
+ logger.debug(
648
+ f"Added {len(serialized_modules)} modules to agent '{self.name}'"
649
+ )
506
650
 
507
651
  # --- Serialize Tools (Callables) ---
508
652
  if self.tools:
@@ -3,7 +3,7 @@
3
3
  from collections.abc import Callable
4
4
  from typing import Any
5
5
 
6
- from flock.core.flock_agent import FlockAgent
6
+ from flock.core.flock_agent import FlockAgent, SignatureType
7
7
  from flock.core.logging.formatters.themes import OutputTheme
8
8
  from flock.evaluators.declarative.declarative_evaluator import (
9
9
  DeclarativeEvaluator,
@@ -24,15 +24,15 @@ class FlockFactory:
24
24
  name: str,
25
25
  description: str | Callable[..., str] | None = None,
26
26
  model: str | Callable[..., str] | None = None,
27
- input: str | Callable[..., str] | None = None,
28
- output: str | Callable[..., str] | None = None,
27
+ input: SignatureType = None,
28
+ output: SignatureType = None,
29
29
  tools: list[Callable[..., Any] | Any] | None = None,
30
30
  use_cache: bool = True,
31
31
  enable_rich_tables: bool = False,
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,
@@ -42,10 +42,12 @@ class FlockFactory:
42
42
  ) -> FlockAgent:
43
43
  """Creates a default FlockAgent.
44
44
 
45
- The default agent includes a declarative evaluator with the following modules:
45
+ The default agent includes the following modules:
46
+ - DeclarativeEvaluator
46
47
  - OutputModule
48
+ - MetricsModule
47
49
 
48
- It also includes often needed configurations like cache usage, rich tables, and output theme.
50
+ It also includes direct acces to the most important configurations.
49
51
  """
50
52
  eval_config = DeclarativeEvaluatorConfig(
51
53
  model=model,