MÓDULO 8 · CLOSED SOURCE MODELS · 25 MIN

GenerationConfig y la respuesta que puede no llegar

GenerationConfig es el panel de control de la generación: cuántas respuestas, cuándo parar, cuánto texto como máximo y con cuánta dispersión. Pero el detalle que más código cambia no está en ese panel: una llamada a un modelo cerrado tiene tres desenlaces, no dos. Además de responder o fallar, puede volver vacía porque el proveedor decidió no contestar — y el atajo con el que casi todo el mundo lee la respuesta no contempla ese tercer caso.
01 — El panel
Qué controla cada mando

Los cuatro parámetros que aparecen en el material hacen cosas de naturaleza distinta, y conviene no meterlos en el mismo saco de «ajustes de creatividad»:

ParámetroQué decideNaturaleza
temperatureCuánto se aplana la distribución antes de muestrearEstadística: cambia cómo se elige
max_output_tokensEl techo de longitud de la salidaPresupuesto: corta, no persuade
stop_sequencesCadenas que, al aparecer, detienen la generaciónComparación de texto, literal
candidate_countCuántas respuestas alternativas pedir de una vezEstructura: cambia la forma de lo que vuelve

Las dos últimas son las que más sorprenden. stop_sequences no expresa una intención, compara cadenas: la guía pide un micro-cuento que «termine con la palabra FIN» y a la vez declara FIN como secuencia de parada, así que el corte ocurre antes de emitirla y la palabra nunca sale. Es una demostración deliberada y elegante — y frágil, porque «Fin», «FINAL» o «fin.» pueden no disparar la misma coincidencia.

// una stop sequence no entiende lo que pediste: solo mira si el texto encaja

Y candidate_count explica por qué la respuesta tiene la forma que tiene. No devuelve un texto: devuelve una lista de candidatos, cada uno con sus partes. Que normalmente haya exactamente uno es lo que permite el atajo de la sección siguiente — y lo que lo hace peligroso.

02 — Desenlaces
Una llamada, tres finales posibles
Desenlace 1

Respuesta

Hay al menos un candidato con contenido. Es el caso que prueban todos los ejemplos y el único para el que el código de la guía está preparado.

finish_reason: STOP
Desenlace 2

Error de transporte

Credencial inválida, región no soportada, cuota agotada, red caída. Nunca llegó a haber generación. Reintentar puede tener sentido.

excepción · 4xx / 5xx
Desenlace 3

Sin contestación

La llamada fue bien, el modelo generó o empezó a generar, y el filtro de contenido lo cortó. No hay error de red que capturar. Reintentar lo mismo no arregla nada.

finish_reason: SAFETY · sin candidatos

El tercero es el que rompe la intuición, porque no se parece a un fallo: la petición HTTP fue correcta y el servidor contestó. Lo que no hay es texto. La propia guía lista Finish reason: SAFETY en su tabla de troubleshooting, con la causa bien explicada, pero en una página distinta del código que lo produce.

03 — Lectura
Leer la respuesta sin mentirse

El material lee la respuesta de dos formas dentro de la misma lección. Primero así:

try:
    respuesta = modelo.generate_content(prompt)
    print(respuesta.text)            # el atajo
except Exception as e:
    print(f"Error: {e}")

Y unas páginas después, así, con un comentario que dice «accedemos al texto de forma segura» y no explica de qué hay que protegerse:

if response.candidates:
    print(response.candidates[0].content.parts[0].text)
else:
    print("La respuesta fue bloqueada por filtros de seguridad.")

No son equivalentes. .text asume que existe un candidato con al menos una parte de texto; cuando el filtro corta, esa suposición no se cumple y el acceso lanza excepción en vez de devolver vacío. Y ahí es donde el primer bloque se vuelve activamente dañino: ese except Exception tan amplio convierte un bloqueo por política de contenido y una caída de red en la misma línea impresa. Se pierde justo la distinción que decide qué hacer después.

La regla práctica: mira la lista de candidatos antes de leer texto, y mira por qué terminó. Lo primero te dice si hay algo; lo segundo, si lo que hay está completo o lo cortaron —el techo de max_output_tokens también termina generaciones, y una respuesta truncada por presupuesto se lee igual de bien que una terminada de verdad.

Auditoría Tres preguntas para responder tú. Una: ¿qué hace exactamente .text en la versión del SDK que tienes instalada cuando no hay candidatos — lanza, devuelve cadena vacía, o devuelve None? La respuesta decide si tu código actual tiene un bug latente o solo una imprecisión. Dos: el material escribe cuatro nombres de modelo distintos entre sus scripts, uno de ellos gemini-1.2-flash; ¿existe esa serie, o es una errata que se manifestará como un error de modelo no encontrado que parecerá un problema de tu clave? Tres: ¿cuántos valores distintos de finish_reason puede devolver hoy la API, y cuáles de ellos significan «hay texto pero está incompleto» en vez de «no hay texto»?
Para hacer
Provocar el tercer desenlace

Escribe un script que imprima, para cada llamada, tres cosas antes de tocar el texto: cuántos candidatos han vuelto, el finish_reason del primero si lo hay, y solo entonces la salida. Lánzalo dos veces: una con un prompt inofensivo y otra con el micro-cuento de terror que propone la guía, subiendo la exigencia hasta que algo se corte.

Cuando consigas un desenlace sin texto, quita el if y deja el .text pelado para ver con tus ojos qué pasa. Ese es el error que en producción aparece de madrugada, y vale mucho más haberlo visto una vez en tu propia terminal que haberlo leído aquí.