Revert "fix(tool_call): normalize non-standard JSON Schema types in tool params" (#26379)
This commit is contained in:
@@ -60,10 +60,7 @@ from sglang.srt.environ import envs
|
||||
from sglang.srt.function_call.core_types import ToolCallItem
|
||||
from sglang.srt.function_call.function_call_parser import FunctionCallParser
|
||||
from sglang.srt.function_call.json_array_parser import JsonArrayParser
|
||||
from sglang.srt.function_call.utils import (
|
||||
get_json_schema_constraint,
|
||||
normalize_json_schema_types,
|
||||
)
|
||||
from sglang.srt.function_call.utils import get_json_schema_constraint
|
||||
from sglang.srt.managers.io_struct import GenerateReqInput
|
||||
from sglang.srt.parser.conversation import generate_chat_conv
|
||||
from sglang.srt.parser.jinja_template_utils import process_content_for_template_format
|
||||
@@ -419,19 +416,9 @@ class OpenAIServingChat(OpenAIServingBase):
|
||||
if tool.function.parameters is None:
|
||||
continue
|
||||
try:
|
||||
# Rewrite DB/ORM-style aliases (e.g. "varchar", "enum", "int")
|
||||
# to standard JSON Schema types before validation. RecursionError
|
||||
# guards against hand-crafted cyclic schemas so the request gets
|
||||
# a 400 instead of crashing into a 500.
|
||||
normalize_json_schema_types(tool.function.parameters)
|
||||
Draft202012Validator.check_schema(tool.function.parameters)
|
||||
except SchemaError as e:
|
||||
return f"Tool {i} function has invalid 'parameters' schema: {str(e)}"
|
||||
except RecursionError:
|
||||
return (
|
||||
f"Tool {i} function 'parameters' schema is too deeply nested "
|
||||
"or contains a cycle."
|
||||
)
|
||||
|
||||
max_output_tokens = request.max_completion_tokens or request.max_tokens
|
||||
server_context_length = self.tokenizer_manager.server_args.context_length
|
||||
|
||||
@@ -8,155 +8,6 @@ from partial_json_parser.core.options import Allow
|
||||
|
||||
from sglang.srt.entrypoints.openai.protocol import Tool, ToolChoice
|
||||
|
||||
_STANDARD_JSON_SCHEMA_TYPES = {
|
||||
"null",
|
||||
"boolean",
|
||||
"object",
|
||||
"array",
|
||||
"number",
|
||||
"string",
|
||||
"integer",
|
||||
}
|
||||
|
||||
# Non-standard ``type`` values commonly emitted by DB/ORM-driven tool-schema
|
||||
# generators. Mapped to the closest JSON Schema 2020-12 primitive so that
|
||||
# ``Draft202012Validator.check_schema`` does not reject an otherwise-usable
|
||||
# tool definition.
|
||||
_JSON_SCHEMA_TYPE_ALIASES: Dict[str, str] = {
|
||||
"str": "string",
|
||||
"text": "string",
|
||||
"varchar": "string",
|
||||
"char": "string",
|
||||
"enum": "string",
|
||||
"uuid": "string",
|
||||
"date": "string",
|
||||
"datetime": "string",
|
||||
"time": "string",
|
||||
"timestamp": "string",
|
||||
"bool": "boolean",
|
||||
"binary": "boolean",
|
||||
"bigint": "integer",
|
||||
"smallint": "integer",
|
||||
"tinyint": "integer",
|
||||
"double": "number",
|
||||
"decimal": "number",
|
||||
"real": "number",
|
||||
"numeric": "number",
|
||||
"arr": "array",
|
||||
"tuple": "array",
|
||||
"set": "array",
|
||||
"map": "object",
|
||||
}
|
||||
|
||||
# Prefix-based matching so that parameterised names like ``int32`` /
|
||||
# ``float64`` / ``list[str]`` / ``dict[str, int]`` resolve. A prefix only
|
||||
# matches when it spans the entire token or is followed by a non-identifier
|
||||
# char, so "int" does not swallow "internal" and "list" does not swallow
|
||||
# "list_price".
|
||||
_PREFIX_BOUNDARY_CHARS = frozenset("0123456789[<( \t")
|
||||
_PREFIX_RULES: Tuple[Tuple[Tuple[str, ...], str], ...] = (
|
||||
(("int", "uint", "long", "short", "unsigned"), "integer"),
|
||||
(("num", "float"), "number"),
|
||||
(("list",), "array"),
|
||||
(("dict",), "object"),
|
||||
)
|
||||
|
||||
|
||||
def _matches_type_prefix(base: str, prefixes: Tuple[str, ...]) -> bool:
|
||||
for p in prefixes:
|
||||
if base == p:
|
||||
return True
|
||||
if (
|
||||
len(base) > len(p)
|
||||
and base.startswith(p)
|
||||
and base[len(p)] in _PREFIX_BOUNDARY_CHARS
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _normalize_single_type(raw: Any) -> Any:
|
||||
if not isinstance(raw, str):
|
||||
return raw
|
||||
if raw in _STANDARD_JSON_SCHEMA_TYPES:
|
||||
return raw
|
||||
# ``split("(", 1)[0]`` strips parenthesized params like ``varchar(255)``
|
||||
# or ``decimal(10,2)`` without the overhead of a regex per call.
|
||||
base = raw.split("(", 1)[0].strip().lower()
|
||||
if base in _STANDARD_JSON_SCHEMA_TYPES:
|
||||
return base
|
||||
mapped = _JSON_SCHEMA_TYPE_ALIASES.get(base)
|
||||
if mapped is not None:
|
||||
return mapped
|
||||
for prefixes, target in _PREFIX_RULES:
|
||||
if _matches_type_prefix(base, prefixes):
|
||||
return target
|
||||
return raw
|
||||
|
||||
|
||||
def normalize_json_schema_types(schema: Any) -> None:
|
||||
"""
|
||||
Walk a JSON Schema in place and rewrite non-standard ``"type"`` values
|
||||
(e.g. ``"varchar"``, ``"enum"``, ``"int"``) to their standard JSON Schema
|
||||
equivalents.
|
||||
|
||||
Acts as a compatibility layer for tool ``parameters`` schemas exported
|
||||
from database / ORM tooling, which often uses DB type names rather than
|
||||
JSON Schema types. Unknown types are left untouched so that downstream
|
||||
validation can still surface genuine errors.
|
||||
|
||||
Mutates the input dict in place; the rewritten schema is also what gets
|
||||
rendered into the model prompt, so e.g. a user-supplied ``"varchar"``
|
||||
reaches the model as ``"string"``. ``$ref`` values are not resolved —
|
||||
callers pass tree-shaped schemas (HTTP JSON input is always a tree).
|
||||
"""
|
||||
if isinstance(schema, list):
|
||||
for item in schema:
|
||||
normalize_json_schema_types(item)
|
||||
return
|
||||
if not isinstance(schema, dict):
|
||||
return
|
||||
|
||||
if "type" in schema:
|
||||
t = schema["type"]
|
||||
if isinstance(t, str):
|
||||
schema["type"] = _normalize_single_type(t)
|
||||
elif isinstance(t, list):
|
||||
schema["type"] = [_normalize_single_type(item) for item in t]
|
||||
|
||||
for key in (
|
||||
"properties",
|
||||
"patternProperties",
|
||||
"$defs",
|
||||
"definitions",
|
||||
"dependentSchemas",
|
||||
):
|
||||
nested = schema.get(key)
|
||||
if isinstance(nested, dict):
|
||||
for v in nested.values():
|
||||
normalize_json_schema_types(v)
|
||||
|
||||
for key in ("anyOf", "oneOf", "allOf", "prefixItems"):
|
||||
nested = schema.get(key)
|
||||
if isinstance(nested, list):
|
||||
for v in nested:
|
||||
normalize_json_schema_types(v)
|
||||
|
||||
for key in (
|
||||
"items",
|
||||
"additionalProperties",
|
||||
"not",
|
||||
"if",
|
||||
"then",
|
||||
"else",
|
||||
"contains",
|
||||
"propertyNames",
|
||||
"unevaluatedItems",
|
||||
"unevaluatedProperties",
|
||||
):
|
||||
if key in schema:
|
||||
normalize_json_schema_types(schema[key])
|
||||
|
||||
|
||||
def _find_common_prefix(s1: str, s2: str) -> str:
|
||||
prefix = ""
|
||||
|
||||
Reference in New Issue
Block a user