Как мы с ChatGPT спецификацию OpenAPI писали: опыт системного аналитика

Приветствую, коллеги! Я ведущий системный аналитик в компании ЛАНИТ. В настоящее время я работаю на крупном государственном проекте в области финансов. В своей статье я решила поделиться этапами совершенствования процесса описания интеграционного взаимодействия на отдельно взятом проекте нашей компании и рассказать, как благодаря использованию генеративной модели ChatGPT мы ускорили процесс формирования документации OpenAPI и частично снизили связанные с этим трудозатраты системных аналитиков.

Статья может быть полезна системным аналитикам и командам, проектирующим API по принципу Design API First (Contract first). Материал основан на реальном опыте системных аналитиков нашего проекта и не является руководством или призывом к действию. 

37edbff9b696b1f858318027449cea13.jpeg

На проекте с микросервисной архитектурой для меня и для многих моих коллег обыденной задачей является формирование требований на реализацию API (Application programming interface) — контрактов взаимодействия между различными компонентами системы, микросервисами, смежными системами и т.д.

Этапы совершенствования процесса документирования API

Можно выделить несколько этапов усовершенствования описания API в команде нашего проекта. Далее я кратко остановлюсь на каждом из них, перечислю их достоинства и недостатки, а также поделюсь опытом и результатами использования ChatGPT при написании спецификации OpenAPI.

Первый этап: генерируем спецификацию API на основе кода (подход Code first)

Требования на реализацию API, как и в целом к системе, мы оформляем в Atlassian Confluence. Для операций API указываются путь запроса, выполняемое действие, тело запроса и ответа (в виде модели данных, DTO (Data Transfer Object)), статусы ответов.

Наше описание DTO — это человеческое описание JSON-схемы. Оно включает единичные объекты, массивы объектов и атрибуты. Вложенность объектов друг в друга или атрибутов в объекты имитируется иерархичностью представления. Для каждого объекта/атрибута указываются название, тип, комментарий, сведения об обязательности. Такой подход к описанию спецификаций требований к разработке создан и внедрен нашим руководителем группы аналитиков на проекте.

Вот пример одного из таких DTO. В этом случае также указывается способ формирования значений атрибутов.

Рисунок 1. Пример DTO
Рисунок 1. Пример DTO

В целом эти правила описания DTO специфичны исключительно для нашего проекта, но их стоит упомянуть, потому что это важно для дальнейшего повествования.

На первом этапе модели данных, описанные приведенным выше способом, и описания API аналитики передавали в виде постановок на разработку команде разработчиков, которые на их основе реализовывали в коде API. Далее уже на базе существующего кода генерировалась спецификация OpenAPI. Фактически использовался подход Code first: сначала пишем код, потом генерируем по нему контракт.

Основной недостаток первого подхода заключается в том, что API и контракт, который мы получаем на выходе от разработчика, часто не соответствует тому, что было на входе в постановке от аналитика (разработка может реализовать немного другую модель данных, переименовать атрибуты и т.п.). К сожалению, аналитик об этом узнает только тогда, когда разработчик предоставит реализованный API. Да, эту проблему можно в какой-то степени нивелировать дополнительными этапами согласования постановки между аналитиком и разработкой, но разница между постановкой и конечным результатом, как правило, имеется. Иногда существенная. Как следствие:

  • достаточная разница по времени между тем, как аналитик описал API в спецификации в Confluence и когда получил готовый жесткий контракт API;

  • аналитику приходится дорабатывать спецификации в Confluence по факту реализации API, чтобы иметь его актуальное описание, доступное для дальнейшего использования другими членами команды, например, представителями тестирования и сопровождения.

Второй этап: генерируем программный код на основе спецификации OpenAPI (подход Contract first)

На втором этапе описанный выше процесс создания документации и разработки API был усовершенствован.

Мы применили так называемый подход Contract first — сначала создаем контракт, потом по нему пишем или генерируем код.

