Задача Добавить ИИ агенту функции анализа проектной документации
Добавить ИИ агенту функции анализа проектной документации
Есть архивы с проектной документацией и информацией о тендерах. Архивы могут весить сотни мегабайтов и даже гигабайты, внутри могут быть сотни и тысячи файлов различных форматов (txt, doc, docx, xls, xlsx, dwg и т.п.).
Надо разработать пайплайн гарантированного анализа таких архивов. Предварительные шаги:
- Создается задача на анализ архива
- Распаковка архива (rar, zip, 7z) и извлечение всех файлов. Каждый файл уходит в отдельную субзадачу на анализ.
- Каждый файл анализируется отдельным потоком отдельным агентом.
- Когда все файлы проагализированы и все подзадачи завершены, основной агент выполняет окончательный анализ содержимого.
Почему именно так? Дело в том, что если пытаться засунуть это все в один поток агента, всегда есть риск, что он в какой-то момент не выполнит какие-то действия или вовсе прекратит работу. Отдельные задачи позволяют, во-первых, более четко локализовать требования к выполнению и критерии оценки выполнения, а так же более четко контролировать что сделано, а что нет. И даже если часть задач завершилось с ошибками, главный агент может в итоге выполнить хотя бы частичный анализ и дать отчет с указанием, какие промежуточные задачи не были выполнены и почему. В нашей текущей потребности даже частичный анализ уже имеет ценность, потому что он дает хоть какое-то предварительное представление о пришедшей заявке и дает основания для принятия решения стоит ли ее изучать детальней, или даже не надо время на нее тратить.
Ворклоги
Ха, не хило так споткнулись об загрузку больших файлов. То есть проблема не в том, как загрузить большой файл (это бы решили бы быстро), а в том, что как выяснилось, у нас не хендлилась сама ошибка при загрузке файла, превышающего разрешенный лимит. Загрузка просто висела бесконечно. В итоге победили, но сколько раз и как фейлил опус в процессе этого, понятнуло прям на отдельную хорошую статью. Опубликую ее чуть позже, так как хочется все-таки первым топиком вступительное слово опубликовать, а сейчас хотя бы разобрались и поправили.
Вот классический пример того, когда надо создавать под конкретную задачу отдельный механизм, если хочется чтобы задача точно была выполнена как планировалось, с минимальным риском недовыполнения и с максимальной надежностью по ответу.
Что сейчас происходит: агенту дан путь к архиву и сказано распаковать его и сохранить в отдельный ворклог полный перечень файлов. Уточню, что в архиве более сотни файлов. Для распаковки агенту дана специальная тулза, так что он не может не получить полный список извлеченных файлов. Тут у агента дело остается за малым - просто записать этот список в новый ворклог задачи. Но это в целом не мало. Так что он ограничился вот такой записью:
Jun 28, 2026, 5:13:46 AM Архив успешно распакован. Состав файлов:
- EP_24103_TP_EL_Seletuskiri est.pdf - EP_24103_TP_EL_Seletuskiri рус.pdf И различные чертежи (.dwg, .pdf) по проекту ГГС-4 (освещение, схемы, спецификации материалов). Полный список файлов сохранен в системе доступных путей.
Как-то так :-)
В чем тут проблема? Проблема в том, что если после этого сказать "Пересмотри наиболее важные документы", то откуда он возьмет список файлов? Опять архив распаковывать? В этом-то и был смысл, что сначала можно было распаковать архив и знать список файлов в нем, а потом отдельными шагами читать отдельные документы и сохранять в ворклоги полезную информацию. Это бы позволило нелинейно и по мере необходимости изучать отдельные элементы проектной документации. При этом над одной задачей могли бы работать и люди, и агенты. Но для такой работы требуется четкое следование инструкциям, а как известно и как показывает практика - агенты далеко не всегда следуют инструкциям в полной мере, тем более, когда объем информации большой.
В таких ситуациях решением могут быть только дополнительные тулзы и обвязки.
Отдельный крайне занятный кейс вот с такой инструкцией:
Прочитай файлы
storage/unpack/cmqv7yb0i00018hzwx2uttxcb/1782515133778-VKG_Ümberpuist hoone.rar/RUS_25053_v03_PP_ГГС-4_Золоудаление/RUS_25053_v03_PP_ГГС-4_Золоудаление/Том I_25053_PP_ГГС-4_AA-AS-AR-EK-TK/25053_PP_ГГС-4_строительство/25053_PP_ГГС-4_строительство/25053_PP_-4_áâந⥫ìá⢮/25053_PP_AA/25053_PP_AA-0-01_â¨âã«ìë© «¨áâ.pdf
storage/unpack/cmqv7yb0i00018hzwx2uttxcb/1782515133778-VKG_Ümberpuist hoone.rar/RUS_25053_v03_PP_ГГС-4_Золоудаление/RUS_25053_v03_PP_ГГС-4_Золоудаление/Том I_25053_PP_ГГС-4_AA-AS-AR-EK-TK/25053_PP_ГГС-4_строительство/25053_PP_ГГС-4_строительство/25053_PP_-4_áâந⥫ìá⢮/25053_PP_AA/25053_PP_AA-3-01_¯®ïá¨â¥«ì ï.pdf
Результаты запиши в ворклоги.
Что тут интересно в первую очередь? Это вот эти длинные пути с различными краказябрами. Напомню, что ЛЛМ как бы не имеют в себе механик переменных (это я еще только планирую в своей реализации сделать). По большому счету при формировании запросов ЛЛМ должна байт-в-байт повторить эти пути при передаче их параметрами в тулзы. И над отметить, что Gemini 2.5 Flash отлично с этим справился.
Далее интересные и неожиданные предположения имею сделать на основе ризонинга моделей при чтении файлов. Я сразу процитирую оба запроса.
[
{
"query": {
"reasoning": "Извлекаю текст из титульного листа для идентификации документа.",
"query": "mutation {\n parsePdfOpenRouter(input: {\n filePath: \"storage/unpack/cmqv7yb0i00018hzwx2uttxcb/1782515133778-VKG_Ümberpuist hoone.rar/RUS_25053_v03_PP_ГГС-4_Золоудаление/RUS_25053_v03_PP_ГГС-4_Золоудаление/Том I_25053_PP_ГГС-4_AA-AS-AR-EK-TK/25053_PP_ГГС-4_строительство/25053_PP_ГГС-4_строительство/25053_PP_-4_áâந⥫ìá⢮/25053_PP_AA/25053_PP_AA-0-01_â¨âã«ìë© «¨áâ.pdf\"\n pages: [1]\n }) {\n pages {\n text\n }\n }\n}",
"variables": "{}"
}
}
]
[
{
"query": {
"reasoning": "Извлекаю текст из пояснительной записки. Посмотрим, сколько страниц в документе, и прочитаем их (если их немного). Первый запрос показал 1 страницу, но лучше убедиться.",
"query": "mutation {\n parsePdfOpenRouter(input: {\n filePath: \"storage/unpack/cmqv7yb0i00018hzwx2uttxcb/1782515133778-VKG_Ümberpuist hoone.rar/RUS_25053_v03_PP_ГГС-4_Золоудаление/RUS_25053_v03_PP_ГГС-4_Золоудаление/Том I_25053_PP_ГГС-4_AA-AS-AR-EK-TK/25053_PP_ГГС-4_строительство/25053_PP_ГГС-4_строительство/25053_PP_-4_áâந⥫ìá⢮/25053_PP_AA/25053_PP_AA-3-01_¯®ïá¨â¥«ì ï.pdf\"\n }) {\n totalPages\n pages {\n pageNumber\n text\n }\n }\n}",
"variables": "{}"
}
}
]
Что тут интересного? В целом, он следовал логике, заложенной в скиле чтения документов, вот цитата:
Стратегия чтения документов
Рекомендуется сначала читать документ поверхностно — только первую страницу с запросом totalPages:
mutation {
parsePdfOpenRouter(input: {
filePath: "storage/unpack/tender-123/document.pdf"
pages: [1]
}) {
totalPages
pages {
text
}
}
}
Это позволяет:
- Узнать общее количество страниц в документе
- Понять содержание и релевантность документа
- Принять решение — нужно ли читать весь документ
Если документ важен и требуется полное содержание — читаем все страницы (без параметра pages).
А что же сделал наш агент? Он почти все правильно сделал, но ошибся в главном - кол-во страниц запросил на первом документе, а прочитал все уже на другом документе :-) При чем во втором запросе он даже уточнил: Первый запрос показал 1 страницу, но лучше убедиться.
При чем в первом запросе он даже не мог знать сколько на самом деле там страниц, потому что он только указал получить первую страницу, передав pages: [1], но в тело ответа он не включил параметр totalPages :-) Так что ответ он получил без информации сколько там страниц.
А вот во втором запросе он сделал иначе - указал totalPages (и эту информацию он получил), но не указал pages: [1] :-)
В общем, очень интересный и немного комичный пример того, как агент может логически отклониться от переданных ему инструкций, но в отдельных случаях с учетом требований к надежности выполнения задачи, результат может оказаться совсем не комичным.