Проблема
Запит виглядає стандартним: зібрати короткий підсумок змін у політиці сервісу і додати джерела.
У трейсах видно інше: за один run агент повернув 7 citations,
але перевірка показала, що 3 джерела ніколи не фетчились, а 2 ведуть у 404.
Для користувача відповідь виглядає впевненою, але вона не відтворюється.
Система не падає.
Вона просто віддає правдоподібні citations без реальних доказів.
Аналогія: уяви аудитора, який у звіті ставить посилання на "папки в архіві", але цих папок ніхто не бачив. Документ виглядає професійно, поки хтось не перевірить джерела. Hallucinated sources в агентних системах працюють так само.
Чому це стається
Hallucinated sources зазвичай виникають не через одну помилку моделі, а через відсутність жорсткого citation-контролю в runtime.
LLM має сильний bias до "повних" відповідей, тому без жорсткої верифікації модель швидше вигадує citation, ніж повертає відповідь без джерела.
У production зазвичай так:
- агент генерує citations як частину "повної" відповіді;
- search snippets помилково сприймаються як докази, хоча сторінки не відкривали;
source_idне прив'язані до evidence snapshots;- без верифікації citations система пропускає unfetched або невалідні джерела;
- якщо fail-closed не налаштований, вигадані джерела доходять до користувача.
У trace це видно як ріст citations_count
при одночасному падінні citation_validity_rate.
Проблема не в одному невдалому URL.
Runtime не блокує неперевірені citations до видачі відповіді.
Які збої трапляються найчастіше
У production найчастіше видно чотири повторювані патерни hallucinated sources.
Неперевірені URL-цитати (Unfetched URL citations)
Агент цитує URL, який ніколи не проходив через http.get або kb.read у цьому run.
Типова причина: citations не обмежені source_id з evidence store.
Snippet замість доказу (Search-as-evidence)
У відповідь потрапляють "джерела" із пошукової видачі, але агент не має підтвердження фактичного вмісту сторінки.
Типова причина: search results змішані з evidence шаром.
Дрейф цитат між кроками (Citation drift)
На ранньому кроці джерело було валідним, але після retry або truncation фінальна відповідь посилається на інший документ.
Типова причина: немає стабільного зв'язку claim -> source_id -> snapshot hash.
Псевдоцитати без покриття тверджень (Claim-source mismatch)
У відповіді є citation-блок, але ключові твердження не мають відповідних джерел.
Типова причина: валідація перевіряє лише "наявність посилань", а не покриття claims.
Як виявляти ці проблеми
Hallucinated sources добре видно по поєднанню citation- і retrieval-метрик.
| Метрика | Сигнал hallucinated sources | Що робити |
|---|---|---|
citation_validity_rate | падає частка цитат, що проходять перевірку | ввести fail-closed verification за source_id |
unfetched_source_rate | у відповідях багато unfetched URL | заборонити URL-citations без evidence snapshot |
source_404_rate | частина джерел не відкривається | перевіряти статус і canonical URL під час fetch |
claim_without_citation_rate | твердження без прив'язки до джерела | додати claim-level coverage check |
citation_stop_reason_rate | часті citations:invalid у runtime | перевірити retrieval quality і policy для tools |
Як відрізнити hallucinated sources від просто неточної відповіді
Не кожна неточність тексту означає вигадані джерела. Ключове питання: чи можна технічно відтворити джерело для кожного критичного твердження.
Нормально, якщо:
- кожна citation вказує на
source_id, який існує в evidence store; - є snapshot metadata (URL, timestamp, hash);
- перевірка claims показує, що джерела покривають ключові висновки.
Небезпечно, якщо:
- у відповіді є URL, яких не було у fetch-кроці;
- цитати існують "для форми", але не покривають основні claims;
- відповіді не можна відтворити на рівні run (
run_id->source_id-> snapshot).
Як зупиняти такі збої
Практично це виглядає так:
- усі джерела проходять через evidence store (snapshot + hash + timestamp);
- модель повертає citations лише як
source_id, а не довільні URLs; - citation verifier перевіряє, що всі
source_idіснують, пройшли fetch і дозволені політикою; - якщо перевірка не пройдена, runtime повертає stop reason і safe fallback.
Мінімальний guard для валідації citations:
from dataclasses import dataclass
import hashlib
import time
@dataclass(frozen=True)
class EvidenceMeta:
source_id: str
url: str
fetched_at: float
text_sha256: str
class EvidenceStore:
def __init__(self):
self.items: dict[str, EvidenceMeta] = {}
def add_snapshot(self, source_id: str, url: str, text: str) -> None:
self.items[source_id] = EvidenceMeta(
source_id=source_id,
url=url,
fetched_at=time.time(),
text_sha256=hashlib.sha256(text.encode("utf-8")).hexdigest(),
)
def has(self, source_id: str) -> bool:
return source_id in self.items
def verify_citations(cited_source_ids: list[str], store: EvidenceStore) -> str | None:
# cited_source_ids are expected to come from structured output
if not cited_source_ids:
return "citations:missing"
unknown = [sid for sid in cited_source_ids if not store.has(sid)]
if unknown:
return "citations:unknown_source_id"
return None
Це базовий guard.
У production його зазвичай доповнюють claim-level coverage check,
allowlist для citation tools і окремим stop reason для unfetched URLs.
verify_citations(...) викликають до фінального рендеру відповіді,
щоб користувач не бачив невалідні джерела.
Де це реалізується в архітектурі
У production контроль hallucinated sources майже завжди розкладений між трьома шарами системи.
Tool Execution Layer відповідає за fetch evidence: статус відповіді, нормалізацію URL, snapshots і hash. Якщо цей шар не зберігає докази, citations неможливо надійно верифікувати.
Agent Runtime контролює structured output, citation verification, stop reasons і fail-closed fallback. Саме тут вирішується, чи потрапить відповідь до користувача.
Memory Layer тримає зв'язок між run і evidence:
run_id, source_id, retention і відтворюваність.
Без цього шару команда не зможе провести нормальний аудит інциденту.
Самоперевірка
Швидка перевірка перед релізом. Відмічайте пункти і дивіться статус нижче.
Це короткий sanity-check, а не формальний аудит.
Прогрес: 0/8
⚠ Є сигнали ризику
Бракує базових контролів. Закрийте ключові пункти цього чекліста перед релізом.
FAQ
Q: Чи можна просто попросити модель "завжди додавати джерела"?
A: Можна, але цього недостатньо. Без runtime-верифікації citations це лише формат відповіді, а не доказ.
Q: Search results можуть бути джерелом?
A: Зазвичай ні. Search дає лише кандидати на джерела.
Доказом стає лише те, що пройшло fetch і збережене як snapshot.
Q: Чи обов'язково зберігати повний текст джерела?
A: Не завжди. Мінімум для аудиту: URL, timestamp, hash і стабільний source_id. Повний текст додають там, де потрібен replay або точні цитати.
Q: Що показувати користувачу, якщо citations невалідні?
A: Явний stop reason, що вже перевірили, і безпечний наступний крок: часткова відповідь без неперевірених джерел або повторний запуск із верифікацією.
Інцидент hallucinated sources майже ніколи не виглядає як гучна аварія. Це тиха втрата довіри, яку зазвичай помічають лише після перевірки джерел. Тому production-агентам потрібні не лише хороші відповіді, а й жорстка citation-дисципліна.
Пов'язані сторінки
Якщо ця проблема виникла у production, корисно також подивитися:
- Чому AI агенти ламаються — загальна карта збоїв у production.
- Context poisoning — як проблемний контекст підштовхує агента до хибних висновків.
- Tool failure — як нестабільні інструменти псують evidence-ланцюг.
- Agent Runtime — де ставити structured output verification і stop reasons.
- Tool Execution Layer — де збирати snapshots і валідувати джерела.
- Memory Layer — де тримати відтворюваність доказів між run'ами.