Запись входящих вызовов Twilio - продолжает звонить «входящий вызов» и «запись обратного вызова»Python

Программы на Python
Ответить Пред. темаСлед. тема
Anonymous
 Запись входящих вызовов Twilio - продолжает звонить «входящий вызов» и «запись обратного вызова»

Сообщение Anonymous »

Я пытаюсь использовать официальное руководство по API реального времени Twilio OpenAI для агентов на основе LLM в реальном времени.
Теперь, когда я пытаюсь внедрить запись звонков, чтобы иметь возможность анализировать кода после этого я продолжаю получать бесконечный цикл HTTPS-запросов (для одного и того же вызова) от /incoming-call до /recording-callback, как показано ниже (все эти HTTPS-запросы были выполнены для одного телефонного звонка:
% uvicorn main:app --host 0.0.0.0 --port 8082
INFO: Started server process [11841]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8082 (Press CTRL+C to quit)
event
Host URL is XXXXX.ngrok-free.app
INFO: 34.227.88.132:0 - "POST /incoming-call HTTP/1.1" 200 OK
event
Host URL is XXXXX.ngrok-free.app
INFO: 34.239.115.233:0 - "POST /incoming-call HTTP/1.1" 200 OK
INFO: 54.163.201.30:0 - "POST /recording-callback HTTP/1.1" 200 OK
event
Host URL is XXXXX.ngrok-free.app
INFO: 54.144.122.199:0 - "POST /incoming-call HTTP/1.1" 200 OK
INFO: 3.89.62.101:0 - "POST /recording-callback HTTP/1.1" 200 OK

Перед вызовом response.record внутри /incoming-call код перейдет в медиапоток и будет работать правильно. Однако теперь по непонятной причине он застрял между «входящим вызовом» и «записью обратного вызова».

Есть идеи или помощь? Спасибо.
import os
import json
import base64
import asyncio
import websockets
import requests
from fastapi import FastAPI, WebSocket, Request, Form
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.websockets import WebSocketDisconnect
from twilio.twiml.voice_response import VoiceResponse, Connect, Say, Stream
from dotenv import load_dotenv

load_dotenv()

# Configuration
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
# PORT = int(os.getenv('PORT', 5050))
PORT = 8082
SYSTEM_MESSAGE = """Example Prompt."""

VOICE = 'ash'
LOG_EVENT_TYPES = [
'error', 'response.content.done', 'rate_limits.updated',
'response.done', 'input_audio_buffer.committed',
'input_audio_buffer.speech_stopped', 'input_audio_buffer.speech_started',
'session.created', 'conversation.item.input_audio_transcription.completed'
]
SHOW_TIMING_MATH = False

app = FastAPI()

if not OPENAI_API_KEY:
raise ValueError('Missing the OpenAI API key. Please set it in the .env file.')

@app.get("/", response_class=JSONResponse)
async def index_page():
return {"message": "Twilio Media Stream Server is running!"}

@app.api_route("/incoming-call", methods=["GET", "POST"])
async def handle_incoming_call(request: Request):
"""Handle incoming call and return TwiML response to connect to Media Stream."""
print("event")
response = VoiceResponse()
# punctuation to improve text-to-speech flow
# response.say("Please wait while we connect your call")
response.pause(length=1)
# response.say("O.K. you can start talking!")
host = request.url.hostname
print(f"Host URL is {host}")

response.record(
recording_status_callback=f'https://{host}/recording-callback',
recording_status_callback_method="POST",
play_beep=True
)

connect = Connect()
connect.stream(url=f'wss://{host}/media-stream')
response.append(connect)
return HTMLResponse(content=str(response), media_type="application/xml")

@app.websocket("/media-stream")
async def handle_media_stream(websocket: WebSocket):
"""Handle WebSocket connections between Twilio and OpenAI."""
print("Client connected")
await websocket.accept()

async with websockets.connect(
'wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01',
extra_headers={
"Authorization": f"Bearer {OPENAI_API_KEY}",
"OpenAI-Beta": "realtime=v1"
}
) as openai_ws:
await initialize_session(openai_ws)

# Connection specific state
stream_sid = None
latest_media_timestamp = 0
last_assistant_item = None
mark_queue = []
response_start_timestamp_twilio = None

async def receive_from_twilio():
"""Receive audio data from Twilio and send it to the OpenAI Realtime API."""
nonlocal stream_sid, latest_media_timestamp
try:
async for message in websocket.iter_text():
data = json.loads(message)
if data['event'] == 'media' and openai_ws.open:
latest_media_timestamp = int(data['media']['timestamp'])
audio_append = {
"type": "input_audio_buffer.append",
"audio": data['media']['payload']
}
await openai_ws.send(json.dumps(audio_append))
elif data['event'] == 'start':
stream_sid = data['start']['streamSid']
print(f"Incoming stream has started {stream_sid}")
response_start_timestamp_twilio = None
latest_media_timestamp = 0
last_assistant_item = None
elif data['event'] == 'mark':
if mark_queue:
mark_queue.pop(0)
except WebSocketDisconnect:
print("Client disconnected.")
if openai_ws.open:
await openai_ws.close()

async def send_to_twilio():
"""Receive events from the OpenAI Realtime API, send audio back to Twilio."""
nonlocal stream_sid, last_assistant_item, response_start_timestamp_twilio
try:
async for openai_message in openai_ws:
response = json.loads(openai_message)
if response['type'] in LOG_EVENT_TYPES:
print(f"Received event: {response['type']}", response)

if response.get('type') == 'response.audio.delta' and 'delta' in response:
audio_payload = base64.b64encode(base64.b64decode(response['delta'])).decode('utf-8')
audio_delta = {
"event": "media",
"streamSid": stream_sid,
"media": {
"payload": audio_payload
}
}
await websocket.send_json(audio_delta)

if response_start_timestamp_twilio is None:
response_start_timestamp_twilio = latest_media_timestamp
if SHOW_TIMING_MATH:
print(f"Setting start timestamp for new response: {response_start_timestamp_twilio}ms")

# Update last_assistant_item safely
if response.get('item_id'):
last_assistant_item = response['item_id']

await send_mark(websocket, stream_sid)

# Trigger an interruption. Your use case might work better using `input_audio_buffer.speech_stopped`, or combining the two.
if response.get('type') == 'input_audio_buffer.speech_started':
print("Speech started detected.")
if last_assistant_item:
print(f"Interrupting response with id: {last_assistant_item}")
await handle_speech_started_event()
if response.get('type') == "conversation.item.input_audio_transcription.completed":
print("Input Audio Transcription Completed Message")
print(f" Id: {response.get('item_id')}")
print(f" Content Index: {response.get('content_index')}")
print(f" Transcript: {response.get('transcript')}")
except Exception as e:
print(f"Error in send_to_twilio: {e}")

async def handle_speech_started_event():
"""Handle interruption when the caller's speech starts."""
nonlocal response_start_timestamp_twilio, last_assistant_item
print("Handling speech started event.")
if mark_queue and response_start_timestamp_twilio is not None:
elapsed_time = latest_media_timestamp - response_start_timestamp_twilio
if SHOW_TIMING_MATH:
print(f"Calculating elapsed time for truncation: {latest_media_timestamp} - {response_start_timestamp_twilio} = {elapsed_time}ms")

if last_assistant_item:
if SHOW_TIMING_MATH:
print(f"Truncating item with ID: {last_assistant_item}, Truncated at: {elapsed_time}ms")

truncate_event = {
"type": "conversation.item.truncate",
"item_id": last_assistant_item,
"content_index": 0,
"audio_end_ms": elapsed_time
}
await openai_ws.send(json.dumps(truncate_event))

await websocket.send_json({
"event": "clear",
"streamSid": stream_sid
})

mark_queue.clear()
last_assistant_item = None
response_start_timestamp_twilio = None

async def send_mark(connection, stream_sid):
if stream_sid:
mark_event = {
"event": "mark",
"streamSid": stream_sid,
"mark": {"name": "responsePart"}
}
await connection.send_json(mark_event)
mark_queue.append('responsePart')

await asyncio.gather(receive_from_twilio(), send_to_twilio())

async def send_initial_conversation_item(openai_ws):
"""Send initial conversation item if AI talks first."""
initial_conversation_item = {
"type": "conversation.item.create",
"item": {
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": SYSTEM_MESSAGE
}
]
}
}
await openai_ws.send(json.dumps(initial_conversation_item))
await openai_ws.send(json.dumps({"type": "response.create"}))

async def initialize_session(openai_ws):
"""Control initial session with OpenAI."""
session_update = {
"type": "session.update",
"session": {
"turn_detection": {"type": "server_vad"},
"input_audio_format": "g711_ulaw",
"output_audio_format": "g711_ulaw",
"voice": VOICE,
"instructions": SYSTEM_MESSAGE,
"modalities": ["text", "audio"],
"temperature": 0.6,
"input_audio_transcription": {
"model": "whisper-1"
},
}
}
print('Sending session update:', json.dumps(session_update))
await openai_ws.send(json.dumps(session_update))
await send_initial_conversation_item(openai_ws)

@app.api_route("/recording-callback", methods=["GET", "POST"])
async def recording_callback(
RecordingSid: str = Form(...),
RecordingUrl: str = Form(...),
RecordingStatus: str = Form(...)
):
print("IN RECORDING")
"""Handle Twilio's recording status callback."""
if RecordingStatus == "completed":
# Download the recording and save it locally
file_path = f"./recordings/{RecordingSid}.mp3"
os.makedirs(os.path.dirname(file_path), exist_ok=True)
try:
response = requests.get(f"{RecordingUrl}.mp3")
with open(file_path, "wb") as file:
file.write(response.content)
print(f"Recording saved: {file_path}")
except Exception as e:
print(f"Failed to download recording: {e}")

return JSONResponse({"status": "success"})

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port="8082")


Подробнее здесь: https://stackoverflow.com/questions/792 ... ording-cal
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Python»