惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
T
Threatpost
Google DeepMind News
Google DeepMind News
WordPress大学
WordPress大学
Recorded Future
Recorded Future
小众软件
小众软件
N
Netflix TechBlog - Medium
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
博客园 - 聂微东
美团技术团队
F
Fortinet All Blogs
I
InfoQ
U
Unit 42
酷 壳 – CoolShell
酷 壳 – CoolShell
阮一峰的网络日志
阮一峰的网络日志
The Cloudflare Blog
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
Vercel News
Vercel News
GbyAI
GbyAI
有赞技术团队
有赞技术团队
博客园 - 【当耐特】
H
Help Net Security
B
Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
Jina AI
Jina AI
The Register - Security
The Register - Security
Hugging Face - Blog
Hugging Face - Blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 叶小钗
Recent Announcements
Recent Announcements
D
DataBreaches.Net
IT之家
IT之家
雷峰网
雷峰网
Y
Y Combinator Blog
W
WeLiveSecurity
P
Proofpoint News Feed
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
量子位
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 司徒正美
月光博客
月光博客
The Hacker News
The Hacker News

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Правильная агентская архитектура в 2026 г. Часть 1. Долговременное состояние (durable state): ход, шаг, событие
kobubu · 2026-04-27 · via Все публикации подряд на Хабре

Время на прочтение12 мин

Охват и читатели512

Туториал

Практическая статья по устройству production-ready агента

Матушка-природа с презрением смотрит на то, как ты отправляешь ии-агенту запрос на 256 тысяч токенов с учетом контекста предыдущего диалога

Матушка-природа с презрением смотрит на то, как ты отправляешь ии-агенту запрос на 256 тысяч токенов с учетом контекста предыдущего диалога

Введение

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

Сейчас мне кажется, что весь софт, который последнее время делается - это один сплошной ии-агент, который потенциально должен уметь всё на свете. При этом пользователи в 2026 году не готовы ни к какой другой форме отношений с приложениями, кроме как промптинг. Если во время презентации продукта они видят больше одной кнопки “отправить промпт”, то сразу заявляют, что им сложно, а у тебя появляется чувство, словно ты им должен заплатить за то, чтобы они осилили твой софт. Ну ладно, мобильные телефоны в итоге ведь превратились в прямоугольники с экранами. Может, и у софта есть “финальная форма” в виде ии-агента с интерфейсом. 

Базовый минимум

Когда говорят об агенте, очень часто имеют в виду “LLM с большим системным промптом и несколькими функциями, которая не останавливается пока не решит, что все сделала”, при этом вся предыдущая история диалога передается по апи в нейросеть при каждом промте. 

Ии-агент считает, что занят чем-то важным

Ии-агент считает, что занят чем-то важным

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

Зрелый агент должен быть устроен иначе. В агенте должны сочетаться durable state (долговременное состояние), явное планирование, типизированные инструменты, approval-политика, журнал событий и отдельный рантайм для фоновых задач.В правильном агенте модель является только одним из компонентов принятия решения.

Долговременное состояние (durable state)

Durable state - это не один объект некоего класса DurableState. Это слой данных, в котором агент хранит факты о своей работе: активные ходы, планы, статусы шагов, ожидающие подтверждения, события, результаты и ошибки. В коде этот слой обычно выражается набором ORM-моделей и сервисов вокруг них.

Durable state позволяет сохранять состояние (историю, текущий план, выполненные шаги, паузы, ожидания) во внешнем хранилище. В таком случае агент сможет пережить: перезапуск рантайма, смену версии модели, ожидание ответа человека дни и недели, высадку Илона Маска на марсе и многое другое. Пользователь сможет остановить агента, а через час сказать «продолжи», и агент поднимет состояние и пойдёт дальше как ни в чём не бывало.

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

Коллеги из Гигачат возложили болт на долговременное состояние

Коллеги из Гигачат возложили болт на долговременное состояние

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

Плохой вариант:

memory = {}

def handle_message(session_id: str, message: str):

    if message == "проанализируй документы":

        memory[session_id] = {

            "status": "running",

            "task": "analyze_documents",

            "step": "started",

        }

        result = analyze_documents()

        memory[session_id]["status"] = "completed"

        memory[session_id]["result"] = result

        return "Готово"

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

Но в отличие от Гигачата, вариант, который вы выкатите в продакшен, будет содержать реализацию набора классов для создания durable state. Минимально следующие классы: AgentTurn (ход), AgentPlanItem (шаг) и AgentEvent (событие), ApprovalGrant  (выданные подтверждения), SessionContext   (состояние сессии), BackgroundJob (фоновая задача). 

Ход агента (Agent turn)

Конференция ии-специалистов в Сан-Франциско 2026 г.

Конференция ии-специалистов в Сан-Франциско 2026 г.

Ход, или turn, представляет собой один полный цикл работы агента на один запрос пользователя.  Сперва введем класс AgentTurn.  Это и будет durable-запись одного хода агентского протокола. Она хранит не только текст пользователя, но и состояние обработки: во что команда была нормализована, нужно ли подтверждение, чем выполнение завершилось и была ли ошибка. Благодаря этому агент не обязан “помнить” ход в промпте или оперативной памяти процесса. Он может восстановить состояние из базы.

class AgentTurn(Base):

#Имя таблицы, куда ORM будет сохранять ходы агента. Один объект AgentTurn в Python соответствует одной строке в таблице agent_turns.

    tablename = “agent_turns” #редактор хабра не дает написать через __

    # Уникальный идентификатор хода.

    # По нему дальше связываются план, события, approvals и результаты выполнения.

    turn_id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)

    # Идентификатор пользовательской сессии.

    # Нужен, чтобы найти все ходы конкретного пользователя или диалога.

    session_id: Mapped[str] = mapped_column(String(200), index=True)

    # Сырой текст пользователя.

    # Например: "проанализируй документы и сделай отчет".

    input_text: Mapped[str] = mapped_column(Text)

    # Нормализованная команда, полученная из input_text.

    # Это уже не свободный текст, а структурированное намерение:

    # {"action": "analyze_documents", "scope": "current_workspace"}.

    normalized_command: Mapped[dict | None] = mapped_column(JSON, nullable=True)

    # Текущий статус хода.

    # Например: created, planned, awaiting_approval, running, completed, failed.

    status: Mapped[str] = mapped_column(String(40), default="created")

    # Требуется ли подтверждение пользователя перед выполнением.

    # Например, если действие изменяет данные или запускает дорогую операцию.

    needs_confirmation: Mapped[bool] = mapped_column(Boolean, default=False)

    # Финальный текстовый ответ агента.

    # Например: "Готово, отчет сформирован".

    output_text: Mapped[str | None] = mapped_column(Text, nullable=True)

    # Ошибка, если ход завершился неуспешно.

    # Хранится durable, чтобы после сбоя можно было понять причину.

    error: Mapped[str | None] = mapped_column(Text, nullable=True)

Допустим, пользователь пишет: “Проверь документы и сделай отчет”. Backend создает turn_id. Дальше все, что агент делает по этому запросу, привязано к этому turn_id:

turn_id = 123

запрос пользователя → план → выполнение инструментов → события прогресса → финальный ответ

Агент не ограничивается одним ходом.  Если, например, ход звучит как: "Analyze documents and make a report", - то внутри него будут такие шаги: 

collect_documents → analyze_documents → generate_report

У хода обычно есть состояние: running, awaiting_approval, completed, failed, cancelled

Шаг хода/плана агента (Agent Plan Item)

Шаг хода, или plan item, это отдельное действие внутри хода. Один ход может состоять из нескольких шагов. 

Ход:   Пользователь: “Проанализируй проект и сделай отчет”

Шаги плана: Найти документы проекта →   Прочитать релевантные файлы →   Проверить → Сформировать отчет