Процесс можно представить в виде последовательности шагов.

  1. Аналитик по-прежнему начинает с описания требований к API в Confluence.

  2. Требования к API оформляются аналитиком в виде YAML-файла — спецификации OpenAPI (ссылка на актуальную версию спецификации).

Такой файл можно оформлять в любом доступном текстовом редакторе, но мы используем Swagger Editor. Он позволяет редактировать спецификации OpenAPI в YAML внутри браузера. У вас есть возможность просматривать документацию в режиме реального времени — слева пишем текст спецификации, справа отображается полнофункциональный интерфейс Swagger UI.

Рисунок 2. Пример работы с редактором Swagger Editor
Рисунок 2. Пример работы с редактором Swagger Editor
  1. Разработанная таким образом спецификация OpenAPI согласовывается с разработкой и в дальнейшем является конечным жестким контрактом взаимодействия между всеми членами ИТ-команды. YAML-файл — место, где изложены единые, согласованные и используемые всеми членами команды требования к API.

  2. На основе спецификации OpenAPI разработка осуществляет генерацию программного кода.

Такой подход позволил решить основные проблемы первого этапа и имеет ряд преимуществ.

  • Удалось сформировать единый согласованный контракт API еще до начала процесса разработки. Еще до реализации API в коде он может использоваться членам ИТ-команды при написании постановок на смежные задачи, алгоритмов, использующих этот API, и т.п.

  • Сократилось время на разработку API со стороны разработки.

  • Комментарии (description) и примеры (example) ― очень важная часть спецификации. При таком подходе они добавляются в спецификацию аналитиками с учетом знания системы с точки зрения бизнеса и понимания потребностей и целей пользователей.

Однако вместе с преимуществами мы получили и недостатки.

В частности, трудоемкость работы аналитика увеличилась. Ведь кроме описания требований в Confluence, теперь нужно формировать YAML-файл с описанием спецификации OpenAPI. В итоге для аналитика добавилось много дополнительной, рутинной, достаточно нудной работы, в основном заключающейся в копировании текстов из спецификации в YAML с преобразованием текста по определенному формату, выделением перечислений (enum), схем данных и проставлением ссылок на них. Учитывая, что только для одного DTO с описанием тела ответа число атрибутов у нас может быть более 50, процесс формирования такого YAML-файла — достаточно затратная по времени задача.

В какой-то момент мне пришла идея возложить этот не очень благодарный труд по преобразованию текста из модели данных на плечи искусственного интеллекта, а именно ChatGPT. На различных этапах эксперимента я использовала модели ChatGPT-3.5 и ChatGPT-4.0 (далее по тексту, когда я упоминаю использование модели искусственного интеллекта, я имею в виду ChatGPT). И тут мы плавно подошли к этапу номер три.

Третий этап: привлекаем ChatGPT к формированию спецификации OpenAPI 

Различные эксперименты по привлечению искусственного интеллекта для решения задач системного анализа я проводила и ранее. На мой взгляд, ИИ оказывается максимально полезен для преобразования текста из одного формата в другой по определенным правилам, особенно когда речь идет про изменение текста в/из формата, который хорошо знаком ChatGPT. А именно эту задачу мы и решали на этапе 2 — преобразовывали текст из модели данных в Confluence (один формат) в YAML (другой формат, известный ChatGPT). Результат получился достаточно интересным, поэтому захотелось им поделиться.

На третьем этапе мы используем подход, аналогичный подходу два, с той лишь разницей, что:

  • задачу формирования YAML-файла теперь аналитик решает с помощью ChatGPT,

  • полученный черновик YAML-файла проверяется и дорабатывается аналитиком (как правило, требуется внесение незначительных правок). На удивление, с этой задачей ChatGPT справляется достаточно хорошо.

