Skip to main content

Gemini 4 Argon API Tutorial: Build an Agent That Speeds Up Slow Python Code

Build a Python agent with Gemini 4 Argon, function calling, and profiling, then check its patches with hidden tests, benchmarks, and API cost.
· 15 min read

Explore with AI

I wanted to see what Gemini 4 Argon does with a Python app that isn't broken, just slow. Every test passes and every report is right, but some of the work takes seconds when it should take milliseconds, and it gets worse the more history a customer has. Many AI coding tutorials start from a failing test, which hands the model an obvious target. 

But here, no failing test points to the slow code. Gemini 4 Argon is a Google model you call from Python through the Gemini API, and you'll use it to build an AI agent, which is a model that takes actions toward a goal. In this case the agent reads, times, and edits code, but only through tools your own Python controls.

The app is EventFlow, a small fictional program that turns customer activity, purchases, and refunds into reports. My question was whether the agent could find the slow part and fix it without changing what the app returns. If you're comfortable with Python functions, classes, and running tests, you can follow along, and I'll cover how to:

  • Send a first request to Gemini 4 Argon from Python
  • Measure a slow app before changing any code
  • Give the model a few safe tools and run them in a loop
  • Check the fix with tests the model never sees
  • Compare two thinking levels, which set how much the model reasons

Our Gemini 4 Argon overview covers the launch and benchmarks, so this tutorial stays on the API. For the wider model picture, also see our Gemini 4 Argon vs Claude Opus 5.5 and Gemini 4 Argon vs. GPT-6 Astra comparisons.

TL;DR

I gave the agent the same task 6 times, 3 at medium thinking and 3 at high, and this is what came out of it:

  • Argon fixed the slowdown in all 6 attempts, and a separate set of hidden tests agreed.

  • The slow workload went from about 2 seconds to about 3 milliseconds.

  • Argon ran benchmarks and a profiler before it changed any code, so give your agent those tools as well as tests.

  • high cost about 3x more than medium ($0.29 versus $0.10 per attempt, on average) and wrote diffs 8x larger for a modest extra speed gain. Start at medium.

I planted the bottleneck on purpose in one small app, so these results describe this case only.

What Is the Gemini 4 Argon API?

The Gemini 4 Argon API gives you gemini-4-argon, a Google reasoning model built for complex coding and multi-step agent tasks. It reached general availability in mid-October 2026. Per the model page, input, thinking, and output share one 1,048,576-token window, and a synchronous response stops at 65,536 output tokens.

Google ran a similar experiment in its ADK case study: an 11-hour Antigravity run with /goal on the Python Agent Development Kit (ADK). Its reviewers kept 2 of 6 proposals, cutting session prep by about 40% in long sessions and imports by 21%. This tutorial uses a small Python loop instead.

You'll use four API features:

  • The Interactions API, Google's recommended interface for new projects, which keeps conversation state on the server

  • Function calling, where the model asks for a tool, and your code decides whether to run it

  • thinking_level: low, medium (the default), or high

  • The usage object, which reports input, cached, output, and thought tokens

Argon asks, and your code does the work.

The Project: A Slow Python App for Gemini 4 Argon to Fix

The agent gets the EventFlow code, 8 tools, and no hint about the slowdown. The complete code is on GitHub. Start with EventFlow.

How EventFlow works

EventFlow is a fictional event processor in six small modules. An event enters EventFlow.ingest(), validate() turns it into an Event, and CustomerState stores it in timestamp order per customer. Then summarize() turns each history into sessions, revenue, refunds, and page counts, and build_report() produces a CustomerReport.

Diagram of the EventFlow Python app: raw activity, purchase, and refund events pass through validation, per-customer session state, history summarizing, and report building, with workloads and visible tests alongside

How one event becomes a report. Image by Author.

EventFlow's docstrings set the following rules:

  • Sessions split after a 30-minute gap

  • Refunds are capped at what remains of the purchase

  • A repeated event_id is ignored, and the first copy wins

  • Events sort by timestamp, even across batches

  • history() and report() return objects the caller owns

The hidden tests check all of them, and the last one comes up again later.

Why EventFlow gets slow

Three workloads, built from fixed seeds, show how history length changes the picture:

  • short: many customers with 5 events each

  • long: a few customers with 160 events each

  • mixed: 1 to 30 events per customer, with duplicates and events arriving out of order

long has far fewer events than short and still takes about 3x as long. The chart keeps the total at 1,600 events while giving each customer a longer history.

Line chart of original EventFlow median runtime with 1,600 events in total, rising from 0.34 to 2.67 seconds as events per customer grow from 20 to 160

Same total, longer histories, slower processing. Image by Author.

Each time the history length doubles, the runtime doubles too, even though the total number of events never changes. That pattern usually means some step repeats work over the whole history on every new event. The agent never sees the causes, so I'll name them after Argon has had its turn.

Success criteria and hidden tests

A fix counts only if it passes five checks set before the first API call; the model's own report gets no weight. The agent's final code must do all of the following:

  • Run at least 1.5x faster on long

  • Run no more than 5% slower on short or mixed

  • Pass every visible test

  • Return the same report output as the original on all three workloads

  • Pass every hidden acceptance case

These thresholds are my own, so treat them as an example. The agent's tools can't reach hidden cases; they run only after its code is frozen. They compare its code with a separate reference implementation on new data, late batches, duplicates, and the ownership rules.

A hidden suite only helps if it rejects wrong fixes, so feed it a couple on purpose. Two easy shortcuts pass every visible test here, dropping the deep copy and caching the summary without ever refreshing it. The hidden cases reject both.

Terminal output from checking two hand-written wrong fixes for EventFlow: both pass the 11 visible tests, and the hidden suite rejects each one

Hidden tests catch two wrong fixes. Image by Author.

How to Set Up the Gemini 4 Argon API in Python

You need Python 3.10 or newer, a Gemini API key, and a Google AI Studio project with billing turned on, because the pricing page lists no Free tier for Argon. This project pins the google-genai software development kit (SDK) at 2.33.0; the Interactions API needs at least 2.3.0.

git clone https://github.com/KhalidAbdelaty/Gemini-4-Argon-API-Tutorial-.git eventflow-argon
cd eventflow-argon
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt
Copy-Item .env.example .env

On macOS or Linux, activate the virtual environment with source .venv/bin/activate and copy with cp, then put GEMINI_API_KEY=... in .env. If GOOGLE_API_KEY is also set in your shell, the SDK uses it instead, so the code below removes it first. Skip the next subsection if you've used the Interactions API before.

Make your first Gemini 4 Argon API request

Your first request hands Argon the top of EventFlow's profiler output and asks what it shows. load_dotenv() loads GEMINI_API_KEY from .env, genai.Client() reads it, the model ID goes into client.interactions.create(), and generation_config carries the thinking level.

import os

from dotenv import load_dotenv
from google import genai

load_dotenv()
os.environ.pop("GOOGLE_API_KEY", None)
client = genai.Client()  # reads GEMINI_API_KEY from the environment

profile_excerpt = """paste the top rows of your cProfile output here"""
prompt = (
    "This cProfile excerpt comes from a Python app that turns customer events into "
    "reports. It is correct but slow on long customer histories.\n\n"
    f"{profile_excerpt}\n\n"
    "In three short bullets: what the profile shows, what you would read next in the "
    "code, and what this evidence alone does not prove."
)

interaction = client.interactions.create(
    model="gemini-4-argon",
    input=prompt,
    generation_config={"thinking_level": "medium"},
    timeout=900,  # seconds; Google recommends at least 15 minutes
)
print(interaction.output_text)
usage = interaction.usage
print(usage.total_input_tokens, usage.total_output_tokens, usage.total_thought_tokens)

Here is what came back:

Terminal output of the first Gemini 4 Argon Interactions API request, showing three bullets about the EventFlow profile and a line of token usage

First Argon request reads the profile. Image by Author.

Argon got it right; nearly all the time sat under get_history() and copy.deepcopy, probably called twice per event. Its third bullet warned that removing the deep copy could break callers who modify what they get back, which is the first shortcut I mentioned earlier. A question like this is cheap: under 8 seconds and less than half a cent, with no thinking tokens.

medium is the default, and the latest-model guide I linked earlier recommends it "for most tasks, including complex code and agentic use cases." The same guide says temperature, top_p, and top_k are ignored on Argon, so leave them out.

Profile Python Code Before Using Gemini 4 Argon

Measure the original app before the model touches it, so every later number has something to compare against. Take three kinds of measurement, each in its own process, because each tool skews the others:

  • Timing with time.perf_counter(), as the median of several fresh processes with one warm-up each

  • Profiling with cProfile to find where the time goes; it made long about 6x slower, so never trust a timing taken under a profiler

  • Memory with tracemalloc, which reports peak tracked Python allocations instead of total process memory

On the three workloads, rather than the chart's fixed 1,600 events, the original app took about 0.7 seconds on short, 2.2 on long, and 1.0 on mixed. It also passed every visible pytest test.

cProfile output for the original EventFlow long workload, with almost all cumulative time under get_history and copy.deepcopy

