Gemini: OpenRouter vs native SDK
Gemini is where the OpenAI-compatible story gets interesting. You can reach it through OpenRouter with your existing adapter, or through the native google-generativeai SDK. The first path is trivial. The second path unlocks features like long context caching and thinking, but costs you code.
# Gemini via OpenRouter requires zero new code.
# Just use the openrouter/ prefix with a Gemini model slug.
result = await chat(
messages=[Message(role='user', content='Hello')],
model='openrouter/google/gemini-2.5-flash',
)If OpenRouter covers your feature needs, this is the answer. One line and Gemini works through the exact same adapter as OpenAI.
import os
import google.generativeai as genai
from providers.base import LLMProvider, Message, ChatResult
class GeminiNativeProvider(LLMProvider):
def __init__(self, api_key: str, default_model: str = 'gemini-2.5-flash'):
genai.configure(api_key=api_key)
self.default_model = default_model
async def chat(self, messages: list[Message], model: str) -> ChatResult:
system = next((m.content for m in messages if m.role == 'system'), None)
history = [
{'role': 'user' if m.role == 'user' else 'model', 'parts': [m.content]}
for m in messages if m.role != 'system'
]
gmodel = genai.GenerativeModel(model, system_instruction=system)
response = await gmodel.generate_content_async(history)
usage = response.usage_metadata
return ChatResult(
text=response.text,
model=f'gemini/{model}',
prompt_tokens=usage.prompt_token_count if usage else None,
completion_tokens=usage.candidates_token_count if usage else None,
)The native adapter has to translate between roles (assistant becomes model), move the system prompt into system_instruction, and reshape parts. Every provider abstraction hides exactly this kind of mess.
Default to OpenRouter. Reach for the native SDK only when you need something OpenRouter does not proxy, like Gemini caching, long-context token billing, or specific safety setting overrides. The important thing is that your app never knows the difference because both sit behind the same chat() contract.
Quiz: Quiz
Loading practice…