Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Datta Nimmaturi
09f470b45b test: add regression tests for empty tool output fix (issue #6047) 2026-06-08 04:30:39 +00:00
Tai An
85840e7e4d fix(studio/responses): emit placeholder for empty tool output
_normalise_responses_input builds a role="tool" ChatMessage straight
from item.output. When a function_call_output carries an empty/falsy
output (e.g. an image-only result whose payload lives outside the
output field), content becomes "" and ChatMessage._validate_role_shape
raises the non-empty-content error, surfacing as a 500 on /v1/responses.
Substitute a placeholder so the turn normalises gracefully.

Fixes #6047
2026-06-06 12:09:34 -07:00
2 changed files with 45 additions and 0 deletions

View file

@ -2797,6 +2797,13 @@ def _normalise_responses_input(payload: ResponsesRequest) -> list[ChatMessage]:
output = item.output
if not isinstance(output, str):
output = json.dumps(output)
if not output:
# An empty/falsy result (e.g. an image-only tool output whose
# payload lives outside `output`) would otherwise build a
# role="tool" ChatMessage with content="", which the strict
# validator rejects with a 500. Emit a placeholder so the turn
# normalises gracefully.
output = "(no output)"
messages.append(
ChatMessage(
role = "tool",

View file

@ -374,6 +374,44 @@ class TestNormaliseResponsesInputWithTools:
# Content is serialised so llama-server sees a string.
assert json.loads(msgs[0].content) == [{"type": "output_text", "text": "ok"}]
def test_empty_tool_output_replaced_with_placeholder(self):
"""Image-only tool results (Anthropic format) send empty output.
Before the fix this crashed the validator with
"role=\"tool\" messages require non-empty \"content\"".
Now it emits "(no output)" so the turn normalises cleanly."""
payload = ResponsesRequest(
input = [
{
"type": "function_call_output",
"call_id": "call_1",
"output": "",
}
],
)
msgs = _normalise_responses_input(payload)
assert msgs[0].role == "tool"
assert msgs[0].tool_call_id == "call_1"
assert msgs[0].content == "(no output)"
# Round-trip through ChatMessage validator must not raise.
ChatMessage(**msgs[0].model_dump(exclude_none = True))
def test_empty_list_output_serialised_to_json_array(self):
"""Empty list output is not falsy after json.dumps (becomes "[]"),
so it should not trigger the placeholder."""
payload = ResponsesRequest(
input = [
{
"type": "function_call_output",
"call_id": "call_1",
"output": [],
}
],
)
msgs = _normalise_responses_input(payload)
assert msgs[0].role == "tool"
assert msgs[0].content == "[]"
ChatMessage(**msgs[0].model_dump(exclude_none = True))
# =====================================================================
# Response mapping — tool_calls → function_call output items