Допустим, у вас есть много PDF-файлов в вашем облачном хранилище Google (GCS) и вы хотите использовать векторную базу данных, чтобы дать вашей большой языковой модели (LLM) больше контекста для более точных и актуальных ответов, вам сначала нужно извлечь, очистить и преобразовать эти PDF-файлы в формат, понятный LLM (например, JSON). Библиотека Unstructured может помочь.

Unstructured.io предлагает мощный набор инструментов, который обрабатывает этапы приема и предварительной обработки данных, позволяя вам сосредоточиться на более интересных последующих этапах конвейера машинного обучения. Unstructured имеет более дюжины коннекторов данных, которые легко интегрируются с различными источниками данных, включая AWS S3, Discord, Slack, Wikipedia и другие.

В этом руководстве мы рассмотрим пошаговое руководство о том, как получить ваши данные из GCS, предварительно обработать эти данные и загрузить их в векторную базу данных для извлечения дополненной генерации (RAG).

Начало работы с неструктурированным

Предпосылки:

  • Установите Unstructured из PyPI или GitHub repo
  • Установите неструктурированные коннекторы Google Cloud здесь
  • Получите неструктурированный ключ API здесь
  • Получите ключ API OpenAI здесь
  • Получите API-ключ Pinecone здесь
  • Ведро Google Cloud Storage (GCS), полное документов, которые вы хотите обработать
  • Базовые знания операций с командной строкой

Прием данных

Включить доступ к GCS:

Чтобы получить доступ к своим файлам из GCS, вам необходимо предоставить доступ к чтению сегментов в вашей учетной записи GCS. Если вы не знакомы с этим процессом, я приведу базовый пример, но если вы уже знакомы с шагами, не стесняйтесь переходить к следующему разделу.

  1. Перейдите в свою консоль Google Cloud (ссылка)
  2. Нажмите на проект, из которого вы планируете получить данные.
  3. В левой части экрана нажмите IAM & Admin -> Учетные записи служб.
  4. Нажмите «+ Создать сервисный аккаунт» и заполните поля
  5. После того, как вы создали новую учетную запись службы, нажмите на нее и перейдите в «KEYS».
  6. Нажмите «ДОБАВИТЬ КЛЮЧ» -> «Создать новый ключ» -> JSON.
  7. Это загрузит файл JSON с вашими ключами. Убедитесь, что эти ключи хранятся надлежащим образом, так как они представляют угрозу безопасности.
  8. Поместите файл JSON в безопасное место и по пути, к которому вы сможете получить доступ позже.

Запуск неструктурированного API с помощью коннектора GCS:

Когда ваш ключ неструктурированного API и корзина GCS готовы, пришло время запустить неструктурированный API. Чтобы запустить команду `unstructured-ingest`, вам необходимо установить неструктурированную библиотеку с открытым исходным кодом, которую можно легко получить из этого репозитория GitHub.

Следуйте инструкциям в репозитории, чтобы установить библиотеку, и обязательно установите следующие дополнительные зависимости:

!pip install "unstructured[gcs,all-docs]" langchain openai pinecone-client

После того, как вы установили дополнительные зависимости, можно запускать API. Для этого руководства я решил запустить API в блокноте Python.

# Add the environment variable for the GCP service account credentials
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = '/Path/to/your/keyfile.json'

command = [
    "unstructured-ingest",
    "gcs",
    "--remote-url", "gs://<YOUR-BUCKET>/",
    "--structured-output-dir", "/YOUR/OUTPUT/PATH",
    "--num-processes", "2",
    "--api-key", "<UNSTRUCTURED-API-KEY>",
    "--partition-by-api"
]

