
Полная версия:
Сергей Кирницкий Право на суждение. Агентность как принцип проектирования ИИ-систем
- + Увеличить шрифт
- - Уменьшить шрифт

Право на суждение
Агентность как принцип проектирования ИИ-систем
Сергей Кирницкий
Оформление обложки Created with Grok
© Сергей Кирницкий, 2026
ISBN 978-5-0070-7172-7
Создано в интеллектуальной издательской системе Ridero
Введение. Право на суждение
Где-то в вашей системе есть строка кода, которую давно никто не открывал. В ней записано число — скажем, четыре: сколько фрагментов доставать из базы знаний на каждый вопрос пользователя. Или правило: модели разрешён один проход, второго не будет. Или развилка: вопросы об оплате — в эту ветку, всё остальное — в ту. Или схема, в которую ответ обязан уложиться, каким бы ни оказался вопрос. Строка появилась при проектировании; писали её, возможно, не вы, а того, кто писал, может уже не быть в команде. С тех пор система пережила смену модели, пару миграций и один редизайн — а строка всё там же, и прямо сейчас она выносит решение за очередного пользователя: что для него релевантно, сколько попыток заслуживает его задача, каким его вопросу вообще позволено быть. Почему число именно такое, никто уже не помнит; спросить не у кого, да и незачем — система работает, жалоб немного, панели мониторинга зелёные, и именно поэтому строку никто не открывает. Но интересно не «почему четыре». Интересен вопрос, который я буду задавать каждому такому месту: чьё это суждение? Кто его на самом деле выносит — модель, которая читает живой вопрос в эту секунду, или код, решивший всё однажды и с тех пор ни разу не передумавший?
Долгое время честный ответ был: код — и он был правильным. Прежнюю модель приходилось вести за руку: она теряла нить на втором шаге, путала инструкцию с данными, выдавала правдоподобную форму вместо верного содержания. Отдавать ей решения о собственном пути было не смелостью, а безответственностью, и инженерная культура вокруг моделей закономерно сложилась как культура недоверия — разумного, заслуженного, многократно подтверждённого продакшеном. Интеллект системы жил в коде: в правилах и ветвлениях, в конвейерах обработки, в маршрутизации запросов. Вокруг того, как обойтись наименьшим участием модели, выросла целая дисциплина: пусть модель делает узкую, проверяемую работу, а думает — код. Модель занимала в этой конструкции одну клетку — классифицировала, размечала, отвечала на заранее подготовленный запрос — и возвращала результат туда, где все остальные решения давно были приняты за неё. Чем меньше от неё зависело, тем спокойнее спала команда.
Это устройство мира кончилось — и кончилось быстрее, чем успели измениться привычки. Интеллект системы сместился из статичной логики приложения в саму модель. Сегодняшняя модель — из лучших, доступных в своём поколении, — держит длинную инструкцию и не теряет её к десятому шагу, удерживает план и пересматривает его по ходу, вызывает инструменты и читает их результаты, замечает собственную ошибку и пробует иначе, работает с контекстом, который прежде не поместился бы целиком. Существенно одно: та работа суждения, которую раньше приходилось вынимать из модели и записывать в код, потому что модель её не тянула, теперь всё чаще оказывается тем, что модель делает лучше кода. И тогда прежняя оболочка из страховки превращается в помеху: она уже не компенсирует слабость — она не даёт проявиться силе. А та строка об этом не знает. Способность модели выросла на поколения; права, выданные ей архитектурой, остались прежними. Для инженера это редкий случай: в системе уже лежит незадействованный ресурс — способность, к которой никто не выписал прав. Обычно выигрыш приходится добывать по крохам, вытачивая проценты из конвейера; здесь он заперт одной строкой.
Этот разрыв — между тем, что модель может, и тем, что ей позволено, — и есть предмет книги, начиная с её названия. Каждый раз, когда внешняя логика решает за модель то, что та могла бы решить сама — что искать, что предпринять, повторить ли попытку, что удержать в памяти, действовать ли, — суждение у модели изымается: выносится один раз, на этапе проектирования, и застывает в коде. Я называю это изъятием агентности, а застывшее решение — замороженным суждением; строгие определения подождут — пока достаточно имён. Замороженное суждение не ошибается. Оно просто не пересматривается — даже когда мир вокруг изменился, а модель, за которую его когда-то заморозили, теперь вынесла бы его лучше. Отсюда и название книги — право на суждение: способность сама по себе не выдаёт прав. Права в системе выдаёт архитектор, и модель будет выносить ровно те суждения, которые он ей оставил, — сколько бы поколений способности ни прошло мимо этой строки. Книга о том, где это право вернуть — потому что способность до него доросла, — и где, так же осознанно, не давать.
Такие замороженные суждения рассыпаны по любой зрелой системе, и почти все они невидимы, потому что выглядят как просто код — как норма, не требующая оправданий. Жёстко заданное число фрагментов, которые всегда достаются из базы, — это решение о том, что релевантно, вынесенное за модель и с тех пор не пересмотренное. Классификатор, раз и навсегда постановивший, какими бывают вопросы пользователей, — решение о природе задачи, застывшее в чьей-то старой таксономии. Единственный разрешённый проход там, где задача просит второго взгляда, — решение о том, сколько усилий заслуживает ответ. Ни одно из этих мест не выглядит как отнятое суждение; все они выглядят как настройки, значения по умолчанию, здравый смысл. В этом и трудность: изъятие не оставляет следов на поверхности — его не видно ни в демо, ни в интерфейсе, ни в метриках, пока запросы похожи на предусмотренные. Всё начинается с навыка замечать такие места: видеть в строке конфигурации не параметр, а решение — чьё-то, старое и до сих пор действующее.
Упрёка тем, кто эти строки писал, здесь нет. Тот, кто обернул слабую модель плотной оболочкой, решал реальную задачу своего времени и решал её правильно. Модель, терявшая нить на втором шаге, не могла вести многоходовый поиск — значит, поиск разумно было свести к одному ходу и задать снаружи; модель, отвечавшая уверенно даже на нерелевантной выборке, не годилась в судьи собственного контекста — значит, контекст решали за неё. Это была не леность мысли, а инженерная честность: точная подгонка архитектуры под то, что модель тогда умела. Само по себе изъятие — законный приём, и отменять его никто не предлагает; упрёк адресован не людям и не старым архитектурам, а инерции — решениям, которые были верны при одних способностях модели и молча пережили их рост. Порок начинается не там, где суждение забирают у модели, а там, где его забирают не выбором, а привычкой. И снять этот упрёк важно раньше любых переворотов: трудно пересматривать выбор, за который тебя не перестали винить.
Главный тезис заявлю сразу и без доказательств. Свобода модели — правильный дефолт проектирования; ограничение — осознанное исключение. Это не призыв дать свободу всему, а дисциплина различения: видеть, какое суждение отдать модели, а какое сознательно оставить приложению, — и настаивать лишь на порядке, в котором эти вопросы задаются, — сначала «что оставить модели», потом «что забрать», а не наоборот. Повод вернуть суждение всегда конкретен: модель способна вынести его лучше замороженного кода — не по определению, а на живых запросах, где код промахивается. Конкретна и цена: вместе с суждением модель получает свободу ошибиться иначе и право пойти путём, которого инженер не закладывал. Разрешите ей повторить попытку, когда первый ответ не сошёлся, — и она вытащит запрос, на котором одноходовый конвейер сдавался; но заплатить придётся лишним раундом и маршрутом, который не расписать заранее. Настоящая ставка возврата — не проценты к метрике, а класс запросов, прежде недосягаемый. А есть решения, которые модели не стоит отдавать вовсе, — недоверенный ввод, недопустимая цена ошибки, требование строгой воспроизводимости. Разморозить суждение — не жест доверия и не идеология, а инженерное решение с поводом и ценой; обе величины останутся на виду до самого конца — ни выгода не спрячется за осторожностью, ни цена за энтузиазмом. Тезис, умалчивающий о своей цене, — лозунг, а не метод.
Я пишу для тех, кто эти системы строит: для инженеров и архитекторов ИИ-систем, техлидов, продакт-инженеров — для всех, кто отвечает за то, где в системе проходит граница между кодом и моделью. От читателя потребуется знакомство с большими языковыми моделями на уровне пользователя API — запрос и ответ, вызов инструмента, контекстное окно; глубокого машинного обучения не нужно, потому что речь не о том, как модели устроены внутри и как их обучают. Предмет — то, что мы строим вокруг готовой модели, и решения, которые в этой обвязке кто-то за кого-то выносит: достаёт ли модель нужные документы сама или ей их подкладывают; разрешён ли второй заход, если первый не дал ответа; чья версия важного остаётся в памяти — модели или того, кто сжал контекст за неё; где систему остановить, а где отпустить. Это не введение в языковые модели с нуля, не туториал под конкретный инструмент и не книга об оркестрации множества агентов между собой — это книга про одну развилку, которая проходит через каждую ИИ-систему, и про то, как проходить её не вслепую. Скорее всего, вы уже выпустили в мир систему, где каждое из этих решений принято, — вопрос лишь в том, кем и глядя ли.
Сквозь книгу проходит одна карта — карта суждений: несколько типовых решений, которые ИИ-система либо выносит за модель, либо доверяет ей, — что и где искать, что делать дальше, повторить ли попытку, как ответить, что помнить, действовать ли самостоятельно. Каждое пройдёт три станции: где его изымают, где его можно вернуть и чего этот возврат стоит. По той же карте движется сквозной пример — ассистент поддержки на базе документации: система, которую хоть раз строил или видел почти каждый и в которой естественно живут все решения карты. Сначала это знакомая версия с изъятой агентностью: подготовленный за модель контекст, классификатор намерений, один проход, шаблонный ответ. Потом она упрётся в потолок на запросе, которого никто не предусмотрел. Потом её перепроектируют, суждение за суждением возвращая модели то, что было заморожено. Потом посчитают цену этой свободы и проверят систему на прочность там, где свобода опасна. И под конец по ней пройдут с чек-листом, как по чужой системе, которую нужно честно оценить. Один и тот же бот через всю книгу — чтобы было видно, как одно и то же решение сначала замораживают, а потом размораживают, и что меняется на каждом шаге.
Осталось одно обещание. Здесь не будет рецептов под конкретный инструмент, протокол или модель; не будет версий, бенчмарков и имён — ничего, что устареет к следующему поколению. Это не осторожность, а позиция: метод переживает модели. Отсюда же — сдержанность со словарём, которым поле описывает само себя. Имена его дисциплин приходят слоями и сменяют друг друга: то, что вчера звалось prompt-инжинирингом, сегодня зовётся context-инжинирингом, а завтра получит новое имя для тех же, по сути, решений. Разговор сознательно пойдёт уровнем выше — на суждениях, а не на терминах сезона; у каждого текущего имени есть мост к тому, как ту же вещь называю я, и эти мосты собраны в глоссарии, чтобы читатель, пришедший с любым словарём, легко нашёл соответствие. Знать имя сезона полезно; строить на нём метод — значит привязать метод к сроку годности имени. Вопрос же «чьё это суждение?» не требует новой модели, чтобы его задать, и не устареет вместе со старой: он останется, когда сегодняшние библиотеки, протоколы и имена дисциплин сменятся неузнаваемо. Сегодня этот вопрос звучит так: пора перестать выбирать путь за модель и позволить ей самой находить лучший путь к задаче — там, где она на это способна, и ровно настолько, насколько способна. Где проходит это «настолько», как найти его для своей системы и чем за него платить — вся остальная книга. Начнём с того, чтобы научиться видеть суждения там, где мы привыкли видеть код, — с той самой строки, которую давно никто не открывал.
Часть I. Изъятие агентности
Две системы могут быть неотличимы снаружи. Один и тот же интерфейс, та же база знаний, та же модель под капотом. Две команды показывают демо своего ассистента поддержки; обе уверенно отвечают на два десятка типовых вопросов; обе выглядят готовыми к продакшену. Разница между ними не проявляется в демо. Она проявляется тремя неделями позже — на двадцать первом вопросе, том самом, который никто не прописал заранее.
Один ассистент на этом вопросе спотыкается ровно и предсказуемо: находит не тот раздел базы, отвечает уверенно и мимо, и не может сказать «мне принесли не то». Другой — переспрашивает базу иначе, замечает, что первый результат не отвечает на вопрос, и находит путь там, где его не прокладывали. Компоненты у них одинаковые. Различаются они тем, кто внутри выносит решения.
Именно это различие — предмет книги, и прежде всего его нужно научиться видеть. Возьмём типичную поддержку на базе знаний, docs-бот, знакомый почти каждому, кто строил что-то на языковых моделях. В самой распространённой его версии почти каждое содержательное решение принято не моделью. Что искать по запросу пользователя — решает механизм извлечения, отдающий модели готовую выборку. Какого рода это запрос и в какую ветку его направить — решает роутер намерения на входе. Стоит ли попробовать ещё раз, если найденное не отвечает на вопрос, — не решает никто: конвейер одноходовый, второй попытки в нём просто нет. Как оформить ответ — задаёт шаблон. Модель в этой системе делает ровно одно: формулирует текст поверх того, что ей уже выбрали, направили и разрешили. Она — последнее звено, а не то, где живёт интеллект.
Каждое из этих решений когда-то было принято — один раз, на этапе проектирования, — и с тех пор не пересматривалось. Не потому, что оно всегда верное, а потому, что его вынесли заранее и заморозили в коде. Замороженное суждение* — это решение, которое кто-то вынес однажды при проектировании системы и застыло в её логике: оно больше не зависит ни от запроса, ни от того, что вернул поиск, ни от того, справляется модель или нет. Замороженное суждение не ошибается в том смысле, в каком ошибается живое. Оно просто не пересматривается.*
Когда суждение, которое модель могла бы вынести сама, выносит за неё внешняя логика и замораживает, — агентность изъята. Это и есть паттерн: изъятие агентности. Инъекционный RAG, роутер на входе, одноходовый конвейер, шаблонный ответ — не четыре независимых технических решения, а четыре точки одного паттерна. И этот паттерн — не экзотика и не признак плохой инженерии. Он повсюду: в поиске, в маршрутизации, в памяти, в том, разрешено ли системе действовать. Спешить осуждать его не стоит, и лекарство подождёт — тем более что не всякое изъятие в нём нуждается. Нужна оптика, в которой знакомый продакшен вдруг читается как список чужих замороженных решений. Дискомфорт этого узнавания — рабочий: пока не увидишь паттерн целиком, обсуждать, что с ним делать, преждевременно.
Глава 1. Что такое агентность в ИИ-системе
Мы легко говорим, что один ассистент «умнее» другого. Но если выложить оба на стол и разобрать по частям, окажется, что части одинаковы: тот же класс модели, та же база, тот же способ доступа к ней. Разница, которую все чувствуют и которая через месяц решит судьбу обоих продуктов, в этой описи компонентов не значится. У неё пока нет имени.
Слово «агентность» напрашивается, но оно перегружено. Для одних это автономные системы, действующие без человека; для других — почти сознание; для третьих — модный ярлык на всём, что вызывает инструменты. Ни одно из этих значений не годится как рабочее: с ними нельзя спроектировать систему, потому что они описывают ауру, а не устройство. Прежде чем строить на понятии агентности, его нужно очистить до чего-то, что можно положить в основу решения.
Расчистка начинается с одной оси и одного определения. Ось: интеллект решения в системе живёт либо в статичной логике приложения, либо в модели — и почти никогда посередине для каждого отдельного решения. Определение: агентность — это мера, в которой суждения выносит модель, а не то, что застыло вокруг неё. Но и то и другое становится инструментом лишь после того, как различие увидено на конкретной системе: определение, пришедшее раньше разницы, запоминается как формулировка, а не как инструмент.
1.1. Интеллект системы: в приложении или в модели
Вернёмся к двум ассистентам поддержки и дадим им конкретную задачу. Пользователь пишет: «Обновил тариф вчера вечером, а лимиты на API остались старые — это нормально или что-то сломалось?». В базе знаний нет статьи с таким заголовком. Есть статья про то, как меняются тарифы, есть отдельная — про то, что изменения лимитов применяются в начале следующего расчётного периода, и есть третья — про типичные задержки синхронизации. Ответ живёт на стыке трёх статей, и ни одна из них по отдельности его не содержит.
Первый ассистент устроен так: запрос пользователя целиком уходит в поиск, поиск возвращает три самых похожих фрагмента, эти фрагменты подставляются в подсказку модели, модель пишет ответ. Запрос был про «лимиты остались старые», и поиск честно принёс самое похожее — статью про изменение лимитов. Модель получает этот фрагмент как данность и на его основании уверенно сообщает: лимиты меняются в начале следующего периода, всё в порядке, ждите. Ответ звучит гладко и почти наверняка неполон: он не различает штатную задержку и настоящий сбой, потому что в принесённой выборке этого различения нет, а спросить, поискать иначе или усомниться в выборке модель не может. Ей нечем: решение о том, что искать и что считать релевантным, уже принято — механизмом извлечения, до неё.
Стоит задержаться на том, чего именно лишена первая система. Не знаний — статья про сбои лежит в той же базе, до неё просто не дотянулись. Не сообразительности — модель одна и та же в обеих системах. Она лишена способности заметить, что отвечает не на тот вопрос. Ей отдали выборку, которую она не запрашивала, и она отвечает на вопрос, который эта выборка задаёт, а не тот, что задал пользователь. Проблема не в том, что система знает мало, а в том, что она не видит границ своего знания в этом конкретном случае, — и уверенность ответа от этого не страдает. Гладкость и правота здесь развязаны.
Второй ассистент устроен иначе. Поиск дан ему не как подкладка под ответ, а как инструмент, которым распоряжается модель. Она видит вопрос, замечает в нём две разные возможности — «штатная задержка» и «сбой», — и это её суждение, а не ветка, выбранная роутером. Она ищет по первой, получает статью про начало расчётного периода, ищет по второй, получает статью про задержки синхронизации, сопоставляет их с деталью «вчера вечером» и отвечает так, как ответил бы человек, знающий базу: скорее всего, это штатная задержка до начала следующего периода, и вот как проверить, что это не сбой. Тот же интерфейс, та же база, тот же класс модели. Разными их сделало одно: во втором случае суждение о том, что здесь вообще происходит и где это искать, вынесла модель.
Важно сразу уточнить масштаб этой оси. Она приложена не к системе целиком, а к каждому её решению по отдельности. Неверно спрашивать «агентна ли эта система» так, как спрашивают, на каком она написана языке: система — не точка на оси, а связка решений, и каждое лежит на своей точке. В одном и том же ассистенте решение «как переформулировать запрос к поиску» может быть отдано модели, а решение «отправлять ли исходящее письмо клиенту без подтверждения человека» — намеренно заморожено в приложении. Это не противоречие и не половинчатость. Это норма: у зрелой системы карта решений пёстрая, и её пестрота — не признак недоделанности, а результат отдельных выборов, каждый со своей причиной. Тумблера «свобода/контроль», делящего систему надвое одним щелчком, не существует; есть десятки мелких решений, и каждое лежит там, куда его положили.
Ось эта не про поддержку и не про поиск — она проступает везде, где приложение обёрнуто вокруг модели. Возьмём систему, извлекающую из входящих документов структурированные данные: контрагент, сумма, срок оплаты. В одном варианте разработчик заранее задаёт жёсткую схему полей, а модель лишь заполняет клетки — что считать «суммой» и «сроком», решено до неё, схемой. В другом модель сама разбирается, что в этом документе есть значимого, и предлагает структуру под конкретный документ. Первый вариант надёжно провалится на документе непредусмотренного вида — там, где значимое поле в схему не заложено, его просто некуда записать; второй с таким документом справится, но заплатит меньшей предсказуемостью формы ответа. Те же две стороны оси, тот же размен — на совсем другом материале. Ось универсальна именно потому, что описывает не предметную область, а место, где принято решение.
Разложим, что именно различает эти системы. Не качество модели — она одна и та же. Не полнота базы — статьи те же самые. Не интерфейс, не язык, не оформление. Различает их место, где вынесено решение. В первой системе решение «что здесь релевантно» вынесено заранее и снаружи — логикой приложения, которая всегда делает одно и то же: берёт запрос, отдаёт в поиск, подставляет верхние результаты. Во второй то же решение вынесено моделью в момент задачи, с учётом её конкретики. Это и есть ось, на которую ложится любая ИИ-система: интеллект системы — это то, где принимаются её содержательные решения: в статичной логике приложения или в модели.
Слово «статичная» здесь несущее. Логика приложения не глупа — она бывает весьма изощрённой. Но она вынесена один раз и не зависит от конкретного запроса: она сделает с двадцать первым вопросом ровно то же, что с первым, даже если двадцать первый требует другого. Модель, напротив, выносит решение всякий раз заново, глядя на то, что перед ней. Оба режима легитимны. Есть решения, которые правильно застывают в приложении: их незачем принимать заново каждый раз, и цена ошибки живой модели тут выше выгоды. Решение о том, что данные одного пользователя нельзя показать другому, правильно заморожено в коде — доверять его живому суждению модели на каждом запросе — не гибкость, а риск, и его застылость здесь — не долг, а защита. И есть решения, застывание которых обходится дорого, — как «что здесь релевантно» у первого ассистента. Вопрос не в том, какой режим лучше вообще. Вопрос в том, какое конкретное решение в каком режиме должно жить.
Есть простой способ определить, по какую сторону оси лежит конкретное решение: посмотреть, меняется ли оно, когда меняется вход. Замороженное решение к входу глухо — оно применяет одну и ту же процедуру к типовому входу и к небывалому. Решение, оставленное модели, вход слышит: на непохожем запросе оно может выйти иначе, потому что выносится под него. Отсюда и слово «интеллект» в названии оси — не как похвала и не как метафора, а почти буквально: интеллект решения тем выше, чем сильнее оно приспосабливается к конкретике задачи, а не воспроизводит заранее заданную форму. Приложение может быть сколь угодно изощрённым как программа и при этом нести нулевой интеллект решения — если это решение одно на все входы. Сложность кода и интеллект решения — разные величины, и их легко перепутать: система с тысячей правил маршрутизации выглядит умной, но каждое из правил заморожено, и на входе за пределами тысячи она беспомощна.
И вот здесь ось из наблюдения превращается в ось проектирования. Где живёт интеллект решения — не свойство, доставшееся системе от природы, и не следствие выбранной модели. Это сумма выборов её авторов, сделанных решение за решением, часто по инерции, часто не глядя. Когда команда первого ассистента писала «запрос → поиск → верхние результаты → модель», она не формулировала это как «отберём у модели суждение о релевантности». Она писала очевидный, стандартный, рекомендованный отовсюду пайплайн. Изъятие произошло не как злой умысел и даже не как решение — как форма по умолчанию. Стартовый шаблон, первый попавшийся пример из документации, готовый рецепт «как сделать RAG» — все они уже расставили решения по оси заранее, и расставили в одну сторону: суждение — приложению, текст — модели. Разработчик получает эту раскладку в наследство вместе с первой строкой кода и чаще всего не замечает, что она вообще была раскладкой, а не единственным способом. Но от того, что оно было незаметным, изъятие не перестало быть выбором. Кто-то расставил на этой оси каждое решение системы, и большинство систем стоят там, где стоят, не потому, что там их место, а потому, что туда их поставила привычка.





