diff --git a/CHANGELOG.md b/CHANGELOG.md index c65e55ac..f5bc35cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Added +- `providers.docling_serve.timeout` (default 300 seconds), forwarded to the docling-serve client's per-request timeout. - `evaluations run --filter/-f CLAUSE`: SQL `WHERE` clause over document columns, applied to the retrieval benchmark's searches and to every capability search during QA. Recorded as `document_filter` in experiment metadata. - `mtrag_clapnq` / `mtrag_clapnq_rewrite` / `mtrag_clapnq_live` / `mtrag_clapnq_live_uncompacted` evaluation datasets: multi-turn QA with gold-prefix and live-session conversation replay, live arms with and without `EvidenceCompactionCapability`, Recall@k/nDCG@k retrieval metrics, eligibility-aware citation scoring, refusal precision/recall, per-turn tool-traffic attributes, and `citation_status` / `turn_citation_status` eval attributes. - Raw chunk metadata is now exposed to search and citation results, through `SearchResult.chunk_meta` and `Citation.chunk_meta`. For context-expanded results, the metadata is that of the anchor chunk. diff --git a/docs/remote-processing.md b/docs/remote-processing.md index 73cefaf9..9c5b9783 100644 --- a/docs/remote-processing.md +++ b/docs/remote-processing.md @@ -64,6 +64,7 @@ providers: docling_serve: base_url: http://localhost:5001 api_key: "" # Optional API key for authentication + timeout: 300 # Per-request timeout in seconds ``` For converter / chunker config options (chunking strategy, tokenizer, diff --git a/haiku_rag_slim/haiku/rag/config/models.py b/haiku_rag_slim/haiku/rag/config/models.py index 2fa58a8e..b122ae8d 100644 --- a/haiku_rag_slim/haiku/rag/config/models.py +++ b/haiku_rag_slim/haiku/rag/config/models.py @@ -295,6 +295,11 @@ class DoclingServeConfig(BaseModel): description="Max attempts per request across the fleet before giving up; " "each retry fails over to another instance.", ) + timeout: float = Field( + default=300, + gt=0, + description="Per-request timeout in seconds for submit, poll and result calls.", + ) circuit_breaker: CircuitBreakerConfig = Field( default_factory=lambda: CircuitBreakerConfig( failure_threshold=3, cooldown_s=30.0 diff --git a/haiku_rag_slim/haiku/rag/providers/docling_serve.py b/haiku_rag_slim/haiku/rag/providers/docling_serve.py index a14cc0fe..23abd5d9 100644 --- a/haiku_rag_slim/haiku/rag/providers/docling_serve.py +++ b/haiku_rag_slim/haiku/rag/providers/docling_serve.py @@ -97,6 +97,7 @@ class DoclingServeClient: return cls( base_urls=config.base_urls, api_key=config.api_key, + timeout=config.timeout, circuit_breaker=config.circuit_breaker, max_attempts=config.max_attempts, ) diff --git a/tests/test_config.py b/tests/test_config.py index 3bcdef6d..19383411 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest import yaml @@ -557,3 +559,21 @@ def test_get_config_initialises_lazily_then_reuses(monkeypatch): first = config_module.get_config() assert isinstance(first, AppConfig) assert config_module.get_config() is first + + +# Every YAML config shipped in the repo has to load. These are what users copy. +_EXAMPLE_CONFIGS = sorted( + (Path(__file__).resolve().parent.parent).glob("**/*.yaml.example") +) + + +def test_example_configs_are_present(): + """Guard against the glob silently matching nothing.""" + assert _EXAMPLE_CONFIGS + + +@pytest.mark.parametrize( + "path", _EXAMPLE_CONFIGS, ids=lambda p: p.parent.name + "/" + p.name +) +def test_example_config_validates(path: Path): + AppConfig.model_validate(yaml.safe_load(path.read_text()) or {}) diff --git a/tests/test_docling_serve_client.py b/tests/test_docling_serve_client.py index 175ed53e..655f9f6c 100644 --- a/tests/test_docling_serve_client.py +++ b/tests/test_docling_serve_client.py @@ -474,11 +474,13 @@ def test_from_config_wires_retry_and_breaker(): ds = config.providers.docling_serve ds.base_url = "http://cfg-n:5001" ds.max_attempts = 7 + ds.timeout = 42.0 ds.circuit_breaker = CircuitBreakerConfig(failure_threshold=9, cooldown_s=90.0) for component in (DoclingServeConverter(config), DoclingServeChunker(config)): client = component.client assert client._max_attempts == 7 + assert client.timeout == 42.0 assert client._breaker_config.failure_threshold == 9 assert client._breaker_config.cooldown_s == 90.0