Найти в Дзене
Искусственный разум

Настройка Supabase + pgvector или почему не стоит связываться с Pinecone

Оглавление

Введение

До сегодняшнего дня мои ИИ-агенты использовали векторную БД Pinecone для долгосрочной памяти, а таблицы Supabase — для краткосрочной (контекстное окно). Краткосрочная память работает просто: подгружаются только последние N сообщений. А вот у Pinecone возникла проблема — он не позволяет полноценно фильтровать воспоминания по метаданным (дата, тема, эмоции, ключевые слова и т.д.). А фильтрация необходима, чтобы повысить релевантность выдачи.

Например, когда ИИ-агент делает запрос к векторной БД, его текст переводится в вектор и сравнивается с векторами воспоминаний. Результаты сортируются по семантической близости. Но близость по смыслу ≠ актуальность: БД может вернуть старые и неактуальные воспоминания, потому что они ближе по смыслу. Pinecone (на бесплатном тарифе) не позволял фильтровать такие результаты по дате и прочим признакам.

Кроме того, бесплатный период Supabase подошёл к концу. Мне дали 2 недели на переход на платный тариф — $25 в месяц. Я не захотел платить «ни за что» и решил развернуть собственный Supabase с pgvector на VPS за 600₽ в месяц.

Вот как я это сделал (всё выполнялось через SSH-клиент "Terminus" кроме пункта "0"):

0. Разворачиваем VPS сервер с установленной на нем Supabase на beget прямо через личный кабинет.

Если у вашего сервиса, где вы хотите разворачивать VPS нет возможности сразу установить Supabase, просто устанавливаете Ubuntu (она уж точно есть), а затем командами:

- Установка зависимостей:

- sudo apt update
- sudo apt install -y curl unzip git

- Установка Supabase CLI:

curl -sL https://github.com/supabase/cli/releases/latest/download/supabase_linux_amd64.tar.gz | tar -xz
sudo mv supabase /usr/local/bin/

- Проверь, установилась ли сама Supabase:

supabase --version

1. Заход в PostgreSQL внутри контейнера:

psql -U postgres

Запускаем PostgreSQL внутри контейнера, используя пользователя postgres. Это нужно, чтобы проверить установленные расширения или подключить новые.

2. Проверка установленных расширений и попытка включить pgvector:

\dx
CREATE EXTENSION vector;

\dx показывает список расширений. Если pgvector уже установлен, CREATE EXTENSION активирует его.

3. Установка pgvector вручную, если оно отсутствует:

docker exec -it supabase-db bash

Заходим внутрь контейнера supabase-db, где установлен PostgreSQL.

apt update
apt install -y git postgresql-server-dev-all

Устанавливаем нужные зависимости для сборки расширений PostgreSQL.

cd /tmp
git clone https://github.com/pgvector/pgvector.git
cd pgvector
make && make install

Клонируем исходники pgvector и компилируем его вручную прямо внутри контейнера.

4. Повторная активация расширения после установки:

CREATE EXTENSION vector;

Теперь команда выполнится успешно. Проверить можно повторно через \dx.

5. Создание таблицы с полем эмбеддинга:

CREATE TABLE memories (
id TEXT PRIMARY KEY,
content TEXT,
embedding VECTOR(1536),
metadata JSONB,
timestamp TIMESTAMPTZ
);

Создаём основную таблицу, в которую n8n будет вставлять эмбеддинги. Тип VECTOR(1536) подходит для OpenAI text-embedding-3-large.

6. Добавление проброса порта PostgreSQL в docker-compose:

ports:
- "5432:5432"

Добавляем внутрь секции db: в docker-compose.yml. Без этого база будет недоступна извне.

7. Отключение Supavisor:

В .env нужно закомментировать параметр POOLER_TENANT_ID, иначе Supabase будет использовать Supavisor, который ограничивает доступ и требует другую схему аутентификации:

# POOLER_TENANT_ID=...

