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:
Yiorgis Gozadinos 2025-11-13 13:58:14 +02:00 committed by GitHub
commit 7140b3410f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 143 additions and 22 deletions

View file

@ -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

View file

@ -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

View file

@ -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}")

View file

@ -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)

View file

@ -53,7 +53,6 @@ def generate_default_config() -> dict:
"environment": "production",
"storage": {
"data_dir": "",
"disable_autocreate": False,
"vacuum_retention_seconds": 86400,
},
"monitor": {

View file

@ -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

View file

@ -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)

View file

@ -40,7 +40,6 @@ def temp_yaml_config(tmp_path, monkeypatch):
"storage": {
"data_dir": "",
"monitor_directories": [],
"disable_autocreate": False,
"vacuum_retention_seconds": 60,
},
"embeddings": {

View 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()