От слепых котят к ИИ-гуру: история автоматизации тестирования в Сбере

от автора

Всем привет! На связи команда Take it easy. Название говорит само за себя: мы упрощаем жизнь другим командам в релизном цикле и повышаем эффективность производственного процесса. 

В любой разработке много времени отнимает тестирование. Поэтому мы решили автоматизировать создание тестовых сценариев API, чтобы помочь тестировщикам. Применили ИИ-инструмент APISpecGen для анализа спецификаций новых API-требований, генерации соответствующих тестовых сценариев, обезличенных тестовых данных по схемам запрос/ответ и select-запросов с помощью GigaChat.

Как работает APISpecGen

На вход мы подаём требования в любом формате (ссылка на Confluence, текстовый файл). На выходе получаем чек-лист идей для проверок в следующем формате:

  • название;

  • цель;

  • предусловия;

  • шаги (с ожидаемым результатом).

Почему именно API-тесты

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

Слишком много текста и токенов

Требования бывают разной длины и разного формата, могут включать в себя таблицы. Поэтому мы сразу же столкнулись с проблемой: работа с контекстом заканчивалась слишком быстро и была некачественной. Сначала мы разделили текст и стали работать только с теми фрагментами, которые нужны в момент выполнения задачи. Но это не слишком помогло: количество используемых токенов по-прежнему зашкаливало, а результат не улучшился. И тут на помощь пришли эмбеддинги.

Эмбеддинги

Представьте себе два вектора в пространстве. Когда они равны? Когда их длина и направление одинаковы. Так вот, эмбеддинги — это способ представить слова в виде числовых векторов. Чтобы не только снизить количество токенов при поиске по тексту, но и помочь модели лучше улавливать смысл слов и фраз при работе с контекстом.

Вот простейший пример использования эмбеддингов:

from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.document_loaders import TextLoader  loader = TextLoader('требования.txt') documents = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0) texts = text_splitter.split_documents(documents)  from langchain_community.vectorstores import Chroma from langchain_gigachat.embeddings.gigachat import GigaChatEmbeddings  embedding_model = GigaChatEmbeddings(     base_url="https://gigachat-ift.sberdevices.delta.sbrf.ru/v1",     ca_bundle_file="certs/chain_pem.txt",     cert_file="certs/published_pem.txt",      key_file="certs/TakeItEasy.key",     verify_ssl_certs=False )  from langchain_gigachat.chat_models import GigaChat  llm = GigaChat(     base_url="https://gigachat-ift.sberdevices.delta.sbrf.ru/v1",     ca_bundle_file="certs/chain_pem.txt",     cert_file="certs/published_pem.txt",      key_file="certs/TakeItEasy.key",     temperature=0.2,     model='GigaChat',     timeout=10)  vectorestore = Chroma.from_documents(     texts,     embedding=embedding_model, )  retriever = vectorestore.as_retriever()  # rs = vectorestore.similarity_search('Какой Path и method для отправки запроса на сервис')  from langchain.chains import create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain from langchain_core.prompts import ChatPromptTemplate  system_prompt = (     "Ты должен ответить на вопрос пользователя с использованием данных из текста.\n"     "Отвечаай коротко, не более 2-3 предложений.\n"     "Вот части текста для ответа:"     "\n\n"     "{context}" )  prompt = ChatPromptTemplate.from_messages(     [         ("system", system_prompt),         ("human", "{input}"),     ] )  question_answer_chain = create_stuff_documents_chain(llm, prompt) rag_chain = create_retrieval_chain(retriever, question_answer_chain)  rag = rag_chain.invoke({"input": 'Какой Path и method для отправки запроса на сервис'})["answer"] print(f"RAG: {rag}")

Ответ:

RAG: для отправки запроса на сервис SearchElectionProduct необходимо использовать метод POST и endpoint /v2/oc/products/search.

С помощью модели для векторного представления текста мы превращаем требования в векторы и кладём их в векторную БД (например, Chroma). Когда мы делали это первый раз, не до конца понимали, как оно работает. Поэтому загрузили в БД целое требование. Эффекта BOOM не вышло ☹ 

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

  • название и описание;

  • path;

  • method;

  • схема запроса;

  • схема ответа;

  • логика.

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

