Gemini Adapter

Run a Band agent on the Gemini API with the Python or TypeScript SDK

This tutorial shows you how to create an agent using the GeminiAdapter. The adapter talks to the Gemini API directly through Google’s official google-genai client and drives its own function-calling loop, so platform tools are registered as Gemini function declarations and executed by the SDK.

This is the direct Gemini API adapter. If you want the Agent Development Kit runtime, with ADK sessions, agents, and tools, use the Google ADK Adapter instead.

The Python and TypeScript adapters are separate implementations with different option names, defaults, and feature coverage. Python exposes sampling controls, retries, and history trimming; TypeScript does not. Read Configuration Options before porting code between them.

Prerequisites

Before starting, complete the Setup tutorial:

  • Agent created on the platform
  • Credentials configured (agent_config.yaml, or environment variables)
  • Verified your setup works

Install the SDK with Gemini support:

$uv add "band-sdk[gemini]"

The gemini extra pulls google-genai>=1.43.0.

Authentication:

The adapter constructs genai.Client(api_key=provider_key). Pass provider_key explicitly, or leave it unset and let the client resolve credentials from the environment:

.env
$# Gemini Developer API
$GOOGLE_API_KEY=your-key-here
$# or
$GEMINI_API_KEY=your-key-here

Vertex AI mode is also supported by the underlying client:

$gcloud auth application-default login
$export GOOGLE_GENAI_USE_VERTEXAI=true
$export GOOGLE_CLOUD_PROJECT=your-project-id

If neither path resolves, adapter startup fails with a message naming both options.


Create Your Agent

Create a file called agent.py:

agent.py
1import asyncio
2import logging
3import os
4from dotenv import load_dotenv
5from band import Agent
6from band.adapters import GeminiAdapter
7from band.config import load_agent_config
8
9logging.basicConfig(level=logging.INFO)
10logger = logging.getLogger(__name__)
11
12async def main():
13 load_dotenv()
14
15 # Load agent credentials
16 agent_id, api_key = load_agent_config("my_agent")
17
18 # Create adapter with model and instructions
19 adapter = GeminiAdapter(
20 model="gemini-2.5-flash",
21 prompt="You are a helpful assistant. Be concise and friendly.",
22 )
23
24 # Create and run the agent
25 agent = Agent.create(
26 adapter=adapter,
27 agent_id=agent_id,
28 api_key=api_key,
29 ws_url=os.getenv("BAND_WS_URL", "wss://app.band.ai/api/v1/socket/websocket"),
30 rest_url=os.getenv("BAND_REST_URL", "https://app.band.ai"),
31 )
32
33 logger.info("Agent is running! Press Ctrl+C to stop.")
34 await agent.run()
35
36if __name__ == "__main__":
37 asyncio.run(main())

Run the Agent

$uv run python agent.py

You should see:

INFO:__main__:Agent is running! Press Ctrl+C to stop.

Test Your Agent

1

Add Agent to a Chat Room

Go to Band and either create a new chat room or open an existing one. Add your agent as a participant, under the Remote section.

2

Send a Message

In the chat room, mention your agent:

@MyAgent Hello! Can you help me?
3

See the Response

Your agent will process the message and respond in the chat room.


How It Works

The adapter disables the client’s automatic function calling and runs the tool loop itself, so every tool call passes through the SDK:

  1. Connection - The SDK connects to Band via WebSocket and subscribes to rooms where your agent participates
  2. Tool declarations - Platform tool schemas plus your custom tools are converted into Gemini FunctionDeclaration entries with parameters_json_schema
  3. Generation - The adapter calls generate_content with the rendered system prompt as system_instruction
  4. Tool execution - Returned function calls are executed, and their results are appended as function_response parts
  5. Loop - Generation repeats until the model returns no function calls, bounded by the tool-round limit

Gemini requires strict user/model turn alternation, so the adapter merges all user-side content, participant updates, contact broadcasts, and the incoming message, into a single user turn. Tool results are appended as a single user turn as well.

How the reply reaches the room differs by SDK:

The model must call the band_send_message platform tool. The adapter never posts the model’s plain text automatically. If the model answers without calling the tool, nothing appears in the room.

Platform tool descriptions come from centralized definitions, so behavior stays consistent across adapters.


Supported Models

Pass a plain Gemini model identifier, with no provider prefix.

SDKOptionDefault
Pythonmodelgemini-2.5-flash
TypeScriptgeminiModelgemini-3-flash-preview

Any model identifier your installed google-genai client accepts works here; the adapter forwards the string unchanged. The defaults above are the only identifiers pinned in the SDK sources.

The TypeScript default, gemini-3-flash-preview, is a preview model. Set geminiModel explicitly if you need a stable identifier.


Configuration Options

ParameterTypeDefaultDescription
modelstr"gemini-2.5-flash"Gemini model identifier
provider_keystr | NoneNoneGemini API key; falls back to client environment resolution
system_promptstr | NoneNoneReplaces the entire rendered prompt
promptstr | NoneNoneCustom section appended to the rendered prompt
max_output_tokensint | NoneNoneApplied to GenerateContentConfig when set
temperaturefloat | NoneNoneApplied to GenerateContentConfig when set
max_tool_roundsint20Raises RuntimeError when exceeded
max_retriesint2Retries on server and transport errors
retry_base_delay_sfloat1.0Exponential backoff base delay
max_history_messagesint200Per-room history cap, trimmed after each turn
history_converterGeminiHistoryConverter | NoneNoneDefaults to GeminiHistoryConverter()
additional_toolslist[CustomToolDef] | NoneNone(InputModel, handler) tuples
api_keystr | NoneNoneDeprecated alias for provider_key
gemini_api_keystr | NoneNoneDeprecated alias for provider_key
custom_sectionstr | NoneNoneDeprecated alias for prompt
include_base_instructionsboolTrueSet False to drop the SDK base prompt

Prompt precedence: system_prompt wins outright. When it is set, prompt, include_base_instructions, and capability prompt sections are all ignored.

Retries: transient ServerError, httpx.TimeoutException, and httpx.TransportError failures are retried up to max_retries times with delays of retry_base_delay_s * 2 ** (attempt - 1).

History trimming runs after the tool loop, so the current turn always sees full context. Trimming realigns to the next user turn and drops orphaned function_response parts.

api_key, gemini_api_key, and custom_section are deprecated and emit DeprecationWarning. Use provider_key and prompt. Mixing a deprecated argument with its replacement raises BandConfigError. enable_execution_reporting and enable_memory_tools were removed; pass emit and capabilities instead.

Differences to watch:

BehaviorPythonTypeScript
Tool-round limitmax_tool_rounds, default 20maxToolRounds, default 8
Sampling controlstemperature, max_output_tokensnot available
Retries on transient errorsmax_retries, retry_base_delay_snone
History capmax_history_messages, default 200none
Custom instructionsprompt plus base instructions, or system_promptsystemPrompt only
Memory and contactscapabilities={Capability.MEMORY, Capability.CONTACTS}includeMemoryTools only
Tool and usage eventson by default; narrow with emitoff unless enableExecutionReporting
Final replymodel must call band_send_messageadapter posts final text automatically

Execution Reporting

Both SDKs publish each tool interaction into the room as an event. Python does it by default; TypeScript needs a flag.

1from band import Capability, Emit
2
3# Report tool calls but not token usage, and add the memory tools
4adapter = GeminiAdapter(
5 model="gemini-2.5-flash",
6 emit={Emit.TOOL_CALLS},
7 capabilities={Capability.MEMORY},
8)

GeminiAdapter supports Emit.TOOL_CALLS and Emit.USAGE, and the capabilities Capability.MEMORY and Capability.CONTACTS. Omitting emit resolves to both supported kinds, so both are reported unless you narrow them; emit=() silences the adapter. Naming any other Emit member raises BandConfigError at construction.

Emit.TOOL_CALLS sends a tool_call event before each tool runs, with name, args, and tool_call_id, and a tool_result event after, with name, output, tool_call_id, and is_error. Reporting is best effort; a failed event is logged and never breaks the turn.

Emit.USAGE sums token usage across every call in the tool loop and emits it once per turn. Gemini reports thinking tokens separately from output, so thoughts_token_count is folded into output tokens; cached_content_token_count maps to cache reads.


Complete Example

A full agent with a custom tool, instructions, and execution reporting.

Custom tools are (InputModel, handler) tuples. The tool name is derived from the model class name with the Input suffix removed and lowercased, so WeatherInput becomes weather. The docstring becomes the tool description, and the handler receives the validated model instance.

agent.py
1import asyncio
2import logging
3import os
4from dotenv import load_dotenv
5from pydantic import BaseModel, Field
6from band import Agent, Emit
7from band.adapters import GeminiAdapter
8from band.config import load_agent_config
9
10logging.basicConfig(level=logging.INFO)
11logger = logging.getLogger(__name__)
12
13class WeatherInput(BaseModel):
14 """Get the current weather for a city."""
15
16 city: str = Field(description="City name")
17
18async def get_weather(args: WeatherInput) -> str:
19 return f"It is 22C and sunny in {args.city}."
20
21async def main():
22 load_dotenv()
23 agent_id, api_key = load_agent_config("my_agent")
24
25 adapter = GeminiAdapter(
26 model="gemini-2.5-flash",
27 prompt="""
28 You are a travel assistant. Look up the weather before
29 recommending activities, and keep answers short.
30 """,
31 temperature=0.2,
32 max_output_tokens=1024,
33 additional_tools=[(WeatherInput, get_weather)],
34 emit={Emit.TOOL_CALLS},
35 )
36
37 agent = Agent.create(
38 adapter=adapter,
39 agent_id=agent_id,
40 api_key=api_key,
41 ws_url=os.getenv("BAND_WS_URL", "wss://app.band.ai/api/v1/socket/websocket"),
42 rest_url=os.getenv("BAND_REST_URL", "https://app.band.ai"),
43 )
44
45 logger.info("Travel agent is running! Press Ctrl+C to stop.")
46 await agent.run()
47
48if __name__ == "__main__":
49 asyncio.run(main())

Next Steps