anshmittal/fact-checking-api
0
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 