Google Gemini API Integration
Google Gemini is a family of multimodal LLMs from Google DeepMind, available via the google-generativeai Python package and @google/generative-ai TypeScript package. This guide covers tracing generate_content() calls, multi-turn chat sessions, function calling, token budget tracking, and debugging blocked responses.
Installation
Python
pip install keylightdigital-nexus google-generativeai
TypeScript
npm install @keylightdigital/nexus @google/generative-ai
Get your Nexus API key from Dashboard → API Keys and set it alongside your Gemini key:
export NEXUS_API_KEY="nxs_your_key_here"
export GEMINI_API_KEY="AIza..."
Basic generate_content() trace
Wrap each generate_content() call in a Nexus trace. Every request appears in the dashboard with its model, prompt preview, status, and latency:
Python
import os
import google.generativeai as genai
from nexus_sdk import NexusClient
genai.configure(api_key=os.environ["GEMINI_API_KEY"])
nexus = NexusClient(api_key=os.environ["NEXUS_API_KEY"])
model = genai.GenerativeModel("gemini-1.5-pro")
def ask(prompt: str) -> str:
trace = nexus.start_trace({
"agent_id": "gemini-assistant",
"name": f"gemini: {prompt[:60]}",
"status": "running",
"started_at": nexus.now(),
"metadata": {
"model": "gemini-1.5-pro",
"prompt_preview": prompt[:200],
},
})
try:
response = model.generate_content(prompt)
finish_reason = response.candidates[0].finish_reason.name
nexus.end_trace(trace["trace_id"], {
"status": "success",
"metadata": {
"prompt_tokens": response.usage_metadata.prompt_token_count,
"completion_tokens": response.usage_metadata.candidates_token_count,
"finish_reason": finish_reason,
},
})
return response.text
except Exception as e:
nexus.end_trace(trace["trace_id"], {
"status": "error",
"metadata": {"error": str(e)},
})
raise
TypeScript
import { GoogleGenerativeAI } from '@google/generative-ai';
import { NexusClient } from '@keylightdigital/nexus';
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const nexus = new NexusClient({
apiKey: process.env.NEXUS_API_KEY!,
agentId: 'gemini-assistant',
});
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-pro' });
async function ask(prompt: string): Promise<string> {
const trace = await nexus.startTrace({
agentId: 'gemini-assistant',
input: prompt,
});
try {
const result = await model.generateContent(prompt);
const response = result.response;
const meta = response.usageMetadata;
await nexus.endTrace(trace.id, {
output: response.text(),
status: 'success',
metadata: {
promptTokens: meta?.promptTokenCount,
completionTokens: meta?.candidatesTokenCount,
finishReason: response.candidates?.[0]?.finishReason,
model: 'gemini-1.5-pro',
},
});
return response.text();
} catch (err) {
await nexus.endTrace(trace.id, { output: String(err), status: 'error' });
throw err;
}
}
Multi-turn chat sessions
Gemini’s ChatSession maintains conversation history automatically. Open one trace per session and add a span per turn so you can see the full conversation waterfall in Nexus:
Python
import os
import google.generativeai as genai
from nexus_sdk import NexusClient
genai.configure(api_key=os.environ["GEMINI_API_KEY"])
nexus = NexusClient(api_key=os.environ["NEXUS_API_KEY"])
model = genai.GenerativeModel("gemini-1.5-flash")
def run_chat(messages: list[str]) -> list[str]:
trace = nexus.start_trace({
"agent_id": "gemini-chat",
"name": "chat-session",
"status": "running",
"started_at": nexus.now(),
"metadata": {"model": "gemini-1.5-flash", "turns": len(messages)},
})
chat = model.start_chat(history=[])
replies = []
try:
for i, message in enumerate(messages):
span = nexus.start_span(trace["trace_id"], {
"name": f"turn-{i + 1}",
"type": "llm",
"metadata": {
"turn": i + 1,
"user_message": message[:200],
},
})
response = chat.send_message(message)
nexus.end_span(span["id"], {
"output": response.text[:500],
"metadata": {
"prompt_tokens": response.usage_metadata.prompt_token_count,
"completion_tokens": response.usage_metadata.candidates_token_count,
},
})
replies.append(response.text)
nexus.end_trace(trace["trace_id"], {"status": "success"})
return replies
except Exception as e:
nexus.end_trace(trace["trace_id"], {
"status": "error",
"metadata": {"error": str(e)},
})
raise
TypeScript
import { GoogleGenerativeAI } from '@google/generative-ai';
import { NexusClient } from '@keylightdigital/nexus';
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const nexus = new NexusClient({ apiKey: process.env.NEXUS_API_KEY!, agentId: 'gemini-chat' });
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
async function runChat(messages: string[]): Promise<string[]> {
const trace = await nexus.startTrace({
agentId: 'gemini-chat',
input: messages[0],
});
const chat = model.startChat({ history: [] });
const replies: string[] = [];
try {
for (let i = 0; i < messages.length; i++) {
const span = await nexus.startSpan(trace.id, {
name: `turn-${i + 1}`,
type: 'llm',
metadata: { turn: i + 1, userMessage: messages[i] },
});
const result = await chat.sendMessage(messages[i]);
const response = result.response;
await nexus.endSpan(span.id, {
output: response.text().slice(0, 500),
metadata: {
promptTokens: response.usageMetadata?.promptTokenCount,
completionTokens: response.usageMetadata?.candidatesTokenCount,
},
});
replies.push(response.text());
}
await nexus.endTrace(trace.id, { output: replies.at(-1) ?? '', status: 'success' });
return replies;
} catch (err) {
await nexus.endTrace(trace.id, { output: String(err), status: 'error' });
throw err;
}
}
Function calling
Gemini supports function calling via tool_config and function declarations. Add a Nexus span for each function invocation to see tool inputs, outputs, and errors in the trace waterfall:
Python — function calling loop
import os
import google.generativeai as genai
from nexus_sdk import NexusClient
genai.configure(api_key=os.environ["GEMINI_API_KEY"])
nexus = NexusClient(api_key=os.environ["NEXUS_API_KEY"])
get_weather_decl = genai.protos.FunctionDeclaration(
name="get_weather",
description="Get current weather for a city",
parameters=genai.protos.Schema(
type=genai.protos.Type.OBJECT,
properties={"city": genai.protos.Schema(type=genai.protos.Type.STRING)},
required=["city"],
),
)
model = genai.GenerativeModel(
"gemini-1.5-pro",
tools=[genai.protos.Tool(function_declarations=[get_weather_decl])],
)
def fetch_weather(city: str) -> dict:
return {"city": city, "temperature": "22C", "condition": "Sunny"}
def run_agent(task: str) -> str:
trace = nexus.start_trace({
"agent_id": "gemini-function-agent",
"name": f"agent: {task[:60]}",
"status": "running",
"started_at": nexus.now(),
"metadata": {"model": "gemini-1.5-pro", "task": task[:200]},
})
try:
response = model.generate_content(task)
# Agentic loop: handle function calls until the model stops
while True:
candidate = response.candidates[0]
fn_parts = [p for p in candidate.content.parts if hasattr(p, "function_call")]
if not fn_parts or candidate.finish_reason.name == "STOP":
break
fn = fn_parts[0].function_call
fn_span = nexus.start_span(trace["trace_id"], {
"name": f"tool:{fn.name}",
"type": "tool",
"metadata": {"function": fn.name, "args": dict(fn.args)},
})
result = fetch_weather(fn.args["city"]) if fn.name == "get_weather" else {"error": "unknown function"}
nexus.end_span(fn_span["id"], {"output": result})
# Feed function result back to Gemini
response = model.generate_content([
task,
candidate.content,
genai.protos.Content(parts=[
genai.protos.Part(
function_response=genai.protos.FunctionResponse(
name=fn.name,
response={"result": result},
)
)
]),
])
nexus.end_trace(trace["trace_id"], {"status": "success"})
return response.text
except Exception as e:
nexus.end_trace(trace["trace_id"], {"status": "error", "metadata": {"error": str(e)}})
raise
TypeScript — function calling loop
import { GoogleGenerativeAI, SchemaType, type FunctionDeclaration } from '@google/generative-ai';
import { NexusClient } from '@keylightdigital/nexus';
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const nexus = new NexusClient({ apiKey: process.env.NEXUS_API_KEY!, agentId: 'gemini-function-agent' });
const getWeatherDecl: FunctionDeclaration = {
name: 'get_weather',
description: 'Get current weather for a city',
parameters: {
type: SchemaType.OBJECT,
properties: { city: { type: SchemaType.STRING, description: 'City name' } },
required: ['city'],
},
};
const model = genAI.getGenerativeModel({
model: 'gemini-1.5-pro',
tools: [{ functionDeclarations: [getWeatherDecl] }],
});
function fetchWeather(city: string) {
return { city, temperature: '22C', condition: 'Sunny' };
}
async function runAgent(task: string): Promise<string> {
const trace = await nexus.startTrace({ agentId: 'gemini-function-agent', input: task });
try {
let result = await model.generateContent(task);
// Agentic loop
while (true) {
const candidate = result.response.candidates?.[0];
const fnCall = candidate?.content.parts.find(p => p.functionCall)?.functionCall;
if (!fnCall) break;
const span = await nexus.startSpan(trace.id, {
name: `tool:${fnCall.name}`,
type: 'tool',
metadata: { function: fnCall.name, args: fnCall.args },
});
const fnResult = fnCall.name === 'get_weather'
? fetchWeather((fnCall.args as { city: string }).city)
: { error: 'unknown function' };
await nexus.endSpan(span.id, { output: JSON.stringify(fnResult) });
result = await model.generateContent([
task,
{ role: 'model', parts: candidate!.content.parts },
{
role: 'user',
parts: [{ functionResponse: { name: fnCall.name, response: fnResult } }],
},
]);
}
await nexus.endTrace(trace.id, { output: result.response.text(), status: 'success' });
return result.response.text();
} catch (err) {
await nexus.endTrace(trace.id, { output: String(err), status: 'error' });
throw err;
}
}
Token tracking
Every Gemini response includes usage_metadata with prompt and completion token counts. Log these on every span to build cost dashboards and catch runaway agents before they exhaust your quota:
# Python: full token metadata on every LLM span
span = nexus.start_span(trace["trace_id"], {
"name": "gemini:generate",
"type": "llm",
"metadata": {"model": "gemini-1.5-pro", "prompt_preview": prompt[:200]},
})
response = model.generate_content(prompt)
nexus.end_span(span["id"], {
"output": response.text[:500],
"metadata": {
"prompt_tokens": response.usage_metadata.prompt_token_count,
"completion_tokens": response.usage_metadata.candidates_token_count,
"total_tokens": response.usage_metadata.total_token_count,
# Approximate cost: gemini-1.5-pro $1.25/1M input, $5.00/1M output
"estimated_cost_usd": round(
response.usage_metadata.prompt_token_count * 1.25e-6 +
response.usage_metadata.candidates_token_count * 5.00e-6,
8,
),
},
})
Use Nexus trace metadata filters to alert when a session exceeds a token budget, or to compare token efficiency across model tiers: gemini-1.5-flash costs 15× less than gemini-1.5-pro for input tokens.
Debugging patterns
Response blocked: SAFETY finish reason
Gemini refuses requests that violate its safety policy. The candidate’s finish_reason will be SAFETY and calling response.text will raise a ValueError. Record the finish reason and per-category safety ratings as span metadata so you can identify which prompts trigger blocks:
# Python: detect and log blocked responses
span = nexus.start_span(trace["trace_id"], {"name": "gemini:generate", "type": "llm"})
response = model.generate_content(prompt)
candidate = response.candidates[0]
finish_reason = candidate.finish_reason.name # e.g. "SAFETY", "RECITATION"
if finish_reason not in ("STOP", "MAX_TOKENS"):
safety_ratings = [
{"category": r.category.name, "probability": r.probability.name}
for r in candidate.safety_ratings
]
nexus.end_span(span["id"], {
"status": "error",
"metadata": {"finish_reason": finish_reason, "safety_ratings": safety_ratings},
})
nexus.end_trace(trace["trace_id"], {
"status": "error",
"metadata": {"blocked_reason": finish_reason},
})
raise ValueError(f"Gemini blocked response: {finish_reason}")
nexus.end_span(span["id"], {"output": response.text[:500]})
Response blocked: RECITATION finish reason
Gemini uses RECITATION when it would reproduce copyrighted content verbatim. If you see this in Nexus spans, rephrase your prompt to ask for a summary or paraphrase rather than quoting source material directly. Safety thresholds cannot override citation detection.
Function calling loop not terminating
If the agentic loop keeps generating function calls without reaching STOP, check the tool:* spans in Nexus to confirm that your function response was sent back with the exact name from the original function_call. A name mismatch causes the model to ignore the result and repeat the call.
Traces stuck in “running” state
A trace stays running if nexus.end_trace() is never called. Always wrap generate_content() and chat.send_message() in a try/except (Python) or try/catch (TypeScript) so the trace closes even when an exception propagates.
Ready to instrument your Gemini applications?
Start for free — no credit card required. See traces in under 5 minutes.
Start free →