Skip to content

Feat/clear mlflow traces pypi 2.0.2 - #67

Merged
ruivieira merged 4 commits into
eval-hub:mainfrom
suppathak:feat/clear-mlflow-traces-pypi-2.0.2
Jul 14, 2026
Merged

ruivieira merged 4 commits into
eval-hub:mainfrom
suppathak:feat/clear-mlflow-traces-pypi-2.0.2

Conversation

@suppathak

@suppathak suppathak commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Three improvements to the IBM CLEAR adapter:

  1. PyPI release — switches clear-eval from the 2.0.0-rc.2 Git ref
    to the stable 2.0.2 PyPI release.

  2. MLflow trace fetch — adds the ability to fetch input traces directly
    from an MLflow tracking server via parameters.mlflow_traces_experiment_name.
    Uses the native mlflow client (mlflow-skinny) since the eval-hub-sdk's
    TracesNamespace has a compatibility gap with MLflow 3.x
    (tracked in eval-hub/eval-hub-sdk#134).

  3. Unified endpoint_url — model.url is now passed as endpoint_url
    in the CLEAR config for both litellm and endpoint backends,
    as suggested by @lilacheden.

Type

  • feat

Testing

  • Tested manually

Tested end-to-end with:

  • inference_backend: litellm + local traces → exit 0, score 0.73
  • inference_backend: endpoint + local traces → exit 0, score 0.75
  • MLflow trace fetch (5 traces from cluster) → exit 0, score 0.72

Breaking changes

mlflow-skinny>=2.0.0 is now a required dependency. Run
pip install -r requirements.txt to pick it up.

Summary by CodeRabbit

  • New Features
    • Added CLEAR trace ingestion supporting S3, MLflow, and local directories.
    • Added optional SPARC-style tool-call analysis via a new configuration switch.
  • Bug Fixes
    • Improved MLflow trace fetching so runs pull complete span details automatically.
    • Tightened and clarified endpoint URL resolution/validation for endpoint-based inference.
  • Documentation
    • Updated local run, MLflow trace steps, and output/dashboard guidance.
    • Documented SPARC enablement and installation requirements.
  • Chores
    • Updated adapter and CLEAR-related dependency versions and extras.

@ruivieira ruivieira added the community-pr External contributor PR label Jun 11, 2026
@williamcaban
williamcaban requested a review from ruivieira June 20, 2026 22:37

@ruivieira ruivieira left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks @suppathak. I've left a couple of questions (inlined).

Comment thread adapters/clear/main.py Outdated
"status": {"status_code": info.status, "description": ""},
"start_time_unix_nano": s.start_time_ns,
"end_time_unix_nano": s.end_time_ns,
"duration_ms": None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@suppathak is the duration available in the traces (e.g. (s.end_time_ns - s.start_time_ns) / 1_000_000) or is it expected to always be None?

Comment thread adapters/clear/requirements.txt Outdated
# EVALHUB_MLFLOW_BACKEND=upstream and: pip install mlflow-skinny
# Required for fetching full traces (with spans) from MLflow when
# parameters.mlflow_traces_experiment_name is set.
mlflow-skinny>=2.0.0

@ruivieira ruivieira Jul 3, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@suppathak is this still necessary after the SDK fixes (eval-hub/eval-hub-sdk#134)?

@ruivieira ruivieira self-assigned this Jul 3, 2026
@ruivieira ruivieira moved this from Todo to In Progress in EvalHub Planning Jul 3, 2026
- Replace mlflow-skinny-based _fetch_mlflow_traces_to_dir with sdk client
  (self.mlflow); drops mlflow-skinny dependency from requirements.txt
- Fix duration_ms: calculate from span start/end ns instead of None
- Update CLEAR to main_with_sparc branch with [tool-calls] extra
- Wire separate_tools parameter into agentic config (SPARC mode)
- Update meta/job.json and docs to document separate_tools option
@coderabbitai

coderabbitai Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2f3bfb40-202a-4c3a-a083-5fb28384a5e1

📥 Commits

Reviewing files that changed from the base of the PR and between b0bcac7 and 57fb163.

📒 Files selected for processing (1)
  • adapters/clear/main.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • adapters/clear/main.py

📝 Walkthrough

Walkthrough

Adds MLflow trace ingestion for CLEAR, updates validation and endpoint config handling, revises job parameters and dependency pins, and refreshes docs for trace sources, MLflow fetch steps, and SPARC tool-call analysis.

Changes

CLEAR adapter changes

Layer / File(s) Summary
MLflow trace fetch helpers and wiring
adapters/clear/main.py
Adds MLflow imports, updated install guidance, and helpers that normalize protobuf-style span attributes and export fetched MLflow traces as per-trace JSON files.
Job data-dir discovery and config validation
adapters/clear/main.py
Fetches MLflow traces into a temporary directory when configured, accepts MLflow trace sources during config validation, and changes endpoint URL resolution for agentic endpoint backends.
Job spec parameters and dependency updates
adapters/clear/meta/job.json, adapters/clear/requirements.txt
Updates job parameters to add separate_tools and mlflow_traces_experiment_name, removes data_dir, and switches the CLEAR dependency to the tool-calls-enabled package source.
Documentation for trace sources and SPARC
adapters/clear/examples/docs/03-local-run.md, adapters/clear/examples/docs/05-benchmarks-and-parameters.md
Rewrites local-run trace-source guidance, adds MLflow trace fetch instructions, clarifies output handling, and adds SPARC tool-call analysis documentation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested labels: kind/feat

Suggested reviewers: gnaulak-redhat, nbs-rh, julpayne

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main changes: CLEAR PyPI update and MLflow trace support.
Description check ✅ Passed The description covers what/why, type, testing, and breaking changes, with only the issue-closing placeholder left blank.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@suppathak

suppathak commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi @ruivieira, addressed both comments in the latest commit (b0bcac7):

  1. duration_ms — now calculated as (end_ns - start_ns) / 1_000_000 from the span's start/end nanosecond timestamps. Set to None only when either value is missing.

  2. mlflow-skinny — removed. The custom _fetch_mlflow_traces_to_dir now uses the eval-hub-sdk's MlflowClient (self.mlflow) directly. This also required a small fix to the sdk itself — get() was calling the 2.0 /info endpoint which returns no spans; I've opened a PR to fix that: fix(mlflow): get() fetches spans via 3.x API; materialize()  eval-hub-sdk#152

Please approve and merge if everything looks good. Thanks!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
adapters/clear/requirements.txt (1)

3-3: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Git-branch dependency contradicts the stated "stable PyPI release" goal and is unpinned to a commit.

PR objective #1 says clear-eval was switched from a git ref to "the stable 2.0.2 PyPI release," but this line still pins a mutable git branch (main_with_sparc), not a tag/commit SHA. This also conflicts with main.py's ImportError text ("pip install clear-eval==2.0.2") and 03-local-run.md's claim ("installed from PyPI"). A force-push to that branch would silently change the installed code on next rebuild.

♻️ Suggested fix
-clear_eval[tool-calls] @ git+https://github.com/IBM/clear.git@main_with_sparc
+clear_eval[tool-calls] @ git+https://github.com/IBM/clear.git@<pinned-commit-sha>

Align main.py's ImportError text and 03-local-run.md with whichever source is authoritative once SPARC support lands on PyPI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/clear/requirements.txt` at line 3, The dependency in
requirements.txt is still using a mutable git branch instead of the intended
stable PyPI release, so update the clear_eval[tool-calls] entry to the
authoritative 2.0.2 source and avoid any branch-based ref. Also make sure the
user-facing guidance in main.py (ImportError text) and 03-local-run.md matches
that same source of truth so they do not claim PyPI if the package is still
coming from git, or vice versa.
🧹 Nitpick comments (3)
adapters/clear/examples/docs/03-local-run.md (1)

20-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify trace-source precedence.

The table lists S3, MLflow, and local path as three parallel sources but doesn't state the precedence when more than one is configured (per main.py, S3-mounted /test_data//data wins over MLflow, which wins over parameters.data_dir). Worth a one-line note to avoid confusion when a job spec configures more than one.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/clear/examples/docs/03-local-run.md` around lines 20 - 27, Clarify
the trace-source selection order in the local run docs: the table in the docs
currently presents S3, MLflow, and local path as equal options, but the runtime
logic in main.py uses precedence when multiple sources are set. Update the docs
near the trace source table to state that S3-mounted /test_data or /data takes
priority over MLflow, and MLflow takes priority over parameters.data_dir, so
users understand which source is chosen when more than one is configured.
adapters/clear/main.py (2)

754-771: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unreachable validation branch.

ep is computed only inside if config.model.url:, and always falls back to config.model.url (truthy in this branch) via or. So not ep at line 760 can never be True — this inner raise is dead code; the only real "missing endpoint" case is already handled by the elif at line 767.

♻️ Suggested simplification
         if config.model.url:
             ep = (
                 config.parameters.get("endpoint_url")
                 or config.parameters.get("inference_url")
                 or config.model.url
             )
-            if agentic_config.get("inference_backend") == "endpoint" and not ep:
-                raise ValueError(
-                    "model.url or parameters.endpoint_url (or inference_url) is required "
-                    "when inference_backend is 'endpoint'"
-                )
             agentic_config["endpoint_url"] = ep
             agentic_config["inference_url"] = ep
         elif agentic_config.get("inference_backend") == "endpoint":
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/clear/main.py` around lines 754 - 771, The validation inside the
config model URL branch is unreachable because ep is always truthy there, so
remove the dead `if agentic_config.get("inference_backend") == "endpoint" and
not ep` check from the `config.model.url` path in `adapters/clear/main.py`. Keep
the existing `elif agentic_config.get("inference_backend") == "endpoint"` guard
as the only missing-endpoint validation, and leave the endpoint assignment logic
in the surrounding configuration block unchanged.

475-506: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Temp MLflow trace directory is never cleaned up.

tempfile.mkdtemp(prefix="clear_mlflow_traces_") (line 489) creates a temp dir that's never removed after the job (unlike CLEAR's own intermediate output dirs, cleaned in _cleanup_intermediate_files). In a long-lived adapter process handling many jobs, this leaks disk space.

♻️ Suggested cleanup
+import shutil as _shutil  # already imported as shutil elsewhere
             mlflow_traces_dir = tempfile.mkdtemp(prefix="clear_mlflow_traces_")
             _fetch_mlflow_traces_to_dir(self.mlflow, config.parameters, mlflow_traces_dir)
             data_dir = mlflow_traces_dir

Track mlflow_traces_dir and shutil.rmtree(mlflow_traces_dir, ignore_errors=True) in a finally block after the pipeline run completes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/clear/main.py` around lines 475 - 506, The temporary MLflow traces
directory created in the CLEAR adapter is never removed, so it can leak disk
space across jobs. Update the flow in main.py around the MLflow fetch path to
track the directory returned by tempfile.mkdtemp in mlflow_traces_dir and ensure
it is deleted with shutil.rmtree(ignore_errors=True) after the pipeline
finishes, ideally in a finally block that covers the run. Make sure the cleanup
only runs when _fetch_mlflow_traces_to_dir was used and does not affect
non-MLflow input paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@adapters/clear/main.py`:
- Line 52: The install guidance in the CLEAR adapter is stale and points users
to the wrong package variant. Update the remediation text in the
`adapters.clear.main` import/error message so it matches the dependency declared
in `requirements.txt`, including the `clear_eval[tool-calls]` extra and the
git-based source/branch rather than `clear-eval==2.0.2`. Keep the message
aligned with the actual install path users should follow when `main` raises the
ImportError.
- Around line 309-370: The trace fetching helper `_fetch_mlflow_traces_to_dir`
is using MLflow APIs and return shapes that don’t match `MlflowClient`. Update
it to call the client’s actual trace methods (`search_traces()` and
`get_trace()`, or the equivalent wrapper methods if this code intentionally
targets a custom client) instead of accessing `mlflow_client.traces`. Also
replace `trace.data.get("spans", [])` with the proper `TraceData` access pattern
such as `.spans` or `.to_dict()` so the span extraction in this function works
without `AttributeError`.

---

Duplicate comments:
In `@adapters/clear/requirements.txt`:
- Line 3: The dependency in requirements.txt is still using a mutable git branch
instead of the intended stable PyPI release, so update the
clear_eval[tool-calls] entry to the authoritative 2.0.2 source and avoid any
branch-based ref. Also make sure the user-facing guidance in main.py
(ImportError text) and 03-local-run.md matches that same source of truth so they
do not claim PyPI if the package is still coming from git, or vice versa.

---

Nitpick comments:
In `@adapters/clear/examples/docs/03-local-run.md`:
- Around line 20-27: Clarify the trace-source selection order in the local run
docs: the table in the docs currently presents S3, MLflow, and local path as
equal options, but the runtime logic in main.py uses precedence when multiple
sources are set. Update the docs near the trace source table to state that
S3-mounted /test_data or /data takes priority over MLflow, and MLflow takes
priority over parameters.data_dir, so users understand which source is chosen
when more than one is configured.

In `@adapters/clear/main.py`:
- Around line 754-771: The validation inside the config model URL branch is
unreachable because ep is always truthy there, so remove the dead `if
agentic_config.get("inference_backend") == "endpoint" and not ep` check from the
`config.model.url` path in `adapters/clear/main.py`. Keep the existing `elif
agentic_config.get("inference_backend") == "endpoint"` guard as the only
missing-endpoint validation, and leave the endpoint assignment logic in the
surrounding configuration block unchanged.
- Around line 475-506: The temporary MLflow traces directory created in the
CLEAR adapter is never removed, so it can leak disk space across jobs. Update
the flow in main.py around the MLflow fetch path to track the directory returned
by tempfile.mkdtemp in mlflow_traces_dir and ensure it is deleted with
shutil.rmtree(ignore_errors=True) after the pipeline finishes, ideally in a
finally block that covers the run. Make sure the cleanup only runs when
_fetch_mlflow_traces_to_dir was used and does not affect non-MLflow input paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 20a3d792-82eb-4d51-a670-0f5d33556cc9

📥 Commits

Reviewing files that changed from the base of the PR and between 2f8fb97 and b0bcac7.

📒 Files selected for processing (5)
  • adapters/clear/examples/docs/03-local-run.md
  • adapters/clear/examples/docs/05-benchmarks-and-parameters.md
  • adapters/clear/main.py
  • adapters/clear/meta/job.json
  • adapters/clear/requirements.txt

Comment thread adapters/clear/main.py Outdated
Comment thread adapters/clear/main.py Outdated
Comment on lines +309 to +370
def _fetch_mlflow_traces_to_dir(mlflow_client: Any, parameters: dict[str, Any], output_dir: str) -> None:
"""Fetch full traces (with spans) from MLflow using the eval-hub-sdk client.

Uses the sdk's TracesNamespace (search + get) to retrieve spans via the MLflow
3.x API, then converts each trace to the flat format CLEAR's preprocessor expects:
{trace_id, experiment_id, state, timestamp_ms, execution_time_ms, tags, spans[]}
"""
experiment_name = parameters.get("mlflow_traces_experiment_name")
experiment_id = parameters.get("mlflow_traces_experiment_id")
max_results = int(parameters.get("mlflow_traces_max_results", 100))
run_id = parameters.get("mlflow_traces_run_id")

if not experiment_name and not experiment_id:
raise ValueError(
"Set parameters.mlflow_traces_experiment_name or mlflow_traces_experiment_id"
)

if not experiment_id and experiment_name:
exp = mlflow_client.get_experiment_by_name(experiment_name)
if exp is None:
raise ValueError(f"MLflow experiment not found: {experiment_name!r}")
experiment_id = exp.experiment_id

filter_string = None
if run_id:
safe_run_id = run_id.strip().replace("'", "''")
filter_string = f"attribute.run_id = '{safe_run_id}'"

all_trace_summaries = []
page_token: str | None = None
while len(all_trace_summaries) < max_results:
page_size = min(100, max_results - len(all_trace_summaries))
traces, page_token = mlflow_client.traces.search(
experiment_ids=[experiment_id],
max_results=page_size,
filter_string=filter_string,
page_token=page_token,
)
if not traces:
break
all_trace_summaries.extend(traces)
if not page_token:
break

if not all_trace_summaries:
raise ValueError(
f"No traces found in MLflow experiment {experiment_name or experiment_id!r}"
)

Path(output_dir).mkdir(parents=True, exist_ok=True)
for summary in all_trace_summaries:
# Re-fetch with get() to include full span data (3.x API)
trace = mlflow_client.traces.get(
request_id=summary.info.request_id,
experiment_id=experiment_id,
)
info = trace.info
rid = info.request_id

spans = []
for s in trace.data.get("spans", []):
attrs = _proto_attrs_to_dict(s.get("attributes", []))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate self.mlflow's definition/type and confirm the traces client shape used
rg -n -B3 -A5 'self\.mlflow\s*=' --type=py
rg -n 'class FrameworkAdapter' -A 30 --type=py
rg -n 'mlflow-skinny' -R --type=txt -g '*requirements*.txt'

Repository: eval-hub/eval-hub-contrib

Length of output: 2002


🌐 Web query:

MLflow MlflowClient search_traces get_trace TraceData spans to_dict traces namespace documentation

💡 Result:

In MLflow, searching and managing traces is primarily handled through the MlflowClient or the fluent mlflow API [1]. Below is the synthesis of the requested components based on current documentation. Search Traces The MlflowClient.search_traces method allows for programmatic retrieval of traces [2][1]. It supports pagination, which is essential for handling large result sets [2]. Recent updates have introduced a locations parameter (replacing the deprecated experiment_ids) for specifying where to search [3][4]. Fluent API: mlflow.search_traces The fluent API returns a pandas DataFrame by default (if installed), which is suitable for analysis [2][5]. As of MLflow 2.21.1+, you can use the return_type parameter (e.g., return_type="list") to retrieve a list of Trace objects instead [6][2]. Accessing Trace Data Once you have a Trace object (e.g., via get_trace or search_traces), you can interact with its internal structure [7][5]. - TraceData: Each Trace object contains a data attribute, which is an instance of TraceData, holding the collection of spans [8][9]. - Spans: Spans represent individual operations within a trace [9]. You can access them via the trace.data.spans property [8][9]. - Conversion to Dictionary: Trace objects and their components provide a to_dict method to serialize them into dictionary format for easier inspection or export [8][10][9][11]. - trace.to_dict: Serializes the entire trace [9][11]. - span.to_dict: Serializes an individual span [8][9]. Namespace There is no prominent "namespace" parameter documented in the core search_traces or tracing API functions. Filtering is primarily achieved through the filter_string parameter, which utilizes a SQL-like Domain-Specific Language (DSL) to query attributes, tags, and metadata (e.g., filter_string="status = 'OK'") [12][6][2]. Example: Pagination with MlflowClient from mlflow import MlflowClient client = MlflowClient page_token = None all_traces = [] while True: results = client.search_traces( locations=["1"], # Replaces experiment_ids filter_string="status = 'OK'", page_token=page_token,) all_traces.extend(results) if not results.token: break page_token = results.token, top_results=[12][2][8][9][3])

Citations:


Align the trace fetch logic with MLflow’s actual return types. MlflowClient uses search_traces()/get_trace() (no .traces namespace), and TraceData exposes .spans/.to_dict() rather than .get(). This path will raise AttributeError unless mlflow_client is a custom wrapper that mimics both APIs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapters/clear/main.py` around lines 309 - 370, The trace fetching helper
`_fetch_mlflow_traces_to_dir` is using MLflow APIs and return shapes that don’t
match `MlflowClient`. Update it to call the client’s actual trace methods
(`search_traces()` and `get_trace()`, or the equivalent wrapper methods if this
code intentionally targets a custom client) instead of accessing
`mlflow_client.traces`. Also replace `trace.data.get("spans", [])` with the
proper `TraceData` access pattern such as `.spans` or `.to_dict()` so the span
extraction in this function works without `AttributeError`.

@suppathak
suppathak requested a review from ruivieira July 8, 2026 22:29
@ruivieira
ruivieira merged commit 0f5b512 into eval-hub:main Jul 14, 2026
4 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in EvalHub Planning Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-pr External contributor PR

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants