Primeros pasos con la API de Responses de OpenAI en Amazon Bedrock (parte 1 de 2)
Los modelos de OpenAI en Amazon Bedrock exponen una superficie de API de Responses compatible con OpenAI para flujos de trabajo de producción que necesitan generación de texto, salidas estructuradas, herramientas de aplicación, entradas directas de archivos, estado de respuesta, almacenamiento en caché de prompts y trabajo en segundo plano. Este manual mantiene los ejemplos concretos al construir un flujo de trabajo de asistente de soporte para BrightCart, un minorista ficticio que maneja solicitudes de reemplazo de pedidos retrasados y dañados.
Usarás el SDK de Python de OpenAI para llamadas de aplicación normales y un pequeño asistente HTTPS sin procesar cuando sea útil inspeccionar el cuerpo exacto de la solicitud. El flujo comienza con la configuración y un pre-vuelo mínimo, luego se superpone el ciclo de vida de la respuesta, los controles del modelo, JSON estructurado, herramientas de aplicación, entrada de archivos, gestión de estado, almacenamiento en caché, procesamiento en segundo plano, compactación de contexto, comprobaciones de operaciones y limpieza.
Aprenderás a:
Configurar un modelo de OpenAI alojado en Bedrock con variables de entorno específicas de Bedrock.
Verificar el endpoint de Responses e inspeccionar el esquema de respuesta, los metadatos de uso y los errores normalizados.
Enviar solicitudes de texto tanto con HTTPS sin procesar como con el SDK de OpenAI.
Generar JSON con restricciones de esquema y transferencias de modo JSON más ligeras.
Llamar a herramientas de función administradas por la aplicación, herramientas paralelas y herramientas de texto personalizadas.
Enviar una entrada directa de PDF, continuar conversaciones con y sin estado, y llevar un contexto de razonamiento cifrado.
Usar el almacenamiento en caché de prompts, el modo en segundo plano, la compactación, las comprobaciones operativas rápidas y la limpieza de respuestas almacenadas.
Requisitos previos: un token de portador para los modelos de OpenAI en Amazon Bedrock, Python 3.9 o posterior, y acceso a la red a tu endpoint compatible con OpenAI de Bedrock.
Esta guía se ejecuta openai.gpt-5.4 en us-west-2 por defecto. Para usar otro emparejamiento compatible, cambia AWS_REGION, BEDROCK_MODEL y BEDROCK_BASE_URL juntos antes de ejecutar las celdas de configuración.
Región de AWS
IDs de modelo compatibles
us-west-2
openai.gpt-5.4
us-east-2
openai.gpt-5.5, openai.gpt-5.4
1. Configurar Amazon Bedrock
Esta sección prepara el entorno de ejecución del notebook. Instala la pequeña pila de Python, lee las variables de entorno específicas de Bedrock, crea una sesión HTTPS sin procesar y un cliente del SDK de OpenAI, descubre metadatos del modelo cuando el endpoint los proporciona y define ayudantes compartidos utilizados en ejemplos posteriores.
Establece estas variables de entorno antes de ejecutar el notebook. El emparejamiento predeterminado es us-west-2 con openai.gpt-5.4.
El token de portador se lee de AWS_BEARER_TOKEN_BEDROCK. Si falta, la celda de configuración lo solicita con un prompt de estilo contraseña y no lo imprime.
1.1 Instalar dependencias
Instala los paquetes utilizados por el notebook. El SDK de OpenAI se usa para los ejemplos de aplicación, requests se usa para llamadas HTTPS sin procesar al endpoint de Responses, y pandas más los ayudantes de visualización de IPython mantienen los resúmenes de solicitudes y respuestas legibles en el renderizador del Cookbook. Inspecciona la salida de la celda solo para confirmar que los paquetes se instalaron o ya estaban presentes.
[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m A new release of pip is available: [0m[31;49m24.0[0m[39;49m -> [0m[32;49m26.1.1[0m
[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m To update, run: [0m[32;49mpip install --upgrade pip[0m
Note: you may need to restart the kernel to use updated packages.
Dependencies installed or already available: openai, requests, pandas, ipython
1.2 Importar bibliotecas y valores predeterminados
Importa las bibliotecas estándar, el SDK, el cliente HTTP y las utilidades de visualización utilizadas en todo el notebook. Esta celda también establece la región y el modelo predeterminados de Bedrock utilizados cuando las variables de entorno no están ya configuradas. Inspecciona los valores predeterminados impresos para confirmar que el notebook comenzará desde us-west-2 y openai.gpt-5.4 a menos que los anules.
from __future__ import annotations
import base64
import builtins
import html
import json
import os
import shlex
import textwrap
import time
from datetime import date, timedelta
from getpass import getpass
from typing import Any, Callable, Iterable
import pandas as pd
import requests
from IPython.display import HTML, Markdown, display
from openai import OpenAI
DEFAULT_REGION = "us-west-2"
DEFAULT_MODEL = "openai.gpt-5.4"
PREFERRED_MODELS = [DEFAULT_MODEL]
def gpt_version_tuple(model_id: str) -> tuple[int, int] | None:
normalized = model_id.lower().removeprefix("openai.")
if not normalized.startswith("gpt-"):
return None
version = normalized.removeprefix("gpt-").split("-")[0]
parts = version.split(".")
try:
major = builtins.int(parts[0])
minor = builtins.int(parts[1]) if len(parts) > 1 else 0
except ValueError:
return None
return major, minor
def prompt_cache_retention_for_model(model_id: str) -> str:
version = gpt_version_tuple(model_id)
if version and version >= (5, 5):
return "24h"
return "in_memory"
pd.set_option("display.max_columns", None)
pd.set_option("display.max_rows", 200)
pd.set_option("display.max_colwidth", None)
pd.set_option("display.width", 160)
def display_wrapped_table(df: pd.DataFrame, *, max_col_width_px: int = 520, index: bool = False) -> None:
if df.empty:
display(Markdown("_No rows to display._"))
return
table_html = df.to_html(index=index, escape=True, border=0)
table_html = table_html.replace('<table border="0" class="dataframe">', '<table class="dataframe wrapped-output-table">')
display(HTML(f"""
<style>
.wrapped-output-table {{
border-collapse: collapse;
width: 100%;
table-layout: auto;
font-size: 13px;
}}
.wrapped-output-table th,
.wrapped-output-table td {{
border: 1px solid #d0d7de;
padding: 6px 8px;
text-align: left;
vertical-align: top;
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
max-width: {max_col_width_px}px;
}}
.wrapped-output-table th {{
background: #f6f8fa;
font-weight: 600;
}}
</style>
{table_html}
"""))
print("Imports loaded.")
print("Default region:", DEFAULT_REGION)
print("Default model:", DEFAULT_MODEL)
Lee la configuración de Bedrock del entorno y construye los clientes. BEDROCK_BASE_URL se normaliza una vez, el requests.Session sin procesar obtiene el token de portador en sus encabezados, y el cliente del SDK de OpenAI se crea explícitamente con el mismo token y URL base. Inspecciona la tabla renderizada para confirmar la región, el modelo, el endpoint, la configuración del cliente del SDK y el comportamiento de limpieza de respuestas almacenadas antes de realizar llamadas en vivo.
from __future__ import annotations
def env_value(*names: str) -> str | None:
for name in names:
value = os.environ.get(name)
if value:
return value
return None
def env_flag(name: str, default: bool = False) -> bool:
value = env_value(name)
if value is None:
return default
return value.strip().lower() in {"1", "true", "yes", "on"}
def normalize_base_url(url: str) -> str:
url = url.strip().rstrip("/")
if url.endswith("/responses"):
return url[: -len("/responses")]
return url
def endpoint(path: str) -> str:
return f"{BEDROCK_BASE_URL}/{path.lstrip('/')}"
def responses_url(base_url: str) -> str:
return f"{normalize_base_url(base_url)}/responses"
API_TIMEOUT_SECONDS = float(env_value("BEDROCK_REQUEST_TIMEOUT_SECONDS") or "60")
MAX_RETRIES = builtins.int(env_value("BEDROCK_MAX_RETRIES") or "0")
CLEAN_UP_STORED_RESPONSES = env_flag("BEDROCK_CLEANUP_STORED_RESPONSES", True)
FAIL_ON_CHECK_FAILURE = env_flag("BEDROCK_FAIL_ON_CHECK_FAILURE", False)
RUN_RESPONSIVENESS_CHECK = env_flag("BEDROCK_RESPONSIVENESS_CHECK", True)
TRANSIENT_STATUS_CODES = {408, 409, 429, 500, 502, 503, 504}
AWS_REGION = (env_value("AWS_REGION") or DEFAULT_REGION).strip() or DEFAULT_REGION
BEDROCK_MODEL = (env_value("BEDROCK_MODEL") or DEFAULT_MODEL).strip() or DEFAULT_MODEL
BEDROCK_BASE_URL = normalize_base_url(
env_value("BEDROCK_BASE_URL") or f"https://bedrock-mantle.{AWS_REGION}.api.aws/openai/v1"
)
RESPONSES_URL = responses_url(BEDROCK_BASE_URL)
AWS_BEARER_TOKEN_BEDROCK = env_value("AWS_BEARER_TOKEN_BEDROCK")
if not AWS_BEARER_TOKEN_BEDROCK:
AWS_BEARER_TOKEN_BEDROCK = getpass("Paste your AWS Bedrock bearer token for this kernel session: ").strip()
if AWS_BEARER_TOKEN_BEDROCK:
os.environ["AWS_BEARER_TOKEN_BEDROCK"] = AWS_BEARER_TOKEN_BEDROCK
if not AWS_BEARER_TOKEN_BEDROCK:
raise RuntimeError("AWS_BEARER_TOKEN_BEDROCK is required to run the live examples.")
http = requests.Session()
http.headers.update({
"Authorization": f"Bearer {AWS_BEARER_TOKEN_BEDROCK}",
"Content-Type": "application/json",
})
client = OpenAI(api_key=AWS_BEARER_TOKEN_BEDROCK, base_url=BEDROCK_BASE_URL, max_retries=0)
BASE_URL = BEDROCK_BASE_URL
config_rows = [
{"setting": "AWS_REGION", "value": AWS_REGION},
{"setting": "BEDROCK_MODEL", "value": BEDROCK_MODEL},
{"setting": "BEDROCK_BASE_URL", "value": BEDROCK_BASE_URL},
{"setting": "SDK client", "value": "OpenAI(api_key=AWS_BEARER_TOKEN_BEDROCK, base_url=BEDROCK_BASE_URL)"},
{"setting": "cleanup stored responses", "value": CLEAN_UP_STORED_RESPONSES},
]
display_wrapped_table(pd.DataFrame(config_rows), max_col_width_px=680)
Descubre los modelos disponibles cuando el endpoint seleccionado expone metadatos de la lista de modelos, luego elige el modelo para el resto del notebook. Si BEDROCK_MODEL está configurado, el notebook usa ese valor; de lo contrario, prefiere openai.gpt-5.4. La llamada a la lista de modelos es opcional porque algunos endpoints compatibles pueden permitir la inferencia incluso cuando los metadatos del modelo no están disponibles. Inspecciona el modelo seleccionado y cualquier fila del catálogo devuelta.
from __future__ import annotations
def list_openai_models(client: OpenAI) -> list[str]:
return sorted(model.id for model in client.models.list(timeout=API_TIMEOUT_SECONDS).data)
def resolve_model_id(client: OpenAI | None) -> tuple[str, list[str], str | None]:
configured_model = env_value("BEDROCK_MODEL")
available_models: list[str] = []
model_discovery_note: str | None = None
if client is not None:
try:
available_models = list_openai_models(client)
except Exception as exc:
status_code = getattr(exc, "status_code", None)
if status_code == 404:
model_discovery_note = "This endpoint did not expose model-list metadata. The guide will continue with the configured model."
else:
model_discovery_note = f"Model-list metadata could not be listed. The guide will continue with the configured model. Details: {builtins.str(exc)[:240]}"
if configured_model:
return configured_model, available_models, model_discovery_note
for candidate in PREFERRED_MODELS:
if candidate in available_models:
return candidate, available_models, model_discovery_note
for candidate in available_models:
if candidate.startswith("openai."):
return candidate, available_models, model_discovery_note
if available_models:
return available_models[0], available_models, model_discovery_note
return PREFERRED_MODELS[0], available_models, model_discovery_note
EXPLICIT_MODEL = env_value("BEDROCK_MODEL")
MODEL_ID, AVAILABLE_MODELS, MODEL_DISCOVERY_NOTE = resolve_model_id(client)
os.environ["BEDROCK_MODEL"] = MODEL_ID
PROMPT_CACHE_RETENTION = prompt_cache_retention_for_model(MODEL_ID)
PROMPT_CACHE_RETENTION_NOTE = (
"GPT-5.5 and later use 24h extended prompt caching; earlier GPT-5 models can use in_memory."
)
config_rows = [{
"selected_model": MODEL_ID,
"model_was_explicit": bool(EXPLICIT_MODEL),
"model_catalog_status": "listed" if AVAILABLE_MODELS else "using configured model",
"discovered_model_count": len(AVAILABLE_MODELS),
"prompt_cache_retention": PROMPT_CACHE_RETENTION,
"prompt_cache_retention_note": PROMPT_CACHE_RETENTION_NOTE,
"note": MODEL_DISCOVERY_NOTE or "Model selection is ready.",
}]
display_wrapped_table(pd.DataFrame(config_rows), max_col_width_px=620)
if AVAILABLE_MODELS:
display_wrapped_table(pd.DataFrame({"available_models": AVAILABLE_MODELS[:25]}), max_col_width_px=520)
else:
print("Continuing with:", MODEL_ID)
<IPython.core.display.HTML object>
selected_model
model_was_explicit
model_catalog_status
discovered_model_count
prompt_cache_retention
prompt_cache_retention_note
note
openai.gpt-5.4
False
using configured model
0
in_memory
GPT-5.5 and later use 24h extended prompt caching; earlier GPT-5 models can use in_memory.
This endpoint did not expose model-list metadata. The guide will continue with the configured model.
Continuing with: openai.gpt-5.4
1.5 Configuración de funciones de ayuda
Define ayudantes compartidos para el flujo de trabajo. Estos ayudantes renderizan las formas de las solicitudes, normalizan los errores de la API, envían solicitudes HTTPS sin procesar, envuelven las llamadas del SDK con reintentos opcionales, extraen output_text, resumen el uso de tokens, rastrean los IDs de respuesta almacenados y muestran tablas compactas. Los ejemplos a continuación se centran en cada concepto de API, mientras que los ayudantes manejan la mecánica repetida. Inspecciona esta celda si quieres entender cómo se procesan el texto de respuesta, el uso, los errores y la limpieza.
from __future__ import annotations
RESULTS_SUMMARY: list[dict[str, Any]] = []
EXAMPLE_RESPONSES: list[dict[str, str]] = []
STORED_RESPONSE_IDS: list[str] = []
OUTPUT_WIDTH = 100
MAX_DISPLAY_TEXT_CHARS = builtins.int(env_value("BEDROCK_MAX_DISPLAY_CHARS") or "1200")
def truncate_display_text(text: Any, *, limit: int = MAX_DISPLAY_TEXT_CHARS) -> str:
rendered = builtins.str(text).strip()
if len(rendered) <= limit:
return rendered
return rendered[:limit].rstrip() + "\n[Display truncated for readability. Inspect the Python variable for the full value.]"
def compact_text(text: Any, limit: int = 220) -> str:
rendered = " ".join(builtins.str(text).split())
if len(rendered) <= limit:
return rendered
return rendered[:limit].rstrip() + "..."
def require(condition: Any, message: str) -> None:
if not condition:
raise ValueError(message)
def warn_or_raise(condition: bool, message: str) -> bool:
if condition:
return True
display(HTML(f"<div style=\"border-left:4px solid #d29922; padding:6px 10px; background:#fff8c5;\"><strong>Warning:</strong> {html.escape(message)}</div>"))
if FAIL_ON_CHECK_FAILURE:
raise AssertionError(message)
return False
def display_text_block(label: str, text: Any, *, limit: int = MAX_DISPLAY_TEXT_CHARS) -> None:
safe_label = html.escape(label)
safe_text = html.escape(truncate_display_text(text, limit=limit))
display(HTML(f"""
<div style="border:1px solid #d0d7de; border-radius:6px; margin:8px 0; overflow:hidden; font-size:13px;">
<div style="background:#f6f8fa; padding:6px 8px; font-weight:600;">{safe_label}</div>
<div style="padding:8px; white-space:pre-wrap; overflow-wrap:anywhere; line-height:1.45;">{safe_text}</div>
</div>
"""))
def print_wrapped(text: Any, *, width: int = OUTPUT_WIDTH) -> None:
print(textwrap.fill(builtins.str(text), width=width, break_long_words=True, break_on_hyphens=False))
def print_json(value: Any, *, width: int = OUTPUT_WIDTH) -> None:
display_json_block("JSON", value)
def print_label(label: str) -> None:
display(HTML(f"<div style=\"font-weight:600; margin:8px 0 4px;\">{html.escape(label)}</div>"))
def print_labeled_text(label: str, text: Any) -> None:
display_text_block(label, text)
def print_labeled_json(label: str, value: Any) -> None:
display_json_block(label, value)
def display_json_block(label: str, value: Any, *, limit: int = MAX_DISPLAY_TEXT_CHARS) -> None:
rendered = json.dumps(value, indent=2, default=builtins.str)
display_text_block(label, rendered, limit=limit)
def summarize_content(content: Any) -> str:
if isinstance(content, builtins.str):
return compact_text(content)
if isinstance(content, builtins.list):
parts: list[str] = []
for item in content:
if not isinstance(item, builtins.dict):
parts.append(compact_text(item, 80))
continue
item_type = item.get("type", "item")
if item_type == "input_text":
parts.append(f"input_text: {compact_text(item.get('text', ''), 120)}")
elif item_type == "input_file":
parts.append(f"input_file: {item.get('filename', '<inline file>')}")
else:
parts.append(item_type)
return "; ".join(parts)
return compact_text(content)
def summarize_input(input_value: Any) -> str:
if isinstance(input_value, builtins.str):
return compact_text(input_value, 260)
if isinstance(input_value, builtins.list):
messages: list[str] = []
for item in input_value[:4]:
if isinstance(item, builtins.dict):
role = item.get("role", item.get("type", "item"))
messages.append(f"{role}: {summarize_content(item.get('content', item))}")
else:
messages.append(compact_text(item, 120))
suffix = f"; +{len(input_value) - 4} more" if len(input_value) > 4 else ""
return f"{len(input_value)} item(s): " + "; ".join(messages) + suffix
return compact_text(input_value, 260)
def summarize_text_format(text_config: Any) -> str:
if not isinstance(text_config, builtins.dict):
return compact_text(text_config)
fmt = text_config.get("format")
if isinstance(fmt, builtins.dict):
fmt_type = fmt.get("type")
if fmt_type == "json_schema":
schema = fmt.get("schema") or {}
required = schema.get("required") or []
return f"json_schema: {fmt.get('name')} strict={fmt.get('strict')} required={len(required)} fields"
if fmt_type:
return builtins.str(fmt_type)
return compact_text(text_config)
def request_summary_rows(payload: dict[str, Any]) -> list[dict[str, str]]:
rows: list[dict[str, str]] = []
ordered_keys = [
"model", "max_output_tokens", "store", "background", "service_tier", "previous_response_id",
"parallel_tool_calls", "prompt_cache_key", "prompt_cache_retention",
]
for key in ordered_keys:
if key in payload:
rows.append({"field": key, "value": compact_text(payload[key], 180)})
if "reasoning" in payload:
rows.append({"field": "reasoning", "value": compact_text(payload["reasoning"], 180)})
if "text" in payload:
rows.append({"field": "text format", "value": summarize_text_format(payload["text"])})
if "include" in payload:
rows.append({"field": "include", "value": compact_text(payload["include"], 180)})
if "tools" in payload:
tool_names = [tool.get("name", tool.get("type", "tool")) for tool in payload.get("tools", [])]
rows.append({"field": "tools", "value": ", ".join(tool_names)})
if "tool_choice" in payload:
rows.append({"field": "tool_choice", "value": compact_text(payload["tool_choice"], 180)})
if "input" in payload:
rows.append({"field": "input", "value": summarize_input(payload["input"])})
return rows
def print_request_shape(payload: dict[str, Any]) -> None:
rows = request_summary_rows(redact_payload(payload))
print_label("Request shape")
display_wrapped_table(pd.DataFrame(rows), max_col_width_px=520)
def print_response_summary(response_or_summary: Any) -> None:
summary = response_or_summary if isinstance(response_or_summary, builtins.dict) and "output" not in response_or_summary else summarize_response(response_or_summary)
preferred = [
"id", "model", "status", "output_item_types", "input_tokens", "cached_input_tokens",
"output_tokens", "total_tokens", "reasoning_output_tokens", "service_tier",
]
rows = [{"field": key, "value": compact_text(summary.get(key), 220)} for key in preferred if key in summary]
print_label("Response summary")
display_wrapped_table(pd.DataFrame(rows), max_col_width_px=420)
def print_key_takeaway(text: str) -> None:
display(HTML(f"<div style=\"border-left:4px solid #1f6feb; padding:6px 10px; background:#f6f8fa; margin:8px 0;\"><strong>Key takeaway:</strong> {html.escape(text)}</div>"))
def redact_payload(payload: dict[str, Any]) -> dict[str, Any]:
def redact(value: Any) -> Any:
if isinstance(value, builtins.dict):
return {
key: ("<inline file data redacted for display>" if key == "file_data" else redact(item))
for key, item in value.items()
}
if isinstance(value, builtins.list):
return [redact(item) for item in value]
return value
return json.loads(json.dumps(redact(payload), default=builtins.str))
def compact_detail(detail: Any) -> str:
if isinstance(detail, (builtins.dict, builtins.list)):
return compact_text(json.dumps(detail, default=builtins.str), 500)
return compact_text(detail, 500)
def record_check(name: str, status: str, detail: Any = "") -> None:
RESULTS_SUMMARY.append({"name": name, "status": status, "detail": compact_detail(detail)})
def record_response(example: str, response_type: str, content: Any, limit: int = 900) -> None:
if isinstance(content, pd.DataFrame):
rendered = content.to_json(orient="records", indent=2)
elif isinstance(content, (builtins.dict, builtins.list)):
rendered = json.dumps(content, indent=2, default=builtins.str)
else:
rendered = builtins.str(content)
rendered = rendered.strip()
if len(rendered) > limit:
rendered = rendered[:limit].rstrip() + chr(10) + "..."
EXAMPLE_RESPONSES.append({
"example": example,
"response_type": response_type,
"response": rendered,
})
def print_response_gallery() -> pd.DataFrame:
gallery = pd.DataFrame(EXAMPLE_RESPONSES)
if gallery.empty:
gallery = pd.DataFrame(columns=["example", "response_type", "response"])
display_wrapped_table(gallery, max_col_width_px=620)
return gallery
def normalize_error(response: requests.Response, body: Any) -> dict[str, Any]:
return {
"exception_class": "HTTPError",
"status_code": response.status_code,
"retryable": response.status_code in TRANSIENT_STATUS_CODES,
"request_id": response.headers.get("x-request-id"),
"body": body,
}
def describe_api_error(exc: Exception) -> dict[str, Any]:
try:
parsed = json.loads(builtins.str(exc))
if isinstance(parsed, builtins.dict) and "status_code" in parsed:
return {
"exception_class": type(exc).__name__,
"status_code": parsed.get("status_code"),
"retryable": parsed.get("retryable"),
"request_id": parsed.get("request_id"),
"message": compact_text(parsed.get("body", parsed), 500),
}
except Exception:
pass
status_code = getattr(exc, "status_code", None)
response = getattr(exc, "response", None)
request_id = None
if response is not None:
headers = getattr(response, "headers", {})
request_id = headers.get("x-request-id") if hasattr(headers, "get") else None
return {
"exception_class": type(exc).__name__,
"status_code": status_code,
"retryable": status_code in TRANSIENT_STATUS_CODES,
"request_id": request_id,
"message": builtins.str(exc)[:500],
}
def request_json(method: str, path: str, *, payload: dict[str, Any] | None = None) -> dict[str, Any]:
response = http.request(
method,
endpoint(path),
json=payload,
timeout=API_TIMEOUT_SECONDS,
)
try:
body = response.json() if response.text else {}
except json.JSONDecodeError:
body = {"raw_text": response.text}
if response.status_code >= 400:
raise RuntimeError(json.dumps(normalize_error(response, body), indent=2, default=builtins.str))
return body
def to_dict(value: Any) -> Any:
if hasattr(value, "model_dump"):
return value.model_dump(mode="json")
if isinstance(value, builtins.list):
return [to_dict(item) for item in value]
if isinstance(value, builtins.dict):
return {key: to_dict(item) for key, item in value.items()}
return value
def output_text(response: Any) -> str:
direct = getattr(response, "output_text", None)
if direct:
return direct
data = to_dict(response)
pieces: list[str] = []
for item in data.get("output", []) or []:
for content in item.get("content", []) or []:
if content.get("type") == "output_text":
pieces.append(content.get("text", ""))
return "".join(pieces)
def response_items(response: Any) -> list[dict[str, Any]]:
data = to_dict(response)
return builtins.list(data.get("output", []) or [])
def first_output_item(response: Any, item_type: str) -> dict[str, Any] | None:
for item in response_items(response):
if item.get("type") == item_type:
return item
return None
def summarize_response(response: Any) -> dict[str, Any]:
data = to_dict(response)
usage = data.get("usage") or {}
input_details = usage.get("input_tokens_details") or {}
output_details = usage.get("output_tokens_details") or {}
return {
"id": data.get("id"),
"model": data.get("model"),
"status": data.get("status"),
"output_item_types": [item.get("type") for item in data.get("output", []) or []],
"input_tokens": usage.get("input_tokens"),
"output_tokens": usage.get("output_tokens"),
"total_tokens": usage.get("total_tokens"),
"cached_input_tokens": input_details.get("cached_tokens"),
"reasoning_output_tokens": output_details.get("reasoning_tokens"),
"service_tier": data.get("service_tier"),
}
def call_with_retries(label: str, func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
kwargs.setdefault("timeout", API_TIMEOUT_SECONDS)
last_exc: Exception | None = None
for attempt in range(1, MAX_RETRIES + 2):
try:
return func(*args, **kwargs)
except Exception as exc:
last_exc = exc
error = describe_api_error(exc)
should_retry = bool(error["retryable"] and attempt <= MAX_RETRIES)
if not should_retry:
raise
time.sleep(min(2 ** (attempt - 1), 8))
raise RuntimeError(f"{label} failed after retries") from last_exc
def create_response(**kwargs: Any) -> Any:
kwargs.setdefault("model", MODEL_ID)
return call_with_retries("responses.create", client.responses.create, **kwargs)
def retrieve_response(response_id: str) -> Any:
return call_with_retries("responses.retrieve", client.responses.retrieve, response_id)
def delete_response(response_id: str) -> Any:
return call_with_retries("responses.delete", client.responses.delete, response_id)
def remember_stored_response(response: Any) -> None:
response_id = getattr(response, "id", None) or to_dict(response).get("id")
if response_id:
STORED_RESPONSE_IDS.append(response_id)
def handle_example_error(features: str | list[str], exc: Exception) -> None:
feature_list = [features] if isinstance(features, builtins.str) else features
error = describe_api_error(exc)
for feature in feature_list:
record_check(feature, "warn", error)
print_labeled_text("Result", "This live call did not complete in this environment.")
print_labeled_json("Response summary", error)
def build_curl_command(payload: dict[str, Any]) -> str:
body = json.dumps(payload)
return " ".join([
"curl", "-sS", shlex.quote(RESPONSES_URL),
"-H", shlex.quote("Content-Type: application/json"),
"-H", shlex.quote("Authorization: Bearer $AWS_BEARER_TOKEN_BEDROCK"),
"-d", shlex.quote(body),
])
def run_raw_http_request(payload: dict[str, Any]) -> dict[str, Any]:
return request_json("POST", "/responses", payload=payload)
print("Helpers ready.")
Helpers ready.
1.6 Verificar el endpoint
La primera llamada en vivo es intencionalmente pequeña. Envía una solicitud mínima de Responses con store=false y una breve instrucción de texto para que puedas detectar problemas de configuración antes de ejecutar ejemplos más ricos. Inspecciona la forma de la solicitud, el texto devuelto, el estado, el modelo, los tipos de elementos de salida y el uso de tokens.
from __future__ import annotations
preflight_payload = {
"model": MODEL_ID,
"input": "Reply with exactly: ok",
"max_output_tokens": 1024,
"store": False,
}
print_request_shape(preflight_payload)
try:
preflight_response = create_response(**preflight_payload)
require(output_text(preflight_response).strip(), "Preflight response did not return output text.")
record_check("Endpoint shape", "pass", RESPONSES_URL)
model_selection_detail = f"{len(AVAILABLE_MODELS)} models discovered" if AVAILABLE_MODELS else "Using configured model; model-list metadata is not required for requests."
record_check("Model selection", "pass", model_selection_detail)
preflight_text = output_text(preflight_response).strip()
record_response("Endpoint verification", "text", preflight_text)
print_labeled_text("Result", preflight_text)
print_response_summary(preflight_response)
print_key_takeaway('A tiny response confirms that the endpoint, key, model, and request shape are working.')
except Exception as exc:
handle_example_error(["Endpoint shape", "Model selection"], exc)
Conclusión clave: Una pequeña respuesta confirma que el endpoint, la clave, el modelo y la forma de la solicitud funcionan.
1.7 Normalizar errores de la API
Las integraciones de producción necesitan un registro de errores consistente para códigos de estado, decisiones de reintento, IDs de solicitud y cuerpos de respuesta. Esta celda documenta la forma de error normalizada utilizada por el notebook sin hacer intencionalmente una solicitud fallida. Las celdas posteriores usan la misma forma cuando una llamada en vivo falla o devuelve un estado que no es 2xx.
from __future__ import annotations
error_taxonomy_example = {
"normalized_fields": ["exception_class", "status_code", "retryable", "request_id", "message"],
"retryable_status_codes": sorted(TRANSIENT_STATUS_CODES),
"notes": "call_with_retries(...) uses this taxonomy for transient retry handling.",
}
record_check("Error handling", "pass", error_taxonomy_example)
print_json(error_taxonomy_example)
Esta sección muestra la superficie de solicitud de Responses desde dos ángulos. Primero, inspeccionas y ejecutas una solicitud HTTPS sin procesar para que el endpoint, los encabezados y el cuerpo JSON sean visibles. Luego, usas el SDK de OpenAI para el mismo tipo de flujo de trabajo de aplicación, que es el camino que la mayoría del código de producción debería preferir una vez que la configuración sea correcta.
2.1 Inspeccionar la forma de la solicitud HTTPS sin procesar
Crea una carga útil mínima de Responses para una respuesta de asistente de soporte de BrightCart y renderiza un comando curl que se puede copiar y pegar. El comando hace referencia a $AWS_BEARER_TOKEN_BEDROCK en lugar de incrustar un token, y el notebook no ejecuta comandos de shell que pongan tokens de portador en los argumentos del proceso. Inspecciona los campos model, input, max_output_tokens y store.
from __future__ import annotations
basic_curl_payload = {
"model": MODEL_ID,
"input": "BrightCart customer Maya asks why replacement order ORDER-8831 is delayed. Write two labeled plain-text lines for the support agent. Do not use leading hyphens or bold text.",
"max_output_tokens": 1024,
"store": False,
}
print_request_shape(basic_curl_payload)
print_labeled_text("Result", build_curl_command(basic_curl_payload))
print_key_takeaway('The curl command shows the raw HTTPS shape behind the SDK call.')
<IPython.core.display.HTML object>
Forma de la solicitud
<IPython.core.display.HTML object>
field
value
model
openai.gpt-5.4
max_output_tokens
1024
store
False
input
BrightCart customer Maya asks why replacement order ORDER-8831 is delayed. Write two labeled plain-text lines for the support agent. Do not use leading hyphens or bold text.
<IPython.core.display.HTML object>
Resultado
curl -sS https://bedrock-mantle.us-west-2.api.aws/openai/v1/responses -H 'Content-Type: application/json' -H 'Authorization: Bearer $AWS_BEARER_TOKEN_BEDROCK' -d '{"model": "openai.gpt-5.4", "input": "BrightCart customer Maya asks why replacement order ORDER-8831 is delayed. Write two labeled plain-text lines for the support agent. Do not use leading hyphens or bold text.", "max_output_tokens": 1024, "store": false}'
<IPython.core.display.HTML object>
Conclusión clave: El comando curl muestra la forma HTTPS sin procesar detrás de la llamada al SDK.
2.2 Enviar la solicitud HTTPS sin procesar
Envía la misma solicitud a través del ayudante HTTPS sin procesar. Esta celda demuestra la ruta POST /responses a nivel de cable y extrae texto del cuerpo de la respuesta recorriendo los elementos de salida. Inspecciona el ID de respuesta devuelto, el modelo, el estado y la salida de texto para comprender el esquema que recibe tu aplicación.
from __future__ import annotations
print_request_shape(basic_curl_payload)
try:
basic_http_response = run_raw_http_request(basic_curl_payload)
record_check("Text generation", "pass", basic_http_response.get("id"))
response_text_parts = []
for item in basic_http_response.get("output", []):
for content in item.get("content", []):
if content.get("type") == "output_text":
response_text_parts.append(content.get("text", ""))
raw_http_output = "".join(response_text_parts).strip()
record_response("First raw HTTPS request", "text", raw_http_output)
print_labeled_text("Result", raw_http_output)
print_labeled_json("Response summary", {
"id": basic_http_response.get("id"),
"model": basic_http_response.get("model"),
"status": basic_http_response.get("status"),
})
print_key_takeaway("The response body contains message output that application code can extract as text.")
except Exception as exc:
handle_example_error("Text generation", exc)
<IPython.core.display.HTML object>
Forma de la solicitud
<IPython.core.display.HTML object>
field
value
model
openai.gpt-5.4
max_output_tokens
1024
store
False
input
BrightCart customer Maya asks why replacement order ORDER-8831 is delayed. Write two labeled plain-text lines for the support agent. Do not use leading hyphens or bold text.
<IPython.core.display.HTML object>
Resultado
Empathy: I’m sorry, Maya — your replacement order ORDER-8831 is delayed because the carrier reported a temporary transit hold at the regional sorting facility.
Action: We’re monitoring the shipment closely and will send you an updated delivery estimate within 24 hours; if there’s no movement by then, we’ll review the next replacement or refund options with you.
Conclusión clave: El cuerpo de la respuesta contiene la salida del mensaje que el código de la aplicación puede extraer como texto.
2.3 Usar el SDK de OpenAI
El SDK de OpenAI puede llamar a las API compatibles con OpenAI cuando pasas explícitamente el token de portador de Bedrock y la URL base. Esta celda envía una solicitud de generación de texto a través de client.responses.create, establece reasoning.effort en low e imprime un resumen de respuesta compacto. Inspecciona el texto de salida, los recuentos de tokens, los tipos de elementos de salida y cualquier metadato de token de razonamiento devuelto por el endpoint.
Documentación oficial: Reasoning models describe el uso del esfuerzo de razonamiento con la API de Responses.
from __future__ import annotations
sdk_text_payload = {
"model": MODEL_ID,
"input": "Write a three-sentence overview for a developer building a BrightCart support assistant with the Responses API.",
"reasoning": {"effort": "low"},
"max_output_tokens": 1024,
"store": False,
}
print_request_shape(sdk_text_payload)
try:
text_response = create_response(**sdk_text_payload)
sdk_text = output_text(text_response).strip()
require(sdk_text, "SDK text response did not return output text.")
record_check("Text generation", "pass", summarize_response(text_response))
record_check("Reasoning effort", "pass", summarize_response(text_response))
record_response("SDK text generation", "text", sdk_text)
print_labeled_text("Result", sdk_text)
print_response_summary(text_response)
print_key_takeaway('The SDK returns a response object with text, status, token usage, and output item metadata.')
except Exception as exc:
handle_example_error(["Text generation", "Reasoning effort"], exc)
<IPython.core.display.HTML object>
Forma de la solicitud
<IPython.core.display.HTML object>
field
value
model
openai.gpt-5.4
max_output_tokens
1024
store
False
reasoning
{'effort': 'low'}
input
Write a three-sentence overview for a developer building a BrightCart support assistant with the Responses API.
<IPython.core.display.HTML object>
Resultado
Use la API de Responses para construir un asistente de soporte de BrightCart que pueda responder preguntas de clientes, resumir políticas y guiar a los usuarios a través de flujos de trabajo comunes como el seguimiento de pedidos, reembolsos y actualizaciones de cuentas. Basa el asistente en la documentación de BrightCart y conéctalo a herramientas o API de backend relevantes para que pueda recuperar datos de pedidos en vivo, verificar el estado de la cuenta y proporcionar respuestas de soporte precisas y conscientes del contexto. Diseña la experiencia en torno a instrucciones claras del sistema, llamadas a herramientas estructuradas y gestión del estado de la conversación para que el asistente se mantenga fiel a la marca, confiable y seguro al manejar los problemas de los clientes.
Conclusión clave: El SDK devuelve un objeto de respuesta con texto, estado, uso de tokens y metadatos de elementos de salida.
2.4 Crear y recuperar una respuesta
La API de Responses puede almacenar una respuesta y recuperarla más tarde por ID. Este patrón es útil para auditorías, depuración y turnos de seguimiento que hacen referencia a un contexto anterior. Esta celda crea una respuesta almacenada, rastrea el ID para su limpieza, la recupera y compara el texto recuperado y los metadatos de uso.
from __future__ import annotations
lifecycle_payload = {
"model": MODEL_ID,
"input": (
"BrightCart is building a support assistant for delayed replacement orders. "
"Return exactly three labeled plain-text lines: goal, data needed, and human-review rule. Do not use leading hyphens or bold text."
),
"max_output_tokens": 1024,
"store": True,
}
print_request_shape(lifecycle_payload)
try:
lifecycle_response = create_response(**lifecycle_payload)
remember_stored_response(lifecycle_response)
retrieved_response = retrieve_response(lifecycle_response.id)
retrieved_summary = summarize_response(retrieved_response)
retrieved_text = output_text(retrieved_response).strip()
require(retrieved_text, "Retrieved response did not contain text output.")
lifecycle_status = "pass" if retrieved_summary.get("status") in {None, "completed"} else "warn"
record_check("Responses lifecycle", lifecycle_status, retrieved_response.id)
record_check("Response schema", "pass", retrieved_summary)
record_check("Usage metadata", "pass" if retrieved_summary.get("total_tokens") is not None else "warn", retrieved_summary)
record_response("Create and retrieve response", "text", retrieved_text)
print_labeled_text("Result", retrieved_text)
print_labeled_json("Created response summary", summarize_response(lifecycle_response))
print_response_summary(retrieved_summary)
print_key_takeaway('store=True lets an application retrieve the response later by ID with usage metadata intact.')
except Exception as exc:
handle_example_error(["Responses lifecycle", "Response schema", "Usage metadata"], exc)
<IPython.core.display.HTML object>
Forma de la solicitud
<IPython.core.display.HTML object>
field
value
model
openai.gpt-5.4
max_output_tokens
1024
store
True
input
BrightCart is building a support assistant for delayed replacement orders. Return exactly three labeled plain-text lines: goal, data needed, and human-review rule. Do not use leading hyphens or bold text.
<IPython.core.display.HTML object>
Resultado
goal: Ayudar a los agentes de soporte a explicar los pedidos de reemplazo retrasados, establecer expectativas y sugerir los próximos pasos.
data needed: ID de pedido, estado del pedido de reemplazo, eventos de envío/seguimiento, motivo del retraso, fecha estimada de envío/entrega, historial de contacto del cliente, estado de inventario/pedidos pendientes y política de reembolso o reenvío aplicable.
human-review rule: Escalar a un humano si el retraso excede los umbrales de la política, el seguimiento es inconsistente o falta, el pedido parece perdido, el cliente es de alto riesgo o está muy molesto, o se solicita cualquier excepción de reembolso/reenvío.
Conclusión clave: store=True permite que una aplicación recupere la respuesta más tarde por ID con los metadatos de uso intactos.
2.5 Añade parámetros de esfuerzo de razonamiento, nivel de servicio y caché de prompt
Los controles del modelo viajan junto con la entrada normal. Esta solicitud combina reasoning.effort, service_tier, prompt_cache_key y prompt_cache_retention para que puedas ver cómo los controles operativos y los metadatos de la caché de prompt aparecen en el mismo esquema de respuesta que la salida de texto ordinaria. Inspecciona service_tier, cached_input_tokens, los metadatos del token de razonamiento y el uso total de tokens.
Nota: Este notebook usa PROMPT_CACHE_RETENTION en lugar de codificar prompt_cache_retention. El valor es in_memory para openai.gpt-5.4, y 24h para openai.gpt-5.5 y modelos posteriores porque esos modelos requieren una caché de prompt extendida.
from __future__ import annotations
control_payload = {
"model": MODEL_ID,
"input": (
"For the BrightCart support assistant, explain prompt caching in exactly two labeled plain-text lines: "
"one latency benefit and one consistency benefit."
),
"reasoning": {"effort": "low"},
"prompt_cache_key": "brightcart-support-policy-guide",
"prompt_cache_retention": PROMPT_CACHE_RETENTION,
"service_tier": "auto",
"max_output_tokens": 1024,
"store": False,
}
print_request_shape(control_payload)
try:
control_response = create_response(**control_payload)
control_summary = summarize_response(control_response)
control_text = output_text(control_response).strip()
require(control_text, "Control response did not return text.")
status = "pass" if control_summary.get("status") in {None, "completed"} else "warn"
record_check("Prompt caching", "pass" if control_summary.get("cached_input_tokens") is not None else "warn", control_summary)
record_check("Service tier", "pass" if control_summary.get("service_tier") is not None else "warn", control_summary)
record_check("Reasoning effort", status, control_summary)
record_response("Service tier and prompt cache request", "text", control_text)
print_labeled_text("Result", control_text)
print_response_summary(control_summary)
print_key_takeaway('Model controls travel with the same request as normal input, while returned metadata can vary by endpoint.')
except Exception as exc:
handle_example_error(["Prompt caching", "Service tier", "Reasoning effort"], exc)
<IPython.core.display.HTML object>
Forma de la solicitud
<IPython.core.display.HTML object>
field
value
model
openai.gpt-5.4
max_output_tokens
1024
store
False
service_tier
auto
prompt_cache_key
brightcart-support-policy-guide
prompt_cache_retention
in_memory
reasoning
{'effort': 'low'}
input
For the BrightCart support assistant, explain prompt caching in exactly two labeled plain-text lines: one latency benefit and one consistency benefit.
<IPython.core.display.HTML object>
Resultado
Latency benefit: Prompt caching lets the BrightCart support assistant reuse previously processed context, reducing response time for repeated or similar requests.
Consistency benefit: Prompt caching helps the BrightCart support assistant return more uniform answers by reusing the same established prompt context across interactions.
Conclusión clave: Los controles del modelo viajan con la misma solicitud que la entrada normal, mientras que los metadatos devueltos pueden variar según el endpoint.
3. Generar JSON estructurado
El JSON estructurado convierte la salida del modelo en datos que el código de la aplicación puede analizar, validar y enrutar. Esta sección compara la salida estricta con esquema restringido con el modo JSON más ligero. Usa las salidas estructuradas cuando tu aplicación necesite un contrato; usa el modo JSON cuando un JSON válido sea suficiente, pero el esquema exacto pueda permanecer flexible.
3.1 Define el esquema de salida estructurada
Define el esquema de ticket de soporte utilizado por la siguiente solicitud en vivo. El esquema enumera los campos exactos que espera la aplicación, incluyendo categoría, prioridad, sentimiento, resumen, acciones requeridas y estado de escalada. Inspecciona la forma de la solicitud para ver cómo text.format.type="json_schema", strict=true y el esquema JSON se adjuntan a una solicitud de respuestas normal.
from __future__ import annotations
support_triage_schema = {
"type": "object",
"properties": {
"ticket_id": {"type": "string"},
"category": {"type": "string", "enum": ["delivery_delay", "return_exchange", "damaged_item", "billing", "account"]},
"priority": {"type": "string", "enum": ["low", "medium", "high", "urgent"]},
"customer_sentiment": {"type": "string"},
"summary": {"type": "string"},
"required_actions": {"type": "array", "items": {"type": "string"}, "minItems": 2},
"escalation_needed": {"type": "boolean"},
},
"required": ["ticket_id", "category", "priority", "customer_sentiment", "summary", "required_actions", "escalation_needed"],
"additionalProperties": False,
}
structured_payload = {
"model": MODEL_ID,
"input": (
"Support ticket TICKET-7429: Maya Chen says ORDER-8831 is a replacement for a damaged standing desk. "
"The replacement is two days late, the carrier scan has not moved, and she needs the desk before Monday. "
"She asks for a supervisor callback and refund options. Triage this ticket for the next support agent."
),
"text": {"format": {"type": "json_schema", "name": "support_ticket_triage", "strict": True, "schema": support_triage_schema}},
"max_output_tokens": 1024,
"store": False,
}
print_request_shape(structured_payload)
print_key_takeaway('The schema is part of the request and defines the fields the next cell validates.')
Support ticket TICKET-7429: Maya Chen says ORDER-8831 is a replacement for a damaged standing desk. The replacement is two days late, the carrier scan has not moved, and she needs the desk before Monday. She asks for a supervisor callback and refund options. T...
<IPython.core.display.HTML object>
Conclusión clave: El esquema es parte de la solicitud y define los campos que la siguiente celda valida.
3.2 Valida la salida con esquema restringido
Llama al modelo con el esquema de la celda anterior, analiza el texto devuelto como JSON y valida los campos importantes en Python. La solicitud de API pide adherencia al esquema, mientras que la validación del lado de la aplicación aún verifica que el objeto devuelto sea adecuado para el enrutamiento posterior. Inspecciona el objeto analizado y el resumen de la respuesta.
from __future__ import annotations
def validate_support_triage(payload: dict[str, Any]) -> dict[str, Any]:
require("ticket_id" in payload, "Missing key: ticket_id")
require(payload.get("ticket_id") == "TICKET-7429", "Ticket ID did not match expected value.")
require("required_actions" in payload, "Missing key: required_actions")
require(isinstance(payload.get("required_actions"), builtins.list), "required_actions must be a list.")
require(len(payload["required_actions"]) >= 2, "required_actions should contain at least two actions.")
return payload
print_request_shape(structured_payload)
try:
structured_response = create_response(**structured_payload)
raw_structured_text = output_text(structured_response).strip()
try:
structured_payload_result = validate_support_triage(json.loads(raw_structured_text))
record_check("Structured Outputs", "pass", structured_payload_result)
record_response("Structured ticket triage", "json", structured_payload_result)
print_labeled_json("Result", structured_payload_result)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON: {e}")
except Exception as parse_exc:
record_check("Structured Outputs", "warn", {"message": "Response did not match the expected schema shape.", "text_sample": raw_structured_text[:600], "error": builtins.str(parse_exc)})
print_labeled_text("Result", "The request completed, but the returned text did not match the expected schema shape.")
print_wrapped(raw_structured_text[:1200])
print_response_summary(structured_response)
print_key_takeaway('Schema-constrained output gives application code a predictable JSON object to parse and validate.')
except Exception as exc:
handle_example_error("Structured Outputs", exc)
Support ticket TICKET-7429: Maya Chen says ORDER-8831 is a replacement for a damaged standing desk. The replacement is two days late, the carrier scan has not moved, and she needs the desk before Monday. She asks for a supervisor callback and refund options. T...
<IPython.core.display.HTML object>
Resultado
{
"ticket_id": "TICKET-7429",
"category": "delivery_delay",
"priority": "urgent",
"customer_sentiment": "frustrated and time-sensitive",
"summary": "Customer Maya Chen reports that ORDER-8831 is a replacement shipment for a previously damaged standing desk. The replacement is now 2 days late, carrier tracking has not updated, and she needs the desk delivered before Monday. She is requesting a supervisor callback and wants to know refund options if the replacement cannot arrive in time.",
"required_actions": [
"Review ORDER-8831 shipment status and confirm last carrier scan/update.",
"Contact carrier or open a trace/escalation for stalled tracking.",
"Check expedited reshipment or alternative fulfillment options to meet the before-Monday deadline.",
"Arrange supervisor callback per customer request.",
"Review and communicate refund options, including refund for replacement order and any prior damaged-item resolution details.",
"Verify whether replacement shipment should be intercepted/returned if a refund or reshipment is approved."
],
"escalation_needed": true
}
Conclusión clave: La salida con esquema restringido le da al código de la aplicación un objeto JSON predecible para analizar y validar.
3.3 Usa el modo JSON
El modo JSON le pide al modelo que devuelva un objeto JSON válido sin imponer un esquema estricto. Esto es útil para entregas ligeras donde aún quieres una salida analizable, pero puedes tolerar un contrato más flexible. Esta celda solicita un objeto de entrega de soporte, lo analiza y verifica las claves esperadas.
from __future__ import annotations
json_mode_payload = {
"model": MODEL_ID,
"input": (
"Return JSON for a support chat handoff with keys customer_name, order_id, issue_summary, next_step, "
"and metrics_to_watch. Context: Maya Chen asks about delayed replacement order ORDER-8831; the carrier scan is stale. "
"metrics_to_watch should be an array."
),
"text": {"format": {"type": "json_object"}},
"max_output_tokens": 1024,
"store": False,
}
print_request_shape(json_mode_payload)
try:
json_mode_response = create_response(**json_mode_payload)
payload = json.loads(output_text(json_mode_response).strip())
require({"customer_name", "order_id", "issue_summary", "next_step", "metrics_to_watch"}.issubset(payload), "JSON mode response missed required keys.")
record_check("JSON mode", "pass", payload)
record_response("JSON support handoff", "json", payload)
print_labeled_json("Result", payload)
print_response_summary(json_mode_response)
print_key_takeaway('JSON mode is useful when valid JSON is enough and a strict schema is not required.')
except Exception as exc:
handle_example_error("JSON mode", exc)
<IPython.core.display.HTML object>
Forma de la solicitud
<IPython.core.display.HTML object>
field
value
model
openai.gpt-5.4
max_output_tokens
1024
store
False
text format
json_object
input
Return JSON for a support chat handoff with keys customer_name, order_id, issue_summary, next_step, and metrics_to_watch. Context: Maya Chen asks about delayed replacement order ORDER-8831; the carrier scan is stale. metrics_to_watch should be an array.
<IPython.core.display.HTML object>
Resultado
{
"customer_name": "Maya Chen",
"order_id": "ORDER-8831",
"issue_summary": "Customer is asking about a delayed replacement order. The carrier tracking scan is stale and has not updated.",
"next_step": "Handoff to support to investigate the carrier delay, verify shipment status, and provide Maya Chen with an update or resolution.",
"metrics_to_watch": [
"tracking_scan_recency",
"carrier_exception_status",
"replacement_order_delivery_eta",
"customer_follow_up_time"
]
}
Conclusión clave: El modo JSON es útil cuando un JSON válido es suficiente y no se requiere un esquema estricto.
3.4 Controla la verbosidad desde el esfuerzo de razonamiento
Los controles de verbosidad ayudan a ajustar la forma de la prosa generada, mientras que el esfuerzo de razonamiento controla cuánto trabajo de razonamiento dedica el modelo antes de responder. El notebook demuestra reasoning.effort en el SDK y las celdas de control del modelo anteriores; esta celda se enfoca en text.verbosity enviando versiones compactas y detalladas del mismo tema de política. Inspecciona el texto lado a lado y los resúmenes de tokens para comparar el estilo y el uso.
from __future__ import annotations
verbosity_prompt = "Explain BrightCart's delayed-replacement policy to a new support agent."
compact_payload = {
"model": MODEL_ID,
"input": verbosity_prompt + " Reply in one sentence under 35 words.",
"text": {"verbosity": "low"},
"max_output_tokens": 1024,
"store": False,
}
detailed_payload = {
"model": MODEL_ID,
"input": verbosity_prompt + " Reply in exactly three numbered plain-text lines, each under 18 words. Do not use leading hyphens or bold text.",
"text": {"verbosity": "high"},
"max_output_tokens": 1024,
"store": False,
}
print_labeled_json("Request shape", {
"compact": redact_payload(compact_payload),
"detailed": redact_payload(detailed_payload),
})
try:
compact_response = create_response(**compact_payload)
detailed_response = create_response(**detailed_payload)
compact_guidance_text = output_text(compact_response).strip()
detailed_guidance_text = output_text(detailed_response).strip()
require(compact_guidance_text and detailed_guidance_text, "Verbosity responses did not return text.")
compact_summary = summarize_response(compact_response)
detailed_summary = summarize_response(detailed_response)
status = "pass" if compact_summary.get("status") in {None, "completed"} and detailed_summary.get("status") in {None, "completed"} else "warn"
record_check("Verbosity", status, {"compact_chars": len(compact_guidance_text), "detailed_chars": len(detailed_guidance_text)})
record_response("Compact policy guidance", "text", compact_guidance_text)
record_response("Detailed policy guidance", "text", detailed_guidance_text)
print_labeled_text("Result: compact guidance", compact_guidance_text)
print_labeled_text("Result: detailed guidance", detailed_guidance_text)
verbosity_summary = pd.DataFrame([
{"request": "compact", **compact_summary},
{"request": "detailed", **detailed_summary},
])
print_label("Response summary")
display_wrapped_table(verbosity_summary, max_col_width_px=420)
print_key_takeaway('Verbosity controls tune the answer style while the prompt still bounds the output.')
except Exception as exc:
handle_example_error("Verbosity", exc)
<IPython.core.display.HTML object>
Forma de la solicitud
{
"compact": {
"model": "openai.gpt-5.4",
"input": "Explain BrightCart's delayed-replacement policy to a new support agent. Reply in one sentence under 35 words.",
"text": {
"verbosity": "low"
},
"max_output_tokens": 1024,
"store": false
},
"detailed": {
"model": "openai.gpt-5.4",
"input": "Explain BrightCart's delayed-replacement policy to a new support agent. Reply in exactly three numbered plain-text lines, each under 18 words. Do not use leading hyphens or bold text.",
"text": {
"verbosity": "high"
},
"max_output_tokens": 1024,
"store": false
}
}
<IPython.core.display.HTML object>
Resultado: guía compacta
BrightCart’s delayed-replacement policy lets customers keep using the original item until the replacement arrives, then return the defective product within the allowed return window.
<IPython.core.display.HTML object>
Resultado: guía detallada
1. BrightCart sends replacements after customers return the original item and warehouse receipt is confirmed.
2. This delay prevents duplicate shipments, verifies eligibility, and reduces fraud or inventory errors.
3. Agents should explain timelines clearly, offer return instructions, and reassure customers once receipt is logged.
Conclusión clave: Los controles de verbosidad ajustan el estilo de la respuesta mientras que el prompt aún limita la salida.
4. Añade herramientas gestionadas por la aplicación
La llamada a funciones permite que el modelo le pida datos o acciones a tu aplicación, pero tu código sigue siendo responsable de ejecutar las herramientas y devolver los resultados. Esta sección define las herramientas locales de BrightCart, luego recorre una sola llamada a función, múltiples llamadas independientes y una herramienta de texto personalizada. Los ejemplos mantienen las salidas de las herramientas deterministas para que el bucle de solicitud sea fácil de inspeccionar.
4.1 Define los esquemas y funciones de las herramientas locales
Define herramientas de muestra locales para el estado del pedido y las búsquedas de perfiles de clientes. Los esquemas de las herramientas describen los nombres, descripciones, formas de los argumentos, campos requeridos y la rigurosidad que el modelo puede usar al decidir qué llamar. Las funciones de Python representan sistemas de aplicación como la gestión de pedidos, CRM o servicios de políticas.
from __future__ import annotations
function_tools = [
{
"type": "function",
"name": "get_order_status",
"description": "Look up a sample BrightCart order status.",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string", "description": "An order ID such as ORDER-8831."}},
"required": ["order_id"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_customer_profile",
"description": "Look up sample customer context for a BrightCart support interaction.",
"parameters": {
"type": "object",
"properties": {"customer_id": {"type": "string", "description": "A customer ID such as CUST-1042."}},
"required": ["customer_id"],
"additionalProperties": False,
},
"strict": True,
},
]
def get_order_status(order_id: str) -> dict[str, Any]:
orders = {
"ORDER-8831": {
"order_id": "ORDER-8831",
"customer_id": "CUST-1042",
"item": "standing desk replacement",
"status": "delayed",
"carrier_scan": "No movement for 36 hours at Denver sort center",
"promised_delivery": (date.today() + timedelta(days=2)).isoformat(),
"recommended_policy": "If delay exceeds 48 hours, offer expedited replacement or 15% concession with agent approval.",
},
"ORDER-2044": {
"order_id": "ORDER-2044",
"customer_id": "CUST-1042",
"item": "ergonomic chair",
"status": "delivered",
"carrier_scan": "Delivered yesterday at front desk",
"promised_delivery": (date.today() - timedelta(days=1)).isoformat(),
"recommended_policy": "Confirm delivery details before opening a replacement request.",
},
}
return orders.get(order_id, {"order_id": order_id, "status": "unknown", "customer_id": None})
def get_customer_profile(customer_id: str) -> dict[str, Any]:
profiles = {
"CUST-1042": {
"customer_id": "CUST-1042",
"name": "Maya Chen",
"loyalty_tier": "Gold",
"region": "California",
"recent_issue": "Damaged standing desk replacement",
"contact_preference": "email with SMS updates for shipping changes",
}
}
return profiles.get(customer_id, {"customer_id": customer_id, "loyalty_tier": "unknown"})
def dispatch_tool_call(call: dict[str, Any]) -> dict[str, Any]:
name = call["name"]
args = json.loads(call["arguments"])
if name == "get_order_status":
output = get_order_status(**args)
elif name == "get_customer_profile":
output = get_customer_profile(**args)
else:
raise ValueError(f"Unsupported tool: {name}")
return {"type": "function_call_output", "call_id": call["call_id"], "output": json.dumps(output)}
print("Sample function tools:")
print_json([tool["name"] for tool in function_tools])
print("\nSample order lookup:")
print_json(get_order_status("ORDER-8831"))
Sample function tools:
<IPython.core.display.HTML object>
JSON
[
"get_order_status",
"get_customer_profile"
]
Sample order lookup:
<IPython.core.display.HTML object>
JSON
{
"order_id": "ORDER-8831",
"customer_id": "CUST-1042",
"item": "standing desk replacement",
"status": "delayed",
"carrier_scan": "No movement for 36 hours at Denver sort center",
"promised_delivery": "2026-06-01",
"recommended_policy": "If delay exceeds 48 hours, offer expedited replacement or 15% concession with agent approval."
}
4.2 Llamar a una herramienta de función
Esta celda ejecuta el bucle básico de llamada a funciones. La primera solicitud le da al modelo una herramienta de estado de pedidos y le pide que elija los argumentos. La aplicación analiza el function_call devuelto, ejecuta la función local de Python, envía un elemento function_call_output de vuelta y pide la respuesta final fundamentada. Inspecciona los argumentos de la herramienta, la salida de la herramienta local, el texto final del modelo y los metadatos de la respuesta.
from __future__ import annotations
function_input = [{"role": "user", "content": "Use get_order_status for ORDER-8831, then explain the next best action for the support agent in two labeled plain-text lines. Do not use leading hyphens or bold text."}]
order_status_tool = [tool for tool in function_tools if tool["name"] == "get_order_status"]
function_request = {
"model": MODEL_ID,
"input": function_input,
"tools": order_status_tool,
"tool_choice": "required",
"max_output_tokens": 1024,
"store": False,
}
def create_tool_plan_with_auto_fallback(request: dict[str, Any]) -> tuple[Any, str]:
try:
return create_response(**request), builtins.str(request.get("tool_choice"))
except Exception as first_exc:
fallback_request = {**request, "tool_choice": "auto"}
try:
return create_response(**fallback_request), "auto"
except Exception:
raise first_exc
print_request_shape(function_request)
try:
function_plan, tool_choice_used = create_tool_plan_with_auto_fallback(function_request)
function_calls = [item for item in response_items(function_plan) if item.get("type") == "function_call"]
if function_calls:
function_call = function_calls[0]
function_args = json.loads(function_call["arguments"])
require(function_args.get("order_id") == "ORDER-8831", f"Unexpected function arguments: {function_args}")
tool_output = dispatch_tool_call(function_call)
final_response = create_response(
model=MODEL_ID,
input=function_input + response_items(function_plan) + [tool_output],
tools=order_status_tool,
max_output_tokens=1024,
store=False,
)
final_answer = output_text(final_response).strip()
tool_output_payload = json.loads(tool_output["output"])
record_check("Function calling", "pass", {"tool_choice_used": tool_choice_used, "arguments": function_args})
record_response("Order-status tool answer", "text", final_answer)
print_labeled_json("Result: tool arguments", function_args)
print_labeled_json("Result: tool output", tool_output_payload)
print_labeled_text("Result: final model answer", final_answer)
print_response_summary(final_response)
print_key_takeaway('Function calling separates model-selected arguments from application-executed business logic.')
else:
fallback_order = get_order_status("ORDER-8831")
fallback_prompt = (
"The model response did not include a function_call item. Use this application lookup result "
"to answer in two labeled plain-text lines without leading hyphens or bold text: " + json.dumps(fallback_order)
)
final_response = create_response(
model=MODEL_ID,
input=function_input + [{"role": "user", "content": fallback_prompt}],
max_output_tokens=1024,
store=False,
)
final_answer = output_text(final_response).strip()
returned_item_types = [item.get("type") for item in response_items(function_plan)]
record_check("Function calling", "warn", {"tool_choice_used": tool_choice_used, "returned_item_types": returned_item_types})
record_response("Order-status local fallback answer", "text", final_answer)
print_labeled_json("Result: returned output item types", returned_item_types)
print_labeled_json("Result: local tool output", fallback_order)
print_labeled_text("Result: final model answer", final_answer)
print_response_summary(final_response)
print_key_takeaway('The local lookup keeps the function-calling pattern understandable even when the model returns text.')
except Exception as exc:
handle_example_error("Function calling", exc)
<IPython.core.display.HTML object>
Forma de la solicitud
<IPython.core.display.HTML object>
field
value
model
openai.gpt-5.4
max_output_tokens
1024
store
False
tools
get_order_status
tool_choice
required
input
1 item(s): user: Usa get_order_status para ORDER-8831, luego explica la siguiente mejor acción para el agente de soporte en dos líneas de texto plano etiquetadas. No uses guiones iniciales ni texto en negrita.
<IPython.core.display.HTML object>
Resultado: argumentos de la herramienta
{
"order_id": "ORDER-8831"
}
<IPython.core.display.HTML object>
Resultado: salida de la herramienta
{
"order_id": "ORDER-8831",
"customer_id": "CUST-1042",
"item": "standing desk replacement",
"status": "delayed",
"carrier_scan": "No movement for 36 hours at Denver sort center",
"promised_delivery": "2026-06-01",
"recommended_policy": "If delay exceeds 48 hours, offer expedited replacement or 15% concession with agent approval."
}
<IPython.core.display.HTML object>
Resultado: respuesta final del modelo
Estado: ORDER-8831 está retrasado; el transportista no muestra movimiento durante 36 horas en el centro de clasificación de Denver, con entrega prometida el 2026-06-01.
Siguiente mejor acción: Monitorea hasta el umbral de 48 horas; si no hay movimiento para entonces, contacta al cliente y ofrécele un reemplazo acelerado o una concesión del 15% con la aprobación del agente.
Conclusión clave: La llamada a funciones separa los argumentos seleccionados por el modelo de la lógica de negocio ejecutada por la aplicación.
Lección del curso «OpenAI Cookbook» de OpenAI, publicado con licencia MIT. Traducción y adaptación al español de IA con Clase. IA con Clase no está afiliado a OpenAI. Ver el original · Licencia
Esta lección es gratuita. El resto del curso se abre con la Membresía de IA con Clase, que incluye todos los cursos del catálogo. Ver precios