a
    'NiO)  ã                   @   s~   d Z ddlZddlmZ ddlmZ ddlmZ eejd�Zzddl	m
Z W n eyb   dZY n0 G dd	„ d	ƒZd
d„ ZdS )zñ
Character Memory System for BaoLife

Provides long-term memory for NPCs by extracting and storing important facts
from conversations. Enables characters to remember player preferences, past events,
and relationship history across sessions.
é    N)ÚAsyncOpenAI)Úget_database_connection)Úconfig)Úapi_key)Útrackerc                   @   sT   e Zd ZdZdd„ Zdd„ Zdd„ Zdd	d
„Zddd„Zdd„ Z	dd„ Z
dd„ ZdS )ÚCharacterMemoryzr
    Persistent memory system for NPC characters.
    Extracts and stores important facts from conversations.
    c                 C   s   || _ || _g | _d| _dS )z“
        Initialize character memory.

        Args:
            character_id: ID of the character
            player_id: ID of the player
        FN)Úcharacter_idÚ	player_idÚfactsÚloaded)Úselfr   r	   © r   ú1/var/www/lichun.app/lichun/ws/character_memory.pyÚ__init__   s    zCharacterMemory.__init__c              
   Ã   s´  t |jƒdk rdS |jdd… }|  ||¡}d|j› d|› d�}�z8tjtjjj	dd|d	œgd
dd�dd�I dH }|j
d jj ¡ }t�rzRt|dƒrœ|jnd}tj| j|d|jj|jj|jjdœdd�}	td|	d›�ƒ W n4 t�y }
 ztd|
› �ƒ W Y d}
~
n
d}
~
0 0 | ¡ dk�rx|�rxdd„ | d¡D ƒ}|�rx|  ||¡ | j |¡ tdt |ƒ› d|j› �ƒ |W S W n4 t�y® }
 ztd|
› �ƒ W Y d}
~
n
d}
~
0 0 g S )a  
        Extract important facts from recent conversation messages.
        Called periodically (e.g., every 5 messages) to update memory.

        Args:
            conversation: conversationObj instance
            character: personClass instance
        é   Néûÿÿÿz.Extract key facts from this conversation that z2 should remember about the player.

Conversation:
a  

Extract facts in this format (one per line):
- [Fact about player's preferences, plans, or important information]

Only extract genuinely important facts (max 3). If nothing important, respond with "None".
Facts should be specific and actionable for future conversations.

Facts:zgpt-4o-miniÚuser)ÚroleÚcontentéÈ   gš™™™™™É?)ÚmodelÚmessagesÚ
max_tokensÚtemperatureg      @)Útimeoutr   Úid)Úprompt_tokensÚcompletion_tokensÚtotal_tokensZfact_extraction)ÚpurposezFact extraction cost: $z.6fz'Failed to track fact extraction usage: Únonec                 S   s.   g | ]&}|  ¡  d ¡r|  ¡ dd…   ¡ ‘qS )Ú-é   N)ÚstripÚ
startswith)Ú.0Úfr   r   r   Ú
<listcomp>p   s   þz1CharacterMemory.extract_facts.<locals>.<listcomp>Ú
z
Extracted z new facts for zFailed to extract facts: )ÚlenÚconversationÚ_format_messages_for_extractionÚ	firstnameÚasyncioÚwait_forÚopenai_clientÚchatÚcompletionsÚcreateÚchoicesÚmessager   r#   Úapi_trackerÚhasattrr   Útrack_usager	   Úusager   r   r   ÚprintÚ	ExceptionÚlowerÚsplitÚ_save_factsr
   Úextend)r   r*   Ú	characterÚrecent_messagesÚconversation_textZextraction_promptÚresultÚ
facts_textÚconversation_idÚcostÚer
   r   r   r   Úextract_facts,   s\    

ý
üù
ý÷$þ
$zCharacterMemory.extract_factsc                 C   sD   g }|D ]0}|j |jkrdn|j}| |› d|j› �¡ qd |¡S )z#Format messages for fact extractionÚPlayerz: r(   )Úsenderr   r,   Úappendr4   Újoin)r   r   r?   Ú	formattedÚmsgZspeakerr   r   r   r+   ‚   s
    z/CharacterMemory._format_messages_for_extractioné
   c              
   C   s
  | j r| jr| jS tƒ }zâz’|jdd��r}| d| j| j|f¡ | ¡ }dd„ |D ƒ| _d| _ tdt	| jƒ› d| j› �ƒ | jW  d  ƒ W W | 
¡  S 1 s 0    Y  W nB t� yî } z(td	|› �ƒ g W  Y d}~W | 
¡  S d}~0 0 W | 
¡  n
| 
¡  0 dS )
zÁ
        Load character's memory facts from database.

        Args:
            limit: Maximum number of facts to load (most recent)

        Returns:
            List of fact strings
        T)Ú
