fluxloop-cli 0.2.30__tar.gz → 0.2.31__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.
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/PKG-INFO +4 -3
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/README.md +2 -2
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/__init__.py +1 -1
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/arg_binder.py +2 -3
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/commands/config.py +0 -1
- fluxloop_cli-0.2.31/fluxloop_cli/commands/evaluate.py +188 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/commands/init.py +1 -1
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/commands/record.py +0 -1
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/commands/run.py +0 -1
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/commands/status.py +1 -2
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/config_loader.py +0 -2
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/artifacts.py +1 -1
- fluxloop_cli-0.2.31/fluxloop_cli/evaluation/report/__init__.py +0 -0
- fluxloop_cli-0.2.31/fluxloop_cli/evaluation/report/aggregator.py +568 -0
- fluxloop_cli-0.2.31/fluxloop_cli/evaluation/report/generator.py +716 -0
- fluxloop_cli-0.2.31/fluxloop_cli/evaluation/report/pipeline.py +83 -0
- fluxloop_cli-0.2.31/fluxloop_cli/evaluation/report/renderer.py +371 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/rules.py +1 -1
- fluxloop_cli-0.2.31/fluxloop_cli/input_generator.py +264 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/llm_generator.py +23 -23
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/main.py +0 -2
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/project_paths.py +0 -1
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/runner.py +2 -1
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/templates.py +201 -252
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/testing/pytest_plugin.py +13 -2
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli.egg-info/PKG-INFO +4 -3
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli.egg-info/SOURCES.txt +5 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli.egg-info/requires.txt +1 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/pyproject.toml +2 -1
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_arg_binder.py +1 -1
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_conversation_supervisor.py +0 -2
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_evaluate_command.py +49 -68
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_evaluation_llm.py +5 -4
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_input_generator.py +0 -2
- fluxloop_cli-0.2.30/fluxloop_cli/commands/evaluate.py +0 -219
- fluxloop_cli-0.2.30/fluxloop_cli/input_generator.py +0 -138
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/commands/__init__.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/commands/doctor.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/commands/generate.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/commands/parse.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/config_schema.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/constants.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/conversation_supervisor.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/environment.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/__init__.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/config.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/engine/__init__.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/engine/analysis.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/engine/core.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/engine/reporting/__init__.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/engine/reporting/html.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/engine/reporting/markdown.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/engine/success.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/llm.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/prompts/__init__.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/prompts/base.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/prompts/information_completeness.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/prompts/intent_recognition.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/prompts/response_clarity.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/evaluation/prompts/response_consistency.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/target_loader.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/testing/__init__.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/testing/types.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli/validators.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli.egg-info/entry_points.txt +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/fluxloop_cli.egg-info/top_level.txt +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/setup.cfg +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_analysis_recommendations.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_config_command.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_init_pytest_template.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_prompt_library.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_pytest_plugin.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_run_command.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_runner_multi_turn.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_success_criteria.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_target_loader.py +0 -0
- {fluxloop_cli-0.2.30 → fluxloop_cli-0.2.31}/tests/test_testing_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fluxloop-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.31
|
|
4
4
|
Summary: FluxLoop CLI for running agent simulations
|
|
5
5
|
Author-email: FluxLoop Team <team@fluxloop.dev>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -27,6 +27,7 @@ Requires-Dist: rich>=13.0
|
|
|
27
27
|
Requires-Dist: python-dotenv>=1.0.0
|
|
28
28
|
Requires-Dist: fluxloop>=0.1.0
|
|
29
29
|
Requires-Dist: ruamel.yaml>=0.17.0
|
|
30
|
+
Requires-Dist: Jinja2>=3.0
|
|
30
31
|
Provides-Extra: dev
|
|
31
32
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
33
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
@@ -67,7 +68,7 @@ The legacy `setting.yaml` is still supported, but new projects created with
|
|
|
67
68
|
- `fluxloop generate inputs` – produce input variations for the active project
|
|
68
69
|
- `fluxloop run experiment` – execute an experiment using `configs/simulation.yaml`
|
|
69
70
|
- `fluxloop parse experiment` – convert experiment outputs into readable artifacts and emit structured per-trace JSON at `per_trace_analysis/per_trace.jsonl`
|
|
70
|
-
- `fluxloop evaluate experiment` –
|
|
71
|
+
- `fluxloop evaluate experiment` – run the LLM-driven evaluation pipeline (LLM-PT → rule aggregation → LLM-OV → HTML render). Requires the parsed per-trace file (or `--per-trace`) and writes an interactive report to `evaluation_report/report.html` by default.
|
|
71
72
|
- `fluxloop config set-llm` – update LLM provider/model in `configs/input.yaml`
|
|
72
73
|
- `fluxloop record enable|disable|status` – toggle recording mode across `.env` and simulation config
|
|
73
74
|
- `fluxloop doctor` – summarize Python, FluxLoop CLI/MCP, and MCP index state for the active environment
|
|
@@ -105,7 +106,7 @@ Evaluation now follows a two-step process so that multi-turn context is preserve
|
|
|
105
106
|
|
|
106
107
|
1. `fluxloop run experiment` – produce `trace_summary.jsonl` (and optionally `observations.jsonl`).
|
|
107
108
|
2. `fluxloop parse experiment <experiment_dir>` – generate markdown summaries and a structured artifact at `per_trace_analysis/per_trace.jsonl`.
|
|
108
|
-
3. `fluxloop evaluate experiment <experiment_dir>` – consume
|
|
109
|
+
3. `fluxloop evaluate experiment <experiment_dir>` – consume that structured file, run LLM-based per-trace + overall analysis, and emit an interactive dashboard at `<experiment_dir>/evaluation_report/report.html` (override with `--output`).
|
|
109
110
|
|
|
110
111
|
`fluxloop evaluate` exits early with guidance when the per-trace artifact is missing. If you relocate the file, supply an explicit path with `--per-trace /path/to/per_trace.jsonl`.
|
|
111
112
|
|
|
@@ -26,7 +26,7 @@ The legacy `setting.yaml` is still supported, but new projects created with
|
|
|
26
26
|
- `fluxloop generate inputs` – produce input variations for the active project
|
|
27
27
|
- `fluxloop run experiment` – execute an experiment using `configs/simulation.yaml`
|
|
28
28
|
- `fluxloop parse experiment` – convert experiment outputs into readable artifacts and emit structured per-trace JSON at `per_trace_analysis/per_trace.jsonl`
|
|
29
|
-
- `fluxloop evaluate experiment` –
|
|
29
|
+
- `fluxloop evaluate experiment` – run the LLM-driven evaluation pipeline (LLM-PT → rule aggregation → LLM-OV → HTML render). Requires the parsed per-trace file (or `--per-trace`) and writes an interactive report to `evaluation_report/report.html` by default.
|
|
30
30
|
- `fluxloop config set-llm` – update LLM provider/model in `configs/input.yaml`
|
|
31
31
|
- `fluxloop record enable|disable|status` – toggle recording mode across `.env` and simulation config
|
|
32
32
|
- `fluxloop doctor` – summarize Python, FluxLoop CLI/MCP, and MCP index state for the active environment
|
|
@@ -64,7 +64,7 @@ Evaluation now follows a two-step process so that multi-turn context is preserve
|
|
|
64
64
|
|
|
65
65
|
1. `fluxloop run experiment` – produce `trace_summary.jsonl` (and optionally `observations.jsonl`).
|
|
66
66
|
2. `fluxloop parse experiment <experiment_dir>` – generate markdown summaries and a structured artifact at `per_trace_analysis/per_trace.jsonl`.
|
|
67
|
-
3. `fluxloop evaluate experiment <experiment_dir>` – consume
|
|
67
|
+
3. `fluxloop evaluate experiment <experiment_dir>` – consume that structured file, run LLM-based per-trace + overall analysis, and emit an interactive dashboard at `<experiment_dir>/evaluation_report/report.html` (override with `--output`).
|
|
68
68
|
|
|
69
69
|
`fluxloop evaluate` exits early with guidance when the per-trace artifact is missing. If you relocate the file, supply an explicit path with `--per-trace /path/to/per_trace.jsonl`.
|
|
70
70
|
|
|
@@ -7,6 +7,8 @@ import json
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any, Callable, Dict, Optional, Sequence
|
|
9
9
|
|
|
10
|
+
from fluxloop.schemas import ExperimentConfig, ReplayArgsConfig, PersonaConfig
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class _AttrDict(dict):
|
|
12
14
|
"""Dictionary that also supports attribute access for keys."""
|
|
@@ -26,8 +28,6 @@ class _AttrDict(dict):
|
|
|
26
28
|
except KeyError as exc: # pragma: no cover
|
|
27
29
|
raise AttributeError(item) from exc
|
|
28
30
|
|
|
29
|
-
from fluxloop.schemas import ExperimentConfig, ReplayArgsConfig, PersonaConfig
|
|
30
|
-
|
|
31
31
|
|
|
32
32
|
class _AwaitableNone:
|
|
33
33
|
"""Simple awaitable that resolves to ``None``."""
|
|
@@ -278,7 +278,6 @@ class ArgBinder:
|
|
|
278
278
|
|
|
279
279
|
def _record(args: Any, kwargs: Any) -> None:
|
|
280
280
|
messages.append((args, kwargs))
|
|
281
|
-
pretty = args[0] if len(args) == 1 and not kwargs else {"args": args, "kwargs": kwargs}
|
|
282
281
|
|
|
283
282
|
def send(*args: Any, **kwargs: Any) -> _AwaitableNone:
|
|
284
283
|
_record(args, kwargs)
|
|
@@ -14,7 +14,6 @@ from rich.syntax import Syntax
|
|
|
14
14
|
from rich.table import Table
|
|
15
15
|
|
|
16
16
|
from ..config_loader import load_experiment_config
|
|
17
|
-
from ..templates import create_env_file, create_gitignore, create_sample_agent
|
|
18
17
|
from ..constants import DEFAULT_CONFIG_PATH, DEFAULT_ROOT_DIR_NAME
|
|
19
18
|
from ..config_schema import CONFIG_SECTION_FILENAMES
|
|
20
19
|
from ..project_paths import (
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Evaluate command for generating interactive reports."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
from dataclasses import asdict
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
import yaml
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
|
|
17
|
+
from ..environment import load_env_chain
|
|
18
|
+
from ..evaluation import load_evaluation_config
|
|
19
|
+
from ..evaluation.artifacts import load_per_trace_records
|
|
20
|
+
from ..evaluation.report.pipeline import ReportPipeline
|
|
21
|
+
|
|
22
|
+
console = Console()
|
|
23
|
+
app = typer.Typer(help="Evaluate experiment outputs and generate interactive reports.")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _load_yaml_file(path: Optional[Path]) -> dict:
|
|
27
|
+
if not path or not path.exists():
|
|
28
|
+
return {}
|
|
29
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
30
|
+
data = yaml.safe_load(handle) or {}
|
|
31
|
+
if isinstance(data, dict):
|
|
32
|
+
return data
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _resolve_project_root(config_path: Path) -> Path:
|
|
37
|
+
config_dir = config_path.parent
|
|
38
|
+
return config_dir.parent if config_dir.name == "configs" else config_dir
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _find_config_file(config_path: Path, filename: str) -> Optional[Path]:
|
|
42
|
+
config_dir = config_path.parent
|
|
43
|
+
project_root = _resolve_project_root(config_path)
|
|
44
|
+
candidates = [
|
|
45
|
+
config_dir / filename,
|
|
46
|
+
project_root / "configs" / filename,
|
|
47
|
+
project_root / filename,
|
|
48
|
+
]
|
|
49
|
+
for candidate in candidates:
|
|
50
|
+
if candidate.exists():
|
|
51
|
+
return candidate
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _prepare_output_directory(path: Path, overwrite: bool) -> None:
|
|
56
|
+
if path.exists():
|
|
57
|
+
if not overwrite:
|
|
58
|
+
raise typer.BadParameter(
|
|
59
|
+
f"Output directory already exists: {path}. Use --overwrite to replace it."
|
|
60
|
+
)
|
|
61
|
+
shutil.rmtree(path)
|
|
62
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.command()
|
|
66
|
+
def experiment(
|
|
67
|
+
experiment_dir: Path = typer.Argument(
|
|
68
|
+
...,
|
|
69
|
+
help="Path to the experiment output directory",
|
|
70
|
+
exists=True,
|
|
71
|
+
dir_okay=True,
|
|
72
|
+
file_okay=False,
|
|
73
|
+
resolve_path=True,
|
|
74
|
+
),
|
|
75
|
+
config: Path = typer.Option(
|
|
76
|
+
Path("configs/evaluation.yaml"),
|
|
77
|
+
"--config",
|
|
78
|
+
"-c",
|
|
79
|
+
help="Path to evaluation configuration file",
|
|
80
|
+
),
|
|
81
|
+
output: Path = typer.Option(
|
|
82
|
+
Path("evaluation_report"),
|
|
83
|
+
"--output",
|
|
84
|
+
"-o",
|
|
85
|
+
help="Output directory name (relative to the experiment directory)",
|
|
86
|
+
),
|
|
87
|
+
overwrite: bool = typer.Option(
|
|
88
|
+
False,
|
|
89
|
+
"--overwrite",
|
|
90
|
+
help="Overwrite output directory if it already exists",
|
|
91
|
+
),
|
|
92
|
+
llm_api_key: Optional[str] = typer.Option(
|
|
93
|
+
None,
|
|
94
|
+
"--llm-api-key",
|
|
95
|
+
help="LLM API key for report generation (optional)",
|
|
96
|
+
envvar="FLUXLOOP_LLM_API_KEY",
|
|
97
|
+
),
|
|
98
|
+
per_trace: Optional[Path] = typer.Option(
|
|
99
|
+
None,
|
|
100
|
+
"--per-trace",
|
|
101
|
+
help="Path to structured per-trace JSONL generated by `fluxloop parse`",
|
|
102
|
+
),
|
|
103
|
+
verbose: bool = typer.Option(
|
|
104
|
+
False,
|
|
105
|
+
"--verbose",
|
|
106
|
+
help="Enable verbose logging",
|
|
107
|
+
),
|
|
108
|
+
):
|
|
109
|
+
"""
|
|
110
|
+
Evaluate experiment outputs and generate an interactive HTML report.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
logging.basicConfig(
|
|
114
|
+
level=logging.DEBUG if verbose else logging.INFO,
|
|
115
|
+
format="%(message)s",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
resolved_experiment_dir = experiment_dir.resolve()
|
|
119
|
+
if not resolved_experiment_dir.is_dir():
|
|
120
|
+
raise typer.BadParameter(f"Experiment directory not found: {resolved_experiment_dir}")
|
|
121
|
+
|
|
122
|
+
config_path = config.resolve() if config.is_absolute() else (Path.cwd() / config).resolve()
|
|
123
|
+
|
|
124
|
+
if per_trace is not None:
|
|
125
|
+
per_trace_path = per_trace.resolve() if per_trace.is_absolute() else (Path.cwd() / per_trace).resolve()
|
|
126
|
+
else:
|
|
127
|
+
per_trace_path = resolved_experiment_dir / "per_trace_analysis" / "per_trace.jsonl"
|
|
128
|
+
|
|
129
|
+
per_trace_records = load_per_trace_records(resolved_experiment_dir, per_trace_path)
|
|
130
|
+
trace_summaries = [record.trace for record in per_trace_records]
|
|
131
|
+
if not trace_summaries:
|
|
132
|
+
raise typer.BadParameter("No traces found in per-trace artifacts.")
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
evaluation_config = load_evaluation_config(config_path)
|
|
136
|
+
except FileNotFoundError as exc:
|
|
137
|
+
raise typer.BadParameter(str(exc)) from exc
|
|
138
|
+
except Exception as exc: # noqa: BLE001
|
|
139
|
+
raise typer.BadParameter(f"Failed to load evaluation config: {exc}") from exc
|
|
140
|
+
|
|
141
|
+
def _log_env_error(path: Path, exc: Exception) -> None:
|
|
142
|
+
console.log(
|
|
143
|
+
f"[yellow]Warning:[/yellow] Failed to load environment from {path}: {exc}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
load_env_chain(
|
|
147
|
+
evaluation_config.get_source_dir(),
|
|
148
|
+
refresh_config=True,
|
|
149
|
+
on_error=_log_env_error,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if llm_api_key is None:
|
|
153
|
+
llm_api_key = os.getenv("FLUXLOOP_LLM_API_KEY") or os.getenv("OPENAI_API_KEY")
|
|
154
|
+
|
|
155
|
+
output_dir = output if output.is_absolute() else (resolved_experiment_dir / output)
|
|
156
|
+
_prepare_output_directory(output_dir, overwrite)
|
|
157
|
+
|
|
158
|
+
input_config_path = _find_config_file(config_path, "input.yaml")
|
|
159
|
+
project_config_path = _find_config_file(config_path, "project.yaml")
|
|
160
|
+
|
|
161
|
+
input_config = _load_yaml_file(input_config_path)
|
|
162
|
+
project_config = _load_yaml_file(project_config_path)
|
|
163
|
+
|
|
164
|
+
config_bundle = {
|
|
165
|
+
"name": project_config.get("name") or resolved_experiment_dir.name,
|
|
166
|
+
"evaluation": asdict(evaluation_config),
|
|
167
|
+
"input": input_config,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
pipeline = ReportPipeline(
|
|
171
|
+
config=config_bundle,
|
|
172
|
+
output_dir=output_dir,
|
|
173
|
+
api_key=llm_api_key,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
message_lines = [
|
|
177
|
+
f"📊 Evaluating experiment at [cyan]{resolved_experiment_dir}[/cyan]",
|
|
178
|
+
f"⚙️ Config: [magenta]{config_path}[/magenta]",
|
|
179
|
+
f"🧵 Per-trace data: [blue]{per_trace_path}[/blue]",
|
|
180
|
+
f"📁 Output: [green]{output_dir}[/green]",
|
|
181
|
+
]
|
|
182
|
+
console.print("\n".join(message_lines))
|
|
183
|
+
|
|
184
|
+
report_path = asyncio.run(pipeline.run(trace_summaries))
|
|
185
|
+
console.print(f"\n✅ Report ready: [bold cyan]{report_path}[/bold cyan]")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
@@ -188,7 +188,7 @@ def project(
|
|
|
188
188
|
console.print("4. Generate inputs: [green]fluxloop generate inputs[/green]")
|
|
189
189
|
console.print("5. Run the experiment: [green]fluxloop run experiment[/green]")
|
|
190
190
|
console.print("6. Parse outputs: [green]fluxloop parse experiment[/green]")
|
|
191
|
-
console.print("7.
|
|
191
|
+
console.print("7. Generate the interactive report (optional): [green]fluxloop evaluate experiment[/green]")
|
|
192
192
|
console.print("8. Diagnose environment anytime: [green]fluxloop doctor[/green]")
|
|
193
193
|
|
|
194
194
|
|
|
@@ -9,7 +9,6 @@ from typing import Callable, Optional
|
|
|
9
9
|
|
|
10
10
|
import typer
|
|
11
11
|
from rich.console import Console
|
|
12
|
-
from rich.live import Live
|
|
13
12
|
from rich.panel import Panel
|
|
14
13
|
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeRemainingColumn
|
|
15
14
|
from rich.table import Table
|
|
@@ -15,7 +15,6 @@ from ..config_schema import CONFIG_SECTION_FILENAMES, CONFIG_REQUIRED_KEYS
|
|
|
15
15
|
from ..project_paths import (
|
|
16
16
|
resolve_config_path,
|
|
17
17
|
resolve_config_directory,
|
|
18
|
-
resolve_root_dir,
|
|
19
18
|
resolve_project_relative,
|
|
20
19
|
)
|
|
21
20
|
|
|
@@ -79,7 +78,7 @@ def check(
|
|
|
79
78
|
from fluxloop.client import FluxLoopClient
|
|
80
79
|
|
|
81
80
|
config = get_config()
|
|
82
|
-
|
|
81
|
+
_client = FluxLoopClient() # noqa: F841 - instantiated for potential future use
|
|
83
82
|
|
|
84
83
|
# Try to connect (this would need a health endpoint)
|
|
85
84
|
status_table.add_row(
|
|
@@ -12,7 +12,6 @@ from pydantic import ValidationError
|
|
|
12
12
|
from .project_paths import resolve_config_path
|
|
13
13
|
from .config_schema import (
|
|
14
14
|
CONFIG_SECTION_FILENAMES,
|
|
15
|
-
CONFIG_SECTION_ORDER,
|
|
16
15
|
CONFIG_REQUIRED_KEYS,
|
|
17
16
|
iter_section_paths,
|
|
18
17
|
is_legacy_config,
|
|
@@ -52,7 +51,6 @@ def load_experiment_config(
|
|
|
52
51
|
|
|
53
52
|
for section_path in iter_section_paths(project_root):
|
|
54
53
|
if not section_path.exists():
|
|
55
|
-
key = section_path.name
|
|
56
54
|
logical_key = _section_key_from_filename(section_path.name)
|
|
57
55
|
if logical_key in CONFIG_REQUIRED_KEYS:
|
|
58
56
|
missing_required.append(section_path.name)
|
|
File without changes
|