Spaces:
Paused
Paused
File size: 7,951 Bytes
3f5e250 49dbcad 3f5e250 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | # server/upload_handler.py
import json, re, io, os
import pandas as pd
import numpy as np
from openai import OpenAI
GLOBAL_TASKS_CACHE = {}
API_BASE_URL = os.environ.get("API_BASE_URL", "https://router.huggingface.co/v1")
MODEL_NAME = os.environ.get("MODEL_NAME", "Qwen/Qwen2.5-Coder-32B-Instruct")
HF_TOKEN = os.environ.get("HF_TOKEN", "")
def get_client():
return OpenAI(base_url=API_BASE_URL, api_key=os.environ.get("HF_TOKEN", HF_TOKEN) or "dummy")
def read_csv_file(content: bytes) -> pd.DataFrame:
return pd.read_csv(io.BytesIO(content))
def read_notebook_file(content: bytes):
try:
nb = json.loads(content.decode("utf-8"))
except:
nb = {"cells": []}
cells = nb.get("cells", [])
code_cells = [
"".join(cell.get("source", []))
for cell in cells
if cell.get("cell_type") == "code"
]
return nb, "\n\n# --- next cell ---\n\n".join(code_cells)
def analyze_and_generate_task(df: pd.DataFrame, hint: str = "") -> dict:
# ββ Truncate to stay well under 128k token limit ββββββββββββββ
MAX_COLS = 30
all_cols = list(df.columns)
cols = all_cols[:MAX_COLS] # cap columns
df_trim = df[cols] # work on trimmed df
dtypes = {col: str(dt) for col, dt in df_trim.dtypes.items()}
nulls = {col: int(v) for col, v in df_trim.isnull().sum().items()}
sample = df_trim.head(3).to_json(orient="records", date_format="iso")[:3000] # cap sample
shape = {"rows": len(df), "cols": len(all_cols)}
omitted = f" (showing first {MAX_COLS} of {len(all_cols)})" if len(all_cols) > MAX_COLS else ""
prompt = f"""You are a senior data engineer. Analyze this raw dataset and create a data cleaning task.
DATASET:
- Shape: {shape}
- Columns{omitted}: {cols}
- Dtypes: {json.dumps(dtypes)[:1000]}
- Null counts: {json.dumps(nulls)[:500]}
- Sample (3 rows): {sample}
- User hint: "{hint[:200]}"
TASK: Identify REAL data quality issues (missing values, bad formatting, outliers, weird types) in this dataset. Then write the clean, production-ready \`correct_code\` pipeline that fixes these exact issues.
Return ONLY valid JSON (no markdown, no backticks):
{{
"task_id": "clean_csv",
"title": "Clean Data Issues",
"description": "2-3 sentences describing the data anomalies found and what needs to be cleaned.",
"issues_found": ["Missing values in col X", "String formatting inconsistency in col Y", "Dates not parsed in col Z"],
"broken_code": "import pandas as pd\\nimport numpy as np\\n\\ndef fix_pipeline(df: pd.DataFrame) -> pd.DataFrame:\\n df = df.copy()\\n # TODO: Implement cleaning logic\\n return df",
"correct_code": "import pandas as pd\\nimport numpy as np\\n\\ndef fix_pipeline(df: pd.DataFrame) -> pd.DataFrame:\\n df = df.copy()\\n # exact pandas logic fixing the above issues\\n return df",
"difficulty": "medium"
}}
CRITICAL RULES:
- ONLY use column names from this list: {cols}
- `broken_code` MUST be a blank template `def fix_pipeline(df): return df.copy()`
- `correct_code` must fix all identified anomalies and return a clean DataFrame
- Keep it practical and robust
- DO NOT invent arbitrary sentinel values like `-1` or `'Not Started'`. If a column has NaNs because it resembles an Excel grouped layout (e.g. `Week 1`, `NaN`, `NaN`), use forward-fill (`.ffill()`).
- DO NOT drastically delete columns or rows. Your job is to format the data properly and correct mistakes, not change the entire structure.
- If there are NO obvious issues and the user hint is empty, return an empty `issues_found` array `[]` and DO NOT invent fake issues."""
client = get_client()
resp = client.chat.completions.create(
model=MODEL_NAME,
messages=[{"role": "user", "content": prompt}],
temperature=0.2,
max_tokens=2000,
)
raw = resp.choices[0].message.content or ""
# Strip any markdown fences
raw = re.sub(r"```(?:json)?\s*", "", raw).strip()
# Extract JSON object
match = re.search(r'\{.*\}', raw, re.DOTALL)
if match:
return json.loads(match.group())
return json.loads(raw)
def analyze_notebook_and_generate_task(notebook_code: str, hint: str = "") -> dict:
# Cap notebook code to ~2000 chars to avoid context overflow
code_sample = notebook_code[:2000]
prompt = f"""You are a senior data engineer. Analyze this Jupyter notebook and create a pipeline debugging task.
NOTEBOOK CODE:
{code_sample}
User hint: "{hint[:200]}"
Return ONLY valid JSON (no markdown, no backticks):
{{
"task_id": "user_upload",
"title": "short title based on what notebook does",
"description": "2-3 sentences about this pipeline and its bugs",
"issues_found": ["bug 1", "bug 2", "bug 3"],
"sample_columns": ["exact_col_1", "exact_col_2", "exact_col_3"],
"broken_code": "import pandas as pd\\nimport numpy as np\\n\\ndef fix_pipeline(df: pd.DataFrame) -> pd.DataFrame:\\n df = df.copy()\\n # broken code here\\n return df",
"correct_code": "import pandas as pd\\nimport numpy as np\\n\\ndef fix_pipeline(df: pd.DataFrame) -> pd.DataFrame:\\n df = df.copy()\\n # correct code here\\n return df",
"difficulty": "medium"
}}
CRITICAL RULES:
- sample_columns MUST list EVERY column name that broken_code and correct_code reference via df['col']
- broken_code and correct_code MUST ONLY use columns listed in sample_columns
- Extract REAL pipeline logic from the notebook code
- broken_code must run without crashing but give wrong results
- correct_code fixes all bugs
- Both must define fix_pipeline(df) -> pd.DataFrame
- If there are NO obvious issues and the user hint is empty, return an empty `issues_found` array `[]`."""
client = get_client()
resp = client.chat.completions.create(
model=MODEL_NAME,
messages=[{"role": "user", "content": prompt}],
temperature=0.2,
max_tokens=2000,
)
raw = resp.choices[0].message.content or ""
raw = re.sub(r"```(?:json)?\s*", "", raw).strip()
match = re.search(r'\{.*\}', raw, re.DOTALL)
if match:
return json.loads(match.group())
return json.loads(raw)
def build_dynamic_task(task_def: dict, input_df: pd.DataFrame) -> dict:
correct_code = task_def["correct_code"]
local_ns = {"pd": pd, "np": np}
correct_code_works = False
try:
exec(compile(correct_code, "<correct>", "exec"), local_ns)
fix_fn = local_ns.get("fix_pipeline")
if not fix_fn:
raise ValueError("fix_pipeline not defined in correct_code")
expected_df = fix_fn(input_df.copy())
if not isinstance(expected_df, pd.DataFrame):
raise ValueError("correct_code must return DataFrame")
correct_code_works = True
except Exception as e:
# Fallback: expected = input (at least schema matches)
print(f"[build_dynamic_task] correct_code failed: {e}")
expected_df = input_df.copy()
diff_map = {"easy": 10, "medium": 15, "hard": 20}
difficulty = task_def.get("difficulty", "medium")
_inp = input_df.copy()
_exp = expected_df.copy()
desc = task_def.get("description", "Fix the data pipeline.")
if not correct_code_works:
desc += f"\n\nNote: The reference solution had errors. Available columns are: {list(input_df.columns)}"
return {
"task_id": "user_upload",
"description": desc,
"title": task_def.get("title", "User Upload Task"),
"broken_code": task_def["broken_code"],
"correct_code": correct_code,
"issues_found": task_def.get("issues_found", []),
"difficulty": difficulty,
"get_input": lambda: _inp.copy(),
"get_expected": lambda: _exp.copy(),
"max_steps": diff_map.get(difficulty, 15),
"input_is_dict": False,
"expected_df": _exp,
}
|