I asked for the latest, and got a confident, stale answer
While drafting an app announcement with Gemini, I asked it to "account for the recent release status," but it answered based on how things stood months earlier. That makes sense: a model answers from what it learned up to its training cutoff.
The way past that wall — "doesn't know the latest" and "can't see my data" — is external tool integration. Gemini offers two paths: the integrations you toggle in the app, and the tools parameter you call from the API. Knowing both lets you pick the right one for the job.
In-app integrations
In the Gemini app (web and mobile), you can toggle connections to Google services from the settings screen. They are designed to fire automatically only when a question calls for them.
Google Search
Activates for news, current events, and real-time figures, folding search results into the answer.
- "What's the weather in Tokyo today?"
- "Summarize recent AI news in three lines"
- "When was company X's latest earnings report?"
Google Maps
Searches places, directions, and store or hotel details.
- "How do I get from Shibuya Station to Tokyo Tower?"
- "Highly-rated Italian restaurants nearby"
YouTube
Searches videos and suggests related content.
- "Find Python machine learning tutorial videos"
Google Flights & Hotels
Searches and compares flights and accommodations.
- "Cheapest flights from Tokyo to Paris next month"
Each integration toggles independently. Turning off the ones you don't need avoids unintended external calls.
From the API: search grounding
To do the same thing from code, you use the tools parameter. Start with search grounding: the model runs a Google Search only when it judges one is needed, then builds the answer on top of those results.
from google import genai
from google.genai import types
client = genai.Client(api_key="YOUR_API_KEY")
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="What's the current weather in Tokyo? Cite your sources.",
config=types.GenerateContentConfig(
tools=[types.Tool(google_search=types.GoogleSearch())],
),
)
print(response.text)
# Pull out the grounding sources
meta = response.candidates[0].grounding_metadata
if meta and meta.grounding_chunks:
for chunk in meta.grounding_chunks:
print("Source:", chunk.web.uri)
else:
print("(No search was used for this request)")The model searches only when it decides outside information is required. A weather question returns grounding_chunks; a question answerable from general knowledge skips search and leaves grounding_metadata empty. Always branch for the no-grounding case — that is the trick to keeping this stable in production. Code that never assumes grounding exists won't crash on the quieter requests.
From the API: the Function Calling round-trip
Search grounding is executed for you on Google's side. To reach your own database or internal API, you use Function Calling. Here is the step most introductions skip: once the model says it wants to call a function, who actually runs it? Your code does.
The flow has five stages: (1) declare the function, (2) the model requests a call, (3) you execute the function, (4) you return the result to the model, (5) the model produces the final answer.
from google import genai
from google.genai import types
client = genai.Client(api_key="YOUR_API_KEY")
# (1) Declare the function
get_weather = types.FunctionDeclaration(
name="get_weather",
description="Return the current weather for a given city",
parameters={
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
},
"required": ["city"],
},
)
tools = [types.Tool(function_declarations=[get_weather])]
# The local implementation that actually runs (normally it hits an API)
def run_get_weather(city: str) -> dict:
fake_db = {"Tokyo": "Clear, 24C", "Osaka": "Cloudy, 22C"}
return {"city": city, "weather": fake_db.get(city, "no data")}
contents = [
types.Content(role="user", parts=[types.Part(text="What's the weather in Tokyo?")]),
]
# (2) First call to the model
resp = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config=types.GenerateContentConfig(tools=tools),
)
part = resp.candidates[0].content.parts[0]
if part.function_call:
call = part.function_call
# (3) Run the function on our side
result = run_get_weather(**dict(call.args))
# (4) Append both the model's call and our result to the history
contents.append(resp.candidates[0].content)
contents.append(
types.Content(
role="tool",
parts=[types.Part(
function_response=types.FunctionResponse(
name=call.name, response=result,
)
)],
)
)
# (5) Get the final answer grounded in the result
final = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config=types.GenerateContentConfig(tools=tools),
)
print(final.text)The key is step (4): append both the model's call (function_call) and your execution result (function_response) to the history before asking for the final answer. Return only the result, and the model loses track of what was asked and what came back, producing an off-target reply. I once spent half a day on a bug caused by appending just one of the two.
Four things I watch in production
Inserting an external tool adds concerns that plain text generation never had. Here is what I check every time while running these in my own indie projects.
- Latency: tool calls involve a network round-trip, so responses slow down. On user-facing screens, an interim "searching…" indicator changes the perceived speed dramatically.
- Cost: grounding searches and multiple round-trips each add charges. Estimating on the free tier before designing your production rate limits keeps things safe.
- Citations: the source URLs in
grounding_chunksare your material for showing readers that an answer is trustworthy. If you display them, account for dead links. - Preventing stray calls: pass only the tools you actually need. Declaring too many raises the odds the model picks an unintended function.
Choosing between them in indie development
For drafts where factual freshness matters — app announcements, release notes — I reach for search grounding. When the answer depends on data that lives only on my side, such as internal metrics or inventory, I use Function Calling. Feeding in app revenue figures (an AdMob report, say) for summarization is another job for the latter.
The in-app integrations are best for getting a feel for the behavior by hand. The API tools come into their own once you fold that behavior into automation.
Which model should call the tools
When you build tool integration, the model you pick shapes the outcome too. In my own setup, I lean on gemini-3.5-flash for the lighter round-trips — search grounding or a single Function Calling hop — where speed matters, and only reach for a higher-tier model when I want to chain several tools and have it reason step by step.
Flash models respond quickly and stay cheaper, which suits features that run in sync with a user's action. For work that keeps no one waiting — a nightly batch, say — where you want precision in picking the right tool and assembling its arguments, switching up is worth it. Build on Flash first; if you see wrong-function picks or dropped arguments, move up. That order wastes the least effort.
One more thing: calling the API without naming a model uses the default, and that default can shift with updates. In automation, pin model explicitly so behavior doesn't change out from under you one day.
Your next step
With your own Gemini API key, run the search-grounding snippet above as-is, and try one question that returns grounding_chunks and one that doesn't. Experiencing the empty case first makes the design decisions far easier when you wire up the Function Calling round-trip.
Thank you for reading. I hope it gives you a solid foothold as you start building.