flock-core 0.4.0b18__py3-none-any.whl → 0.4.0b20__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/cli/utils.py ADDED
@@ -0,0 +1,135 @@
1
+ # src/pilot_rules/collector/utils.py
2
+ import datetime
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from rich import box
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
10
+ from rich.table import Table
11
+
12
+ # Create a shared console instance for consistent styling
13
+ console = Console()
14
+
15
+
16
+ def get_file_metadata(file_path: str) -> dict[str, Any]:
17
+ """Extract metadata from a file."""
18
+ metadata = {
19
+ "path": file_path,
20
+ "size_bytes": 0,
21
+ "line_count": 0,
22
+ "last_modified": "Unknown",
23
+ "created": "Unknown",
24
+ }
25
+
26
+ try:
27
+ p = Path(file_path)
28
+ stats = p.stat()
29
+ metadata["size_bytes"] = stats.st_size
30
+ metadata["last_modified"] = datetime.datetime.fromtimestamp(
31
+ stats.st_mtime
32
+ ).strftime("%Y-%m-%d %H:%M:%S")
33
+ # ctime is platform dependent (creation on Windows, metadata change on Unix)
34
+ # Use mtime as a reliable fallback for "created" if ctime is older than mtime
35
+ ctime = stats.st_ctime
36
+ mtime = stats.st_mtime
37
+ best_ctime = ctime if ctime <= mtime else mtime # Heuristic
38
+ metadata["created"] = datetime.datetime.fromtimestamp(
39
+ best_ctime
40
+ ).strftime("%Y-%m-%d %H:%M:%S")
41
+
42
+ try:
43
+ # Attempt to read as text, fallback for binary or encoding issues
44
+ with p.open("r", encoding="utf-8", errors="ignore") as f:
45
+ content = f.read()
46
+ metadata["line_count"] = len(content.splitlines())
47
+ except (OSError, UnicodeDecodeError) as read_err:
48
+ # Handle cases where reading might fail (binary file, permissions etc.)
49
+ console.print(
50
+ f"[yellow]⚠ Warning:[/yellow] Could not read content/count lines for [cyan]{file_path}[/cyan]: [red]{read_err}[/red]"
51
+ )
52
+ metadata["line_count"] = 0 # Indicate unreadable or binary
53
+
54
+ except Exception as e:
55
+ console.print(
56
+ f"[yellow]⚠ Warning:[/yellow] Could not get complete metadata for [cyan]{file_path}[/cyan]: [red]{e}[/red]"
57
+ )
58
+
59
+ return metadata
60
+
61
+
62
+ # --- Rich Formatting Utilities ---
63
+
64
+
65
+ def print_header(title: str, style: str = "blue") -> None:
66
+ """Print a styled header with a panel."""
67
+ console.rule()
68
+ console.print(
69
+ Panel.fit(f"[bold {style}]{title}[/bold {style}]", border_style=style)
70
+ )
71
+
72
+
73
+ def print_subheader(title: str, style: str = "cyan") -> None:
74
+ """Print a styled subheader."""
75
+ console.print(f"[bold {style}]== {title} ==[/bold {style}]")
76
+
77
+
78
+ def print_success(message: str) -> None:
79
+ """Print a success message."""
80
+ console.print(f"[bold green]✓[/bold green] {message}")
81
+
82
+
83
+ def print_error(message: str, exit_code: int | None = None) -> None:
84
+ """Print an error message and optionally exit."""
85
+ console.print(f"[bold red]✗ ERROR:[/bold red] {message}")
86
+ if exit_code is not None:
87
+ exit(exit_code)
88
+
89
+
90
+ def print_warning(message: str) -> None:
91
+ """Print a warning message."""
92
+ console.print(f"[yellow]⚠ Warning:[/yellow] {message}")
93
+
94
+
95
+ def create_progress() -> Progress:
96
+ """Create a standardized progress bar."""
97
+ return Progress(
98
+ SpinnerColumn(),
99
+ TextColumn("[bold blue]{task.description}"),
100
+ BarColumn(complete_style="green", finished_style="green"),
101
+ TextColumn("[bold]{task.completed}/{task.total}"),
102
+ console=console,
103
+ )
104
+
105
+
106
+ def create_task_table(title: str) -> Table:
107
+ """Create a standardized table for displaying task information."""
108
+ table = Table(
109
+ title=title, show_header=True, header_style="bold cyan", box=box.ROUNDED
110
+ )
111
+ return table
112
+
113
+
114
+ def print_file_stats(files: list[str], title: str = "File Statistics") -> None:
115
+ """Print statistics about a list of files."""
116
+ if not files:
117
+ console.print("[yellow]No files found to display statistics.[/yellow]")
118
+ return
119
+
120
+ table = Table(title=title, show_header=True, header_style="bold magenta")
121
+ table.add_column("Statistic", style="cyan")
122
+ table.add_column("Value", style="green")
123
+
124
+ extensions = {Path(f).suffix.lower() for f in files if Path(f).suffix}
125
+ total_size = sum(get_file_metadata(f).get("size_bytes", 0) for f in files)
126
+ total_lines = sum(get_file_metadata(f).get("line_count", 0) for f in files)
127
+
128
+ table.add_row("Total Files", str(len(files)))
129
+ table.add_row("Total Size", f"{total_size / 1024:.2f} KB")
130
+ table.add_row("Total Lines", str(total_lines))
131
+ table.add_row(
132
+ "Extensions", ", ".join(sorted(extensions)) if extensions else "None"
133
+ )
134
+
135
+ console.print(table)
flock/core/flock.py CHANGED
@@ -150,8 +150,7 @@ class Flock(BaseModel, Serializable):
150
150
  )
