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.

Files changed (38) hide show
  1. flock/__init__.py +23 -11
  2. flock/cli/constants.py +2 -4
  3. flock/cli/create_flock.py +220 -1
  4. flock/cli/execute_flock.py +200 -0
  5. flock/cli/load_flock.py +27 -7
  6. flock/cli/loaded_flock_cli.py +202 -0
  7. flock/cli/manage_agents.py +443 -0
  8. flock/cli/view_results.py +29 -0
  9. flock/cli/yaml_editor.py +283 -0
  10. flock/core/__init__.py +2 -2
  11. flock/core/api/__init__.py +11 -0
  12. flock/core/api/endpoints.py +222 -0
  13. flock/core/api/main.py +237 -0
  14. flock/core/api/models.py +34 -0
  15. flock/core/api/run_store.py +72 -0
  16. flock/core/api/ui/__init__.py +0 -0
  17. flock/core/api/ui/routes.py +271 -0
  18. flock/core/api/ui/utils.py +119 -0
  19. flock/core/flock.py +509 -388
  20. flock/core/flock_agent.py +384 -121
  21. flock/core/flock_registry.py +532 -0
  22. flock/core/logging/logging.py +97 -23
  23. flock/core/mixin/dspy_integration.py +363 -158
  24. flock/core/serialization/__init__.py +7 -1
  25. flock/core/serialization/callable_registry.py +52 -0
  26. flock/core/serialization/serializable.py +259 -37
  27. flock/core/serialization/serialization_utils.py +199 -0
  28. flock/evaluators/declarative/declarative_evaluator.py +2 -0
  29. flock/modules/memory/memory_module.py +17 -4
  30. flock/modules/output/output_module.py +9 -3
  31. flock/workflow/activities.py +2 -2
  32. {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/METADATA +6 -3
  33. {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/RECORD +36 -22
  34. flock/core/flock_api.py +0 -214
  35. flock/core/registry/agent_registry.py +0 -120
  36. {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/WHEEL +0 -0
  37. {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/entry_points.txt +0 -0
  38. {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()
@@ -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
- "flock": "magenta",
55
- "interpreter": "cyan",
56
- "memory": "yellow",
57
- "activities": "blue",
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
- "registry": "white",
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
- "agent": "light-magenta",
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 ANSI color code name for the given category."""
78
- return COLOR_MAP.get(category, "magenta") # fallback color
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 to the entire formatted message."""
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
- color = color_for_category(category)
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
- # Apply truncation to the full formatted message
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"<{color}>[{category}]</{color}> | {message}\n"
177
+ f"{category_styled} | {message}\n" # Apply the sequentially styled category
104
178
  )
105
179
 
106
180