Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUTF8=1 \
PYTHONIOENCODING=UTF-8 \
LANG=en_US.UTF-8 \
LLAMA_INDEX_CACHE_DIR=/tmp/llama_index \
LLAMA_INDEX_CACHE_DIR=/app-root/.cache/llama_index \
TIKTOKEN_CACHE_DIR=/app-root/.tiktoken_cache \
HF_HOME=/app-root/.cache/huggingface
HF_HOME=/app-root/.cache/huggingface \
HF_HUB_OFFLINE=1 \
TRANSFORMERS_OFFLINE=1

COPY --from=builder /app-root/.venv .venv
COPY --from=builder /app-root/.tiktoken_cache .tiktoken_cache
Expand All @@ -95,9 +97,11 @@ COPY --from=lightspeed-rag-content /rag/vector_db/ocp_product_docs ./vector_db/o
COPY --chmod=775 embeddings_model ./embeddings_model
RUN if [ -d /cachi2/output/deps/generic ]; then \
cp /cachi2/output/deps/generic/all-mpnet-base-v2-model.safetensors embeddings_model/all-mpnet-base-v2/model.safetensors ; \
cp /cachi2/output/deps/generic/granite-embedding-30m-english-model.safetensors embeddings_model/granite-embedding-30m-english/model.safetensors ; \
fi && \
for f in \
"embeddings_model/all-mpnet-base-v2/model.safetensors|https://huggingface.co/sentence-transformers/all-mpnet-base-v2/resolve/main/model.safetensors" \
"embeddings_model/granite-embedding-30m-english/model.safetensors|https://huggingface.co/ibm-granite/granite-embedding-30m-english/resolve/main/model.safetensors" \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
; do \
path="${f%%|*}" && url="${f##*|}" && \
if [ ! -f "$path" ]; then \
Expand All @@ -107,16 +111,22 @@ RUN if [ -d /cachi2/output/deps/generic ]; then \
{ echo "ERROR: corrupt safetensors file: $path" ; exit 1 ; } ; \
done

