@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / dfa8852db2
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 6, 15:45 UTC

[Refactor] Consolidate GPT-OSS reasoning parser tests (#36915)

Signed-off-by: sfeng33 <4florafeng@gmail.com> Signed-off-by: Flora Feng <4florafeng@gmail.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

Flora Feng committed Mar 16, 2026 at 15:53 UTC dfa8852db20a75374e5451789fbee1c535f62315
4 files changed +145 -459
tests/entrypoints/openai/test_gptoss_structural_tags_integration.py deleted
-279
@@ -1,279 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -"""Integration tests for GPT-OSS structural tags functionality (PR #25515)."""
5 -
6 -import json
7 -from unittest.mock import Mock
8 -
9 -import pytest
10 -
11 -from vllm.entrypoints.mcp.tool_server import ToolServer
12 -from vllm.reasoning.gptoss_reasoning_parser import (
13 - GptOssReasoningParser,
14 -)
15 -from vllm.sampling_params import StructuredOutputsParams
16 -
17 -
18 -class TestGptOssStructuralTagsIntegration:
19 - """Integration tests for structural tags in GPT-OSS tool calls."""
20 -
21 - @pytest.fixture
22 - def mock_tokenizer(self):
23 - """Create a mock tokenizer."""
24 - tokenizer = Mock()
25 - tokenizer.encode = Mock(return_value=[1, 2, 3, 4, 5])
26 - tokenizer.get_vocab = Mock(return_value={"<|end|>": 6})
27 - return tokenizer
28 -
29 - @pytest.fixture
30 - def gptoss_parser(self, mock_tokenizer):
31 - """Create a real GptOssReasoningParser instance."""
32 - return GptOssReasoningParser(mock_tokenizer)
33 -
34 - @pytest.fixture
35 - def tool_server_with_python(self):
36 - """Create a tool server with Python tool enabled."""
37 - tool_server = Mock(spec=ToolServer)
38 - tool_server.has_tool = Mock(side_effect=lambda tool: tool == "python")
39 - return tool_server
40 -
41 - @pytest.fixture
42 - def tool_server_empty(self):
43 - """Create a tool server with no tools."""
44 - tool_server = Mock(spec=ToolServer)
45 - tool_server.has_tool = Mock(return_value=False)
46 - return tool_server
47 -
48 - def test_end_to_end_no_tools(self, gptoss_parser):
49 - """Test end-to-end flow when no tools are available."""
50 - # Test the parser directly
51 - result = gptoss_parser.prepare_structured_tag(None, None)
52 - parsed_result = json.loads(result)
53 -
54 - # Verify basic structure
55 - assert parsed_result["type"] == "structural_tag"
56 - assert parsed_result["format"]["type"] == "triggered_tags"
57 - assert len(parsed_result["format"]["tags"]) == 1
58 -
59 - # Verify only analysis channel is allowed
60 - analysis_tag = parsed_result["format"]["tags"][0]
61 - assert analysis_tag["begin"] == "<|channel|>analysis<|message|>"
62 - assert analysis_tag["content"]["type"] == "any_text"
63 - assert analysis_tag["end"] == "<|end|>"
64 -
65 - # Verify triggers
66 - assert parsed_result["format"]["triggers"] == ["<|channel|>analysis"]
67 - assert parsed_result["format"]["stop_after_first"] is False
68 -
69 - def test_end_to_end_with_python_tool(self, gptoss_parser, tool_server_with_python):
70 - """Test end-to-end flow with Python tool enabled."""
71 - result = gptoss_parser.prepare_structured_tag(None, tool_server_with_python)
72 - parsed_result = json.loads(result)
73 -
74 - # Should have analysis tag + 2 python tags
75 - assert len(parsed_result["format"]["tags"]) == 3
76 -
77 - # Verify all expected tags are present
78 - tag_begins = [tag["begin"] for tag in parsed_result["format"]["tags"]]
79 - expected_begins = [
80 - "<|channel|>analysis<|message|>",
81 - "<|channel|>commentary to=python",
82 - "<|channel|>analysis to=python",
83 - ]
84 -
85 - for expected in expected_begins:
86 - assert expected in tag_begins
87 -
88 - # Verify triggers include commentary
89 - assert "<|channel|>analysis" in parsed_result["format"]["triggers"]
90 - assert "<|channel|>commentary to=" in parsed_result["format"]["triggers"]
91 -
92 - def test_structured_outputs_params_integration(
93 - self, gptoss_parser, tool_server_with_python
94 - ):
95 - """Test integration with StructuredOutputsParams."""
96 - # Generate structural tag
97 - structural_tag = gptoss_parser.prepare_structured_tag(
98 - None, tool_server_with_python
99 - )
100 -
101 - # Create StructuredOutputsParams
102 - params = StructuredOutputsParams(structural_tag=structural_tag)
103 -
104 - # Verify the tag is properly stored and accessible
105 - assert params.structural_tag == structural_tag
106 -
107 - # Verify the tag is valid JSON
108 - parsed_tag = json.loads(params.structural_tag)
109 - assert parsed_tag["type"] == "structural_tag"
110 -
111 - @pytest.mark.parametrize(
112 - "browser, python, container, expected_tags",
113 - [
114 - # No tools
115 - (False, False, False, 1),
116 - # Single tool
117 - (True, False, False, 3),
118 - # Multiple tools
119 - (True, True, False, 5),
120 - # All tools
121 - (True, True, True, 7),
122 - ],
123 - )
124 - def test_tool_server_interaction_flow(
125 - self, gptoss_parser, browser, python, container, expected_tags
126 - ):
127 - """Test the complete tool server interaction flow."""
128 -
129 - # Create a mock ToolServer
130 - tool_server = Mock(spec=ToolServer)
131 -
132 - # Simulate tool availability based on parameters
133 - tool_server.has_tool = Mock(
134 - side_effect=lambda tool: {
135 - "browser": browser,
136 - "python": python,
137 - "container": container,
138 - }.get(tool, False)
139 - )
140 -
141 - # Run the parser and verify results
142 - result = gptoss_parser.prepare_structured_tag(None, tool_server)
143 - parsed_result = json.loads(result)
144 -
145 - # Validate number of tags
146 - assert len(parsed_result["format"]["tags"]) == expected_tags
147 -
148 - # Verify tool-specific tags exist for enabled tools
149 - tag_begins = [tag["begin"] for tag in parsed_result["format"]["tags"]]
150 - for tool, enabled in {
151 - "browser": browser,
152 - "python": python,
153 - "container": container,
154 - }.items():
155 - if enabled:
156 - assert f"<|channel|>commentary to={tool}" in tag_begins
157 - assert f"<|channel|>analysis to={tool}" in tag_begins
158 -
159 - def test_original_tag_preservation(self, gptoss_parser, tool_server_with_python):
160 - """Test that original tags are preserved when provided."""
161 - original_tag = '{"type": "custom_tag", "data": "preserved"}'
162 -
163 - result = gptoss_parser.prepare_structured_tag(
164 - original_tag, tool_server_with_python
165 - )
166 -
167 - # Should return original tag unchanged
168 - assert result == original_tag
169 -
170 - @pytest.mark.parametrize(
171 - "tools",
172 - [
173 - [],
174 - ["browser"],
175 - ["python"],
176 - ["container"],
177 - ["browser", "python"],
178 - ["browser", "container"],
179 - ["python", "container"],
180 - ["browser", "python", "container"],
181 - ],
182 - )
183 - def test_json_validity_comprehensive(self, gptoss_parser, tools):
184 - """Test JSON validity across all possible tool combinations."""
185 -
186 - tool_server = Mock(spec=ToolServer)
187 - tool_server.has_tool = Mock(side_effect=lambda tool: tool in tools)
188 -
189 - result = gptoss_parser.prepare_structured_tag(None, tool_server)
190 -
191 - # Should be valid JSON
192 - parsed_result = json.loads(result)
193 -
194 - # Should have correct structure
195 - assert parsed_result["type"] == "structural_tag"
196 - assert "format" in parsed_result
197 - assert "tags" in parsed_result["format"]
198 - assert "triggers" in parsed_result["format"]
199 -
200 - # Tag count should be: 1 (analysis) + 2 * len(tools)
201 - expected_tag_count = 1 + (2 * len(tools))
202 - assert len(parsed_result["format"]["tags"]) == expected_tag_count
203 -
204 - def test_error_handling_invalid_tool_server(self, gptoss_parser):
205 - """Test error handling with invalid tool server."""
206 - # Tool server that raises exceptions
207 - tool_server = Mock(spec=ToolServer)
208 - tool_server.has_tool = Mock(side_effect=Exception("Tool server error"))
209 -
210 - # Should handle gracefully and still return a valid tag
211 - with pytest.raises(Exception, match="Tool server error"):
212 - gptoss_parser.prepare_structured_tag(None, tool_server)
213 -
214 - def test_concurrent_requests_isolation(self, gptoss_parser):
215 - """Test that concurrent requests don't interfere with each other."""
216 - # Simulate concurrent requests with different tool servers
217 - tool_server_1 = Mock(spec=ToolServer)
218 - tool_server_1.has_tool = Mock(side_effect=lambda tool: tool == "python")
219 -
220 - tool_server_2 = Mock(spec=ToolServer)
221 - tool_server_2.has_tool = Mock(side_effect=lambda tool: tool == "browser")
222 -
223 - # Generate tags concurrently
224 - result_1 = gptoss_parser.prepare_structured_tag(None, tool_server_1)
225 - result_2 = gptoss_parser.prepare_structured_tag(None, tool_server_2)
226 -
227 - # Parse results
228 - parsed_1 = json.loads(result_1)
229 - parsed_2 = json.loads(result_2)
230 -
231 - # Verify they have different tool configurations
232 - tags_1 = [tag["begin"] for tag in parsed_1["format"]["tags"]]
233 - tags_2 = [tag["begin"] for tag in parsed_2["format"]["tags"]]
234 -
235 - # Result 1 should have python tags
236 - assert "<|channel|>commentary to=python" in tags_1
237 - assert "<|channel|>commentary to=browser" not in tags_1
238 -
239 - # Result 2 should have browser tags
240 - assert "<|channel|>commentary to=browser" in tags_2
241 - assert "<|channel|>commentary to=python" not in tags_2
242 -
243 - def test_tag_format_consistency(self, gptoss_parser):
244 - """Test that all generated tags follow consistent format."""
245 - tool_server = Mock(spec=ToolServer)
246 - tool_server.has_tool = Mock(
247 - side_effect=lambda tool: tool in ["python", "browser"]
248 - )
249 -
250 - result = gptoss_parser.prepare_structured_tag(None, tool_server)
251 - parsed_result = json.loads(result)
252 -
253 - # Verify all tags have required fields
254 - for tag in parsed_result["format"]["tags"]:
255 - assert "begin" in tag
256 - assert "content" in tag
257 - assert "end" in tag
258 - assert tag["content"]["type"] == "any_text"
259 - assert tag["end"] == "<|end|>"
260 -
261 - # Verify begin format
262 - assert tag["begin"].startswith("<|channel|>")
263 -
264 - def test_trigger_configuration(self, gptoss_parser):
265 - """Test trigger configuration for different tool setups."""
266 - # Test with no tools
267 - result_no_tools = gptoss_parser.prepare_structured_tag(None, None)
268 - parsed_no_tools = json.loads(result_no_tools)
269 - assert parsed_no_tools["format"]["triggers"] == ["<|channel|>analysis"]
270 -
271 - # Test with tools
272 - tool_server = Mock(spec=ToolServer)
273 - tool_server.has_tool = Mock(side_effect=lambda tool: tool == "python")
274 -
275 - result_with_tools = gptoss_parser.prepare_structured_tag(None, tool_server)
276 - parsed_with_tools = json.loads(result_with_tools)
277 -
278 - expected_triggers = ["<|channel|>analysis", "<|channel|>commentary to="]
279 - assert set(parsed_with_tools["format"]["triggers"]) == set(expected_triggers)
tests/reasoning/test_gptoss_reasoning_parser.py
+139 -1
@@ -1,11 +1,19 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 +import json
5 +from unittest.mock import Mock
6 +
7 import pytest
8 from transformers import AutoTokenizer
9
10 +from vllm.entrypoints.mcp.tool_server import ToolServer
11 from vllm.reasoning import ReasoningParser
8 -from vllm.reasoning.gptoss_reasoning_parser import GptOssReasoningParser
12 +from vllm.reasoning.gptoss_reasoning_parser import (
13 + GptOssReasoningParser,
14 + from_builtin_tool_to_tag,
15 + no_func_reasoning_tag,
16 +)
17
18 REASONING_MODEL_NAME = "openai/gpt-oss-120b"
19
@@ -142,3 +150,133 @@ def test_gptoss_is_reasoning_end(
150 output_ids = gpt_oss_tokenizer.convert_tokens_to_ids(output)
151 actual_is_reasoning_end = parser.is_reasoning_end(output_ids)
152 assert is_reasoning_end == actual_is_reasoning_end
153 +
154 +
155 +class TestGptOssStructuralTags:
156 + """Test cases for GptOssReasoningParser structural tag functionality."""
157 +
158 + @pytest.fixture
159 + def mock_tokenizer(self):
160 + """Create a mock tokenizer for testing."""
161 + tokenizer = Mock()
162 + tokenizer.encode = Mock(return_value=[1, 2, 3, 4, 5])
163 + tokenizer.get_vocab = Mock(return_value={"<|end|>": 6})
164 + return tokenizer
165 +
166 + @pytest.fixture
167 + def reasoning_parser(self, mock_tokenizer):
168 + """Create a GptOssReasoningParser instance."""
169 + return GptOssReasoningParser(mock_tokenizer)
170 +
171 + def test_prepare_structured_tag_no_tool_server(self, reasoning_parser):
172 + """Test prepare_structured_tag with no tool server."""
173 + result = reasoning_parser.prepare_structured_tag(None, None)
174 + expected = json.dumps(no_func_reasoning_tag)
175 +
176 + assert result == expected
177 +
178 + # Verify the structure is correct
179 + parsed = json.loads(result)
180 + assert parsed["type"] == "structural_tag"
181 + assert parsed["format"]["type"] == "triggered_tags"
182 + assert len(parsed["format"]["tags"]) == 1
183 + assert parsed["format"]["tags"][0]["begin"] == "<|channel|>analysis<|message|>"
184 + assert parsed["format"]["triggers"] == ["<|channel|>analysis"]
185 +
186 + def test_prepare_structured_tag_with_original_tag(self, reasoning_parser):
187 + """Test prepare_structured_tag when original_tag is provided."""
188 + original_tag = '{"custom": "tag"}'
189 + result = reasoning_parser.prepare_structured_tag(original_tag, None)
190 +
191 + # Should return the original tag unchanged
192 + assert result == original_tag
193 +
194 + def test_from_builtin_tool_to_tag(self):
195 + """Test from_builtin_tool_to_tag function."""
196 + tags = from_builtin_tool_to_tag("python")
197 +
198 + assert len(tags) == 2
199 + assert tags[0]["begin"] == "<|channel|>commentary to=python"
200 + assert tags[0]["content"]["type"] == "any_text"
201 + assert tags[0]["end"] == "<|end|>"
202 +
203 + assert tags[1]["begin"] == "<|channel|>analysis to=python"
204 + assert tags[1]["content"]["type"] == "any_text"
205 + assert tags[1]["end"] == "<|end|>"
206 +
207 + @pytest.mark.parametrize(
208 + "tools",
209 + [
210 + [],
211 + ["browser"],
212 + ["python"],
213 + ["container"],
214 + ["browser", "python"],
215 + ["browser", "container"],
216 + ["python", "container"],
217 + ["browser", "python", "container"],
218 + ],
219 + )
220 + def test_json_validity_comprehensive(self, reasoning_parser, tools):
221 + """Test JSON validity across all possible tool combinations."""
222 + tool_server = Mock(spec=ToolServer)
223 + tool_server.has_tool = Mock(side_effect=lambda tool: tool in tools)
224 +
225 + result = reasoning_parser.prepare_structured_tag(None, tool_server)
226 + parsed_result = json.loads(result)
227 +
228 + assert parsed_result["type"] == "structural_tag"
229 + assert "format" in parsed_result
230 + assert "tags" in parsed_result["format"]
231 + assert "triggers" in parsed_result["format"]
232 +
233 + # Tag count should be: 1 (analysis) + 2 * len(tools)
234 + expected_tag_count = 1 + (2 * len(tools))
235 + assert len(parsed_result["format"]["tags"]) == expected_tag_count
236 +
237 + # Verify triggers are correctly configured
238 + expected_triggers = ["<|channel|>analysis"]
239 + if tools:
240 + expected_triggers.append("<|channel|>commentary to=")
241 + assert set(parsed_result["format"]["triggers"]) == set(expected_triggers)
242 +
243 + def test_no_cross_request_state_pollution(self, reasoning_parser):
244 + """Test that sequential calls with different tool servers produce
245 + independent results, guarding against shared mutable state
246 + (e.g. missing deepcopy in tag_with_builtin_funcs)."""
247 + tool_server_1 = Mock(spec=ToolServer)
248 + tool_server_1.has_tool = Mock(side_effect=lambda tool: tool == "python")
249 +
250 + tool_server_2 = Mock(spec=ToolServer)
251 + tool_server_2.has_tool = Mock(side_effect=lambda tool: tool == "browser")
252 +
253 + result_1 = reasoning_parser.prepare_structured_tag(None, tool_server_1)
254 + result_2 = reasoning_parser.prepare_structured_tag(None, tool_server_2)
255 +
256 + tags_1 = [tag["begin"] for tag in json.loads(result_1)["format"]["tags"]]
257 + tags_2 = [tag["begin"] for tag in json.loads(result_2)["format"]["tags"]]
258 +
259 + assert "<|channel|>commentary to=python" in tags_1
260 + assert "<|channel|>commentary to=browser" not in tags_1
261 +
262 + assert "<|channel|>commentary to=browser" in tags_2
263 + assert "<|channel|>commentary to=python" not in tags_2
264 +
265 + def test_tag_format_consistency(self, reasoning_parser):
266 + """Test that all generated tags follow consistent format,
267 + catching malformed tags from from_builtin_tool_to_tag."""
268 + tool_server = Mock(spec=ToolServer)
269 + tool_server.has_tool = Mock(
270 + side_effect=lambda tool: tool in ["python", "browser"]
271 + )
272 +
273 + result = reasoning_parser.prepare_structured_tag(None, tool_server)
274 + parsed_result = json.loads(result)
275 +
276 + for tag in parsed_result["format"]["tags"]:
277 + assert "begin" in tag
278 + assert "content" in tag
279 + assert "end" in tag
280 + assert tag["content"]["type"] == "any_text"
281 + assert tag["end"] == "<|end|>"
282 + assert tag["begin"].startswith("<|channel|>")
tests/v1/structured_output/test_gptoss_structural_tags.py deleted
-173
@@ -1,173 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -"""Unit tests for GPT-OSS structural tag support in reasoning (PR #25515)."""
5 -
6 -import json
7 -from unittest.mock import Mock
8 -
9 -import pytest
10 -
11 -from vllm.entrypoints.mcp.tool_server import ToolServer
12 -from vllm.reasoning.gptoss_reasoning_parser import (
13 - GptOssReasoningParser,
14 - from_builtin_tool_to_tag,
15 - no_func_reaonsing_tag,
16 - tag_with_builtin_funcs,
17 -)
18 -
19 -
20 -class TestGptOssReasoningParser:
21 - """Test cases for GptOssReasoningParser structural tag functionality."""
22 -
23 - @pytest.fixture
24 - def mock_tokenizer(self):
25 - """Create a mock tokenizer for testing."""
26 - tokenizer = Mock()
27 - tokenizer.encode = Mock(return_value=[1, 2, 3, 4, 5])
28 - tokenizer.get_vocab = Mock(return_value={"<|end|>": 6})
29 - return tokenizer
30 -
31 - @pytest.fixture
32 - def reasoning_parser(self, mock_tokenizer):
33 - """Create a GptOssReasoningParser instance."""
34 - return GptOssReasoningParser(mock_tokenizer)
35 -
36 - @pytest.fixture
37 - def mock_tool_server_empty(self):
38 - """Create a mock ToolServer with no tools."""
39 - tool_server = Mock(spec=ToolServer)
40 - tool_server.has_tool = Mock(return_value=False)
41 - return tool_server
42 -
43 - @pytest.fixture
44 - def mock_tool_server_with_browser(self):
45 - """Create a mock ToolServer with browser tool."""
46 - tool_server = Mock(spec=ToolServer)
47 - tool_server.has_tool = Mock(side_effect=lambda tool: tool == "browser")
48 - return tool_server
49 -
50 - @pytest.fixture
51 - def mock_tool_server_with_all_tools(self):
52 - """Create a mock ToolServer with all builtin tools."""
53 - tool_server = Mock(spec=ToolServer)
54 - tool_server.has_tool = Mock(
55 - side_effect=lambda tool: tool in ["browser", "python", "container"]
56 - )
57 - return tool_server
58 -
59 - def test_prepare_structured_tag_no_tool_server(self, reasoning_parser):
60 - """Test prepare_structured_tag with no tool server."""
61 - result = reasoning_parser.prepare_structured_tag(None, None)
62 - expected = json.dumps(no_func_reaonsing_tag)
63 -
64 - assert result == expected
65 -
66 - # Verify the structure is correct
67 - parsed = json.loads(result)
68 - assert parsed["type"] == "structural_tag"
69 - assert parsed["format"]["type"] == "triggered_tags"
70 - assert len(parsed["format"]["tags"]) == 1
71 - assert parsed["format"]["tags"][0]["begin"] == "<|channel|>analysis<|message|>"
72 - assert parsed["format"]["triggers"] == ["<|channel|>analysis"]
73 -
74 - def test_prepare_structured_tag_with_all_tools(
75 - self, reasoning_parser, mock_tool_server_with_all_tools
76 - ):
77 - """Test prepare_structured_tag with all builtin tools."""
78 - result = reasoning_parser.prepare_structured_tag(
79 - None, mock_tool_server_with_all_tools
80 - )
81 - parsed = json.loads(result)
82 -
83 - # Should have analysis tag + tags for all 3 tools (2 tags each)
84 - assert len(parsed["format"]["tags"]) == 7 # 1 analysis + 6 tool tags
85 -
86 - # Check all tool tags are present
87 - tag_begins = [tag["begin"] for tag in parsed["format"]["tags"]]
88 - for tool in ["browser", "python", "container"]:
89 - assert f"<|channel|>commentary to={tool}" in tag_begins
90 - assert f"<|channel|>analysis to={tool}" in tag_begins
91 -
92 - def test_prepare_structured_tag_with_original_tag(self, reasoning_parser):
93 - """Test prepare_structured_tag when original_tag is provided."""
94 - original_tag = '{"custom": "tag"}'
95 - result = reasoning_parser.prepare_structured_tag(original_tag, None)
96 -
97 - # Should return the original tag unchanged
98 - assert result == original_tag
99 -
100 - def test_from_builtin_tool_to_tag(self):
101 - """Test from_builtin_tool_to_tag function."""
102 - tags = from_builtin_tool_to_tag("python")
103 -
104 - assert len(tags) == 2
105 - assert tags[0]["begin"] == "<|channel|>commentary to=python"
106 - assert tags[0]["content"]["type"] == "any_text"
107 - assert tags[0]["end"] == "<|end|>"
108 -
109 - assert tags[1]["begin"] == "<|channel|>analysis to=python"
110 - assert tags[1]["content"]["type"] == "any_text"
111 - assert tags[1]["end"] == "<|end|>"
112 -
113 - def test_tag_with_builtin_funcs(self):
114 - """Test tag_with_builtin_funcs function."""
115 - builtin_tools = ["browser", "python"]
116 - result = tag_with_builtin_funcs(no_func_reaonsing_tag, builtin_tools)
117 -
118 - assert result["type"] == "structural_tag"
119 - # Should have original analysis tag + 2 tags per tool
120 - assert len(result["format"]["tags"]) == 5 # 1 + 2*2
121 -
122 - # Should have added commentary trigger
123 - assert "<|channel|>commentary to=" in result["format"]["triggers"]
124 - assert "<|channel|>analysis" in result["format"]["triggers"]
125 -
126 - def test_tag_structure_invariants(self):
127 - """Test that the basic tag structure follows expected format."""
128 - # Test the base no_func_reaonsing_tag structure
129 - assert no_func_reaonsing_tag["type"] == "structural_tag"
130 - assert no_func_reaonsing_tag["format"]["type"] == "triggered_tags"
131 - assert no_func_reaonsing_tag["format"]["stop_after_first"] is False
132 -
133 - # Verify analysis tag structure
134 - analysis_tag = no_func_reaonsing_tag["format"]["tags"][0]
135 - assert analysis_tag["begin"] == "<|channel|>analysis<|message|>"
136 - assert analysis_tag["content"]["type"] == "any_text"
137 - assert analysis_tag["end"] == "<|end|>"
138 -
139 - def test_json_serialization_valid(
140 - self, reasoning_parser, mock_tool_server_with_all_tools
141 - ):
142 - """Test that all generated tags produce valid JSON."""
143 - # Test with no tool server
144 - result1 = reasoning_parser.prepare_structured_tag(None, None)
145 - json.loads(result1) # Should not raise
146 -
147 - # Test with empty tool server
148 - empty_server = Mock(spec=ToolServer)
149 - empty_server.has_tool = Mock(return_value=False)
150 - result2 = reasoning_parser.prepare_structured_tag(None, empty_server)
151 - json.loads(result2) # Should not raise
152 -
153 - # Test with tools
154 - result3 = reasoning_parser.prepare_structured_tag(
155 - None, mock_tool_server_with_all_tools
156 - )
157 - json.loads(result3) # Should not raise
158 -
159 - @pytest.mark.parametrize("tool_name", ["browser", "python", "container"])
160 - def test_single_tool_integration(self, reasoning_parser, tool_name):
161 - """Test integration with individual tools."""
162 - tool_server = Mock(spec=ToolServer)
163 - tool_server.has_tool = Mock(side_effect=lambda tool: tool == tool_name)
164 -
165 - result = reasoning_parser.prepare_structured_tag(None, tool_server)
166 - parsed = json.loads(result)
167 -
168 - # Should have 1 analysis + 2 tool-specific tags
169 - assert len(parsed["format"]["tags"]) == 3
170 -
171 - tag_begins = [tag["begin"] for tag in parsed["format"]["tags"]]
172 - assert f"<|channel|>commentary to={tool_name}" in tag_begins
173 - assert f"<|channel|>analysis to={tool_name}" in tag_begins
vllm/reasoning/gptoss_reasoning_parser.py
+6 -6
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
18
19 logger = init_logger(__name__)
20
21 -no_func_reaonsing_tag = {
21 +no_func_reasoning_tag = {
22 "type": "structural_tag",
23 "format": {
24 "type": "triggered_tags",
@@ -51,10 +51,10 @@ def from_builtin_tool_to_tag(tool: str) -> list[dict]:
51 return tag
52
53
54 -def tag_with_builtin_funcs(no_func_reaonsing_tag, builtin_tool_list: list[str]) -> dict:
54 +def tag_with_builtin_funcs(no_func_reasoning_tag, builtin_tool_list: list[str]) -> dict:
55 import copy
56
57 - new_tag = copy.deepcopy(no_func_reaonsing_tag)
57 + new_tag = copy.deepcopy(no_func_reasoning_tag)
58 new_tag["format"]["triggers"].append("<|channel|>commentary to=")
59
60 for tool in builtin_tool_list:
@@ -162,7 +162,7 @@ class GptOssReasoningParser(ReasoningParser):
162 ) -> str | None:
163 if original_tag is None:
164 if tool_server is None:
165 - return json.dumps(no_func_reaonsing_tag)
165 + return json.dumps(no_func_reasoning_tag)
166 else:
167 builtin_tool_list: list[str] = []
168 if tool_server.has_tool("browser"):
@@ -175,11 +175,11 @@ class GptOssReasoningParser(ReasoningParser):
175 if len(builtin_tool_list) > 0:
176 logger.info("Builtin_tool_list: %s", builtin_tool_list)
177 func_tag = json.dumps(
178 - tag_with_builtin_funcs(no_func_reaonsing_tag, builtin_tool_list)
178 + tag_with_builtin_funcs(no_func_reasoning_tag, builtin_tool_list)
179 )
180 else:
181 logger.info("Builtin_tool_list is empty")
182 - func_tag = json.dumps(no_func_reaonsing_tag)
182 + func_tag = json.dumps(no_func_reasoning_tag)
183
184 return func_tag
185 else: