Обновить

Как я строил ИИ-обвязку рабочего места аналитика в IT-компании

Уровень сложностиСредний
Время на прочтение26 мин
Охват и читатели6.5K
Всего голосов 2: ↑1 и ↓10
Комментарии15

Комментарии 15

Как вы работаете с противоречиями требований? Особенно с мягкими и плохо обследованными?

Как вы боретесь с архитектурным дрифтом и в частности попаданию чуждых терминов и абстракций не из языка предметной области? LLM склонно искажать терминологию, описание и абстракции даже если они изначально были (а если не было то ещё хуже)

Противоречия. Я это понимаю так: от заказчика или от регулятора приходят противоречивые требования.

Поверхностный анализ их не показывает: каждое требование по отдельности выглядит нормально, конфликт виден только когда сводишь их вместе.

У меня под это отдельная роль, которая запускается при анализе исходных данных. Задача агента — найти противоречия, показать источники, показать сценарий, в котором они сталкиваются, и сформулировать вопрос, который идет в основу запроса на уточнение.

На практике у меня два режима:

  1. По умолчанию агент останавливает подготовку спорного фрагмента;

  2. Если пользователь обвязки просит продолжить — в артефакте будут пометки о неопределённости, с ссылкамина реестр открытых вопросов.

После ответов на открытые вопросы, артефакт надо пересобрать.

Отдельно — самопроверка. У агента есть инструкция пройти по своему результату и проверить: у каждого утверждения есть источник. Утверждение без источника — это кандидат на внимательную проверку.

Отдельный случай — мягкое требование без критерия проверки: «должно работать быстро», «интерфейс должен быть удобным». Противоречия в тексте нет, но критериев работоспособности или удобства тоже нет. Надо учить агента на примерах замечать такие неопределённости.

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

Термины. В моей практике на одной из задач помогло снижение температуры до 0,2. Но это настройка вариативности генерации, а не гарантия соблюдения инструкций или сохранения терминологии.

Соответствие предметной области лежит в другой плоскости.

Основная опора — роли, навыки, инструкции, шаблоны и память, а при необходимости — ещё и глоссарий. Роли, навыки, инструкции и шаблоны задают агенту порядок работы. Сведения о предметной области он должен брать из исходных документов и согласованных решений; память и глоссарий помогают сохранять и использовать это знание. Его приходится учить доменной области, а не ждать, что он уже специалист.

Ближе всего сравнение со студентом на практике: память хорошая, читает быстро, схватывает на лету — но ошибается там, где у него нет опыта. Конечно, утрирую, но доля правды тут есть. И как студенту нужен наставник, который проверяет работу, агенту нужен человек, который принимает результат. Разбор ошибок и накопление проверенных примеров нужны, чтобы одни и те же ошибки не повторялись.

Для накопления знаний о доменной области у меня используется многослойная память. Целиком она не загружается: для текущей задачи подбираются только релевантные части. Логи сессии разбирает рефлектор и решает, что и в какой слой сохранять.

Итоги подводятся за день, неделю, месяц, год. Раз в неделю, месяц и год проводится уборка памяти: устаревшее и ошибочное нельзя бесконтрольно накапливать, иначе деградация. Нужна отдельная роль — владелец памяти и рефлексии, со своими инструкциями.

При подгрузке опыта обязательна проверка на коллизии. Нерелевантное текущей задаче не подгружается. Устаревшие или ошибочные записи разбираются и очищаются с согласованием у пользователя.

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

Дрейф. Здесь важно не только сохранить названия, но и не изменить незаметно смысл сущностей и связи между ними. Для меня улучшение отличается от дрейфа тем, что изменение рассмотрено и принято, а не появилось само по дороге.

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

За изменения самой обвязки у меня отвечает владелец стандарта: через него идут улучшения, обновления и проверка на противоречия. При этом изменения смысла требований и доменных сущностей должны быть согласованы с ответственным за них специалистом, а не возникать по инициативе агента незаметно для пользователя.

Про искажения. Похожий опыт был у меня с недорогими моделями. С флагманскими проблемы другого рода. Я хотел бы сравнить: напишите пожалйста, на какой модели и в каком контексте это проявлялось у вас?

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

Пробовал часть перечисленного.

Матрица противоречий/конфликтов есть. LLM её отслеживает. Но она быстро вырастает до нехорошего размера.

Поэтому периодически приходится переформулировать ментальную модель - минимальную удобную модель/набор абстракций для описания предметной области, анализа и синтеза. Чтобы всё помещалось в голове. А матрицу противоречий проверять и очищать.

от заказчика или от регулятора приходят противоречивые требования

Они не приходят. Они тайно живут и скрываются. Как только их становится видно каждый дурак© их сбалансирует и исправит

Не уверен, что правильно понимаю. Могу помочь, если дадите больше деталей (можно в личку телеграм Nikolai_999)

В тех процесса, что я строю, генерация артефактов связано со статическими исходными данными. Если обновляется регуляторка или новые вводные от заказчика, предыдущие коллизии не имеют значения и ценности, а держать их в памяти просто нет смысла. Выводы необходимо пересобрать заново. Если я правильно понимаю ваш тезис про "матрицу противоречий" то вы делаете тоже самое.

По вашим текстам не понятно почему у вас архитектурный дрифт. Разбухание контекста надо решать на уровне архитектуры вначале проекта. Рефакторинг и миграции те ещё танцы с бубнами.

Задача: обновление справочников ОКОГУ в распределенной системе. Бывают случаи, когда ведомство аннулировано или разделено на 2. Обновление может привести к невалидности уже существующих записей "ноды" или записей, принимаемых от другой "ноды". Вопрос что делать в примерно десятке случаев коллизий? Причём, эти случаи сразу не видны, они находятся в процессе доработки или что хуже при попытке миграции старых данных. Накапливается непредусмотренная изначально боль. И вот приходится создать абстракции "коллизия", чтобы снизить число частных случаев с десятка до 2-3 и проверить полноту что рассмотрены все случаи.

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

Так вот, LLM

  1. Не заметит накопленную боль

  2. Не создаст новую удобную абстракцию

  3. Расконцентрирует, собьёт внимание человека

  4. Не выберет лучший вариант реакции

  5. Если человек создаст варианты абстракции, то LLM видя лишь часть картины собьёт фокус - создаст архитектурный дрифт.

Упрощённо говоря, так.

Последовательно по шагам:

  1. Накопление боли и противоречий. LLM не заметит и отвлечёт человека инфо мусором.

  2. Попытка анализа.. LLM заметит лишь часть. Bias. При повторении - архитектурный дрифт.

  3. Потеря аналитических способностей человека. Коллапс ментальной модели: старая явно не годная, а новую создать уже труднее чем при постоянном архитектурном контроле и проектировании "руками".

Паттерн вполне повторяемый от проекта к проекту

Спасибо — теперь картина сложилась. И сразу признаю: я недопонимал ваш случай.

У меня артефакт — проекция источника: обновилась регуляторка, старая версия вытеснена, выводы пересобрались заново.

У вас обновление справочника не вытесняет записи — оно меняет их валидность, а записи уже несут реальные события. Поэтому боль накапливается: каждая версия справочника добавляет новые комбинации «запись × версия × момент», и каждую нужно согласовать отдельно.

Мой тезис «старые коллизии теряют ценность» здесь не работает — забираю назад.

Понимаю, что графовые связи и слепок «на момент» у вас, скорее всего, есть. Они отвечают на вопрос «почему запись была валидна тогда». Но не на вопрос «что с ней делать сейчас, когда вышел новый релиз ».

Плюс распределённость: пока одна нода уже на новой версии, а другая все еще на предыдущей, они обмениваются записями, и запись, валидная на одной, может быть невалидной на другой. Это не решается метаданными — это решается политикой, а политика требует выбора.

С вашими выводами согласен — но для режима «LLM вместо аналитика». Не заметить боль, не создать абстракцию, не выбрать реакцию — это всё про агента-автономиста.

Я строю другой режим — «LLM под аналитиком»: порог «пора обобщать», сама абстракция и выбор реакции остаются человеку. Агенту — переборная работа вокруг:

  • кластеризовать записанные инциденты,

  • генерировать кандидатов классификации,

  • проверять полноту покрытия (каждый из десяти случаев ложится ровно в один класс; что не ложится — кандидат в незамеченные),

  • раскладывать варианты реакции в таблицу со стоимостью и риском.

