Kogann commited on
Commit
c19209b
Β·
verified Β·
1 Parent(s): ea0982b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -14
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) -> str:
69
- """Single entry point to the LLM. Returns only the newly generated text."""
 
 
 
 
 
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][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
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 # |r| at or above this is a duplicate encoding, not a finding
 
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
- t["groups"] = "\n\n".join(blocks) if blocks else "_No low-cardinality groupings._"
 
 
 
 
 
 
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}", max_new_tokens=1200)))
 
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(generate(FIX_SYSTEM, fix, max_new_tokens=1200)))
 
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": "iris",
710
- "label": "🌸 Iris β€” a clean baseline",
711
- "blurb": "No missing values, a row-id column the agent drops, tight correlations.",
712
- "dataset": "scikit-learn/iris",
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, "mstz/titanic"
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="mstz/titanic",
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)