flock-core 0.4.528__py3-none-any.whl → 0.5.0b0__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 (130) hide show
  1. flock/cli/execute_flock.py +1 -1
  2. flock/cli/manage_agents.py +6 -6
  3. flock/components/__init__.py +30 -0
  4. flock/components/evaluation/__init__.py +9 -0
  5. flock/components/evaluation/declarative_evaluation_component.py +222 -0
  6. flock/components/routing/__init__.py +15 -0
  7. flock/{routers/conditional/conditional_router.py → components/routing/conditional_routing_component.py} +61 -53
  8. flock/components/routing/default_routing_component.py +103 -0
  9. flock/components/routing/llm_routing_component.py +206 -0
  10. flock/components/utility/__init__.py +15 -0
  11. flock/{modules/enterprise_memory/enterprise_memory_module.py → components/utility/memory_utility_component.py} +195 -173
  12. flock/{modules/performance/metrics_module.py → components/utility/metrics_utility_component.py} +110 -95
  13. flock/{modules/output/output_module.py → components/utility/output_utility_component.py} +47 -45
  14. flock/core/__init__.py +26 -18
  15. flock/core/agent/__init__.py +16 -0
  16. flock/core/agent/flock_agent_components.py +104 -0
  17. flock/core/agent/flock_agent_execution.py +101 -0
  18. flock/core/agent/flock_agent_integration.py +206 -0
  19. flock/core/agent/flock_agent_lifecycle.py +177 -0
  20. flock/core/agent/flock_agent_serialization.py +381 -0
  21. flock/core/api/endpoints.py +2 -2
  22. flock/core/api/service.py +2 -2
  23. flock/core/component/__init__.py +15 -0
  24. flock/core/{flock_module.py → component/agent_component_base.py} +136 -34
  25. flock/core/component/evaluation_component.py +56 -0
  26. flock/core/component/routing_component.py +74 -0
  27. flock/core/component/utility_component.py +69 -0
  28. flock/core/config/flock_agent_config.py +49 -2
  29. flock/core/evaluation/utils.py +3 -2
  30. flock/core/execution/batch_executor.py +1 -1
  31. flock/core/execution/evaluation_executor.py +2 -2
  32. flock/core/execution/opik_executor.py +1 -1
  33. flock/core/flock.py +147 -493
  34. flock/core/flock_agent.py +195 -1032
  35. flock/core/flock_factory.py +114 -90
  36. flock/core/flock_scheduler.py +1 -1
  37. flock/core/flock_server_manager.py +8 -8
  38. flock/core/logging/logging.py +1 -0
  39. flock/core/mcp/flock_mcp_server.py +53 -48
  40. flock/core/mcp/{flock_mcp_tool_base.py → flock_mcp_tool.py} +2 -2
  41. flock/core/mcp/mcp_client.py +9 -9
  42. flock/core/mcp/mcp_client_manager.py +9 -9
  43. flock/core/mcp/mcp_config.py +24 -24
  44. flock/core/mixin/dspy_integration.py +5 -5
  45. flock/core/orchestration/__init__.py +18 -0
  46. flock/core/orchestration/flock_batch_processor.py +94 -0
  47. flock/core/orchestration/flock_evaluator.py +113 -0
  48. flock/core/orchestration/flock_execution.py +288 -0
  49. flock/core/orchestration/flock_initialization.py +125 -0
  50. flock/core/orchestration/flock_server_manager.py +67 -0
  51. flock/core/orchestration/flock_web_server.py +117 -0
  52. flock/core/registry/__init__.py +45 -0
  53. flock/core/registry/agent_registry.py +69 -0
  54. flock/core/registry/callable_registry.py +139 -0
  55. flock/core/registry/component_discovery.py +142 -0
  56. flock/core/registry/component_registry.py +64 -0
  57. flock/core/registry/config_mapping.py +64 -0
  58. flock/core/registry/decorators.py +137 -0
  59. flock/core/registry/registry_hub.py +205 -0
  60. flock/core/registry/server_registry.py +57 -0
  61. flock/core/registry/type_registry.py +86 -0
  62. flock/core/serialization/flock_serializer.py +36 -32
  63. flock/core/serialization/serialization_utils.py +28 -25
  64. flock/core/util/hydrator.py +1 -1
  65. flock/core/util/input_resolver.py +29 -2
  66. flock/mcp/servers/sse/flock_sse_server.py +10 -10
  67. flock/mcp/servers/stdio/flock_stdio_server.py +10 -10
  68. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +10 -10
  69. flock/mcp/servers/websockets/flock_websocket_server.py +10 -10
  70. flock/platform/docker_tools.py +3 -3
  71. flock/webapp/app/chat.py +1 -1
  72. flock/webapp/app/main.py +9 -5
  73. flock/webapp/app/services/flock_service.py +1 -1
  74. flock/webapp/app/services/sharing_store.py +1 -0
  75. flock/workflow/activities.py +67 -92
  76. flock/workflow/agent_execution_activity.py +6 -6
  77. flock/workflow/flock_workflow.py +1 -1
  78. flock_core-0.5.0b0.dist-info/METADATA +272 -0
  79. {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/RECORD +82 -95
  80. flock/core/flock_evaluator.py +0 -60
  81. flock/core/flock_registry.py +0 -702
  82. flock/core/flock_router.py +0 -83
  83. flock/evaluators/__init__.py +0 -1
  84. flock/evaluators/declarative/__init__.py +0 -1
  85. flock/evaluators/declarative/declarative_evaluator.py +0 -217
  86. flock/evaluators/memory/memory_evaluator.py +0 -90
  87. flock/evaluators/test/test_case_evaluator.py +0 -38
  88. flock/evaluators/zep/zep_evaluator.py +0 -59
  89. flock/modules/__init__.py +0 -1
  90. flock/modules/assertion/__init__.py +0 -1
  91. flock/modules/assertion/assertion_module.py +0 -286
  92. flock/modules/callback/__init__.py +0 -1
  93. flock/modules/callback/callback_module.py +0 -91
  94. flock/modules/enterprise_memory/README.md +0 -99
  95. flock/modules/mem0/__init__.py +0 -1
  96. flock/modules/mem0/mem0_module.py +0 -126
  97. flock/modules/mem0_async/__init__.py +0 -1
  98. flock/modules/mem0_async/async_mem0_module.py +0 -126
  99. flock/modules/memory/__init__.py +0 -1
  100. flock/modules/memory/memory_module.py +0 -429
  101. flock/modules/memory/memory_parser.py +0 -125
  102. flock/modules/memory/memory_storage.py +0 -736
  103. flock/modules/output/__init__.py +0 -1
  104. flock/modules/performance/__init__.py +0 -1
  105. flock/modules/zep/__init__.py +0 -1
  106. flock/modules/zep/zep_module.py +0 -192
  107. flock/routers/__init__.py +0 -1
  108. flock/routers/agent/__init__.py +0 -1
  109. flock/routers/agent/agent_router.py +0 -236
  110. flock/routers/agent/handoff_agent.py +0 -58
  111. flock/routers/default/__init__.py +0 -1
  112. flock/routers/default/default_router.py +0 -80
  113. flock/routers/feedback/feedback_router.py +0 -114
  114. flock/routers/list_generator/list_generator_router.py +0 -166
  115. flock/routers/llm/__init__.py +0 -1
  116. flock/routers/llm/llm_router.py +0 -365
  117. flock/tools/__init__.py +0 -0
  118. flock/tools/azure_tools.py +0 -781
  119. flock/tools/code_tools.py +0 -167
  120. flock/tools/file_tools.py +0 -149
  121. flock/tools/github_tools.py +0 -157
  122. flock/tools/markdown_tools.py +0 -205
  123. flock/tools/system_tools.py +0 -9
  124. flock/tools/text_tools.py +0 -810
  125. flock/tools/web_tools.py +0 -90
  126. flock/tools/zendesk_tools.py +0 -147
  127. flock_core-0.4.528.dist-info/METADATA +0 -675
  128. {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/WHEEL +0 -0
  129. {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/entry_points.txt +0 -0
  130. {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,117 @@
1
+ # src/flock/core/orchestration/flock_web_server.py
2
+ """Web server and CLI management functionality for Flock orchestrator."""
3
+
4
+ from collections.abc import Callable, Sequence
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from flock.core.logging.logging import get_logger
8
+
9
+ if TYPE_CHECKING:
10
+ from flock.core.api.custom_endpoint import FlockEndpoint
11
+ from flock.core.flock import Flock
12
+ from flock.core.flock_agent import FlockAgent
13
+
14
+ logger = get_logger("flock.web_server")
15
+
16
+
17
+ class FlockWebServer:
18
+ """Handles web server and CLI functionality for Flock orchestrator."""
19
+
20
+ def __init__(self, flock: "Flock"):
21
+ self.flock = flock
22
+
23
+ def serve(
24
+ self,
25
+ host: str = "127.0.0.1",
26
+ port: int = 8344,
27
+ server_name: str = "Flock Server",
28
+ ui: bool = True,
29
+ chat: bool = False,
30
+ chat_agent: str | None = None,
31
+ chat_message_key: str = "message",
32
+ chat_history_key: str = "history",
33
+ chat_response_key: str = "response",
34
+ ui_theme: str | None = None,
35
+ custom_endpoints: Sequence["FlockEndpoint"]
36
+ | dict[tuple[str, list[str] | None], Callable[..., Any]]
37
+ | None = None,
38
+ ) -> None:
39
+ """Launch an HTTP server that exposes the core REST API and, optionally, the browser-based UI."""
40
+ try:
41
+ from flock.webapp.run import start_unified_server
42
+ except ImportError:
43
+ logger.error(
44
+ "Web application components not found (flock.webapp.run). "
45
+ "Cannot start HTTP server. Ensure webapp dependencies are installed."
46
+ )
47
+ return
48
+
49
+ logger.info(
50
+ f"Attempting to start server for Flock '{self.flock.name}' on {host}:{port}. UI enabled: {ui}"
51
+ )
52
+
53
+ start_unified_server(
54
+ flock_instance=self.flock,
55
+ host=host,
56
+ port=port,
57
+ server_title=server_name,
58
+ enable_ui_routes=ui,
59
+ enable_chat_routes=chat,
60
+ ui_theme=ui_theme,
61
+ custom_endpoints=custom_endpoints,
62
+ )
63
+
64
+ def start_api(
65
+ self,
66
+ host: str = "127.0.0.1",
67
+ port: int = 8344,
68
+ server_name: str = "Flock Server",
69
+ create_ui: bool = True,
70
+ ui_theme: str | None = None,
71
+ custom_endpoints: Sequence["FlockEndpoint"]
72
+ | dict[tuple[str, list[str] | None], Callable[..., Any]]
73
+ | None = None,
74
+ ) -> None:
75
+ """Deprecated: Use serve() instead."""
76
+ import warnings
77
+
78
+ warnings.warn(
79
+ "start_api() is deprecated and will be removed in a future release. "
80
+ "Use serve() instead.",
81
+ DeprecationWarning,
82
+ stacklevel=2,
83
+ )
84
+ # Delegate to the new serve() method (create_ui maps to ui)
85
+ return self.serve(
86
+ host=host,
87
+ port=port,
88
+ server_name=server_name,
89
+ ui=create_ui,
90
+ ui_theme=ui_theme,
91
+ custom_endpoints=custom_endpoints,
92
+ )
93
+
94
+ def start_cli(
95
+ self,
96
+ start_agent: "FlockAgent | str | None" = None,
97
+ server_name: str = "Flock CLI",
98
+ show_results: bool = False,
99
+ edit_mode: bool = False,
100
+ ) -> None:
101
+ """Starts an interactive CLI for this Flock instance."""
102
+ try:
103
+ from flock.cli.runner import start_flock_cli
104
+ except ImportError:
105
+ logger.error(
106
+ "CLI components not found. Cannot start CLI. "
107
+ "Ensure CLI dependencies are installed."
108
+ )
109
+ return
110
+
111
+ logger.info(f"Starting CLI for Flock '{self.flock.name}'...")
112
+ start_flock_cli(
113
+ flock=self.flock, # Pass the Flock instance
114
+ server_name=server_name,
115
+ show_results=show_results,
116
+ edit_mode=edit_mode,
117
+ )
@@ -0,0 +1,45 @@
1
+ # src/flock/core/registry/__init__.py
2
+ """Modern thread-safe registry system using composition pattern.
3
+
4
+ This module provides a complete refactor of the FlockRegistry using
5
+ the proven composition pattern from Flock and FlockAgent refactoring.
6
+ """
7
+
8
+
9
+ from flock.core.registry.agent_registry import AgentRegistry
10
+ from flock.core.registry.callable_registry import CallableRegistry
11
+ from flock.core.registry.component_discovery import ComponentDiscovery
12
+
13
+ # Specialized registry components (for advanced usage)
14
+ from flock.core.registry.component_registry import ComponentRegistry
15
+ from flock.core.registry.config_mapping import ConfigMapping
16
+ from flock.core.registry.decorators import (
17
+ flock_callable,
18
+ flock_component,
19
+ flock_tool,
20
+ flock_type,
21
+ )
22
+ from flock.core.registry.registry_hub import RegistryHub, get_registry
23
+ from flock.core.registry.server_registry import ServerRegistry
24
+ from flock.core.registry.type_registry import TypeRegistry
25
+
26
+ __all__ = [
27
+ # Main API
28
+ "RegistryHub",
29
+ "get_registry",
30
+
31
+ # Decorators
32
+ "flock_component",
33
+ "flock_tool",
34
+ "flock_callable",
35
+ "flock_type",
36
+
37
+ # Specialized registries (for advanced usage)
38
+ "ComponentRegistry",
39
+ "CallableRegistry",
40
+ "AgentRegistry",
41
+ "ServerRegistry",
42
+ "TypeRegistry",
43
+ "ConfigMapping",
44
+ "ComponentDiscovery",
45
+ ]
@@ -0,0 +1,69 @@
1
+ # src/flock/core/registry/agent_registry.py
2
+ """Agent instance registration and lookup functionality."""
3
+
4
+ import threading
5
+ from typing import TYPE_CHECKING
6
+
7
+ from flock.core.logging.logging import get_logger
8
+
9
+ if TYPE_CHECKING:
10
+ from flock.core.flock_agent import FlockAgent
11
+
12
+ logger = get_logger("registry.agents")
13
+
14
+
15
+ class AgentRegistry:
16
+ """Manages FlockAgent instance registration and lookup with thread safety."""
17
+
18
+ def __init__(self, lock: threading.RLock):
19
+ self._lock = lock
20
+ self._agents: dict[str, "FlockAgent"] = {}
21
+
22
+ def register_agent(self, agent: "FlockAgent", *, force: bool = False) -> None:
23
+ """Register a FlockAgent instance by its name.
24
+
25
+ Args:
26
+ agent: The agent instance to register.
27
+ force: If True, allow overwriting an existing **different** agent registered under the same name.
28
+ If False and a conflicting registration exists, a ValueError is raised.
29
+ """
30
+ if not hasattr(agent, "name") or not agent.name:
31
+ logger.error("Attempted to register an agent without a valid 'name' attribute.")
32
+ return
33
+
34
+ with self._lock:
35
+ if agent.name in self._agents and self._agents[agent.name] is not agent:
36
+ # Same agent already registered → silently ignore; different instance → error/force.
37
+ if not force:
38
+ raise ValueError(
39
+ f"Agent '{agent.name}' already registered with a different instance. "
40
+ "Pass force=True to overwrite the existing registration."
41
+ )
42
+ logger.warning(f"Overwriting existing agent '{agent.name}' registration due to force=True.")
43
+
44
+ self._agents[agent.name] = agent
45
+ logger.debug(f"Registered agent: {agent.name}")
46
+
47
+ def get_agent(self, name: str) -> "FlockAgent | None":
48
+ """Retrieve a registered FlockAgent instance by name."""
49
+ with self._lock:
50
+ agent = self._agents.get(name)
51
+ if not agent:
52
+ logger.warning(f"Agent '{name}' not found in registry.")
53
+ return agent
54
+
55
+ def get_all_agent_names(self) -> list[str]:
56
+ """Return a list of names of all registered agents."""
57
+ with self._lock:
58
+ return list(self._agents.keys())
59
+
60
+ def get_all_agents(self) -> dict[str, "FlockAgent"]:
61
+ """Get all registered agents."""
62
+ with self._lock:
63
+ return self._agents.copy()
64
+
65
+ def clear(self) -> None:
66
+ """Clear all registered agents."""
67
+ with self._lock:
68
+ self._agents.clear()
69
+ logger.debug("Cleared all registered agents")
@@ -0,0 +1,139 @@
1
+ # src/flock/core/registry/callable_registry.py
2
+ """Callable function/method registration and lookup functionality."""
3
+
4
+ import builtins
5
+ import importlib
6
+ import threading
7
+ from collections.abc import Callable
8
+ from typing import Any
9
+
10
+ from flock.core.logging.logging import get_logger
11
+
12
+ logger = get_logger("registry.callables")
13
+
14
+
15
+ class CallableRegistry:
16
+ """Manages callable registration with smart lookup and thread safety."""
17
+
18
+ def __init__(self, lock: threading.RLock):
19
+ self._lock = lock
20
+ self._callables: dict[str, Callable] = {}
21
+
22
+ def register_callable(self, func: Callable, name: str | None = None) -> str | None:
23
+ """Register a callable (function/method). Returns its path string identifier."""
24
+ path_str = name or self._get_path_string(func)
25
+ if not path_str:
26
+ logger.warning(f"Could not register callable {getattr(func, '__name__', 'unknown')}: Unable to determine path string")
27
+ return None
28
+
29
+ with self._lock:
30
+ if path_str in self._callables and self._callables[path_str] != func:
31
+ logger.warning(f"Callable '{path_str}' already registered with a different function. Overwriting.")
32
+
33
+ self._callables[path_str] = func
34
+ logger.debug(f"Registered callable: '{path_str}' ({getattr(func, '__name__', 'unknown')})")
35
+ return path_str
36
+
37
+ def get_callable(self, name_or_path: str) -> Callable:
38
+ """Retrieve a callable by its registered name or full path string.
39
+
40
+ Attempts dynamic import if not found directly. Prioritizes exact match,
41
+ then searches for matches ending with '.{name}'.
42
+ """
43
+ # 1. Try exact match first (covers full paths and simple names if registered that way)
44
+ with self._lock:
45
+ if name_or_path in self._callables:
46
+ logger.debug(f"Found callable '{name_or_path}' directly in registry.")
47
+ return self._callables[name_or_path]
48
+
49
+ # 2. If not found, and it looks like a simple name, search registered paths
50
+ if "." not in name_or_path:
51
+ with self._lock:
52
+ matches = []
53
+ for path_str, func in self._callables.items():
54
+ # Check if path ends with ".{simple_name}" or exactly matches simple_name
55
+ if path_str == name_or_path or path_str.endswith(f".{name_or_path}"):
56
+ matches.append(func)
57
+
58
+ if len(matches) == 1:
59
+ logger.debug(f"Found unique callable for simple name '{name_or_path}' via path '{self.get_callable_path_string(matches[0])}'.")
60
+ return matches[0]
61
+ elif len(matches) > 1:
62
+ # Ambiguous simple name - require full path
63
+ found_paths = [self.get_callable_path_string(f) for f in matches]
64
+ logger.error(f"Ambiguous callable name '{name_or_path}'. Found matches: {found_paths}. Use full path string for lookup.")
65
+ raise KeyError(f"Ambiguous callable name '{name_or_path}'. Use full path string.")
66
+
67
+ # 3. Attempt dynamic import if it looks like a full path
68
+ if "." in name_or_path:
69
+ logger.debug(f"Callable '{name_or_path}' not in registry cache, attempting dynamic import.")
70
+ try:
71
+ module_name, func_name = name_or_path.rsplit(".", 1)
72
+ module = importlib.import_module(module_name)
73
+ func = getattr(module, func_name)
74
+ if callable(func):
75
+ self.register_callable(func, name_or_path) # Cache dynamically imported
76
+ logger.info(f"Successfully imported and registered module callable '{name_or_path}'")
77
+ return func
78
+ else:
79
+ raise TypeError(f"Dynamically imported object '{name_or_path}' is not callable.")
80
+ except (ImportError, AttributeError, TypeError) as e:
81
+ logger.error(f"Failed to dynamically load/find callable '{name_or_path}': {e}", exc_info=False)
82
+
83
+ # 4. Handle built-ins if not found yet (might be redundant if simple name check worked)
84
+ elif name_or_path in builtins.__dict__:
85
+ func = builtins.__dict__[name_or_path]
86
+ if callable(func):
87
+ self.register_callable(func, name_or_path) # Cache it
88
+ logger.info(f"Found and registered built-in callable '{name_or_path}'")
89
+ return func
90
+
91
+ # 5. Final failure
92
+ logger.error(f"Callable '{name_or_path}' not found in registry or via import.")
93
+ raise KeyError(f"Callable '{name_or_path}' not found.")
94
+
95
+ def get_callable_path_string(self, func: Callable) -> str | None:
96
+ """Get the path string for a callable, registering it if necessary."""
97
+ # First try to find by direct identity
98
+ with self._lock:
99
+ for path_str, registered_func in self._callables.items():
100
+ if func == registered_func:
101
+ logger.debug(f"Found existing path string for callable: '{path_str}'")
102
+ return path_str
103
+
104
+ # If not found by identity, generate path, register, and return
105
+ path_str = self.register_callable(func)
106
+ if path_str:
107
+ logger.debug(f"Generated and registered new path string for callable: '{path_str}'")
108
+ else:
109
+ logger.warning(f"Failed to generate path string for callable {getattr(func, '__name__', 'unknown')}")
110
+
111
+ return path_str
112
+
113
+ def get_all_callables(self) -> dict[str, Callable]:
114
+ """Get all registered callables."""
115
+ with self._lock:
116
+ return self._callables.copy()
117
+
118
+ def clear(self) -> None:
119
+ """Clear all registered callables."""
120
+ with self._lock:
121
+ self._callables.clear()
122
+ logger.debug("Cleared all registered callables")
123
+
124
+ @staticmethod
125
+ def _get_path_string(obj: Callable | type) -> str | None:
126
+ """Generate a unique path string 'module.ClassName' or 'module.function_name'."""
127
+ try:
128
+ module = obj.__module__
129
+ name = obj.__name__
130
+ if module == "builtins":
131
+ return name
132
+ # Check if it's nested (basic check, might not cover all edge cases)
133
+ if "." in name and hasattr(__import__(module).__dict__, name.split(".")[0]):
134
+ # Likely a nested class/method - serialization might need custom handling or pickle
135
+ logger.warning(f"Object {name} appears nested in {module}. Path string might be ambiguous.")
136
+ return f"{module}.{name}"
137
+ except AttributeError:
138
+ logger.warning(f"Could not determine module/name for object: {obj}")
139
+ return None
@@ -0,0 +1,142 @@
1
+ # src/flock/core/registry/component_discovery.py
2
+ """Component discovery and auto-registration functionality."""
3
+
4
+ import importlib
5
+ import importlib.util
6
+ import inspect
7
+ import os
8
+ import pkgutil
9
+ import threading
10
+ from dataclasses import is_dataclass
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ from pydantic import BaseModel
14
+ from flock.core.logging.logging import get_logger
15
+
16
+ if TYPE_CHECKING:
17
+ from flock.core.registry.registry_hub import RegistryHub
18
+ from flock.core.component.agent_component_base import AgentComponent
19
+
20
+ logger = get_logger("registry.discovery")
21
+
22
+
23
+ class ComponentDiscovery:
24
+ """Handles automatic component discovery and registration."""
25
+
26
+ def __init__(self, registry_hub: "RegistryHub"):
27
+ self.registry_hub = registry_hub
28
+ self._packages_to_scan = [
29
+ "flock.tools",
30
+ "flock.components", # Updated to use unified components
31
+ ]
32
+
33
+ def discover_and_register_components(self) -> None:
34
+ """Auto-register components from known core packages."""
35
+ for package_name in self._packages_to_scan:
36
+ try:
37
+ package_spec = importlib.util.find_spec(package_name)
38
+ if package_spec and package_spec.origin:
39
+ package_path_list = [os.path.dirname(package_spec.origin)]
40
+ logger.info(f"Recursively scanning for modules in package: {package_name} (path: {package_path_list[0]})")
41
+
42
+ # Use walk_packages to recursively find all modules
43
+ for module_loader, module_name, is_pkg in pkgutil.walk_packages(
44
+ path=package_path_list,
45
+ prefix=package_name + ".", # Ensures module_name is fully qualified
46
+ onerror=lambda name: logger.warning(f"Error importing module {name} during scan.")
47
+ ):
48
+ if not is_pkg and not module_name.split('.')[-1].startswith("_"):
49
+ # We are interested in actual modules, not sub-packages themselves for registration
50
+ # And also skip modules starting with underscore (e.g. __main__.py)
51
+ try:
52
+ logger.debug(f"Attempting to auto-register components from module: {module_name}")
53
+ self.register_module_components(module_name)
54
+ except ImportError as e:
55
+ logger.warning(
56
+ f"Could not auto-register from {module_name}: Module not found or import error: {e}"
57
+ )
58
+ except Exception as e: # Catch other potential errors during registration
59
+ logger.error(
60
+ f"Unexpected error during auto-registration of {module_name}: {e}",
61
+ exc_info=True
62
+ )
63
+ else:
64
+ logger.warning(f"Could not find package spec for '{package_name}' to auto-register components/tools.")
65
+ except Exception as e:
66
+ logger.error(f"Error while trying to dynamically register from '{package_name}': {e}", exc_info=True)
67
+
68
+ def register_module_components(self, module_or_path: Any) -> None:
69
+ """Scan a module (object or path string) and automatically register.
70
+
71
+ - Functions as callables.
72
+ - Pydantic Models and Dataclasses as types.
73
+ - Subclasses of AgentComponent as components.
74
+ """
75
+ try:
76
+ if isinstance(module_or_path, str):
77
+ module = importlib.import_module(module_or_path)
78
+ elif inspect.ismodule(module_or_path):
79
+ module = module_or_path
80
+ else:
81
+ logger.error(
82
+ f"Invalid input for auto-registration: {module_or_path}. Must be module object or path string."
83
+ )
84
+ return
85
+
86
+ logger.info(f"Auto-registering components from module: {module.__name__}")
87
+ registered_count = {"callable": 0, "type": 0, "component": 0}
88
+
89
+ for name, obj in inspect.getmembers(module):
90
+ if name.startswith("_"):
91
+ continue # Skip private/internal
92
+
93
+ # Register Functions as Callables
94
+ if (
95
+ inspect.isfunction(obj)
96
+ and obj.__module__ == module.__name__
97
+ ):
98
+ if self.registry_hub.callables.register_callable(obj):
99
+ registered_count["callable"] += 1
100
+
101
+ # Register Classes (Types and Components)
102
+ elif inspect.isclass(obj) and obj.__module__ == module.__name__:
103
+ is_component = False
104
+
105
+ # Register as Component if subclass of AgentComponent
106
+ try:
107
+ from flock.core.component.agent_component_base import AgentComponent
108
+ if (
109
+ issubclass(obj, AgentComponent)
110
+ and self.registry_hub.components.register_component(obj)
111
+ ):
112
+ registered_count["component"] += 1
113
+ is_component = True # Mark as component
114
+ except ImportError:
115
+ # AgentComponent not available during setup
116
+ pass
117
+
118
+ # Register as Type if Pydantic Model or Dataclass
119
+ # A component can also be a type used in signatures
120
+ base_model_or_dataclass = isinstance(obj, type) and (
121
+ issubclass(obj, BaseModel) or is_dataclass(obj)
122
+ )
123
+ if (
124
+ base_model_or_dataclass
125
+ and self.registry_hub.types.register_type(obj)
126
+ and not is_component
127
+ ):
128
+ # Only increment type count if it wasn't already counted as component
129
+ registered_count["type"] += 1
130
+
131
+ logger.info(
132
+ f"Auto-registration summary for {module.__name__}: "
133
+ f"{registered_count['callable']} callables, "
134
+ f"{registered_count['type']} types, "
135
+ f"{registered_count['component']} components."
136
+ )
137
+
138
+ except Exception as e:
139
+ logger.error(
140
+ f"Error during auto-registration for {module_or_path}: {e}",
141
+ exc_info=True,
142
+ )
@@ -0,0 +1,64 @@
1
+ # src/flock/core/registry/component_registry.py
2
+ """Component class registration and lookup functionality."""
3
+
4
+ import threading
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from flock.core.logging.logging import get_logger
8
+
9
+ if TYPE_CHECKING:
10
+ from flock.core.component.agent_component_base import AgentComponent
11
+
12
+ logger = get_logger("registry.components")
13
+
14
+
15
+ class ComponentRegistry:
16
+ """Manages component class registration and lookup with thread safety."""
17
+
18
+ def __init__(self, lock: threading.RLock):
19
+ self._lock = lock
20
+ self._components: dict[str, type] = {}
21
+
22
+ def register_component(self, component_class: type, name: str | None = None) -> str | None:
23
+ """Register a component class (evaluation, routing, utility components)."""
24
+ type_name = name or component_class.__name__
25
+ if not type_name:
26
+ logger.error(f"Could not determine name for component class: {component_class}")
27
+ return None
28
+
29
+ with self._lock:
30
+ if type_name in self._components and self._components[type_name] != component_class:
31
+ logger.warning(f"Component class '{type_name}' already registered. Overwriting.")
32
+
33
+ self._components[type_name] = component_class
34
+ logger.debug(f"Registered component class: {type_name}")
35
+ return type_name
36
+
37
+ def get_component(self, type_name: str) -> type:
38
+ """Retrieve a component class by its type name."""
39
+ with self._lock:
40
+ if type_name in self._components:
41
+ return self._components[type_name]
42
+
43
+ logger.error(f"Component class '{type_name}' not found in registry.")
44
+ raise KeyError(f"Component class '{type_name}' not found. Ensure it is registered.")
45
+
46
+ def get_component_type_name(self, component_class: type) -> str | None:
47
+ """Get the type name for a component class, registering it if necessary."""
48
+ with self._lock:
49
+ for type_name, registered_class in self._components.items():
50
+ if component_class == registered_class:
51
+ return type_name
52
+ # If not found, register using class name and return
53
+ return self.register_component(component_class)
54
+
55
+ def get_all_components(self) -> dict[str, type]:
56
+ """Get all registered components."""
57
+ with self._lock:
58
+ return self._components.copy()
59
+
60
+ def clear(self) -> None:
61
+ """Clear all registered components."""
62
+ with self._lock:
63
+ self._components.clear()
64
+ logger.debug("Cleared all registered components")
@@ -0,0 +1,64 @@
1
+ # src/flock/core/registry/config_mapping.py
2
+ """Config-to-component mapping functionality."""
3
+
4
+ import threading
5
+ from typing import TypeVar
6
+
7
+ from pydantic import BaseModel
8
+ from flock.core.logging.logging import get_logger
9
+
10
+ logger = get_logger("registry.config_mapping")
11
+
12
+ ConfigType = TypeVar("ConfigType", bound=BaseModel)
13
+ ClassType = TypeVar("ClassType", bound=type)
14
+
15
+ # Global config mapping with thread safety
16
+ _component_config_map: dict[type[BaseModel], type] = {}
17
+ _config_map_lock = threading.RLock()
18
+
19
+
20
+ class ConfigMapping:
21
+ """Manages config-to-component mappings with thread safety."""
22
+
23
+ def __init__(self, lock: threading.RLock):
24
+ self._lock = lock
25
+
26
+ def register_config_component_pair(
27
+ self, config_cls: type[ConfigType], component_cls: type[ClassType]
28
+ ) -> None:
29
+ """Explicitly register the mapping between a config and component class."""
30
+ # Component config validation can be added here if needed
31
+ # Add more checks if needed (e.g., component_cls inherits from Module/Router/Evaluator)
32
+
33
+ with _config_map_lock:
34
+ if (
35
+ config_cls in _component_config_map
36
+ and _component_config_map[config_cls] != component_cls
37
+ ):
38
+ logger.warning(
39
+ f"Config class {config_cls.__name__} already mapped to {_component_config_map[config_cls].__name__}. "
40
+ f"Overwriting with {component_cls.__name__}."
41
+ )
42
+
43
+ _component_config_map[config_cls] = component_cls
44
+ logger.debug(
45
+ f"Registered config mapping: {config_cls.__name__} -> {component_cls.__name__}"
46
+ )
47
+
48
+ def get_component_class_for_config(
49
+ self, config_cls: type[ConfigType]
50
+ ) -> type[ClassType] | None:
51
+ """Look up the Component Class associated with a Config Class."""
52
+ with _config_map_lock:
53
+ return _component_config_map.get(config_cls)
54
+
55
+ def get_all_config_mappings(self) -> dict[type[BaseModel], type]:
56
+ """Get all config-to-component mappings."""
57
+ with _config_map_lock:
58
+ return _component_config_map.copy()
59
+
60
+ def clear_config_mappings(self) -> None:
61
+ """Clear all config-to-component mappings."""
62
+ with _config_map_lock:
63
+ _component_config_map.clear()
64
+ logger.debug("Cleared all config-to-component mappings")