Skip to content

Commit

Permalink
Merge pull request #44 from leonvanbokhorst/Refactoring-generic-agent…
Browse files Browse the repository at this point in the history
…,-testing,-NER-implementation

Refactor generic agent and NER implementation
  • Loading branch information
leonvanbokhorst authored Oct 15, 2024
2 parents 9f7d565 + dc23734 commit 2863406
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 136 deletions.
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ fonttools==4.54.1
iniconfig==2.0.0
kiwisolver==1.4.7
matplotlib==3.9.2
numpy==2.1.2
numpy==2.0.2
packaging==24.1
pillow==10.4.0
pluggy==1.5.0
Expand All @@ -17,3 +17,6 @@ six==1.16.0
pydantic
torch
torchvision
textblob
spacy==3.8.2
https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl
82 changes: 67 additions & 15 deletions src/active_inference_forager/agents/generic_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
from pydantic import Field, ConfigDict
from collections import deque
import random
import re
from textblob import TextBlob
import spacy

from active_inference_forager.agents.base_agent import BaseAgent
from active_inference_forager.agents.belief_node import BeliefNode
Expand Down Expand Up @@ -80,9 +83,13 @@ class GenericAgent(BaseAgent):
episode_lengths: List[int] = Field(default_factory=list)
total_steps: int = Field(default=0)

# NLP model
nlp: spacy.language.Language = Field(default_factory=lambda: spacy.load("en_core_web_sm"))

model_config = ConfigDict(arbitrary_types_allowed=True)

def __init__(self, state_dim: int, action_dim: int, **kwargs):
def __init__(self, action_dim: int, **kwargs):
state_dim = 17 # Updated to match the environment's state dimension
super().__init__(state_dim=state_dim, action_dim=action_dim, **kwargs)

self.root_belief = BeliefNode(
Expand All @@ -94,12 +101,19 @@ def __init__(self, state_dim: int, action_dim: int, **kwargs):
self.target_network.load_state_dict(self.q_network.state_dict())
self.optimizer = optim.Adam(self.q_network.parameters(), lr=self.learning_rate)

self.initialize_belief_and_action_space()
self.action_space = [
"ask_question",
"provide_information",
"clarify",
"suggest_action",
"express_empathy",
"end_conversation",
]
self.exploration_rate = self.epsilon_start

def take_action(self, state: np.ndarray) -> str:
if np.random.rand() < self.exploration_rate:
return np.random.choice(self.action_space)
return random.choice(self.action_space)
state_tensor = torch.FloatTensor(state).unsqueeze(0).to(self.device)
q_values = self.q_network(state_tensor)
return self.action_space[q_values.argmax().item()]
Expand Down Expand Up @@ -274,19 +288,57 @@ def _build_belief_hierarchy(self, node: BeliefNode, level: int):
self._build_belief_hierarchy(child, level + 1)

def process_user_input(self, user_input: str) -> np.ndarray:
features = np.zeros(5)
features = np.zeros(17) # Updated to match the environment's state dimension

# Basic text statistics
words = user_input.split()
features[0] = len(words)
features[1] = user_input.count("?") / len(words)
features[2] = user_input.count("!") / len(words)
features[3] = len(user_input) / 100
features[4] = sum(
1
for word in words
if word.lower() in ["please", "thank", "thanks", "appreciate"]
) / len(words)

features = features.astype(float)
features[0] = len(words) # Word count
features[1] = sum(len(word) for word in words) / max(len(words), 1) # Average word length
features[2] = user_input.count("?") / max(len(words), 1) # Question mark frequency
features[3] = user_input.count("!") / max(len(words), 1) # Exclamation mark frequency

# Sentiment analysis
blob = TextBlob(user_input)
features[4] = blob.sentiment.polarity # Sentiment polarity (-1 to 1)
features[5] = blob.sentiment.subjectivity # Subjectivity (0 to 1)

# Keyword detection
keywords = ["help", "explain", "understand", "confused", "clarify"]
features[6] = sum(word.lower() in keywords for word in words) / max(len(words), 1)

# Complexity indicators
features[7] = len(set(words)) / max(len(words), 1) # Lexical diversity
features[8] = sum(len(word) > 6 for word in words) / max(len(words), 1) # Proportion of long words

# Politeness indicator
polite_words = ["please", "thank", "thanks", "appreciate", "kindly"]
features[9] = sum(word.lower() in polite_words for word in words) / max(len(words), 1)

# spaCy processing
doc = self.nlp(user_input)

# Named Entity Recognition
features[10] = len(doc.ents) / max(len(words), 1) # Named entity density

# Part-of-speech tagging
pos_counts = {pos: 0 for pos in ['NOUN', 'VERB', 'ADJ', 'ADV']}
for token in doc:
if token.pos_ in pos_counts:
pos_counts[token.pos_] += 1
features[11] = pos_counts['NOUN'] / max(len(words), 1) # Noun density
features[12] = pos_counts['VERB'] / max(len(words), 1) # Verb density

# Dependency parsing
features[13] = len([token for token in doc if token.dep_ == 'ROOT']) / max(len(words), 1) # Main clause density

# Sentence complexity (using dependency parse tree depth)
def tree_depth(token):
return 1 + max((tree_depth(child) for child in token.children), default=0)

features[14] = sum(tree_depth(sent.root) for sent in doc.sents) / max(len(list(doc.sents)), 1) # Average parse tree depth

# Additional features to match the environment's state dimension
features[15] = len([token for token in doc if token.is_stop]) / max(len(words), 1) # Stop word density
features[16] = len([token for token in doc if token.is_punct]) / max(len(words), 1) # Punctuation density

return features
58 changes: 42 additions & 16 deletions src/active_inference_forager/agents/philosophy_tutor_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
from typing import Dict, List
from pydantic import Field
from active_inference_forager.agents.generic_agent import GenericAgent
import spacy


class PhilosophyTutorAgent(GenericAgent):
knowledge_base: Dict[str, Dict] = Field(default_factory=dict)
nlp: spacy.language.Language = Field(default_factory=lambda: spacy.load("en_core_web_sm"))

def __init__(self, state_dim: int, action_dim: int, **kwargs):
super().__init__(state_dim=state_dim, action_dim=action_dim, **kwargs)
def __init__(self, action_dim: int, **kwargs):
super().__init__(action_dim=action_dim, **kwargs)
self.action_space = [
"explain_concept",
"ask_question",
Expand Down Expand Up @@ -67,25 +69,50 @@ def generate_response(self, action: str, state: np.ndarray) -> str:
return "I'm not sure how to respond to that."

def explain_philosophical_concept(self, state: np.ndarray) -> str:
# TODO: Implement logic to choose a concept based on the state
concept = "epistemology" # Placeholder
# Use the state vector to choose a concept
concepts = list(self.knowledge_base['concepts'].keys())
concept_index = int(state[0] * len(concepts)) % len(concepts)
concept = concepts[concept_index]
return f"Let me explain {concept}: {self.knowledge_base['concepts'][concept]}"

def ask_socratic_question(self, state: np.ndarray) -> str:
# TODO: Implement logic to generate a relevant question based on the state
return "What do you think it means to truly know something?"
# Use the state vector to generate a relevant question
questions = [
"What do you think it means to truly know something?",
"How can we determine what is morally right or wrong?",
"What is the nature of reality, in your opinion?",
"How do you think we can achieve a just society?",
]
question_index = int(state[1] * len(questions)) % len(questions)
return questions[question_index]

def introduce_related_idea(self, state: np.ndarray) -> str:
# TODO: Implement logic to choose a related idea based on the state
return "Have you considered how this relates to the concept of free will?"
# Use the state vector to choose a related idea
ideas = [
"free will",
"consciousness",
"personal identity",
"the meaning of life",
]
idea_index = int(state[2] * len(ideas)) % len(ideas)
return f"Have you considered how this relates to the concept of {ideas[idea_index]}?"

def provide_example(self, state: np.ndarray) -> str:
# TODO: Implement logic to choose a relevant example based on the state
return "For instance, consider how we use logic in everyday decision-making..."
# Use the state vector to choose a relevant example
examples = [
"Consider how we use logic in everyday decision-making...",
"Think about how ethical considerations shape our laws and social norms...",
"Reflect on how our understanding of reality influences our actions...",
"Examine how our beliefs about knowledge affect our learning processes...",
]
example_index = int(state[3] * len(examples)) % len(examples)
return examples[example_index]

def suggest_thought_experiment(self, state: np.ndarray) -> str:
# TODO: Implement logic to choose a thought experiment based on the state
experiment = "The Cave" # Placeholder
# Use the state vector to choose a thought experiment
experiments = list(self.knowledge_base['thought_experiments'].keys())
experiment_index = int(state[4] * len(experiments)) % len(experiments)
experiment = experiments[experiment_index]
return f"Let's explore {experiment}: {self.knowledge_base['thought_experiments'][experiment]}"

def acknowledge_limitation(self, state: np.ndarray) -> str:
Expand All @@ -95,9 +122,8 @@ def update_belief(self, state: np.ndarray):
# We're now working directly with the state vector
super().update_belief(state)

def process_user_input(self, state: np.ndarray) -> np.ndarray:
# This method now processes the state vector instead of a string
# We'll return the state as is, since it's already a numpy array
return state
def process_user_input(self, user_input: str) -> np.ndarray:
# Use the GenericAgent's process_user_input method
return super().process_user_input(user_input)

# ... rest of the class implementation remains the same ...
Loading

0 comments on commit 2863406

Please sign in to comment.