Merge branch 'main' into feat/read-only-db
This commit is contained in:
commit
8c0adb47e1
11 changed files with 187 additions and 18 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -21,5 +21,6 @@ def get_qa_agent(
|
|||
return QuestionAnswerAgent(
|
||||
client=client,
|
||||
model_config=config.qa.model,
|
||||
config=config,
|
||||
system_prompt=system_prompt,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue