Инженерия reducer: как сократить то, что должна читать модель
Русский перевод X Article о reducer в multi-agent pipeline: как детерминированная очистка между воркерами и моделью синтеза снижает стоимость и задержку.
Оригинал опубликован Gipp 🦅 в X: https://x.com/gippp69/status/2087120797206819322. Этот черновик подготовлен как русская версия исходного материала.
Большинство людей, которые масштабируют multi-agent pipeline, оптимизируют не тот слой.
Они добавляют больше воркеров.
Больше воркеров означает больше покрытия.
Больше покрытия означает больше сырого текста, который потом попадает в модель, пишущую финальный ответ.
Никто не закладывает бюджет на эту модель. Все закладывают бюджет на воркеров.
Именно этот слой ломается первым.
Вот что исправило это у меня: один детерминированный reducer между 40 параллельными воркерами Claude Haiku и Claude Sonnet, моделью, которая пишет финальный отчёт. Стоимость одного запуска упала на 86%. Задержка упала на 78%.
TLDR: если не хотите читать всё, функция reducer и получившиеся цифры находятся внизу. Сначала скопируйте функцию, спорьте об остальном потом.
Введение
У меня это сломалось раньше, чем я понял почему.
Сначала полные цифры:
- 40 параллельных воркеров Claude Haiku, по одному запросу на каждого, затем один синтезирующий вызов Claude Sonnet
- Сырой объединённый вывод до очистки: 41 200 токенов
- Тот же вывод после reducer только на коде, без модели: 5 300 токенов
- Стоимость синтеза за запуск: $1.38 до, $0.19 после
- Задержка синтеза: 51 секунда до, 11 секунд после
- Противоречия, которые reducer поднял наверх, хотя сырой дамп их прятал: 23
- Запуски, эскалированные человеку из-за неоднозначного финального ответа: 6 из 50 до, 1 из 50 после
Ниже — откуда взялись эти цифры и код, который дал это снижение.
1. Как выглядел pipeline
Сорок воркеров, каждому даётся узкий срез одного исследовательского вопроса. Найти источники, извлечь утверждение, приложить доказательство.
Каждый воркер работал на Claude Haiku: достаточно дешёвой модели, чтобы без долгих раздумий распараллелить задачу на сорок вызовов. Вывод каждого воркера напрямую попадал в один prompt, а Claude Sonnet читал все сорок результатов и писал финальный отчёт.
Это казалось эффективным. Сорок дешёвых вызовов, один сильный вызов в конце.

2. Что на самом деле попадало в модель синтеза
Не сорок чистых находок. Сорок маленьких документов, каждый со своей преамбулой, своим форматированием и своим способом цитировать источник.
Пятнадцать из сорока почти дублировали друг друга: одно и то же утверждение под другим углом, один и тот же источник, процитированный тремя разными способами.
Шесть были malformed: отсутствующее поле, битая временная метка, утверждение без приложенного доказательства.
Модель синтеза должна была прочитать всё это, сама заметить дубликаты, решить, какие битые записи игнорировать, и только потом начать рассуждать об отчёте.
Я построил исследовательский pipeline. Но дорогой модели я отправил задачу по очистке данных, переодетую в одежду исследовательского pipeline.

Есть причина, почему это стоит дороже не только в токенах. Liu и коллеги из Stanford (2023, опубликовано в TACL 2024) показали, что точность модели при поиске релевантной информации в длинном контексте имеет U-образную форму: она сильна в начале и в конце ввода и заметно хуже, когда ответ спрятан в середине, даже у моделей, созданных для длинного контекста. Свалить сорок неупорядоченных находок в один prompt — это не просто раздуть счёт. Это положить большую часть данных ровно туда, где модель читает хуже всего.
3. Reducer
Для этапа очистки модель не нужна. Нужен код.
from dataclasses import dataclass
from collections import defaultdict
@dataclass
class Finding:
claim: str
evidence: str
source: str
confidence: float
timestamp: str
def reduce_findings(raw: list[Finding]) -> list[Finding]:
valid = [f for f in raw if f.claim and f.evidence and f.source]
grouped: dict[str, list[Finding]] = defaultdict(list)
for f in valid:
key = normalize(f.claim)
grouped[key].append(f)
deduped = []
for key, group in grouped.items():
best = max(group, key=lambda f: f.confidence)
if len(group) > 1:
best.evidence += f" [confirmed by {len(group)-1} other worker(s)]"
deduped.append(best)
return sorted(deduped, key=lambda f: f.confidence, reverse=True)Выкинуть malformed-записи. Сгруппировать по нормализованному утверждению. Оставить в каждой группе версию с максимальной уверенностью. Отметить, когда несколько воркеров независимо пришли к одному и тому же утверждению, потому что такое согласие само по себе является сигналом для модели синтеза, а не тем, что она должна заново обнаруживать, читая сорок документов подряд.
4. Цифры ещё раз и откуда взялась каждая
- 41 200 токенов в сыром виде, 5 300 токенов после reduce. Это 34 уникальные находки из 40 сырых записей: шесть malformed-записей отброшены, и их потеря видна в логе запуска, а не молча растворена.
- $1.38 → $0.19 за запуск. Модель синтеза тарифицируется по тому, что читает. Когда она читает на 87% меньше входа при той же цене за токен, почти всё снижение стоимости приходит отсюда.
- 51 секунда → 11 секунд. Более длинный ввод означает больше времени на внимание к нему до того, как модель сгенерирует первый токен настоящего ответа. Сокращение входа сократило ожидание ещё до начала реального рассуждения.
Ни одна из этих трёх цифр не требовала более умной модели. Они требовали, чтобы модель перестала делать работу, с которой код всегда справлялся лучше. Распараллеливание на сорок воркеров Haiku уже было дешёвой частью. Исправление было не в более дешёвом воркере, а в том, чтобы дать Sonnet меньше мусора для чтения в конце.

5. Что меня удивило
Я ожидал снижения стоимости и задержки. Я не ожидал, что reducer поймает то, что сырой синтез пропускал.
Группировка по нормализованному утверждению не просто удаляет дубликаты, она выявляет расхождения. Два воркера пришли к одному утверждению с разной уверенностью или, хуже, два воркера пришли к противоречащим утверждениям об одном факте — это становится видно сразу, как только вы группируете, а не склеиваете всё подряд.
На тестовых запусках группировка выявила 23 прямых противоречия между воркерами, которые сырой, несгруппированный дамп ни разу не пометил. Потому что противоречие на первой и тридцатой странице стены текста — не то, что модель надёжно ловит простым чтением вперёд.
Структурное сокращение — это не только оптимизация стоимости. Это место, куда можно поставить проверку, которой у обычной конкатенации вообще нет.
6. Защитные проверки
finding missing claim, evidence, or source -> dropped before grouping, logged as malformed
two findings in one group disagree on the fact -> flagged as a contradiction, sent to synthesis explicitly
a group has only one member -> passes through unchanged, no false confidence added
reduce_findings() receives an empty list -> returns empty, synthesis step is skipped, not run on nothingЧетыре проверки, ни одной модельной операции, и каждая ловит то, что раньше зависело от того, заметит ли это модель синтеза сама.
7. Что я ещё не тестировал
Функция normalize(), которая группирует утверждения, делает важную работу, и я пока не стресс-тестировал её за пределами этой выборки. Это similarity match по тексту утверждения, и я ещё не знаю её false-merge rate: насколько часто два действительно разных утверждения, похожие по формулировке, схлопываются в одно. Это баг корректности, спрятанный за выигрышем в стоимости, и мне нужно больше запусков, прежде чем я доверю это чему-то, что не могу вручную spot-check'нуть.
8. Playbook
- Если больше одного воркера кормит одну финальную модель, поставьте reducer между ними до того, как трогать prompt.
- Дедупликация, отбрасывание malformed-записей и группировка — это задачи для кода. Рассуждать о том, что осталось, должна модель. Не давайте одному перетекать в другое.
- Измерьте размер сырого входа до любой другой оптимизации. Количество токенов, входящих в synthesis call, обычно и есть вся история стоимости.
- Считайте согласие и расхождение между воркерами данными, которые reducer должен поднять наверх, а не шумом, который модель должна заново обнаружить.
Суть
Сорок воркеров Claude Haiku никогда не были дорогой частью. Дорогой частью был Claude Sonnet, который читал все сорок выводов в сыром виде и занимался очисткой до того, как мог начать рассуждать. Reducer, который не трогает само рассуждение, сократил счёт на 86% и ожидание на 78%, просто забрав работу у Sonnet, а не заставив его работать усерднее.
Если хотите больше таких разборов, я публикую один каждые пару дней в Telegram и X. Оба бесплатные.