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 search_web, analyze_with_gemini
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
- # 1. Przeszukiwanie sieci
19
- web_results = search_web(statement, max_results=5)
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
- for idx in used_indices:
36
- source_idx = idx - 1 # Korekta indeksu (model liczy od 1)
37
- if 0 <= source_idx < len(web_results):
38
- r = web_results[source_idx]
39
- used_sources.append(FactCheckSource(
40
- title=r["title"],
41
- url=r["url"],
42
- snippet=r["snippet"]
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=used_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 search_web(query: str, max_results: int = 5) -> List[Dict[str, str]]:
12
- """Przeszukuje internet bez limit贸w i bez kluczy API za pomoc膮 DuckDuckGo."""
13
- logger.info(f"Wyszukiwanie w sieci dla zapytania: {query}")
14
- try:
15
- with DDGS() as ddgs:
16
- results = ddgs.text(query, max_results=max_results)
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
- "sources_used_indices": []
41
  }
42
 
43
  genai.configure(api_key=api_key)
44
 
45
- # Przygotowanie czytelnego tekstu ze 藕r贸d艂ami dla LLM
46
- sources_text = ""
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
- Twoim zadaniem jest ocena, czy podane STWIERDZENIE jest prawdziwe, fa艂szywe czy sporne na podstawie dostarczonych WYNIK脫W WYSZUKIWANIA.
52
 
53
  STWIERDZENIE DO WERYFIKACJI:
54
  "{statement}"
55
 
56
- WYNIKI WYSZUKIWANIA:
57
- {sources_text}
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 wraz z odniesieniem do 藕r贸de艂.",
63
- "confidence": 0.85,
64
- "sources_used_indices": [1, 3]
65
  }}
66
 
67
- Zasady oceny:
68
- - "PRAWDA": Wyniki jednoznacznie potwierdzaj膮 to stwierdzenie.
69
- - "FA艁SZ": Wyniki wykazuj膮 b艂膮d, dezinformacj臋 lub bezpo艣rednio zaprzeczaj膮 stwierdzeniu.
70
- - "SPORNE": Istniej膮 sprzeczne informacje, jest to kwestia opinii lub 藕r贸d艂a nie daj膮 jednoznacznej odpowiedzi.
71
-
72
- Zwr贸膰 TYLKO czysty obiekt JSON. Nie dodawaj blok贸w kodu ```json ani 偶adnych komentarzy poza obiektem JSON."""
73
 
74
  try:
75
- model = genai.GenerativeModel("gemini-1.5-flash")
 
 
 
 
 
76
  response = model.generate_content(
77
  prompt,
78
  generation_config=genai.types.GenerationConfig(
79
- temperature=0.0, # Niska temperatura chroni przed zmy艣laniem (hallucination)
80
- response_mime_type="application/json"
81
  )
82
  )
83
 
84
  raw_text = response.text.strip()
 
85
 
86
- # Oczyszczenie formatowania markdown, gdyby model mimo wszystko go doda艂
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
- return json.loads(raw_text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- "sources_used_indices": []
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
  }