lexsi-ds-agent / docs /MODELING_TOOLS.md
adityatannaarya's picture
added utils and reorganized
e68dffb
|
Raw
History Blame Contribute Delete
12.6 kB
# Modeling Tools - added by Aditya
This document describes the modeling-tools layer of the Lexsi DS Agent v0:
the three production tools owned by Aditya per the design doc Β§4
(`train_tabular_model`, `predict`, `explain_prediction`) plus the
supporting test infrastructure. It assumes you've read the project
[README](README.md) and the [v0 design doc](docs/v0_design.md).
For the rest of the agent (data connectors, NL→SQL, KG retrieval,
summarization), see Bhavish's tools under `lexsi_ds/agent/tools/`.
---
## TL;DR
```bash
# Run the full modeling flow offline β€” no Lexsi auth required
python scripts/demo_modeling_offline.py
# Run the test suite (47 hermetic modeling tests + 32 PKDD-gated tests)
python -m pytest tests/ -q
# Bundle a zip for review
bash scripts/package.sh
```
---
## What was delivered
### 1. Three production tools (live SDK)
Each replaces a `not_implemented` stub with a full implementation against
the `lexsi-sdk==0.1.46` surface. Argument schemas match the design doc
Β§4 contract; downstream observation shapes match what
`summarize_result` expects in cache.
| File | What it does | Key SDK call |
|---|---|---|
| `lexsi_ds/agent/tools/train_tabular_model.py` | Uploads a context DataFrame + trains a model in one shot. Defaults to XGBoost; supports TabPFN / OrionMSP / TabICL / TabDPT / OrionBix / Mitra / ContextTab (requires `compute_type`). | `TabularProject.upload_data(data, tag, config=ProjectConfig, compute_type, tunning_strategy)` |
| `lexsi_ds/agent/tools/predict.py` | Uploads the predict DataFrame under a sibling tag, runs batch inference, surfaces top-K by probability. | `TabularProject.model_inference(tag, model_name, pod)` |
| `lexsi_ds/agent/tools/explain_prediction.py` | Per-case XAI for the top-K cases (or explicit `case_ids`). Reads `shap_feature_importance` directly, calls `xai_summary` and `xai_similar_cases` opt-in. Handles both flat and multiclass-nested SHAP dicts. | `TabularProject.case_predict(unique_identifier, tag, model_name, xai)` |
### 2. Offline test infrastructure
The three tools talk to Lexsi over the network. To run them in tests
and demos without an SDK token, this delivery includes a faithful test
double that mirrors the SDK contract in executable form.
| File | Purpose |
|---|---|
| `lexsi_ds/agent/testing/__init__.py` | Re-exports `FakeTabularProject`, `FakeCaseTabular`, `make_offline_ctx`. |
| `lexsi_ds/agent/testing/fakes.py` | `FakeTabularProject` mirrors `upload_data`'s two-branch behavior (first-call sets project config + trains; subsequent calls just store tags), foundation-model `compute_type` check, `model_inference`, `case_predict`. `FakeCaseTabular` mirrors the live class field-for-field for the subset `explain_prediction` reads. Same role `StubLLMClient` plays for text. If the SDK ever changes a kwarg name, this file is the canary β€” tests break before production does. |
### 3. Production bootstrap
| File | Purpose |
|---|---|
| `lexsi_ds/agent/lexsi_bootstrap.py` | `bootstrap_lexsi_handles()` β€” env-driven login + project resolution. Mirrors the `LexsiTextClient.from_env` pattern. Returns `LexsiHandles{org, workspace, tab_project, text_project}`. Asserts `isinstance(tab_project, TabularProject)` so wrong-modality misconfiguration fails at boot, not at first tool call. |
### 4. Test suite
| File | Tests | What it covers |
|---|---|---|
| `tests/test_train_tabular_model.py` | 15 | XGBoost / TabPFN / sample_percentage happy paths; missing_df, bad_target, empty_target, missing_compute_type, no_tab_project error branches; entity-column resolution, tag minting, ProjectConfig 12-key shape, active-model resolution. |
| `tests/test_predict.py` | 13 | Happy path, explicit model_id, top_k bounding, null-target stripping; missing_df, empty_predict_df, no_model_id, no_tab_project; column-name normalization, top-K helpers. |
| `tests/test_explain_prediction.py` | 15 | Auto / explicit case_ids, include_similar, summary caching; no_model_id, no_tab_project, no_predict_tag, no_cases, all_cases_failed; SHAP top-k for flat / nested-multiclass / empty / garbage dicts. |
| `tests/test_modeling_e2e.py` | 4 | Full 3-step flow with cache handoff; predict-without-train fails clean; explain-without-predict fails clean; registry membership. |
| `tests/conftest.py` | (fixtures) | Adds offline fixtures: `loan_context_df`, `loan_predict_df`, `fake_tab_project`, `ctx_with_fake`, `ctx_offline`, `seed_sql_result`. |
### 5. Demo + packaging
| File | Purpose |
|---|---|
| `scripts/demo_modeling_offline.py` | Runnable end-to-end demo: `train β†’ predict β†’ explain` against `FakeTabularProject`. Flags: `--rows`, `--predict-rows`, `--top-k`, `--model-type`, `--compute-type`, `--seed`, `--lexsi` (swap fake for real SDK), `-v`. |
| `scripts/package.sh` | Produces `dist/lexsi_ds_agent-modeling.zip` excluding caches / sessions / runs / PKDD source. `OUT=` env overrides the output path. |
---
## Architectural decisions
1. **`predict` uses batch `model_inference`; `explain_prediction` uses per-row `case_predict`.** The per-case XAI path is needed only for the K rows we actually want to explain. Running `case_predict` on the full predict_df would cost K Γ— the inference time and the bias-monitor / policy hooks would fire K times for no extra information.
2. **No external polling.** The live `upload_data` already calls `poll_events` internally and blocks until training is done. The agent loop layers wall-clock guards via daemon threads so a stuck backend cannot hang the loop:
| Env var | Default | Affects |
|---|---|---|
| `LEXSI_TRAIN_TIMEOUT_S` | 900 (15 min) | `upload_data` train call |
| `LEXSI_PREDICT_TIMEOUT_S` | 600 (10 min) | `upload_data` predict upload, `model_inference` |
| `LEXSI_CASE_TIMEOUT_S` | 60 | per-case `case_predict` |
| `LEXSI_XAI_BUDGET_S` | 300 (5 min) | total across all explained cases |
3. **Offline-runnable via `FakeTabularProject`** mirroring the live SDK surface, exactly the way `StubLLMClient` mirrors `LexsiTextClient`. Pins the SDK contract in executable form so contract drift breaks tests, not production.
---
## Cache contract
This is what links the three tools together and what `summarize_result`
reads in the final step.
After `train_tabular_model` β†’ `predict` β†’ `explain_prediction`:
| Cache key | Type | Set by |
|---|---|---|
| `last_model_id` | `str` (the live Lexsi `model_name`) | train |
| `last_train_tag` | `str` | train |
| `last_train_df_label` | `str` | train |
| `last_model_task` | `dict{target_column, entity_column, task_type, model_type}` | train |
| `last_predict_tag` | `str` | predict |
| `last_predict_df_label` | `str` | predict |
| `last_predictions` | `pd.DataFrame` (input cols + Prediction + Probability) | predict |
| `last_prediction_columns` | `dict{pred, prob, entity}` | predict |
| `last_xai` | `list[dict]` (one entry per case: case_id, pred_value, pred_category, shap_top, shap_full, summary, optionally similar_preview) | explain |
| `last_xai_failures` | `list[dict]` | explain |
---
## How to run
### Offline (no auth, no compute, no network)
```bash
python scripts/demo_modeling_offline.py --rows 100 --predict-rows 25 --top-k 5
```
Runs all three tools against `FakeTabularProject` and prints the cache
state. The SHAP drivers in the demo are deterministic per seed (not
per case) because the fake uses a single seeded RNG β€” the live Lexsi
backend produces real per-case SHAP.
### Against real Lexsi
```bash
export SDK_ACCESS_TOKEN=... # from app.lexsi.ai/sdk
export LEXSI_ORG_NAME=personal # or your org
export LEXSI_WORKSPACE_NAME=your_ws
export LEXSI_TABULAR_PROJECT_NAME=your_proj
python scripts/demo_modeling_offline.py --lexsi --model-type XGBoost
# foundation model:
python scripts/demo_modeling_offline.py --lexsi --model-type TabPFN --compute-type small
```
### Wiring into the live agent loop
```python
from lexsi_ds.agent.context import AgentContext
from lexsi_ds.agent.lexsi_bootstrap import bootstrap_lexsi_handles
from lexsi_ds.llm.client import factory as llm_factory
handles = bootstrap_lexsi_handles()
ctx = AgentContext(
dataset=..., # from Bhavish's connect_datalake / load_pkdd_handle()
run_id=...,
org=handles.org,
text_project=handles.text_project,
tab_project=handles.tab_project,
llm=llm_factory("lexsi"),
)
# Three modeling tools are already in the registry; the planner can call them by name.
```
---
## How to test
```bash
python -m pytest tests/ # all 47 modeling tests + 32 PKDD-gated
python -m pytest tests/test_modeling_e2e.py # end-to-end only
python -m pytest tests/ -k "happy_path" # happy paths only
python -m pytest tests/ -k "error or no_" # error branches only
```
All modeling tests are hermetic β€” no network, no DuckDB, no Lexsi
auth. Bhavish's PKDD-gated tests skip cleanly when the DuckDB isn't
bootstrapped.
---
## SDK cross-verification
Every SDK call site was cross-verified against `lexsi-sdk==0.1.46` via
introspection of installed signatures + reading source bodies for
`xai_summary`, `xai_similar_cases`, and `Workspace.project`.
### Verified call sites
| Call site | Live signature in `lexsi-sdk==0.1.46` | Result |
|---|---|---|
| `TabularProject.upload_data(data, tag, config, compute_type, tunning_strategy)` | matches exactly (preserving the SDK's `tunning_strategy` typo, *not* `tuning_strategy`) | βœ… |
| `TabularProject.model_inference(tag, model_name, pod)` | matches | βœ… |
| `TabularProject.case_predict(unique_identifier, tag, model_name, xai)` | matches | βœ… |
| `TabularProject.active_model()` / `.models()` | returns `pd.DataFrame` with `model_name` column β€” matches my reads | βœ… |
| `CaseTabular` field reads (`shap_feature_importance`, `pred_value`, `pred_category`, `summary`, `similar_cases_data`, `model_name`, `data_id`, `unique_identifier`) | all present | βœ… |
| `CaseTabular.xai_summary()` / `.xai_similar_cases()` | source confirmed β€” `xai_summary` returns string (caches on `self.summary`); `xai_similar_cases` returns `pd.DataFrame \| str`; my code handles both | βœ… |
| Login chain: `xai.login` β†’ `xai.organization` β†’ `Organization.workspace` β†’ `Workspace.project` | matches; `Workspace.project()` is modality-dispatching | βœ… |
| `ProjectConfig` TypedDict shape | exact 12-key match | βœ… |
---
## Known limitations / deliberately deferred
- **No bias-monitor / risk-policy hookup.** The SDK exposes these on `case_predict(..., risk_policies=True)`; not wired in v0 because design doc scope is risk *scoring* and *explanation*, not policy enforcement. Single arg passthrough when needed.
- **No PEFT / fine-tune path.** Foundation models default to `tunning_strategy="inference"` (zero-shot) to keep the demo path fast. PEFT / base-ft requires `peft_config` / `tunning_config` β€” the SDK supports them; the tool args don't expose them yet. Easy follow-on if needed.
- **Single active model assumed.** `_resolve_active_model_name` falls back to the most recent row in `.models()` if `.active_model()` is empty, but doesn't model multiple concurrent active models. The live SDK enforces a single-active-model invariant, so this should be fine.
- **Fake SHAP is deterministic per project, not per case.** In the offline demo, top-K cases show the same SHAP drivers because the fake uses a single seeded RNG. The live Lexsi backend produces per-case SHAP β€” this is a cosmetic limitation of the test double, not the production tools.
---
## File index β€” exactly what was added or changed
```
NEW lexsi_ds/agent/lexsi_bootstrap.py
NEW lexsi_ds/agent/testing/__init__.py
NEW lexsi_ds/agent/testing/fakes.py
REPL lexsi_ds/agent/tools/train_tabular_model.py (stub β†’ production)
REPL lexsi_ds/agent/tools/predict.py (stub β†’ production)
REPL lexsi_ds/agent/tools/explain_prediction.py (stub β†’ production)
NEW tests/__init__.py
MERGE tests/conftest.py (added offline fixtures alongside existing PKDD fixtures)
NEW tests/test_train_tabular_model.py (15 tests)
NEW tests/test_predict.py (13 tests)
NEW tests/test_explain_prediction.py (15 tests)
NEW tests/test_modeling_e2e.py (4 tests)
NEW scripts/demo_modeling_offline.py
NEW scripts/package.sh
NEW HANDOFF.md (delivery handoff note)
NEW MODELING_TOOLS.md (this file)
```