Retrieval-Augmented Generation (RAG) швидко стає основою в світі штучного інтелекту. Згідно з останнім звідомленням від Menlo Ventures, RAG зараз має лідерство з 51% рівнем впровадження — вражаючий стрибок порівняно з 31% лише рік тому.
Але що таке RAG, і чому його популярність зростає так стрімко?
У цьому блозі я поясню концепцію RAG та її величезну популярність через практичний приклад: побудова системи запит- відповіді від початку до кінця на основі знань Timeplus із застосуванням RAG. Незалежно від того, чи ви є ентузіастом ШІ, чи розробником, який хоче впровадити передові рішення, цей посібник допоможе вам зрозуміти, як RAG поєднує генеративний ШІ та реальний пошук даних, щоб досягти виняткових результатів.
Що таке Retrieval-Augmented Generation?
Retrieval-Augmented Generation (RAG) — це метод ШІ/LLM, що поєднує пошук релевантної інформації (retrieval) із генерацією відповідей (generation). Це робить відповіді LLM більш точними та актуальними, використовуючи дані в реальному часі замість того, щоб покладатися лише на попередньо навчені знання.
Чому RAG корисний?
- Покращена точність
Завдяки тому, що відповіді ґрунтуються на отриманій інформації, RAG знижує ризик виникнення "галюцинацій" — ситуацій, коли модель генерує правдоподібні, але некоректні відповіді. - Спеціалізація за доменом
Це дозволяє інтегрувати приватні або спеціалізовані бази знань, що робить RAG ідеальним для таких галузей, як охорона здоров’я, фінанси та підтримка клієнтів. - Динамічні знання
RAG може інтегрувати останні дані та оновлення, долаючи обмеження статичних навчальних наборів даних. - Ефективність
Замість того, щоб покладатися на величезну універсальну модель, RAG використовує менші, орієнтовані на конкретні знання джерела, що зменшує обчислювальне навантаження.
Тепер давайте розглянемо реальний приклад, щоб зрозуміти, як використовувати RAG.
Архітектура системи запит- відповідь
Наша мета — побудувати систему запитів і відповідей на основі LLM для надання відповідей на запитання користувачів про Timeplus. Ось що ми побудуємо:
Робочий процес
- Вхід користувача: Користувач задає питання про Timeplus.
- Вбудовування тексту: Застосунок перетворює запитання користувача в вбудовування за допомогою Ollama.
- Пошук за векторами: Застосунок використовує це вбудовування для виконання пошуку за векторами в базі знань, щоб отримати релевантну інформацію.
- Побудова запиту: Застосунок створює запит, використовуючи отриману інформацію.
- Генерація відповіді: Застосунок надсилає запит до LLM, який генерує відповідь на основі отриманої інформації.
Створення векторної бази знань за допомогою Timeplus
Ключовим компонентом RAG є векторна база даних, яка зберігає знання у вигляді векторів і дозволяє проводити семантичний пошук за допомогою подібності векторів. У цьому випадку ми використовуватимемо Timeplus як нашу векторну базу даних.
Кроки для створення векторної бази даних
- Перетворити всі документи Timeplus на векторний формат.
- Зберегти дані у потоці Timeplus.
Ось схема для потоку Timeplus:
CREATE STREAM IF NOT EXISTS vector_store (
`name` string,
`id` string DEFAULT to_string(uuid()),
`text` string,
`vector` array(float64),
`metadata` map(string, string)
);
- name: Назва колекції документів.
- id: Генерований унікальний ID.
- text: Оригінальний текст документа.
- vector: Вбудовування тексту (масив типу float64).
- metadata: Додаткова метаінформація для фільтрації та пошуку.
Генерація вбудовувань за допомогою Ollama
Для генерування вбудовувань для документів ми використовуватимемо Ollama. Ollama підтримує запуск великих мовних моделей локально на вашому комп'ютері.
Ось код на Python:
import os
from openai import OpenAI
client = OpenAI(
base_url=os.getenv("LLM_BASE_URL"),
api_key="ollama"
)
def embedding(input):
response = client.embeddings.create(
input=input,
model="mxbai-embed-large:latest"
)
return response.data[0].embedding
Індексація документів у векторну базу даних
Наступний скрипт зчитує документи з локальної папки, генерує вбудовування та вставляє дані в потік Timeplus:
import os
from pathlib import Path
from proton_driver import client
timeplus_host = os.getenv("TIMEPLUS_HOST")
timeplus_user = os.getenv("TIMEPLUS_USER")
timeplus_password = os.getenv("TIMEPLUS_PASSWORD")
c = client.Client(
host=timeplus_host, port=8463, user=timeplus_user, password=timeplus_password
)
def read_files_from_path(folder_path, suffix):
text_content = []
for file_path in Path(folder_path).glob(f"*.{suffix}"):
with file_path.open("r", encoding="utf-8") as file:
text_content.append((file.read(), file_path))
return text_content
class Indexer:
def __init__(self, name, path):
self._doc_path = path
self._name = name
def index(self):
doc_texts = read_files_from_path(self._doc_path, "md")
for content in doc_texts:
text = content[0]
filename = os.path.basename(content[1])
embedding_vector = embedding(input=text)
metadata = {"filename": filename}
c.execute(
"INSERT INTO vector_store (name, text, vector, metadata) VALUES",
[[self._name, text, embedding_vector, metadata]]
)
indexer = Indexer("timeplus_doc", "./path/to/docs")
indexer.index()
У наведеному коді Python ми по черзі зчитуємо всі документи, перетворюємо ці документи у векторний формат і вставляємо їх у потік vectorstore. Ми використовуємо шлях до файлу як метадані. І всі ці документи мають ім’я timeplusdoc.
Тепер, коли ми індексували всі відповідні знання в потік Timeplus, наступним кроком буде показати, як здійснити пошук за векторами.
Виконання пошуку за векторами
Коли векторна база даних заповнена, ми можемо виконати пошук за векторами, щоб знайти найбільш релевантні документи. Схожість між векторами вимірюється за допомогою метрік відстані, таких як:
- L2 Відстань (Евклідову) Вимірює відстань по прямій між точками.
- L1 Відстань (Манхеттенська) Вимірює відстань по сітці між точками.
- Косинусна відстань Вимірює кут між векторами, зосереджуючи увагу на орієнтації, а не на величині.
Пошук за векторами просто поверне найближчі вектори на основі відстані між вектором запиту та всіма іншими векторами в базі даних, що відповідають умовам. Це можна представити за допомогою наступного SQL-запиту:
SELECT * FROM table(stream)
ORDER BY DistanceFunction(vectors, reference_vector)
LIMIT N;
Щоб виконати такий пошук, reference_vector — це вбудовування тексту, який ми хочемо знайти. Це також означає, що при виконанні такого SQL-запиту нам потрібно викликати модель вбудовування.
Timeplus Remote UDF можна використовувати для виклику зовнішніх сервісів або інструментів. Завдяки використанню UDF, ми можемо виконати наведений пошуковий SQL без додаткової обробки даних, перетворюючи наше питання у вбудовування.
Ось приклад SQL-запиту для пошуку:
SELECT
text, metadata, l2_distance(vector, embedding('що таке streaming query')) AS score
FROM
table(vector_store)
ORDER BY
score ASC
LIMIT 3;
Згідно з цим прикладом запиту, вхідне питання — «що таке streaming query», і найбільш релевантними документами за l2 відстанню є три документи: functionsforstreaming.md, query-syntax.md та working-with-streams.md.
Побудова запиту на основі RAG
Як я вже згадував, ключові моменти RAG полягають у тому, щоб додати релевантний контекст до запиту.
У нашому випадку, RAG означає пошук найбільш релевантних документів і вставку їх у запит.
Ось SQL для побудови запиту:
WITH 'what is a streaming query' AS question
SELECT
array_string_concat(array_reduce('group_array', group_array(text))) AS relevant_docs,
concat('Based on following relevant information: ', relevant_docs,' Answer following question : ', question) as prompt
FROM (
SELECT
text, l2_distance(vector, embedding(question)) AS score
FROM
table(vector_store)
ORDER BY
score ASC
LIMIT 3
)
- Клаузула WITH визначає, яке питання ми будемо ставити LLM.
- Підзапит у FROM — це пошук за векторами, який повертає топ-3 найбільш релевантні документи.
- Функція arraystringconcat разом з array_reduce зливає три текстові документи в один, який використовується як вхід для RAG у запиті.
- Функція concat просто створює фінальний запит, поєднуючи релевантні документи та питання в єдиний запит.
Запустивши цей запит, ми отримуємо запит з релевантним контекстом:
Based on following relevant information: # Streaming Processing
Relevant docs top1 …
Relevant docs top2 …
Relevant docs top3 …
Answer following question : what is a streaming query
Генерація відповідей за допомогою LLM
На останньому кроці ми просто відправляємо цей запит до LLM і повертаємо результат користувачу. Подібно до використання remote UDF для вбудовування, ми використовуємо інший remote UDF під назвою chat, який відправляє запитаний текст до LLM.
Ось основний код Python для функції remote chat:
import os
from openai import OpenAI
client = OpenAI(
base_url=os.getenv("LLM_BASE_URL"),
api_key="ollama"
)
def chat(messages):
response = client.chat.completions.create(
model="llama3.2:1b",
messages=[
{"role": "user", "content": messages},
],
temperature=0
)
return response.choices[0].message.content
Ми використовуємо локальний Ollama з моделлю https://ollama.com/library/llama3.2:1b
Фінальний SQL для відповіді на питання буде таким:
WITH 'what is a streaming query' AS question
SELECT
array_string_concat(array_reduce('group_array', group_array(text))) AS relevant_docs,
concat('Based on following relevant information: ', relevant_docs,' Answer following question : ', question) as prompt,
chat(prompt) as response
FROM (
SELECT
text, l2_distance(vector, embedding(question)) AS score
FROM
table(vector_store)
ORDER BY
score ASC
LIMIT 3
)
Ось відповідь від моделі llama3.2:1b:
A streaming query is a type of SQL query that runs continuously on a stream of data, rather than waiting for the entire dataset to be loaded into memory before executing the query.
In traditional relational databases, queries are typically executed against a fixed dataset, and the results are returned in a single pass. However, with Timeplus, you can create streams that continuously scan new events and output new results as they become available.
Streaming queries allow you to analyze data in real-time, without having to wait for the entire dataset to be loaded into memory. This is particularly useful when working with large datasets or streaming data from IoT devices, social media platforms, or other sources.
Some common use cases for streaming queries include:
* Real-time analytics: Analyzing data as it becomes available, such as tracking website traffic or monitoring stock prices.
* Event-driven processing: Processing events in real-time, such as sending notifications when a new record is inserted into a database.
* IoT data analysis: Analyzing sensor data from IoT devices to gain insights into the behavior of the device.
To create a streaming query with Timeplus, you can use the `table` function and specify the stream name and the type of query you want to execute.
Наприклад:
```sql
SELECT count(*) FROM stream1 WHERE timestamp > '2022-01-01 00:00:00';
Цей запит виконується на потоці stream1
, підраховуючи кількість рядків, де поле timestamp
більше за 1 січня 2022 року.
Streaming запити можна використовувати з різними функціями Timeplus, такими як:
* ingest
: Завантажити дані в потік.
* query
: Виконати запит до потоку.
* history
: Отримати історію потоку.
* purge
: Видалити старі записи з потоку.
Використовуючи streaming запити, ви можете отримати реальні інсайти з ваших даних і приймати більш обґрунтовані рішення.
```
До цього моменту ми успішно побудували систему для запитів і відповідей, і для запуску такої системи вам потрібен лише один простий SQL.
Підсумок
Цей покроковий посібник демонструє, як побудувати систему запитів і відповідей на основі RAG, використовуючи Ollama і Timeplus. Комбінуючи пошук та генерацію, ви можете створювати AI-застосунки, які є одночасно точними та динамічними, використовуючи дані в реальному часі для отримання змістовних результатів. Спробуйте потужність RAG у своїх проєктах вже сьогодні!
Повний код можна знайти тут: https://github.com/timeplus-io/examples/tree/main/ragquestionandanswersystem
Перекладено з: Building a RAG-Based Question/Answer System Using Ollama and Timeplus