coffloader 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.
@@ -0,0 +1,42 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ *.egg
11
+ .eggs/
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+ ENV/
17
+
18
+ # IDE
19
+ .idea/
20
+ .vscode/
21
+ *.swp
22
+ *.swo
23
+
24
+ # Testing
25
+ .pytest_cache/
26
+ .coverage
27
+ htmlcov/
28
+ .mypy_cache/
29
+
30
+ # OS
31
+ .DS_Store
32
+ Thumbs.db
33
+
34
+ # Project specific
35
+ *.db
36
+
37
+ # Secrets
38
+ .env
39
+
40
+ # Build artifacts
41
+ *.whl
42
+ *.tar.gz
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.1.0] - 2026-06-07
6
+
7
+ ### Added
8
+ - Initial release
9
+ - `Coffloader` main class with `write`, `search`, `read`, `inspect`, `delete` methods
10
+ - `MemoryBackend`, `LocalBackend`, `CompositeBackend` for flexible storage
11
+ - SQLite FTS5 index for BM25 keyword search
12
+ - Optional semantic search with sentence-transformers (`pip install coffloader[embed]`)
13
+ - Hybrid search combining BM25 + embeddings via Reciprocal Rank Fusion
14
+ - Size limits with reject or metadata-only modes
15
+ - Namespace filtering for multi-session/multi-agent isolation
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 coffloader contributors
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 THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: coffloader
3
+ Version: 0.1.0
4
+ Summary: External memory for AI agents — offload context to a VFS, index summaries, retrieve on demand.
5
+ Project-URL: Homepage, https://github.com/mingyk/coffloader
6
+ Project-URL: Repository, https://github.com/mingyk/coffloader
7
+ Author: coffloader contributors
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agent,context,llm,memory,rag,vfs
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.9
21
+ Provides-Extra: dev
22
+ Requires-Dist: mypy>=1.10; extra == 'dev'
23
+ Requires-Dist: pytest>=8.0; extra == 'dev'
24
+ Requires-Dist: ruff>=0.4; extra == 'dev'
25
+ Provides-Extra: embed
26
+ Requires-Dist: numpy>=1.21; extra == 'embed'
27
+ Requires-Dist: sentence-transformers>=2.2; extra == 'embed'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # coffloader
31
+
32
+ **External memory for AI agents** — offload context to a VFS, index caller-provided summaries, retrieve on demand.
33
+
34
+ [![Python](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/)
35
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
36
+ [![Status](https://img.shields.io/badge/status-pre--alpha-orange.svg)](#status)
37
+
38
+ ```bash
39
+ pip install coffloader # core (BM25 search)
40
+ pip install coffloader[embed] # + semantic search (sentence-transformers)
41
+ ```
42
+
43
+ ---
44
+
45
+ ## What it does
46
+
47
+ Agents accumulate context faster than any window allows. coffloader offloads content to storage, keeps a searchable index of summaries, and retrieves full content on demand.
48
+
49
+ ```
50
+ write(content, summary) → store blob + index summary
51
+ search(query) → top-k summaries + addresses
52
+ read(address) → full content
53
+ ```
54
+
55
+ **Key constraints:**
56
+ - `summary` is **required** on write — your agent/LLM provides it, not coffloader
57
+ - No LLM calls inside the library — pure storage and retrieval
58
+ - Caller handles contradiction detection, dedup, and reasoning
59
+
60
+ ---
61
+
62
+ ## Quick start
63
+
64
+ ```python
65
+ from coffloader import Coffloader
66
+
67
+ store = Coffloader()
68
+
69
+ # 1. Offload a conversation segment (summary comes from your agent)
70
+ store.write(
71
+ content="[Turn 1] User: I was charged twice for order #9910...",
72
+ summary="Customer reports duplicate charge on order #9910",
73
+ metadata={"session_id": "ticket_8842", "segment": 1},
74
+ path="/sessions/ticket_8842/seg_001.txt",
75
+ )
76
+
77
+ # 2. Later: search when user asks about earlier context
78
+ hits = store.search("order number", namespace="/sessions/ticket_8842/")
79
+
80
+ # 3. Load full content and inject into your LLM
81
+ text = store.read_text(hits[0].address)
82
+ ```
83
+
84
+ **The loop:** offload cold context → search when needed → read and inject.
85
+
86
+ ---
87
+
88
+ ## API
89
+
90
+ ```python
91
+ store = Coffloader(
92
+ backend=None, # default: in-memory VFS
93
+ max_bytes=512_000, # default: 512 KB — reject oversized payloads
94
+ on_oversize="reject", # "reject" or "metadata_only"
95
+ hybrid=True, # default: True — use BM25 + embeddings if available
96
+ min_similarity=0.3, # default: 0.3 — filter out weak embedding matches
97
+ # lower = more results, less relevant
98
+ # higher = fewer results, more relevant
99
+ # set to 0.0 to disable filtering
100
+ )
101
+
102
+ # Store content with a caller-provided summary
103
+ result = store.write(content, summary, metadata={}, path=None)
104
+
105
+ # Search indexed summaries (returns TocEntry list, not full content)
106
+ hits = store.search(query, k=5, filters={}, namespace=None)
107
+ # ^^^ number of results to return
108
+
109
+ # Load full content
110
+ data = store.read(address) # bytes
111
+ text = store.read_text(address) # str
112
+
113
+ # Check size before writing
114
+ check = store.inspect(content) # .acceptable, .byte_count
115
+
116
+ # Delete
117
+ store.delete(address)
118
+ ```
119
+
120
+ **Defaults are exposed as class attributes:**
121
+ ```python
122
+ Coffloader.DEFAULT_MAX_BYTES # 512_000
123
+ Coffloader.DEFAULT_MIN_SIMILARITY # 0.3
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Composite backends
129
+
130
+ Route paths to different storage:
131
+
132
+ ```python
133
+ from coffloader import Coffloader, CompositeBackend, LocalBackend, MemoryBackend
134
+
135
+ store = Coffloader(
136
+ backend=CompositeBackend(
137
+ default=MemoryBackend(),
138
+ routes={"/archive/": LocalBackend(root="./data")},
139
+ )
140
+ )
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Patterns
146
+
147
+ **Long session (segmented):** Offload every ~15 turns. Search returns precise segments, not the whole transcript.
148
+
149
+ ```python
150
+ store.write(content=turns_1_15, summary="...", path="/sessions/abc/seg_001.txt")
151
+ store.write(content=turns_16_30, summary="...", path="/sessions/abc/seg_002.txt")
152
+ ```
153
+
154
+ **Tool output:** Offload large grep/API results with a structural summary (no LLM needed).
155
+
156
+ ```python
157
+ store.write(
158
+ content=grep_output,
159
+ summary=f"grep error src/ → {n} matches",
160
+ path=f"/active/{session}/tool_001.txt",
161
+ )
162
+ ```
163
+
164
+ **Multi-agent:** Use namespaces for isolation (`/agent/{id}/`) or sharing (`/shared/`).
165
+
166
+ ---
167
+
168
+ ## Limits
169
+
170
+ - Max payload: 512 KB by default (configurable)
171
+ - Oversized content is rejected or recorded as metadata-only
172
+ - No silent truncation
173
+
174
+ ---
175
+
176
+ ## Status
177
+
178
+ Pre-alpha. Core API is stable: `write`, `search`, `read`, `inspect`, `delete`.
179
+
180
+ **Working:**
181
+ - BM25 (keyword) search via SQLite FTS5
182
+ - Semantic search via `[embed]` optional extra
183
+ - Hybrid search (BM25 + embeddings) with Reciprocal Rank Fusion
184
+
185
+ **Not yet implemented:**
186
+ - Persistent index to disk
187
+ - Sharded TOC for large corpora
188
+
189
+ ---
190
+
191
+ ## Non-goals
192
+
193
+ - LLM calls from the library
194
+ - Automatic dedup, contradiction detection, or memory merge
195
+ - Knowledge graphs or hierarchical rollups
196
+
197
+ ---
198
+
199
+ ## License
200
+
201
+ MIT
@@ -0,0 +1,172 @@
1
+ # coffloader
2
+
3
+ **External memory for AI agents** — offload context to a VFS, index caller-provided summaries, retrieve on demand.
4
+
5
+ [![Python](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/)
6
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
+ [![Status](https://img.shields.io/badge/status-pre--alpha-orange.svg)](#status)
8
+
9
+ ```bash
10
+ pip install coffloader # core (BM25 search)
11
+ pip install coffloader[embed] # + semantic search (sentence-transformers)
12
+ ```
13
+
14
+ ---
15
+
16
+ ## What it does
17
+
18
+ Agents accumulate context faster than any window allows. coffloader offloads content to storage, keeps a searchable index of summaries, and retrieves full content on demand.
19
+
20
+ ```
21
+ write(content, summary) → store blob + index summary
22
+ search(query) → top-k summaries + addresses
23
+ read(address) → full content
24
+ ```
25
+
26
+ **Key constraints:**
27
+ - `summary` is **required** on write — your agent/LLM provides it, not coffloader
28
+ - No LLM calls inside the library — pure storage and retrieval
29
+ - Caller handles contradiction detection, dedup, and reasoning
30
+
31
+ ---
32
+
33
+ ## Quick start
34
+
35
+ ```python
36
+ from coffloader import Coffloader
37
+
38
+ store = Coffloader()
39
+
40
+ # 1. Offload a conversation segment (summary comes from your agent)
41
+ store.write(
42
+ content="[Turn 1] User: I was charged twice for order #9910...",
43
+ summary="Customer reports duplicate charge on order #9910",
44
+ metadata={"session_id": "ticket_8842", "segment": 1},
45
+ path="/sessions/ticket_8842/seg_001.txt",
46
+ )
47
+
48
+ # 2. Later: search when user asks about earlier context
49
+ hits = store.search("order number", namespace="/sessions/ticket_8842/")
50
+
51
+ # 3. Load full content and inject into your LLM
52
+ text = store.read_text(hits[0].address)
53
+ ```
54
+
55
+ **The loop:** offload cold context → search when needed → read and inject.
56
+
57
+ ---
58
+
59
+ ## API
60
+
61
+ ```python
62
+ store = Coffloader(
63
+ backend=None, # default: in-memory VFS
64
+ max_bytes=512_000, # default: 512 KB — reject oversized payloads
65
+ on_oversize="reject", # "reject" or "metadata_only"
66
+ hybrid=True, # default: True — use BM25 + embeddings if available
67
+ min_similarity=0.3, # default: 0.3 — filter out weak embedding matches
68
+ # lower = more results, less relevant
69
+ # higher = fewer results, more relevant
70
+ # set to 0.0 to disable filtering
71
+ )
72
+
73
+ # Store content with a caller-provided summary
74
+ result = store.write(content, summary, metadata={}, path=None)
75
+
76
+ # Search indexed summaries (returns TocEntry list, not full content)
77
+ hits = store.search(query, k=5, filters={}, namespace=None)
78
+ # ^^^ number of results to return
79
+
80
+ # Load full content
81
+ data = store.read(address) # bytes
82
+ text = store.read_text(address) # str
83
+
84
+ # Check size before writing
85
+ check = store.inspect(content) # .acceptable, .byte_count
86
+
87
+ # Delete
88
+ store.delete(address)
89
+ ```
90
+
91
+ **Defaults are exposed as class attributes:**
92
+ ```python
93
+ Coffloader.DEFAULT_MAX_BYTES # 512_000
94
+ Coffloader.DEFAULT_MIN_SIMILARITY # 0.3
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Composite backends
100
+
101
+ Route paths to different storage:
102
+
103
+ ```python
104
+ from coffloader import Coffloader, CompositeBackend, LocalBackend, MemoryBackend
105
+
106
+ store = Coffloader(
107
+ backend=CompositeBackend(
108
+ default=MemoryBackend(),
109
+ routes={"/archive/": LocalBackend(root="./data")},
110
+ )
111
+ )
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Patterns
117
+
118
+ **Long session (segmented):** Offload every ~15 turns. Search returns precise segments, not the whole transcript.
119
+
120
+ ```python
121
+ store.write(content=turns_1_15, summary="...", path="/sessions/abc/seg_001.txt")
122
+ store.write(content=turns_16_30, summary="...", path="/sessions/abc/seg_002.txt")
123
+ ```
124
+
125
+ **Tool output:** Offload large grep/API results with a structural summary (no LLM needed).
126
+
127
+ ```python
128
+ store.write(
129
+ content=grep_output,
130
+ summary=f"grep error src/ → {n} matches",
131
+ path=f"/active/{session}/tool_001.txt",
132
+ )
133
+ ```
134
+
135
+ **Multi-agent:** Use namespaces for isolation (`/agent/{id}/`) or sharing (`/shared/`).
136
+
137
+ ---
138
+
139
+ ## Limits
140
+
141
+ - Max payload: 512 KB by default (configurable)
142
+ - Oversized content is rejected or recorded as metadata-only
143
+ - No silent truncation
144
+
145
+ ---
146
+
147
+ ## Status
148
+
149
+ Pre-alpha. Core API is stable: `write`, `search`, `read`, `inspect`, `delete`.
150
+
151
+ **Working:**
152
+ - BM25 (keyword) search via SQLite FTS5
153
+ - Semantic search via `[embed]` optional extra
154
+ - Hybrid search (BM25 + embeddings) with Reciprocal Rank Fusion
155
+
156
+ **Not yet implemented:**
157
+ - Persistent index to disk
158
+ - Sharded TOC for large corpora
159
+
160
+ ---
161
+
162
+ ## Non-goals
163
+
164
+ - LLM calls from the library
165
+ - Automatic dedup, contradiction detection, or memory merge
166
+ - Knowledge graphs or hierarchical rollups
167
+
168
+ ---
169
+
170
+ ## License
171
+
172
+ MIT
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "coffloader"
7
+ version = "0.1.0"
8
+ description = "External memory for AI agents — offload context to a VFS, index summaries, retrieve on demand."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "coffloader contributors" }]
13
+ keywords = ["llm", "agent", "memory", "context", "rag", "vfs"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
24
+ ]
25
+
26
+ dependencies = []
27
+
28
+ [project.optional-dependencies]
29
+ embed = ["sentence-transformers>=2.2", "numpy>=1.21"]
30
+ dev = ["pytest>=8.0", "ruff>=0.4", "mypy>=1.10"]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/mingyk/coffloader"
34
+ Repository = "https://github.com/mingyk/coffloader"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/coffloader"]
38
+
39
+ [tool.ruff]
40
+ line-length = 100
41
+ target-version = "py39"
42
+
43
+ [tool.ruff.lint]
44
+ select = ["E", "F", "I", "UP"]
45
+
46
+ [tool.mypy]
47
+ python_version = "3.9"
48
+ strict = true
@@ -0,0 +1,17 @@
1
+ """coffloader — External memory for AI agents."""
2
+
3
+ from .backends import CompositeBackend, LocalBackend, MemoryBackend
4
+ from .store import Coffloader
5
+ from .toc import InspectResult, TocEntry, WriteResult
6
+
7
+ __version__ = "0.1.0"
8
+
9
+ __all__ = [
10
+ "Coffloader",
11
+ "TocEntry",
12
+ "WriteResult",
13
+ "InspectResult",
14
+ "CompositeBackend",
15
+ "LocalBackend",
16
+ "MemoryBackend",
17
+ ]
@@ -0,0 +1,8 @@
1
+ """Storage backends for coffloader."""
2
+
3
+ from .base import BackendProtocol
4
+ from .composite import CompositeBackend
5
+ from .local import LocalBackend
6
+ from .memory import MemoryBackend
7
+
8
+ __all__ = ["BackendProtocol", "CompositeBackend", "LocalBackend", "MemoryBackend"]
@@ -0,0 +1,23 @@
1
+ """Backend protocol for VFS storage."""
2
+
3
+ from typing import Protocol
4
+
5
+
6
+ class BackendProtocol(Protocol):
7
+ """Interface for storage backends."""
8
+
9
+ def write(self, path: str, data: bytes) -> None:
10
+ """Store data at the given path."""
11
+ ...
12
+
13
+ def read(self, path: str) -> bytes:
14
+ """Read data from the given path. Raises KeyError if not found."""
15
+ ...
16
+
17
+ def delete(self, path: str) -> bool:
18
+ """Delete data at the given path. Returns True if deleted, False if not found."""
19
+ ...
20
+
21
+ def exists(self, path: str) -> bool:
22
+ """Check if path exists."""
23
+ ...
@@ -0,0 +1,41 @@
1
+ """Composite backend that routes by path prefix."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .base import BackendProtocol
6
+ from .memory import MemoryBackend
7
+
8
+
9
+ class CompositeBackend:
10
+ """Route paths to different backends based on prefix.
11
+
12
+ Longest matching prefix wins. Unmatched paths go to the default backend.
13
+ """
14
+
15
+ def __init__(
16
+ self,
17
+ default: BackendProtocol | None = None,
18
+ routes: dict[str, BackendProtocol] | None = None,
19
+ ) -> None:
20
+ self._default: BackendProtocol = default or MemoryBackend()
21
+ self._routes = routes or {}
22
+ # Sort routes by length descending for longest-prefix matching
23
+ self._sorted_prefixes = sorted(self._routes.keys(), key=len, reverse=True)
24
+
25
+ def _get_backend(self, path: str) -> BackendProtocol:
26
+ for prefix in self._sorted_prefixes:
27
+ if path.startswith(prefix):
28
+ return self._routes[prefix]
29
+ return self._default
30
+
31
+ def write(self, path: str, data: bytes) -> None:
32
+ self._get_backend(path).write(path, data)
33
+
34
+ def read(self, path: str) -> bytes:
35
+ return self._get_backend(path).read(path)
36
+
37
+ def delete(self, path: str) -> bool:
38
+ return self._get_backend(path).delete(path)
39
+
40
+ def exists(self, path: str) -> bool:
41
+ return self._get_backend(path).exists(path)
@@ -0,0 +1,37 @@
1
+ """Local filesystem storage backend."""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ class LocalBackend:
7
+ """Store blobs on local disk under a root directory."""
8
+
9
+ def __init__(self, root: str | Path) -> None:
10
+ self._root = Path(root).resolve()
11
+ self._root.mkdir(parents=True, exist_ok=True)
12
+
13
+ def _resolve(self, path: str) -> Path:
14
+ # Strip leading slash for joining
15
+ relative = path.lstrip("/")
16
+ return self._root / relative
17
+
18
+ def write(self, path: str, data: bytes) -> None:
19
+ file_path = self._resolve(path)
20
+ file_path.parent.mkdir(parents=True, exist_ok=True)
21
+ file_path.write_bytes(data)
22
+
23
+ def read(self, path: str) -> bytes:
24
+ file_path = self._resolve(path)
25
+ if not file_path.exists():
26
+ raise KeyError(f"Path not found: {path}")
27
+ return file_path.read_bytes()
28
+
29
+ def delete(self, path: str) -> bool:
30
+ file_path = self._resolve(path)
31
+ if file_path.exists():
32
+ file_path.unlink()
33
+ return True
34
+ return False
35
+
36
+ def exists(self, path: str) -> bool:
37
+ return self._resolve(path).exists()
@@ -0,0 +1,25 @@
1
+ """In-memory storage backend."""
2
+
3
+
4
+ class MemoryBackend:
5
+ """Store blobs in a Python dict. Data lost on process exit."""
6
+
7
+ def __init__(self) -> None:
8
+ self._store: dict[str, bytes] = {}
9
+
10
+ def write(self, path: str, data: bytes) -> None:
11
+ self._store[path] = data
12
+
13
+ def read(self, path: str) -> bytes:
14
+ if path not in self._store:
15
+ raise KeyError(f"Path not found: {path}")
16
+ return self._store[path]
17
+
18
+ def delete(self, path: str) -> bool:
19
+ if path in self._store:
20
+ del self._store[path]
21
+ return True
22
+ return False
23
+
24
+ def exists(self, path: str) -> bool:
25
+ return path in self._store
@@ -0,0 +1,15 @@
1
+ """Index implementations for TOC search."""
2
+
3
+ from .fts import FTSIndex
4
+
5
+ # Optional imports for embedding-based search
6
+ try:
7
+ from .embeddings import EmbeddingIndex
8
+ from .hybrid import HybridIndex
9
+ EMBEDDINGS_AVAILABLE = True
10
+ except ImportError:
11
+ EMBEDDINGS_AVAILABLE = False
12
+ EmbeddingIndex = None # type: ignore
13
+ HybridIndex = None # type: ignore
14
+
15
+ __all__ = ["FTSIndex", "EmbeddingIndex", "HybridIndex", "EMBEDDINGS_AVAILABLE"]