Skip to content
9 changes: 7 additions & 2 deletions usersimcrs/simulator/agenda_based/agenda_based_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ def __init__(
nlg: NLG module generating textual responses.
ratings: Historical ratings.
"""
super().__init__(id=id, domain=domain, item_collection=item_collection)
self._preference_model = preference_model
super().__init__(
id=id,
domain=domain,
item_collection=item_collection,
preference_model=preference_model,
)
self._preference_model = self.preference_model

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant, you can simple use self.preference_model.

self._interaction_model = interaction_model
self._interaction_model.initialize_agenda(self.information_need)
self._nlu = nlu
Expand Down
21 changes: 17 additions & 4 deletions usersimcrs/simulator/llm/llm_dual_prompt_user_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
the user response.
"""

from typing import Optional

from dialoguekit.core.utterance import Utterance
from dialoguekit.participant import DialogueParticipant
from usersimcrs.core.simulation_domain import SimulationDomain
Expand All @@ -19,6 +21,7 @@
UtteranceGenerationPrompt,
)
from usersimcrs.simulator.user_simulator import UserSimulator
from usersimcrs.user_modeling.preference_model import PreferenceModel
from usersimcrs.user_modeling.persona import Persona


Expand All @@ -32,7 +35,8 @@ def __init__(
item_type: str,
task_definition: str = DEFAULT_TASK_DEFINITION,
stop_definition: str = DEFAULT_STOP_DEFINITION,
persona: Persona = None,
persona: Optional[Persona] = None,
preference_model: Optional[PreferenceModel] = None,
) -> None:
"""Initializes the user simulator.

Expand All @@ -45,14 +49,23 @@ def __init__(
stop_definition: Definition of the stop task. Defaults to
DEFAULT_STOP_DEFINITION.
persona: Persona of the user. Defaults to None.
preference_model: Preference model. Defaults to None.
"""
super().__init__(id, domain, item_collection)
super().__init__(id, domain, item_collection, preference_model)
self.llm_interface = llm_interface
self.generation_prompt = UtteranceGenerationPrompt(
self.information_need, item_type, task_definition, persona
self.information_need,
item_type,
task_definition,
persona,
preference_model,
)
self.stop_prompt = StopPrompt(
self.information_need, item_type, stop_definition, persona
self.information_need,
item_type,
stop_definition,
persona,
preference_model,
)

def _generate_response(self, agent_utterance: Utterance) -> Utterance:
Expand Down
15 changes: 12 additions & 3 deletions usersimcrs/simulator/llm/llm_single_prompt_user_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
model.
"""

from typing import Optional

from dialoguekit.core.utterance import Utterance
from dialoguekit.participant import DialogueParticipant
from usersimcrs.core.simulation_domain import SimulationDomain
Expand All @@ -14,6 +16,7 @@
UtteranceGenerationPrompt,
)
from usersimcrs.simulator.user_simulator import UserSimulator
from usersimcrs.user_modeling.preference_model import PreferenceModel
from usersimcrs.user_modeling.persona import Persona


Expand All @@ -26,7 +29,8 @@ def __init__(
llm_interface: LLMInterface,
item_type: str,
task_definition: str = DEFAULT_TASK_DEFINITION,
persona: Persona = None,
persona: Optional[Persona] = None,
preference_model: Optional[PreferenceModel] = None,
) -> None:
"""Initializes the user simulator.