Использование нейросети на третьем этапе позволило нам частично решить основную проблему второго этапа ― снизить трудозатраты аналитика. Теперь мы тратим на описание API примерно столько же времени, сколько на первом этапе, но в итоге получаем все преимущества второго этапа ― жесткий единый контракт взаимодействия посредством API и снижение трудозатрат разработки.

Пишем спецификацию OpenAPI с помощью ChatGPT

Спецификация OpenAPI представляет собой один из способов описания REST API и в версии 3.0 на верхнем уровне включает следующие объекты:

  • openapi,

  • info,

  • servers,

  • paths,

  • components,

  • security,

  • tags,

  • externalDocs.

Самой полезной для нас оказалась помощь нейросети при генерации текста для объекта components. В статье мы будем описывать формирование именно этой части спецификации OpenAPI. Объект components, в свою очередь, включает объект schemas — описание моделей данных (фактически это и есть наши DTO), которые могут быть переиспользованы в различных местах в документе спецификации. ChatGPT не только помогает в формировании этого раздела спецификации в части преобразования нашей модели данных в формат спецификации OpenAPI, но и формировании вложенных структур данных с автоматической расстановкой ссылок на связанные объекты. Посмотрим, как это все работает на примере.

Допустим, у нас есть описание DTO тела ответа нашего API (см. рисунок 1), есть массив oList и вложенные в него массив tList, объекты docInfo и additInfo.

Декомпозируем задачу. Для начала отправим следующий промпт (запрос) в ChatGPT (просим нейросеть преобразовать в формат спецификации OpenAPI текстовое описание объекта additInfo):

Перепиши нижеприведенный текст в формате схемы описания OpenAPI. additInfo / Object (Дополнительная информация) creationTS / date-time (Дата и время формирования записи) = document / O_list / CreationDataTime currencyCode / string (Код валюты) = document / O_list / Currency_code enteredAmount/ number (Сумма в коде валюты) = document / O_list / Entered_amount operationName/ string (Наименование операции) = document / O_list / Operation_name effectiveDate / date (Дата вступления в силу учетных данных) = document / O_list / Effective_Date

Обращаю внимание, что описание объекта из Confluence я взяла как есть ― ИИ получил на вход текст без дополнительных пояснений и форматирования. Есть только требование, в какой формат требуется преобразовать текст. Плюс во входных данных присутствует лишняя информация, которая для формирования нашего YAML не требуется.

Получаем следующий ответ от ChatGPT (все ответы модели привожу без правок):

components: schemas: AdditInfo: type: object description: "Дополнительная информация" properties: creationTS: type: string format: date-time description: "Дата и время формирования записи" currencyCode: type: string description: "Код валюты" enteredAmount: type: number description: "Сумма в коде валюты" operationName: type: string description: "Наименование операции" effectiveDate: type: string format: date description: "Дата вступления в силу учетных данных" required: - creationTS - currencyCode - enteredAmount - operationName - effectiveDate

На мой взгляд, выглядит неплохо. ИИ правильно извлек из входящего текста наименования атрибутов, их тип, описание и верно составил описание в требуемом формате. Можно было бы придраться, что был добавлен блок required с обязательными атрибутами, хотя явных указаний из входящего задания на это не было, но при необходимости этот блок можно убрать или скорректировать.

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

 currencyCode: type: string description: "Код валюты" example: "USD" enteredAmount: type: number description: "Сумма в коде валюты" example: 12345.67

Теперь отправим аналогичный промпт для объекта docInfo. Получаем следующий ответ:

components: schemas: DocInfo: type: object description: "Данные о документах" properties: formCode: type: string description: "Тип входящего формуляра" example: "FORM001" docTypeCode: type: string nullable: true description: "Тип первичного учетного документа" example: "INV" docName: type: string nullable: true description: "Наименование первичного учетного документа" example: "Invoice" docNumber: type: string description: "Номер первичного учетного документа" example: "12345-INV" docDate: type: string format: date description: "Дата первичного учетного документа" example: "2024-03-18" required: - formCode - docNumber - docDate

Можно отметить, что модель учла мои пожелания из предыдущего запроса ― добавила примеры. Интересно, что для атрибутов, для которых в исходном DTO было указано: «если не нашли, то null», модель добавила «nullable: true» в результирующем описании схемы. Также в этот раз модель правильнее составила блок required: вместо добавления всех атрибутов добавлены только те, для которых не указано «nullable: true».

Далее аналогичным образом отправляем на обработку ИИ описание массива tList.

Получим следующий результат:

components: schemas: TList: type: array description: "Проводки" items: $ref: '#/components/schemas/Transaction' Transaction: type: object description: "Информация о проводке" properties: id: type: integer nullable: true description: "Идентификатор проводки" example: 12345 account: type: string nullable: true description: "Счет учета" example: "03224" valCode: type: string nullable: true description: "Код валюты" example: "USD"

В ответе мы видим, что модель отдельно выделила запись массива Transaction, описала для этой записи схему данных, а только потом дала описание для массива TList, где для элементов массива уже вставлены ссылки $ref на схему элементов массива. Отмечу, что каких-то дополнительных указаний в промпте для этого я не давала, исходный промпт по-прежнему выглядел как «Перепиши нижеприведенный текст в формате схемы описания OpenAPI» с указанием текста DTO.

Завершающим шагом попросим модель объединить эти результаты в итоговую схему. Отправляем промпт «Перепиши нижеприведенный текст в формате схемы описания OpenAPI. tList сделай массивом, для элементов массива сделай ссылки на схему. docInfo и additInfo — элементы oList. docInfo и additInfo сделай объектами с указанием ссылки на их схемы. Все эти схемы ты составлял в предыдущих ответах» и приводим описание DTO для массива oList (описание для docInfo и additInfo в промпт не включаем).

Получим следующий результат (описание схем для Transaction, DocInfo и AdditInfo для краткости не привожу, т.к. результат по ним совпадает с тем, что приводила ранее):

components: schemas: OList: type: array description: "Список записей" items: $ref: '#/components/schemas/OListItem' OListItem: type: object description: "Запись учета" properties: exKey: type: string description: "Ключ логической ссылки" example: "d3f4c5b7-1a2b-3c4d-5e6f-7g8h9i0j1k2l" status: type: string nullable: true description: "Статус записи" enum: - COMPLETED - CANCELED example: "COMPLETED" amount: type: number description: "Сумма в рублях" example: 12345.67 tList: type: array description: "Проводки" items: $ref: '#/components/schemas/Transaction' docInfo: $ref: '#/components/schemas/DocInfo' additInfo: $ref: '#/components/schemas/AdditInfo'

Модель правильно сформировала схему, расставила ссылки на описания отдельных элементов, как это и требовалось. Также были переиспользованы результаты предыдущих запросов. Из них были извлечены описания для Transaction, DocInfo и AdditInfo. Для атрибута «status», где в исходном DTO было определено множество возможных значений, модель добавила в описание enum.

Если попросить модель привести описание блока paths: с результатом oList, получим следующий результат.

paths: /o-list: get: summary: Получение списка записей учета description: Возвращает список записей учета (oList) с детализацией проводок, документа и дополнительной информации. operationId: getOList tags: - Records responses: '200': description: Успешный ответ, содержащий список записей учета. content: application/json: schema: $ref: '#/components/schemas/OList' '400': description: Неверный запрос. content: application/json: schema: type: object properties: error: type: string description: Описание ошибки example: "Некорректный формат запроса" '500': description: Внутренняя ошибка сервера. content: application/json: schema: type: object properties: error: type: string description: Описание ошибки example: "Внутренняя ошибка сервера"

Таким образом, мы получили черновик нашего YAML. Понятно, что это только набросок, но модель сделала за нас большую часть рутинной работы, и нам потребуется немного времени, чтобы довести это описание до финального состояния.

