o
    پi6                     @   s   d Z ddlZddlZddlZddlZddlmZmZmZm	Z	m
Z
 ddlmZ ddlmZ ddlmZ ddlmZmZmZmZ eeZG dd	 d	eZdS )
a  
Detector for LFM2 (Liquid Foundation Model 2) function call format.

Format Structure (Pythonic style):
```
<|tool_call_start|>[function_name(arg1="value1", arg2="value2")]<|tool_call_end|>
```

Multiple tool calls:
```
<|tool_call_start|>[func1(arg="val"), func2(arg="val")]<|tool_call_end|>
```

Also supports JSON format:
```
<|tool_call_start|>[{"name": "func_name", "arguments": {...}}]<|tool_call_end|>
```
    N)AnyDictListOptionalTuple)Tool)envs)BaseFormatDetector)StreamingParseResultStructureInfoToolCallItem_GetInfoFuncc                	       s:  e Zd ZdZ fddZdedefddZdej	de
fd	d
Zdejdedeeef dee fddZdedee deee ef fddZdedee deee ef fddZdedee dee fddZdedee defddZdedefddZdedee defddZdefdd Zdefd!d"Z  ZS )#Lfm2Detectorae  
    Detector for LFM2 (Liquid Foundation Model 2) function call format.

    Supports both Pythonic and JSON formats:

    Pythonic:
    ```
    <|tool_call_start|>[calculator(expression="5 * 7")]<|tool_call_end|>
    ```

    JSON:
    ```
    <|tool_call_start|>[{"name": "calculator", "arguments": {"expression": "5 * 7"}}]<|tool_call_end|>
    ```
    c                    s    t    d| _d| _d| _dS )zJ
        Initializes the detector with necessary state variables.
        <|tool_call_start|>z<|tool_call_end|> N)super__init__	bot_token	eot_tokentool_call_separatorself	__class__ Z/home/ubuntu/.local/lib/python3.10/site-packages/sglang/srt/function_call/lfm2_detector.pyr   8   s   

zLfm2Detector.__init__textreturnc                 C   s
   | j |v S )z4Check if the text contains an LFM2 format tool call.)r   r   r   r   r   r   has_tool_callA   s   
zLfm2Detector.has_tool_callvalc                    s   t |tjr	|jS t |tjr fddt|j|jD S t |tjr- fdd|j	D S t |tj
r?t fdd|j	D S t |tjrb|jdkrLdS |jd	krSd
S |jdkrZdS td|j t |tjrt |jtjr |j}t |ttfr| S td| tdt|j )z
        Extract Python literal value from AST node.

        Handles constants, dicts, and lists recursively.
        Reuses pattern from PythonicDetector.
        c                    s*   i | ]\}}|d ur  |  |qS N_get_parameter_value).0kvr   r   r   
<dictcomp>O   s
    z5Lfm2Detector._get_parameter_value.<locals>.<dictcomp>c                    s   g | ]}  |qS r   r"   r$   r&   r   r   r   
<listcomp>U   s    z5Lfm2Detector._get_parameter_value.<locals>.<listcomp>c                 3   s    | ]}  |V  qd S r!   r"   r(   r   r   r   	<genexpr>W   s    z4Lfm2Detector._get_parameter_value.<locals>.<genexpr>TrueTFalseFNoneNzUnsupported name reference: z!Cannot negate non-numeric value: z+Tool call arguments must be literals, got: )
isinstanceastConstantvaluer   zipkeysvaluesr   eltsr   tupleNameid
ValueErrorUnaryOpopUSubr#   operandintfloattype__name__)r   r    innerr   r   r   r#   E   s4   



z!Lfm2Detector._get_parameter_valuecall
call_indextool_indicesc                 C   s   t |jtjstdt|jj  dS |jj}||vr,td|  t	j
 s,dS i }|jD ]8}|jdu r>td q1z| |j||j< W q1 tyi } ztd|j d|  W Y d}~ dS d}~ww t||tj|ddd	S )
am  
        Parse a single AST Call node into a ToolCallItem.

        Args:
            call: AST Call node representing a function call
            call_index: Index of this call in the list of calls
            tool_indices: Mapping of tool names to their indices

        Returns:
            ToolCallItem if successful, None if the call should be skipped
        z/Tool call function must be a simple name, got: Nz,Model attempted to call undefined function: z2Tool call with **kwargs unpacking is not supportedzFailed to parse argument z: F)ensure_ascii)
tool_indexname
parameters)r.   funcr/   r7   loggerwarningr@   rA   r8   r   SGLANG_FORWARD_UNKNOWN_TOOLSgetkeywordsargr#   r1   r9   r   jsondumps)r   rC   rD   rE   function_name	argumentskeyworder   r   r   _parse_pythonic_callm   s:   



z!Lfm2Detector._parse_pythonic_callcontenttoolsc              
   C   sR  |  }| |}zit|}|jrt|jd ddnd}|du r&g dfW S t|tjr0|j}nt|tj	r:|g}ng dt
|j fW S tdd |D sSg dfW S g }t|D ]\}}	| |	||}
|
durm||
 qY|d	fW S  ty } zg d
