Исследовательские агенты: веб-поиск, синтез из множества источников и цепочки цитирования


title: "Исследовательские агенты: веб-поиск, синтез из множества источников и цепочки цитирования" slug: research-agents-web-synthesis-2026-ru date: 2026-02-22 lang: ru

Исследовательские агенты: веб-поиск, синтез из множества источников и цепочки цитирования

Ключевые факты

  • tavily-python: Последняя версия 0.7.22 (PyPI на 2026-03-01)
  • langchain: Последняя версия 1.2.10
  • langgraph: Последняя версия 1.0.10
  • duckduckgo-search: Последняя версия 8.1.1
  • newspaper3k: Последняя версия 0.2.8
  • Tavily Search API: Возвращает до 10 результатов на запрос по умолчанию; max_results настраивается 1-20
  • Tavily API время ответа: Обычно 800-1500ms для базового поиска, 1500-3000ms для продвинутого
  • Tavily ценообразование: $0.001 на поиск для базового тарифа; объёмные тарифы доступны свыше 10K поисков/месяц
  • Perplexity API модели: sonar, sonar-pro, sonar-reasoning (Q1 2026)
  • Perplexity API стоимость: Приблизительно $0.005-0.015 за запрос в зависимости от модели и длины контекста
  • Brave Search API: Бесплатный тариф 2000 запросов/месяц; платный тариф от $5/месяц
  • Ограничение частоты при веб-скрейпинге: Стандартная практика 1-2 запроса/секунду на домен; используйте задержку между запросами
  • robots.txt соответствие: Требуется перед скрейпингом; проверьте /robots.txt и уважайте директиву Crawl-delay
  • Формат цитирования: Сохраняйте (source_url, claim_text, extraction_timestamp) для каждого утверждения
  • Отслеживание цитирований: Поддерживайте DAG (directed acyclic graph) связывающий утверждения → источники → ссылки
  • Окно контекста для синтеза: Выделите 16K-32K токенов для синтеза из нескольких источников с Claude; зарезервируйте 4K на вывод
  • Типичное разложение исследовательского запроса: Сложный вопрос → 3-8 подвопросов → 15-40 отдельных поисковых запросов
  • Пороги качества источников: Оценка ≥0.7 для первичных источников, ≥0.4 для вспомогательного материала
  • Параллельное выполнение поиска: Запускайте поиски в Tavily, Brave, Serper одновременно используя asyncio.gather()
  • Дедупликация: Используйте канонизацию URL (удалите параметры запроса, фрагменты) перед дедупликацией по URL

Что такое исследовательский агент

Исследовательский агент — это автономная система, которая выполняет итеративный сбор и синтез информации, отличаясь от статических RAG систем, которые запрашивают фиксированный корпус. Основной цикл следует этому паттерну:

Разложение запроса → Параллельный поиск → Оценка качества источников → Извлечение утверждений → Кросс-источниковая верификация → Обнаружение пробелов → Дополнительные запросы → Синтез

В отличие от чат-бота или RAG системы, исследовательский агент:

  • Динамически планирует многошаговые стратегии поиска на основе начальных результатов
  • Оценивает надёжность источников и проверяет перекрёстные ссылки на утверждения
  • Обнаруживает противоречия между источниками и явно их выявляет
  • Рекурсивно следует цепочкам цитирования для обнаружения первичных источников
  • Производит структурированные отчёты с проверяемыми цитированиями

Агент работает циклами: выполняет поиски, оценивает найденное, выявляет пробелы в охвате, формулирует новые запросы для заполнения пробелов, повторяет до достижения достаточной глубины. Эта итеративная доработка отличает исследовательские агенты от однопроходных систем извлечения.

Каркас принятия решений

Выберите правильный подход на основе потребностей в информации:

