Fix LFM 2 tool parser. (#27614)
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user