# Scenario 9: User Profile Management

This scenario demonstrates powermem's UserMemory feature - automatic user profile extraction, management, and integration with memory search.

## Prerequisites

- Python 3.11+
- powermem installed (`pip install powermem`)
- LLM provider configured (for profile extraction)
- OceanBase configured as vector store (UserMemory requires OceanBase)

> **Important Note**: `UserMemory` requires OceanBase as the storage backend. If you attempt to use `UserMemory` with a different storage provider, it will raise a `ValueError`. Please configure OceanBase as your vector store provider.

## Understanding User Profile Management

UserMemory extends Memory with user profile capabilities:

- **Automatic profile extraction**: Extracts user-related information from conversations (name, profession, interests, preferences, etc.)
- **Continuous profile updates**: Updates and refines the profile based on new conversations
- **Profile storage**: Stores profiles separately from memories for efficient retrieval
- **Joint search**: Optionally includes profile information when searching memories

## Step 1: Initialize UserMemory

First, let's initialize UserMemory with proper configuration:

```python
# user_profile_example.py
from powermem import UserMemory, auto_config

# Load configuration (auto-loads from .env or uses defaults)
config = auto_config()

# Create UserMemory instance
user_memory = UserMemory(config=config)

print("✓ UserMemory initialized successfully!")
```

**Run this code:**
```bash
python user_profile_example.py
```

**Expected output:**
```
✓ UserMemory initialized successfully!
```

## Step 2: Add Conversation and Extract Profile

Add a conversation and automatically extract user profile information:

```python
# user_profile_example.py
from powermem import UserMemory, auto_config

config = auto_config()
user_memory = UserMemory(config=config)

# Add a conversation
conversation = [
    {"role": "user", "content": "Hi, I'm Alice. I'm a 28-year-old software engineer from San Francisco. I work at a tech startup and love Python programming."},
    {"role": "assistant", "content": "Nice to meet you, Alice! Python is a great language for software engineering."}
]

result = user_memory.add(
    messages=conversation,
    user_id="user_001",
    agent_id="assistant_agent",
    run_id="session_001"
)

print(f"✓ Conversation added successfully")
print(f"  - Profile extracted: {result.get('profile_extracted', False)}")
if result.get('profile_content'):
    print(f"  - Profile content: {result['profile_content']}")
print(f"  - Memory results count: {len(result.get('results', []))}")
```

**Run this code:**
```bash
python user_profile_example.py
```

**Expected output:**
```
✓ Conversation added successfully
  - Profile extracted: True
  - Profile content: Name: Alice. Age: 28. Location: San Francisco. Profession: Software engineer at a tech startup. Interests: Python programming.
  - Memory results count: 1
```

## Step 3: Update Profile with More Conversations

Add more conversations to update and refine the user profile:

```python
# user_profile_example.py
from powermem import UserMemory, auto_config

config = auto_config()
user_memory = UserMemory(config=config)

# First conversation
conversation1 = [
    {"role": "user", "content": "Hi, I'm Alice. I'm a software engineer from San Francisco."},
    {"role": "assistant", "content": "Nice to meet you, Alice!"}
]

result1 = user_memory.add(
    messages=conversation1,
    user_id="user_001",
    agent_id="assistant_agent"
)

print("=== First conversation ===")
print(f"Profile extracted: {result1.get('profile_extracted', False)}")
if result1.get('profile_content'):
    print(f"Profile: {result1['profile_content']}")

# Second conversation - adds more information
conversation2 = [
    {"role": "user", "content": "I also enjoy reading science fiction novels and playing tennis on weekends."},
    {"role": "assistant", "content": "That sounds like great hobbies!"}
]

result2 = user_memory.add(
    messages=conversation2,
    user_id="user_001",
    agent_id="assistant_agent"
)

print("\n=== Second conversation ===")
print(f"Profile updated: {result2.get('profile_extracted', False)}")
if result2.get('profile_content'):
    print(f"Updated profile: {result2['profile_content']}")
```

**Run this code:**
```bash
python user_profile_example.py
```

