Skip to content

Commit 6e548f4

Browse files
Merge pull request #162 from ComplexData-MILA/source-filter
Filter blocked and low-credibility sources
2 parents 0969aed + 5d4f0f8 commit 6e548f4

1 file changed

Lines changed: 80 additions & 12 deletions

File tree

app/services/implementations/web_search_service.py

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
1-
from typing import List, Optional
2-
import aiohttp
3-
from datetime import UTC, datetime
41
import logging
2+
from datetime import UTC, datetime
3+
from typing import List, Optional
54
from uuid import UUID, uuid4
6-
from app.core.config import settings
5+
6+
import aiohttp
77
from sqlalchemy.exc import IntegrityError
88

9+
from app.core.config import settings
910
from app.core.exceptions import ValidationError
11+
from app.core.utils.url import normalize_domain_name
1012
from app.models.database.models import SourceModel
11-
from app.services.interfaces.web_search_service import WebSearchServiceInterface
1213
from app.repositories.implementations.source_repository import SourceRepository
1314
from app.services.domain_service import DomainService
14-
from app.core.utils.url import normalize_domain_name
15+
from app.services.interfaces.web_search_service import WebSearchServiceInterface
1516

1617
logger = logging.getLogger(__name__)
1718

19+
# hard filter
20+
BLOCKED_SOURCE_DOMAINS = {
21+
"reddit.com",
22+
"instagram.com",
23+
"youtube.com",
24+
"youtu.be",
25+
"bsky.app",
26+
"bsky.social",
27+
"tiktok.com",
28+
"facebook.com",
29+
"x.com",
30+
"twitter.com",
31+
"threads.net",
32+
}
33+
1834

1935
class GoogleWebSearchService(WebSearchServiceInterface):
2036
def __init__(self, domain_service: DomainService, source_repository: SourceRepository):
@@ -24,24 +40,50 @@ def __init__(self, domain_service: DomainService, source_repository: SourceRepos
2440
self.domain_service = domain_service
2541
self.source_repository = source_repository
2642

43+
def _is_blocked_domain(self, domain_name: str) -> bool:
44+
normalized_domain = normalize_domain_name(domain_name)
45+
return any(
46+
normalized_domain == blocked_domain or normalized_domain.endswith(f".{blocked_domain}")
47+
for blocked_domain in BLOCKED_SOURCE_DOMAINS
48+
)
49+
50+
def _has_acceptable_credibility(self, credibility_score: Optional[float]) -> bool:
51+
# This removes both unknown credibility and explicit 0 credibility.
52+
return credibility_score is not None and credibility_score > 0
53+
54+
def _is_allowed_source(self, source: SourceModel) -> bool:
55+
domain_name = (
56+
source.domain.domain_name
57+
if hasattr(source, "domain") and source.domain
58+
else normalize_domain_name(source.url)
59+
)
60+
return not self._is_blocked_domain(domain_name) and self._has_acceptable_credibility(source.credibility_score)
61+
62+
def _filter_allowed_sources(self, sources: List[SourceModel]) -> List[SourceModel]:
63+
return [source for source in sources if self._is_allowed_source(source)]
64+
2765
async def search_and_create_sources(
2866
self, claim_text: str, search_id: UUID, num_results: int = 5, language: str = "english"
2967
) -> List[SourceModel]:
3068
"""Search for sources and create or update records."""
3169
try:
70+
logger.warning("NEW SOURCE FILTER CODE IS RUNNING")
3271
params = {
3372
"key": self.api_key,
3473
"cx": self.search_engine_id,
3574
"q": claim_text,
36-
"num": min(num_results, 10),
75+
# "num": min(num_results, 10),
76+
# Fetch max results because source policy filtering happens after search, not in the query.
77+
"num": 10,
3778
"fields": "items(title,link,snippet)",
3879
}
3980
if language == "english":
4081
params = {
4182
"key": self.api_key,
4283
"cx": self.search_engine_id,
4384
"q": claim_text,
44-
"num": min(num_results, 10),
85+
# "num": min(num_results, 10),
86+
"num": 10,
4587
"fields": "items(title,link,snippet)",
4688
"lr": "lang_en",
4789
}
@@ -50,7 +92,8 @@ async def search_and_create_sources(
5092
"key": self.api_key,
5193
"cx": self.search_engine_id,
5294
"q": claim_text,
53-
"num": min(num_results, 10),
95+
# "num": min(num_results, 10),
96+
"num": 10,
5497
"fields": "items(title,link,snippet)",
5598
"lr": "lang_fr",
5699
}
@@ -70,16 +113,38 @@ async def search_and_create_sources(
70113

71114
for item in data["items"]:
72115
try:
116+
# domain_name = normalize_domain_name(item["link"])
117+
# domain, is_new = await self.domain_service.get_or_create_domain(domain_name)
118+
119+
# if is_new:
120+
# logger.info(f"Created new domain record for: {domain_name}")
121+
122+
# source = await self._create_new_source(item, search_id, domain.id, domain.credibility_score)
123+
# if source:
124+
# sources.append(source)
125+
# logger.debug(f"Created new source for URL: {item['link']}")
73126
domain_name = normalize_domain_name(item["link"])
127+
if self._is_blocked_domain(domain_name):
128+
logger.info(f"Skipping blocked source domain: {domain_name}")
129+
continue
74130
domain, is_new = await self.domain_service.get_or_create_domain(domain_name)
75-
76131
if is_new:
77132
logger.info(f"Created new domain record for: {domain_name}")
133+
if not self._has_acceptable_credibility(domain.credibility_score):
134+
logger.info(
135+
f"Skipping source with low/unknown credibility: "
136+
f"{domain_name} ({domain.credibility_score})"
137+
)
138+
continue
78139

79-
source = await self._create_new_source(item, search_id, domain.id, domain.credibility_score)
140+
source = await self._create_new_source(
141+
item,
142+
search_id,
143+
domain.id,
144+
domain.credibility_score,
145+
)
80146
if source:
81147
sources.append(source)
82-
logger.debug(f"Created new source for URL: {item['link']}")
83148

84149
except Exception as e:
85150
logger.error(f"Error processing search result: {str(e)}", exc_info=True)
@@ -128,6 +193,7 @@ async def _create_new_source(
128193

129194
def format_sources_for_prompt(self, sources: List[SourceModel], language: str = "english") -> str:
130195
"""Format sources into a string for the LLM prompt."""
196+
sources = self._filter_allowed_sources(sources)
131197
if language == "english":
132198
if not sources:
133199
return "No reliable sources found."
@@ -178,10 +244,12 @@ def format_sources_for_prompt(self, sources: List[SourceModel], language: str =
178244

179245
def calculate_overall_credibility(self, sources: List[SourceModel]) -> float:
180246
"""Calculate overall credibility score for a set of sources."""
247+
sources = self._filter_allowed_sources(sources)
181248
if not sources:
182249
return 0.0
183250

184251
# Filter out sources with null credibility scores
252+
# valid_scores = [source.credibility_score for source in sources if source.credibility_score is not None]
185253
valid_scores = [source.credibility_score for source in sources if source.credibility_score is not None]
186254

187255
if not valid_scores:

0 commit comments

Comments
 (0)