11"""Streaming query handler using Responses API."""
22
3+ import datetime
34import json
45import logging
5- from datetime import UTC , datetime
66from typing import Annotated , Any , AsyncIterator , Optional , cast
77
88from fastapi import APIRouter , Depends , HTTPException , Request
5353 UnauthorizedResponse ,
5454 UnprocessableEntityResponse ,
5555)
56- from utils .types import ReferencedDocument
56+ from utils .types import RAGChunk , ReferencedDocument
5757from utils .endpoints import (
5858 check_configuration_loaded ,
5959 validate_and_retrieve_conversation ,
8585from utils .suid import normalize_conversation_id
8686from utils .token_counter import TokenCounter
8787from utils .types import ResponsesApiParams , TurnSummary
88+ from utils .vector_search import format_rag_context_for_injection , perform_vector_search
8889
8990logger = logging .getLogger (__name__ )
9091router = APIRouter (tags = ["streaming_query" ])
100101 404 : NotFoundResponse .openapi_response (
101102 examples = ["conversation" , "model" , "provider" ]
102103 ),
103- 413 : PromptTooLongResponse .openapi_response (),
104+ # 413: PromptTooLongResponse.openapi_response(),
104105 422 : UnprocessableEntityResponse .openapi_response (),
105106 429 : QuotaExceededResponse .openapi_response (),
106107 500 : InternalServerErrorResponse .openapi_response (examples = ["configuration" ]),
@@ -144,7 +145,7 @@ async def streaming_query_endpoint_handler( # pylint: disable=too-many-locals
144145 check_configuration_loaded (configuration )
145146
146147 user_id , _user_name , _skip_userid_check , token = auth
147- started_at = datetime .now (UTC ).strftime ("%Y-%m-%dT%H:%M:%SZ" )
148+ started_at = datetime .datetime . now (datetime . UTC ).strftime ("%Y-%m-%dT%H:%M:%SZ" )
148149
149150 # Check token availability
150151 check_tokens_available (configuration .quota_limiters , user_id )
@@ -172,6 +173,17 @@ async def streaming_query_endpoint_handler( # pylint: disable=too-many-locals
172173
173174 client = AsyncLlamaStackClientHolder ().get_client ()
174175
176+ pre_rag_chunks : list [RAGChunk ] = []
177+ doc_ids_from_chunks : list [ReferencedDocument ] = []
178+
179+ _ , _ , doc_ids_from_chunks , pre_rag_chunks = await perform_vector_search (
180+ client , query_request , configuration
181+ )
182+ rag_context = format_rag_context_for_injection (pre_rag_chunks )
183+ if rag_context :
184+ query_request = query_request .model_copy (deep = True )
185+ query_request .query = query_request .query + rag_context
186+
175187 # Prepare API request parameters
176188 responses_params = await prepare_responses_params (
177189 client = client ,
@@ -212,6 +224,7 @@ async def streaming_query_endpoint_handler( # pylint: disable=too-many-locals
212224 generator , turn_summary = await retrieve_response_generator (
213225 responses_params = responses_params ,
214226 context = context ,
227+ doc_ids_from_chunks = doc_ids_from_chunks ,
215228 )
216229
217230 response_media_type = (
@@ -234,6 +247,7 @@ async def streaming_query_endpoint_handler( # pylint: disable=too-many-locals
234247async def retrieve_response_generator (
235248 responses_params : ResponsesApiParams ,
236249 context : ResponseGeneratorContext ,
250+ doc_ids_from_chunks : list [ReferencedDocument ],
237251) -> tuple [AsyncIterator [str ], TurnSummary ]:
238252 """
239253 Retrieve the appropriate response generator.
@@ -273,6 +287,8 @@ async def retrieve_response_generator(
273287 response = await context .client .responses .create (
274288 ** responses_params .model_dump ()
275289 )
290+ # Store pre-RAG documents for later merging
291+ turn_summary .pre_rag_documents = doc_ids_from_chunks
276292 return response_generator (response , context , turn_summary ), turn_summary
277293
278294 # Handle know LLS client errors only at stream creation time and shield execution
@@ -373,7 +389,7 @@ async def generate_response(
373389 turn_summary .referenced_documents ,
374390 context .query_request .media_type or MEDIA_TYPE_JSON ,
375391 )
376- completed_at = datetime .now (UTC ).strftime ("%Y-%m-%dT%H:%M:%SZ" )
392+ completed_at = datetime .datetime . now (datetime . UTC ).strftime ("%Y-%m-%dT%H:%M:%SZ" )
377393
378394 # Store query results (transcript, conversation details, cache)
379395 logger .info ("Storing query results" )
@@ -571,9 +587,21 @@ async def response_generator( # pylint: disable=too-many-branches,too-many-stat
571587 turn_summary .token_usage = extract_token_usage (
572588 latest_response_object , context .model_id
573589 )
574- turn_summary .referenced_documents = parse_referenced_documents (
575- latest_response_object
576- )
590+ tool_based_documents = parse_referenced_documents (latest_response_object )
591+
592+ # Merge pre-RAG documents with tool-based documents (similar to query.py)
593+ if turn_summary .pre_rag_documents :
594+ all_documents = turn_summary .pre_rag_documents + tool_based_documents
595+ seen = set ()
596+ deduplicated_documents = []
597+ for doc in all_documents :
598+ key = (doc .doc_url , doc .doc_title )
599+ if key not in seen :
600+ seen .add (key )
601+ deduplicated_documents .append (doc )
602+ turn_summary .referenced_documents = deduplicated_documents
603+ else :
604+ turn_summary .referenced_documents = tool_based_documents
577605
578606
579607def stream_http_error_event (
@@ -608,7 +636,7 @@ def stream_http_error_event(
608636
609637def format_stream_data (d : dict ) -> str :
610638 """
611- Format a dictionary as a Server-Sent Events (SSE) data string .
639+ Create a response generator function for Responses API streaming .
612640
613641 Parameters:
614642 d (dict): The data to be formatted as an SSE event.
@@ -694,22 +722,24 @@ def stream_event(data: dict, event_type: str, media_type: str) -> str:
694722 """Build an item to yield based on media type.
695723
696724 Args:
697- data: The data to yield.
698- event_type: The type of event (e.g. token, tool request, tool execution).
699- media_type: Media type of the response (e.g. text or JSON).
725+ data: Dictionary containing the event data
726+ event_type: Type of event (token, tool call, etc.)
727+ media_type: The media type for the response format
700728
701729 Returns:
702- str: The formatted string or JSON to yield.
730+ SSE- formatted string representing the event
703731 """
704732 if media_type == MEDIA_TYPE_TEXT :
705733 if event_type == LLM_TOKEN_EVENT :
706- return data [ "token" ]
734+ return data . get ( "token" , "" )
707735 if event_type == LLM_TOOL_CALL_EVENT :
708- return f"\n Tool call : { json . dumps ( data ) } \n "
736+ return f"[Tool Call : { data . get ( 'function_name' , 'unknown' ) } ] \n "
709737 if event_type == LLM_TOOL_RESULT_EVENT :
710- return f"\n Tool result: { json .dumps (data )} \n "
711- logger .error ("Unknown event type: %s" , event_type )
738+ return "[Tool Result]\n "
739+ if event_type == LLM_TURN_COMPLETE_EVENT :
740+ return ""
712741 return ""
742+
713743 return format_stream_data (
714744 {
715745 "event" : event_type ,
0 commit comments