
Всем привет! На связи команда 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 и положить его в тестовый сценарий (помним, что требования неконсистентные).
Это был один из первых написанных нами агентов. Вот его алгоритм:
На вход подаётся схема запроса (в любом виде).
Агент преобразовывает её в 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:
Владимир Казурин
Александра Якунина
Мария Лаврентьева
Ranger21
Запрос и промпт простейшие, а качество ответа зависит от модели, чем сейчас GigaChat страдает...