Каждый шаг плана может быть связан с конкретным инструментом: tool_name = excel_reader, tool_name = csv_reader. Шаг плана отвечает на вопрос: “Какие конкретные действия агент решил выполнить, чтобы закрыть ход?” У шага тоже есть статус: pending, running, completed, failed, skipped, awaiting_approval. Для описания шага этого нужна отдельная таблица и сущность.

class AgentPlanItem(Base):

    # Таблица, где хранятся отдельные шаги планов агента.

    tablename = "agent_plan_items"

    # Уникальный идентификатор шага плана.

    item_id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)

    # Ссылка на ход агента, которому принадлежит этот шаг.

    # ForeignKey("agent_turns.turn_id") означает:

    # это поле связано с turn_id из таблицы agent_turns.

    turn_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("agent_turns.turn_id"))

    # Порядковый номер шага внутри плана.

    # Например: 0 - собрать данные, 1 - проанализировать, 2 - сформировать отчет.

    step_index: Mapped[int] = mapped_column(default=0)

    # Имя инструмента, который нужно вызвать на этом шаге.

    # Например: "collect_documents", "analyze_documents", "generate_report".

    tool_name: Mapped[str] = mapped_column(String(120))

    # Аргументы для инструмента.

    # Например: {"scope": "current_workspace", "format": "markdown"}.

    args: Mapped[dict] = mapped_column(JSON, default=dict)

    # Режим подтверждения.

    # safe_readonly - безопасный read-only шаг;

    # confirm_once - нужно подтверждение один раз;

    # mutating - действие меняет состояние и требует строгой проверки.

    approval_mode: Mapped[str] = mapped_column(String(40), default="safe_readonly")

    # Статус конкретного шага.

    # Например: created, running, completed, failed.

    status: Mapped[str] = mapped_column(String(40), default="created")

    # Результат выполнения шага, если он завершился успешно.

    # Например: {"documents_found": 12}.

    result: Mapped[dict | None] = mapped_column(JSON, nullable=True)

    # Ошибка выполнения шага, если он упал.

    error: Mapped[str | None] = mapped_column(Text, nullable=True)

А еще нам нужен журнал событий агента (AgentEvent).

Журнал событий Агента (Agent event)

В жизни агента много интересных событий

В жизни агента много интересных событий

Событие, или event, это запись о том, что что-то произошло во время хода.

Например: turn_started, plan_started, plan_ready, tool_started, tool_progress, tool_completed, approval_requested, verification_started, turn_completed,

Пример события:

{

  "type": "tool_progress",

  "turn_id": "123",

  "tool_name": "summarize_project",

  "status": "running",

  "message": "Анализирую документы: 40%",

  "payload": {

    "percent": 40,

    "done": 4,

    "total": 10

  }

}

Если AgentTurn отвечает на вопрос: ”Что за пользовательский ход сейчас обрабатывается?” То AgentEvent отвечает: “Что происходило во времени?” Список событий может быть таким: turn_created, plan_created, approval_requested, tool_started, tool_completed, tool_failed,  turn_completed.

class AgentEvent(Base):

    # Таблица, где хранится timeline агента:

    # что произошло, когда произошло и к чему это относится.

    tablename = "agent_events"

    # Уникальный идентификатор события.

    event_id: Mapped[uuid.UUID] = mapped_column(

        primary_key=True,

        default=uuid.uuid4,

    )

    # К какому ходу агента относится событие.

    # Например, событие "tool_started" относится к конкретному AgentTurn.

    turn_id: Mapped[uuid.UUID] = mapped_column(

        ForeignKey("agent_turns.turn_id"),

    )

    # К какому шагу плана относится событие.

    # Может быть пустым, если событие относится ко всему ходу,

    # а не к конкретному шагу.

    # Например:

    # turn_started      -> item_id = None

    # approval_requested -> item_id = None

    # tool_started      -> item_id = id конкретного AgentPlanItem

    # tool_completed    -> item_id = id конкретного AgentPlanItem

    item_id: Mapped[uuid.UUID | None] = mapped_column(nullable=True)

