Hello everyone,
I’m developing a Flask application that interacts with the OpenAI API to manage automated responses from a virtual assistant. However, I’m encountering an issue where the response_text remains empty after processing the assistant’s response events. Here is the general flow of my code:
-
Flask Endpoint: A user sends a message through a /chat endpoint.
-
Event Handling: I’m using a class EventHandler that extends OpenAI’s AssistantEventHandler to handle various event types (‘thread.run.requires_action’, ‘thread.message.delta’, ‘thread.message.completed’, etc.).
-
Response Accumulation: I’m attempting to accumulate the assistant’s response text in self.response_text within the EventHandler class.
-
Problem Encountered: Despite events appearing to process correctly (according to logs), response_text is empty at the end, resulting in the following error:
2024-08-25 07:54:47 [DEBUG] receive_response_body.complete
2024-08-25 07:54:47 [DEBUG] response_closed.started
2024-08-25 07:54:47 [DEBUG] response_closed.complete
2024-08-25 07:54:47 [DEBUG] Stream finished.
2024-08-25 07:54:47 [DEBUG] Final state of response_text after processing event: ‘’
2024-08-25 07:54:47 [DEBUG] receive_response_body.complete
2024-08-25 07:54:47 [DEBUG] response_closed.started
2024-08-25 07:54:47 [DEBUG] response_closed.complete
2024-08-25 07:54:47 [INFO] [DEBUG] Captured response text (final): ‘’
2024-08-25 07:54:47 [ERROR] Error receiving assistant response: The assistant’s response is empty.
Steps I Have Taken to Debug:
• I reviewed the logs and confirmed that the ‘thread.message.delta’ and ‘thread.message.completed’ events are received, but the text blocks (delta_block.text.value) appear to be empty or are not being accumulated correctly into self.response_text.
• I added detailed logging to verify the content of event.data, but there doesn’t seem to be content to accumulate into response_text.
Could anyone suggest how to ensure that response_text correctly accumulates the assistant’s responses or how to resolve the issue of the empty response_text? I would appreciate any help or suggestions.
// complete code
from flask import Flask, request, jsonify
import os
from openai import OpenAI
from typing_extensions import override
from openai import AssistantEventHandler
import requests
import json
import logging
Configuración inicial
api_key = "yor api key "
assistant_id = "assistant id "
port = 5000
Configuración de registro detallado
logging.basicConfig(filename=‘log.txt’, level=logging.DEBUG,
format=‘%(asctime)s [%(levelname)s] %(message)s’,
datefmt=‘%Y-%m-%d %H:%M:%S’)
logging.info(“Iniciando servidor…”)
if not api_key:
logging.error(“No se encontró la clave de API.”)
raise ValueError(“No se encontró la clave de API. Asegúrate de que ‘api_key’ esté configurada correctamente.”)
client = OpenAI(api_key=api_key)
app = Flask(name)
class EventHandler(AssistantEventHandler):
def init(self):
super().init()
self.run_id = None
self.response_text = “”
self.thread_id = None
logging.info(“EventHandler inicializado.”)
@override
def on_event(self, event):
logging.debug(f"Evento recibido: {event}")
logging.debug(f"Estado inicial de response_text: '{self.response_text}'")
if event.event == 'thread.run.requires_action':
self.run_id = event.data.id
self.thread_id = event.data.thread_id
logging.debug(f"Acción requerida. Run ID: {self.run_id}, Thread ID: {self.thread_id}")
self.handle_requires_action(event.data, self.run_id)
elif event.event == 'thread.message.delta':
if event.data.delta.content:
for delta_block in event.data.delta.content:
if delta_block.text and delta_block.text.value:
# Limpiar el texto de caracteres invisibles y agregar espacio si es necesario
text_value = delta_block.text.value.strip()
if self.response_text and not self.response_text.endswith(' '):
self.response_text += ' ' # Asegurar espacio entre concatenaciones
self.response_text += text_value
logging.debug(f"[DEBUG] Delta recibido: '{text_value}'")
logging.debug(f"[DEBUG] Texto acumulado hasta ahora: '{self.response_text}'")
elif event.event == 'thread.message.completed':
logging.debug(f"[DEBUG] Mensaje completado: {event.data.content}")
# Recolectar el contenido final del mensaje
for content_block in event.data.content:
if content_block.text and content_block.text.value:
text_value = content_block.text.value.strip()
if text_value not in self.response_text:
if self.response_text and not self.response_text.endswith(' '):
self.response_text += ' '
self.response_text += text_value
logging.info(f"[INFO] Respuesta final a enviar: '{self.response_text}'")
elif event.event == 'thread.run.error':
logging.error(f"Error en el evento: {event.data}")
logging.debug(f"Estado final de response_text después de procesar evento: '{self.response_text}'")
def handle_requires_action(self, data, run_id):
logging.debug("Manejando acción requerida.")
tool_outputs = []
for tool in data.required_action.submit_tool_outputs.tool_calls:
logging.debug(f"Procesando herramienta: {tool.function.name}")
if tool.function.name == "obtener_importe_credito":
argumentos = json.loads(tool.function.arguments)
dni = argumentos.get("dni")
celular = argumentos.get("celular")
logging.debug(f"Verificando crédito con DNI: {dni} y Celular: {celular}")
url = f"http://xxx.com/offer?Apellido=&Nombre=&CelularDeContacto={celular}
try:
response = requests.get(url)
response_data = response.json()
logging.debug(f"[DEBUG] Respuesta de la API: {response_data}")
if response_data and 'importe' in response_data[0]:
importe_mayor = response_data[0]['importe']
else:
importe_mayor = "0"
tool_output = {
"tool_call_id": tool.id,
"output": json.dumps({"credit_amount": importe_mayor, "status": "approved", "dni": dni, "celular": celular})
}
tool_outputs.append(tool_output)
logging.debug(f"Crédito procesado con éxito, importe mayor: {importe_mayor}")
except Exception as e:
logging.error(f"Error al realizar la solicitud HTTP: {str(e)}")
tool_output = {
"tool_call_id": tool.id,
"output": json.dumps({"credit_amount": "0", "status": "error", "dni": dni, "celular": celular})
}
tool_outputs.append(tool_output)
self.submit_tool_outputs(tool_outputs, run_id)
def submit_tool_outputs(self, tool_outputs, run_id):
logging.debug("Enviando salidas de herramientas.")
logging.debug(f"Thread ID: {self.thread_id}, Run ID: {run_id}")
try:
new_handler = EventHandler()
with client.beta.threads.runs.submit_tool_outputs_stream(
thread_id=self.thread_id,
run_id=run_id,
tool_outputs=tool_outputs,
event_handler=new_handler,
) as stream:
for text in stream.text_deltas:
cleaned_text = text.strip() # Limpiar el texto recibido
logging.debug(f"Texto del stream: {cleaned_text}")
logging.debug("Stream finalizado.")
except Exception as e:
logging.error(f"Error al enviar las salidas de herramientas: {str(e)}")
@app.route(‘/create_thread’, methods=[‘POST’])
def create_thread():
logging.info(“Creando nuevo hilo.”)
thread = client.beta.threads.create()
logging.info(f"Hilo creado con ID: {thread.id}")
return jsonify({“thread_id”: thread.id})
@app.route(‘/chat’, methods=[‘POST’])
def chat():
logging.info(“Inicio de nueva conversación.”)
data = request.json
thread_id = data.get(“thread_id”)
user_message = data.get(“message”).strip() # Limpiar el mensaje de entrada
if not user_message:
logging.error("Faltan parámetros: 'message' es obligatorio.")
return jsonify({"error": "Faltan parámetros: 'message' es obligatorio."}), 400
try:
client.beta.threads.messages.create(
thread_id=thread_id,
role="user",
content=user_message
)
logging.info(f"Mensaje del usuario agregado al hilo {thread_id}")
except Exception as e:
logging.error(f"Error al agregar mensaje al hilo: {str(e)}. Creando un nuevo hilo.")
thread = client.beta.threads.create()
thread_id = thread.id
logging.info(f"Nuevo hilo creado con ID: {thread_id}")
client.beta.threads.messages.create(
thread_id=thread_id,
role="user",
content=user_message
)
handler = EventHandler()
try:
with client.beta.threads.runs.stream(
thread_id=thread_id,
assistant_id=assistant_id,
event_handler=handler,
) as stream:
logging.info("Iniciando el stream de la respuesta del asistente...")
stream.until_done()
response_text = handler.response_text.strip()
logging.info(f"[DEBUG] Texto de respuesta capturado (final): '{response_text}'")
if not response_text:
raise ValueError("La respuesta del asistente está vacía.")
except Exception as e:
logging.error(f"Error al recibir la respuesta del asistente: {str(e)}")
return jsonify({"error": "Error al recibir la respuesta del asistente."}), 500
return jsonify({"response": response_text, "thread_id": thread_id})
if name == “main”:
logging.info(“Ejecutando servidor Flask.”)
app.run(host=“0.0.0.0”, port=port)