pipV1 / services /anthropic_client.py
Itsjustamit's picture
some updates
cd40a43 verified
"""
Anthropic Claude client for Pip's emotional intelligence.
Handles: Emotion analysis, action decisions, intervention logic.
"""
import os
import json
from typing import AsyncGenerator
import anthropic
class AnthropicClient:
"""Claude-powered emotional intelligence for Pip."""
def __init__(self, api_key: str = None):
"""Initialize with optional custom API key."""
self.api_key = api_key or os.getenv("ANTHROPIC_API_KEY")
self.available = bool(self.api_key)
if self.available:
self.client = anthropic.Anthropic(api_key=self.api_key)
self.async_client = anthropic.AsyncAnthropic(api_key=self.api_key)
else:
self.client = None
self.async_client = None
print("⚠️ Anthropic: No API key found - service disabled")
self.model = "claude-sonnet-4-20250514"
def is_available(self) -> bool:
"""Check if the client is available."""
return self.available
async def analyze_emotion(self, user_input: str, system_prompt: str) -> dict:
"""
Analyze user's emotional state with nuance.
Returns structured emotion data.
"""
if not self.available or not self.async_client:
return {
"primary_emotions": ["neutral"],
"intensity": 5,
"pip_expression": "neutral",
"intervention_needed": False
}
response = await self.async_client.messages.create(
model=self.model,
max_tokens=1024,
system=system_prompt,
messages=[
{"role": "user", "content": user_input}
]
)
# Parse JSON response
try:
content = response.content[0].text
# Try to extract JSON from the response
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
return json.loads(content.strip())
except (json.JSONDecodeError, IndexError):
# Fallback if JSON parsing fails
return {
"primary_emotions": ["neutral"],
"intensity": 5,
"concerning_flags": [],
"underlying_needs": ["conversation"],
"pip_expression": "neutral",
"intervention_needed": False,
"raw_response": response.content[0].text
}
async def decide_action(self, emotion_state: dict, system_prompt: str) -> dict:
"""
Decide what action Pip should take based on emotional state.
"""
if not self.available or not self.async_client:
return {
"action": "reflect",
"image_style": "gentle",
"voice_tone": "warm"
}
response = await self.async_client.messages.create(
model=self.model,
max_tokens=512,
system=system_prompt,
messages=[
{"role": "user", "content": f"Emotion state: {json.dumps(emotion_state)}"}
]
)
try:
content = response.content[0].text
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
return json.loads(content.strip())
except (json.JSONDecodeError, IndexError):
return {
"action": "reflect",
"image_style": "gentle",
"voice_tone": "warm",
"raw_response": response.content[0].text
}
async def generate_response_stream(
self,
user_input: str,
emotion_state: dict,
action: dict,
system_prompt: str,
conversation_history: list = None
) -> AsyncGenerator[str, None]:
"""
Generate Pip's conversational response with streaming.
"""
if not self.available or not self.async_client:
yield "I'm here with you. Let me think about what you shared..."
return
messages = conversation_history or []
# Add context about current emotional state
context = f"""
[Current emotional context]
User's emotions: {emotion_state.get('primary_emotions', [])}
Intensity: {emotion_state.get('intensity', 5)}/10
Action to take: {action.get('action', 'reflect')}
Voice tone: {action.get('voice_tone', 'warm')}
[User's message]
{user_input}
"""
messages.append({"role": "user", "content": context})
async with self.async_client.messages.stream(
model=self.model,
max_tokens=1024,
system=system_prompt,
messages=messages
) as stream:
async for text in stream.text_stream:
yield text
async def generate_intervention_response(
self,
user_input: str,
emotion_state: dict,
system_prompt: str
) -> AsyncGenerator[str, None]:
"""
Generate a gentle intervention response for concerning emotional states.
"""
if not self.available or not self.async_client:
yield "I hear you, and I want you to know that what you're feeling matters. Take a moment to breathe..."
return
context = f"""
[INTERVENTION NEEDED]
User message: {user_input}
Detected emotions: {emotion_state.get('primary_emotions', [])}
Intensity: {emotion_state.get('intensity', 5)}/10
Concerning flags: {emotion_state.get('concerning_flags', [])}
Remember: Acknowledge briefly, then gently introduce curiosity/wonder.
Do NOT be preachy or clinical.
"""
async with self.async_client.messages.stream(
model=self.model,
max_tokens=1024,
system=system_prompt,
messages=[{"role": "user", "content": context}]
) as stream:
async for text in stream.text_stream:
yield text
async def generate_text(self, prompt: str) -> str:
"""
Generate text response for a given prompt.
Used for summaries and other text generation needs.
"""
if not self.available or not self.async_client:
return ""
try:
response = await self.async_client.messages.create(
model=self.model,
max_tokens=500,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
except Exception as e:
print(f"Claude text generation error: {e}")
return ""