fluxloop-cli 0.2.16__tar.gz → 0.2.18__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.16 → fluxloop_cli-0.2.18}/PKG-INFO +20 -1
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/README.md +19 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/__init__.py +1 -1
- fluxloop_cli-0.2.18/fluxloop_cli/commands/__init__.py +15 -0
- fluxloop_cli-0.2.18/fluxloop_cli/commands/doctor.py +252 -0
- fluxloop_cli-0.2.18/fluxloop_cli/commands/evaluate.py +183 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/__init__.py +37 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/config.py +707 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/engine/__init__.py +7 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/engine/analysis.py +262 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/engine/core.py +387 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/engine/reporting/__init__.py +49 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/engine/reporting/html.py +257 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/engine/reporting/markdown.py +156 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/engine/success.py +325 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/llm.py +310 -0
- fluxloop_cli-0.2.18/fluxloop_cli/evaluation/rules.py +262 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/main.py +3 -1
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/templates.py +110 -16
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli.egg-info/PKG-INFO +20 -1
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli.egg-info/SOURCES.txt +14 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/pyproject.toml +1 -1
- fluxloop_cli-0.2.18/tests/test_evaluate_command.py +335 -0
- fluxloop_cli-0.2.16/fluxloop_cli/commands/__init__.py +0 -5
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/arg_binder.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/commands/config.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/commands/generate.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/commands/init.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/commands/parse.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/commands/record.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/commands/run.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/commands/status.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/config_loader.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/config_schema.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/constants.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/input_generator.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/llm_generator.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/project_paths.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/runner.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/target_loader.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli/validators.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli.egg-info/entry_points.txt +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli.egg-info/requires.txt +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/fluxloop_cli.egg-info/top_level.txt +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/setup.cfg +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/tests/test_arg_binder.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/tests/test_config_command.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/tests/test_input_generator.py +0 -0
- {fluxloop_cli-0.2.16 → fluxloop_cli-0.2.18}/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.
|
|
3
|
+
Version: 0.2.18
|
|
4
4
|
Summary: FluxLoop CLI for running agent simulations
|
|
5
5
|
Author-email: FluxLoop Team <team@fluxloop.dev>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -66,11 +66,30 @@ The legacy `setting.yaml` is still supported, but new projects created with
|
|
|
66
66
|
- `fluxloop generate inputs` – produce input variations for the active project
|
|
67
67
|
- `fluxloop run experiment` – execute an experiment using `configs/simulation.yaml`
|
|
68
68
|
- `fluxloop parse experiment` – convert experiment outputs into readable artifacts
|
|
69
|
+
- `fluxloop evaluate experiment` – score experiment outputs and generate evaluation reports
|
|
69
70
|
- `fluxloop config set-llm` – update LLM provider/model in `configs/input.yaml`
|
|
70
71
|
- `fluxloop record enable|disable|status` – toggle recording mode across `.env` and simulation config
|
|
72
|
+
- `fluxloop doctor` – summarize Python, FluxLoop CLI/MCP, and MCP index state for the active environment
|
|
71
73
|
|
|
72
74
|
Run `fluxloop --help` or `fluxloop <command> --help` for more detail.
|
|
73
75
|
|
|
76
|
+
## Quick Setup Script
|
|
77
|
+
|
|
78
|
+
To prepare a fresh checkout (create `.venv`, install dependencies, and run diagnostics):
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
bash scripts/setup_fluxloop_env.sh --target-source-root path/to/your/source
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Options:
|
|
85
|
+
|
|
86
|
+
- `--python PATH` – choose a specific interpreter (default `python3`)
|
|
87
|
+
- `--target-source-root PATH` – pre-populate VSCode `fluxloop.targetSourceRoot`
|
|
88
|
+
- `--skip-doctor` – skip the final `fluxloop doctor` check
|
|
89
|
+
|
|
90
|
+
After running the script, open the folder in VSCode and use `FluxLoop: Show Environment Info`
|
|
91
|
+
or `FluxLoop: Run Doctor` to confirm the environment.
|
|
92
|
+
|
|
74
93
|
## Runner Integration Patterns
|
|
75
94
|
|
|
76
95
|
Configure how FluxLoop calls your code in `configs/simulation.yaml`:
|
|
@@ -26,11 +26,30 @@ 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
|
|
29
|
+
- `fluxloop evaluate experiment` – score experiment outputs and generate evaluation reports
|
|
29
30
|
- `fluxloop config set-llm` – update LLM provider/model in `configs/input.yaml`
|
|
30
31
|
- `fluxloop record enable|disable|status` – toggle recording mode across `.env` and simulation config
|
|
32
|
+
- `fluxloop doctor` – summarize Python, FluxLoop CLI/MCP, and MCP index state for the active environment
|
|
31
33
|
|
|
32
34
|
Run `fluxloop --help` or `fluxloop <command> --help` for more detail.
|
|
33
35
|
|
|
36
|
+
## Quick Setup Script
|
|
37
|
+
|
|
38
|
+
To prepare a fresh checkout (create `.venv`, install dependencies, and run diagnostics):
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
bash scripts/setup_fluxloop_env.sh --target-source-root path/to/your/source
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Options:
|
|
45
|
+
|
|
46
|
+
- `--python PATH` – choose a specific interpreter (default `python3`)
|
|
47
|
+
- `--target-source-root PATH` – pre-populate VSCode `fluxloop.targetSourceRoot`
|
|
48
|
+
- `--skip-doctor` – skip the final `fluxloop doctor` check
|
|
49
|
+
|
|
50
|
+
After running the script, open the folder in VSCode and use `FluxLoop: Show Environment Info`
|
|
51
|
+
or `FluxLoop: Run Doctor` to confirm the environment.
|
|
52
|
+
|
|
34
53
|
## Runner Integration Patterns
|
|
35
54
|
|
|
36
55
|
Configure how FluxLoop calls your code in `configs/simulation.yaml`:
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Doctor command for diagnosing FluxLoop environment issues.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from dataclasses import dataclass, asdict
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
import typer
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.padding import Padding
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
from rich.table import Table
|
|
21
|
+
|
|
22
|
+
from ..project_paths import resolve_config_directory, resolve_config_path
|
|
23
|
+
from ..constants import DEFAULT_CONFIG_PATH, DEFAULT_ROOT_DIR_NAME
|
|
24
|
+
|
|
25
|
+
app = typer.Typer(
|
|
26
|
+
help="Diagnose FluxLoop CLI, MCP and environment setup.",
|
|
27
|
+
invoke_without_command=True,
|
|
28
|
+
no_args_is_help=False,
|
|
29
|
+
)
|
|
30
|
+
console = Console()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class CommandResult:
|
|
35
|
+
success: bool
|
|
36
|
+
path: Optional[str]
|
|
37
|
+
output: Optional[str]
|
|
38
|
+
error: Optional[str]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _which(executable: str) -> Optional[str]:
|
|
42
|
+
from shutil import which
|
|
43
|
+
|
|
44
|
+
return which(executable)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _run_command(command: str, *args: str) -> CommandResult:
|
|
48
|
+
candidate = _which(command)
|
|
49
|
+
if not candidate:
|
|
50
|
+
return CommandResult(False, None, None, f"{command} not found on PATH")
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
completed = subprocess.run(
|
|
54
|
+
[candidate, *args],
|
|
55
|
+
capture_output=True,
|
|
56
|
+
text=True,
|
|
57
|
+
check=True,
|
|
58
|
+
)
|
|
59
|
+
return CommandResult(True, candidate, completed.stdout.strip(), completed.stderr.strip() or None)
|
|
60
|
+
except subprocess.CalledProcessError as exc:
|
|
61
|
+
return CommandResult(
|
|
62
|
+
False,
|
|
63
|
+
candidate,
|
|
64
|
+
exc.stdout.strip() or None,
|
|
65
|
+
exc.stderr.strip() or exc.stdout.strip() or str(exc),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _detect_virtual_environment() -> Dict[str, Any]:
|
|
70
|
+
env_vars = {
|
|
71
|
+
"VIRTUAL_ENV": os.environ.get("VIRTUAL_ENV"),
|
|
72
|
+
"CONDA_PREFIX": os.environ.get("CONDA_PREFIX"),
|
|
73
|
+
"UV_PROJECT_ENV": os.environ.get("UV_PROJECT_ENV"),
|
|
74
|
+
}
|
|
75
|
+
virtualized = sys.prefix != getattr(sys, "base_prefix", sys.prefix)
|
|
76
|
+
return {
|
|
77
|
+
"python_executable": sys.executable,
|
|
78
|
+
"python_version": platform.python_version(),
|
|
79
|
+
"platform": platform.platform(),
|
|
80
|
+
"virtual_env": virtualized,
|
|
81
|
+
"virtual_env_path": (
|
|
82
|
+
env_vars["VIRTUAL_ENV"]
|
|
83
|
+
or env_vars["CONDA_PREFIX"]
|
|
84
|
+
or (Path(sys.prefix).as_posix() if virtualized else None)
|
|
85
|
+
),
|
|
86
|
+
"environment_variables": {k: v for k, v in env_vars.items() if v},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _check_index_directory(index_dir: Path) -> Dict[str, Any]:
|
|
91
|
+
exists = index_dir.exists()
|
|
92
|
+
chunks_file = index_dir / "chunks.jsonl"
|
|
93
|
+
chunks_exists = chunks_file.exists()
|
|
94
|
+
chunks_size = chunks_file.stat().st_size if chunks_exists else 0
|
|
95
|
+
return {
|
|
96
|
+
"exists": exists,
|
|
97
|
+
"path": index_dir.as_posix(),
|
|
98
|
+
"chunks_exists": chunks_exists,
|
|
99
|
+
"chunks_size": chunks_size,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _resolve_project_info(root: Path, project: Optional[str]) -> Dict[str, Any]:
|
|
104
|
+
config_path = resolve_config_path(DEFAULT_CONFIG_PATH, project, root)
|
|
105
|
+
config_dir = resolve_config_directory(project, root)
|
|
106
|
+
return {
|
|
107
|
+
"root": str(root.resolve()),
|
|
108
|
+
"config_directory": str(config_dir.resolve()),
|
|
109
|
+
"project_yaml": str(config_path.resolve()),
|
|
110
|
+
"project_yaml_exists": config_path.exists(),
|
|
111
|
+
"config_directory_exists": config_dir.exists(),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _run_diagnostics(
|
|
116
|
+
project: Optional[str],
|
|
117
|
+
root: Path,
|
|
118
|
+
index_dir: Optional[Path],
|
|
119
|
+
output_json: bool,
|
|
120
|
+
) -> None:
|
|
121
|
+
console.print(Panel.fit("FluxLoop Environment Doctor", border_style="blue"))
|
|
122
|
+
|
|
123
|
+
virtual_info = _detect_virtual_environment()
|
|
124
|
+
fluxloop_result = _run_command("fluxloop", "--version")
|
|
125
|
+
mcp_result = _run_command("fluxloop-mcp", "--help")
|
|
126
|
+
python_result = _run_command(sys.executable, "--version")
|
|
127
|
+
|
|
128
|
+
resolved_root = root
|
|
129
|
+
project_info = _resolve_project_info(resolved_root, project)
|
|
130
|
+
resolved_index_dir = (
|
|
131
|
+
index_dir
|
|
132
|
+
if index_dir is not None
|
|
133
|
+
else Path.home() / ".fluxloop" / "mcp" / "index" / "dev"
|
|
134
|
+
)
|
|
135
|
+
index_info = _check_index_directory(resolved_index_dir)
|
|
136
|
+
|
|
137
|
+
diagnostics = {
|
|
138
|
+
"python": {
|
|
139
|
+
"executable": sys.executable,
|
|
140
|
+
"version": virtual_info["python_version"],
|
|
141
|
+
"platform": virtual_info["platform"],
|
|
142
|
+
"command_output": python_result.output,
|
|
143
|
+
},
|
|
144
|
+
"virtual_environment": virtual_info,
|
|
145
|
+
"fluxloop_cli": asdict(fluxloop_result),
|
|
146
|
+
"fluxloop_mcp": asdict(mcp_result),
|
|
147
|
+
"project": project_info,
|
|
148
|
+
"mcp_index": index_info,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if output_json:
|
|
152
|
+
typer.echo(json.dumps(diagnostics, indent=2, default=str))
|
|
153
|
+
raise typer.Exit()
|
|
154
|
+
|
|
155
|
+
items_panel = Table(show_header=False, box=None)
|
|
156
|
+
items_panel.add_column("Component", style="cyan", no_wrap=True)
|
|
157
|
+
items_panel.add_column("Status")
|
|
158
|
+
items_panel.add_column("Details", style="dim")
|
|
159
|
+
|
|
160
|
+
items_panel.add_row(
|
|
161
|
+
"Python",
|
|
162
|
+
"[green]✓[/green]" if python_result.success else "[red]✗[/red]",
|
|
163
|
+
f"{virtual_info['python_version']} ({sys.executable})",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
items_panel.add_row(
|
|
167
|
+
"Virtual Env",
|
|
168
|
+
"[green]✓[/green]" if virtual_info["virtual_env"] else "[yellow]–[/yellow]",
|
|
169
|
+
virtual_info["virtual_env_path"] or "Global interpreter",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
items_panel.add_row(
|
|
173
|
+
"FluxLoop CLI",
|
|
174
|
+
"[green]✓[/green]" if fluxloop_result.success else "[red]✗[/red]",
|
|
175
|
+
fluxloop_result.path or fluxloop_result.error or "Not found",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
items_panel.add_row(
|
|
179
|
+
"FluxLoop MCP",
|
|
180
|
+
"[green]✓[/green]" if mcp_result.success else "[red]✗[/red]",
|
|
181
|
+
mcp_result.path or mcp_result.error or "Not found",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
index_status = "[green]✓[/green]" if index_info["exists"] else "[yellow]–[/yellow]"
|
|
185
|
+
index_details = index_info["path"]
|
|
186
|
+
if index_info["exists"]:
|
|
187
|
+
chunk_detail = (
|
|
188
|
+
f"chunks.jsonl ({index_info['chunks_size']} bytes)"
|
|
189
|
+
if index_info["chunks_exists"]
|
|
190
|
+
else "missing chunks.jsonl"
|
|
191
|
+
)
|
|
192
|
+
index_details = f"{index_details} • {chunk_detail}"
|
|
193
|
+
|
|
194
|
+
items_panel.add_row("MCP Index", index_status, index_details)
|
|
195
|
+
|
|
196
|
+
config_status = (
|
|
197
|
+
"[green]✓[/green]" if project_info["project_yaml_exists"] else "[yellow]–[/yellow]"
|
|
198
|
+
)
|
|
199
|
+
config_details = (
|
|
200
|
+
f"{project_info['project_yaml']} (project.yaml)"
|
|
201
|
+
if project_info["project_yaml_exists"]
|
|
202
|
+
else "Run: fluxloop init project"
|
|
203
|
+
)
|
|
204
|
+
items_panel.add_row("Project Config", config_status, config_details)
|
|
205
|
+
|
|
206
|
+
console.print(Padding(items_panel, (1, 0, 1, 0)))
|
|
207
|
+
|
|
208
|
+
if fluxloop_result.error or mcp_result.error:
|
|
209
|
+
console.print("[bold red]Errors[/bold red]")
|
|
210
|
+
if fluxloop_result.error:
|
|
211
|
+
console.print(f"• FluxLoop CLI: {fluxloop_result.error}")
|
|
212
|
+
if mcp_result.error:
|
|
213
|
+
console.print(f"• fluxloop-mcp: {mcp_result.error}")
|
|
214
|
+
console.print()
|
|
215
|
+
|
|
216
|
+
if diagnostics["fluxloop_cli"]["output"]:
|
|
217
|
+
console.print(Panel(diagnostics["fluxloop_cli"]["output"], title="fluxloop --version", border_style="green"))
|
|
218
|
+
if diagnostics["fluxloop_mcp"]["output"]:
|
|
219
|
+
console.print(Panel(diagnostics["fluxloop_mcp"]["output"], title="fluxloop-mcp --help", border_style="green"))
|
|
220
|
+
|
|
221
|
+
console.print(Padding(Panel.fit("Doctor completed", border_style="green"), (1, 0, 0, 0)))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@app.callback()
|
|
225
|
+
def doctor_callback(
|
|
226
|
+
ctx: typer.Context,
|
|
227
|
+
project: Optional[str] = typer.Option(
|
|
228
|
+
None, "--project", "-p", help="Project name under the FluxLoop root directory."
|
|
229
|
+
),
|
|
230
|
+
root: Path = typer.Option(
|
|
231
|
+
Path(DEFAULT_ROOT_DIR_NAME),
|
|
232
|
+
"--root",
|
|
233
|
+
help="FluxLoop root directory (defaults to ./fluxloop).",
|
|
234
|
+
),
|
|
235
|
+
index_dir: Optional[Path] = typer.Option(
|
|
236
|
+
None,
|
|
237
|
+
"--index-dir",
|
|
238
|
+
help="Override FluxLoop MCP index directory.",
|
|
239
|
+
),
|
|
240
|
+
output_json: bool = typer.Option(
|
|
241
|
+
False,
|
|
242
|
+
"--json",
|
|
243
|
+
help="Output diagnostic information as JSON.",
|
|
244
|
+
),
|
|
245
|
+
) -> None:
|
|
246
|
+
"""
|
|
247
|
+
Diagnose the FluxLoop CLI, MCP installation, virtual environment, and project configuration.
|
|
248
|
+
"""
|
|
249
|
+
if ctx.invoked_subcommand:
|
|
250
|
+
return
|
|
251
|
+
_run_diagnostics(project, root, index_dir, output_json)
|
|
252
|
+
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Evaluate command for scoring experiment outputs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from ..evaluation import EvaluationOptions, load_evaluation_config, run_evaluation
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
app = typer.Typer(help="Evaluate experiment outputs and generate reports.")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command()
|
|
18
|
+
def experiment(
|
|
19
|
+
experiment_dir: Path = typer.Argument(
|
|
20
|
+
...,
|
|
21
|
+
help="Path to the experiment output directory",
|
|
22
|
+
exists=True,
|
|
23
|
+
dir_okay=True,
|
|
24
|
+
file_okay=False,
|
|
25
|
+
resolve_path=True,
|
|
26
|
+
),
|
|
27
|
+
config: Path = typer.Option(
|
|
28
|
+
Path("configs/evaluation.yaml"),
|
|
29
|
+
"--config",
|
|
30
|
+
"-c",
|
|
31
|
+
help="Path to evaluation configuration file",
|
|
32
|
+
),
|
|
33
|
+
output: Path = typer.Option(
|
|
34
|
+
Path("evaluation"),
|
|
35
|
+
"--output",
|
|
36
|
+
"-o",
|
|
37
|
+
help="Output directory name (relative to the experiment directory)",
|
|
38
|
+
),
|
|
39
|
+
overwrite: bool = typer.Option(
|
|
40
|
+
False,
|
|
41
|
+
"--overwrite",
|
|
42
|
+
help="Overwrite output directory if it already exists",
|
|
43
|
+
),
|
|
44
|
+
llm_api_key: Optional[str] = typer.Option(
|
|
45
|
+
None,
|
|
46
|
+
"--llm-api-key",
|
|
47
|
+
help="LLM API key for judge evaluators (optional)",
|
|
48
|
+
envvar="FLUXLOOP_LLM_API_KEY",
|
|
49
|
+
),
|
|
50
|
+
sample_rate: Optional[float] = typer.Option(
|
|
51
|
+
None,
|
|
52
|
+
"--sample-rate",
|
|
53
|
+
help="Override LLM evaluation sample rate (0.0-1.0)",
|
|
54
|
+
),
|
|
55
|
+
max_llm_calls: Optional[int] = typer.Option(
|
|
56
|
+
None,
|
|
57
|
+
"--max-llm",
|
|
58
|
+
help="Maximum number of LLM evaluations to run",
|
|
59
|
+
),
|
|
60
|
+
report: Optional[str] = typer.Option(
|
|
61
|
+
None,
|
|
62
|
+
"--report",
|
|
63
|
+
help="Report output format to generate (md, html, both)",
|
|
64
|
+
metavar="FORMAT",
|
|
65
|
+
),
|
|
66
|
+
report_template: Optional[Path] = typer.Option(
|
|
67
|
+
None,
|
|
68
|
+
"--report-template",
|
|
69
|
+
help="Path to custom HTML report template",
|
|
70
|
+
exists=False,
|
|
71
|
+
file_okay=True,
|
|
72
|
+
dir_okay=False,
|
|
73
|
+
),
|
|
74
|
+
baseline: Optional[Path] = typer.Option(
|
|
75
|
+
None,
|
|
76
|
+
"--baseline",
|
|
77
|
+
help="Path to baseline summary.json file for comparisons",
|
|
78
|
+
exists=False,
|
|
79
|
+
file_okay=True,
|
|
80
|
+
dir_okay=False,
|
|
81
|
+
),
|
|
82
|
+
verbose: bool = typer.Option(
|
|
83
|
+
False,
|
|
84
|
+
"--verbose",
|
|
85
|
+
help="Enable verbose logging",
|
|
86
|
+
),
|
|
87
|
+
):
|
|
88
|
+
"""
|
|
89
|
+
Evaluate experiment outputs and generate aggregate reports.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
resolved_experiment_dir = experiment_dir.resolve()
|
|
93
|
+
if not resolved_experiment_dir.is_dir():
|
|
94
|
+
raise typer.BadParameter(f"Experiment directory not found: {resolved_experiment_dir}")
|
|
95
|
+
|
|
96
|
+
if not config.is_absolute():
|
|
97
|
+
config_path = (Path.cwd() / config).resolve()
|
|
98
|
+
else:
|
|
99
|
+
config_path = config
|
|
100
|
+
|
|
101
|
+
if sample_rate is not None and not 0.0 <= sample_rate <= 1.0:
|
|
102
|
+
raise typer.BadParameter("--sample-rate must be between 0.0 and 1.0")
|
|
103
|
+
|
|
104
|
+
if max_llm_calls is not None and max_llm_calls < 0:
|
|
105
|
+
raise typer.BadParameter("--max-llm must be a non-negative integer")
|
|
106
|
+
|
|
107
|
+
report_format: Optional[str] = None
|
|
108
|
+
if report is not None:
|
|
109
|
+
candidate = report.lower()
|
|
110
|
+
if candidate not in {"md", "html", "both"}:
|
|
111
|
+
raise typer.BadParameter("--report must be one of: md, html, both")
|
|
112
|
+
report_format = candidate
|
|
113
|
+
|
|
114
|
+
report_template_path: Optional[Path] = None
|
|
115
|
+
if report_template is not None:
|
|
116
|
+
resolved_template = report_template
|
|
117
|
+
if not resolved_template.is_absolute():
|
|
118
|
+
resolved_template = (Path.cwd() / resolved_template).resolve()
|
|
119
|
+
if not resolved_template.exists():
|
|
120
|
+
raise typer.BadParameter(f"Report template not found: {resolved_template}")
|
|
121
|
+
report_template_path = resolved_template
|
|
122
|
+
|
|
123
|
+
baseline_path: Optional[Path] = None
|
|
124
|
+
if baseline is not None:
|
|
125
|
+
resolved_baseline = baseline
|
|
126
|
+
if not resolved_baseline.is_absolute():
|
|
127
|
+
resolved_baseline = (Path.cwd() / resolved_baseline).resolve()
|
|
128
|
+
baseline_path = resolved_baseline
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
evaluation_config = load_evaluation_config(config_path)
|
|
132
|
+
except FileNotFoundError as exc:
|
|
133
|
+
raise typer.BadParameter(str(exc)) from exc
|
|
134
|
+
except Exception as exc: # noqa: BLE001
|
|
135
|
+
raise typer.BadParameter(f"Failed to load evaluation config: {exc}") from exc
|
|
136
|
+
|
|
137
|
+
output_dir = output
|
|
138
|
+
if not output_dir.is_absolute():
|
|
139
|
+
output_dir = resolved_experiment_dir / output_dir
|
|
140
|
+
|
|
141
|
+
options = EvaluationOptions(
|
|
142
|
+
output_dir=output_dir,
|
|
143
|
+
overwrite=overwrite,
|
|
144
|
+
llm_api_key=llm_api_key,
|
|
145
|
+
sample_rate=sample_rate,
|
|
146
|
+
max_llm_calls=max_llm_calls,
|
|
147
|
+
verbose=verbose,
|
|
148
|
+
report_format=report_format, # type: ignore[arg-type]
|
|
149
|
+
report_template=report_template_path,
|
|
150
|
+
baseline_path=baseline_path,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
effective_report_format = report_format or evaluation_config.report.output
|
|
154
|
+
template_display = (
|
|
155
|
+
str(report_template_path)
|
|
156
|
+
if report_template_path is not None
|
|
157
|
+
else evaluation_config.report.template_path or "default"
|
|
158
|
+
)
|
|
159
|
+
baseline_display = (
|
|
160
|
+
str(baseline_path)
|
|
161
|
+
if baseline_path is not None
|
|
162
|
+
else evaluation_config.additional_analysis.comparison.baseline_path
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
message_lines = [
|
|
166
|
+
f"📊 Evaluating experiment at [cyan]{resolved_experiment_dir}[/cyan]",
|
|
167
|
+
f"⚙️ Config: [magenta]{config_path}[/magenta]",
|
|
168
|
+
f"📁 Output: [green]{output_dir}[/green]",
|
|
169
|
+
f"📝 Report: [yellow]{effective_report_format.upper()}[/yellow] (template: [cyan]{template_display}[/cyan])",
|
|
170
|
+
]
|
|
171
|
+
if baseline_display:
|
|
172
|
+
message_lines.append(f"📈 Baseline: [cyan]{baseline_display}[/cyan]")
|
|
173
|
+
|
|
174
|
+
console.print("\n".join(message_lines))
|
|
175
|
+
|
|
176
|
+
summary = run_evaluation(resolved_experiment_dir, evaluation_config, options)
|
|
177
|
+
|
|
178
|
+
if verbose:
|
|
179
|
+
console.print("\n[bold]Summary[/bold]")
|
|
180
|
+
for key, value in summary.items():
|
|
181
|
+
console.print(f"• {key}: {value}")
|
|
182
|
+
|
|
183
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Evaluation framework for FluxLoop experiments.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .config import (
|
|
6
|
+
AdditionalAnalysisConfig,
|
|
7
|
+
AdvancedConfig,
|
|
8
|
+
AggregateConfig,
|
|
9
|
+
EvaluationConfig,
|
|
10
|
+
EvaluationGoalConfig,
|
|
11
|
+
EvaluatorConfig,
|
|
12
|
+
LimitsConfig,
|
|
13
|
+
ReportConfig,
|
|
14
|
+
ReportOutput,
|
|
15
|
+
RuleDefinition,
|
|
16
|
+
SuccessCriteriaConfig,
|
|
17
|
+
load_evaluation_config,
|
|
18
|
+
)
|
|
19
|
+
from .engine import EvaluationOptions, run_evaluation
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"AdditionalAnalysisConfig",
|
|
23
|
+
"AdvancedConfig",
|
|
24
|
+
"AggregateConfig",
|
|
25
|
+
"EvaluationConfig",
|
|
26
|
+
"EvaluationGoalConfig",
|
|
27
|
+
"EvaluatorConfig",
|
|
28
|
+
"LimitsConfig",
|
|
29
|
+
"ReportConfig",
|
|
30
|
+
"ReportOutput",
|
|
31
|
+
"RuleDefinition",
|
|
32
|
+
"SuccessCriteriaConfig",
|
|
33
|
+
"EvaluationOptions",
|
|
34
|
+
"load_evaluation_config",
|
|
35
|
+
"run_evaluation",
|
|
36
|
+
]
|
|
37
|
+
|