Spaces:
Sleeping
A newer version of the Gradio SDK is available: 6.22.0
Lexsi DS Agent β Developer / KT Guide
A code-level companion to DSAGENT_OVERVIEW.md (which is
the what/why). This is the how: file map, control flow, the contracts you'll
touch, configuration, and the full benchmark-running mechanics. Read the overview
first, then this.
1. Mental model in one paragraph
One question β one AgentLoop.run() β a ReAct loop: build a system prompt
(framing + active-dataset summary + tool catalog), ask the LLM for a JSON decision
({"thought","tool","args"} or {"thought","final_answer"}), validate args with
the tool's pydantic schema, run the tool against a shared AgentContext, append the
observation to history, repeat until a final answer or the step budget. Tools read
and write a shared ctx.cache scratchpad. The data substrate is always DuckDB;
tabular ML runs on remote Lexsi pods via the SDK. The loop is generic β it has
no opinion on which tools to call.
2. Repo layout (code-oriented)
lexsi_ds/
agent/
loop.py AgentLoop.run() β the ReAct orchestrator (start here)
context.py AgentContext, DatasetHandle, TableInfo, ColumnInfo
prompts.py PLANNER_SYSTEM, render_tool_catalog, tool-selector (intent routing)
datasource.py DuckDB handle loaders + multi-DB materialization + DB cache
tools/
base.py Tool / ToolSpec / ToolResult contract (the interface)
__init__.py REGISTRY: dict[str, Tool] (30 tools)
<name>.py one tool per file, each exports `TOOL: Tool`
_tabular_common.py shared helpers for the model-lifecycle tools
(resolve_tab_project, active_model_name, df_records, β¦)
kg_index.py embedding/lexical index over the PKDD knowledge graph
table_index.py embedding index over table schemas (schema linking, big DBs)
session.py SessionContext + multi-turn merge
turn_classifier.py classify a turn: new / refine / abandon
reference_resolver.py resolve "it/that/those" against prior turns
samples.py curated demo questions (by_id, SAMPLES) β UI chips + tests
llm/client.py LLMClient protocol, StubLLMClient, LexsiTextClient, cache, timeout
schema/
raw.py PKDD DDL introspection (introspect())
context_graph.py ContextGraph β the banking knowledge graph
data/loader.py PKDD TSVs -> artifacts/financial.duckdb
paths.py DUCKDB_PATH, ARTIFACTS_DIR, etc.
app/gradio_app.py the UI (build_ui(); demo.launch on GRADIO_PORT)
benchmark/
dab/
adapter.py parse a DAB dataset -> DatasetHandle + tasks (+ DB cache key)
runner.py run agent per DAB task, score Pass@1 via the task's validate.py
DataAgentBench/ the (gitignored) upstream checkout + DB files
pkdd_runner.py run agent over bench/pkdd/questions.yaml, auto-score
datagen/ template pipeline that GENERATES bench/pkdd/*.yaml
bench/pkdd/
questions.yaml 40 single-turn bench items (analytic/predictive/adversarial)
sessions.yaml multi-turn sessions
p2_feature_engineering.yaml 10 feature-eng items (separate `assertions` schema)
benchmark/fixtures/pkdd_seed.duckdb pinned PKDD snapshot the bench scores against
scripts/
repro_tabicl/ TabICL inference + per-case XAI failure repro
repro_model_lifecycle/ model-lifecycle SDK probe harness (self-contained):
data/ the PKDD CSVs (local copy)
repro_model_lifecycle.py TabICL-first (clean)
repro_model_lifecycle_xgboost_first.py XGBoost-first (#05-013 cascade repro)
tests/ pytest suite (test_b_tools, test_v1_multiturn, test_dab_*, β¦)
docs/ design + results + this guide
3. The run lifecycle (loop.py)
AgentLoop(ctx, registry=REGISTRY, max_steps=18).run(question) -> AgentRun
run(question):
1. (multi-turn) classify_turn() -> new/refine/abandon; resolve_reference()
- "abandon" short-circuits with a polite answer.
2. ctx.cache["question"] = question
3. system = _render_system(...) # built ONCE per run, re-sent every step
- _select_tools(question): LLM intent classifier -> render only relevant tool group(s)
- render_dataset_block(ctx) + render_tool_catalog(tools) + PLANNER_SYSTEM
4. history = ["USER QUESTION: ..."]
5. while n < max_steps:
user_msg = "\n\n".join(history) + "You have {max_steps-n} tool calls left..."
decision = parse(ctx.llm.complete(system, user_msg))
if "final_answer": finalize + return
tool = registry.get(decision["tool"])
result = _call_tool(tool, decision["args"]) # pydantic-validate then tool.run()
history.append("TOOL CALL ...\nOBSERVATION: " + result.summary[:1500])
# anti-loop: same tool fails 2x in a row -> inject "[LOOP GUARD] re-plan" directive
if result.meta.get("pause_for_user"): return (clarify pause) # interactive only
6. budget exhausted -> _force_final(): ask once for a final answer
Key methods: run, _render_system, _select_tools, _call_tool, _force_final.
Dispatch always uses the full self.registry; the tool selector only narrows
what the planner sees in the prompt, never what it can call.
Data classes:
AgentRunβrun_id, question, dataset_id, steps[], final_answer, ok, error, pending_clarification, system_prompt.Stepβn, thought, tool, args, result(ToolResult), raw_llm_text, latency_s, prompt. The full per-turn user message lives on each Step (so trajectories are reproducible).
4. Core data structures (context.py)
AgentContext β built once per run, passed to every tool:
dataset: DatasetHandle,run_idorg / text_project / tab_project / tab_projectsβ Lexsi SDK handles (may be None offline)llmβ anLLMClientcache: dictβ run-scoped scratchpad (see Β§6)sessionβ optionalSessionContext(multi-turn)interactive: boolβ True in the UI (clarify pauses); False in bench/headless (clarify falls back to itsdefault).duck(read_only=None)β open a DuckDB connection ondataset.duckdb_path(read-only for bundled, writable otherwise)
DatasetHandle β id, kind, duckdb_path, tables: list[TableInfo], connector_id, kg. kind β {bundled, upload, connector, attached}. Everything funnels through ctx.duck() β duckdb_path, so "switch dataset" = build a new handle and assign ctx.dataset.
TableInfo = name, columns: list[ColumnInfo], n_rows, comment. ColumnInfo = name, dtype, comment, sample (the sample value is the per-column representative value the planner sees in the schema β populated at describe time).
5. The tool contract β how to add a tool (tools/base.py)
A tool is any object with .spec: ToolSpec and .run(args, ctx) -> ToolResult.
# lexsi_ds/agent/tools/my_tool.py
from pydantic import BaseModel, Field
from lexsi_ds.agent.context import AgentContext
from lexsi_ds.agent.tools.base import Tool, ToolResult, ToolSpec
class MyToolArgs(BaseModel):
table_or_label: str = Field(..., description="...") # match sibling arg names!
def _run(args: MyToolArgs, ctx: AgentContext) -> ToolResult:
con = ctx.duck()
...
return ToolResult(ok=True, summary="short text the LLM sees (<~200 tok)",
payload={"dataframe": df}) # rich data; LLM never sees it
class _MyTool:
spec = ToolSpec(name="my_tool", description="...(LLM sees verbatim)...",
args_schema=MyToolArgs, returns="what the observation looks like")
def run(self, args, ctx): return _run(args, ctx)
TOOL: Tool = _MyTool()
Then register it: add from .my_tool import TOOL as my_tool_tool and an entry in
REGISTRY in tools/init.py. If it fits an
intent group, add it to _TOOL_GROUPS in prompts.py
so the tool selector surfaces it (see Β§8).
Contract notes:
summaryis the only thing the LLM sees;payloadis for the UI / downstream tools.- Return
ok=Falsewith an actionablesummaryon expected failures β the planner reads it and re-plans. Unexpected exceptions propagate; the loop wraps them. - Arg-name convention: sibling tools that take a source use
table_or_label. Match it (or alias via pydanticAliasChoices) β mismatches show up asvalidation_errorand waste planner steps (this bit us; seetransform_column). - Args are validated in
_call_toolviatool.spec.args_schema.model_validate(raw_args).
6. The cache contract (ctx.cache)
Run-scoped (session-scoped in multi-turn). Producers β consumers, by key:
| Key | Producer | Consumer |
|---|---|---|
question |
loop | tool selector |
tool_intents::<q> |
_select_tools |
(memoize the intent classification) |
sql_result:<label> |
run_sql |
profile_data, sample_rows, transform_column, predict |
last_sql_result, last_sql |
run_sql |
PKDD scorer, downstream |
profile:<label> |
profile_data |
train_tabular_model, dq checks |
last_train_columns |
train_tabular_model |
predict (schema alignment) |
last_predictions, last_predict_tag, last_model_id |
predict |
explain_prediction, evaluate_predictions |
last_eval |
evaluate_predictions |
summarize_result |
When adding a tool that hands data downstream, write a documented key here.
7. Data layer (datasource.py)
Handle loaders (each returns a DatasetHandle):
load_pkdd_handle()β bundled PKDD (DUCKDB_PATH = artifacts/financial.duckdb), KG attached.load_upload_handle(spec)β CSV/Parquet β per-session DuckDB.load_s3_connector_handle(spec)βread_parquet(s3://β¦)views.load_attached_handle(specs, *, dataset_id, target_path=None)β fold heterogeneous DBs into one DuckDB.load_postgres_connector_handle/load_mongo_connector_handleβ live DB connectors (reuse the materialize helpers).
Multi-DB fold-in (_build_attached_duckdb): each source becomes <alias>_<table>:
- sqlite/duckdb β
ATTACH+CREATE TABLE AS SELECT(_materialize_file_db) - postgres β restore the
.sqldump into an embedded pgserver, copy via postgres scanner (_materialize_postgres_dump); tears down thepgdatadir after. - mongo β read
.bsonserver-lessly, flatten nested β JSON text (_materialize_mongo_dump).
Schema sampling: _describe_tables β _column_samples runs one SELECT * LIMIT 5 per table and stores a representative value on ColumnInfo.sample. This is what lets the planner see "Date: VARCHAR sample='31 Dec 1986, 00:00'" and parse with TRY_CAST/strptime instead of naive casts.
DB cache (build-once) β load_attached_handle(target_path=...): if the master exists at target_path, reuse it; else materialize once (atomic .building β rename). Each run gets an isolated working copy (shutil.copyfile β artifacts/sessions/attach_*.duckdb) so agent writes never mutate the master. The DAB adapter computes the master path + content-fingerprint (see Β§13).
Session files live in artifacts/sessions/ via _new_session_db(); the DAB runner deletes the per-dataset working copy when done (_cleanup_working_copy).
8. Prompts + the token-lean tool selector (prompts.py)
PLANNER_SYSTEMβ the ReAct framing + hard rules + JSON response format.render_tool_catalog(registry)β renders each tool as## name+ description + one-line arg signature (_compact_args, not full JSON Schema β ~64% fewer tokens)- Returns. Enums/Literals keep their choices.
render_dataset_block(ctx)/render_session_context(...)β dataset summary + multi-turn prefix.- Tool selector (gated, LLM-based):
_select_tools(in loop.py) calls a one-shot classifier (TOOL_INTENT_SYSTEM) once per run, cached.parse_tool_intentsβsubset_for_intents(intents, registry)renders core tools + the union of matched groups (_CORE_TOOLS,_TOOL_GROUPS); ambiguous/multi-intent β full catalog. Toggle withLEXSI_TOOL_SELECTOR=0. Dispatch is always against the full registry.
9. LLM client (llm/client.py)
LLMClientprotocol:.complete(system, user) -> LLMResult(text, raw).- Implementations:
StubLLMClient(offline sentinels),EchoGoldLLMClient(tests),LexsiTextClient(real;LexsiTextClient.from_env()), built viafactory("lexsi"|"stub"). - Caching: in-memory LRU (
LEXSI_LLM_CACHE_MAX, default 256) keyed on(model, provider, max_tokens, system, user); optional disk layer whenLEXSI_LLM_CACHE_DIRis set (the bench runners set it β replays across runs). Disable withLEXSI_LLM_CACHE=0. - Timeout/retry (important): every gateway call goes through
_call_with_timeout(daemon-thread guard,LEXSI_LLM_TIMEOUT_Sdefault 180,LEXSI_LLM_RETRIESdefault 1). Without it a stalled gateway request freezes the whole run β this was a real incident. - Model-family handling: GPT-5 / o-series reject
max_tokens; the client POSTs directly withmax_completion_tokens(_post_chat_with_completion_tokens).
10. Multi-turn
SessionContext (session.py) seeds ctx.cache from the
session at run start and lifts changes back at the end. classify_turn
(turn_classifier.py) labels each turn
new/refine/abandon; resolve (reference_resolver.py)
resolves anaphora and hands the planner an explicit reference.
11. Configuration (environment variables)
Required for the real LLM path: SDK_ACCESS_TOKEN, LEXSI_ORG_NAME,
LEXSI_WORKSPACE_NAME, LEXSI_TEXT_PROJECT_NAME, LEXSI_TEXT_PROVIDER,
LEXSI_TEXT_MODEL. Optional: LEXSI_TEXT_PROVIDER_API_KEY, LEXSI_TEXT_MAX_TOKENS.
Tabular: LEXSI_TABULAR_PROJECT_NAME (per-run projects created on train, so usually unset).
| Var | Default | Purpose |
|---|---|---|
LEXSI_TOOL_SELECTOR |
1 |
gate the LLM tool selector (0 = always full catalog) |
LEXSI_LLM_TIMEOUT_S |
180 |
hard timeout per gateway call |
LEXSI_LLM_RETRIES |
1 |
retries on timeout/failure |
LEXSI_LLM_CACHE |
1 |
in-memory LLM cache on/off |
LEXSI_LLM_CACHE_DIR |
β | enable on-disk LLM cache (bench runners set this) |
LEXSI_LLM_CACHE_MAX |
256 |
LRU size |
DUCKDB_PATH |
artifacts/financial.duckdb |
bundled PKDD DB |
DAB_ROOT |
benchmark/dab/DataAgentBench |
DAB checkout |
GRADIO_PORT |
7860 (7850 in Docker) |
UI port |
LEXSI_UI_RUN_TIMEOUT_S, LEXSI_UI_HEARTBEAT_S, LEXSI_UI_SSR |
β | UI tuning |
LEXSI_TRAIN_TIMEOUT_S, LEXSI_PREDICT_TIMEOUT_S, LEXSI_XAI_BUDGET_S |
β | Lexsi pod waits |
LEXSI_ROUNDTRIP_MODEL/_PROVIDER/_DISABLE |
β | datagen Stage-4 back-translator |
(Connector creds β PG_*, MONGO_URI, AWS_*, MYSQL_* β are read by the live connect_datalake paths.)
12. Local dev setup
# deps (extras: lexsi, ui, dev, embeddings, connectors)
uv sync --extra lexsi --extra ui --extra dev --extra embeddings --extra connectors
# bootstrap the bundled PKDD DuckDB (one-time, ~10s)
uv run python -m lexsi_ds.data.loader
# drive the agent from Python (offline stub LLM)
uv run python -c "
from lexsi_ds.agent import AgentContext, AgentLoop
from lexsi_ds.agent.datasource import load_pkdd_handle
from lexsi_ds.llm.client import factory
ctx = AgentContext(dataset=load_pkdd_handle(), run_id='local', llm=factory('stub'))
print(AgentLoop(ctx=ctx).run('Top 5 districts by number of accounts.').final_answer)
"
# UI
uv run python -m app.gradio_app # http://localhost:7860
# tests (LLM-gated ones skip without the Lexsi env)
uv run pytest -q
13. Benchmark mechanisms (all of them)
There are three harnesses. All write full trajectories so failures are debuggable.
13a. DataAgentBench (external; Pass@1) β benchmark/dab/
What it is: 17 datasets across sqlite/duckdb/postgres/mongo; each task ships a
question + ground_truth.csv + its own validate.py. We score Pass@1 by calling
that validate.py on the agent's final answer.
Code:
- adapter.py:
load_dataset(dir)βDabDataset(parsesdb_config.yamlintoAttachSpecs viaparse_db_config, loads each task'svalidate.pyvia_load_validate).build_handle(dataset, cache_dir=...)βload_attached_handlewith a content-keyed master path (<cache_dir>/<dataset>__<_fingerprint>.duckdb). - runner.py:
mainβrun_dataset(builds the handle once per dataset, loops tasks) βrun_task(composes the question with the DAB description + table-name mapping, runsAgentLoop, callsvalidate). DB working copy cleaned per dataset (_cleanup_working_copy).
Setup + run:
git clone https://github.com/ucbepic/DataAgentBench benchmark/dab/DataAgentBench
uv sync --extra connectors # needed for the postgres/mongo datasets
export SDK_ACCESS_TOKEN=β¦ LEXSI_WORKSPACE_NAME=β¦ LEXSI_TEXT_PROJECT_NAME=β¦ # + org/model/provider
# one dataset / one task / sweep all
uv run python -m benchmark.dab.runner --dataset DEPS_DEV_V1 --llm lexsi
uv run python -m benchmark.dab.runner --dataset DEPS_DEV_V1 --query query1 --llm lexsi
uv run python -m benchmark.dab.runner --all --llm lexsi
# the sqlite/duckdb "core" (5 datasets / 17 tasks, no connectors extra), real Pass@1
uv run python -m benchmark.dab.runner --all --db-types sqlite,duckdb --llm lexsi \
--iterations 3 --max-steps 20 --log-dir artifacts/dab_runs/run1
Flags: --dataset/--all, --db-types t1,t2 (filter --all), --query,
--iterations, --max-steps (default 12), --use-hints, --log-dir,
--db-cache-dir/--no-db-cache, --llm-cache-dir/--no-llm-cache, -v.
Caching (two layers, both speed re-runs):
- DB cache
artifacts/dab_dbs/β each dataset's fold-in materialized once; reused across runs; per-run working copy auto-deleted. - LLM disk cache
artifacts/llm_cache/β gateway responses keyed on the prompt; a failed sweep resumes cheaply.
Outputs: per-task trace JSON in --log-dir (<dataset>_<query>_iter<N>.json β full
steps, prompts, raw LLM output, payloads, validator verdict) + a console scoreboard
(per-dataset pass counts, wall vs agent time, LLM reuse). Latest results writeup:
dab_results_v1.md; integration detail: dab_integration.md.
13b. PKDD-Curated (internal) β benchmark/pkdd_runner.py
What it is: our own suite over the bundled PKDD dataset, scored against the pinned
fixture benchmark/fixtures/pkdd_seed.duckdb. Question schema in
benchmark/datagen/types.py (Row); the live set is
bench/pkdd/questions.yaml (40 items: analytic,
predictive, adversarial).
Scoring (pkdd_runner.py, by scoring_rule):
row_set_equality(analytic) β_score_row_set: agent'slast_sql_resultvsgold_sqlexecuted on the fixture, as a multiset of sorted-value rows (column-rename tolerant; lenient scalar fallback).exact_match(numeric) β_score_numeric: reference value withintolerance.judge_rubric(adversarial) β_score_behavioral: heuristic β fail if it used aforbidden_toolor answered when it should clarify/refuse.ndcg@k(predictive) β captured but not auto-scored unless--include-predictive(those train real models, slow). Plan precision/recall computed for every row.
Run:
export SDK_ACCESS_TOKEN=β¦ LEXSI_WORKSPACE_NAME=β¦ LEXSI_TEXT_PROJECT_NAME=β¦ # + org/model/provider
uv run python -m benchmark.pkdd_runner --llm lexsi --log-dir artifacts/pkdd_runs
# scope / debug
uv run python -m benchmark.pkdd_runner --tier analytic --limit 5 --llm lexsi
uv run python -m benchmark.pkdd_runner --include-predictive --llm lexsi
Flags: --tier, --include-predictive, --limit, --llm, --log-dir,
--llm-cache-dir/--no-llm-cache, -v. Outputs: per-row trace JSON + a per-tier
scoreboard (pass/scored + plan p/r). The agent queries the fixture (handle from
load_pkdd_handle() with duckdb_path repointed to the fixture).
Note:
bench/pkdd/sessions.yaml(multi-turn) andp2_feature_engineering.yaml(separateassertionsschema) are not run bypkdd_runneryet.
13c. Datagen pipeline (generates the PKDD questions) β benchmark/datagen/
Template-based; authoring is per-template, so ~10 templates yield 500+ rows. Stages: column inventory β templates β instantiate β execute gold SQL on the pinned fixture β LLM round-trip verify β predictive enrichment β multi-turn compose β emit YAML.
uv run python -m benchmark.datagen pin-fixture # snapshot artifacts/financial.duckdb
uv run python -m benchmark.datagen run --skip-roundtrip # fast (skip LLM Stage 4)
uv run python -m benchmark.datagen run # full (needs Lexsi text env)
Every emitted row carries fixture_sha; a scoreboard from a different sha isn't comparable.
The grading design (5 axes, tiers, pass^k) lives in v1_benchmarking.md.
13d. SDK repro / probe scripts β scripts/repro_*
Standalone, self-contained scripts that hit the live Lexsi SDK against a real trained model and report OK/FAIL per call. They're how we validate the SDK-backed tools and file platform bugs. Each catches every call so one run produces a complete report; each leaves its project on the platform for Activity-Log inspection.
scripts/repro_tabicl/β reproduces TabICLmodel_inference+ per-case XAI failures (emptyExceptionon T4 pods, inference hang/timeout,case_predictValidationError). Ships its own PKDD CSVs indata/.scripts/repro_model_lifecycle/β probe harness for the model-lifecycle tools (list_available_models,select_active_model,compare_models,check_drift,monitor_performance) + the future explanation methods. Trains TabICL + XGBoost on the (local) PKDD CSVs and calls each SDK method. Two scripts:repro_model_lifecycle.py(TabICL-first, the clean path) andrepro_model_lifecycle_xgboost_first.py(XGBoost-first, reproduces the#05-013cascade). Run:uv run python scripts/repro_model_lifecycle/repro_model_lifecycle.py(env:SDK_ACCESS_TOKEN,LEXSI_WORKSPACE_NAME,LEXSI_ORG_NAME).
Live SDK findings from the lifecycle repro (last run 13 OK / 4 FAIL):
#05-013β classic-ML (XGBoost) explainability scans every stored column, including the server-injectedtagcolumn, and failsfloat()on a non-numeric one. The SDK uploads clean data + an explicitfeature_include(tabular.py:285,368); explainability ignores it. Model still builds (non-fatal), butupload_dataraises and rolls back the upload β cascade (train_modelβ "Upload files first", predict upload β "Project Config is required"). TabICL's foundation path doesn't trip it. Fix for the agent: treat#05-013as success-with-warning, and train foundation-first / drop or encode non-numeric columns.evals_tabular(<foundation model>)βException: 'model'(KeyError) β works for classic ML, breaks for TabICL. Blockscompare_modelson foundation models.get_model_performance(...)β'utf-8' codec can't decode β¦β response decode bug. Blocksmonitor_performance.- per-case XAI (
case_predict,get_feature_importance) needs (a) the model active + an inference run first (else "Inference status inactive"), and (b) SHAP computed at train (xai=["shap"]) β which itself trips#05-013for classic ML.case_predict(risk_policies=True)works without SHAP.
The five lifecycle tools degrade gracefully against these (per-model error capture
in compare_models; actionable ok=False in monitor_performance). Record new
findings in sdk_issues.md.
14. Deploy
- Docker (Dockerfile):
python:3.10-slim, CPU-only torch, pinned deps, bakesartifacts/financial.duckdb(the PKDD source TSVs are Git-LFS pointers), serves Gradio on 7850. Build for the platform arch:docker buildx build --platform linux/amd64 --provenance=false --sbom=false -t bplexsi/lexsi-ds-agent:prod_vN --push . - HF Space β a Gradio Space (
README.mdfrontmattersdk: gradio,app_file: app/gradio_app.py); the Dockerfile is ignored there. - Lexsi platform β config.yaml: one container on port 7850; the
real
SDK_ACCESS_TOKENgoes in the platform env tab (the repo keeps a placeholder).
15. Sharp edges (things that bit us)
- No LLM timeout = frozen run. Always keep
_call_with_timeoutin the path (Β§9). - Tool arg-name drift. The planner reuses sibling names (
table_or_label,source_columns,prompt); mismatched schemas βvalidation_errorloops. Alias them. - DataFrame truthiness.
x = cache.get(a) or cache.get(b)raises on a DataFrame; use explicitis None. - String-typed dates/numbers. Source columns are often VARCHAR; rely on the
ColumnInfo.sample+ theTRY_CAST/try_strptimeguidance in the text_to_sql prompt. - DB cache contamination. The master must stay pristine β agents write to the working copy, never the master (Β§7).
artifacts/sessions/growth. Working copies/pgdata are cleaned now; if you add a loader, clean up after it.- Single-run bench numbers are noisy (DAB sits in an 8β9/17 band, 11/17 ceiling).
Use
--iterationsand read the per-task/failure breakdown, not the headline.
16. "Where do I look forβ¦"
| Want to⦠| Go to |
|---|---|
| change the planner behavior / rules | prompts.py (PLANNER_SYSTEM) |
| add/modify a tool | tools/<name>.py + tools/__init__.py (+ _TOOL_GROUPS) |
| change which tools the planner sees | prompts.py selector (_CORE_TOOLS, _TOOL_GROUPS) |
| connect a new datasource | datasource.py (loaders) + connect_datalake tool |
| change LLM / caching / timeout | llm/client.py |
| run/extend DAB | benchmark/dab/{adapter,runner}.py |
| run/extend PKDD | benchmark/pkdd_runner.py + bench/pkdd/questions.yaml |
| regenerate PKDD questions | benchmark/datagen/ |
| model-lifecycle tools (list/select/compare/drift/monitor) | tools/{list_available_models,select_active_model,compare_models,check_drift,monitor_performance}.py + tools/_tabular_common.py |
| probe the live SDK / file an SDK bug | scripts/repro_model_lifecycle/, scripts/repro_tabicl/ β docs/sdk_issues.md |
| the UI | app/gradio_app.py |
| roadmap / what's next | v1_tools.md, tools_remaining_and_dab_hardening.md |