fluxloop-cli 0.2.23__tar.gz → 0.2.25__tar.gz

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.
Files changed (62) hide show
  1. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/PKG-INFO +2 -1
  2. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/__init__.py +1 -1
  3. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/commands/record.py +17 -10
  4. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/conversation_supervisor.py +14 -0
  5. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/engine/core.py +27 -3
  6. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/runner.py +29 -0
  7. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/templates.py +1 -1
  8. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli.egg-info/PKG-INFO +2 -1
  9. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli.egg-info/requires.txt +1 -0
  10. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/pyproject.toml +2 -1
  11. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/README.md +0 -0
  12. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/arg_binder.py +0 -0
  13. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/commands/__init__.py +0 -0
  14. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/commands/config.py +0 -0
  15. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/commands/doctor.py +0 -0
  16. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/commands/evaluate.py +0 -0
  17. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/commands/generate.py +0 -0
  18. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/commands/init.py +0 -0
  19. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/commands/parse.py +0 -0
  20. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/commands/run.py +0 -0
  21. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/commands/status.py +0 -0
  22. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/config_loader.py +0 -0
  23. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/config_schema.py +0 -0
  24. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/constants.py +0 -0
  25. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/environment.py +0 -0
  26. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/__init__.py +0 -0
  27. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/config.py +0 -0
  28. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/engine/__init__.py +0 -0
  29. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/engine/analysis.py +0 -0
  30. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/engine/reporting/__init__.py +0 -0
  31. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/engine/reporting/html.py +0 -0
  32. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/engine/reporting/markdown.py +0 -0
  33. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/engine/success.py +0 -0
  34. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/llm.py +0 -0
  35. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/prompts/__init__.py +0 -0
  36. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/prompts/base.py +0 -0
  37. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/prompts/information_completeness.py +0 -0
  38. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/prompts/intent_recognition.py +0 -0
  39. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/prompts/response_clarity.py +0 -0
  40. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/prompts/response_consistency.py +0 -0
  41. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/evaluation/rules.py +0 -0
  42. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/input_generator.py +0 -0
  43. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/llm_generator.py +0 -0
  44. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/main.py +0 -0
  45. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/project_paths.py +0 -0
  46. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/target_loader.py +0 -0
  47. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli/validators.py +0 -0
  48. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli.egg-info/SOURCES.txt +0 -0
  49. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
  50. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli.egg-info/entry_points.txt +0 -0
  51. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/fluxloop_cli.egg-info/top_level.txt +0 -0
  52. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/setup.cfg +0 -0
  53. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/tests/test_analysis_recommendations.py +0 -0
  54. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/tests/test_arg_binder.py +0 -0
  55. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/tests/test_config_command.py +0 -0
  56. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/tests/test_evaluate_command.py +0 -0
  57. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/tests/test_evaluation_llm.py +0 -0
  58. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/tests/test_input_generator.py +0 -0
  59. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/tests/test_prompt_library.py +0 -0
  60. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/tests/test_run_command.py +0 -0
  61. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/tests/test_success_criteria.py +0 -0
  62. {fluxloop_cli-0.2.23 → fluxloop_cli-0.2.25}/tests/test_target_loader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluxloop-cli
3
- Version: 0.2.23
3
+ Version: 0.2.25
4
4
  Summary: FluxLoop CLI for running agent simulations
5
5
  Author-email: FluxLoop Team <team@fluxloop.dev>
6
6
  License: Apache-2.0
@@ -26,6 +26,7 @@ Requires-Dist: httpx>=0.24.0
26
26
  Requires-Dist: rich>=13.0
27
27
  Requires-Dist: python-dotenv>=1.0.0
28
28
  Requires-Dist: fluxloop>=0.1.0
29
+ Requires-Dist: ruamel.yaml>=0.17.0
29
30
  Provides-Extra: dev
30
31
  Requires-Dist: pytest>=7.0; extra == "dev"
31
32
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -2,7 +2,7 @@
2
2
  FluxLoop CLI - Command-line interface for running agent simulations.
