Windows XP, VB6 і AutoCAD. Як ми підключили AI-агентів до системи, якій близько 20 років
Привіт, мене звати Романів Роман, я Lead Engineer у компанії SoftServe. Працюю в R&D команді у напрямку в Agentic AI, ми фокусуємось на побудові рішень із використанням Agentic AI підходу та допомагаємо клієнтам модернізувати legacy проекти, адаптуючи їх до сучасних технологій, та підбирати практичний сценарій застосування Gen AI.
За останні декілька років мені доводилось працювати з різними типами систем: від класичних бекенд рішень до більш сучасних систем з використанням хмарної інфраструктури та AI компонентів. Але був один проєкт, який виглядав наче «привіт» з іншої епохи. Це типовий legacy проект, який потрібно було модернізувати, але тут ми стикнулись з аплікацією, яка історично була побудована на VB6/VBA, тісно інтегрована з AutoCAD і працювала на WindowsXP. Вона була критично важливою для клієнта і використовувалася щодня — без неї бізнес зупинився б.
Класичний підхід виглядав дуже дорогим і довгим, а головне — ризикованим. Тому ми вирішили піти шляхом використання Agentic AI. Зараз чи не кожен хоче так чи інакше підключити АІ до свого проєкту, але на практиці це не завжди дає цінність. Тож в цій статті я поділюся практичним досвідом, як ми прийшли до такого рішення, як готували legacy-код для роботи з агентами, чому обрали для цього OpenHands, які підходи спрацювали, а які — ні, і з якими челенджами довелося зіткнутись під час міграції.
Важливо: Agentic AI і LLM розвиваються дуже швидко. На момент, коли ви читаєте статтю, можуть вже бути новіші моделі, оркестратори й підходи, про які йдеться нижче. Але, на мою думку, застосовані принципи лишаються актуальними, навіть якщо конкретні інструменти ви заміните на інші.
З чого все почалося
На перший погляд задача виглядала не дуже складною. Потрібно було legacy-систему перенести на сучасний стек і зробити з десктопної аплікації вебзастосунок. Але буквально через кілька днів роботи стало зрозуміло, що це не такий простий випадок.
У початковому коді ми мали чотири UserForm, два модулі (і допоміжні тестові сценарії), лише в експорті UserForm1.frm було понад 18 тисяч рядків VBA-коду в одному файлі (усього у формах і модулях було близько 20 тисяч). На головній формі були сотні UI-елементів (вкладені MultiPage, десятки вкладок для панелей), понад 160 обробників подій.
Також було понад десять Excel-шаблонів (.xlsm, .xlsx), стандарти панелей і окремі VBA-макроси всередині, без яких цифри, матеріали й малюнки не сходилися. Генерація креслень використовувала бібліотеку DWG-шаблонів і роботу через AutoCAD. Плюс Windows XP у VM, бо частину залежностей, які в нас були (Office XP Web Components, старі COM-компоненти), інакше просто неможливо було відтворити й запустити аплікацію для валідації та тестування.
Архітектурно це виглядало так: основна логіка на VBA/VB6 працювала всередині AutoCAD; UI — форми, які заповнювали Excel через старі Office-компоненти; креслення генерувалися через COM-об’єкти AutoCAD. Тобто вся система була намертво прибита до desktop-середовища і технологій, які сьогодні майже ніхто не використовує.
Розповім на прикладі. Є кнопка збереження роботи. На UI — це просто кнопка, під капотом — цілий ланцюжок, куди входить перевірка типу замовлення, обчислення імені тимчасової папки з номера роботи (буквально вирізанням підрядка), створення структури каталогів, запуск кількох Excel-макросів, які заповнюють аркуші, рахують матеріали й роботи, генерація текстових документів, а паралельно ще й створення DWG-файлів через AutoCAD. Жоден шматок цієї логіки не жив в одному місці, він був розкиданий між обробником кнопки, глобальними змінними форми, макросами в Excel і COM-викликами. У вебі це не переноситься «як є», бо старі Office-компоненти на клієнті не працюють, AutoCAD у браузері — тим паче.
Тому ми відразу зафіксували цільову архітектуру як заміну кожної desktop-залежності окремим шаром. Грубо кажучи, VB/VBA з формами в AutoCAD ми переписували на C# з UI на Blazor. Excel-макроси й трансформації перенесли на серверну обробку, лишивши самі файли-розрахунки незмінними, щоб формули працювали як раніше. Генерацію креслень винесли в окремий Python-сервіс, а перегляд креслень у браузері реалізували через web viewer. Ключова ідея полягала в тому, щоб не просто емулювати AutoCAD у браузері, а свідомо розрізати моноліт на веб-UI плюс кілька мікросервісів.
Саме на цьому етапі стало ясно, що класичне «сісти й переписати руками» або з’їсть колосальну кількість часу, або породить гору помилок. Саме в цей момент ми вперше серйозно подивилися в бік агентів.
Як ми прийшли до рішення з АІ-агентами
Ми не починали з думки «а давайте використаємо AI». Це рішення скоріше було продиктоване певними обмеженнями.
Обсяг коду був великий, логіка — складною, а люди, які писали цю систему і могли б пояснити, як це працює, вже давно змінили місце роботи. До того ж багато речей було важко інтерпретувати без контексту UI. Агенту бракувало не лише коду, а й контексту.
Тому ми почали експериментувати з Agentic підходом, де AI працює не як одноразовий генератор коду, а як частина довшого процесу.
Роль агента ми сформулювали максимально приземлено. Він мав допомагати з набором конкретних інженерних задач у межах узгодженої архітектури, наприклад:
- зняти «знімок» legacy у вигляді Markdown-опису форм і залежностей;
- підготувати мапінг старого UI на нові компоненти;
- переносити логіку маленькими шматками;
- згенерувати каркас мікросервісів;
- допомогти з тестами й логуванням там, де є з чим звіряти.
Відповідальність за результат завжди лишалася на команді. Ми задавали архітектуру, обмежували обсяг кожної сесії, читали diff і вирішували, де можна спростити, а де ні.
Першим ми спробували найпростіший варіант — звичайний чат з LLM, де треба було скопіювати фрагмент VBA, попросити переписати його на C# і подивитися, що вийде. Але дуже швидко ми побачили системні обмеження такого підходу.
По-перше, не вміщувався контекст. Тільки головний файл форми — це десятки тисяч рядків. Навіть якщо різати його на частини по
По-друге, відповідь виглядала правдоподібно, але не відповідала домену. Наприклад, для сценарію збереження чат міг згенерувати акуратний C#-метод, який створює папку й записує файл. Але в legacy-системі цей сценарій включав значно більше кроків: розрахунки, перевірки, запуск макросів, формування документів і підготовку файлів для креслень. У результаті чат фактично зрізав більшу частину поведінки, бо не мав повного графа викликів.
По-третє, немає стану між кроками. Сьогодні модель перенесла один обробник, а завтра інший, і вже на ходу перейменовує поля моделі або вигадує новий шар сервісів. Без зовнішньої пам’яті кожну сесію доводилося починати майже з нуля.
І по-четверте — і це головне — не було зворотного зв’язку з реальністю. Чат не запускав збірку, не відкривав Excel, не порівнював результат макроса з еталоном на віртуалці. Тому помилки, які виглядали дрібними в коді, на практиці могли повністю змінювати поведінку сценарію.
Чому важлива не сама модель, а організація роботи
Перелом стався не завдяки кращій моделі, а через підхід до організації роботи. Було кілька експериментів, після яких стало ясно, що агентний цикл має сенс.
- Перший — подати UI моделі як текст. Ми зібрали control map (дерево форми + JSON-властивості елементів) і дали агенту задачу, описавши структуру інтерфейсу та зв’язки між елементами. Вперше модель стабільно змогла згенерувати мапінг на Blazor-модель без вигаданих полів, бо контекст був структурований, а не представлений у вигляді сирих файлів.
- Другий — документація перед кодом. Ми змусили агента спочатку підготувати великий .md-файл з описом форм, шаблонів, структури даних і файлів. Наступні задачі вже посилались на цей документ, а не на пам’ять чату.
- Третій — вузька задача плюс таск-файл. Ми обмежили scope до одного невеликого сценарію, завели чекліст у Markdown і після кожної сесії оновлювали статус. Агент міг читати репозиторій, змінювати кілька файлів і повертатися до тієї ж задачі наступного дня, використовуючи той самий task-файл. Саме тут ми відчули різницю між чатом і оркестрованим процесом.
Саме після цього ми формально перейшли до Agentic-підходу. LLM працювала як учасник довшого циклу з інструментами (редактор, термінал, інколи браузер для роботи з документацією), пам’яттю у файлах і людиною, яка тримала межі задачі.
Спершу — тести, а не код
Класичний TDD рідко працює з legacy без специфікації. У нашому випадку це був радше test-first підхід, який спирався на поведінку наявної системи: спочатку зафіксувати, що саме має відбутися, а вже потім писати або генерувати агентом код, який це відтворює.
Спочатку інженер проганяє сценарій на віртуалці у старій системі й зберігає результат: конкретні цифри, імена файлів, фрагменти згенерованих документів. Це і є точка відліку, а не припущення моделі. Далі ці результати перетворюються на тест-кейси: фіксований набір вхідних даних (номер роботи, тип замовлення, виконавець) і очікуваний результат. Для чистих формул — звичайні unit-тести з прикладами «вхід → вихід». Для Excel, макросів і креслень — інтеграційні перевірки з порівнянням очікуваного й фактичного результатів. І вже тоді агент отримує не абстрактне «перепиши збереження роботи», а конкретне завдання: зробити так, щоб проходили ось ці тести.
Навіщо це? Бо без тестів агент вважає задачу виконаною, щойно проєкт зібрався. Тести додають об’єктивний критерій. Класичний приклад: тест на вартість матеріалів очікує умовні 227, а реалізація повертає 2. Код може виглядати цілком логічним, але тест він не проходить. Саме така різниця між очікуваним і фактичним результатом і ставала завданням для наступної сесії агента.
Чесно кажучи, покрити юніт-тестами все на 100% не вдалося. Найскладніші частини — Excel-макроси, генерація креслень, візуальна перевірка результату — залишилися на «золотих файлах», віртуалці й viewer’і. Але саме test-first підхід не дав архітектурі та назвам полів поступово «роз’їхатися» між сесіями.
Чому обрали локальний OpenHands
Спочатку ми дивились у сторону cloud-рішень, таких як Devin або OpenHands у хмарі. Але дуже швидко вперлись у вартість. Будь-яка складніша операція, як наприклад аналіз файлів, створення PR, ітерації з кодом, — коштує грошей. І при роботі з масштабним legacy кодом ці витрати ростуть дуже швидко.
Ми подивились, що ми платимо не лише за запит до GPT, а за сесію агента в цілому. У рахунок потрапляє:
- Повторне читання великих файлів. На кожній ітерації агент знову підтягував у контекст великі VBA-файли, описи Excel-логіки, попередні зміни й diff. Одна задача на кшталт «зрозумій форму і запропонуй мапінг» легко перетворювалась на багато циклів: read → plan → edit → error → read again.
- Дорогі моделі за замовчуванням. Часто стоїть топова модель, в якої якість вища, але повільніше і дорожче за 1M токенів, для механічних задач (таблиця мапінгу, Markdown-док) це overkill
- Операції платформи. Аналіз репозиторію, автоматичні PR, інколи обмеження на розмір workspace або час сесії. Для legacy, де поруч лежать `.xlsm`, експорти з VM і великі бінарники, це або коштує додаткових коштів, або вимагає обхідних шляхів.
Тому ми перейшли на локальний запуск. Це дало нам значно більше контролю. Ми самі керуємо вибором моделей, оптимізуємо витрати й не залежимо від обмежень платформи. У локальному варіанті лишається фактично один прямий рахунок і токени обраної моделі через власний API-ключ. Коміти, PR і повторні прогони збірки кошти вже не з’їдають.
Окремо додам про моделі. Ми свідомо не тримали одну модель на весь проєкт, а перемикали їх під тип задачі. Для документації й табличних мапінгів брали швидші й дешевші моделі — якості цілком вистачало. Для складної бізнес-логіки (те саме збереження роботи з гілками й макросами) вмикали сильніші — вони краще тримають зв’язки між кроками, хоча й повільніші, і без перевірки на віртуалці все одно не обійтися.
Логіка проста: одна сесія з великим контекстом на топовій моделі могла крутитися десятки хвилин і коштувати як кілька днів роботи на дешевшій. Тож вибір моделі для нас — теж інструмент оптимізації, як і обмеження обсягу в таск-файлі.
Найбільший виклик — підготувати систему до агентів
А тепер найнеочікуваніше: найскладнішим етапом виявилася не сама міграція, а підготовка legacy до того, щоб з ним взагалі могли працювати агенти.
VBA-застосунки побудовані навколо event-driven UI, де форма — центральний елемент. І тут виникає проблема. Опис однієї форми в репозиторії фактично складається з двох файлів з однаковою назвою, але різними розширеннями. Один — текстовий експорт: оголошення контролів, частина властивостей, посилання на код обробників. Його можна читати як звичайний текст.
Другий — супутній бінарний ресурс тієї самої форми. Це не ще один файл із кодом, а двійкові дані, які середовище зберігає окремо: іконки кнопок, картинки на формі, інколи дані для ActiveX-елементів та інші серіалізовані фрагменти розмітки. У редакторі форм усе це відкривається як звичайна форма, але git diff або агент бачать лише текстовий файл, тому частина інформації для них просто недоступна.
Якщо зовсім коротко: один файл — це те, що читається рядками, а другий — це картинки й бінарні шматки форми, які без рідної IDE у таблицю не розгорнеш. Щоб це обійти, ми зробили напівручний процес: розбирали структуру UI, мапили елементи форми й відновлювали їхню вкладеність.
Виглядало це приблизно так:
Form: MyForm
MultiPage1 | Home
MultiPage2
Page1 | Settings
Label1 | User Name -> TextBox1
Додатково написали невеликий модуль, який експортував властивості кожного елемента у структурований вигляд. Це дало агенту значно більше контексту, бо без цього етапу він банально не розумів, що відбувається в системі.
Креслення і навіщо тут multimodal
Окрема історія — це генерація креслень. У legacy вона відбувалася через COM, фактично всередині AutoCAD. Застосунок відкривав DWG-шаблони, підставляв розміри, вирізи, вікна тощо і зберігав результат. Тобто рушій — це жива сесія AutoCAD, а не файл, який ми згенерували з даних. Відтворити це один в один у вебі неможливо — браузер AutoCAD не запускає. Тому генерацію ми винесли в окремий Python-сервіс (FastAPI плюс бібліотека для роботи з DXF), а веб лишився оркестратором і viewer’ом.
Взаємодія вийшла простою — звичайний HTTP між .NET і Python, без жодного AutoCAD на сервері:
Blazor (UI) → C# сервіс
→ HTTP POST /api/.../drawings/panel (JSON: дані панелі, вирізи, ...)
→ Python-сервіс генерації креслень
→ бібліотека DXF: файл у потрібній папці
← шлях до файлу
Вкладка перегляду → веб-viewer відкриває згенероване креслення
Але був нюанс, який спочатку загнав нас у глухий кут. Ми не знали, як пояснити агенту, що саме потрібно «намалювати». У VBA немає окремого PDF із зразком, як має виглядати панель. Є код, DWG-шаблони і результат в AutoCAD. Текстом не передаси, де саме має стояти прямокутний виріз відносно кута й які підписи на шарах.
Тут нам допомогла multimodal-модель. Ми використовували її, щоб:
- проаналізувати референсне зображення з legacy і витягнути список об’єктів та орієнтирів;
- порівняти скрін з viewer’а після генерації й побачити, що виріз зміщений або рамка не та;
- накидати чернетку текстової специфікації для коду;
- поставити уточнювальні питання до того, як ітерувати («виріз відраховуємо від лівого нижнього кута чи від центру?»).
Сам код DXF, як і раніше, писала бібліотека в Python, а multimodal був перекладачем візуального еталона в інструкції для агента. Працювало це непогано, але не ідеально — про що розповім в блоці «що не спрацювало».
Ще одна річ, без якої нічого б не запрацювало, — жорстка організація роботи агента. Ми досить швидко зрозуміли, що без чіткої структури все розвалюється, бо агент забуває контекст, починає генерувати несумісні рішення, галюцинує.
Тому ввели простий, але дуже структурований цикл: агент читає контекстні файли, формулює ціль задачі, виконує кроки й оновлює файли. Ми давали йому завдання підтримувати таск-файли з чеклістами, короткий context summary між сесіями й опис архітектури. Виглядає елементарно, але саме це дозволило працювати з довгими сценаріями, а не топтатися на одному місці.
Як ми контролювали якість
Ми не вважали роботу зробленою лише тому, що агент написав код і проєкт зібрався. Для нас головне було, щоб нова система поводилася так само, як стара на тих самих вхідних даних: давала ті самі цифри в Excel, створювала ті самі файли в папках і той самий вміст документів. І важливо, що спочатку ми знімали еталон зі старої системи, а вже потім звіряли з ним мігровану, а не навпаки.
На практиці це означало кілька рівнів перевірки. Еталон знімали на віртуалці зі старим оточенням. Якщо щось не збігалося, ми не намагалися втримати це в голові, а просто клали поруч два блоки — «очікувано» і «фактично». Саме цей diff і ставав наступною задачею для агента. Статус Done у чеклісті з’являвся лише після такого порівняння на фіксованому наборі даних, а не після успішної збірки.
Окремо перевіряли форми — карту контролів, підписи полів, сценарії видимості вкладок, бо агент регулярно пропускав умовні таби. Для креслень критерієм була візуальна схожість: файл відкривався у viewer’і й мав відповідати референсу. Якщо збіг був лише частковим, задача залишалася у статусі Pending. Це дозволяло клієнту бачити реальний стан роботи і не давало агенту закривати задачу лише тому, що файл уже був створений.
Що не спрацювало
Якщо коротко, то частка ручної роботи виявилася більшою, ніж очікує клієнт (особливо, коли в презентаціях обіцяють, що AI майже самостійно перепише проєкт); але меншою, ніж повністю ручна міграція без агента. Ми очікували прискорення на чернетках і документації і отримали його. Але ілюзії, що інженер перетвориться на людину, яка раз на тиждень апрувить PR, не справдилися.
Що було треба розуміти в роботі з AI:
- Агент губить контекст. Між сесіями він не пам’ятає вчорашніх рішень, і без зовнішньої пам’яті у файлах знову пропонує інші імена полів, інші аркуші, іншу структуру сервісів. Доводилося тримати пам’ять проєкту в репозиторії й щоразу явно задавати обсяг сесії.
- Агент інколи вигадує логіку. Код компілюється, а поведінка не збігається з оригіналом, ті самі «227 проти 2» або виріз не від того кута. Лікується це лише прогоном сценарію на віртуалці й фіксацією «очікувано / фактично».
- Ітерації довгі. Одна підзадача — це кілька сесій агента плюс наші правки між ними. І контролювати якість непросто: «готово від агента» ще не означає «збігається з legacy».
Висновок, який ми зробили для себе — це не автоматизація, а асистент. Агент не веде міграцію сам, адже саме інженер задає архітектуру, обмежує задачу, рев’юить diff і ухвалює рішення.
Висновки і практичні поради
Після цього проєкту я переконався, що agentic-підхід працює навіть із важким legacy, де логіка накопичувалася роками, а людей, які її пам’ятають, немає в компанії. Але без дисципліни він майже не дає користі. Найкраще агенти показали себе на рутинній роботі, як от підготувати чернетку, розібрати структуру, зробити мапінг, написати каркас сервісу. Там, де потрібно ухвалювати архітектурні рішення або розбиратися з нетиповою логікою, інженер усе ще залишається в центрі процесу.
Кілька практичних порад наостанок на основі того, що спрацювало в нас.
- Спершу зробіть legacy читабельним для моделі. Не варто одразу віддавати їй увесь репозиторій. Краще експортувати UI у текст, зробити один документ з описом системи, завести журнал рішень із блоками «очікувано / фактично» і зафіксувати тестові входи. Якщо новому розробнику довелося б пів години пояснювати, як працює якась частина системи, агент теж із нею не впорається.
- Дробіть роботу на маленькі перевірювані кроки. Один task-файл — одна задача. Критерій готовності теж має бути конкретним. Наприклад, не «перепиши код», а «на таких-то вхідних даних вміст Excel і список файлів збігаються з еталоном або відхилення задокументоване».
- Обмежуйте обсяг кожної сесії. На старті явно фіксуйте, які файли агент може читати, яка архітектура вже не обговорюється і що сьогодні поза межами задачі. Без меж агент почне покращувати архітектуру під себе, і ви витрачатимете сесії на відкат, а не на прогрес.
- Не чекайте магії. Домовтеся з собою і зі стейкхолдерами, що перша відповідь агента — це чернетка, а не реліз. Те, що проєкт збирається, означає лише, що він компілюється. Креслення, розрахунки й інші критичні результати все одно має перевірити людина. Якщо цього не пояснити замовнику на початку, очікування дуже швидко розходяться з реальністю.
- Беріть оркестратор, який редагує файли, термінал і довгі сесії, і рахуйте токени, а не лише підписку. Підбирайте модель під задачу: дешевшу — на мапінги й документацію, сильнішу — на складні гілки логіки. А все, що неможливо відтворити в браузері, виносьте в окремий мікросервіс із вузьким API — агенту куди простіше цілитися в простий HTTP, ніж у нетрі COM.
Якщо підсумувати одним реченням, то Agentic AI на міграції legacy — це керований конвеєр. Він добре прибирає рутину. Але тільки тоді, коли в проєкті вже є порядок: зрозуміла структура, зафіксовані еталони й чіткі правила перевірки. Інакше більшу частину часу доведеться виправляти його власні помилки.
6 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів«ми хотіли уникнути переписування всього, тому переписали все»
А ви пробували будувати граф залежностей у файлах/функціях?
Таким чином LLM Агент не губився би так сильно, бо міг би проіндексувати і знати в майбутньому що, де лежить і що робить.
а чим вони оце
в граф розпарсять?
готові солюшни (типу Graphify чи Understand-Anything) це не розпарсять, вони VBA не підтримують.
там треба це в кілька проходів парсити, частинами; брати ANTLR граматику для VBA і вперед на міни — будувати AST-дерево по коду.
У них на це дофіга часу піде, якщо немає готового інструменту куди можна просто граматику додати і трохи підтюнити парсінг.
як варіант, можна «граф» без графа як такого будувати, просто проходитись ллм-кою в кілька проходів і на кожному створювати/доповнювати якусь knowledge base структуру. Тільки потім все це треба ще верифікувати — по готовій базі потім проходитись, звіряти з кодом. Коротше, треба досить багато обв’язки (агентів, memory) для цього підготувати. А вони, я так розумію, практично з нуля починали.
Впевнений, що з Claude-ом можна навіть Graphify прокачати, чи щось власне швидко написати, як мінімум по методах і файлах.
можна, але не дуже швидко. якщо дженерік структуру робити, то в загальному вигляді виходить десь так, файлами і методами не обходишся.
у VBA трохи по іншому але плюс-мінус на те ж вийде
Web — це гидотство. Поламали так гарно вибудовану систему.