diff --git a/python/semantic_kernel/contents/chat_history.py b/python/semantic_kernel/contents/chat_history.py index e5daec19fef5..d368c346a6b7 100644 --- a/python/semantic_kernel/contents/chat_history.py +++ b/python/semantic_kernel/contents/chat_history.py @@ -353,6 +353,25 @@ def from_rendered_prompt(cls: type[_T], rendered_prompt: str) -> _T: elif item.tag == CHAT_HISTORY_TAG: for message in item: messages.append(ChatMessageContent.from_element(message)) + else: + # Unknown XML tags (e.g. HTML tags like

,

, ) are not + # SK template tags. Serialize them back to their original text + # representation and append to the preceding message so that the + # full prompt content is preserved. + saved_tail = item.tail + item.tail = None + raw = unescape(tostring(item, encoding="unicode", short_empty_elements=False)) + item.tail = saved_tail + if messages: + messages[-1].content = (messages[-1].content or "") + raw + else: + messages.append(ChatMessageContent(role=AuthorRole.USER, content=raw)) + if item.tail: + if item.tail.strip(): + messages[-1].content = (messages[-1].content or "") + unescape(item.tail) + else: + messages[-1].content = (messages[-1].content or "") + item.tail + continue if item.tail and item.tail.strip(): messages.append(ChatMessageContent(role=AuthorRole.USER, content=unescape(item.tail.strip()))) if len(messages) == 1 and messages[0].role == AuthorRole.SYSTEM: diff --git a/python/tests/unit/contents/test_chat_history.py b/python/tests/unit/contents/test_chat_history.py index ac41949a7325..81b79315723b 100644 --- a/python/tests/unit/contents/test_chat_history.py +++ b/python/tests/unit/contents/test_chat_history.py @@ -612,6 +612,47 @@ def test_to_from_file(chat_history: ChatHistory, tmp_path): assert chat_history_2.messages[4] == chat_history.messages[4] +def test_from_rendered_prompt_preserves_html_p_tag(): + """HTML

tags in prompts should be preserved as text, not treated as template tags. + + 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 "

What is your name?

" in chat_history.messages[0].content + + +def test_from_rendered_prompt_preserves_multiple_html_tags(): + """Multiple HTML tags in prompts should be preserved as text.""" + rendered = "

First paragraph

A div
" + chat_history = ChatHistory.from_rendered_prompt(rendered) + assert len(chat_history.messages) == 1 + assert "

First paragraph

" in chat_history.messages[0].content + assert "
A div
" in chat_history.messages[0].content + + +def test_from_rendered_prompt_preserves_html_with_surrounding_text(): + """HTML tags surrounded by plain text should preserve all content.""" + rendered = "Hello world today" + chat_history = ChatHistory.from_rendered_prompt(rendered) + assert len(chat_history.messages) == 1 + assert "Hello" in chat_history.messages[0].content + assert "world" in chat_history.messages[0].content + assert "today" in chat_history.messages[0].content + + +def test_from_rendered_prompt_sk_tags_still_work_with_html(): + """SK template tags should still be parsed correctly even when HTML tags are present.""" + rendered = 'Tell me about bold text' + chat_history = ChatHistory.from_rendered_prompt(rendered) + assert len(chat_history.messages) == 1 + assert chat_history.messages[0].role == AuthorRole.USER + + def test_chat_history_serialize(chat_history: ChatHistory): class CustomResultClass: def __init__(self, result):