Capítulo 6: Llamada a funciones y uso de herramientas
6.5 Descripción General de la API de Respuestas
Al interactuar con la API de Chat Completions, entender el manejo de respuestas es crucial para construir aplicaciones robustas. Exploremos por qué esto es importante y cómo funciona en detalle:
Primero, enviar solicitudes es solo la mitad de la ecuación - el verdadero poder radica en manejar adecuadamente las respuestas. La API devuelve un objeto de respuesta bien estructurado que contiene varios componentes clave:
- Texto Generado: La salida principal del modelo es el contenido central de la respuesta. Puede tomar varias formas:
- Respuestas conversacionales: Diálogo natural y respuestas interactivas
- Perspectivas analíticas: Análisis de datos, explicaciones e interpretaciones
- Contenido creativo: Historias, artículos u otro texto generado
- Resultados de resolución de problemas: Código, soluciones matemáticas o razonamiento lógico
- Metadatos: Información técnica esencial sobre la interacción, incluyendo:
- Estadísticas de uso de tokens para monitorear costos: Rastrea tokens de entrada, tokens de completado y uso total para facturación y optimización
- Marcas de tiempo de procesamiento: Registra cuándo se recibió, procesó y completó la solicitud
- Parámetros específicos del modelo utilizados: Documenta la temperatura, top_p, penalización de frecuencia y otras configuraciones
- Detalles de formato de respuesta: Información sobre cómo se estructuró y formateó la salida
- Llamadas a Funciones: Cuando se habilita la llamada a funciones, la respuesta incluye:
- Nombres y descripciones de funciones
- Parámetros requeridos y opcionales
- Formatos de salida esperados
- Estado de ejecución y resultados
- Indicadores de Estado: Retroalimentación completa sobre la generación de respuesta:
- Razón de finalización: Indica si la respuesta fue completa ("stop"), alcanzó límites de tokens ("length"), o necesitó llamadas a funciones
- Estados de error: Cualquier problema encontrado durante el procesamiento
- Métricas de calidad: Puntuaciones de confianza u otras mediciones relevantes
En esta sección, profundizaremos en cada uno de estos componentes, mostrando ejemplos prácticos de cómo extraer, procesar y utilizar estos datos de manera efectiva en sus aplicaciones. Entender estos elementos es esencial para construir sistemas confiables y listos para producción que puedan manejar casos extremos y proporcionar experiencias óptimas al usuario.
6.5.1 Comprendiendo la Estructura de Respuesta de la API
Cuando envía una solicitud a la API de Chat Completions, recibirá un objeto JSON de respuesta completo que contiene varios componentes cruciales. Esta estructura de respuesta está cuidadosamente diseñada para proporcionar no solo la salida del modelo, sino también metadatos importantes sobre la interacción.
La respuesta incluye información detallada sobre el uso de tokens, estado de procesamiento y cualquier llamada a función que se haya activado. También contiene métricas de calidad y datos de manejo de errores que ayudan a garantizar un rendimiento robusto de la aplicación. Exploremos cada uno de estos componentes en detalle, entendiendo cómo trabajan juntos para proporcionar una imagen completa de la interacción con la API:
choices:
Este es un array que sirve como contenedor principal para las respuestas del modelo. Puede contener múltiples respuestas si ha solicitado alternativas. La estructura del array permite recibir múltiples completados de una sola llamada a la API, lo cual es útil para generar opciones diversas o realizar pruebas A/B de respuestas.
- Cada elemento en el array contiene un campo message - aquí es donde encontrará el texto de salida real generado por el modelo. Por ejemplo:
response["choices"][0]["message"]["content"] # Accessing the first response
response["choices"][1]["message"]["content"] # Accessing the second response (if n>1)
- El campo message es versátil - puede contener respuestas de texto estándar, llamadas a funciones para ejecutar acciones específicas, o incluso formatos especializados según los parámetros de tu solicitud. Por ejemplo:
# Standard text response
{"message": {"role": "assistant", "content": "Hello! How can I help you?"}}
# Function call response
{"message": {"role": "assistant", "function_call": {
"name": "get_weather",
"arguments": "{\"location\": \"London\", \"unit\": \"celsius\"}"
}}}
Los metadatos adicionales en cada opción proporcionan información crucial sobre la respuesta:
- index: La posición de esta opción en el array
- finish_reason: Indica por qué el modelo dejó de generar ("stop", "length", "function_call", etc.)
- logprobs: Información opcional de probabilidad logarítmica cuando se solicita
usage:
Este campo vital te ayuda a monitorear y optimizar tu consumo de la API proporcionando estadísticas detalladas del uso de tokens. Actúa como un sistema integral de seguimiento que permite a los desarrolladores entender exactamente cómo sus solicitudes a la API están utilizando los recursos del modelo.
Desglosa el uso de tokens en tres métricas clave:
prompt_tokens
: El número de tokens en tu entrada. Considera un prompt básico como "Traduce 'Hola' al español" que podría usar 5-6 tokens, mientras que un prompt complejo de varios párrafos podría usar cientos de tokens.completion_tokens
: El número de tokens en la respuesta del modelo. Una traducción simple podría usar 1-2 tokens, mientras que un análisis detallado podría usar varios cientos de tokens.total_tokens
: La suma de tokens del prompt y de la completación. Por ejemplo, si tu prompt usa 50 tokens y la respuesta usa 150 tokens, tu uso total sería de 200 tokens.
Entender estas métricas es crucial para gestionar costos y asegurar un uso eficiente de la API en tus aplicaciones:
- Planificación de Presupuesto: Al monitorear el uso de tokens, puedes estimar costos con mayor precisión. Por ejemplo, si sabes que tu solicitud promedio usa 200 tokens en total, puedes multiplicar esto por tu volumen esperado de solicitudes y el precio por token.
- Oportunidades de Optimización: Un alto número de prompt_tokens podría indicar oportunidades para hacer tus prompts más concisos, mientras que un alto número de completion_tokens podría sugerir añadir restricciones más específicas a tus solicitudes.
- Arquitectura del Sistema: Estas métricas ayudan a informar decisiones sobre estrategias de caché y si conviene agrupar ciertos tipos de solicitudes.
finish_reason:
Este campo proporciona contexto importante sobre cómo y por qué el modelo completó su respuesta. Los valores comunes incluyen:
"stop"
: Completación natural de la respuesta - indica que el modelo alcanzó un punto de parada natural o encontró una secuencia de parada. Por ejemplo, al responder una pregunta como "¿Cuánto es 2+2?", el modelo podría responder "4" y detenerse naturalmente."length"
: La respuesta alcanzó el límite de tokens - significa que la salida del modelo fue truncada debido a que alcanzó el máximo de tokens permitidos. Por ejemplo, si estableces max_tokens=50 pero la respuesta necesita más tokens para completarse, se detendrá en 50 tokens y devolverá "length" como razón de finalización."function_call"
: El modelo solicitó llamar a una función - indica que el modelo determinó que necesita ejecutar una función para proporcionar la respuesta apropiada. Por ejemplo, si se le pregunta "¿Cuál es el clima en París?", el modelo podría solicitar llamar a una función get_weather()."content_filter"
: La respuesta fue filtrada debido a la política de contenido - ocurre cuando el contenido generado activa los filtros de la API.
Esta información es esencial para el manejo de errores, la validación de respuestas y determinar si necesitas ajustar los parámetros de tu solicitud. Aquí te mostramos cómo podrías manejar diferentes razones de finalización:
def handle_response(response):
finish_reason = response.choices[0].finish_reason
if finish_reason == "length":
# Consider increasing max_tokens or breaking request into smaller chunks
print("Response was truncated. Consider increasing max_tokens.")
elif finish_reason == "function_call":
# Execute the requested function
function_call = response.choices[0].message.function_call
handle_function_call(function_call)
elif finish_reason == "content_filter":
# Handle filtered content appropriately
print("Response was filtered. Please modify your prompt.")
Comprender la razón de finalización te ayuda a implementar mecanismos de respaldo adecuados y asegurar que tu aplicación maneje efectivamente todos los posibles escenarios de respuesta. Por ejemplo:
- Si finish_reason es "length", podrías querer hacer una solicitud adicional para obtener el contenido restante
- Si finish_reason es "function_call", deberías ejecutar la función solicitada y continuar la conversación con el resultado de la función
- Si finish_reason es "content_filter", podrías necesitar modificar tu prompt o implementar mensajes de error apropiados
6.5.2 Análisis de la Respuesta
Exploremos un ejemplo práctico que demuestra cómo manejar los datos de respuesta en Python. Examinaremos paso a paso cómo extraer, analizar y procesar los diversos componentes de una respuesta de la API, incluyendo el contenido del mensaje, metadatos e información sobre el uso de tokens. Este ejemplo te ayudará a entender la implementación práctica del manejo de respuestas en tus aplicaciones.
Ejemplo: Análisis Básico de una Respuesta de Chat Completion
import openai
import os
from dotenv import load_dotenv
# Load API key from your secure environment file.
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
# Send a request with a basic conversation.
messages = [
{"role": "system", "content": "You are a knowledgeable assistant."},
{"role": "user", "content": "What is the current temperature in Paris?"}
]
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=messages,
max_tokens=100,
temperature=0.5
)
# Access the first choice in the response.
choice = response["choices"][0]
# Extract message content.
output_text = choice["message"]["content"]
finish_reason = choice.get("finish_reason")
usage_data = response.get("usage", {})
print("Generated Response:")
print(output_text)
print("\nFinish Reason:", finish_reason)
print("Usage Details:", usage_data)
Explicación:
- Array de opciones:Accedemos al primer elemento en el array de opciones, ya que muchas solicitudes típicamente devuelven una salida dominante.
- Contenido del Mensaje:El texto generado actual se encuentra en el campo
message
de la opción. - Razón de Finalización:Esto nos indica si la respuesta terminó porque alcanzó la condición de
stop
, el límite de tokens, o mediante una llamada a función. - Uso:Los datos de uso te permiten rastrear cuántos tokens se consumieron, ayudándote a gestionar costos y optimizar prompts.
6.5.3 Manejo de Respuestas de Llamadas a Funciones
Cuando las llamadas a funciones están habilitadas en tu solicitud de API, la estructura de respuesta incluye un campo adicional llamado function_call
dentro del objeto message. Este campo es crucial para implementar acciones automatizadas basadas en las decisiones del modelo. Por ejemplo, si el modelo determina que necesita obtener datos del clima o realizar un cálculo, incluirá instrucciones específicas de llamada a función en este campo.
El campo function_call
contiene dos componentes clave: el nombre de la función a ejecutar y una cadena JSON de argumentos. Este formato estructurado asegura que tu aplicación pueda procesar y ejecutar sistemáticamente las funciones solicitadas. Así es como puedes manejar ese escenario:
Ejemplo: Manejo de una Respuesta de Llamada a Función
# Assume a previous request was made with function calling enabled.
if response["choices"][0].get("finish_reason") == "function_call":
function_call_data = response["choices"][0]["message"]["function_call"]
function_name = function_call_data.get("name")
arguments = function_call_data.get("arguments")
print("The model requested to call the following function:")
print("Function Name:", function_name)
print("Arguments:", arguments)
else:
print("No function call was made. Response:")
print(output_text)
Aquí está el desglose:
- Primero, el código verifica si la respuesta indica una llamada a función examinando el
finish_reason
. - Si se detecta una llamada a función, extrae dos elementos clave de información:
- El nombre de la función a ejecutar
- Una cadena JSON que contiene los argumentos de la función
El código sigue este flujo lógico:
- Verifica si
finish_reason
es igual a "function_call" - Si es verdadero, extrae los datos de la llamada a función de la respuesta
- Obtiene el nombre de la función y los argumentos usando el método
.get()
(que maneja de forma segura las claves faltantes) - Imprime los detalles de la función
- Si no se realizó ninguna llamada a función, imprime la respuesta regular en su lugar
Este enfoque estructurado asegura que tu aplicación pueda procesar y ejecutar sistemáticamente cualquier función solicitada por el modelo.
Para resumir este ejemplo: Cuando el finish_reason
indica una llamada a función, extraemos tanto el nombre de la función como sus argumentos, que luego pueden pasarse a tu función predefinida.
6.5.4 Manejo de Respuestas en Tiempo Real
Para las respuestas en streaming, la API devuelve datos en pequeños fragmentos incrementales en lugar de esperar la respuesta completa. Este enfoque, conocido como Eventos Enviados por el Servidor (SSE), permite el procesamiento en tiempo real de la salida del modelo. A medida que llega cada fragmento, puedes recorrerlos secuencialmente, procesando y mostrando el contenido inmediatamente. Esto es particularmente útil para:
- Crear interfaces de usuario receptivas que muestran el texto mientras se genera
- Procesar respuestas muy largas sin esperar a que se completen
- Implementar animaciones de escritura o efectos de carga progresiva
Aquí, recorres cada fragmento cuando llega, permitiendo el procesamiento y visualización inmediata del contenido:
Ejemplo: Manejo de Respuestas API en Streaming
import openai
import os
from dotenv import load_dotenv
import json
import time
# Load environment variables
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
def stream_chat_completion(messages, model="gpt-4o", max_tokens=100):
try:
# Initialize streaming response
response_stream = openai.ChatCompletion.create(
model=model,
messages=messages,
max_tokens=max_tokens,
temperature=0.5,
stream=True # Enable streaming
)
# Variables to collect the full response
collected_messages = []
collected_chunks = []
print("Streaming response:\n")
# Process each chunk as it arrives
for chunk in response_stream:
collected_chunks.append(chunk) # Save the chunk for later analysis
if "choices" in chunk:
chunk_message = chunk["choices"][0].get("delta", {})
# Extract and handle different parts of the message
if "content" in chunk_message:
content = chunk_message["content"]
collected_messages.append(content)
print(content, end="", flush=True)
# Handle function calls if present
if "function_call" in chunk_message:
print("\nFunction call detected!")
print(json.dumps(chunk_message["function_call"], indent=2))
print("\n\nStreaming complete!")
# Calculate and display statistics
full_response = "".join(collected_messages)
chunk_count = len(collected_chunks)
print(f"\nStats:")
print(f"Total chunks received: {chunk_count}")
print(f"Total response length: {len(full_response)} characters")
return full_response, collected_chunks
except Exception as e:
print(f"An error occurred: {str(e)}")
return None, None
# Example usage
if __name__ == "__main__":
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Tell me a short story about a cat."}
]
response, chunks = stream_chat_completion(messages)
Desglose del Código:
- Importaciones y Configuración
- Se importan las bibliotecas esenciales incluyendo OpenAI SDK, OS para variables de entorno y JSON para el análisis
- Las variables de entorno se cargan usando dotenv para una gestión segura de la clave API
- Estructura de la Función Principal
- La función stream_chat_completion encapsula toda la funcionalidad de streaming
- Acepta parámetros para mensajes, modelo y max_tokens con valores predeterminados sensatos
- Manejo de Errores
- El bloque try-except captura y maneja posibles errores de la API
- Proporciona informes de errores de manera elegante sin que el programa falle
- Procesamiento del Stream
- Inicializa listas para recopilar tanto mensajes completos como fragmentos sin procesar
- Procesa cada fragmento cuando llega en tiempo real
- Maneja tanto el contenido regular como las posibles llamadas a funciones
- Estadísticas e Informes
- Realiza un seguimiento del número de fragmentos recibidos
- Calcula la longitud total de la respuesta
- Proporciona retroalimentación detallada sobre el proceso de streaming
- Valores de Retorno
- Devuelve tanto la respuesta completa como todos los fragmentos recopilados
- Permite análisis o procesamiento adicional si es necesario
Este enfoque permite que tu aplicación muestre partes de la respuesta inmediatamente, lo cual es especialmente útil para escenarios interactivos o de retroalimentación en vivo.
Entender la estructura de las respuestas de la API es fundamental para integrar exitosamente las capacidades de OpenAI en tu aplicación. Analicemos esto en componentes clave:
Resumen de los Campos de Respuesta:
El campo choices
contiene la salida real del modelo, incluyendo texto generado y cualquier llamada a función. El campo usage
proporciona conteos detallados de tokens para entrada y salida, ayudándote a rastrear el consumo de la API. El campo finish_reason
indica por qué terminó la respuesta, ya sea naturalmente, debido a límites de longitud o por una llamada a función.
Tipos de Respuesta:
Hay tres tipos principales de respuestas que necesitarás manejar:
- Respuestas normales: Salida de texto estándar del modelo
- Llamadas a funciones: Cuando el modelo solicita ejecutar funciones específicas
- Respuestas en streaming: Fragmentos de datos en tiempo real para procesamiento inmediato
Mejores Prácticas:
Para construir aplicaciones robustas:
- Siempre validar la estructura de la respuesta antes de procesarla
- Implementar un manejo adecuado de errores para cada tipo de respuesta
- Usar streaming para una mejor experiencia de usuario con respuestas largas
- Monitorear el uso de tokens para optimizar costos
- Mantener el contexto de la conversación mediante un manejo adecuado de mensajes
Al dominar estos aspectos del manejo de respuestas de la API, puedes crear aplicaciones más confiables y eficientes que aprovechen al máximo las capacidades de OpenAI mientras mantienes un rendimiento y rentabilidad óptimos.
6.5 Descripción General de la API de Respuestas
Al interactuar con la API de Chat Completions, entender el manejo de respuestas es crucial para construir aplicaciones robustas. Exploremos por qué esto es importante y cómo funciona en detalle:
Primero, enviar solicitudes es solo la mitad de la ecuación - el verdadero poder radica en manejar adecuadamente las respuestas. La API devuelve un objeto de respuesta bien estructurado que contiene varios componentes clave:
- Texto Generado: La salida principal del modelo es el contenido central de la respuesta. Puede tomar varias formas:
- Respuestas conversacionales: Diálogo natural y respuestas interactivas
- Perspectivas analíticas: Análisis de datos, explicaciones e interpretaciones
- Contenido creativo: Historias, artículos u otro texto generado
- Resultados de resolución de problemas: Código, soluciones matemáticas o razonamiento lógico
- Metadatos: Información técnica esencial sobre la interacción, incluyendo:
- Estadísticas de uso de tokens para monitorear costos: Rastrea tokens de entrada, tokens de completado y uso total para facturación y optimización
- Marcas de tiempo de procesamiento: Registra cuándo se recibió, procesó y completó la solicitud
- Parámetros específicos del modelo utilizados: Documenta la temperatura, top_p, penalización de frecuencia y otras configuraciones
- Detalles de formato de respuesta: Información sobre cómo se estructuró y formateó la salida
- Llamadas a Funciones: Cuando se habilita la llamada a funciones, la respuesta incluye:
- Nombres y descripciones de funciones
- Parámetros requeridos y opcionales
- Formatos de salida esperados
- Estado de ejecución y resultados
- Indicadores de Estado: Retroalimentación completa sobre la generación de respuesta:
- Razón de finalización: Indica si la respuesta fue completa ("stop"), alcanzó límites de tokens ("length"), o necesitó llamadas a funciones
- Estados de error: Cualquier problema encontrado durante el procesamiento
- Métricas de calidad: Puntuaciones de confianza u otras mediciones relevantes
En esta sección, profundizaremos en cada uno de estos componentes, mostrando ejemplos prácticos de cómo extraer, procesar y utilizar estos datos de manera efectiva en sus aplicaciones. Entender estos elementos es esencial para construir sistemas confiables y listos para producción que puedan manejar casos extremos y proporcionar experiencias óptimas al usuario.
6.5.1 Comprendiendo la Estructura de Respuesta de la API
Cuando envía una solicitud a la API de Chat Completions, recibirá un objeto JSON de respuesta completo que contiene varios componentes cruciales. Esta estructura de respuesta está cuidadosamente diseñada para proporcionar no solo la salida del modelo, sino también metadatos importantes sobre la interacción.
La respuesta incluye información detallada sobre el uso de tokens, estado de procesamiento y cualquier llamada a función que se haya activado. También contiene métricas de calidad y datos de manejo de errores que ayudan a garantizar un rendimiento robusto de la aplicación. Exploremos cada uno de estos componentes en detalle, entendiendo cómo trabajan juntos para proporcionar una imagen completa de la interacción con la API:
choices:
Este es un array que sirve como contenedor principal para las respuestas del modelo. Puede contener múltiples respuestas si ha solicitado alternativas. La estructura del array permite recibir múltiples completados de una sola llamada a la API, lo cual es útil para generar opciones diversas o realizar pruebas A/B de respuestas.
- Cada elemento en el array contiene un campo message - aquí es donde encontrará el texto de salida real generado por el modelo. Por ejemplo:
response["choices"][0]["message"]["content"] # Accessing the first response
response["choices"][1]["message"]["content"] # Accessing the second response (if n>1)
- El campo message es versátil - puede contener respuestas de texto estándar, llamadas a funciones para ejecutar acciones específicas, o incluso formatos especializados según los parámetros de tu solicitud. Por ejemplo:
# Standard text response
{"message": {"role": "assistant", "content": "Hello! How can I help you?"}}
# Function call response
{"message": {"role": "assistant", "function_call": {
"name": "get_weather",
"arguments": "{\"location\": \"London\", \"unit\": \"celsius\"}"
}}}
Los metadatos adicionales en cada opción proporcionan información crucial sobre la respuesta:
- index: La posición de esta opción en el array
- finish_reason: Indica por qué el modelo dejó de generar ("stop", "length", "function_call", etc.)
- logprobs: Información opcional de probabilidad logarítmica cuando se solicita
usage:
Este campo vital te ayuda a monitorear y optimizar tu consumo de la API proporcionando estadísticas detalladas del uso de tokens. Actúa como un sistema integral de seguimiento que permite a los desarrolladores entender exactamente cómo sus solicitudes a la API están utilizando los recursos del modelo.
Desglosa el uso de tokens en tres métricas clave:
prompt_tokens
: El número de tokens en tu entrada. Considera un prompt básico como "Traduce 'Hola' al español" que podría usar 5-6 tokens, mientras que un prompt complejo de varios párrafos podría usar cientos de tokens.completion_tokens
: El número de tokens en la respuesta del modelo. Una traducción simple podría usar 1-2 tokens, mientras que un análisis detallado podría usar varios cientos de tokens.total_tokens
: La suma de tokens del prompt y de la completación. Por ejemplo, si tu prompt usa 50 tokens y la respuesta usa 150 tokens, tu uso total sería de 200 tokens.
Entender estas métricas es crucial para gestionar costos y asegurar un uso eficiente de la API en tus aplicaciones:
- Planificación de Presupuesto: Al monitorear el uso de tokens, puedes estimar costos con mayor precisión. Por ejemplo, si sabes que tu solicitud promedio usa 200 tokens en total, puedes multiplicar esto por tu volumen esperado de solicitudes y el precio por token.
- Oportunidades de Optimización: Un alto número de prompt_tokens podría indicar oportunidades para hacer tus prompts más concisos, mientras que un alto número de completion_tokens podría sugerir añadir restricciones más específicas a tus solicitudes.
- Arquitectura del Sistema: Estas métricas ayudan a informar decisiones sobre estrategias de caché y si conviene agrupar ciertos tipos de solicitudes.
finish_reason:
Este campo proporciona contexto importante sobre cómo y por qué el modelo completó su respuesta. Los valores comunes incluyen:
"stop"
: Completación natural de la respuesta - indica que el modelo alcanzó un punto de parada natural o encontró una secuencia de parada. Por ejemplo, al responder una pregunta como "¿Cuánto es 2+2?", el modelo podría responder "4" y detenerse naturalmente."length"
: La respuesta alcanzó el límite de tokens - significa que la salida del modelo fue truncada debido a que alcanzó el máximo de tokens permitidos. Por ejemplo, si estableces max_tokens=50 pero la respuesta necesita más tokens para completarse, se detendrá en 50 tokens y devolverá "length" como razón de finalización."function_call"
: El modelo solicitó llamar a una función - indica que el modelo determinó que necesita ejecutar una función para proporcionar la respuesta apropiada. Por ejemplo, si se le pregunta "¿Cuál es el clima en París?", el modelo podría solicitar llamar a una función get_weather()."content_filter"
: La respuesta fue filtrada debido a la política de contenido - ocurre cuando el contenido generado activa los filtros de la API.
Esta información es esencial para el manejo de errores, la validación de respuestas y determinar si necesitas ajustar los parámetros de tu solicitud. Aquí te mostramos cómo podrías manejar diferentes razones de finalización:
def handle_response(response):
finish_reason = response.choices[0].finish_reason
if finish_reason == "length":
# Consider increasing max_tokens or breaking request into smaller chunks
print("Response was truncated. Consider increasing max_tokens.")
elif finish_reason == "function_call":
# Execute the requested function
function_call = response.choices[0].message.function_call
handle_function_call(function_call)
elif finish_reason == "content_filter":
# Handle filtered content appropriately
print("Response was filtered. Please modify your prompt.")
Comprender la razón de finalización te ayuda a implementar mecanismos de respaldo adecuados y asegurar que tu aplicación maneje efectivamente todos los posibles escenarios de respuesta. Por ejemplo:
- Si finish_reason es "length", podrías querer hacer una solicitud adicional para obtener el contenido restante
- Si finish_reason es "function_call", deberías ejecutar la función solicitada y continuar la conversación con el resultado de la función
- Si finish_reason es "content_filter", podrías necesitar modificar tu prompt o implementar mensajes de error apropiados
6.5.2 Análisis de la Respuesta
Exploremos un ejemplo práctico que demuestra cómo manejar los datos de respuesta en Python. Examinaremos paso a paso cómo extraer, analizar y procesar los diversos componentes de una respuesta de la API, incluyendo el contenido del mensaje, metadatos e información sobre el uso de tokens. Este ejemplo te ayudará a entender la implementación práctica del manejo de respuestas en tus aplicaciones.
Ejemplo: Análisis Básico de una Respuesta de Chat Completion
import openai
import os
from dotenv import load_dotenv
# Load API key from your secure environment file.
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
# Send a request with a basic conversation.
messages = [
{"role": "system", "content": "You are a knowledgeable assistant."},
{"role": "user", "content": "What is the current temperature in Paris?"}
]
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=messages,
max_tokens=100,
temperature=0.5
)
# Access the first choice in the response.
choice = response["choices"][0]
# Extract message content.
output_text = choice["message"]["content"]
finish_reason = choice.get("finish_reason")
usage_data = response.get("usage", {})
print("Generated Response:")
print(output_text)
print("\nFinish Reason:", finish_reason)
print("Usage Details:", usage_data)
Explicación:
- Array de opciones:Accedemos al primer elemento en el array de opciones, ya que muchas solicitudes típicamente devuelven una salida dominante.
- Contenido del Mensaje:El texto generado actual se encuentra en el campo
message
de la opción. - Razón de Finalización:Esto nos indica si la respuesta terminó porque alcanzó la condición de
stop
, el límite de tokens, o mediante una llamada a función. - Uso:Los datos de uso te permiten rastrear cuántos tokens se consumieron, ayudándote a gestionar costos y optimizar prompts.
6.5.3 Manejo de Respuestas de Llamadas a Funciones
Cuando las llamadas a funciones están habilitadas en tu solicitud de API, la estructura de respuesta incluye un campo adicional llamado function_call
dentro del objeto message. Este campo es crucial para implementar acciones automatizadas basadas en las decisiones del modelo. Por ejemplo, si el modelo determina que necesita obtener datos del clima o realizar un cálculo, incluirá instrucciones específicas de llamada a función en este campo.
El campo function_call
contiene dos componentes clave: el nombre de la función a ejecutar y una cadena JSON de argumentos. Este formato estructurado asegura que tu aplicación pueda procesar y ejecutar sistemáticamente las funciones solicitadas. Así es como puedes manejar ese escenario:
Ejemplo: Manejo de una Respuesta de Llamada a Función
# Assume a previous request was made with function calling enabled.
if response["choices"][0].get("finish_reason") == "function_call":
function_call_data = response["choices"][0]["message"]["function_call"]
function_name = function_call_data.get("name")
arguments = function_call_data.get("arguments")
print("The model requested to call the following function:")
print("Function Name:", function_name)
print("Arguments:", arguments)
else:
print("No function call was made. Response:")
print(output_text)
Aquí está el desglose:
- Primero, el código verifica si la respuesta indica una llamada a función examinando el
finish_reason
. - Si se detecta una llamada a función, extrae dos elementos clave de información:
- El nombre de la función a ejecutar
- Una cadena JSON que contiene los argumentos de la función
El código sigue este flujo lógico:
- Verifica si
finish_reason
es igual a "function_call" - Si es verdadero, extrae los datos de la llamada a función de la respuesta
- Obtiene el nombre de la función y los argumentos usando el método
.get()
(que maneja de forma segura las claves faltantes) - Imprime los detalles de la función
- Si no se realizó ninguna llamada a función, imprime la respuesta regular en su lugar
Este enfoque estructurado asegura que tu aplicación pueda procesar y ejecutar sistemáticamente cualquier función solicitada por el modelo.
Para resumir este ejemplo: Cuando el finish_reason
indica una llamada a función, extraemos tanto el nombre de la función como sus argumentos, que luego pueden pasarse a tu función predefinida.
6.5.4 Manejo de Respuestas en Tiempo Real
Para las respuestas en streaming, la API devuelve datos en pequeños fragmentos incrementales en lugar de esperar la respuesta completa. Este enfoque, conocido como Eventos Enviados por el Servidor (SSE), permite el procesamiento en tiempo real de la salida del modelo. A medida que llega cada fragmento, puedes recorrerlos secuencialmente, procesando y mostrando el contenido inmediatamente. Esto es particularmente útil para:
- Crear interfaces de usuario receptivas que muestran el texto mientras se genera
- Procesar respuestas muy largas sin esperar a que se completen
- Implementar animaciones de escritura o efectos de carga progresiva
Aquí, recorres cada fragmento cuando llega, permitiendo el procesamiento y visualización inmediata del contenido:
Ejemplo: Manejo de Respuestas API en Streaming
import openai
import os
from dotenv import load_dotenv
import json
import time
# Load environment variables
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
def stream_chat_completion(messages, model="gpt-4o", max_tokens=100):
try:
# Initialize streaming response
response_stream = openai.ChatCompletion.create(
model=model,
messages=messages,
max_tokens=max_tokens,
temperature=0.5,
stream=True # Enable streaming
)
# Variables to collect the full response
collected_messages = []
collected_chunks = []
print("Streaming response:\n")
# Process each chunk as it arrives
for chunk in response_stream:
collected_chunks.append(chunk) # Save the chunk for later analysis
if "choices" in chunk:
chunk_message = chunk["choices"][0].get("delta", {})
# Extract and handle different parts of the message
if "content" in chunk_message:
content = chunk_message["content"]
collected_messages.append(content)
print(content, end="", flush=True)
# Handle function calls if present
if "function_call" in chunk_message:
print("\nFunction call detected!")
print(json.dumps(chunk_message["function_call"], indent=2))
print("\n\nStreaming complete!")
# Calculate and display statistics
full_response = "".join(collected_messages)
chunk_count = len(collected_chunks)
print(f"\nStats:")
print(f"Total chunks received: {chunk_count}")
print(f"Total response length: {len(full_response)} characters")
return full_response, collected_chunks
except Exception as e:
print(f"An error occurred: {str(e)}")
return None, None
# Example usage
if __name__ == "__main__":
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Tell me a short story about a cat."}
]
response, chunks = stream_chat_completion(messages)
Desglose del Código:
- Importaciones y Configuración
- Se importan las bibliotecas esenciales incluyendo OpenAI SDK, OS para variables de entorno y JSON para el análisis
- Las variables de entorno se cargan usando dotenv para una gestión segura de la clave API
- Estructura de la Función Principal
- La función stream_chat_completion encapsula toda la funcionalidad de streaming
- Acepta parámetros para mensajes, modelo y max_tokens con valores predeterminados sensatos
- Manejo de Errores
- El bloque try-except captura y maneja posibles errores de la API
- Proporciona informes de errores de manera elegante sin que el programa falle
- Procesamiento del Stream
- Inicializa listas para recopilar tanto mensajes completos como fragmentos sin procesar
- Procesa cada fragmento cuando llega en tiempo real
- Maneja tanto el contenido regular como las posibles llamadas a funciones
- Estadísticas e Informes
- Realiza un seguimiento del número de fragmentos recibidos
- Calcula la longitud total de la respuesta
- Proporciona retroalimentación detallada sobre el proceso de streaming
- Valores de Retorno
- Devuelve tanto la respuesta completa como todos los fragmentos recopilados
- Permite análisis o procesamiento adicional si es necesario
Este enfoque permite que tu aplicación muestre partes de la respuesta inmediatamente, lo cual es especialmente útil para escenarios interactivos o de retroalimentación en vivo.
Entender la estructura de las respuestas de la API es fundamental para integrar exitosamente las capacidades de OpenAI en tu aplicación. Analicemos esto en componentes clave:
Resumen de los Campos de Respuesta:
El campo choices
contiene la salida real del modelo, incluyendo texto generado y cualquier llamada a función. El campo usage
proporciona conteos detallados de tokens para entrada y salida, ayudándote a rastrear el consumo de la API. El campo finish_reason
indica por qué terminó la respuesta, ya sea naturalmente, debido a límites de longitud o por una llamada a función.
Tipos de Respuesta:
Hay tres tipos principales de respuestas que necesitarás manejar:
- Respuestas normales: Salida de texto estándar del modelo
- Llamadas a funciones: Cuando el modelo solicita ejecutar funciones específicas
- Respuestas en streaming: Fragmentos de datos en tiempo real para procesamiento inmediato
Mejores Prácticas:
Para construir aplicaciones robustas:
- Siempre validar la estructura de la respuesta antes de procesarla
- Implementar un manejo adecuado de errores para cada tipo de respuesta
- Usar streaming para una mejor experiencia de usuario con respuestas largas
- Monitorear el uso de tokens para optimizar costos
- Mantener el contexto de la conversación mediante un manejo adecuado de mensajes
Al dominar estos aspectos del manejo de respuestas de la API, puedes crear aplicaciones más confiables y eficientes que aprovechen al máximo las capacidades de OpenAI mientras mantienes un rendimiento y rentabilidad óptimos.
6.5 Descripción General de la API de Respuestas
Al interactuar con la API de Chat Completions, entender el manejo de respuestas es crucial para construir aplicaciones robustas. Exploremos por qué esto es importante y cómo funciona en detalle:
Primero, enviar solicitudes es solo la mitad de la ecuación - el verdadero poder radica en manejar adecuadamente las respuestas. La API devuelve un objeto de respuesta bien estructurado que contiene varios componentes clave:
- Texto Generado: La salida principal del modelo es el contenido central de la respuesta. Puede tomar varias formas:
- Respuestas conversacionales: Diálogo natural y respuestas interactivas
- Perspectivas analíticas: Análisis de datos, explicaciones e interpretaciones
- Contenido creativo: Historias, artículos u otro texto generado
- Resultados de resolución de problemas: Código, soluciones matemáticas o razonamiento lógico
- Metadatos: Información técnica esencial sobre la interacción, incluyendo:
- Estadísticas de uso de tokens para monitorear costos: Rastrea tokens de entrada, tokens de completado y uso total para facturación y optimización
- Marcas de tiempo de procesamiento: Registra cuándo se recibió, procesó y completó la solicitud
- Parámetros específicos del modelo utilizados: Documenta la temperatura, top_p, penalización de frecuencia y otras configuraciones
- Detalles de formato de respuesta: Información sobre cómo se estructuró y formateó la salida
- Llamadas a Funciones: Cuando se habilita la llamada a funciones, la respuesta incluye:
- Nombres y descripciones de funciones
- Parámetros requeridos y opcionales
- Formatos de salida esperados
- Estado de ejecución y resultados
- Indicadores de Estado: Retroalimentación completa sobre la generación de respuesta:
- Razón de finalización: Indica si la respuesta fue completa ("stop"), alcanzó límites de tokens ("length"), o necesitó llamadas a funciones
- Estados de error: Cualquier problema encontrado durante el procesamiento
- Métricas de calidad: Puntuaciones de confianza u otras mediciones relevantes
En esta sección, profundizaremos en cada uno de estos componentes, mostrando ejemplos prácticos de cómo extraer, procesar y utilizar estos datos de manera efectiva en sus aplicaciones. Entender estos elementos es esencial para construir sistemas confiables y listos para producción que puedan manejar casos extremos y proporcionar experiencias óptimas al usuario.
6.5.1 Comprendiendo la Estructura de Respuesta de la API
Cuando envía una solicitud a la API de Chat Completions, recibirá un objeto JSON de respuesta completo que contiene varios componentes cruciales. Esta estructura de respuesta está cuidadosamente diseñada para proporcionar no solo la salida del modelo, sino también metadatos importantes sobre la interacción.
La respuesta incluye información detallada sobre el uso de tokens, estado de procesamiento y cualquier llamada a función que se haya activado. También contiene métricas de calidad y datos de manejo de errores que ayudan a garantizar un rendimiento robusto de la aplicación. Exploremos cada uno de estos componentes en detalle, entendiendo cómo trabajan juntos para proporcionar una imagen completa de la interacción con la API:
choices:
Este es un array que sirve como contenedor principal para las respuestas del modelo. Puede contener múltiples respuestas si ha solicitado alternativas. La estructura del array permite recibir múltiples completados de una sola llamada a la API, lo cual es útil para generar opciones diversas o realizar pruebas A/B de respuestas.
- Cada elemento en el array contiene un campo message - aquí es donde encontrará el texto de salida real generado por el modelo. Por ejemplo:
response["choices"][0]["message"]["content"] # Accessing the first response
response["choices"][1]["message"]["content"] # Accessing the second response (if n>1)
- El campo message es versátil - puede contener respuestas de texto estándar, llamadas a funciones para ejecutar acciones específicas, o incluso formatos especializados según los parámetros de tu solicitud. Por ejemplo:
# Standard text response
{"message": {"role": "assistant", "content": "Hello! How can I help you?"}}
# Function call response
{"message": {"role": "assistant", "function_call": {
"name": "get_weather",
"arguments": "{\"location\": \"London\", \"unit\": \"celsius\"}"
}}}
Los metadatos adicionales en cada opción proporcionan información crucial sobre la respuesta:
- index: La posición de esta opción en el array
- finish_reason: Indica por qué el modelo dejó de generar ("stop", "length", "function_call", etc.)
- logprobs: Información opcional de probabilidad logarítmica cuando se solicita
usage:
Este campo vital te ayuda a monitorear y optimizar tu consumo de la API proporcionando estadísticas detalladas del uso de tokens. Actúa como un sistema integral de seguimiento que permite a los desarrolladores entender exactamente cómo sus solicitudes a la API están utilizando los recursos del modelo.
Desglosa el uso de tokens en tres métricas clave:
prompt_tokens
: El número de tokens en tu entrada. Considera un prompt básico como "Traduce 'Hola' al español" que podría usar 5-6 tokens, mientras que un prompt complejo de varios párrafos podría usar cientos de tokens.completion_tokens
: El número de tokens en la respuesta del modelo. Una traducción simple podría usar 1-2 tokens, mientras que un análisis detallado podría usar varios cientos de tokens.total_tokens
: La suma de tokens del prompt y de la completación. Por ejemplo, si tu prompt usa 50 tokens y la respuesta usa 150 tokens, tu uso total sería de 200 tokens.
Entender estas métricas es crucial para gestionar costos y asegurar un uso eficiente de la API en tus aplicaciones:
- Planificación de Presupuesto: Al monitorear el uso de tokens, puedes estimar costos con mayor precisión. Por ejemplo, si sabes que tu solicitud promedio usa 200 tokens en total, puedes multiplicar esto por tu volumen esperado de solicitudes y el precio por token.
- Oportunidades de Optimización: Un alto número de prompt_tokens podría indicar oportunidades para hacer tus prompts más concisos, mientras que un alto número de completion_tokens podría sugerir añadir restricciones más específicas a tus solicitudes.
- Arquitectura del Sistema: Estas métricas ayudan a informar decisiones sobre estrategias de caché y si conviene agrupar ciertos tipos de solicitudes.
finish_reason:
Este campo proporciona contexto importante sobre cómo y por qué el modelo completó su respuesta. Los valores comunes incluyen:
"stop"
: Completación natural de la respuesta - indica que el modelo alcanzó un punto de parada natural o encontró una secuencia de parada. Por ejemplo, al responder una pregunta como "¿Cuánto es 2+2?", el modelo podría responder "4" y detenerse naturalmente."length"
: La respuesta alcanzó el límite de tokens - significa que la salida del modelo fue truncada debido a que alcanzó el máximo de tokens permitidos. Por ejemplo, si estableces max_tokens=50 pero la respuesta necesita más tokens para completarse, se detendrá en 50 tokens y devolverá "length" como razón de finalización."function_call"
: El modelo solicitó llamar a una función - indica que el modelo determinó que necesita ejecutar una función para proporcionar la respuesta apropiada. Por ejemplo, si se le pregunta "¿Cuál es el clima en París?", el modelo podría solicitar llamar a una función get_weather()."content_filter"
: La respuesta fue filtrada debido a la política de contenido - ocurre cuando el contenido generado activa los filtros de la API.
Esta información es esencial para el manejo de errores, la validación de respuestas y determinar si necesitas ajustar los parámetros de tu solicitud. Aquí te mostramos cómo podrías manejar diferentes razones de finalización:
def handle_response(response):
finish_reason = response.choices[0].finish_reason
if finish_reason == "length":
# Consider increasing max_tokens or breaking request into smaller chunks
print("Response was truncated. Consider increasing max_tokens.")
elif finish_reason == "function_call":
# Execute the requested function
function_call = response.choices[0].message.function_call
handle_function_call(function_call)
elif finish_reason == "content_filter":
# Handle filtered content appropriately
print("Response was filtered. Please modify your prompt.")
Comprender la razón de finalización te ayuda a implementar mecanismos de respaldo adecuados y asegurar que tu aplicación maneje efectivamente todos los posibles escenarios de respuesta. Por ejemplo:
- Si finish_reason es "length", podrías querer hacer una solicitud adicional para obtener el contenido restante
- Si finish_reason es "function_call", deberías ejecutar la función solicitada y continuar la conversación con el resultado de la función
- Si finish_reason es "content_filter", podrías necesitar modificar tu prompt o implementar mensajes de error apropiados
6.5.2 Análisis de la Respuesta
Exploremos un ejemplo práctico que demuestra cómo manejar los datos de respuesta en Python. Examinaremos paso a paso cómo extraer, analizar y procesar los diversos componentes de una respuesta de la API, incluyendo el contenido del mensaje, metadatos e información sobre el uso de tokens. Este ejemplo te ayudará a entender la implementación práctica del manejo de respuestas en tus aplicaciones.
Ejemplo: Análisis Básico de una Respuesta de Chat Completion
import openai
import os
from dotenv import load_dotenv
# Load API key from your secure environment file.
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
# Send a request with a basic conversation.
messages = [
{"role": "system", "content": "You are a knowledgeable assistant."},
{"role": "user", "content": "What is the current temperature in Paris?"}
]
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=messages,
max_tokens=100,
temperature=0.5
)
# Access the first choice in the response.
choice = response["choices"][0]
# Extract message content.
output_text = choice["message"]["content"]
finish_reason = choice.get("finish_reason")
usage_data = response.get("usage", {})
print("Generated Response:")
print(output_text)
print("\nFinish Reason:", finish_reason)
print("Usage Details:", usage_data)
Explicación:
- Array de opciones:Accedemos al primer elemento en el array de opciones, ya que muchas solicitudes típicamente devuelven una salida dominante.
- Contenido del Mensaje:El texto generado actual se encuentra en el campo
message
de la opción. - Razón de Finalización:Esto nos indica si la respuesta terminó porque alcanzó la condición de
stop
, el límite de tokens, o mediante una llamada a función. - Uso:Los datos de uso te permiten rastrear cuántos tokens se consumieron, ayudándote a gestionar costos y optimizar prompts.
6.5.3 Manejo de Respuestas de Llamadas a Funciones
Cuando las llamadas a funciones están habilitadas en tu solicitud de API, la estructura de respuesta incluye un campo adicional llamado function_call
dentro del objeto message. Este campo es crucial para implementar acciones automatizadas basadas en las decisiones del modelo. Por ejemplo, si el modelo determina que necesita obtener datos del clima o realizar un cálculo, incluirá instrucciones específicas de llamada a función en este campo.
El campo function_call
contiene dos componentes clave: el nombre de la función a ejecutar y una cadena JSON de argumentos. Este formato estructurado asegura que tu aplicación pueda procesar y ejecutar sistemáticamente las funciones solicitadas. Así es como puedes manejar ese escenario:
Ejemplo: Manejo de una Respuesta de Llamada a Función
# Assume a previous request was made with function calling enabled.
if response["choices"][0].get("finish_reason") == "function_call":
function_call_data = response["choices"][0]["message"]["function_call"]
function_name = function_call_data.get("name")
arguments = function_call_data.get("arguments")
print("The model requested to call the following function:")
print("Function Name:", function_name)
print("Arguments:", arguments)
else:
print("No function call was made. Response:")
print(output_text)
Aquí está el desglose:
- Primero, el código verifica si la respuesta indica una llamada a función examinando el
finish_reason
. - Si se detecta una llamada a función, extrae dos elementos clave de información:
- El nombre de la función a ejecutar
- Una cadena JSON que contiene los argumentos de la función
El código sigue este flujo lógico:
- Verifica si
finish_reason
es igual a "function_call" - Si es verdadero, extrae los datos de la llamada a función de la respuesta
- Obtiene el nombre de la función y los argumentos usando el método
.get()
(que maneja de forma segura las claves faltantes) - Imprime los detalles de la función
- Si no se realizó ninguna llamada a función, imprime la respuesta regular en su lugar
Este enfoque estructurado asegura que tu aplicación pueda procesar y ejecutar sistemáticamente cualquier función solicitada por el modelo.
Para resumir este ejemplo: Cuando el finish_reason
indica una llamada a función, extraemos tanto el nombre de la función como sus argumentos, que luego pueden pasarse a tu función predefinida.
6.5.4 Manejo de Respuestas en Tiempo Real
Para las respuestas en streaming, la API devuelve datos en pequeños fragmentos incrementales en lugar de esperar la respuesta completa. Este enfoque, conocido como Eventos Enviados por el Servidor (SSE), permite el procesamiento en tiempo real de la salida del modelo. A medida que llega cada fragmento, puedes recorrerlos secuencialmente, procesando y mostrando el contenido inmediatamente. Esto es particularmente útil para:
- Crear interfaces de usuario receptivas que muestran el texto mientras se genera
- Procesar respuestas muy largas sin esperar a que se completen
- Implementar animaciones de escritura o efectos de carga progresiva
Aquí, recorres cada fragmento cuando llega, permitiendo el procesamiento y visualización inmediata del contenido:
Ejemplo: Manejo de Respuestas API en Streaming
import openai
import os
from dotenv import load_dotenv
import json
import time
# Load environment variables
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
def stream_chat_completion(messages, model="gpt-4o", max_tokens=100):
try:
# Initialize streaming response
response_stream = openai.ChatCompletion.create(
model=model,
messages=messages,
max_tokens=max_tokens,
temperature=0.5,
stream=True # Enable streaming
)
# Variables to collect the full response
collected_messages = []
collected_chunks = []
print("Streaming response:\n")
# Process each chunk as it arrives
for chunk in response_stream:
collected_chunks.append(chunk) # Save the chunk for later analysis
if "choices" in chunk:
chunk_message = chunk["choices"][0].get("delta", {})
# Extract and handle different parts of the message
if "content" in chunk_message:
content = chunk_message["content"]
collected_messages.append(content)
print(content, end="", flush=True)
# Handle function calls if present
if "function_call" in chunk_message:
print("\nFunction call detected!")
print(json.dumps(chunk_message["function_call"], indent=2))
print("\n\nStreaming complete!")
# Calculate and display statistics
full_response = "".join(collected_messages)
chunk_count = len(collected_chunks)
print(f"\nStats:")
print(f"Total chunks received: {chunk_count}")
print(f"Total response length: {len(full_response)} characters")
return full_response, collected_chunks
except Exception as e:
print(f"An error occurred: {str(e)}")
return None, None
# Example usage
if __name__ == "__main__":
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Tell me a short story about a cat."}
]
response, chunks = stream_chat_completion(messages)
Desglose del Código:
- Importaciones y Configuración
- Se importan las bibliotecas esenciales incluyendo OpenAI SDK, OS para variables de entorno y JSON para el análisis
- Las variables de entorno se cargan usando dotenv para una gestión segura de la clave API
- Estructura de la Función Principal
- La función stream_chat_completion encapsula toda la funcionalidad de streaming
- Acepta parámetros para mensajes, modelo y max_tokens con valores predeterminados sensatos
- Manejo de Errores
- El bloque try-except captura y maneja posibles errores de la API
- Proporciona informes de errores de manera elegante sin que el programa falle
- Procesamiento del Stream
- Inicializa listas para recopilar tanto mensajes completos como fragmentos sin procesar
- Procesa cada fragmento cuando llega en tiempo real
- Maneja tanto el contenido regular como las posibles llamadas a funciones
- Estadísticas e Informes
- Realiza un seguimiento del número de fragmentos recibidos
- Calcula la longitud total de la respuesta
- Proporciona retroalimentación detallada sobre el proceso de streaming
- Valores de Retorno
- Devuelve tanto la respuesta completa como todos los fragmentos recopilados
- Permite análisis o procesamiento adicional si es necesario
Este enfoque permite que tu aplicación muestre partes de la respuesta inmediatamente, lo cual es especialmente útil para escenarios interactivos o de retroalimentación en vivo.
Entender la estructura de las respuestas de la API es fundamental para integrar exitosamente las capacidades de OpenAI en tu aplicación. Analicemos esto en componentes clave:
Resumen de los Campos de Respuesta:
El campo choices
contiene la salida real del modelo, incluyendo texto generado y cualquier llamada a función. El campo usage
proporciona conteos detallados de tokens para entrada y salida, ayudándote a rastrear el consumo de la API. El campo finish_reason
indica por qué terminó la respuesta, ya sea naturalmente, debido a límites de longitud o por una llamada a función.
Tipos de Respuesta:
Hay tres tipos principales de respuestas que necesitarás manejar:
- Respuestas normales: Salida de texto estándar del modelo
- Llamadas a funciones: Cuando el modelo solicita ejecutar funciones específicas
- Respuestas en streaming: Fragmentos de datos en tiempo real para procesamiento inmediato
Mejores Prácticas:
Para construir aplicaciones robustas:
- Siempre validar la estructura de la respuesta antes de procesarla
- Implementar un manejo adecuado de errores para cada tipo de respuesta
- Usar streaming para una mejor experiencia de usuario con respuestas largas
- Monitorear el uso de tokens para optimizar costos
- Mantener el contexto de la conversación mediante un manejo adecuado de mensajes
Al dominar estos aspectos del manejo de respuestas de la API, puedes crear aplicaciones más confiables y eficientes que aprovechen al máximo las capacidades de OpenAI mientras mantienes un rendimiento y rentabilidad óptimos.
6.5 Descripción General de la API de Respuestas
Al interactuar con la API de Chat Completions, entender el manejo de respuestas es crucial para construir aplicaciones robustas. Exploremos por qué esto es importante y cómo funciona en detalle:
Primero, enviar solicitudes es solo la mitad de la ecuación - el verdadero poder radica en manejar adecuadamente las respuestas. La API devuelve un objeto de respuesta bien estructurado que contiene varios componentes clave:
- Texto Generado: La salida principal del modelo es el contenido central de la respuesta. Puede tomar varias formas:
- Respuestas conversacionales: Diálogo natural y respuestas interactivas
- Perspectivas analíticas: Análisis de datos, explicaciones e interpretaciones
- Contenido creativo: Historias, artículos u otro texto generado
- Resultados de resolución de problemas: Código, soluciones matemáticas o razonamiento lógico
- Metadatos: Información técnica esencial sobre la interacción, incluyendo:
- Estadísticas de uso de tokens para monitorear costos: Rastrea tokens de entrada, tokens de completado y uso total para facturación y optimización
- Marcas de tiempo de procesamiento: Registra cuándo se recibió, procesó y completó la solicitud
- Parámetros específicos del modelo utilizados: Documenta la temperatura, top_p, penalización de frecuencia y otras configuraciones
- Detalles de formato de respuesta: Información sobre cómo se estructuró y formateó la salida
- Llamadas a Funciones: Cuando se habilita la llamada a funciones, la respuesta incluye:
- Nombres y descripciones de funciones
- Parámetros requeridos y opcionales
- Formatos de salida esperados
- Estado de ejecución y resultados
- Indicadores de Estado: Retroalimentación completa sobre la generación de respuesta:
- Razón de finalización: Indica si la respuesta fue completa ("stop"), alcanzó límites de tokens ("length"), o necesitó llamadas a funciones
- Estados de error: Cualquier problema encontrado durante el procesamiento
- Métricas de calidad: Puntuaciones de confianza u otras mediciones relevantes
En esta sección, profundizaremos en cada uno de estos componentes, mostrando ejemplos prácticos de cómo extraer, procesar y utilizar estos datos de manera efectiva en sus aplicaciones. Entender estos elementos es esencial para construir sistemas confiables y listos para producción que puedan manejar casos extremos y proporcionar experiencias óptimas al usuario.
6.5.1 Comprendiendo la Estructura de Respuesta de la API
Cuando envía una solicitud a la API de Chat Completions, recibirá un objeto JSON de respuesta completo que contiene varios componentes cruciales. Esta estructura de respuesta está cuidadosamente diseñada para proporcionar no solo la salida del modelo, sino también metadatos importantes sobre la interacción.
La respuesta incluye información detallada sobre el uso de tokens, estado de procesamiento y cualquier llamada a función que se haya activado. También contiene métricas de calidad y datos de manejo de errores que ayudan a garantizar un rendimiento robusto de la aplicación. Exploremos cada uno de estos componentes en detalle, entendiendo cómo trabajan juntos para proporcionar una imagen completa de la interacción con la API:
choices:
Este es un array que sirve como contenedor principal para las respuestas del modelo. Puede contener múltiples respuestas si ha solicitado alternativas. La estructura del array permite recibir múltiples completados de una sola llamada a la API, lo cual es útil para generar opciones diversas o realizar pruebas A/B de respuestas.
- Cada elemento en el array contiene un campo message - aquí es donde encontrará el texto de salida real generado por el modelo. Por ejemplo:
response["choices"][0]["message"]["content"] # Accessing the first response
response["choices"][1]["message"]["content"] # Accessing the second response (if n>1)
- El campo message es versátil - puede contener respuestas de texto estándar, llamadas a funciones para ejecutar acciones específicas, o incluso formatos especializados según los parámetros de tu solicitud. Por ejemplo:
# Standard text response
{"message": {"role": "assistant", "content": "Hello! How can I help you?"}}
# Function call response
{"message": {"role": "assistant", "function_call": {
"name": "get_weather",
"arguments": "{\"location\": \"London\", \"unit\": \"celsius\"}"
}}}
Los metadatos adicionales en cada opción proporcionan información crucial sobre la respuesta:
- index: La posición de esta opción en el array
- finish_reason: Indica por qué el modelo dejó de generar ("stop", "length", "function_call", etc.)
- logprobs: Información opcional de probabilidad logarítmica cuando se solicita
usage:
Este campo vital te ayuda a monitorear y optimizar tu consumo de la API proporcionando estadísticas detalladas del uso de tokens. Actúa como un sistema integral de seguimiento que permite a los desarrolladores entender exactamente cómo sus solicitudes a la API están utilizando los recursos del modelo.
Desglosa el uso de tokens en tres métricas clave:
prompt_tokens
: El número de tokens en tu entrada. Considera un prompt básico como "Traduce 'Hola' al español" que podría usar 5-6 tokens, mientras que un prompt complejo de varios párrafos podría usar cientos de tokens.completion_tokens
: El número de tokens en la respuesta del modelo. Una traducción simple podría usar 1-2 tokens, mientras que un análisis detallado podría usar varios cientos de tokens.total_tokens
: La suma de tokens del prompt y de la completación. Por ejemplo, si tu prompt usa 50 tokens y la respuesta usa 150 tokens, tu uso total sería de 200 tokens.
Entender estas métricas es crucial para gestionar costos y asegurar un uso eficiente de la API en tus aplicaciones:
- Planificación de Presupuesto: Al monitorear el uso de tokens, puedes estimar costos con mayor precisión. Por ejemplo, si sabes que tu solicitud promedio usa 200 tokens en total, puedes multiplicar esto por tu volumen esperado de solicitudes y el precio por token.
- Oportunidades de Optimización: Un alto número de prompt_tokens podría indicar oportunidades para hacer tus prompts más concisos, mientras que un alto número de completion_tokens podría sugerir añadir restricciones más específicas a tus solicitudes.
- Arquitectura del Sistema: Estas métricas ayudan a informar decisiones sobre estrategias de caché y si conviene agrupar ciertos tipos de solicitudes.
finish_reason:
Este campo proporciona contexto importante sobre cómo y por qué el modelo completó su respuesta. Los valores comunes incluyen:
"stop"
: Completación natural de la respuesta - indica que el modelo alcanzó un punto de parada natural o encontró una secuencia de parada. Por ejemplo, al responder una pregunta como "¿Cuánto es 2+2?", el modelo podría responder "4" y detenerse naturalmente."length"
: La respuesta alcanzó el límite de tokens - significa que la salida del modelo fue truncada debido a que alcanzó el máximo de tokens permitidos. Por ejemplo, si estableces max_tokens=50 pero la respuesta necesita más tokens para completarse, se detendrá en 50 tokens y devolverá "length" como razón de finalización."function_call"
: El modelo solicitó llamar a una función - indica que el modelo determinó que necesita ejecutar una función para proporcionar la respuesta apropiada. Por ejemplo, si se le pregunta "¿Cuál es el clima en París?", el modelo podría solicitar llamar a una función get_weather()."content_filter"
: La respuesta fue filtrada debido a la política de contenido - ocurre cuando el contenido generado activa los filtros de la API.
Esta información es esencial para el manejo de errores, la validación de respuestas y determinar si necesitas ajustar los parámetros de tu solicitud. Aquí te mostramos cómo podrías manejar diferentes razones de finalización:
def handle_response(response):
finish_reason = response.choices[0].finish_reason
if finish_reason == "length":
# Consider increasing max_tokens or breaking request into smaller chunks
print("Response was truncated. Consider increasing max_tokens.")
elif finish_reason == "function_call":
# Execute the requested function
function_call = response.choices[0].message.function_call
handle_function_call(function_call)
elif finish_reason == "content_filter":
# Handle filtered content appropriately
print("Response was filtered. Please modify your prompt.")
Comprender la razón de finalización te ayuda a implementar mecanismos de respaldo adecuados y asegurar que tu aplicación maneje efectivamente todos los posibles escenarios de respuesta. Por ejemplo:
- Si finish_reason es "length", podrías querer hacer una solicitud adicional para obtener el contenido restante
- Si finish_reason es "function_call", deberías ejecutar la función solicitada y continuar la conversación con el resultado de la función
- Si finish_reason es "content_filter", podrías necesitar modificar tu prompt o implementar mensajes de error apropiados
6.5.2 Análisis de la Respuesta
Exploremos un ejemplo práctico que demuestra cómo manejar los datos de respuesta en Python. Examinaremos paso a paso cómo extraer, analizar y procesar los diversos componentes de una respuesta de la API, incluyendo el contenido del mensaje, metadatos e información sobre el uso de tokens. Este ejemplo te ayudará a entender la implementación práctica del manejo de respuestas en tus aplicaciones.
Ejemplo: Análisis Básico de una Respuesta de Chat Completion
import openai
import os
from dotenv import load_dotenv
# Load API key from your secure environment file.
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
# Send a request with a basic conversation.
messages = [
{"role": "system", "content": "You are a knowledgeable assistant."},
{"role": "user", "content": "What is the current temperature in Paris?"}
]
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=messages,
max_tokens=100,
temperature=0.5
)
# Access the first choice in the response.
choice = response["choices"][0]
# Extract message content.
output_text = choice["message"]["content"]
finish_reason = choice.get("finish_reason")
usage_data = response.get("usage", {})
print("Generated Response:")
print(output_text)
print("\nFinish Reason:", finish_reason)
print("Usage Details:", usage_data)
Explicación:
- Array de opciones:Accedemos al primer elemento en el array de opciones, ya que muchas solicitudes típicamente devuelven una salida dominante.
- Contenido del Mensaje:El texto generado actual se encuentra en el campo
message
de la opción. - Razón de Finalización:Esto nos indica si la respuesta terminó porque alcanzó la condición de
stop
, el límite de tokens, o mediante una llamada a función. - Uso:Los datos de uso te permiten rastrear cuántos tokens se consumieron, ayudándote a gestionar costos y optimizar prompts.
6.5.3 Manejo de Respuestas de Llamadas a Funciones
Cuando las llamadas a funciones están habilitadas en tu solicitud de API, la estructura de respuesta incluye un campo adicional llamado function_call
dentro del objeto message. Este campo es crucial para implementar acciones automatizadas basadas en las decisiones del modelo. Por ejemplo, si el modelo determina que necesita obtener datos del clima o realizar un cálculo, incluirá instrucciones específicas de llamada a función en este campo.
El campo function_call
contiene dos componentes clave: el nombre de la función a ejecutar y una cadena JSON de argumentos. Este formato estructurado asegura que tu aplicación pueda procesar y ejecutar sistemáticamente las funciones solicitadas. Así es como puedes manejar ese escenario:
Ejemplo: Manejo de una Respuesta de Llamada a Función
# Assume a previous request was made with function calling enabled.
if response["choices"][0].get("finish_reason") == "function_call":
function_call_data = response["choices"][0]["message"]["function_call"]
function_name = function_call_data.get("name")
arguments = function_call_data.get("arguments")
print("The model requested to call the following function:")
print("Function Name:", function_name)
print("Arguments:", arguments)
else:
print("No function call was made. Response:")
print(output_text)
Aquí está el desglose:
- Primero, el código verifica si la respuesta indica una llamada a función examinando el
finish_reason
. - Si se detecta una llamada a función, extrae dos elementos clave de información:
- El nombre de la función a ejecutar
- Una cadena JSON que contiene los argumentos de la función
El código sigue este flujo lógico:
- Verifica si
finish_reason
es igual a "function_call" - Si es verdadero, extrae los datos de la llamada a función de la respuesta
- Obtiene el nombre de la función y los argumentos usando el método
.get()
(que maneja de forma segura las claves faltantes) - Imprime los detalles de la función
- Si no se realizó ninguna llamada a función, imprime la respuesta regular en su lugar
Este enfoque estructurado asegura que tu aplicación pueda procesar y ejecutar sistemáticamente cualquier función solicitada por el modelo.
Para resumir este ejemplo: Cuando el finish_reason
indica una llamada a función, extraemos tanto el nombre de la función como sus argumentos, que luego pueden pasarse a tu función predefinida.
6.5.4 Manejo de Respuestas en Tiempo Real
Para las respuestas en streaming, la API devuelve datos en pequeños fragmentos incrementales en lugar de esperar la respuesta completa. Este enfoque, conocido como Eventos Enviados por el Servidor (SSE), permite el procesamiento en tiempo real de la salida del modelo. A medida que llega cada fragmento, puedes recorrerlos secuencialmente, procesando y mostrando el contenido inmediatamente. Esto es particularmente útil para:
- Crear interfaces de usuario receptivas que muestran el texto mientras se genera
- Procesar respuestas muy largas sin esperar a que se completen
- Implementar animaciones de escritura o efectos de carga progresiva
Aquí, recorres cada fragmento cuando llega, permitiendo el procesamiento y visualización inmediata del contenido:
Ejemplo: Manejo de Respuestas API en Streaming
import openai
import os
from dotenv import load_dotenv
import json
import time
# Load environment variables
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
def stream_chat_completion(messages, model="gpt-4o", max_tokens=100):
try:
# Initialize streaming response
response_stream = openai.ChatCompletion.create(
model=model,
messages=messages,
max_tokens=max_tokens,
temperature=0.5,
stream=True # Enable streaming
)
# Variables to collect the full response
collected_messages = []
collected_chunks = []
print("Streaming response:\n")
# Process each chunk as it arrives
for chunk in response_stream:
collected_chunks.append(chunk) # Save the chunk for later analysis
if "choices" in chunk:
chunk_message = chunk["choices"][0].get("delta", {})
# Extract and handle different parts of the message
if "content" in chunk_message:
content = chunk_message["content"]
collected_messages.append(content)
print(content, end="", flush=True)
# Handle function calls if present
if "function_call" in chunk_message:
print("\nFunction call detected!")
print(json.dumps(chunk_message["function_call"], indent=2))
print("\n\nStreaming complete!")
# Calculate and display statistics
full_response = "".join(collected_messages)
chunk_count = len(collected_chunks)
print(f"\nStats:")
print(f"Total chunks received: {chunk_count}")
print(f"Total response length: {len(full_response)} characters")
return full_response, collected_chunks
except Exception as e:
print(f"An error occurred: {str(e)}")
return None, None
# Example usage
if __name__ == "__main__":
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Tell me a short story about a cat."}
]
response, chunks = stream_chat_completion(messages)
Desglose del Código:
- Importaciones y Configuración
- Se importan las bibliotecas esenciales incluyendo OpenAI SDK, OS para variables de entorno y JSON para el análisis
- Las variables de entorno se cargan usando dotenv para una gestión segura de la clave API
- Estructura de la Función Principal
- La función stream_chat_completion encapsula toda la funcionalidad de streaming
- Acepta parámetros para mensajes, modelo y max_tokens con valores predeterminados sensatos
- Manejo de Errores
- El bloque try-except captura y maneja posibles errores de la API
- Proporciona informes de errores de manera elegante sin que el programa falle
- Procesamiento del Stream
- Inicializa listas para recopilar tanto mensajes completos como fragmentos sin procesar
- Procesa cada fragmento cuando llega en tiempo real
- Maneja tanto el contenido regular como las posibles llamadas a funciones
- Estadísticas e Informes
- Realiza un seguimiento del número de fragmentos recibidos
- Calcula la longitud total de la respuesta
- Proporciona retroalimentación detallada sobre el proceso de streaming
- Valores de Retorno
- Devuelve tanto la respuesta completa como todos los fragmentos recopilados
- Permite análisis o procesamiento adicional si es necesario
Este enfoque permite que tu aplicación muestre partes de la respuesta inmediatamente, lo cual es especialmente útil para escenarios interactivos o de retroalimentación en vivo.
Entender la estructura de las respuestas de la API es fundamental para integrar exitosamente las capacidades de OpenAI en tu aplicación. Analicemos esto en componentes clave:
Resumen de los Campos de Respuesta:
El campo choices
contiene la salida real del modelo, incluyendo texto generado y cualquier llamada a función. El campo usage
proporciona conteos detallados de tokens para entrada y salida, ayudándote a rastrear el consumo de la API. El campo finish_reason
indica por qué terminó la respuesta, ya sea naturalmente, debido a límites de longitud o por una llamada a función.
Tipos de Respuesta:
Hay tres tipos principales de respuestas que necesitarás manejar:
- Respuestas normales: Salida de texto estándar del modelo
- Llamadas a funciones: Cuando el modelo solicita ejecutar funciones específicas
- Respuestas en streaming: Fragmentos de datos en tiempo real para procesamiento inmediato
Mejores Prácticas:
Para construir aplicaciones robustas:
- Siempre validar la estructura de la respuesta antes de procesarla
- Implementar un manejo adecuado de errores para cada tipo de respuesta
- Usar streaming para una mejor experiencia de usuario con respuestas largas
- Monitorear el uso de tokens para optimizar costos
- Mantener el contexto de la conversación mediante un manejo adecuado de mensajes
Al dominar estos aspectos del manejo de respuestas de la API, puedes crear aplicaciones más confiables y eficientes que aprovechen al máximo las capacidades de OpenAI mientras mantienes un rendimiento y rentabilidad óptimos.