This commit is contained in:
2025-09-02 04:41:06 +02:00
parent 45eb2b8bc5
commit 793213a834
19 changed files with 955 additions and 805 deletions

View File

@@ -1,10 +1,12 @@
import logging
import random
from datetime import datetime
from http.client import responses
from pprint import pprint
from tqdm.asyncio import tqdm
from typing import List, Self
from living_agents import MemoryStream, LLMAgent, Character, PromptManager, Memory
from living_agents.datatypes import CharacterTemplate
from living_agents.datatypes import CharacterTemplate, CharacterTrait
from llm_connector import LLMMessage
logger = logging.getLogger(__name__)
@@ -37,9 +39,10 @@ class CharacterAgent:
async def perceive(self, observation: str, skip_scoring=False) -> None:
"""Add new observation to memory stream"""
if skip_scoring:
await self.memory_stream.add_observation(observation)
new_memory = await self.memory_stream.add_observation(observation)
else:
await self.memory_stream.add_observation(observation, self._score_memory_importance)
new_memory = await self.memory_stream.add_observation(observation, self._score_memory_importance)
await self._analyze_trait_impact(new_memory)
async def react_to_situation(self, situation: str) -> str:
"""Generate reaction based on memory and character"""
@@ -118,37 +121,26 @@ Summary:"""
except:
return f"{self.character.name} is a {self.character.age}-year-old {self.character.occupation}."
async def _get_related_memories_for_scoring(self, memory_text: str, exclude_self=None, k=5) -> List:
"""Get memories related to the one being scored"""
# Get embedding for the memory being scored
memory_embedding = await self.llm.get_embedding(memory_text)
# Calculate similarity to other memories
similarities = []
for mem in self.memory_stream.memories:
if mem == exclude_self:
continue
if mem.embedding:
from sklearn.metrics.pairwise import cosine_similarity
similarity = cosine_similarity([memory_embedding], [mem.embedding])[0][0]
similarities.append((similarity, mem))
# Return top K most similar memories
similarities.sort(reverse=True, key=lambda x: x[0])
return [mem for _, mem in similarities[:k]]
async def _score_memory_importance(self, memory: Memory) -> int:
"""Score importance with related memories as context"""
related_memories = await self._get_related_memories_for_scoring(memory.description, exclude_self=memory, k=5)
related_memories = await self.memory_stream.get_related_memories_for_scoring(memory.description, exclude_self=memory, k=5)
prompt_context = {'character': self._get_character_prompt(),
prompt_context = {'character_context': self._get_character_prompt(),
'character_name': self.character.name,
'related_memories': "\n".join([m.description for m in related_memories]),
'memory_text': memory.description,
'memory_type': memory.memory_type}
'memory_text': memory.description}
if memory.memory_type == 'observation':
prompt = PromptManager.get_prompt('score_observation_importance', prompt_context)
elif memory.memory_type == 'reflection':
prompt = PromptManager.get_prompt('score_reflection_importance', prompt_context)
elif memory.memory_type == 'plan':
prompt = PromptManager.get_prompt('score_plan_importance', prompt_context)
prompt = PromptManager.get_prompt('score_importance_with_context', prompt_context)
# if reflection or plan, add related memories.
if memory.memory_type == 'reflection' or memory.memory_type == 'plan':
for rel_memory in related_memories:
memory.related_memories.append(rel_memory)
try:
response = await self.llm.chat([{"role": "user", "content": prompt}], max_tokens=5)
@@ -157,7 +149,38 @@ Summary:"""
except:
return 5 # Default
async def _extract_character_from_memories(self) -> Character:
async def _analyze_trait_impact(self, memory: Memory):
traits_summary = "\n".join([f" - {trait.strength}/10 {trait.name} ({trait.description})" for trait in self.character.traits]) if self.character.traits else "No traits yet."
prompt_context = {'character_name': self.character.name,
'current_traits': traits_summary,
'new_observation': memory.description}
prompt, schema = PromptManager.get_prompt_with_schema('assess_trait_impact', prompt_context)
messages: List[LLMMessage] = [{'role': 'user', 'content': prompt}]
response = await self.llm.client.get_structured_response(messages, schema)
for trait_update in response['trait_updates']:
trait_to_update = self.character.get_trait(trait_update['trait_name'], trait_update['description'])
if trait_update['action'] == 'create' or trait_update['action'] == 'strengthen':
await self._strengthen_trait(trait_to_update)
else:
await self._weaken_trait(trait_to_update)
@staticmethod
async def _strengthen_trait(trait: CharacterTrait, steepness: float = 1.0):
if trait.strength >= 10:
return
if random.random() < trait.change_by_probability(steepness):
trait.strength += 1
trait.updated = datetime.now()
async def _weaken_trait(self, trait: CharacterTrait, steepness: float = 1.0):
if random.random() < trait.change_by_probability(steepness):
trait.strength -= 1
trait.updated = datetime.now()
if trait.strength <= 0:
self.character.traits.remove(trait)
async def _generate_character_from_memories(self) -> Character:
"""Extract Character info from memories using JSON"""
# Get different types of memories with targeted queries
@@ -221,14 +244,16 @@ Summary:"""
# create the character before we score to include the character in the prompts
# Extract character info from memories to populate Character object
logger.info(f"Creating Character...")
instance.character = await instance._extract_character_from_memories()
instance.character = await instance._generate_character_from_memories()
logger.info(f"Added {len(instance.memory_stream.memories)} memories, now scoring importance...")
# Score all memories with full context
for memory in tqdm(instance.memory_stream.memories, desc="Scoring memory importance", unit="memory"):
# Score all observations with importance
observations = [memory for memory in instance.memory_stream.memories if memory.memory_type == 'observation']
for memory in tqdm(observations, desc="Scoring memory importance", unit="memory"):
# Score with related context
memory.importance_score = await instance._score_memory_importance(memory)
await instance._analyze_trait_impact(memory)
logger.info(f"Character {instance.character.name} created successfully")
return instance