fixed response

This commit is contained in:
Utsho Dey 2024-12-18 13:10:03 +06:00
parent 9be0343695
commit 15a0061a0d
6 changed files with 107 additions and 60 deletions

View file

@ -1,13 +1,17 @@
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
import httpx import httpx
from typing import Union
from app.config import GOOGLE_API_KEY, GOOGLE_FACT_CHECK_BASE_URL, OPENAI_API_KEY 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.api.scrap_websites import search_websites, SearchRequest
from app.services.openai_client import OpenAIClient from app.services.openai_client import OpenAIClient
from app.models.fact_check_models import ( from app.models.fact_check_models import (
FactCheckRequest, FactCheckRequest,
FactCheckResponse, FactCheckResponse,
UnverifiedFactCheckResponse,
ErrorResponse, ErrorResponse,
Source, Source,
VerdictEnum,
ConfidenceEnum
) )
from app.websites.fact_checker_website import get_all_sources from app.websites.fact_checker_website import get_all_sources
@ -15,7 +19,7 @@ fact_check_router = APIRouter()
openai_client = OpenAIClient(OPENAI_API_KEY) 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.""" """Generate a fact check report using OpenAI based on the fact check results."""
try: 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. 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 1. Include all source URLs and names in the sources list
2. Keep the explanation focused on verifiable facts 2. Keep the explanation focused on verifiable facts
3. Include dates when available 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: 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)", "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)", "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)" "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: if "claims" in fact_check_data:
system_prompt = base_system_prompt 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""" 4. Note any conflicting information between sources"""
response = await openai_client.generate_text_response( 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: try:
# First try to parse the response directly
response_data = response["response"] response_data = response["response"]
# Clean up sources before validation
if isinstance(response_data.get("sources"), list): if isinstance(response_data.get("sources"), list):
cleaned_sources = [] cleaned_sources = []
for source in response_data["sources"]: for source in response_data["sources"]:
if isinstance(source, str): 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}) cleaned_sources.append({"url": url, "name": source})
elif isinstance(source, dict): elif isinstance(source, dict):
# Ensure URL has proper scheme
url = source.get("url", "") url = source.get("url", "")
if url and not url.startswith("http"): if url and not url.startswith("http"):
source["url"] = f"https://{url}" source["url"] = f"https://{url}"
cleaned_sources.append(source) cleaned_sources.append(source)
response_data["sources"] = cleaned_sources response_data["sources"] = cleaned_sources
fact_check_response = FactCheckResponse(**response_data) if response_data["verdict"] == "Unverified" or not response_data.get("sources"):
return fact_check_response return UnverifiedFactCheckResponse(**response_data)
return FactCheckResponse(**response_data)
except Exception as validation_error: except Exception as validation_error:
print(f"Response validation error: {str(validation_error)}") print(f"Response validation error: {str(validation_error)}")
raise HTTPException( return UnverifiedFactCheckResponse(
status_code=422, claim=query,
detail=ErrorResponse( verdict=VerdictEnum.UNVERIFIED,
detail=f"Invalid response format: {str(validation_error)}", confidence=ConfidenceEnum.LOW,
error_code="VALIDATION_ERROR", sources=[],
path="/check-facts", evidence="An error occurred while processing the fact check results.",
).dict(), 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: except Exception as e:
print(f"Error generating fact report: {str(e)}") print(f"Error generating fact report: {str(e)}")
raise HTTPException( return UnverifiedFactCheckResponse(
status_code=500, claim=query,
detail=ErrorResponse( verdict=VerdictEnum.UNVERIFIED,
detail="Error generating fact report", confidence=ConfidenceEnum.LOW,
error_code="FACT_CHECK_ERROR", sources=[],
path="/check-facts", evidence="An error occurred while generating the fact check report.",
).dict(), 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): async def check_facts(request: FactCheckRequest):
""" """
Fetch fact check results and generate a comprehensive report. Fetch fact check results and generate a comprehensive report.
""" """
if not GOOGLE_API_KEY or not GOOGLE_FACT_CHECK_BASE_URL: if not GOOGLE_API_KEY or not GOOGLE_FACT_CHECK_BASE_URL:
raise HTTPException( return UnverifiedFactCheckResponse(
status_code=500, claim=request.query,
detail=ErrorResponse( verdict=VerdictEnum.UNVERIFIED,
detail="Google API key or base URL is not configured", confidence=ConfidenceEnum.LOW,
error_code="CONFIGURATION_ERROR", sources=[],
path="/check-facts", evidence="The fact-checking service is not properly configured.",
).dict(), 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"} headers = {"Content-Type": "application/json"}
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
# Get fact checker sources from the centralized configuration
fact_checker_sources = get_all_sources() fact_checker_sources = get_all_sources()
for source in fact_checker_sources: for source in fact_checker_sources:
@ -170,7 +188,8 @@ async def check_facts(request: FactCheckRequest):
try: try:
search_request = SearchRequest( 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) ai_response = await search_websites(search_request)
@ -178,11 +197,10 @@ async def check_facts(request: FactCheckRequest):
except Exception as e: except Exception as e:
print(f"Error in AI fact check: {str(e)}") print(f"Error in AI fact check: {str(e)}")
raise HTTPException( return await generate_fact_report(request.query, {
status_code=404, "status": "no_results",
detail=ErrorResponse( "verification_result": {
detail="No fact check results found", "no_sources_found": True,
error_code="NOT_FOUND", "reason": str(e)
path="/check-facts", }
).dict(), })
)

View file

@ -33,12 +33,44 @@ class Source(BaseModel):
@validator("url") @validator("url")
def validate_url(cls, v): def validate_url(cls, v):
# Basic URL validation without requiring HTTP/HTTPS
if not v or len(v) < 3: if not v or len(v) < 3:
raise ValueError("URL must not be empty and must be at least 3 characters") raise ValueError("URL must not be empty and must be at least 3 characters")
return v 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): class FactCheckResponse(BaseModel):
claim: str = Field( claim: str = Field(
..., ...,
@ -47,11 +79,11 @@ class FactCheckResponse(BaseModel):
description="The exact claim being verified", description="The exact claim being verified",
) )
verdict: VerdictEnum = Field(..., description="The verification verdict") verdict: VerdictEnum = Field(..., description="The verification verdict")
confidence: ConfidenceEnum = Field( confidence: ConfidenceEnum = Field(..., description="Confidence level in the verdict")
..., description="Confidence level in the verdict"
)
sources: List[Source] = Field( 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( evidence: str = Field(
..., ...,
@ -82,15 +114,11 @@ class FactCheckResponse(BaseModel):
{ {
"url": "https://www.nasa.gov/mars-exploration", "url": "https://www.nasa.gov/mars-exploration",
"name": "NASA 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.", "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.", "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 since the first Mars rovers began sending back images.", "additional_context": "Similar false claims about alien structures on Mars have circulated periodically.",
} }
} }
@ -98,4 +126,4 @@ class FactCheckResponse(BaseModel):
class ErrorResponse(BaseModel): class ErrorResponse(BaseModel):
detail: str detail: str
error_code: str = Field(..., example="VALIDATION_ERROR") error_code: str = Field(..., example="VALIDATION_ERROR")
path: str = Field(..., example="/check-facts") path: str = Field(..., example="/check-facts")

BIN
images-test.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -17,6 +17,7 @@ origins = [
"http://localhost:5173", "http://localhost:5173",
"http://0.0.0.0", "http://0.0.0.0",
"http://0.0.0.0:5173", "http://0.0.0.0:5173",
"*"
] ]