Skip to content

Commit cf588c1

Browse files
committed
Solr enrichment in library mode
- The solr enrichment script runs also when running lightspeed-stack in library mode - Hid is_chunk:true filtering attribute from the user
1 parent a491022 commit cf588c1

10 files changed

Lines changed: 98 additions & 69 deletions

docs/config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ Only relevant when ``"okp"`` is listed in ``rag.inline`` or ``rag.tool``.
408408
| Field | Type | Description |
409409
|-------|------|-------------|
410410
| offline | boolean | When True, use parent_id for OKP chunk source URLs. When False, use reference_url for chunk source URLs. |
411-
| chunk_filter_query | string | OKP filter query applied to every OKP search request. Defaults to 'is_chunk:true' to restrict results to chunk documents. To add extra constraints, extend the expression using boolean syntax, e.g. 'is_chunk:true AND product:*openshift*'. |
411+
| chunk_filter_query | string | Additional OKP filter query applied to every OKP search request. Use Solr boolean syntax, e.g. 'product:\*ansible\* AND product:\*openshift\*'. |
412412
413413
414414
## PostgreSQLDatabaseConfiguration

docs/rag_guide.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -343,15 +343,13 @@ curl -sX POST http://localhost:8080/v1/query \
343343
344344
**Query Filtering:**
345345
346-
To filter the Solr context, set the `chunk_filter_query` field in the `okp` section of
346+
To further filter the OKP context, set the `chunk_filter_query` field in the `okp` section of
347347
`lightspeed-stack.yaml`. Filters follow the Solr key:value format and are applied as a static
348-
`fq` parameter on every OKP search request. The default value `"is_chunk:true"` restricts
349-
results to chunk documents. To add extra constraints, extend the expression using Solr boolean
350-
syntax:
348+
`fq` parameter on every OKP search request.
351349
352350
```yaml
353351
okp:
354-
chunk_filter_query: "is_chunk:true AND product:*openshift*"
352+
chunk_filter_query: "product:*openshift*"
355353
```
356354

357355
> [!NOTE]

examples/lightspeed-stack-byok-okp-rag.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,6 @@ rag:
6666
# OKP provider settings (only used when 'okp' is listed in rag.inline or rag.tool)
6767
okp:
6868
offline: true # true = use parent_id for source URLs, false = use reference_url
69-
# Solr fq applied to every OKP search request. Combine with AND for extra constraints:
70-
# chunk_filter_query: "is_chunk:true AND product:*openshift*"
71-
chunk_filter_query: "is_chunk:true"
69+
# Additional Solr filter query applied to every OKP search request.
70+
# Use Solr boolean syntax
71+
# chunk_filter_query: "product:*ansible* AND product:*openshift*"

src/client.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
from llama_stack_client import APIConnectionError, AsyncLlamaStackClient # type: ignore
1212

1313
from configuration import configuration
14-
from llama_stack_configuration import enrich_byok_rag, YamlDumper
14+
from llama_stack_configuration import YamlDumper, enrich_byok_rag, enrich_solr
15+
from log import get_logger
1516
from models.config import LlamaStackConfiguration
1617
from models.responses import ServiceUnavailableResponse
1718
from utils.types import Singleton
18-
from log import get_logger
1919

2020
logger = get_logger(__name__)
2121

@@ -52,14 +52,9 @@ async def _load_library_client(self, config: LlamaStackConfiguration) -> None:
5252
)
5353
logger.info("Using Llama stack as library client")
5454

55-
byok_rag = [b.model_dump() for b in configuration.configuration.byok_rag]
56-
57-
if byok_rag: # BYOK RAG configured - enrich and store enriched path
58-
self._config_path = self._enrich_library_config(
59-
config.library_client_config_path, byok_rag
60-
)
61-
else: # No RAG - store original path
62-
self._config_path = config.library_client_config_path
55+
self._config_path = self._enrich_library_config(
56+
config.library_client_config_path
57+
)
6358

6459
client = AsyncLlamaStackAsLibraryClient(self._config_path)
6560
await client.initialize()
@@ -78,21 +73,22 @@ def _load_service_client(self, config: LlamaStackConfiguration) -> None:
7873
base_url=base_url, api_key=api_key, timeout=config.timeout
7974
)
8075

81-
def _enrich_library_config(
82-
self, input_config_path: str, byok_rag: list[dict]
83-
) -> str:
84-
"""Enrich llama-stack config with BYOK RAG settings.
85-
86-
Only called when BYOK RAG is configured.
87-
"""
76+
def _enrich_library_config(self, input_config_path: str) -> str:
77+
"""Enrich llama-stack config with BYOK RAG and OKP Solr settings."""
8878
try:
8979
with open(input_config_path, "r", encoding="utf-8") as f:
9080
ls_config = yaml.safe_load(f)
9181
except (OSError, yaml.YAMLError) as e:
9282
logger.warning("Failed to read llama-stack config: %s", e)
9383
return input_config_path
9484

95-
enrich_byok_rag(ls_config, byok_rag)
85+
config = configuration.configuration
86+
87+
# Enrichment: BYOK RAG
88+
enrich_byok_rag(ls_config, [b.model_dump() for b in config.byok_rag])
89+
90+
# Enrichment: Solr - enabled when "okp" appears in either inline or tool list
91+
enrich_solr(ls_config, config.rag.model_dump(), config.okp.model_dump())
9692

9793
enriched_path = os.path.join(
9894
tempfile.gettempdir(), "llama_stack_enriched_config.yaml"

src/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@
184184
SOLR_VECTOR_SEARCH_DEFAULT_SCORE_THRESHOLD = 0.3
185185
SOLR_VECTOR_SEARCH_DEFAULT_MODE = "hybrid"
186186

187+
# Internal Solr filter always applied to restrict results to chunk documents
188+
SOLR_CHUNK_FILTER_QUERY = "is_chunk:true"
189+
187190
# SOLR OKP RAG
188191
MIMIR_DOC_URL = "https://mimir.corp.redhat.com"
189192

src/llama_stack_configuration.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -377,19 +377,36 @@ def enrich_byok_rag(ls_config: dict[str, Any], byok_rag: list[dict[str, Any]]) -
377377
# =============================================================================
378378

379379

380-
def enrich_solr(ls_config: dict[str, Any], solr_config: dict[str, Any]) -> None:
380+
def enrich_solr( # pylint: disable=too-many-locals
381+
ls_config: dict[str, Any],
382+
rag_config: dict[str, Any],
383+
okp_config: dict[str, Any],
384+
) -> None:
381385
"""Enrich Llama Stack config with Solr settings.
382386
383387
Args:
384388
ls_config: Llama Stack configuration dict (modified in place)
385-
solr_config: Solr configuration dict. Expected keys:
386-
- enabled (bool): whether Solr enrichment should run
389+
rag_config: RAG configuration dict. Used keys:
390+
- inline (list[str]): inline RAG IDs
391+
- tool (list[str]): tool RAG IDs
392+
okp_config: OKP configuration dict. Used keys:
387393
- chunk_filter_query (str): Solr filter query for chunk retrieval
388394
"""
389-
if not solr_config or not solr_config.get("enabled"):
395+
inline_ids = rag_config.get("inline") or []
396+
tool_ids = rag_config.get("tool") or []
397+
okp_enabled = constants.OKP_RAG_ID in inline_ids or constants.OKP_RAG_ID in tool_ids
398+
399+
if not okp_enabled:
390400
logger.info("OKP is not enabled: skipping")
391401
return
392402

403+
user_filter = okp_config.get("chunk_filter_query")
404+
chunk_filter_query = (
405+
f"{constants.SOLR_CHUNK_FILTER_QUERY} AND {user_filter}"
406+
if user_filter
407+
else constants.SOLR_CHUNK_FILTER_QUERY
408+
)
409+
393410
logger.info("Enriching Llama Stack config with OKP")
394411

395412
# Add vector_io provider for Solr
@@ -420,9 +437,6 @@ def enrich_solr(ls_config: dict[str, Any], solr_config: dict[str, Any]) -> None:
420437
embedding_dim_env = (
421438
f"${{env.SOLR_EMBEDDING_DIM:={constants.SOLR_DEFAULT_EMBEDDING_DIMENSION}}}"
422439
)
423-
424-
chunk_filter_query = solr_config.get("chunk_filter_query", "is_chunk:true")
425-
426440
ls_config["providers"]["vector_io"].append(
427441
{
428442
"provider_id": constants.SOLR_PROVIDER_ID,
@@ -543,15 +557,7 @@ def generate_configuration(
543557
enrich_byok_rag(ls_config, config.get("byok_rag", []))
544558

545559
# Enrichment: Solr - enabled when "okp" appears in either inline or tool list
546-
rag_config = config.get("rag", {})
547-
inline_ids = rag_config.get("inline") or []
548-
tool_ids = rag_config.get("tool") or []
549-
okp_enabled = constants.OKP_RAG_ID in inline_ids or constants.OKP_RAG_ID in tool_ids
550-
okp_config = config.get("okp", {})
551-
chunk_filter_query = okp_config.get("chunk_filter_query", "is_chunk:true")
552-
enrich_solr(
553-
ls_config, {"enabled": okp_enabled, "chunk_filter_query": chunk_filter_query}
554-
)
560+
enrich_solr(ls_config, config.get("rag", {}), config.get("okp", {}))
555561

556562
logger.info("Writing Llama Stack configuration into file %s", output_file)
557563

src/models/config.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,13 +1740,11 @@ class OkpConfiguration(ConfigurationBase):
17401740
"When False, use reference_url for chunk source URLs.",
17411741
)
17421742

1743-
chunk_filter_query: str = Field(
1744-
default="is_chunk:true",
1743+
chunk_filter_query: Optional[str] = Field(
1744+
default=None,
17451745
title="OKP chunk filter query",
1746-
description="OKP filter query applied to every OKP search request. "
1747-
"Defaults to 'is_chunk:true' to restrict results to chunk documents. "
1748-
"To add extra constraints, extend the expression using boolean syntax, "
1749-
"e.g. 'is_chunk:true AND product:*openshift*'.",
1746+
description="Additional OKP filter query applied to every OKP search request. "
1747+
"Use Solr boolean syntax, e.g. 'product:ansible AND product:*openshift*'.",
17501748
)
17511749

17521750

tests/unit/models/config/test_dump_configuration.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def test_dump_configuration(tmp_path: Path) -> None:
212212
},
213213
"okp": {
214214
"offline": True,
215-
"chunk_filter_query": "is_chunk:true",
215+
"chunk_filter_query": None,
216216
},
217217
"splunk": None,
218218
"deployment_environment": "development",
@@ -563,7 +563,7 @@ def test_dump_configuration_with_quota_limiters(tmp_path: Path) -> None:
563563
},
564564
"okp": {
565565
"offline": True,
566-
"chunk_filter_query": "is_chunk:true",
566+
"chunk_filter_query": None,
567567
},
568568
"splunk": None,
569569
"deployment_environment": "development",
@@ -792,7 +792,7 @@ def test_dump_configuration_with_quota_limiters_different_values(
792792
},
793793
"okp": {
794794
"offline": True,
795-
"chunk_filter_query": "is_chunk:true",
795+
"chunk_filter_query": None,
796796
},
797797
"splunk": None,
798798
"deployment_environment": "development",
@@ -996,7 +996,7 @@ def test_dump_configuration_byok(tmp_path: Path) -> None:
996996
},
997997
"okp": {
998998
"offline": True,
999-
"chunk_filter_query": "is_chunk:true",
999+
"chunk_filter_query": None,
10001000
},
10011001
"splunk": None,
10021002
"deployment_environment": "development",
@@ -1185,7 +1185,7 @@ def test_dump_configuration_pg_namespace(tmp_path: Path) -> None:
11851185
},
11861186
"okp": {
11871187
"offline": True,
1188-
"chunk_filter_query": "is_chunk:true",
1188+
"chunk_filter_query": None,
11891189
},
11901190
"splunk": None,
11911191
"deployment_environment": "development",