Допустим, у нас модель данных описывается иначе, например, тело ответа нашего API описано в виде таблицы:

Рисунок 3. Пример модели данных тела ответа API в виде таблицы
Рисунок 3. Пример модели данных тела ответа API в виде таблицы

Если отправить в ChatGPT промпт: «Вот таблица. Перепиши текст в формате схемы описания OpenAPI» и вставить таблицу (простым копированием без каких-либо дополнительных пояснений). Получаем такой ответ от модели:

 components: schemas: EntityRecord: type: object description: Запись сущности с уникальным идентификатором, ссылками на версии и метаинформацией. properties: gid: type: string format: uuid description: Уникальный идентификатор записи. example: "123e4567-e89b-12d3-a456-426614174000" ex_key: type: string format: uuid description: Ключ логической ссылки на запись сущности. example: "123e4567-e89b-12d3-a456-426614174001" ex_version_key: type: string format: uuid nullable: true description: Ключ логической ссылки на группу набора версий сущности. example: "123e4567-e89b-12d3-a456-426614174002" entity_version: type: string maxLength: 63 nullable: true description: Версия сущности. example: "v1.0.0" created_ts: type: string format: date-time description: Дата и время создания записи (служебное поле). example: "2024-11-12T14:37:00Z" updated_ts: type: string format: date-time description: Дата и время изменения записи (служебное поле). example: "2024-11-12T15:00:00Z" required: - gid - ex_key - created_ts - updated_ts

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

Выводы

  • ChatGPT может значительно сократить трудозатраты системного аналитика по формированию спецификации OpenAPI, взяв на себя наиболее рутинную часть задачи.

  • ChatGPT максимально полезен для задач преобразования текста из одного формата в другой по определенным правилам. Особенно когда речь идет про преобразование в/из формата, который хорошо знаком ChatGPT. Абсолютно согласна с Юрием Куприяновым:   «Вообще выглядит так, что нейросеть сильно облегчает именно часть работы по складыванию букв в слова и слов в фразы, а вот образ результата вам нужно заранее себе представлять хоть в каком-то виде (или в детальном, смотря насколько в детали вы собираетесь уходить)».

  • Наилучший результат от нейросети удается получить, если декомпозировать исходную задачу и давать языковой модели задания последовательно, начиная с простых и постепенно усложняя задачу. Если сразу дать в одном промпте сложную большую задачу, ChatGPT начинает отлынивать, может прервать ответ и написать что-то из серии «далее аналогично», пропускать строки и т.п.

  • Нужно обязательно проверять результат работы ChatGPT. Относимся к нему как к младшему, достаточно смышленому специалисту, которому можно поручить рутинную часть задач. За ним проверяем? Конечно! Результат он выдает приемлемый, очень похожий на правду, но, вполне возможно, потребуется немного ваших правок. В любом случае времени на эти правки вы потратите значительно меньше, чем если бы сами делали эту задачу самостоятельно. Плюс большая часть рутины будет не на ваших плечах.

Порой слышу рассуждения о том, заменит ли нас ИИ. Я согласна с Теренсом Тао, математиком, обладателем Филдсовской премии, что ИИ стоит рассматривать не как конкурента, а как помощника, так как «у людей и машин очень разные сильные стороны, которые следует рассматривать как дополняющие друг друга, а не конкурирующие», и «лучше рассматривать ИИ как дополнительный способ выполнения задач». ИИ может быть хорошим помощником и взять часть рутины на себя, чтобы у нас было больше времени заниматься тем, что мы любим — интересными задачами и творчеством. Я верю, что со временем ИИ не заменит нас, а позволит выйти на новый уровень, решать более сложные и масштабные задачи.

Всем добра.

*Статья написана в рамках ХабраЧелленджа 3.0, который прошел в ЛАНИТ осенью 2024 года. О том, что такое ХабраЧеллендж, читайте здесь.

© Habrahabr.ru