From d486eb31cb27d3592d7a1af9f7db2a9bae359a93 Mon Sep 17 00:00:00 2001 From: OiPunk Date: Thu, 5 Mar 2026 12:39:36 +0800 Subject: [PATCH 1/2] Python: fix prompt template engine blocking HTML tags in content When a prompt contains HTML tags like

,

, etc., the ChatHistory.from_rendered_prompt() method was parsing them as XML elements and silently discarding their content because they did not match any known SK template tags (message, chat_history). The fix treats unknown XML tags as literal text by serializing them back to their original string representation and appending that text to the surrounding message content, so HTML markup passes through to the LLM unchanged. Fixes #13632 --- .../semantic_kernel/contents/chat_history.py | 17 ++++++++ .../tests/unit/contents/test_chat_history.py | 43 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/python/semantic_kernel/contents/chat_history.py b/python/semantic_kernel/contents/chat_history.py index e5daec19fef5..a773c232c7bc 100644 --- a/python/semantic_kernel/contents/chat_history.py +++ b/python/semantic_kernel/contents/chat_history.py @@ -353,6 +353,23 @@ 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 text and append to the + # previous message so that the original 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)) + # For unknown tags the tail is part of the surrounding text, + # so keep it in the same message instead of starting a new one. + if item.tail: + messages[-1].content = (messages[-1].content or "") + unescape(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..4802ac097446 100644 --- a/python/tests/unit/contents/test_chat_history.py +++ b/python/tests/unit/contents/test_chat_history.py @@ -612,6 +612,49 @@ 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_text_around(): + """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) + # The tag inside a is handled by ChatMessageContent.from_element + 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): From b50427208efb7b8c08f13a88f12a59f94793ba85 Mon Sep 17 00:00:00 2001 From: weiguangli-io Date: Fri, 13 Mar 2026 11:16:33 +0800 Subject: [PATCH 2/2] fix: ruff-format string concatenation in test_chat_history --- python/tests/unit/contents/test_chat_history.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/tests/unit/contents/test_chat_history.py b/python/tests/unit/contents/test_chat_history.py index 4802ac097446..3c1b92945296 100644 --- a/python/tests/unit/contents/test_chat_history.py +++ b/python/tests/unit/contents/test_chat_history.py @@ -618,8 +618,7 @@ def test_from_rendered_prompt_preserves_html_p_tag(): 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?

"' + '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