CoolFace
Apppublic

anshmittal/fact-checking-api

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
new.py355 linesDownload Raw Back to root
1from fastapi import FastAPI, HTTPException2from pydantic import BaseModel3import os4from dotenv import load_dotenv5import logging6import openai7from typing import Dict, Any8 9# Load .env locally if present (HF Spaces will use Secrets - no .env required there)10load_dotenv()11 12# Basic logging13logging.basicConfig(level=logging.INFO)14logger = logging.getLogger(__name__)15 16# Read API key from env17OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")18if not OPENROUTER_API_KEY:19    raise RuntimeError(20        "OPENROUTER_API_KEY environment variable is required. "21        "On Hugging Face Spaces, add it under Settings -> Secrets."22    )23 24# Configure OpenAI client to use OpenRouter25client = openai.OpenAI(26    base_url="https://openrouter.ai/api/v1",27    api_key=OPENROUTER_API_KEY,28    default_headers={29        "HTTP-Referer": "https://your-domain.com",  # Optional: replace with your domain30        "X-Title": "Fact Checker API",  # Optional: for OpenRouter leaderboards31    }32)33 34app = FastAPI(title="Fact Checker API (OpenRouter + Web Search)",35              description="Fact-check text using Gemini via OpenRouter with web search")36 37class TextInput(BaseModel):38    text: str39 40class FactCheckResponse(BaseModel):41    is_factual: str   # "correct", "incorrect", or "unsure"42    summary: str43    explanation: str44 45@app.post("/fact-check", response_model=FactCheckResponse)46def fact_check_text(input_data: TextInput) -> FactCheckResponse:47    """48    Fact-check the provided text using Gemini via OpenRouter with web search.49    Uses the :online suffix method for web search.50    """51    # Build human-friendly prompt that instructs the model to use web search52    prompt = f"""53Please fact-check the following text and provide your analysis in the exact format below. You have access to current web search results to verify the claims.54 55Text to analyze: "{input_data.text}"56 57Instructions:581. Use the web search results to verify the claims in the text592. Determine if the text is factually CORRECT, INCORRECT, or if there's INSUFFICIENT DATA603. Provide a short SUMMARY line614. Provide a clear EXPLANATION with evidence and sources when available62 63Respond exactly like:64STATUS: [CORRECT/INCORRECT/UNSURE]65SUMMARY: [short summary]66EXPLANATION: [detailed explanation with sources]67"""68 69    try:70        # Use Gemini model through OpenRouter with web search enabled71        # The ":online" suffix enables web search functionality72        model_name = "google/gemini-2.5-flash:online"73        74        response = client.chat.completions.create(75            model=model_name,76            messages=[77                {78                    "role": "user", 79                    "content": prompt80                }81            ],82            max_tokens=1000,83            temperature=0.3,84        )85 86        model_text = response.choices[0].message.content87        if not model_text:88            raise HTTPException(status_code=500, detail="No text returned from model.")89        90        model_text = model_text.strip()91        logger.info(f"Model response: {model_text[:200]}...")  # Log first 200 chars for debugging92 93        # Parse the model's exact-format response94        status = "unsure"95        summary = ""96        explanation = ""97 98        for line in model_text.splitlines():99            line = line.strip()100            if line.upper().startswith("STATUS:"):101                s = line[len("STATUS:"):].strip().lower()102                if "correct" in s and "incorrect" not in s:103                    status = "correct"104                elif "incorrect" in s:105                    status = "incorrect"106                else:107                    status = "unsure"108            elif line.upper().startswith("SUMMARY:"):109                summary = line[len("SUMMARY:"):].strip()110            elif line.upper().startswith("EXPLANATION:"):111                explanation = line[len("EXPLANATION:"):].strip()112            # If multi-line explanation, keep appending following lines (simple heuristic)113            elif explanation and line:114                explanation += "\n" + line115 116        # Fallback if parsing failed117        if not summary and not explanation:118            summary = "Analysis completed (raw model output)"119            explanation = model_text120            status = "unsure"121 122        # Additional safety defaults123        if status == "incorrect" and not explanation:124            explanation = "The model marked the text incorrect but did not provide a reason."125        elif status == "unsure" and not explanation:126            explanation = "There is insufficient reliable data available to make a definitive determination."127        elif status == "correct" and not explanation:128            explanation = "The provided information appears consistent with available data."129 130        return FactCheckResponse(is_factual=status, summary=summary, explanation=explanation)131 132    except Exception as e:133        logger.exception("Fact-check generation failed")134        raise HTTPException(status_code=500, detail=f"Error processing fact-check request: {e}")135 136@app.post("/fact-check-plugin", response_model=FactCheckResponse)137def fact_check_text_plugin(input_data: TextInput) -> FactCheckResponse:138    """139    Alternative endpoint using explicit web plugin configuration.140    This method uses the web plugin directly with customizable parameters.141    """142    prompt = f"""143Please fact-check the following text and provide your analysis in the exact format below. You have access to current web search results to verify the claims.144 145Text to analyze: "{input_data.text}"146 147Instructions:1481. Use the web search results to verify the claims in the text1492. Determine if the text is factually CORRECT, INCORRECT, or if there's INSUFFICIENT DATA1503. Provide a short SUMMARY line1514. Provide a clear EXPLANATION with evidence and sources when available152 153Respond exactly like:154STATUS: [CORRECT/INCORRECT/UNSURE]155SUMMARY: [short summary]156EXPLANATION: [detailed explanation with sources]157"""158 159    try:160        # Use regular Gemini model with explicit web plugin configuration161        response = client.chat.completions.create(162            model="google/gemini-2.5-flash",163            messages=[164                {165                    "role": "user", 166                    "content": prompt167                }168            ],169            max_tokens=1000,170            temperature=0.3,171            # Correct web plugin configuration according to OpenRouter docs172            plugins=[173                {174                    "name": "web",175                    "arguments": {176                        "max_results": 5,177                        "search_prompt": f"Find reliable, current information to fact-check this claim: {input_data.text}"178                    }179                }180            ]181        )182 183        # Rest of the processing is identical184        model_text = response.choices[0].message.content185        if not model_text:186            raise HTTPException(status_code=500, detail="No text returned from model.")187        188        model_text = model_text.strip()189        logger.info(f"Model response: {model_text[:200]}...")  # Log first 200 chars for debugging190 191        # Parse the model's exact-format response (same parsing logic)192        status = "unsure"193        summary = ""194        explanation = ""195 196        for line in model_text.splitlines():197            line = line.strip()198            if line.upper().startswith("STATUS:"):199                s = line[len("STATUS:"):].strip().lower()200                if "correct" in s and "incorrect" not in s:201                    status = "correct"202                elif "incorrect" in s:203                    status = "incorrect"204                else:205                    status = "unsure"206            elif line.upper().startswith("SUMMARY:"):207                summary = line[len("SUMMARY:"):].strip()208            elif line.upper().startswith("EXPLANATION:"):209                explanation = line[len("EXPLANATION:"):].strip()210            elif explanation and line:211                explanation += "\n" + line212 213        if not summary and not explanation:214            summary = "Analysis completed (raw model output)"215            explanation = model_text216            status = "unsure"217 218        if status == "incorrect" and not explanation:219            explanation = "The model marked the text incorrect but did not provide a reason."220        elif status == "unsure" and not explanation:221            explanation = "There is insufficient reliable data available to make a definitive determination."222        elif status == "correct" and not explanation:223            explanation = "The provided information appears consistent with available data."224 225        return FactCheckResponse(is_factual=status, summary=summary, explanation=explanation)226 227    except Exception as e:228        logger.exception("Fact-check generation failed")229        raise HTTPException(status_code=500, detail=f"Error processing fact-check request: {e}")230 231@app.post("/fact-check-with-context", response_model=FactCheckResponse)232def fact_check_with_search_context(input_data: TextInput) -> FactCheckResponse:233    """234    Enhanced endpoint that first performs web search, then uses those results for fact-checking.235    This approach ensures the model receives search results and can reference them directly.236    """237    # Step 1: Get web search results first238    search_prompt = f"Find reliable, current information to fact-check this claim: {input_data.text}"239    240    try:241        # First call to get search results242        search_response = client.chat.completions.create(243            model="google/gemini-2.5-flash:online",244            messages=[245                {246                    "role": "user", 247                    "content": search_prompt248                }249            ],250            max_tokens=500,251            temperature=0.1,252        )253 254        search_results = search_response.choices[0].message.content255        if not search_results:256            search_results = "No search results available."257        258        # Step 2: Use search results for fact-checking259        fact_check_prompt = f"""260Based on the web search results below, please fact-check the following text and provide your analysis in the exact format specified.261 262Text to fact-check: "{input_data.text}"263 264Web search results:265{search_results}266 267Instructions:2681. Use the web search results above to verify the claims in the text2692. Determine if the text is factually CORRECT, INCORRECT, or if there's INSUFFICIENT DATA2703. Provide a short SUMMARY line2714. Provide a clear EXPLANATION with evidence and sources when available272 273Respond exactly like:274STATUS: [CORRECT/INCORRECT/UNSURE]275SUMMARY: [short summary]276EXPLANATION: [detailed explanation with sources from the search results]277"""278 279        # Second call for fact-checking with search context280        fact_check_response = client.chat.completions.create(281            model="google/gemini-2.5-flash",282            messages=[283                {284                    "role": "user", 285                    "content": fact_check_prompt286                }287            ],288            max_tokens=1000,289            temperature=0.3,290        )291 292        model_text = fact_check_response.choices[0].message.content293        if not model_text:294            raise HTTPException(status_code=500, detail="No text returned from model.")295        296        model_text = model_text.strip()297        logger.info(f"Fact-check response: {model_text[:200]}...")298 299        # Parse the response (same parsing logic)300        status = "unsure"301        summary = ""302        explanation = ""303 304        for line in model_text.splitlines():305            line = line.strip()306            if line.upper().startswith("STATUS:"):307                s = line[len("STATUS:"):].strip().lower()308                if "correct" in s and "incorrect" not in s:309                    status = "correct"310                elif "incorrect" in s:311                    status = "incorrect"312                else:313                    status = "unsure"314            elif line.upper().startswith("SUMMARY:"):315                summary = line[len("SUMMARY:"):].strip()316            elif line.upper().startswith("EXPLANATION:"):317                explanation = line[len("EXPLANATION:"):].strip()318            elif explanation and line:319                explanation += "\n" + line320 321        if not summary and not explanation:322            summary = "Analysis completed with search context"323            explanation = model_text324            status = "unsure"325 326        if status == "incorrect" and not explanation:327            explanation = "The model marked the text incorrect but did not provide a reason."328        elif status == "unsure" and not explanation:329            explanation = "There is insufficient reliable data available to make a definitive determination."330        elif status == "correct" and not explanation:331            explanation = "The provided information appears consistent with available data."332 333        return FactCheckResponse(is_factual=status, summary=summary, explanation=explanation)334 335    except Exception as e:336        logger.exception("Fact-check with context generation failed")337        raise HTTPException(status_code=500, detail=f"Error processing fact-check request: {e}")338 339@app.get("/")340def root():341    return {342        "message": "Fact Checker API using OpenRouter (Gemini) + Web Search",343        "endpoints": {344            "/fact-check": "POST - Main fact-checking endpoint with web search (:online suffix)",345            "/fact-check-plugin": "POST - Alternative endpoint with explicit web plugin config",346            "/fact-check-with-context": "POST - Enhanced endpoint with explicit search context"347        },348        "method": "POST",349        "notes": "Set OPENROUTER_API_KEY in environment (Spaces Secrets). Web search enabled via OpenRouter.",350        "pricing": "Web search costs $4 per 1000 results (default max 5 results = ~$0.02 per request)"351    }352 353@app.get("/health")354def health_check():355    return {"status": "healthy"}