AI-агент для автоматизации десктопных приложений

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
AI-агент для автоматизации десктопных приложений
Средняя
~2-4 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

AI-агент для автоматизации desktop-приложений

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

Подход 1: pywinauto + LLM для Windows-приложений

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, etc.)",
            "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}

Практический кейс: автоматизация 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С напрямую.

Сроки

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