clear-eval 2.0.3__tar.gz → 2.0.4__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.
- {clear_eval-2.0.3 → clear_eval-2.0.4}/PKG-INFO +1 -1
- {clear_eval-2.0.3 → clear_eval-2.0.4}/pyproject.toml +1 -1
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/dashboard/generate_static_dashboard.py +121 -7
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/build_json_results.py +131 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/run_trajectory_evaluation_pipeline.py +3 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/trace_evaluation/base_evaluator.py +11 -2
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/run_clear_agentic_eval.py +3 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/constants.py +11 -1
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/eval_utils.py +58 -15
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/full_pipeline.py +11 -3
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/inference_utils/llm_client.py +153 -64
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/setup/default_config.yaml +1 -1
- clear_eval-2.0.4/src/clear_eval/pipeline/use_cases/tool_call_use_case.py +375 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval.egg-info/PKG-INFO +1 -1
- clear_eval-2.0.3/src/clear_eval/pipeline/use_cases/tool_call_use_case.py +0 -482
- {clear_eval-2.0.3 → clear_eval-2.0.4}/LICENSE +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/README.md +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/setup.cfg +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/__init__.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/dashboard/agentic_workflow_dashboard.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/dashboard/launch_dashboard.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/dashboard/path_analysis.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/argument_definitions.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/create_ui_input.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/clear_analysis/base_clear_runner.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/clear_analysis/issues_clear_runner.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/clear_analysis/root_cause_clear_runner.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/clear_analysis/run_clear_analysis.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/trace_evaluation/full_trajectory_evaluator.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/trace_evaluation/full_trajectory_prompts.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/trace_evaluation/rubric_evaluator.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/trace_evaluation/rubric_generator.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/full_traces_evaluation/trace_evaluation/task_success_evaluator.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/preprocess_traces/compact_trace_formatter.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/preprocess_traces/preprocess_traces.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/preprocess_traces/process_langfuse_traces.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/preprocess_traces/process_mlflow_traces.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/preprocess_traces/trace_utils.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/run_clear_step_analysis.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/setup/__init__.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/setup/config_example.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/setup/config_example.yaml +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/setup/default_agentic_config.yaml +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/setup/rh_config_example.yaml +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/setup/test_tools_config_example.yaml +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/utils.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/analysis_runner.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/args.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/cli.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/dashboard/__init__.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/dashboard/show_analysis_dashboard.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/load_ui.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/logging_config.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/__init__.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/caching_utils.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/config_loader.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/evaluation_criteria.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/external_judge.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/inference_utils/__init__.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/inference_utils/endpoint_backends.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/inference_utils/langchain_chat_models.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/propmts.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/setup/__init__.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/threading_utils.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/use_cases/__init__.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/use_cases/eval_use_case.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/use_cases/external_judge_use_case.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/pipeline/use_cases/use_case_utils.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/__init__.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/pipeline_summary.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-006eee07eed4484592661b1bdba32a75.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-0e0148ed21eeebc046f03f63e4418723.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-0e1ef041647642c958a8aaa1892fdb88.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-4ae9a4305b7a0319ec516ef4eef4ae17.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-4eead8c00e900cc84f217a9a10724ecf.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-50adf266e9c1522dae57a6290a61596f.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-554015d661e4f69cfc629b6d46898421.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-55a6003784e34390f47c5c929d8f4db0.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-5818239708258c81fff3a4bb0b144802.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-5e63ae3886b225ae89a07b7fa4aafe0d.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-640f5750b635929a88547548f0cbb4da.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-6b2d02f30d4f0d3f4ca7d8f1469913ae.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-6c852575331f2e0a02a331d1ddaae59f.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-8e68becd37326e6fec6a7d3d3b47dbbb.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-b35ed52266fd023dd808950c6848f062.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-bccc245d6d47cdf730fd5db3028b52a3.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-d6010cdc5e36a50046fc10f33f0bc427.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-edadad8b3a67a24a43d145ae07c0c12a.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-f082801e1021022e0756e7d089f1558c.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/traces_data/tr-f34a9851b31fafffe527c5b2d6262de8.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_results/mlflow/my_experiment/unified_ui_results.zip +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/langfuse/434f3f96ac470e805428c4be45b4f2a3.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/langfuse/4c354cfb5b17f61af073df6796f2b236.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/langfuse/4deb34a4521fcedf92083d0a93fe11d4.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/langfuse/c685b7eb7826a87149bd2c42a50e47c5.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/langfuse/ebf1d6897941adb63bcc62aa7c50e8f8.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-006eee07eed4484592661b1bdba32a75.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-0e0148ed21eeebc046f03f63e4418723.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-0e1ef041647642c958a8aaa1892fdb88.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-4ae9a4305b7a0319ec516ef4eef4ae17.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-4eead8c00e900cc84f217a9a10724ecf.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-50adf266e9c1522dae57a6290a61596f.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-554015d661e4f69cfc629b6d46898421.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-55a6003784e34390f47c5c929d8f4db0.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-5818239708258c81fff3a4bb0b144802.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-5e63ae3886b225ae89a07b7fa4aafe0d.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-640f5750b635929a88547548f0cbb4da.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-6b2d02f30d4f0d3f4ca7d8f1469913ae.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-6c852575331f2e0a02a331d1ddaae59f.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-8e68becd37326e6fec6a7d3d3b47dbbb.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-b35ed52266fd023dd808950c6848f062.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-bccc245d6d47cdf730fd5db3028b52a3.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-d6010cdc5e36a50046fc10f33f0bc427.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-edadad8b3a67a24a43d145ae07c0c12a.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-f082801e1021022e0756e7d089f1558c.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/agentic/research_agent_traces/mlflow/tr-f34a9851b31fafffe527c5b2d6262de8.json +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/gsm8k/__init__.py +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/gsm8k/analysis_results_gsm8k_default.zip +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/gsm8k/gsm8k_default_predictions.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/sample_data/tool_calls/tool_calls_sample_data.csv +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval.egg-info/SOURCES.txt +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval.egg-info/dependency_links.txt +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval.egg-info/entry_points.txt +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval.egg-info/requires.txt +0 -0
- {clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "clear_eval"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.4"
|
|
8
8
|
description = "A python API sdk facilitating Error Analysis via LLM-as-a-Judge"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Lilach Eden", email = "lilache@il.ibm.com"},
|
{clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/dashboard/generate_static_dashboard.py
RENAMED
|
@@ -42,7 +42,7 @@ def _maybe_parse_json_text(value):
|
|
|
42
42
|
return value
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def load_json_data(json_path,
|
|
45
|
+
def load_json_data(json_path, num_examples=3):
|
|
46
46
|
"""Load all dashboard data from clear_results.json and map it to the HTML data shape."""
|
|
47
47
|
json_path = Path(json_path)
|
|
48
48
|
|
|
@@ -79,8 +79,8 @@ def load_json_data(json_path, include_examples=True):
|
|
|
79
79
|
for issue in issues:
|
|
80
80
|
issue_text = issue.get("issue_text") or issue.get("issue_id") or ""
|
|
81
81
|
examples = []
|
|
82
|
-
if
|
|
83
|
-
for occurrence in issue.get("occurrences", [])[:
|
|
82
|
+
if num_examples:
|
|
83
|
+
for occurrence in issue.get("occurrences", [])[:num_examples]:
|
|
84
84
|
tasks_list.add(occurrence.get("trace_id"))
|
|
85
85
|
span_reference = occurrence.get("span_reference", {})
|
|
86
86
|
input_output_pair = occurrence.get("input_output_pair", {})
|
|
@@ -121,13 +121,22 @@ def load_json_data(json_path, include_examples=True):
|
|
|
121
121
|
key=lambda row: -row["count"],
|
|
122
122
|
) + [row for row in issues_table if row["is_no_issue"]]
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
entry = {
|
|
125
125
|
"total_evals": int(summary.get("total_interactions", 0)),
|
|
126
126
|
"avg_score": round(float(summary["avg_score"]), 2) if summary.get("avg_score") is not None else None,
|
|
127
127
|
"unique_issues": len(issues),
|
|
128
128
|
"unique_tasks": node_stats.get(agent_name, {}).get("unique_tasks", 0),
|
|
129
129
|
"issues_table": issues_table,
|
|
130
|
+
"recommendations": None,
|
|
130
131
|
}
|
|
132
|
+
if agent_type == "tools":
|
|
133
|
+
# Recommendations now travel inside the per-agent payload
|
|
134
|
+
# produced by build_json_results._aggregate_recommendations,
|
|
135
|
+
# so the dashboard just reads the key off the JSON.
|
|
136
|
+
recs = agent_type_payload.get("recommendations")
|
|
137
|
+
if recs and (recs.get("by_tool") or recs.get("by_system_prompt")):
|
|
138
|
+
entry["recommendations"] = recs
|
|
139
|
+
agents_data[agent_label] = entry
|
|
131
140
|
|
|
132
141
|
return {
|
|
133
142
|
"unique_tasks": int(stats.get("total_traces", 0)),
|
|
@@ -234,6 +243,26 @@ body{font-family:'Inter',-apple-system,BlinkMacSystemFont,sans-serif;background:
|
|
|
234
243
|
.chat-message-content{font-size:14px;color:#1E293B;white-space:pre-wrap;word-break:break-word;line-height:1.6}
|
|
235
244
|
.chat-message-tools{margin-top:10px;padding-top:10px;border-top:1px dashed #CBD5E1}
|
|
236
245
|
.expand-indicator{display:block;margin-top:8px;color:#6366F1;font-weight:800;font-size:13px}
|
|
246
|
+
.recs-block{margin-top:24px}
|
|
247
|
+
.recs-subblock{margin-top:14px}
|
|
248
|
+
.recs-subblock-title{font-size:14px;font-weight:800;color:#4338CA;text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
|
|
249
|
+
.rec-card{background:#fff;border:1px solid #D8E1EE;border-radius:12px;padding:14px 16px;margin-bottom:12px;box-shadow:0 1px 3px rgba(15,23,42,.04)}
|
|
250
|
+
.rec-card-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px}
|
|
251
|
+
.rec-target-pill{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;padding:2px 8px;border-radius:999px;background:#EEF2FF;color:#4338CA}
|
|
252
|
+
.rec-target-pill.system{background:#DBEAFE;color:#1D4ED8}
|
|
253
|
+
.rec-target-pill.tool{background:#DCFCE7;color:#166534}
|
|
254
|
+
.rec-target-pill.param{background:#FEF3C7;color:#92400E}
|
|
255
|
+
.rec-scope{font-size:13px;color:#334155;font-weight:600}
|
|
256
|
+
.rec-importance{margin-left:auto;font-size:12px;font-weight:700;color:#6366F1}
|
|
257
|
+
.rec-count{font-size:12px;color:#64748B}
|
|
258
|
+
.rec-rationale{font-size:13px;color:#475569;line-height:1.6;margin-bottom:10px}
|
|
259
|
+
.rec-diff{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12.5px;background:#0F172A;color:#E2E8F0;padding:12px 14px;border-radius:10px;overflow:auto;max-height:260px;white-space:pre;line-height:1.5}
|
|
260
|
+
.rec-diff .rec-line-add{color:#86EFAC}
|
|
261
|
+
.rec-diff .rec-line-del{color:#FCA5A5}
|
|
262
|
+
.rec-diff .rec-line-hunk{color:#93C5FD}
|
|
263
|
+
.rec-tool-header{display:flex;align-items:baseline;gap:12px;margin:14px 0 6px}
|
|
264
|
+
.rec-tool-name{font-size:15px;font-weight:800;color:#0F172A}
|
|
265
|
+
.rec-tool-count{font-size:12px;color:#64748B}
|
|
237
266
|
@media(max-width:768px){.container{padding:16px}.dashboard-header{padding:24px}.agent-metrics-grid{grid-template-columns:repeat(2,1fr)}}
|
|
238
267
|
.info-tooltip{position:relative;display:inline-block}
|
|
239
268
|
.info-tooltip-trigger{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;margin-left:6px;border-radius:50%;background:#E2E8F0;color:#475569;font-size:11px;font-weight:700;cursor:help;vertical-align:middle}
|
|
@@ -567,7 +596,8 @@ function heatFg(val, min, max) {
|
|
|
567
596
|
<div class="agent-metric"><div class="agent-metric-label">#Traces</div><div class="agent-metric-value">${ad.unique_tasks}</div></div>
|
|
568
597
|
</div></div>
|
|
569
598
|
<div class="section-header" style="margin-top:0"><div class="section-title">Discovered Issues</div><div class="section-subtitle">Recurring problems identified in this agent's outputs <span class="info-tooltip"><span class="info-tooltip-trigger" tabindex="0" aria-label="Issue matching explanation">i</span><span class="info-tooltip-bubble">Each llm call can be matched to any number of discovered issues, or to no issues.</span></span></div></div>
|
|
570
|
-
${tableHtml}
|
|
599
|
+
${tableHtml}
|
|
600
|
+
${renderRecommendations(ad.recommendations)}`;
|
|
571
601
|
container.appendChild(sec);
|
|
572
602
|
}
|
|
573
603
|
})();
|
|
@@ -579,14 +609,98 @@ function toggleIssueExamples(id, rowEl){
|
|
|
579
609
|
const indicator=rowEl.querySelector('.expand-indicator');
|
|
580
610
|
if(indicator) indicator.textContent=isHidden?'▾ Examples':'▸ Examples';
|
|
581
611
|
}
|
|
612
|
+
|
|
613
|
+
// ─── SPARC recommendations renderer ─────────────────────────────────────
|
|
614
|
+
function escHtml(s){return esc(s||'');}
|
|
615
|
+
function renderDiff(raw){
|
|
616
|
+
if(!raw) return '';
|
|
617
|
+
const lines=String(raw).split('\n');
|
|
618
|
+
const out=lines.map(line=>{
|
|
619
|
+
const safe=escHtml(line);
|
|
620
|
+
if(line.startsWith('+++')||line.startsWith('---')) return `<span class="rec-line-hunk">${safe}</span>`;
|
|
621
|
+
if(line.startsWith('@@')) return `<span class="rec-line-hunk">${safe}</span>`;
|
|
622
|
+
if(line.startsWith('+')) return `<span class="rec-line-add">${safe}</span>`;
|
|
623
|
+
if(line.startsWith('-')) return `<span class="rec-line-del">${safe}</span>`;
|
|
624
|
+
return safe;
|
|
625
|
+
});
|
|
626
|
+
return `<div class="rec-diff">${out.join('\n')}</div>`;
|
|
627
|
+
}
|
|
628
|
+
function targetPillClass(target){
|
|
629
|
+
if(target==='system_prompt') return 'system';
|
|
630
|
+
if(target==='tool_description') return 'tool';
|
|
631
|
+
if(target==='parameter_description'||target==='parameter_examples') return 'param';
|
|
632
|
+
return '';
|
|
633
|
+
}
|
|
634
|
+
function renderRecCard(rec, scope){
|
|
635
|
+
const imp=(typeof rec.importance_mean==='number')?rec.importance_mean:(rec.importance||0);
|
|
636
|
+
const count=rec.count||1;
|
|
637
|
+
const cls=targetPillClass(rec.target||'system_prompt');
|
|
638
|
+
const rationale=rec.rationale?`<div class="rec-rationale">${escHtml(rec.rationale)}</div>`:'';
|
|
639
|
+
const scopeLabel=scope?`<span class="rec-scope">${escHtml(scope)}</span>`:'';
|
|
640
|
+
return `<div class="rec-card">
|
|
641
|
+
<div class="rec-card-head">
|
|
642
|
+
<span class="rec-target-pill ${cls}">${escHtml(rec.target||'')}</span>
|
|
643
|
+
${scopeLabel}
|
|
644
|
+
<span class="rec-count">×${count}</span>
|
|
645
|
+
<span class="rec-importance">importance ${imp.toFixed(2)}</span>
|
|
646
|
+
</div>
|
|
647
|
+
${rationale}
|
|
648
|
+
${renderDiff(rec.diff)}
|
|
649
|
+
</div>`;
|
|
650
|
+
}
|
|
651
|
+
function renderRecommendations(recs){
|
|
652
|
+
if(!recs) return '';
|
|
653
|
+
const byTool=recs.by_tool||{};
|
|
654
|
+
const bySystem=recs.by_system_prompt||[];
|
|
655
|
+
const toolEntries=Object.entries(byTool);
|
|
656
|
+
if(!toolEntries.length && !bySystem.length) return '';
|
|
657
|
+
|
|
658
|
+
const header=`<div class="section-header" style="margin-top:28px">
|
|
659
|
+
<div class="section-title">SPARC Recommendations</div>
|
|
660
|
+
<div class="section-subtitle">Unified-diff suggestions derived from the call judgments (${recs.total||0} recommendation occurrence(s))</div>
|
|
661
|
+
</div>`;
|
|
662
|
+
|
|
663
|
+
let body='<div class="recs-block">';
|
|
664
|
+
|
|
665
|
+
if(bySystem.length){
|
|
666
|
+
body+='<div class="recs-subblock"><div class="recs-subblock-title">System Prompt</div>';
|
|
667
|
+
for(const r of bySystem){ body+=renderRecCard(r, null); }
|
|
668
|
+
body+='</div>';
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if(toolEntries.length){
|
|
672
|
+
body+='<div class="recs-subblock"><div class="recs-subblock-title">Per-Tool Suggestions</div>';
|
|
673
|
+
// Sort tools by total recommendations descending
|
|
674
|
+
toolEntries.sort((a,b)=>{
|
|
675
|
+
const ca=a[1].reduce((s,x)=>s+(x.count||1),0);
|
|
676
|
+
const cb=b[1].reduce((s,x)=>s+(x.count||1),0);
|
|
677
|
+
return cb-ca || a[0].localeCompare(b[0]);
|
|
678
|
+
});
|
|
679
|
+
for(const [toolName, items] of toolEntries){
|
|
680
|
+
const total=items.reduce((s,x)=>s+(x.count||1),0);
|
|
681
|
+
body+=`<div class="rec-tool-header">
|
|
682
|
+
<span class="rec-tool-name">${escHtml(toolName)}</span>
|
|
683
|
+
<span class="rec-tool-count">${items.length} distinct suggestion(s), ${total} occurrence(s)</span>
|
|
684
|
+
</div>`;
|
|
685
|
+
for(const r of items){
|
|
686
|
+
const scope=r.parameter_name?`${toolName} · ${r.parameter_name}`:toolName;
|
|
687
|
+
body+=renderRecCard(r, scope);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
body+='</div>';
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
body+='</div>';
|
|
694
|
+
return header+body;
|
|
695
|
+
}
|
|
582
696
|
</script>
|
|
583
697
|
</body>
|
|
584
698
|
</html>"""
|
|
585
699
|
|
|
586
700
|
|
|
587
|
-
def generate_html(json_path, output_path=None,
|
|
701
|
+
def generate_html(json_path, output_path=None, num_examples=3):
|
|
588
702
|
"""Generate static HTML dashboard from a clear_results.json file."""
|
|
589
|
-
data = load_json_data(json_path,
|
|
703
|
+
data = load_json_data(json_path, num_examples=num_examples)
|
|
590
704
|
logging.info(f"Generating Static HTML report from {json_path}")
|
|
591
705
|
|
|
592
706
|
input_path = Path(json_path)
|
|
@@ -9,10 +9,12 @@ Creates a structured JSON with all issues mapped to spans, including:
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import ast
|
|
12
|
+
import hashlib
|
|
12
13
|
import json
|
|
13
14
|
import logging
|
|
14
15
|
import math
|
|
15
16
|
import os
|
|
17
|
+
import re
|
|
16
18
|
from collections import defaultdict
|
|
17
19
|
from datetime import datetime
|
|
18
20
|
from pathlib import Path
|
|
@@ -28,6 +30,9 @@ logger = logging.getLogger(__name__)
|
|
|
28
30
|
# Must match the subdir name used in run_clear_step_analysis.run_analysis_for_agent.
|
|
29
31
|
TOOL_CALLS_SUBDIR = "tool_calls"
|
|
30
32
|
|
|
33
|
+
# SPARC recommendation targets that scope to a specific tool (vs. system prompt)
|
|
34
|
+
_TOOL_SCOPED_TARGETS = ("tool_description", "parameter_description", "parameter_examples")
|
|
35
|
+
|
|
31
36
|
|
|
32
37
|
def build_workflow_graph(traj_df):
|
|
33
38
|
"""Build workflow graph data from trajectory dataframe."""
|
|
@@ -142,6 +147,126 @@ def _parse_meta_data(meta_data_str):
|
|
|
142
147
|
return {}
|
|
143
148
|
|
|
144
149
|
|
|
150
|
+
_WHITESPACE_RE = re.compile(r"\s+")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _normalize_diff(diff: str) -> str:
|
|
154
|
+
"""Whitespace-normalize a unified diff for stable hashing/dedup."""
|
|
155
|
+
if not isinstance(diff, str):
|
|
156
|
+
return ""
|
|
157
|
+
return _WHITESPACE_RE.sub(" ", diff).strip()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _diff_hash(diff: str) -> str:
|
|
161
|
+
"""Stable short hash of the normalized diff text for grouping."""
|
|
162
|
+
return hashlib.sha1(_normalize_diff(diff).encode("utf-8")).hexdigest()[:16]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _parse_recs(cell: Any) -> List[Dict[str, Any]]:
|
|
166
|
+
"""Parse a ``sparc_recommendations`` cell into a list of rec dicts.
|
|
167
|
+
|
|
168
|
+
Accepts JSON arrays (the canonical form), already-parsed lists, or
|
|
169
|
+
``"[]"``/empty/NaN. Anything else returns an empty list."""
|
|
170
|
+
if cell is None or (isinstance(cell, float) and pd.isna(cell)):
|
|
171
|
+
return []
|
|
172
|
+
if isinstance(cell, list):
|
|
173
|
+
return [r for r in cell if isinstance(r, dict)]
|
|
174
|
+
if isinstance(cell, str) and cell.strip():
|
|
175
|
+
try:
|
|
176
|
+
parsed = json.loads(cell)
|
|
177
|
+
except (json.JSONDecodeError, TypeError):
|
|
178
|
+
return []
|
|
179
|
+
if isinstance(parsed, list):
|
|
180
|
+
return [r for r in parsed if isinstance(r, dict)]
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _aggregate_recommendations(results_df: pd.DataFrame) -> Optional[Dict[str, Any]]:
|
|
185
|
+
"""Group per-row SPARC recommendations into ``by_tool`` and
|
|
186
|
+
``by_system_prompt`` buckets.
|
|
187
|
+
|
|
188
|
+
Returns ``None`` if the DataFrame has no ``sparc_recommendations`` column
|
|
189
|
+
or if no row produced any recommendation (runtime mode / static-only)."""
|
|
190
|
+
if "sparc_recommendations" not in results_df.columns:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
# by_tool: {tool_name: {hash: accumulator}}
|
|
194
|
+
by_tool: Dict[str, Dict[str, Dict[str, Any]]] = defaultdict(dict)
|
|
195
|
+
# by_system_prompt: {hash: accumulator}
|
|
196
|
+
by_system: Dict[str, Dict[str, Any]] = {}
|
|
197
|
+
total = 0
|
|
198
|
+
|
|
199
|
+
for _, row in results_df.iterrows():
|
|
200
|
+
for rec in _parse_recs(row.get("sparc_recommendations")):
|
|
201
|
+
target = rec.get("target")
|
|
202
|
+
diff = rec.get("diff") or ""
|
|
203
|
+
if not target or not diff:
|
|
204
|
+
continue
|
|
205
|
+
try:
|
|
206
|
+
importance = max(0.0, min(1.0, float(rec.get("importance", 0.0))))
|
|
207
|
+
except (TypeError, ValueError):
|
|
208
|
+
importance = 0.0
|
|
209
|
+
h = _diff_hash(diff)
|
|
210
|
+
entry_template = {
|
|
211
|
+
"hash": h,
|
|
212
|
+
"target": target,
|
|
213
|
+
"diff": diff,
|
|
214
|
+
"rationale": rec.get("rationale", ""),
|
|
215
|
+
"tool_name": rec.get("tool_name"),
|
|
216
|
+
"parameter_name": rec.get("parameter_name"),
|
|
217
|
+
"count": 0,
|
|
218
|
+
"importance_sum": 0.0,
|
|
219
|
+
"importance_max": 0.0,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if target == "system_prompt":
|
|
223
|
+
bucket = by_system.setdefault(h, dict(entry_template))
|
|
224
|
+
elif target in _TOOL_SCOPED_TARGETS:
|
|
225
|
+
tool_name = rec.get("tool_name") or "<unknown>"
|
|
226
|
+
bucket = by_tool[tool_name].setdefault(h, dict(entry_template))
|
|
227
|
+
else:
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
bucket["count"] += 1
|
|
231
|
+
bucket["importance_sum"] += importance
|
|
232
|
+
if importance > bucket["importance_max"]:
|
|
233
|
+
bucket["importance_max"] = importance
|
|
234
|
+
total += 1
|
|
235
|
+
|
|
236
|
+
if total == 0:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
def _finalize(entries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
240
|
+
out = []
|
|
241
|
+
for e in entries:
|
|
242
|
+
mean = e["importance_sum"] / e["count"] if e["count"] else 0.0
|
|
243
|
+
out.append({
|
|
244
|
+
"hash": e["hash"],
|
|
245
|
+
"target": e["target"],
|
|
246
|
+
"diff": e["diff"],
|
|
247
|
+
"rationale": e["rationale"],
|
|
248
|
+
"tool_name": e["tool_name"],
|
|
249
|
+
"parameter_name": e["parameter_name"],
|
|
250
|
+
"count": e["count"],
|
|
251
|
+
"importance_mean": round(mean, 4),
|
|
252
|
+
"importance_max": round(e["importance_max"], 4),
|
|
253
|
+
})
|
|
254
|
+
out.sort(key=lambda r: (-r["importance_mean"], -r["count"], r["target"]))
|
|
255
|
+
return out
|
|
256
|
+
|
|
257
|
+
by_tool_out: Dict[str, List[Dict[str, Any]]] = {
|
|
258
|
+
tool: _finalize(list(entries.values()))
|
|
259
|
+
for tool, entries in by_tool.items()
|
|
260
|
+
}
|
|
261
|
+
by_system_out = _finalize(list(by_system.values()))
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
"by_tool": by_tool_out,
|
|
265
|
+
"by_system_prompt": by_system_out,
|
|
266
|
+
"total": total,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
145
270
|
def _parse_issues_list(recurring_issues_str):
|
|
146
271
|
"""Parse recurring issues string into a list."""
|
|
147
272
|
if pd.isna(recurring_issues_str) or not recurring_issues_str:
|
|
@@ -315,6 +440,12 @@ def _process_results_dir(
|
|
|
315
440
|
|
|
316
441
|
result["no_issues"] = no_issue_spans
|
|
317
442
|
|
|
443
|
+
# Aggregate SPARC actionable recommendations (tool-call results only carry
|
|
444
|
+
# the column; reasoning_eval CSVs don't, so this is a no-op there).
|
|
445
|
+
recs = _aggregate_recommendations(results_df)
|
|
446
|
+
if recs is not None:
|
|
447
|
+
result["recommendations"] = recs
|
|
448
|
+
|
|
318
449
|
# Attach scores and weighted severity for pass/fail calculation
|
|
319
450
|
result["_scores"] = [_safe_float(row.get('score', 0)) for _, row in results_df.iterrows()]
|
|
320
451
|
result["_weighted_severity"] = sum(
|
|
@@ -721,6 +721,9 @@ def main():
|
|
|
721
721
|
config.get('run_name')
|
|
722
722
|
)
|
|
723
723
|
|
|
724
|
+
# Update config with resolved run_name so downstream components use the same name
|
|
725
|
+
config['run_name'] = run_name
|
|
726
|
+
|
|
724
727
|
# Convert paths
|
|
725
728
|
traj_input_dir = Path(config['data_dir'])
|
|
726
729
|
rubric_dir = Path(config['rubric_dir']) if config.get('rubric_dir') else None
|
|
@@ -95,6 +95,7 @@ class TrajectoryEvaluator(ABC):
|
|
|
95
95
|
overwrite: bool = False,
|
|
96
96
|
max_workers: int = 7,
|
|
97
97
|
max_files: int | None = None,
|
|
98
|
+
checkpoint_every: int = 0,
|
|
98
99
|
full_trace_evaluation_criteria: dict | None = None,
|
|
99
100
|
):
|
|
100
101
|
"""
|
|
@@ -110,6 +111,7 @@ class TrajectoryEvaluator(ABC):
|
|
|
110
111
|
max_files: Maximum number of files to process (for testing)
|
|
111
112
|
full_trace_evaluation_criteria: Custom flat evaluation criteria dict
|
|
112
113
|
that replaces all default dimensions when set
|
|
114
|
+
checkpoint_every: Save intermediate results every N items (0 = disabled)
|
|
113
115
|
"""
|
|
114
116
|
self.inference_config = inference_config
|
|
115
117
|
self.traj_input_dir = Path(traj_input_dir)
|
|
@@ -118,6 +120,7 @@ class TrajectoryEvaluator(ABC):
|
|
|
118
120
|
self.overwrite = overwrite
|
|
119
121
|
self.max_workers = max_workers
|
|
120
122
|
self.max_files = max_files
|
|
123
|
+
self.checkpoint_every = checkpoint_every
|
|
121
124
|
self.full_trace_evaluation_criteria = full_trace_evaluation_criteria
|
|
122
125
|
|
|
123
126
|
# Create results directory: output_dir/evaluation_type[/model_subdir]
|
|
@@ -543,14 +546,20 @@ class TrajectoryEvaluator(ABC):
|
|
|
543
546
|
for entry in entries
|
|
544
547
|
]
|
|
545
548
|
|
|
549
|
+
# Caching params
|
|
550
|
+
item_ids = [entry["traj_name"] for entry in entries] if self.checkpoint_every else None
|
|
551
|
+
cache_path = str(self.results_dir / "run_cache.jsonl") if self.checkpoint_every else None
|
|
552
|
+
|
|
546
553
|
# Use pipeline's parallel execution with progress bar (always async)
|
|
547
554
|
start = time.time()
|
|
548
555
|
parallel_results = run_parallel(
|
|
549
556
|
func=self.evaluate_single,
|
|
550
557
|
inputs=inputs,
|
|
551
|
-
use_async=True,
|
|
552
558
|
max_workers=self.max_workers,
|
|
553
|
-
progress_desc=f"Evaluating trajectories ({self.__class__.__name__})"
|
|
559
|
+
progress_desc=f"Evaluating trajectories ({self.__class__.__name__})",
|
|
560
|
+
checkpoint_every=self.checkpoint_every,
|
|
561
|
+
checkpoint_path=cache_path,
|
|
562
|
+
item_ids=item_ids,
|
|
554
563
|
)
|
|
555
564
|
elapsed = time.time() - start
|
|
556
565
|
|
{clear_eval-2.0.3 → clear_eval-2.0.4}/src/clear_eval/agentic/pipeline/run_clear_agentic_eval.py
RENAMED
|
@@ -337,6 +337,9 @@ def main():
|
|
|
337
337
|
config.get('run_name')
|
|
338
338
|
)
|
|
339
339
|
|
|
340
|
+
# Update config with resolved run_name so downstream components use the same name
|
|
341
|
+
config['run_name'] = run_name
|
|
342
|
+
|
|
340
343
|
# Extract parameters
|
|
341
344
|
data_dir = Path(config['data_dir'])
|
|
342
345
|
from_raw_traces = config.get('from_raw_traces')
|
|
@@ -11,4 +11,14 @@ SHORTCOMING_LIST_FILE_PREFIX = "shortcoming_list"
|
|
|
11
11
|
MAPPING_FILE_PREFIX = "mapping_results"
|
|
12
12
|
MAPPING_NO_ISSUES = "NO_ISSUES"
|
|
13
13
|
ANALYSIS_SKIPPED = "Analysis Skipped"
|
|
14
|
-
DEFAULT_ISSUES_FORMAT_MODE = "shortcomings"
|
|
14
|
+
DEFAULT_ISSUES_FORMAT_MODE = "shortcomings"
|
|
15
|
+
|
|
16
|
+
# SPARC-specific per-row columns produced by the tool-call use case. They are
|
|
17
|
+
# carried through analysis CSVs unchanged so downstream aggregation
|
|
18
|
+
# (build_json_results) and dashboards can read per-row judgments without
|
|
19
|
+
# re-running SPARC.
|
|
20
|
+
_SPARC_COLUMNS_PASSTHROUGH = (
|
|
21
|
+
"sparc_decision",
|
|
22
|
+
"sparc_score_1_to_5",
|
|
23
|
+
"sparc_recommendations",
|
|
24
|
+
)
|
|
@@ -5,13 +5,15 @@ import numpy as np
|
|
|
5
5
|
from clear_eval.pipeline.caching_utils import save_dataframe_to_cache
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from clear_eval.pipeline.constants import IDENTIFIED_SHORTCOMING_COL, EVALUATION_TEXT_COL, EVALUATION_SUMMARY_COL, \
|
|
8
|
-
SHORTCOMING_PREFIX, SCORE_COL, ERROR_COL, MAPPING_NO_ISSUES, ANALYSIS_SKIPPED, DEFAULT_ISSUES_FORMAT_MODE
|
|
8
|
+
SHORTCOMING_PREFIX, SCORE_COL, ERROR_COL, MAPPING_NO_ISSUES, ANALYSIS_SKIPPED, DEFAULT_ISSUES_FORMAT_MODE, \
|
|
9
|
+
_SPARC_COLUMNS_PASSTHROUGH
|
|
9
10
|
from clear_eval.pipeline.propmts import get_summarization_prompt, \
|
|
10
11
|
get_shortcomings_clustering_prompt, get_issues_mapping_system_prompt, get_issues_mapping_human_prompt, get_synthesis_prompt, get_synthesis_prompt_cont
|
|
11
12
|
import re
|
|
12
13
|
from clear_eval.pipeline.inference_utils.llm_client import run_parallel, run_async, get_llm_client
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
16
|
+
|
|
15
17
|
def is_missing_or_error(eval_text):
|
|
16
18
|
if pd.isna(eval_text) or not eval_text or not eval_text.strip() or \
|
|
17
19
|
eval_text.startswith(ANALYSIS_SKIPPED) or eval_text.startswith("Error:"):
|
|
@@ -32,6 +34,16 @@ async def evaluate_row(row, config, llm, generate_evaluation_model_prompt_func):
|
|
|
32
34
|
return parse_evaluation_response(content)
|
|
33
35
|
|
|
34
36
|
|
|
37
|
+
def _get_checkpoint_cache_path(config, stage_suffix):
|
|
38
|
+
"""Build a cache file path for run_parallel checkpointing."""
|
|
39
|
+
checkpoint_path = config.get('checkpoint_path', '')
|
|
40
|
+
if checkpoint_path:
|
|
41
|
+
# Derive cache path from the main checkpoint CSV path
|
|
42
|
+
return checkpoint_path.replace('.csv', f'_cache_{stage_suffix}.jsonl')
|
|
43
|
+
output_dir = config.get('output_dir', '.')
|
|
44
|
+
return os.path.join(output_dir, f"cache_{stage_suffix}.jsonl")
|
|
45
|
+
|
|
46
|
+
|
|
35
47
|
def evaluate_single_records(df, llm, config, get_evaluation_prompt_func, score_col=SCORE_COL):
|
|
36
48
|
"""Evaluates predictions and adds scores."""
|
|
37
49
|
logger.info(f"\n--- Evaluating Predictions ---")
|
|
@@ -52,13 +64,19 @@ def evaluate_single_records(df, llm, config, get_evaluation_prompt_func, score_c
|
|
|
52
64
|
for idx, row in df.iterrows():
|
|
53
65
|
inputs.append((row, config, llm, get_evaluation_prompt_func))
|
|
54
66
|
|
|
67
|
+
checkpoint_every = config.get('checkpoint_every', 0)
|
|
68
|
+
item_ids = [str(row[config['qid_column']]) for _, row in df.iterrows()] if checkpoint_every else None
|
|
69
|
+
cache_path = _get_checkpoint_cache_path(config, "eval") if checkpoint_every else None
|
|
70
|
+
|
|
55
71
|
results = run_parallel(
|
|
56
72
|
func=evaluate_row,
|
|
57
73
|
inputs=inputs,
|
|
58
|
-
use_async=True,
|
|
59
74
|
max_workers=config['max_workers'],
|
|
60
75
|
error_prefix="Evaluation: ",
|
|
61
|
-
progress_desc="Evaluating predictions"
|
|
76
|
+
progress_desc="Evaluating predictions",
|
|
77
|
+
checkpoint_every=checkpoint_every,
|
|
78
|
+
checkpoint_path=cache_path,
|
|
79
|
+
item_ids=item_ids,
|
|
62
80
|
)
|
|
63
81
|
|
|
64
82
|
for i, result in enumerate(results):
|
|
@@ -83,19 +101,25 @@ def produce_summaries_per_record(df, llm, config):
|
|
|
83
101
|
|
|
84
102
|
logger.info(f"Generating evaluation summaries for {len(inputs)} items ...")
|
|
85
103
|
|
|
104
|
+
df[EVALUATION_SUMMARY_COL] = ""
|
|
105
|
+
if ERROR_COL not in df.columns:
|
|
106
|
+
df[ERROR_COL] = [[] for _ in range(len(df))]
|
|
107
|
+
|
|
108
|
+
checkpoint_every = config.get('checkpoint_every', 0)
|
|
109
|
+
item_ids = [str(row[config['qid_column']]) for _, row in df.iterrows()] if checkpoint_every else None
|
|
110
|
+
cache_path = _get_checkpoint_cache_path(config, "summary") if checkpoint_every else None
|
|
111
|
+
|
|
86
112
|
results = run_parallel(
|
|
87
113
|
func=generate_evaluation_summary,
|
|
88
114
|
inputs=inputs,
|
|
89
|
-
use_async=True,
|
|
90
115
|
max_workers=config['max_workers'],
|
|
91
116
|
error_prefix="Summary: ",
|
|
92
|
-
progress_desc="Generating evaluation summaries"
|
|
117
|
+
progress_desc="Generating evaluation summaries",
|
|
118
|
+
checkpoint_every=checkpoint_every,
|
|
119
|
+
checkpoint_path=cache_path,
|
|
120
|
+
item_ids=item_ids,
|
|
93
121
|
)
|
|
94
122
|
|
|
95
|
-
df[EVALUATION_SUMMARY_COL] = ""
|
|
96
|
-
if ERROR_COL not in df.columns:
|
|
97
|
-
df[ERROR_COL] = [[] for _ in range(len(df))]
|
|
98
|
-
|
|
99
123
|
for i, result in enumerate(results):
|
|
100
124
|
if result.is_success:
|
|
101
125
|
df.at[df.index[i], EVALUATION_SUMMARY_COL] = result.result
|
|
@@ -383,7 +407,9 @@ async def analyze_shortcoming_row(eval_text, question_id, shortcomings_list, llm
|
|
|
383
407
|
|
|
384
408
|
|
|
385
409
|
def map_shortcomings_to_records(df, llm, shortcomings_list,
|
|
386
|
-
use_full_text, qid_col, max_workers, high_score_threshold,
|
|
410
|
+
use_full_text, qid_col, max_workers, high_score_threshold,
|
|
411
|
+
score_col = SCORE_COL, format_mode=DEFAULT_ISSUES_FORMAT_MODE,
|
|
412
|
+
checkpoint_every = 0, cache_path = None):
|
|
387
413
|
"""Analyzes evaluation text for the dynamically generated shortcomings."""
|
|
388
414
|
logger.info(f"\n--- Analyzing Shortcomings based on Synthesized List ---")
|
|
389
415
|
df[IDENTIFIED_SHORTCOMING_COL] = ""
|
|
@@ -420,13 +446,17 @@ def map_shortcomings_to_records(df, llm, shortcomings_list,
|
|
|
420
446
|
inputs.append((str(row[evaluation_text_col]), row.get(qid_col, f"row_{idx}"), shortcomings_list, llm, system_prompt, format_mode))
|
|
421
447
|
logger.info(f"Mapping {n_records_to_map}/{len(df)} records to {len(shortcomings_list)} discovered shortcomings.")
|
|
422
448
|
|
|
449
|
+
item_ids = [row.get(qid_col, f"row_{idx}") for idx, row in df.iterrows()] if checkpoint_every else None
|
|
450
|
+
|
|
423
451
|
results = run_parallel(
|
|
424
452
|
func=analyze_shortcoming_row,
|
|
425
453
|
inputs=inputs,
|
|
426
|
-
use_async=True,
|
|
427
454
|
max_workers=max_workers,
|
|
428
455
|
error_prefix="Mapping: ",
|
|
429
|
-
progress_desc="Analyzing shortcomings"
|
|
456
|
+
progress_desc="Analyzing shortcomings",
|
|
457
|
+
checkpoint_every = checkpoint_every,
|
|
458
|
+
checkpoint_path = cache_path,
|
|
459
|
+
item_ids = item_ids,
|
|
430
460
|
)
|
|
431
461
|
|
|
432
462
|
for i, result in enumerate(results):
|
|
@@ -509,13 +539,19 @@ def generate_model_predictions(df, llm, config):
|
|
|
509
539
|
for i, row in df.iterrows():
|
|
510
540
|
inputs.append((llm, row[config['model_input_column']], row[config['qid_column']]))
|
|
511
541
|
|
|
542
|
+
checkpoint_every = config.get('checkpoint_every', 0)
|
|
543
|
+
item_ids = [str(row[config['qid_column']]) for _, row in df.iterrows()] if checkpoint_every else None
|
|
544
|
+
cache_path = _get_checkpoint_cache_path(config, "gen") if checkpoint_every else None
|
|
545
|
+
|
|
512
546
|
results = run_parallel(
|
|
513
547
|
func=predict_row,
|
|
514
548
|
inputs=inputs,
|
|
515
|
-
use_async=True,
|
|
516
549
|
max_workers=config["max_workers"],
|
|
517
550
|
error_prefix="Prediction: ",
|
|
518
|
-
progress_desc="Generating predictions"
|
|
551
|
+
progress_desc="Generating predictions",
|
|
552
|
+
checkpoint_every=checkpoint_every,
|
|
553
|
+
checkpoint_path=cache_path,
|
|
554
|
+
item_ids=item_ids,
|
|
519
555
|
)
|
|
520
556
|
|
|
521
557
|
for i, result in enumerate(results):
|
|
@@ -606,10 +642,17 @@ def convert_results_to_ui_input(df, config, task_data):
|
|
|
606
642
|
df.loc[:, "recurring_issues_str"] = df.apply(lambda r: get_recurring_issues_list(r), axis=1)
|
|
607
643
|
custom_output_df["recurring_issues_str"] = df["recurring_issues_str"]
|
|
608
644
|
|
|
645
|
+
# Carry SPARC per-row columns through unchanged. Aggregation happens
|
|
646
|
+
# in build_json_results._process_results_dir, which reads these.
|
|
647
|
+
for col in _SPARC_COLUMNS_PASSTHROUGH:
|
|
648
|
+
if col in df.columns:
|
|
649
|
+
custom_output_df[col] = df[col]
|
|
650
|
+
|
|
609
651
|
required_cols =[config.get(r, r) for r in required_input_fields] + config.get("input_columns", []) + \
|
|
610
652
|
["question_id", 'model_input', 'response',
|
|
611
653
|
'score', 'evaluation_text', 'evaluation_summary',
|
|
612
|
-
'recurring_issues', 'recurring_issues_str', 'ground_truth', 'error']
|
|
654
|
+
'recurring_issues', 'recurring_issues_str', 'ground_truth', 'error'] + \
|
|
655
|
+
[c for c in _SPARC_COLUMNS_PASSTHROUGH if c in df.columns]
|
|
613
656
|
required_cols = list(dict.fromkeys(required_cols))
|
|
614
657
|
|
|
615
658
|
for col in required_cols:
|
|
@@ -16,10 +16,11 @@ from clear_eval.pipeline.constants import (GENERATION_FILE_PREFIX, SHORTCOMING_L
|
|
|
16
16
|
from clear_eval.pipeline.caching_utils import load_dataframe_from_cache, save_dataframe_to_cache, save_json_to_cache, \
|
|
17
17
|
ensure_dir, \
|
|
18
18
|
load_json_from_cache, resolve_data_path
|
|
19
|
-
from clear_eval.pipeline.eval_utils import map_shortcomings_to_records, get_model_name_for_file,
|
|
19
|
+
from clear_eval.pipeline.eval_utils import map_shortcomings_to_records, get_model_name_for_file, \
|
|
20
|
+
convert_results_to_ui_input, \
|
|
20
21
|
load_inputs, synthesize_shortcomings_from_df, \
|
|
21
22
|
remove_duplicates_shortcomings, run_predictions_generation_save_results, produce_summaries_per_record, \
|
|
22
|
-
generate_model_predictions
|
|
23
|
+
generate_model_predictions, _get_checkpoint_cache_path
|
|
23
24
|
from clear_eval.pipeline.inference_utils.llm_client import get_llm_client
|
|
24
25
|
from clear_eval.pipeline.config_loader import load_yaml
|
|
25
26
|
|
|
@@ -101,6 +102,7 @@ def convert_to_ui_format(mapped_data_df, output_dir, config, file_name_info):
|
|
|
101
102
|
logger.info(f"Custom formatted analysis results saved to {output_path}")
|
|
102
103
|
save_ui_input_results(output_df, output_path, config)
|
|
103
104
|
|
|
105
|
+
|
|
104
106
|
def save_ui_input_results(output_df, output_path, config):
|
|
105
107
|
parquet_bytes = get_parquet_bytes(output_df)
|
|
106
108
|
json_bytes = json.dumps(config, indent=2).encode()
|
|
@@ -223,9 +225,13 @@ def resolve_issues_and_map(df, config, eval_llm, resume_enabled, checkpoint_path
|
|
|
223
225
|
use_full_text = config['use_full_text_for_analysis']
|
|
224
226
|
qid_col = config['qid_column']
|
|
225
227
|
max_workers = config['max_workers']
|
|
228
|
+
checkpoint_every = config.get("checkpoint_every", 0)
|
|
229
|
+
cache_path =_get_checkpoint_cache_path(config, "map") if checkpoint_every else None
|
|
230
|
+
|
|
226
231
|
high_score_threshold = config.get("high_score_threshold", 1)
|
|
227
232
|
df = map_shortcomings_to_records(df, eval_llm, shortcoming_list, use_full_text,
|
|
228
|
-
qid_col, max_workers, high_score_threshold, format_mode=format_mode
|
|
233
|
+
qid_col, max_workers, high_score_threshold, format_mode=format_mode,
|
|
234
|
+
checkpoint_every = checkpoint_every, cache_path=cache_path)
|
|
229
235
|
save_dataframe_to_cache(df, checkpoint_path)
|
|
230
236
|
return df
|
|
231
237
|
|
|
@@ -240,6 +246,7 @@ def run_aggregation_from_df(config, df, file_name_info, eval_llm=None):
|
|
|
240
246
|
resume_enabled = config['resume_enabled']
|
|
241
247
|
format_mode = get_issues_format(config)
|
|
242
248
|
checkpoint_path = f"{output_dir}/{CHECKPOINT_FILE_PREFIX}_{file_name_info}.csv"
|
|
249
|
+
config['checkpoint_path'] = checkpoint_path
|
|
243
250
|
shortcoming_list_output_path = f"{output_dir}/{SHORTCOMING_LIST_FILE_PREFIX}_{file_name_info}.json"
|
|
244
251
|
deduplicated_shortcomings_list_output_path = f"{output_dir}/{SHORTCOMING_LIST_FILE_PREFIX}_{file_name_info}_dedup.json"
|
|
245
252
|
zip_path = f"{output_dir}/analysis_results_{file_name_info}.zip"
|
|
@@ -270,6 +277,7 @@ def run_eval_pipeline(config):
|
|
|
270
277
|
|
|
271
278
|
generate_issues = config.get("generate_issues", True)
|
|
272
279
|
checkpoint_path = f"{output_dir}/{CHECKPOINT_FILE_PREFIX}_{run_info}.csv"
|
|
280
|
+
config['checkpoint_path'] = checkpoint_path
|
|
273
281
|
zip_path = f"{output_dir}/analysis_results_{run_info}.zip"
|
|
274
282
|
|
|
275
283
|
# If final output exists, nothing to do
|