Skip to content

Fork test: PR #1129 - feat(anomaly): add LLM-generated AI explanations for has_no_row_anomalies#1238

Closed
mwojtyczka wants to merge 41 commits into
mainfrom
fork-test/pr-1129
Closed

Fork test: PR #1129 - feat(anomaly): add LLM-generated AI explanations for has_no_row_anomalies#1238
mwojtyczka wants to merge 41 commits into
mainfrom
fork-test/pr-1129

Conversation

@mwojtyczka

Copy link
Copy Markdown
Contributor

Automated sync from fork PR for CI testing.

Original PR: #1129

All tests, including unit and integration tests run on this PR (they are skipped for fork PRs).

fedeflowers and others added 19 commits April 28, 2026 12:30
…+ fix no-cheat CI

Pivot the has_no_row_anomalies AI explanations to a single ai_query (Spark SQL)
execution path and remove the DSPy/driver executor, per review direction.

- Delete the DSPy/driver path (build_language_model, _explain_one_group,
  _call_llm_for_groups, _add_explanation_column_driver, dspy signature) and the
  LLMModelConfig.executor field; AI explanations need no extra dependency.
- UUID-suffix the (segment, pattern) working column via ScoringOutputColumns so it
  can never collide with a user column (threaded through ExplanationContext).
- Rename the ai_explanation struct field pattern -> top_features.
- Redact segment values listed in redact_columns; escape backslash + quote in the
  redact SQL literal; build ai_query responseFormat and confidence thresholds from
  single sources of truth; warn when max_groups < eligible segments.
- Drop model_name from the prompt and add anti-pattern guidance + two few-shot
  exemplars.
- Fix the failing no-cheat / no-pylint-disable CI the right way: remove the
  redundant per-line disables and add protected-access to pylint per-file-ignores
  (no CHEAT bypass). import-outside-toplevel disappears with the driver path.
- Replace driver/DSPy tests with ai_query unit + integration tests, including a
  prompt-quality guard (dominant feature referenced + length caps).

Signed SHAP display and config/prompt knobs are intentionally deferred (see PR).

Co-authored-by: Isaac
@mwojtyczka mwojtyczka requested a review from a team as a code owner June 10, 2026 21:48
@mwojtyczka mwojtyczka added do-not-merge The PR is blocked, e.g. being worked on and not ready. Not using draft to be able to test changes. fork-test labels Jun 10, 2026
@mwojtyczka mwojtyczka requested review from grusin-db and removed request for a team June 10, 2026 21:48
vb-dbrks added 3 commits June 12, 2026 14:52
…uery responseFormat, demo label

- LLMModelConfig.__post_init__ now validates max_tokens (positive int) and
  temperature (non-negative) alongside max_retries, so a bad value raises a clean
  InvalidParameterError instead of a raw TypeError at ai_query SQL-build time
  (and no non-positive max_tokens / out-of-range temperature reaches the endpoint).
- Escape _AI_QUERY_RESPONSE_FORMAT via _sql_string_literal when interpolating it
  into the ai_query SQL — a no-op today (the JSON carries no quotes) but hardens
  against a future _PROMPT_OUTPUT_FIELDS entry that contains a single quote.
- Rename the demo's "Section 5" -> "Section 5a" now that "Section 5b: AI
  explanations" was added.

Co-authored-by: Isaac
… test

The rendered prompt is the contract the model sees; an edit to any _PROMPT_* table
silently changes every explanation. Add a fast unit test (no LLM, no workspace, ms)
asserting _render_ai_query_prompt_header() matches a committed golden snapshot
(tests/resources/ai_query_prompt_header.txt), so prompt changes must be deliberate
and surface in review as a diff. Full output-quality evaluation stays with #1030.

Co-authored-by: Isaac
…document pattern collisions

- _sanitize now trims an overrun LLM output field back to a word boundary (drops the
  trailing partial word) instead of cutting mid-word, when it exceeds _LLM_FIELD_MAX_LEN.
