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.
Files changed (87) hide show
  1. supabase_easy_rag-0.1.0/.env.example +71 -0
  2. supabase_easy_rag-0.1.0/.github/workflows/ci.yml +41 -0
  3. supabase_easy_rag-0.1.0/.github/workflows/publish.yml +74 -0
  4. supabase_easy_rag-0.1.0/.gitignore +58 -0
  5. supabase_easy_rag-0.1.0/LICENSE +20 -0
  6. supabase_easy_rag-0.1.0/MIGRATION_GUIDE.md +34 -0
  7. supabase_easy_rag-0.1.0/PKG-INFO +267 -0
  8. supabase_easy_rag-0.1.0/QUICKSTART.md +155 -0
  9. supabase_easy_rag-0.1.0/README.md +230 -0
  10. supabase_easy_rag-0.1.0/eval/README.md +79 -0
  11. supabase_easy_rag-0.1.0/eval/corpora/fetcher.py +97 -0
  12. supabase_easy_rag-0.1.0/eval/corpora/registry.json +52 -0
  13. supabase_easy_rag-0.1.0/eval/data/haystack/distractors/cloud_architecture.md +13 -0
  14. supabase_easy_rag-0.1.0/eval/data/haystack/distractors/company_policy.md +13 -0
  15. supabase_easy_rag-0.1.0/eval/data/haystack/distractors/database_indexing.md +13 -0
  16. supabase_easy_rag-0.1.0/eval/data/haystack/distractors/llm_fine_tuning.md +15 -0
  17. supabase_easy_rag-0.1.0/eval/data/haystack/needles/db_config_timeout.md +12 -0
  18. supabase_easy_rag-0.1.0/eval/data/haystack/needles/financial_acquisition.md +12 -0
  19. supabase_easy_rag-0.1.0/eval/data/haystack/needles/medical_dosage.md +12 -0
  20. supabase_easy_rag-0.1.0/eval/data/haystack/needles/rls_audit_hash.md +12 -0
  21. supabase_easy_rag-0.1.0/eval/data/haystack/needles/starlight_passkey.md +15 -0
  22. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_1_depth_0.md +81 -0
  23. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_1_depth_100.md +81 -0
  24. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_1_depth_25.md +81 -0
  25. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_1_depth_50.md +81 -0
  26. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_1_depth_75.md +81 -0
  27. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_2_depth_0.md +78 -0
  28. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_2_depth_100.md +78 -0
  29. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_2_depth_25.md +78 -0
  30. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_2_depth_50.md +78 -0
  31. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_2_depth_75.md +78 -0
  32. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_3_depth_0.md +78 -0
  33. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_3_depth_100.md +78 -0
  34. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_3_depth_25.md +78 -0
  35. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_3_depth_50.md +78 -0
  36. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_3_depth_75.md +78 -0
  37. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_4_depth_0.md +78 -0
  38. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_4_depth_100.md +78 -0
  39. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_4_depth_25.md +78 -0
  40. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_4_depth_50.md +78 -0
  41. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_4_depth_75.md +78 -0
  42. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_5_depth_0.md +78 -0
  43. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_5_depth_100.md +78 -0
  44. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_5_depth_25.md +78 -0
  45. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_5_depth_50.md +78 -0
  46. supabase_easy_rag-0.1.0/eval/data/synthetic_docs/needle_5_depth_75.md +78 -0
  47. supabase_easy_rag-0.1.0/eval/dataset.json +62 -0
  48. supabase_easy_rag-0.1.0/eval/dataset_niah.json +47 -0
  49. supabase_easy_rag-0.1.0/eval/dataset_niah_synthetic.json +317 -0
  50. supabase_easy_rag-0.1.0/eval/evaluate.py +216 -0
  51. supabase_easy_rag-0.1.0/eval/generate_dataset.py +127 -0
  52. supabase_easy_rag-0.1.0/eval/output/.gitkeep +0 -0
  53. supabase_easy_rag-0.1.0/eval/providers/azure_llm.py +52 -0
  54. supabase_easy_rag-0.1.0/eval/run_full_cycle.py +123 -0
  55. supabase_easy_rag-0.1.0/eval/run_niah.py +143 -0
  56. supabase_easy_rag-0.1.0/eval/test_isolation.py +72 -0
  57. supabase_easy_rag-0.1.0/examples/azure_example.py +44 -0
  58. supabase_easy_rag-0.1.0/examples/openai_example.py +30 -0
  59. supabase_easy_rag-0.1.0/pyproject.toml +56 -0
  60. supabase_easy_rag-0.1.0/sql/01_schema.sql +305 -0
  61. supabase_easy_rag-0.1.0/sql/02_functions.sql +608 -0
  62. supabase_easy_rag-0.1.0/supabase_easy_rag/__init__.py +34 -0
  63. supabase_easy_rag-0.1.0/supabase_easy_rag/cli.py +169 -0
  64. supabase_easy_rag-0.1.0/supabase_easy_rag/config.py +126 -0
  65. supabase_easy_rag-0.1.0/supabase_easy_rag/core/client.py +255 -0
  66. supabase_easy_rag-0.1.0/supabase_easy_rag/core/exceptions.py +14 -0
  67. supabase_easy_rag-0.1.0/supabase_easy_rag/core/models.py +54 -0
  68. supabase_easy_rag-0.1.0/supabase_easy_rag/ingestion/chunker.py +74 -0
  69. supabase_easy_rag-0.1.0/supabase_easy_rag/ingestion/facets.py +97 -0
  70. supabase_easy_rag-0.1.0/supabase_easy_rag/ingestion/parser.py +140 -0
  71. supabase_easy_rag-0.1.0/supabase_easy_rag/ingestion/syncer.py +355 -0
  72. supabase_easy_rag-0.1.0/supabase_easy_rag/providers/__init__.py +13 -0
  73. supabase_easy_rag-0.1.0/supabase_easy_rag/providers/azure.py +73 -0
  74. supabase_easy_rag-0.1.0/supabase_easy_rag/providers/base.py +19 -0
  75. supabase_easy_rag-0.1.0/supabase_easy_rag/providers/chat_base.py +16 -0
  76. supabase_easy_rag-0.1.0/supabase_easy_rag/providers/local_provider.py +28 -0
  77. supabase_easy_rag-0.1.0/supabase_easy_rag/providers/openai.py +104 -0
  78. supabase_easy_rag-0.1.0/supabase_easy_rag/py.typed +1 -0
  79. supabase_easy_rag-0.1.0/supabase_easy_rag/retrieval/engine.py +324 -0
  80. supabase_easy_rag-0.1.0/supabase_easy_rag/retrieval/postgrest_client.py +99 -0
  81. supabase_easy_rag-0.1.0/supabase_easy_rag/security/tokens.py +64 -0
  82. supabase_easy_rag-0.1.0/tests/__init__.py +3 -0
  83. supabase_easy_rag-0.1.0/tests/test_parser.py +115 -0
  84. supabase_easy_rag-0.1.0/tests/test_providers.py +61 -0
  85. supabase_easy_rag-0.1.0/tests/test_retrieval.py +82 -0
  86. supabase_easy_rag-0.1.0/tests/test_security.py +90 -0
  87. 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: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
41
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](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.