Объяснение:

  • os.environ[‘GOOGLE_APPLICATION_CREDENTIALS’] =/path/to/your/keyfile.json — добавьте переменную среды для учетных данных учетной записи службы GCP.
  • «gcs» — указывает коннектор данных
  • Установите выходной каталог, используя параметр `-structured-output-dir`
  • Укажите `—num-processes`, чтобы распределить рабочую нагрузку между несколькими процессами.
  • Включите `-api-key` из предыдущего шага в ваш вызов API.
  • Используйте флаг `-partition-by-api`, чтобы указать, что раздел работает через API, а не через библиотеку.

Мы с гордостью представляем наш бесплатный API, созданный специально для энтузиастов и разработчиков, увлекающихся прототипированием. Мы выделили определенное количество преданных работников, чтобы обеспечить бесперебойную работу и удовлетворить потребности нашего сообщества пользователей. Тем не менее, мы понимаем потребности крупных корпораций и выполнение тяжелых задач. Имея это в виду, мы рады сообщить, что на горизонте появится API корпоративного уровня.

Просмотрите выходные данные API:

После завершения команды вы найдете структурированные выходные данные в указанном выходном каталоге. Эти файлы содержат информацию, извлеченную из ваших неструктурированных данных, готовую для анализа или дальнейшей обработки.

Предварительная обработка данных

Когда мы создаем системы RAG, наша модель LLM работает только в том контексте, который им предоставляется. По этой причине мы хотим удалить ненужные текстовые элементы, найденные в наших файлах. К счастью, Unstructured классифицирует текст по типам элементов, и когда Unstructured находит элементы, которые не может классифицировать, он помечает их как UncategorizedText. Часто этот UncategorizedText бесполезен и отвлекает нашего LLM. В этом уроке мы удалим эти элементы UncategorizedText с помощью следующей вспомогательной функции.

def remove_element_from_json(filepath, item_to_remove):
    # Read the file and load its contents as JSON
    with open(filepath, 'r') as file:
        data = json.load(file)

    # Filter out the elements with the specified 'type'
    updated_data = [item for item in data if item['type'] != item_to_remove]

    # Write the updated data back to the file
    with open(filepath, 'w') as file:
        json.dump(updated_data, file, indent=4)

Теперь вы можете вызвать вышеупомянутую вспомогательную функцию, указав путь к файлу документа и тип элемента, который вы хотите удалить.

filepath = '/YOUR/OUTPUT/PATH'
remove_element_from_json(filepath, "UncategorizedText")

Загрузить файлы Json в Langchain:

Следующим шагом будет загрузка очищенных и обработанных структурированных данных в загрузчики документов LangChain. В этом случае мы будем использовать UnstructuredFileLoader от LangChain.

from langchain.document_loaders import UnstructuredFileLoader

# Now you will load to files outputted from the Unstructured API. You'll find the files in the output directory. 
loader = UnstructuredFileLoader(
"/YOUR/OUTPUT/PATH", 
post_processors=[clean_extra_whitespace],
)
docs = loader.load()

Разбить текстовые файлы на части:

Далее нам нужно будет разбить исходные документы на фрагменты, чтобы наши модели больших языков (LLM) могли обрабатывать потенциально длинные документы. Текущее ограничение LLM заключается в том, что они могут обрабатывать только определенный объем контекста. Мы разбиваем данные на куски, чтобы фрагменты текста были для них удобоваримыми.

Для целей этого урока я решил использовать LangChain RecursiveCharacterTextSplitter, однако у них есть множество различных методов фрагментации, которые можно найти здесь.

text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, 
chunk_overlap=200
)
docs = text_splitter.split_documents(docs)

Инициализация базы данных векторов

Настройка шишки:

После того, как мы разбили наши данные, мы можем встроить их и сохранить в векторной базе данных. В этой демонстрации я собираюсь использовать сосновую шишку, однако существует множество различных вариантов, каждый из которых имеет свои сильные стороны.

# Storing API keys like this poses a security risk. Only do this in prototyping
os.environ['PINECONE_API_KEY'] = "<PINECONE-API-KEY>"