| fW  Y d}~S d}~w ty } ztd g d| fW  Y d}~S d}~ww )a  
        Parse Pythonic format tool calls using AST.

        Args:
            content: The content between tool call tags (without the tags)
            tools: List of available tools

        Returns:
            Tuple of (list of parsed calls, error message if any)
        r   r1   Nz"Empty or invalid Python expressionz%Expected function call or list, got: c                 s   s    | ]	}t |tjV  qd S r!   )r.   r/   Call)r$   rV   r   r   r   r*      s    z7Lfm2Detector._parse_pythonic_content.<locals>.<genexpr>z+Not all elements in list are function callsr   zPython syntax error: z.Unexpected error in pythonic tool call parsingzUnexpected error: )strip_get_tool_indicesr/   parsebodygetattrr.   r   r5   rZ   r@   rA   all	enumeraterW   appendSyntaxError	ExceptionrK   	exception)r   rX   rY   rE   moduleparsed
call_nodescallsrD   rC   itemrV   r   r   r   _parse_pythonic_content   s>   






z$Lfm2Detector._parse_pythonic_contentc              
   C   s^   |  }zt|}| ||}|dfW S  tjy. } zg d| fW  Y d}~S d}~ww )a  
        Parse JSON format tool calls.

        Uses parse_base_json from BaseFormatDetector for consistent handling
        of SGLANG_FORWARD_UNKNOWN_TOOLS and tool validation.

        Args:
            content: The content between tool call tags (without the tags)
            tools: List of available tools

        Returns:
            Tuple of (list of parsed calls, error message if any)
        r   zJSON parse error: N)r[   rQ   loadsparse_base_jsonJSONDecodeError)r   rX   rY   rg   ri   rV   r   r   r   _parse_json_content   s   

z Lfm2Detector._parse_json_contentc                 C   sz   |  }|ds|dr%| ||\}}|r|S |r%td| d | ||\}}|r1|S |r;td|  g S )zk
        Parse the content between tool call tags.
        Handles both JSON and Pythonic formats.
        z[{{zJSON parsing failed: z, trying Pythonic formatzFailed to parse tool calls: )r[   
startswithro   rK   debugrk   rL   )r   rX   rY   ri   errorr   r   r   _parse_tool_calls_content   s   z&Lfm2Detector._parse_tool_calls_contentc           
      C   s   | | j}|dkr|d|  n|}| j|vrt|g dS t| j dt| j }t||tj}g }|D ]}| 	||}	|
|	 q:t||dS )zW
        One-time parsing: Detects and parses tool calls in the provided text.
        N)normal_textri   z(.*?))findr   r[   r
   reescaper   findallDOTALLrt   extend)
r   r   rY   idxrv   patternmatch_result_listri   match_resultparsed_callsr   r   r   detect_and_parse
  s   
zLfm2Detector.detect_and_parsec                 C   s   | | jd | jdS )z Remove special tokens from text.r   )replacer   r   r   r   r   r   _strip_special_tokens  s   z"Lfm2Detector._strip_special_tokensnew_textc                 C   sz  |  j |7  _ | | j | j}| | j | j}| j | j}|dkrH|r:| j d|  }| j | d | _ t|dS | | j }d| _ t|dS |dkrS| j d| nd}| j | j|t| j }	|	dkr|r||rw| j |d | _ t|dS tddS |r| j |d | _ t|dS tddS | j ||	t| j  }
| j |	t| j d }| |
|}|| _ |r||j	pd |_	|S )a  
        Streaming incremental parsing for LFM2 tool calls.

        This implementation properly handles Pythonic format by:
        1. Buffering until we see complete <|tool_call_start|>[...]<|tool_call_end|>
        2. Emitting normal text before tool calls immediately
        3. Parsing complete tool call blocks using detect_and_parse

        Based on PythonicDetector streaming logic.
        ru   N)rv   r   r   )
_buffer_ends_with_partial_tokenr   r   rw   r
   r   lenr   rv   )r   r   rY   partial_botpartial_eotbot_pos	safe_textrv   normal_text_beforeeot_postool_call_block	remainingresultr   r   r   parse_streaming_increment#  s>   





z&Lfm2Detector.parse_streaming_incrementc                 C   s   dS )a  
        Return False because LFM2 uses Pythonic format which is not JSON-compatible.

        structural_tag only supports JSON-compatible content between begin and end,
        so it cannot parse Pythonic function call syntax like `func(arg="val")`.
        Fr   r   r   r   r   supports_structural_tago  s   z$Lfm2Detector.supports_structural_tagc                 C   s   dd S )z
        Return structure info for constrained generation.

        Note: This is provided for completeness but won't be used since
        supports_structural_tag() returns False.
        c                 S   s   t d|  d dddS )Nz<|tool_call_start|>[(z)]<|tool_call_end|>r   )beginendtrigger)r   )rH   r   r   r   <lambda>  s
    
z-Lfm2Detector.structure_info.<locals>.<lambda>r   r   r   r   r   structure_infox  s   zLfm2Detector.structure_info) rA   
__module____qualname____doc__r   strboolr   r/   ASTr   r#   rZ   r>   r   r   r   rW   r   r   r   rk   ro   rt   r
   r   r   r   r   r   r   __classcell__r   r   r   r   r   '   s\    	(

1
4


L	r   )r   r/   rQ   loggingrx   typingr   r   r   r   r   &sglang.srt.entrypoints.openai.protocolr   sglang.srt.environr   -sglang.srt.function_call.base_format_detectorr	   #sglang.srt.function_call.core_typesr
   r   r   r   	getLoggerrA   rK   r   r   r   r   r   <module>   s    