151
151
 
152
152
  # Initialize console if needed for banner
153
- if self.show_flock_banner: # Use instance attribute
154
- init_console()
153
+ init_console(clear_screen=True, show_banner=self.show_flock_banner)
155
154
 
156
155
  # Set Temporal debug environment variable
157
156
  self._set_temporal_debug_flag()
@@ -225,7 +224,7 @@ class Flock(BaseModel, Serializable):
225
224
  raise ValueError("Agent must have a name.")
226
225
 
227
226
  if agent.name in self._agents:
228
- logger.warning(f"Agent '{agent.name}' already exists. Overwriting.")
227
+ raise ValueError("Agent with this name already exists.")
229
228
  self._agents[agent.name] = agent
230
229
  FlockRegistry.register_agent(agent) # Register globally
231
230
 
@@ -38,6 +38,7 @@ class FlockFactory:
38
38
  print_context: bool = False,
39
39
  write_to_file: bool = False,
40
40
  stream: bool = False,
41
+ include_thought_process: bool = False,
41
42
  ) -> FlockAgent:
42
43
  """Creates a default FlockAgent.
43
44
 
@@ -52,6 +53,7 @@ class FlockFactory:
52
53
  max_tokens=max_tokens,
53
54
  temperature=temperature,
54
55
  stream=stream,
56
+ include_thought_process=include_thought_process,
55
57
  )
56
58
 
57
59
  evaluator = DeclarativeEvaluator(name="default", config=eval_config)
@@ -79,6 +79,7 @@ COLOR_MAP = {
79
79
  "api.ui.routes": "light-blue",
80
80
  "api.ui.utils": "cyan",
81
81
  # Default/Unknown
82
+ "evaluators.declarative": "light-green",
82
83
  "unknown": "light-black",
83
84
  }
84
85
 
@@ -90,6 +91,7 @@ LOGGERS = [
90
91
  "serialization", # General serialization (new - can be base for others)
91
92
  "serialization.utils", # Serialization helpers (new, more specific)
92
93
  "evaluator", # Base evaluator category (new/optional)
94
+ "evaluators.declarative", # Declarative evaluator specifics
93
95
  "module", # Base module category (new/optional)
94
96
  "router", # Base router category (new/optional)
95
97
  "mixin.dspy", # DSPy integration specifics (new)
@@ -336,8 +336,9 @@ class DSPyIntegrationMixin:
336
336
  def _select_task(
337
337
  self,
338
338
  signature: Any,
339
- agent_type_override: AgentType,
339
+ override_evaluator_type: AgentType,
340
340
  tools: list[Any] | None = None,
341
+ kwargs: dict[str, Any] = {},
341
342
  ) -> Any:
342
343
  """Select and instantiate the appropriate DSPy Program/Module."""
343
344
  try:
@@ -360,7 +361,7 @@ class DSPyIntegrationMixin:
360
361
  )
361
362
 
362
363
  dspy_program = None
363
- selected_type = agent_type_override
364
+ selected_type = override_evaluator_type
364
365
 
365
366
  # Determine type if not overridden
366
367
  if not selected_type:
@@ -374,11 +375,12 @@ class DSPyIntegrationMixin:
374
375
 
375
376
  try:
376
377
  if selected_type == "ChainOfThought":
377
- dspy_program = dspy.ChainOfThought(signature)
378
+ dspy_program = dspy.ChainOfThought(signature, **kwargs)
378
379
  elif selected_type == "ReAct":
379
- # ReAct requires tools, even if empty list
380
+ if not kwargs:
381
+ kwargs = {"max_iters": 10}
380
382
  dspy_program = dspy.ReAct(
381
- signature, tools=processed_tools or [], max_iters=10
383
+ signature, tools=processed_tools or [], **kwargs
382
384
  )
383
385
  elif selected_type == "Predict": # Default or explicitly Completion
384
386
  dspy_program = dspy.Predict(signature)
@@ -401,8 +403,10 @@ class DSPyIntegrationMixin:
401
403
 
402
404
  def _process_result(
403
405
  self, result: Any, inputs: dict[str, Any]
404
- ) -> dict[str, Any]:
406
+ ) -> tuple[dict[str, Any], float, list]:
405
407
  """Convert the DSPy result object to a dictionary."""
408
+ import dspy
409
+
406
410
  if result is None:
407
411
  logger.warning("DSPy program returned None result.")
408
412
  return {}
@@ -429,7 +433,12 @@ class DSPyIntegrationMixin:
429
433
  logger.debug(f"Processed DSPy result to dict: {output_dict}")
430
434
  # Optionally merge inputs back if desired (can make result dict large)
431
435
  final_result = {**inputs, **output_dict}
432
- return final_result
436
+
437
+ lm = dspy.settings.get("lm")
438
+ cost = sum([x["cost"] for x in lm.history if x["cost"] is not None])
439
+ lm_history = lm.inspect_history()
440
+
441
+ return final_result, cost, lm_history
433
442
 
434
443
  except Exception as conv_error:
435
444
  logger.error(
@@ -5,8 +5,17 @@ import ast
5
5
  import builtins
6
6
  import importlib
7
7
  import sys
8
+ import types
9
+ import typing
8
10
  from collections.abc import Mapping, Sequence
9
- from typing import TYPE_CHECKING, Any, get_args, get_origin
11
+ from typing import (
12
+ TYPE_CHECKING,
13
+ Any,
14
+ Literal,
15
+ Union,
16
+ get_args,
17
+ get_origin,
18
+ )
10
19
 
11
20
  from pydantic import BaseModel
12
21
 
@@ -14,6 +23,7 @@ from pydantic import BaseModel
14
23
  if TYPE_CHECKING:
15
24
  pass
16
25
 
26
+ from flock.core.flock_registry import get_registry
17
27
  from flock.core.logging.logging import get_logger
18
28
 
19
29
  logger = get_logger("serialization.utils")
@@ -23,6 +33,65 @@ logger = get_logger("serialization.utils")
23
33
 
24
34
  # --- Serialization Helper ---
25
35
 
36
+ # src/flock/util/hydrator.py (or import from serialization_utils)
37
+
38
+
39
+ def _format_type_to_string(type_hint: type) -> str:
40
+ """Converts a Python type object back into its string representation."""
41
+ # This needs to handle various typing scenarios (List, Dict, Union, Optional, Literal, custom types)
42
+ origin = typing.get_origin(type_hint)
43
+ args = typing.get_args(type_hint)
44
+
45
+ # Handle common cases first
46
+ if origin is list or origin is list:
47
+ if args:
48
+ return f"list[{_format_type_to_string(args[0])}]"
49
+ return "list[Any]" # Or just "list"
50
+ elif origin is dict or origin is dict:
51
+ if args and len(args) == 2:
52
+ return f"dict[{_format_type_to_string(args[0])}, {_format_type_to_string(args[1])}]"
53
+ return "dict[Any, Any]" # Or just "dict"
54
+ elif origin is Union or origin is types.UnionType:
55
+ # Handle Optional[T] as Union[T, NoneType]
56
+ if len(args) == 2 and type(None) in args:
57
+ inner_type = next(t for t in args if t is not type(None))
58
+ return _format_type_to_string(inner_type)
59
+ # return f"Optional[{_format_type_to_string(inner_type)}]"
60
+ return (
61
+ f"Union[{', '.join(_format_type_to_string(arg) for arg in args)}]"
62
+ )
63
+ elif origin is Literal:
64
+ formatted_args = []
65
+ for arg in args:
66
+ if isinstance(arg, str):
67
+ formatted_args.append(f"'{arg}'")
68
+ else:
69
+ formatted_args.append(str(arg))
70
+ return f"Literal[{', '.join(formatted_args)}]"
71
+ elif hasattr(
72
+ type_hint, "__forward_arg__"
73
+ ): # Handle ForwardRefs if necessary
74
+ return type_hint.__forward_arg__
75
+ elif hasattr(type_hint, "__name__"):
76
+ # Handle custom types registered in registry (get preferred name)
77
+ registry = get_registry()
78
+ for (
79
+ name,
80
+ reg_type,
81
+ ) in registry._types.items(): # Access internal for lookup
82
+ if reg_type == type_hint:
83
+ return name # Return registered name
84
+ return type_hint.__name__ # Fallback to class name if not registered
85
+ else:
86
+ # Fallback for complex types or types not handled above
87
+ type_repr = str(type_hint).replace("typing.", "") # Basic cleanup
88
+ type_repr = str(type_hint).replace("| None", "")
89
+ type_repr = type_repr.strip()
90
+ logger.debug(
91
+ f"Using fallback string representation for type: {type_repr}"
92
+ )
93
+ return type_repr
94
+
26
95
 
27
96
  def extract_identifiers_from_type_str(type_str: str) -> set[str]:
28
97
  """Extract all identifiers from a type annotation string using the AST."""
@@ -43,7 +43,7 @@ def display_hummingbird():
43
43
  """)
44
44
 
45
45
 
46
- def init_console(clear_screen: bool = True):
46
+ def init_console(clear_screen: bool = True, show_banner: bool = True):
47
47
  """Display the Flock banner."""
48
48
  banner_text = Text(
49
49
  f"""
@@ -60,7 +60,9 @@ def init_console(clear_screen: bool = True):
60
60
  )
61
61
  if clear_screen:
62
62
  console.clear()
63
- console.print(banner_text)
63
+
64
+ if show_banner:
65
+ console.print(banner_text)
64
66
  console.print(
65
67
  f"[italic]'Magpie'[/] milestone - [bold]white duck GmbH[/] - [cyan]https://whiteduck.de[/]\n"
66
68
  )