Skip to main content

Adding New Patient Agents

This guide explains how to add new patient/client simulation methods to PatientHub.

Overview

Patient agents implement different simulation approaches. Each method typically comes from a research paper and has unique characteristics for modeling patient behavior.

Architecture

patienthub/clients/
├── __init__.py # Agent registry
├── basic.py # Base class
├── patientPsi.py # Example implementation
├── consistentMI.py # Another example
└── your_new_agent.py # Your new agent

Step 1: Create Agent File

Create a new file in patienthub/clients/:

# patienthub/clients/myAgent.py

from typing import Any, Optional
from langchain_core.messages import AIMessage, HumanMessage

from patienthub.base.agents import BaseAgent
from patienthub.brain.profile import PatientProfile


class MyAgent(BaseAgent):
"""
Implement your patient simulation method.

Paper: Your Paper Title
Venue: Conference Year
"""

def __init__(
self,
configs: Any,
lang: str = "en",
**kwargs
):
super().__init__(configs=configs, lang=lang, **kwargs)

# Load your character data
self._load_character()

# Initialize any method-specific state
self.internal_state = {}

def _load_character(self):
"""Load character from data file."""
import json
with open(self.configs.data_path, 'r') as f:
characters = json.load(f)

self.character = characters[self.configs.data_idx]

# Parse into profile if needed
self.profile = PatientProfile.from_dict(self.character)

def _build_system_prompt(self) -> str:
"""Construct the system prompt for your method."""
prompt = f"""You are simulating a patient named {self.character['name']}.

Background:
{self.character.get('background', 'No background provided.')}

Presenting Problem:
{self.character.get('presenting_problem', 'No presenting problem specified.')}

Your role is to respond as this patient would in a therapy session.
Maintain consistency with the character description.
"""
# Add method-specific instructions
if 'special_instructions' in self.character:
prompt += f"\n\nSpecial Instructions:\n{self.character['special_instructions']}"

return prompt

def set_therapist(self, therapist_info: dict):
"""Set information about the therapist (optional)."""
self.therapist_info = therapist_info

def generate_response(
self,
message: str,
**kwargs
) -> AIMessage:
"""
Generate a patient response to therapist input.

Args:
message: The therapist's message
**kwargs: Additional parameters

Returns:
AIMessage containing the patient's response
"""
# Build messages for LLM
messages = [
{"role": "system", "content": self._build_system_prompt()},
]

# Add conversation history
for msg in self.conversation_history:
messages.append({
"role": "assistant" if isinstance(msg, AIMessage) else "user",
"content": msg.content
})

# Add current message
messages.append({"role": "user", "content": message})

# Call LLM
response = self.llm.invoke(messages)

# Update internal state (method-specific)
self._update_state(message, response.content)

# Store in history
self.conversation_history.append(HumanMessage(content=message))
self.conversation_history.append(AIMessage(content=response.content))

return AIMessage(content=response.content)

def _update_state(self, therapist_msg: str, patient_response: str):
"""Update internal state based on interaction."""
# Implement your method's state update logic
pass

def get_state(self) -> dict:
"""Return current internal state for evaluation."""
return {
"profile": self.profile.to_dict() if self.profile else {},
"internal_state": self.internal_state,
"turn_count": len(self.conversation_history) // 2,
}

def reset(self):
"""Reset agent state for new session."""
self.conversation_history = []
self.internal_state = {}

Step 2: Register the Agent

Add your agent to the registry in patienthub/clients/__init__.py:

from patienthub.clients.myAgent import MyAgent

CLIENT_REGISTRY = {
# ... existing agents ...
'myAgent': MyAgent,
}

def get_client(configs, lang: str = "en", **kwargs):
agent_type = configs.agent_type
if agent_type not in CLIENT_REGISTRY:
raise ValueError(f"Unknown client type: {agent_type}")
return CLIENT_REGISTRY[agent_type](configs=configs, lang=lang, **kwargs)

Step 3: Create Character Data

Add character data in data/characters/:

// data/characters/MyAgent.json
[
{
"name": "Taylor",
"age": 29,
"gender": "non-binary",
"occupation": "graphic designer",
"background": "Recently moved to a new city for work...",
"presenting_problem": "Experiencing social anxiety...",
"personality": {
"traits": ["introverted", "creative", "sensitive"],
"communication_style": "thoughtful, sometimes hesitant"
},
"special_instructions": "Tends to pause before answering difficult questions"
}
]

Step 4: Create Configuration

Add a Hydra config in data/characters/ or reference via CLI:

# Can be used as: client=myAgent
agent_type: myAgent
model_type: OPENAI
model_name: gpt-4o
temperature: 0.7
max_tokens: 1024
max_retries: 3
data_path: data/characters/MyAgent.json
data_idx: 0

Step 5: Add Tests

Create tests in patienthub/tests/:

# patienthub/tests/test_myAgent.py

import pytest
from omegaconf import OmegaConf
from patienthub.clients import get_client


@pytest.fixture
def agent_config():
return OmegaConf.create({
'agent_type': 'myAgent',
'model_type': 'OPENAI',
'model_name': 'gpt-4o-mini',
'temperature': 0.0, # Deterministic for tests
'max_tokens': 256,
'max_retries': 3,
'data_path': 'data/characters/MyAgent.json',
'data_idx': 0,
})


def test_agent_initialization(agent_config):
"""Test that agent initializes correctly."""
agent = get_client(configs=agent_config, lang='en')
assert agent is not None
assert agent.character['name'] == 'Taylor'


def test_agent_response(agent_config):
"""Test that agent generates valid responses."""
agent = get_client(configs=agent_config, lang='en')
response = agent.generate_response("Hello, how are you today?")
assert response is not None
assert len(response.content) > 0


def test_agent_conversation(agent_config):
"""Test multi-turn conversation."""
agent = get_client(configs=agent_config, lang='en')

r1 = agent.generate_response("What brings you in today?")
r2 = agent.generate_response("Tell me more about that.")

# Should maintain conversation history
assert len(agent.conversation_history) == 4 # 2 turns x 2 messages

Step 6: Add Documentation

Create a doc page in docs/docs/components/:

---
sidebar_position: N
---

# MyAgent

> Paper Title Here

**Venue**: Conference Year

## Overview

Brief description of your method.

## Key Features

- Feature 1
- Feature 2

## Usage

\`\`\`bash
uv run python -m examples.simulate client=myAgent
\`\`\`

...

Advanced Features

Implementing State Transitions

class StatefulAgent(BaseAgent):
STATES = ['initial', 'engaged', 'resistant', 'resolved']

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.current_state = 'initial'

def _update_state(self, therapist_msg: str, patient_response: str):
# Analyze interaction and potentially transition
if self._detect_engagement(therapist_msg):
self.current_state = 'engaged'
# ... more logic

Adding Memory/Multi-session Support

class MemoryAgent(BaseAgent):
def save_session(self, path: str):
"""Save session for multi-session support."""
import json
with open(path, 'w') as f:
json.dump({
'history': [m.content for m in self.conversation_history],
'state': self.internal_state,
}, f)

def load_session(self, path: str):
"""Load previous session."""
import json
with open(path, 'r') as f:
data = json.load(f)
# Restore state...

Checklist

Before submitting your new agent:

  • Agent class in patienthub/clients/
  • Registered in __init__.py
  • Character data file created
  • Unit tests passing
  • Documentation page added
  • Example usage works: python -m examples.simulate client=yourAgent