Проверка источников и цитирование
Сопоставление утверждений из разных источников и добавление URL источников к фактам.
«Проверка источников и цитирование» — бесплатный урок AI Agents на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Agents, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Agents содержит 4 уроков всего.
Почему проверка источников важна
Агент может получить ложное утверждение из некачественного источника и представить его как факт. Без проверки исследовательский агент представляет собой всего лишь усовершенствованный поисковик без фильтра качества.
Проверка добавляет уровень контроля, который определяет, подтверждаются ли утверждения несколькими независимыми источниками.
Правило двух источников
Утверждение считается проверенным, если встречается как минимум в двух независимых источниках. «Независимые» означает разные домены: если одно и то же утверждение найдено на двух сайтах, которые оба ссылаются на одну исходную статью, это не считается.
from urllib.parse import urlparse
def extract_domain(url: str) -> str:
return urlparse(url).netloc.replace('www.', '')
def is_verified(fact: str, all_facts: list[dict]) -> bool:
matching_domains = set()
for f in all_facts:
if fact.lower() in f['fact'].lower() or f['fact'].lower() in fact.lower():
matching_domains.add(extract_domain(f['source']))
return len(matching_domains) >= 2
# Example
facts = [
{'fact': 'Inflation peaked at 9.1% in June 2022', 'source': 'https://bls.gov/cpi'},
{'fact': 'US inflation hit 9.1% peak in mid-2022', 'source': 'https://reuters.com/article/a'}
]
print(is_verified('inflation peaked at 9.1%', facts)) # TrueСемантическое сопоставление на основе LLM
Сопоставление строк не обнаруживает перефразированные утверждения. Используйте LLM, чтобы определить, эквивалентны ли два факта по смыслу, прежде чем считать их подтверждением из разных источников.
import openai, json
client = openai.OpenAI(api_key='YOUR_OPENAI_KEY')
def claims_are_equivalent(claim_a: str, claim_b: str) -> bool:
prompt = (
f'Are these two statements making the same factual claim?\n'
f'A: "{claim_a}"\n'
f'B: "{claim_b}"\n'
f'Answer JSON: {{"equivalent": true/false}}'
)
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('equivalent', False)
print(claims_are_equivalent(
'CPI hit 9.1% in June 2022',
'US consumer prices rose 9.1 percent year-over-year in June 2022'
)) # TrueОценка качества источников
Все источники имеют разную ценность. Оценивайте каждый источник по типу домена, дате публикации и известной надежности. Источники первого уровня — государственная статистика и рецензируемые журналы — должны иметь больший вес, чем блоги или социальные сети.
from datetime import datetime, timezone
HIGH_AUTHORITY_DOMAINS = [
'gov', 'edu', 'nature.com', 'science.org', 'pubmed.ncbi.nlm.nih.gov',
'reuters.com', 'bbc.com', 'apnews.com', 'who.int'
]
def score_source(url: str, publication_date: str | None) -> float:
score = 0.5 # baseline
domain = extract_domain(url)
tld = domain.split('.')[-1]
# Domain authority
if any(ha in domain for ha in HIGH_AUTHORITY_DOMAINS):
score += 0.3
elif tld in ('gov', 'edu', 'org'):
score += 0.1
# Recency
if publication_date:
try:
pub = datetime.fromisoformat(publication_date)
days_old = (datetime.now(timezone.utc) - pub.replace(tzinfo=timezone.utc)).days
if days_old < 365:
score += 0.1
elif days_old > 1825: # 5 years
score -= 0.1
except ValueError:
pass
return min(1.0, max(0.0, score))Сопоставление утверждений с источниками
Для каждого утверждения, которое агент собирается включить в итоговый отчет, сопоставьте его со всеми полученными документами, чтобы найти лучшие подтверждающие источники.
def cross_reference(claim: str, all_facts: list[dict]) -> list[dict]:
supporting = []
seen_domains = set()
for fact in all_facts:
if claims_are_equivalent(claim, fact['fact']):
domain = extract_domain(fact['source'])
if domain not in seen_domains: # only one per domain
seen_domains.add(domain)
supporting.append({
'source': fact['source'],
'domain': domain,
'fact': fact['fact'],
'score': score_source(fact['source'], fact.get('publication_date'))
})
return sorted(supporting, key=lambda x: x['score'], reverse=True)Проверка квалификации авторов
Для научных или медицинских утверждений важна квалификация авторов. Исследование с указанными авторами из аккредитованных учреждений имеет больший вес, чем анонимная публикация в блоге. Извлекайте сведения об авторах и устанавливайте флаг для утверждений из анонимных источников.
def assess_authorship(url: str, page_text: str) -> dict:
prompt = (
f'From this webpage text, extract:\n'
f'1. Author name(s) (or null)\n'
f'2. Author affiliation/credentials (or null)\n'
f'3. Publication name (or null)\n'
f'Return JSON: {{"authors": [], "affiliation": null, "publication": null}}\n\n'
f'TEXT:\n{page_text[:2000]}'
)
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)Пометка непроверенных утверждений
Утверждения, которые нельзя сопоставить с двумя независимыми источниками, следует помечать как непроверенные в результате. В зависимости от уровня риска отчета непроверенные утверждения можно исключить или представить с оговоркой.
def classify_claims(claims: list[str], all_facts: list[dict]) -> list[dict]:
classified = []
for claim in claims:
support = cross_reference(claim, all_facts)
classified.append({
'claim': claim,
'verified': len(support) >= 2,
'sources': support[:3], # top 3 supporting sources
'confidence': 'high' if len(support) >= 3 else
'medium' if len(support) == 2 else
'low'
})
return classifiedФормат цитирования: встроенные ссылки и список источников
Отчеты должны содержать встроенные ссылки на источники — надстрочные номера — и список источников в конце. Формируйте и то и другое на основе проверенных метаданных источников.
def format_citations(classified_claims: list[dict]) -> tuple[list[str], list[str]]:
ref_list = []
ref_map = {} # url -> citation number
inline_claims = []
for item in classified_claims:
nums = []
for src in item['sources']:
url = src['source']
if url not in ref_map:
ref_map[url] = len(ref_list) + 1
ref_list.append(f'[{ref_map[url]}] {url}')
nums.append(f'[{ref_map[url]}]')
citation_str = ''.join(nums)
prefix = '' if item['verified'] else '[UNVERIFIED] '
inline_claims.append(f'{prefix}{item["claim"]} {citation_str}')
return inline_claims, ref_list
if __name__ == '__main__':
demo_claims = [
{'claim': 'Revenue grew 12% year over year', 'verified': True, 'sources': [{'source': 'https://example.com/report'}]},
{'claim': 'The CEO plans to step down', 'verified': False, 'sources': [{'source': 'https://example.com/rumor'}]},
]
inline, refs = format_citations(demo_claims)
for line in inline:
print(line)
print('References:')
for r in refs:
print(' ', r)
Выявление противоречий между источниками
Иногда два надежных источника противоречат друг другу. Выявляйте противоречия и представляйте обе стороны, а не выбирайте одну молча. Это особенно важно для спорных эмпирических утверждений.
def find_contradictions(claim: str, all_facts: list[dict]) -> list[dict]:
contradictions = []
for fact in all_facts:
if extract_domain(fact['source']) == extract_domain(claim):
continue
prompt = (
f'Does fact B contradict fact A?\n'
f'A: "{claim}"\nB: "{fact["fact"]}"\n'
f'JSON: {{"contradicts": true/false}}'
)
resp = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'}
)
result = json.loads(resp.choices[0].message.content)
if result.get('contradicts'):
contradictions.append(fact)
return contradictionsОтчет о результатах проверки
Перед созданием итогового отчета подготовьте сводку проверки: сколько утверждений проверено, сколько не проверено, какие источники имеют самые высокие оценки авторитетности и какие противоречия выявлены.
def verification_report_card(classified: list[dict]) -> dict:
total = len(classified)
verified = sum(1 for c in classified if c['verified'])
all_sources = [
src for c in classified for src in c['sources']
]
top_domains = sorted(
set(s['domain'] for s in all_sources),
key=lambda d: max(s['score'] for s in all_sources if s['domain'] == d),
reverse=True
)[:5]
return {
'total_claims': total,
'verified': verified,
'unverified': total - verified,
'verification_rate': f'{verified/max(total,1)*100:.0f}%',
'top_sources': top_domains
}
if __name__ == '__main__':
demo_classified = [
{'verified': True, 'sources': [{'domain': 'docs.python.org', 'score': 1.3}]},
{'verified': False, 'sources': [{'domain': 'quora.com', 'score': 0.5}]},
]
card = verification_report_card(demo_classified)
for k, v in card.items():
print(f'{k}: {v}')
Результат с порогом достоверности
Установите минимальный порог проверки перед публикацией. Если проверено менее 70% утверждений, предложите циклу research выполнить еще одну итерацию, специально нацеленную на непроверенные утверждения.
MIN_VERIFICATION_RATE = 0.70
def should_run_more_research(classified: list[dict]) -> list[str]:
unverified = [c for c in classified if not c['verified']]
total = len(classified)
if total == 0:
return []
rate = (total - len(unverified)) / total
if rate >= MIN_VERIFICATION_RATE:
return [] # Sufficient confidence — stop
# Return search queries to verify remaining claims
return [f'verify: {c["claim"]}' for c in unverified[:3]]
if __name__ == '__main__':
demo_classified = [
{'claim': 'A', 'verified': True},
{'claim': 'B', 'verified': False},
{'claim': 'C', 'verified': False},
]
followups = should_run_more_research(demo_classified)
print('Follow-up queries needed:', followups)
Что означает «независимые» источники в правиле проверки по двум источникам
Критерий независимости предотвращает проверку в эхо-камере, когда одно и то же утверждение восходит к единственному исходному источнику, на который ссылаются многие публикации.
Итоги проверки источников
Проверенный research требует: подтверждения двумя источниками из разных доменов, семантического сопоставления LLM для обнаружения перефразированных эквивалентов, оценки качества источников — авторитетности домена и актуальности, выявления противоречий и результата с порогом достоверности, который запускает дополнительный research, если доля проверенных утверждений слишком мала.
Часто задаваемые вопросы
Урок «Проверка источников и цитирование» бесплатный?
Да — полный текст урока «Проверка источников и цитирование» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Agents, подпишись на CoddyKit PRO. Курс AI Agents содержит 4 уроков всего.
Чему я научусь в уроке «Проверка источников и цитирование»?
Сопоставление утверждений из разных источников и добавление URL источников к фактам. Ты практикуешь AI Agents с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Agents?
Предыдущий опыт не требуется. AI Agents на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Проверка источников и цитирование»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Agents?
Да. Каждый урок AI Agents включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Проектирование многоэтапного исследовательского цикла
- Проверка источников и цитирование
- Генерация структурированных отчётов
- Проверка фактов и предотвращение галлюцинаций