Download sglang/test/registered/function_call/test_parallel_tool_calls.py from FasterDFlash/Hanrui: direct link, hf CLI and curl.
- Browser
- Download file 5.86 kB
-
https://huggingface.co/FasterDFlash/Hanrui/resolve/main/sglang/test/registered/function_call/test_parallel_tool_calls.py
- Command line
-
hf download hf://FasterDFlash/Hanrui/sglang/test/registered/function_call/test_parallel_tool_calls.py
-
curl -L -o test_parallel_tool_calls.py https://huggingface.co/FasterDFlash/Hanrui/resolve/main/sglang/test/registered/function_call/test_parallel_tool_calls.py
5.86 kB
| import json | |
| """ | |
| Test case for parallel tool call parsing. | |
| This test verifies that the parser correctly handles parallel tool calls | |
| with array parameters in JSON array format. | |
| Scenario: | |
| - Model outputs two parallel tool calls in JSON array format | |
| - Both tools have array parameters (e.g., "title": ["7.8.9 H-9 ..."]) | |
| - First tool completes with closing braces | |
| - Second tool starts with opening brace | |
| - The parser must correctly handle the '[' characters in array parameters | |
| without confusing them with the JSON array start | |
| Expected behavior: Both tools should be parsed correctly. | |
| """ | |
| import unittest | |
| from sglang.srt.entrypoints.openai.protocol import Function, Tool | |
| from sglang.srt.function_call.json_array_parser import JsonArrayParser | |
| from sglang.test.ci.ci_register import register_cpu_ci | |
| register_cpu_ci(1.0, "default") | |
| class TestParallelToolCalls(unittest.TestCase): | |
| """Test case for parallel tool call parsing with array parameters.""" | |
| def setUp(self): | |
| """Set up test tools and detector.""" | |
| self.tools = [ | |
| Tool( | |
| type="function", | |
| function=Function( | |
| name="search_docs", | |
| description="Search documents", | |
| parameters={ | |
| "type": "object", | |
| "properties": { | |
| "title": { | |
| "type": "array", | |
| "items": {"type": "string"}, | |
| "description": "Document title", | |
| } | |
| }, | |
| "required": ["title"], | |
| }, | |
| ), | |
| ), | |
| ] | |
| self.detector = JsonArrayParser() | |
| def _accumulate_tool_calls(self, tool_calls, result): | |
| """Helper method to accumulate tool call results from parsing output.""" | |
| if not result.calls: | |
| return | |
| for call in result.calls: | |
| if call.tool_index is None: | |
| continue | |
| while len(tool_calls) <= call.tool_index: | |
| tool_calls.append({"name": "", "parameters": ""}) | |
| if call.name: | |
| tool_calls[call.tool_index]["name"] = call.name | |
| if call.parameters: | |
| tool_calls[call.tool_index]["parameters"] += call.parameters | |
| def test_parallel_tool_calls_with_array_parameters(self): | |
| """ | |
| Test parsing two parallel tool calls where both have array parameters. | |
| This test reproduces the specific scenario: | |
| - Two tool calls separated by comma | |
| - Both tools have array parameters containing '[' character | |
| - First tool completes with '}},' | |
| - Second tool starts with '{"name": ..., "parameters": {"title": ["' | |
| Expected: Both tools should be parsed correctly without errors. | |
| """ | |
| # Simulate more realistic streaming chunks where | |
| # the key issue is the comma separator followed by second tool with array param | |
| chunks = [ | |
| "[\n", | |
| ' {"name": "search_docs", "parameters": {"title": ["7.8.9"', | |
| '], "filename": "doc1"}},\n', | |
| ' {"name": "search_docs", "parameters": {"title": ', | |
| '["4.8"], "filename": "doc2"}}', | |
| "]", | |
| ] | |
| tool_calls = [] | |
| errors = [] | |
| for i, chunk in enumerate(chunks): | |
| try: | |
| result = self.detector.parse_streaming_increment(chunk, self.tools) | |
| # Collect tool calls | |
| self._accumulate_tool_calls(tool_calls, result) | |
| except Exception as e: | |
| errors.append(f"Chunk {i} ({repr(chunk)}): {type(e).__name__}: {e}") | |
| # Verify no errors occurred | |
| if errors: | |
| self.fail("Errors occurred during parsing:\n" + "\n".join(errors)) | |
| # Verify both tool calls were parsed | |
| self.assertEqual(len(tool_calls), 2, "Should have parsed exactly 2 tool calls") | |
| # Verify first tool call | |
| self.assertEqual( | |
| tool_calls[0]["name"], | |
| "search_docs", | |
| "First tool name should be search_docs", | |
| ) | |
| params1 = json.loads(tool_calls[0]["parameters"]) | |
| self.assertEqual(params1["title"], ["7.8.9"], "First tool title should match") | |
| self.assertEqual( | |
| params1["filename"], "doc1", "First tool filename should be doc1" | |
| ) | |
| # Verify second tool call | |
| self.assertEqual( | |
| tool_calls[1]["name"], | |
| "search_docs", | |
| "Second tool name should be search_docs", | |
| ) | |
| params2 = json.loads(tool_calls[1]["parameters"]) | |
| self.assertEqual(params2["title"], ["4.8"], "Second tool title should match") | |
| self.assertEqual( | |
| params2["filename"], "doc2", "Second tool filename should be doc2" | |
| ) | |
| def test_simple_parallel_tool_calls(self): | |
| """ | |
| Test a simpler case of two parallel tool calls with array parameters. | |
| This is a minimal test case that still tests the core functionality. | |
| """ | |
| chunks = [ | |
| "[\n", | |
| ' {"name": "search_docs", "parameters": {"title": ["a"]}},', | |
| "\n", | |
| ' {"name": "search_docs", "parameters": {"title": ["b"]}}', | |
| "]", | |
| ] | |
| tool_calls = [] | |
| for chunk in chunks: | |
| result = self.detector.parse_streaming_increment(chunk, self.tools) | |
| self._accumulate_tool_calls(tool_calls, result) | |
| # Should parse both tools successfully | |
| self.assertEqual(len(tool_calls), 2, "Should parse 2 tool calls") | |
| self.assertEqual(tool_calls[0]["name"], "search_docs") | |
| self.assertEqual(tool_calls[1]["name"], "search_docs") | |
| if __name__ == "__main__": | |
| unittest.main() | |