| import os |
| import asyncio |
| import json |
| import base64 |
| import requests |
| from fastapi import FastAPI, WebSocket, WebSocketDisconnect |
| from groq import Groq |
|
|
| app = FastAPI() |
|
|
| |
| GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "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") |
| SARVAM_API_KEY = os.environ.get("SARVAM_API_KEY", "sk_ibddkc1o_5ByGCOrIePXVZihmXubVUgTm") |
| groq_client = Groq(api_key=GROQ_API_KEY) |
|
|
| conversation_history = [ |
| {"role": "system", "content": "You are a real-time conversational voice assistant. Keep answers incredibly brief, punchy, and conversational."} |
| ] |
|
|
| @app.websocket("/conversational-stream") |
| async def websocket_endpoint(websocket: WebSocket): |
| await websocket.accept() |
| print("π Client connected to real-time voice stream.") |
| |
| |
| tts_task = None |
|
|
| try: |
| while True: |
| |
| data = await websocket.receive_text() |
| message = json.loads(data) |
| |
| |
| |
| |
| if message.get("type") == "INTERRUPT" or message.get("type") == "AUDIO_START": |
| if tts_task and not tts_task.done(): |
| tts_task.cancel() |
| print("π AI Interrupted by user speech! Stopping current audio output.") |
| if message.get("type") == "INTERRUPT": |
| continue |
|
|
| |
| if message.get("type") == "TEXT_INPUT": |
| user_text = message.get("text") |
| conversation_history.append({"role": "user", "content": user_text}) |
| |
| |
| completion = groq_client.chat.completions.create( |
| model="openai/gpt-oss-20b", |
| messages=conversation_history |
| ) |
| ai_text = completion.choices[0].message.content |
| conversation_history.append({"role": "assistant", "content": ai_text}) |
| |
| |
| tts_task = asyncio.create_task(stream_tts_to_client(websocket, ai_text)) |
|
|
| except WebSocketDisconnect: |
| print("π Client disconnected.") |
| except asyncio.CancelledError: |
| pass |
|
|
| async def stream_tts_to_client(websocket: WebSocket, text: str): |
| """Generates audio via Sarvam and pushes chunks down the WebSocket securely.""" |
| try: |
| |
| headers = {"api-subscription-key": SARVAM_API_KEY, "Content-Type": "application/json"} |
| payload = {"text": text, "voice": "bulbul:v3", "language_code": "en-IN", "output_format": "wav"} |
| |
| response = requests.post("https://api.sarvam.ai/text-to-speech", headers=headers, json=payload) |
| if response.status_code == 200: |
| audio_base64 = response.json().get("audios", [None])[0] |
| if audio_base64: |
| |
| |
| audio_bytes = base64.b64decode(audio_base64) |
| chunk_size = 4096 |
| |
| for i in range(0, len(audio_bytes), chunk_size): |
| chunk = audio_bytes[i:i+chunk_size] |
| await websocket.send_json({ |
| "type": "AUDIO_CHUNK", |
| "audio": base64.b64encode(chunk).decode('utf-8') |
| }) |
| |
| await asyncio.sleep(0.01) |
| |
| await websocket.send_json({"type": "AUDIO_END"}) |
| except asyncio.CancelledError: |
| |
| print("π§Ό TTS Streaming task cleanly aborted.") |