Update app.py
Browse files
app.py
CHANGED
|
@@ -41,7 +41,8 @@ import seaborn as sns # noqa: E402
|
|
| 41 |
from matplotlib.ticker import (FuncFormatter, LogLocator, # noqa: E402
|
| 42 |
NullFormatter, ScalarFormatter)
|
| 43 |
from huggingface_hub import HfApi, hf_hub_download # noqa: E402
|
| 44 |
-
from transformers import AutoModelForCausalLM, AutoTokenizer # noqa: E402
|
|
|
|
| 45 |
|
| 46 |
# --------------------------------------------------------------------------- #
|
| 47 |
# Model β must be placed on cuda at module level for ZeroGPU
|
|
@@ -64,19 +65,57 @@ print(f"[startup] device={DEVICE} dtype={DTYPE}")
|
|
| 64 |
PLOTS_DIR = "plots"
|
| 65 |
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
@torch.inference_mode()
|
| 68 |
-
def generate(system: str, user: str, max_new_tokens: int = 900
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
msgs = [{"role": "system", "content": system}, {"role": "user", "content": user}]
|
| 71 |
text = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
|
| 72 |
inputs = tok(text, return_tensors="pt").to(model.device)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
out = model.generate(
|
| 74 |
**inputs,
|
| 75 |
max_new_tokens=max_new_tokens,
|
| 76 |
do_sample=False, # greedy -> reproducible
|
| 77 |
pad_token_id=tok.pad_token_id,
|
|
|
|
| 78 |
)
|
| 79 |
-
return tok.decode(out[0][
|
| 80 |
|
| 81 |
|
| 82 |
# --------------------------------------------------------------------------- #
|
|
@@ -354,7 +393,8 @@ def format_error(err: str, code: str) -> str:
|
|
| 354 |
# --------------------------------------------------------------------------- #
|
| 355 |
# 5. Deterministic facts and tables
|
| 356 |
# --------------------------------------------------------------------------- #
|
| 357 |
-
REDUNDANT_R = 0.99
|
|
|
|
| 358 |
|
| 359 |
|
| 360 |
def fmt_num(v):
|
|
@@ -415,7 +455,13 @@ def make_tables(df: pd.DataFrame, max_card: int = 6) -> dict:
|
|
| 415 |
for c in df.columns if df[c].nunique(dropna=True) <= max_card
|
| 416 |
for b in bins if b != c
|
| 417 |
]
|
| 418 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
|
| 420 |
real, dup = corr_pairs(df)
|
| 421 |
t["corr"] = (pd.DataFrame(real[:8]).to_markdown(index=False)
|
|
@@ -632,7 +678,8 @@ def _pipeline(instruction: str, dataset: str):
|
|
| 632 |
*BLANK)
|
| 633 |
|
| 634 |
code = sanitize(extract_code(
|
| 635 |
-
generate(CODE_SYSTEM, f"{context}\n\nTask: {instruction}",
|
|
|
|
| 636 |
res = run_code(code, df)
|
| 637 |
yield (f"Attempt 1: {'ok' if res['ok'] else 'failed'}. Executing β¦",
|
| 638 |
None, code, res["stdout"], res["plots"], "", "")
|
|
@@ -643,7 +690,8 @@ def _pipeline(instruction: str, dataset: str):
|
|
| 643 |
res["plots"], "", "")
|
| 644 |
fix = (f"{context}\n\nThis code failed:\n```python\n{code}\n```\n\n"
|
| 645 |
f"Error:\n{format_error(res['error'], code)}\n\nReturn the corrected script.")
|
| 646 |
-
code = sanitize(extract_code(
|
|
|
|
| 647 |
res = run_code(code, df)
|
| 648 |
|
| 649 |
plots = sorted(glob.glob(f"{PLOTS_DIR}/*.png"))
|
|
@@ -698,6 +746,7 @@ def run_agent(instruction: str, dataset: str):
|
|
| 698 |
# --------------------------------------------------------------------------- #
|
| 699 |
DEFAULT_INSTRUCTION = ("Run a comprehensive exploratory data analysis, highlighting "
|
| 700 |
"missing values, distributions, and key feature correlations.")
|
|
|
|
| 701 |
|
| 702 |
QS_DIR = "quickstarts"
|
| 703 |
QUICKSTARTS = [
|
|
@@ -706,10 +755,10 @@ QUICKSTARTS = [
|
|
| 706 |
"blurb": "77% of `cabin` is missing, but the raw file hides it as `''`.",
|
| 707 |
"dataset": "mstz/titanic",
|
| 708 |
"instruction": DEFAULT_INSTRUCTION},
|
| 709 |
-
{"slug": "
|
| 710 |
-
"label": "
|
| 711 |
-
"blurb": "
|
| 712 |
-
"dataset": "
|
| 713 |
"instruction": "Explore this dataset: missing values, distributions, correlations."},
|
| 714 |
{"slug": "housing",
|
| 715 |
"label": "π Canada housing β skewed prices",
|
|
@@ -756,7 +805,7 @@ def quickstart_handler(spec: dict):
|
|
| 756 |
|
| 757 |
|
| 758 |
def reset_form():
|
| 759 |
-
return DEFAULT_INSTRUCTION,
|
| 760 |
|
| 761 |
|
| 762 |
# --------------------------------------------------------------------------- #
|
|
@@ -797,7 +846,7 @@ with gr.Blocks(title="EDAgent", theme=gr.themes.Soft(), css=CSS) as demo:
|
|
| 797 |
with gr.Row():
|
| 798 |
instruction = gr.Textbox(label="Prompt instruction", value=DEFAULT_INSTRUCTION,
|
| 799 |
lines=3, scale=3)
|
| 800 |
-
dataset = gr.Textbox(label="Hugging Face dataset id", value=
|
| 801 |
placeholder="owner/name", lines=1, scale=1)
|
| 802 |
with gr.Row():
|
| 803 |
run_btn = gr.Button("π Run EDA Agent", variant="primary", size="lg", scale=4)
|
|
|
|
| 41 |
from matplotlib.ticker import (FuncFormatter, LogLocator, # noqa: E402
|
| 42 |
NullFormatter, ScalarFormatter)
|
| 43 |
from huggingface_hub import HfApi, hf_hub_download # noqa: E402
|
| 44 |
+
from transformers import (AutoModelForCausalLM, AutoTokenizer, # noqa: E402
|
| 45 |
+
StoppingCriteria, StoppingCriteriaList)
|
| 46 |
|
| 47 |
# --------------------------------------------------------------------------- #
|
| 48 |
# Model β must be placed on cuda at module level for ZeroGPU
|
|
|
|
| 65 |
PLOTS_DIR = "plots"
|
| 66 |
|
| 67 |
|
| 68 |
+
class StopOnClosingFence(StoppingCriteria):
|
| 69 |
+
"""Stop once a fenced block has been opened and closed.
|
| 70 |
+
|
| 71 |
+
Decoding the tail is more reliable than matching token ids: ``` tokenizes
|
| 72 |
+
differently depending on what precedes it. Decoding on every step would be
|
| 73 |
+
wasteful, so we only check every `check_every` tokens.
|
| 74 |
+
"""
|
| 75 |
+
|
| 76 |
+
def __init__(self, tokenizer, prompt_len, fences=2, check_every=8):
|
| 77 |
+
self.tok, self.prompt_len = tokenizer, prompt_len
|
| 78 |
+
self.fences, self.check_every = fences, check_every
|
| 79 |
+
self._step = 0
|
| 80 |
+
|
| 81 |
+
def __call__(self, input_ids, scores, **kwargs):
|
| 82 |
+
self._step += 1
|
| 83 |
+
done = False
|
| 84 |
+
if self._step % self.check_every == 0:
|
| 85 |
+
text = self.tok.decode(input_ids[0][self.prompt_len:],
|
| 86 |
+
skip_special_tokens=True)
|
| 87 |
+
done = text.count("```") >= self.fences
|
| 88 |
+
# one flag per batch row: StoppingCriteriaList ORs the results together
|
| 89 |
+
return torch.full((input_ids.shape[0],), done,
|
| 90 |
+
dtype=torch.bool, device=input_ids.device)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
@torch.inference_mode()
|
| 94 |
+
def generate(system: str, user: str, max_new_tokens: int = 900,
|
| 95 |
+
stop_fences: int = 0) -> str:
|
| 96 |
+
"""Single entry point to the LLM. Returns only the newly generated text.
|
| 97 |
+
|
| 98 |
+
stop_fences=2 halts as soon as the closing ``` of a code block is emitted,
|
| 99 |
+
instead of running on to max_new_tokens with trailing prose.
|
| 100 |
+
"""
|
| 101 |
msgs = [{"role": "system", "content": system}, {"role": "user", "content": user}]
|
| 102 |
text = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
|
| 103 |
inputs = tok(text, return_tensors="pt").to(model.device)
|
| 104 |
+
prompt_len = inputs["input_ids"].shape[1]
|
| 105 |
+
|
| 106 |
+
criteria = None
|
| 107 |
+
if stop_fences:
|
| 108 |
+
criteria = StoppingCriteriaList(
|
| 109 |
+
[StopOnClosingFence(tok, prompt_len, fences=stop_fences)])
|
| 110 |
+
|
| 111 |
out = model.generate(
|
| 112 |
**inputs,
|
| 113 |
max_new_tokens=max_new_tokens,
|
| 114 |
do_sample=False, # greedy -> reproducible
|
| 115 |
pad_token_id=tok.pad_token_id,
|
| 116 |
+
stopping_criteria=criteria,
|
| 117 |
)
|
| 118 |
+
return tok.decode(out[0][prompt_len:], skip_special_tokens=True)
|
| 119 |
|
| 120 |
|
| 121 |
# --------------------------------------------------------------------------- #
|
|
|
|
| 393 |
# --------------------------------------------------------------------------- #
|
| 394 |
# 5. Deterministic facts and tables
|
| 395 |
# --------------------------------------------------------------------------- #
|
| 396 |
+
REDUNDANT_R = 0.99 # |r| at or above this is a duplicate encoding, not a finding
|
| 397 |
+
MAX_GROUP_BLOCKS = 10 # one-hot-heavy datasets otherwise emit dozens of tables
|
| 398 |
|
| 399 |
|
| 400 |
def fmt_num(v):
|
|
|
|
| 455 |
for c in df.columns if df[c].nunique(dropna=True) <= max_card
|
| 456 |
for b in bins if b != c
|
| 457 |
]
|
| 458 |
+
if blocks:
|
| 459 |
+
t["groups"] = "\n\n".join(blocks[:MAX_GROUP_BLOCKS])
|
| 460 |
+
if len(blocks) > MAX_GROUP_BLOCKS:
|
| 461 |
+
t["groups"] += (f"\n\n_+{len(blocks) - MAX_GROUP_BLOCKS} more group "
|
| 462 |
+
"comparisons omitted._")
|
| 463 |
+
else:
|
| 464 |
+
t["groups"] = "_No low-cardinality groupings._"
|
| 465 |
|
| 466 |
real, dup = corr_pairs(df)
|
| 467 |
t["corr"] = (pd.DataFrame(real[:8]).to_markdown(index=False)
|
|
|
|
| 678 |
*BLANK)
|
| 679 |
|
| 680 |
code = sanitize(extract_code(
|
| 681 |
+
generate(CODE_SYSTEM, f"{context}\n\nTask: {instruction}",
|
| 682 |
+
max_new_tokens=1200, stop_fences=2)))
|
| 683 |
res = run_code(code, df)
|
| 684 |
yield (f"Attempt 1: {'ok' if res['ok'] else 'failed'}. Executing β¦",
|
| 685 |
None, code, res["stdout"], res["plots"], "", "")
|
|
|
|
| 690 |
res["plots"], "", "")
|
| 691 |
fix = (f"{context}\n\nThis code failed:\n```python\n{code}\n```\n\n"
|
| 692 |
f"Error:\n{format_error(res['error'], code)}\n\nReturn the corrected script.")
|
| 693 |
+
code = sanitize(extract_code(
|
| 694 |
+
generate(FIX_SYSTEM, fix, max_new_tokens=1200, stop_fences=2)))
|
| 695 |
res = run_code(code, df)
|
| 696 |
|
| 697 |
plots = sorted(glob.glob(f"{PLOTS_DIR}/*.png"))
|
|
|
|
| 746 |
# --------------------------------------------------------------------------- #
|
| 747 |
DEFAULT_INSTRUCTION = ("Run a comprehensive exploratory data analysis, highlighting "
|
| 748 |
"missing values, distributions, and key feature correlations.")
|
| 749 |
+
DEFAULT_DATASET = "Kogann/stockmatch-synthetic"
|
| 750 |
|
| 751 |
QS_DIR = "quickstarts"
|
| 752 |
QUICKSTARTS = [
|
|
|
|
| 755 |
"blurb": "77% of `cabin` is missing, but the raw file hides it as `''`.",
|
| 756 |
"dataset": "mstz/titanic",
|
| 757 |
"instruction": DEFAULT_INSTRUCTION},
|
| 758 |
+
{"slug": "stockmatch",
|
| 759 |
+
"label": "π StockMatch β synthetic stocks",
|
| 760 |
+
"blurb": "12,500 generated stocks β does synthetic data carry real structure?",
|
| 761 |
+
"dataset": "Kogann/stockmatch-synthetic",
|
| 762 |
"instruction": "Explore this dataset: missing values, distributions, correlations."},
|
| 763 |
{"slug": "housing",
|
| 764 |
"label": "π Canada housing β skewed prices",
|
|
|
|
| 805 |
|
| 806 |
|
| 807 |
def reset_form():
|
| 808 |
+
return DEFAULT_INSTRUCTION, DEFAULT_DATASET
|
| 809 |
|
| 810 |
|
| 811 |
# --------------------------------------------------------------------------- #
|
|
|
|
| 846 |
with gr.Row():
|
| 847 |
instruction = gr.Textbox(label="Prompt instruction", value=DEFAULT_INSTRUCTION,
|
| 848 |
lines=3, scale=3)
|
| 849 |
+
dataset = gr.Textbox(label="Hugging Face dataset id", value=DEFAULT_DATASET,
|
| 850 |
placeholder="owner/name", lines=1, scale=1)
|
| 851 |
with gr.Row():
|
| 852 |
run_btn = gr.Button("π Run EDA Agent", variant="primary", size="lg", scale=4)
|