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
           Nz.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 }zz|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 s0    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  }zz| }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 s0    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  }zzd| 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  } zzF|  *}|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