Как мы с ChatGPT спецификацию OpenAPI писали: опыт системного аналитика
Приветствую, коллеги! Я ведущий системный аналитик в компании ЛАНИТ. В настоящее время я работаю на крупном государственном проекте в области финансов. В своей статье я решила поделиться этапами совершенствования процесса описания интеграционного взаимодействия на отдельно взятом проекте нашей компании и рассказать, как благодаря использованию генеративной модели ChatGPT мы ускорили процесс формирования документации OpenAPI и частично снизили связанные с этим трудозатраты системных аналитиков.
Статья может быть полезна системным аналитикам и командам, проектирующим API по принципу Design API First (Contract first). Материал основан на реальном опыте системных аналитиков нашего проекта и не является руководством или призывом к действию.

На проекте с микросервисной архитектурой для меня и для многих моих коллег обыденной задачей является формирование требований на реализацию API (Application programming interface) — контрактов взаимодействия между различными компонентами системы, микросервисами, смежными системами и т.д.
Этапы совершенствования процесса документирования API
Можно выделить несколько этапов усовершенствования описания API в команде нашего проекта. Далее я кратко остановлюсь на каждом из них, перечислю их достоинства и недостатки, а также поделюсь опытом и результатами использования ChatGPT при написании спецификации OpenAPI.
Первый этап: генерируем спецификацию API на основе кода (подход Code first)
Требования на реализацию API, как и в целом к системе, мы оформляем в Atlassian Confluence. Для операций API указываются путь запроса, выполняемое действие, тело запроса и ответа (в виде модели данных, DTO (Data Transfer Object)), статусы ответов.
Наше описание DTO — это человеческое описание JSON-схемы. Оно включает единичные объекты, массивы объектов и атрибуты. Вложенность объектов друг в друга или атрибутов в объекты имитируется иерархичностью представления. Для каждого объекта/атрибута указываются название, тип, комментарий, сведения об обязательности. Такой подход к описанию спецификаций требований к разработке создан и внедрен нашим руководителем группы аналитиков на проекте.
Вот пример одного из таких DTO. В этом случае также указывается способ формирования значений атрибутов.

В целом эти правила описания DTO специфичны исключительно для нашего проекта, но их стоит упомянуть, потому что это важно для дальнейшего повествования.
На первом этапе модели данных, описанные приведенным выше способом, и описания API аналитики передавали в виде постановок на разработку команде разработчиков, которые на их основе реализовывали в коде API. Далее уже на базе существующего кода генерировалась спецификация OpenAPI. Фактически использовался подход Code first: сначала пишем код, потом генерируем по нему контракт.
Основной недостаток первого подхода заключается в том, что API и контракт, который мы получаем на выходе от разработчика, часто не соответствует тому, что было на входе в постановке от аналитика (разработка может реализовать немного другую модель данных, переименовать атрибуты и т.п.). К сожалению, аналитик об этом узнает только тогда, когда разработчик предоставит реализованный API. Да, эту проблему можно в какой-то степени нивелировать дополнительными этапами согласования постановки между аналитиком и разработкой, но разница между постановкой и конечным результатом, как правило, имеется. Иногда существенная. Как следствие:
достаточная разница по времени между тем, как аналитик описал API в спецификации в Confluence и когда получил готовый жесткий контракт API;
аналитику приходится дорабатывать спецификации в Confluence по факту реализации API, чтобы иметь его актуальное описание, доступное для дальнейшего использования другими членами команды, например, представителями тестирования и сопровождения.
Второй этап: генерируем программный код на основе спецификации OpenAPI (подход Contract first)
На втором этапе описанный выше процесс создания документации и разработки API был усовершенствован.
Мы применили так называемый подход Contract first — сначала создаем контракт, потом по нему пишем или генерируем код.
Процесс можно представить в виде последовательности шагов.
Аналитик по-прежнему начинает с описания требований к API в Confluence.
Требования к API оформляются аналитиком в виде YAML-файла — спецификации OpenAPI (ссылка на актуальную версию спецификации).
Такой файл можно оформлять в любом доступном текстовом редакторе, но мы используем Swagger Editor. Он позволяет редактировать спецификации OpenAPI в YAML внутри браузера. У вас есть возможность просматривать документацию в режиме реального времени — слева пишем текст спецификации, справа отображается полнофункциональный интерфейс Swagger UI.

Разработанная таким образом спецификация OpenAPI согласовывается с разработкой и в дальнейшем является конечным жестким контрактом взаимодействия между всеми членами ИТ-команды. YAML-файл — место, где изложены единые, согласованные и используемые всеми членами команды требования к API.
На основе спецификации 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 описано в виде таблицы:

Если отправить в 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 года. О том, что такое ХабраЧеллендж, читайте здесь.