tests/unit/models/config/test_rag_configuration.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def test_default_values(self) -> None:
7373
"""Test that OkpConfiguration has correct default values."""
7474
config = OkpConfiguration()
7575
assert config.offline is True
76-
assert config.chunk_filter_query == "is_chunk:true"
76+
assert config.chunk_filter_query is None
7777

7878
def test_offline_false(self) -> None:
7979
"""Test offline can be set to False (online mode)."""
@@ -82,10 +82,8 @@ def test_offline_false(self) -> None:
8282

8383
def test_custom_chunk_filter_query(self) -> None:
8484
"""Test that chunk_filter_query can be customised."""
85-
config = OkpConfiguration(
86-
chunk_filter_query="is_chunk:true AND product:*openshift*"
87-
)
88-
assert config.chunk_filter_query == "is_chunk:true AND product:*openshift*"
85+
config = OkpConfiguration(chunk_filter_query="product:*openshift*")
86+
assert config.chunk_filter_query == "product:*openshift*"
8987

9088
def test_no_unknown_fields_allowed(self) -> None:
9189
"""Test that OkpConfiguration rejects unknown fields."""

tests/unit/test_llama_stack_configuration.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -403,24 +403,27 @@ def test_generate_configuration_with_byok(tmp_path: Path) -> None:
403403
# =============================================================================
404404

405405

406+
_OKP_RAG_CONFIG = {"inline": ["okp"]}
407+
408+
406409
def test_enrich_solr_skips_when_not_enabled() -> None:
407-
"""Test enrich_solr does nothing when Solr is not enabled."""
410+
"""Test enrich_solr does nothing when OKP is not in rag inline or tool lists."""
408411
ls_config: dict[str, Any] = {}
409-
enrich_solr(ls_config, {"enabled": False})
412+
enrich_solr(ls_config, {"inline": [], "tool": []}, {})
410413
assert not ls_config
411414

412415

413416
def test_enrich_solr_skips_when_empty_config() -> None:
414-
"""Test enrich_solr does nothing with empty config."""
417+
"""Test enrich_solr does nothing with empty rag config."""
415418
ls_config: dict[str, Any] = {}
416-
enrich_solr(ls_config, {})
419+
enrich_solr(ls_config, {}, {})
417420
assert not ls_config
418421