**Expected output:**
```
=== First conversation ===
Profile extracted: True
Profile: Name: Alice. Profession: Software engineer. Location: San Francisco.

=== Second conversation ===
Profile updated: True
Updated profile: Name: Alice. Profession: Software engineer. Location: San Francisco. Hobbies: Reading science fiction novels, playing tennis.
```

## Step 4: Get User Profile

Retrieve the user profile directly:

```python
# user_profile_example.py
from powermem import UserMemory, auto_config

config = auto_config()
user_memory = UserMemory(config=config)

# Add some conversations first
conversation = [
    {"role": "user", "content": "I'm Bob, a data scientist. I love machine learning and hiking."},
    {"role": "assistant", "content": "Great to meet you, Bob!"}
]

user_memory.add(
    messages=conversation,
    user_id="user_002",
    agent_id="assistant_agent"
)

# Get the user profile
profile = user_memory.profile(
    user_id="user_002"
)

if profile:
    print("✓ Profile retrieved successfully")
    print(f"  - Profile ID: {profile.get('id')}")
    print(f"  - User ID: {profile.get('user_id')}")
    if profile.get('profile_content'):
        print(f"  - Profile content: {profile.get('profile_content', '')}")
    if profile.get('topics'):
        print(f"  - Topics: {profile.get('topics')}")
    print(f"  - Created at: {profile.get('created_at')}")
    print(f"  - Updated at: {profile.get('updated_at')}")
else:
    print("✗ No profile found")
```

**Run this code:**
```bash
python user_profile_example.py
```

**Expected output:**
```
✓ Profile retrieved successfully
  - Profile ID: 1234567890123456789
  - User ID: user_002
  - Agent ID: assistant_agent
  - Run ID: 
  - Profile content: Name: Bob. Profession: Data scientist. Interests: Machine learning, hiking.
  - Created at: 2024-01-15T10:30:00
  - Updated at: 2024-01-15T10:30:00
```

## Step 5: Search Memories Without Profile

Search memories without including the profile:

```python
# user_profile_example.py
from powermem import UserMemory, auto_config

config = auto_config()
user_memory = UserMemory(config=config)

user_id = "user_003"

# Add some conversations
user_memory.add(
    messages=[
        {"role": "user", "content": "I'm Charlie, a product manager. I work on mobile apps."},
        {"role": "assistant", "content": "Interesting!"}
    ],
    user_id=user_id,
    agent_id="assistant_agent"
)

user_memory.add(
    messages=[
        {"role": "user", "content": "I prefer agile development methodology."},
        {"role": "assistant", "content": "That's a popular approach."}
    ],
    user_id=user_id,
    agent_id="assistant_agent"
)

# Search without profile
results = user_memory.search(
    query="work and preferences",
    user_id=user_id,
    agent_id="assistant_agent",
    limit=5,
    add_profile=False  # Don't include profile
)

print(f"✓ Search completed")
print(f"  - Results count: {len(results.get('results', []))}")
print(f"  - Profile included: {'profile_content' in results}")

for i, result in enumerate(results.get('results', []), 1):
    print(f"  {i}. {result.get('memory', '')} (score: {result.get('score', 0):.2f})")
```

**Run this code:**
```bash
python user_profile_example.py
```

**Expected output:**
```
✓ Search completed
  - Results count: 2
  - Profile included: False
  1. Works on mobile apps as a product manager (score: 0.85)
  2. Prefers agile development methodology (score: 0.78)
```

## Optional: Query Rewrite with User Profile

`UserMemory.search()` can optionally rewrite the query using the user's profile to improve recall. This feature is **optional** and disabled by default. It only runs when `query_rewrite.enabled=True` and a `user_id` with `profile_content` is available. If the profile is missing, the query is too short, or the rewrite fails, it falls back to the original query.

Practical example (profile + ambiguous query):

```python

# user_profile_example.py
from powermem import UserMemory, auto_config

config = auto_config()
user_memory = UserMemory(config=config)

user_id = "user_rewrite_demo"

# Build profile with relocation info
user_memory.add(
    messages="Last month I moved from Chengdu to Hangzhou, and started a new job.",
    user_id=user_id,
    profile_type="content"
)

# Ambiguous query that relies on profile context
results = user_memory.search(
    query="Recommend some delicious food near my place.",
    user_id=user_id,
    limit=5
)
```

