fluxloop-cli 0.2.30__py3-none-any.whl → 0.2.32__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.
- fluxloop_cli/__init__.py +1 -1
- fluxloop_cli/arg_binder.py +2 -3
- fluxloop_cli/commands/config.py +0 -1
- fluxloop_cli/commands/evaluate.py +89 -114
- fluxloop_cli/commands/init.py +1 -1
- fluxloop_cli/commands/record.py +0 -1
- fluxloop_cli/commands/run.py +0 -1
- fluxloop_cli/commands/status.py +1 -2
- fluxloop_cli/config_loader.py +0 -2
- fluxloop_cli/evaluation/artifacts.py +1 -1
- fluxloop_cli/evaluation/config.py +41 -2
- fluxloop_cli/evaluation/report/__init__.py +0 -0
- fluxloop_cli/evaluation/report/aggregator.py +626 -0
- fluxloop_cli/evaluation/report/generator.py +759 -0
- fluxloop_cli/evaluation/report/pdf_exporter.py +71 -0
- fluxloop_cli/evaluation/report/pipeline.py +105 -0
- fluxloop_cli/evaluation/report/renderer.py +394 -0
- fluxloop_cli/evaluation/rules.py +1 -1
- fluxloop_cli/evaluation/templates/report.html.j2 +7072 -0
- fluxloop_cli/input_generator.py +138 -15
- fluxloop_cli/llm_generator.py +23 -23
- fluxloop_cli/main.py +0 -2
- fluxloop_cli/project_paths.py +0 -1
- fluxloop_cli/runner.py +2 -1
- fluxloop_cli/templates.py +201 -252
- fluxloop_cli/testing/pytest_plugin.py +13 -2
- {fluxloop_cli-0.2.30.dist-info → fluxloop_cli-0.2.32.dist-info}/METADATA +5 -3
- fluxloop_cli-0.2.32.dist-info/RECORD +58 -0
- fluxloop_cli-0.2.30.dist-info/RECORD +0 -51
- {fluxloop_cli-0.2.30.dist-info → fluxloop_cli-0.2.32.dist-info}/WHEEL +0 -0
- {fluxloop_cli-0.2.30.dist-info → fluxloop_cli-0.2.32.dist-info}/entry_points.txt +0 -0
- {fluxloop_cli-0.2.30.dist-info → fluxloop_cli-0.2.32.dist-info}/top_level.txt +0 -0
fluxloop_cli/__init__.py
CHANGED
fluxloop_cli/arg_binder.py
CHANGED
|
@@ -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)
|
fluxloop_cli/commands/config.py
CHANGED
|
@@ -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 (
|
|
@@ -1,19 +1,65 @@
|
|
|
1
|
-
"""Evaluate command for
|
|
1
|
+
"""Evaluate command for generating interactive reports."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
5
7
|
import os
|
|
8
|
+
import shutil
|
|
9
|
+
from dataclasses import asdict
|
|
6
10
|
from pathlib import Path
|
|
7
11
|
from typing import Optional
|
|
8
12
|
|
|
9
13
|
import typer
|
|
14
|
+
import yaml
|
|
10
15
|
from rich.console import Console
|
|
11
16
|
|
|
12
17
|
from ..environment import load_env_chain
|
|
13
|
-
from ..evaluation import
|
|
18
|
+
from ..evaluation import load_evaluation_config
|
|
19
|
+
from ..evaluation.artifacts import load_per_trace_records
|
|
20
|
+
from ..evaluation.report.pipeline import ReportPipeline
|
|
14
21
|
|
|
15
22
|
console = Console()
|
|
16
|
-
app = typer.Typer(help="Evaluate experiment outputs and generate reports.")
|
|
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)
|
|
17
63
|
|
|
18
64
|
|
|
19
65
|
@app.command()
|
|
@@ -33,7 +79,7 @@ def experiment(
|
|
|
33
79
|
help="Path to evaluation configuration file",
|
|
34
80
|
),
|
|
35
81
|
output: Path = typer.Option(
|
|
36
|
-
Path("
|
|
82
|
+
Path("evaluation_report"),
|
|
37
83
|
"--output",
|
|
38
84
|
"-o",
|
|
39
85
|
help="Output directory name (relative to the experiment directory)",
|
|
@@ -46,41 +92,9 @@ def experiment(
|
|
|
46
92
|
llm_api_key: Optional[str] = typer.Option(
|
|
47
93
|
None,
|
|
48
94
|
"--llm-api-key",
|
|
49
|
-
help="LLM API key for
|
|
95
|
+
help="LLM API key for report generation (optional)",
|
|
50
96
|
envvar="FLUXLOOP_LLM_API_KEY",
|
|
51
97
|
),
|
|
52
|
-
sample_rate: Optional[float] = typer.Option(
|
|
53
|
-
None,
|
|
54
|
-
"--sample-rate",
|
|
55
|
-
help="Override LLM evaluation sample rate (0.0-1.0)",
|
|
56
|
-
),
|
|
57
|
-
max_llm_calls: Optional[int] = typer.Option(
|
|
58
|
-
None,
|
|
59
|
-
"--max-llm",
|
|
60
|
-
help="Maximum number of LLM evaluations to run",
|
|
61
|
-
),
|
|
62
|
-
report: Optional[str] = typer.Option(
|
|
63
|
-
None,
|
|
64
|
-
"--report",
|
|
65
|
-
help="Report output format to generate (md, html, both)",
|
|
66
|
-
metavar="FORMAT",
|
|
67
|
-
),
|
|
68
|
-
report_template: Optional[Path] = typer.Option(
|
|
69
|
-
None,
|
|
70
|
-
"--report-template",
|
|
71
|
-
help="Path to custom HTML report template",
|
|
72
|
-
exists=False,
|
|
73
|
-
file_okay=True,
|
|
74
|
-
dir_okay=False,
|
|
75
|
-
),
|
|
76
|
-
baseline: Optional[Path] = typer.Option(
|
|
77
|
-
None,
|
|
78
|
-
"--baseline",
|
|
79
|
-
help="Path to baseline summary.json file for comparisons",
|
|
80
|
-
exists=False,
|
|
81
|
-
file_okay=True,
|
|
82
|
-
dir_okay=False,
|
|
83
|
-
),
|
|
84
98
|
per_trace: Optional[Path] = typer.Option(
|
|
85
99
|
None,
|
|
86
100
|
"--per-trace",
|
|
@@ -93,59 +107,29 @@ def experiment(
|
|
|
93
107
|
),
|
|
94
108
|
):
|
|
95
109
|
"""
|
|
96
|
-
Evaluate experiment outputs and generate
|
|
110
|
+
Evaluate experiment outputs and generate an interactive HTML report.
|
|
97
111
|
"""
|
|
98
112
|
|
|
113
|
+
logging.basicConfig(
|
|
114
|
+
level=logging.DEBUG if verbose else logging.INFO,
|
|
115
|
+
format="%(message)s",
|
|
116
|
+
)
|
|
117
|
+
|
|
99
118
|
resolved_experiment_dir = experiment_dir.resolve()
|
|
100
119
|
if not resolved_experiment_dir.is_dir():
|
|
101
120
|
raise typer.BadParameter(f"Experiment directory not found: {resolved_experiment_dir}")
|
|
102
121
|
|
|
103
|
-
|
|
104
|
-
config_path = (Path.cwd() / config).resolve()
|
|
105
|
-
else:
|
|
106
|
-
config_path = config
|
|
107
|
-
|
|
108
|
-
if sample_rate is not None and not 0.0 <= sample_rate <= 1.0:
|
|
109
|
-
raise typer.BadParameter("--sample-rate must be between 0.0 and 1.0")
|
|
110
|
-
|
|
111
|
-
if max_llm_calls is not None and max_llm_calls < 0:
|
|
112
|
-
raise typer.BadParameter("--max-llm must be a non-negative integer")
|
|
113
|
-
|
|
114
|
-
report_format: Optional[str] = None
|
|
115
|
-
if report is not None:
|
|
116
|
-
candidate = report.lower()
|
|
117
|
-
if candidate not in {"md", "html", "both"}:
|
|
118
|
-
raise typer.BadParameter("--report must be one of: md, html, both")
|
|
119
|
-
report_format = candidate
|
|
120
|
-
|
|
121
|
-
report_template_path: Optional[Path] = None
|
|
122
|
-
if report_template is not None:
|
|
123
|
-
resolved_template = report_template
|
|
124
|
-
if not resolved_template.is_absolute():
|
|
125
|
-
resolved_template = (Path.cwd() / resolved_template).resolve()
|
|
126
|
-
if not resolved_template.exists():
|
|
127
|
-
raise typer.BadParameter(f"Report template not found: {resolved_template}")
|
|
128
|
-
report_template_path = resolved_template
|
|
129
|
-
|
|
130
|
-
baseline_path: Optional[Path] = None
|
|
131
|
-
if baseline is not None:
|
|
132
|
-
resolved_baseline = baseline
|
|
133
|
-
if not resolved_baseline.is_absolute():
|
|
134
|
-
resolved_baseline = (Path.cwd() / resolved_baseline).resolve()
|
|
135
|
-
baseline_path = resolved_baseline
|
|
122
|
+
config_path = config.resolve() if config.is_absolute() else (Path.cwd() / config).resolve()
|
|
136
123
|
|
|
137
124
|
if per_trace is not None:
|
|
138
|
-
per_trace_path = per_trace
|
|
139
|
-
if not per_trace_path.is_absolute():
|
|
140
|
-
per_trace_path = (Path.cwd() / per_trace_path).resolve()
|
|
125
|
+
per_trace_path = per_trace.resolve() if per_trace.is_absolute() else (Path.cwd() / per_trace).resolve()
|
|
141
126
|
else:
|
|
142
127
|
per_trace_path = resolved_experiment_dir / "per_trace_analysis" / "per_trace.jsonl"
|
|
143
128
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
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.")
|
|
149
133
|
|
|
150
134
|
try:
|
|
151
135
|
evaluation_config = load_evaluation_config(config_path)
|
|
@@ -168,52 +152,43 @@ def experiment(
|
|
|
168
152
|
if llm_api_key is None:
|
|
169
153
|
llm_api_key = os.getenv("FLUXLOOP_LLM_API_KEY") or os.getenv("OPENAI_API_KEY")
|
|
170
154
|
|
|
171
|
-
output_dir = output
|
|
172
|
-
|
|
173
|
-
output_dir = resolved_experiment_dir / output_dir
|
|
155
|
+
output_dir = output if output.is_absolute() else (resolved_experiment_dir / output)
|
|
156
|
+
_prepare_output_directory(output_dir, overwrite)
|
|
174
157
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
overwrite=overwrite,
|
|
178
|
-
llm_api_key=llm_api_key,
|
|
179
|
-
sample_rate=sample_rate,
|
|
180
|
-
max_llm_calls=max_llm_calls,
|
|
181
|
-
verbose=verbose,
|
|
182
|
-
report_format=report_format, # type: ignore[arg-type]
|
|
183
|
-
report_template=report_template_path,
|
|
184
|
-
baseline_path=baseline_path,
|
|
185
|
-
per_trace_path=per_trace_path,
|
|
186
|
-
)
|
|
158
|
+
input_config_path = _find_config_file(config_path, "input.yaml")
|
|
159
|
+
project_config_path = _find_config_file(config_path, "project.yaml")
|
|
187
160
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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,
|
|
198
174
|
)
|
|
199
175
|
|
|
200
176
|
message_lines = [
|
|
201
177
|
f"📊 Evaluating experiment at [cyan]{resolved_experiment_dir}[/cyan]",
|
|
202
178
|
f"⚙️ Config: [magenta]{config_path}[/magenta]",
|
|
203
|
-
f"📁 Output: [green]{output_dir}[/green]",
|
|
204
|
-
f"📝 Report: [yellow]{effective_report_format.upper()}[/yellow] (template: [cyan]{template_display}[/cyan])",
|
|
205
179
|
f"🧵 Per-trace data: [blue]{per_trace_path}[/blue]",
|
|
180
|
+
f"📁 Output: [green]{output_dir}[/green]",
|
|
206
181
|
]
|
|
207
|
-
if baseline_display:
|
|
208
|
-
message_lines.append(f"📈 Baseline: [cyan]{baseline_display}[/cyan]")
|
|
209
|
-
|
|
210
182
|
console.print("\n".join(message_lines))
|
|
211
183
|
|
|
212
|
-
|
|
184
|
+
artifacts = asyncio.run(pipeline.run(trace_summaries))
|
|
185
|
+
console.print(f"\n✅ Report ready: [bold cyan]{artifacts.html_path}[/bold cyan]")
|
|
186
|
+
if artifacts.pdf_path:
|
|
187
|
+
console.print(f"🖨️ PDF ready: [bold green]{artifacts.pdf_path}[/bold green]")
|
|
188
|
+
else:
|
|
189
|
+
console.print(
|
|
190
|
+
"⚠️ PDF export skipped (WeasyPrint is unavailable or PDF rendering failed)."
|
|
191
|
+
)
|
|
213
192
|
|
|
214
|
-
if verbose:
|
|
215
|
-
console.print("\n[bold]Summary[/bold]")
|
|
216
|
-
for key, value in summary.items():
|
|
217
|
-
console.print(f"• {key}: {value}")
|
|
218
193
|
|
|
219
194
|
|
fluxloop_cli/commands/init.py
CHANGED
|
@@ -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
|
|
fluxloop_cli/commands/record.py
CHANGED
fluxloop_cli/commands/run.py
CHANGED
|
@@ -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
|
fluxloop_cli/commands/status.py
CHANGED
|
@@ -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(
|
fluxloop_cli/config_loader.py
CHANGED
|
@@ -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)
|
|
@@ -229,6 +229,8 @@ class EvaluationConfig:
|
|
|
229
229
|
additional_analysis: AdditionalAnalysisConfig = field(default_factory=AdditionalAnalysisConfig)
|
|
230
230
|
report: ReportConfig = field(default_factory=ReportConfig)
|
|
231
231
|
advanced: AdvancedConfig = field(default_factory=AdvancedConfig)
|
|
232
|
+
metrics: Dict[str, Any] = field(default_factory=dict)
|
|
233
|
+
efficiency: Dict[str, Any] = field(default_factory=dict)
|
|
232
234
|
|
|
233
235
|
def set_source_dir(self, source_dir: Path) -> None:
|
|
234
236
|
"""Remember the directory where this config file was loaded from."""
|
|
@@ -383,12 +385,45 @@ def _parse_limits(raw: Any) -> LimitsConfig:
|
|
|
383
385
|
)
|
|
384
386
|
|
|
385
387
|
|
|
388
|
+
def _parse_metrics(raw: Any) -> Dict[str, Any]:
|
|
389
|
+
if raw is None:
|
|
390
|
+
return {}
|
|
391
|
+
if not isinstance(raw, dict):
|
|
392
|
+
raise ValueError("metrics must be a mapping")
|
|
393
|
+
normalized: Dict[str, Any] = {}
|
|
394
|
+
for key, value in raw.items():
|
|
395
|
+
if not isinstance(value, dict):
|
|
396
|
+
raise ValueError(f"metrics.{key} must be a mapping")
|
|
397
|
+
normalized[key] = dict(value)
|
|
398
|
+
return normalized
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _parse_efficiency(raw: Any) -> Dict[str, Any]:
|
|
402
|
+
if raw is None:
|
|
403
|
+
return {}
|
|
404
|
+
if not isinstance(raw, dict):
|
|
405
|
+
raise ValueError("efficiency must be a mapping")
|
|
406
|
+
normalized: Dict[str, Any] = {}
|
|
407
|
+
for key, value in raw.items():
|
|
408
|
+
if not isinstance(value, dict):
|
|
409
|
+
raise ValueError(f"efficiency.{key} must be a mapping")
|
|
410
|
+
normalized[key] = dict(value)
|
|
411
|
+
return normalized
|
|
412
|
+
|
|
413
|
+
|
|
386
414
|
def _parse_evaluation_goal(raw: Any) -> EvaluationGoalConfig:
|
|
387
415
|
if raw is None:
|
|
388
416
|
return EvaluationGoalConfig()
|
|
417
|
+
|
|
418
|
+
if isinstance(raw, str):
|
|
419
|
+
# Backwards compatibility for older templates that used a bare string.
|
|
420
|
+
return EvaluationGoalConfig(text=raw.strip())
|
|
421
|
+
|
|
389
422
|
if not isinstance(raw, dict):
|
|
390
|
-
raise ValueError("evaluation_goal must be a mapping")
|
|
391
|
-
|
|
423
|
+
raise ValueError("evaluation_goal must be a mapping or string")
|
|
424
|
+
|
|
425
|
+
text_value = raw.get("text")
|
|
426
|
+
text = str(text_value) if text_value is not None else ""
|
|
392
427
|
return EvaluationGoalConfig(text=text)
|
|
393
428
|
|
|
394
429
|
|
|
@@ -711,6 +746,8 @@ def load_evaluation_config(path: Path) -> EvaluationConfig:
|
|
|
711
746
|
additional_analysis = _parse_additional_analysis(data.get("additional_analysis"))
|
|
712
747
|
report = _parse_report(data.get("report"))
|
|
713
748
|
advanced = _parse_advanced(data.get("advanced"))
|
|
749
|
+
metrics = _parse_metrics(data.get("metrics"))
|
|
750
|
+
efficiency = _parse_efficiency(data.get("efficiency"))
|
|
714
751
|
|
|
715
752
|
config_dir = path.parent.resolve()
|
|
716
753
|
|
|
@@ -723,6 +760,8 @@ def load_evaluation_config(path: Path) -> EvaluationConfig:
|
|
|
723
760
|
additional_analysis=additional_analysis,
|
|
724
761
|
report=report,
|
|
725
762
|
advanced=advanced,
|
|
763
|
+
metrics=metrics,
|
|
764
|
+
efficiency=efficiency,
|
|
726
765
|
)
|
|
727
766
|
|
|
728
767
|
config.set_source_dir(config_dir)
|
|
File without changes
|