Expand All @@ -37,11 +41,16 @@ def __init__(
task_definition: Definition of the task to be performed.
Defaults to DEFAULT_TASK_DEFINITION.
persona: Persona of the user. Defaults to None.
preference_model: Preference model. Defaults to None.
"""
super().__init__(id, domain, item_collection)
super().__init__(id, domain, item_collection, preference_model)
self.llm_interface = llm_interface
self.prompt = UtteranceGenerationPrompt(
self.information_need, item_type, task_definition, persona
self.information_need,
item_type,
task_definition,
persona,
preference_model,
)

def _generate_response(self, agent_utterance: Utterance) -> Utterance:
Expand Down
34 changes: 32 additions & 2 deletions usersimcrs/simulator/llm/prompt/prompt.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Interface for prompt."""

from abc import ABC, abstractmethod
from typing import Optional

from dialoguekit.core.utterance import Utterance
from dialoguekit.participant.participant import DialogueParticipant
from usersimcrs.core.information_need import InformationNeed
from usersimcrs.user_modeling.preference_model import PreferenceModel
from usersimcrs.user_modeling.persona import Persona


Expand All @@ -14,7 +16,8 @@ def __init__(
information_need: InformationNeed,
item_type: str,
prompt_definition: str,
persona: Persona = None,
persona: Optional[Persona] = None,
preference_model: Optional[PreferenceModel] = None,
) -> None:
"""Initializes the prompt.

Expand All @@ -23,18 +26,45 @@ def __init__(
item_type: The type of the item to be recommended.
prompt_definition: The definition of the task to be performed.
persona: The persona of the user. Defaults to None.
preference_model: Preference model. Defaults to None.
"""
self.information_need = information_need
self.item_type = item_type
self.prompt_definition = prompt_definition
self.persona = persona
self.preference_model = preference_model
self._initial_prompt = self.build_new_prompt()
self._prompt_context = ""

@property
def prompt_text(self) -> str:
"""Prompt for the user simulator."""
return self._initial_prompt + "\n" + self._prompt_context
return (
self._initial_prompt
+ "\n"
+ self._preference_context
+ "\n"
+ self._prompt_context
)

@property
def _preference_context(self) -> str:
"""Returns current preference context for the prompt."""
if not self.preference_model:
return ""

preference_summary = self.preference_model.get_preference_summary()
if not preference_summary:
return ""

return (
"USER PREFERENCES: "
f"{preference_summary}\n"
"Treat these preferences as soft background tastes, not as new "
"requirements. Use them to shape what the user accepts, "
"rejects, asks to avoid, or follows up on. Do not force every "
"liked preference into the opening request.\n"
)
Comment thread
dkkdark marked this conversation as resolved.

@abstractmethod
def build_new_prompt(self, **kwargs) -> str:
Expand Down
19 changes: 16 additions & 3 deletions usersimcrs/simulator/llm/prompt/stop_prompt.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Define the prompt for stopping the conversation."""

from typing import Optional

from usersimcrs.core.information_need import InformationNeed
from usersimcrs.simulator.llm.prompt.prompt import Prompt
from usersimcrs.user_modeling.preference_model import PreferenceModel
from usersimcrs.user_modeling.persona import Persona

DEFAULT_STOP_DEFINITION = (
Expand All @@ -21,7 +24,8 @@ def __init__(
information_need: InformationNeed,
item_type: str,
prompt_definition: str = DEFAULT_STOP_DEFINITION,
persona: Persona = None,
persona: Optional[Persona] = None,
preference_model: Optional[PreferenceModel] = None,
) -> None:
"""Initializes the prompt.

Expand All @@ -31,9 +35,14 @@ def __init__(
prompt_definition: The definition of the task to be performed.
Defaults to DEFAULT_STOP_DEFINITION.
persona: The persona of the user. Defaults to None.
preference_model: Preference model. Defaults to None.
"""
super().__init__(
information_need, item_type, prompt_definition, persona
information_need,
item_type,
prompt_definition,
persona,
preference_model,
)

@property
Expand All @@ -42,6 +51,7 @@ def prompt_text(self) -> str:
return (
self._initial_prompt
+ "\n"
+ self._preference_context

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Why note have a "\n" after like for the other elements of the prompt?

+ self._prompt_context
+ "\n"
+ "CONTINUE: "
Expand All @@ -66,7 +76,10 @@ def build_new_prompt(self) -> str:
for key, value in self.persona.characteristics.items()
]
)
initial_prompt += f"PERSONA: {stringified_characteristics}\n"
persona_text = (
self.persona.persona_description or stringified_characteristics
)
initial_prompt += f"PERSONA: {persona_text}\n"

initial_prompt += "\nHISTORY:\n"
return initial_prompt
18 changes: 15 additions & 3 deletions usersimcrs/simulator/llm/prompt/utterance_generation_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
for Task-Oriented Dialog Systems", arXiv 2306.00774.
"""

from typing import Optional

from dialoguekit.core.utterance import Utterance
from dialoguekit.participant.participant import DialogueParticipant
from usersimcrs.core.information_need import InformationNeed
from usersimcrs.simulator.llm.prompt.prompt import Prompt
from usersimcrs.user_modeling.preference_model import PreferenceModel
from usersimcrs.user_modeling.persona import Persona

DEFAULT_TASK_DEFINITION = (
Expand All @@ -34,7 +37,8 @@ def __init__(
information_need: InformationNeed,
item_type: str,
prompt_definition: str = DEFAULT_TASK_DEFINITION,
persona: Persona = None,
persona: Optional[Persona] = None,
preference_model: Optional[PreferenceModel] = None,
) -> None:
"""Initializes the prompt.

Expand All @@ -44,9 +48,14 @@ def __init__(
prompt_definition: The definition of the task to be performed.
Defaults to DEFAULT_TASK_DEFINITION.
persona: The persona of the user. Defaults to None.
preference_model: Preference model. Defaults to None.
"""
super().__init__(
information_need, item_type, prompt_definition, persona
information_need,
item_type,
prompt_definition,
persona,
preference_model,
)

def build_new_prompt(self) -> str:
Expand All @@ -67,7 +76,10 @@ def build_new_prompt(self) -> str:
for key, value in self.persona.characteristics.items()
]
)
initial_prompt += f"PERSONA: {stringified_characteristics}\n"
persona_text = (
self.persona.persona_description or stringified_characteristics
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you could avoid the creation of stringified_characteristics if the persona_description exist (similar comment for stop_prompt.py)

initial_prompt += f"PERSONA: {persona_text}\n"
else:
initial_prompt += (
"Be precise with the REQUIREMENTS, clear and concise.\n"
Expand Down
6 changes: 6 additions & 0 deletions usersimcrs/simulator/user_simulator.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"""User simulator abstract class."""

from abc import ABC, abstractmethod
from typing import Optional

from dialoguekit.core.annotated_utterance import AnnotatedUtterance
from dialoguekit.core.utterance import Utterance
from dialoguekit.participant.user import User, UserType
from usersimcrs.core.information_need import generate_random_information_need
from usersimcrs.core.simulation_domain import SimulationDomain
from usersimcrs.items.item_collection import ItemCollection
from usersimcrs.user_modeling.preference_model import PreferenceModel


class UserSimulator(User, ABC):
Expand All @@ -16,11 +18,13 @@ def __init__(
id: str,
domain: SimulationDomain,
item_collection: ItemCollection,
preference_model: Optional[PreferenceModel] = None,
) -> None:
"""Initializes the user simulator."""
super().__init__(id, UserType.SIMULATOR)
self._domain = domain
self._item_collection = item_collection
self.preference_model = preference_model
self.get_new_information_need()

def get_new_information_need(self) -> None:
Expand Down Expand Up @@ -53,4 +57,6 @@ def receive_utterance(self, utterance: Utterance) -> None:
response = self._generate_response(utterance)
if not isinstance(response, AnnotatedUtterance):
response = AnnotatedUtterance.from_utterance(response)
if self.preference_model:
self.preference_model.update_from_dialogue(response)
self._dialogue_connector.register_user_utterance(response)
4 changes: 4 additions & 0 deletions usersimcrs/user_modeling/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
from usersimcrs.user_modeling.simple_preference_model import (
SimplePreferenceModel,
)
from usersimcrs.user_modeling.structured_preference_model import (
StructuredPreferenceModel,
)
from usersimcrs.user_modeling.persona import Persona

__all__ = [
"ContextModel",
"PreferenceModel",
"PKGPreferenceModel",
"SimplePreferenceModel",
"StructuredPreferenceModel",
"Persona",
]
24 changes: 22 additions & 2 deletions usersimcrs/user_modeling/persona.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
from __future__ import annotations

"""Persona, which is a profile of the user to represent different backgrounds
(e.g., age, gender, education), personality types, and behavioral tendencies
(e.g., patience, conscientiousness, or curiosity)."""

from dataclasses import dataclass
from typing import Any, Dict
from typing import Any, Dict, Optional


@dataclass
class Persona:
"""Represents personal user characteristics as key-value pairs."""

characteristics: Dict[str, Any]
persona_description: Optional[str] = None

@classmethod
def from_config(cls, persona_config: Dict[str, Any]) -> Persona:
"""Creates a persona from a configuration dictionary.

The configuration must provide characteristics under
``characteristics`` and may include an optional ``persona_description``.

Args:
persona_config: Persona configuration.

Returns:
Persona instance.
"""
return cls(
persona_config.get("characteristics", {}),
persona_config.get("persona_description"),
)
8 changes: 8 additions & 0 deletions usersimcrs/user_modeling/pkg_preference_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,11 @@ def get_slot_value_preference(self, slot: str, value: str) -> float:
# TODO: Query PKG to retrieve slot-value preference.
preference = None
return preference

def get_preference_summary(self, max_preferences: int = 10) -> str:
"""Returns an empty summary until PKG prompt support is added."""
return ""

def update_from_dialogue(self, dialogue) -> None:
"""PKG-backed dialogue updates are not implemented yet."""
return None
Comment thread
dkkdark marked this conversation as resolved.
Loading
Loading