Fix GLM-4.7 MoE Detector complex JSON Schema type parsing (#15753)

This commit is contained in:
Leoyzen
2026-01-10 01:21:10 +08:00
committed by GitHub
parent 76b3c698d6
commit 8ef5b90528
4 changed files with 869 additions and 20 deletions

View File

@@ -12,6 +12,7 @@ from sglang.srt.function_call.core_types import (
ToolCallItem,
_GetInfoFunc,
)
from sglang.srt.function_call.utils import infer_type_from_json_schema
logger = logging.getLogger(__name__)
@@ -31,6 +32,14 @@ def get_argument_type(
) -> Optional[str]:
"""Get the expected type of a function argument from tool definitions.
Supports complex JSON Schema definitions including:
- Direct type field (including type arrays)
- anyOf/oneOf: parameter can be any of multiple types
- enum: parameter must be one of enum values
- allOf: parameter must satisfy all type definitions
- properties: inferred as object type
- items: inferred as array type
Args:
func_name: Name of the function/tool
arg_key: Name of the argument
@@ -58,7 +67,8 @@ def get_argument_type(
arg_spec = properties.get(arg_key)
if isinstance(arg_spec, dict):
return arg_spec.get("type")
# Use the new type inference function for complex JSON Schema support
return infer_type_from_json_schema(arg_spec)
return None
@@ -243,25 +253,48 @@ class Glm47MoeDetector(BaseFormatDetector):
tools: List of available tools
Returns:
Type string: 'string', 'number', or 'object'
Type string: 'string', 'number', 'object', 'array', or 'boolean'
"""
arg_type = get_argument_type(func_name, key, tools)
if arg_type:
return arg_type
# Auto-detect type from value (best effort)
first_chars = (
self._current_value.strip()[:10]
if self._current_value and self._current_value.strip()
else ""
)
if first_chars:
first_char = first_chars[0]
# Improved auto-detection type from value (best effort)
value_content = self._current_value.strip() if self._current_value else ""
if not value_content:
return "string"
# Try to parse as valid JSON first
try:
parsed = json.loads(value_content)
if isinstance(parsed, dict):
return "object"
elif isinstance(parsed, list):
return "array"
elif isinstance(parsed, bool):
return "boolean"
elif isinstance(parsed, (int, float)):
return "number"
# For string values, check if they look like numbers
elif isinstance(parsed, str):
if parsed.isdigit() or (
parsed.startswith("-") and parsed[1:].isdigit()
):
return "number"
return "string"
except json.JSONDecodeError:
# Not valid JSON, try heuristic detection
first_char = value_content[0] if value_content else ""
if first_char.isdigit() or first_char in ["-", "."]:
return "number"
elif first_char in ["{", "["]:
return "object"
elif first_char in ['"', "'"]:
return "string"
# Default to string (safest fallback)
return "string"
def _format_value_complete(self, value: str, value_type: str) -> str:

View File

@@ -12,6 +12,7 @@ from sglang.srt.function_call.core_types import (
ToolCallItem,
_GetInfoFunc,
)
from sglang.srt.function_call.utils import infer_type_from_json_schema
logger = logging.getLogger(__name__)
@@ -31,6 +32,14 @@ def get_argument_type(
) -> Optional[str]:
"""Get the expected type of a function argument from tool definitions.
Supports complex JSON Schema definitions including:
- Direct type field (including type arrays)
- anyOf/oneOf: parameter can be any of multiple types
- enum: parameter must be one of enum values
- allOf: parameter must satisfy all type definitions
- properties: inferred as object type
- items: inferred as array type
Args:
func_name: Name of the function/tool
arg_key: Name of the argument
@@ -48,7 +57,9 @@ def get_argument_type(
properties = {}
if arg_key not in properties:
return None
return properties[arg_key].get("type", None)
# Use new type inference function for complex JSON Schema support
return infer_type_from_json_schema(properties[arg_key])
def _convert_to_number(value: str) -> Any:
@@ -216,21 +227,48 @@ class Glm4MoeDetector(BaseFormatDetector):
tools: List of available tools
Returns:
Type string: 'string', 'number', or 'object'
Type string: 'string', 'number', 'object', 'array', or 'boolean'
"""
arg_type = get_argument_type(func_name, key, tools)
if arg_type:
return arg_type
# Auto-detect type from value (best effort)
first_chars = self._current_value.strip()[:10] if self._current_value else ""
if first_chars:
first_char = first_chars[0]
# Improved auto-detection type from value (best effort)
value_content = self._current_value.strip() if self._current_value else ""
if not value_content:
return "string"
# Try to parse as valid JSON first
try:
parsed = json.loads(value_content)
if isinstance(parsed, dict):
return "object"
elif isinstance(parsed, list):
return "array"
elif isinstance(parsed, bool):
return "boolean"
elif isinstance(parsed, (int, float)):
return "number"
# For string values, check if they look like numbers
elif isinstance(parsed, str):
if parsed.isdigit() or (
parsed.startswith("-") and parsed[1:].isdigit()
):
return "number"
return "string"
except json.JSONDecodeError:
# Not valid JSON, try heuristic detection
first_char = value_content[0] if value_content else ""
if first_char.isdigit() or first_char in ["-", "."]:
return "number"
elif first_char in ["{", "["]:
return "object"
elif first_char in ['"', "'"]:
return "string"
# Default to string (safest fallback)
return "string"
def _format_value_complete(self, value: str, value_type: str) -> str:

View File

@@ -1,6 +1,6 @@
from json import JSONDecodeError, JSONDecoder
from json.decoder import WHITESPACE
from typing import Any, List, Literal, Optional, Tuple, Union
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
import orjson
import partial_json_parser
@@ -101,6 +101,109 @@ def _get_tool_schema(tool: Tool) -> dict:
}
def infer_type_from_json_schema(schema: Dict[str, Any]) -> Optional[str]:
"""
Infer the primary type of a parameter from JSON Schema.
Supports complex JSON Schema structures including:
- Direct type field (including type arrays)
- anyOf/oneOf: parameter can be any of multiple types
- enum: parameter must be one of enum values
- allOf: parameter must satisfy all type definitions
- properties: inferred as object type
- items: inferred as array type
Args:
schema: JSON Schema definition
Returns:
Inferred type ('string', 'number', 'object', 'array', etc.) or None
"""
if not isinstance(schema, dict):
return None
# Priority 1: Direct type field (including type arrays)
if "type" in schema:
type_value = schema["type"]
if isinstance(type_value, str):
return type_value
elif isinstance(type_value, list) and type_value:
# Handle type arrays: return first non-null type
non_null_types = [t for t in type_value if t != "null"]
if non_null_types:
return non_null_types[0]
return "string" # If only null, default to string
# Priority 2: Handle anyOf/oneOf
if "anyOf" in schema or "oneOf" in schema:
schemas = schema.get("anyOf") or schema.get("oneOf")
types = []
if isinstance(schemas, list):
for sub_schema in schemas:
inferred_type = infer_type_from_json_schema(sub_schema)
if inferred_type:
types.append(inferred_type)
if types:
# If all types are the same, return unified type
if len(set(types)) == 1:
return types[0]
# When types differ, prioritize string (safest)
if "string" in types:
return "string"
# Otherwise return first type
return types[0]
# Priority 3: Handle enum (infer type from enum values)
if "enum" in schema and isinstance(schema["enum"], list):
if not schema["enum"]:
return "string"
# Infer type from enum values
enum_types = set()
for value in schema["enum"]:
if value is None:
enum_types.add("null")
elif isinstance(value, bool):
enum_types.add("boolean")
elif isinstance(value, int):
enum_types.add("integer")
elif isinstance(value, float):
enum_types.add("number")
elif isinstance(value, str):
enum_types.add("string")
elif isinstance(value, list):
enum_types.add("array")
elif isinstance(value, dict):
enum_types.add("object")
# If type is uniform, return that type
if len(enum_types) == 1:
return enum_types.pop()
# Mixed types, prioritize string
return "string"
# Priority 4: Handle allOf (must satisfy all types)
if "allOf" in schema and isinstance(schema["allOf"], list):
schemas = schema["allOf"]
for sub_schema in schemas:
inferred_type = infer_type_from_json_schema(sub_schema)
if inferred_type and inferred_type != "string":
return inferred_type
return "string"
# Priority 5: Infer object type
if "properties" in schema:
return "object"
# Priority 6: Infer array type
if "items" in schema:
return "array"
return None
def get_json_schema_constraint(
tools: List[Tool], tool_choice: Union[ToolChoice, Literal["required"]]
) -> Optional[dict]: