flock-core 0.2.14__py3-none-any.whl → 0.2.16__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/__init__.py CHANGED
@@ -1,5 +1,8 @@
1
1
  """Flock package initialization."""
2
2
 
3
+ from flock.cli.constants import CLI_THEME_BUILDER
4
+ from flock.core.logging.formatters.theme_builder import theme_builder
5
+
3
6
 
4
7
  def main():
5
8
  """Main function."""
@@ -35,6 +38,7 @@ def main():
35
38
  CLI_LOAD_FLOCK,
36
39
  # CLI_LOAD_EXAMPLE,
37
40
  questionary.Separator(),
41
+ CLI_THEME_BUILDER,
38
42
  CLI_SETTINGS,
39
43
  questionary.Separator(),
40
44
  CLI_START_ADVANCED_MODE,
@@ -45,6 +49,8 @@ def main():
45
49
 
46
50
  if result == CLI_LOAD_FLOCK:
47
51
  load_flock()
52
+ if result == CLI_THEME_BUILDER:
53
+ theme_builder()
48
54
 
49
55
 
50
56
  if __name__ == "__main__":
flock/cli/constants.py CHANGED
@@ -4,6 +4,7 @@ CLI_CREATE_AGENT = "Create an agent"
4
4
  CLI_CREATE_FLOCK = "Create a flock"
5
5
  CLI_LOAD_AGENT = "Load an agent"
6
6
  CLI_LOAD_FLOCK = "Load a *.flock file"
7
+ CLI_THEME_BUILDER = "Theme builder"
7
8
  CLI_LOAD_EXAMPLE = "Load a example"
8
9
  CLI_SETTINGS = "Settings"
9
10
  CLI_START_ADVANCED_MODE = "Start advanced mode (coming soon)"
flock/config.py CHANGED
@@ -44,4 +44,3 @@ TELEMETRY = TelemetryConfig(
44
44
  OTEL_ENABLE_FILE,
45
45
  OTEL_ENABLE_SQL,
46
46
  )
47
- TELEMETRY.setup_tracing()
@@ -1,12 +1,16 @@
1
1
  # src/your_package/core/execution/local_executor.py
2
2
  from flock.core.context.context import FlockContext
3
3
  from flock.core.logging.logging import get_logger
4
- from flock.workflow.activities import run_agent # This should be the local activity function
4
+ from flock.workflow.activities import (
5
+ run_agent, # This should be the local activity function
6
+ )
5
7
 
6
8
  logger = get_logger("flock")
7
9
 
8
10
 
9
- async def run_local_workflow(context: FlockContext, output_formatter, box_result: bool = True) -> dict:
11
+ async def run_local_workflow(
12
+ context: FlockContext, box_result: bool = True
13
+ ) -> dict:
10
14
  """Execute the agent workflow locally (for debugging).
11
15
 
12
16
  Args:
@@ -18,7 +22,7 @@ async def run_local_workflow(context: FlockContext, output_formatter, box_result
18
22
  A dictionary containing the workflow result.
19
23
  """
20
24
  logger.info("Running local debug workflow")
21
- result = await run_agent(context, output_formatter)
25
+ result = await run_agent(context)
22
26
  if box_result:
23
27
  from box import Box
24
28
 
@@ -1,9 +1,7 @@
1
1
  # src/your_package/core/execution/temporal_executor.py
2
- from devtools import pprint
3
2
 
4
3
  from flock.core.context.context import FlockContext
5
4
  from flock.core.context.context_vars import FLOCK_RUN_ID
6
- from flock.core.logging.formatters.formatter_factory import FormatterFactory
7
5
  from flock.core.logging.logging import get_logger
8
6
  from flock.workflow.activities import (
9
7
  run_agent, # Activity function used in Temporal
@@ -16,7 +14,6 @@ logger = get_logger("flock")
16
14
 
17
15
  async def run_temporal_workflow(
18
16
  context: FlockContext,
19
- output_formatter,
20
17
  box_result: bool = True,
21
18
  ) -> dict:
22
19
  """Execute the agent workflow via Temporal for robust, distributed processing.
@@ -43,11 +40,7 @@ async def run_temporal_workflow(
43
40
 
44
41
  agent_name = context.get_variable("FLOCK_CURRENT_AGENT")
45
42
  logger.debug("Formatting Temporal result", agent=agent_name)
46
- if output_formatter:
47
- formatter = FormatterFactory.create_formatter(output_formatter)
48
- formatter.display(result, agent_name, output_formatter.wait_for_input)
49
- else:
50
- pprint(result)
43
+
51
44
  if box_result:
52
45
  from box import Box
53
46
 
flock/core/flock.py CHANGED
@@ -10,13 +10,12 @@ import cloudpickle
10
10
  from opentelemetry import trace
11
11
  from opentelemetry.baggage import get_baggage, set_baggage
12
12
 
13
+ from flock.config import TELEMETRY
13
14
  from flock.core.context.context import FlockContext
14
15
  from flock.core.context.context_manager import initialize_context
15
16
  from flock.core.execution.local_executor import run_local_workflow
16
17
  from flock.core.execution.temporal_executor import run_temporal_workflow
17
18
  from flock.core.flock_agent import FlockAgent
18
- from flock.core.logging.formatters.base_formatter import FormatterOptions
19
- from flock.core.logging.formatters.pprint_formatter import PrettyPrintFormatter
20
19
  from flock.core.logging.logging import get_logger
21
20
  from flock.core.registry.agent_registry import Registry
22
21
  from flock.core.util.cli_helper import display_banner
@@ -24,7 +23,7 @@ from flock.core.util.input_resolver import top_level_to_keys
24
23
 
25
24
  T = TypeVar("T", bound=FlockAgent)
26
25
  logger = get_logger("flock")
27
-
26
+ TELEMETRY.setup_tracing()
28
27
  tracer = trace.get_tracer(__name__)
29
28
 
30
29
 
@@ -40,9 +39,6 @@ class Flock:
40
39
  model: str = "openai/gpt-4o",
41
40
  local_debug: bool = False,
42
41
  enable_logging: bool = False,
43
- output_formatter: FormatterOptions = FormatterOptions(
44
- PrettyPrintFormatter
45
- ),
46
42
  show_cli_banner: bool = True,
47
43
  ):
48
44
  """Initialize the Flock orchestrator.
@@ -57,9 +53,6 @@ class Flock:
57
53
  span.set_attribute("model", model)
58
54
  span.set_attribute("local_debug", local_debug)
59
55
  span.set_attribute("enable_logging", enable_logging)
60
- span.set_attribute(
61
- "output_formatter", output_formatter.formatter.__name__
62
- )
63
56
  logger.info(
64
57
  "Initializing Flock",
65
58
  model=model,
@@ -80,7 +73,6 @@ class Flock:
80
73
  self.context = FlockContext()
81
74
  self.model = model
82
75
  self.local_debug = local_debug
83
- self.output_formatter = output_formatter
84
76
  self.start_agent: FlockAgent | str | None = None
85
77
  self.input: dict = {}
86
78
 
@@ -410,13 +402,9 @@ class Flock:
410
402
  )
411
403
 
412
404
  if self.local_debug:
413
- return await run_local_workflow(
414
- self.context, self.output_formatter, box_result
415
- )
405
+ return await run_local_workflow(self.context, box_result)
416
406
  else:
417
- return await run_temporal_workflow(
418
- self.context, self.output_formatter, box_result
419
- )
407
+ return await run_temporal_workflow(self.context, box_result)
420
408
  except Exception as e:
421
409
  logger.exception("Execution failed", error=str(e))
422
410
  raise
flock/core/flock_agent.py CHANGED
@@ -12,6 +12,10 @@ import cloudpickle
12
12
  from pydantic import BaseModel, Field
13
13
 
14
14
  from flock.core.context.context import FlockContext
15
+ from flock.core.logging.formatters.themed_formatter import (
16
+ ThemedAgentResultFormatter,
17
+ )
18
+ from flock.core.logging.formatters.themes import OutputTheme
15
19
  from flock.core.logging.logging import get_logger
16
20
  from flock.core.mixin.dspy_integration import AgentType, DSPyIntegrationMixin
17
21
  from flock.core.mixin.prompt_parser import PromptParserMixin
@@ -48,6 +52,25 @@ class FlockAgentConfig:
48
52
  )
49
53
 
50
54
 
55
+ @dataclass
56
+ class FlockAgentOutputConfig:
57
+ """Configuration options for a FlockAgent."""
58
+
59
+ render_table: bool = field(
60
+ default=False, metadata={"description": "Renders a table."}
61
+ )
62
+ theme: OutputTheme = field( # type: ignore
63
+ default=OutputTheme.afterglow,
64
+ metadata={"description": "Disables the agent's output."},
65
+ )
66
+ max_length: int = field(
67
+ default=1000, metadata={"description": "Disables the agent's output."}
68
+ )
69
+ wait_for_input: bool = field(
70
+ default=False, metadata={"description": "Wait for input."}
71
+ )
72
+
73
+
51
74
  @dataclass
52
75
  class HandOff:
53
76
  """Base class for handoff returns."""
@@ -136,8 +159,8 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
136
159
  """
137
160
 
138
161
  name: str = Field(..., description="Unique identifier for the agent.")
139
- model: str = Field(
140
- "openai/gpt-4o", description="The model to use (e.g., 'openai/gpt-4o')."
162
+ model: str | None = Field(
163
+ None, description="The model to use (e.g., 'openai/gpt-4o')."
141
164
  )
142
165
  description: str | Callable[..., str] = Field(
143
166
  "", description="A human-readable description of the agent."
@@ -186,6 +209,11 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
186
209
  description="Configuration options for the agent, such as serialization settings.",
187
210
  )
188
211
 
212
+ output_config: FlockAgentOutputConfig = Field(
213
+ default_factory=FlockAgentOutputConfig,
214
+ description="Configuration options for the agent's output.",
215
+ )
216
+
189
217
  # Lifecycle callback fields: if provided, these callbacks are used instead of overriding the methods.
190
218
  initialize_callback: Callable[[dict[str, Any]], Awaitable[None]] | None = (
191
219
  Field(
@@ -414,6 +442,7 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
414
442
  try:
415
443
  await self.initialize(inputs)
416
444
  result = await self.evaluate(inputs)
445
+ self.display_output(result)
417
446
  await self.terminate(inputs, result)
418
447
  span.set_attribute("result", str(result))
419
448
  logger.info("Agent run completed", agent=self.name)
@@ -426,6 +455,18 @@ class FlockAgent(BaseModel, ABC, PromptParserMixin, DSPyIntegrationMixin):
426
455
  span.record_exception(run_error)
427
456
  raise
428
457
 
458
+ def display_info(self) -> None:
459
+ pass
460
+
461
+ def display_output(self, result: dict[str, Any]) -> None:
462
+ """Display the agent's output using the configured output formatter."""
463
+ ThemedAgentResultFormatter(
464
+ self.output_config.theme,
465
+ self.output_config.max_length,
466
+ self.output_config.render_table,
467
+ self.output_config.wait_for_input,
468
+ ).display_result(result, self.name)
469
+
429
470
  async def run_temporal(self, inputs: dict[str, Any]) -> dict[str, Any]:
430
471
  """Execute this agent via a Temporal workflow for enhanced fault tolerance and asynchronous processing.
431
472
 
@@ -0,0 +1,38 @@
1
+ """Enum Builder."""
2
+
3
+ import os
4
+ import pathlib
5
+ import re
6
+
7
+ theme_folder = pathlib.Path(__file__).parent.parent.parent.parent / "themes"
8
+
9
+ if not theme_folder.exists():
10
+ raise FileNotFoundError(f"Theme folder not found: {theme_folder}")
11
+
12
+ theme_files = [
13
+ pathlib.Path(f.path).stem for f in os.scandir(theme_folder) if f.is_file()
14
+ ]
15
+
16
+ theme_enum_entries = {}
17
+ for theme in theme_files:
18
+ safe_name = (
19
+ theme.replace("-", "_")
20
+ .replace(" ", "_")
21
+ .replace("(", "_")
22
+ .replace(")", "_")
23
+ .replace("+", "_")
24
+ .replace(".", "_")
25
+ )
26
+
27
+ if re.match(r"^\d", safe_name):
28
+ safe_name = f"_{safe_name}"
29
+
30
+ theme_enum_entries[safe_name] = theme
31
+
32
+ with open("theme_enum.py", "w") as f:
33
+ f.write("from enum import Enum\n\n")
34
+ f.write("class OutputOptionsTheme(Enum):\n")
35
+ for safe_name, original_name in theme_enum_entries.items():
36
+ f.write(f' {safe_name} = "{original_name}"\n')
37
+
38
+ print("Generated theme_enum.py ✅")
@@ -371,7 +371,7 @@ def save_theme(theme: dict, filename: pathlib.Path) -> None:
371
371
  # --- Main Interactive Loop --- #
372
372
 
373
373
 
374
- def main():
374
+ def theme_builder():
375
375
  console = Console(force_terminal=True, color_system="truecolor")
376
376
  themes_dir = pathlib.Path(__file__).parent.parent.parent.parent / "themes"
377
377
  theme_files = load_theme_files(themes_dir)
@@ -458,7 +458,7 @@ def main():
458
458
  )
459
459
  if sel2.lower() == "r":
460
460
  console.print("Regenerating samples...")
461
- main() # restart the builder
461
+ theme_builder() # restart the builder
462
462
  return
463
463
  try:
464
464
  sel2 = int(sel2)
@@ -474,7 +474,3 @@ def main():
474
474
  save_path = themes_dir / filename
475
475
  save_theme(chosen_sample_theme, save_path)
476
476
  console.print(f"\n[green]Theme saved as {save_path}.[/green]")
477
-
478
-
479
- if __name__ == "__main__":
480
- main()
@@ -5,16 +5,19 @@ import random
5
5
  import re
6
6
  from typing import Any
7
7
 
8
- from devtools import pprint
9
8
  from temporalio import workflow
10
9
 
11
- from flock.core.logging.formatters.base_formatter import BaseFormatter
10
+ from flock.core.logging.formatters.themes import OutputTheme
12
11
 
13
12
  with workflow.unsafe.imports_passed_through():
13
+ from pygments.style import Style
14
+ from pygments.token import Token
14
15
  from rich import box
15
16
  from rich.console import Console, Group
16
17
  from rich.panel import Panel
18
+ from rich.syntax import PygmentsSyntaxTheme, Syntax
17
19
  from rich.table import Table
20
+ from rich.theme import Theme
18
21
 
19
22
  import toml # install with: pip install toml
20
23
 
@@ -335,18 +338,101 @@ def create_rich_renderable(
335
338
  return s
336
339
 
337
340
 
338
- class ThemedAgentResultFormatter(BaseFormatter):
341
+ def load_syntax_theme_from_file(filepath: str) -> dict:
342
+ """Load a syntax highlighting theme from a TOML file and map it to Rich styles."""
343
+ with open(filepath) as f:
344
+ theme = toml.load(f)
345
+
346
+ if "colors" not in theme:
347
+ raise ValueError(
348
+ f"Theme file {filepath} does not contain a 'colors' section."
349
+ )
350
+
351
+ # Map theme colors to syntax categories
352
+ syntax_theme = {
353
+ "background": theme["colors"]["primary"].get("background", "#161719"),
354
+ "text": theme["colors"]["primary"].get("foreground", "#c5c8c6"),
355
+ "comment": theme["colors"]["normal"].get("black", "#666666"),
356
+ "keyword": theme["colors"]["bright"].get("magenta", "#ff79c6"),
357
+ "builtin": theme["colors"]["bright"].get("cyan", "#8be9fd"),
358
+ "string": theme["colors"]["bright"].get("green", "#50fa7b"),
359
+ "name": theme["colors"]["bright"].get("blue", "#6272a4"),
360
+ "number": theme["colors"]["bright"].get("yellow", "#f1fa8c"),
361
+ "operator": theme["colors"]["bright"].get("red", "#ff5555"),
362
+ "punctuation": theme["colors"]["normal"].get("white", "#bbbbbb"),
363
+ "error": theme["colors"]["bright"].get("red", "#ff5555"),
364
+ }
365
+
366
+ return syntax_theme
367
+
368
+
369
+ def create_rich_syntax_theme(syntax_theme: dict) -> Theme:
370
+ """Convert a syntax theme dict to a Rich-compatible Theme."""
371
+ return Theme(
372
+ {
373
+ "background": f"on {syntax_theme['background']}",
374
+ "text": syntax_theme["text"],
375
+ "keyword": f"bold {syntax_theme['keyword']}",
376
+ "builtin": f"bold {syntax_theme['builtin']}",
377
+ "string": syntax_theme["string"],
378
+ "name": syntax_theme["name"],
379
+ "number": syntax_theme["number"],
380
+ "operator": syntax_theme["operator"],
381
+ "punctuation": syntax_theme["punctuation"],
382
+ "error": f"bold {syntax_theme['error']}",
383
+ }
384
+ )
385
+
386
+
387
+ def create_pygments_syntax_theme(syntax_theme: dict) -> PygmentsSyntaxTheme:
388
+ """Convert a syntax theme dict to a Pygments-compatible Rich syntax theme."""
389
+
390
+ class CustomSyntaxStyle(Style):
391
+ """Dynamically generated Pygments style based on the loaded theme."""
392
+
393
+ background_color = syntax_theme["background"]
394
+ styles = {
395
+ Token.Text: syntax_theme["text"],
396
+ Token.Comment: f"italic {syntax_theme['comment']}",
397
+ Token.Keyword: f"bold {syntax_theme['keyword']}",
398
+ Token.Name.Builtin: f"bold {syntax_theme['builtin']}",
399
+ Token.String: syntax_theme["string"],
400
+ Token.Name: syntax_theme["name"],
401
+ Token.Number: syntax_theme["number"],
402
+ Token.Operator: syntax_theme["operator"],
403
+ Token.Punctuation: syntax_theme["punctuation"],
404
+ Token.Error: f"bold {syntax_theme['error']}",
405
+ }
406
+
407
+ return PygmentsSyntaxTheme(CustomSyntaxStyle)
408
+
409
+
410
+ class ThemedAgentResultFormatter:
339
411
  """Formats agent results in a Rich table with nested subtables and theme support."""
340
412
 
341
- def __init__(self, theme: str = "atom", max_length: int = -1):
413
+ def __init__(
414
+ self,
415
+ theme: OutputTheme = OutputTheme.afterglow,
416
+ max_length: int = -1,
417
+ render_table: bool = True,
418
+ wait_for_input: bool = False,
419
+ ):
342
420
  """Initialize the formatter with a theme and optional max length."""
343
421
  self.theme = theme
344
422
  self.styles = None
345
423
  self.max_length = max_length
424
+ self.render_table = render_table
425
+ self.wait_for_input = wait_for_input
346
426
 
347
427
  def format_result(
348
- self, result: dict[str, Any], agent_name: str, theme, styles
428
+ self,
429
+ result: dict[str, Any],
430
+ agent_name: str,
431
+ theme,
432
+ styles,
349
433
  ) -> Panel:
434
+ from devtools import pformat
435
+
350
436
  """Format an agent's result as a Rich Panel containing a table."""
