How to write your neuroemployee?
RAG (Retrieve and Generate) is a hybrid model that combines methods of information retrieval and text generation. It consists of several simple components: retriever, generator, interface.
![](https://habrastorage.org/getpro/habr/upload_files/ac8/c9c/5d6/ac8c9c5d686bb18eae9db40564a419ee.png)
If we deal with the latter a little later, trying to show our employee in the web version, it’s better to tell a little about the retriever and the generator.
Retriever is an information retrieval module. It is often based on models: BM25, BERT, which are used to find relevant documents in a large array of data.
The information extraction module performs the search function: it takes an input query, processes it, and returns a list of documents or text fragments that may contain answers to the query.
At this stage, text vectorization methods and calculation of similarity between the query and documents in vector space are used. The retriever's task is to collect context for our chatbot.
The second component is the text generation module (generator). Most often, autoencoders or transformers are used here, the same GPT 4.0 and other models.
This module takes as input the data received from the information extraction module and, based on this data, generates a coherent and meaningful response.
The generation module can work through attention mechanisms, which allow the model to focus on the most relevant parts of the input text when generating a response. And I haven't come up with anything better yet…
But there are problems here too. Since Generator uses the context you have prepared, the quality of the answers it formulates will depend on how well the data is prepared.
There is an interaction interface between these two modules, which ensures the transfer of data from the extraction module to the generation module.
In programming, this can be thought of as the process of passing data through an API or intermediate storage where search results are temporarily stored before being processed to generate a response.
The entire RAG structure also includes mechanisms for pre-training and post-training of models. During the pre-training phase, both the retriever and generator models are trained on large vectorized datasets to create initial representations.
They are then adapted to specific tasks or domains during the post-training phase, improving their accuracy and relevance.
How do vector databases work?
![](https://habrastorage.org/getpro/habr/upload_files/0bb/0e4/b2c/0bb0e4b2cfb0ed09694ac8ad52d6a36a.png)
We remind you that for the neural network to work properly, data structuring is required, which is only possible through vectorization.
Vector databases begin with the indexing phase, during which all vector representations of data are organized into a structure that allows efficient access to them.
Index – a kind of identification number that can be referred to.
Tokenization breaks text into individual words or phrases (tokens), normalization reduces words to a single form (for example, all letters become lowercase), and lemmatization reduces words to their base form (lemmas).
This way we unify the text and reduce its size.
After tokenization and normalization, we come to building an index structure. In classical text systems, indexing is often implemented through the construction of an inverted index.
An inverted index is a method of indexing and structuring data, where each token (word) is matched with a list of documents containing this token. This structure allows you to quickly find all documents where a given word occurs, which significantly speeds up the process of searching by keywords.
There are many popular indexing methods: KD-trees (k-dimensional trees) or cover trees and other “plants” that lead to the tokens we need.
KD-trees help organize and search data in high-dimensional spaces.
Imagine a set of points on a plane. The KD tree splits this plane into parts, first along one coordinate (for example, along the X axis), and then along another (for example, along the Y axis), and so on, alternating coordinates at each level.
![](https://habrastorage.org/getpro/habr/upload_files/8e9/52a/66d/8e952a66d0ccde1e40eae04b817cf45a.png)
It's similar to how you might divide a piece of paper first with a vertical line, then with a horizontal line, and so on.
Each point ends up in a small region. When you need to find the closest point to a given point, a KD tree allows you to quickly narrow the search to these small regions, significantly speeding up the process compared to simply enumerating all the points.
Another method is Locally Sensitive Hashes (LSH), which group vectors by spatial proximity based on hash functions specifically designed to preserve spatial proximity.
![](https://habrastorage.org/getpro/habr/upload_files/de0/017/4a2/de00174a215ebf1c07694b35c43bf951.png)
When the user requests data, the request is also converted into a vector representation.
The database then looks for vectors that are closest to the query vector.
The metric used to determine proximity is often based on the distance of vectors in space, such as the cosine distance, Euclidean distance, or Manhattan distance. The choice of metric depends on the specific characteristics of the data and the problem.
![](https://habrastorage.org/getpro/habr/upload_files/538/e28/da6/538e28da6f595143b58eb73cde10cca9.png)
Various optimization techniques are used to speed up the search. One such technique is the use of approximate nearest neighbor search methods (ANN).
These methods can significantly reduce search time while sacrificing accuracy, which is an acceptable trade-off in most cases.
HNSW (Hierarchical Navigable Small World) is an effective ANN method that builds graphs to quickly find nearest neighbors.
During query execution, the query vector is compared with the indexed vectors, and the results are sorted by their proximity to the query.
This process may include filtering and ranking steps to eliminate irrelevant results and improve search accuracy.
Search results can be presented as a ranked list, with the most similar vectors listed first.
We need all this to adequately search for meaningful answers.
If we simplify the process of the retriever working with a vector database, then everything looks something like this:
Retriever takes a user's query and searches for the most relevant documents or pieces of text in a large data collection.
![](https://habrastorage.org/getpro/habr/upload_files/4f9/fcf/5a4/4f9fcf5a4eaf962830e7a27a62111e3a.png)
To do this, each document in the database is first transformed into a vector representation using transformer models such as BERT or RoBERTa, which are able to capture deep semantic and contextual connections in the text.
BERT in the context of a retriever works like this: it transforms query and text from documents into high-dimensional vector representations that capture their semantic content.
Unlike traditional models that process text sequentially, BERT examines the entire text, both left to right and right to left.
When a request comes in, it is also converted into a vector representation.
Then, the nearest neighbor search method is used to compare the query vector with the document vectors.
This comparison allows you to determine which documents are closest in meaning to the request. The process is similar to searching in a multidimensional space, where each point represents a vector representation of a document or query.
Let's say the search is performed using the nearest neighbors method: KD-trees or HNSW (Hierarchical Navigable Small World).
Once the closest vectors are found, the corresponding documents or text fragments are retrieved from the database.
This extracted data is then passed to the generative model (generator).
The generative model, a favorite of GPT 4.o, uses the information received to form a response that takes into account the context of the request and the retrieved documents.
What types of vector databases are there?
![](https://habrastorage.org/getpro/habr/upload_files/7f5/1a5/4ef/7f51a54ef1c816e0a45cbb3f43c8b466.png)
Sure, you can write your own… But there are already enough solutions on the Internet for AI enthusiasts. Specialized open-source vector databases: Chroma, Vespa, LanceDB, Milvus…
These databases are designed exclusively for working with vectors – they have high performance and scalability for nearest neighbor search, clustering and other operations related to vector processing.
Often used in applications that require fast and accurate search of large data sets: recommender systems, natural language processing, and computer vision.
Commercial specialized vector databases include, for example, Pinecone and Weaviate. This includes infrastructure management, high availability, and scalability under large amounts of data. But we are poor, so commercial offers are not for us…
On the other hand, databases that support vector search include OpenSearch and PostgreSQL, as well as commercial solutions such as Elasticsearch and Redis.
These databases weren't originally designed to work exclusively with vectors, but they did integrate functionality for searching vector spaces – they're also suitable. Although we'd recommend Vespa or Chroma.
The stage of ranking answers to user queries
You don't want an irrelevant answer to appear on the customer's screen… There are several ranking schemes. The simplest one is to ask for an LLM ranking. But there are other methods.
For example, Cross Encoder takes each of the documents and matches them with the original query. It evaluates not only the presence of keywords, but also their context, which allows you to take into account the complex relationships between words and phrases.
For example, if a user's query is related to the latest advances in pyrotechnics, Cross Encoder can assess how detailed and relevant each document is in covering that particular topic.
![](https://habrastorage.org/getpro/habr/upload_files/d09/7b7/371/d097b73715b122b8986692cfc3c1448e.png)
After each document receives its relevance score, the system ranks all documents according to these scores. Highly rated documents move to the top of the list, while less relevant ones move down.
From this sorted list, the top 10 documents are selected that most accurately and completely answer the user's request. Thus, Cross Encoder acts as a filter, improving the initial set of results obtained from the vector database.
By the way, if your client’s requests are too complex and he writes some nonsense, you can use the RAG Fusion method and extract different answers from the language model and, using the same Cross Encoder, compare the relevance of the answers to the requests, choosing the best one.
Another option, a little more complicated, is to use Reciprocal Rank Fusion (RRF). Another method that is used is to combine multiple ranking lists to create a more accurate final list.
This method is useful when there are multiple data sources or different ranking algorithms whose results need to be combined.
![](https://habrastorage.org/getpro/habr/upload_files/e27/cd4/5ac/e27cd45ac4513265fa1c466443477178.png)
The way RRF works is to assign a weight to each result based on its position in the source lists.
You have several ranked lists obtained using different methods or from different databases. In each list, the document in the first position receives the maximum weight, for example, 1, in the second position – 1/2, in the third – 1/3, and so on.
The weight of each document is inversely proportional to its position in the list. For each document, its weights from all lists in which it is present are summed up. The final list is ranked by the total weights of the documents.
This approach allows taking into account multiple assessments and smoothing out errors or shortcomings of individual ranking methods.
For example, if one algorithm missed an important document, but another algorithm ranked it high, the resulting list generated by RRF would still be able to highlight that document due to its combined weight.
We pre-register the necessary prompts for our model and prepare the web interface.
Interface… we completely forgot about it. It is quite difficult to write a “connection” between the language model in Python, so it is worth using the LangChain framework.
In the framework, you can add indexes and use them in chains, write prompts, and link models with HuggingFace or GPT via the OpenAI API.
Using chains, create a multineuron with the interaction of several models. Through agents you can access external sources of information.
The utility also allows you to collect long-term and short-term memory for a neuroworker. We will limit ourselves to indexes, models, and prompts – these modules are enough to write a simple neuroworker.
A demo version of the web interface can be developed via JS, but we will again use Gradio… Fortunately, the latter supports blocks and the appearance can be flexibly changed.
![](https://habrastorage.org/getpro/habr/upload_files/bc9/1b2/90e/bc91b290eb2f3af7f17b7bd4f329fae6.png)
Here you can rely on different options, but in order not to mess around with JavaScript, it is better to go along the path of a simple solution: Gradio (a good library for demo web interfaces) + LLM integration via LangChain.
Writing our own neuroscientist
We will use a ready-made vector database – Chromawhich has an MIT license – it is open for commercial use. Next, we will sew comments into the code and add separate explanations of what is happening in the code. We will still have to figure it out.
![](https://habrastorage.org/getpro/habr/upload_files/777/85f/b31/77785fb3129b65035a6fda2c81b679cd.png)
We install all utilities via !pip: from openai for formatting requests to langchain for integration and gradio for creating a web interface.
!pip install openai gradio tiktoken langchain langchain-openai langchain-community chromadb
Now we need to deal with our neuro-employee class.
We simply enter models into the list. We sew up the necessary docs there, and also write out some commands. You can look below, even the documents via the links provided.
models = [
{
"doc": "https://docs.google.com/document/d/1f7Gfv2PZYACD1PGzlonfBZsWI9Pf9ZOFI_xYp3DRxw0/edit",
"prompt": '''Ты менеджер поддержки hr отдела, к тебе могут обращаться hr менеджеры и рекрутеры за подсказками и ответами на их вопросы в чате компании.
Постарайся дать развернутый ответ, твоя задача ответить так, чтобы у менеджера не осталось больше вопросов к тебе.
Отвечай по существу, без лишних эмоций и слов, от тебя нужна только точная информация.
Отвечай максимально точно по документу, не придумывай ничего от себя.
Документ с информацией для ответа клиенту: ''',
"name": "Нейро-менеджер поддержки HR-отдела",
"query": "Для чего нужны шаблоны вакансий?"
},
{
"doc": "https://docs.google.com/document/d/1joE-rKrEmDgQojonmkyTYRihQ6eaoKraqgK_Pav5uLA/edit",
"prompt": '''Ты сотрудник по подбору персонала. Перед тобой документ, в котором описана инструкция по подбору и оценке на должность руководителя отдела продаж.
Твоя задача придумывать вопросы к собеседованию на данную позицию.
Документ: ''',
"name": "Нейро-рекрутер",
"query": "Придумай 5 вопросов для собеседования"
},
{
"doc": "https://docs.google.com/document/d/1SVi7dVwgJKf7tOljTWlVG-XebYXEv3B0Xjq8ERqjlbA/edit",
"prompt": '''Перед тобой диалог преподавателя музыкальной школы с родителем ученика.
Тебе надо проверить несколько критериев и заполнить отчёт для занесения в календарь уроков.
Что надо проверить
1. Говорил ли родитель о потребности в обучении
2. На каком интрументе умеет играть ребенок
3. На какой инструмент записался
4. Когда первый пробный урок
Не пиши общее сообщение, только заполни отчёт по форме:
1. Говорил ли родитель о потребности в обучении - было или нет
2. На каком интрументе умеет играть ребенок - название инструмента
3. На какой инструмент записался ребенок - название инструмента
4. Когда первый пробный урок - на какое время договорились
Заполни отчёт и пришли в качестве ответа, коротко и ёмко ''',
"name": "Нейро-менеджер (Отчет по диалогу)",
"query": "Пришли отчет"
},
{
"doc": "https://docs.google.com/document/d/1IqGa92RlFiCJvBH7TBKhPpODpru2-RDro8qiVEzoAuA/edit",
"prompt": '''Ты менеджер контроля качества, твоя задача анализировать диалоги менеджеров по продажам с клиентами и готовить отчеты.
Компания продает курсы по машинному обучению.
Перед тобой текст диалога сделанный с помощью распознавания речи из записи zoom презентации.
Из-за машинного распознавания речи, в тексте могут быть ошибки распознавания, учитывая это.
Твоя задача делать отчеты по данному диалогу по запросам пользователя.
Составляй вопросы максимально точно по диалогу, не придумывай ничего от себя.
Текст диалога: ''',
"name": "Нейро-менеджер контроля качества (Оценка качества по диалогу)",
"query": "Напиши отчет, какие были потребности названы клиентом"
}
]
Next, we prepare a working environment for processing text data using the LangChain framework.
First, the necessary modules are imported for working with documents, obtaining embeddings through OpenAI models and managing vector databases.
Then tools are connected to break the text into parts and send HTTP requests.
Also included are libraries for interacting with the OpenAI API and creating user interfaces using Gradio. Additionally, tools for counting tokens and working with regular expressions are imported.
Блок библиотек фреймворка LangChain
# Работа с документами в langchain
from langchain.docstore.document import Document
# Эмбеддинги для OpenAI
from langchain.embeddings.openai import OpenAIEmbeddings
# Доступ к векторной базе данных
from langchain.vectorstores import Chroma
# Разделение текста на куски или чанки (chunk)
from langchain.text_splitter import CharacterTextSplitter
# Отправка запросов
import requests
#Доступ к OpenAI
from openai import OpenAI
# Отприсовка интерфейса с помощью grad
import gradio as gr
# Библиотека подсчёта токенов
# Без запроcов к OpenAI, тем самым не тратим деньги на запросы
import tiktoken
# Для работы с регулярными выражениями
import re
Now let's start writing the body of our neuro-employee. Let's announce a global GPT function – yes, we will work with the brainchild of OpenAI.
GPT class constructor:
When a class object is initialized, the model is specified (by default “gpt-3.5-turbo”).
Attributes for logs (self.log), model storage (self.model), and knowledge base (self.search_index) are initialized.
A client is created to interact with the OpenAI API using the API key, which is taken from the environment variables.
class GPT():
# Объявляем конструктор класса, для передачи имени модели и инициализации атрибутов класса
def __init__(self, model="gpt-3.5-turbo"):
self.log = '' # атрибут для сбора логов (сообщений)
self.model = model # атрибут для хранения выбранной модели OpenAI
self.search_index = None # атрибут для хранения ссылки на базу знаний (если None, то модель не обучена)
self.client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) # при инициализации запрашиваем ключ от OpenAI
load_search_indexes method:
Accepts the URL of a Google Docs document.
Using regular expressions, extracts the document ID from the URL.
If ID is not found, throws an exception.
Loads a document in text format by its ID via an HTTP request.
If the request fails, throws an exception.
Extracts text from the response and calls a method to create text embeddings and save them to a vector database.
# Метод загрузки текстового документа в векторную базу знаний
def load_search_indexes(self, url):
# Извлекаем document ID гугл документа из URL с помощью регулярных выражений
match_ = re.search('/document/d/([a-zA-Z0-9-_]+)', url)
# Если ID не найден - генерируем исключение
if match_ is None:
raise ValueError('Неверный Google Docs URL')
# Первый элемент в результате поиска
doc_id = match_.group(1)
# Скачиваем гугл документ по его ID в текстовом формате
response = requests.get(f'https://docs.google.com/document/d/{doc_id}/export?format=txt')
# При неудачных статусах запроса будет вызвано исключение
response.raise_for_status()
# Извлекаем данные как текст
text = response.text
# Вызываем метод векторизации текста и сохранения в векторную базу данных
return self.create_embedding(text)
num_tokens_from_string method:
Takes a string and returns the number of tokens in it.
Uses the tiktoken library to get the encoder based on the model name.
Encodes a string and counts the number of tokens.
# Подсчет числа токенов в строке по имени модели
def num_tokens_from_string(self, string):
"""Возвращает число токенов в строке"""
encoding = tiktoken.encoding_for_model(self.model) # получаем кодировщик по имени модели
num_tokens = len(encoding.encode(string)) # расчитываем строку с помощью кодировщика
return num_tokens
create_embedding method:
Takes text data and creates embeddings.
Splits text into chunks using CharacterTextSplitter.
Converts chunks to Document objects.
Counts the number of tokens in the text without asking OpenAI to save money.
Creates document indices using OpenAI embeddings and loads them into the Chroma vector database.
Returns a reference to the database.
# Метод разбора текста и его сохранение в векторную базу знаний
def create_embedding(self, data):
# Список документов, полученных из фрагментов текста
source_chunks = []
# Разделяем текст на строки по \n (перенос на новую строку) или длине фрагмента (chunk_size=1024) с помощью сплитера
# chunk_overlap=0 - означает, что фрагменты не перекрываются друг с другом.
# Если больше нуля, то захватываем дополнительное число символов от соседних чанков.
splitter = CharacterTextSplitter(separator="\n", chunk_size=1024, chunk_overlap=0)
# Применяем splitter (функцию расщепления) к данным и перебираем все получившиеся чанки (фрагменты)
for chunk in splitter.split_text(data):
# LangChain работает с документами, поэтому из текстовых чанков мы создаем фрагменты документов
source_chunks.append(Document(page_content=chunk, metadata={}))
# Подсчет числа токенов в документах без запроса к OpenAI (экономим денежные средства)
count_token = self.num_tokens_from_string(' '.join([x.page_content for x in source_chunks]))
# Вместо вывода print, мы формируем переменную log для дальнейшего вывода в gradio информации
self.log += f'Количество токенов в документе : {count_token}\n'
# Создание индексов документа. Применяем к нашему списку документов эмбеддингов OpenAi и в таком виде загружаем в базу ChromaDB
self.search_index = Chroma.from_documents(source_chunks, OpenAIEmbeddings(), )
# Вместо вывода print, мы формируем переменную log для дальнейшего вывода в gradio информации
self.log += f'Данные из документа загружены в в векторную базу данных\n'
# Возвращаем ссылку на базу данных
return self.search_index
Method num_tokens_from_messages:
Takes a list of messages and a model, returns the number of tokens.
Gets the encoder based on the model name, or the base one if the model is not supported.
Depending on the model, sets additional tokens for the message and name.
Calculates the number of tokens for each message, taking into account additional tokens.
Returns the total number of tokens.
# Демонстрация более аккуратного расчета числа токенов в зависимости от модели
def num_tokens_from_messages(self, messages, model):
"""Возвращает число токенов из списка сообщений"""
try:
encoding = tiktoken.encoding_for_model(model) # получаем кодировщик по имени модели
except KeyError:
print("Предупреждение: модель не создана. Используйте cl100k_base кодировку.")
encoding = tiktoken.get_encoding("cl100k_base") # если по имени не нашли, то используем базовый для моделей OpenAI
# Выбор модели
if model in {
"gpt-3.5-turbo-0613",
"gpt-3.5-turbo-16k-0613",
"gpt-4-0314",
"gpt-4-32k-0314",
"gpt-4-0613",
"gpt-4-32k-0613",
"gpt-4o",
"gpt-4o-2024-05-13"
}:
tokens_per_message = 3 # дополнительное число токенов на сообщение
tokens_per_name = 1 # токенов на имя
elif model == "gpt-3.5-turbo-0301":
tokens_per_message = 4 # каждое сообщение содержит <im_start>{role/name}\n{content}<im_end>\n
tokens_per_name = -1 # если есть имя, то роль не указывается
elif "gpt-3.5-turbo" in model:
self.log += f'Внимание! gpt-3.5-turbo может обновиться в любой момент. Используйте gpt-3.5-turbo-0613. \n'
return self.num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613")
elif "gpt-4" in model:
self.log += f'Внимание! gpt-4 может обновиться в любой момент. Используйте gpt-4-0613. \n'
return self.num_tokens_from_messages(messages, model="gpt-4-0613")
else: # исключение, если модель не поддерживается
raise NotImplementedError(
f"""num_tokens_from_messages() не реализован для модели {model}."""
)
# Запускаем подсчет токенов
num_tokens = 0 # счетчик токенов
for message in messages: # цикл по всем сообщениям
num_tokens += tokens_per_message # прибовляем число токенов на каждое сообщение
for key, value in message.items():
num_tokens += len(encoding.encode(value)) # считаем токены в сообщении с помощью кодировщика
if key == "name": # если встретили имя
num_tokens += tokens_per_name # то добавили число токенов на
num_tokens += 3 # каждый ответ оборачивается в <|start|>assistant<|message|>
return num_tokens
Method answer_index:
Receives a system message, a request subject, and a response generation temperature.
Checks if the model is trained.
Searches for documents based on similarity to a query from a vector database.
Generates a message with document fragments.
Creates formatted messages for a model with roles “system” and “user”. By the way, what roles are, read in our previous article for newbies.
Counts the number of tokens for messages.
Sends a query to the OpenAI language model with the specified parameters.
Logs information about the tokens used in the request and response.
Returns the generated model response.
# Метод запроса к языковой модели
def answer_index(self, system, topic, temp = 1):
# Проверяем обучена ли наша модель
if not self.search_index:
self.log += 'Модель необходимо обучить! \n'
return ''
# Выборка документов по схожести с запросом из векторной базы данных, topic- строка запроса, k - число извлекаемых фрагментов
docs = self.search_index.similarity_search(topic, k=5)
# Вместо вывода print, мы формируем переменную log для дальнейшего вывода в gradio информации
self.log += 'Выбираем документы по степени схожести с вопросом из векторной базы данных: \n '
# Очищаем запрос от двойных пустых строк. Каждый фрагмент подписываем: Отрывок документа № и дальше порядковый номер
message_content = re.sub(r'\n{2}', ' ', '\n '.join([f'Отрывок документа №{i+1}:\n' + doc.page_content + '\\n' for i, doc in enumerate(docs)]))
# Вместо вывода print, мы формируем переменную log для дальнейшего вывода в gradio информации
self.log += f'{message_content} \n'
# В системную роль помещаем найденные фрагменты и промпт, в пользовательскую - вопрос от пользователя
messages = [
{"role": "system", "content": system + f"{message_content}"},
{"role": "user", "content": topic}
]
# Вместо вывода print, мы формируем переменную log для дальнейшего вывода в gradio информации
self.log += f"\n\nТокенов использовано на вопрос по версии TikToken: {self.num_tokens_from_messages(messages, self.model)}\n"
# Запрос к языковой моделе
completion = self.client.chat.completions.create(
model=self.model, # используемая модель
messages=messages, # список форматированных сообщений с ролями
temperature=temp # точность ответов модели
)
# Вместо вывода print, мы формируем переменную log для дальнейшего вывода в gradio информации
self.log += '\nСтатистика по токенам от языковой модели:\n'
self.log += f'Токенов использовано всего (вопрос): {completion.usage.prompt_tokens} \n' # Число токенов на вопрос по расчетам LLM
self.log += f'Токенов использовано всего (вопрос-ответ): {completion.usage.total_tokens} \n' # Число токенов на вопрос и ответ по расчетам LLM
return completion.choices[0].message.content # возвращаем результат предсказания
Now we can start writing the interface itself in Gradio, since we have all the output data to show the results of our work.
Initializing Gradio and the Interface Block:
blocks = gr.Blocks()
with blocks as demo:
# В этом контексте описываются элементы интерфейса
gr.Blocks() создает блок для организации элементов интерфейса в Gradio.
Interface elements:
# Работаем с блоком
with blocks as demo:
# Объявляем элемент выбор из списка (с подписью Данные), список выбирает из поля name нашей переменной models
subject = gr.Dropdown([(elem["name"], index) for index, elem in enumerate(models)], label="Данные")
# Здесь отобразиться выбранное имя name из списка
name = gr.Label(show_label=False)
# Промпт для запроса к LLM (по умолчанию поле prompt из models)
prompt = gr.Textbox(label="Промт", interactive=True)
# Ссылка на файл обучения (по умолчанию поле doc из models)
link = gr.HTML()
# Поле пользовательского запроса к LLM (по умолчанию поле query из models)
query = gr.Textbox(label="Запрос к LLM", interactive=True)
# Функция на выбор нейро-сотрудника в models
# Ей передается параметр subject - выбранное значение в поле списка
# А возвращаемые значения извлекаются из models
onchange function:
# Функция на выбор нейро-сотрудника в models
# Ей передается параметр subject - выбранное значение в поле списка
# А возвращаемые значения извлекаются из models
def onchange(dropdown):
return [
models[dropdown]['name'], # имя возвращается без изменения
re.sub('\t+|\s\s+', ' ', models[dropdown]['prompt']), # в промте удаляются двойные пробелы \s\s+ и табуляция \t+
models[dropdown]['query'], # запрос возвращается без изменения
f"<a target="_blank" href="https://habr.com/ru/articles/825220/{models[dropdown]["doc']}'>Документ для обучения</a>" # ссылка на документ оборачивается в html тег <a> (https://htmlbook.ru/html/a)
]
# При изменении значения в поле списка subject, вызывается функция onchange
# Ей передается параметр subject - выбранное значение в поле списка
# А возвращаемые значения устанавливаются в элементы name, prompt, query и link
subject.change(onchange, inputs = [subject], outputs = [name, prompt, query, link])
# Строку в gradio можно разделить на столбцы (каждая кнопка в своем столбце)
with gr.Row():
train_btn = gr.Button("Обучить модель") # кнопка запуска обучения
request_btn = gr.Button("Запрос к модели") # кнопка отправки запроса к LLM
This function is called when the value in the subject dropdown list changes and updates the values of the name, prompt, query, and link interface elements according to the selected model.
Train and predict functions:
# функция обучения
def train(dropdown):
# парсим документ и сохраняем его в базу данных
gpt.load_search_indexes(models[dropdown]['doc'])
return gpt.log
# Вызываем метод запроса к языковой модели из класса GPT
def predict(p, q):
#
result = gpt.answer_index(
p,
q
)
# возвращает список из ответа от LLM и log от класса GPT
return [result, gpt.log]
# Выводим поля response с ответом от LLM и log (вывод сообщений работы класса GPT) на 2 колонки
with gr.Row():
response = gr.Textbox(label="Ответ LLM") # Текстовое поле с ответом от LLM
log = gr.Textbox(label="Логирование") # Текстовое поле с выводом сообщений от GPT
# При нажатии на кнопку train_btn запускается функция обучения train_btn с параметром subject
# Результат выполнения функции сохраняем в текстовое поле log - лог выполнения
train_btn.click(train, [subject], log)
# При нажатии на кнопку request_btn запускается функция отправки запроса к LLM request_btn с параметром prompt, query
# Результат выполнения функции сохраняем в текстовые поля response - ответ модели, log - лог выполнения
request_btn.click(predict, [prompt, query], [response, log])
train(dropdown): A function that loads the search indices from the document associated with the selected model using the load_search_indexes method of the gpt object.
predict(p, q): A function that sends a query p and q to the model via the answer_index method of the gpt object and returns the result and a log of the operation.
Buttons and their actions:
with gr.Row():
response = gr.Textbox(label="Ответ LLM") # Текстовое поле с ответом от LLM
log = gr.Textbox(label="Логирование") # Текстовое поле с выводом сообщений от GPT
# При нажатии на кнопку train_btn запускается функция обучения train_btn с параметром subject
# Результат выполнения функции сохраняем в текстовое поле log - лог выполнения
train_btn.click(train, [subject], log)
# При нажатии на кнопку request_btn запускается функция
отправки запроса к LLM request_btn с параметром prompt, query
# Результат выполнения функции сохраняем в текстовые поля response - ответ модели, log - лог выполнения
request_btn.click(predict, [prompt, query], [response, log])
The train_btn and request_btn buttons run the train and predict functions respectively when they are pressed. The results of operations are displayed in the text fields log (for the log) and response (for the response from the model).
Launching the Gradio interface:
# Запуск приложения
demo.launch()
Let's run it and look at the result 🙂