Skip to content

Parity#402

Draft
thorrester wants to merge 2 commits into
mainfrom
parity
Draft

Parity#402
thorrester wants to merge 2 commits into
mainfrom
parity

Conversation

@thorrester
Copy link
Copy Markdown
Member

@thorrester thorrester commented May 15, 2026

Pull Request

Short Summary

Adds a fluent experiment API — opsml.log_metric(), opsml.log_param(), and friends — backed by a process-global ActiveStack<Experiment> in Rust, with per-framework log_model shortcuts (sklearn_log_model, torch_log_model, etc.) that register a ModelCard in one call. Also refactors the trace dashboard filter state from flat fields into a composable FilterClause tree, dropping N parallel facet API calls down to 2.

Context

Fluent experiment API

Previously, logging to an experiment required a captured reference: exp.log_metric(...). This PR adds free functions that resolve the current experiment from a process-global stack pushed/popped by Experiment.__enter__/__exit__:

Before:

with start_experiment(space="training", name="run") as exp:
    exp.log_metric("accuracy", 0.94)
    exp.log_parameter("lr", 0.001)

After:

import opsml

with start_experiment(space="training", name="run"):
    opsml.log_metric("accuracy", 0.94)
    opsml.log_param("lr", 0.001)

The stack lives in crates/opsml_experiment/src/active.rs as a Mutex<Vec<Py<Experiment>>>. Single-threaded only — the comment is explicit that concurrent experiments must use the explicit form. active_experiment() exposes the current experiment for callers that need it directly.

Flavor log_model functions

Each supported framework now has a {framework}_log_model(model, *, name, space=None, ...) free function in py-opsml/src/flavors.rs. When space is omitted, it reads the active experiment's space — so opsml.sklearn.log_model(...) inside a with start_experiment(...) block just works. Returns a registered ModelCard.

ModelInterface subclass fix

PyO3 requires __new__ and __init__ to be separate for Python subclassing to work. ModelInterface previously used #[new] with a full constructor signature, which broke subclasses that pass extra kwargs. Split into __new__(*args, **kwargs) -> Self::default() + __init__(self, model=None, ...) that does the real initialization. The stubs were updated to reflect the **kwargs in __init__ and the explicit __new__ signature.

Trace filter refactor

The TraceDashboard was managing filter state as flat struct fields (service_name, service_namespace, service_version, service_instance_id, status_code, has_errors, attribute_filters). Every filter change fired up to 5 parallel getServerTraceFacets calls (one per field cleared to compute "what else matches"). This PR replaces the flat shape with a composable FilterClause tree in clause.ts:

// Before: clear individual fields per-facet call
getServerTraceFacets(fetch, { ...filters.filters, service_name: undefined })
getServerTraceFacets(fetch, { ...filters.filters, service_namespace: undefined })
// ... 3 more

// After: remove one dimension from the clause
getServerTraceFacets(fetch, { ...filters.filters, clause: removeClauseDimension(clause, "service") })
getServerTraceFacets(fetch, { ...filters.filters, clause: removeClauseDimension(clause, "status_code") })

Down to 2 calls. clause.ts (~370 lines) covers builders (serviceClause, statusCodeClause, attrClause, durationMinClause, etc.), combinators (andClause, addToClause, replaceClauseDimension, removeClauseDimension), and an ActiveFilter type for chip display. Also adds validation.ts for trace span input validation and waterfall.ts for the depth-first span sort that was previously inlined in TraceWaterfall.svelte. SvelteMap/SvelteSet replaces bare Map/Set for reactive correctness.

Dev tooling

mise.toml gains dev:e2e:* tasks for a full E2E harness against a live Scouter instance (expects a sibling ../scouter checkout or SCOUTER_DIR override). Python formatter consolidated from isort + black + ruff to ruff format + ruff check --fix. pylint dropped in favor of ty. Both Makefile and py-opsml/makefile removed.

File/Area Change
crates/opsml_experiment/src/active.rs New ActiveStack<Experiment> — push/pop/current
crates/opsml_experiment/src/fluent.rs Free functions: log_metric, log_param, log_artifact, log_figure, set_tag, active_experiment
crates/opsml_experiment/src/experiment.rs __enter__/__exit__ push/pop the stack; adds log_param/log_params/set_tag/set_tags aliases
py-opsml/src/flavors.rs Per-framework log_model free functions, space resolution from active experiment
crates/opsml_interfaces/src/model/base/interface.rs Split #[new] + __init__ for PyO3 subclass compatibility
crates/opsml_server/opsml_ui/src/lib/components/trace/clause.ts New FilterClause algebra replacing flat filter fields
crates/opsml_server/opsml_ui/src/lib/components/trace/TraceDashboard.svelte Filter handlers rewritten to use clause combinators; 5 facet calls → 2
crates/opsml_server/opsml_ui/src/lib/components/trace/waterfall.ts Extracted depth-first span sort
crates/opsml_server/opsml_ui/src/lib/components/trace/validation.ts Trace span validation layer
py-opsml/python/opsml/__init__.py Re-exports all fluent free functions + flavor log_model wrappers
py-opsml/python/opsml/{sklearn,torch,...}/ New framework sub-packages with stubs
py-opsml/tests/test_fluent.py 228 lines of fluent API tests including error cases and nested experiment behavior
py-opsml/tests/test_fluent_flavors.py Round-trip registration tests for every supported framework
mise.toml E2E harness tasks, formatter consolidation to ruff, pylint removed
Makefile, py-opsml/makefile Deleted

Is this a Breaking Change?

No. All Python additions are new exports; existing Experiment method signatures are unchanged. The trace filter refactor is internal UI state — the API server routes and response contracts are unmodified. ModelInterface.__new__ now accepts *args/**kwargs, which is strictly more permissive for Python subclasses.

@github-actions
Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Manifest Files

py-opsml/uv.lock
  • black@24.10.0
  • astroid@3.3.11
  • dill@0.4.0
  • isort@5.13.2
  • mccabe@0.7.0
  • mypy-extensions@1.1.0
  • pylint@3.3.9
  • tomlkit@0.14.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant