Skip to content

Add RFC 25601: Typed Client API to Submit Issue Detection for Traces - #52

Open
Rohitkanithi wants to merge 4 commits into
mlflow:mainfrom
Rohitkanithi:rfc/25601-issue-detection-client-api
Open

Rohitkanithi wants to merge 4 commits into
mlflow:mainfrom
Rohitkanithi:rfc/25601-issue-detection-client-api

Conversation

@Rohitkanithi

Copy link
Copy Markdown

Summary

This RFC proposes adding a formal, typed public client API (submit_issue_detection and search_issues) to MlflowClient and formalizing the backend RPC endpoint (POST /api/2.0/mlflow/issues/invoke) for running issue detection on traces.

Signed-off-by: Rohitkanithi <63586900+Rohitkanithi@users.noreply.github.com>
…quence flows

Signed-off-by: Rohitkanithi <63586900+Rohitkanithi@users.noreply.github.com>
Signed-off-by: Rohitkanithi <63586900+Rohitkanithi@users.noreply.github.com>

@joshuawong-db joshuawong-db left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for making a RFC. I would suggest starting with the minimal API requirements

  • The routes to be added
  • Request/Response for each of the routes
  • Brief description in prose or pseudocode what this route does
  • Any important questions worth highlighting (eg job completion semantics).

This whole process may take a while.

Let's start with the minimal surface area and then expand from there as needed. There are also some claims in the RFC that look off and do not seem to match code

│ mlflow.server.handlers._invoke_issue_detection_handler │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 1. Authentication & Permission Check │ │
│ │ validate_can_update_experiment(experiment_id) │ │

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This does not preserve the existing authorization contract. The current AJAX route requires both experiment UPDATE and USE on a caller-supplied secret_id; the proposed RPC is mapped only to validate_can_update_experiment, and that mapping also applies to the existing AJAX route. Please preserve the issue-detection validator and add tests for both REST and AJAX paths.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I've updated the RFC to preserve validate_can_invoke_issue_detection() so both experiment UPDATE and USE on secret_id remain enforced across REST and AJAX routes, and will include tests for both.

},
)

# 1. Authorization: Verify caller has write permissions on the experiment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The authorization contract also needs READ permission for every supplied trace_id, not only UPDATE on the destination experiment. Otherwise a caller who knows a trace ID from another experiment can run analysis over it and persist derived issue content into an experiment they control. Can we reuse the all-traces authorization pattern from validate_can_batch_get_traces?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated the spec to adopt the validate_can_batch_get_traces pattern to look up parent experiments and enforce READ permission across all supplied trace_ids.

issues = client.search_issues(
experiment_id="101",
source_run_id=job.run_id,
filter_string="severity = 'high' AND status = 'pending'",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This exact example fails for two independent reasons: severity is not a supported issue-search field, and the linked implementation wraps the supplied filter in parentheses when adding source_run_id, which SearchIssuesUtils rejects

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I've fixed the example to use supported search keys (status and source_run_id) with clean AND conjunctions and no parentheses.

// Categories of issues to inspect (e.g. "hallucination", "tool_error").
repeated string categories = 3;

// Optional LLM provider identifier (e.g. "openai", "anthropic", "bedrock").

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What are the valid combinations and precedence rules for provider, model, secret_id, and endpoint_name? These fields currently describe an implicit union, and accepted combinations such as endpoint_name + secret_id without provider fail at runtime in #25881. Please make the two invocation modes explicit—ideally as one model URI or a typed union—and specify validation for every combination.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I made the two invocation modes explicit in the revised RFC: either AI Gateway (endpoint_name) or direct provider (provider + model, optional secret_id), with validation rejecting invalid combinations.

```

#### Implementation Details across Backends:
* **`RestStore` & `DatabricksRestStore`**:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This overstates backend support. The linked implementation dispatches from RestStore, while DatabricksRestStore.submit_issue_detection() explicitly raises MlflowNotImplementedException. I suggest keeping Databricks explicitly out of scope for this RFC and documenting the actual support matrix, rather than implying that DatabricksRestStore implements this path.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I've updated the Out of Scope section to explicitly keep Databricks out of scope for this RFC since DatabricksRestStore raises MlflowNotImplementedException.

┌─────────────────────────────────────────────────────────────────────────────────┐
│ ASYNC BACKGROUND EXECUTION ENGINE │
│ │
│ invoke_issue_detection_job(job_id, run_id, experiment_id, trace_ids...) │

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is not the existing function signature or launch path. invoke_issue_detection_job does not receive job_id; the handler passes the function to submit_job(), which creates that ID. Calling invoke_issue_detection_job(...) directly, as the later pseudocode does, would execute it synchronously and return a result dictionary rather than a job handle.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated the dispatch description to match the real server path where submit_job() creates the job_id and dispatches asynchronously.

In `mlflow/store/tracking/abstract_store.py`:

```python
@abstractmethod

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we model this after the existing optional issue-store methods: a concrete base method that raises MlflowNotImplementedException? That is also what #25881 implements and gives unsupported stores a defined failure instead of making the entire store hierarchy abstract.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated the store contract to specify a concrete base method on AbstractStore that raises MlflowNotImplementedException.

│ │
│ ┌───────────────────────┐ ┌───────────────────────┐ ┌─────────────────┐ │
│ │ 1. Load Trace Spans ├──►│ 2. Execute LLM Judges ├──►│ 3. Aggregate & │ │
│ │ from Tracking Store│ │ via AI Gateway │ │ Cluster Bugs │ │

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is not always an AI Gateway call. endpoint_name produces a gateway:/... URI, while provider + model invokes that provider directly using credentials injected into the job subprocess. Please treat these as distinct execution paths.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

AI Gateway routing via gateway:/... when endpoint_name is used, vs direct provider execution using subprocess credentials.

* **Server-Side Threading**:
Jobs execute inside the server's background thread/task runner. Resource limits and concurrency are governed by server configuration rather than client connection lifespan.
* **Paginated Queries**:
`search_issues` supports cursor-based pagination (`page_token`, `max_results`) to ensure querying thousands of issues remains performant.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is offset pagination encoded in an opaque page token, not cursor/keyset pagination: search_issues decodes an offset and applies query.offset(...).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Clarified that pagination uses token-encoded numeric offsets (query.offset(...)).

│ │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ runs Table │ │ issues Table │ │
│ │ - run_id │ │ - issue_id (UUID) │ │

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue_id is stored as a string in the format iss-<uuid hex>, not as a UUID. The same diagram also omits the persisted REJECTED status and NOT_AN_ISSUE severity that the RFC lists later. Please make the schema diagram match the actual entity/database model.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated the schema to reflect the string iss- format and added REJECTED to status and NOT_AN_ISSUE to severity.

Signed-off-by: Rohitkanithi <63586900+Rohitkanithi@users.noreply.github.com>
@Rohitkanithi

Copy link
Copy Markdown
Author

Hey @joshuawong-db, thanks for the review.

I've replied to all your inline comments and pushed an update (83f0abe) refactoring the RFC down to the minimal API surface area you suggested, with all routes, schemas, and auth contracts strictly aligned with the codebase.

Ready for review

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.

2 participants