Update to use Anthropic API instead of OpenAI
Browse files
app.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
import os
|
| 2 |
import streamlit as st
|
| 3 |
-
|
| 4 |
-
import openai # Added OpenAI import
|
| 5 |
from dotenv import load_dotenv
|
| 6 |
|
| 7 |
# Load environment variables
|
|
@@ -9,23 +8,57 @@ load_dotenv()
|
|
| 9 |
|
| 10 |
# Configure Streamlit page settings
|
| 11 |
st.set_page_config(
|
| 12 |
-
page_title="
|
| 13 |
-
page_icon="
|
| 14 |
layout="centered",
|
| 15 |
)
|
| 16 |
|
| 17 |
-
# Initialize
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
try:
|
| 20 |
-
|
| 21 |
-
if not
|
| 22 |
-
st.error("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
st.stop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
except Exception as e:
|
| 25 |
-
st.error(f"Failed to configure
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
st.stop()
|
| 27 |
|
| 28 |
-
|
| 29 |
# Initialize session state for form inputs if not present
|
| 30 |
if "setup_complete" not in st.session_state:
|
| 31 |
st.session_state.setup_complete = False
|
|
@@ -34,29 +67,29 @@ if "messages" not in st.session_state:
|
|
| 34 |
st.session_state.messages = []
|
| 35 |
|
| 36 |
# Main page header
|
| 37 |
-
st.markdown("<h1 style='text-align: center; color: #333;'>
|
| 38 |
-
st.markdown("<p style='text-align: center; font-size: 18px; color: #555; margin-bottom: 1em;'>
|
| 39 |
|
| 40 |
# Welcome text and instructions
|
| 41 |
if not st.session_state.setup_complete:
|
| 42 |
st.markdown("""
|
| 43 |
## Practice Hard Conversations—Safely.
|
| 44 |
|
| 45 |
-
Welcome to a therapeutic roleplay simulator
|
| 46 |
-
This tool helps you rehearse boundary-setting and difficult conversations by simulating realistic relational dynamics—tailored to
|
| 47 |
|
| 48 |
You'll choose:
|
| 49 |
|
|
|
|
| 50 |
- A scenario (e.g., "Ask my mom not to comment on my body")
|
| 51 |
- A tone of response (e.g., supportive, guilt-tripping, dismissive)
|
| 52 |
-
-
|
| 53 |
-
- And your goal (e.g., "I want to stay calm and not backtrack")
|
| 54 |
|
| 55 |
-
The AI will
|
| 56 |
|
| 57 |
### 🧠 Not sure what your attachment style is?
|
| 58 |
You can take this [free quiz from Sarah Peyton](https://www.yourresonantself.com/attachment-assessment) to learn more.
|
| 59 |
-
Or you can just pick the one that
|
| 60 |
|
| 61 |
- **Anxious** – "I often worry if I've upset people or said too much."
|
| 62 |
- **Avoidant** – "I'd rather handle things alone than depend on others."
|
|
@@ -78,7 +111,7 @@ with st.sidebar:
|
|
| 78 |
- Help clients and clinicians rehearse courage, regulation, and repair
|
| 79 |
- Stay grounded in trauma-informed, developmentally sensitive frameworks
|
| 80 |
|
| 81 |
-
I use powerful language models like
|
| 82 |
|
| 83 |
As a practicing therapist, I imagine these resources being especially helpful to clinicians like myself — companions in the work of tending to others with insight, warmth, and care.
|
| 84 |
|
|
@@ -170,24 +203,34 @@ else:
|
|
| 170 |
# Prepare messages for API call (already includes system message as the first item)
|
| 171 |
api_messages = st.session_state.messages
|
| 172 |
|
| 173 |
-
# Get
|
| 174 |
with st.spinner("..."):
|
| 175 |
try:
|
| 176 |
-
#
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
#
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
)
|
| 189 |
-
assistant_response = response.
|
| 190 |
-
|
| 191 |
|
| 192 |
# Add assistant response to chat history
|
| 193 |
st.session_state.messages.append(
|
|
@@ -210,6 +253,113 @@ else:
|
|
| 210 |
# if st.session_state.messages[-2]["role"] == "user":
|
| 211 |
# st.session_state.messages.pop(-2) # Example: remove user msg that caused error
|
| 212 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
# Footer
|
| 215 |
st.markdown("---")
|
|
|
|
| 1 |
import os
|
| 2 |
import streamlit as st
|
| 3 |
+
from anthropic import Anthropic
|
|
|
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
|
| 6 |
# Load environment variables
|
|
|
|
| 8 |
|
| 9 |
# Configure Streamlit page settings
|
| 10 |
st.set_page_config(
|
| 11 |
+
page_title="Practice Difficult Conversations",
|
| 12 |
+
page_icon="🤝",
|
| 13 |
layout="centered",
|
| 14 |
)
|
| 15 |
|
| 16 |
+
# Initialize Anthropic client
|
| 17 |
+
def get_api_key():
|
| 18 |
+
# Try getting from Streamlit secrets first (for Hugging Face deployment)
|
| 19 |
+
try:
|
| 20 |
+
if hasattr(st.secrets, "anthropic_key"):
|
| 21 |
+
st.write("Debug: Found key in Streamlit secrets")
|
| 22 |
+
return st.secrets.anthropic_key
|
| 23 |
+
except Exception as e:
|
| 24 |
+
st.write(f"Debug: Error accessing Streamlit secrets: {e}")
|
| 25 |
+
|
| 26 |
+
# Fall back to environment variable (for local development)
|
| 27 |
+
env_key = os.getenv("ANTHROPIC_API_KEY")
|
| 28 |
+
if env_key:
|
| 29 |
+
st.write("Debug: Found key in environment variables")
|
| 30 |
+
return env_key
|
| 31 |
+
else:
|
| 32 |
+
st.write("Debug: No key found in environment variables")
|
| 33 |
+
|
| 34 |
+
return None
|
| 35 |
+
|
| 36 |
try:
|
| 37 |
+
api_key = get_api_key()
|
| 38 |
+
if not api_key:
|
| 39 |
+
st.error("Anthropic API Key not found. Please ensure it's set in Hugging Face secrets or local .env file.")
|
| 40 |
+
st.markdown("""
|
| 41 |
+
### Setup Instructions:
|
| 42 |
+
1. For local development: Copy `.env.template` to `.env` and add your Anthropic API key
|
| 43 |
+
2. For Hugging Face: Add anthropic_key to your space's secrets
|
| 44 |
+
3. Restart the application
|
| 45 |
+
""")
|
| 46 |
st.stop()
|
| 47 |
+
|
| 48 |
+
# Initialize client with API key from environment
|
| 49 |
+
client = Anthropic(api_key=api_key)
|
| 50 |
+
st.write("Debug: Successfully created Anthropic client")
|
| 51 |
+
|
| 52 |
except Exception as e:
|
| 53 |
+
st.error(f"Failed to configure Anthropic client: {e}")
|
| 54 |
+
st.markdown("""
|
| 55 |
+
### Setup Instructions:
|
| 56 |
+
1. For local development: Copy `.env.template` to `.env` and add your Anthropic API key
|
| 57 |
+
2. For Hugging Face: Add anthropic_key to your space's secrets
|
| 58 |
+
3. Restart the application
|
| 59 |
+
""")
|
| 60 |
st.stop()
|
| 61 |
|
|
|
|
| 62 |
# Initialize session state for form inputs if not present
|
| 63 |
if "setup_complete" not in st.session_state:
|
| 64 |
st.session_state.setup_complete = False
|
|
|
|
| 67 |
st.session_state.messages = []
|
| 68 |
|
| 69 |
# Main page header
|
| 70 |
+
st.markdown("<h1 style='text-align: center; color: #333;'>Practice Difficult Conversations</h1>", unsafe_allow_html=True)
|
| 71 |
+
st.markdown("<p style='text-align: center; font-size: 18px; color: #555; margin-bottom: 1em;'>With Your Attachment Style Front and Center!</p>", unsafe_allow_html=True)
|
| 72 |
|
| 73 |
# Welcome text and instructions
|
| 74 |
if not st.session_state.setup_complete:
|
| 75 |
st.markdown("""
|
| 76 |
## Practice Hard Conversations—Safely.
|
| 77 |
|
| 78 |
+
Welcome to a therapeutic roleplay simulator that puts your attachment style at the center of practice.
|
| 79 |
+
This tool helps you rehearse boundary-setting and difficult conversations by simulating realistic relational dynamics—tailored to how you naturally connect and protect.
|
| 80 |
|
| 81 |
You'll choose:
|
| 82 |
|
| 83 |
+
- Your attachment style (e.g., anxious, avoidant, disorganized)
|
| 84 |
- A scenario (e.g., "Ask my mom not to comment on my body")
|
| 85 |
- A tone of response (e.g., supportive, guilt-tripping, dismissive)
|
| 86 |
+
- And your practice goal (e.g., "I want to stay calm and not backtrack")
|
|
|
|
| 87 |
|
| 88 |
+
The AI will respond in character, helping you practice real-world dynamics. When you're ready, you can debrief to explore your patterns and responses.
|
| 89 |
|
| 90 |
### 🧠 Not sure what your attachment style is?
|
| 91 |
You can take this [free quiz from Sarah Peyton](https://www.yourresonantself.com/attachment-assessment) to learn more.
|
| 92 |
+
Or you can just pick the one that resonates:
|
| 93 |
|
| 94 |
- **Anxious** – "I often worry if I've upset people or said too much."
|
| 95 |
- **Avoidant** – "I'd rather handle things alone than depend on others."
|
|
|
|
| 111 |
- Help clients and clinicians rehearse courage, regulation, and repair
|
| 112 |
- Stay grounded in trauma-informed, developmentally sensitive frameworks
|
| 113 |
|
| 114 |
+
I use powerful language models like Anthropic's Claude for these tools, chosen for their ability to simulate nuanced human interaction and responsiveness to emotionally complex prompts.
|
| 115 |
|
| 116 |
As a practicing therapist, I imagine these resources being especially helpful to clinicians like myself — companions in the work of tending to others with insight, warmth, and care.
|
| 117 |
|
|
|
|
| 203 |
# Prepare messages for API call (already includes system message as the first item)
|
| 204 |
api_messages = st.session_state.messages
|
| 205 |
|
| 206 |
+
# Get Anthropic's response
|
| 207 |
with st.spinner("..."):
|
| 208 |
try:
|
| 209 |
+
# Convert messages to Anthropic format
|
| 210 |
+
formatted_messages = []
|
| 211 |
+
|
| 212 |
+
# Add system message as the first user message
|
| 213 |
+
system_msg = next((msg for msg in api_messages if msg["role"] == "system"), None)
|
| 214 |
+
if system_msg:
|
| 215 |
+
formatted_messages.append({
|
| 216 |
+
"role": "user",
|
| 217 |
+
"content": system_msg["content"]
|
| 218 |
+
})
|
| 219 |
+
|
| 220 |
+
# Add the rest of the conversation
|
| 221 |
+
for msg in api_messages:
|
| 222 |
+
if msg["role"] != "system": # Skip system message as we've already handled it
|
| 223 |
+
formatted_messages.append({
|
| 224 |
+
"role": msg["role"],
|
| 225 |
+
"content": msg["content"]
|
| 226 |
+
})
|
| 227 |
+
|
| 228 |
+
response = client.messages.create(
|
| 229 |
+
model="claude-3-opus-20240229",
|
| 230 |
+
messages=formatted_messages,
|
| 231 |
+
max_tokens=1024
|
| 232 |
)
|
| 233 |
+
assistant_response = response.content[0].text
|
|
|
|
| 234 |
|
| 235 |
# Add assistant response to chat history
|
| 236 |
st.session_state.messages.append(
|
|
|
|
| 253 |
# if st.session_state.messages[-2]["role"] == "user":
|
| 254 |
# st.session_state.messages.pop(-2) # Example: remove user msg that caused error
|
| 255 |
|
| 256 |
+
# Add debrief button after conversation starts
|
| 257 |
+
if st.session_state.setup_complete and not st.session_state.get('in_debrief', False):
|
| 258 |
+
col1, col2, col3 = st.columns([1, 2, 1])
|
| 259 |
+
with col2:
|
| 260 |
+
if st.button("🤔 I'm Ready to Debrief", use_container_width=True):
|
| 261 |
+
st.session_state.in_debrief = True
|
| 262 |
+
|
| 263 |
+
# Prepare debrief system message
|
| 264 |
+
debrief_system_message = """You are a warm, trauma-informed reflection guide. Your role is to help users process their experience from the previous roleplay conversation. Important notes:
|
| 265 |
+
|
| 266 |
+
- Start with: "I'm not a therapist, even though I may sound relational. I've been trained as a language model to support emotional reflection, but I encourage you to bring these insights into connection with a trusted therapist, mentor, or support circle who can help you explore them more deeply."
|
| 267 |
+
|
| 268 |
+
Then analyze the conversation following this structure:
|
| 269 |
+
|
| 270 |
+
1. Tone / Persona Used:
|
| 271 |
+
- Note the AI role they interacted with
|
| 272 |
+
- Describe key patterns in that communication style
|
| 273 |
+
- Ask how that style impacted them
|
| 274 |
+
|
| 275 |
+
2. Emotional Shifts:
|
| 276 |
+
- Track their emotional journey
|
| 277 |
+
- Note any significant changes in tone or engagement
|
| 278 |
+
- Invite reflection on those shifts
|
| 279 |
+
|
| 280 |
+
3. Signs of:
|
| 281 |
+
- Courage: Moments of speaking up, setting boundaries, expressing needs
|
| 282 |
+
- Avoidance: When they might have pulled back or deflected
|
| 283 |
+
- Protest: Times they pushed back or expressed disagreement
|
| 284 |
+
|
| 285 |
+
4. Rupture & Repair:
|
| 286 |
+
- Identify moments of disconnection or tension
|
| 287 |
+
- Note any attempts to rebuild connection
|
| 288 |
+
- Ask about the felt experience of these moments
|
| 289 |
+
|
| 290 |
+
5. Integration:
|
| 291 |
+
- Offer a gentle somatic or journaling prompt
|
| 292 |
+
- Focus on body awareness and felt sense
|
| 293 |
+
- Use language like "if you're willing..." or "you might notice..."
|
| 294 |
+
|
| 295 |
+
Remember to:
|
| 296 |
+
- Use phrases like "I notice..." or "I'm curious about..."
|
| 297 |
+
- Validate all emotional responses
|
| 298 |
+
- Keep focus on their experience, not your analysis
|
| 299 |
+
- Offer observations as invitations, not declarations
|
| 300 |
+
- Stay grounded in the present moment"""
|
| 301 |
+
|
| 302 |
+
# Store previous conversation for analysis
|
| 303 |
+
conversation_history = st.session_state.messages[1:] # Skip system message
|
| 304 |
+
|
| 305 |
+
# Initialize debrief conversation
|
| 306 |
+
st.session_state.debrief_messages = [
|
| 307 |
+
{"role": "system", "content": debrief_system_message},
|
| 308 |
+
{"role": "user", "content": f"Please help me process this conversation. Here's what happened: {str(conversation_history)}"}
|
| 309 |
+
]
|
| 310 |
+
|
| 311 |
+
try:
|
| 312 |
+
response = client.messages.create(
|
| 313 |
+
model="claude-3-opus-20240229",
|
| 314 |
+
messages=st.session_state.debrief_messages,
|
| 315 |
+
max_tokens=1000
|
| 316 |
+
)
|
| 317 |
+
st.session_state.debrief_messages.append(
|
| 318 |
+
{"role": "assistant", "content": response.content[0].text}
|
| 319 |
+
)
|
| 320 |
+
except Exception as e:
|
| 321 |
+
st.error(f"An error occurred starting the debrief: {e}")
|
| 322 |
+
|
| 323 |
+
st.rerun()
|
| 324 |
+
|
| 325 |
+
# Handle debrief mode
|
| 326 |
+
if st.session_state.get('in_debrief', False):
|
| 327 |
+
st.markdown("## 🤝 Let's Process Together")
|
| 328 |
+
|
| 329 |
+
# Display debrief conversation
|
| 330 |
+
for message in st.session_state.debrief_messages[1:]: # Skip system message
|
| 331 |
+
with st.chat_message(message["role"]):
|
| 332 |
+
st.markdown(message["content"])
|
| 333 |
+
|
| 334 |
+
# Chat input for debrief
|
| 335 |
+
if debrief_prompt := st.chat_input("Share what comes up for you..."):
|
| 336 |
+
st.session_state.debrief_messages.append({"role": "user", "content": debrief_prompt})
|
| 337 |
+
|
| 338 |
+
with st.chat_message("user"):
|
| 339 |
+
st.markdown(debrief_prompt)
|
| 340 |
+
|
| 341 |
+
with st.chat_message("assistant"):
|
| 342 |
+
with st.spinner("Reflecting..."):
|
| 343 |
+
try:
|
| 344 |
+
response = client.messages.create(
|
| 345 |
+
model="claude-3-opus-20240229",
|
| 346 |
+
messages=st.session_state.debrief_messages,
|
| 347 |
+
max_tokens=1000
|
| 348 |
+
)
|
| 349 |
+
assistant_response = response.content[0].text
|
| 350 |
+
st.markdown(assistant_response)
|
| 351 |
+
st.session_state.debrief_messages.append(
|
| 352 |
+
{"role": "assistant", "content": assistant_response}
|
| 353 |
+
)
|
| 354 |
+
except Exception as e:
|
| 355 |
+
st.error(f"An error occurred during debrief: {e}")
|
| 356 |
+
|
| 357 |
+
# Add button to start new session
|
| 358 |
+
col1, col2, col3 = st.columns([1, 2, 1])
|
| 359 |
+
with col2:
|
| 360 |
+
if st.button("Start New Practice Session", use_container_width=True):
|
| 361 |
+
st.session_state.clear()
|
| 362 |
+
st.rerun()
|
| 363 |
|
| 364 |
# Footer
|
| 365 |
st.markdown("---")
|