@@ -292,6 +292,25 @@ async def prepare_responses_params( # pylint: disable=too-many-arguments,too-ma
292292 )
293293
294294
295+ def extract_vector_store_ids_from_tools (
296+ tools : Optional [list [dict [str , Any ]]],
297+ ) -> list [str ]:
298+ """Extract vector store IDs from prepared tool configurations.
299+
300+ Parameters:
301+ tools: The prepared tools list from ResponsesApiParams.
302+
303+ Returns:
304+ List of vector store IDs used in file_search tools, or empty list.
305+ """
306+ if not tools :
307+ return []
308+ for tool in tools :
309+ if tool .get ("type" ) == "file_search" :
310+ return tool .get ("vector_store_ids" , [])
311+ return []
312+
313+
295314def get_rag_tools (vector_store_ids : list [str ]) -> Optional [list [dict [str , Any ]]]:
296315 """Convert vector store IDs to tools format for Responses API.
297316
@@ -390,14 +409,18 @@ def _get_token_value(original: str, header: str) -> str | None:
390409
391410def parse_referenced_documents (
392411 response : Optional [OpenAIResponseObject ],
412+ vector_store_ids : Optional [list [str ]] = None ,
413+ rag_id_mapping : Optional [dict [str , str ]] = None ,
393414) -> list [ReferencedDocument ]:
394415 """Parse referenced documents from Responses API response.
395416
396417 Args:
397418 response: The OpenAI Response API response object
419+ vector_store_ids: Vector store IDs used in the query for source resolution.
420+ rag_id_mapping: Mapping from vector_db_id to user-facing rag_id.
398421
399422 Returns:
400- List of referenced documents with doc_url and doc_title
423+ List of referenced documents with doc_url, doc_title, and source
401424 """
402425 documents : list [ReferencedDocument ] = []
403426 # Use a set to track unique documents by (doc_url, doc_title) tuple
@@ -407,6 +430,10 @@ def parse_referenced_documents(
407430 if response is None or not response .output :
408431 return documents
409432
433+ resolved_source = _resolve_single_store_source (
434+ vector_store_ids or [], rag_id_mapping or {}
435+ )
436+
410437 for output_item in response .output :
411438 item_type = getattr (output_item , "type" , None )
412439
@@ -434,13 +461,36 @@ def parse_referenced_documents(
434461 final_url = doc_url if doc_url else None
435462 if (final_url , doc_title ) not in seen_docs :
436463 documents .append (
437- ReferencedDocument (doc_url = final_url , doc_title = doc_title )
464+ ReferencedDocument (
465+ doc_url = final_url ,
466+ doc_title = doc_title ,
467+ source = resolved_source ,
468+ )
438469 )
439470 seen_docs .add ((final_url , doc_title ))
440471
441472 return documents
442473
443474
475+ def _resolve_single_store_source (
476+ vector_store_ids : list [str ],
477+ rag_id_mapping : dict [str , str ],
478+ ) -> Optional [str ]:
479+ """Resolve source name when there is exactly one vector store.
480+
481+ Parameters:
482+ vector_store_ids: The vector store IDs used in the query.
483+ rag_id_mapping: Mapping from vector_db_id to user-facing rag_id.
484+
485+ Returns:
486+ The resolved rag_id if exactly one store is used, None otherwise.
487+ """
488+ if len (vector_store_ids ) == 1 :
489+ store_id = vector_store_ids [0 ]
490+ return rag_id_mapping .get (store_id )
491+ return None
492+
493+
444494def extract_token_usage (
445495 response : Optional [OpenAIResponseObject ], model_id : str
446496) -> TokenCounter :
@@ -522,15 +572,19 @@ def extract_token_usage(
522572 return token_counter
523573
524574
525- def build_tool_call_summary ( # pylint: disable=too-many-return-statements,too-many-branches
575+ def build_tool_call_summary ( # pylint: disable=too-many-return-statements,too-many-branches,too-many-locals
526576 output_item : OpenAIResponseOutput ,
527577 rag_chunks : list [RAGChunk ],
578+ vector_store_ids : Optional [list [str ]] = None ,
579+ rag_id_mapping : Optional [dict [str , str ]] = None ,
528580) -> tuple [Optional [ToolCallSummary ], Optional [ToolResultSummary ]]:
529581 """Translate Responses API tool outputs into ToolCallSummary and ToolResultSummary.
530582
531583 Args:
532584 output_item: An OpenAIResponseOutput item from the response.output array
533585 rag_chunks: List to append extracted RAG chunks to (from file_search_call items)
586+ vector_store_ids: Vector store IDs used in the query for source resolution.
587+ rag_id_mapping: Mapping from vector_db_id to user-facing rag_id.
534588
535589 Returns:
536590 Tuple of (ToolCallSummary, ToolResultSummary), one may be None
@@ -551,7 +605,9 @@ def build_tool_call_summary( # pylint: disable=too-many-return-statements,too-m
551605
552606 if item_type == "file_search_call" :
553607 file_search_item = cast (FileSearchCall , output_item )
554- extract_rag_chunks_from_file_search_item (file_search_item , rag_chunks )
608+ extract_rag_chunks_from_file_search_item (
609+ file_search_item , rag_chunks , vector_store_ids , rag_id_mapping
610+ )
555611 response_payload : Optional [dict [str , Any ]] = None
556612 if file_search_item .results is not None :
557613 response_payload = {
@@ -731,20 +787,79 @@ def build_tool_result_from_mcp_output_item_done(
731787 )
732788
733789
790+ def _resolve_source_for_result (
791+ result : Any ,
792+ vector_store_ids : list [str ],
793+ rag_id_mapping : dict [str , str ],
794+ ) -> Optional [str ]:
795+ """Resolve the human-friendly index name for a file search result.
796+
797+ Uses the vector store mapping to convert internal llama-stack IDs
798+ to user-facing rag_ids from configuration.
799+
800+ Parameters:
801+ result: A file search result object with optional attributes.
802+ vector_store_ids: The vector store IDs used in this query.
803+ rag_id_mapping: Mapping from vector_db_id to user-facing rag_id.
804+
805+ Returns:
806+ The resolved index name, or None if resolution is not possible.
807+ """
808+ if len (vector_store_ids ) == 1 :
809+ store_id = vector_store_ids [0 ]
810+ return rag_id_mapping .get (store_id , result .filename )
811+
812+ if len (vector_store_ids ) > 1 :
813+ attributes = getattr (result , "attributes" , {}) or {}
814+ attr_store_id : Optional [str ] = attributes .get ("vector_store_id" )
815+ if attr_store_id and attr_store_id in rag_id_mapping :
816+ return rag_id_mapping [attr_store_id ]
817+
818+ return result .filename
819+
820+
821+ def _build_chunk_attributes (result : Any ) -> Optional [dict [str , Any ]]:
822+ """Extract document metadata attributes from a file search result.
823+
824+ Parameters:
825+ result: A file search result object with optional attributes.
826+
827+ Returns:
828+ Dictionary of metadata attributes, or None if no attributes available.
829+ """
830+ attributes = getattr (result , "attributes" , None )
831+ if not attributes :
832+ return None
833+ if isinstance (attributes , dict ):
834+ return attributes if attributes else None
835+ return None
836+
837+
734838def extract_rag_chunks_from_file_search_item (
735839 item : FileSearchCall ,
736840 rag_chunks : list [RAGChunk ],
841+ vector_store_ids : Optional [list [str ]] = None ,
842+ rag_id_mapping : Optional [dict [str , str ]] = None ,
737843) -> None :
738844 """Extract RAG chunks from a file search tool call item.
739845
740846 Args:
741847 item: The file search tool call item
742848 rag_chunks: List to append extracted RAG chunks to
849+ vector_store_ids: Vector store IDs used in the query for source resolution.
850+ rag_id_mapping: Mapping from vector_db_id to user-facing rag_id.
743851 """
744852 if item .results is not None :
745853 for result in item .results :
854+ source = _resolve_source_for_result (
855+ result , vector_store_ids or [], rag_id_mapping or {}
856+ )
857+ attributes = _build_chunk_attributes (result )
746858 rag_chunk = RAGChunk (
747- content = result .text , source = result .filename , score = result .score
859+ content = result .text ,
860+ source = source ,
861+ score = result .score ,
862+ attributes = attributes ,
748863 )
749864 rag_chunks .append (rag_chunk )
750865
0 commit comments