-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
207 lines (167 loc) · 8.3 KB
/
Copy pathapp.py
File metadata and controls
207 lines (167 loc) · 8.3 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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import os
import asyncio
import json
import base64
from dotenv import load_dotenv
from quart import Quart, Response, request, websocket
import websockets
import google.generativeai as genai
from pinecone import Pinecone, ServerlessSpec
# Load environment variables
load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
GENAI_API_KEY = os.getenv("GENAI_API_KEY")
PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
PLIVO_AUTH_ID = os.getenv("PLIVO_AUTH_ID")
PLIVO_AUTH_TOKEN = os.getenv("PLIVO_AUTH_TOKEN")
PLIVO_FROM_NUMBER = os.getenv("PLIVO_FROM_NUMBER")
if not OPENAI_API_KEY or not GENAI_API_KEY or not PINECONE_API_KEY:
raise ValueError("Missing required API keys (OPENAI_API_KEY, GENAI_API_KEY, PINECONE_API_KEY)")
# Configure Gemini and Pinecone
genai.configure(api_key=GENAI_API_KEY)
pc = Pinecone(api_key=PINECONE_API_KEY)
INDEX_NAME = "voice-bot-gemini-embedding-004-index"
if INDEX_NAME not in pc.list_indexes().names():
pc.create_index(
name=INDEX_NAME,
dimension=768,
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1"),
)
index = pc.Index(INDEX_NAME)
app = Quart(__name__)
PORT = 5000
SYSTEM_MESSAGE = "You are a helpful and friendly AI assistant with access to product-related knowledge."
# Helper: request LLM Gemini to summarize answer based on context
def request_llm_to_get_summarize(query: str, context: str) -> str:
prompt = f"""
You are a highly accurate and detail-oriented question-answering assistant. Respond based only on these product search results:
{context}
User question: {query}
Provide a clear and concise answer using only the given info.
"""
response = genai.GenerativeModel("gemini-1.5-flash").generate_content(prompt)
return response.text
# Async handler: search vector DB and retrieve answer
async def get_vector_search_answer(user_question: str, namespace: str = "") -> str:
# Get query embedding
query_response = genai.embed_content(model="models/text-embedding-004", content=user_question)
query_embedding = query_response["embedding"]
# Query Pinecone
search_results = index.query(vector=query_embedding, namespace=namespace, top_k=5, include_metadata=True)
context_lst = []
for match in search_results["matches"]:
chunk_text = match["metadata"]["text"]
context_lst.append(chunk_text)
context = "\n-----------------\n".join(context_lst)
answer = request_llm_to_get_summarize(user_question, context)
return answer
@app.route("/webhook", methods=["GET", "POST"])
async def webhook():
caller_number = request.args.get("From", "Unknown")
print(f"Incoming call from: {caller_number}")
stream_url = f"ws://{request.host}/media-stream"
xml_data = f"""<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Speak voice="Polly.Amy">Welcome to NinjaGenie! Please ask anything you want to know!</Speak>
<Stream streamTimeout="86400" keepCallAlive="true" bidirectional="true" contentType="audio/x-mulaw;rate=8000" audioTrack="inbound">
{stream_url}
</Stream>
</Response>
"""
return Response(xml_data, mimetype="application/xml")
@app.websocket("/media-stream")
async def media_stream():
print("Plivo client connected")
plivo_ws = websocket
url = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01"
headers = {
"Authorization": f"Bearer {OPENAI_API_KEY}",
"OpenAI-Beta": "realtime=v1",
}
try:
async with websockets.connect(url, extra_headers=headers) as openai_ws:
print("Connected to OpenAI real-time API")
# Send initial session update i have to send the tools in this session update
# to find where the openai is responsding.
# In that response i will have the function based on that function i will call the corresponding function or tool.
#
session_update = {
"type": "session.update",
"session": {
"turn_detection": {"type": "server_vad"},
"input_audio_format": "g711_ulaw",
"output_audio_format": "g711_ulaw",
"voice": "alloy",
"instructions": SYSTEM_MESSAGE,
"modalities": ["text", "audio"],
"temperature": 0.8
}
}
await openai_ws.send(json.dumps(session_update))
async def receive_from_plivo():
while True:
message = await plivo_ws.receive()
data = json.loads(message)
if data.get("event") == "media" and openai_ws.open:
audio_append = {
"type": "input_audio_buffer.append",
"audio": data["media"]["payload"]
}
await openai_ws.send(json.dumps(audio_append))
elif data.get("event") == "start":
print("Plivo Audio streaming started")
plivo_ws.stream_id = data["start"]["streamId"]
async def receive_from_openai():
async for message in openai_ws:
response = json.loads(message)
# Handle session updated
if response.get("type") == "session.updated":
print("OpenAI session updated")
# Handle audio delta response to send to Plivo
elif response.get("type") == "response.audio.delta":
audio_delta = {
"event": "playAudio",
"media": {
"contentType": "audio/x-mulaw",
"sampleRate": 8000,
"payload": base64.b64encode(base64.b64decode(response["delta"])).decode('utf-8')
}
}
await plivo_ws.send(json.dumps(audio_delta))
# Handle text transcript for product query detection
elif response.get("type") == "input_audio_buffer.transcript":
transcript = response.get("transcript", "")
print(f"Transcript received: {transcript}")
# Here you can detect product-related queries or commands and call vector search
if transcript.lower().startswith("product:") or "price" in transcript.lower():
# Remove "product:" prefix if present
query = transcript
if transcript.lower().startswith("product:"):
query = transcript[len("product:"):].strip()
# Replace with namespace or default as needed
namespace = "Tec Nviirons Sample data testing.xlsx 2025-05-02 09:05:24"
# Call vector search answer handler
answer = await get_vector_search_answer(query, namespace)
# For now, just log the answer since sending to OpenAI websocket as user prompt is invalid
print(f"Vector search answer: {answer}")
# TODO: Integrate TTS and send as audio delta to Plivo websocket for user playback
# Handle speech start event to clear audio buffer if needed
elif response.get("type") == "input_audio_buffer.speech_started":
print("Speech detected, clearing audio buffer")
clear_audio = {
"event": "clearAudio",
"stream_id": getattr(plivo_ws, "stream_id", None)
}
await plivo_ws.send(json.dumps(clear_audio))
# Handle response cancellation or other types as needed
await asyncio.gather(receive_from_plivo(), receive_from_openai())
except Exception as e:
print(f"Error in OpenAI WebSocket: {e}")
except websockets.ConnectionClosed:
print("OpenAI server closed connection")
except asyncio.CancelledError:
print("Cancelled error")
if __name__ == "__main__":
print("Starting server...")
app.run(host="0.0.0.0", port=PORT)