-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackend_services.py
More file actions
159 lines (134 loc) · 6.01 KB
/
Copy pathbackend_services.py
File metadata and controls
159 lines (134 loc) · 6.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import json
import httpx
from config import GOOGLE_API_KEY, ELEVENLABS_API_KEY
from models import ChatRequest, ChatResponse, SpeechRequest, GroundingSource
# Defensive Import for Gemini
# Defensive Import for Gemini
try:
# from google import genai
# HAS_GEMINI = True
print("DEBUG: Force-disabling Gemini for isolation testing.", flush=True)
HAS_GEMINI = False
except ImportError as e:
print(f"CRITICAL WARNING: Could not import google.genai: {e}")
HAS_GEMINI = False
# --- Gemini Service ---
class GeminiService:
def __init__(self):
self.client = None
if not HAS_GEMINI:
print("GeminiService: Library missing. Chat disabled.")
return
if GOOGLE_API_KEY:
try:
self.client = genai.Client(api_key=GOOGLE_API_KEY)
self.model_name = os.getenv("GEMINI_MODEL_ID", "gemini-2.0-flash")
except Exception as e:
print(f"Gemini Client Init Warning: {e}")
self.client = None
else:
print("Gemini Service Warning: GOOGLE_API_KEY not set. Chat will fail.")
self.client = None
async def generate_response(self, request: ChatRequest) -> ChatResponse:
if not self.client:
return ChatResponse(text="I cannot speak. My mind key (GOOGLE_API_KEY) is missing.", sources=[])
immersive_wrapper = f"""
Role: {request.system_instruction}
Constraint: 2 sentences max. No markdown. Never break character.
"""
# Convert history to format expected by Google GenAI SDK if needed,
# or pass primarily as contents. The SDK usually expects a specific structure.
# Based on SDK usage: contents=[...] where items are dicts or Content objects
contents = []
for msg in request.history:
contents.append({
"role": msg.role,
"parts": [{"text": part.text} for part in msg.parts]
})
contents.append({
"role": "user",
"parts": [{"text": request.prompt}]
})
try:
response = self.client.models.generate_content(
model=self.model_name,
contents=contents,
config={
"system_instruction": immersive_wrapper,
"temperature": 0.7,
"tools": [{"google_search": {}}]
}
)
text = response.text or "I am momentarily speechless..."
sources = []
if response.candidates and response.candidates[0].grounding_metadata:
chunks = response.candidates[0].grounding_metadata.grounding_chunks or []
for chunk in chunks:
if chunk.web and chunk.web.uri and chunk.web.title:
sources.append(GroundingSource(title=chunk.web.title, url=chunk.web.uri))
return ChatResponse(text=text, sources=sources)
except Exception as e:
print(f"Gemini Service Error: {e}")
# Identify fallback
return ChatResponse(
text="The temporal link is failing. I cannot hear you clearly.",
sources=[]
)
# --- Gemini Voice Service ---
class GeminiVoiceService:
def __init__(self):
self.api_key = GOOGLE_API_KEY or os.getenv("GOOGLE_API_KEY")
async def generate_speech(self, request: SpeechRequest) -> bytes:
clean_text = request.text.replace("*", "").strip()
voice_id = request.voice_id
if not self.api_key:
raise Exception("GOOGLE_API_KEY missing for speech generation")
try:
from google import genai
client = genai.Client(api_key=self.api_key)
voice_map = {
"UGTtbzgh3HObxRjWaSpr": "Puck", # Einstein
"4RZ84U1b4WCqpu57LvIq": "Aoede", # Cleopatra
"IRHApOXLvnW57QJPQH2P": "Charon", # Da Vinci
"E4IXevHtHpKGh0bvrPPr": "Kore" # Lovelace
}
voice_name = voice_map.get(voice_id, "Fenrir")
print(f"DEBUG: Generating TTS with voice: {voice_name}")
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=clean_text,
config={
"response_modalities": ["AUDIO"],
"speech_config": {
"voice_config": {
"prebuilt_voice_config": {
"voice_name": voice_name
}
}
}
}
)
audio_data = None
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
for part in response.candidates[0].content.parts:
if part.inline_data:
audio_data = part.inline_data.data
break
if not audio_data:
raise Exception("No audio data returned by Gemini.")
if isinstance(audio_data, str):
import base64
audio_data = base64.b64decode(audio_data)
# Gemini returns raw 16-bit PCM at 24000Hz. Add a proper WAV header so the browser can decode it.
import io, wave
with io.BytesIO() as wav_io:
with wave.open(wav_io, 'wb') as wav_file:
wav_file.setnchannels(1)
wav_file.setsampwidth(2)
wav_file.setframerate(24000)
wav_file.writeframes(audio_data)
audio_data = wav_io.getvalue()
return audio_data
except Exception as e:
print(f"CRITICAL GEMINI VOICE EXCEPTION: {str(e)}", flush=True)
raise e