Сценарий Рекомендуемый подход Обоснование
Запрос требует свежих веб-данных (новости, недавние события) Исследовательский агент RAG индексы статичны; веб-поиск обеспечивает актуальность
Запрос может быть ответен известным корпусом (внутренние доки, исторические данные) RAG Быстрее, дешевле, управляемые источники
Необходимо кросс-ссылаться на несколько противоречивых источников Исследовательский агент Встроенное обнаружение противоречий
Простой фактический поиск без синтеза Perplexity API Встроенная исследовательская возможность; низкие накладные расходы на разработку
Необходимо отслеживать происхождение цитирования для аудита Исследовательский агент Полный контроль над цепочкой цитирования и метаданными источников
Бюджет ограничен, высокий объём запросов RAG Нет per-query API затрат после индексирования
Необходимо следовать цепочкам ссылок к первичным источникам Исследовательский агент Рекурсивная способность следования цитированиям
Вопрос требует экспертного синтеза из 10+ источников Исследовательский агент Синтез из нескольких источников с оценкой качества

Когда Perplexity API подходит: Вам нужны ответы, обоснованные веб-данными, без построения собственной инфраструктуры, не требуется кастомизация цитирования и вы готовы к проприетарной модели.

Когда RAG подходит: Ваша информационная вселенная ограничена и управляема, не меняется быстро, и вам нужна предсказуемая latency и стоимость.

Когда исследовательский агент подходит: Вам нужен анализ публикационного качества с явными цепочками цитирования, обнаружением противоречий и рекурсивной глубиной, и у вас есть инженерные ресурсы для поддержки pipeline.

Таблица справочников параметров

Параметр Значение Примечания
max_results (Tavily) 5-10 Большие значения линейно увеличивают стоимость; убывающая отдача за 10
search_depth (Tavily) "basic" или "advanced" Advanced включает глубший crawling; ~2x медленнее и дороже
include_raw_content (Tavily) true Необходимо для извлечения утверждений; возвращает полный текст страницы
freshness (Brave) "pw", "pm", "py" Прошлая неделя/месяц/год; используйте pw для чувствительного ко времени исследования
recency_window_days 30-90 Фильтруйте источники по дате публикации; 30 для новостей, 90 для технического
credibility_threshold 0.4-0.7 Минимальная оценка для включения источника; 0.4 для широкого охвата, 0.7 для высокого качества
max_depth (следование цитированиям) 2-3 Рекурсивная глубина для следования ссылкам; 2 это типичное, 3 для исчерпывающего
max_results_per_source 3-5 При использовании поиска в нескольких источниках; баланс охват vs. шум
timeout (HTTP запросы) 10-30 секунд Timeout веб-скрейпинга; увеличьте для медленных сайтов
claim_extraction_chunk_size 3000 символов Длина контента передаваемого LLM для извлечения утверждений; баланс контекст vs. стоимость
synthesis_token_budget 16000-32000 Всего входных токенов для финального синтеза; зависит от окна контекста модели

Распространённые ошибки

Ошибка 1: Не дедупликация по canonical URL

Влияние: Один источник считается несколько раз, раздутый подсчёт источников, потраченная обработка.

Неправильно:

all_results = []
for results in results_per_source:
    all_results.extend(results)  # No deduplication

return all_results

Правильно:

from urllib.parse import urlparse, urlunparse

def canonicalize_url(url: str) -> str:
    parsed = urlparse(url)
    # Remove query params and fragment
    canonical = urlunparse((parsed.scheme, parsed.netloc, parsed.path, '', '', ''))
    return canonical.lower()

seen_urls = set()
unique_results = []
for r in all_results:
    canonical = canonicalize_url(r.url)
    if canonical not in seen_urls:
        seen_urls.add(canonical)
        unique_results.append(r)

Ошибка 2: Выполнение поисков последовательно вместо параллельного

Влияние: Исследовательский цикл в 3 раза медленнее; latency увеличивается пропорционально.

Неправильно:

results_tavily = await search_tavily(query)
results_brave = await search_brave(query)
results_serper = await search_serper(query)
# 3 sequential awaits = 3x latency

Правильно:

