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("---")
|