""" RazorShield — AI-Powered Merchant Fraud & Risk Intelligence System. Hugging Face Space Backend Application powered by Gradio and NVIDIA NIM API. Architecture: Vercel Next.js Frontend | | HTTPS / Gradio API v Hugging Face Space | +-- CPU Risk Pipeline | +-- preprocessing | +-- calibrated XGBoost | +-- merchant temporal state | +-- incident engine | +-- policy engine | +-- NVIDIA NIM API +-- openai/gpt-oss-20b """ from __future__ import annotations from datetime import datetime import json import logging import os from pathlib import Path import time from typing import Any import gradio as gr import pandas as pd from src.api.schemas import ( AnalyzeTransactionResponse, CampaignInfoResponse, DecisionResponse, MerchantRiskResponse, PerformanceMetricsResponse, TransactionRiskResponse, ) from src.explanation.fallback import DeterministicFallbackExplainer from src.explanation.schemas import ExplanationInput try: from openai import OpenAI except ImportError: OpenAI = None from src.incident.incident_engine import MerchantIncidentEngine from src.inference.adapter import InferenceAdapter from src.inference.preprocessing import validate_raw_api_payload from src.risk_engine.campaign import CampaignRegistration from src.risk_engine.schemas import TransactionInput # ============================================================================= # Logging # ============================================================================= logging.basicConfig( level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s", ) LOGGER = logging.getLogger("razorshield-app") # ============================================================================= # Environment # ============================================================================= SLM_MODEL_NAME = os.getenv( "NVIDIA_MODEL", os.getenv("SLM_MODEL", "openai/gpt-oss-20b"), ) POLICY_MODE_DEFAULT = os.getenv( "POLICY_MODE", "BALANCED", ) APP_VERSION = os.getenv( "RAZORSHIELD_VERSION", "phase-9-integration", ) # ============================================================================= # Engine Initialization # ============================================================================= LOGGER.info("Initializing RazorShield engines...") INCIDENT_ENGINE = MerchantIncidentEngine( policy_mode=POLICY_MODE_DEFAULT, persistence_n=2, ) INFERENCE_ADAPTER = InferenceAdapter() # ============================================================================= # ZeroGPU Space Compatibility Hook # ============================================================================= try: import spaces @spaces.GPU def _zero_gpu_register(): """Satisfies HF ZeroGPU static detector.""" return True LOGGER.info("ZeroGPU compatibility hook registered.") except Exception: pass # ============================================================================= # NVIDIA NIM Explanation Layer # ============================================================================= NVIDIA_BASE_URL = os.getenv( "NVIDIA_BASE_URL", "https://integrate.api.nvidia.com/v1", ) NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY", "").strip() SLM_MODEL_NAME = os.getenv( "NVIDIA_MODEL", "openai/gpt-oss-20b", ) NVIDIA_TIMEOUT_SECONDS = float( os.getenv("NVIDIA_TIMEOUT_SECONDS", "30") ) NVIDIA_CLIENT = None SLM_LOADED = False if OpenAI is None: LOGGER.warning("OpenAI SDK is not installed; deterministic explanation fallback will be used.") elif NVIDIA_API_KEY: NVIDIA_CLIENT = OpenAI( base_url=NVIDIA_BASE_URL, api_key=NVIDIA_API_KEY, ) # NVIDIA API models are remote; there is no local model-loading step. SLM_LOADED = True LOGGER.info( "NVIDIA NIM explanation client configured | model=%s", SLM_MODEL_NAME, ) else: LOGGER.warning( "NVIDIA_API_KEY is not configured; deterministic explanation fallback will be used." ) # ============================================================================= # Grounded NVIDIA Explanation Helper # ============================================================================= def _generate_explanation(exp_input: ExplanationInput) -> tuple[dict[str, Any], dict[str, Any]]: """ Convert deterministic RazorShield evidence into a concise grounded explanation. IMPORTANT: - The NVIDIA model never determines the risk decision. - All risk values originate from the deterministic engines. - The model is only allowed to verbalize supplied evidence. - If NVIDIA is unavailable or output fails grounding checks, use the deterministic fallback explainer. """ if NVIDIA_CLIENT is None: fallback = DeterministicFallbackExplainer.generate_fallback_explanation( exp_input, failure_reason="NVIDIA_API_KEY/OpenAI client unavailable", ) return ( fallback.model_dump(), { "valid": True, "method": "deterministic_fallback", "reason": "NVIDIA API unavailable", }, ) evidence = exp_input.model_dump() system_prompt = """You are RazorShield's evidence explanation engine. Your ONLY task is to convert supplied deterministic fraud-risk evidence into a concise, grounded explanation for a merchant risk operator. CRITICAL RULES: 1. Never make or change the risk decision. 2. Never invent facts, amounts, customers, transactions, causes, or signals. 3. Use ONLY values present in the evidence JSON. 4. Preserve decision, incident state, severity, campaign status, and numeric values. 5. Do not describe the output as a probability unless the supplied field is explicitly a probability. 6. Explain why the deterministic engine reached its decision using the supplied signals. 7. Keep the explanation concise: 40-100 words. 8. Return JSON only with exactly these keys: summary, decision, severity, evidence 9. "decision" must exactly equal recommended_action. 10. "severity" must exactly equal severity from the evidence. 11. "evidence" must be a short array of strings containing only grounded facts. """ user_prompt = ( "Convert the following deterministic RazorShield evidence into the required JSON.\n\n" + json.dumps(evidence, ensure_ascii=False, default=str) ) try: completion = NVIDIA_CLIENT.chat.completions.create( model=SLM_MODEL_NAME, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ], temperature=0.1, top_p=1, max_tokens=2048, stream=False, timeout=NVIDIA_TIMEOUT_SECONDS, response_format={"type": "json_object"}, ) content = completion.choices[0].message.content or "" cleaned = content.strip() if "```json" in cleaned: cleaned = cleaned.split("```json")[1].split("```")[0].strip() elif "```" in cleaned: cleaned = cleaned.split("```")[1].split("```")[0].strip() start_idx = cleaned.find("{") end_idx = cleaned.rfind("}") if start_idx != -1 and end_idx != -1 and end_idx > start_idx: cleaned = cleaned[start_idx : end_idx + 1] generated = json.loads(cleaned) # ---- Deterministic grounding validation ---- if not isinstance(generated, dict): raise ValueError("NVIDIA output is not a JSON object") required = {"summary", "decision", "severity", "evidence"} if not required.issubset(generated): raise ValueError("NVIDIA output is missing required explanation fields") if str(generated["decision"]) != str(exp_input.recommended_action): raise ValueError("Generated decision contradicts deterministic decision") if str(generated["severity"]) != str(exp_input.severity): raise ValueError("Generated severity contradicts deterministic severity") if not isinstance(generated["evidence"], list): raise ValueError("Generated evidence must be a list") # Basic numeric grounding: every numeric token used by the model must occur # in the supplied evidence. This intentionally errs on the safe side. source_numbers = set() for value in evidence.values(): if isinstance(value, (int, float)) and not isinstance(value, bool): source_numbers.add(str(value)) source_numbers.add(f"{float(value):g}") generated_text = json.dumps(generated, ensure_ascii=False) import re output_numbers = re.findall( r"(? str: """ Serialize API response consistently. """ return json.dumps( payload, indent=2, default=str, ) # ============================================================================= # Public API: Health # ============================================================================= def health_check() -> str: """ Lightweight backend health/status endpoint. This endpoint does not execute the risk engine or consume NVIDIA NIM API. Exposed as: api_name="health_check" """ return _json_response( { "status": "healthy", "service": "razorshield-api", "version": APP_VERSION, "backend": "huggingface-spaces-nvidia-nim", "risk_engine": "READY", "incident_engine": "READY", "slm": { "model": SLM_MODEL_NAME, "configured": bool(NVIDIA_CLIENT), "status": "READY" if NVIDIA_CLIENT else "FALLBACK", "execution": "NVIDIA_NIM_API", }, "timestamp": datetime.utcnow().isoformat() + "Z", } ) # ============================================================================= # Public API: Analyze Transaction # ============================================================================= def analyze_transaction( merchant_id: str, transaction_id: str, customer_id: str = "C_UNKNOWN", device_id: str = "D_UNKNOWN", event_time: str = "", amount: float = 100.0, payment_method: str = "card", transaction_type: str = "sale", policy_mode: str = "BALANCED", ) -> str: """ Analyze a single transaction through the complete RazorShield pipeline. Pipeline: Validation ↓ Calibrated Transaction Risk ↓ Merchant Temporal State ↓ Spike Detection ↓ Incident Persistence ↓ Policy Decision ↓ Grounded Explanation Exposed as: api_name="analyze_transaction" """ t_start_total = time.perf_counter() # ------------------------------------------------------------------------- # Event time # ------------------------------------------------------------------------- if not event_time or not str(event_time).strip(): event_time = datetime.now().isoformat() # ------------------------------------------------------------------------- # Raw payload # ------------------------------------------------------------------------- raw_payload = { "merchant_id": merchant_id, "transaction_id": transaction_id, "customer_id": customer_id, "device_id": device_id, "event_time": event_time, "amount": amount, "payment_method": payment_method, "transaction_type": transaction_type, "policy_mode": policy_mode, } # ------------------------------------------------------------------------- # 1. Validation # ------------------------------------------------------------------------- try: api_input = validate_raw_api_payload(raw_payload) except ValueError as val_err: LOGGER.warning( "Transaction validation failed: %s", val_err, ) return _json_response( { "error": "Validation Error", "details": str(val_err), } ) # ------------------------------------------------------------------------- # 2. Risk + Incident Engine # ------------------------------------------------------------------------- t_start_risk = time.perf_counter() tx_input = TransactionInput( transaction_id=api_input.transaction_id, merchant_id=api_input.merchant_id, customer_id=api_input.customer_id, device_id=api_input.device_id, event_time=api_input.event_time, amount=api_input.amount, payment_method=api_input.payment_method, transaction_type=api_input.transaction_type, ) tx_dec, inc_dec = INCIDENT_ENGINE.process_transaction( tx_input ) t_risk_ms = ( time.perf_counter() - t_start_risk ) * 1000.0 # ------------------------------------------------------------------------- # 3. Explanation Layer # ------------------------------------------------------------------------- t_start_slm = time.perf_counter() slm_ms = 0.0 exp_input = ExplanationInput( merchant_id=api_input.merchant_id, incident_state=inc_dec["incident_state"], severity=inc_dec["severity"], incident_score=inc_dec["incident_score"], spike_probability=inc_dec["spike_probability"], fraud_excess_ratio=inc_dec["fraud_excess_ratio"], velocity_ratio=inc_dec["velocity_ratio"], suspicious_windows=inc_dec["suspicious_windows"], total_suspicious_windows=inc_dec["total_suspicious_windows"], campaign_active=inc_dec["campaign_active"], policy_mode=api_input.policy_mode, signals=inc_dec["signals"], recommended_action=tx_dec.decision, ) if inc_dec["incident_state"] in ["INVESTIGATE", "ALERT"]: exp_json, val_res = _generate_explanation(exp_input) slm_ms = ( time.perf_counter() - t_start_slm ) * 1000.0 else: exp_out = DeterministicFallbackExplainer.generate_fallback_explanation( exp_input, failure_reason="Deterministic processing (Normal risk)", ) exp_json = exp_out.model_dump() val_res = { "valid": True, "method": "deterministic_fallback", "reason": "Normal risk state", } # ------------------------------------------------------------------------- # 4. Total latency # ------------------------------------------------------------------------- t_total_ms = ( time.perf_counter() - t_start_total ) * 1000.0 # ------------------------------------------------------------------------- # 5. Structured response # ------------------------------------------------------------------------- resp = AnalyzeTransactionResponse( transaction_id=api_input.transaction_id, merchant_id=api_input.merchant_id, transaction_risk=TransactionRiskResponse( fraud_probability=tx_dec.calibrated_fraud_probability, ), merchant_risk=MerchantRiskResponse( spike_probability=inc_dec["spike_probability"], fraud_excess_ratio=inc_dec["fraud_excess_ratio"], velocity_ratio=inc_dec["velocity_ratio"], incident_state=inc_dec["incident_state"], severity=inc_dec["severity"], incident_score=inc_dec["incident_score"], suspicious_windows=inc_dec["suspicious_windows"], ), campaign=CampaignInfoResponse( active=inc_dec["campaign_active"], campaign_name=( "PROMOTIONAL_SALE" if inc_dec["campaign_active"] else None ), ), decision=DecisionResponse( action=tx_dec.decision, policy_mode=api_input.policy_mode, ), explanation=exp_json, performance=PerformanceMetricsResponse( risk_engine_latency_ms=round( t_risk_ms, 3, ), slm_latency_ms=round( slm_ms, 3, ), total_latency_ms=round( t_total_ms, 3, ), ), ) return _json_response( resp.model_dump() ) # ============================================================================= # Public API: Merchant State # ============================================================================= def analyze_merchant( merchant_id: str, ) -> str: """ Return live merchant temporal state. Exposed as: api_name="analyze_merchant" """ if not merchant_id or not str(merchant_id).strip(): return _json_response( { "error": "Validation Error", "details": "Missing merchant_id", } ) m_id = str(merchant_id).strip() m_state = ( INCIDENT_ENGINE .risk_engine .state_manager .get_state(m_id) ) inc_state = ( INCIDENT_ENGINE .get_incident_state(m_id) ) result = { "merchant_id": m_id, "rolling_window": { "rolling_txn_count_15m": ( m_state.rolling_15m_volume ), "baseline_txn_count_15m": ( m_state.baseline_txn_15m ), "velocity_ratio": round( m_state.velocity_ratio, 2, ), "estimated_fraud_count": round( m_state.calibrated_estimated_fraud_count, 4, ), "expected_fraud_count": round( m_state.expected_fraud_count, 4, ), "fraud_excess_ratio": round( m_state.fraud_excess_ratio, 2, ), }, "incident_state": inc_state.to_dict(), } return _json_response(result) # ============================================================================= # Public API: Scenario Replay # ============================================================================= def run_scenario( scenario_name: str, policy_mode: str = "BALANCED", ) -> str: """ Replay Dataset B test scenarios chronologically. Exposed as: api_name="run_scenario" """ root_dir = Path(__file__).resolve().parent feat_path = ( root_dir / "data" / "processed" / "dataset_b_features.parquet" ) scenario_map = { "NORMAL": "normal", "VOLUME_ONLY_SPIKE": "volume_only_spike", "AMOUNT_SHIFT": "amount_shift", "FRAUD_SPIKE": "fraud_spike", "FRAUD_DURING_FLASH_SALE": "fraud_spike", } scenario_type = scenario_map.get( scenario_name.upper(), "normal", ) json_path = ( root_dir / "data" / "processed" / "demo_scenarios.json" ) if json_path.exists(): df_b = pd.read_json(json_path) elif feat_path.exists(): df_b = pd.read_parquet(feat_path) else: LOGGER.error("Demo scenario dataset missing at %s", json_path) return _json_response( { "error": f"Demo scenario dataset missing at {json_path}" } ) test_df = df_b[ df_b["split"] == "test" ].copy() scenario_df = test_df[ test_df["scenario_type"] == scenario_type ].copy() if len(scenario_df) == 0: return _json_response( { "error": ( f"No scenarios found for type " f"'{scenario_type}'" ) } ) # Deterministic demo scenario first_scenario_id = ( scenario_df["scenario_id"].iloc[0] ) demo_txs = ( scenario_df[ scenario_df["scenario_id"] == first_scenario_id ] .sort_values("event_time") ) merchant_id = str( demo_txs["merchant_id"].iloc[0] ) # ------------------------------------------------------------------------- # Campaign registration # ------------------------------------------------------------------------- if ( "FLASH_SALE" in scenario_name.upper() or scenario_name.upper() == "VOLUME_ONLY_SPIKE" ): min_t = demo_txs[ "event_time" ].min() max_t = demo_txs[ "event_time" ].max() INCIDENT_ENGINE.register_campaign( CampaignRegistration( merchant_id=merchant_id, campaign_name="DEMO_FLASH_SALE", start_time=min_t, end_time=max_t, expected_volume_multiplier=4.0, ) ) # ------------------------------------------------------------------------- # Replay # ------------------------------------------------------------------------- t_start = time.perf_counter() state_counts = { "NORMAL": 0, "INVESTIGATE": 0, "ALERT": 0, } last_tx_dec = None last_inc_dec = None for _, row in demo_txs.iterrows(): tx_input = TransactionInput( transaction_id=str( row["transaction_id"] ), merchant_id=str( row["merchant_id"] ), customer_id=str( row.get( "customer_id", "C_DEMO", ) ), device_id=str( row.get( "device_id", "D_DEMO", ) ), event_time=row["event_time"], amount=float( row["amount"] ), payment_method="card", transaction_type="sale", ) predicted_probability = float( row.get( "predicted_fraud_prob", 0.01, ) ) ( last_tx_dec, last_inc_dec, ) = INCIDENT_ENGINE.process_transaction( tx_input, calibrated_fraud_prob=predicted_probability, ) state_counts[ last_inc_dec["incident_state"] ] += 1 elapsed_ms = ( time.perf_counter() - t_start ) * 1000.0 # ------------------------------------------------------------------------- # Final explanation # ------------------------------------------------------------------------- exp_input = ExplanationInput( merchant_id=merchant_id, incident_state=last_inc_dec["incident_state"], severity=last_inc_dec["severity"], incident_score=last_inc_dec["incident_score"], spike_probability=last_inc_dec["spike_probability"], fraud_excess_ratio=last_inc_dec["fraud_excess_ratio"], velocity_ratio=last_inc_dec["velocity_ratio"], suspicious_windows=last_inc_dec["suspicious_windows"], total_suspicious_windows=last_inc_dec["total_suspicious_windows"], campaign_active=last_inc_dec["campaign_active"], policy_mode=policy_mode, signals=last_inc_dec["signals"], recommended_action=( last_tx_dec.decision if last_tx_dec else "APPROVE" ), ) exp_json, _ = _generate_explanation(exp_input) result = { "scenario_name": scenario_name, "scenario_id": first_scenario_id, "merchant_id": merchant_id, "total_transactions": len(demo_txs), "replay_time_ms": round( elapsed_ms, 2, ), "incident_state_distribution": state_counts, "final_incident_state": ( last_inc_dec["incident_state"] ), "final_severity": ( last_inc_dec["severity"] ), "explanation": exp_json, } return _json_response(result) # ============================================================================= # Public API: Direct Evidence Explanation # ============================================================================= def explain_evidence( evidence_json: str, ) -> str: """ Convert structured evidence into grounded SLM explanation. Exposed as: api_name="explain_evidence" """ try: data = json.loads( evidence_json ) exp_input = ExplanationInput( **data ) exp_json, val_res = _generate_explanation(exp_input) result = { "explanation": exp_json, "validation": val_res, } return _json_response(result) except Exception as exc: LOGGER.exception( "Explanation generation failed." ) return _json_response( { "error": "Explanation Generation Error", "details": str(exc), } ) # ============================================================================= # Public API: Reset # ============================================================================= def reset_demo_state() -> str: """ Reset all merchant temporal states, incident states and campaign registrations. Exposed as: api_name="reset_demo_state" """ INCIDENT_ENGINE.reset_state() INFERENCE_ADAPTER.tracker.reset() return _json_response( { "status": "SUCCESS", "message": ( "All merchant states and campaigns reset." ), } ) # ============================================================================= # Gradio Application # ============================================================================= def build_gradio_app() -> gr.Blocks: """ Construct the Gradio backend application. The Gradio UI exists primarily as: 1. Space demonstration interface 2. API endpoint exposure 3. Manual backend verification """ theme = gr.themes.Soft( primary_hue="indigo", secondary_hue="slate", ) with gr.Blocks( theme=theme, title="RazorShield API & Risk Intelligence", ) as demo: gr.Markdown( """ # RazorShield — AI-Powered Merchant Fraud & Risk Intelligence ### Real-Time Calibrated Transaction Fraud, Temporal Merchant Incident Detection & Grounded NVIDIA SLM Explanation Layer **Backend:** Hugging Face Spaces **Risk Engine:** Deterministic **SLM:** openai/gpt-oss-20b via NVIDIA NIM **Execution:** NVIDIA API (no local GPU required) """ ) # --------------------------------------------------------------------- # Health # --------------------------------------------------------------------- with gr.Tab("System Health"): gr.Markdown( "#### Live backend and SLM health status" ) health_btn = gr.Button( "Check System Health", variant="primary", ) health_out = gr.Code( language="json", label="Health Response", ) health_btn.click( fn=health_check, inputs=[], outputs=[health_out], api_name="health_check", ) # --------------------------------------------------------------------- # Transaction # --------------------------------------------------------------------- with gr.Tab("Transaction Risk Analysis"): gr.Markdown( """ #### Submit transaction payload for real-time risk assessment The decision is produced by the deterministic risk engine. The SLM only generates a grounded explanation. """ ) with gr.Row(): with gr.Column(): m_id_in = gr.Textbox( value="M_101", label="Merchant ID", ) tx_id_in = gr.Textbox( value="TX_994182", label="Transaction ID", ) cust_id_in = gr.Textbox( value="C_1048", label="Customer ID", ) dev_id_in = gr.Textbox( value="D_882", label="Device ID", ) time_in = gr.Textbox( value=datetime.now().isoformat(), label="Event Time (ISO 8601)", ) amt_in = gr.Number( value=125.50, label="Amount ($)", ) pm_in = gr.Dropdown( choices=[ "card", "ach", "crypto", "paypal", ], value="card", label="Payment Method", ) tt_in = gr.Dropdown( choices=[ "sale", "transfer", "refund", ], value="sale", label="Transaction Type", ) pol_in = gr.Dropdown( choices=[ "CONSERVATIVE", "BALANCED", "HIGH_SENSITIVITY", ], value="BALANCED", label="Policy Mode", ) btn_analyze = gr.Button( "Analyze Transaction", variant="primary", ) with gr.Column(): tx_out = gr.Code( language="json", label="Structured API Response", ) btn_analyze.click( fn=analyze_transaction, inputs=[ m_id_in, tx_id_in, cust_id_in, dev_id_in, time_in, amt_in, pm_in, tt_in, pol_in, ], outputs=[tx_out], api_name="analyze_transaction", ) # --------------------------------------------------------------------- # Scenario Replay # --------------------------------------------------------------------- with gr.Tab("Scenario Replay Demo"): gr.Markdown( """ #### Replay Dataset B test scenarios chronologically """ ) with gr.Row(): with gr.Column(): sc_select = gr.Dropdown( choices=[ "NORMAL", "VOLUME_ONLY_SPIKE", "AMOUNT_SHIFT", "FRAUD_SPIKE", "FRAUD_DURING_FLASH_SALE", ], value="FRAUD_SPIKE", label="Select Demo Scenario", ) sc_policy = gr.Dropdown( choices=[ "CONSERVATIVE", "BALANCED", "HIGH_SENSITIVITY", ], value="BALANCED", label="Policy Mode", ) btn_run_sc = gr.Button( "Run Scenario Replay", variant="primary", ) with gr.Column(): sc_out = gr.Code( language="json", label="Scenario Execution Summary", ) btn_run_sc.click( fn=run_scenario, inputs=[ sc_select, sc_policy, ], outputs=[sc_out], api_name="run_scenario", ) # --------------------------------------------------------------------- # Merchant State # --------------------------------------------------------------------- with gr.Tab("Merchant Incident State"): gr.Markdown( """ #### Query live merchant temporal rolling state """ ) with gr.Row(): with gr.Column(): m_query_in = gr.Textbox( value="M_101", label="Merchant ID", ) btn_m_query = gr.Button( "Query Merchant State", ) with gr.Column(): m_query_out = gr.Code( language="json", label="Merchant Incident State", ) btn_m_query.click( fn=analyze_merchant, inputs=[m_query_in], outputs=[m_query_out], api_name="analyze_merchant", ) # --------------------------------------------------------------------- # SLM Explanation # --------------------------------------------------------------------- with gr.Tab("SLM Grounding Validator"): gr.Markdown( """ #### Convert structured risk evidence into a grounded explanation The SLM does not determine the risk decision. """ ) with gr.Row(): with gr.Column(): ev_in = gr.Code( language="json", value=json.dumps( { "merchant_id": "M_101", "incident_state": "ALERT", "severity": "HIGH", "incident_score": 0.88, "spike_probability": 0.92, "fraud_excess_ratio": 8.2, "velocity_ratio": 4.1, "suspicious_windows": 3, "total_suspicious_windows": 3, "campaign_active": True, "policy_mode": "BALANCED", "signals": [ { "name": "fraud_excess_ratio", "value": 8.2, "direction": "elevated", }, { "name": "velocity_ratio", "value": 4.1, "direction": "suppressed", }, ], "recommended_action": "ALERT", }, indent=2, ), label="Structured Evidence Input", ) btn_exp_ev = gr.Button( "Generate SLM Explanation" ) with gr.Column(): ev_out = gr.Code( language="json", label="Grounded SLM Output", ) btn_exp_ev.click( fn=explain_evidence, inputs=[ev_in], outputs=[ev_out], api_name="explain_evidence", ) # --------------------------------------------------------------------- # Reset # --------------------------------------------------------------------- with gr.Row(): btn_reset = gr.Button( "Reset Demo State", variant="stop", ) reset_out = gr.Textbox( label="Reset Status", interactive=False, ) btn_reset.click( fn=reset_demo_state, inputs=[], outputs=[reset_out], api_name="reset_demo_state", ) return demo # ============================================================================= # Build Application # ============================================================================= demo = build_gradio_app() # ============================================================================= # Launch # ============================================================================= if __name__ == "__main__": LOGGER.info("Starting RazorShield Gradio Space...") LOGGER.info( "NVIDIA explanation model=%s | API configured=%s | no local GPU required", SLM_MODEL_NAME, bool(NVIDIA_CLIENT), ) demo.launch( server_name="0.0.0.0", server_port=7860, show_error=True, )