# Pre-populate HuggingFace cache so models can be loaded by ID with TRANSFORMERS_OFFLINE=1
# Pre-populate HuggingFace cache so models can be loaded by ID with TRANSFORMERS_OFFLINE=1.
# Two cache trees are needed: the standard HF hub cache (used by huggingface_hub and
# transformers when no cache_dir override is given) and the llama_index cache (passed as
# cache_folder to SentenceTransformer by llama_index's HuggingFaceEmbedding wrapper).
USER root
RUN for model_dir in all-mpnet-base-v2 granite-embedding-30m-english; do \
case "$model_dir" in \
all-mpnet-base-v2) hf_id="sentence-transformers--all-mpnet-base-v2" ;; \
granite-embedding-30m-english) hf_id="ibm-granite--granite-embedding-30m-english" ;; \
esac && \
repo_dir="/app-root/.cache/huggingface/hub/models--${hf_id}" && \
mkdir -p "$repo_dir/snapshots/local" "$repo_dir/refs" && \
echo "local" > "$repo_dir/refs/main" && \
ln -sf "/app-root/embeddings_model/${model_dir}"/* "$repo_dir/snapshots/local/" ; \
for cache_root in /app-root/.cache/huggingface/hub /app-root/.cache/llama_index; do \
repo_dir="${cache_root}/models--${hf_id}" && \
mkdir -p "$repo_dir/snapshots/local" "$repo_dir/refs" && \
printf '%s' "local" > "$repo_dir/refs/main" && \
ln -sf "/app-root/embeddings_model/${model_dir}"/* "$repo_dir/snapshots/local/" ; \
done ; \
done && \
chown -R 1001:0 /app-root/.cache

Expand Down
6 changes: 3 additions & 3 deletions artifacts.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ artifacts:
- download_url: "https://huggingface.co/sentence-transformers/all-mpnet-base-v2/resolve/main/model.safetensors"
checksum: "sha256:78c0197b6159d92658e319bc1d72e4c73a9a03dd03815e70e555c5ef05615658"
filename: "all-mpnet-base-v2-model.safetensors"
# - download_url: "https://huggingface.co/ibm-granite/granite-embedding-30m-english/resolve/main/model.safetensors"
# checksum: "sha256:3b1fcdc9c5eb954f603bc386474e321505ff29c6c67f21e3aa8db3d2d1a533cf"
# filename: "granite-embedding-30m-english-model.safetensors"
- download_url: "https://huggingface.co/ibm-granite/granite-embedding-30m-english/resolve/main/model.safetensors"
checksum: "sha256:3b1fcdc9c5eb954f603bc386474e321505ff29c6c67f21e3aa8db3d2d1a533cf"
filename: "granite-embedding-30m-english-model.safetensors"
83 changes: 83 additions & 0 deletions docs/ai/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,89 @@ Config classes have two validation phases:

If you add a new config class that needs file existence checks or cross-section validation, add a `validate_yaml()` method and call it from the appropriate parent config's `validate_yaml()`.

### Optional Solr hybrid RAG (`ols_config.solr_hybrid`)

Omit the key to leave Solr hybrid RAG off (no client, no tool). When present, values map to `SolrHybridSettings` and the feature is active. `solr_http_base` must be a valid `http` or `https` URL with a host (checked in `validate_yaml()`).

When ``solr_hybrid`` is defined, every entry under ``reference_content.indexes``
must include ``byok_index: true`` if any local indexes are configured. That rejects
Solr together with an unmarked local vector index (duplicate product RAG); omit
``indexes`` for Solr-only, or mark BYOK indexes explicitly.

```yaml
ols_config:
solr_hybrid:
solr_http_base: "https://solr.example.com:8983"
# max_results, hybrid_*, max_expansion_neighbors: optional overrides
```

Do not duplicate the same product documentation in Solr and a local product index.
You may use ``solr_hybrid`` for product docs and ``reference_content`` for separate
BYOK indexes; each BYOK row must set ``byok_index: true`` when ``solr_hybrid`` is present.

### Hybrid search and chunk expansion

OLS sends a hybrid query to OKP Solr's ``/hybrid-search`` endpoint. The query
performs keyword retrieval first, then reranks candidates by KNN vector
similarity. See the
[OKP RAG Chunk Retrieval Strategy](https://docs.google.com/document/d/1W7G3Tbz5peMAh8cnGcpKvayQAciAyDXzXY_5KXdkY_0/edit?tab=t.0)
for the full specification.

Solr returns raw matched chunks. OLS implements chunk expansion client-side
(all logic in ``SolrHybridSearch`` in ``ols/src/rag_index/solr_support.py``):

1. **Deduplicate by parent** — keep only the first (highest-scored) chunk per
``parent_id``. Chunks without a ``parent_id`` are kept as-is.
2. **Cap results** — slice the deduped list to ``max_results``.
3. **Compute per-chunk budget** — divide the tool's ``tools_token_budget``
(set by the execution framework via ``tool.metadata``) equally across the
deduped chunks. When the budget is 0, skip expansion entirely and return
the matched chunk as-is.
4. **Fetch family** — for each matched chunk, query Solr ``/select`` for all
chunks sharing the same ``parent_id`` AND ``heading_id`` (ordered by
``chunk_index``). Orphan chunks (missing ``heading_id``) skip this step.
5. **Expand around match** — starting from the matched chunk, alternate
between previous and next siblings until one of these limits is hit:
- per-chunk token budget exhausted (tracked via ``num_tokens``)
- ``max_expansion_neighbors`` reached on each side (default 2, configurable
0–10 in ``SolrHybridSettings``; 0 disables expansion)
6. **Concatenate** — assemble the expanded chunks in ``chunk_index`` order,
strip HTML, and return as the tool result (one content block per deduped
match).

Relevant Solr fields for expansion:

| Field | Purpose |
|---|---|
| ``parent_id`` | Groups chunks by source document |
| ``chunk_index`` | Sequential ordering for neighbor expansion |
| ``heading_id`` | Family grouping (chunks under the same heading) |
| ``num_tokens`` | Token count per chunk for budget tracking |

Relevant config fields on ``SolrHybridSettings``:

| Field | Default | Purpose |
|---|---|---|
| ``max_results`` | 5 | Max deduped chunks returned |
| ``max_expansion_neighbors`` | 2 | Max siblings per side during expansion (0 disables) |

### OCP version resolution at startup

When ``solr_hybrid`` is configured, ``SolrHybridSearch.__init__`` resolves the
OCP product version for ``chunk_filter_query``:

1. Read the ``OCP_CLUSTER_VERSION`` environment variable (set by the operator).
If not set, raise ``InvalidConfigurationError`` and stop.
2. Query Solr for available versions of ``openshift_container_platform``
(facet on ``product_version`` with ``fq=product:openshift_container_platform``).
3. Clamp the requested version to the nearest available:
- env version < lowest available → use lowest available
- env version > highest available → use highest available
- otherwise → use the closest available version ≤ requested
4. Build ``chunk_filter_query``:
``is_chunk:true AND product:openshift_container_platform AND product_version:<resolved>``


## Important Constants

Config-related constants live in `ols/constants.py`:
Expand Down
3 changes: 2 additions & 1 deletion docs/config.puml
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,14 @@ class "RHOAIVLLMConfig" as ols.app.models.config.RHOAIVLLMConfig {
credentials_path : str
}
class "ReferenceContent" as ols.app.models.config.ReferenceContent {
embeddings_model_path : Optional[FilePath]
indexes : Optional[list[ReferenceContentIndex]]
validate_yaml() -> None
}
class "ReferenceContentIndex" as ols.app.models.config.ReferenceContentIndex {
product_docs_index_id : Optional[str]
product_docs_index_path : Optional[FilePath]
product_docs_origin : Optional[str]
byok_index : bool
validate_yaml() -> None
}
class "SchedulerConfig" as ols.app.models.config.SchedulerConfig {
Expand Down
6 changes: 3 additions & 3 deletions examples/olsconfig-local-ollama.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ llm_providers:
ols_config:
# max_workers: 1
reference_content:
# product_docs_index_path: "./vector_db/ocp_product_docs/4.15"
# product_docs_index_id: ocp-product-docs-4_15
# embeddings_model_path: "./embeddings_model"
# indexes:
# - product_docs_index_path: "./vector_db/ocp_product_docs/4.15"
# product_docs_index_id: ocp-product-docs-4_15
conversation_cache:
type: memory
memory:
Expand Down
9 changes: 6 additions & 3 deletions examples/olsconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,16 @@ llm_providers:
- name: model-name
ols_config:
# max_workers: 1
# Product docs via Solr hybrid (OKP). Omit solr_hybrid to disable.
solr_hybrid:
solr_http_base: "https://solr.example.com:8983"
# BYOK / local FAISS indexes.
# With solr_hybrid present, every index must set byok_index: true.
reference_content:
indexes:
- product_docs_index_path: "./vector_db/ocp_product_docs/4.17"
product_docs_index_id: ocp-product-docs-4_17
- product_docs_index_path: "./vector_db/user_application_docs/version_1"
product_docs_index_id: user-application-docs-version_1
embeddings_model_path: "./embeddings_model/all-mpnet-base-v2"
byok_index: true
conversation_cache:
type: memory
memory:
Expand Down
6 changes: 3 additions & 3 deletions examples/openshift-lightspeed-tls.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,9 @@ data:
ols_config:
max_workers: 1
reference_content:
product_docs_index_path: "./vector_db/ocp_product_docs/4.15"
product_docs_index_id: ocp-product-docs-4_15
embeddings_model_path: "./embeddings_model"
indexes:
- product_docs_index_path: "./vector_db/ocp_product_docs/4.15"
product_docs_index_id: ocp-product-docs-4_15
conversation_cache:
type: memory
memory:
Expand Down
9 changes: 7 additions & 2 deletions ols/app/endpoints/ols.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,17 +541,22 @@ def generate_response(
streaming=streaming,
audit_ctx=audit_ctx,
)
rag_retriever = (
config.rag_index_loader.get_retriever()
if config.ols_config.reference_content is not None
else None
)
if streaming:
return docs_summarizer.generate_response(
llm_request.query,
config.rag_index_loader.get_retriever(),
rag_retriever,
user_id=user_id,
conversation_id=conversation_id,
skip_user_id_check=skip_user_id_check,
)
response = docs_summarizer.create_response(
llm_request.query,
config.rag_index_loader.get_retriever(),
rag_retriever,
user_id=user_id,
conversation_id=conversation_id,
skip_user_id_check=skip_user_id_check,
Expand Down
Loading