CoolFace
Apppublic

anshmittal/fact-checking-api

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
main.py146 linesDownload Raw Back to root
1# app.py
2from fastapi import FastAPI, HTTPException
3from pydantic import BaseModel
4import os
5from dotenv import load_dotenv
6import logging
7
8# Google GenAI SDK imports
9from google import genai
10from google.genai.types import GenerateContentConfig, GoogleSearch, HttpOptions, Tool
11
12# Load .env locally if present (HF Spaces will use Secrets - no .env required there)
13load_dotenv()
14
15# Basic logging
16logging.basicConfig(level=logging.INFO)
17logger = logging.getLogger(__name__)
18
19# Read API key from env
20GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
21if not GOOGLE_API_KEY:
22    raise RuntimeError(
23        "GOOGLE_API_KEY environment variable is required. "
24        "On Hugging Face Spaces, add it under Settings -> Secrets."
25    )
26
27# Ensure client sees the key (client also reads from env automatically)
28os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEY
29
30# Create GenAI client (explicit API version ensures compatibility)
31client = genai.Client()   # uses env GOOGLE_API_KEY automatically
32
33app = FastAPI(title="Fact Checker API (GenAI + Google Search grounding)",
34              description="Fact-check text using Gemini + Google Search grounding")
35
36class TextInput(BaseModel):
37    text: str
38
39class FactCheckResponse(BaseModel):
40    is_factual: str   # "correct", "incorrect", or "unsure"
41    summary: str
42    explanation: str
43
44@app.post("/fact-check", response_model=FactCheckResponse)
45def fact_check_text(input_data: TextInput) -> FactCheckResponse:
46    """
47    Fact-check the provided text using a Gemini model grounded with Google Search.
48    """
49    # Build human-friendly prompt that instructs the model to use the web and produce a strict format
50    prompt = f"""
51Please fact-check the following text and provide your analysis in the exact format below. Use web search if needed; do not hallucinate.
52
53Text to analyze: "{input_data.text}"
54
55Instructions:
561. Determine if the text is factually CORRECT, INCORRECT, or if there's INSUFFICIENT DATA.
572. Provide a short SUMMARY line.
583. Provide a clear EXPLANATION with evidence (cite if available).
59Respond exactly like:
60STATUS: [CORRECT/INCORRECT/UNSURE]
61SUMMARY: [short summary]
62EXPLANATION: [detailed explanation]
63"""
64
65    try:
66        # Choose a model that supports tools/grounding (e.g. gemini-2.5-flash or gemini-2.0-flash).
67        # You can change this to another model variant that supports tool grounding.
68        model_name = "gemini-2.5-flash"
69
70        # Configure the generation request to enable Google Search as a Tool
71        config = GenerateContentConfig(
72            tools=[
73                Tool(google_search=GoogleSearch())
74            ]
75        )
76
77        # Generate content (synchronous call)
78        response = client.models.generate_content(
79            model=model_name,
80            contents=prompt,
81            config=config
82        )
83
84        # The high-level 'text' helper returns the combined text result in samples/docs
85        model_text = response.text or ""
86        model_text = model_text.strip()
87
88        if not model_text:
89            raise HTTPException(status_code=500, detail="No text returned from model.")
90
91        # Parse the model's exact-format response
92        status = "unsure"
93        summary = ""
94        explanation = ""
95
96        for line in model_text.splitlines():
97            line = line.strip()
98            if line.upper().startswith("STATUS:"):
99                s = line[len("STATUS:"):].strip().lower()
100                if "correct" in s and "incorrect" not in s:
101                    status = "correct"
102                elif "incorrect" in s:
103                    status = "incorrect"
104                else:
105                    status = "unsure"
106            elif line.upper().startswith("SUMMARY:"):
107                summary = line[len("SUMMARY:"):].strip()
108            elif line.upper().startswith("EXPLANATION:"):
109                explanation = line[len("EXPLANATION:"):].strip()
110            # If multi-line explanation, keep appending following lines (simple heuristic)
111            elif explanation and line:
112                explanation += "\n" + line
113
114        # Fallback if parsing failed
115        if not summary and not explanation:
116            summary = "Analysis completed (raw model output)"
117            explanation = model_text
118            status = "unsure"
119
120        # Additional safety defaults
121        if status == "incorrect" and not explanation:
122            explanation = "The model marked the text incorrect but did not provide a reason."
123        elif status == "unsure" and not explanation:
124            explanation = "There is insufficient reliable data available to make a definitive determination."
125        elif status == "correct" and not explanation:
126            explanation = "The provided information appears consistent with available data."
127
128        return FactCheckResponse(is_factual=status, summary=summary, explanation=explanation)
129
130    except Exception as e:
131        logger.exception("Fact-check generation failed")
132        raise HTTPException(status_code=500, detail=f"Error processing fact-check request: {e}")
133
134@app.get("/")
135def root():
136    return {
137        "message": "Fact Checker API using Google GenAI (Gemini) + Google Search grounding",
138        "endpoint": "/fact-check",
139        "method": "POST",
140        "notes": "Set GOOGLE_API_KEY in environment (Spaces Secrets)."
141    }
142
143@app.get("/health")
144def health_check():
145    return {"status": "healthy"}
146