o
    Ã¿iè  ã                   @   sT   d Z ddlZddlmZ ddlmZ ddlmZmZ ddl	m
Z
 G dd„ deƒZdS )	uû  Perplexity LLM adapter for Pipecat.

Perplexity's API uses an OpenAI-compatible interface but enforces stricter
constraints on conversation history structure:

1. **Strict role alternation** â€” Messages must alternate between "user"/"tool"
   and "assistant" roles. Consecutive messages with the same role (e.g. two
   "user" messages in a row) are rejected with:
   ``"messages must be an alternating sequence of user/tool and assistant messages"``

2. **No non-initial system messages** â€” "system" messages are only allowed at
   the start of the conversation. A system message after a non-system message
   causes:
   ``"only the initial message can have the system role"``

3. **Last message must be user/tool** â€” The final message in the conversation
   must have role "user" or "tool". A trailing "assistant" message causes:
   ``"the last message must have the user or tool role"``

This adapter transforms the message list to satisfy all three constraints before
the messages are sent to Perplexity's API.
é    N)ÚList)ÚChatCompletionMessageParam)ÚOpenAILLMAdapterÚOpenAILLMInvocationParams)Ú
LLMContextc                       sD   e Zd ZdZdedef‡ fdd„Zdee dee fdd„Z	‡  Z
S )	ÚPerplexityLLMAdapteraR  Adapter that transforms messages to satisfy Perplexity's API constraints.

    Perplexity's API is stricter than OpenAI about message structure. This
    adapter extends ``OpenAILLMAdapter`` and applies message transformations
    to ensure compliance with Perplexity's constraints (role alternation,
    no non-initial system messages, last message must be user/tool).

    The transformations are applied in ``get_llm_invocation_params`` after the
    parent adapter extracts messages from the LLM context, and before
    ``build_chat_completion_params`` prepends ``system_instruction``.
    ÚcontextÚreturnc                    s&   t ƒ  |¡}|  t|d ƒ¡|d< |S )aW  Get OpenAI-compatible invocation parameters with Perplexity message fixes applied.

        Args:
            context: The LLM context containing messages, tools, etc.

        Returns:
            Dictionary of parameters for Perplexity's ChatCompletion API, with
            messages transformed to satisfy Perplexity's constraints.
        Úmessages)ÚsuperÚget_llm_invocation_paramsÚ_transform_messagesÚlist)Úselfr   Úparams©Ú	__class__© ú`/home/ubuntu/.local/lib/python3.10/site-packages/pipecat/adapters/services/perplexity_adapter.pyr   4   s   
z.PerplexityLLMAdapter.get_llm_invocation_paramsr
   c                 C   sŒ  |s|S t  |¡}d}tt|ƒƒD ]}||  d¡dkr%|s$d|| d< qd}qd}|t|ƒd k rª|| }||d  }|d |d   krJdkrQn n|d7 }nQ|d |d kržt| d¡tƒrkd	|d d
œg|d< t| d¡tƒr}d	|d d
œg|d< t| d¡tƒr–t| d¡tƒr–|d  |d ¡ | 	|d ¡ n|d7 }|t|ƒd k s2|rÄ|d  d¡dkrÄ| 	¡  |rÄ|d  d¡dksµ|S )uê  Transform messages to satisfy Perplexity's API constraints.

        Applies three transformation steps in order:

        1. **Convert non-initial system messages to user** â€” Any system message
           after the initial system message block is converted to role "user",
           since Perplexity rejects system messages after a non-system message.

        2. **Merge consecutive same-role messages** â€” After the above
           conversions, adjacent messages with the same role are merged using
           list-of-dicts content format. This ensures strict role alternation
           (e.g. a converted systemâ†’user message adjacent to an existing user
           message gets merged).

        3. **Remove trailing assistant messages** â€” If the last message is
           "assistant", remove it. OpenAI appears to silently ignore trailing
           assistant messages server-side, so removing them preserves equivalent
           behavior while satisfying Perplexity's "last message must be
           user/tool" constraint.

        Note: we intentionally do *not* convert a trailing system message to
        "user". That would make the transformation unstable across calls â€”
        Perplexity appears to have statefulness/caching within a conversation,
        so a message that was sent as "user" in one call but becomes "system"
        in the next (once more messages are appended) causes errors. If the
        context consists entirely of system messages, the Perplexity API call
        will fail, but that mistake will be caught right away.

        Args:
            messages: List of message dicts with "role" and "content" keys.

        Returns:
            Transformed list of message dicts satisfying Perplexity's constraints.
        TÚroleÚsystemÚuserFr   é   ÚcontentÚtext)Útyper   éÿÿÿÿÚ	assistant)
ÚcopyÚdeepcopyÚrangeÚlenÚgetÚ
isinstanceÚstrr   ÚextendÚpop)r   r
   Úin_initial_system_blockÚiÚcurrentÚnext_msgr   r   r   r   B   s@   %
€ 

ÿíÿz(PerplexityLLMAdapter._transform_messages)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r   r   r   r   r   Ú__classcell__r   r   r   r   r   '   s    ÿþr   )r.   r   Útypingr   Úopenai.types.chatr   Ú)pipecat.adapters.services.open_ai_adapterr   r   Ú*pipecat.processors.aggregators.llm_contextr   r   r   r   r   r   Ú<module>   s   