lexsi-ds-agent / docs /p2_feature_engineering.md
bp-lexsi's picture
Add P1/P2 agent tools: data-quality, sampling, join-paths, feature/target engineering
8bf2593
|
Raw
History Blame Contribute Delete
7.16 kB

A newer version of the Gradio SDK is available: 6.22.0

Upgrade

P2 Feature & Target Engineering Tools

** Aditya Β· Sprint: June 1–12 Β· Status: shipped (registry 18 β†’ 22 tools in this snapshot)**

Four tools that close the gap between "I have a SQL result" and "I can train an honest model on it": derive_feature, define_target, time_split, handle_imbalance. Specs follow docs/v1_tools.md Β§5; this doc is the implementation reference.

The canonical predictive chain is now:

run_sql(label="loans")
  β†’ derive_feature(df_label="loans", name="payment_burden",
                   expression="payments / amount")
  β†’ define_target(df_label="loans", target_column="y_default",
                  positive_definition="status = 'B'",
                  scope_filter="status IN ('A','B')",
                  entity_column="loan_id")
  β†’ time_split(df_label="loans", time_col="granted_date",
               cutoff="2017-06-01")
  β†’ handle_imbalance(df_label="train")          # defaults target from define_target
  β†’ train_tabular_model(df_label="train", target_column="y_default", ...)
  β†’ predict / evaluate_predictions on "test"

1. derive_feature

lexsi_ds/agent/tools/derive_feature.py

Adds ONE column to a cached run_sql DataFrame from a scalar expression β€” a ratio, date diff, or CASE bucket β€” without a SQL round-trip. Mutates sql_result:<df_label> in place (v1_tools Β§5.1 contract).

Arg Type Default Notes
df_label str β€” cached frame to mutate
name str β€” new column; identifier-validated
expression str β€” scalar SQL (duckdb) or df.eval (pandas)
engine "duckdb" | "pandas" "duckdb" duckdb is binder-validated
overwrite bool False guard against clobbering source columns

Failure modes surfaced as ok=False with a recoverable observation: unknown_label (lists cached labels), binder errors (lists available columns), column_exists, forbidden_expression (statement-level SQL keywords blocked), non_rowwise_expression (aggregates rejected β€” those belong in run_sql).

2. define_target

lexsi_ds/agent/tools/define_target.py

Materializes the prediction target as an explicit column and records the recipe β€” task type, positive definition, scope filter, entity column β€” under last_target_definition. Fixes the v0 failure mode of training on a raw multi-code column when the question was binary.

Arg Type Default Notes
df_label str β€”
target_column str β€” created if positive_definition given, else validated
task_type "classification" | "regression" "classification"
positive_definition str | None None SQL boolean (cls) / numeric expr (reg)
scope_filter str | None None rows kept for training; rest dropped
entity_column str | None None recorded so training excludes it

Validation: β‰₯2 classes for classification (else single_class_target), ≀20 classes (else high_cardinality_target β€” likely a regression target), numeric dtype for regression, NULL-target rows flagged.

3. time_split

lexsi_ds/agent/tools/time_split.py

Temporal train/test split: train = strictly before the cutoff, test = at/after. Two modes β€” explicit cutoff (ISO date) or test_fraction (chronologically-last fraction; the derived cutoff is reported). VARCHAR date columns are coerced; >20% parse failures β†’ bad_time_column. A cutoff outside the data range β†’ degenerate_split with the actual range in the observation so the planner can recover. The observation states the no-overlap invariant explicitly ("every train row precedes every test row") so the summariser can cite it.

Cache: sql_result:<train_label>, sql_result:<test_label>, last_split = {kind:"time", time_col, cutoff, train_label, test_label, n_train, n_test, source_label}. Sides under 50 rows are warned per the spec.

4. handle_imbalance

lexsi_ds/agent/tools/handle_imbalance.py

Class-balance report + ranked strategy proposal β€” it never resamples itself (the planner/user decides; silent data surgery is not shippable in an enterprise deployment). Defaults its target from last_target_definition so it chains naturally after define_target.

Severity bands on the imbalance ratio (majority/minority): balanced (<1.5), mild (<3), moderate (<10), severe (β‰₯10). Strategy ranking is data-aware: class weights always first; undersampling only when the majority can spare rows (β‰₯2k); SMOTE/oversampling when the minority is tiny (<200); **Lexsi synthetic augmentation offered only when ctx.org is attached** (train_synthetic_model β†’ generate_synthetic_data_points on the platform), so offline runs stay clean; metric guidance (AUROC/PR-AUC over accuracy) always appended for moderate/severe. Continuous targets (>20 distinct values) are rejected with non_categorical_target and a pointer at the regression path.

Cache: last_imbalance_report.


5. Feature lineage (enterprise upgrade, this iteration)

lexsi_ds/agent/tools/_lineage.py

All four tools append a structured record to ctx.cache["feature_lineage"] (ordinal, tool, UTC timestamp, operation details). This gives every trained model a reproducible "how the training frame was built" recipe β€” the model-risk-management audit artifact regulated customers ask for, and the data source for the planned export_report (P3) audit section. Append-only, run-scoped, zero hot-path cost.

6. New planner rules

Rules 20–23 added to PLANNER_SYSTEM (lexsi_ds/agent/prompts.py), one per tool, additive over 1–19:

  • 20 derive features with derive_feature, don't re-run SQL
  • 21 time-split temporal data before training β€” never random-split it
  • 22 define_target whenever the label must be constructed
  • 23 handle_imbalance before classification training; cite AUROC/PR-AUC when moderate/severe

Merge note: Bhavish's June-10 branch also claims rules 20–22 for his P1 tools (detect_data_quality_issues, suggest_join_paths, sample_rows). Merges shoud include second renumbers β€” the rules are independent prose blocks, so it's a mechanical shift.

7. Benchmark coverage

bench/pkdd/p2_feature_engineering.yaml β€” 10 items:

  • happy + error path per tool (8), trajectory-scored: expected/forbidden tools, ok flag, error code, observation substrings, cache keys written;
  • the leakage pair (2): the same default-risk question random-split vs time-split. Passes when the time-split run trains strictly pre-cutoff and reports an AUROC lower than the leaky random-split AUROC β€” demonstrating the leak rather than asserting a fixed number. This validates demo step 3.

8. Tests

tests/test_p2_feature_engineering.py β€” 25 hermetic tests (in-memory DuckDB, no PKDD file, no SDK, no LLM), reusing the offline-synthetic fixture family from conftest.py plus a local temporal_loan_df fixture with a time-drifting label. Covers every happy/error path above, the cache contracts, the no-temporal-overlap invariant, lineage ordering, and registry wiring.