Spaces:
Sleeping
Sleeping
Trololindo commited on
Commit 路
9fe5e6c
1
Parent(s): 7f03076
podejscie 2 googleless
Browse files
backend/app/api/factcheck_router.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
from fastapi import APIRouter, HTTPException
|
| 2 |
from app.models.factcheck_schemas import FactCheckRequest, FactCheckResponse, FactCheckSource
|
| 3 |
-
from app.services.factcheck_service import
|
| 4 |
|
| 5 |
router = APIRouter()
|
| 6 |
|
|
@@ -15,43 +15,21 @@ async def fact_check_endpoint(payload: FactCheckRequest):
|
|
| 15 |
if len(statement) < 10:
|
| 16 |
raise HTTPException(status_code=400, detail="Tekst do weryfikacji musi mie膰 co najmniej 10 znak贸w.")
|
| 17 |
|
| 18 |
-
#
|
| 19 |
-
|
| 20 |
-
if not web_results:
|
| 21 |
-
return FactCheckResponse(
|
| 22 |
-
verdict="SPORNE",
|
| 23 |
-
explanation="Wyszukiwarka nie zwr贸ci艂a 偶adnych wynik贸w w internecie dla tego stwierdzenia, co uniemo偶liwia weryfikacj臋.",
|
| 24 |
-
confidence=0.0,
|
| 25 |
-
sources=[]
|
| 26 |
-
)
|
| 27 |
-
|
| 28 |
-
# 2. Analiza przez LLM
|
| 29 |
-
analysis = await analyze_with_gemini(statement, web_results)
|
| 30 |
-
|
| 31 |
-
# 3. Przypisanie 藕r贸de艂 na podstawie decyzji LLM
|
| 32 |
-
used_indices = analysis.get("sources_used_indices", [])
|
| 33 |
-
used_sources = []
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
# Je艣li model nie wskaza艂 konkretnych indeks贸w, dajemy top 3 znalezione 藕r贸d艂a
|
| 46 |
-
if not used_sources:
|
| 47 |
-
used_sources = [
|
| 48 |
-
FactCheckSource(title=r["title"], url=r["url"], snippet=r["snippet"])
|
| 49 |
-
for r in web_results[:3]
|
| 50 |
-
]
|
| 51 |
-
|
| 52 |
return FactCheckResponse(
|
| 53 |
verdict=analysis.get("verdict", "SPORNE"),
|
| 54 |
explanation=analysis.get("explanation", "Brak szczeg贸艂owego uzasadnienia."),
|
| 55 |
confidence=analysis.get("confidence", 0.5),
|
| 56 |
-
sources=
|
| 57 |
)
|
|
|
|
| 1 |
from fastapi import APIRouter, HTTPException
|
| 2 |
from app.models.factcheck_schemas import FactCheckRequest, FactCheckResponse, FactCheckSource
|
| 3 |
+
from app.services.factcheck_service import analyze_with_gemini_grounding
|
| 4 |
|
| 5 |
router = APIRouter()
|
| 6 |
|
|
|
|
| 15 |
if len(statement) < 10:
|
| 16 |
raise HTTPException(status_code=400, detail="Tekst do weryfikacji musi mie膰 co najmniej 10 znak贸w.")
|
| 17 |
|
| 18 |
+
# Wywo艂ujemy us艂ug臋 integruj膮c膮 Google Search i Gemini
|
| 19 |
+
analysis = await analyze_with_gemini_grounding(statement)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
# Konwersja s艂ownik贸w na obiekty Pydantic
|
| 22 |
+
formatted_sources = []
|
| 23 |
+
for s in analysis.get("sources", []):
|
| 24 |
+
formatted_sources.append(FactCheckSource(
|
| 25 |
+
title=s["title"],
|
| 26 |
+
url=s["url"],
|
| 27 |
+
snippet=s["snippet"]
|
| 28 |
+
))
|
| 29 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
return FactCheckResponse(
|
| 31 |
verdict=analysis.get("verdict", "SPORNE"),
|
| 32 |
explanation=analysis.get("explanation", "Brak szczeg贸艂owego uzasadnienia."),
|
| 33 |
confidence=analysis.get("confidence", 0.5),
|
| 34 |
+
sources=formatted_sources
|
| 35 |
)
|
backend/app/services/factcheck_service.py
CHANGED
|
@@ -3,32 +3,16 @@ import json
|
|
| 3 |
import re
|
| 4 |
import os
|
| 5 |
from typing import Dict, Any, List
|
| 6 |
-
from duckduckgo_search import DDGS
|
| 7 |
import google.generativeai as genai
|
| 8 |
|
| 9 |
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
-
def
|
| 12 |
-
"""
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
formatted_results = []
|
| 18 |
-
for r in results:
|
| 19 |
-
formatted_results.append({
|
| 20 |
-
"title": r.get("title", "Brak tytu艂u"),
|
| 21 |
-
"url": r.get("href", ""),
|
| 22 |
-
"snippet": r.get("body", "Brak opisu")
|
| 23 |
-
})
|
| 24 |
-
return formatted_results
|
| 25 |
-
except Exception as e:
|
| 26 |
-
logger.error(f"B艂膮d wyszukiwania DuckDuckGo: {e}", exc_info=True)
|
| 27 |
-
return []
|
| 28 |
-
|
| 29 |
-
async def analyze_with_gemini(statement: str, sources: List[Dict[str, str]]) -> Dict[str, Any]:
|
| 30 |
-
"""Analizuje stwierdzenie na podstawie wynik贸w wyszukiwania za pomoc膮 Gemini API."""
|
| 31 |
-
# Pobieramy klucz bezpo艣rednio ze 艣rodowiska lub .env
|
| 32 |
api_key = os.getenv("GEMINI_API_KEY")
|
| 33 |
|
| 34 |
if not api_key:
|
|
@@ -37,64 +21,83 @@ async def analyze_with_gemini(statement: str, sources: List[Dict[str, str]]) ->
|
|
| 37 |
"verdict": "SPORNE",
|
| 38 |
"explanation": "B艂膮d backendu: Brak skonfigurowanego klucza GEMINI_API_KEY w pliku .env.",
|
| 39 |
"confidence": 0.0,
|
| 40 |
-
"
|
| 41 |
}
|
| 42 |
|
| 43 |
genai.configure(api_key=api_key)
|
| 44 |
|
| 45 |
-
#
|
| 46 |
-
|
| 47 |
-
for idx, s in enumerate(sources, start=1):
|
| 48 |
-
sources_text += f"[{idx}] Tytu艂: {s['title']}\nURL: {s['url']}\nTre艣膰: {s['snippet']}\n\n"
|
| 49 |
-
|
| 50 |
prompt = f"""Jeste艣 zaawansowanym asystentem do weryfikacji fakt贸w (fact-checking).
|
| 51 |
-
|
| 52 |
|
| 53 |
STWIERDZENIE DO WERYFIKACJI:
|
| 54 |
"{statement}"
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
Wygeneruj rzeteln膮 analiz臋. Odpowiedz w j臋zyku polskim. Twoja odpowied藕 MUSI by膰 poprawnym, czystym obiektem JSON o nast臋puj膮cym formacie (i niczym innym):
|
| 60 |
{{
|
| 61 |
"verdict": "PRAWDA" lub "FA艁SZ" lub "SPORNE",
|
| 62 |
-
"explanation": "Zwi臋z艂e (2-4 zdania), merytoryczne i obiektywne uzasadnienie werdyktu w j臋zyku polskim
|
| 63 |
-
"confidence": 0.85,
|
| 64 |
-
"sources_used_indices": [1, 3]
|
| 65 |
}}
|
| 66 |
|
| 67 |
-
|
| 68 |
-
- "PRAWDA":
|
| 69 |
-
- "FA艁SZ":
|
| 70 |
-
- "SPORNE":
|
| 71 |
-
|
| 72 |
-
Zwr贸膰 TYLKO czysty obiekt JSON. Nie dodawaj blok贸w kodu ```json ani 偶adnych komentarzy poza obiektem JSON."""
|
| 73 |
|
| 74 |
try:
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
response = model.generate_content(
|
| 77 |
prompt,
|
| 78 |
generation_config=genai.types.GenerationConfig(
|
| 79 |
-
temperature=0.0
|
| 80 |
-
response_mime_type="application/json"
|
| 81 |
)
|
| 82 |
)
|
| 83 |
|
| 84 |
raw_text = response.text.strip()
|
|
|
|
| 85 |
|
| 86 |
-
#
|
| 87 |
if raw_text.startswith("```"):
|
| 88 |
match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", raw_text, re.DOTALL)
|
| 89 |
if match:
|
| 90 |
raw_text = match.group(1)
|
| 91 |
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
except Exception as e:
|
| 94 |
-
logger.error(f"B艂膮d analizy Gemini API: {e}", exc_info=True)
|
| 95 |
return {
|
| 96 |
"verdict": "SPORNE",
|
| 97 |
"explanation": f"Wyst膮pi艂 b艂膮d komunikacji z modelem j臋zykowym: {str(e)}",
|
| 98 |
"confidence": 0.0,
|
| 99 |
-
"
|
| 100 |
}
|
|
|
|
| 3 |
import re
|
| 4 |
import os
|
| 5 |
from typing import Dict, Any, List
|
|
|
|
| 6 |
import google.generativeai as genai
|
| 7 |
|
| 8 |
logger = logging.getLogger(__name__)
|
| 9 |
|
| 10 |
+
async def analyze_with_gemini_grounding(statement: str) -> Dict[str, Any]:
|
| 11 |
+
"""
|
| 12 |
+
Analizuje stwierdzenie, automatycznie przeszukuj膮c internet za pomoc膮
|
| 13 |
+
wbudowanego w Gemini narz臋dzia Google Search Grounding.
|
| 14 |
+
Rozwi膮zuje to ca艂kowicie problemy z blokowaniem i timeoutami wyszukiwarek.
|
| 15 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
api_key = os.getenv("GEMINI_API_KEY")
|
| 17 |
|
| 18 |
if not api_key:
|
|
|
|
| 21 |
"verdict": "SPORNE",
|
| 22 |
"explanation": "B艂膮d backendu: Brak skonfigurowanego klucza GEMINI_API_KEY w pliku .env.",
|
| 23 |
"confidence": 0.0,
|
| 24 |
+
"sources": []
|
| 25 |
}
|
| 26 |
|
| 27 |
genai.configure(api_key=api_key)
|
| 28 |
|
| 29 |
+
# Poniewa偶 nie mo偶emy 艂膮czy膰 narz臋dzia wyszukiwania (Google Search) z trybem JSON w konfiguracji API,
|
| 30 |
+
# wymuszamy struktur臋 JSON za pomoc膮 precyzyjnego promptu systemowego.
|
|
|
|
|
|
|
|
|
|
| 31 |
prompt = f"""Jeste艣 zaawansowanym asystentem do weryfikacji fakt贸w (fact-checking).
|
| 32 |
+
Przeanalizuj poni偶sze stwierdzenie, korzystaj膮c z wyszukiwarki Google (masz do niej dost臋p jako narz臋dzie), aby zweryfikowa膰 jego prawdziwo艣膰 w czasie rzeczywistym.
|
| 33 |
|
| 34 |
STWIERDZENIE DO WERYFIKACJI:
|
| 35 |
"{statement}"
|
| 36 |
|
| 37 |
+
Twoja odpowied藕 musi by膰 wy艂膮cznie poprawnym obiektem JSON (bez blok贸w kodu typu ```json, bez dodatkowego tekstu na pocz膮tku ani na ko艅cu).
|
| 38 |
+
Format JSON:
|
|
|
|
|
|
|
| 39 |
{{
|
| 40 |
"verdict": "PRAWDA" lub "FA艁SZ" lub "SPORNE",
|
| 41 |
+
"explanation": "Zwi臋z艂e (2-4 zdania), merytoryczne i obiektywne uzasadnienie werdyktu w j臋zyku polskim, wyja艣niaj膮ce co m贸wi膮 fakty."
|
|
|
|
|
|
|
| 42 |
}}
|
| 43 |
|
| 44 |
+
Wskaz贸wki do werdyktu:
|
| 45 |
+
- "PRAWDA": Najnowsze fakty i wiarygodne 藕r贸d艂a w pe艂ni potwierdzaj膮 to stwierdzenie.
|
| 46 |
+
- "FA艁SZ": Fakty jednoznacznie zaprzeczaj膮 temu stwierdzeniu.
|
| 47 |
+
- "SPORNE": Informacje w sieci s膮 sprzeczne, jest to kwestia opinii lub brak jednoznacznych dowod贸w.
|
| 48 |
+
"""
|
|
|
|
| 49 |
|
| 50 |
try:
|
| 51 |
+
# Inicjalizacja modelu z wbudowanym narz臋dziem Google Search
|
| 52 |
+
model = genai.GenerativeModel(
|
| 53 |
+
model_name="gemini-1.5-flash",
|
| 54 |
+
tools=[{"google_search": {}}] # W艂膮czenie Google Search Grounding
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
response = model.generate_content(
|
| 58 |
prompt,
|
| 59 |
generation_config=genai.types.GenerationConfig(
|
| 60 |
+
temperature=0.0 # Niska temperatura chroni przed zmy艣laniem (halucynacjami)
|
|
|
|
| 61 |
)
|
| 62 |
)
|
| 63 |
|
| 64 |
raw_text = response.text.strip()
|
| 65 |
+
logger.info(f"Surowa odpowied藕 Gemini: {raw_text}")
|
| 66 |
|
| 67 |
+
# Wyczyszczenie tekstu z ewentualnych znacznik贸w markdown ```json ... ```
|
| 68 |
if raw_text.startswith("```"):
|
| 69 |
match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", raw_text, re.DOTALL)
|
| 70 |
if match:
|
| 71 |
raw_text = match.group(1)
|
| 72 |
|
| 73 |
+
result_json = json.loads(raw_text)
|
| 74 |
+
|
| 75 |
+
# Wyci膮ganie realnych 藕r贸de艂 (link贸w i tytu艂贸w), z kt贸rych skorzysta艂 model
|
| 76 |
+
sources = []
|
| 77 |
+
candidate = response.candidates[0]
|
| 78 |
+
metadata = getattr(candidate, "grounding_metadata", None)
|
| 79 |
+
|
| 80 |
+
if metadata and getattr(metadata, "grounding_chunks", None):
|
| 81 |
+
for chunk in metadata.grounding_chunks:
|
| 82 |
+
if chunk.web:
|
| 83 |
+
sources.append({
|
| 84 |
+
"title": chunk.web.title,
|
| 85 |
+
"url": chunk.web.uri,
|
| 86 |
+
"snippet": "殴r贸d艂o zweryfikowane bezpo艣rednio przez wyszukiwark臋 Google."
|
| 87 |
+
})
|
| 88 |
+
|
| 89 |
+
return {
|
| 90 |
+
"verdict": result_json.get("verdict", "SPORNE"),
|
| 91 |
+
"explanation": result_json.get("explanation", "Brak uzasadnienia."),
|
| 92 |
+
"confidence": 0.95 if result_json.get("verdict") in ["PRAWDA", "FA艁SZ"] else 0.5,
|
| 93 |
+
"sources": sources
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
except Exception as e:
|
| 97 |
+
logger.error(f"B艂膮d analizy Gemini Grounding API: {e}", exc_info=True)
|
| 98 |
return {
|
| 99 |
"verdict": "SPORNE",
|
| 100 |
"explanation": f"Wyst膮pi艂 b艂膮d komunikacji z modelem j臋zykowym: {str(e)}",
|
| 101 |
"confidence": 0.0,
|
| 102 |
+
"sources": []
|
| 103 |
}
|