dictionarya  
                    SELECT fact, importance, learned_date
                    FROM character_memory
                    WHERE character_id = %s AND player_id = %s
                    ORDER BY importance DESC, learned_date DESC
                    LIMIT %s
                c                 S   s   g | ]}|d  ‘qS )Úfactr   )r%   Úrowr   r   r   r'   £   ó    z.CharacterMemory.load_facts.<locals>.<listcomp>zLoaded z facts for character Nz!Failed to load character memory: )r   r
   r   ÚcursorÚexecuter   r	   Úfetchallr9   r)   Úcloser:   )r   ÚlimitÚmydbrS   ÚresultsrF   r   r   r   Ú
load_factsŠ   s*    
úøýzCharacterMemory.load_factsr   c                 C   s   | j s|  ¡  | jd|… S )z¶
        Get relevant facts for context injection.

        Args:
            max_facts: Maximum number of facts to return

        Returns:
            List of fact strings
        N)r   rZ   r
   )r   Z	max_factsr   r   r   Úget_relevant_facts±   s    
z"CharacterMemory.get_relevant_factsc                 C   s$   |   ¡ }|sdS d |¡}d|› �S )z£
        Get formatted memory context for prompt injection.

        Returns:
            String containing memory context, or empty string if no memories
        Ú z; z,You remember these things about the player: )r[   rK   )r   r
   rC   r   r   r   Úget_memory_contextÁ   s
    
z"CharacterMemory.get_memory_contextc           	      C   s  t ƒ }zðz¢| ¡ �†}g }|D ]D}tdtdt|ƒd ƒƒ}| | j| j||t|dƒrX|j	ndf¡ q| 
d|¡ | ¡  tdt|ƒ› d�ƒ W d  ƒ n1 sž0    Y  W n@ tyê } z(td|› �ƒ td	|› �ƒ W Y d}~n
d}~0 0 W | ¡  n
| ¡  0 dS )
zª
        Save extracted facts to database.

        Args:
            facts: List of fact strings
            conversation: conversationObj instance for metadata
        rN   é   r   NzØ
                    INSERT INTO character_memory
                    (character_id, player_id, fact, importance, conversation_id, learned_date)
                    VALUES (%s, %s, %s, %s, %s, NOW())
                zSaved z facts to databasez"Failed to save facts to database: zFacts were: )r   rS   ÚminÚmaxr)   rJ   r   r	   r6   r   ÚexecutemanyÚcommitr9   r:   rV   )	r   r
   r*   rX   rS   ÚvaluesrP   Z
importancerF   r   r   r   r=   Ñ   s,    
û	ü6&zCharacterMemory._save_factsc              
   C   sº   t ƒ }z¤zd| ¡ �H}| d| j| jf¡ | ¡  g | _d| _td| j› �ƒ W d  ƒ n1 s`0    Y  W n2 t	yž } ztd|› �ƒ W Y d}~n
d}~0 0 W | 
¡  n
| 
¡  0 dS )z6Clear all facts for this character-player relationshipz�
                    DELETE FROM character_memory
                    WHERE character_id = %s AND player_id = %s
                Fz Cleared all facts for character NzFailed to clear facts: )r   rS   rT   r   r	   rb   r
   r   r9   r:   rV   )r   rX   rS   rF   r   r   r   Úclear_factsü   s    

ý2&zCharacterMemory.clear_factsN)rN   )r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   rG   r+   rZ   r[   r]   r=   rd   r   r   r   r   r      s   V
'
+r   c               
   C   sœ   t ƒ } z†zF|  ¡ �*}| d¡ |  ¡  tdƒ W d  ƒ n1 sB0    Y  W n2 ty€ } ztd|› �ƒ W Y d}~n
d}~0 0 W |  ¡  n
|  ¡  0 dS )zn
    Create character_memory table if it doesn't exist.
    Call this during server startup or migration.
    a~  
                CREATE TABLE IF NOT EXISTS character_memory (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    character_id VARCHAR(36) NOT NULL,
                    player_id VARCHAR(36) NOT NULL,
                    fact TEXT NOT NULL,
                    learned_date DATETIME NOT NULL,
                    importance TINYINT DEFAULT 5,
                    conversation_id VARCHAR(36),
                    INDEX idx_char_player (character_id, player_id),
                    INDEX idx_importance (importance DESC),
                    INDEX idx_learned_date (learned_date DESC)
                )
            z'Character memory table created/verifiedNz)Failed to create character_memory table: )r   rS   rT   rb   r9   r:   rV   )rX   rS   rF   r   r   r   Úcreate_character_memory_table  s    

*&ri   )rh   r-   Úopenair   Údatabase.db_operationsr   r   ÚOPENAI_API_KEYr/   Úapi_usage_trackerr   r5   ÚImportErrorr   ri   r   r   r   r   Ú<module>   s   
 y