Merge pull request #139 from ggozad/fix/create-db-only-on-write
Drop disable_auto_create flag, simply create only on write operations
This commit is contained in:
commit
7140b3410f
9 changed files with 143 additions and 22 deletions
|
|
@ -50,9 +50,11 @@
|
|||
|
||||
- **Document Creation**: Optimized `create_document` to skip unnecessary DoclingDocument conversion when chunks are pre-provided
|
||||
- **FileReader**: Error messages now include both original exception details and file path for easier debugging
|
||||
- **Database Auto-creation**: Read operations (search, list, get, ask, research) no longer auto-create empty databases. Write operations (add, add-src, delete, rebuild) still create the database as needed. This prevents the confusing scenario where a search query creates an empty database. Fixes issue #137.
|
||||
|
||||
### Removed
|
||||
|
||||
- **BREAKING**: Removed `disable_autocreate` config option - the behavior is now automatic based on operation type
|
||||
- **BREAKING**: Removed legacy `ResearchStream` and `ResearchStreamEvent` classes (replaced by AG-UI event protocol)
|
||||
|
||||
## [0.15.0] - 2025-11-07
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ environment: production
|
|||
|
||||
storage:
|
||||
data_dir: "" # Empty = use default platform location
|
||||
disable_autocreate: false
|
||||
vacuum_retention_seconds: 86400
|
||||
|
||||
monitor:
|
||||
|
|
@ -561,16 +560,14 @@ Authentication is handled through standard cloud provider credentials (AWS CLI,
|
|||
|
||||
**Note:** Table optimization is automatically handled by LanceDB Cloud (`db://` URIs) and is disabled for better performance. For object storage backends (S3, Azure, GCS), optimization is still performed locally.
|
||||
|
||||
#### Disable database auto-creation
|
||||
#### Database Auto-creation
|
||||
|
||||
By default, haiku.rag creates the local LanceDB directory and required tables on first use. To prevent accidental database creation and fail fast if a database hasn't been set up yet:
|
||||
haiku.rag intelligently handles database creation based on operation type:
|
||||
|
||||
```yaml
|
||||
storage:
|
||||
disable_autocreate: true
|
||||
```
|
||||
- **Write operations** (add, add-src, delete, rebuild): Automatically create the database and required tables if they don't exist
|
||||
- **Read operations** (list, get, search, ask, research): Fail with a clear error if the database doesn't exist
|
||||
|
||||
When enabled, for local paths, haiku.rag errors if the LanceDB directory does not exist, and it will not create parent directories.
|
||||
This prevents the common mistake where a search query accidentally creates an empty database. To initialize your database, simply add your first document using `haiku-rag add` or `haiku-rag add-src`.
|
||||
|
||||
### Document Processing
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,9 @@ class HaikuRAGApp:
|
|||
)
|
||||
|
||||
async def list_documents(self, filter: str | None = None):
|
||||
async with HaikuRAG(db_path=self.db_path, config=self.config) as self.client:
|
||||
async with HaikuRAG(
|
||||
db_path=self.db_path, config=self.config, allow_create=False
|
||||
) as self.client:
|
||||
documents = await self.client.list_documents(filter=filter)
|
||||
for doc in documents:
|
||||
self._rich_print_document(doc, truncate=True)
|
||||
|
|
@ -170,7 +172,9 @@ class HaikuRAGApp:
|
|||
)
|
||||
|
||||
async def get_document(self, doc_id: str):
|
||||
async with HaikuRAG(db_path=self.db_path, config=self.config) as self.client:
|
||||
async with HaikuRAG(
|
||||
db_path=self.db_path, config=self.config, allow_create=False
|
||||
) as self.client:
|
||||
doc = await self.client.get_document_by_id(doc_id)
|
||||
if doc is None:
|
||||
self.console.print(f"[red]Document with id {doc_id} not found.[/red]")
|
||||
|
|
@ -190,7 +194,9 @@ class HaikuRAGApp:
|
|||
)
|
||||
|
||||
async def search(self, query: str, limit: int = 5, filter: str | None = None):
|
||||
async with HaikuRAG(db_path=self.db_path, config=self.config) as self.client:
|
||||
async with HaikuRAG(
|
||||
db_path=self.db_path, config=self.config, allow_create=False
|
||||
) as self.client:
|
||||
results = await self.client.search(query, limit=limit, filter=filter)
|
||||
if not results:
|
||||
self.console.print("[yellow]No results found.[/yellow]")
|
||||
|
|
@ -213,7 +219,9 @@ class HaikuRAGApp:
|
|||
deep: Use deep QA mode (multi-step reasoning)
|
||||
verbose: Show verbose output
|
||||
"""
|
||||
async with HaikuRAG(db_path=self.db_path, config=self.config) as self.client:
|
||||
async with HaikuRAG(
|
||||
db_path=self.db_path, config=self.config, allow_create=False
|
||||
) as self.client:
|
||||
try:
|
||||
if deep:
|
||||
from haiku.rag.graph.deep_qa.dependencies import DeepQAContext
|
||||
|
|
@ -258,7 +266,9 @@ class HaikuRAGApp:
|
|||
question: The research question
|
||||
verbose: Show AG-UI event stream during execution
|
||||
"""
|
||||
async with HaikuRAG(db_path=self.db_path, config=self.config) as client:
|
||||
async with HaikuRAG(
|
||||
db_path=self.db_path, config=self.config, allow_create=False
|
||||
) as client:
|
||||
try:
|
||||
self.console.print("[bold cyan]Starting research[/bold cyan]")
|
||||
self.console.print(f"[bold blue]Question:[/bold blue] {question}")
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class HaikuRAG:
|
|||
db_path: Path | None = None,
|
||||
config: AppConfig = Config,
|
||||
skip_validation: bool = False,
|
||||
allow_create: bool = True,
|
||||
):
|
||||
"""Initialize the RAG client with a database path.
|
||||
|
||||
|
|
@ -35,12 +36,17 @@ class HaikuRAG:
|
|||
db_path: Path to the database file. If None, uses config.storage.data_dir.
|
||||
config: Configuration to use. Defaults to global Config.
|
||||
skip_validation: Whether to skip configuration validation on database load.
|
||||
allow_create: Whether to allow database creation. If False, will raise error
|
||||
if database doesn't exist (for read operations).
|
||||
"""
|
||||
self._config = config
|
||||
if db_path is None:
|
||||
db_path = self._config.storage.data_dir / "haiku.rag.lancedb"
|
||||
self.store = Store(
|
||||
db_path, config=self._config, skip_validation=skip_validation
|
||||
db_path,
|
||||
config=self._config,
|
||||
skip_validation=skip_validation,
|
||||
allow_create=allow_create,
|
||||
)
|
||||
self.document_repository = DocumentRepository(self.store)
|
||||
self.chunk_repository = ChunkRepository(self.store)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ def generate_default_config() -> dict:
|
|||
"environment": "production",
|
||||
"storage": {
|
||||
"data_dir": "",
|
||||
"disable_autocreate": False,
|
||||
"vacuum_retention_seconds": 86400,
|
||||
},
|
||||
"monitor": {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from haiku.rag.utils import get_default_data_dir
|
|||
|
||||
class StorageConfig(BaseModel):
|
||||
data_dir: Path = Field(default_factory=get_default_data_dir)
|
||||
disable_autocreate: bool = False
|
||||
vacuum_retention_seconds: int = 86400
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,11 @@ class SettingsRecord(LanceModel):
|
|||
|
||||
class Store:
|
||||
def __init__(
|
||||
self, db_path: Path, config: AppConfig = Config, skip_validation: bool = False
|
||||
self,
|
||||
db_path: Path,
|
||||
config: AppConfig = Config,
|
||||
skip_validation: bool = False,
|
||||
allow_create: bool = True,
|
||||
):
|
||||
self.db_path: Path = db_path
|
||||
self._config = config
|
||||
|
|
@ -62,14 +66,14 @@ class Store:
|
|||
|
||||
# Local filesystem handling for DB directory
|
||||
if not self._has_cloud_config():
|
||||
if self._config.storage.disable_autocreate:
|
||||
# LanceDB uses a directory path for local databases; enforce presence
|
||||
if not allow_create:
|
||||
# Read operations should not create the database
|
||||
if not db_path.exists():
|
||||
raise FileNotFoundError(
|
||||
f"LanceDB path does not exist: {db_path}. Auto-creation is disabled."
|
||||
f"Database does not exist: {db_path}. Use a write operation (add, add-src) to create it."
|
||||
)
|
||||
else:
|
||||
# Ensure parent directories exist when autocreation allowed
|
||||
# Write operations - ensure parent directories exist
|
||||
if not db_path.parent.exists():
|
||||
Path.mkdir(db_path.parent, parents=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ def temp_yaml_config(tmp_path, monkeypatch):
|
|||
"storage": {
|
||||
"data_dir": "",
|
||||
"monitor_directories": [],
|
||||
"disable_autocreate": False,
|
||||
"vacuum_retention_seconds": 60,
|
||||
},
|
||||
"embeddings": {
|
||||
|
|
|
|||
105
tests/test_database_autocreate.py
Normal file
105
tests/test_database_autocreate.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from haiku.rag.client import HaikuRAG
|
||||
from haiku.rag.config import AppConfig
|
||||
|
||||
|
||||
def test_read_operations_do_not_create_database():
|
||||
"""Test that read operations fail if database doesn't exist."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = Path(tmpdir) / "test.lancedb"
|
||||
|
||||
config = AppConfig()
|
||||
|
||||
# Read operation with allow_create=False should fail
|
||||
with pytest.raises(
|
||||
FileNotFoundError,
|
||||
match="Database does not exist.*Use a write operation",
|
||||
):
|
||||
HaikuRAG(db_path=db_path, config=config, allow_create=False)
|
||||
|
||||
|
||||
def test_write_operations_create_database():
|
||||
"""Test that write operations create the database."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = Path(tmpdir) / "test.lancedb"
|
||||
|
||||
config = AppConfig()
|
||||
|
||||
# Write operation with allow_create=True (default) should succeed
|
||||
client = HaikuRAG(db_path=db_path, config=config, allow_create=True)
|
||||
assert db_path.exists()
|
||||
client.close()
|
||||
|
||||
|
||||
async def test_add_document_creates_database():
|
||||
"""Test that add operations create the database."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = Path(tmpdir) / "test.lancedb"
|
||||
|
||||
config = AppConfig()
|
||||
|
||||
# Create a document (write operation) should work and create DB
|
||||
async with HaikuRAG(
|
||||
db_path=db_path, config=config, allow_create=True
|
||||
) as client:
|
||||
doc = await client.create_document("Test content")
|
||||
assert doc.id is not None
|
||||
assert doc.content == "Test content"
|
||||
assert db_path.exists()
|
||||
|
||||
|
||||
async def test_search_fails_if_database_does_not_exist():
|
||||
"""Test that search operations fail if DB doesn't exist."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = Path(tmpdir) / "test.lancedb"
|
||||
|
||||
config = AppConfig()
|
||||
|
||||
# Read operation (search) should fail if DB doesn't exist
|
||||
with pytest.raises(
|
||||
FileNotFoundError,
|
||||
match="Database does not exist.*Use a write operation",
|
||||
):
|
||||
async with HaikuRAG(
|
||||
db_path=db_path, config=config, allow_create=False
|
||||
) as client:
|
||||
await client.search("test query")
|
||||
|
||||
|
||||
async def test_read_operations_work_after_database_created():
|
||||
"""Test that read operations work after DB is created via write operation."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = Path(tmpdir) / "test.lancedb"
|
||||
|
||||
config = AppConfig()
|
||||
|
||||
# First, create DB via write operation
|
||||
async with HaikuRAG(
|
||||
db_path=db_path, config=config, allow_create=True
|
||||
) as client:
|
||||
await client.create_document("Test content", uri="test://doc1")
|
||||
|
||||
# Now read operations should work since DB exists
|
||||
async with HaikuRAG(
|
||||
db_path=db_path, config=config, allow_create=False
|
||||
) as client:
|
||||
docs = await client.list_documents()
|
||||
assert len(docs) == 1
|
||||
assert docs[0].content == "Test content"
|
||||
|
||||
|
||||
def test_default_allow_create_is_true():
|
||||
"""Test that allow_create defaults to True for backward compatibility."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db_path = Path(tmpdir) / "test.lancedb"
|
||||
|
||||
config = AppConfig()
|
||||
|
||||
# Without specifying allow_create, it should default to True
|
||||
client = HaikuRAG(db_path=db_path, config=config)
|
||||
assert db_path.exists()
|
||||
client.close()
|
||||
Loading…
Reference in a new issue