Gemini generateContent
MarkdownFormato nativo de Google Gemini: para Gemini CLI, Google GenAI SDK y clientes REST.
Guía completa
https://api.ailoot.ai/v1beta/models/{model}:generateContentcurl "https://api.ailoot.ai/v1beta/models/gemini-3.8-flash:generateContent" \
-H "x-goog-api-key: $AILOOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [
{"role": "user", "parts": [{"text": "Inventa tres nombres para una cafetería."}]}
]
}' from google import genai
from google.genai import types
import os
client = genai.Client(
api_key=os.environ["AILOOT_API_KEY"],
http_options=types.HttpOptions(base_url="https://api.ailoot.ai"), # sin /v1beta
)
resp = client.models.generate_content(
model="gemini-3.8-flash",
contents="Inventa tres nombres para una cafetería.",
)
print(resp.text)import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({
apiKey: process.env.AILOOT_API_KEY,
httpOptions: { baseUrl: "https://api.ailoot.ai" }, // sin /v1beta
});
const resp = await ai.models.generateContent({
model: "gemini-3.8-flash",
contents: "Inventa tres nombres para una cafetería.",
});
console.log(resp.text);{
"candidates": [
{
"content": {
"role": "model",
"parts": [{ "text": "1. «Grano y vapor» 2. «Molienda matutina» 3. «Cafetería de la esquina»" }]
},
"finishReason": "STOP"
}
],
"usageMetadata": {
"promptTokenCount": 9,
"candidatesTokenCount": 27,
"totalTokenCount": 36
},
"modelVersion": "gemini-3.8-flash"
}Autenticación
- x-goog-api-keyheaderobligatorio
Clave
sk-…. También se admitenAuthorization: Bearerox-api-key. Por compatibilidad con Google, también se admite?key=en la URL, con la prioridad más baja;?api_key=se rechaza con 400. Recomendamos usar un encabezado para que la clave no aparezca en los registros.
Ruta
- modelstringobligatorio
ID de un modelo con formato Gemini, por ejemplo,
gemini-3.8-flashogemini-3.1-pro. Hay 27 en total.
https://api.ailoot.ai/v1beta/models/{model}:streamGenerateContent?alt=ssehttps://api.ailoot.ai/v1beta/modelsCuerpo de la solicitud
- contentsarrayobligatorio
Mensajes con
role(user/model) yparts: text, inlineData/fileData, functionCall/functionResponse. Los adjuntos deben ser de los tipos que admite el modelo como entrada. - systemInstructionobjectopcional
Instrucción de sistema en formato de texto:
{"parts": [{"text": "…"}]}. - generationConfigobjectopcional
generationConfigadmitetemperature,topP,topK,maxOutputTokens,thinkingConfig,responseMimeType(text/plainoapplication/json),responseSchemaoresponseJsonSchema(objeto JSON),stopSequences(arreglo de cadenas),candidateCount: 1, unseedde tipo entero, y los valores numéricospresencePenaltyyfrequencyPenalty. Los rangos válidos y las funciones disponibles dependen del modelo específico; para obtener una respuesta JSON, elige un único formato de esquema compatible yapplication/json. En los modelos de texto,responseModalitiessolo admite["TEXT"]. - safetySettingsarrayopcional
Objetos con los campos obligatorios
categoryythresholdy el campo opcionalmethod. Los valores deben ser compatibles con el modelo específico. - toolsarrayopcional
Las funciones del cliente
functionDeclarationslas ejecuta tu aplicación. Los modelos de texto también admiten{"googleSearch": {}}y{"urlContext": {}}, por separado o juntos en el arreglotools. Ni Google Search ni URL context se pueden combinar confunctionDeclarationsen una misma solicitud. Para generar imágenes solo se admiten funciones del cliente. No se admiten otras herramientas integradas ni caché explícita. Search/URL context pueden requerir una reserva por el límite completo de entrada; los valores reales detoolUsePromptTokenCountse contabilizan como tokens de entrada. - toolConfigobjectopcional
functionCallingConfig: mode (AUTO/ANY/NONE/VALIDATED/MODE_UNSPECIFIED), allowedFunctionNames.
Si el modelo no admite un campo, un adjunto o una combinación de herramientas, la solicitud se rechaza antes de reservar saldo y no se cobra nada. Se devuelve HTTP 400 con status INVALID_ARGUMENT y la clase invalid_request (en ErrorInfo.reason — INVALID_REQUEST). Un JSON no válido, errores en los parámetros de ruta y otros fallos de validación también devuelven 400; lee el mensaje de error y el Request ID. Las capacidades dependen del modelo: consulta sus formatos y entradas en el catálogo.
PDF, audio, video y enlaces
En los modelos Gemini que admiten el tipo de entrada correspondiente, parts[].inlineData contiene mimeType y datos base64 no vacíos; el PDF debe ser un archivo PDF real. fileData contiene una dirección pública https en fileUri y el tipo MIME del archivo. En YouTube se puede omitir el tipo MIME. http, las direcciones privadas, gs:// y los objetos de Google Files API no se admiten, tampoco la carga mediante Google Files API. En los videos se puede agregar videoMetadata: startOffset/endOffset como cadenas en segundos (por ejemplo, 1.5s) y fps debe ser mayor que 0 y no superar 24. El tamaño del cuerpo, el contexto, la entrada admitida y el límite de respuesta se validan según el modelo y el formato.
| Entrada | Tipos MIME admitidos |
|---|---|
| Imágenes inline | image/png, image/jpeg, image/webp |
| Documentos | application/pdf; text/plain, text/html, text/css, text/csv, text/xml, text/rtf, text/markdown, text/javascript, application/x-javascript, text/x-python, application/x-python |
| Audio | audio/wav, audio/mp3, audio/mpeg, audio/aiff, audio/aac, audio/ogg, audio/flac |
| Video | video/mp4, video/mpeg, video/mov, video/quicktime, video/avi, video/x-flv, video/mpg, video/webm, video/wmv, video/3gpp |
import json, os, urllib.request
def generate(model, payload):
req = urllib.request.Request(
"https://api.ailoot.ai/v1beta/models/" + model + ":generateContent",
data=json.dumps(payload).encode(),
headers={"x-goog-api-key": os.environ["AILOOT_API_KEY"],
"Content-Type": "application/json"},
)
# Un solo envío. Si se agota el tiempo de espera, consulta el Request ID en tu historial de uso antes de reintentar.
with urllib.request.urlopen(req, timeout=300) as response:
print("Request ID:", response.headers.get("X-Request-ID"))
return json.load(response)
import base64, pathlib, sys
# Proporciona un archivo local real, ya sea PDF, audio o video, y su tipo MIME compatible.
path, mime = pathlib.Path(sys.argv[1]), sys.argv[2]
payload = {"contents": [{"role": "user", "parts": [
{"text": "Describe brevemente el contenido del archivo."},
{"inlineData": {"mimeType": mime,
"data": base64.b64encode(path.read_bytes()).decode()}},
]}]}
print(generate("gemini-3.8-flash", payload))
# Adjunto alternativo: {"fileData": {"mimeType": mime, "fileUri": public_https_url}}
# Para una URL real de YouTube: {"fileData": {"fileUri": youtube_url}}Search y URL context
Las funciones del cliente functionDeclarations las ejecuta tu aplicación. Los modelos de texto también admiten {"googleSearch": {}} y {"urlContext": {}}, por separado o juntos en el arreglo tools. Ni Google Search ni URL context se pueden combinar con functionDeclarations en una misma solicitud. Para generar imágenes solo se admiten funciones del cliente. No se admiten otras herramientas integradas ni caché explícita. Search/URL context pueden requerir una reserva por el límite completo de entrada; los valores reales de toolUsePromptTokenCount se contabilizan como tokens de entrada.
{"tools": [{"googleSearch": {}}, {"urlContext": {}}]}Imágenes de salida
Solo gemini-3.1-flash-image puede devolver imágenes en el formato Gemini. Esta función es distinta de /v1/images/generations: los demás modelos de imágenes no funcionan aquí. Este modelo admite hasta 131072 tokens de entrada y 32768 de respuesta; el costo completo de la respuesta más larga se reserva por adelantado.
responseModalities : un conjunto no vacío de TEXT/IMAGE, sin repeticiones. imageConfig.imageSize: 1K/2K/4K; aspectRatio: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9, 1:4, 4:1, 1:8, 8:1. Este modelo admite texto y PNG/JPEG/WebP inline como entrada; no admite PDF/audio/video/fileData ni Search/URL context integrados. La disponibilidad y los precios para tu clave se encuentran en el catálogo y en GET /v1/models.
import json, os, urllib.request
def generate(model, payload):
req = urllib.request.Request(
"https://api.ailoot.ai/v1beta/models/" + model + ":generateContent",
data=json.dumps(payload).encode(),
headers={"x-goog-api-key": os.environ["AILOOT_API_KEY"],
"Content-Type": "application/json"},
)
# Un solo envío. Si se agota el tiempo de espera, consulta el Request ID en tu historial de uso antes de reintentar.
with urllib.request.urlopen(req, timeout=300) as response:
print("Request ID:", response.headers.get("X-Request-ID"))
return json.load(response)
import base64, pathlib
reply = generate("gemini-3.1-flash-image", {
"contents": [{"role": "user", "parts": [{"text": "Acuarela: un faro al amanecer"}]}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"imageConfig": {"aspectRatio": "1:1", "imageSize": "1K"},
},
})
index = 0
for candidate in reply.get("candidates", []):
for part in candidate.get("content", {}).get("parts", []):
if "text" in part:
print(part["text"])
blob = part.get("inlineData", {})
if blob.get("mimeType", "").startswith("image/") and blob.get("data"):
index += 1
extension = {"image/png": "png", "image/jpeg": "jpg",
"image/webp": "webp"}.get(blob["mimeType"], "bin")
pathlib.Path(f"gemini-image-v{index}.{extension}").write_bytes(
base64.b64decode(blob["data"]))
print("All returned images:", index)Ciclo completo de llamadas a funciones
El cliente ejecuta las funciones. Envía de vuelta todo el content original del modelo con thoughtSignature sin cambios, y luego functionResponse con el mismo name y el mismo id, si estaba presente en functionCall.
import json, os, urllib.request
def generate(model, payload):
req = urllib.request.Request(
"https://api.ailoot.ai/v1beta/models/" + model + ":generateContent",
data=json.dumps(payload).encode(),
headers={"x-goog-api-key": os.environ["AILOOT_API_KEY"],
"Content-Type": "application/json"},
)
# Un solo envío. Si se agota el tiempo de espera, consulta el Request ID en tu historial de uso antes de reintentar.
with urllib.request.urlopen(req, timeout=300) as response:
print("Request ID:", response.headers.get("X-Request-ID"))
return json.load(response)
tools = [{"functionDeclarations": [{
"name": "multiply", "description": "Multiplicar dos números",
"parameters": {"type": "object", "properties": {
"a": {"type": "number"}, "b": {"type": "number"}},
"required": ["a", "b"]},
}]}]
contents = [{"role": "user", "parts": [{"text": "Multiplica 7 por 8 con la función"}]}]
for turn in range(4): # Limita el ciclo de herramientas; no vuelvas a enviar una solicitud cuyo resultado no esté claro.
reply = generate("gemini-3.8-flash", {"contents": contents, "tools": tools})
content = reply["candidates"][0]["content"]
contents.append(content) # Conserva TODAS (ALL) las partes originales (parts) y thoughtSignature sin cambios.
results = []
for part in content.get("parts", []):
call = part.get("functionCall")
if not call:
continue
if call["name"] != "multiply":
raise ValueError("Unexpected function")
args = call["args"]
result = {"name": call["name"], "response": {"value": args["a"] * args["b"]}}
if "id" in call:
result["id"] = call["id"]
results.append({"functionResponse": result})
if not results:
print(content)
break
contents.append({"role": "user", "parts": results})
else:
raise RuntimeError("Tool loop limit reached")Respuesta
- candidates[].content.partsarray
Respuesta del modelo.
- usageMetadataobject
promptTokenCount,candidatesTokenCount,totalTokenCount.