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.
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ámetro | Qué decide | Naturaleza |
|---|---|---|
temperature | Cuánto se aplana la distribución antes de muestrear | Estadística: cambia cómo se elige |
max_output_tokens | El techo de longitud de la salida | Presupuesto: corta, no persuade |
stop_sequences | Cadenas que, al aparecer, detienen la generación | Comparación de texto, literal |
candidate_count | Cuántas respuestas alternativas pedir de una vez | Estructura: 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.
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.
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: STOPCredencial inválida, región no soportada, cuota agotada, red caída. Nunca llegó a haber generación. Reintentar puede tener sentido.
excepción · 4xx / 5xxLa 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 candidatosEl 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.
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.
.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»?
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í.