Как сделать ИИ-агента, который «понимает» базу данных и общается с клиентами

(практический гайд по pgvector + OpenAI Embeddings)

Задача 1

Мы хотим, чтобы ИИ-агент мог работать с большой базой данных и обрабатывать её так, как будто он «читает» всё содержимое.
Причём важно:

  • Базу можно обновлять: добавлять новые товары, пользователей, сообщения.
  • Доступ к данным должен быть быстрым и экономить ресурсы.
  • Агент должен уметь находить в базе релевантную информацию по смыслу, а не только по ключевым словам.

Рассмотрим два типичных сценария:

  1. Работа с товарами и клиентами — агент помогает вести консультации, отвечает на вопросы покупателей, собирает обратную связь и напоминает о повторной покупке.
  2. Агент-модератор сообщества (например, в Telegram-группе) — следит за тем, кто за что отвечает, кто что умеет, и напоминает участникам о пропущенных сообщениях.

Сложности

Главная проблема: контекстное окно модели ограничено.
Мы не можем каждый раз загружать в запрос к ИИ:

  • всю базу товаров (сотни позиций),
  • всех пользователей (тысячи клиентов),
  • или всю историю переписки группы.

Это:

  • дорого,
  • медленно,
  • технически ограничено (модель просто «не переварит» весь массив).

Решение — векторная база данных (pgvector)

Чтобы ИИ работал эффективно, мы будем хранить данные в векторном формате в PostgreSQL.
Для этого используется расширение pgvector. Оно добавляет новые типы данных для хранения эмбеддингов (векторов).

Типы:

  • vector(n) → обычный массив чисел (float4). n = размерность, например vector(1536).
  • halfvec(n) → массив в половинной точности (экономия памяти, меньше точность).
  • sparsevec(n) → хранит только ненулевые значения (для очень «разреженных» векторов).

👉 В нашей задаче мы будем использовать vector(1536) или vector(3072).


Получение вектора (эмбеддинга) через OpenAI API

Чтобы текст (например, описание БАДа или сообщение пользователя) стал «понятен» машине, его нужно преобразовать в вектор признаков.

Делаем запрос к OpenAI:

curl https://api.openai.com/v1/embeddings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "input": "Описание БАДа или вопрос пациента",
    "model": "text-embedding-3-small"
  }'

В ответ мы получаем массив чисел, например:

[0.008011777, 0.029998904, -0.02706108, ... ]

Этот массив и есть наш вектор (embedding).
Его мы сохраняем в колонку типа vector(1536) в таблице PostgreSQL.


Поиск по смыслу (semantic search)

Теперь, когда у каждого объекта (товара, клиента, сообщения) есть векторное представление, мы можем находить похожие данные.

Например, пользователь спрашивает:

«Что у вас есть для укрепления сердца и сосудов?»

  1. Запрос пользователя преобразуем в embedding через OpenAI.
  2. Делаем SQL-запрос: SELECT id, name, description FROM customatic_products ORDER BY embedding <-> '[вектор из API]' LIMIT 5; Здесь оператор <-> считает косинусное расстояние между векторами.
  3. На выходе мы получаем 5 наиболее похожих товаров, даже если в тексте не было точных совпадений по словам («сосуды» vs «артерии»).

Это и есть магия — поиск по смыслу, а не по ключевым словам.

Как обновлять базу и добавлять новые данные в pgvector через Make.com

Задача 2

У нас уже есть таблица customatic_products в Supabase, где каждый товар хранится вместе с вектором (embedding) для поиска по смыслу. Теперь нужно настроить процесс так, чтобы при добавлении или изменении товара мы автоматически:

  1. Получали все данные по товару (название, состав, показания, способ применения).
  2. Склеивали их в единый текст.
  3. Отправляли этот текст в OpenAI Embeddings API.
  4. Сохраняли вектор в поле embedding (тип vector(1536)) нашей базы.

Шаг 1. Добавляем товар

Представим, что мы добавили новый товар через интерфейс WeWeb или напрямую в Supabase. В таблице появился новый id.


Шаг 2. Запуск сценария в Make.com

Создаём сценарий:

  • Webhook / Database trigger (ловим событие: новый товар или обновление).
  • Получаем все данные товара из базы (Get a record).

Шаг 3. Склеиваем данные в единый текст

Нам нужно собрать все ключевые поля (название, описание, состав, показания и т.д.) в одну строку, чтобы embedding учитывал всё:

Пример:

Pride Cardio Premium (комплекс 1+2)  
Состав: лиственница, гинкго билоба, бакопа Монье...  
Показания: артериальная гипертензия, нарушения памяти, профилактика инсультов...  
Применение: Pride Cardio 1 — по 1 таблетке в день, Pride Cardio 2 — по 1 капсуле 2 раза в день.  

👉 В Make это можно сделать через модуль Set Variable или прямо в поле запроса к OpenAI.


Шаг 4. Делаем embedding

Отправляем текст в OpenAI Embeddings API:

POST https://api.openai.com/v1/embeddings
Authorization: Bearer {{api_key}}
Content-Type: application/json

{
  "input": "текст товара",
  "model": "text-embedding-3-small"
}

ибо перед отправкой в OpenAI Embeddings API нужно экранировать текст:
— заменить все переносы строк на \n,
— убрать или экранировать кавычки ("\").

Вы также можете сделать это таким образом:

{{replace(your_text; «\n»; «\n»)}}

Однако надежнее с помощью модуля Create Json.

На выходе получаем массив из 1536 чисел.


Шаг 5. Подготовка массива для Supabase

Вот тут главная хитрость ⚡️

По умолчанию Make вернёт embedding как JSON-массив (и при вставке в Supabase он уйдёт как строка "[...]"). Postgres pgvector это не понимает.

Решение: применяем функцию join:

[{{join(3.body.data[1].embedding; ",")}}]

👉 Теперь массив превращается в правильную строку без лишних кавычек:

[0.025095636,-0.015868386,-0.012682295,-0.0007661358]

Шаг 6. Записываем в Supabase

Обычный Upsert-запрос в таблицу:



✅ Готово! Теперь каждый новый или изменённый товар автоматически получает вектор в базе.

А теперь нам надо организовать поиск по базе. Для этого нам надо в supabase создать RPC-функцию.


1. Создаем функцию в Supabase Dashboard

Заходим в Supabase Dashboard → SQL Editor и выполняем там этот код:

CREATE OR REPLACE FUNCTION customatic_match_products(
query_embedding vector,
match_count int DEFAULT 3
)
RETURNS TABLE (
id uuid,
name text,
description text,
similarity float
)
LANGUAGE sql
STABLE
AS $$
SELECT
id,
name,
description,
1 - (embedding <=> query_embedding) AS similarity
FROM customatic_products
ORDER BY embedding <=> query_embedding
LIMIT match_count;
$$;

match_products — это название функции
query_embedding — переменная с эмбедингом запроса
match_count — количество возвращаемых строк

id uuid,
name text,
description text
это поля, который мы вытаскиваем из базы

customatic_products — имя таблицы

similarity — это виртуальный столбец (его в базе быть не должно) он показывает насколько релевантен запрос.

В Make делаем Supabase API Call на /rest/v1/rpc/match_products

В body передаем JSON собранный заранее модулем CreateJSON
Не забываем сджойнить дату из модуля ChatGPT по такому же принципу, чтобы не получать ошибку формата данных
[{{join(9.body.data[1].embedding; «,»)}}]

Получаем JSON такого вида:

{
"match_count":2,
"query_embedding":"[-0.00731227...]"
}

Anton Derbushev
Anton Derbushev
Статей: 34

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *