import os import sys import json from pathlib import Path import gradio as gr import time # Make your repo importable (expecting a folder named causal-agent at repo root) sys.path.append(str(Path(__file__).parent / "causal-agent")) from auto_causal.agent import run_causal_analysis # uses env for provider/model # -------- LLM config (OpenAI only; key via HF Secrets) -------- os.environ.setdefault("LLM_PROVIDER", "openai") os.environ.setdefault("LLM_MODEL", "gpt-4o") # Lazy import to avoid import-time errors if key missing def _get_openai_client(): if os.getenv("LLM_PROVIDER", "openai") != "openai": raise RuntimeError("Only LLM_PROVIDER=openai is supported in this demo.") if not os.getenv("OPENAI_API_KEY"): raise RuntimeError("Missing OPENAI_API_KEY (set as a Space Secret).") try: # OpenAI SDK v1+ from openai import OpenAI return OpenAI() except Exception as e: raise RuntimeError(f"OpenAI SDK not available: {e}") # -------- System prompt you asked for (verbatim) -------- SYSTEM_PROMPT = """You are an expert in statistics and causal inference. You will be given: 1) The original research question. 2) The analysis method used. 3) The estimated effects, confidence intervals, standard errors, and p-values for each treatment group compared to the control group. 4) A brief dataset description. Your task is to produce a clear, concise, and non-technical summary that: - Directly answers the research question. - States whether the effect is statistically significant. - Quantifies the effect size and explains what it means in practical terms (e.g., percentage point change). - Mentions the method used in one sentence. - Optionally ranks the treatment effects from largest to smallest if multiple treatments exist. Formatting rules: - Use bullet points or short paragraphs. - Report effect sizes to two decimal places. - Clearly state the interpretation in plain English without technical jargon. Example Output Structure: - **Method:** [Name of method + 1-line rationale] - **Key Finding:** [Main answer to the research question] - **Details:** - [Treatment name]: +X.XX percentage points (95% CI: [L, U]), p < 0.001 — [Significance comment] - … - **Rank Order of Effects:** [Largest → Smallest] """ def _extract_minimal_payload(agent_result: dict) -> dict: """ Extract the minimal, LLM-friendly payload from run_causal_analysis output. Falls back gracefully if any fields are missing. """ # Try both top-level and nested (your JSON showed both patterns) res = agent_result or {} results = res.get("results", {}) if isinstance(res.get("results"), dict) else {} inner = results.get("results", {}) if isinstance(results.get("results"), dict) else {} vars_ = results.get("variables", {}) if isinstance(results.get("variables"), dict) else {} dataset_analysis = results.get("dataset_analysis", {}) if isinstance(results.get("dataset_analysis"), dict) else {} # Pull best-available fields question = ( results.get("original_query") or dataset_analysis.get("original_query") or res.get("query") or "N/A" ) method = ( inner.get("method_used") or res.get("method_used") or results.get("method_used") or "N/A" ) effect_estimate = ( inner.get("effect_estimate") or res.get("effect_estimate") or {} ) confidence_interval = ( inner.get("confidence_interval") or res.get("confidence_interval") or {} ) standard_error = ( inner.get("standard_error") or res.get("standard_error") or {} ) p_value = ( inner.get("p_value") or res.get("p_value") or {} ) dataset_desc = ( results.get("dataset_description") or res.get("dataset_description") or "N/A" ) return { "original_question": question, "method_used": method, "estimates": { "effect_estimate": effect_estimate, "confidence_interval": confidence_interval, "standard_error": standard_error, "p_value": p_value, }, "dataset_description": dataset_desc, } def _format_effects_md(effect_estimate: dict) -> str: """ Minimal human-readable view of effect estimates for display. """ if not effect_estimate or not isinstance(effect_estimate, dict): return "_No effect estimates found._" # Render as bullet list lines = [] for k, v in effect_estimate.items(): try: lines.append(f"- **{k}**: {float(v):+.4f}") except Exception: lines.append(f"- **{k}**: {v}") return "\n".join(lines) def _summarize_with_llm(payload: dict) -> str: """ Calls OpenAI with the provided SYSTEM_PROMPT and the JSON payload as the user message. Returns the model's text, or raises on error. """ client = _get_openai_client() model_name = os.getenv("LLM_MODEL", "gpt-4o-mini") user_content = ( "Summarize the following causal analysis results:\n\n" + json.dumps(payload, indent=2, ensure_ascii=False) ) # Use Chat Completions for broad compatibility resp = client.chat.completions.create( model=model_name, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_content}, ], temperature=0 ) text = resp.choices[0].message.content.strip() return text def run_agent(query: str, csv_path: str, dataset_description: str): """ Modified to use yield for progressive updates and immediate feedback """ # Immediate feedback - show processing has started processing_html = """
{method}