Skip to content

Python: keep empty tool results through the prompt round trip - #14686

Open
Himansh Raj (iamthehimansh) wants to merge 1 commit into
microsoft:mainfrom
iamthehimansh:fix/empty-tool-result-prompt-roundtrip
Open

Himansh Raj (iamthehimansh) wants to merge 1 commit into
microsoft:mainfrom
iamthehimansh:fix/empty-tool-result-prompt-roundtrip

Conversation

@iamthehimansh

Copy link
Copy Markdown

Motivation and Context

Fixes #14685.

Since #14472, ChatHistory.add_tool_message("") keeps an empty tool result. But that result doesn't survive the XML prompt round trip that KernelFunctionFromPrompt performs on every chat completion call. to_element() renders it as a self-closing <function_result id="call_1" />. When from_element() parses that back, it reads element.text, which is None for an empty element. ChatMessageContent then stringifies the value, so the model receives the literal tool result "None" instead of an empty one.

Description

  • FunctionResultContent.from_element now maps a missing element text to "". Non-empty results are unchanged.
  • Added test_element_round_trip_keeps_result (empty and non-empty), which serialises the element to XML and parses it back.
  • Added test_chat_history_prompt_round_trip_keeps_empty_tool_result, which reproduces the issue end to end through to_prompt() / from_rendered_prompt().

Before (main 4bb64ee, macOS arm64, Python 3.12):

{'role': 'tool', 'content': 'None', 'tool_call_id': 'call_1'}

After:

{'role': 'tool', 'content': '', 'tool_call_id': 'call_1'}

Tests, run from python/:

  • pytest tests/unit/contents: 421 passed on main, 424 passed with this change.
  • With the source change reverted, the two empty-result tests fail: 2 failed, 25 passed in test_function_result_content.py.
  • ruff check / ruff format --check on both files are clean.

This doesn't overlap the open #14375 (the from_function_call_content_and_result factory) or #14480 (the add_tool_message path), and it doesn't touch the JSON serialize() / restore_chat_history() path, which already keeps "".

Contribution Checklist

AI-assisted: yes (Cana)

FunctionResultContent.to_element renders an empty result as a self-closing
element, and from_element read its text back as None, which ChatMessageContent
then sent to the model as the string "None". Map missing text to "".

Fixes microsoft#14685
Copilot AI balanced review requested due to automatic review settings October 29, 2026 19:27
@iamthehimansh
Himansh Raj (iamthehimansh) requested a review from a team as a code owner October 29, 2026 19:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@semantic-kernel-automation semantic-kernel-automation Bot added the python Pull requests for the Python Semantic Kernel label Oct 29, 2026

This branch was successfully deployed

1 active deployment
github-app-auth — 844833c4 Deployed Oct 29, 2026 by iamthehimansh via add_label #29493
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests for the Python Semantic Kernel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants