diff --git a/python/semantic_kernel/contents/chat_history.py b/python/semantic_kernel/contents/chat_history.py index e5daec19fef5..97e325b23405 100644 --- a/python/semantic_kernel/contents/chat_history.py +++ b/python/semantic_kernel/contents/chat_history.py @@ -350,11 +350,23 @@ def from_rendered_prompt(cls: type[_T], rendered_prompt: str) -> _T: for item in xml_prompt: if item.tag == CHAT_MESSAGE_CONTENT_TAG: messages.append(ChatMessageContent.from_element(item)) + if item.tail and item.tail.strip(): + messages.append(ChatMessageContent(role=AuthorRole.USER, content=unescape(item.tail.strip()))) elif item.tag == CHAT_HISTORY_TAG: for message in item: messages.append(ChatMessageContent.from_element(message)) - if item.tail and item.tail.strip(): - messages.append(ChatMessageContent(role=AuthorRole.USER, content=unescape(item.tail.strip()))) + if item.tail and item.tail.strip(): + messages.append(ChatMessageContent(role=AuthorRole.USER, content=unescape(item.tail.strip()))) + else: + # Unrecognized XML element (e.g. HTML tags like

,

, etc.) + # Serialize it back to string and append to the last message's content + # or create a new user message, so that HTML tags in prompts are preserved. + raw = tostring(item, encoding="unicode", short_empty_elements=False) + tail = item.tail or "" + if messages: + messages[-1].content = (messages[-1].content or "") + raw + tail + else: + messages.append(ChatMessageContent(role=AuthorRole.USER, content=raw + tail)) if len(messages) == 1 and messages[0].role == AuthorRole.SYSTEM: messages[0].role = AuthorRole.USER return cls(messages=messages) diff --git a/python/tests/unit/contents/test_chat_history.py b/python/tests/unit/contents/test_chat_history.py index ac41949a7325..ceb06f487f0a 100644 --- a/python/tests/unit/contents/test_chat_history.py +++ b/python/tests/unit/contents/test_chat_history.py @@ -628,3 +628,42 @@ def __str__(self) -> str: ]) chat_history.add_tool_message([FunctionResultContent(id="test1", result=custom_result)]) assert "CustomResultTestValue" in chat_history.serialize() + + +def test_chat_history_from_rendered_prompt_with_html_tags(): + """Test that HTML tags like

in prompts are preserved and not silently dropped. + + Regression test for https://github.com/microsoft/semantic-kernel/issues/13632 + """ + rendered = 'Translate following message from English language into the Spanish language - "

What is your name?

"' + chat_history = ChatHistory.from_rendered_prompt(rendered) + assert len(chat_history.messages) == 1 + assert chat_history.messages[0].role == AuthorRole.USER + assert "

" in chat_history.messages[0].content + assert "What is your name?" in chat_history.messages[0].content + assert "

" in chat_history.messages[0].content + + +def test_chat_history_from_rendered_prompt_with_multiple_html_tags(): + """Test that multiple HTML tags in prompts are preserved.""" + rendered = "Here is bold and italic text" + chat_history = ChatHistory.from_rendered_prompt(rendered) + assert len(chat_history.messages) == 1 + assert chat_history.messages[0].role == AuthorRole.USER + assert "bold" in chat_history.messages[0].content + assert "italic" in chat_history.messages[0].content + + +def test_chat_history_from_rendered_prompt_html_with_message_tags(): + """Test that HTML tags work alongside recognized message tags.""" + rendered = 'You are helpfulTranslate:

Hello

please' + chat_history = ChatHistory.from_rendered_prompt(rendered) + assert chat_history.messages[0].role == AuthorRole.SYSTEM + assert chat_history.messages[0].content == "You are helpful" + # The HTML content and surrounding text should be preserved + found_html = False + for msg in chat_history.messages[1:]: + if "

" in (msg.content or "") and "Hello" in (msg.content or ""): + found_html = True + break + assert found_html, f"HTML tag

Hello

not found in messages: {[m.content for m in chat_history.messages]}"