supabase-easy-rag 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- supabase_easy_rag-0.1.0/.env.example +71 -0
- supabase_easy_rag-0.1.0/.github/workflows/ci.yml +41 -0
- supabase_easy_rag-0.1.0/.github/workflows/publish.yml +74 -0
- supabase_easy_rag-0.1.0/.gitignore +58 -0
- supabase_easy_rag-0.1.0/LICENSE +20 -0
- supabase_easy_rag-0.1.0/MIGRATION_GUIDE.md +34 -0
- supabase_easy_rag-0.1.0/PKG-INFO +267 -0
- supabase_easy_rag-0.1.0/QUICKSTART.md +155 -0
- supabase_easy_rag-0.1.0/README.md +230 -0
- supabase_easy_rag-0.1.0/eval/README.md +79 -0
- supabase_easy_rag-0.1.0/eval/corpora/fetcher.py +97 -0
- supabase_easy_rag-0.1.0/eval/corpora/registry.json +52 -0
- supabase_easy_rag-0.1.0/eval/data/haystack/distractors/cloud_architecture.md +13 -0
- supabase_easy_rag-0.1.0/eval/data/haystack/distractors/company_policy.md +13 -0
- supabase_easy_rag-0.1.0/eval/data/haystack/distractors/database_indexing.md +13 -0
- supabase_easy_rag-0.1.0/eval/data/haystack/distractors/llm_fine_tuning.md +15 -0
- supabase_easy_rag-0.1.0/eval/data/haystack/needles/db_config_timeout.md +12 -0
- supabase_easy_rag-0.1.0/eval/data/haystack/needles/financial_acquisition.md +12 -0
- supabase_easy_rag-0.1.0/eval/data/haystack/needles/medical_dosage.md +12 -0
- supabase_easy_rag-0.1.0/eval/data/haystack/needles/rls_audit_hash.md +12 -0
- supabase_easy_rag-0.1.0/eval/data/haystack/needles/starlight_passkey.md +15 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_1_depth_0.md +81 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_1_depth_100.md +81 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_1_depth_25.md +81 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_1_depth_50.md +81 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_1_depth_75.md +81 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_2_depth_0.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_2_depth_100.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_2_depth_25.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_2_depth_50.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_2_depth_75.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_3_depth_0.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_3_depth_100.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_3_depth_25.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_3_depth_50.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_3_depth_75.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_4_depth_0.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_4_depth_100.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_4_depth_25.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_4_depth_50.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_4_depth_75.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_5_depth_0.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_5_depth_100.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_5_depth_25.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_5_depth_50.md +78 -0
- supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_5_depth_75.md +78 -0
- supabase_easy_rag-0.1.0/eval/dataset.json +62 -0
- supabase_easy_rag-0.1.0/eval/dataset_niah.json +47 -0
- supabase_easy_rag-0.1.0/eval/dataset_niah_synthetic.json +317 -0
- supabase_easy_rag-0.1.0/eval/evaluate.py +216 -0
- supabase_easy_rag-0.1.0/eval/generate_dataset.py +127 -0
- supabase_easy_rag-0.1.0/eval/output/.gitkeep +0 -0
- supabase_easy_rag-0.1.0/eval/providers/azure_llm.py +52 -0
- supabase_easy_rag-0.1.0/eval/run_full_cycle.py +123 -0
- supabase_easy_rag-0.1.0/eval/run_niah.py +143 -0
- supabase_easy_rag-0.1.0/eval/test_isolation.py +72 -0
- supabase_easy_rag-0.1.0/examples/azure_example.py +44 -0
- supabase_easy_rag-0.1.0/examples/openai_example.py +30 -0
- supabase_easy_rag-0.1.0/pyproject.toml +56 -0
- supabase_easy_rag-0.1.0/sql/01_schema.sql +305 -0
- supabase_easy_rag-0.1.0/sql/02_functions.sql +608 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/__init__.py +34 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/cli.py +169 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/config.py +126 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/core/client.py +255 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/core/exceptions.py +14 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/core/models.py +54 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/ingestion/chunker.py +74 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/ingestion/facets.py +97 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/ingestion/parser.py +140 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/ingestion/syncer.py +355 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/providers/__init__.py +13 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/providers/azure.py +73 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/providers/base.py +19 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/providers/chat_base.py +16 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/providers/local_provider.py +28 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/providers/openai.py +104 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/py.typed +1 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/retrieval/engine.py +324 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/retrieval/postgrest_client.py +99 -0
- supabase_easy_rag-0.1.0/supabase_easy_rag/security/tokens.py +64 -0
- supabase_easy_rag-0.1.0/tests/__init__.py +3 -0
- supabase_easy_rag-0.1.0/tests/test_parser.py +115 -0
- supabase_easy_rag-0.1.0/tests/test_providers.py +61 -0
- supabase_easy_rag-0.1.0/tests/test_retrieval.py +82 -0
- supabase_easy_rag-0.1.0/tests/test_security.py +90 -0
- supabase_easy_rag-0.1.0/uv.lock +876 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Supabase Easy RAG — Environment Variables
|
|
2
|
+
# Copy to .env and fill. Never commit .env with real secrets.
|
|
3
|
+
# See README.md / QUICKSTART.md for setup. All comments in English.
|
|
4
|
+
|
|
5
|
+
# =============================================================================
|
|
6
|
+
# 1. Supabase — required
|
|
7
|
+
# =============================================================================
|
|
8
|
+
SUPABASE_URL=https://your-project.supabase.co
|
|
9
|
+
SUPABASE_PUBLISHABLE_KEY=sb_publishable_YOUR_PUBLISHABLE_KEY
|
|
10
|
+
SUPABASE_SECRET_KEY=sb_secret_YOUR_SECRET_KEY
|
|
11
|
+
# Legacy aliases (still supported as fallback):
|
|
12
|
+
# SUPABASE_ANON_KEY=sb_publishable_YOUR_PUBLISHABLE_KEY
|
|
13
|
+
# SUPABASE_SERVICE_ROLE_KEY=sb_secret_YOUR_SECRET_KEY
|
|
14
|
+
|
|
15
|
+
# =============================================================================
|
|
16
|
+
# 2. Chat LLM — required, OpenAI-compatible API
|
|
17
|
+
# Model name is explicit in config (e.g. text-embedding-3-small, gpt-5.4-nano).
|
|
18
|
+
# Works with Azure OpenAI, OpenAI, or any OpenAI-compatible endpoint.
|
|
19
|
+
# =============================================================================
|
|
20
|
+
LLM_MODEL=gpt-5.4-nano
|
|
21
|
+
LLM_ENDPOINT=https://your-resource.services.ai.azure.com/
|
|
22
|
+
LLM_API_KEY=sk-YOUR_LLM_API_KEY
|
|
23
|
+
|
|
24
|
+
# Optional: separate model for secondary slot (defaults to LLM_MODEL with nano->mini)
|
|
25
|
+
# LLM_MINI_MODEL=gpt-5.4-mini
|
|
26
|
+
|
|
27
|
+
# Legacy aliases for chat (fallback, not needed if LLM_* set):
|
|
28
|
+
# KNOWLEDGEBASE_CHAT_MODEL=gpt-5.4-nano
|
|
29
|
+
# KNOWLEDGEBASE_CHAT_ENDPOINT=https://your-resource.services.ai.azure.com/
|
|
30
|
+
# KNOWLEDGEBASE_CHAT_API_KEY=sk-YOUR_KEY
|
|
31
|
+
# AZURE_OPENAI_ENDPOINT_NANO=https://your-resource.openai.azure.com/
|
|
32
|
+
# AZURE_OPENAI_API_KEY_NANO=sk-YOUR_KEY
|
|
33
|
+
# AZURE_OPENAI_DEPLOYMENT_NANO=gpt-5.4-nano
|
|
34
|
+
# AZURE_OPENAI_API_VERSION_NANO=2024-02-15-preview
|
|
35
|
+
|
|
36
|
+
# =============================================================================
|
|
37
|
+
# 3. Embeddings — optional separate endpoint/key, else reuses Chat LLM
|
|
38
|
+
# =============================================================================
|
|
39
|
+
EMBEDDING_MODEL=text-embedding-3-small
|
|
40
|
+
# EMBEDDING_ENDPOINT=https://your-resource.services.ai.azure.com/
|
|
41
|
+
# EMBEDDING_API_KEY=sk-YOUR_EMBEDDING_KEY
|
|
42
|
+
# EMBEDDING_PROVIDER=azure
|
|
43
|
+
# KNOWLEDGEBASE_EMBEDDING_DIM=1536
|
|
44
|
+
|
|
45
|
+
# Legacy aliases for embeddings (fallback):
|
|
46
|
+
# KNOWLEDGEBASE_EMBEDDING_MODEL=text-embedding-3-small
|
|
47
|
+
# KNOWLEDGEBASE_EMBEDDING_ENDPOINT=https://your-resource.services.ai.azure.com/
|
|
48
|
+
# KNOWLEDGEBASE_EMBEDDING_API_KEY=sk-YOUR_KEY
|
|
49
|
+
# AZURE_OPENAI_ENDPOINT_EMBEDDING=https://your-resource.openai.azure.com/
|
|
50
|
+
# AZURE_OPENAI_API_KEY_EMBEDDING=sk-YOUR_KEY
|
|
51
|
+
# AZURE_OPENAI_DEPLOYMENT_EMBEDDING=text-embedding-3-small
|
|
52
|
+
# OPENAI_API_KEY=sk-YOUR_OPENAI_KEY
|
|
53
|
+
# OPENAI_BASE_URL=https://api.openai.com/v1
|
|
54
|
+
|
|
55
|
+
# =============================================================================
|
|
56
|
+
# 4. RAG — optional tuning (defaults shown)
|
|
57
|
+
# =============================================================================
|
|
58
|
+
# KNOWLEDGEBASE_SCHEMA=knowledgebase
|
|
59
|
+
# KNOWLEDGEBASE_USE_RLS=false
|
|
60
|
+
# KNOWLEDGEBASE_ACCESS_TOKEN=kb_live_YOUR_TOKEN
|
|
61
|
+
# KNOWLEDGEBASE_CHUNK_SIZE=800
|
|
62
|
+
# KNOWLEDGEBASE_CHUNK_OVERLAP=100
|
|
63
|
+
# KNOWLEDGEBASE_PROCESS_BATCH_SIZE=20
|
|
64
|
+
# KNOWLEDGEBASE_DEFAULT_MATCH_COUNT=5
|
|
65
|
+
|
|
66
|
+
# =============================================================================
|
|
67
|
+
# 5. Alternative names — priority: LLM_* > KNOWLEDGEBASE_* > AZURE_* > OPENAI_*
|
|
68
|
+
# =============================================================================
|
|
69
|
+
# For OpenAI directly (no Azure):
|
|
70
|
+
# OPENAI_API_KEY=sk-YOUR_OPENAI_KEY
|
|
71
|
+
# OPENAI_BASE_URL=https://api.openai.com/v1
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "main", "master" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "main", "master" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout code
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python-version }}
|
|
25
|
+
cache: "pip"
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
python -m pip install -e ".[dev]"
|
|
31
|
+
|
|
32
|
+
- name: Run unit tests (offline, mocked)
|
|
33
|
+
env:
|
|
34
|
+
SUPABASE_URL: "http://localhost:54321"
|
|
35
|
+
SUPABASE_SERVICE_ROLE_KEY: "dummy-service-key"
|
|
36
|
+
SUPABASE_ANON_KEY: "dummy-anon-key"
|
|
37
|
+
OPENAI_API_KEY: "sk-dummy-key"
|
|
38
|
+
LLM_API_KEY: "sk-dummy-key"
|
|
39
|
+
EMBEDDING_API_KEY: "sk-dummy-key"
|
|
40
|
+
run: |
|
|
41
|
+
python -m pytest tests/ -v
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
push:
|
|
7
|
+
tags:
|
|
8
|
+
- "v*"
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
inputs:
|
|
11
|
+
repository_url:
|
|
12
|
+
description: "Target repository (leave empty for PyPI, or 'https://test.pypi.org/legacy/' for TestPyPI)"
|
|
13
|
+
required: false
|
|
14
|
+
default: ""
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
name: Build distribution 📦
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout code
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.11"
|
|
29
|
+
|
|
30
|
+
- name: Install build tools
|
|
31
|
+
run: |
|
|
32
|
+
python -m pip install --upgrade pip
|
|
33
|
+
python -m pip install build twine
|
|
34
|
+
|
|
35
|
+
- name: Build sdist and wheel
|
|
36
|
+
run: python -m build
|
|
37
|
+
|
|
38
|
+
- name: Check package metadata with twine
|
|
39
|
+
run: twine check dist/*
|
|
40
|
+
|
|
41
|
+
- name: Upload distribution artifacts
|
|
42
|
+
uses: actions/upload-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: pypi-artifacts
|
|
45
|
+
path: dist/
|
|
46
|
+
retention-days: 7
|
|
47
|
+
|
|
48
|
+
publish:
|
|
49
|
+
name: Publish to PyPI 🚀
|
|
50
|
+
needs: [build]
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
permissions:
|
|
53
|
+
# Required for PyPI Trusted Publishing (OIDC)
|
|
54
|
+
id-token: write
|
|
55
|
+
contents: read
|
|
56
|
+
environment:
|
|
57
|
+
name: pypi
|
|
58
|
+
url: https://pypi.org/p/supabase-easy-rag
|
|
59
|
+
|
|
60
|
+
steps:
|
|
61
|
+
- name: Download distribution artifacts
|
|
62
|
+
uses: actions/download-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: pypi-artifacts
|
|
65
|
+
path: dist/
|
|
66
|
+
|
|
67
|
+
- name: Publish to PyPI via Trusted Publishing
|
|
68
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
69
|
+
with:
|
|
70
|
+
repository-url: ${{ inputs.repository_url || '' }}
|
|
71
|
+
# If using static token instead of OIDC trusted publishing, secret can be passed:
|
|
72
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
73
|
+
skip-existing: true
|
|
74
|
+
print-hash: true
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual Environments
|
|
24
|
+
.venv
|
|
25
|
+
venv/
|
|
26
|
+
ENV/
|
|
27
|
+
env/
|
|
28
|
+
|
|
29
|
+
# IDE / Editors
|
|
30
|
+
.vscode/
|
|
31
|
+
.idea/
|
|
32
|
+
*.swp
|
|
33
|
+
*.swo
|
|
34
|
+
|
|
35
|
+
# Environment Variables — never commit real secrets
|
|
36
|
+
.env
|
|
37
|
+
.env.local
|
|
38
|
+
.env.*.local
|
|
39
|
+
!.env.example
|
|
40
|
+
|
|
41
|
+
# Testing & Coverage
|
|
42
|
+
.pytest_cache/
|
|
43
|
+
.coverage
|
|
44
|
+
htmlcov/
|
|
45
|
+
|
|
46
|
+
# OS / temp
|
|
47
|
+
.DS_Store
|
|
48
|
+
Thumbs.db
|
|
49
|
+
|
|
50
|
+
# Supabase local
|
|
51
|
+
supabase/.temp/
|
|
52
|
+
supabase/.branches/
|
|
53
|
+
|
|
54
|
+
# Local eval reports (generated) — all outputs go to eval/output/
|
|
55
|
+
eval/output/*
|
|
56
|
+
!eval/output/.gitkeep
|
|
57
|
+
eval/report*.json
|
|
58
|
+
eval/*.log
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Cherkasov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO MECHANICAL FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
18
|
+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
|
20
|
+
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Migration Guide — Supabase Cloud
|
|
2
|
+
|
|
3
|
+
If your Supabase database (`https://your-project.supabase.co`) is brand new, the `knowledgebase` schema does not exist yet (resulting in `PGRST106 Invalid schema: knowledgebase`). You need to run the SQL migration scripts once in the Supabase SQL Editor.
|
|
4
|
+
|
|
5
|
+
## Setup Steps (2 minutes)
|
|
6
|
+
|
|
7
|
+
1. Open `https://supabase.com/dashboard/project/YOUR_PROJECT_REF/sql/new` (replace `YOUR_PROJECT_REF` with your project reference from `SUPABASE_URL`).
|
|
8
|
+
2. Copy and run the entire content of `sql/01_schema.sql` (or `supabase/migrations/20260811000001_schema.sql`).
|
|
9
|
+
3. Copy and run the entire content of `sql/02_functions.sql` (or `supabase/migrations/20260811000002_functions.sql`).
|
|
10
|
+
4. Verify the database tables:
|
|
11
|
+
```sql
|
|
12
|
+
select schema_name from information_schema.schemata where schema_name='knowledgebase';
|
|
13
|
+
select table_name from information_schema.tables where table_schema='knowledgebase';
|
|
14
|
+
```
|
|
15
|
+
You should see 8 tables: `documents`, `document_owners`, `document_sections`, `chunks`, `facets`, `document_facets`, `ingestion_runs`, and `access_tokens`.
|
|
16
|
+
|
|
17
|
+
5. Expose schema for PostgREST (if not exposed automatically):
|
|
18
|
+
Dashboard -> Settings -> API -> Exposed schemas -> add `knowledgebase` (or execute in SQL Editor):
|
|
19
|
+
```sql
|
|
20
|
+
-- Only needed if Supabase does not expose the schema automatically
|
|
21
|
+
alter role anon set pgrst.db_schemas = 'public, storage, graphql_public, knowledgebase';
|
|
22
|
+
alter role service_role set pgrst.db_schemas = 'public, storage, graphql_public, knowledgebase';
|
|
23
|
+
select pg_reload_conf();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
6. Test connection:
|
|
27
|
+
```bash
|
|
28
|
+
uv run python -c "from supabase_easy_rag.retrieval.postgrest_client import create_postgrest_client; c=create_postgrest_client('https://your-project.supabase.co','sb_secret_YOUR_SECRET_KEY', schema_name='knowledgebase'); print(c.schema('knowledgebase').table('documents').select('id').limit(1).execute().data)"
|
|
29
|
+
```
|
|
30
|
+
Should return `[]` without `PGRST106` errors.
|
|
31
|
+
|
|
32
|
+
## Context
|
|
33
|
+
PostgREST in Supabase Cloud exposes `public, graphql_public` schemas by default. Creating the `knowledgebase` schema makes Supabase expose it dynamically, but a reload might be required. Once migrated, RLS policies (`auth.uid() -> documents.owner_id`) and hybrid RPC functions (`search_chunks_hybrid`, `*_rls`) operate seamlessly.
|
|
34
|
+
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: supabase-easy-rag
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Production-ready, lightweight Hybrid RAG (Vector + Full-Text Search + Facets) engine for Supabase.
|
|
5
|
+
Author: Alexander Cherkasov
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: embeddings,hybrid-search,pgvector,postgrest,rag,supabase,vector
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Database
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: httpx>=0.24.0
|
|
20
|
+
Requires-Dist: openai>=1.0.0
|
|
21
|
+
Requires-Dist: postgrest<1.0.0,>=0.10.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0.0
|
|
23
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
24
|
+
Requires-Dist: rich>=13.0.0
|
|
25
|
+
Requires-Dist: typer>=0.9.0
|
|
26
|
+
Provides-Extra: async
|
|
27
|
+
Requires-Dist: httpx>=0.24.0; extra == 'async'
|
|
28
|
+
Provides-Extra: azure
|
|
29
|
+
Requires-Dist: openai>=1.0.0; extra == 'azure'
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
34
|
+
Provides-Extra: openai
|
|
35
|
+
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# Supabase Easy RAG
|
|
39
|
+
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
[](https://python.org)
|
|
42
|
+
|
|
43
|
+
**Supabase Easy RAG** is a production-ready, lightweight Hybrid RAG (Retrieval-Augmented Generation) engine for Supabase PostgreSQL.
|
|
44
|
+
|
|
45
|
+
It brings enterprise-grade **Hybrid Search (Weighted Vector + Full-Text Search + Title Boosting)**, **Hierarchical Metadata & Path Facets**, **Incremental Markdown Document Syncing**, and **Fine-Grained Access Control via RLS (auth.uid())** to your Supabase application without heavy framework overhead.
|
|
46
|
+
|
|
47
|
+
Inspired by Supabase's official guide [RAG with Permissions](https://supabase.com/docs/guides/ai/rag-with-permissions) — pgvector + Row Level Security.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## ⚡ Features
|
|
52
|
+
|
|
53
|
+
- **Fine-Grained Access Control (RLS)**: Per-document `owner_id = auth.uid()` + join table `document_owners` for shared docs, public/private visibility. Search automatically filters by `auth.uid()` via RLS policies.
|
|
54
|
+
- **Hybrid Search Engine**: Combines cosine vector similarity (70%), full-text search (`tsvector` / `websearch_to_tsquery`, 30%), and exact/partial title matching boosts.
|
|
55
|
+
- **Hierarchical Markdown Ingestion**: Automatically extracts document titles, section structures (H2–H6), metadata headers, and builds directory/attribute facets.
|
|
56
|
+
- **Incremental Sync**: Uses SHA-256 checksums to sync only new or modified documents, saving embedding API costs.
|
|
57
|
+
- **Dual Auth Mode**: `service_role` + SHA-256 tokens for backend jobs **and** `anon` + user JWT for end-user RLS (zero token needed).
|
|
58
|
+
- **Graceful Fallbacks**: If embedding providers fail, search automatically falls back to full-text search without breaking runtime applications.
|
|
59
|
+
- **Pluggable Embedding Providers**: Supports OpenAI, Azure OpenAI, and custom callbacks.
|
|
60
|
+
- **CLI & Python SDK**: Easy CLI (`easy-rag`) for SQL migrations, syncing, querying, and managing access tokens.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## ⚡ Performance & Retrieval Speed
|
|
65
|
+
|
|
66
|
+
| Retrieval Mode | Latency (Avg) | Indexing Type | Best Used For |
|
|
67
|
+
| :--- | :---: | :--- | :--- |
|
|
68
|
+
| **Hybrid Search** | **< 1.5 ms** | HNSW (`vector_cosine_ops`) + GIN (`tsvector`) | Production default — combines semantic context with exact keywords |
|
|
69
|
+
| **Vector Search (ANN)** | **< 1.2 ms** | HNSW (`m=16`, `ef_construction=64`) | Semantic similarity, concept matching, multi-lingual queries |
|
|
70
|
+
| **Full-Text Search (FTS)** | **< 0.8 ms** | GIN index (`websearch_to_tsquery`) | Exact part numbers, hash codes, IDs, exact term matches |
|
|
71
|
+
| **Async Retrieval Engine** | **Sub-millisecond** | Async PostgREST HTTP Connection Pool | High-concurrency async web frameworks (FastAPI, Starlette, Trio) |
|
|
72
|
+
|
|
73
|
+
### 🚀 Bulk Ingestion Throughput
|
|
74
|
+
- **Bulk Array Sync**: Chunks, facets, and document relationships are inserted in optimized bulk arrays (`table.insert([...])`), eliminating $N+1$ PostgREST roundtrips.
|
|
75
|
+
- **Incremental SHA-256 Hashing**: Bypasses un-modified files, saving 100% of embedding API costs on repeated sync runs.
|
|
76
|
+
- **Postgres HNSW Indexing**: Uses HNSW vector index (`WITH (m = 16, ef_construction = 64)`) to deliver sub-2ms query times even at scale.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 🚀 Quick Start
|
|
81
|
+
|
|
82
|
+
### 1. Installation
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install supabase-easy-rag
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 2. Apply Database Migrations to Supabase
|
|
89
|
+
|
|
90
|
+
Export the SQL migration files using the CLI:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
easy-rag init-sql --output ./migrations
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Run `01_schema.sql` and `02_functions.sql` inside your Supabase SQL Editor.
|
|
97
|
+
|
|
98
|
+
`01_schema.sql` now creates:
|
|
99
|
+
- `knowledgebase.documents.owner_id UUID REFERENCES auth.users(id) DEFAULT auth.uid()`
|
|
100
|
+
- `knowledgebase.document_owners` (many-to-many, for shared docs)
|
|
101
|
+
- RLS policies `Users can query their own document sections/chunks` (`document_id IN (SELECT id FROM documents WHERE owner_id = auth.uid())`)
|
|
102
|
+
|
|
103
|
+
### 3. Set Environment Variables
|
|
104
|
+
|
|
105
|
+
Create a `.env` file:
|
|
106
|
+
|
|
107
|
+
```env
|
|
108
|
+
# Always needed
|
|
109
|
+
SUPABASE_URL="https://your-project.supabase.co"
|
|
110
|
+
SUPABASE_SERVICE_ROLE_KEY="your-service-role-key"
|
|
111
|
+
OPENAI_API_KEY="sk-..."
|
|
112
|
+
|
|
113
|
+
# For end-user RLS mode (Supabase Auth)
|
|
114
|
+
SUPABASE_ANON_KEY="your-anon-key"
|
|
115
|
+
# Optional: force RLS mode globally
|
|
116
|
+
KNOWLEDGEBASE_USE_RLS="false"
|
|
117
|
+
|
|
118
|
+
# For legacy token mode (backend jobs)
|
|
119
|
+
KNOWLEDGEBASE_ACCESS_TOKEN="kb_live_your_generated_access_token"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 4. Create an Access Token (token mode, optional)
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
easy-rag create-token "My Production Agent Token"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 🔐 RAG with Permissions (RLS)
|
|
131
|
+
|
|
132
|
+
This implements the pattern from Supabase's guide verbatim:
|
|
133
|
+
|
|
134
|
+
```sql
|
|
135
|
+
-- Documents track owner
|
|
136
|
+
create table knowledgebase.documents (
|
|
137
|
+
id uuid primary key default gen_random_uuid(),
|
|
138
|
+
owner_id uuid references auth.users(id) default auth.uid(),
|
|
139
|
+
...
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
-- Chunks filtered via linked document
|
|
143
|
+
create policy "Users can query their own chunks"
|
|
144
|
+
on knowledgebase.chunks for select to authenticated using (
|
|
145
|
+
document_id in (
|
|
146
|
+
select id from knowledgebase.documents
|
|
147
|
+
where owner_id = auth.uid()
|
|
148
|
+
or exists (select 1 from knowledgebase.document_owners where document_id = documents.id and owner_id = auth.uid())
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Now every `select` or vector search via `authenticated` role is implicitly filtered:
|
|
154
|
+
|
|
155
|
+
```sql
|
|
156
|
+
select * from knowledgebase.chunks
|
|
157
|
+
where embedding <#> query_embedding < -threshold
|
|
158
|
+
order by embedding <#> query_embedding;
|
|
159
|
+
-- only returns chunks for docs you own
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Alternative scenarios covered
|
|
163
|
+
|
|
164
|
+
**1. Documents owned by multiple people** — use `knowledgebase.document_owners`:
|
|
165
|
+
|
|
166
|
+
```sql
|
|
167
|
+
insert into knowledgebase.document_owners (document_id, owner_id) values ('doc-uuid', 'user-uuid');
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Policy already checks `document_owners` join table.
|
|
171
|
+
|
|
172
|
+
**2. External user DB / FDW** — uncomment the `app.current_user_id` policy in `01_schema.sql` or use custom JWT with `auth.uid()`:
|
|
173
|
+
|
|
174
|
+
```sql
|
|
175
|
+
-- Direct Postgres connection
|
|
176
|
+
set app.current_user_id = '<current-user-id>';
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**3. Public vs Private docs** — during sync:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Private (default): owner_id = explicit or auth.uid()
|
|
183
|
+
easy-rag sync ./docs --owner-id a0eebc99-...
|
|
184
|
+
|
|
185
|
+
# Public: readable by all authenticated
|
|
186
|
+
easy-rag sync ./docs --public
|
|
187
|
+
|
|
188
|
+
# Or via metadata in markdown:
|
|
189
|
+
## Metadata
|
|
190
|
+
- **Owner ID**: a0eebc99-...
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 💻 Python Usage
|
|
196
|
+
|
|
197
|
+
### Token mode (backend jobs)
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from supabase_easy_rag import EasyRagClient
|
|
201
|
+
|
|
202
|
+
client = EasyRagClient() # uses SERVICE_ROLE + KNOWLEDGEBASE_ACCESS_TOKEN
|
|
203
|
+
|
|
204
|
+
# 1. Sync a directory of Markdown documents (service_role can set owner)
|
|
205
|
+
client.sync_directory("./docs", owner_id="a0eebc99-...")
|
|
206
|
+
|
|
207
|
+
# 2. Perform Hybrid Search (token checked via assert_retrieval_access)
|
|
208
|
+
results = client.search_hybrid(
|
|
209
|
+
query="How do I configure vector indexes?",
|
|
210
|
+
match_count=5,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
for item in results:
|
|
214
|
+
print(f"[{item.hybrid_score:.4f}] {item.document_title} > {item.section_title or 'Main'}")
|
|
215
|
+
print(item.chunk_text)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### RLS mode (end-user, per Supabase guide)
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from supabase_easy_rag import EasyRagClient
|
|
222
|
+
|
|
223
|
+
# Option A: explicit user JWT (from Supabase Auth)
|
|
224
|
+
client = EasyRagClient(user_jwt="eyJhbGci...") # uses ANON_KEY + user JWT
|
|
225
|
+
results = client.search_hybrid("How do I configure vector indexes?") # no token needed, RLS filters via auth.uid()
|
|
226
|
+
|
|
227
|
+
# Option B: scoped per-request helper
|
|
228
|
+
backend = EasyRagClient() # service_role
|
|
229
|
+
user_client = backend.for_user(user_jwt)
|
|
230
|
+
results = user_client.search_hybrid("my private docs")
|
|
231
|
+
|
|
232
|
+
# Works for all search types
|
|
233
|
+
user_client.search_vector("hello", use_rls=True)
|
|
234
|
+
user_client.search_fts("hello", use_rls=True)
|
|
235
|
+
|
|
236
|
+
# RLS also works via PostgREST directly with RLS variants:
|
|
237
|
+
# knowledgebase.search_chunks_hybrid_rls, match_chunks_by_embedding_rls, etc. (SECURITY INVOKER)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Direct Supabase JS (RLS)
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
const { data } = await supabase
|
|
244
|
+
.schema('knowledgebase')
|
|
245
|
+
.rpc('search_chunks_hybrid_rls', {
|
|
246
|
+
p_query: 'vector indexes',
|
|
247
|
+
p_query_embedding: embedding,
|
|
248
|
+
p_match_count: 5
|
|
249
|
+
})
|
|
250
|
+
// RLS automatically filters to current user
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 🛠️ CLI Reference
|
|
256
|
+
|
|
257
|
+
- `easy-rag init-sql` — Generate Supabase SQL migration files.
|
|
258
|
+
- `easy-rag sync <directory> [--owner-id UUID] [--public]` — Sync markdown files to Supabase with RLS ownership.
|
|
259
|
+
- `easy-rag query "<text>" [--mode hybrid|vector|fts] [--token TOKEN] [--rls --user-jwt JWT]` — Run search (token or RLS mode).
|
|
260
|
+
- `easy-rag create-token "<name>"` — Create a new RAG access token (token mode).
|
|
261
|
+
- `easy-rag list-tokens` — List all registered access tokens.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 📄 License
|
|
266
|
+
|
|
267
|
+
Distributed under the MIT License. See [LICENSE](LICENSE) for details.
|