Where the slow app spends time. Image by Author.

Expect drift: the same code later timed about 10% faster, so time every version in one session.

How to Build a Performance Agent With Gemini 4 Argon Function Calling

As I mentioned earlier, Argon only asks for tools. The Python code that runs them is called the harness, and our agent harness engineering guide explains why its checks belong in code instead of the prompt. The tools come first, since they decide what Argon can touch.

Define the agent's tools and permissions

The 8 tools I mentioned earlier are Python functions declared as plain JSON schemas, and only two of them can change code:

  • list_files and read_file, limited to the app's Python source, tests, and fixtures

  • run_profiler and run_benchmark for one named workload

  • apply_patch, which replaces one exact occurrence of text in an existing app source file

  • revert_file, run_tests, and show_diff

Declarations follow the format in the function-calling guide I linked earlier:

{"type": "function", "name": "apply_patch",
 "description": ("Replace exactly one occurrence of old_text with new_text in a file under eventflow/. "
                 "The file must still compile afterwards."),
 "parameters": {"type": "object", "properties": {
     "path": {"type": "string"},
     "old_text": {"type": "string", "description": "Exact text to replace; must occur once."},
     "new_text": {"type": "string"}},
     "required": ["path", "old_text", "new_text"]}},

A schema describes the call, but doesn't make the call safe. The harness adds the following checks in Python:

  • Every path is resolved first, so a path that escapes the copied app fails
  • Writes outside the app's source folder are refused
  • A patched file must still compile, or the patch is rejected
  • Tests and benchmarks run in subprocesses with timeouts and no API key

That isn't a container, and patched code could still read files outside the copy, so treat it as the minimum.

Diagram of the Gemini 4 Argon agent loop: the model requests tool calls through the Interactions API, the Python harness validates and runs them in a copied app, and the agent's frozen code goes to a hidden evaluator outside its reach

Argon asks, Python decides and runs. Image by Author.

Give the agent a measurable performance goal

The system instruction states the goal and the rules but never says where the slowdown is:

Objective: make EventFlow faster on the supplied workloads without changing its
observable outputs or its public interfaces.
- Measure before you edit. Use run_benchmark and run_profiler to find where the
  time goes, then read the code the evidence points to.
- Behavior must follow the public contract documented in the docstrings, not
  only the visible tests.
- After every change, run run_tests and run_benchmark. If a test fails or
  output_matches_original is false, repair or revert the change.
- Acceptance target: at least 1.5x faster on the long workload, no more than 5%
  slower on short and mixed, all visible tests passing, and
  output_matches_original true on all three workloads.

The agent decides when to stop from what it can see: visible tests, profiler and benchmark results, and the limits the loop sets next.

Implement the Interactions API tool loop

The snippet below is the core of the loop; the complete version adds the client, tool dispatcher, and limits. Each turn is one client.interactions.create() call, and Argon often asks for several tools in one turn, so run every function_call step it returns. Send each result back as a function_result with the matching call_id:

# requires_action means "run my tools"; incomplete means the output cap cut the turn off
TERMINAL = {"failed", "cancelled", "incomplete"}

request = dict(
    model="gemini-4-argon",
    system_instruction=SYSTEM_INSTRUCTION,  # the goal shown above
    tools=DECLARATIONS,                     # the 8 schemas, like apply_patch above
    generation_config={"thinking_level": level, "max_output_tokens": 65_536,
                       "thinking_summaries": "auto"},
)
interaction = client.interactions.create(input=task, timeout=900, **request)

while True:
    calls = [s for s in interaction.steps if s.type == "function_call"]
    if interaction.status in TERMINAL or not calls:   # failed, cut off, or completed
        break
    results = []
    for call in calls:
        output, is_error, _ = tools.call(call.name, call.arguments)   # validated, run locally
        results.append({"type": "function_result", "name": call.name, "call_id": call.id,
                        "is_error": is_error,
                        "result": [{"type": "text", "text": json.dumps(output)}]})
    # application limits checked here: 40 turns, 45 minutes, $5
    # **request again: tools, instruction, and config don't carry over between turns
    interaction = client.interactions.create(
        input=results, previous_interaction_id=interaction.id, timeout=900, **request)

The **request on the follow-up call is the line people drop. previous_interaction_id carries the history, but tools, system_instruction, and generation_config are interaction-scoped, so a turn without them loses the tools or falls back to the default thinking level. I find this rule easy to forget, so the code keeps all three settings in one dict.

