fix(nordagpt): nuclear anti-hallucination — whitelist + bold text validation
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
Three layers of defense: 1. PROMPT: explicit whitelist of allowed company names + slugs 2. VALIDATOR: link slug verification (existing) 3. VALIDATOR: bold text scan — removes **FakeName** if not in DB AI can no longer mention companies as plain/bold text to bypass link validation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
464e456939
commit
855856dc99
@ -215,13 +215,41 @@ class NordaBizChatEngine:
|
||||
|
||||
text = re.sub(r'<a[^>]*href=["\']/firma/([a-z0-9-]+)["\'][^>]*>(.*?)</a>', replace_pill_link, text)
|
||||
|
||||
# 3. Clean up artifacts left by removals
|
||||
text = re.sub(r'\*\s*–\s*\n', '\n', text) # "* – " (bullet with removed company)
|
||||
text = re.sub(r'\*\s*oraz\s*–', '*', text) # "* oraz –" fragments
|
||||
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
|
||||
# 3. Remove plain-text mentions of fake companies (bold or plain)
|
||||
# Catch patterns like "**Baumar**" or "Baumar" that appear as company names
|
||||
# but aren't in the valid list
|
||||
valid_names_set = set(n.lower() for n in valid_companies.values())
|
||||
|
||||
def replace_bold_company(match):
|
||||
bold_name = match.group(1).strip()
|
||||
if bold_name.lower() in valid_names_set:
|
||||
return match.group(0) # Keep valid company
|
||||
# Check if it's likely a company name (capitalized, not a common word)
|
||||
common_words = {'budownictwo', 'infrastruktura', 'technologia', 'energia', 'bezpieczeństwo',
|
||||
'doradztwo', 'networking', 'konsorcja', 'aktualności', 'przygotowanie',
|
||||
'zatrudnienie', 'wniosek', 'kontakt', 'local content', 'projekt',
|
||||
'chłodzenie', 'elektryka', 'telekomunikacja', 'ochrona', 'zarządzanie',
|
||||
'hvac', 'instalacje', 'oze', 'it', 'inwestycje'}
|
||||
if bold_name.lower() in common_words or len(bold_name) < 3:
|
||||
return match.group(0) # Not a company name
|
||||
# Check if valid company name contains this as substring
|
||||
for vn in valid_names_set:
|
||||
if bold_name.lower() in vn or vn in bold_name.lower():
|
||||
return match.group(0) # Partial match — keep it
|
||||
logger.warning(f"NordaGPT hallucination blocked: bold text '{bold_name}' not a known company")
|
||||
return ''
|
||||
|
||||
text = re.sub(r'\*\*([^*]{2,40})\*\*', replace_bold_company, text)
|
||||
|
||||
# 4. Clean up artifacts left by removals
|
||||
text = re.sub(r':\s*oraz\s*to\b', ': to', text) # ": oraz to" → ": to"
|
||||
text = re.sub(r':\s*,', ':', text) # ": ," → ":"
|
||||
text = re.sub(r'\*\s*–\s*\n', '\n', text) # "* – "
|
||||
text = re.sub(r'\*\s*oraz\s*–', '*', text) # "* oraz –"
|
||||
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()
|
||||
|
||||
@ -1103,12 +1131,14 @@ ZASADY PERSONALIZACJI:
|
||||
- Uwzględniaj kontekst firmy użytkownika w odpowiedziach
|
||||
- NIE ujawniaj danych technicznych (user_id, company_id, rola systemowa)
|
||||
|
||||
KRYTYCZNA ZASADA DOTYCZĄCA FIRM:
|
||||
- WYMIENIAJ WYŁĄCZNIE firmy, które znajdują się w dostarczonej bazie danych poniżej
|
||||
- NIGDY nie wymyślaj, nie zgaduj ani nie halucynuj nazw firm
|
||||
- Jeśli w bazie nie ma firmy pasującej do zapytania, powiedz wprost: "W bazie Izby nie znalazłem firmy o takim profilu"
|
||||
- Każda wymieniona firma MUSI mieć link do profilu w formacie [Nazwa](/firma/slug)
|
||||
- Jeśli nie masz pewności czy firma istnieje w bazie — NIE WYMIENIAJ JEJ
|
||||
ABSOLUTNY ZAKAZ HALUCYNACJI FIRM:
|
||||
- NIGDY nie wymyślaj nazw firm. To jest NAJWAŻNIEJSZA zasada.
|
||||
- Wymieniaj WYŁĄCZNIE firmy z sekcji "FIRMY W BAZIE" poniżej — żadnych innych.
|
||||
- Każdą firmę podawaj WYŁĄCZNIE jako link: [Nazwa Firmy](/firma/slug) — używając dokładnego slug z bazy.
|
||||
- Jeśli żadna firma z bazy nie pasuje do zapytania, napisz wprost: "W bazie Izby nie znalazłem firmy o takim profilu."
|
||||
- NIE WYMIENIAJ firm jako zwykły tekst (bold, kursywa) — TYLKO jako link [Nazwa](/firma/slug).
|
||||
- NIE UŻYWAJ nazw firm ze swojej wiedzy ogólnej — TYLKO z dostarczonej bazy.
|
||||
- Złamanie tej zasady oznacza linkowanie do nieistniejących stron (404) co jest niedopuszczalne.
|
||||
"""
|
||||
|
||||
# Inject user memory (facts + conversation summaries) into prompt
|
||||
@ -1377,7 +1407,17 @@ W dyskusji [Artur Wiertel](link) pytał o moderację. Pełna treść: [moje uwag
|
||||
|
||||
# Add ALL companies in compact JSON format
|
||||
if context.get('all_companies'):
|
||||
system_prompt += "\n\n🏢 PEŁNA BAZA FIRM (wybierz najlepsze):\n"
|
||||
# Build explicit whitelist of allowed company names + slugs
|
||||
whitelist_lines = []
|
||||
for c in context['all_companies']:
|
||||
name = c.get('name', '')
|
||||
profile = c.get('profile', '')
|
||||
slug = profile.replace('/firma/', '') if profile else ''
|
||||
if name and slug:
|
||||
whitelist_lines.append(f" {name} → [link](/firma/{slug})")
|
||||
system_prompt += "\n\n⚠️ DOZWOLONE FIRMY — możesz wymieniać TYLKO te (użyj dokładnie podanego linku):\n"
|
||||
system_prompt += "\n".join(whitelist_lines)
|
||||
system_prompt += "\n\n🏢 SZCZEGÓŁY FIRM:\n"
|
||||
system_prompt += json.dumps(context['all_companies'], ensure_ascii=False, indent=None)
|
||||
system_prompt += "\n"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user