Fix LFM 2 tool parser. (#27614)

This commit is contained in:
Yi (Vincent) Zhong
2026-07-30 10:15:08 +00:00
committed by GitHub
parent fd86795107
commit 1f04eaab6a
@@ -37,6 +37,16 @@ from sglang.srt.function_call.utils import safe_ast_parse
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_PYTHONIC_NAME_LITERALS = {
"True": True,
"False": False,
"None": None,
"true": True,
"false": False,
"null": None,
}
class Lfm2Detector(BaseFormatDetector): class Lfm2Detector(BaseFormatDetector):
""" """
Detector for LFM2 (Liquid Foundation Model 2) function call format. Detector for LFM2 (Liquid Foundation Model 2) function call format.
@@ -87,15 +97,12 @@ class Lfm2Detector(BaseFormatDetector):
elif isinstance(val, ast.Tuple): elif isinstance(val, ast.Tuple):
return tuple(self._get_parameter_value(v) for v in val.elts) return tuple(self._get_parameter_value(v) for v in val.elts)
elif isinstance(val, ast.Name): elif isinstance(val, ast.Name):
# Handle True, False, None as names in older Python # Python True/False/None are ast.Constant on modern Python, but
if val.id == "True": # accept their legacy node shape plus LFM2's JSON-literal spellings.
return True try:
elif val.id == "False": return _PYTHONIC_NAME_LITERALS[val.id]
return False except KeyError:
elif val.id == "None": raise ValueError(f"Unsupported name reference: {val.id}") from None
return None
else:
raise ValueError(f"Unsupported name reference: {val.id}")
elif isinstance(val, ast.UnaryOp) and isinstance(val.op, ast.USub): elif isinstance(val, ast.UnaryOp) and isinstance(val.op, ast.USub):
# Handle negative numbers like -5 # Handle negative numbers like -5
inner = self._get_parameter_value(val.operand) inner = self._get_parameter_value(val.operand)
@@ -107,6 +114,19 @@ class Lfm2Detector(BaseFormatDetector):
f"Tool call arguments must be literals, got: {type(val).__name__}" f"Tool call arguments must be literals, got: {type(val).__name__}"
) )
def _get_function_name(self, func: ast.AST) -> Optional[str]:
"""Extract a flat or dotted function name from a Pythonic call node."""
parts: List[str] = []
while isinstance(func, ast.Attribute):
parts.append(func.attr)
func = func.value
if not isinstance(func, ast.Name):
return None
parts.append(func.id)
return ".".join(reversed(parts))
def _parse_pythonic_call( def _parse_pythonic_call(
self, call: ast.Call, call_index: int, tool_indices: Dict[str, int] self, call: ast.Call, call_index: int, tool_indices: Dict[str, int]
) -> Optional[ToolCallItem]: ) -> Optional[ToolCallItem]:
@@ -121,14 +141,13 @@ class Lfm2Detector(BaseFormatDetector):
Returns: Returns:
ToolCallItem if successful, None if the call should be skipped ToolCallItem if successful, None if the call should be skipped
""" """
if not isinstance(call.func, ast.Name): function_name = self._get_function_name(call.func)
if function_name is None:
logger.warning( logger.warning(
f"Tool call function must be a simple name, got: {type(call.func).__name__}" f"Tool call function must be a name or dotted name, got: {type(call.func).__name__}"
) )
return None return None
function_name = call.func.id
# Validate that the function exists in the tools # Validate that the function exists in the tools
if function_name not in tool_indices: if function_name not in tool_indices:
logger.warning( logger.warning(