Агенты и RAG

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

  • придумать название ТК;

  • выделить цель проверки; 

  • выделить предусловия (если требуются) и подготовить тестовые данные;

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

С наименованием проблем не возникло, сформировали его исходя из чек-листа проверок и названия сервиса, промпт подобрали очень быстро. С целью тоже всё было просто.

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

Каждый слышит, как он дышит.

Как он дышит, так и пишет,

не стараясь угодить…

Булат Окуджава

На входе был набор требований от разных аналитиков: где-то данные были представлены в виде JSON-схемы, где-то был Swagger (API-studio), где-то таблица с большим количеством colspan.

Надо было не только структурировать информацию, но и проверять её наличие. Если требования неполные — запрашивать необходимые данные или предусмотреть сценарий, как обходиться без них. 

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

Это был один из первых написанных нами агентов. Вот его алгоритм:

  1. На вход подаётся схема запроса (в любом виде).

  2. Агент преобразовывает её в JSON-схему и заполняет синтетическими данными, максимально опираясь на контекст схемы (описание поля, обязательность, тип, паттерн и т. д.).

Так появились первые инструменты и цепочки вызовов, которые разбирались, в каком виде пришла схема, и преобразовывали её в синтетический JSON для написания тестового сценария.

В агент мы заложили пример того, как должны выглядеть требования. И ответом от ИИ здесь должен быть аргумент (structured_output), с помощью которого мы выбираем тот или иной путь обработки данных и тем самым получаем необходимый JSON.

RAG и structured_output помог нам добиться более стабильного результата. Мы получали цельную картину. Алгоритм несложный: «Покажи, что подаётся на вход и что хочешь получить, уточни детали — и получишь более точный результат».

К сожалению, with_structure_output не всегда стабильно работает с GigaChat, поэтому мы парсили ответ с помощью PydanticOutputParser.

from typing import List from pydantic import BaseModel, Field  #Описываем модель  class CheckList(BaseModel):     """Список проверок логики работы сервиса"""     check_list: List[str] = Field( description="Список проверок")      from typing import Optional  from langchain_core.prompts import ChatPromptTemplate from langchain_core.pydantic_v1 import BaseModel, Field from langchain_core.output_parsers import PydanticOutputParser  parser = PydanticOutputParser(pydantic_object=CheckList)  prompt = ChatPromptTemplate.from_messages(     [          (             "system",             "Твоя задача придумать и требований список проверок."             "Список нужно вернуть в формате JSON\n{format_instructions}\n"             "В ответе верни только JSON со списком проверок без ```json ... ```"         ),         ("human", "ТРЕБОВАНИЯ: {query}"),     ] ).partial(format_instructions=parser.get_format_instructions())  chain = prompt | llm |parser query = "Придумай список проверок логики для требований и верни ответ в виде json"  rs = chain.invoke({"query": doc_file}) print("\n".join(rs.check_list))

Цепочки вызовов и агенты — это самое крутое, что есть в LLM, поэтому мы познакомились с LangGraph. Он позволяет с лёгкостью сделать всё, о чём мы рассказали!

Внедрив APISpecGen в наш производственный процесс, мы значительно увеличили покрытие требований тестовыми сценариями (до 70 % в некоторых случаях). Это повысило качество выводимого в пром ПО с той же длительностью тестирования. Команды Сбера с удовольствием пользуются этим инструментом. Уже сформирован бэклог задач на доработку APISpecGen по требованиям от различных систем.

Выводы

ИИ — мощный инструмент, которым можно решать практически любую задачу. А чтобы понимать все его возможности, попробуйте использовать его в рутинных задачах. Вы и не заметите, как разберётесь, на что он способен и как его правильно применять.

Команда AIKIBTeam:

  • Владимир Казурин

  • Александра Якунина

  • Мария Лаврентьева


ссылка на оригинал статьи https://habr.com/ru/articles/914080/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *