Допустим, у вас есть много 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. Полный код можно найти здесь.

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

Первоначально опубликовано на https://medium.com 14 августа 2023 г.