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 +135 -0
- flock/core/flock.py +2 -3
- flock/core/flock_factory.py +2 -0
- flock/core/logging/logging.py +2 -0
- flock/core/mixin/dspy_integration.py +16 -7
- flock/core/serialization/serialization_utils.py +70 -1
- flock/core/util/cli_helper.py +4 -2
- flock/core/util/hydrator.py +305 -285
- flock/evaluators/declarative/declarative_evaluator.py +40 -6
- flock_core-0.4.0b20.dist-info/METADATA +292 -0
- {flock_core-0.4.0b18.dist-info → flock_core-0.4.0b20.dist-info}/RECORD +14 -13
- flock_core-0.4.0b18.dist-info/METADATA +0 -572
- {flock_core-0.4.0b18.dist-info → flock_core-0.4.0b20.dist-info}/WHEEL +0 -0
- {flock_core-0.4.0b18.dist-info → flock_core-0.4.0b20.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.0b18.dist-info → flock_core-0.4.0b20.dist-info}/licenses/LICENSE +0 -0
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
|
-
|
|
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
|
-
|
|
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
|
|
flock/core/flock_factory.py
CHANGED
|
@@ -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)
|
flock/core/logging/logging.py
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
380
|
+
if not kwargs:
|
|
381
|
+
kwargs = {"max_iters": 10}
|
|
380
382
|
dspy_program = dspy.ReAct(
|
|
381
|
-
signature, tools=processed_tools or [],
|
|
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
|
-
|
|
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
|
|
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."""
|
flock/core/util/cli_helper.py
CHANGED
|
@@ -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
|
-
|
|
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
|
)
|