tasks = [
    search_tavily(query),
    search_brave(query),
    search_serper(query)
]
results_per_source = await asyncio.gather(*tasks, return_exceptions=True)
# All execute concurrently; latency = max(individual latencies)

Ошибка 3: Неграмотная обработка ошибок API

Влияние: Сбой одного API разрушает весь pipeline исследования; не возвращаются частичные результаты.

Неправильно:

results_per_source = await asyncio.gather(*tasks)
# Raises exception if any task fails

Правильно:

results_per_source = await asyncio.gather(*tasks, return_exceptions=True)

all_results = []
for results in results_per_source:
    if isinstance(results, Exception):
        continue  # Skip failed source, continue with others
    all_results.extend(results)

Ошибка 4: Извлечение утверждений без атрибуции источника

Влияние: Невозможно проверить утверждения, нет цепочки цитирования, отчёт неверифицируем.

Неправильно:

claims = [
    {"claim": "LLMs achieve 94% accuracy", "subject": "LLM performance"}
]
# No source URL attached

Правильно:

for claim in claims:
    claim["source_url"] = source_url
    claim["extraction_timestamp"] = datetime.now(timezone.utc).isoformat()
# Every claim tied to origin

Ошибка 5: Игнорирование robots.txt перед веб-скрейпингом

Влияние: Юридические/этические нарушения, блокировка IP, скрейпер заблокирован сайтом.

Неправильно:

async with session.get(url) as response:
    html = await response.text()
# No robots.txt check

Правильно:

import urllib.robotparser

rp = urllib.robotparser.RobotFileParser()
rp.set_url(f"{parsed_url.scheme}://{parsed_url.netloc}/robots.txt")
rp.read()

if rp.can_fetch("*", url):
    async with session.get(url) as response:
        html = await response.text()
else:
    # Skip this URL; robots.txt disallows
    pass

Интеграция поисковых инструментов

Исследовательские агенты интегрируют множество поисковых API для максимизации охвата и снижения систематической погрешности. Различные поисковые движки индексируют разный контент и ранжируют результаты по-разному.

Tavily Search API

Оптимизирован для AI исследовательских случаев. Возвращает структурированный JSON с полным извлечением содержимого страницы.

import aiohttp
from dataclasses import dataclass
from typing import Optional

@dataclass
class SearchResult:
    url: str
    title: str
    snippet: str
    content: str
    source: str
    published_date: Optional[str]
    relevance_score: float

async def search_tavily(
    api_key: str,
    query: str,
    max_results: int = 5,
    search_depth: str = "advanced"
) -> list[SearchResult]:
    """
    Tavily search with full content extraction.

    Args:
        api_key: Tavily API key
        query: Search query string
        max_results: Number of results (1-20)
        search_depth: "basic" or "advanced"
    """
    async with aiohttp.ClientSession() as session:
        payload = {
            "api_key": api_key,
            "query": query,
            "search_depth": search_depth,
            "include_answer": True,
            "include_raw_content": True,
            "max_results": max_results
        }

        async with session.post(
            "https://api.tavily.com/search",
            json=payload
        ) as response:
            data = await response.json()

            results = []
            for r in data.get("results", []):
                results.append(SearchResult(
                    url=r["url"],
                    title=r["title"],
                    snippet=r.get("content", "")[:500],
                    content=r.get("raw_content", r.get("content", "")),
                    source="tavily",
                    published_date=r.get("published_date"),
                    relevance_score=r.get("score", 0.5)
                ))

            return results

DuckDuckGo Search (пакет duckduckgo-search)

Бесплатный API без требования ключа. Хорош для широких запросов, менее структурированный вывод.

from duckduckgo_search import DDGS

def search_duckduckgo(query: str, max_results: int = 10) -> list[SearchResult]:
    """
    DuckDuckGo search via duckduckgo-search package.
    No API key required; rate-limited.
    """
    results = []
    with DDGS() as ddgs:
        for r in ddgs.text(query, max_results=max_results):
            results.append(SearchResult(
                url=r["link"],
                title=r["title"],
                snippet=r["body"],
                content=r["body"],  # DDG returns snippets, not full content
                source="duckduckgo",
                published_date=None,
                relevance_score=0.5  # DDG doesn't expose scores
            ))
    return results

Google Search через Serper API

Программный доступ к результатам Google. Хорош для авторитетных источников и академического контента.

async def search_serper(
    api_key: str,
    query: str,
    max_results: int = 5,
    search_type: str = "search"
) -> list[SearchResult]:
    """
    Google search via Serper API.

    Args:
        search_type: "search" for web, "scholar" for academic
    """
    async with aiohttp.ClientSession() as session:
        payload = {
            "q": query,
            "num": max_results,
            "gl": "us",
            "hl": "en"
        }

        headers = {
            "X-API-KEY": api_key,
            "Content-Type": "application/json"
        }

        endpoint = f"https://google.serper.dev/{search_type}"

        async with session.post(
            endpoint,
            json=payload,
            headers=headers
        ) as response:
            data = await response.json()

            results = []
            for r in data.get("organic", []):
                results.append(SearchResult(
                    url=r["link"],
                    title=r["title"],
                    snippet=r.get("snippet", ""),
                    content=r.get("snippet", ""),
                    source="serper",
                    published_date=r.get("date"),
                    relevance_score=1.0 / (r.get("position", 10))
                ))

            return results

Параллельное выполнение по нескольким источникам

Комбинируйте несколько поисковых API для максимизации охвата:

async def search_all_sources(
    query: str,
    tavily_key: str,
    serper_key: str,
    max_per_source: int = 5
) -> list[SearchResult]:
    """Execute searches across all sources in parallel."""
    tasks = [
        search_tavily(tavily_key, query, max_per_source),
        search_serper(serper_key, query, max_per_source)
    ]

    results_per_source = await asyncio.gather(*tasks, return_exceptions=True)

    all_results = []
    for results in results_per_source:
        if isinstance(results, Exception):
            continue  # Skip failed sources
        all_results.extend(results)

    # Deduplicate by canonical URL
    from urllib.parse import urlparse, urlunparse

    def canonicalize_url(url: str) -> str:
        parsed = urlparse(url)
        return urlunparse((parsed.scheme, parsed.netloc, parsed.path, '', '', '')).lower()

    seen_urls = set()
    unique_results = []
    for r in all_results:
        canonical = canonicalize_url(r.url)
        if canonical not in seen_urls:
            seen_urls.add(canonical)
            unique_results.append(r)

    return unique_results

Синтез из нескольких источников

Синтезирование информации из нескольких источников требует параллельного извлечения, дедупликации, оценки качества источников и отслеживания цитирований.

Оценка качества источников

Не все веб-источники имеют одинаковую надёжность. Присвойте оценки качества на основе авторитета домена и сигналов контента.

class SourceQualityAssessor:
    def __init__(self):
        self.high_authority_domains = {
            ".gov", ".edu", ".org",
            "nature.com", "science.org", "arxiv.org",
            "who.int", "cdc.gov", "nist.gov"
        }

        self.low_credibility_signals = {
            "wordpress.com", "blogspot.com", "medium.com"
        }

    def assess(self, result: SearchResult) -> dict:
        """Quick rule-based quality assessment."""
        score = 0.5
        flags = []

        from urllib.parse import urlparse
        domain = urlparse(result.url).netloc.lower()

        # Domain authority
        for auth_domain in self.high_authority_domains:
            if auth_domain in domain:
                score += 0.3
                flags.append("authoritative_domain")
                break

        for low_domain in self.low_credibility_signals:
            if low_domain in domain:
                score -= 0.2
                flags.append("user_generated_platform")
                break

        # Content length signal
        if len(result.content) > 2000:
            score += 0.1
            flags.append("substantial_content")
        elif len(result.content) < 200:
            score -= 0.15
            flags.append("thin_content")

        return {
            "score": max(0.0, min(1.0, score)),
            "flags": flags,
            "domain": domain
        }

Извлечение утверждений с атрибуцией источника

Извлекайте проверяемые утверждения и поддерживайте связь цитирования:

import anthropic
import json
from datetime import datetime, timezone

class ClaimExtractor:
    def __init__(self, client: anthropic.Anthropic):
        self.client = client

    def extract_claims(
        self,
        content: str,
        source_url: str,
        research_question: str
    ) -> list[dict]:
        """Extract verifiable claims with source attribution."""

        prompt = f"""Extract factual claims from this source relevant to the research question.

Research question: {research_question}
Source URL: {source_url}
Content: {content[:3000]}

Extract claims that are:
- Specific and verifiable
- Relevant to the research question
- Attributed (who says what)

Return JSON array:
[
  {{
    "claim": "specific factual statement",
    "claim_type": "statistic/finding/opinion/definition",
    "subject": "what the claim is about",
    "confidence_in_claim": "high/medium/low",
    "quote": "exact quote from source if available"
  }}
]"""

        response = self.client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=800,
            messages=[{"role": "user", "content": prompt}]
        )

        claims = json.loads(response.content[0].text)

        # Attach source metadata to every claim
        timestamp = datetime.now(timezone.utc).isoformat()
        for claim in claims:
            claim["source_url"] = source_url
            claim["extraction_timestamp"] = timestamp

        return claims

Кросс-источниковая верификация

Проверяйте утверждения путём проверки на подтверждение или противоречие между источниками:

def verify_claim_across_sources(
    client: anthropic.Anthropic,
    claim: dict,
    all_claims: list[dict]
) -> dict:
    """Check if claim is corroborated or contradicted by other sources."""

    # Find claims from other sources on same subject
    other_sources_claims = [
        c for c in all_claims
        if c["source_url"] != claim["source_url"]
        and c["subject"] == claim.get("subject")
    ]

    if not other_sources_claims:
        return {
            "verification_status": "unverified",
            "corroborating_sources": [],
            "contradicting_sources": []
        }

    claims_text = "\n".join([
        f"Source {c['source_url']}: {c['claim']}"
        for c in other_sources_claims[:5]
    ])

    prompt = f"""Compare this claim against claims from other sources:

Target claim: "{claim['claim']}"
Source: {claim['source_url']}

Other sources' claims on same topic:
{claims_text}

Determine:
1. Which sources CORROBORATE (agree with) this claim?
2. Which sources CONTRADICT this claim?

Return JSON:
{{
  "verification_status": "verified/partially_verified/contradicted/unverified",
  "corroborating_sources": ["url1", "url2"],
  "contradicting_sources": ["url3"],
  "contradiction_details": "if contradicted, explain discrepancy"
}}"""

    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=400,
        messages=[{"role": "user", "content": prompt}]
    )

    return json.loads(response.content[0].text)

Построение DAG цитирований

Поддерживайте направленный ациклический граф, связывающий утверждения с источниками и ссылками:

from typing import Dict, List, Set

class CitationGraph:
    def __init__(self):
        self.nodes: Dict[str, dict] = {}  # url -> node metadata
        self.edges: List[tuple] = []  # (from_url, to_url, relation_type)

    def add_source(self, url: str, metadata: dict):
        """Add a source node to the graph."""
        self.nodes[url] = {
            **metadata,
            "node_type": "source"
        }

    def add_claim(self, claim_id: str, claim_data: dict, source_url: str):
        """Add a claim node linked to its source."""
        self.nodes[claim_id] = {
            **claim_data,
            "node_type": "claim"
        }
        self.edges.append((claim_id, source_url, "extracted_from"))

    def add_citation(self, citing_url: str, cited_url: str):
        """Add citation relationship between sources."""
        self.edges.append((citing_url, cited_url, "cites"))

    def get_citation_chain(self, claim_id: str) -> List[str]:
        """Get full citation chain from claim to primary sources."""
        chain = []
        visited = set()

        def traverse(node_id):
            if node_id in visited:
                return
            visited.add(node_id)
            chain.append(node_id)

            for from_id, to_id, rel in self.edges:
                if from_id == node_id:
                    traverse(to_id)

        traverse(claim_id)
        return chain

Цикл итеративной доработки

Исследовательские агенты улучшают результаты благодаря итеративной доработке запросов на основе выявленных пробелов.

Разложение запроса

Разбейте сложные вопросы на подвопросы:

class ResearchPlanner:
    def __init__(self, client: anthropic.Anthropic):
        self.client = client

    def decompose_query(self, research_question: str) -> dict:
        """Break complex research question into structured search plan."""

        prompt = f"""Decompose this research question into a structured search plan.

Research question: {research_question}

Create a research plan with:
1. Core sub-questions (main aspects to research)
2. Specific search queries for each sub-question
3. Source types needed (academic, news, official docs)
4. Time scope (how recent must sources be?)

Return JSON:
{{
  "core_aspects": [
    {{
      "aspect": "description of research dimension",
      "priority": "critical/important/supplementary",
      "search_queries": ["query 1", "query 2"],
      "preferred_sources": ["academic", "industry", "news"],
      "requires_recency": true/false,
      "recency_window_days": 90
    }}
  ],
  "total_estimated_queries": <number>,
  "research_depth": "shallow/moderate/deep"
}}"""

        response = self.client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1000,
            messages=[{"role": "user", "content": prompt}]
        )

        return json.loads(response.content[0].text)

Обнаружение пробелов

Выявляйте что именно отсутствует после начального исследования:

def detect_gaps(
    client: anthropic.Anthropic,
    research_question: str,
    current_claims: list[dict]
) -> list[str]:
    """Identify gaps in research coverage."""

    claims_summary = "\n".join([
        f"- {c['claim']} (from: {c['source_url']})"
        for c in current_claims[:20]
    ])

    prompt = f"""Analyze current research findings and identify gaps.

Research question: {research_question}

Current findings:
{claims_summary}

What important aspects of the question are NOT covered by current findings?
List specific gaps that need additional research.

Return JSON array of gap descriptions:
["gap 1 description", "gap 2 description", ...]"""

    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=400,
        messages=[{"role": "user", "content": prompt}]
    )

    return json.loads(response.content[0].text)

Генерация дополнительных запросов

Генерируйте целенаправленные дополнительные запросы для заполнения пробелов:

def generate_follow_up_queries(
    client: anthropic.Anthropic,
    research_question: str,
    identified_gaps: list[str]
) -> list[str]:
    """Generate follow-up search queries to fill identified gaps."""

    prompt = f"""Generate targeted search queries to fill research gaps.

Original question: {research_question}

Identified gaps:
{chr(10).join(f'- {g}' for g in identified_gaps)}

Generate 3-5 specific search queries that would address these gaps.

Return JSON array: ["query1", "query2", ...]"""

    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=300,
        messages=[{"role": "user", "content": prompt}]
    )

    return json.loads(response.content[0].text)

Полный цикл итеративного исследования

async def iterative_research(
    client: anthropic.Anthropic,
    search_client,
    research_question: str,
    max_iterations: int = 3
) -> dict:
    """Execute iterative research with gap detection and refinement."""

    planner = ResearchPlanner(client)
    extractor = ClaimExtractor(client)

    # Initial decomposition
    research_plan = planner.decompose_query(research_question)

    all_claims = []
    iteration = 0

    while iteration < max_iterations:
        # Execute current round of queries
        if iteration == 0:
            queries = [
                q for aspect in research_plan["core_aspects"]
                for q in aspect["search_queries"][:2]
            ]
        else:
            # Detect gaps and generate follow-ups
            gaps = detect_gaps(client, research_question, all_claims)
            if not gaps:
                break  # No more gaps, research complete
            queries = generate_follow_up_queries(client, research_question, gaps)

        # Execute searches
        for query in queries:
            results = await search_all_sources(query, tavily_key="...", serper_key="...")

            for result in results:
                claims = extractor.extract_claims(
                    result.content,
                    result.url,
                    research_question
                )
                all_claims.extend(claims)

        iteration += 1

    return {
        "total_claims": len(all_claims),
        "iterations": iteration,
        "claims": all_claims
    }

Производительность и бенчмарки

Примечание: Приведённые ниже цифры являются иллюстративными оценками на основе типичных production-конфигураций, а не измерениями конкретной системы.

Latency поисковых API

Исследовательские агенты чувствительны к latency. Параллельное выполнение поисковых API критично:

  • Tavily базовый поиск: 800-1500ms за запрос
  • Tavily продвинутый поиск: 1500-3000ms за запрос
  • Brave Search API: 400-800ms за запрос
  • Serper (Google): 600-1200ms за запрос
  • DuckDuckGo (бесплатный): 1000-2000ms за запрос

Влияние параллельного выполнения: Запуск 3 источников параллельно даёт общую latency приблизительно равную самому медленному источнику, а не сумме всех источников. Для Tavily (1500ms) + Brave (600ms) + Serper (900ms), последовательное выполнение занимает ~3000ms, тогда как параллельное выполнение занимает ~1500ms.

Объём LLM вызовов

Типичная комплексная исследовательская задача включает:

  • Разложение запроса: 1 LLM вызов
  • Извлечение утверждений: 10-30 LLM вызовов (1 на источник)
  • Кросс-источниковая верификация: 5-15 LLM вызовов
  • Обнаружение противоречий: 3-8 LLM вызовов
  • Обнаружение пробелов: 2-4 LLM вызова за итерацию
  • Финальный синтез: 1 LLM вызов

Всего для глубокого исследования: 25-60 LLM вызовов. При ~$0.003 за вызов (Claude Sonnet, 1K входа/500 выхода токенов), стоимость за исследовательскую задачу составляет ~$0.08-$0.18.

Потребление токенов

Разбивка использования токенов для синтеза из нескольких источников:

  • Содержимое результатов поиска (10 источников × 3000 символов): ~15K токенов
  • Извлечённые утверждения (30 утверждений × 100 токенов): ~3K токенов
  • Метаданные источников и цитирования: ~2K токенов
  • Prompt синтеза и инструкции: ~1K токенов
  • Всего входа: ~21K токенов
  • Выход (комплексный отчёт): ~4K токенов

Выделите 16K-32K входных токенов для шага синтеза в зависимости от количества источников и глубины. Используйте Claude Sonnet 4 или Opus для качества синтеза.

Преимущества кэширования

Стратегии кэширования значительно снижают стоимость для повторного исследования:

  • Кэширование результатов поиска: Кэшируйте результаты поиска по хешу запроса; снижает API стоимость при повторных запросах
  • Кэширование извлечения утверждений: Кэшируйте извлечённые утверждения по хешу контента; избегайте переизвлечения
  • Кэширование качества источников: Кэшируйте оценки качества по домену/URL; амортизируйте стоимость оценки

Оценённые сбережения: Кэширование может сократить объём LLM вызовов на 40-60% для исследования перекрывающихся тем.

Сквозное время исследования

Для сложного исследовательского вопроса требующего глубокого анализа:

  • Разложение запроса: 2-3 секунды
  • Выполнение начального поиска (3 источника, 5 запросов): 8-12 секунд (параллельно)
  • Извлечение утверждений (15 источников): 30-45 секунд (параллельная батчевка)
  • Кросс-источниковая верификация: 10-15 секунд
  • Обнаружение пробелов + итерация дополнения: 15-20 секунд
  • Финальный синтез: 8-12 секунд

Всего: 70-110 секунд для комплексного исследования. Поверхностное исследование (одна итерация, меньше источников) завершается за 30-50 секунд.

Эксперт-человек потребовал бы 30-90 минут для сравнимой глубины и качества цитирования.