| import json |
| import os |
| import time |
| from typing import List, Tuple |
|
|
| import gradio as gr |
| import requests |
| from duckduckgo_search import DDGS |
|
|
|
|
| DEFAULT_MODEL = "gemma-4-31b-it" |
| GOOGLE_API_BASE = "https://generativelanguage.googleapis.com/v1beta/models" |
| DEBUG_LOG_PATH = "debug-1caba1.log" |
| DEBUG_SESSION_ID = "1caba1" |
|
|
|
|
| def debug_log(run_id: str, hypothesis_id: str, location: str, message: str, data: dict) -> None: |
| payload = { |
| "sessionId": DEBUG_SESSION_ID, |
| "runId": run_id, |
| "hypothesisId": hypothesis_id, |
| "location": location, |
| "message": message, |
| "data": data, |
| "timestamp": int(time.time() * 1000), |
| } |
| |
| with open(DEBUG_LOG_PATH, "a", encoding="utf-8") as f: |
| f.write(json.dumps(payload, ensure_ascii=True) + "\n") |
| |
|
|
|
|
| def normalize_model_id(model_input: str) -> str: |
| cleaned = (model_input or "").strip().lower().replace(" ", "-") |
| aliases = { |
| "gemma-4-31b-it": "gemma-4-31b-it", |
| "gemma4-31b-it": "gemma-4-31b-it", |
| "gemma-4-31b-instruct": "gemma-4-31b-it", |
| "gemma-4-31b": "gemma-4-31b-it", |
| } |
| return aliases.get(cleaned, cleaned) |
|
|
|
|
| def parse_model_response(text: str, option_a: str, option_b: str) -> Tuple[str, str, str]: |
| fallback_choice = option_a or option_b or "Expand to Singapore Market" |
| fallback_reason = "High expected growth with favorable risk profile." |
| fallback_confidence = "76%" |
|
|
| if not text: |
| return fallback_choice, fallback_reason, fallback_confidence |
|
|
| lines = [line.strip() for line in text.split("\n") if line.strip()] |
| choice = "" |
| reason = "" |
| confidence = "" |
|
|
| for line in lines: |
| lower = line.lower() |
| if not choice and lower.startswith("choice:"): |
| choice = line[7:].strip() |
| elif not reason and lower.startswith("reason:"): |
| reason = line[7:].strip() |
| elif not confidence and lower.startswith("confidence:"): |
| confidence = line[11:].strip() |
|
|
| if not reason and lines: |
| reason = " ".join(lines[:2]) |
|
|
| return ( |
| choice or fallback_choice, |
| reason or fallback_reason, |
| confidence or fallback_confidence, |
| ) |
|
|
|
|
| def build_web_context(dilemma: str, option_a: str, option_b: str, max_results: int) -> str: |
| query = " ".join( |
| [ |
| dilemma or "", |
| option_a or "", |
| option_b or "", |
| "market trends risk analysis recent data", |
| ] |
| ).strip() |
|
|
| if not query: |
| return "No web context available." |
|
|
| rows: List[str] = [] |
| |
| debug_log( |
| "pre-fix", |
| "H1", |
| "app.py:build_web_context", |
| "DuckDuckGo query prepared", |
| {"query_len": len(query), "max_results": int(max_results)}, |
| ) |
| |
| with DDGS() as ddgs: |
| results = ddgs.text(query, max_results=max_results) |
| for idx, item in enumerate(results, start=1): |
| title = (item.get("title") or "").strip() |
| href = (item.get("href") or "").strip() |
| body = (item.get("body") or "").strip() |
| if not (title or href or body): |
| continue |
| rows.append(f"{idx}. {title}\nURL: {href}\nSnippet: {body}") |
| |
| debug_log( |
| "pre-fix", |
| "H1", |
| "app.py:build_web_context", |
| "DuckDuckGo query completed", |
| {"result_count": len(rows)}, |
| ) |
| |
|
|
| return "\n\n".join(rows) if rows else "No web results returned by DuckDuckGo." |
|
|
|
|
| def call_google_model( |
| api_key: str, |
| model: str, |
| dilemma: str, |
| option_a: str, |
| option_b: str, |
| web_context: str, |
| ) -> str: |
| endpoint = f"{GOOGLE_API_BASE}/{model}:generateContent?key={api_key}" |
| |
| debug_log( |
| "pre-fix", |
| "H3", |
| "app.py:call_google_model", |
| "Prepared model endpoint", |
| {"model": model, "endpoint_without_key": f"{GOOGLE_API_BASE}/{model}:generateContent"}, |
| ) |
| |
| prompt = "\n".join( |
| [ |
| "You are a strategic decision assistant.", |
| "Given a dilemma and two options, pick the best option.", |
| "Return exactly 3 lines in this strict format:", |
| "CHOICE: <best option text>", |
| "REASON: <single concise reason>", |
| "CONFIDENCE: <0-100%>", |
| "", |
| "Use the web context below as additional evidence when relevant.", |
| f"WEB_CONTEXT:\n{web_context or 'No web context provided.'}", |
| "", |
| f"DILEMMA: {dilemma or 'N/A'}", |
| f"OPTION_ALPHA: {option_a or 'N/A'}", |
| f"OPTION_BETA: {option_b or 'N/A'}", |
| ] |
| ) |
|
|
| response = requests.post( |
| endpoint, |
| headers={"Content-Type": "application/json"}, |
| data=json.dumps({"contents": [{"parts": [{"text": prompt}]}]}), |
| timeout=60, |
| ) |
|
|
| data = response.json() |
| |
| debug_log( |
| "pre-fix", |
| "H4", |
| "app.py:call_google_model", |
| "Model response received", |
| { |
| "status_code": int(response.status_code), |
| "ok": bool(response.ok), |
| "has_candidates": isinstance(data.get("candidates", []), list), |
| "error_message": data.get("error", {}).get("message", ""), |
| }, |
| ) |
| |
| if not response.ok: |
| message = data.get("error", {}).get("message", "Google AI Studio request failed.") |
| raise RuntimeError(message) |
|
|
| candidates = data.get("candidates", []) |
| if not candidates: |
| return "" |
|
|
| parts = candidates[0].get("content", {}).get("parts", []) |
| return "\n".join(part.get("text", "") for part in parts).strip() |
|
|
|
|
| def analyze_decision( |
| dilemma: str, |
| option_a: str, |
| option_b: str, |
| model_input: str, |
| api_key_input: str, |
| use_web_search: bool, |
| max_search_results: int, |
| ) -> Tuple[str, str, str, str, str]: |
| api_key = (api_key_input or "").strip() or os.getenv("GOOGLE_API_KEY", "").strip() |
| model = normalize_model_id(model_input) or DEFAULT_MODEL |
| web_context = "Web search disabled." |
| |
| debug_log( |
| "pre-fix", |
| "H2", |
| "app.py:analyze_decision", |
| "Decision analysis started", |
| { |
| "has_api_key": bool(api_key), |
| "model": model, |
| "use_web_search": bool(use_web_search), |
| "max_search_results": int(max_search_results), |
| "has_dilemma": bool((dilemma or "").strip()), |
| "has_option_a": bool((option_a or "").strip()), |
| "has_option_b": bool((option_b or "").strip()), |
| }, |
| ) |
| |
|
|
| if not api_key: |
| return ( |
| option_a or option_b or "Fallback Recommendation", |
| "Missing API key. Add `GOOGLE_API_KEY` in Hugging Face Space secrets or input it in the field.", |
| "N/A", |
| model, |
| web_context, |
| ) |
|
|
| try: |
| if use_web_search: |
| safe_results = max(1, min(int(max_search_results), 8)) |
| web_context = build_web_context(dilemma, option_a, option_b, safe_results) |
| raw = call_google_model(api_key, model, dilemma, option_a, option_b, web_context) |
| choice, reason, confidence = parse_model_response(raw, option_a, option_b) |
| |
| debug_log( |
| "pre-fix", |
| "H5", |
| "app.py:analyze_decision", |
| "Parsed model output", |
| { |
| "raw_len": len(raw or ""), |
| "choice_len": len(choice or ""), |
| "reason_len": len(reason or ""), |
| "confidence": confidence, |
| }, |
| ) |
| |
| return choice, reason, confidence, model, web_context |
| except Exception as exc: |
| |
| debug_log( |
| "pre-fix", |
| "H4", |
| "app.py:analyze_decision", |
| "Analyze decision failed", |
| {"error": str(exc)}, |
| ) |
| |
| return ( |
| option_a or option_b or "Fallback Recommendation", |
| f"Model call failed: {exc}", |
| "N/A", |
| model, |
| web_context, |
| ) |
|
|
|
|
| with gr.Blocks(theme=gr.themes.Soft(), title="The Oracle | Decision Engine") as demo: |
| gr.Markdown("## The Oracle - Hugging Face Spaces App") |
| gr.Markdown( |
| "Enter your dilemma and options, then run analysis with Google AI Studio (Gemma). " |
| "Default model is `gemma-4-31b-it`." |
| ) |
|
|
| with gr.Row(): |
| dilemma = gr.Textbox( |
| label="Core dilemma", |
| placeholder="Should we pivot strategy toward AI-integrated logistics by Q3?", |
| lines=4, |
| ) |
|
|
| with gr.Row(): |
| option_a = gr.Textbox(label="Option Alpha", placeholder="Immediate full pivot") |
| option_b = gr.Textbox(label="Option Beta", placeholder="Incremental integration") |
|
|
| with gr.Row(): |
| model_input = gr.Textbox(label="Model ID", value=DEFAULT_MODEL) |
| api_key_input = gr.Textbox( |
| label="Google API key (optional if set in Space secrets as GOOGLE_API_KEY)", |
| type="password", |
| ) |
| with gr.Row(): |
| use_web_search = gr.Checkbox(label="Enable DuckDuckGo web search context", value=True) |
| max_search_results = gr.Slider( |
| label="DuckDuckGo results to use", |
| minimum=1, |
| maximum=8, |
| step=1, |
| value=4, |
| ) |
|
|
| run_btn = gr.Button("Generate Analysis", variant="primary") |
|
|
| with gr.Row(): |
| out_choice = gr.Textbox(label="Recommended Choice") |
| out_reason = gr.Textbox(label="Reason") |
| with gr.Row(): |
| out_confidence = gr.Textbox(label="Confidence") |
| out_model = gr.Textbox(label="Model Used") |
| out_web_context = gr.Textbox(label="DuckDuckGo Research Context", lines=12) |
|
|
| run_btn.click( |
| fn=analyze_decision, |
| inputs=[ |
| dilemma, |
| option_a, |
| option_b, |
| model_input, |
| api_key_input, |
| use_web_search, |
| max_search_results, |
| ], |
| outputs=[out_choice, out_reason, out_confidence, out_model, out_web_context], |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|