3
3
  """
4
4
 
5
- __version__ = "0.2.23"
5
+ __version__ = "0.2.25"
6
6
 
7
7
  from .main import app
8
8
 
@@ -8,6 +8,7 @@ from typing import Dict, Optional
8
8
  import typer
9
9
  from rich.console import Console
10
10
  from rich.table import Table
11
+ from ruamel.yaml import YAML, CommentedMap
11
12
 
12
13
  from ..constants import DEFAULT_ROOT_DIR_NAME
13
14
  from ..project_paths import (
@@ -20,6 +21,9 @@ from ..config_schema import CONFIG_SECTION_FILENAMES
20
21
 
21
22
  app = typer.Typer()
22
23
  console = Console()
24
+ _yaml = YAML()
25
+ _yaml.indent(mapping=2, sequence=4, offset=2)
26
+ _yaml.preserve_quotes = True
23
27
 
24
28
 
25
29
  def _load_env(env_path: Path) -> Dict[str, str]:
@@ -44,22 +48,25 @@ def _update_simulation(recording_enabled: bool, project: Optional[str], root: Pa
44
48
  except KeyError:
45
49
  return
46
50
 
47
- data: Dict[str, object] = {}
48
51
  if simulation_path.exists():
49
- import yaml
52
+ with simulation_path.open("r", encoding="utf-8") as handle:
53
+ loaded = _yaml.load(handle) or CommentedMap()
54
+ else:
55
+ loaded = CommentedMap()
50
56
 
51
- data = yaml.safe_load(simulation_path.read_text()) or {}
57
+ if not isinstance(loaded, CommentedMap):
58
+ loaded = CommentedMap(loaded)
52
59
 
53
- replay = data.setdefault("replay_args", {})
54
- replay["enabled"] = recording_enabled
60
+ replay = loaded.get("replay_args")
61
+ if not isinstance(replay, CommentedMap):
62
+ replay = CommentedMap(replay or {})
63
+ loaded["replay_args"] = replay
55
64
 
56
- import yaml
65
+ replay["enabled"] = recording_enabled
57
66
 
58
67
  simulation_path.parent.mkdir(parents=True, exist_ok=True)
59
- simulation_path.write_text(
60
- yaml.dump(data, sort_keys=False, default_flow_style=False),
61
- encoding="utf-8",
62
- )
68
+ with simulation_path.open("w", encoding="utf-8") as handle:
69
+ _yaml.dump(loaded, handle)
63
70
 
64
71
 
65
72
  @app.command()
@@ -9,6 +9,7 @@ next user utterance consistent with the experiment persona and service context.
9
9
  from __future__ import annotations
10
10
 
11
11
  import json
12
+ import logging
12
13
  import os
13
14
  import re
14
15
  from dataclasses import dataclass
@@ -18,6 +19,7 @@ import httpx
18
19
 
19
20
  from fluxloop.schemas import MultiTurnSupervisorConfig
20
21
 
22
+ logger = logging.getLogger(__name__)
21
23
  SupervisorDecisionType = Literal["continue", "terminate"]
22
24
 
23
25
 
@@ -118,6 +120,13 @@ class ConversationSupervisor:
118
120
  )
119
121
 
120
122
  provider = (self.config.provider or "openai").lower()
123
+ logger.debug(
124
+ "supervisor.decide provider=%s turns=%d persona=%r service=%r",
125
+ provider,
126
+ len(turns),
127
+ persona_description,
128
+ service_context,
129
+ )
121
130
  if provider == "mock":
122
131
  return self._mock_decision(conversation_state)
123
132
  if provider == "openai":
@@ -131,6 +140,11 @@ class ConversationSupervisor:
131
140
  """Deterministic supervisor used for tests, scripted runs, or offline modes."""
132
141
 
133
142
  metadata = self.config.metadata or {}
143
+ logger.debug(
144
+ "mock supervisor state: scripted_questions=%s context_keys=%s",
145
+ isinstance(metadata.get("scripted_questions"), list),
146
+ list(conversation_state.get("context", {}).keys()),
147
+ )
134
148
 
135
149
  scripted_questions = metadata.get("scripted_questions")
136
150
  if isinstance(scripted_questions, list):
@@ -13,6 +13,13 @@ from typing import Any, Dict, List, Optional, Tuple, Literal
13
13
 
14
14
  import shutil
15
15
  from rich.console import Console
16
+ from rich.progress import (
17
+ BarColumn,
18
+ Progress,
19
+ SpinnerColumn,
20
+ TextColumn,
21
+ TimeRemainingColumn,
22
+ )
16
23
 
17
24
  from ..config import AggregateConfig, EvaluationConfig, EvaluatorConfig
18
25
  from ..llm import LLMEvaluationManager, LLMResult
@@ -321,9 +328,26 @@ def run_evaluation(
321
328
  output_dir=options.output_dir,
322
329
  )
323
330
 
324
- for trace in traces:
325
- outcome = _evaluate_trace(trace, config, llm_manager)
326
- results.append(outcome)
331
+ console.print("\n[bold green]🧪 Evaluating traces...[/bold green]\n")
332
+
333
+ with Progress(
334
+ SpinnerColumn(),
335
+ TextColumn("[progress.description]{task.description}"),
336
+ BarColumn(),
337
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
338
+ TimeRemainingColumn(),
339
+ TextColumn("({task.completed} of {task.total})"),
340
+ console=console,
341
+ ) as progress:
342
+ task_id = progress.add_task(
343
+ "Evaluating traces",
344
+ total=len(traces),
345
+ )
346
+
347
+ for trace in traces:
348
+ outcome = _evaluate_trace(trace, config, llm_manager)
349
+ results.append(outcome)
350
+ progress.advance(task_id)
327
351
 
328
352
  _write_per_trace(results, options.output_dir)
329
353
 
@@ -7,6 +7,7 @@ from __future__ import annotations
7
7
  import asyncio
8
8
  import contextvars
9
9
  import json
10
+ import logging
10
11
  import inspect
11
12
  import os
12
13
  import time
@@ -27,6 +28,7 @@ from .arg_binder import ArgBinder
27
28
  from .conversation_supervisor import ConversationSupervisor, SupervisorDecision
28
29
 
29
30
  console = Console()
31
+ logger = logging.getLogger(__name__)
30
32
 
31
33
 
32
34
  class ExperimentRunner:
@@ -499,6 +501,13 @@ class ExperimentRunner:
499
501
  while True:
500
502
  callback_messages: Dict[str, Any] = {}
501
503
 
504
+ logger.debug(
505
+ "multi-turn request: iteration=%s turn=%s input_type=%s preview=%r",
506
+ iteration,
507
+ turn_count,
508
+ type(current_user_input).__name__,
509
+ current_user_input if isinstance(current_user_input, str) else str(current_user_input),
510
+ )
502
511
  result = await self._call_agent(
503
512
  agent_func,
504
513
  current_user_input,
@@ -524,6 +533,13 @@ class ExperimentRunner:
524
533
  coerced = await self._coerce_result_to_text(result)
525
534
  assistant_output = coerced if coerced is not None else str(result)
526
535
 
536
+ logger.debug(
537
+ "multi-turn agent output: raw_type=%s assistant_output_type=%s preview=%r",
538
+ type(result).__name__,
539
+ type(assistant_output).__name__,
540
+ assistant_output if isinstance(assistant_output, str) else str(assistant_output),
541
+ )
542
+
527
543
  final_output = assistant_output
528
544
 
529
545
  conversation_state["turns"].append(
@@ -547,6 +563,14 @@ class ExperimentRunner:
547
563
  service_context=service_context,
548
564
  )
549
565
  last_decision = decision
566
+ logger.debug(
567
+ "multi-turn supervisor decision: decision=%s termination=%r next_type=%s",
568
+ decision.decision,
569
+ decision.termination_reason,
570
+ type(decision.next_user_message).__name__
571
+ if decision.next_user_message is not None
572
+ else None,
573
+ )
550
574
 
551
575
  if decision.decision == "terminate":
552
576
  termination_reason = (
@@ -569,6 +593,11 @@ class ExperimentRunner:
569
593
  "Supervisor decided to continue but provided no next_user_message."
570
594
  )
571
595
 
596
+ logger.debug(
597
+ "multi-turn next user message: type=%s preview=%r",
598
+ type(next_user_message).__name__,
599
+ next_user_message if isinstance(next_user_message, str) else str(next_user_message),
600
+ )
572
601
  conversation_state["turns"].append(
573
602
  {
574
603
  "role": "user",
@@ -155,7 +155,7 @@ def create_simulation_config(project_name: str) -> str:
155
155
  When continuing, craft the next user message consistent with the persona.
156
156
  When terminating, explain the reason and provide any closing notes.
157
157
  metadata:
158
- scripted_questions: [] # 순차 재생용 사용자 발화 배열 (예: [" 질문", "둘째 질문", ...])
158
+ scripted_questions: [] # Array of user utterances for sequential playback (e.g., ["First question", "Second question", ...])
159
159
  mock_decision: terminate # Fallback when no scripted questions remain
160
160
  mock_reason: script_complete # Termination reason for scripted runs
161
161
  mock_closing: "Thanks for the help. I have no further questions."
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluxloop-cli
3
- Version: 0.2.23
3
+ Version: 0.2.25
4
4
  Summary: FluxLoop CLI for running agent simulations
5
5
  Author-email: FluxLoop Team <team@fluxloop.dev>
6
6
  License: Apache-2.0
@@ -26,6 +26,7 @@ Requires-Dist: httpx>=0.24.0
26
26
  Requires-Dist: rich>=13.0
27
27
  Requires-Dist: python-dotenv>=1.0.0
28
28
  Requires-Dist: fluxloop>=0.1.0
29
+ Requires-Dist: ruamel.yaml>=0.17.0
29
30
  Provides-Extra: dev
30
31
  Requires-Dist: pytest>=7.0; extra == "dev"
31
32
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -5,6 +5,7 @@ httpx>=0.24.0
5
5
  rich>=13.0
6
6
  python-dotenv>=1.0.0
7
7
  fluxloop>=0.1.0
8
+ ruamel.yaml>=0.17.0
8
9
 
9
10
  [anthropic]
10
11
  anthropic>=0.7.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fluxloop-cli"
7
- version = "0.2.23"
7
+ version = "0.2.25"
8
8
  description = "FluxLoop CLI for running agent simulations"
9
9
  authors = [
10
10
  {name = "FluxLoop Team", email = "team@fluxloop.dev"}
@@ -32,6 +32,7 @@ dependencies = [
32
32
  "rich>=13.0",
33
33
  "python-dotenv>=1.0.0",
34
34
  "fluxloop>=0.1.0",
35
+ "ruamel.yaml>=0.17.0",
35
36
  ]
36
37
 
37
38
  [project.optional-dependencies]
File without changes
File without changes