@@ -1791,7 +1791,10 @@ async def test_emits_content_chunk_for_string_content(self):
17911791 if e .content_part is not None and e .content_part .chunk is not None
17921792 ]
17931793 assert len (chunk_events ) == 1
1794- assert chunk_events [0 ].content_part .chunk .data == "Hello!"
1794+ event = chunk_events [0 ]
1795+ assert event .content_part is not None
1796+ assert event .content_part .chunk is not None
1797+ assert event .content_part .chunk .data == "Hello!"
17951798
17961799 @pytest .mark .asyncio
17971800 async def test_emits_content_chunk_for_list_content (self ):
@@ -1811,7 +1814,10 @@ async def test_emits_content_chunk_for_list_content(self):
18111814 if e .content_part is not None and e .content_part .chunk is not None
18121815 ]
18131816 assert len (chunk_events ) == 1
1814- assert chunk_events [0 ].content_part .chunk .data == "Hello Maxwell!"
1817+ event = chunk_events [0 ]
1818+ assert event .content_part is not None
1819+ assert event .content_part .chunk is not None
1820+ assert event .content_part .chunk .data == "Hello Maxwell!"
18151821
18161822 @pytest .mark .asyncio
18171823 async def test_emits_no_chunk_for_empty_content (self ):
@@ -1859,9 +1865,7 @@ async def test_emits_tool_call_start_events_when_has_tool_calls(self):
18591865 msg = AIMessage (
18601866 content = "" ,
18611867 id = "msg-1" ,
1862- tool_calls = [
1863- {"id" : "tool-1" , "name" : "search" , "args" : {"query" : "cats" }}
1864- ],
1868+ tool_calls = [{"id" : "tool-1" , "name" : "search" , "args" : {"query" : "cats" }}],
18651869 )
18661870
18671871 result = await mapper .map_event (msg )
@@ -1873,9 +1877,12 @@ async def test_emits_tool_call_start_events_when_has_tool_calls(self):
18731877 if e .tool_call is not None and e .tool_call .start is not None
18741878 ]
18751879 assert len (tool_start_events ) == 1
1876- assert tool_start_events [0 ].tool_call .tool_call_id == "tool-1"
1877- assert tool_start_events [0 ].tool_call .start .tool_name == "search"
1878- assert tool_start_events [0 ].tool_call .start .input == {"query" : "cats" }
1880+ tool_event = tool_start_events [0 ]
1881+ assert tool_event .tool_call is not None
1882+ assert tool_event .tool_call .start is not None
1883+ assert tool_event .tool_call .tool_call_id == "tool-1"
1884+ assert tool_event .tool_call .start .tool_name == "search"
1885+ assert tool_event .tool_call .start .input == {"query" : "cats" }
18791886
18801887 @pytest .mark .asyncio
18811888 async def test_stores_tool_call_to_message_id_mapping (self ):
@@ -1906,6 +1913,52 @@ async def test_tracks_seen_message_id(self):
19061913
19071914 assert "msg-42" in mapper .seen_message_ids
19081915
1916+ @pytest .mark .asyncio
1917+ async def test_processes_citations_in_content (self ):
1918+ """Should strip citation tags, emit cleaned text, and attach citation to chunk."""
1919+ mapper = UiPathChatMessagesMapper ("test-runtime" , None )
1920+ msg = AIMessage (
1921+ content = 'Some fact<uip:cite title="Doc" url="https://doc.com" /> and more.' ,
1922+ id = "msg-1" ,
1923+ )
1924+
1925+ result = await mapper .map_event (msg )
1926+
1927+ assert result is not None
1928+ chunk_events = [
1929+ e
1930+ for e in result
1931+ if e .content_part is not None and e .content_part .chunk is not None
1932+ ]
1933+ texts : list [str ] = []
1934+ for e in chunk_events :
1935+ assert e .content_part is not None
1936+ assert e .content_part .chunk is not None
1937+ assert e .content_part .chunk .data is not None
1938+ texts .append (e .content_part .chunk .data )
1939+ full_text = "" .join (texts )
1940+ assert "uip:cite" not in full_text
1941+ assert "Some fact" in full_text
1942+
1943+ # The "Some fact" chunk should carry an attached citation
1944+ citation_chunk = next (
1945+ e
1946+ for e in chunk_events
1947+ if e .content_part is not None
1948+ and e .content_part .chunk is not None
1949+ and e .content_part .chunk .citation is not None
1950+ )
1951+ assert citation_chunk .content_part is not None
1952+ assert citation_chunk .content_part .chunk is not None
1953+ citation_event = citation_chunk .content_part .chunk .citation
1954+ assert citation_event is not None
1955+ assert citation_event .end is not None
1956+ assert len (citation_event .end .sources ) == 1
1957+ source = citation_event .end .sources [0 ]
1958+ assert isinstance (source , UiPathConversationCitationSourceUrl )
1959+ assert source .url == "https://doc.com"
1960+ assert source .title == "Doc"
1961+
19091962 @pytest .mark .asyncio
19101963 async def test_pii_masked_response_full_flow (self ):
19111964 """End-to-end: PII-masked response arrives as single AIMessage with list content."""
@@ -1932,40 +1985,12 @@ async def test_pii_masked_response_full_flow(self):
19321985 if e .content_part is not None and e .content_part .chunk is not None
19331986 ]
19341987 assert len (chunk_events ) >= 1
1935- full_text = "" .join (e .content_part .chunk .data for e in chunk_events )
1988+ texts : list [str ] = []
1989+ for e in chunk_events :
1990+ assert e .content_part is not None
1991+ assert e .content_part .chunk is not None
1992+ assert e .content_part .chunk .data is not None
1993+ texts .append (e .content_part .chunk .data )
1994+ full_text = "" .join (texts )
19361995 assert "Hello!" in full_text
19371996 assert result [- 1 ].end is not None
1938-
1939- @pytest .mark .asyncio
1940- async def test_processes_citations_in_content (self ):
1941- """Should strip citation tags, emit cleaned text, and attach citation to chunk."""
1942- mapper = UiPathChatMessagesMapper ("test-runtime" , None )
1943- msg = AIMessage (
1944- content = 'Some fact<uip:cite title="Doc" url="https://doc.com" /> and more.' ,
1945- id = "msg-1" ,
1946- )
1947-
1948- result = await mapper .map_event (msg )
1949-
1950- assert result is not None
1951- chunk_events = [
1952- e
1953- for e in result
1954- if e .content_part is not None and e .content_part .chunk is not None
1955- ]
1956- full_text = "" .join (e .content_part .chunk .data for e in chunk_events )
1957- assert "uip:cite" not in full_text
1958- assert "Some fact" in full_text
1959-
1960- # The "Some fact" chunk should carry an attached citation
1961- citation_chunks = [
1962- e for e in chunk_events if e .content_part .chunk .citation is not None
1963- ]
1964- assert len (citation_chunks ) == 1
1965- citation_event = citation_chunks [0 ].content_part .chunk .citation
1966- assert citation_event .end is not None
1967- assert len (citation_event .end .sources ) == 1
1968- source = citation_event .end .sources [0 ]
1969- assert isinstance (source , UiPathConversationCitationSourceUrl )
1970- assert source .url == "https://doc.com"
1971- assert source .title == "Doc"
0 commit comments