Сколько времени нужно, чтобы создать полезного, дешевого и точного бота для вопросов и ответов?
Ответ: 5 минут. Просто воспользуйтесь стеком технологий RAG с открытым исходным кодом, LangChain и простой в использовании векторной базой данных Milvus. Следует подчеркнуть, что стоимость этого бота вопросов и ответов очень низка, поскольку нам не нужно вызывать API-интерфейсы большой языковой модели во время итераций отзыва, оценки и разработки. Только на последнем этапе — при формировании окончательных результатов вопросов и ответов API будет вызываться один раз.
Если вы хотите узнать больше о технологии бота вопросов и ответов, вы можете просмотреть исходный код на GitHub ( https://github.com/zilliztech/akcio). Полный код этой статьи доступен через Bootcamp ( https://github.com/milvus-io/bootcamp/blob/master/bootcamp/RAG/readthedocs_zilliz_langchain.ipynb).
Прежде чем официально начать, давайте рассмотрим RAG. Основная цель RAG — обеспечить поддержку вывода текста с помощью генеративного ИИ. Другими словами, RAG использует факты и пользовательские данные, чтобы уменьшить иллюзии LLM. В частности, в RAG мы можем использовать надежный и заслуживающий доверия текст пользовательских данных, например документацию по продукту, и впоследствии получать аналогичные результаты из векторной базы данных. Точный текстовый ответ затем вставляется в «Подсказку» как «контекст» вместе с «вопросом» и передается в LLM, такой как ChatGPT OpenAI. В конечном итоге LLM генерирует ответ в чате, основанный на фактах.
Конкретный процесс RAG:
-
Подготовьте надежные пользовательские данные и модель внедрения.
-
Используйте Encoder для разделения данных и создания векторов внедрения, а также сохранения данных и метаданных в базе данных векторов.
-
Пользователь задает вопрос. Используйте тот же кодировщик, что и на шаге 1, чтобы преобразовать задачу в вектор внедрения.
-
Семантический поиск с использованием векторных баз данных для получения ответов на вопросы.
-
Используйте текстовый блок ответа на поиск в качестве «контекста», а результаты вопросов пользователя — для формирования подсказки. Отправьте запрос в LLM.
-
LLM генерирует ответ.
01. Получить данные
Для начала давайте представим инструменты, которые будут использоваться в процессе строительства:
Milvus — это высокопроизводительная векторная база данных с открытым исходным кодом, которая упрощает процесс поиска неструктурированных данных. Milvus может хранить, индексировать и искать большие векторные данные встраивания.
OpenAI в основном разрабатывает модели и инструменты искусственного интеллекта, а ее самый известный продукт — GPT.
Инструмент и библиотека-оболочка LangChain могут помочь разработчикам построить мост между традиционным программным обеспечением и LLM.
Мы будем использовать страницу документации продукта.ReadTheDocs — это бесплатное программное обеспечение для документации с открытым исходным кодом, которое генерирует документацию через Sphinx.
Download readthedocs pages locally.
DOCS_PAGE="https://pymilvus.readthedocs.io/en/latest/"
wget -r -A.html -P rtdocs --header="Accept-Charset: UTF-8" $DOCS_PAGE
Приведенный выше код загружает страницу документации по локальному пути rtdocs
. Далее прочитайте эти документы в LangChain:
#!pip install langchain
from langchain.document_loaders import ReadTheDocsLoader
loader = ReadTheDocsLoader(
"rtdocs/pymilvus.readthedocs.io/en/latest/",
features="html.parser")
docs = loader.load()
02. Используйте структуру HTML для разделения данных
Необходимо определить стратегию фрагментации, размер фрагмента и перекрытие фрагментов. Для этого урока наша конфигурация выглядит следующим образом:
-
Стратегия дробления = разделение в соответствии со структурой заголовка Markdown.
-
Размер плитки = использовать параметр модели внедрения.
MAX_SEQ_LENGTH
-
Перекрытие = 10-15%
-
функция =
Langchain HTMLHeaderTextSplitter разделяет заголовки файлов уценки.
Langchain RecursiveCharacterTextSplitter разбивает длинный текст.
from langchain.text_splitter import HTMLHeaderTextSplitter, RecursiveCharacterTextSplitter
Define the headers to split on for the HTMLHeaderTextSplitter
headers_to_split_on = [
("h1", "Header 1"),
("h2", "Header 2"),]
Create an instance of the HTMLHeaderTextSplitter
html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
Use the embedding model parameters.
chunk_size = MAX_SEQ_LENGTH - HF_EOS_TOKEN_LENGTH
chunk_overlap = np.round(chunk_size * 0.10, 0)
Create an instance of the RecursiveCharacterTextSplitter
child_splitter = RecursiveCharacterTextSplitter(
chunk_size = chunk_size,
chunk_overlap = chunk_overlap,
length_function = len,)
Split the HTML text using the HTMLHeaderTextSplitter.
html_header_splits = []
for doc in docs:
splits = html_splitter.split_text(doc.page_content)
for split in splits:
# Add the source URL and header values to the metadata
metadata = {}
new_text = split.page_content
for header_name, metadata_header_name in headers_to_split_on:
header_value = new_text.split("¶ ")[0].strip()
metadata[header_name] = header_value
try:
new_text = new_text.split("¶ ")[1].strip()
except:
break
split.metadata = {
**metadata,
"source": doc.metadata["source"]}
# Add the header to the text
split.page_content = split.page_content
html_header_splits.extend(splits)
Split the documents further into smaller, recursive chunks.
chunks = child_splitter.split_documents(html_header_splits)
end_time = time.time()
print(f"chunking time: {end_time - start_time}")
print(f"docs: {len(docs)}, split into: {len(html_header_splits)}")
print(f"split into chunks: {len(chunks)}, type: list of {type(chunks[0])}")
Inspect a chunk.
print()
print("Looking at a sample chunk...")
print(chunks[1].page_content[:100])
print(chunks[1].metadata)
Этот текстовый блок поддерживается документацией. Кроме того, заголовки и текстовые блоки сохраняются вместе, и заголовки можно использовать позже.
03.Создать вектор внедрения
Последние результаты тестирования производительности MTEB показывают, что модель внедрения/отзыва с открытым исходным кодом и OpenAI Embeddings (ada-002) имеют схожие эффекты. Небольшая модель с наивысшим баллом на рисунке ниже — это bge-large-en-v1.5
модель, которая будет выбрана для этой статьи.
На рисунке выше показан список рейтингов модели внедрения, самый высокий рейтинг — voyage-lite-01-instruct(size 4.2 GB, and third rankbge-base-en-v1.5(size 1.5 GB)
. OpenAIEmbeddingtext-embeddings-ada-002
Занял 22-е место.
Теперь давайте инициализируем модель;
#pip install torch, sentence-transformers
import torch
from sentence_transformers import SentenceTransformer
Initialize torch settings
DEVICE = torch.device('cuda:3'
if torch.cuda.is_available()
else 'cpu')
Load the encoder model from huggingface model hub.
model_name = "BAAI/bge-base-en-v1.5"
encoder = SentenceTransformer(model_name, device=DEVICE)
Get the model parameters and save for later.
MAX_SEQ_LENGTH = encoder.get_max_seq_length()
EMBEDDING_LENGTH = encoder.get_sentence_embedding_dimension()
Затем используйте модель для создания векторов внедрения и интегрируйте все данные в словарь.
chunk_list = []
for chunk in chunks:
# Generate embeddings using encoder from HuggingFace.
embeddings = torch.tensor(encoder.encode([chunk.page_content]))
embeddings = F.normalize(embeddings, p=2, dim=1)
converted_values = list(map(np.float32, embeddings))[0]
# Assemble embedding vector, original text chunk, metadata.
chunk_dict = {
'vector': converted_values,
'text': chunk.page_content,
'source': chunk.metadata['source'],
'h1': chunk.metadata['h1'][:50],
'h2': chunk.metadata['h1'][:50],}
chunk_list.append(chunk_dict)
04. Создаем индекс в Milvus и вставляем данные
Исходные текстовые блоки мы храним в векторной базе данных в виде vector
, text
, source
, h1
, .h2
Запустите и подключитесь к серверу Milvus. Чтобы использовать бессерверный кластер, вам необходимо предоставить его при подключении ZILLIZ_API_KEY
.
#pip install pymilvus
from pymilvus import connections
ENDPOINT=”https://xxxx.api.region.zillizcloud.com:443”
connections.connect(
uri=ENDPOINT,
token=TOKEN)
Создайте коллекцию Milvus и назовите ее MilvusDocs
. Коллекция похожа на таблицу в традиционной базе данных: она имеет схему и определяет поля и типы данных. Размеры векторов в схеме должны соответствовать размерам векторов, созданных моделью внедрения. Тем временем создайте индекс:
from pymilvus import (
FieldSchema, DataType,
CollectionSchema, Collection)
1. Define a minimum expandable schema.
fields = [
FieldSchema(“pk”, DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(“vector”, DataType.FLOAT_VECTOR, dim=768),]
schema = CollectionSchema(
fields,
enable_dynamic_field=True,)
2. Create the collection.
mc = Collection(“MilvusDocs”, schema)
3. Index the collection.
mc.create_index(
field_name=”vector”,
index_params={
“index_type”: “AUTOINDEX”,
“metric_type”: “COSINE”,}
Вставка данных в Milvus/Zilliz происходит быстрее, чем в Pinecone!
Insert data into the Milvus collection.
insert_result = mc.insert(chunk_list)
After final entity is inserted, call flush
to stop growing segments left in memory.
mc.flush()
print(mc.partitions)
05. Задавайте вопросы
Далее мы можем использовать возможности семантического поиска, чтобы ответить на вопросы о документе. Семантический поиск использует методы ближайшего соседа в векторном пространстве, чтобы найти документ, наиболее подходящий для ответа на вопрос пользователя. Цель семантического поиска — понять значение вопросов и документов, а не просто сопоставить ключевые слова. В процессе поиска Milvus также может использовать метаданные для улучшения качества поиска ( expr=
используя логические выражения в опциях Milvus API).
Define a sample question about your data.
QUESTION = "what is the default distance metric used in AUTOINDEX?"
QUERY = [question]
Before conducting a search, load the data into memory.
mc.load()
Embed the question using the same encoder.
embedded_question = torch.tensor(encoder.encode([QUESTION]))
Normalize embeddings to unit length.
embedded_question = F.normalize(embedded_question, p=2, dim=1)
Convert the embeddings to list of list of np.float32.
embedded_question = list(map(np.float32, embedded_question))
Return top k results with AUTOINDEX.
TOP_K = 5
Run semantic vector search using your query and the vector database.
start_time = time.time()
results = mc.search(
data=embedded_question,
anns_field="vector",
# No params for AUTOINDEX
param={},
# Boolean expression if any
expr="",
output_fields=["h1", "h2", "text", "source"],
limit=TOP_K,
consistency_level="Eventually")
elapsed_time = time.time() - start_time
print(f"Milvus search time: {elapsed_time} sec")
Вот результаты поиска, вводим этот текст в context
поле:
for n, hits in enumerate(results):
print(f"{n}th query result")
for hit in hits:
print(hit)
Assemble the context as a stuffed string.
context = ""
for r in results[0]:
text = r.entity.text
context += f"{text} "
Also save the context metadata to retrieve along with the answer.
context_metadata = {
"h1": results[0][0].entity.h1,
"h2": results[0][0].entity.h2,
"source": results[0][0].entity.source,}
На рисунке выше видно, что было получено 5 текстовых блоков. Первый текстовый блок содержит ответ на вопрос. Поскольку мы используем его во время извлечения output_fields=
, поля вывода, возвращаемые в результате извлечения, будут содержать ссылки и метаданные.
id: 445766022949255988, distance: 0.708217978477478, entity: {
'chunk': "...# Optional, default MetricType.L2 } timeout (float) –
An optional duration of time in seconds to allow for the
RPC. …",
'source': 'https://pymilvus.readthedocs.io/en/latest/api.html',
'h1': 'API reference',
'h2': 'Client'}
06. Используйте LLM для генерации ответов на вопросы пользователей на основе контекста.
На этом этапе мы будем использовать небольшую генеративную модель искусственного интеллекта (LLM), которая доступна через HuggingFace.
#pip install transformers
from transformers import AutoTokenizer, pipeline
tiny_llm = "deepset/tinyroberta-squad2"
tokenizer = AutoTokenizer.from_pretrained(tiny_llm)
context cannot be empty so just put random text in it.
QA_input = {
'question': question,
'context': 'The quick brown fox jumped over the lazy dog'}
nlp = pipeline('question-answering',
model=tiny_llm,
tokenizer=tokenizer)
result = nlp(QA_input)
print(f"Question: {question}")
print(f"Answer: {result['answer']}")
Ответ не очень точный.Попробуем задать тот же вопрос, используя припомненный текст:
QA_input = {
'question': question,
'context': context,}
nlp = pipeline('question-answering',
model=tiny_llm,
tokenizer=tokenizer)
result = nlp(QA_input)
Print the question, answer, grounding sources and citations.
Answer = assemble_grounding_sources(result[‘answer’], context_metadata)
print(f"Question: {question}")
print(answer)
Ответ гораздо точнее!
Затем мы попытались использовать GPT OpenAI и обнаружили, что результаты ответов были такими же, как у робота с открытым исходным кодом, который мы создали сами.
def prepare_response(response):
return response["choices"][-1]["message"]["content"]
def generate_response(
llm,
temperature=0.0, #0 for reproducible experiments
grounding_sources=None,
system_content="", assistant_content="", user_content=""):
response = openai.ChatCompletion.create(
model=llm,
temperature=temperature,
api_key=openai.api_key,
messages=[
{"role": "system", "content": system_content},
{"role": "assistant", "content": assistant_content},
{"role": "user", "content": user_content}, ])
answer = prepare_response(response=response)
# Add the grounding sources and citations.
answer = assemble_grounding_sources(answer, grounding_sources)
return answer
Generate response
response = generate_response(
llm="gpt-3.5-turbo-1106",
temperature=0.0,
grounding_sources=context_metadata,
system_content="Answer the question using the context provided. Be succinct.",
user_content=f"question: {QUESTION}, context: {context}")
Print the question, answer, grounding sources and citations.
print(f"Question: {QUESTION}")
print(response)
07. Резюме
В этой статье полностью показано, как создать чат-бот RAG для пользовательских документов. Благодаря LangChain, Milvus и LLM с открытым исходным кодом мы можем легко реализовать бесплатные вопросы и ответы по сформулированным данным.
Broadcom объявила о прекращении обновления версии Deepin-IDE существующей партнерской программы VMware, новый внешний вид. WAVE SUMMIT отмечает свое 10-е издание. Вэнь Синьиян получит последнее раскрытие! Чжоу Хунъи: Уроженец Хунмэна обязательно добьется успеха. Полный исходный код GTA 5 стал достоянием общественности. Линус: Я не буду читать код в канун Рождества. Я выпущу новую версию набора инструментов Java Hutool-5.8.24. в следующем году. Давайте вместе жаловаться на Furion. Коммерческая разведка: лодка прошла. Ван Чжуншань, v4.9.1.15 Apple выпускает мультимодальную модель большого языка с открытым исходным кодом Компания Ferret Yakult подтверждает утечку данных 95G