fix(nordagpt): structural anti-hallucination — validate ALL company links against DB
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Prompt rules don't work — AI ignores them and invents company names. Added _validate_company_references() post-processor that: - Loads all valid company slugs from DB - Scans every /firma/ link in AI response - REMOVES links to companies that don't exist - Cleans up empty list items left by removals - Applied to BOTH send_message() and send_message_stream() This is the ONLY reliable way to prevent hallucinated companies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d7a8cbe459
commit
c167794bb6
@ -134,6 +134,61 @@ class NordaBizChatEngine:
|
||||
self.model_name = self.gemini_service.model_name
|
||||
self.model = None
|
||||
|
||||
@staticmethod
|
||||
def _get_valid_company_slugs() -> set:
|
||||
"""Load all valid company slugs from DB. Cached per-request."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from database import Company
|
||||
rows = db.query(Company.slug, Company.name).filter(Company.status == 'active').all()
|
||||
return {r.slug: r.name for r in rows if r.slug}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@staticmethod
|
||||
def _validate_company_references(text: str) -> str:
|
||||
"""
|
||||
Post-process AI response: remove links to companies that don't exist in DB.
|
||||
This is the ONLY reliable way to prevent hallucinated company names.
|
||||
"""
|
||||
import re
|
||||
|
||||
valid_companies = NordaBizChatEngine._get_valid_company_slugs()
|
||||
valid_slugs = set(valid_companies.keys())
|
||||
valid_names_lower = {name.lower(): name for name in valid_companies.values()}
|
||||
|
||||
# 1. Validate markdown links to /firma/slug — remove if slug doesn't exist
|
||||
def replace_link(match):
|
||||
link_text = match.group(1)
|
||||
slug = match.group(2)
|
||||
if slug in valid_slugs:
|
||||
return match.group(0) # Keep valid link
|
||||
else:
|
||||
logger.warning(f"NordaGPT hallucination blocked: removed link to non-existent company slug '{slug}' (text: '{link_text}')")
|
||||
return '' # Remove entire link
|
||||
|
||||
text = re.sub(r'\[([^\]]+)\]\(/firma/([a-z0-9-]+)\)', replace_link, text)
|
||||
|
||||
# 2. Validate pill-style links that the frontend renders
|
||||
def replace_pill_link(match):
|
||||
full_match = match.group(0)
|
||||
slug = match.group(1)
|
||||
if slug in valid_slugs:
|
||||
return full_match
|
||||
else:
|
||||
logger.warning(f"NordaGPT hallucination blocked: removed pill link to '{slug}'")
|
||||
return ''
|
||||
|
||||
text = re.sub(r'<a[^>]*href=["\']/firma/([a-z0-9-]+)["\'][^>]*>.*?</a>', replace_pill_link, text)
|
||||
|
||||
# 3. Clean up empty list items and double spaces left by removals
|
||||
text = re.sub(r'\n\s*\*\s*\n', '\n', text) # empty bullet points
|
||||
text = re.sub(r'\n\s*-\s*\n', '\n', text) # empty list items
|
||||
text = re.sub(r' +', ' ', text) # double spaces
|
||||
text = re.sub(r'\n{3,}', '\n\n', text) # triple+ newlines
|
||||
|
||||
return text.strip()
|
||||
|
||||
def start_conversation(
|
||||
self,
|
||||
user_id: int,
|
||||
@ -294,6 +349,9 @@ class NordaBizChatEngine:
|
||||
user_context=user_context
|
||||
)
|
||||
|
||||
# CRITICAL: Validate all company references — remove hallucinated firms
|
||||
response = self._validate_company_references(response)
|
||||
|
||||
# Calculate metrics for per-message tracking in AIChatMessage table
|
||||
latency_ms = int((time.time() - start_time) * 1000)
|
||||
if self.gemini_service:
|
||||
@ -1644,6 +1702,9 @@ W dyskusji [Artur Wiertel](link) pytał o moderację. Pełna treść: [moje uwag
|
||||
# Post-process links in full response
|
||||
full_response_text = self._postprocess_links(full_response_text, context)
|
||||
|
||||
# CRITICAL: Validate all company references — remove hallucinated firms
|
||||
full_response_text = self._validate_company_references(full_response_text)
|
||||
|
||||
# Calculate metrics
|
||||
latency_ms = int((time.time() - start_time) * 1000)
|
||||
input_tokens = len(full_prompt) // 4
|
||||
|
||||
Loading…
Reference in New Issue
Block a user