AI-агент для автоматизації desktop-додатків — розробка

Desktop-додатки без API — класичний головний біль інженера з автоматизації. Legacy ERP, CAD-програми, банк-клієнти, 1С у режимі thick client. Selenium їх не бачить, REST API немає. Ми вирішуємо це завдання через AI-агента, який або використовує Computer Use (скріншот + керування мишею/клавіатурою),

Напрямки AI-розробки

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    998
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1267
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    713
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    1003

Desktop-додатки без API — класичний головний біль інженера з автоматизації. Legacy ERP, CAD-програми, банк-клієнти, 1С у режимі thick client. Selenium їх не бачить, REST API немає. Ми вирішуємо це завдання через AI-агента, який або використовує Computer Use (скріншот + керування мишею/клавіатурою), або звертається до accessibility API (pywinauto, UI Automation) — більш надійний варіант для Windows-додатків. У цій статті розберемо, як побудувати такого агента, які підводні камені та скільки це економить.

Чому AI-агент, а не RPA?

Класичні RPA-системи (UiPath, Blue Prism) потребують жорсткої розмітки інтерфейсу і ламаються при будь-якому оновленні. AI-агент на базі LLM розуміє UI на рівні семантики: йому не потрібен заздалегідь заданий селектор — він аналізує дерево елементів і приймає рішення про дію. Це знижує вартість підтримки на 40–60% на дистанції. Ми спираємось на стандарти Windows UI Automation API та документацію pywinauto.

Як ми будуємо такого агента?

В основі лежить зв'язка pywinauto + Anthropic API. pywinauto звертається до Windows UI Automation API — та ж технологія, яку використовує екранний диктор. Елементи знаходяться за accessibility-атрибутами (AutomationId, Name, ControlType), а не по пікселях. Значно надійніше за скріншоти.

from anthropic import Anthropic import pywinauto from pywinauto.application import Application from pywinauto.findwindows import ElementNotFoundError import json import subprocess import time client = Anthropic() class DesktopAppAgent: """AI-агент для автоматизації Windows desktop-додатків""" def __init__(self, app_path: str = None, app_title: str = None): self.app_path = app_path self.app_title = app_title self.app = None self.main_window = None def launch_or_connect(self): """Запускає додаток або підключається до запущеного""" try: if self.app_title: self.app = Application(backend="uia").connect(title_re=f".*{self.app_title}.*") elif self.app_path: self.app = Application(backend="uia").start(self.app_path) time.sleep(2) # чекаємо ініціалізації except pywinauto.findwindows.ElementNotFoundError: if self.app_path: self.app = Application(backend="uia").start(self.app_path) time.sleep(2) self.main_window = self.app.top_window() def get_ui_tree(self, max_depth: int = 4) -> dict: """Отримує дерево UI-елементів""" def extract_element(element, depth=0): if depth > max_depth: return None try: info = { "name": element.window_text()[:100] if element.window_text() else "", "control_type": element.element_info.control_type, "automation_id": element.element_info.automation_id or "", "enabled": element.is_enabled(), "visible": element.is_visible(), "rect": str(element.rectangle()), } children = [] for child in element.children(): child_info = extract_element(child, depth + 1) if child_info and (child_info["visible"] or child_info["enabled"]): children.append(child_info) if children: info["children"] = children[:20] # не більше 20 дочірніх return info except Exception: return None return extract_element(self.main_window) def find_and_interact(self, instruction: str) -> str: """LLM визначає який елемент потрібен і що з ним робити""" ui_tree = self.get_ui_tree() response = client.messages.create( model="claude-sonnet-4-5", max_tokens=512, messages=[{ "role": "user", "content": f"""Проаналізуй дерево UI і поверни JSON з дією: {{ "action": "click|type|select|get_value|find", "automation_id": "ID елемента якщо є", "name": "ім'я елемента", "control_type": "тип елемента", "value": "значення для type/select" }} Інструкція: {instruction} UI-дерево: {json.dumps(ui_tree, ensure_ascii=False)[:4000]} Тільки JSON.""" }], ) try: text = response.content[0].text action = json.loads(text[text.find("{"):text.rfind("}") + 1]) return self._execute_ui_action(action) except Exception as e: return f"Помилка парсингу: {e}" def _execute_ui_action(self, action: dict) -> str: """Виконує дію з UI-елементом""" try: # Шукаємо елемент за automation_id або іменем element = None if action.get("automation_id"): element = self.main_window.child_window( auto_id=action["automation_id"] ) elif action.get("name"): element = self.main_window.child_window( title=action["name"], control_type=action.get("control_type"), ) if not element: return "Елемент не знайдено" act = action.get("action", "click") if act == "click": element.click_input() return f"Клікнув на {action.get('name', action.get('automation_id'))}" elif act == "type": element.set_edit_text(action.get("value", "")) return f"Ввів: {action.get('value', '')}" elif act == "select": element.select(action.get("value", "")) return f"Вибрал: {action.get('value', '')}" elif act == "get_value": return element.window_text() or element.get_value() except ElementNotFoundError: return f"Елемент не знайдено: {action}" except Exception as e: return f"Помилка: {type(e).__name__}: {e}" return "Дію виконано" class DesktopWorkflowAgent: """Високорівневий агент для виконання завдань у desktop-додатку""" TOOLS = [ { "name": "get_ui_state", "description": "Отримує поточний стан UI-дерева додатку", "input_schema": {"type": "object", "properties": {}}, }, { "name": "interact_with_element", "description": "Взаємодіє з UI-елементом (клік, введення тексту, вибір)", "input_schema": { "type": "object", "properties": { "automation_id": {"type": "string"}, "action": {"type": "string", "enum": ["click", "type", "select", "get_value"]}, "value": {"type": "string"}, }, "required": ["action"], }, }, { "name": "wait", "description": "Чекає зміни стану додатку", "input_schema": { "type": "object", "properties": { "seconds": {"type": "number", "default": 1.0}, "wait_for_element": {"type": "string"}, }, }, }, { "name": "keyboard_shortcut", "description": "Натискає комбінацію клавіш (Ctrl+S, Alt+F4, тощо)", "input_schema": { "type": "object", "properties": { "shortcut": {"type": "string", "description": "Наприклад: Ctrl+S, Alt+Tab, F2"}, }, "required": ["shortcut"], }, }, ] def __init__(self, desktop_agent: DesktopAppAgent): self.agent = desktop_agent async def run(self, task: str) -> dict: messages = [{"role": "user", "content": task}] steps = 0 while steps < 40: response = client.messages.create( model="claude-sonnet-4-5", max_tokens=1024, system="Ти — агент автоматизації desktop-додатку. Використовуй інструменти послідовно.", tools=self.TOOLS, messages=messages, ) tool_results = [] done = False for block in response.content: if hasattr(block, "text") and block.text: if "виконано" in block.text.lower() or "завершено" in block.text.lower(): done = True elif block.type == "tool_use": result = "" inp = block.input if block.name == "get_ui_state": result = json.dumps(self.agent.get_ui_tree(max_depth=3), ensure_ascii=False)[:3000] elif block.name == "interact_with_element": result = self.agent._execute_ui_action(inp) elif block.name == "wait": time.sleep(inp.get("seconds", 1.0)) result = "Waited" elif block.name == "keyboard_shortcut": import pyautogui keys = inp["shortcut"].replace("+", " ").split() pyautogui.hotkey(*[k.lower() for k in keys]) result = f"Pressed {inp['shortcut']}" tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": result, }) if done or response.stop_reason == "end_turn": return {"success": True, "steps": steps} messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results}) steps += 1 return {"success": False, "steps": steps} 

Який підхід обрати: pywinauto vs Computer Use?

Критерій pywinauto + UI Automation Computer Use (скріншот)
Надійність Висока — робота з елементами за атрибутами Середня — залежить від роздільної здатності екрану та контрасту
Швидкість Швидко — без рендерингу зображень Повільно — потрібна генерація скріншоту та його аналіз
Підтримка змін UI Часткова — при оновленні automation_id може змінитися Висока — не залежить від атрибутів
Підтримувані додатки Windows (WPF, WinForms, 1C, SAP) Будь-які з графічним інтерфейсом
Доступ до складних елементів (таблиці, гриди) Є через UI Automation pattern Обмежений (тільки видима область)

Ми використовуємо обидва підходи: pywinauto як основний, Computer Use як fallback. Якщо додаток має доступну модель UI — надаємо перевагу pywinauto, інакше Computer Use.

Коли потрібен Computer Use як fallback?

Computer Use виручає, коли додаток використовує кастомну графіку або не надає accessibility-дерево. Наприклад, старі CAD-системи або візуалізатори даних. У таких випадках агент робить скріншот області та передає його LLM для аналізу. Однак це повільніше і менш надійно: latency p99 вищий, і модель може помилитися у визначенні координат. Тому застосовуємо Computer Use тільки там, де pywinauto безсилий.

Практичний кейс: автоматизація 1С:Бухгалтерія з нашої практики

Задача: щомісячне формування 40 звітів у 1С для 12 юросіб. Процес займав 3 робочих дні двох бухгалтерів.

Підхід: pywinauto для 1С desktop-клієнта (версія 8.3). UI Automation працює з 1С через COM-об'єкти та accessibility API.

Результати:

  • 40 звітів × 12 юросіб: 3 робочих дні → 4 години (нічний запуск)
  • Помилки ручного введення: знизилися до 0
  • Складність: 1С періодично змінює automation_id при оновленнях — довелося додати fallback-пошук за іменем елемента

Ключовий момент при роботі з 1С: додаток використовує кастомний двигун, не всі елементи видно через стандартний UI Automation. Частина автоматизації реалізована через COM-інтерфейс 1С напряму. Наш досвід показує, що гібридна схема (pywinauto + COM) дає кращий результат.

Що входить в роботу?

  1. Аналіз UI-дерева додатку — виявлення доступних елементів, складання карти автоматизації.
  2. Розробка агента для конкретного workflow — реалізація послідовності дій з fallback-логікою.
  3. Інтеграція з LLM — вибір моделі (Claude або GPT), налаштування промптів для стійкої генерації JSON-команд.
  4. Бетчева обробка + моніторинг — запуск за розкладом, надсилання алертів про помилки.
  5. Документація — опис архітектури, інструкція із запуску, підтримка.

Орієнтовні терміни

Етап Тривалість
Аналіз UI-дерева + базова автоматизація 3–5 днів
Конкретний workflow (форма → обробка → результат) 1–2 тижні
1С/SAP специфіка (COM + pywinauto) +1 тиждень
Бетчева обробка + моніторинг +1 тиждень

Ми гарантуємо стабільність агента протягом усього строку експлуатації — у разі оновлення додатку коригуємо селектори та промпти. Працюємо з desktop-автоматизацією понад 5 років, виконано понад 20 проєктів для банків, рітейлу та логістики.

Отримайте консультацію по вашому сценарію — оцінимо проєкт за 1–2 дні. Зв'яжіться з нами, ми підкажемо оптимальне рішення під ключ.