Compare commits

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

2 commits

Author SHA1 Message Date
imagineer99
61c23e5e7a test: deduplicate empty content validation coverage 2026-04-28 18:45:15 +01:00
imagineer99
d6c39467dd fix: allow image-only chat messages 2026-04-28 18:36:18 +01:00
2 changed files with 20 additions and 8 deletions

View file

@ -396,7 +396,10 @@ class ChatMessage(BaseModel):
if self.name is not None and self.role != "tool": if self.name is not None and self.role != "tool":
raise ValueError('"name" is only valid on role="tool" messages.') raise ValueError('"name" is only valid on role="tool" messages.')
# Per-role content requirements. # Per-role content requirements. OpenAI-compatible clients may send
# ``content=""`` for image-only turns when the image travels in a
# companion field such as Studio's ``image_base64`` extension, so treat
# empty strings as present content for user/system messages.
if self.role == "tool": if self.role == "tool":
if not self.tool_call_id: if not self.tool_call_id:
raise ValueError( raise ValueError(
@ -411,9 +414,9 @@ class ChatMessage(BaseModel):
'role="assistant" messages require either "content" or "tool_calls".' 'role="assistant" messages require either "content" or "tool_calls".'
) )
else: # "user" | "system" else: # "user" | "system"
if not self.content: if self.content is None or self.content == []:
raise ValueError( raise ValueError(
f'role="{self.role}" messages require non-empty "content".' f'role="{self.role}" messages require "content".'
) )
return self return self

View file

@ -144,13 +144,14 @@ class TestChatMessageToolRoles:
# ── Role-aware content requirements ──────────────────────────── # ── Role-aware content requirements ────────────────────────────
def test_user_empty_content_rejected(self): @pytest.mark.parametrize("role", ["user", "system"])
with pytest.raises(ValidationError): def test_empty_string_content_allowed(self, role):
ChatMessage(role = "user", content = "") msg = ChatMessage(role = role, content = "")
assert msg.content == ""
def test_system_empty_content_rejected(self): def test_user_missing_content_rejected(self):
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
ChatMessage(role = "system", content = "") ChatMessage(role = "user")
def test_user_empty_list_content_rejected(self): def test_user_empty_list_content_rejected(self):
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
@ -226,6 +227,14 @@ class TestChatCompletionRequestToolFields:
assert len(req.tools) == 1 assert len(req.tools) == 1
assert req.tools[0]["function"]["name"] == "get_weather" assert req.tools[0]["function"]["name"] == "get_weather"
def test_image_base64_allows_empty_user_text(self):
req = ChatCompletionRequest(
messages = [{"role": "user", "content": ""}],
image_base64 = "aW1hZ2U=",
)
assert req.messages[0].content == ""
assert req.image_base64 == "aW1hZ2U="
def test_tool_choice_string_auto(self): def test_tool_choice_string_auto(self):
assert self._make(tool_choice = "auto").tool_choice == "auto" assert self._make(tool_choice = "auto").tool_choice == "auto"