Your Agent Is Searching From a Desktop Nobody Uses

Mobile and desktop return different results for the same query. If your users are on phones, your agent is reading the wrong page.

Profile picture of Serply
Serply
Side-by-side desktop and mobile search results for one query

Search engines have served different results to phones and desktops for years. Not slightly different — different ordering, different features, different sites. Local results surface more aggressively on mobile, feature blocks push organic results further down, and pages that fail mobile usability checks lose ground.

If your agent answers questions for people on phones and searches from a desktop context, it’s reading a page its users will never see.

Switching device

The X-User-Agent header takes desktop or mobile, and defaults to desktop:

import os
import requests
from urllib.parse import quote_plus

API_KEY = os.environ["SERPLY_API_KEY"]


def search(query: str, device: str = "desktop", num: int = 10,
           location: str = "US") -> dict:
    resp = requests.get(
        f"https://api.serply.io/v1/search/q={quote_plus(query)}&num={num}",
        headers={
            "X-Api-Key": API_KEY,
            "X-User-Agent": device,
            "X-Proxy-Location": location,
        },
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()

The response echoes back device_type and device_region, which is worth logging — it’s how you confirm the header took effect rather than assuming it did.

Measuring the gap

from urllib.parse import urlparse
from concurrent.futures import ThreadPoolExecutor


def domain(url: str) -> str:
    host = urlparse(url or "").netloc.lower()
    return host[4:] if host.startswith("www.") else host


def compare(query: str, location: str = "US", num: int = 10) -> dict:
    with ThreadPoolExecutor(max_workers=2) as pool:
        d = pool.submit(search, query, "desktop", num, location)
        m = pool.submit(search, query, "mobile", num, location)
        desktop, mobile = d.result(), m.result()

    dr = desktop.get("results", [])
    mr = mobile.get("results", [])
    dd = [domain(r.get("link", "")) for r in dr]
    md = [domain(r.get("link", "")) for r in mr]

    shared = set(dd) & set(md)
    return {
        "query": query,
        "top1_same": bool(dd and md and dd[0] == md[0]),
        "overlap": len(shared) / len(set(dd) | set(md)) if (dd or md) else 0,
        "desktop_only": sorted(set(dd) - set(md)),
        "mobile_only": sorted(set(md) - set(dd)),
        "rank_shifts": {
            host: (dd.index(host) + 1, md.index(host) + 1)
            for host in shared
            if dd.index(host) != md.index(host)
        },
        "features": {
            "desktop": feature_counts(desktop),
            "mobile": feature_counts(mobile),
        },
    }


def feature_counts(data: dict) -> dict:
    def n(key):
        v = data.get(key)
        return len(v) if isinstance(v, list) else (1 if v else 0)

    return {
        "ads": data.get("ads_count") or n("ads"),
        "answers": n("answers"),
        "knowledge_graph": 1 if data.get("knowledge_graph") else 0,
        "paa": n("related_questions"),
        "local": n("places") + n("local_businesses"),
        "images": n("image_results"),
        "shopping": n("shopping_ads"),
        "carousel": data.get("carousel_count") or n("carousel"),
    }

Compare domains rather than URLs. Mobile frequently returns a differently-parameterised URL for the same page, and exact matching reports a difference that isn’t one.

Where the difference actually shows up

Run this across a query set and the pattern is consistent rather than random.

Navigational and well-established informational queries look nearly identical — both devices agree, because there’s one obvious answer.

Local and commercial queries diverge hard. Mobile shows more local results and shows them higher, because the engine assumes proximity intent from a phone.

The bigger effect is usually vertical, not horizontal. Even when the organic ordering matches, mobile stacks more feature blocks above it. A result at organic position 3 on both devices might have two blocks above it on desktop and six on mobile, which is why the realPosition field matters here:

def effective_position(data: dict, target_domain: str) -> int | None:
    for r in data.get("results", []):
        if domain(r.get("link", "")) == target_domain:
            return r.get("realPosition") or r.get("position")
    return None

position is rank among organic results; realPosition is where it actually sits on the page. The desktop-to-mobile gap in realPosition for the same site is the number that explains a traffic difference.

Picking a device for your agent

Match the context your answer will be used in, not your own laptop.

DEVICE_BY_INTENT = {
    "local": "mobile",       # "near me", restaurants, opening hours
    "commercial": "mobile",  # product searches, "buy X"
    "technical": "desktop",  # documentation, error messages, code
    "research": "desktop",   # long-form articles, papers, analysis
}


def device_for(query: str) -> str:
    q = query.lower()
    if any(t in q for t in ("near me", "open now", "nearby", "hours", "directions")):
        return "mobile"
    if any(t in q for t in ("error", "stack trace", "api", "documentation", "config")):
        return "desktop"
    return "desktop"

For technical questions desktop is genuinely better, and not for aesthetic reasons — documentation sites, forums, and code hosts rank more reliably there, and mobile results for a stack trace skew toward aggregator pages.

For a consumer assistant answering “is there a hardware store open right now,” mobile is the correct context and desktop will under-surface exactly the local results you need.

Auditing both when it matters

For SEO work, the comparison is the deliverable:

def device_audit(query: str, your_domain: str, location: str = "US") -> dict:
    with ThreadPoolExecutor(max_workers=2) as pool:
        d = pool.submit(search, query, "desktop", 20, location)
        m = pool.submit(search, query, "mobile", 20, location)
        desktop, mobile = d.result(), m.result()

    return {
        "query": query,
        "desktop_position": effective_position(desktop, your_domain),
        "mobile_position": effective_position(mobile, your_domain),
        "desktop_features_above": sum(feature_counts(desktop).values()),
        "mobile_features_above": sum(feature_counts(mobile).values()),
    }

Running this across a keyword set surfaces the specific pages where you rank fine on desktop and are buried on mobile. That’s usually a small list, and it’s usually where the traffic problem is.

One practical caution

Don’t compare a mobile search run today against a desktop search run yesterday and attribute the difference to device. Results shift over time on their own. Fire both requests concurrently, as above — same moment, same location, one variable.