From 295648615cb0cd1c46422a697cf8c93bce2549cf Mon Sep 17 00:00:00 2001 From: "Ethan T." Date: Fri, 13 Mar 2026 13:21:10 +0800 Subject: [PATCH 1/2] fix(python): handle unknown XML tags (e.g. HTML) as literal text in from_rendered_prompt When a prompt contains HTML tags like

,

, , etc., the from_rendered_prompt method wraps the prompt in a element and parses it as XML. Previously, child elements whose tag names did not match known SK template tags (message, chat_history) were silently skipped, causing their text content to be lost. Now, unrecognized tags are serialized back to their original text representation and appended to the preceding message content, preserving the full original prompt. Fixes #13632 --- .../semantic_kernel/contents/chat_history.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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: From 9fcee7c32067d13bfdbef6f92c543e08b05d5953 Mon Sep 17 00:00:00 2001 From: "Ethan T." Date: Fri, 13 Mar 2026 13:21:32 +0800 Subject: [PATCH 2/2] test(python): add regression tests for HTML tags in prompt templates Add four test cases to verify that HTML tags in prompts are preserved as literal text and not silently dropped by the XML parser in from_rendered_prompt(). Covers: single

tag, multiple HTML tags, HTML with surrounding text, and SK template tags mixed with HTML. Related to #13632 --- .../tests/unit/contents/test_chat_history.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) 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):