flock-core 0.3.30__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.

@@ -0,0 +1,283 @@
1
+ """YAML Editor for Flock CLI.
2
+
3
+ This module provides functionality to view, edit, and validate YAML configurations
4
+ for Flock and FlockAgent instances.
5
+ """
6
+
7
+ import os
8
+ import subprocess
9
+ import tempfile
10
+ from pathlib import Path
11
+
12
+ import questionary
13
+ import yaml
14
+ from rich.console import Console
15
+ from rich.panel import Panel
16
+ from rich.syntax import Syntax
17
+ from rich.table import Table
18
+
19
+ from flock.core.flock import Flock
20
+ from flock.core.flock_agent import FlockAgent
21
+ from flock.core.util.cli_helper import init_console
22
+
23
+ # Create console instance
24
+ console = Console()
25
+
26
+
27
+ def yaml_editor(flock_or_agent: Flock | FlockAgent | None = None):
28
+ """YAML Editor main entry point.
29
+
30
+ Args:
31
+ flock_or_agent: Optional Flock or FlockAgent instance to edit
32
+ """
33
+ init_console()
34
+ console.print(Panel("[bold green]YAML Editor[/]"), justify="center")
35
+
36
+ if flock_or_agent is None:
37
+ # If no object provided, provide options to load from file
38
+ _yaml_file_browser()
39
+ return
40
+
41
+ while True:
42
+ init_console()
43
+ console.print(Panel("[bold green]YAML Editor[/]"), justify="center")
44
+
45
+ # Determine object type
46
+ if isinstance(flock_or_agent, Flock):
47
+ obj_type = "Flock"
48
+ console.print(
49
+ f"Editing [bold cyan]Flock[/] with {len(flock_or_agent._agents)} agents"
50
+ )
51
+ elif isinstance(flock_or_agent, FlockAgent):
52
+ obj_type = "FlockAgent"
53
+ console.print(
54
+ f"Editing [bold cyan]FlockAgent[/]: {flock_or_agent.name}"
55
+ )
56
+ else:
57
+ console.print("[bold red]Error: Unknown object type[/]")
58
+ input("\nPress Enter to continue...")
59
+ return
60
+
61
+ console.line()
62
+
63
+ choice = questionary.select(
64
+ "What would you like to do?",
65
+ choices=[
66
+ questionary.Separator(line=" "),
67
+ "View Current YAML",
68
+ "Edit YAML Directly",
69
+ "Abstract Editor (Visual)",
70
+ "Validate YAML",
71
+ "Save to File",
72
+ questionary.Separator(),
73
+ "Back to Main Menu",
74
+ ],
75
+ ).ask()
76
+
77
+ if choice == "View Current YAML":
78
+ _view_yaml(flock_or_agent)
79
+ elif choice == "Edit YAML Directly":
80
+ flock_or_agent = _edit_yaml_directly(flock_or_agent)
81
+ elif choice == "Abstract Editor (Visual)":
82
+ flock_or_agent = _abstract_editor(flock_or_agent)
83
+ elif choice == "Validate YAML":
84
+ _validate_yaml(flock_or_agent)
85
+ elif choice == "Save to File":
86
+ _save_to_file(flock_or_agent)
87
+ elif choice == "Back to Main Menu":
88
+ break
89
+
90
+ if choice != "Back to Main Menu":
91
+ input("\nPress Enter to continue...")
92
+
93
+
94
+ def _yaml_file_browser():
95
+ """Browser for YAML files to load."""
96
+ console.print("\n[bold]YAML File Browser[/]")
97
+ console.line()
98
+
99
+ current_dir = os.getcwd()
100
+ console.print(f"Current directory: [cyan]{current_dir}[/]")
101
+
102
+ # List .yaml/.yml files in current directory
103
+ yaml_files = list(Path(current_dir).glob("*.yaml")) + list(
104
+ Path(current_dir).glob("*.yml")
105
+ )
106
+
107
+ if not yaml_files:
108
+ console.print("[yellow]No YAML files found in current directory.[/]")
109
+ input("\nPress Enter to continue...")
110
+ return
111
+
112
+ # Display files
113
+ table = Table(title="YAML Files")
114
+ table.add_column("Filename", style="cyan")
115
+ table.add_column("Size", style="green")
116
+ table.add_column("Last Modified", style="yellow")
117
+
118
+ for file in yaml_files:
119
+ table.add_row(
120
+ file.name, f"{file.stat().st_size} bytes", f"{file.stat().st_mtime}"
121
+ )
122
+
123
+ console.print(table)
124
+
125
+ # TODO: Add file selection and loading
126
+
127
+
128
+ def _view_yaml(obj: Flock | FlockAgent):
129
+ """View the YAML representation of an object.
130
+
131
+ Args:
132
+ obj: The object to view as YAML
133
+ """
134
+ yaml_str = obj.to_yaml()
135
+
136
+ # Display with syntax highlighting
137
+ syntax = Syntax(
138
+ yaml_str,
139
+ "yaml",
140
+ theme="monokai",
141
+ line_numbers=True,
142
+ code_width=100,
143
+ word_wrap=True,
144
+ )
145
+
146
+ init_console()
147
+ console.print(Panel("[bold green]YAML View[/]"), justify="center")
148
+ console.print(syntax)
149
+
150
+
151
+ def _edit_yaml_directly(obj: Flock | FlockAgent) -> Flock | FlockAgent:
152
+ """Edit the YAML representation directly using an external editor.
153
+
154
+ Args:
155
+ obj: The object to edit
156
+
157
+ Returns:
158
+ The updated object
159
+ """
160
+ # Convert to YAML
161
+ yaml_str = obj.to_yaml()
162
+
163
+ # Create a temporary file
164
+ with tempfile.NamedTemporaryFile(
165
+ suffix=".yaml", mode="w+", delete=False
166
+ ) as tmp:
167
+ tmp.write(yaml_str)
168
+ tmp_path = tmp.name
169
+
170
+ try:
171
+ # Determine which editor to use
172
+ editor = os.environ.get(
173
+ "EDITOR", "notepad" if os.name == "nt" else "nano"
174
+ )
175
+
176
+ # Open the editor
177
+ console.print(
178
+ f"\nOpening {editor} to edit YAML. Save and exit when done."
179
+ )
180
+ subprocess.call([editor, tmp_path])
181
+
182
+ # Read updated YAML
183
+ with open(tmp_path) as f:
184
+ updated_yaml = f.read()
185
+
186
+ # Parse back to object
187
+ try:
188
+ if isinstance(obj, Flock):
189
+ updated_obj = Flock.from_yaml(updated_yaml)
190
+ console.print("\n[green]✓[/] YAML parsed successfully!")
191
+ return updated_obj
192
+ elif isinstance(obj, FlockAgent):
193
+ updated_obj = FlockAgent.from_yaml(updated_yaml)
194
+ console.print("\n[green]✓[/] YAML parsed successfully!")
195
+ return updated_obj
196
+ except Exception as e:
197
+ console.print(f"\n[bold red]Error parsing YAML:[/] {e!s}")
198
+ console.print("\nKeeping original object.")
199
+ return obj
200
+
201
+ finally:
202
+ # Clean up the temporary file
203
+ try:
204
+ os.unlink(tmp_path)
205
+ except:
206
+ pass
207
+
208
+
209
+ def _abstract_editor(obj: Flock | FlockAgent) -> Flock | FlockAgent:
210
+ """Edit object using an abstract form-based editor.
211
+
212
+ Args:
213
+ obj: The object to edit
214
+
215
+ Returns:
216
+ The updated object
217
+ """
218
+ console.print("\n[yellow]Abstract visual editor not yet implemented.[/]")
219
+ console.print("Will provide a form-based editor for each field.")
220
+
221
+ # For now, just return the original object
222
+ return obj
223
+
224
+
225
+ def _validate_yaml(obj: Flock | FlockAgent):
226
+ """Validate the YAML representation of an object.
227
+
228
+ Args:
229
+ obj: The object to validate
230
+ """
231
+ try:
232
+ yaml_str = obj.to_yaml()
233
+
234
+ # Attempt to parse with PyYAML
235
+ yaml.safe_load(yaml_str)
236
+
237
+ # Attempt to deserialize back to object
238
+ if isinstance(obj, Flock):
239
+ Flock.from_yaml(yaml_str)
240
+ elif isinstance(obj, FlockAgent):
241
+ FlockAgent.from_yaml(yaml_str)
242
+
243
+ console.print("\n[green]✓[/] YAML validation successful!")
244
+ except Exception as e:
245
+ console.print(f"\n[bold red]YAML validation failed:[/] {e!s}")
246
+
247
+
248
+ def _save_to_file(obj: Flock | FlockAgent):
249
+ """Save object to a YAML file.
250
+
251
+ Args:
252
+ obj: The object to save
253
+ """
254
+ # Determine default filename based on object type
255
+ if isinstance(obj, Flock):
256
+ default_name = "my_flock.flock.yaml"
257
+ elif isinstance(obj, FlockAgent):
258
+ default_name = f"{obj.name}.agent.yaml"
259
+ else:
260
+ default_name = "unknown.yaml"
261
+
262
+ # Get file path
263
+ file_path = questionary.text(
264
+ "Enter file path to save YAML:",
265
+ default=default_name,
266
+ ).ask()
267
+
268
+ # Ensure the file has the correct extension
269
+ if not file_path.endswith((".yaml", ".yml")):
270
+ file_path += ".yaml"
271
+
272
+ # Create directory if it doesn't exist
273
+ save_path = Path(file_path)
274
+ save_path.parent.mkdir(parents=True, exist_ok=True)
275
+
276
+ try:
277
+ # Save to file
278
+ with open(file_path, "w") as f:
279
+ f.write(obj.to_yaml())
280
+
281
+ console.print(f"\n[green]✓[/] Saved to {file_path}")
282
+ except Exception as e:
283
+ console.print(f"\n[bold red]Error saving file:[/] {e!s}")
flock/core/flock.py CHANGED
@@ -73,6 +73,10 @@ class Flock(BaseModel, Serializable):
73
73
  It is serializable to various formats like YAML and JSON.