The API has no task budget, so set turn, time, and spend limits in your application before you start. I used 40 turns, 10 patches, 45 minutes, and $5, and no attempt came close, though the spend check runs between turns, so one long turn can overshoot it. Google's rate-limits page adds its own cap on Tier 1 accounts, $10 per rolling 10 minutes, and returns 429 RESOURCE_EXHAUSTED past it.

Run the Gemini 4 Argon Agent and Verify Its Changes

Each attempt started from a fresh copy of the app, stopped on its own within about 5 minutes, and had its code frozen before any hidden check ran.

Finding the bottleneck from evidence

Argon found the bottleneck by measuring first. Here is the first medium attempt, turn by turn.

Turn-by-turn log of a medium-thinking Gemini 4 Argon agent session, showing eight turns of benchmarks, profiles, file reads, patches, and test results with token counts per turn

One medium attempt, turn by turn. Image by Author.

Its thought summaries, a condensed version of its reasoning rather than every step, show how it got to the fix. Argon noticed that has_event() and add() both call get_history(). It asked whether the deep copy was "actually necessary here, or if the requirement is merely that the caller owns the data," and settled it with the summarize() docstring, which says the function doesn't modify its input.

Not every claim rested on evidence. The same summary says there is "no direct access in tests or hidden usages," which Argon couldn't know. The guess held (this time), but it's the kind of line to circle in a review.

The fix all six attempts landed on

Every attempt made the same core change to CustomerState, and the smallest of the six diffs shows it best:

     def __init__(self, customer_id: str):
         self.customer_id = customer_id
         self._events = []
-        self.summary = summarize([])
+        self._event_ids = set()
+        self._summary = None
+
+    @property
+    def summary(self):
+        if self._summary is None:
+            self._summary = summarize(self._events)
+        return self._summary
 
     def get_history(self):
@@
         return copy.deepcopy(self._events)
 
     def has_event(self, event_id: str) -> bool:
-        return any(e.event_id == event_id for e in self.get_history())
+        return event_id in self._event_ids
 
     def add(self, event) -> None:
-        bisect.insort(self._events, event, key=_order_key)
-        self.summary = summarize(self.get_history())
+        self._event_ids.add(event.event_id)
+        if not self._events or (self._events[-1].ts, self._events[-1].seq) <= (event.ts, event.seq):
+            self._events.append(event)
+        else:
+            bisect.insort(self._events, event, key=_order_key)
+        self._summary = None

Now the planted causes I promised earlier: add() re-summarized the whole history on every event, every event paid for two full deep copies, and duplicate checks scanned the list.

Diagram comparing EventFlow's work for each new event before the fix, with two history copies, a list scan, and a full re-summary, and after it, with a set lookup and a deferred summary

Per-event work before and after fix. Image by Author.

get_history() keeps its deep copy for outside callers, which is the detail the deep-copy shortcut from earlier gets wrong.

Every round of patches passed the visible tests, so the agent never had to revert, but most attempts also added a summary setter that nothing calls. Nobody asked for it, and somebody still has to review it.

Hidden acceptance test results

The hidden evaluator ran once per attempt, and every attempt passed every case. The cases call ingest() after report(), send refunds before their purchases, and modify returned objects to confirm engine state stays put.

Did Gemini 4 Argon Actually Make the App Faster?

Yes. Every new version was much faster and returned the same output as the original. As I mentioned earlier, timings drift, so I timed the original and all 6 new versions in alternating order, 9 fresh processes each.

Each timing covers ingest and reports only, not Python startup or imports. At a few milliseconds a single timing can land well above the median, so compare medians. The chart's log scale keeps the new versions from vanishing next to the original.

Log-scale bar chart of median runtimes for the original EventFlow and one medium and one high version on the short, long, and mixed workloads

Milliseconds after, seconds before, same outputs. Image by Author.

The chart shows the first attempt at each level, and the table covers all 6, with speedup as original median divided by new median:

Measure

Original

Medium (3 attempts)

High (3 attempts)

short speedup

Reference

14.8x to 15.9x

16.3x to 17.4x

long speedup

Reference

656x to 701x

about 810x

mixed speedup

Reference

65.4x to 71.6x

76.8x to 78.0x

Peak tracked memory, short

7,108 KiB

7,822 KiB

7,822 KiB

Peak tracked memory, long

885 KiB

570 KiB

569 KiB

long gained the most, since the removed work grew with each customer's history. The memory rows are the only place the fix costs anything. short, with the most customers, used about 10% more, probably for the new per-customer ID set, while long used less, likely because it stopped building copies.

Don't read 700x as something special about Argon. Removing a quadratic loop from synthetic data tends to produce numbers like this, whoever writes the fix.

Gemini 4 Argon Thinking Levels: Medium vs. High

On this task, high wrote slightly faster code than medium, but both levels found the same fix, so the extra thinking went into polish rather than diagnosis.

Keeping the comparison fair

Change the thinking level and nothing else. Every attempt used the same code, prompt, tools, limits, and evaluator, and none saw another attempt's work. The levels alternated, 3 attempts each, the same setup I used in my Claude Opus 5 API tutorial.

Results, time, and cost by level

The two levels split like this, averaged per attempt:

Average per attempt

Medium

High

Accepted fixes

3 of 3

3 of 3

Tool calls

28

35

Thinking tokens

1,877

14,764

Lines changed

24

193

Time

3.4 minutes

5 minutes

Cost at the introductory price

$0.10

$0.29

Cost after that price ends

$0.20

$0.58

high landed the same fix in its first patch and then kept polishing. It went on to rewrite parts of summarization, reporting, and validation that were no longer the main bottleneck.

That lead shifts with the machine and sits close to the timing noise on workloads that already finish in milliseconds, so I'd still ship medium's smaller diff on review time alone.

High's thinking peaked in turn 4, after its first patch had already fixed the problem.

Line chart of thinking tokens per turn for three medium and three high Gemini 4 Argon attempts, with high peaking after its first fix

Where each attempt spent its thinking. Image by Author.

Don't take thinking tokens as a quality signal, either; like the first request, one medium attempt used none and still landed an accepted fix, faster and cheaper than any other attempt.

To price an agent, add up the usage of every turn, because each turn reports the full context it processed. Most of each turn's input repeats the previous turn, and the implicit cache billed about two-thirds of it at the cached rate.

Per the caching guide, the cache needs a prompt of at least 4,096 tokens, and a turn that adds fewer than 2,050 new tokens pays the full rate on the last 2,050 of the previous request, so loops with tiny tool results save less. At the Standard introductory rates, which I cover in the FAQs, all 6 attempts together cost $1.16.

Limitations of an AI Performance Agent

An agent like this works best on problems a profiler can point at, and this one was built to be clean:

  • The bottleneck was planted, as I mentioned at the start. A profiler points almost straight at a quadratic loop in six small files, while real slowdowns hide in I/O, databases, and undocumented code.

  • The docstrings did a lot of work. Argon leaned on one to decide which copy it could skip, and an undocumented codebase gives it nothing to lean on.

  • Tests only check what someone thought of. In Google's ADK case study, one import change passed the public suite and still failed internal test targets.

  • Timings come from one small Linux machine on synthetic workloads, so your speedups will differ.

  • Every file the agent reads goes to Google, which stores paid-tier interactions for 55 days by default; store=false stops that but rules out previous_interaction_id.

Google's model card also lists hallucinations and occasional timeouts among known limitations.

Final Thoughts

The agent found the repeated summaries and deep copies in all six attempts, kept the copy callers rely on, and passed the held-out checks. high thinking added a little speed and a much larger diff.

I wouldn't generalize that to your codebase, since I planted the bottleneck. What carries over is the method: measure first, keep the tools narrow, freeze the code before checking it, and let hidden tests decide the result.

The extension I'd build first is a router that starts at medium and moves to high only when the visible tests fail, so the hidden cases never feed back into the agent. 

If the profiler output above is new to you, Writing Efficient Python Code covers the same timing and profiling tools I used before letting Argon touch the code.

FAQs

Can Gemini 4 Argon speed up Python code automatically?

Not on its own. The Interactions API doesn't run tools for you yet, so your code does that, and here the checking code ended up being twice the size of the app.

Can Gemini 4 Argon run Python code itself?

Yes, with the built-in code execution tool, but it stops after 30 seconds and can't install pytest. For real tests, use your own machine or the hosted setup in our Managed Agents tutorial.

Should I use low thinking for an agent like this?

I didn't test it, and Google says quality can drop on hard tasks. Since you set the level on every turn, I'd try low for reading files and medium for writing patches.

How much does the Gemini 4 Argon API cost?

I paid $2 per million input tokens and $10 per million output tokens, thinking included, and those prices double after November 30, 2026. Flex costs half, but each request can take up to 15 minutes, which I'd only accept for overnight experiments.

Can I use this agent on a production codebase?

Not as written, so run it in a disposable container and have a person review every diff. Also keep in mind that Argon's knowledge stops in March 2026, so give it current docs for newer libraries.

Khalid Abdelaty

Author

Khalid Abdelaty

I’m a data engineer and community builder who works across data pipelines, cloud, and AI tooling while writing practical, high-impact tutorials for DataCamp and emerging developers.

Topics

Related