diff --git a/app/api/__pycache__/fact_check.cpython-312.pyc b/app/api/__pycache__/fact_check.cpython-312.pyc index f784c29..a189688 100644 Binary files a/app/api/__pycache__/fact_check.cpython-312.pyc and b/app/api/__pycache__/fact_check.cpython-312.pyc differ diff --git a/app/api/fact_check.py b/app/api/fact_check.py index ab4cd9f..4d870a8 100644 --- a/app/api/fact_check.py +++ b/app/api/fact_check.py @@ -1,13 +1,17 @@ from fastapi import APIRouter, HTTPException import httpx +from typing import Union from app.config import GOOGLE_API_KEY, GOOGLE_FACT_CHECK_BASE_URL, OPENAI_API_KEY from app.api.scrap_websites import search_websites, SearchRequest from app.services.openai_client import OpenAIClient from app.models.fact_check_models import ( FactCheckRequest, FactCheckResponse, + UnverifiedFactCheckResponse, ErrorResponse, Source, + VerdictEnum, + ConfidenceEnum ) from app.websites.fact_checker_website import get_all_sources @@ -15,7 +19,7 @@ fact_check_router = APIRouter() openai_client = OpenAIClient(OPENAI_API_KEY) -async def generate_fact_report(query: str, fact_check_data: dict) -> FactCheckResponse: +async def generate_fact_report(query: str, fact_check_data: dict) -> Union[FactCheckResponse, UnverifiedFactCheckResponse]: """Generate a fact check report using OpenAI based on the fact check results.""" try: base_system_prompt = """You are a professional fact-checking reporter. Your task is to create a detailed fact check report based on the provided data. Focus on accuracy, clarity, and proper citation of sources. @@ -24,7 +28,24 @@ Rules: 1. Include all source URLs and names in the sources list 2. Keep the explanation focused on verifiable facts 3. Include dates when available -4. Maintain objectivity in the report""" +4. Maintain objectivity in the report +5. If no reliable sources are found, provide a clear explanation why""" + + # If no sources were found, return an unverified response + if not fact_check_data.get("claims") and ( + not fact_check_data.get("urls_found") or + fact_check_data.get("status") == "no_results" or + fact_check_data.get("verification_result", {}).get("no_sources_found") + ): + return UnverifiedFactCheckResponse( + claim=query, + verdict=VerdictEnum.UNVERIFIED, + confidence=ConfidenceEnum.LOW, + sources=[], + evidence="No fact-checking sources have verified this claim yet.", + explanation="Our search across reputable fact-checking websites did not find any formal verification of this claim. This doesn't mean the claim is false - just that it hasn't been formally fact-checked yet.", + additional_context="The claim may be too recent for fact-checkers to have investigated, or it may not have been widely circulated enough to warrant formal fact-checking." + ) base_user_prompt = """Generate a comprehensive fact check report in this exact JSON format: { @@ -40,9 +61,7 @@ Rules: "evidence": "A concise summary of the key evidence (1-2 sentences)", "explanation": "A detailed explanation including who verified it, when it was verified, and the key findings (2-3 sentences)", "additional_context": "Important context about the verification process, limitations, or broader implications (1-2 sentences)" -} - -Ensure all URLs in sources are complete (including https:// if missing) and each source has both a URL and name.""" +}""" if "claims" in fact_check_data: system_prompt = base_system_prompt @@ -71,75 +90,74 @@ Ensure all URLs in sources are complete (including https:// if missing) and each 4. Note any conflicting information between sources""" response = await openai_client.generate_text_response( - system_prompt=system_prompt, user_prompt=user_prompt, max_tokens=1000 + system_prompt=system_prompt, + user_prompt=user_prompt, + max_tokens=1000 ) try: - # First try to parse the response directly response_data = response["response"] - # Clean up sources before validation if isinstance(response_data.get("sources"), list): cleaned_sources = [] for source in response_data["sources"]: if isinstance(source, str): - # Convert string sources to Source objects - url = ( - source if source.startswith("http") else f"https://{source}" - ) + url = source if source.startswith("http") else f"https://{source}" cleaned_sources.append({"url": url, "name": source}) elif isinstance(source, dict): - # Ensure URL has proper scheme url = source.get("url", "") if url and not url.startswith("http"): source["url"] = f"https://{url}" cleaned_sources.append(source) response_data["sources"] = cleaned_sources - fact_check_response = FactCheckResponse(**response_data) - return fact_check_response + if response_data["verdict"] == "Unverified" or not response_data.get("sources"): + return UnverifiedFactCheckResponse(**response_data) + return FactCheckResponse(**response_data) except Exception as validation_error: print(f"Response validation error: {str(validation_error)}") - raise HTTPException( - status_code=422, - detail=ErrorResponse( - detail=f"Invalid response format: {str(validation_error)}", - error_code="VALIDATION_ERROR", - path="/check-facts", - ).dict(), + return UnverifiedFactCheckResponse( + claim=query, + verdict=VerdictEnum.UNVERIFIED, + confidence=ConfidenceEnum.LOW, + sources=[], + evidence="An error occurred while processing the fact check results.", + explanation="The system encountered an error while validating the fact check results.", + additional_context="This is a technical error and does not reflect on the truthfulness of the claim." ) except Exception as e: print(f"Error generating fact report: {str(e)}") - raise HTTPException( - status_code=500, - detail=ErrorResponse( - detail="Error generating fact report", - error_code="FACT_CHECK_ERROR", - path="/check-facts", - ).dict(), + return UnverifiedFactCheckResponse( + claim=query, + verdict=VerdictEnum.UNVERIFIED, + confidence=ConfidenceEnum.LOW, + sources=[], + evidence="An error occurred while generating the fact check report.", + explanation="The system encountered an unexpected error while processing the fact check request.", + additional_context="This is a technical error and does not reflect on the truthfulness of the claim." ) -@fact_check_router.post("/check-facts", response_model=FactCheckResponse) +@fact_check_router.post("/check-facts", response_model=Union[FactCheckResponse, UnverifiedFactCheckResponse]) async def check_facts(request: FactCheckRequest): """ Fetch fact check results and generate a comprehensive report. """ if not GOOGLE_API_KEY or not GOOGLE_FACT_CHECK_BASE_URL: - raise HTTPException( - status_code=500, - detail=ErrorResponse( - detail="Google API key or base URL is not configured", - error_code="CONFIGURATION_ERROR", - path="/check-facts", - ).dict(), + return UnverifiedFactCheckResponse( + claim=request.query, + verdict=VerdictEnum.UNVERIFIED, + confidence=ConfidenceEnum.LOW, + sources=[], + evidence="The fact-checking service is not properly configured.", + explanation="The system is missing required API configuration for fact-checking services.", + additional_context="This is a temporary system configuration issue." ) headers = {"Content-Type": "application/json"} async with httpx.AsyncClient() as client: - # Get fact checker sources from the centralized configuration fact_checker_sources = get_all_sources() for source in fact_checker_sources: @@ -170,7 +188,8 @@ async def check_facts(request: FactCheckRequest): try: search_request = SearchRequest( - search_text=request.query, source_types=["fact_checkers"] + search_text=request.query, + source_types=["fact_checkers"] ) ai_response = await search_websites(search_request) @@ -178,11 +197,10 @@ async def check_facts(request: FactCheckRequest): except Exception as e: print(f"Error in AI fact check: {str(e)}") - raise HTTPException( - status_code=404, - detail=ErrorResponse( - detail="No fact check results found", - error_code="NOT_FOUND", - path="/check-facts", - ).dict(), - ) + return await generate_fact_report(request.query, { + "status": "no_results", + "verification_result": { + "no_sources_found": True, + "reason": str(e) + } + }) \ No newline at end of file diff --git a/app/models/__pycache__/fact_check_models.cpython-312.pyc b/app/models/__pycache__/fact_check_models.cpython-312.pyc index 1e810e2..91cf86c 100644 Binary files a/app/models/__pycache__/fact_check_models.cpython-312.pyc and b/app/models/__pycache__/fact_check_models.cpython-312.pyc differ diff --git a/app/models/fact_check_models.py b/app/models/fact_check_models.py index 3ab4a8c..59ffbfe 100644 --- a/app/models/fact_check_models.py +++ b/app/models/fact_check_models.py @@ -33,12 +33,44 @@ class Source(BaseModel): @validator("url") def validate_url(cls, v): - # Basic URL validation without requiring HTTP/HTTPS if not v or len(v) < 3: raise ValueError("URL must not be empty and must be at least 3 characters") return v +class UnverifiedFactCheckResponse(BaseModel): + claim: str = Field( + ..., + min_length=10, + max_length=1000, + description="The exact claim being verified", + ) + verdict: VerdictEnum = Field(..., description="The verification verdict") + confidence: ConfidenceEnum = Field(..., description="Confidence level in the verdict") + sources: List[Source] = Field( + default=[], + description="List of sources used in verification" + ) + evidence: str = Field( + ..., + min_length=20, + max_length=500, + description="Concise summary of key evidence", + ) + explanation: str = Field( + ..., + min_length=50, + max_length=1000, + description="Detailed explanation of verification findings", + ) + additional_context: str = Field( + ..., + min_length=20, + max_length=500, + description="Important context about the verification", + ) + + class FactCheckResponse(BaseModel): claim: str = Field( ..., @@ -47,11 +79,11 @@ class FactCheckResponse(BaseModel): description="The exact claim being verified", ) verdict: VerdictEnum = Field(..., description="The verification verdict") - confidence: ConfidenceEnum = Field( - ..., description="Confidence level in the verdict" - ) + confidence: ConfidenceEnum = Field(..., description="Confidence level in the verdict") sources: List[Source] = Field( - ..., min_items=1, description="List of sources used in verification" + ..., + min_items=1, + description="List of sources used in verification" ) evidence: str = Field( ..., @@ -82,15 +114,11 @@ class FactCheckResponse(BaseModel): { "url": "https://www.nasa.gov/mars-exploration", "name": "NASA Mars Exploration", - }, - { - "url": "https://factcheck.org/2024/mars-claims", - "name": "FactCheck.org", - }, + } ], "evidence": "NASA has made no such announcement. Recent Mars rover images show natural rock formations.", - "explanation": "Multiple fact-checking organizations investigated this claim. NASA's official communications and Mars mission reports from 2024 contain no mention of alien structures. The viral images being shared are misidentified natural geological formations.", - "additional_context": "Similar false claims about alien structures on Mars have circulated periodically since the first Mars rovers began sending back images.", + "explanation": "Multiple fact-checking organizations investigated this claim. NASA's official communications and Mars mission reports from 2024 contain no mention of alien structures.", + "additional_context": "Similar false claims about alien structures on Mars have circulated periodically.", } } @@ -98,4 +126,4 @@ class FactCheckResponse(BaseModel): class ErrorResponse(BaseModel): detail: str error_code: str = Field(..., example="VALIDATION_ERROR") - path: str = Field(..., example="/check-facts") + path: str = Field(..., example="/check-facts") \ No newline at end of file diff --git a/images-test.jpg b/images-test.jpg new file mode 100644 index 0000000..868ab17 Binary files /dev/null and b/images-test.jpg differ diff --git a/main.py b/main.py index 7048f3b..6db5c7d 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,7 @@ origins = [ "http://localhost:5173", "http://0.0.0.0", "http://0.0.0.0:5173", + "*" ]