- Document the (segment, top-2) pattern-collision trade-off in the Row Anomaly guide:
  groups sharing the same top-2 contributors merge into one explanation — intentional,
  keeps the LLM-call count (and cost) low at the price of a less specific narrative.

Co-authored-by: Isaac
vb-dbrks and others added 9 commits June 12, 2026 19:37
…ful degradation

Make enable_contributions and enable_ai_explanation default to True so row
anomaly checks produce SHAP contributions and plain-language AI explanations
out of the box, reducing setup friction.

- check_funcs / scoring_config: flip both defaults to True
- rename public param llm_model_config -> ai_explanation_llm_model_config
- degrade instead of erroring: when enable_contributions is False, AI
  explanations are disabled with a warning (not an exception)
- graceful endpoint guard: probe the Model Serving endpoint once before
  issuing ai_query; if unreachable, skip explanations with a warning and let
  scoring complete (no run failure when the endpoint/Foundation Model APIs
  are unavailable)
- document the ai_query requirement (serverless or DBR 15.4 LTS+) in the
  guide, reference, and demo Section 5b
- tests: unit coverage for defaults + downgrade behaviour; integration
  coverage for default-on, graceful degradation, and contributions-off
- conftest: opt the broad anomaly suite out of SHAP/LLM cost by default

Co-authored-by: Isaac
…partition warning

The per-pattern top-N selection used an unpartitioned Window (global totals +
row_number) and returned that windowed DataFrame as the group projection that
ai_query consumes downstream. Because callers re-action the scored DataFrame
(e.g. the demo counts anomalies at several thresholds), Spark re-analysed the
window on every action and re-emitted "No Partition Defined for Window
operation" outside the local suppression block — noisy for users, though
harmless (the window only ran over the small per-pattern aggregate).

Collect the top-N keys + totals once (under the existing suppression), then
rebuild the kept projection by filtering the per-pattern aggregate to those
known keys with isin(...). Equivalent selection, no window in the returned
lineage, so no warning on downstream actions.

Co-authored-by: Isaac
…un, not per action

The per-group ai_query results were left-joined lazily onto the scored
DataFrame, so every downstream action (count, display, write) re-executed the
lineage and re-invoked ai_query for every group — multiplying LLM cost and
latency per action and contradicting the documented "one call per group per
scoring run" cost model. A notebook that counts anomalies at several
thresholds paid a full LLM pass per count.

Materialise the per-group results once (collect + rebuild as a local
DataFrame) before the join. The payload is small and bounded: at most
max_groups rows, each with three length-capped text fields.

Co-authored-by: Isaac
…atch baseline stats

Three scoring/training-path performance improvements (no feature changes):

1. SHAP only for anomalous rows. Contributions were computed for every row
   inside the scoring UDFs even though they are only surfaced for rows at or
   above the threshold. The UDFs now recompute severity from the model's
   quantile points in numpy (epsilon-over-inclusive) and run TreeSHAP only on
   that subset; add_info_column gates the struct field so the observable
   contract is exact — anomalous rows always carry contributions, other rows
   a null map. SHAP cost now scales with the anomaly count, not table size.

2. No more scoring self-join. Scorers joined the scored frame (derived from
   the input) back onto the input on a monotonically_increasing_id — double
   computation, a full shuffle, and a join keyed on a non-deterministic id.
   The original row now rides through feature engineering inside a
   collision-proof struct column and is restored with a select after scoring.

3. Baseline drift statistics in two actions. compute_baseline_statistics ran
   two Spark actions (full scans) per column; now one combined aggregate
   select plus one multi-column approxQuantile regardless of column count.

Docs updated for the contributions-only-for-anomalous-rows contract (and two
stale "disabled by default" lines from the default-on change); threshold
exploration guidance added — severity is still computed for every row, score
at a lower threshold if sub-threshold contributions are needed.

Co-authored-by: Isaac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge The PR is blocked, e.g. being worked on and not ready. Not using draft to be able to test changes. fork-test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants