Ga naar hoofdinhoud

Gemini 2.5 Computer Use-gids met demoproject: bouw een vacatures-zoekagent

Leer hoe je Gemini 2.5 Computer Use gebruikt om een AI-gestuurde vacatures-zoekagent te bouwen. Deze hands-on tutorial leidt je door browserautomatisering met Playwright en Streamlit, geen API’s nodig.
Bijgewerkt 2 okt 2026  · 8 min lezen

Verken met AI

ChatGPTClaudePerplexity

Het Gemini-team heeft onlangs Gemini 2.5 Computer Use geïntroduceerd, een gespecialiseerd model dat een live scherm kan zien en erop kan handelen door te klikken, te typen, te scrollen en op het web te navigeren zoals een menselijke operator.

In deze gids slaan we abstracte benchmarks over en bouwen we iets praktisch: een Streamlit-app die Computer Use inzet om een echte browser te bedienen, Google te doorzoeken naar vacaturemeldingen, een filter toe te passen en resultaten naar CSV te exporteren, zonder zoek-API’s van derden.

In deze tutorial leer je hoe je:

  • De Gemini 2.5 Computer Use-loop configureert
  • De agent uitvoert met Playwright (Chromium), met een allowlist en optionele menselijke bevestiging voor risicovolle stappen.
  • Een Streamlit-UI bouwt met live actielogs en screenshots na elke beurt.
  • Organische resultaten scrapt en een CSV downloadt.

Aan het einde heb je een zoekagent voor vacatures die nauwkeurige vacaturemeldingen voor je selecteert.

Demo image

Wil je meer leren over Gemini 2.5? Bekijk dan onze Gemini 2.5 Pro-tutorial, met features, tests, toegang, benchmarks en meer. Lees ook over Gemini 3 Pro, het nieuwste model van Google.

Wat is Gemini 2.5 Computer Use?

Gemini 2.5 Computer Use is een gespecialiseerd (preview) model en tool in de Gemini API waarmee je agents kunt bouwen die een browser bedienen. In plaats van site-specifieke API’s aan te roepen, werkt het model vanaf screenshots. Het kan zien wat er op de pagina staat en vervolgens handelen door UI-acties uit te sturen zoals navigate, click_at, scroll_document, enz.  

De clientcode ontvangt die voorgestelde acties, voert ze uit en stuurt een nieuwe combinatie van een screenshot en URL terug zodat het model de volgende stap kan bepalen. 

Hoe de Computer Use-loop werkt

agent loop

Bron: Gemini-documentatie

Zo werkt de bovenstaande agentloop end-to-end:

  1. Stuur een verzoek: Begin met een verzoek dat de Computer Use-tool, een doelprompt en een initiële screenshot van de gebruikersinterface bevat.
  2. Ontvang een antwoord: Het model reageert met een toelichting en een of meer functieroepen (zoals click_at of type_text_at) en een veiligheidsbeslissing.
  3. Uitvoering: Voer vervolgens de voorgestelde acties uit. Als de actie als regulier/toegestaan is gemarkeerd, kunnen we deze uitvoeren; als bevestiging vereist is, vragen we de gebruiker en gaan we alleen verder met expliciete goedkeuring.
  4. Staat teruggeven: Na uitvoering sturen we de staat terug naar het model via een FunctionResponse die een nieuwe screenshot en de huidige URL bevat.
  5. Herhalen: We herhalen deze zien–beslissen–handelen–observeren-cyclus totdat de taak voltooid is, er een fout optreedt, of de gebruiker of het model stopt.

Bouw een vacatures-zoekagent met Gemini 2.5 Computer Use en Streamlit

In deze sectie bouwen we een Streamlit-gestuurde vacatures-zoekagent die een echte browser aanstuurt met Gemini 2.5 Computer Use en Playwright, en vervolgens resultaten naar CSV exporteert.

Zo werkt het:

  • De gebruiker voert in de Streamlit-zijbalk een lijst met vacaturetrefwoorden in.
  • De agent instrueert Gemini om Google te openen, elke query uit te voeren en een tijdsfilter toe te passen.
  • De agent stopt op de eerste pagina met organische resultaten en toont live screenshots naast actielogs bij elke beurt.
  • Daarna scrapt hij titels, links en snippets van pagina 1 en voegt die samen over alle trefwoorden.
  • Tot slot rendert hij een resultatentabel plus een downloadbare CSV.

Onder de motorkap dwingt de app een allowlist van domeinen af voor veilige navigatie, ondersteunt optionele menselijke bevestiging voor risicovolle stappen en gebruikt de Computer Use-loop voor eenvoudig debuggen.

Voordat we beginnen: 

Een paar verantwoorde gebruiksscenario’s en sitebeleid:

  • Reikwijdte van automatisering: Deze demo automatiseert alleen Google Zoeken (op de allowlist) en scrapt organische resultaten op pagina 1.
  • Respecteer sitevoorwaarden: Automatiseer geen sites met restrictieve servicevoorwaarden en probeer nooit CAPTCHAs te omzeilen. Als er een CAPTCHA verschijnt, voltooi die dan handmatig en laat het model daarna doorgaan.
  • Human-in-the-loop-veiligheid: Als Gemini een risicovolle actie voorstelt (zoals een CAPTCHA-interactie of aankopen), stopt de app standaard. Hij gaat alleen verder als je auto-bevestigen expliciet inschakelt.

Stap 1: Vereisten

  • Python 3.10+
  • Een Gemini API-sleutel van Google AI Studio
  • macOS / Windows / Linux met Playwright Chromium geïnstalleerd

Stap 1.1: Imports 

Zorg er eerst voor dat je de volgende imports hebt geïnstalleerd:

python -m venv .venv && source .venv/bin/activate
pip install streamlit google-genai playwright python-dotenv
playwright install chromium

De bovenstaande commando’s zetten een virtuele omgeving op en installeren alle kernafhankelijkheden die nodig zijn om de app te bouwen, namelijk  Streamlit voor de UI, google-genai om de Gemini API aan te roepen, Playwright voor browserautomatisering, en python-dotenv voor het laden van omgevingsvariabelen en Chromium voor Playwright.

Stap 1.2: De Gemini API-sleutel instellen 

Nu de dependencies zijn geïnstalleerd, stellen we de Gemini API-sleutel in vanuit AI Studio. 

  • Log in bij AI Studio en zoek op “Get API Key”
  • Klik op Create API key
  • Selecteer een bestaand Google Cloud-project of maak er een met facturering ingeschakeld.

Create API Key

  • Klik eenvoudig op link a billing account en voeg je kaartgegevens toe ter verificatie.
  • Kopieer ten slotte de API-sleutel uit AI Studio en sla deze op.

Maak nu een .env-bestand in je projectmap en voeg daar je API-sleutel toe:

GOOGLE_API_KEY=YOUR_REAL_KEY
ALLOWED_HOSTS=google.com

Het pakket python-dotenv laadt GOOGLE_API_KEY tijdens runtime zodat de app het Gemini 2.5 Computer Use-model kan aanroepen.

Stap 2: Modelconfiguratie instellen

Laten we nu voorbereiden op runtime, met inbegrip van imports, constanten, model-ID en authenticatie. Dit zorgt ervoor dat de app met Gemini praat en dat navigatie veilig binnen een allowlist-domein blijft.

import os, io, time, csv, base64, urllib.parse
from typing import List, Dict, Tuple
import streamlit as st
from dotenv import load_dotenv
from playwright.sync_api import sync_playwright
from google import genai
from google.genai import types
from google.genai.types import Content, Part
W, H = 1440, 900
MODEL = "gemini-2.5-computer-use-preview-10-2025"  
load_dotenv()
API_KEY = os.getenv("GOOGLE_API_KEY")
if not API_KEY:
    st.stop()
ALLOWED_HOSTS = {h.strip().lower() for h in os.getenv("ALLOWED_HOSTS", "google.com").split(",") if h.strip()}
client = genai.Client(api_key=API_KEY)

We beginnen met het importeren van de kernbibliotheken zoals Streamlit, Playwright, google-genai en kleine utilities (dotenv, os, io, csv, time, urllib) voor configuratie en I/O.

Een vaste viewport (W, H = 1440 × 900) houdt screenshots en coördinatenmapping consistent voor Computer Use, en het previewmodel is ingesteld op gemini-2.5-computer-use-preview-10-2025. 

De API-sleutel wordt geladen met load_dotenv() en os.getenv("GOOGLE_API_KEY"). De navigatie is voor de veiligheid ingesteld met een allowlist (standaard google.com). 

Tot slot initialiseert genai.Client(api_key=API_KEY) de Gemini-SDK zodat de app de agentloop kan draaien en resultaten in Streamlit kan weergeven.

Stap 3: Hulpfuncties maken

Voordat we naar de kern van de app gaan, moeten we een paar hulpfuncties instellen. Ze helpen bij veiligheidscontroles, coördinatenconversie, het uitvoeren van acties voor Gemini’s functieroepen, SERP-scraping en het exporteren van resultaten als CSV. 

Stap 3.1: Veiligheid en uitvoering van acties

We beginnen met een backendhelper die veilig browserbeheer mogelijk maakt. Dit omvat een functie voor domein-allowlistcontrole om navigatie te begrenzen, een coördinatenconverter om het 0–999-raster van het model naar de vaste viewport te mappen, en een actiedispatcher die Computer Use-functieroepen interpreteert en ze via Playwright uitvoert.

def host_allowed(url: str) -> bool:
    try:
        netloc = urllib.parse.urlparse(url).netloc.lower()
        return any(netloc.endswith(allowed) for allowed in ALLOWED_HOSTS)
    except Exception:
        return False
def denorm(v: int, size: int) -> int:
    return int(v/1000*size)
def exec_calls(candidate, page, viewport, *, approve_all=False) -> List[Tuple[str, Dict]]:
    W, H = viewport
    results = []
    for part in candidate.content.parts:
        fc = getattr(part, "function_call", None)
        if not fc:
            continue
        name, args = fc.name, (fc.args or {})
        sd = args.get("safety_decision")
        if sd and sd.get("decision") == "require_confirmation":
            reason = sd.get("explanation", "Model flagged a risky action.")
            log_box.warning(f"[SAFETY requires confirmation] {reason}")
            if not approve_all:
                st.stop()
            results.append((name, {"safety_acknowledgement": "true"}))
        if name == "navigate":
            target = args.get("url", "")
            if target and not host_allowed(target):
                log_box.error(f"[BLOCKED] Non-allowlisted host: {target}")
                results.append((name, {"error": "blocked_by_allowlist"}))
                continue
        try:
            if name == "open_web_browser":
                pass
            elif name == "navigate":
                page.goto(args["url"], timeout=30000)
            elif name == "search":
                page.goto("https://www.google.com", timeout=30000)
            elif name == "click_at":
                page.mouse.click(denorm(args["x"], W), denorm(args["y"], H))
            elif name == "hover_at":
                page.mouse.move(denorm(args["x"], W), denorm(args["y"], H))
            elif name == "type_text_at":
                x, y = denorm(args["x"], W), denorm(args["y"], H)
                page.mouse.click(x, y)
                if args.get("clear_before_typing", True):
                    page.keyboard.press("Meta+A"); page.keyboard.press("Backspace")
                page.keyboard.type(args["text"])
                if args.get("press_enter", True):
                    page.keyboard.press("Enter")
            elif name == "scroll_document":
                page.mouse.wheel(0, 800 if args["direction"] == "down" else -800)
            elif name == "key_combination":
                page.keyboard.press(args["keys"])
            page.wait_for_load_state("networkidle", timeout=10000)
            results.append((name, {}))
            time.sleep(0.6)
        except Exception as e:
            results.append((name, {"error": str(e)}))
    return results

Zo past elke functie in de pijplijn:

  • host_allowed()-functie: Deze functie parseert de doel-URL, normaliseert de hostnaam naar kleine letters en controleert of die eindigt op een domein in ALLOWED_HOSTS uit het .env-bestand. 
  • denorm()-functie: Deze utility zet de pointercoördinaten om naar echte pixels voor de huidige viewport. Cruciaal voor precieze kliks, hovers en typen, zeker bij wisselende schermformaten of headless-runnen.
  • exec_calls()-functie: De uitvoerfunctie scant de modelrespons op functieroepen en dispatcht elk naar Playwright. Hij blokkeert navigatie naar niet-allowlisted hosts, voert de ondersteunde acties uit, wacht op network idle om de pagina te stabiliseren en registreert per actie de uitkomst (inclusief fouten). Deze resultaten worden vervolgens gebruikt om FunctionResponses naar het model te construeren.

Samen handhaven deze functies grenzen, houden ze de pointerwiskunde nauwkeurig en loggen ze gedetailleerde resultaten.

Stap 3.2: Feedback na acties en gegevens vastleggen

Na het uitvoeren van acties heeft de agent feedback nodig om de volgende stap te bepalen en nuttige output te leveren. Deze stap biedt drie hulpfuncties:

def fr_from(page, results):
    shot = page.screenshot(type="png")
    url = page.url
    frs = []
    for name, result in results:
        frs.append(types.FunctionResponse(
            name=name,
            response={"url": url, **result},
            parts=[types.FunctionResponsePart(
                inline_data=types.FunctionResponseBlob(mime_type="image/png", data=shot)
            )]
        ))
    return frs, shot
def scrape_google_serp(page, max_items=10):
    items = []
    anchors = page.locator('div#search a:has(h3)')
    count = min(anchors.count(), max_items)
    for i in range(count):
        a = anchors.nth(i)
        title = a.locator('h3').inner_text()
        link = a.get_attribute('href')
        snippet = ""
        snips = page.locator('div#search .VwiC3b')
        if snips.count() > i:
            snippet = snips.nth(i).inner_text()
        items.append({"title": title, "link": link, "snippet": snippet})
    return items
def to_csv_download(rows: List[Dict], name="results.csv"):
    if not rows:
        return None
    out = io.StringIO()
    writer = csv.DictWriter(out, fieldnames=["keyword", "title", "link", "snippet"])
    writer.writeheader(); writer.writerows(rows)
    b = out.getvalue().encode("utf-8")
    href = f"data:text/csv;base64,{base64.b64encode(b).decode()}"
    st.download_button("Download CSV", data=b, file_name=name, mime="text/csv")

Zo werken de hulpfuncties na acties:

  • fr_from()-functie: Deze maakt een nieuwe PNG-screenshot en pakt de huidige URL, en bouwt vervolgens een FunctionResponse voor elke uitgevoerde actie. Dit voegt visuele context toe voor de volgende inferencebeurt en bewaart een controleerbaar spoor van wat er is gebeurd.
  • scrape_google_serp()-functie: Vervolgens extraheren we titels, links en snippets van de eerste resultatenpagina van Google met robuuste selectors zoals a:has(h3) voor titels/links en .VwiC3b voor snippets. De scraper begrenst resultaten met max_items en retourneert schone, gestructureerde rijen die klaar zijn voor analyse of export.
  • to_csv_download()-functie: Deze functie genereert een in-memory CSV met consistente koppen en biedt die aan via Streamlits download_button().

Nu alle hulpfuncties klaar zijn, kunnen we er een Streamlit-app omheen bouwen.

Stap 4: De Streamlit-applicatie bouwen

Deze stap verbindt de Streamlit-UI met de Computer Use-agentloop. Hij rendert de interface, beheert hoe de gebruiker met de agent werkt en zorgt dat de output aansluit op hun behoefte.

st.set_page_config(page_title="Jobs Search with Gemini Computer Use", layout="wide")
st.title("Jobs Search with Gemini 2.5 Computer Use")
with st.sidebar:
    st.markdown("**Search inputs**")
    default_kw = "data scientist remote\nml engineer \ngenai research india"
    kw_text = st.text_area("Job keywords", value=default_kw, height=120)
    query_mode = st.selectbox("Query style", ["<kw> jobs", 'site:linkedin.com/jobs "<kw>"'], index=0)
    use_past_week = st.checkbox('Ask agent to set "Past week" filter', value=True)
    turns = st.slider("Max agent turns per keyword", 3, 12, 6)
    auto_confirm = st.checkbox("Auto-approve risky actions", value=False, help="If model requests confirmation (e.g., CAPTCHA), auto-approve instead of pausing.")
    run_btn = st.button("Run search")
log_col, shot_col = st.columns([0.45, 0.55])
log_box = log_col.container(height=520)
shot_box = shot_col.container(height=520)
table_box = st.container()
if run_btn:
    keywords = [k.strip() for k in kw_text.splitlines() if k.strip()]
    all_rows = []
    log_box.write("Initializing browser...")
    pw = sync_playwright().start()
    browser = pw.chromium.launch(headless=False)
    ctx = browser.new_context(viewport={"width": W, "height": H})
    page = ctx.new_page()
    try:
        for kw in keywords:
            log_box.subheader(f"▶ {kw}")
            page.goto("https://www.google.com", timeout=30000)
            initial_shot = page.screenshot(type="png")
            base_query = f'{kw} jobs' if query_mode == "<kw> jobs" else f'site:linkedin.com/jobs "{kw}"'
            goal = (
                f'Search Google for "{base_query}". '
                f'{"Open Tools and set time filter to Past week. " if use_past_week else ""}'
                'Stop when first-page results are fully visible; do NOT open the Jobs panel.'
            )
            contents = [Content(role="user", parts=[Part(text=goal), Part.from_bytes(data=initial_shot, mime_type="image/png")])]
            cfg = types.GenerateContentConfig(
                tools=[types.Tool(computer_use=types.ComputerUse(
                    environment=types.Environment.ENVIRONMENT_BROWSER
                ))]
            )
            for turn in range(turns):
                log_box.caption(f"Turn {turn+1}: thinking…")
                resp = client.models.generate_content(model=MODEL, contents=contents, config=cfg)
                cand = resp.candidates[0]
                contents.append(cand.content)
                narr = " ".join([p.text for p in cand.content.parts if getattr(p, "text", None)])
                if narr:
                    log_box.info(narr[:400])
                fcs = [p.function_call for p in cand.content.parts if getattr(p, "function_call", None)]
                if not fcs:
                    log_box.success("Agent stopped proposing actions.")
                    break
                for fc in fcs:
                    log_box.write(f"→ {fc.name} {fc.args or {}}")
                results = exec_calls(cand, page, (W, H), approve_all=auto_confirm)
                frs, shot = fr_from(page, results)
                contents.append(Content(role="user", parts=[Part(function_response=fr) for fr in frs]))
                shot_box.image(shot, caption=page.url, width='stretch')
            rows = scrape_google_serp(page)
            for r in rows:
                r["keyword"] = kw
            all_rows.extend(rows)
            log_box.success(f"{kw}: collected {len(rows)} results")
        if all_rows:
            table_box.dataframe(all_rows, width='stretch')
            to_csv_download(all_rows, name="jobs_google_results.csv")
        else:
            st.warning("No rows collected. Try fewer keywords or fewer turns.")
    finally:
        browser.close()
        pw.stop()

Zo werkt deze pijplijn in detail:

  • De app begint met het configureren van de pagina met een brede lay-out en een duidelijke titel zodat gebruikers meteen weten wat de tool doet. 
  • In de zijbalk geven gebruikers vacaturetrefwoorden op, kiezen ze de querystijl, een tijdsfilter, het maximale aantal agentbeurten per trefwoord en schakelen ze auto-bevestigen voor risicovolle acties in of uit. Daarna start een Run search-knop de workflow.
  • Het hoofdgedeelte is in twee kolommen verdeeld: links een “log”-container die tekstuele updates streamt en rechts een “screenshot”-container met live afbeeldingen van elke beurt. Daaronder staat een aparte container voor de uiteindelijke resultatentabel.
  • Wanneer de gebruiker op Run search klikt, schrijft de interface voortgangsberichten naar het logpaneel, werkt het screenshotpaneel bij zodra nieuwe beelden beschikbaar zijn en na het verwerken van alle trefwoorden rendert hij de geaggregeerde resultaten als dataframe. 

Om deze app te draaien, voer je simpelweg het volgende bash-commando uit in je terminal:

python -m streamlit run app.py

Tip: Start Streamlit altijd vanuit je venv om “ModuleNotFoundError”-problemen te vermijden.

Conclusie

Deze tutorial laat zien hoe je een visuele vacatures-zoekagent bouwt met Gemini 2.5 Computer Use om een browser te bedienen, filters toe te passen en resultaten te exporteren via een Streamlit-UI. 

Vanaf hier kun je de workflow uitbreiden met paginering om meer pagina’s te doorlopen, de scraper uitbreiden met publicatiedata, locaties en bedrijfsnamen, of een sandbox-vacaturebord op de allowlist zetten. 

Wil je meer leren over computer use en agentische AI? Bekijk dan vooral de cursus Introduction to AI Agents.


Aashi Dutt's photo
Author
Aashi Dutt
LinkedIn
Twitter

Ik ben een Google Developers Expert in ML (Gen AI), een Kaggle 3x Expert en een Women Techmakers Ambassador met meer dan 3 jaar ervaring in tech. In 2020 heb ik een healthtech-startup mee opgericht en ik volg een master computer science aan Georgia Tech, met als specialisatie machine learning.

Onderwerpen
AI Agents
Kunstmatige intelligentie
Large Language Models

Top DataCamp-cursussen

Cursus

AI-agents bouwen met Google ADK

1 uur
7.7K
Maak stap voor stap een assistent voor klantenondersteuning met de Agent Development Kit (ADK) van Google.
Bekijk detailsRight Arrow
Start Cursus
Meer bekijkenRight Arrow
Gerelateerd

blog

AI vanaf nul leren in 2026: een complete gids van de experts

Ontdek alles wat je moet weten om in 2026 AI te leren, van tips om te beginnen tot handige resources en inzichten van industrie-experts.
Adel Nehme's photo

Adel Nehme

15 min

Meer BekijkenMeer Bekijken