Skip to content
Merged
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
6 changes: 6 additions & 0 deletions Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
155 changes: 155 additions & 0 deletions patches/fix_tool_executor_attributes.py
Original file line number Diff line number Diff line change
@@ -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()
7 changes: 5 additions & 2 deletions run-no-guard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions run.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
Loading