Merge branch 'main' into feat/read-only-db

This commit is contained in:
Yiorgis Gozadinos 2025-12-19 12:01:36 +02:00 committed by GitHub
commit 8c0adb47e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 187 additions and 18 deletions

View file

@ -9,6 +9,15 @@
- Excludes write tools (`add_document_*`, `delete_document`) from MCP server
- Disables file monitor with warning when `--read-only` is used with `serve --monitor`
### Fixed
- **File Monitor Path Validation**: Monitor now validates directories exist before watching ([#204](https://github.com/ggozad/haiku.rag/issues/204))
- Provides clear error message pointing to `haiku.rag.yaml` configuration
- Prevents cryptic `FileNotFoundError: No path was found` from watchfiles
- **Docker Documentation**: Improved Docker setup instructions
- Added volume mount examples for config file and documents directory
- Clarified that `monitor.directories` must use container paths, not host paths
## [0.21.0] - 2025-12-18
### Added

View file

@ -44,19 +44,35 @@ Mount your config file and data directory:
```bash
docker run -p 8001:8001 \
-v $(pwd)/haiku.rag.yaml:/app/haiku.rag.yaml \
-v $(pwd)/data:/data \
-v /path/to/haiku.rag.yaml:/app/haiku.rag.yaml \
-v /path/to/data:/data \
haiku-rag
```
The container will automatically use the mounted `haiku.rag.yaml` configuration file.
To enable file monitoring, also mount a documents directory:
```bash
docker run -p 8001:8001 \
-v /path/to/haiku.rag.yaml:/app/haiku.rag.yaml \
-v /path/to/data:/data \
-v /path/to/docs:/docs \
haiku-rag haiku-rag serve --mcp --monitor
```
Your `haiku.rag.yaml` must reference the **container path** for monitoring:
```yaml
monitor:
directories:
- /docs # Container path, not host path
```
For API keys (OpenAI, Anthropic, etc.), pass them as environment variables:
```bash
docker run -p 8001:8001 \
-v $(pwd)/haiku.rag.yaml:/app/haiku.rag.yaml \
-v $(pwd)/data:/data \
-v /path/to/haiku.rag.yaml:/app/haiku.rag.yaml \
-v /path/to/data:/data \
-e OPENAI_API_KEY=your-key-here \
haiku-rag
```

View file

@ -93,7 +93,10 @@ Build locally to include all features and document processing without docling-se
```bash
docker build -f docker/Dockerfile -t haiku-rag .
docker run -p 8001:8001 -v $(pwd)/data:/data haiku-rag
docker run -p 8001:8001 \
-v /path/to/haiku.rag.yaml:/app/haiku.rag.yaml \
-v /path/to/data:/data \
haiku-rag
```
See `docker/README.md` for complete build and configuration instructions.
See `docker/README.md` for complete build and configuration instructions, including how to enable file monitoring.

View file

@ -1,8 +1,7 @@
from pydantic import BaseModel
from pydantic_ai import Agent
from haiku.rag.config import Config
from haiku.rag.config.models import ModelConfig
from haiku.rag.config.models import AppConfig, ModelConfig
from haiku.rag.utils import get_model
ANSWER_EQUIVALENCE_RUBRIC = """You are evaluating whether two answers to the same question are semantically equivalent.
@ -36,10 +35,9 @@ class LLMJudgeResponseSchema(BaseModel):
class LLMJudge:
"""LLM-as-judge for evaluating answer equivalence using Pydantic AI."""
def __init__(self, model: str = "gpt-oss"):
# Create model using get_model with thinking disabled
def __init__(self, model: str = "gpt-oss", config: AppConfig | None = None):
model_config = ModelConfig(provider="ollama", name=model, enable_thinking=False)
model_obj = get_model(model_config, Config)
model_obj = get_model(model_config, config)
# Create Pydantic AI agent
self._agent = Agent(

View file

@ -14,13 +14,36 @@ This setup showcases the minimal haiku.rag-slim image combined with external doc
## Quick Start
```bash
# Create required directories
mkdir -p data docs
cp haiku.rag.yaml.example haiku.rag.yaml # Edit as needed
# Create config file from example (required)
cp haiku.rag.yaml.example haiku.rag.yaml
# Start services
docker compose up -d
```
Place documents in `docs/` for automatic indexing.
## Volume Mounts
The docker-compose.yml mounts three volumes:
| Host Path | Container Path | Purpose |
|-----------|---------------|---------|
| `./data` | `/data` | Persistent LanceDB database |
| `./docs` | `/docs` | Documents to monitor and index |
| `./haiku.rag.yaml` | `/app/haiku.rag.yaml` | Configuration file |
**Important:** The `haiku.rag.yaml` config file must exist before running `docker compose up`. Copy it from the example:
```bash
cp haiku.rag.yaml.example haiku.rag.yaml
```
The example config sets `monitor.directories: [/docs]` - this is the **container path**, not your host path. Documents placed in `./docs` on your host will appear at `/docs` inside the container.
## Usage
```bash

View file

@ -18,57 +18,129 @@ class TextFileHandler:
# Plain text extensions that we'll read directly
text_extensions: ClassVar[list[str]] = [
".astro",
".bash",
".c",
".clj",
".cljs",
".cpp",
".cs",
".css",
".dart",
".elm",
".ex",
".exs",
".fs",
".fsx",
".go",
".gql",
".graphql",
".groovy",
".h",
".hcl",
".hpp",
".hs",
".java",
".jl",
".js",
".json",
".kt",
".less",
".lua",
".mdx",
".mjs",
".ml",
".mli",
".nim",
".nix",
".php",
".pl",
".pm",
".proto",
".ps1",
".py",
".r",
".rb",
".rs",
".sass",
".scala",
".scss",
".sh",
".sql",
".svelte",
".swift",
".tf",
".toml",
".ts",
".tsx",
".txt",
".vue",
".xml",
".yaml",
".yml",
".zig",
]
# Code file extensions with their markdown language identifiers
code_markdown_identifier: ClassVar[dict[str, str]] = {
".astro": "astro",
".bash": "bash",
".c": "c",
".clj": "clojure",
".cljs": "clojure",
".cpp": "cpp",
".cs": "csharp",
".css": "css",
".dart": "dart",
".elm": "elm",
".ex": "elixir",
".exs": "elixir",
".fs": "fsharp",
".fsx": "fsharp",
".go": "go",
".gql": "graphql",
".graphql": "graphql",
".groovy": "groovy",
".h": "c",
".hcl": "hcl",
".hpp": "cpp",
".hs": "haskell",
".java": "java",
".jl": "julia",
".js": "javascript",
".json": "json",
".kt": "kotlin",
".less": "less",
".lua": "lua",
".mjs": "javascript",
".ml": "ocaml",
".mli": "ocaml",
".nim": "nim",
".nix": "nix",
".php": "php",
".pl": "perl",
".pm": "perl",
".proto": "protobuf",
".ps1": "powershell",
".py": "python",
".r": "r",
".rb": "ruby",
".rs": "rust",
".sass": "sass",
".scala": "scala",
".scss": "scss",
".sh": "bash",
".sql": "sql",
".svelte": "svelte",
".swift": "swift",
".tf": "hcl",
".toml": "toml",
".ts": "typescript",
".tsx": "tsx",
".vue": "vue",
".xml": "xml",
".yaml": "yaml",
".yml": "yaml",
".zig": "zig",
}
@staticmethod

View file

@ -90,6 +90,18 @@ class FileWatcher:
self.supported_extensions = get_converter(config).supported_extensions
async def observe(self):
if not self.paths:
logger.warning("No directories configured for monitoring")
return
# Validate all paths exist before attempting to watch
missing_paths = [p for p in self.paths if not Path(p).exists()]
if missing_paths:
raise FileNotFoundError(
f"Monitor directories do not exist: {missing_paths}. "
"Check your haiku.rag.yaml configuration."
)
logger.info(f"Watching files in {self.paths}")
filter = FileFilter(
ignore_patterns=self.ignore_patterns,

View file

@ -21,5 +21,6 @@ def get_qa_agent(
return QuestionAnswerAgent(
client=client,
model_config=config.qa.model,
config=config,
system_prompt=system_prompt,
)

View file

@ -3,8 +3,7 @@ from pydantic_ai import Agent, RunContext
from pydantic_ai.output import ToolOutput
from haiku.rag.client import HaikuRAG
from haiku.rag.config import Config
from haiku.rag.config.models import ModelConfig
from haiku.rag.config.models import AppConfig, ModelConfig
from haiku.rag.graph.research.models import Citation, RawSearchAnswer, resolve_citations
from haiku.rag.qa.prompts import QA_SYSTEM_PROMPT
from haiku.rag.store.models import SearchResult
@ -23,10 +22,11 @@ class QuestionAnswerAgent:
self,
client: HaikuRAG,
model_config: ModelConfig,
config: AppConfig | None = None,
system_prompt: str | None = None,
):
self._client = client
model_obj = get_model(model_config, Config)
model_obj = get_model(model_config, config)
self._agent = Agent(
model=model_obj,

View file

@ -8,6 +8,7 @@ from packaging.version import Version, parse
if TYPE_CHECKING:
from rich.console import RenderableType
from haiku.rag.config.models import AppConfig, ModelConfig
from haiku.rag.graph.research.models import Citation
@ -44,8 +45,8 @@ def apply_common_settings(
def get_model(
model_config: Any,
app_config: Any | None = None,
model_config: "ModelConfig",
app_config: "AppConfig | None" = None,
) -> Any:
"""
Get a model instance for the specified configuration.

View file

@ -440,3 +440,37 @@ async def test_file_watcher_orphan_handles_spaces_in_filenames():
# Should NOT delete the document since file exists
mock_client.delete_document.assert_not_called()
@pytest.mark.asyncio
async def test_file_watcher_observe_raises_on_missing_paths():
"""Test observe() raises FileNotFoundError when directories don't exist."""
mock_client = AsyncMock(spec=HaikuRAG)
test_config = AppConfig(
monitor=MonitorConfig(
directories=[Path("/nonexistent/path/that/does/not/exist")]
)
)
watcher = FileWatcher(client=mock_client, config=test_config)
with pytest.raises(FileNotFoundError) as exc_info:
await watcher.observe()
assert "Monitor directories do not exist" in str(exc_info.value)
assert "haiku.rag.yaml" in str(exc_info.value)
@pytest.mark.asyncio
async def test_file_watcher_observe_returns_early_when_no_directories():
"""Test observe() returns early when no directories are configured."""
mock_client = AsyncMock(spec=HaikuRAG)
test_config = AppConfig(monitor=MonitorConfig(directories=[]))
watcher = FileWatcher(client=mock_client, config=test_config)
# Should return without error when no directories configured
await watcher.observe()
# No documents should have been processed
mock_client.create_document_from_source.assert_not_called()