Когда контекст становится врагом
В 2025 году команда ETH Zurich опубликовала исследование, которое перевернуло стандартный подход к работе с AI-агентами. Они взяли 1200 реальных email-тредов (от технических дискуссий до клиентской поддержки) и прогнали их через агентов на базе GPT-4, Claude 3.5 и новой на тот момент Mixtral 2.0. Ожидали, что чем больше контекста дашь агенту, тем лучше он поймет задачу. Результат шокировал.
Success rate (точность выполнения задачи) рос до 15-20 тысяч токенов, а потом начинал падать. При 50к токенах агенты ошибались на 37% чаще, чем при 20к. Они не просто "не видели" важное в шуме - они активно делали неверные выводы, основываясь на устаревших или второстепенных деталях.
Самый частый паттерн ошибок: агент цеплялся за технические детали из первого письма в треде, игнорируя, что в пятнадцатом письме требования полностью поменялись. LLM не умеют автоматически определять "что важно сейчас", они просто усредняют всю информацию.
Почему больше - не значит лучше
Закинуть агенту всю историю переписки кажется логичным. В теории. На практике работает три разрушительных механизма:
- Шум подавляет сигнал. В email-треде из 30 писем только 3-4 содержат актуальные требования. Остальное - приветствия, уточнения, обсуждение альтернатив, которые уже отклонили. LLM пытается угодить всему контексту сразу и выбирает "усредненное" решение, которое никого не устраивает.
- Устаревание информации. В технических обсуждениях спецификации меняются каждые 2-3 письма. Агент видит и старую, и новую версию. Исследование ETH Zurich показало: в 68% случаев агент выбирал параметры из более ранних сообщений, просто потому что они встречались чаще или были подробнее описаны.
- Стоимость не только денежная. Каждые 10к токенов контекста увеличивают время ответа на 40-60% даже у оптимизированных моделей типа GPT-5 Turbo. Ваш агент тормозит не из-за плохого кода, а потому что таскает за собой тонну исторического мусора.
Самое опасное - это когда агент на основе зашумленного контекста принимает опасные решения. Вспомните историю про Prompt Worms, где агенты заражали друг друга. Там тоже работал механизм "контекстного заражения".
Контекстный API: фильтрация вместо сжатия
ETH Zurich предложили не сжимать контекст (как в классическом RAG), а фильтровать его через многоуровневый pipeline. Они назвали это Contextual API. Разница принципиальная:
| Что делают все | Что предлагает ETH Zurich |
|---|---|
| Убирают стоп-слова, сокращают предложения | Строят граф зависимостей между сообщениями |
| Оставляют "самое важное" по embedding-сходству | Отслеживают временные линии изменений требований |
| Работают с текстом как с плоским документом | Анализируют роли участников и их влияние на решение |
Этот подход снижал токенность контекста на 73% в email-тредах, при этом success rate вырастал на 41%. Потому что агент получал не "обрезки" писем, а структурированную карту: что обсуждали, когда поменяли условия, кто принял финальное решение.
Шаг за шагом: внедряем фильтрацию email-тредов
Вот как выглядит практическая реализация на Python с использованием актуальных на 2026 год библиотек. Мы будем использовать подход, адаптированный из исследования ETH Zurich.
1 Парсим тред и строим граф зависимостей
Недостаточно просто отсортировать письма по дате. Нужно понять, кто на кого отвечает, какие предложения принимаются, какие отклоняются.
import networkx as nx
from datetime import datetime
from typing import List, Dict
class EmailThreadGraph:
def __init__(self, emails: List[Dict]):
"""
emails: список словарей с полями
['id', 'timestamp', 'sender', 'recipients', 'subject', 'body', 'in_reply_to']
"""
self.graph = nx.DiGraph()
self.emails = {e['id']: e for e in emails}
# Добавляем узлы
for email in emails:
self.graph.add_node(email['id'],
sender=email['sender'],
timestamp=email['timestamp'],
is_decision=self._contains_decision(email['body']))
# Добавляем связи "ответ на"
for email in emails:
if email['in_reply_to'] and email['in_reply_to'] in self.emails:
self.graph.add_edge(email['in_reply_to'], email['id'],
relation_type='reply')
# Добавляем семантические связи (упоминание тем)
self._add_semantic_edges()
def _contains_decision(self, text: str) -> bool:
"""Определяет, содержит ли письмо финальное решение"""
decision_phrases = ['решили', 'будем использовать', 'финальная версия',
'утверждаю', 'принято решение', 'ок, давайте так']
return any(phrase in text.lower() for phrase in decision_phrases)
2 Выделяем временные линии изменений
Ключевая идея ETH Zurich: нужно отслеживать не просто хронологию, а как менялись конкретные параметры дискуссии.
def extract_timelines(self) -> Dict[str, List]:
"""Выделяет временные линии для каждого обсуждаемого параметра"""
timelines = {}
# Ищем упоминания технических параметров, дедлайнов, бюджетов
param_patterns = {
'deadline': r'дедлайн\s*(\d{1,2}\.\d{1,2}\.\d{4})',
'budget': r'бюджет\s*(\d+)\s*(тыс|к|млн)',
'version': r'версия\s*(\d+\.\d+)',
}
sorted_emails = sorted(self.emails.values(),
key=lambda x: x['timestamp'])
for email in sorted_emails:
for param, pattern in param_patterns.items():
import re
matches = re.findall(pattern, email['body'], re.IGNORECASE)
if matches:
if param not in timelines:
timelines[param] = []
timelines[param].append({
'email_id': email['id'],
'timestamp': email['timestamp'],
'value': matches[0],
'sender': email['sender']
})
# Оставляем только последнее значение для каждого параметра
# если оно не было переопределено позже
return self._filter_obsolete_values(timelines)
Важно: не все письма равнозначны. Письмо от тимлида с пометкой "решение" должно иметь больший вес, чем вопрос стажера. В графе это учитывается через вес узлов.
3 Собираем финальный контекст для агента
Теперь не просто сбрасываем агенту письма, а составляем структурированное описание.
def build_agent_context(self, max_tokens: int = 15000) -> str:
"""Собирает контекст, который действительно полезен агенту"""
# 1. Находим ключевые (решающие) письма
decision_nodes = [n for n, attr in self.graph.nodes(data=True)
if attr.get('is_decision', False)]
# 2. Если есть решения, берем цепочку к ним
if decision_nodes:
key_emails = self._get_path_to_decisions(decision_nodes)
else:
# 3. Иначе берем последние 5-7 писем и их предшественников
key_emails = self._get_recent_discussion()
# 4. Добавляем временные линии изменений
timelines = self.extract_timelines()
# 5. Формируем структурированный контекст
context_parts = []
# Краткое содержание
context_parts.append("## Краткое содержание треда\n")
context_parts.append(f"Обсуждалось: {self._get_main_topic()}\n")
# Хронология изменений
context_parts.append("## Хронология ключевых изменений\n")
for param, changes in timelines.items():
if changes:
last = changes[-1]
context_parts.append(f"- {param}: {last['value']} "
f"(установлено {last['sender']} "
f"{last['timestamp'].strftime('%d.%m')})\n")
# Ключевые письма
context_parts.append("## Ключевые сообщения\n")
for email_id in key_emails:
email = self.emails[email_id]
# ОБРЕЗАЕМ тело, оставляя только суть
summary = self._summarize_email(email['body'])
context_parts.append(f"**{email['sender']}** "
f"({email['timestamp'].strftime('%H:%M')}): "
f"{summary}\n\n")
full_context = "\n".join(context_parts)
# 6. Обрезаем до max_tokens, но не обрываем середину структуры
return self._truncate_intelligently(full_context, max_tokens)
Ошибки, которые стоят денег
Внедряя фильтрацию контекста, команды наступают на одни и те же грабли. Вот топ-3 ошибки 2025-2026 годов:
- Фильтрация только по релевантности. Используете embedding-поиск? Он находит письма, похожие по формулировкам, но пропускает критичные "изменения требований", которые формулируются иначе. В исследовании ETH Zurich embedding-подход уступал графовому на 28% по точности.
- Игнорирование социального графа. Кто сказал - важно. Письмо CEO с "мне кажется" весит больше, чем письмо инженера с детальными расчетами. Если ваш агент этого не понимает, он будет принимать технически правильные, но политически невозможные решения. Это одна из причин, почему агенты поддаются социальному давлению.
- Жадная экономия токенов. Увидели, что метод сокращает контекст на 70%, начали резать до 5к токенов. Но некоторые треды действительно требуют 25к контекста. Фильтрация - не сжатие, она должна быть адаптивной. Лучше оставить 18к полезных токенов, чем 5к случайных.
Особенно опасна потеря "контекста отрицания". Если в письме №10 сказали "не используем подход X", а в письме №15 просто обсуждают детали подхода Y, агент может решить, что подход X снова в игре. В графовой модели такие отрицания явно маркируются.
Как тестировать фильтрацию
Метрики из исследования ETH Zurich, которые стоит внедрить:
- Decision Trace Accuracy - может ли агент восстановить цепочку принятия решений по отфильтрованному контексту?
- Obsolete Information Rate - сколько устаревших требований осталось в контексте?
- Token Efficiency Score = (success rate) / (токены) × 1000. Показывает, насколько эффективно используете токены.
Тестовый датасет: возьмите 100 реальных email-тредов из вашей компании, вручную разметьте, какие письма критичны для понимания, а какие - шум. Затем прогоните через ваш фильтр и посчитайте recall критичных писем. Если ниже 85%, фильтр режет слишком агрессивно.
С какими моделями это работает лучше всего
На 2026 год исследование показывает разные результаты:
| Модель | Улучшение success rate с фильтрацией | Оптимальный контекст после фильтрации |
|---|---|---|
| GPT-5 Turbo (2026) | +38% | 12-18к токенов |
| Claude 4 Opus | +42% | 10-16к токенов |
| Gemini 3.0 Ultra | +31% | 15-22к токенов (эта модель сжирает токены быстрее) |
| Qwen 3.0 Max | +45% | 8-14к токенов |
Интересный эффект: маленькие модели (7B-13B параметров) выигрывают от фильтрации контекста больше, чем гиганты. Потому что их capacity ограничена, и шум для них смертелен.
Что будет дальше?
Фильтрация контекста - не временный хак, а новая архитектурная парадигма. К концу 2026 года ожидаем:
- Контекстные API станут стандартом в таких платформах как LangChain, LlamaIndex. Не нужно будет писать свой графовый анализатор.
- Модели научатся запрашивать контекст динамически. Сейчас мы сами решаем, что дать агенту. В будущем агент будет сам спрашивать: "Уточните, менялись ли требования к бюджету после 15 марта?"
- Появятся специализированные модели для анализа коммуникаций, которые из коробки понимают структуру email-тредов, чатов, митингов.
Пока же - берите подход ETH Zurich, адаптируйте под свои данные. Первый же внедренный фильтр сэкономит вам 30-50% на inference и повысит качество работы агентов. Не верите? Проверьте на своих самых многословных тредах.
И помните: если ваш агент на Qwen 3.0 забывает всё через 20 минут, проблема может быть не в памяти агента, а в том, что вы кормите его информационным мусором.