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: