Проверка фактов и предотвращение галлюцинаций
Проверка на основе опоры на источники: каждое утверждение должно подтверждаться найденным источником.
«Проверка фактов и предотвращение галлюцинаций» — бесплатный урок AI Agents на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Agents, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Agents содержит 4 уроков всего.
Проблема галлюцинаций в агентах research
LLM может создавать правдоподобные утверждения, не имеющие оснований в полученных источниках. Для агента research это особенно опасно, поскольку результат выглядит авторитетным и сопровождается ссылками, которые на самом деле могут не подтверждать утверждение.
Предотвращение галлюцинаций должно быть одной из первостепенных задач.
Привязка к источникам: каждое утверждение связано с источником
Основной принцип привязки к источникам заключается в том, что каждое фактическое утверждение в итоговом результате должно быть прослеживаемо как минимум до одного найденного документа. Утверждения, которые невозможно проследить до источника, являются либо галлюцинациями, либо неподтверждёнными — и то и другое недопустимо в исследовательском отчёте.
def check_grounding(claim: str, retrieved_docs: list[dict]) -> dict:
doc_texts = '\n\n'.join(
f'[DOC {i+1}] ({d["url"]})\n{d["text"][:800]}'
for i, d in enumerate(retrieved_docs[:5])
)
return {
'claim': claim,
'docs': doc_texts,
'grounded': None # to be filled by LLM verifier
}
if __name__ == '__main__':
docs = [{'url': 'https://example.com/geo', 'text': 'Paris is the capital of France.'}]
result = check_grounding('Paris is the capital of France.', docs)
print('Claim:', result['claim'])
print('Supporting docs used:')
print(result['docs'])
Шаблон «LLM как проверяющая модель»
Используйте отдельный вызов LLM — «проверяющую модель» — чтобы определить, подтверждается ли утверждение предоставленными источниками. Так создаётся система взаимной проверки, в которой генератор и проверяющая модель работают в независимых вызовах.
import openai, json
client = openai.OpenAI(api_key='YOUR_OPENAI_KEY')
def verify_claim(claim: str, source_texts: list[str]) -> dict:
sources_block = '\n---\n'.join(source_texts[:3])
prompt = (
f'Is the following claim directly supported by the provided sources?\n'
f'Claim: "{claim}"\n\n'
f'Sources:\n{sources_block}\n\n'
f'Return JSON: {{\n'
f' "supported": true/false,\n'
f' "confidence": 0.0-1.0,\n'
f' "reason": "one sentence explanation"\n'
f'}}'
)
resp = client.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'}
)
return json.loads(resp.choices[0].message.content)Фильтрация результата по степени уверенности
Установите пороговые значения для оценки уверенности проверяющей модели. Утверждения с оценкой выше 0,85 публикуются. Утверждения с оценкой от 0,5 до 0,85 публикуются с оговоркой. Утверждения с оценкой ниже 0,5 исключаются.
INCLUDE_THRESHOLD = 0.85
DISCLAIMER_THRESHOLD = 0.50
def gate_claim(claim: str, source_texts: list[str]) -> dict:
result = verify_claim(claim, source_texts)
conf = result.get('confidence', 0.0)
supported = result.get('supported', False)
if not supported or conf < DISCLAIMER_THRESHOLD:
return {'action': 'exclude', 'claim': claim, 'reason': result.get('reason')}
elif conf < INCLUDE_THRESHOLD:
return {
'action': 'include_with_disclaimer',
'claim': f'[LOW CONFIDENCE] {claim}',
'reason': result.get('reason')
}
else:
return {'action': 'include', 'claim': claim}Выявление характерных шаблонов галлюцинаций
Некоторые шаблоны утверждений особенно часто приводят к галлюцинациям: точные статистические данные (проценты и денежные суммы), конкретные даты, названные люди и причинно-следственные связи. Проверяйте такие утверждения особенно тщательно.
import re
def is_high_risk_claim(claim: str) -> bool:
patterns = [
r'\d+\.?\d*\s*%', # percentages: 9.1%
r'\$\s*\d+', # dollar amounts
r'\b(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{4}', # dates
r'\b[A-Z][a-z]+\s+[A-Z][a-z]+\s+(said|stated|argued|claimed)', # named quotes
r'(caused?|led to|resulted in)', # causal claims
]
return any(re.search(p, claim) for p in patterns)
print(is_high_risk_claim('Inflation hit 9.1% in June 2022')) # True
print(is_high_risk_claim('Inflation was elevated')) # FalseЦикл проверки с разбиением на утверждения
Для длинных разделов разбейте текст на отдельные утверждения, независимо проверьте каждое из них, а затем заново соберите раздел, включив только проверенные утверждения.
def decompose_into_claims(section_text: str) -> list[str]:
prompt = (
f'Break this text into individual verifiable factual claims.\n'
f'Each claim should be a single sentence containing exactly one fact.\n'
f'Return JSON: {{"claims": ["..."]}}\n\n'
f'TEXT:\n{section_text}'
)
resp = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'}
)
return json.loads(resp.choices[0].message.content).get('claims', [])Сборка проверенного текста
После фильтрации каждого утверждения заново соберите чистый, связный раздел только из проверенных утверждений. Исключите утверждения с низкой уверенностью и отредактируйте текст для удобства чтения.
def reconstruct_section(verified_claims: list[str],
section_name: str) -> str:
claims_text = '\n'.join(f'- {c}' for c in verified_claims)
prompt = (
f'Rewrite these verified facts as a coherent {section_name} section.\n'
f'Do NOT add any new information not present in the claims.\n'
f'Only use the facts provided.\n\n'
f'VERIFIED CLAIMS:\n{claims_text}'
)
resp = client.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': prompt}]
)
return resp.choices[0].message.contentВыявление числовых искажений
LLM иногда незаметно изменяют числа, приведённые в источнике (например, превращают 9,1% в 9,2%). Извлеките числа и из утверждения, и из текста источника, а затем явно сравните их.
import re
def extract_numbers(text: str) -> list[float]:
matches = re.findall(r'[-+]?\d*\.?\d+', text)
return [float(m) for m in matches]
def check_numeric_drift(claim: str, source_text: str,
tolerance: float = 0.01) -> bool:
claim_nums = extract_numbers(claim)
source_nums = extract_numbers(source_text)
for cn in claim_nums:
found_match = any(abs(cn - sn) <= tolerance for sn in source_nums)
if not found_match:
return True # numeric drift detected
return False
print(check_numeric_drift(
'Inflation reached 9.2% in June 2022',
'The CPI rose 9.1 percent in June 2022'
)) # True — 9.2 vs 9.1Аудит атрибуции
После создания полного отчёта проведите аудит атрибуции: для каждого предложения в отчёте подтвердите, что его можно связать с одним из источников. Отметьте все предложения, для которых не найден соответствующий фрагмент источника.
def attribution_audit(report_text: str, sources: list[dict]) -> list[str]:
sentences = [s.strip() for s in report_text.split('.') if len(s.strip()) > 20]
unattributed = []
for sentence in sentences:
found = False
for src in sources:
if any(word in src.get('text', '') for word in sentence.split()[:5]):
found = True
break
if not found:
unattributed.append(sentence)
return unattributed
# Flag unattributed sentences for manual review or re-verification
if __name__ == '__main__':
report = ("Water boils at 100 degrees Celsius at sea level. "
"The moon is made primarily of green cheese according to this document.")
sources = [{'text': 'Water boils at 100C (212F) at standard atmospheric pressure.'}]
unattributed = attribution_audit(report, sources)
print('Unattributed sentences (need manual review):')
for s in unattributed:
print(' -', s)
Осторожные формулировки для неопределённых утверждений
Не всё удаётся проверить с высокой степенью уверенности. Вместо того чтобы полностью исключать пограничные утверждения, используйте осторожные формулировки: «Некоторые аналитики предполагают…», «Согласно X…», «Сообщалось, что…». Это сохраняет информацию и одновременно показывает наличие неопределённости.
def hedge_claim(claim: str, confidence: float) -> str:
if confidence >= 0.85:
return claim # state as fact
elif confidence >= 0.65:
hedges = ['Some sources suggest', 'According to available evidence',
'Analysts have noted']
return f'{hedges[hash(claim) % len(hedges)]}, {claim.lower()}'
else:
return f'It has been reported (with low confidence) that {claim.lower()}'
print(hedge_claim('Inflation peaked at 9.1%', 0.90))
print(hedge_claim('Supply chain disruptions contributed to inflation', 0.72))
print(hedge_claim('Specific policy caused inflation', 0.45))Мониторинг доли галлюцинаций
Отслеживайте долю галлюцинаций с течением времени: какой процент созданных утверждений не проходит проверку? Установите порог оповещения. Если показатель резко возрастает, качество разработки запросов или поиска источников ухудшилось.
verification_log = [] # In production: a database
def log_verification(claim: str, supported: bool, confidence: float):
verification_log.append({
'claim': claim[:100],
'supported': supported,
'confidence': confidence
})
def hallucination_rate() -> float:
if not verification_log:
return 0.0
failed = sum(1 for v in verification_log if not v['supported'])
return failed / len(verification_log)
def check_hallucination_alert(threshold: float = 0.15):
rate = hallucination_rate()
if rate > threshold:
print(f'ALERT: Hallucination rate {rate:.1%} exceeds threshold {threshold:.1%}')
return rate
if __name__ == '__main__':
log_verification('The sky is blue', True, 0.95)
log_verification('The moon is made of cheese', False, 0.2)
log_verification('Water boils at 100C at sea level', True, 0.99)
rate = check_hallucination_alert(threshold=0.15)
print(f'Hallucination rate so far: {rate:.1%}')
Какова основная цель шаблона «LLM как проверяющая модель»?
Шаблон «LLM как проверяющая модель» — это важный архитектурный подход к предотвращению галлюцинаций. Необходимо понимать, что именно он проверяет и почему проверка выполняется отдельным вызовом.
Итоги по предотвращению галлюцинаций
Предотвращайте галлюцинации с помощью проверок привязки к источникам (каждое утверждение связано с источником), LLM как проверяющей модели (отдельный вызов оценивает подтверждение), фильтрации по уверенности (исключаются утверждения ниже порога), выявления числовых искажений, осторожных формулировок для пограничных случаев и мониторинга доли галлюцинаций для обнаружения ухудшений.
Часто задаваемые вопросы
Урок «Проверка фактов и предотвращение галлюцинаций» бесплатный?
Да — полный текст урока «Проверка фактов и предотвращение галлюцинаций» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Agents, подпишись на CoddyKit PRO. Курс AI Agents содержит 4 уроков всего.
Чему я научусь в уроке «Проверка фактов и предотвращение галлюцинаций»?
Проверка на основе опоры на источники: каждое утверждение должно подтверждаться найденным источником. Ты практикуешь AI Agents с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Agents?
Предыдущий опыт не требуется. AI Agents на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Проверка фактов и предотвращение галлюцинаций»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Agents?
Да. Каждый урок AI Agents включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Проектирование многоэтапного исследовательского цикла
- Проверка источников и цитирование
- Генерация структурированных отчётов
- Проверка фактов и предотвращение галлюцинаций