diff --git a/Containerfile b/Containerfile index fafe7f3..470759a 100644 --- a/Containerfile +++ b/Containerfile @@ -29,6 +29,12 @@ COPY ./pyproject.toml ./ RUN uv sync --no-dev +# Patch llama-stack 0.3.5 tool_executor to pass through file attributes +# (filename, doc_url, etc.) in Responses API file_search_call results. +# See: https://github.com/redhat-ai-dev/llama-stack/patches/ +COPY ./patches/fix_tool_executor_attributes.py /tmp/fix_tool_executor_attributes.py +RUN .venv/bin/python /tmp/fix_tool_executor_attributes.py && rm /tmp/fix_tool_executor_attributes.py + FROM registry.access.redhat.com/ubi9/python-312-minimal:9.7@sha256:2ac60c655288a88ec55df5e2154b9654629491e3c58b5c54450fb3d27a575cb6 ARG APP_ROOT=/app-root WORKDIR /app-root diff --git a/Makefile b/Makefile index 4f17f56..976f6dc 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -RAG_CONTENT_IMAGE ?= quay.io/redhat-ai-dev/rag-content:experimental-release-1.8-lcs +RAG_CONTENT_IMAGE ?= quay.io/redhat-ai-dev/rag-content:release-1.9-lcs VENV := $(CURDIR)/scripts/python-scripts/.venv PYTHON := $(VENV)/bin/python3 PIP := $(VENV)/bin/pip3 diff --git a/patches/fix_tool_executor_attributes.py b/patches/fix_tool_executor_attributes.py new file mode 100644 index 0000000..67d1bf5 --- /dev/null +++ b/patches/fix_tool_executor_attributes.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Patch llama_stack tool_executor.py to pass through file attributes. + +Llama Stack 0.3.5 has a bug where _build_result_messages hardcodes +`filename=doc_id` and `attributes={}` for file_search_call results, +even though the actual attributes are available from the vector store +search. This patch: + +1. Adds `search_result_attributes` to the metadata returned by + `_execute_knowledge_search_via_vector_store`. +2. Updates `_build_result_messages` to use `citation_files` and + `search_result_attributes` from metadata instead of the hardcoded + values. +3. Normalises attributes so that `doc_url` is set from `docs_url` if + missing, and uses the `title` attribute as the filename when + available (instead of the raw compound filename string). + +Usage (typically called from a Containerfile): + python patches/fix_tool_executor_attributes.py +""" + +import importlib +import inspect +import sys + + +def _find_tool_executor_path() -> str: + """Return the filesystem path to the installed tool_executor.py.""" + mod = importlib.import_module( + "llama_stack.providers.inline.agents.meta_reference.responses.tool_executor" + ) + path = inspect.getfile(mod) + print(f"[patch] Found tool_executor.py at: {path}") + return path + + +def _patch_file(path: str) -> None: + with open(path, "r") as f: + source = f.read() + + original = source # keep a copy for comparison + + # --------------------------------------------------------------- + # Patch 1 – _execute_knowledge_search_via_vector_store + # Add search_result_attributes to the metadata dict. + # --------------------------------------------------------------- + if "search_result_attributes" not in source: + source = source.replace( + ' return ToolInvocationResult(\n' + ' content=content_items,\n' + ' metadata={\n' + ' "document_ids": [r.file_id for r in search_results],\n' + ' "chunks": [r.content[0].text if r.content else "" for r in search_results],\n' + ' "scores": [r.score for r in search_results],\n' + ' "citation_files": citation_files,\n' + ' },\n' + ' )', + ' search_result_attributes = [r.attributes or {} for r in search_results]\n' + '\n' + ' return ToolInvocationResult(\n' + ' content=content_items,\n' + ' metadata={\n' + ' "document_ids": [r.file_id for r in search_results],\n' + ' "chunks": [r.content[0].text if r.content else "" for r in search_results],\n' + ' "scores": [r.score for r in search_results],\n' + ' "citation_files": citation_files,\n' + ' "search_result_attributes": search_result_attributes,\n' + ' },\n' + ' )', + ) + print("[patch] Injected search_result_attributes into metadata dict") + else: + print("[patch] search_result_attributes already present – skipping patch 1") + + # --------------------------------------------------------------- + # Patch 2 – _build_result_messages + # Use citation_files and search_result_attributes from metadata, + # normalise attributes (doc_url alias), and use title attribute + # as filename. + # --------------------------------------------------------------- + + # 2a. Replace the entire results-building block + old_block = ( + ' if result and "document_ids" in result.metadata:\n' + ' message.results = []\n' + ' for i, doc_id in enumerate(result.metadata["document_ids"]):\n' + ' text = result.metadata["chunks"][i] if "chunks" in result.metadata else None\n' + ' score = result.metadata["scores"][i] if "scores" in result.metadata else None\n' + ' message.results.append(\n' + ' OpenAIResponseOutputMessageFileSearchToolCallResults(\n' + ' file_id=doc_id,\n' + ' filename=doc_id,\n' + ' text=text,\n' + ' score=score,\n' + ' attributes={},\n' + ' )\n' + ' )' + ) + new_block = ( + ' if result and "document_ids" in result.metadata:\n' + ' sr_citation_files = result.metadata.get("citation_files", {})\n' + ' sr_attributes = result.metadata.get("search_result_attributes", [])\n' + ' message.results = []\n' + ' for i, doc_id in enumerate(result.metadata["document_ids"]):\n' + ' text = result.metadata["chunks"][i] if "chunks" in result.metadata else None\n' + ' score = result.metadata["scores"][i] if "scores" in result.metadata else None\n' + ' attrs = dict(sr_attributes[i]) if i < len(sr_attributes) else {}\n' + ' # Normalise: add doc_url from docs_url if not already present\n' + ' if "doc_url" not in attrs and "docs_url" in attrs:\n' + ' attrs["doc_url"] = attrs["docs_url"]\n' + ' # Use clean title from attributes; fall back to citation_files then doc_id\n' + ' display_name = attrs.get("title") or sr_citation_files.get(doc_id, doc_id)\n' + ' message.results.append(\n' + ' OpenAIResponseOutputMessageFileSearchToolCallResults(\n' + ' file_id=doc_id,\n' + ' filename=display_name,\n' + ' text=text,\n' + ' score=score,\n' + ' attributes=attrs,\n' + ' )\n' + ' )' + ) + + if "sr_citation_files" not in source: + count = source.count(old_block) + if count == 0: + print("[patch] WARNING: Could not find the results-building block to replace") + print("[patch] The tool_executor.py may have been modified. Manual patching required.") + sys.exit(1) + source = source.replace(old_block, new_block) + print("[patch] Replaced results-building block with normalised version") + else: + print("[patch] sr_citation_files already present – skipping patch 2") + + if source == original: + print("[patch] No changes were needed – file already patched") + return + + with open(path, "w") as f: + f.write(source) + + print(f"[patch] Successfully patched {path}") + + +def main() -> None: + try: + path = _find_tool_executor_path() + _patch_file(path) + except Exception as e: + print(f"[patch] ERROR: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/run-no-guard.yaml b/run-no-guard.yaml index a8247cd..2348844 100644 --- a/run-no-guard.yaml +++ b/run-no-guard.yaml @@ -81,7 +81,7 @@ providers: storage_dir: /tmp/llama-stack-files metadata_store: table_name: files_metadata - backend: sql_default + backend: sql_files storage: backends: kv_default: @@ -90,9 +90,12 @@ storage: sql_default: type: sql_sqlite db_path: /tmp/sql_store.db + sql_files: + type: sql_sqlite + db_path: /rag-content/vector_db/rhdh_product_docs/1.9/files_metadata.db faiss_kv: type: kv_sqlite - db_path: /rag-content/vector_db/rhdh_product_docs/1.8/faiss_store.db + db_path: /rag-content/vector_db/rhdh_product_docs/1.9/faiss_store.db stores: metadata: namespace: registry diff --git a/run.yaml b/run.yaml index d507144..8021f6c 100644 --- a/run.yaml +++ b/run.yaml @@ -91,7 +91,7 @@ providers: storage_dir: /tmp/llama-stack-files metadata_store: table_name: files_metadata - backend: sql_default + backend: sql_files storage: backends: kv_default: @@ -100,9 +100,12 @@ storage: sql_default: type: sql_sqlite db_path: /tmp/sql_store.db + sql_files: + type: sql_sqlite + db_path: /rag-content/vector_db/rhdh_product_docs/1.9/files_metadata.db faiss_kv: type: kv_sqlite - db_path: /rag-content/vector_db/rhdh_product_docs/1.8/faiss_store.db + db_path: /rag-content/vector_db/rhdh_product_docs/1.9/faiss_store.db stores: metadata: namespace: registry