351
437
  box_style = (
352
438
  getattr(box, styles["table_box"])
@@ -394,14 +480,32 @@ class ThemedAgentResultFormatter(BaseFormatter):
394
480
  )
395
481
  table.add_row(key, rich_renderable)
396
482
 
397
- return Panel(
398
- table,
399
- title="🐤🐧🐓🦆",
400
- title_align=styles["panel_title_align"],
401
- border_style=styles["panel_border_style"],
402
- padding=styles["panel_padding"],
403
- style=styles["panel_style"],
404
- )
483
+ s = pformat(result, highlight=False)
484
+
485
+ if self.render_table:
486
+ return Panel(
487
+ table,
488
+ title="🐤🐧🐓🦆",
489
+ title_align=styles["panel_title_align"],
490
+ border_style=styles["panel_border_style"],
491
+ padding=styles["panel_padding"],
492
+ style=styles["panel_style"],
493
+ )
494
+ else:
495
+ syntax = Syntax(
496
+ s, # The formatted string
497
+ "python", # Highlight as Python (change this for other formats)
498
+ theme=self.syntax_style, # Choose a Rich theme (matches your color setup)
499
+ line_numbers=False,
500
+ )
501
+ return Panel(
502
+ syntax,
503
+ title=agent_name,
504
+ title_align=styles["panel_title_align"],
505
+ border_style=styles["panel_border_style"],
506
+ padding=styles["panel_padding"],
507
+ style=styles["panel_style"],
508
+ )
405
509
 
406
510
  def display_result(self, result: dict[str, Any], agent_name: str) -> None:
407
511
  """Print an agent's result using Rich formatting."""
@@ -410,7 +514,11 @@ class ThemedAgentResultFormatter(BaseFormatter):
410
514
  pathlib.Path(__file__).parent.parent.parent.parent / "themes"
411
515
  )
412
516
  all_themes = list(themes_dir.glob("*.toml"))
413
- theme = theme + ".toml" if not theme.endswith(".toml") else theme
517
+ theme = (
518
+ theme.value + ".toml"
519
+ if not theme.value.endswith(".toml")
520
+ else theme.value
521
+ )
414
522
  theme = (
415
523
  pathlib.Path(__file__).parent.parent.parent.parent
416
524
  / "themes"
@@ -426,6 +534,9 @@ class ThemedAgentResultFormatter(BaseFormatter):
426
534
 
427
535
  styles = get_default_styles(theme_dict)
428
536
  self.styles = styles
537
+ self.syntax_style = create_pygments_syntax_theme(
538
+ load_syntax_theme_from_file(theme)
539
+ )
429
540
 
430
541
  console = Console()
431
542
  panel = self.format_result(
@@ -435,8 +546,5 @@ class ThemedAgentResultFormatter(BaseFormatter):
435
546
  styles=styles,
436
547
  )
437
548
  console.print(panel)
438
-
439
- @staticmethod
440
- def display_data(data: dict[str, Any]) -> None:
441
- """Print agent data using Rich formatting."""
442
- pprint(data)
549
+ if self.wait_for_input:
550
+ console.input(prompt="Press Enter to continue...")