@@ -3542,13 +3542,18 @@ def test_streaming_annotation_added_with_url_citation() -> None:
35423542 assert region ["end_index" ] == 112
35433543
35443544
3545- def _make_url_citation_event (* , title : str , get_url : str | None = None ) -> MagicMock :
3545+ def _make_url_citation_event (
3546+ * ,
3547+ title : str ,
3548+ get_url : str | None = None ,
3549+ url : str = "https://example.search.windows.net/" ,
3550+ ) -> MagicMock :
35463551 event = MagicMock ()
35473552 event .type = "response.output_text.annotation.added"
35483553 event .annotation_index = 0
35493554 event .annotation = {
35503555 "type" : "url_citation" ,
3551- "url" : "https://example.search.windows.net/" ,
3556+ "url" : url ,
35523557 "title" : title ,
35533558 "start_index" : 100 ,
35543559 "end_index" : 112 ,
@@ -3558,6 +3563,17 @@ def _make_url_citation_event(*, title: str, get_url: str | None = None) -> Magic
35583563 return event
35593564
35603565
3566+ def _make_mcp_call_done_event (output : str ) -> MagicMock :
3567+ event = MagicMock ()
3568+ event .type = "response.output_item.done"
3569+ event .item = MagicMock ()
3570+ event .item .type = "mcp_call"
3571+ event .item .id = "mcp_test"
3572+ event .item .call_id = None
3573+ event .item .output = output
3574+ return event
3575+
3576+
35613577def _make_azure_ai_search_output_event (output : Any , * , event_type : str = "response.output_item.done" ) -> MagicMock :
35623578 event = MagicMock ()
35633579 event .type = event_type
@@ -3691,6 +3707,110 @@ def test_streaming_azure_ai_search_output_ignores_unusable_get_url_data(title: s
36913707 assert "get_url" not in annotation ["additional_properties" ]
36923708
36933709
3710+ def test_streaming_mcp_searchindex_citation_enriched_from_mcp_output () -> None :
3711+ """MCP search-index citations are enriched from retrieved document metadata in MCP output."""
3712+ client = OpenAIChatClient (model = "test-model" , api_key = "test-key" )
3713+ chat_options = ChatOptions ()
3714+ function_call_ids : dict [int , tuple [str , str ]] = {}
3715+ document_id = "inspection_procedures_p1_c0"
3716+ mcp_output = f"""
3717+ Retrieved 1 document.
3718+
3719+ 【4:1†source】
3720+ {{
3721+ "id": "{ document_id } ",
3722+ "content": "Inspection Procedures content",
3723+ "title": "Inspection Procedures",
3724+ "source": "inspection_procedures.pdf"
3725+ }}
3726+ """
3727+
3728+ mcp_update = client ._parse_chunk_from_openai (
3729+ _make_mcp_call_done_event (mcp_output ),
3730+ chat_options ,
3731+ function_call_ids ,
3732+ )
3733+ citation_update = client ._parse_chunk_from_openai (
3734+ _make_url_citation_event (
3735+ title = f"mcp://searchindex/{ document_id } " ,
3736+ url = f"mcp://searchindex/{ document_id } " ,
3737+ ),
3738+ chat_options ,
3739+ function_call_ids ,
3740+ )
3741+
3742+ response = client ._finalize_response_updates ([mcp_update , citation_update ])
3743+
3744+ annotation = next (
3745+ annotation
3746+ for message in response .messages
3747+ for content in message .contents
3748+ for annotation in (content .annotations or [])
3749+ )
3750+ assert annotation ["additional_properties" ]["mcp_document_id" ] == document_id
3751+ assert annotation ["additional_properties" ]["document_title" ] == "Inspection Procedures"
3752+ assert annotation ["additional_properties" ]["source" ] == "inspection_procedures.pdf"
3753+
3754+
3755+ def test_parse_response_enriches_mcp_searchindex_citation_from_mcp_output () -> None :
3756+ """Non-streaming Responses output also gets MCP search-index document metadata."""
3757+ client = OpenAIChatClient (model = "test-model" , api_key = "test-key" )
3758+ document_id = "ticket_management_policy_p1_c0"
3759+
3760+ mock_mcp_item = MagicMock ()
3761+ mock_mcp_item .type = "mcp_call"
3762+ mock_mcp_item .id = "mcp_123"
3763+ mock_mcp_item .call_id = None
3764+ mock_mcp_item .name = "knowledge_base_retrieve"
3765+ mock_mcp_item .server_label = "knowledge-base"
3766+ mock_mcp_item .arguments = '{"queries":["ticket policy"]}'
3767+ mock_mcp_item .output = f"""
3768+ Retrieved 1 document.
3769+
3770+ 【14:1†source】
3771+ {{
3772+ "id": "{ document_id } ",
3773+ "content": "Ticket Management Policy content",
3774+ "title": "Ticket Management Policy",
3775+ "source": "ticket_management_policy.pdf"
3776+ }}
3777+ """
3778+
3779+ mock_annotation = MagicMock ()
3780+ mock_annotation .type = "url_citation"
3781+ mock_annotation .title = f"mcp://searchindex/{ document_id } "
3782+ mock_annotation .url = f"mcp://searchindex/{ document_id } "
3783+ mock_annotation .start_index = 221
3784+ mock_annotation .end_index = 233
3785+
3786+ mock_message_content = MagicMock ()
3787+ mock_message_content .type = "output_text"
3788+ mock_message_content .text = "All tickets must be acknowledged within 1 hour.【14:1†source】"
3789+ mock_message_content .annotations = [mock_annotation ]
3790+ mock_message_content .logprobs = None
3791+
3792+ mock_message_item = MagicMock ()
3793+ mock_message_item .type = "message"
3794+ mock_message_item .content = [mock_message_content ]
3795+
3796+ mock_response = MagicMock ()
3797+ mock_response .id = "response_123"
3798+ mock_response .model = "test-model"
3799+ mock_response .created_at = 1000000000
3800+ mock_response .metadata = {}
3801+ mock_response .output = [mock_mcp_item , mock_message_item ]
3802+ mock_response .usage = None
3803+ mock_response .status = "completed"
3804+ mock_response .conversation = None
3805+
3806+ response = client ._parse_response_from_openai (mock_response , options = {})
3807+
3808+ annotation = response .messages [0 ].contents [- 1 ].annotations [0 ]
3809+ assert annotation ["additional_properties" ]["mcp_document_id" ] == document_id
3810+ assert annotation ["additional_properties" ]["document_title" ] == "Ticket Management Policy"
3811+ assert annotation ["additional_properties" ]["source" ] == "ticket_management_policy.pdf"
3812+
3813+
36943814def test_streaming_annotation_added_with_url_citation_no_url () -> None :
36953815 """Test streaming annotation added event with url_citation but missing url is ignored."""
36963816 client = OpenAIChatClient (model = "test-model" , api_key = "test-key" )
0 commit comments