flock-core 0.4.0b19__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/mixin/dspy_integration.py +16 -7
- flock/core/util/cli_helper.py +4 -2
- flock/evaluators/declarative/declarative_evaluator.py +40 -6
- flock/modules/output/output_module.py +194 -0
- {flock_core-0.4.0b19.dist-info → flock_core-0.4.0b20.dist-info}/METADATA +32 -20
- {flock_core-0.4.0b19.dist-info → flock_core-0.4.0b20.dist-info}/RECORD +12 -10
- {flock_core-0.4.0b19.dist-info → flock_core-0.4.0b20.dist-info}/WHEEL +0 -0
- {flock_core-0.4.0b19.dist-info → flock_core-0.4.0b20.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.0b19.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)
|
|
@@ -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(
|
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
|
)
|
|
@@ -19,7 +19,7 @@ logger = get_logger("evaluators.declarative")
|
|
|
19
19
|
class DeclarativeEvaluatorConfig(FlockEvaluatorConfig):
|
|
20
20
|
"""Configuration for the DeclarativeEvaluator."""
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
override_evaluator_type: str | None = None
|
|
23
23
|
model: str | None = "openai/gpt-4o"
|
|
24
24
|
use_cache: bool = True
|
|
25
25
|
temperature: float = 0.0
|
|
@@ -28,6 +28,11 @@ class DeclarativeEvaluatorConfig(FlockEvaluatorConfig):
|
|
|
28
28
|
default=False,
|
|
29
29
|
description="Enable streaming output from the underlying DSPy program.",
|
|
30
30
|
)
|
|
31
|
+
include_thought_process: bool = Field(
|
|
32
|
+
default=False,
|
|
33
|
+
description="Include the thought process in the output.",
|
|
34
|
+
)
|
|
35
|
+
kwargs: dict[str, Any] = Field(default_factory=dict)
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
class DeclarativeEvaluator(
|
|
@@ -40,6 +45,9 @@ class DeclarativeEvaluator(
|
|
|
40
45
|
description="Evaluator configuration",
|
|
41
46
|
)
|
|
42
47
|
|
|
48
|
+
cost: float = 0.0
|
|
49
|
+
lm_history: list = Field(default_factory=list)
|
|
50
|
+
|
|
43
51
|
async def evaluate(
|
|
44
52
|
self, agent: FlockAgent, inputs: dict[str, Any], tools: list[Any]
|
|
45
53
|
) -> dict[str, Any]:
|
|
@@ -68,8 +76,9 @@ class DeclarativeEvaluator(
|
|
|
68
76
|
)
|
|
69
77
|
agent_task = self._select_task(
|
|
70
78
|
_dspy_signature,
|
|
71
|
-
|
|
79
|
+
override_evaluator_type=self.config.override_evaluator_type,
|
|
72
80
|
tools=tools,
|
|
81
|
+
kwargs=self.config.kwargs,
|
|
73
82
|
)
|
|
74
83
|
except Exception as setup_error:
|
|
75
84
|
logger.error(
|
|
@@ -109,21 +118,46 @@ class DeclarativeEvaluator(
|
|
|
109
118
|
if delta_content:
|
|
110
119
|
console.print(delta_content, end="")
|
|
111
120
|
|
|
112
|
-
result_dict = self._process_result(
|
|
121
|
+
result_dict, cost, lm_history = self._process_result(
|
|
122
|
+
chunk, inputs
|
|
123
|
+
)
|
|
124
|
+
self.cost = cost
|
|
125
|
+
self.lm_history = lm_history
|
|
113
126
|
|
|
114
127
|
console.print("\n")
|
|
115
|
-
return
|
|
128
|
+
return self.filter_thought_process(
|
|
129
|
+
result_dict, self.config.include_thought_process
|
|
130
|
+
)
|
|
116
131
|
|
|
117
132
|
else: # Non-streaming path
|
|
118
133
|
logger.info(f"Evaluating agent '{agent.name}' without streaming.")
|
|
119
134
|
try:
|
|
120
135
|
# Ensure the call is awaited if the underlying task is async
|
|
121
136
|
result_obj = agent_task(**inputs)
|
|
122
|
-
result_dict = self._process_result(
|
|
123
|
-
|
|
137
|
+
result_dict, cost, lm_history = self._process_result(
|
|
138
|
+
result_obj, inputs
|
|
139
|
+
)
|
|
140
|
+
self.cost = cost
|
|
141
|
+
self.lm_history = lm_history
|
|
142
|
+
return self.filter_thought_process(
|
|
143
|
+
result_dict, self.config.include_thought_process
|
|
144
|
+
)
|
|
124
145
|
except Exception as e:
|
|
125
146
|
logger.error(
|
|
126
147
|
f"Error during non-streaming evaluation for agent '{agent.name}': {e}",
|
|
127
148
|
exc_info=True,
|
|
128
149
|
)
|
|
129
150
|
raise RuntimeError(f"Evaluation failed: {e}") from e
|
|
151
|
+
|
|
152
|
+
def filter_thought_process(
|
|
153
|
+
result_dict: dict[str, Any], include_thought_process: bool
|
|
154
|
+
) -> dict[str, Any]:
|
|
155
|
+
"""Filter out thought process from the result dictionary."""
|
|
156
|
+
if include_thought_process:
|
|
157
|
+
return result_dict
|
|
158
|
+
else:
|
|
159
|
+
return {
|
|
160
|
+
k: v
|
|
161
|
+
for k, v in result_dict.items()
|
|
162
|
+
if not (k.startswith("reasoning") or k.startswith("trajectory"))
|
|
163
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Output formatting and display functionality for agents."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from flock.core.context.context_vars import FLOCK_BATCH_SILENT_MODE
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from flock.core import FlockAgent
|
|
11
|
+
|
|
12
|
+
from flock.core.context.context import FlockContext
|
|
13
|
+
from flock.core.flock_module import FlockModule, FlockModuleConfig
|
|
14
|
+
from flock.core.logging.formatters.themed_formatter import (
|
|
15
|
+
ThemedAgentResultFormatter,
|
|
16
|
+
)
|
|
17
|
+
from flock.core.logging.formatters.themes import OutputTheme
|
|
18
|
+
from flock.core.logging.logging import get_logger
|
|
19
|
+
|
|
20
|
+
# from flock.core.logging.formatters.themes import OutputTheme
|
|
21
|
+
# from flock.core.logging.logging import get_logger
|
|
22
|
+
# from flock.core.serialization.json_encoder import FlockJSONEncoder
|
|
23
|
+
|
|
24
|
+
logger = get_logger("module.output")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OutputModuleConfig(FlockModuleConfig):
|
|
28
|
+
"""Configuration for output formatting and display."""
|
|
29
|
+
|
|
30
|
+
theme: OutputTheme = Field(
|
|
31
|
+
default=OutputTheme.afterglow, description="Theme for output formatting"
|
|
32
|
+
)
|
|
33
|
+
render_table: bool = Field(
|
|
34
|
+
default=False, description="Whether to render output as a table"
|
|
35
|
+
)
|
|
36
|
+
max_length: int = Field(
|
|
37
|
+
default=1000, description="Maximum length for displayed output"
|
|
38
|
+
)
|
|
39
|
+
truncate_long_values: bool = Field(
|
|
40
|
+
default=True, description="Whether to truncate long values in display"
|
|
41
|
+
)
|
|
42
|
+
show_metadata: bool = Field(
|
|
43
|
+
default=True, description="Whether to show metadata like timestamps"
|
|
44
|
+
)
|
|
45
|
+
format_code_blocks: bool = Field(
|
|
46
|
+
default=True,
|
|
47
|
+
description="Whether to apply syntax highlighting to code blocks",
|
|
48
|
+
)
|
|
49
|
+
custom_formatters: dict[str, str] = Field(
|
|
50
|
+
default_factory=dict,
|
|
51
|
+
description="Custom formatters for specific output types",
|
|
52
|
+
)
|
|
53
|
+
no_output: bool = Field(
|
|
54
|
+
default=False,
|
|
55
|
+
description="Whether to suppress output",
|
|
56
|
+
)
|
|
57
|
+
print_context: bool = Field(
|
|
58
|
+
default=False,
|
|
59
|
+
description="Whether to print the context",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class OutputModule(FlockModule):
|
|
64
|
+
"""Module that handles output formatting and display."""
|
|
65
|
+
|
|
66
|
+
name: str = "output"
|
|
67
|
+
config: OutputModuleConfig = Field(
|
|
68
|
+
default_factory=OutputModuleConfig, description="Output configuration"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def __init__(self, name: str, config: OutputModuleConfig):
|
|
72
|
+
super().__init__(name=name, config=config)
|
|
73
|
+
self._formatter = ThemedAgentResultFormatter(
|
|
74
|
+
theme=self.config.theme,
|
|
75
|
+
max_length=self.config.max_length,
|
|
76
|
+
render_table=self.config.render_table,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def _format_value(self, value: Any, key: str) -> str:
|
|
80
|
+
"""Format a single value based on its type and configuration."""
|
|
81
|
+
# Check for custom formatter
|
|
82
|
+
if key in self.config.custom_formatters:
|
|
83
|
+
formatter_name = self.config.custom_formatters[key]
|
|
84
|
+
if hasattr(self, f"_format_{formatter_name}"):
|
|
85
|
+
return getattr(self, f"_format_{formatter_name}")(value)
|
|
86
|
+
|
|
87
|
+
# Default formatting based on type
|
|
88
|
+
if isinstance(value, dict):
|
|
89
|
+
return self._format_dict(value)
|
|
90
|
+
elif isinstance(value, list):
|
|
91
|
+
return self._format_list(value)
|
|
92
|
+
elif isinstance(value, str) and self.config.format_code_blocks:
|
|
93
|
+
return self._format_potential_code(value)
|
|
94
|
+
else:
|
|
95
|
+
return str(value)
|
|
96
|
+
|
|
97
|
+
def _format_dict(self, d: dict[str, Any], indent: int = 0) -> str:
|
|
98
|
+
"""Format a dictionary with proper indentation."""
|
|
99
|
+
lines = []
|
|
100
|
+
for k, v in d.items():
|
|
101
|
+
formatted_value = self._format_value(v, k)
|
|
102
|
+
if (
|
|
103
|
+
self.config.truncate_long_values
|
|
104
|
+
and len(formatted_value) > self.config.max_length
|
|
105
|
+
):
|
|
106
|
+
formatted_value = (
|
|
107
|
+
formatted_value[: self.config.max_length] + "..."
|
|
108
|
+
)
|
|
109
|
+
lines.append(f"{' ' * indent}{k}: {formatted_value}")
|
|
110
|
+
return "\n".join(lines)
|
|
111
|
+
|
|
112
|
+
def _format_list(self, lst: list[Any]) -> str:
|
|
113
|
+
"""Format a list with proper indentation."""
|
|
114
|
+
return "\n".join(f"- {self._format_value(item, '')}" for item in lst)
|
|
115
|
+
|
|
116
|
+
def _format_potential_code(self, text: str) -> str:
|
|
117
|
+
"""Format text that might contain code blocks."""
|
|
118
|
+
import re
|
|
119
|
+
|
|
120
|
+
def replace_code_block(match):
|
|
121
|
+
code = match.group(2)
|
|
122
|
+
lang = match.group(1) if match.group(1) else ""
|
|
123
|
+
# Here you could add syntax highlighting
|
|
124
|
+
return f"```{lang}\n{code}\n```"
|
|
125
|
+
|
|
126
|
+
# Replace code blocks with formatted versions
|
|
127
|
+
text = re.sub(
|
|
128
|
+
r"```(\w+)?\n(.*?)\n```", replace_code_block, text, flags=re.DOTALL
|
|
129
|
+
)
|
|
130
|
+
return text
|
|
131
|
+
|
|
132
|
+
async def post_evaluate(
|
|
133
|
+
self,
|
|
134
|
+
agent: "FlockAgent",
|
|
135
|
+
inputs: dict[str, Any],
|
|
136
|
+
result: dict[str, Any],
|
|
137
|
+
context: FlockContext | None = None,
|
|
138
|
+
) -> dict[str, Any]:
|
|
139
|
+
"""Format and display the output."""
|
|
140
|
+
logger.debug("Formatting and displaying output")
|
|
141
|
+
|
|
142
|
+
# Determine if output should be suppressed
|
|
143
|
+
is_silent = self.config.no_output or (
|
|
144
|
+
context and context.get_variable(FLOCK_BATCH_SILENT_MODE, False)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if is_silent:
|
|
148
|
+
logger.debug("Output suppressed (config or batch silent mode).")
|
|
149
|
+
return result # Skip console output
|
|
150
|
+
|
|
151
|
+
logger.debug("Formatting and displaying output to console.")
|
|
152
|
+
|
|
153
|
+
if self.config.print_context and context:
|
|
154
|
+
# Add context snapshot if requested (be careful with large contexts)
|
|
155
|
+
try:
|
|
156
|
+
# Create a copy or select relevant parts to avoid modifying original result dict directly
|
|
157
|
+
display_result = result.copy()
|
|
158
|
+
display_result["context_snapshot"] = (
|
|
159
|
+
context.to_dict()
|
|
160
|
+
) # Potential performance hit
|
|
161
|
+
except Exception:
|
|
162
|
+
display_result = result.copy()
|
|
163
|
+
display_result["context_snapshot"] = (
|
|
164
|
+
"[Error serializing context]"
|
|
165
|
+
)
|
|
166
|
+
result_to_display = display_result
|
|
167
|
+
else:
|
|
168
|
+
result_to_display = result
|
|
169
|
+
|
|
170
|
+
if not hasattr(self, "_formatter") or self._formatter is None:
|
|
171
|
+
self._formatter = ThemedAgentResultFormatter(
|
|
172
|
+
theme=self.config.theme,
|
|
173
|
+
max_length=self.config.max_length,
|
|
174
|
+
render_table=self.config.render_table,
|
|
175
|
+
wait_for_input=self.config.wait_for_input,
|
|
176
|
+
)
|
|
177
|
+
self._formatter.display_result(result_to_display, agent.name)
|
|
178
|
+
|
|
179
|
+
return result # Return the original, unmodified result
|
|
180
|
+
|
|
181
|
+
def update_theme(self, new_theme: OutputTheme) -> None:
|
|
182
|
+
"""Update the output theme."""
|
|
183
|
+
self.config.theme = new_theme
|
|
184
|
+
self._formatter = ThemedAgentResultFormatter(
|
|
185
|
+
theme=self.config.theme,
|
|
186
|
+
max_length=self.config.max_length,
|
|
187
|
+
render_table=self.config.render_table,
|
|
188
|
+
wait_for_input=self.config.wait_for_input,
|
|
189
|
+
write_to_file=self.config.write_to_file,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def add_custom_formatter(self, key: str, formatter_name: str) -> None:
|
|
193
|
+
"""Add a custom formatter for a specific output key."""
|
|
194
|
+
self.config.custom_formatters[key] = formatter_name
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flock-core
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.0b20
|
|
4
4
|
Summary: Declarative LLM Orchestration at Scale
|
|
5
5
|
Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -58,8 +58,6 @@ Requires-Dist: markdownify>=0.14.1; extra == 'tools'
|
|
|
58
58
|
Requires-Dist: tavily-python>=0.5.0; extra == 'tools'
|
|
59
59
|
Description-Content-Type: text/markdown
|
|
60
60
|
|
|
61
|
-
# 🚀 Flock: The Declarative AI Agent Framework 🚀
|
|
62
|
-
|
|
63
61
|
<p align="center">
|
|
64
62
|
<!-- Placeholder for your Flock Logo/Banner - Replace URL -->
|
|
65
63
|
<img alt="Flock Banner" src="https://raw.githubusercontent.com/whiteducksoftware/flock/master/docs/assets/images/flock.png" width="600">
|
|
@@ -95,20 +93,25 @@ Built with real-world deployment in mind, Flock integrates seamlessly with tools
|
|
|
95
93
|
|
|
96
94
|
Flock offers a different way to build agentic systems:
|
|
97
95
|
|
|
98
|
-
| Traditional Agent Frameworks 😟 | Flock Framework 🐤🐧🐓🦆
|
|
99
|
-
|
|
|
100
|
-
| 🤯 **Prompt Nightmare** | ✅ **Declarative Simplicity**
|
|
101
|
-
| *Long, brittle, hard-to-tune prompts*
|
|
102
|
-
| 💥 **Fragile & Unpredictable** | ⚡ **Robust & Production-Ready**
|
|
103
|
-
| *Single errors can halt everything*
|
|
104
|
-
| 🧩 **Monolithic & Rigid**
|
|
105
|
-
| *Hard to extend or modify logic*
|
|
106
|
-
| ⛓️ **Basic Chaining**
|
|
107
|
-
| *Often just linear workflows*
|
|
108
|
-
| 🧪 **Difficult Testing** | ✅ **Testable Components**
|
|
109
|
-
| *Hard to unit test prompt logic*
|
|
110
|
-
| 📄 **Unstructured Output** | ✨ **Structured Data Handling**
|
|
111
|
-
| *Parsing unreliable LLM text output*
|
|
96
|
+
| Traditional Agent Frameworks 😟 | Flock Framework 🐤🐧🐓🦆 |
|
|
97
|
+
| :------------------------------------ | :------------------------------------- |
|
|
98
|
+
| 🤯 **Prompt Nightmare** | ✅ **Declarative Simplicity** |
|
|
99
|
+
| *Long, brittle, hard-to-tune prompts* | *Clear input/output specs (typed!)* |
|
|
100
|
+
| 💥 **Fragile & Unpredictable** | ⚡ **Robust & Production-Ready** |
|
|
101
|
+
| *Single errors can halt everything* | *Fault-tolerant via Temporal option* |
|
|
102
|
+
| 🧩 **Monolithic & Rigid** | 🔧 **Modular & Flexible** |
|
|
103
|
+
| *Hard to extend or modify logic* | *Pluggable Evaluators, Modules, Tools* |
|
|
104
|
+
| ⛓️ **Basic Chaining** | 🚀 **Advanced Orchestration** |
|
|
105
|
+
| *Often just linear workflows* | *Dynamic Routing, Batch Processing* |
|
|
106
|
+
| 🧪 **Difficult Testing** | ✅ **Testable Components** |
|
|
107
|
+
| *Hard to unit test prompt logic* | *Clear I/O contracts aid testing* |
|
|
108
|
+
| 📄 **Unstructured Output** | ✨ **Structured Data Handling** |
|
|
109
|
+
| *Parsing unreliable LLM text output* | *Native Pydantic/Typed Dict support* |
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## 📹 Video Demo
|
|
113
|
+
|
|
114
|
+
https://github.com/user-attachments/assets/bdab4786-d532-459f-806a-024727164dcc
|
|
112
115
|
|
|
113
116
|
## 💡 Core Concepts
|
|
114
117
|
|
|
@@ -210,7 +213,17 @@ if __name__ == "__main__":
|
|
|
210
213
|
print("Ensure your LLM API key (e.g., OPENAI_API_KEY) is set in your .env file!")
|
|
211
214
|
```
|
|
212
215
|
|
|
213
|
-
##
|
|
216
|
+
## 🐤 New in Flock 0.4.0 `Magpie` 🐤
|
|
217
|
+
|
|
218
|
+
### REST API - Deploy Flock Agents as REST API Endpoints
|
|
219
|
+
|
|
220
|
+
### Web UI - Test Flock Agents in the Browser
|
|
221
|
+
|
|
222
|
+
### CLI Tool - Manage Flock Agents via the Command Line
|
|
223
|
+
|
|
224
|
+
### Serialization - Share, Deploy, and Run Flock Agents by human readable yaml files
|
|
225
|
+
|
|
226
|
+
### ✨ Utility: @flockclass Hydrator
|
|
214
227
|
|
|
215
228
|
Flock also provides conveniences. The @flockclass decorator allows you to easily populate Pydantic models using an LLM:
|
|
216
229
|
|
|
@@ -269,11 +282,10 @@ Ways to contribute:
|
|
|
269
282
|
- Improve documentation.
|
|
270
283
|
- Contribute new Modules, Evaluators, or Routers.
|
|
271
284
|
- Add examples to the flock-showcase repository.
|
|
272
|
-
- Join our Discord Community to discuss development! <!-- Add Discord link -->
|
|
273
285
|
|
|
274
286
|
## 📜 License
|
|
275
287
|
|
|
276
|
-
Flock is licensed under the
|
|
288
|
+
Flock is licensed under the MIT License. See the LICENSE file for details.
|
|
277
289
|
|
|
278
290
|
## 🏢 About
|
|
279
291
|
|
|
@@ -14,14 +14,15 @@ flock/cli/manage_agents.py,sha256=Psl014LCrJmBgwrjsp7O3WNlWvQmVd_IDud3rd0lnLI,12
|
|
|
14
14
|
flock/cli/registry_management.py,sha256=mAHy3wT97YgODR0gVOkTXDqR5NIPzM-E-z9dEtw9-tw,29790
|
|
15
15
|
flock/cli/runner.py,sha256=TgiuhRLkpa6dn3C-3eCmWx-bWUlTjaH0sD7Y-O7MrYM,1122
|
|
16
16
|
flock/cli/settings.py,sha256=Z_TXBzCYlCmSaKrJ_CQCdYy-Cj29gpI4kbC_2KzoKqg,27025
|
|
17
|
+
flock/cli/utils.py,sha256=JJrvM-1D2tbWkicrtkhOgRqVqYb0MdA2XtHYGOYuPRw,4644
|
|
17
18
|
flock/cli/view_results.py,sha256=dOzK0O1FHSIDERnx48y-2Xke9BkOHS7pcOhs64AyIg0,781
|
|
18
19
|
flock/cli/yaml_editor.py,sha256=K3N0bh61G1TSDAZDnurqW9e_-hO6CtSQKXQqlDhCjVo,12527
|
|
19
20
|
flock/cli/assets/release_notes.md,sha256=bqnk50jxM3w5uY44Dc7MkdT8XmRREFxrVBAG9XCOSSU,4896
|
|
20
21
|
flock/core/__init__.py,sha256=p7lmQULRu9ejIAELfanZiyMhW0CougIPvyFHW2nqBFQ,847
|
|
21
|
-
flock/core/flock.py,sha256=
|
|
22
|
+
flock/core/flock.py,sha256=K7EsY7K8avH57mJ3I_y0Ehlk0G8bYt4ZpBFYPtjgZ4o,25420
|
|
22
23
|
flock/core/flock_agent.py,sha256=ZmkiHd2oLao_263b7nmf26TQfyevX9_HNlhHPIkd3UM,33497
|
|
23
24
|
flock/core/flock_evaluator.py,sha256=dOXZeDOGZcAmJ9ahqq_2bdGUU1VOXY4skmwTVpAjiVw,1685
|
|
24
|
-
flock/core/flock_factory.py,sha256=
|
|
25
|
+
flock/core/flock_factory.py,sha256=vLkCASLh7Vrb5NFjb4ZQT5xN3zsUDud51hAQxb82oTk,2993
|
|
25
26
|
flock/core/flock_module.py,sha256=96aFVYAgwpKN53xGbivQDUpikOYGFCxK5mqhclOcxY0,3003
|
|
26
27
|
flock/core/flock_registry.py,sha256=ekYpQgSkZVnbyPbl8gA7nf54brt94rYZZBe2RwEGtUc,20828
|
|
27
28
|
flock/core/flock_router.py,sha256=A5GaxcGvtiFlRLHBTW7okh5RDm3BdKam2uXvRHRaj7k,2187
|
|
@@ -55,7 +56,7 @@ flock/core/logging/span_middleware/baggage_span_processor.py,sha256=gJfRl8FeB6jd
|
|
|
55
56
|
flock/core/logging/telemetry_exporter/base_exporter.py,sha256=rQJJzS6q9n2aojoSqwCnl7ZtHrh5LZZ-gkxUuI5WfrQ,1124
|
|
56
57
|
flock/core/logging/telemetry_exporter/file_exporter.py,sha256=nKAjJSZtA7FqHSTuTiFtYYepaxOq7l1rDvs8U8rSBlA,3023
|
|
57
58
|
flock/core/logging/telemetry_exporter/sqlite_exporter.py,sha256=CDsiMb9QcqeXelZ6ZqPSS56ovMPGqOu6whzBZRK__Vg,3498
|
|
58
|
-
flock/core/mixin/dspy_integration.py,sha256=
|
|
59
|
+
flock/core/mixin/dspy_integration.py,sha256=MplWCkJZymtmf1646yUYlpBxaz39SOenW7EQ1SpSQnE,17681
|
|
59
60
|
flock/core/mixin/prompt_parser.py,sha256=eOqI-FK3y17gVqpc_y5GF-WmK1Jv8mFlkZxTcgweoxI,5121
|
|
60
61
|
flock/core/serialization/__init__.py,sha256=CML7fPgG6p4c0CDBlJ_uwV1aZZhJKK9uy3IoIHfO87w,431
|
|
61
62
|
flock/core/serialization/callable_registry.py,sha256=sUZECTZWsM3fJ8FDRQ-FgLNW9hF26nY17AD6fJKADMc,1419
|
|
@@ -70,12 +71,12 @@ flock/core/tools/llm_tools.py,sha256=Bdt4Dpur5dGpxd2KFEQyxjfZazvW1HCDKY6ydMj6UgQ
|
|
|
70
71
|
flock/core/tools/markdown_tools.py,sha256=W6fGM48yGHbifVlaOk1jOtVcybfRbRmf20VbDOZv8S4,6031
|
|
71
72
|
flock/core/tools/zendesk_tools.py,sha256=deZAyUi9j-_yZaTayLQVJaFXIqIct-P6C8IGN5UU_tM,3528
|
|
72
73
|
flock/core/tools/dev_tools/github.py,sha256=a2OTPXS7kWOVA4zrZHynQDcsmEi4Pac5MfSjQOLePzA,5308
|
|
73
|
-
flock/core/util/cli_helper.py,sha256=
|
|
74
|
+
flock/core/util/cli_helper.py,sha256=nbA3n7zpVopV7bDf_aB3HHRYXmIBSwT1yqpjiVl4r9g,49848
|
|
74
75
|
flock/core/util/file_path_utils.py,sha256=Odf7uU32C-x1KNighbNERSiMtkzW4h8laABIoFK7A5M,6246
|
|
75
76
|
flock/core/util/hydrator.py,sha256=z0kvqr4j3piExOb_T5EgATuNm77ZlqIKvLpt4PJusO8,10949
|
|
76
77
|
flock/core/util/input_resolver.py,sha256=g9vDPdY4OH-G7qjas5ksGEHueokHGFPMoLOvC-ngeLo,5984
|
|
77
78
|
flock/core/util/loader.py,sha256=j3q2qem5bFMP2SmMuYjb-ISxsNGNZd1baQmpvAnRUUk,2244
|
|
78
|
-
flock/evaluators/declarative/declarative_evaluator.py,sha256=
|
|
79
|
+
flock/evaluators/declarative/declarative_evaluator.py,sha256=Pi1GsQUGL8eJrl6GFWYpu7O9kXI11xIskdBVg08n8mo,6055
|
|
79
80
|
flock/evaluators/memory/azure_search_evaluator.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
81
|
flock/evaluators/memory/memory_evaluator.py,sha256=SmerXyNaqm8DTV0yw-WqWkn9DXIf6x-nPG1eyTV6NY8,3452
|
|
81
82
|
flock/evaluators/natural_language/natural_language_evaluator.py,sha256=6nVEeh8_uwv_h-d3FWlA0GbzDzRtdhvxCGKirHtyvOU,2012
|
|
@@ -86,6 +87,7 @@ flock/modules/callback/callback_module.py,sha256=volGGgHtY19qj1wHR6m5a_hmXSbV3Ca
|
|
|
86
87
|
flock/modules/memory/memory_module.py,sha256=bSkdFBW-Pp5ldHhXi8v4kfRM7zknfLR2fsOtbTosucI,14916
|
|
87
88
|
flock/modules/memory/memory_parser.py,sha256=FLH7GL8XThvHiCMfX3eQH7Sz-f62fzhAUmO6_gaDI7U,4372
|
|
88
89
|
flock/modules/memory/memory_storage.py,sha256=CNcLDMmvv0x7Z3YMKr6VveS_VCa7rKPw8l2d-XgqokA,27246
|
|
90
|
+
flock/modules/output/output_module.py,sha256=tpNqkL8cB0cvikr1fmEJ-5E2SAeb-rYhht8IP90mEF0,7281
|
|
89
91
|
flock/modules/performance/metrics_module.py,sha256=UD9OjY4-zAvauMD7YyDYqE1gyIhzpdr3JkBT8j9knxY,16790
|
|
90
92
|
flock/modules/zep/zep_module.py,sha256=x7JG6O6xnwwum0RETIqKYbA3xzdcvX2aUuns0Cl0c2Q,6014
|
|
91
93
|
flock/platform/docker_tools.py,sha256=fpA7-6rJBjPOUBLdQP4ny2QPgJ_042nmqRn5GtKnoYw,1445
|
|
@@ -439,8 +441,8 @@ flock/workflow/activities.py,sha256=eVZDnxGJl_quNO-UTV3YgvTV8LrRaHN3QDAA1ANKzac,
|
|
|
439
441
|
flock/workflow/agent_activities.py,sha256=NhBZscflEf2IMfSRa_pBM_TRP7uVEF_O0ROvWZ33eDc,963
|
|
440
442
|
flock/workflow/temporal_setup.py,sha256=VWBgmBgfTBjwM5ruS_dVpA5AVxx6EZ7oFPGw4j3m0l0,1091
|
|
441
443
|
flock/workflow/workflow.py,sha256=I9MryXW_bqYVTHx-nl2epbTqeRy27CAWHHA7ZZA0nAk,1696
|
|
442
|
-
flock_core-0.4.
|
|
443
|
-
flock_core-0.4.
|
|
444
|
-
flock_core-0.4.
|
|
445
|
-
flock_core-0.4.
|
|
446
|
-
flock_core-0.4.
|
|
444
|
+
flock_core-0.4.0b20.dist-info/METADATA,sha256=gHmRUklybXUkMM1Z_XDaMxCcgVzOpSeS9ksy1MUZfIw,12970
|
|
445
|
+
flock_core-0.4.0b20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
446
|
+
flock_core-0.4.0b20.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
|
|
447
|
+
flock_core-0.4.0b20.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
|
|
448
|
+
flock_core-0.4.0b20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|