Contrast: rewrite enabled vs disabled

```python
import os

# Enable query rewrite via env
os.environ["QUERY_REWRITE_ENABLED"] = "true"
user_memory_rewrite = UserMemory(config=auto_config())

# Disable query rewrite via env
os.environ["QUERY_REWRITE_ENABLED"] = "false"
user_memory_no_rewrite = UserMemory(config=auto_config())

# Same query, same user profile
query = "Recommend some delicious food near my place."

result_with_rewrite = user_memory_rewrite.search(
    query=query,
    user_id=user_id,
    limit=5
)

result_without_rewrite = user_memory_no_rewrite.search(
    query=query,
    user_id=user_id,
    limit=5
)
```

Example config:

```python
config = {
    # ... other config
    "query_rewrite": {
        "enabled": True,
        # "prompt": "Rewrite queries to be specific and grounded in the user profile."
    }
}
```

Environment variables are also supported (see `.env.example`):

```bash
QUERY_REWRITE_ENABLED=false
# QUERY_REWRITE_PROMPT=
# QUERY_REWRITE_MODEL_OVERRIDE=
```

- `QUERY_REWRITE_PROMPT` is optional custom instructions for rewrite.
- `QUERY_REWRITE_MODEL_OVERRIDE` is optional and must be another model from the same LLM provider family.

## Step 6: Search Memories With Profile

Search memories and include the user profile in results:

```python
# user_profile_example.py
from powermem import UserMemory, auto_config

config = auto_config()
user_memory = UserMemory(config=config)

user_id = "user_004"

# Add conversations
user_memory.add(
    messages=[
        {"role": "user", "content": "I'm Diana, a UX designer. I love creating beautiful interfaces."},
        {"role": "assistant", "content": "That's wonderful!"}
    ],
    user_id=user_id,
    agent_id="assistant_agent"
)

# Search with profile
results = user_memory.search(
    query="user background and interests",
    user_id=user_id,
    agent_id="assistant_agent",
    limit=5,
    add_profile=True  # Include profile
)

print(f"✓ Search with profile completed")
print(f"  - Results count: {len(results.get('results', []))}")
print(f"  - Profile included: {'profile_content' in results}")

if 'profile_content' in results:
    print(f"\n  User Profile:")
    print(f"  {results['profile_content']}")

print(f"\n  Search Results:")
for i, result in enumerate(results.get('results', []), 1):
    print(f"  {i}. {result.get('memory', '')} (score: {result.get('score', 0):.2f})")
```

**Run this code:**
```bash
python user_profile_example.py
```

**Expected output:**
```
✓ Search with profile completed
  - Results count: 1
  - Profile included: True

  User Profile:
  Name: Diana. Profession: UX designer. Interests: Creating beautiful interfaces.

  Search Results:
  1. Works as a UX designer, loves creating beautiful interfaces (score: 0.92)
```

## Step 7: Extract Profile in Native Language

You can specify a native language for profile extraction, ensuring the profile is written in the user's preferred language regardless of the conversation language:

```python
# user_profile_example.py
from powermem import UserMemory, auto_config

config = auto_config()
user_memory = UserMemory(config=config)

# Example 1: English conversation, Chinese profile
conversation_en = [
    {"role": "user", "content": "I am a software engineer working in Beijing. I love drinking tea and reading books."},
    {"role": "assistant", "content": "That sounds great!"}
]

result_zh = user_memory.add(
    messages=conversation_en,
    user_id="user_bilingual_001",
    native_language="zh"  # Extract profile in Chinese
)

print("✓ English conversation processed")
if result_zh.get('profile_content'):
    print(f"  - Profile (Chinese): {result_zh['profile_content']}")

# Example 2: Chinese conversation, English profile
conversation_zh = [
    {"role": "user", "content": "I'm 25 years old, working at Microsoft in Seattle."},
    {"role": "assistant", "content": "Nice to meet you"}
]

result_en = user_memory.add(
    messages=conversation_zh,
    user_id="user_bilingual_002",
    native_language="en"  # Extract profile in English
)

print("\n✓ Chinese conversation processed")
if result_en.get('profile_content'):
    print(f"  - Profile (English): {result_en['profile_content']}")

# Example 3: Structured topics with native language
result_topics = user_memory.add(
    messages="I'm 25 years old, working at Microsoft in Seattle.",
    user_id="user_bilingual_003",
    profile_type="topics",
    native_language="zh"  # Topic values in Chinese, keys remain English
)

print("\n✓ Structured topics extracted")
if result_topics.get('topics'):
    print(f"  - Topics: {result_topics['topics']}")
```

**Run this code:**
```bash
python user_profile_example.py
```

**Supported Language Codes:**

| Code | Language | Code | Language |
|------|----------|------|----------|
| zh | Chinese | en | English |
| ja | Japanese | ko | Korean |
| fr | French | de | German |
| es | Spanish | it | Italian |
| pt | Portuguese | ru | Russian |
| ar | Arabic | hi | Hindi |
| th | Thai | vi | Vietnamese |

## Step 8: Delete User Profile

Delete a user profile:

```python
# user_profile_example.py
from powermem import UserMemory, auto_config

config = auto_config()
user_memory = UserMemory(config=config, agent_id="assistant_agent")

user_id = "user_007"

# Add conversation and create profile
user_memory.add(
    messages=[
        {"role": "user", "content": "I'm Grace, a teacher."},
        {"role": "assistant", "content": "Nice to meet you!"}
    ],
    user_id=user_id
)

# Get profile to confirm it exists
profile = user_memory.profile(user_id=user_id)
if profile:
    profile_id = profile.get('id')
    print(f"✓ Profile exists with ID: {profile_id}")
    
    # Delete the profile
    deleted = user_memory.delete_profile(
        user_id=user_id
    )
    
    if deleted:
        print(f"✓ Profile deleted successfully")
    else:
        print(f"✗ Failed to delete profile")
    
    # Verify deletion
    profile_after = user_memory.profile(user_id=user_id)
    if not profile_after:
        print("✓ Profile confirmed deleted")
    else:
        print("✗ Profile still exists")
```

**Run this code:**
```bash
python user_profile_example.py
```

**Expected output:**
```
✓ Profile exists with ID: 1234567890123456789
✓ Profile deleted successfully
✓ Profile confirmed deleted
```

## Complete Example

Here's a complete example combining all the features:

```python
# complete_user_profile_example.py
from powermem import UserMemory, auto_config

def main():
    # Load configuration
    config = auto_config()
    
    # Initialize UserMemory
    user_memory = UserMemory(config=config, agent_id="demo_agent")
    user_id = "demo_user"
    
    print("=" * 60)
    print("UserMemory Profile Management Example")
    print("=" * 60)
    
    # Step 1: Add initial conversation
    print("\n1. Adding initial conversation...")
    conversation1 = [
        {"role": "user", "content": "Hi, I'm Alex, a 32-year-old data scientist from New York. I specialize in machine learning and love reading tech blogs."},
        {"role": "assistant", "content": "Nice to meet you, Alex! That's fascinating."}
    ]
    
    result1 = user_memory.add(
        messages=conversation1,
        user_id=user_id,
        run_id="session_001"
    )
    
    print(f"   ✓ Profile extracted: {result1.get('profile_extracted', False)}")
    if result1.get('profile_content'):
        print(f"   Profile: {result1['profile_content']}")
    
    # Step 2: Update profile with more information
    print("\n2. Updating profile with more conversations...")
    conversation2 = [
        {"role": "user", "content": "I also enjoy hiking and photography on weekends."},
        {"role": "assistant", "content": "Those are great hobbies!"}
    ]
    
    result2 = user_memory.add(
        messages=conversation2,
        user_id=user_id,
        run_id="session_002"
    )
    
    print(f"   ✓ Profile updated: {result2.get('profile_extracted', False)}")
    
    # Step 3: Get full profile
    print("\n3. Retrieving full profile...")
    profile = user_memory.profile(
        user_id=user_id
    )
    
    if profile:
        print(f"   ✓ Profile ID: {profile.get('id')}")
        print(f"   Profile content: {profile.get('profile_content', '')}")
        print(f"   Last updated: {profile.get('updated_at')}")
    
    # Step 4: Search with profile
    print("\n4. Searching memories with profile...")
    search_results = user_memory.search(
        query="user interests and hobbies",
        user_id=user_id,
        limit=5,
        add_profile=True
    )
    
    if 'profile_content' in search_results:
        print(f"   User Profile: {search_results['profile_content']}")
    
    print(f"\n   Found {len(search_results.get('results', []))} memories:")
    for i, result in enumerate(search_results.get('results', []), 1):
        print(f"   {i}. {result.get('memory', '')} (score: {result.get('score', 0):.2f})")
    
    # Step 5: Cleanup (optional)
    print("\n5. Cleaning up...")
    # Note: In production, you might want to keep the profiles
    # deleted = user_memory.delete_profile(user_id=user_id)
    # if deleted:
    #     print("   ✓ Profile deleted")
    
    print("\n" + "=" * 60)
    print("Example completed successfully!")
    print("=" * 60)

if __name__ == "__main__":
    main()
```

**Run this code:**
```bash
python complete_user_profile_example.py
```

## Best Practices

1. **Always provide `user_id`**: The `add()` method requires `user_id`; ensure each user has a unique identifier

2. **Use `agent_id` to distinguish agents**: In multi-agent scenarios, use `agent_id` to separate profiles and memories across agents

3. **Use `run_id` appropriately**: Use `run_id` to distinguish sessions or runs for more precise profile management

4. **Check profiles regularly**: Use `profile()` to periodically check and ensure profile information remains accurate

5. **Include profile when needed**: When additional context is useful, set `add_profile=True` to include the user profile in search results

6. **Handle empty profiles**: If `profile()` returns `{}`, no profile has been extracted yet; call `add()` with conversation data first

7. **Profile updates**: Profiles are automatically updated when you add new conversations with the same `user_id`, `agent_id`, and `run_id` combination

8. **Message filtering**: By default, only `user` role messages are used for profile extraction (assistant messages are excluded). You can customize this with `include_roles` and `exclude_roles` parameters

9. **Query rewrite (optional)**: Enable `query_rewrite.enabled=True` to let `search()` rewrite queries using profile content; it falls back to the original query when unavailable

## Step 8: Filter Messages by Roles

By default, `UserMemory.add()` only extracts profile information from user messages. You can customize which message roles are used for profile extraction:

```python
# user_profile_example.py
from powermem import UserMemory, auto_config

config = auto_config()
user_memory = UserMemory(config=config)

# Conversation with multiple roles
conversation = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Hi, I'm Frank, a doctor from Boston."},
    {"role": "assistant", "content": "Nice to meet you, Frank!"},
    {"role": "tool", "content": "Weather data: Boston, 72°F"}
]

# Default behavior: only include 'user' messages, exclude 'assistant' messages
result = user_memory.add(
    messages=conversation,
    user_id="user_008"
)
print("Default filtering (user only):")
print(f"  Profile: {result.get('profile_content', 'N/A')}")

# Include all messages (disable filtering)
result = user_memory.add(
    messages=conversation,
    user_id="user_009",
    include_roles=None,  # or []
    exclude_roles=None   # or []
)
print("\nNo filtering (all roles):")
print(f"  Profile: {result.get('profile_content', 'N/A')}")

# Custom filtering: include user and system, exclude tool
result = user_memory.add(
    messages=conversation,
    user_id="user_010",
    include_roles=["user", "system"],
    exclude_roles=["tool"]
)
print("\nCustom filtering (user + system, exclude tool):")
print(f"  Profile: {result.get('profile_content', 'N/A')}")
```

**Run this code:**
```bash
python user_profile_example.py
```

**Expected output:**
```
Default filtering (user only):
  Profile: Name: Frank. Profession: Doctor. Location: Boston.

No filtering (all roles):
  Profile: Name: Frank. Profession: Doctor. Location: Boston.

Custom filtering (user + system, exclude tool):
  Profile: Name: Frank. Profession: Doctor. Location: Boston.
```

## Related Documents

- [UserMemory Guide](../guides/0010-user_memory.md) - Detailed guide on UserMemory features
- [Getting Started](scenario_1_basic_usage.md) - Learn the basics of PowerMem
- [Multi-Agent Guide](scenario_3_multi_agent.md) - Using multiple agents with Memory
