Skip to content

Python: Bug: An empty tool result becomes the string 'None' after ChatHistory.to_prompt() / from_rendered_prompt() #14685

Open

Description

@BlueX888

Describe the bug

An empty tool result becomes the literal string "None" after the prompt round trip that every ChatCompletionClientBase invocation performs.

history = ChatHistory()
history.add_tool_message("", tool_call_id="call_1", function_name="my-plugin-do")
rendered = history.to_prompt()
parsed = ChatHistory.from_rendered_prompt(rendered)

Actual:

original tool message:
  result field: ''
  to_dict():   {'role': 'tool', 'content': '', 'tool_call_id': 'call_1'}
after to_prompt() -> from_rendered_prompt():
  result field: None
  to_dict():   {'role': 'tool', 'content': 'None', 'tool_call_id': 'call_1'}
rendered template: <chat_history>...<message role="tool"><function_result id="call_1" /></message></chat_history>

The empty element parses to result=None, and str(None) is what gets sent to the model. The same happens through the Jinja2 messages helper ({{ messages(chat_history) }}) and through KernelFunctionFromPrompt, which performs this round trip for every chat completion. JSON serialize() / restore_chat_history() preserves "", so only the XML prompt path is affected.

Root cause: FunctionResultContent.from_element sets result=element.text (python/semantic_kernel/contents/function_result_content.py:119), which is None for an empty element; ChatMessageContent._parse_items then stringifies it (python/semantic_kernel/contents/chat_message_content.py:335-336).

To Reproduce

Empty tool results are supported since #14472 (ChatHistory.add_tool_message("")). Run from python/:

uv run python repro_tool_result_roundtrip.py

Expected behavior

An empty result stays "" through to_prompt() / from_rendered_prompt(); to_dict() should keep content='', not 'None'. (Alternatively, rendering could emit an explicit empty text so parsing round-trips.)

Platform

  • Language: Python
  • Source: main branch at 4bb64ee (2026-10-28), semantic-kernel 1.46.0
  • AI model: n/a (deterministic round trip, no model call)
  • OS: Windows 11

Additional context

Reported with AI assistance (analysis and reproduction script); the reporter ran the reproduction and verified the output above.

Activity

  1. changed the title [-]Bug: An empty tool result becomes the string 'None' after ChatHistory.to_prompt() / from_rendered_prompt()[/-] [+]Python: Bug: An empty tool result becomes the string 'None' after ChatHistory.to_prompt() / from_rendered_prompt()[/+] on Oct 29, 2026
  2. BlueX888 commented on Oct 29, 2026

    @BlueX888
    Author

    I plan to implement this if the direction is right: normalize a missing <function_result> element text to "" in FunctionResultContent.from_element (or always emit a text node in to_element), plus regression coverage for the empty-result round trip and the Jinja messages helper. The reproduction above is deterministic and needs no model calls.

    Could this issue be assigned to me (哈基米 (@BlueX888))? Happy to adjust the approach first if you'd prefer a different fix location.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pythonPull requests for the Python Semantic Kerneltriage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions