flock-core 0.3.23__py3-none-any.whl → 0.3.31__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/__init__.py +23 -11
- flock/cli/constants.py +2 -4
- flock/cli/create_flock.py +220 -1
- flock/cli/execute_flock.py +200 -0
- flock/cli/load_flock.py +27 -7
- flock/cli/loaded_flock_cli.py +202 -0
- flock/cli/manage_agents.py +443 -0
- flock/cli/view_results.py +29 -0
- flock/cli/yaml_editor.py +283 -0
- flock/core/__init__.py +2 -2
- flock/core/api/__init__.py +11 -0
- flock/core/api/endpoints.py +222 -0
- flock/core/api/main.py +237 -0
- flock/core/api/models.py +34 -0
- flock/core/api/run_store.py +72 -0
- flock/core/api/ui/__init__.py +0 -0
- flock/core/api/ui/routes.py +271 -0
- flock/core/api/ui/utils.py +119 -0
- flock/core/flock.py +509 -388
- flock/core/flock_agent.py +384 -121
- flock/core/flock_registry.py +532 -0
- flock/core/logging/logging.py +97 -23
- flock/core/mixin/dspy_integration.py +363 -158
- flock/core/serialization/__init__.py +7 -1
- flock/core/serialization/callable_registry.py +52 -0
- flock/core/serialization/serializable.py +259 -37
- flock/core/serialization/serialization_utils.py +199 -0
- flock/evaluators/declarative/declarative_evaluator.py +2 -0
- flock/modules/memory/memory_module.py +17 -4
- flock/modules/output/output_module.py +9 -3
- flock/workflow/activities.py +2 -2
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/METADATA +6 -3
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/RECORD +36 -22
- flock/core/flock_api.py +0 -214
- flock/core/registry/agent_registry.py +0 -120
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/WHEEL +0 -0
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/entry_points.txt +0 -0
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
# src/flock/core/flock_registry.py
|
|
2
|
+
"""Centralized registry for managing Agents, Callables, Types, and Component Classes
|
|
3
|
+
within the Flock framework to support dynamic lookup and serialization.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations # Add this at the very top
|
|
7
|
+
|
|
8
|
+
import importlib
|
|
9
|
+
import inspect
|
|
10
|
+
import sys
|
|
11
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
12
|
+
from dataclasses import is_dataclass
|
|
13
|
+
from typing import ( # Add TYPE_CHECKING
|
|
14
|
+
TYPE_CHECKING,
|
|
15
|
+
Any,
|
|
16
|
+
Literal,
|
|
17
|
+
Optional,
|
|
18
|
+
TypeVar,
|
|
19
|
+
Union,
|
|
20
|
+
overload,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from pydantic import BaseModel
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from flock.core.flock_agent import (
|
|
27
|
+
FlockAgent, # Import only for type checking
|
|
28
|
+
)
|
|
29
|
+
from flock.core.flock_evaluator import FlockEvaluator
|
|
30
|
+
from flock.core.flock_module import FlockModule
|
|
31
|
+
from flock.core.flock_router import FlockRouter
|
|
32
|
+
|
|
33
|
+
COMPONENT_BASE_TYPES = (FlockModule, FlockEvaluator, FlockRouter)
|
|
34
|
+
IS_COMPONENT_CHECK_ENABLED = True
|
|
35
|
+
else:
|
|
36
|
+
# Define dummy types or skip check if not type checking
|
|
37
|
+
FlockAgent = Any # Or define a dummy class
|
|
38
|
+
COMPONENT_BASE_TYPES = ()
|
|
39
|
+
IS_COMPONENT_CHECK_ENABLED = False
|
|
40
|
+
|
|
41
|
+
# Fallback if core types aren't available during setup
|
|
42
|
+
|
|
43
|
+
from flock.core.logging.logging import get_logger
|
|
44
|
+
|
|
45
|
+
logger = get_logger("registry")
|
|
46
|
+
T = TypeVar("T")
|
|
47
|
+
ClassType = TypeVar("ClassType", bound=type)
|
|
48
|
+
FuncType = TypeVar("FuncType", bound=Callable)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class FlockRegistry:
|
|
52
|
+
"""Singleton registry for Agents, Callables (functions/methods).
|
|
53
|
+
|
|
54
|
+
Types (Pydantic/Dataclasses used in signatures), and Component Classes
|
|
55
|
+
(Modules, Evaluators, Routers).
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
_instance = None
|
|
59
|
+
|
|
60
|
+
_agents: dict[str, FlockAgent]
|
|
61
|
+
_callables: dict[str, Callable]
|
|
62
|
+
_types: dict[str, type]
|
|
63
|
+
_components: dict[str, type] # For Module, Evaluator, Router classes
|
|
64
|
+
|
|
65
|
+
def __new__(cls):
|
|
66
|
+
if cls._instance is None:
|
|
67
|
+
cls._instance = super().__new__(cls)
|
|
68
|
+
cls._instance._initialize()
|
|
69
|
+
logger.info("FlockRegistry instance created.")
|
|
70
|
+
return cls._instance
|
|
71
|
+
|
|
72
|
+
def _initialize(self):
|
|
73
|
+
"""Initialize the internal dictionaries."""
|
|
74
|
+
self._agents = {}
|
|
75
|
+
self._callables = {}
|
|
76
|
+
self._types = {}
|
|
77
|
+
self._components = {}
|
|
78
|
+
logger.debug("FlockRegistry initialized internal stores.")
|
|
79
|
+
# Auto-register core Python types
|
|
80
|
+
self._register_core_types()
|
|
81
|
+
|
|
82
|
+
def _register_core_types(self):
|
|
83
|
+
"""Registers common built-in and typing types."""
|
|
84
|
+
core_types = [
|
|
85
|
+
str,
|
|
86
|
+
int,
|
|
87
|
+
float,
|
|
88
|
+
bool,
|
|
89
|
+
list,
|
|
90
|
+
dict,
|
|
91
|
+
tuple,
|
|
92
|
+
set,
|
|
93
|
+
Any,
|
|
94
|
+
Mapping,
|
|
95
|
+
Sequence,
|
|
96
|
+
TypeVar,
|
|
97
|
+
Literal,
|
|
98
|
+
Optional,
|
|
99
|
+
Union, # Common typing generics
|
|
100
|
+
]
|
|
101
|
+
for t in core_types:
|
|
102
|
+
try:
|
|
103
|
+
self.register_type(t)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f"Failed to auto-register core type {t}: {e}")
|
|
106
|
+
|
|
107
|
+
# --- Path String Generation ---
|
|
108
|
+
@staticmethod
|
|
109
|
+
def _get_path_string(obj: Callable | type) -> str | None:
|
|
110
|
+
"""Generates a unique path string 'module.ClassName' or 'module.function_name'."""
|
|
111
|
+
try:
|
|
112
|
+
module = obj.__module__
|
|
113
|
+
name = obj.__name__
|
|
114
|
+
if module == "builtins":
|
|
115
|
+
return name
|
|
116
|
+
# Check if it's nested (basic check, might not cover all edge cases)
|
|
117
|
+
if "." in name and hasattr(sys.modules[module], name.split(".")[0]):
|
|
118
|
+
# Likely a nested class/method - serialization might need custom handling or pickle
|
|
119
|
+
logger.warning(
|
|
120
|
+
f"Object {name} appears nested in {module}. Path string might be ambiguous."
|
|
121
|
+
)
|
|
122
|
+
return f"{module}.{name}"
|
|
123
|
+
except AttributeError:
|
|
124
|
+
logger.warning(f"Could not determine module/name for object: {obj}")
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
# --- Agent Registration ---
|
|
128
|
+
def register_agent(self, agent: FlockAgent) -> None:
|
|
129
|
+
"""Registers a FlockAgent instance by its name."""
|
|
130
|
+
if not hasattr(agent, "name") or not agent.name:
|
|
131
|
+
logger.error(
|
|
132
|
+
"Attempted to register an agent without a valid 'name' attribute."
|
|
133
|
+
)
|
|
134
|
+
return
|
|
135
|
+
if agent.name in self._agents and self._agents[agent.name] != agent:
|
|
136
|
+
logger.warning(
|
|
137
|
+
f"Agent '{agent.name}' already registered. Overwriting."
|
|
138
|
+
)
|
|
139
|
+
self._agents[agent.name] = agent
|
|
140
|
+
logger.debug(f"Registered agent: {agent.name}")
|
|
141
|
+
|
|
142
|
+
def get_agent(self, name: str) -> FlockAgent | None:
|
|
143
|
+
"""Retrieves a registered FlockAgent instance by name."""
|
|
144
|
+
agent = self._agents.get(name)
|
|
145
|
+
if not agent:
|
|
146
|
+
logger.warning(f"Agent '{name}' not found in registry.")
|
|
147
|
+
return agent
|
|
148
|
+
|
|
149
|
+
def get_all_agent_names(self) -> list[str]:
|
|
150
|
+
"""Returns a list of names of all registered agents."""
|
|
151
|
+
return list(self._agents.keys())
|
|
152
|
+
|
|
153
|
+
# --- Callable Registration ---
|
|
154
|
+
def register_callable(
|
|
155
|
+
self, func: Callable, name: str | None = None
|
|
156
|
+
) -> str | None:
|
|
157
|
+
"""Registers a callable (function/method). Returns its path string identifier."""
|
|
158
|
+
path_str = name or self._get_path_string(func)
|
|
159
|
+
if path_str:
|
|
160
|
+
if (
|
|
161
|
+
path_str in self._callables
|
|
162
|
+
and self._callables[path_str] != func
|
|
163
|
+
):
|
|
164
|
+
logger.warning(
|
|
165
|
+
f"Callable '{path_str}' already registered. Overwriting."
|
|
166
|
+
)
|
|
167
|
+
self._callables[path_str] = func
|
|
168
|
+
logger.debug(f"Registered callable: {path_str}")
|
|
169
|
+
return path_str
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
def get_callable(self, path_str: str) -> Callable:
|
|
173
|
+
"""Retrieves a callable by its path string, attempting dynamic import if not found."""
|
|
174
|
+
if path_str in self._callables:
|
|
175
|
+
return self._callables[path_str]
|
|
176
|
+
|
|
177
|
+
logger.debug(
|
|
178
|
+
f"Callable '{path_str}' not in registry, attempting dynamic import."
|
|
179
|
+
)
|
|
180
|
+
try:
|
|
181
|
+
if "." not in path_str: # Built-ins
|
|
182
|
+
builtins_module = importlib.import_module("builtins")
|
|
183
|
+
if hasattr(builtins_module, path_str):
|
|
184
|
+
func = getattr(builtins_module, path_str)
|
|
185
|
+
if callable(func):
|
|
186
|
+
self.register_callable(func, path_str) # Cache it
|
|
187
|
+
return func
|
|
188
|
+
raise KeyError(f"Built-in callable '{path_str}' not found.")
|
|
189
|
+
|
|
190
|
+
module_name, func_name = path_str.rsplit(".", 1)
|
|
191
|
+
module = importlib.import_module(module_name)
|
|
192
|
+
func = getattr(module, func_name)
|
|
193
|
+
if callable(func):
|
|
194
|
+
self.register_callable(
|
|
195
|
+
func, path_str
|
|
196
|
+
) # Cache dynamically imported
|
|
197
|
+
return func
|
|
198
|
+
else:
|
|
199
|
+
raise TypeError(
|
|
200
|
+
f"Dynamically imported object '{path_str}' is not callable."
|
|
201
|
+
)
|
|
202
|
+
except (ImportError, AttributeError, KeyError, TypeError) as e:
|
|
203
|
+
logger.error(
|
|
204
|
+
f"Failed to dynamically load/find callable '{path_str}': {e}"
|
|
205
|
+
)
|
|
206
|
+
raise KeyError(
|
|
207
|
+
f"Callable '{path_str}' not found or failed to load: {e}"
|
|
208
|
+
) from e
|
|
209
|
+
|
|
210
|
+
def get_callable_path_string(self, func: Callable) -> str | None:
|
|
211
|
+
"""Gets the path string for a callable, registering it if necessary."""
|
|
212
|
+
for path_str, registered_func in self._callables.items():
|
|
213
|
+
if func == registered_func:
|
|
214
|
+
return path_str
|
|
215
|
+
# If not found by identity, generate path, register, and return
|
|
216
|
+
return self.register_callable(func)
|
|
217
|
+
|
|
218
|
+
# --- Type Registration ---
|
|
219
|
+
def register_type(
|
|
220
|
+
self, type_obj: type, name: str | None = None
|
|
221
|
+
) -> str | None:
|
|
222
|
+
"""Registers a class/type (Pydantic, Dataclass, etc.) used in signatures."""
|
|
223
|
+
type_name = name or type_obj.__name__
|
|
224
|
+
if type_name:
|
|
225
|
+
if type_name in self._types and self._types[type_name] != type_obj:
|
|
226
|
+
logger.warning(
|
|
227
|
+
f"Type '{type_name}' already registered. Overwriting."
|
|
228
|
+
)
|
|
229
|
+
self._types[type_name] = type_obj
|
|
230
|
+
logger.debug(f"Registered type: {type_name}")
|
|
231
|
+
return type_name
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
def get_type(self, type_name: str) -> type:
|
|
235
|
+
"""Retrieves a registered type by its name."""
|
|
236
|
+
if type_name in self._types:
|
|
237
|
+
return self._types[type_name]
|
|
238
|
+
else:
|
|
239
|
+
# Consider adding dynamic import attempts for types if needed,
|
|
240
|
+
# but explicit registration is generally safer for types.
|
|
241
|
+
logger.error(f"Type '{type_name}' not found in registry.")
|
|
242
|
+
raise KeyError(
|
|
243
|
+
f"Type '{type_name}' not found. Ensure it is registered."
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# --- Component Class Registration ---
|
|
247
|
+
def register_component(
|
|
248
|
+
self, component_class: type, name: str | None = None
|
|
249
|
+
) -> str | None:
|
|
250
|
+
"""Registers a component class (Module, Evaluator, Router)."""
|
|
251
|
+
type_name = name or component_class.__name__
|
|
252
|
+
if type_name:
|
|
253
|
+
# Optional: Add check if it's a subclass of expected bases
|
|
254
|
+
# if COMPONENT_BASE_TYPES and not issubclass(component_class, COMPONENT_BASE_TYPES):
|
|
255
|
+
# logger.warning(f"Registering class '{type_name}' which is not a standard Flock component type.")
|
|
256
|
+
if (
|
|
257
|
+
type_name in self._components
|
|
258
|
+
and self._components[type_name] != component_class
|
|
259
|
+
):
|
|
260
|
+
logger.warning(
|
|
261
|
+
f"Component class '{type_name}' already registered. Overwriting."
|
|
262
|
+
)
|
|
263
|
+
self._components[type_name] = component_class
|
|
264
|
+
logger.debug(f"Registered component class: {type_name}")
|
|
265
|
+
return type_name
|
|
266
|
+
return None
|
|
267
|
+
|
|
268
|
+
def get_component(self, type_name: str) -> type:
|
|
269
|
+
"""Retrieves a component class by its type name."""
|
|
270
|
+
if type_name in self._components:
|
|
271
|
+
return self._components[type_name]
|
|
272
|
+
else:
|
|
273
|
+
# Dynamic import attempts similar to get_callable could be added here if desired,
|
|
274
|
+
# targeting likely module locations based on type_name conventions.
|
|
275
|
+
logger.error(
|
|
276
|
+
f"Component class '{type_name}' not found in registry."
|
|
277
|
+
)
|
|
278
|
+
raise KeyError(
|
|
279
|
+
f"Component class '{type_name}' not found. Ensure it is registered."
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
def get_component_type_name(self, component_class: type) -> str | None:
|
|
283
|
+
"""Gets the type name for a component class, registering it if necessary."""
|
|
284
|
+
for type_name, registered_class in self._components.items():
|
|
285
|
+
if component_class == registered_class:
|
|
286
|
+
return type_name
|
|
287
|
+
# If not found, register using class name and return
|
|
288
|
+
return self.register_component(component_class)
|
|
289
|
+
|
|
290
|
+
# --- Auto-Registration ---
|
|
291
|
+
def register_module_components(self, module_or_path: Any) -> None:
|
|
292
|
+
"""Scans a module (object or path string) and automatically registers.
|
|
293
|
+
|
|
294
|
+
- Functions as callables.
|
|
295
|
+
- Pydantic Models and Dataclasses as types.
|
|
296
|
+
- Subclasses of FlockModule, FlockEvaluator, FlockRouter as components.
|
|
297
|
+
"""
|
|
298
|
+
try:
|
|
299
|
+
if isinstance(module_or_path, str):
|
|
300
|
+
module = importlib.import_module(module_or_path)
|
|
301
|
+
elif inspect.ismodule(module_or_path):
|
|
302
|
+
module = module_or_path
|
|
303
|
+
else:
|
|
304
|
+
logger.error(
|
|
305
|
+
f"Invalid input for auto-registration: {module_or_path}. Must be module object or path string."
|
|
306
|
+
)
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
logger.info(
|
|
310
|
+
f"Auto-registering components from module: {module.__name__}"
|
|
311
|
+
)
|
|
312
|
+
registered_count = {"callable": 0, "type": 0, "component": 0}
|
|
313
|
+
|
|
314
|
+
for name, obj in inspect.getmembers(module):
|
|
315
|
+
if name.startswith("_"):
|
|
316
|
+
continue # Skip private/internal
|
|
317
|
+
|
|
318
|
+
# Register Functions as Callables
|
|
319
|
+
if (
|
|
320
|
+
inspect.isfunction(obj)
|
|
321
|
+
and obj.__module__ == module.__name__
|
|
322
|
+
):
|
|
323
|
+
if self.register_callable(obj):
|
|
324
|
+
registered_count["callable"] += 1
|
|
325
|
+
|
|
326
|
+
# Register Classes (Types and Components)
|
|
327
|
+
elif inspect.isclass(obj) and obj.__module__ == module.__name__:
|
|
328
|
+
is_component = False
|
|
329
|
+
# Register as Component if subclass of base types
|
|
330
|
+
if (
|
|
331
|
+
COMPONENT_BASE_TYPES
|
|
332
|
+
and issubclass(obj, COMPONENT_BASE_TYPES)
|
|
333
|
+
and self.register_component(obj)
|
|
334
|
+
):
|
|
335
|
+
registered_count["component"] += 1
|
|
336
|
+
is_component = True # Mark as component
|
|
337
|
+
|
|
338
|
+
# Register as Type if Pydantic Model or Dataclass
|
|
339
|
+
# A component can also be a type used in signatures
|
|
340
|
+
base_model_or_dataclass = isinstance(obj, type) and (
|
|
341
|
+
issubclass(obj, BaseModel) or is_dataclass(obj)
|
|
342
|
+
)
|
|
343
|
+
if (
|
|
344
|
+
base_model_or_dataclass
|
|
345
|
+
and self.register_type(obj)
|
|
346
|
+
and not is_component
|
|
347
|
+
):
|
|
348
|
+
# Only increment type count if it wasn't already counted as component
|
|
349
|
+
registered_count["type"] += 1
|
|
350
|
+
|
|
351
|
+
logger.info(
|
|
352
|
+
f"Auto-registration summary for {module.__name__}: "
|
|
353
|
+
f"{registered_count['callable']} callables, "
|
|
354
|
+
f"{registered_count['type']} types, "
|
|
355
|
+
f"{registered_count['component']} components."
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
except Exception as e:
|
|
359
|
+
logger.error(
|
|
360
|
+
f"Error during auto-registration for {module_or_path}: {e}",
|
|
361
|
+
exc_info=True,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# --- Initialize Singleton ---
|
|
366
|
+
_registry_instance = FlockRegistry()
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# --- Convenience Access ---
|
|
370
|
+
# Provide a function to easily get the singleton instance
|
|
371
|
+
def get_registry() -> FlockRegistry:
|
|
372
|
+
"""Returns the singleton FlockRegistry instance."""
|
|
373
|
+
return _registry_instance
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# Type hinting for decorators to preserve signature
|
|
377
|
+
@overload
|
|
378
|
+
def flock_component(cls: ClassType) -> ClassType: ...
|
|
379
|
+
@overload
|
|
380
|
+
def flock_component(
|
|
381
|
+
*, name: str | None = None
|
|
382
|
+
) -> Callable[[ClassType], ClassType]: ...
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def flock_component(
|
|
386
|
+
cls: ClassType | None = None, *, name: str | None = None
|
|
387
|
+
) -> Any:
|
|
388
|
+
"""Decorator to register a Flock Component class (Module, Evaluator, Router).
|
|
389
|
+
|
|
390
|
+
Usage:
|
|
391
|
+
@flock_component
|
|
392
|
+
class MyModule(FlockModule): ...
|
|
393
|
+
|
|
394
|
+
@flock_component(name="CustomRouterAlias")
|
|
395
|
+
class MyRouter(FlockRouter): ...
|
|
396
|
+
"""
|
|
397
|
+
registry = get_registry()
|
|
398
|
+
|
|
399
|
+
def decorator(inner_cls: ClassType) -> ClassType:
|
|
400
|
+
if not inspect.isclass(inner_cls):
|
|
401
|
+
raise TypeError("@flock_component can only decorate classes.")
|
|
402
|
+
component_name = name or inner_cls.__name__
|
|
403
|
+
registry.register_component(inner_cls, name=component_name)
|
|
404
|
+
return inner_cls
|
|
405
|
+
|
|
406
|
+
if cls is None:
|
|
407
|
+
# Called as @flock_component(name="...")
|
|
408
|
+
return decorator
|
|
409
|
+
else:
|
|
410
|
+
# Called as @flock_component
|
|
411
|
+
return decorator(cls)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# Type hinting for decorators
|
|
415
|
+
@overload
|
|
416
|
+
def flock_tool(func: FuncType) -> FuncType: ...
|
|
417
|
+
@overload
|
|
418
|
+
def flock_tool(
|
|
419
|
+
*, name: str | None = None
|
|
420
|
+
) -> Callable[[FuncType], FuncType]: ...
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def flock_tool(func: FuncType | None = None, *, name: str | None = None) -> Any:
|
|
424
|
+
"""Decorator to register a callable function/method as a Tool (or general callable).
|
|
425
|
+
|
|
426
|
+
Usage:
|
|
427
|
+
@flock_tool
|
|
428
|
+
def my_web_search(query: str): ...
|
|
429
|
+
|
|
430
|
+
@flock_tool(name="utils.calculate_pi")
|
|
431
|
+
def compute_pi(): ...
|
|
432
|
+
"""
|
|
433
|
+
registry = get_registry()
|
|
434
|
+
|
|
435
|
+
def decorator(inner_func: FuncType) -> FuncType:
|
|
436
|
+
if not callable(inner_func):
|
|
437
|
+
raise TypeError("@flock_tool can only decorate callables.")
|
|
438
|
+
# Let registry handle default name generation if None
|
|
439
|
+
registry.register_callable(inner_func, name=name)
|
|
440
|
+
return inner_func
|
|
441
|
+
|
|
442
|
+
if func is None:
|
|
443
|
+
# Called as @flock_tool(name="...")
|
|
444
|
+
return decorator
|
|
445
|
+
else:
|
|
446
|
+
# Called as @flock_tool
|
|
447
|
+
return decorator(func)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# Alias for clarity if desired
|
|
451
|
+
# flock_callable = flock_tool
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@overload
|
|
455
|
+
def flock_type(cls: ClassType) -> ClassType: ...
|
|
456
|
+
@overload
|
|
457
|
+
def flock_type(
|
|
458
|
+
*, name: str | None = None
|
|
459
|
+
) -> Callable[[ClassType], ClassType]: ...
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def flock_type(cls: ClassType | None = None, *, name: str | None = None) -> Any:
|
|
463
|
+
"""Decorator to register a Type (Pydantic Model, Dataclass) used in signatures.
|
|
464
|
+
|
|
465
|
+
Usage:
|
|
466
|
+
@flock_type
|
|
467
|
+
class MyDataModel(BaseModel): ...
|
|
468
|
+
|
|
469
|
+
@flock_type(name="UserInput")
|
|
470
|
+
@dataclass
|
|
471
|
+
class UserQuery: ...
|
|
472
|
+
"""
|
|
473
|
+
registry = get_registry()
|
|
474
|
+
|
|
475
|
+
def decorator(inner_cls: ClassType) -> ClassType:
|
|
476
|
+
if not inspect.isclass(inner_cls):
|
|
477
|
+
raise TypeError("@flock_type can only decorate classes.")
|
|
478
|
+
type_name = name or inner_cls.__name__
|
|
479
|
+
registry.register_type(inner_cls, name=type_name)
|
|
480
|
+
return inner_cls
|
|
481
|
+
|
|
482
|
+
if cls is None:
|
|
483
|
+
# Called as @flock_type(name="...")
|
|
484
|
+
return decorator
|
|
485
|
+
else:
|
|
486
|
+
# Called as @flock_type
|
|
487
|
+
return decorator(cls)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
# --- Auto-register known core components and tools ---
|
|
491
|
+
def _auto_register_by_path():
|
|
492
|
+
components_to_register = [
|
|
493
|
+
(
|
|
494
|
+
"flock.evaluators.declarative.declarative_evaluator",
|
|
495
|
+
"DeclarativeEvaluator",
|
|
496
|
+
),
|
|
497
|
+
("flock.evaluators.memory.memory_evaluator", "MemoryEvaluator"),
|
|
498
|
+
("flock.modules.output.output_module", "OutputModule"),
|
|
499
|
+
("flock.modules.performance.metrics_module", "MetricsModule"),
|
|
500
|
+
("flock.modules.memory.memory_module", "MemoryModule"),
|
|
501
|
+
# ("flock.modules.hierarchical.module", "HierarchicalMemoryModule"), # Uncomment if exists
|
|
502
|
+
("flock.routers.default.default_router", "DefaultRouter"),
|
|
503
|
+
("flock.routers.llm.llm_router", "LLMRouter"),
|
|
504
|
+
("flock.routers.agent.agent_router", "AgentRouter"),
|
|
505
|
+
]
|
|
506
|
+
for module_path, class_name in components_to_register:
|
|
507
|
+
try:
|
|
508
|
+
module = importlib.import_module(module_path)
|
|
509
|
+
component_class = getattr(module, class_name)
|
|
510
|
+
_registry_instance.register_component(component_class)
|
|
511
|
+
except (ImportError, AttributeError) as e:
|
|
512
|
+
logger.warning(f"{class_name} not found for auto-registration: {e}")
|
|
513
|
+
|
|
514
|
+
# Auto-register standard tools by scanning modules
|
|
515
|
+
tool_modules = [
|
|
516
|
+
"flock.core.tools.basic_tools",
|
|
517
|
+
"flock.core.tools.azure_tools",
|
|
518
|
+
"flock.core.tools.dev_tools.github",
|
|
519
|
+
"flock.core.tools.llm_tools",
|
|
520
|
+
"flock.core.tools.markdown_tools",
|
|
521
|
+
]
|
|
522
|
+
for module_path in tool_modules:
|
|
523
|
+
try:
|
|
524
|
+
_registry_instance.register_module_components(module_path)
|
|
525
|
+
except ImportError as e:
|
|
526
|
+
logger.warning(
|
|
527
|
+
f"Could not auto-register tools from {module_path}: {e}"
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
# Bootstrapping the registry
|
|
532
|
+
_auto_register_by_path()
|
flock/core/logging/logging.py
CHANGED
|
@@ -46,50 +46,110 @@ def get_current_trace_id() -> str:
|
|
|
46
46
|
return "no-trace"
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
# ---------------------------------------------------------------------
|
|
50
|
-
# 2. A color map for different logger names
|
|
51
|
-
# You can add or change entries as you like.
|
|
52
|
-
# ---------------------------------------------------------------------
|
|
53
49
|
COLOR_MAP = {
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
50
|
+
# Core & Orchestration
|
|
51
|
+
"flock": "magenta", # Color only
|
|
52
|
+
"agent": "blue", # Color only
|
|
53
|
+
"workflow": "cyan", # Color only
|
|
54
|
+
"activities": "cyan",
|
|
58
55
|
"context": "green",
|
|
59
|
-
|
|
56
|
+
# Components & Mechanisms
|
|
57
|
+
"registry": "yellow", # Color only
|
|
58
|
+
"serialization": "yellow",
|
|
59
|
+
"serialization.utils": "light-yellow",
|
|
60
|
+
"evaluator": "light-blue",
|
|
61
|
+
"module": "light-green",
|
|
62
|
+
"router": "light-magenta",
|
|
63
|
+
"mixin.dspy": "yellow",
|
|
64
|
+
# Specific Modules (Examples)
|
|
65
|
+
"memory": "yellow",
|
|
66
|
+
"module.output": "green",
|
|
67
|
+
"module.metrics": "blue",
|
|
68
|
+
"module.zep": "red",
|
|
69
|
+
"module.hierarchical": "light-green",
|
|
70
|
+
# Tools & Execution
|
|
60
71
|
"tools": "light-black",
|
|
61
|
-
"
|
|
72
|
+
"interpreter": "light-yellow",
|
|
73
|
+
# API Components
|
|
74
|
+
"api": "white", # Color only
|
|
75
|
+
"api.main": "white",
|
|
76
|
+
"api.endpoints": "light-black",
|
|
77
|
+
"api.run_store": "light-black",
|
|
78
|
+
"api.ui": "light-blue", # Color only
|
|
79
|
+
"api.ui.routes": "light-blue",
|
|
80
|
+
"api.ui.utils": "cyan",
|
|
81
|
+
# Default/Unknown
|
|
82
|
+
"unknown": "light-black",
|
|
62
83
|
}
|
|
63
84
|
|
|
64
85
|
LOGGERS = [
|
|
86
|
+
"flock", # Core Flock orchestration
|
|
87
|
+
"agent", # General agent operations
|
|
88
|
+
"context", # Context management
|
|
89
|
+
"registry", # Unified registry operations (new)
|
|
90
|
+
"serialization", # General serialization (new - can be base for others)
|
|
91
|
+
"serialization.utils", # Serialization helpers (new, more specific)
|
|
92
|
+
"evaluator", # Base evaluator category (new/optional)
|
|
93
|
+
"module", # Base module category (new/optional)
|
|
94
|
+
"router", # Base router category (new/optional)
|
|
95
|
+
"mixin.dspy", # DSPy integration specifics (new)
|
|
96
|
+
"memory", # Memory module specifics
|
|
97
|
+
"module.output", # Output module specifics (example specific module)
|
|
98
|
+
"module.metrics", # Metrics module specifics (example specific module)
|
|
99
|
+
"module.zep", # Zep module specifics (example specific module)
|
|
100
|
+
"module.hierarchical", # Hierarchical memory specifics (example specific module)
|
|
101
|
+
"interpreter", # Code interpreter (if still used)
|
|
102
|
+
"activities", # Temporal activities
|
|
103
|
+
"workflow", # Temporal workflow logic
|
|
104
|
+
"tools", # Tool execution/registration
|
|
105
|
+
"api", # General API server (new)
|
|
106
|
+
"api.main", # API main setup (new)
|
|
107
|
+
"api.endpoints", # API endpoints (new)
|
|
108
|
+
"api.run_store", # API run state management (new)
|
|
109
|
+
"api.ui", # UI general (new)
|
|
110
|
+
"api.ui.routes", # UI routes (new)
|
|
111
|
+
"api.ui.utils", # UI utils (new)
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
BOLD_CATEGORIES = [
|
|
65
115
|
"flock",
|
|
66
|
-
"interpreter",
|
|
67
|
-
"memory",
|
|
68
|
-
"activities",
|
|
69
|
-
"context",
|
|
70
|
-
"registry",
|
|
71
|
-
"tools",
|
|
72
116
|
"agent",
|
|
117
|
+
"workflow",
|
|
118
|
+
"registry",
|
|
119
|
+
"api",
|
|
120
|
+
"api.ui",
|
|
73
121
|
]
|
|
74
122
|
|
|
75
123
|
|
|
76
124
|
def color_for_category(category: str) -> str:
|
|
77
|
-
"""Return the
|
|
78
|
-
|
|
125
|
+
"""Return the Rich markup color code name for the given category."""
|
|
126
|
+
# Handle potentially nested names like 'serialization.utils'
|
|
127
|
+
# Try exact match first, then go up the hierarchy
|
|
128
|
+
if category in COLOR_MAP:
|
|
129
|
+
return COLOR_MAP[category]
|
|
130
|
+
parts = category.split(".")
|
|
131
|
+
for i in range(len(parts) - 1, 0, -1):
|
|
132
|
+
parent_category = ".".join(parts[:i])
|
|
133
|
+
if parent_category in COLOR_MAP:
|
|
134
|
+
return COLOR_MAP[parent_category]
|
|
135
|
+
# Fallback to default 'unknown' color
|
|
136
|
+
return COLOR_MAP.get("unknown", "light-black") # Final fallback
|
|
79
137
|
|
|
80
138
|
|
|
81
139
|
def custom_format(record):
|
|
82
|
-
"""A formatter that applies truncation
|
|
140
|
+
"""A formatter that applies truncation and sequential styling tags."""
|
|
83
141
|
t = record["time"].strftime("%Y-%m-%d %H:%M:%S")
|
|
84
142
|
level_name = record["level"].name
|
|
85
143
|
category = record["extra"].get("category", "unknown")
|
|
86
144
|
trace_id = record["extra"].get("trace_id", "no-trace")
|
|
87
|
-
|
|
145
|
+
color_tag = color_for_category(
|
|
146
|
+
category
|
|
147
|
+
) # Get the color tag name (e.g., "yellow")
|
|
88
148
|
|
|
89
|
-
# Get the formatted message (already includes args)
|
|
90
149
|
message = record["message"]
|
|
150
|
+
message = message.replace("{", "{{").replace("}", "}}")
|
|
91
151
|
|
|
92
|
-
#
|
|
152
|
+
# MAX_LENGTH = 500 # Example value
|
|
93
153
|
if len(message) > MAX_LENGTH:
|
|
94
154
|
truncated_chars = len(message) - MAX_LENGTH
|
|
95
155
|
message = (
|
|
@@ -97,10 +157,24 @@ def custom_format(record):
|
|
|
97
157
|
+ f"<yellow>...+({truncated_chars} chars)</yellow>"
|
|
98
158
|
)
|
|
99
159
|
|
|
160
|
+
# Determine if category needs bolding (can refine this logic)
|
|
161
|
+
needs_bold = category in BOLD_CATEGORIES
|
|
162
|
+
|
|
163
|
+
# Apply tags sequentially
|
|
164
|
+
category_styled = f"[{category}]" # Start with the plain category name
|
|
165
|
+
category_styled = (
|
|
166
|
+
f"<{color_tag}>{category_styled}</{color_tag}>" # Wrap with color
|
|
167
|
+
)
|
|
168
|
+
if needs_bold:
|
|
169
|
+
category_styled = (
|
|
170
|
+
f"<bold>{category_styled}</bold>" # Wrap with bold if needed
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Final format string using sequential tags for category
|
|
100
174
|
return (
|
|
101
175
|
f"<green>{t}</green> | <level>{level_name: <8}</level> | "
|
|
102
176
|
f"<cyan>[trace_id: {trace_id}]</cyan> | "
|
|
103
|
-
f"
|
|
177
|
+
f"{category_styled} | {message}\n" # Apply the sequentially styled category
|
|
104
178
|
)
|
|
105
179
|
|
|
106
180
|
|