Skip to content

Commit 3880626

Browse files
committed
test(responses): responses API Response object unit tests
1 parent 708b2c1 commit 3880626

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import pytest
2+
from pydantic import TypeAdapter, ValidationError
3+
4+
from llama_stack.apis.agents.openai_responses import OpenAIResponsesToolChoice
5+
from llama_stack.apis.tools.openai_tool_choice import (
6+
ToolChoiceAllowed,
7+
ToolChoiceCustom,
8+
ToolChoiceFunction,
9+
ToolChoiceMcp,
10+
ToolChoiceOptions,
11+
ToolChoiceTypes,
12+
)
13+
14+
15+
def test_tool_choice_discriminated_options():
16+
adapter = TypeAdapter(OpenAIResponsesToolChoice)
17+
18+
cases = [
19+
({"type": "function", "name": "search"}, ToolChoiceFunction, "function"),
20+
({"type": "mcp", "server_label": "deepwiki"}, ToolChoiceMcp, "mcp"),
21+
({"type": "custom", "name": "my_tool"}, ToolChoiceCustom, "custom"),
22+
(
23+
{
24+
"type": "allowed_tools",
25+
"mode": "auto",
26+
"tools": [{"type": "function", "name": "foo"}],
27+
},
28+
ToolChoiceAllowed,
29+
"allowed_tools",
30+
),
31+
]
32+
33+
for payload, expected_cls, expected_type in cases:
34+
obj = adapter.validate_python(payload)
35+
assert isinstance(obj, expected_cls)
36+
assert getattr(obj, "type") == expected_type
37+
38+
dumped = obj.model_dump()
39+
reparsed = adapter.validate_python(dumped)
40+
assert isinstance(reparsed, expected_cls)
41+
assert reparsed.model_dump() == dumped
42+
43+
44+
def test_tool_choice_literal_options():
45+
adapter = TypeAdapter(OpenAIResponsesToolChoice)
46+
options_adapter = TypeAdapter(ToolChoiceOptions)
47+
48+
for v in ("none", "auto", "required"):
49+
# Validate via the specific literal adapter
50+
assert options_adapter.validate_python(v) == v
51+
# And via the top-level union adapter
52+
assert adapter.validate_python(v) == v
53+
54+
55+
def test_tool_choice_rejects_invalid_value():
56+
adapter = TypeAdapter(OpenAIResponsesToolChoice)
57+
58+
with pytest.raises(ValidationError):
59+
adapter.validate_python("invalid")
60+
with pytest.raises(ValidationError):
61+
adapter.validate_python({"type": "unknown_variant"})
62+
63+
64+
def test_tool_choice_types_accepts_each_variant_value():
65+
adapter = TypeAdapter(OpenAIResponsesToolChoice)
66+
67+
allowed_values = [
68+
"file_search",
69+
"web_search_preview",
70+
"computer_use_preview",
71+
"web_search_preview_2025_03_11",
72+
"image_generation",
73+
"code_interpreter",
74+
]
75+
76+
for v in allowed_values:
77+
obj = adapter.validate_python({"type": v})
78+
assert isinstance(obj, ToolChoiceTypes)
79+
assert obj.type == v
80+
assert obj.model_dump() == {"type": v}
81+
82+
83+
def test_tool_choice_rejects_invalid_discriminator_value():
84+
adapter = TypeAdapter(OpenAIResponsesToolChoice)
85+
with pytest.raises(ValidationError):
86+
adapter.validate_python({"type": "unknown_variant"})
87+
88+
89+
def test_tool_choice_rejects_missing_required_fields():
90+
adapter = TypeAdapter(OpenAIResponsesToolChoice)
91+
# Missing "name" for function
92+
with pytest.raises(ValidationError):
93+
adapter.validate_python({"type": "function"})
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pytest
2+
from typing import Literal
3+
4+
from llama_stack.strong_typing.schema import JsonSchemaGenerator
5+
6+
7+
def test_single_literal_generates_const_schema():
8+
gen = JsonSchemaGenerator()
9+
schema = gen.type_to_schema(Literal["hello"]) # type: ignore[valid-type]
10+
11+
assert schema["const"] == "hello"
12+
assert schema["type"] == "string"
13+
14+
15+
def test_multi_literal_generates_enum_schema():
16+
gen = JsonSchemaGenerator()
17+
schema = gen.type_to_schema(Literal["a", "b", "c"]) # type: ignore[valid-type]
18+
19+
assert schema["enum"] == ["a", "b", "c"]
20+
assert schema["type"] == "string"
21+
22+
23+
def test_mixed_type_literal_raises():
24+
gen = JsonSchemaGenerator()
25+
with pytest.raises((ValueError, TypeError)):
26+
_ = gen.type_to_schema(Literal["x", 1]) # type: ignore[valid-type]

tests/unit/providers/agents/meta_reference/test_openai_responses.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,3 +868,20 @@ async def test_create_openai_response_with_invalid_text_format(openai_responses_
868868
model=model,
869869
text=OpenAIResponseText(format={"type": "invalid"}),
870870
)
871+
872+
873+
def test_openai_response_text_default_format_unique_instance():
874+
a = OpenAIResponseText()
875+
b = OpenAIResponseText()
876+
877+
assert a.format is not None
878+
assert b.format is not None
879+
880+
# Defaults to text format
881+
assert a.format.get("type") == "text"
882+
assert b.format.get("type") == "text"
883+
884+
# Unique instances (no shared mutable default)
885+
assert a.format is not b.format
886+
a.format["name"] = "custom-name"
887+
assert "name" not in b.format

0 commit comments

Comments
 (0)