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 una cabecera 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(array de cadenas),candidateCount: 1, unseedde tipo entero, y los valores numéricospresencePenaltyyfrequencyPenalty. Los rangos válidos y las funciones disponibles dependen del modelo concreto; 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 concreto. - 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 arraytools. 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, vídeo 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 vídeos se puede añadir 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 |
| Vídeo | 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. Tras un tiempo de espera agotado, 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 de PDF, audio o vídeo 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 array 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 coste completo de la respuesta más larga se reserva de antemano.
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. Tras un tiempo de espera agotado, 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 function calling
El cliente ejecuta las funciones. Devuelve el content original completo del modelo con thoughtSignature sin cambios, seguido de 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. Tras un tiempo de espera agotado, 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 bucle 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 las partes originales 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.