# Pinecone Environment Variable
os.environ['PINECONE_ENV'] = "<PINECONE-LOCATION>"

pinecone.init(
    api_key=os.getenv("PINECONE_API_KEY"),  # find at app.pinecone.io
    environment=os.getenv("PINECONE_ENV"),  # next to api key in console
)

index_name = "gcs-demo"
# First, check if our index already exists. If it doesn't, we create it
if index_name not in pinecone.list_indexes():
    # we create a new index
    pinecone.create_index(
      name=index_name,
      metric='cosine',
      dimension=1536  
)

Создайте вспомогательные функции:

Следующий шаг — после того, как мы инициализировали нашу базу данных Pinecone, нам нужно создать вспомогательную функцию, чтобы получить все соответствующие фрагменты информации из нашей векторной базы данных, чтобы позже использовать их в нашем LLM (вдохновение).

limit = 3600
embed_model = "text-embedding-ada-002"

def retrieve(query):
    res = openai.Embedding.create(
        input=[query],
        engine=embed_model
    )

    # retrieve from Pinecone
    xq = res['data'][0]['embedding']

    # get relevant contexts
    res = index.query(xq, top_k=3, include_metadata=True)
    contexts = [
        x['metadata']['text'] for x in res['matches']
    ]

    # build our prompt with the retrieved contexts included
    prompt_start = (
        "Given the contextual information and not prior knowledge, answer the question. If the answer is not in the context, inform the user that you can't answer the question.\n\n"+
        "Context:\n"
    )
    prompt_end = (
        f"\n\nQuestion: {query}\nAnswer:"
    )
    # append contexts until hitting limit
    for i in range(1, len(contexts)):
        if len("\n\n---\n\n".join(contexts[:i])) >= limit:
            prompt = (
                prompt_start +
                "\n\n---\n\n".join(contexts[:i-1]) +
                prompt_end
            )
            break
        elif i == len(contexts)-1:
            prompt = (
                prompt_start +
                "\n\n---\n\n".join(contexts) +
                prompt_end
            )
    return prompt

Теперь, когда у нас есть вспомогательная функция для извлечения соответствующих фрагментов информации, нам нужна вспомогательная функция, которая будет принимать наше расширенное приглашение и передавать его в наш LLM.

def complete(prompt):
    # query text-davinci-003
    res = openai.Completion.create(
        engine='text-davinci-003',
        prompt=prompt,
        temperature=0,
        max_tokens=400,
        top_p=1,
        frequency_penalty=0,
        presence_penalty=0,
        stop=None
    )
    return res['choices'][0]['text'].strip()

Собираем все вместе

Запрос векторной базы данных:

С нашими двумя вспомогательными функциями мы теперь можем использовать нашу базу данных векторов для извлечения соответствующих фрагментов информации, чтобы помочь нашему LLM ответить на вопросы, связанные с предметной областью.

query = "A question you'd like to ask about your PDF"

# first we retrieve relevant items from Pinecone
query_with_contexts = retrieve(query)
query_with_contexts

Как только мы получили необходимый контекст, мы можем передать его в наш LLM, чтобы ответить на наш вопрос.

# then we complete the context-infused query
complete(query_with_contexts)

Вывод:

В этом блоге мы перешли от неструктурированных необработанных PDF-файлов, хранящихся в корзине GCS, к их предварительной обработке и загрузке в векторную базу данных, чтобы наш LLM мог выполнять запросы. В этом процессе есть много потенциальных улучшений, включая методы фрагментации, но я надеюсь, что теперь вы можете взять любой формат файла и построить вокруг него конвейер RAG. Полный код можно найти в этой записной книжке Google Colab.

Если у вас есть вопросы, присоединяйтесь к нашей группе сообщества Slack, чтобы общаться с другими пользователями, задавать вопросы, делиться своим опытом и получать последние обновления. Нам не терпится увидеть, что вы построите!