419422

420423
def test_enrich_solr_adds_vector_io_provider() -> None:
421424
"""Test enrich_solr adds Solr provider to vector_io section."""
422425
ls_config: dict[str, Any] = {}
423-
enrich_solr(ls_config, {"enabled": True})
426+
enrich_solr(ls_config, _OKP_RAG_CONFIG, {})
424427

425428
assert "providers" in ls_config
426429
assert "vector_io" in ls_config["providers"]
@@ -431,7 +434,7 @@ def test_enrich_solr_adds_vector_io_provider() -> None:
431434
def test_enrich_solr_adds_vector_store_registration() -> None:
432435
"""Test enrich_solr registers the Solr vector store."""
433436
ls_config: dict[str, Any] = {}
434-
enrich_solr(ls_config, {"enabled": True})
437+
enrich_solr(ls_config, _OKP_RAG_CONFIG, {})
435438

436439
assert "registered_resources" in ls_config
437440
store_ids = [
@@ -443,7 +446,7 @@ def test_enrich_solr_adds_vector_store_registration() -> None:
443446
def test_enrich_solr_adds_embedding_model() -> None:
444447
"""Test enrich_solr registers the Solr embedding model."""
445448
ls_config: dict[str, Any] = {}
446-
enrich_solr(ls_config, {"enabled": True})
449+
enrich_solr(ls_config, _OKP_RAG_CONFIG, {})
447450

448451
model_ids = [m["model_id"] for m in ls_config["registered_resources"]["models"]]
449452
assert "solr_embedding" in model_ids
@@ -454,7 +457,7 @@ def test_enrich_solr_skips_duplicate_provider() -> None:
454457
ls_config: dict[str, Any] = {
455458
"providers": {"vector_io": [{"provider_id": "okp_solr"}]}
456459
}
457-
enrich_solr(ls_config, {"enabled": True})
460+
enrich_solr(ls_config, _OKP_RAG_CONFIG, {})
458461

459462
provider_ids = [p["provider_id"] for p in ls_config["providers"]["vector_io"]]
460463
assert provider_ids.count("okp_solr") == 1
@@ -465,7 +468,7 @@ def test_enrich_solr_skips_duplicate_vector_store() -> None:
465468
ls_config: dict[str, Any] = {
466469
"registered_resources": {"vector_stores": [{"vector_store_id": "portal-rag"}]}
467470
}
468-
enrich_solr(ls_config, {"enabled": True})
471+
enrich_solr(ls_config, _OKP_RAG_CONFIG, {})
469472

470473
store_ids = [
471474
s["vector_store_id"] for s in ls_config["registered_resources"]["vector_stores"]
@@ -482,7 +485,7 @@ def test_enrich_solr_preserves_existing_config() -> None:
482485
"models": [{"model_id": "existing_model"}],
483486
},
484487
}
485-
enrich_solr(ls_config, {"enabled": True})
488+
enrich_solr(ls_config, _OKP_RAG_CONFIG, {})
486489

487490
provider_ids = [p["provider_id"] for p in ls_config["providers"]["vector_io"]]
488491
assert "existing_provider" in provider_ids
@@ -493,3 +496,30 @@ def test_enrich_solr_preserves_existing_config() -> None:
493496
]
494497
assert "existing_store" in store_ids
495498
assert "portal-rag" in store_ids
499+
500+
501+
def test_enrich_solr_default_chunk_filter_query() -> None:
502+
"""Test enrich_solr uses the internal chunk filter when no user filter is set."""
503+
ls_config: dict[str, Any] = {}
504+
enrich_solr(ls_config, _OKP_RAG_CONFIG, {})
505+
506+
provider = next(
507+
p for p in ls_config["providers"]["vector_io"] if p["provider_id"] == "okp_solr"
508+
)
509+
assert (
510+
provider["config"]["chunk_window_config"]["chunk_filter_query"]
511+
== "is_chunk:true"
512+
)
513+
514+
515+
def test_enrich_solr_user_chunk_filter_query_is_conjoined() -> None:
516+
"""Test enrich_solr ANDs the user filter with the internal chunk filter."""
517+
ls_config: dict[str, Any] = {}
518+
enrich_solr(ls_config, _OKP_RAG_CONFIG, {"chunk_filter_query": "product:ansible"})
519+
520+
provider = next(
521+
p for p in ls_config["providers"]["vector_io"] if p["provider_id"] == "okp_solr"
522+
)
523+
assert provider["config"]["chunk_window_config"]["chunk_filter_query"] == (
524+
"is_chunk:true AND product:ansible"
525+
)

0 commit comments

Comments
 (0)