Skip to content
unlob

Google · Python

Build a research agent with Google ADK

Build a research agent on Google’s Agent Development Kit, wrapping unlob as a FunctionTool alongside Gemini models.

What you are building

Your process

root_agent

Instruction plus tools

adk web

Local dev UI over the run

FunctionTools

web_search

Schema from the docstring

assemble_context

Schema from the docstring

Services

Gemini or Vertex AI

gemini-2.5-pro

unlob

Your own index

Your side of the API. unlob is one HTTPS service among the others — nothing here is specific to how our index works, so this agent survives a change of framework.

Install

Dependenciesbash
pip install google-adk httpx

Step by step

  1. Write a plain function

    ADK builds the tool schema from the signature and docstring, so a normal typed Python function is already a tool. Return a dict — ADK expects structured output.

    tools.pypython
    import os, httpx
    
    
    def web_search(query: str, min_sources: int = 2) -> dict:
        """Search the web on unlob's own index.
    
        Returns metadata only — url, title, snippet and independent_sources.
        Never returns page bodies.
    
        Args:
            query: What to search for.
            min_sources: Independent sources required. Use 3 for factual claims.
    
        Returns:
            A dict with a "results" list.
        """
        r = httpx.get("https://api.unlob.com/search",
                      headers={"x-api-key": os.environ["UNLOB_API_KEY"]},
                      params={"q": query,
                              "min_independent_sources": min_sources,
                              "limit": 8}, timeout=15)
        r.raise_for_status()
        return {"results": r.json()["results"]}
  2. Declare the agent

    Pass the functions directly; ADK wraps them as FunctionTools. `adk web` then gives you a local UI that shows every tool call and response.

    agent.pypython
    from google.adk.agents import Agent
    
    root_agent = Agent(
        name="researcher",
        model="gemini-2.5-pro",
        description="Researches questions against the open web with citations.",
        instruction=(
            "Search before you answer. Prefer claims carried by several "
            "independent sources, and say so when a claim rests on one. "
            "Always cite URLs."
        ),
        tools=[web_search, assemble_context],
    )
  3. Run it and watch what it did

    ADK ships a local dev UI that shows every tool call, its arguments and its response beside the conversation. For working out why an agent chose one tool over another it beats reading logs, and it is the strongest argument for ADK over a bare SDK.

    Two ways to run itbash
    # Interactive, with the trace UI at http://localhost:8000
    adk web
    
    # Or headless, for a one-shot run
    adk run . --input "What changed in the EU AI Act GPAI rules in 2026?"
    
    # The agent variable MUST be named root_agent or both of these report
    # finding no agents, with no hint as to why.

What happens when it runs

YouADK runnerGeminiunlobrun(question)instruction + tool schemasfunction callGET /searchresults dictfunction responseanswerfinal response
One turn of the loop. Search returns metadata only, so the agent spends a few hundred tokens deciding rather than tens of thousands reading.

The whole thing

Complete and runnable. Set the two environment variables and it works.

agent.py — completepython
"""A research agent on Google ADK, backed by the unlob web index.

    pip install google-adk httpx
    export UNLOB_API_KEY=ulb_...
    export GOOGLE_API_KEY=...

Run the dev UI with:  adk web
"""
import os

import httpx
from google.adk.agents import Agent

API = "https://api.unlob.com"
HEADERS = {"x-api-key": os.environ["UNLOB_API_KEY"]}


def web_search(query: str, min_sources: int = 2) -> dict:
    """Search the web on unlob's own index.

    Returns metadata only — url, title, snippet and independent_sources.
    Never returns page bodies.

    Args:
        query: What to search for.
        min_sources: Independent sources required. Use 3 for factual claims.

    Returns:
        A dict with a "results" list.
    """
    r = httpx.get(f"{API}/search", headers=HEADERS, params={
        "q": query, "min_independent_sources": min_sources, "limit": 8,
    }, timeout=15)
    r.raise_for_status()
    return {"results": r.json()["results"]}


def assemble_context(query: str, budget: int = 3000) -> dict:
    """Build a corroborated context pack packed to a token budget.

    Prefer this over several searches when you need to read source material
    rather than just find it.

    Args:
        query: The question the pack should answer.
        budget: Maximum tokens of context to return.

    Returns:
        A dict with "used_tokens" and a "passages" list.
    """
    r = httpx.get(f"{API}/assemble_context", headers=HEADERS, params={
        "query": query, "budget": budget, "min_independent_sources": 2,
    }, timeout=30)
    r.raise_for_status()
    return r.json()


root_agent = Agent(
    name="researcher",
    model="gemini-2.5-pro",
    description="Researches questions against the open web with citations.",
    instruction=(
        "Search before you answer. Prefer claims carried by several "
        "independent sources, and say so when a claim rests on one. "
        "Always cite URLs."
    ),
    tools=[web_search, assemble_context],
)

What will go wrong

The failures that cost an afternoon rather than a minute, because they produce something that looks like it is working.

Where this agent can go next

Every result the agent receives is a node in the coverage graph. These are the tools you can add to it without leaving Google ADK — each is one more entry in the same tools list.

relatedNear-duplicatescorroborateIndependent sourcesauthoritiesAuthoritative hostsdossierThe entity briefpathA second documentassemble_contextA packed context setA search resultwhat your agent has
The two marked in blue are the tools this tutorial wires up. The other four are the same shape.

Frequently asked questions

Can I use Claude models with ADK?

ADK supports other providers through LiteLLM, though Gemini is the native path and the smoothest one. If Claude is a hard requirement, LangGraph or Pydantic AI treat provider choice as first-class.

What does the dev UI give me?

`adk web` runs a local interface showing every tool call, its arguments and its response, alongside the conversation. For debugging why an agent chose one tool over another it is faster than reading logs.

You need a key to run this

10,000 requests a month on the free tier, no card. Enough to build the agent and put a real evaluation set through it.