Создайте бесплатного бота вопросов и ответов за 5 минут: Milvus + LangChain

Сколько времени нужно, чтобы создать полезного, дешевого и точного бота для вопросов и ответов?

Ответ: 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:

  1. Подготовьте надежные пользовательские данные и модель внедрения.

  2. Используйте Encoder для разделения данных и создания векторов внедрения, а также сохранения данных и метаданных в базе данных векторов.

  3. Пользователь задает вопрос. Используйте тот же кодировщик, что и на шаге 1, чтобы преобразовать задачу в вектор внедрения.

  4. Семантический поиск с использованием векторных баз данных для получения ответов на вопросы.

  5. Используйте текстовый блок ответа на поиск в качестве «контекста», а результаты вопросов пользователя — для формирования подсказки. Отправьте запрос в LLM.

  6. 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
{{o.name}}
{{м.имя}}

рекомендация

отmy.oschina.net/u/4209276/blog/10324159
рекомендация