Выбор всё равно за человеком.

Моей версией, что вам попробовать, поделюсь в следующем сообщении.

Старые коллизии теряют ценность

Особенность распределенных систем документооборота что важно состояние на момент выверки (конечный момент). В середине процесса могут быть грязные данные, отставание отдельных нод и прочие фокусы "согласованности в конечном итоге". И да, теоретически, старые коллизии могут самозалечиваться. Но чаще наоборот: в середине жизненного цикла они не заметны а на выверке вылезают

Да, не самый распространенный кейс.

В остальном всё верно.

Что нужно сделать

Нужно разобрать коллизии, которые возникают при обновлении справочника ОКОГУ в распределённой системе.

Сначала — собрать и не потерять инциденты, включая те, что всплывают при доработке и миграции. Отделить наблюдаемое поведение от причины. Проверить причинные гипотезы различающими проверками.

Затем — сгруппировать частные случаи в общее правило через рабочее понятие «коллизия»: снижать число специальных правил, но не объявлять сжатие выполненным, пока случаи не следуют из общих условий или не отмечены как исключения. Полноту проверять относительно явно заявленной области, а не «вообще всех случаев».

Далее — выбрать реакции через контракты и измерения. Измерять только то, что меняет выбор.

В конце — принять ограниченный прототип.

Для сбора фактов, гипотез и кандидатной классификации нужны подтверждённые источники и назначенные роли сбора и анализа. Для принятия правил и реакций нужен владелец предметной политики. Без него правила остаются кандидатными.

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

Выглядит верно. Главное, вовремя это обнаружить.

Но LLM создает иллюзию завершенности.

Как Киса Воробьянинов "Geben Sie mir bitte.. а, ну это я помню". И так ни разу до конца по немецки фразу и не завершил, не проверил свои знания.

Подскажите, сталкивались ли вы с проблемой векторного поиска. узостью конуса при однородности корпуса? ( Все записи в векторной базе (LanceDB) стилистически и лексически очень однородны (рефлексия от первого лица на русском языке). Из-за этого возникла проблема узкого конуса (анизотропии) — косинусное расстояние между совершенно разными по смыслу воспоминаниями почти одинаковое, поиск выдает шум ) Хочу внедрить архитектуру памяти V2 с новым слоем фильтрации/активации. Как лучше разжать это векторное пространство на русском языке: менять модель эмбеддингов, прикручивать поверх жесткий реранкер (Reranker), уходить в гибридный поиск или менять саму структуру чанков?

С нерелевантной выдачей памяти сталкивался, но расстояния между эмбеддингами не исследовал и именно анизотропию как причину у себя не подтверждал. Векторную БД использую для памяти агентов и индексации кода, reranker у меня тоже есть. Но совпадение симптома ещё не означает совпадения причины, поэтому мой опыт - не готовый рецепт лечения «узкого конуса».

Я бы начал не с попытки разжать пространство, а с проверки, на каком этапе теряется смысл: при подготовке записи, формировании запроса, отборе кандидатов или их ранжировании. Близкие расстояния - наблюдение, но сами по себе ещё не объясняют причину шума.

Сначала - чанкование. Его я изучал на проекте, где требовалось собрать источник истины из нормативной документации и законодательства. До продакшна проект не дошёл, поэтому ссылаться на него как на доказанный результат не буду.

Я бы посмотрел, как именно нарезана память. Чанк должен быть самостоятельным смысловым фрагментом: решение вместе с условиями его применения, ошибка вместе с причиной и исправлением, факт вместе с тем, к чему он относится. Если просто резать текст через одинаковое число символов или токенов, можно оставить проблему в одном куске, решение в другом, а исключение - в третьем. Тогда поиск получает обрывки, и даже удачно выбранная модель не восстанавливает автоматически все потерянные связи.

Следующий вопрос - как представить этот фрагмент для поиска. В моей схеме у записи должен быть блок наподобие description: короткое описание того, о чём она и при каких обстоятельствах полезна. Это не обязательно сокращённый пересказ. Для поиска может быть важнее явно назвать проблему, условия применимости и принятое решение. По такому описанию можно искать и предварительно оценивать релевантность кандидата, а исходный фрагмент сохранять для проверки и использования.