7.1. Настройка доступа в pg_hba.conf:

nano /var/lib/postgresql/data/pg_hba.conf

В конец файла добавляется строка:

host all all 0.0.0.0/0 md5

Это разрешает подключение к PostgreSQL с любого IP-адреса (можно ограничить при необходимости).

8. Перезапуск Supabase:

cd /opt/beget/supabase
docker compose down
docker compose up -d

Обновляем контейнеры после правки конфигурации.

9. Подключение к базе из n8n:

Создаём PostgreSQL credentials в интерфейсе n8n:

  • Host: parukesepem.beget.app
  • Port: 5432
  • Database: postgres
  • User: supabase_admin
  • Password: b1ZcdBzOX0q9odaXRU6a5g2X1SJWJiEK
  • SSL: Off

10. Проверка подключения через psql с другой машины:

psql -h parukesepem.beget.app -U supabase_admin -d postgres

Если соединение успешно — значит, Supabase полностью доступен извне, и pgvector работает.

Выводы

  • Pinecone не подходит для продвинутого использования: отсутствует фильтрация по метаданным, неясное ценообразование.
  • Supabase дал возможность поднять собственную инфраструктуру с поддержкой pgvector и SQL-запросов.
  • pgvector пришлось собирать вручную внутри контейнера PostgreSQL.
  • Supavisor был отключён, чтобы не мешал прямому подключению к базе.
  • Через pg_hba.conf был разрешён внешний доступ.
  • n8n теперь может напрямую взаимодействовать с таблицей memories — вставлять эмбеддинги, фильтровать по JSONB, делать семантический поиск.

⚙️ Система полностью автономна, дёшева в обслуживании и масштабируема.

Затем я написал SQL функцию (конечно же с помощью Клода) для своей таблицы:

CREATE OR REPLACE FUNCTION match_documents(
filter jsonb DEFAULT '{}',
match_count int DEFAULT 10,
query_embedding vector DEFAULT NULL
)
RETURNS TABLE (
id text,
content text,
metadata jsonb,
similarity float
)
LANGUAGE plpgsql
AS $$
BEGIN
IF query_embedding IS NULL THEN
RAISE EXCEPTION 'query_embedding cannot be NULL';
END IF;
RETURN QUERY
SELECT
m.id,
m.content,
m.metadata,
1 - (m.embedding <=> query_embedding) as similarity
FROM memories m
WHERE 1 - (m.embedding <=> query_embedding) > 0.5
ORDER BY m.embedding <=> query_embedding
LIMIT match_count;
END;
$$;

И теперь могу делать ГИБРИДНЫЕ запросы в таблицу, которые совмещают и векторный поиск и SQL запросы для фильтрации:

Вот запрос для функции match_documents в нужном формате:

sql

SELECT * FROMmatch_documents(

'{}'::jsonb,

5,

'{{ "[" + $json.data[0].embedding.join(",") + "]" }}'::vector

);

С фильтрами по метаданным:

Поиск по эмоции:

sql

SELECT * FROMmatch_documents(

'{"emotion": "интерес"}'::jsonb,

5,

'{{ "[" + $json.data[0].embedding.join(",") + "]" }}'::vector

);

Поиск за сегодняшний день:

sql

SELECT * FROMmatch_documents(

'{"today": true}'::jsonb,

5,

'{{ "[" + $json.data[0].embedding.join(",") + "]" }}'::vector

);

Поиск за диапазон дат:

sql

SELECT * FROMmatch_documents(

'{"date_from": "2025-08-01", "date_to": "2025-08-06"}'::jsonb,

5,

'{{ "[" + $json.data[0].embedding.join(",") + "]" }}'::vector

);

Комбинированный поиск (эмоция + дата):

sql

SELECT * FROMmatch_documents(

'{"emotion": "интерес", "today": true}'::jsonb,

5,

'{{ "[" + $json.data[0].embedding.join(",") + "]" }}'::vector

);