74
74
  """
75
75
 
76
+ name: str = Field(
77
+ default_factory=lambda: f"flock_{uuid.uuid4().hex[:8]}",
78
+ description="A unique identifier for this Flock instance.",
79
+ )
76
80
  model: str | None = Field(
77
81
  default="openai/gpt-4o",
78
82
  description="Default model identifier to be used for agents if not specified otherwise.",
@@ -527,6 +531,45 @@ class Flock(BaseModel, Serializable):
527
531
  host=host, port=port, server_name=server_name, create_ui=create_ui
528
532
  )
529
533
 
534
+ # --- CLI Start Method ---
535
+ def start_cli(
536
+ self,
537
+ server_name: str = "Flock CLI",
538
+ show_results: bool = False,
539
+ edit_mode: bool = False,
540
+ ) -> None:
541
+ """Start a CLI interface for this Flock instance.
542
+
543
+ This method loads the CLI with the current Flock instance already available,
544
+ allowing users to execute, edit, or manage agents from the existing configuration.
545
+
546
+ Args:
547
+ server_name: Optional name for the CLI interface
548
+ show_results: Whether to initially show results of previous runs
549
+ edit_mode: Whether to open directly in edit mode
550
+ """
551
+ # Import locally to avoid circular imports
552
+ try:
553
+ from flock.cli.loaded_flock_cli import start_loaded_flock_cli
554
+ except ImportError:
555
+ logger.error(
556
+ "CLI components not found. Cannot start CLI. "
557
+ "Ensure the CLI modules are properly installed."
558
+ )
559
+ return
560
+
561
+ logger.info(
562
+ f"Starting CLI interface with loaded Flock instance ({len(self._agents)} agents)"
563
+ )
564
+
565
+ # Pass the current Flock instance to the CLI
566
+ start_loaded_flock_cli(
567
+ flock=self,
568
+ server_name=server_name,
569
+ show_results=show_results,
570
+ edit_mode=edit_mode,
571
+ )
572
+
530
573
  # --- Static Method Loaders (Keep for convenience) ---
531
574
  @staticmethod
532
575
  def load_from_file(file_path: str) -> Flock:
@@ -528,87 +528,5 @@ def _auto_register_by_path():
528
528
  )
529
529
 
530
530
 
531
- def _auto_register_core():
532
- logger.debug("Auto-registering core Flock components and tools...")
533
- # Register base classes themselves if needed by name (e.g., for type checks)
534
- # _registry_instance.register_component(FlockEvaluator)
535
- # _registry_instance.register_component(FlockModule)
536
- # _registry_instance.register_component(FlockRouter)
537
-
538
- # Import and register known implementations
539
- try:
540
- from flock.evaluators.declarative.declarative_evaluator import (
541
- DeclarativeEvaluator,
542
- )
543
-
544
- _registry_instance.register_component(DeclarativeEvaluator)
545
- except ImportError:
546
- logger.warning("DeclarativeEvaluator not found for auto-registration.")
547
- try:
548
- from flock.evaluators.memory.memory_evaluator import MemoryEvaluator
549
-
550
- _registry_instance.register_component(MemoryEvaluator)
551
- except ImportError:
552
- logger.warning("MemoryEvaluator not found for auto-registration.")
553
- # Add other standard evaluators...
554
-
555
- try:
556
- from flock.modules.output.output_module import OutputModule
557
-
558
- _registry_instance.register_component(OutputModule)
559
- except ImportError:
560
- logger.warning("OutputModule not found for auto-registration.")
561
- try:
562
- from flock.modules.performance.metrics_module import MetricsModule
563
-
564
- _registry_instance.register_component(MetricsModule)
565
- except ImportError:
566
- logger.warning("MetricsModule not found for auto-registration.")
567
- try:
568
- from flock.modules.memory.memory_module import MemoryModule
569
-
570
- _registry_instance.register_component(MemoryModule)
571
- except ImportError:
572
- logger.warning("MemoryModule not found for auto-registration.")
573
- # Add other standard modules...
574
-
575
- try:
576
- from flock.routers.default.default_router import DefaultRouter
577
-
578
- _registry_instance.register_component(DefaultRouter)
579
- except ImportError:
580
- logger.warning("DefaultRouter not found for auto-registration.")
581
- try:
582
- from flock.routers.llm.llm_router import LLMRouter
583
-
584
- _registry_instance.register_component(LLMRouter)
585
- except ImportError:
586
- logger.warning("LLMRouter not found for auto-registration.")
587
- try:
588
- from flock.routers.agent.agent_router import AgentRouter
589
-
590
- _registry_instance.register_component(AgentRouter)
591
- except ImportError:
592
- logger.warning("AgentRouter not found for auto-registration.")
593
- # Add other standard routers...
594
-
595
- # Auto-register standard tools
596
- try:
597
- from flock.core.tools import (
598
- azure_tools,
599
- basic_tools,
600
- llm_tools,
601
- markdown_tools,
602
- )
603
- from flock.core.tools.dev_tools import github
604
-
605
- _registry_instance.register_module_components(basic_tools)
606
- _registry_instance.register_module_components(azure_tools)
607
- _registry_instance.register_module_components(github)
608
- _registry_instance.register_module_components(llm_tools)
609
- _registry_instance.register_module_components(markdown_tools)
610
- except ImportError as e:
611
- logger.warning(f"Could not auto-register standard tools: {e}")
612
-
613
-
614
- _auto_register_core()
531
+ # Bootstrapping the registry
532
+ _auto_register_by_path()
@@ -5,7 +5,6 @@ import re # Import re for parsing
5
5
  import typing
6
6
  from typing import Any, Literal
7
7
 
8
- from flock.core.flock_registry import get_registry # Use FlockRegistry
9
8
  from flock.core.logging.logging import get_logger
10
9
 
11
10
  # Import split_top_level (assuming it's moved or copied appropriately)
@@ -15,7 +14,6 @@ from flock.core.logging.logging import get_logger
15
14
  # Define split_top_level here or ensure it's imported
16
15
 
17
16
  logger = get_logger("mixin.dspy")
18
- FlockRegistry = get_registry() # Get singleton instance
19
17
 
20
18
  # Type definition for agent type override
21
19
  AgentType = Literal["ReAct", "Completion", "ChainOfThought"] | None
@@ -69,6 +67,11 @@ def _resolve_type_string(type_str: str) -> type:
69
67
  Handles built-ins, registered types, and common typing generics like
70
68
  List, Dict, Optional, Union, Literal.
71
69
  """
70
+ # Import registry here to avoid circular imports
71
+ from flock.core.flock_registry import get_registry
72
+
73
+ FlockRegistry = get_registry()
74
+
72
75
  type_str = type_str.strip()
73
76
  logger.debug(f"Attempting to resolve type string: '{type_str}'")
74
77
 
@@ -3,20 +3,20 @@
3
3
 
4
4
  import importlib
5
5
  from collections.abc import Mapping, Sequence
6
- from typing import Any
6
+ from typing import TYPE_CHECKING, Any
7
7
 
8
8
  from pydantic import BaseModel
9
9
 
10
- # Import the registry
11
- # from .callable_registry import CallableRegistry # Old way
12
- from flock.core.flock_registry import (
13
- COMPONENT_BASE_TYPES,
14
- get_registry, # New way
15
- )
10
+ # Use TYPE_CHECKING to avoid circular imports
11
+ if TYPE_CHECKING:
12
+ pass
13
+
16
14
  from flock.core.logging.logging import get_logger
17
15
 
18
16
  logger = get_logger("serialization.utils")
19
- FlockRegistry = get_registry() # Get singleton instance
17
+
18
+ # Remove this line to avoid circular import at module level
19
+ # FlockRegistry = get_registry() # Get singleton instance
20
20
 
21
21
  # --- Serialization Helper ---
22
22
 
@@ -26,6 +26,11 @@ def serialize_item(item: Any) -> Any:
26
26
  Converts known callables to their path strings using FlockRegistry.
27
27
  Converts Pydantic models using model_dump.
28
28
  """
29
+ # Import the registry lazily when needed
30
+ from flock.core.flock_registry import get_registry
31
+
32
+ FlockRegistry = get_registry()
33
+
29
34
  if isinstance(item, BaseModel):
30
35
  dumped = item.model_dump(mode="json", exclude_none=True)
31
36
  return serialize_item(dumped)
@@ -74,6 +79,11 @@ def deserialize_item(item: Any) -> Any:
74
79
  Converts reference dicts back to actual callables or types using FlockRegistry.
75
80
  Handles nested lists and dicts.
76
81
  """
82
+ # Import the registry lazily when needed
83
+ from flock.core.flock_registry import get_registry
84
+
85
+ FlockRegistry = get_registry()
86
+
77
87
  if isinstance(item, Mapping):
78
88
  if "__callable_ref__" in item and len(item) == 1:
79
89
  path_str = item["__callable_ref__"]
@@ -138,6 +148,11 @@ def deserialize_component(
138
148
  """Deserializes a component (Module, Evaluator, Router) from its dict representation.
139
149
  Uses the 'type' field to find the correct class via FlockRegistry.
140
150
  """
151
+ # Import the registry and COMPONENT_BASE_TYPES lazily when needed
152
+ from flock.core.flock_registry import COMPONENT_BASE_TYPES, get_registry
153
+
154
+ FlockRegistry = get_registry()
155
+
141
156
  if data is None:
142
157
  return None
143
158
  if not isinstance(data, dict):
@@ -9,6 +9,8 @@ from flock.core.mixin.prompt_parser import PromptParserMixin
9
9
 
10
10
 
11
11
  class DeclarativeEvaluatorConfig(FlockEvaluatorConfig):
12
+ """Configuration for the DeclarativeEvaluator."""
13
+
12
14
  agent_type_override: str | None = None
13
15
  model: str | None = "openai/gpt-4o"
14
16
  use_cache: bool = True
@@ -6,8 +6,12 @@ from typing import Any, Literal
6
6
  from pydantic import Field
7
7
  from tqdm import tqdm
8
8
 
9
- from flock.core import FlockAgent, FlockModule, FlockModuleConfig
10
9
  from flock.core.context.context import FlockContext
10
+
11
+ # if TYPE_CHECKING:
12
+ # from flock.core import FlockAgent
13
+ from flock.core.flock_agent import FlockAgent
14
+ from flock.core.flock_module import FlockModule, FlockModuleConfig
11
15
  from flock.core.logging.logging import get_logger
12
16
  from flock.modules.memory.memory_parser import MemoryMappingParser
13
17
  from flock.modules.memory.memory_storage import FlockMemoryStore, MemoryEntry
@@ -282,7 +286,10 @@ class MemoryModule(FlockModule):
282
286
  return set(concept_list)
283
287
 
284
288
  async def _summarize_mode(
285
- self, agent: FlockAgent, inputs: dict[str, Any], result: dict[str, Any]
289
+ self,
290
+ agent: FlockAgent,
291
+ inputs: dict[str, Any],
292
+ result: dict[str, Any],
286
293
  ) -> str:
287
294
  """Extract information chunks using summary mode."""
288
295
  split_signature = agent.create_dspy_signature_class(
@@ -300,7 +307,10 @@ class MemoryModule(FlockModule):
300
307
  return "\n".join(split_result.chunks)
301
308
 
302
309
  async def _semantic_splitter_mode(
303
- self, agent: FlockAgent, inputs: dict[str, Any], result: dict[str, Any]
310
+ self,
311
+ agent: FlockAgent,
312
+ inputs: dict[str, Any],
313
+ result: dict[str, Any],
304
314
  ) -> str | list[dict[str, str]]:
305
315
  """Extract information chunks using semantic mode."""
306
316
  split_signature = agent.create_dspy_signature_class(
@@ -318,7 +328,10 @@ class MemoryModule(FlockModule):
318
328
  return split_result.chunks
319
329
 
320
330
  async def _character_splitter_mode(
321
- self, agent: FlockAgent, inputs: dict[str, Any], result: dict[str, Any]
331
+ self,
332
+ agent: FlockAgent,
333
+ inputs: dict[str, Any],
334
+ result: dict[str, Any],
322
335
  ) -> list[str]:
323
336
  """Extract information chunks by splitting text into fixed character lengths."""
324
337
  full_text = json.dumps(inputs) + (json.dumps(result) if result else "")
@@ -3,11 +3,13 @@
3
3
  import json
4
4
  import os
5
5
  from datetime import datetime
6
- from typing import Any
6
+ from typing import TYPE_CHECKING, Any
7
7
 
8
8
  from pydantic import Field
9
9
 
10
- from flock.core import FlockAgent
10
+ if TYPE_CHECKING:
11
+ from flock.core import FlockAgent
12
+
11
13
  from flock.core.context.context import FlockContext
12
14
  from flock.core.flock_module import FlockModule, FlockModuleConfig
13
15
  from flock.core.logging.formatters.themed_formatter import (
@@ -17,6 +19,10 @@ from flock.core.logging.formatters.themes import OutputTheme
17
19
  from flock.core.logging.logging import get_logger
18
20
  from flock.core.serialization.json_encoder import FlockJSONEncoder
19
21
 
22
+ # from flock.core.logging.formatters.themes import OutputTheme
23
+ # from flock.core.logging.logging import get_logger
24
+ # from flock.core.serialization.json_encoder import FlockJSONEncoder
25
+
20
26
  logger = get_logger("module.output")
21
27
 
22
28
 
@@ -168,7 +174,7 @@ class OutputModule(FlockModule):
168
174
 
169
175
  async def post_evaluate(
170
176
  self,
171
- agent: FlockAgent,
177
+ agent: "FlockAgent",
172
178
  inputs: dict[str, Any],
173
179
  result: dict[str, Any],
174
180
  context: FlockContext | None = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flock-core
3
- Version: 0.3.30
3
+ Version: 0.3.31
4
4
  Summary: Declarative LLM Orchestration at Scale
5
5
  Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
6
6
  License-File: LICENSE