# Уникальный идентификатор сессии

session_id: Mapped[str] = mapped_column(String(200), index=True)

    # Тип события.

    # Например: "turn_started", "tool_started", "tool_completed", "tool_failed".

    event_type: Mapped[str] = mapped_column(String(80), index=True)

    # Статус, связанный с событием.

    # Например: "running", "completed", "failed", "awaiting_approval".

    status: Mapped[str | None] = mapped_column(String(40), nullable=True)

    # Произвольные дополнительные данные события.

    # Например прогресс, ошибка, имя инструмента, количество обработанных объектов.

    payload: Mapped[dict] = mapped_column(JSON, default=dict)

    # Время создания события.

    # По нему можно построить timeline выполнения.

    created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)

Теперь посмотрим, как оно работает вместе.

Пример работы: Turn→Plan Item → Event

Конь ходит буквой Г

Конь ходит буквой Г

Пример: пользователь написал: ”Проанализируй документы и сделай отчет”.

Система создала AgentTurn и три шага плана:

1. collect_documents

2. analyze_documents

3. generate_report

Во время выполнения будут появляться события:

AgentEvent(

    turn_id=turn.turn_id,

    item_id=None,

    event_type="turn_started",

    status="running",

    payload={},

)

Потом старт первого инструмента:

AgentEvent(

    turn_id=turn.turn_id,

    item_id=collect_documents_item.item_id,

    event_type="tool_started",

    status="running",

    payload={

        "tool_name": "collect_documents",

        "args": {"scope": "current_workspace"},

    },

)

Потом завершение первого инструмента:

AgentEvent(

    turn_id=turn.turn_id,

    item_id=collect_documents_item.item_id,

    event_type="tool_completed",

    status="completed",

    payload={

        "tool_name": "collect_documents",

        "documents_found": 12,

    },

)

Потом прогресс второго шага:

AgentEvent(

    turn_id=turn.turn_id,

    item_id=analyze_documents_item.item_id,

    event_type="tool_progress",

    status="running",

    payload={

        "tool_name": "analyze_documents",

        "done": 40,

        "total": 100,

        "percent": 40,

    },

)

Если что-то упало:

AgentEvent(

    turn_id=turn.turn_id,

    item_id=analyze_documents_item.item_id,

    event_type="tool_failed",

    status="failed",

    payload={

        "tool_name": "analyze_documents",

        "error": "external service timeout",

        "retryable": True,

    },

)

И в конце:

AgentEvent(

    turn_id=turn.turn_id,

    item_id=None,

    event_type="turn_completed",

    status="completed",

    payload={

        "output": "Отчет сформирован",

    },

)

В результате UI становится гораздо показательнее. Без событий UI может показать только: 

“Агент думает…”

С событиями UI может показывать нормальный прогресс: 

Собираю документы...

Нашел 12 документов.

Анализирую документы: 40%.

Формирую отчет...

Готово.

То есть frontend не должен гадать, что происходит. Он читает события.

Пример для фронтенда:

def get_turn_events(db: Session, turn_id: uuid.UUID):

    return (

        db.query(AgentEvent)

        .filter(AgentEvent.turn_id == turn_id)

        .order_by(AgentEvent.created_at.asc())

        .all()

    )

Помимо хорошего UX на фронтенде, такая структура способствует лучшей отладке, восстановлению после рестарта, аудиту, тестам.

Но AgentTurn, AgentPlanItem и AgentEvent - это еще не весь durable state. Они описывают конкретный ход: что пользователь попросил, какой план построен, какие шаги выполнялись и какие события произошли. В production-агенте обычно нужны еще другие сущности: ApprovalGrant  (выданные подтверждения), SessionContext   (состояние сессии), BackgroundJob  #фоновые задачи и, возможно, ProjectContext   (состояние проекта), но о них в следующий раз.

P.S. Описания классов не полные, можете менять их любым образом

Телеграм канал автора, где он что‑то пишет про ML, NLP и разработку