Skip to content

Commit 66806c4

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

File tree

3 files changed

+149
-0
lines changed

3 files changed

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