Для рефлексии это особенно важно: «сегодня я сделал, понял, попробовал» описывает процесс, но не обязательно помогает найти конкретное решение в будущем. При этом описание не исправляет неудачную нарезку. Если в исходном куске смешаны разные темы или оборвана важная оговорка, краткий пересказ может закрепить ошибку. Поэтому сначала стоит проверить границы смысловых фрагментов, а уже потом готовить их поисковые описания.

Кроме содержания записи, важно понимать, к чему она относится. В моей схеме память - не общий массив фрагментов: записи должны быть привязаны к проектам, людям, темам и другим значимым сущностям. Одна запись может относиться сразу к нескольким из них (графовые связи). Эти привязки помогают определить область поиска, а не выбирать из всей памяти только по близости векторов.

Между записями можно формировать связи уже при сохранении: решение относится к проекту, уточняет прежнее решение, возникло из определённой ошибки. Тогда найденный фрагмент становится точкой входа в связанный контекст, а не изолированным куском текста. Но и эти связи надо проверять: если агент начнёт придумывать их по ассоциации, появится ещё один источник шума.

Если краткого описания (description) недостаточно или оно не подходит, можно добавить поисковые теги. Но тогда возникает следующий вопрос: кто и по каким правилам их формирует? Если агент каждый раз придумывает новые обозначения для одного понятия, мы просто переносим источник шума из текста в метаданные. И описания, и теги нужно сверять с исходной записью, иначе поиск будет хорошо находить неверный пересказ.

Повторюсь, это моя логика организации памяти, а не подтверждённое лечение анизотропии. Поможет ли она в вашем случае, нужно проверять на реальных запросах.

Запись и запрос должны идти навстречу друг другу. Я бы обязательно проверил гипотезу, что шум возникает не только из-за корпуса, но и из-за того, как агент формулирует запрос. Передаёт ли он конкретную проблему и контекст или ищет что-то общее вроде «полезный опыт работы с требованиями»?

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

Отдельно сравнил бы поиск через приближённый индекс с точным поиском по тем же векторам и с той же метрикой. Если выдача заметно различается, сначала стоит проверить настройки индекса. Если оба варианта дают шум, искать причину нужно дальше - в запросе, представлении записей или эмбеддингах.

Из перечисленных вариантов я бы выбирал по месту сбоя:

  • Нужный смысл разорван между кусками или смешан с посторонними темами: пересмотреть чанкование, сохранив условия, исключения и связь с исходником.

  • Смысловой фрагмент подготовлен корректно, но не попадает в кандидаты даже при точном поиске: проверять запрос, поисковое представление записи и модель эмбеддингов.

  • Попадает, но оказывается ниже шума: проверять семантический reranker. Он может переупорядочить кандидатов, но не вернуть то, чего среди них нет.

  • Важны точные термины, названия и идентификаторы: пробовать гибридный поиск.

  • Смешиваются разные проекты, типы опыта или устаревшие решения: вводить фильтрацию по области применимости и статусу. Это не разожмёт пространство, но ограничит поиск подходящими записями.

Все эти изменения я бы проверял по одному на небольшом наборе реальных запросов с заранее известными подходящими записями. Добавить туда стоит и вопросы, на которые в памяти нет ответа: важно не только найти нужное, но и не подгрузить шум вместо него.

Для предметного сравнения полезно понять, как у вас устроена запись в память: рефлексии сразу массово отправляются на векторизацию или перед этим выделяются смысловые фрагменты, описания и метаданные? Есть ли возможность менять этот этап, или вы работаете с уже готовой базой?

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

В истории с Confluence я бы записывал при каждой попытке публикации ревизию инструкций, которые агент для неё загрузил. Если скрипт публикации обновляется отдельно, полезна и его версия. Когда код пытается прочитать токен из переменной окружения, пусть он добавляет в ту же запись, увидел ли переменную (только да или нет). При сбое эту строку можно приложить к обращению, а после отката сверить загруженную ревизию с той, на которую откатывались.

Версионность реализована. При разборе багов всегда учитывается. Я настроил телеграм-бота, который запрашивал логи у коллег, давай промпт, указывая какой роли в какую сессию его отдать, чтоб получить выводы до передачи проблемы в беклог.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации