Причина по которая я захотел поделиться своим опытом простая. В каждой средней компании есть много микросервисов и различных АПИ. Команды пилять микросервисы. Каждая команда взаимодействует друг с другом. Спасибо большое Фастапи и ее создателю за то что сделали прекрасынй фрейморк на основе пайдентика генерирующий все необходимые спеки. Но проблема все еще актуальна, некоторым стартапам не хватает времени писать нормальную документацию - дак может и не надо, если часть кода уже генерируется.
\
\ Я решил попробовать провести эксперимент и соединить Swagger и с OpenAI и посмотреть что из этого получиться. Это статья туториал / гайд для тех кто хочет узнать чем все кончилось и как я это сделал на основе RAG with ChromaDB and Langchain.
\ Цель
Fast to find. Information and ask about something “How to create item with API?“
\ Все шаги в RAG делятся на 3 части
\
Пишем сплиттердля того чтобы написать сплиттер обычно лангчейн предоставляет много заготовленных штук, но так как в нашем случае спецификация это список запросов которые связаны с моделями лучше всего взять и разбить это на просто запросы. Для этого я написал простой снипет. Это максимально простая функция кторая разделяет всю полученную спеку на простые части. И добавляет связанные модели к патчу.
def get_openapi_spec_paths(specification: dict) -> dict: paths = [] for p in specification["paths"]: for m in specification["paths"][p]: path = specification["paths"][p][m] path["method"] = m path["path"] = p paths.append(path) return paths Загрузка документовПосле того как мы разделили на чанки нам нужно их заэнкодировать в векторное предстовление и загрузить в хрома дб. В дальнейшем перед тем как делать запрос в чат гпт мы сделаем векторный поиск по эмбедингам чтобы достать релевантные документы.
\
import json from langchain.docstore.document import Document from langchain_openai import OpenAIEmbeddings from langchain_chroma import Chroma specification = get_openapi_spec(url) paths = get_openapi_spec_paths(specification) dumped_paths = dump_openapi_spec_to_chroma_docs(paths) # Dump documents to Chroma documents for p in paths: dumped_paths.append( Document(page_content=json.dumps(p), metadata={"source": "local"}) ) # Init embeddings model embeddings = OpenAIEmbeddings( model="text-embedding-ada-002" ) # Upload to database Chroma.from_documents( documents=dumped_paths, embedding=embeddings, persist_directory="data", collection_name="spec", )\ В этом примере, мы сохраняем данные локально в директорию ‘data’, но Chroma может работать как и в короткой памяти так и как отдельный инстанс.
Пишем ретривер и чейнМы готовы к тому чтобы сделать первый запрос. Я загрузил пример простого API про собачек, который написал на фастапи и добавил ссылку в скрипт.
\ В качестве модели мы будем использовать chatgpt-4o
embeddings = OpenAIEmbeddings(model=settings.OPENAI_API_EMBEDDINGS_MODEL) llm = ChatOpenAI(api_key=settings.OPENAI_API_KEY, model=settings.OPEN_API_MODEL) chroma_db = Chroma( persist_directory="data", embedding_function=embeddings, collection_name="spec", ) retriever = chroma_db.as_retriever() prompt = PromptTemplate.from_template( """ System: You are an assistant that converts OpenAPI JSON specs into neatly structured, human-readable text with summary, description, tags, produces, responses, parameters, method, and curl examples. EXAMPLE ANSWER: ### GET /dogs **Summary**: Retrieve a list of dogs **Description**: Get a filtered list of dogs based on query parameters such as breed, age, or size. **Tags**: Dogs **Produces**: application/json **Parameters**: - **Query**: - `breed` (string, optional): Filter by dog breed. - `age` (integer, optional): Filter by dog age. - `size` (string, optional): Filter by dog size (small, medium, large). **Method**: GET **Curl Example**: ```sh curl -X GET "https://api.example.com/dogs?breed=labrador&size=medium" -H "Authorization: Bearer\ Я докуртил few shot промп и финальную версию можно увидеть в гитхаб репозитори.
Пример на основе LammaЕсли вы не готовы использовать OpenAPI в целях например безопасности или по другим причнам, всегда есть возможность запустить на Lamma7B для этого установить себе ламу. Спульте и заменить llm на
\
# pull model before usage # ollama pull llama3.1 from langchain_ollama import OllamaLLM llm = OllamaLLM(model="llama3.1:8b") Lets Add StreamLeteкуда же без этого молодца? В наших экспериментах? Даа.
\
import streamlit as st if "messages" not in st.session_state: st.session_state.messages = [] for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) if prompt := st.chat_input("Enter your message."): st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.markdown(prompt) with st.chat_message("assistant"): if (len(st.session_state.messages)): r = chat_with_model(st.session_state.messages[-1]["content"]) response = st.write(r) st.session_state.messages.append({"role": "assistant", "content": response})\ Пару строк и получаем что то что мы уже можем трогать.
\
Результаты и тестовая спекаВ рещультате я проетстировал
\ Резудльтаты впечатляют, все зависит от модели. А теперь подумайте сколько времени сэкономит это компаниям?
ЗаключениеЕсли вы нашли эту статью и эксперимент полезной для себя, поставьте звезду на гитхаб! RAG помог мне сохранить время и силы разобраться в разных спеках различных команд. В любом случае чем понятнее контекст и так далее тем улчше. Поэтому пишите качесвенно докстринги и документацию Фастапи и пайдентик в этом помогут.
\ Ссылка на гитхабыч чтобы запустить проект локльно (не забудь нажать на звездочку)
All Rights Reserved. Copyright , Central Coast Communications, Inc.