flock-core 0.4.0b25__py3-none-any.whl → 0.4.0b27__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
@@ -10,6 +10,7 @@ from datetime import datetime
10
10
  from typing import TYPE_CHECKING, Any, TypeVar
11
11
 
12
12
  from flock.core.serialization.json_encoder import FlockJSONEncoder
13
+ from flock.workflow.temporal_config import TemporalActivityConfig
13
14
 
14
15
  if TYPE_CHECKING:
15
16
  from flock.core.context.context import FlockContext
@@ -19,7 +20,6 @@ if TYPE_CHECKING:
19
20
 
20
21
  from opentelemetry import trace
21
22
  from pydantic import BaseModel, Field
22
- 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
@@ -38,8 +38,6 @@ from flock.core.serialization.serialization_utils import (
38
38
  serialize_item,
39
39
  )
40
40
 
41
- console = Console()
42
-
43
41
  logger = get_logger("agent")
44
42
  tracer = trace.get_tracer(__name__)
45
43
  T = TypeVar("T", bound="FlockAgent")
@@ -113,6 +111,12 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
113
111
  description="Dictionary of FlockModules attached to this agent.",
114
112
  )
115
113
 
114
+ # --- Temporal Configuration (Optional) ---
115
+ temporal_activity_config: TemporalActivityConfig | None = Field(
116
+ default=None,
117
+ description="Optional Temporal settings specific to this agent's activity execution.",
118
+ )
119
+
116
120
  # --- Runtime State (Excluded from Serialization) ---
117
121
  context: FlockContext | None = Field(
118
122
  default=None,
@@ -133,6 +137,7 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
133
137
  modules: dict[str, "FlockModule"] | None = None, # Use dict for modules
134
138
  write_to_file: bool = False,
135
139
  wait_for_input: bool = False,
140
+ temporal_activity_config: TemporalActivityConfig | None = None,
136
141
  **kwargs,
137
142
  ):
138
143
  super().__init__(
@@ -149,6 +154,7 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
149
154
  modules=modules
150
155
  if modules is not None
151
156
  else {}, # Ensure modules is a dict
157
+ temporal_activity_config=temporal_activity_config,
152
158
  **kwargs,
153
159
  )
154
160
 
@@ -230,8 +236,7 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
230
236
 
231
237
  if self.write_to_file:
232
238
  self._save_output(self.name, result)
233
- if self.wait_for_input:
234
- console.input(prompt="Press Enter to continue...")
239
+
235
240
  except Exception as module_error:
236
241
  logger.error(
237
242
  "Error during terminate",
@@ -727,202 +732,196 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
727
732
 
728
733
  @classmethod
729
734
  def from_dict(cls: type[T], data: dict[str, Any]) -> T:
730
- """Create instance from dictionary representation."""
731
- from flock.core.flock_registry import get_registry
732
-
733
- logger.debug(
734
- f"Deserializing agent from dict. Provided keys: {list(data.keys())}"
735
+ """Deserialize the agent from a dictionary, including components, tools, and callables."""
736
+ from flock.core.flock_registry import (
737
+ get_registry, # Import registry locally
735
738
  )
736
- if "name" not in data:
737
- raise ValueError("Agent data must include a 'name' field.")
738
- FlockRegistry = get_registry()
739
- agent_name = data["name"] # For logging context
740
- logger.info(f"Deserializing agent '{agent_name}'")
741
-
742
- # Pop complex components to handle them after basic agent instantiation
743
- evaluator_data = data.pop("evaluator", None)
744
- router_data = data.pop("handoff_router", None)
745
- modules_data = data.pop("modules", {})
746
- tools_data = data.pop("tools", [])
747
- description_callable = data.pop("description_callable", None)
748
- input_callable = data.pop("input_callable", None)
749
- output_callable = data.pop("output_callable", None)
750
739
 
740
+ registry = get_registry()
751
741
  logger.debug(
752
- f"Agent '{agent_name}' has {len(modules_data)} modules and {len(tools_data)} tools"
742
+ f"Deserializing agent from dict. Keys: {list(data.keys())}"
753
743
  )
754
744
 
755
- # Deserialize remaining data recursively (handles nested basic types/callables)
756
- # Note: Pydantic v2 handles most basic deserialization well if types match.
757
- # Explicit deserialize_item might be needed if complex non-pydantic structures exist.
758
- # For now, assume Pydantic handles basic fields based on type hints.
759
- deserialized_basic_data = data # Assume Pydantic handles basic fields
760
-
761
- try:
762
- # Create the agent instance using Pydantic's constructor
763
- logger.debug(
764
- f"Creating agent instance with fields: {list(deserialized_basic_data.keys())}"
765
- )
766
- agent = cls(**deserialized_basic_data)
767
- except Exception as e:
768
- logger.error(
769
- f"Pydantic validation/init failed for agent '{agent_name}': {e}",
770
- exc_info=True,
771
- )
745
+ # --- Separate Data ---
746
+ component_configs = {}
747
+ callable_configs = {}
748
+ tool_config = []
749
+ agent_data = {}
750
+
751
+ component_keys = [
752
+ "evaluator",
753
+ "handoff_router",
754
+ "modules",
755
+ "temporal_activity_config",
756
+ ]
757
+ callable_keys = [
758
+ "description_callable",
759
+ "input_callable",
760
+ "output_callable",
761
+ ]
762
+ tool_key = "tools"
763
+
764
+ for key, value in data.items():
765
+ if key in component_keys and value is not None:
766
+ component_configs[key] = value
767
+ elif key in callable_keys and value is not None:
768
+ callable_configs[key] = value
769
+ elif key == tool_key and value is not None:
770
+ tool_config = value # Expecting a list of names
771
+ elif key not in component_keys + callable_keys + [
772
+ tool_key
773
+ ]: # Avoid double adding
774
+ agent_data[key] = value
775
+ # else: ignore keys that are None or already handled
776
+
777
+ # --- Deserialize Base Agent ---
778
+ # Ensure required fields like 'name' are present if needed by __init__
779
+ if "name" not in agent_data:
772
780
  raise ValueError(
773
- f"Failed to initialize agent '{agent_name}' from dict: {e}"
774
- ) from e
781
+ "Agent data must include a 'name' field for deserialization."
782
+ )
783
+ agent_name_log = agent_data["name"] # For logging
784
+ logger.info(f"Deserializing base agent data for '{agent_name_log}'")
785
+
786
+ # Pydantic should handle base fields based on type hints in __init__
787
+ agent = cls(**agent_data)
788
+ logger.debug(f"Base agent '{agent.name}' instantiated.")
775
789
 
776
- # --- Deserialize and Attach Components ---
790
+ # --- Deserialize Components ---
791
+ logger.debug(f"Deserializing components for '{agent.name}'")
777
792
  # Evaluator
778
- if evaluator_data:
793
+ if "evaluator" in component_configs:
779
794
  try:
780
- logger.debug(
781
- f"Deserializing evaluator for agent '{agent_name}'"
782
- )
783
795
  agent.evaluator = deserialize_component(
784
- evaluator_data, FlockEvaluator
785
- )
786
- if agent.evaluator is None:
787
- raise ValueError("deserialize_component returned None")
788
- logger.debug(
789
- f"Deserialized evaluator '{agent.evaluator.name}' of type '{evaluator_data.get('type')}' for agent '{agent_name}'"
796
+ component_configs["evaluator"], FlockEvaluator
790
797
  )
798
+ logger.debug(f"Deserialized evaluator for '{agent.name}'")
791
799
  except Exception as e:
792
800
  logger.error(
793
- f"Failed to deserialize evaluator for agent '{agent_name}': {e}",
801
+ f"Failed to deserialize evaluator for '{agent.name}': {e}",
794
802
  exc_info=True,
795
803
  )
796
- # Decide: raise error or continue without evaluator?
797
- # raise ValueError(f"Failed to deserialize evaluator for agent '{agent_name}': {e}") from e
798
804
 
799
- # Router
800
- if router_data:
805
+ # Handoff Router
806
+ if "handoff_router" in component_configs:
801
807
  try:
802
- logger.debug(f"Deserializing router for agent '{agent_name}'")
803
808
  agent.handoff_router = deserialize_component(
804
- router_data, FlockRouter
805
- )
806
- if agent.handoff_router is None:
807
- raise ValueError("deserialize_component returned None")
808
- logger.debug(
809
- f"Deserialized router '{agent.handoff_router.name}' of type '{router_data.get('type')}' for agent '{agent_name}'"
809
+ component_configs["handoff_router"], FlockRouter
810
810
  )
811
+ logger.debug(f"Deserialized handoff_router for '{agent.name}'")
811
812
  except Exception as e:
812
813
  logger.error(
813
- f"Failed to deserialize router for agent '{agent_name}': {e}",
814
+ f"Failed to deserialize handoff_router for '{agent.name}': {e}",
814
815
  exc_info=True,
815
816
  )
816
- # Decide: raise error or continue without router?
817
817
 
818
818
  # Modules
819
- if modules_data:
820
- agent.modules = {} # Ensure it's initialized
821
- logger.debug(
822
- f"Deserializing {len(modules_data)} modules for agent '{agent_name}'"
823
- )
824
- for name, module_data in modules_data.items():
819
+ if "modules" in component_configs:
820
+ agent.modules = {} # Initialize
821
+ for module_name, module_data in component_configs[
822
+ "modules"
823
+ ].items():
825
824
  try:
826
- logger.debug(
827
- f"Deserializing module '{name}' of type '{module_data.get('type')}' for agent '{agent_name}'"
828
- )
829
825
  module_instance = deserialize_component(
830
826
  module_data, FlockModule
831
827
  )
832
828
  if module_instance:
833
- # Ensure instance name matches key if possible
834
- module_instance.name = module_data.get("name", name)
835
- agent.add_module(
836
- module_instance
837
- ) # Use add_module for consistency
829
+ # Use add_module for potential logic within it
830
+ agent.add_module(module_instance)
838
831
  logger.debug(
839
- f"Successfully added module '{name}' to agent '{agent_name}'"
832
+ f"Deserialized and added module '{module_name}' for '{agent.name}'"
840
833
  )
841
- else:
842
- raise ValueError("deserialize_component returned None")
843
834
  except Exception as e:
844
835
  logger.error(
845
- f"Failed to deserialize module '{name}' for agent '{agent_name}': {e}",
836
+ f"Failed to deserialize module '{module_name}' for '{agent.name}': {e}",
846
837
  exc_info=True,
847
838
  )
848
- # Decide: skip module or raise error?
839
+
840
+ # Temporal Activity Config
841
+ if "temporal_activity_config" in component_configs:
842
+ try:
843
+ agent.temporal_activity_config = TemporalActivityConfig(
844
+ **component_configs["temporal_activity_config"]
845
+ )
846
+ logger.debug(
847
+ f"Deserialized temporal_activity_config for '{agent.name}'"
848
+ )
849
+ except Exception as e:
850
+ logger.error(
851
+ f"Failed to deserialize temporal_activity_config for '{agent.name}': {e}",
852
+ exc_info=True,
853
+ )
854
+ agent.temporal_activity_config = None
849
855
 
850
856
  # --- Deserialize Tools ---
851
857
  agent.tools = [] # Initialize tools list
852
- if tools_data:
853
- # Get component registry to look up function imports
854
- registry = get_registry()
855
- components = getattr(registry, "_callables", {})
858
+ if tool_config:
856
859
  logger.debug(
857
- f"Deserializing {len(tools_data)} tools for agent '{agent_name}'"
860
+ f"Deserializing {len(tool_config)} tools for '{agent.name}'"
858
861
  )
859
- logger.debug(
860
- f"Available callables in registry: {list(components.keys())}"
861
- )
862
-
863
- for tool_name in tools_data:
862
+ # Use get_callable to find each tool
863
+ for tool_name_or_path in tool_config:
864
864
  try:
865
- logger.debug(f"Looking for tool '{tool_name}' in registry")
866
- # First try to lookup by simple name in the registry's callables
867
- found = False
868
- for path_str, func in components.items():
869
- if (
870
- path_str.endswith("." + tool_name)
871
- or path_str == tool_name
872
- ):
873
- agent.tools.append(func)
874
- found = True
875
- logger.info(
876
- f"Found tool '{tool_name}' via path '{path_str}' for agent '{agent_name}'"
877
- )
878
- break
879
-
880
- # If not found by simple name, try manual import
881
- if not found:
865
+ found_tool = registry.get_callable(tool_name_or_path)
866
+ if found_tool and callable(found_tool):
867
+ agent.tools.append(found_tool)
882
868
  logger.debug(
883
- f"Attempting to import tool '{tool_name}' from modules"
869
+ f"Resolved and added tool '{tool_name_or_path}' for agent '{agent.name}'"
884
870
  )
885
- # Check in relevant modules (could be customized based on project structure)
886
- import __main__
887
-
888
- if hasattr(__main__, tool_name):
889
- agent.tools.append(getattr(__main__, tool_name))
890
- found = True
891
- logger.info(
892
- f"Found tool '{tool_name}' in __main__ module for agent '{agent_name}'"
893
- )
894
-
895
- if not found:
871
+ else:
872
+ # Should not happen if get_callable returns successfully but just in case
896
873
  logger.warning(
897
- f"Could not find tool '{tool_name}' for agent '{agent_name}'"
874
+ f"Registry returned non-callable for tool '{tool_name_or_path}' for agent '{agent.name}'. Skipping."
898
875
  )
876
+ except (
877
+ ValueError
878
+ ) as e: # get_callable raises ValueError if not found/ambiguous
879
+ logger.warning(
880
+ f"Could not resolve tool '{tool_name_or_path}' for agent '{agent.name}': {e}. Skipping."
881
+ )
899
882
  except Exception as e:
900
883
  logger.error(
901
- f"Error adding tool '{tool_name}' to agent '{agent_name}': {e}",
884
+ f"Unexpected error resolving tool '{tool_name_or_path}' for agent '{agent.name}': {e}. Skipping.",
902
885
  exc_info=True,
903
886
  )
904
887
 
905
- if description_callable:
906
- logger.debug(
907
- f"Deserializing description callable '{description_callable}' for agent '{agent_name}'"
908
- )
909
- agent.description = components[description_callable]
888
+ # --- Deserialize Callables ---
889
+ logger.debug(f"Deserializing callable fields for '{agent.name}'")
890
+ # available_callables = registry.get_all_callables() # Incorrect
910
891
 
911
- if input_callable:
912
- logger.debug(
913
- f"Deserializing input callable '{input_callable}' for agent '{agent_name}'"
914
- )
915
- agent.input = components[input_callable]
892
+ def resolve_and_assign(field_name: str, callable_key: str):
893
+ if callable_key in callable_configs:
894
+ callable_name = callable_configs[callable_key]
895
+ try:
896
+ # Use get_callable to find the signature function
897
+ found_callable = registry.get_callable(callable_name)
898
+ if found_callable and callable(found_callable):
899
+ setattr(agent, field_name, found_callable)
900
+ logger.debug(
901
+ f"Resolved callable '{callable_name}' for field '{field_name}' on agent '{agent.name}'"
902
+ )
903
+ else:
904
+ logger.warning(
905
+ f"Registry returned non-callable for name '{callable_name}' for field '{field_name}' on agent '{agent.name}'. Field remains default."
906
+ )
907
+ except (
908
+ ValueError
909
+ ) as e: # get_callable raises ValueError if not found/ambiguous
910
+ logger.warning(
911
+ f"Could not resolve callable '{callable_name}' in registry for field '{field_name}' on agent '{agent.name}': {e}. Field remains default."
912
+ )
913
+ except Exception as e:
914
+ logger.error(
915
+ f"Unexpected error resolving callable '{callable_name}' for field '{field_name}' on agent '{agent.name}': {e}. Field remains default.",
916
+ exc_info=True,
917
+ )
918
+ # Else: key not present, field retains its default value from __init__
916
919
 
917
- if output_callable:
918
- logger.debug(
919
- f"Deserializing output callable '{output_callable}' for agent '{agent_name}'"
920
- )
921
- agent.output = components[output_callable]
920
+ resolve_and_assign("description", "description_callable")
921
+ resolve_and_assign("input", "input_callable")
922
+ resolve_and_assign("output", "output_callable")
922
923
 
923
- logger.info(
924
- f"Successfully deserialized agent '{agent_name}' with {len(agent.modules)} modules and {len(agent.tools)} tools"
925
- )
924
+ logger.info(f"Successfully deserialized agent '{agent.name}'.")
926
925
  return agent
927
926
 
928
927
  # --- Pydantic v2 Configuration ---
@@ -14,6 +14,7 @@ from flock.modules.performance.metrics_module import (
14
14
  MetricsModule,
15
15
  MetricsModuleConfig,
16
16
  )
17
+ from flock.workflow.temporal_config import TemporalActivityConfig
17
18
 
18
19
 
19
20
  class FlockFactory:
@@ -39,6 +40,7 @@ class FlockFactory:
39
40
  write_to_file: bool = False,
40
41
  stream: bool = False,
41
42
  include_thought_process: bool = False,
43
+ temporal_activity_config: TemporalActivityConfig | None = None,
42
44
  ) -> FlockAgent:
43
45
  """Creates a default FlockAgent.
44
46
 
@@ -69,6 +71,7 @@ class FlockFactory:
69
71
  evaluator=evaluator,
70
72
  write_to_file=write_to_file,
71
73
  wait_for_input=wait_for_input,
74
+ temporal_activity_config=temporal_activity_config,
72
75
  )
73
76
  output_config = OutputModuleConfig(
74
77
  render_table=enable_rich_tables,
@@ -1,15 +1,10 @@
1
1
  from importlib.metadata import PackageNotFoundError, version
2
2
 
3
- from rich.console import Console
4
- from rich.syntax import Text
5
-
6
3
  try:
7
4
  __version__ = version("flock-core")
8
5
  except PackageNotFoundError:
9
6
  __version__ = "0.2.0"
10
7
 
11
- console = Console()
12
-
13
8
 
14
9
  def display_hummingbird():
15
10
  """Display the hummingbird."""
@@ -45,6 +40,10 @@ def display_hummingbird():
45
40
 
46
41
  def init_console(clear_screen: bool = True, show_banner: bool = True):
47
42
  """Display the Flock banner."""
43
+ from rich.console import Console
44
+ from rich.syntax import Text
45
+
46
+ console = Console()
48
47
  banner_text = Text(
49
48
  f"""
50
49
  🦆 🐓 🐤 🐧
@@ -70,6 +69,10 @@ def init_console(clear_screen: bool = True, show_banner: bool = True):
70
69
 
71
70
  def display_banner_no_version():
72
71
  """Display the Flock banner."""
72
+ from rich.console import Console
73
+ from rich.syntax import Text
74
+
75
+ console = Console()
73
76
  banner_text = Text(
74
77
  """
75
78
  🦆 🐓 🐤 🐧
@@ -1,9 +1,12 @@
1
1
  from collections.abc import Generator
2
2
  from typing import Any
3
3
 
4
- import dspy
4
+ from temporalio import workflow
5
+
6
+ with workflow.unsafe.imports_passed_through():
7
+ import dspy
8
+
5
9
  from pydantic import Field, PrivateAttr
6
- from rich.console import Console
7
10
 
8
11
  from flock.core.flock_agent import FlockAgent
9
12
  from flock.core.flock_evaluator import FlockEvaluator, FlockEvaluatorConfig
@@ -12,8 +15,6 @@ from flock.core.logging.logging import get_logger
12
15
  from flock.core.mixin.dspy_integration import DSPyIntegrationMixin
13
16
  from flock.core.mixin.prompt_parser import PromptParserMixin
14
17
 
15
- console = Console()
16
-
17
18
  logger = get_logger("evaluators.declarative")
18
19
 
19
20
 
@@ -56,6 +57,9 @@ class DeclarativeEvaluator(
56
57
  """Evaluate using DSPy, with optional asynchronous streaming."""
57
58
  # --- Setup Signature and LM ---
58
59
  try:
60
+ from rich.console import Console
61
+
62
+ console = Console()
59
63
  _dspy_signature = self.create_dspy_signature_class(
60
64
  agent.name,
61
65
  agent.description,
@@ -24,6 +24,9 @@ class MetricPoint(BaseModel):
24
24
  value: int | float | str
25
25
  tags: dict[str, str] = {}
26
26
 
27
+ class Config:
28
+ arbitrary_types_allowed = True
29
+
27
30
 
28
31
  class MetricsModuleConfig(FlockModuleConfig):
29
32
  """Configuration for performance metrics collection."""