Your Agent Is Searching From the Wrong Country
Search results are geo-personalized. If your agent always queries from one region, it's giving every user the same locally-wrong answer.


- The header
- Seeing the difference
- Making it a tool parameter
- Defaulting from user context
- Multi-region for comparison questions
- Device matters too
- What to actually do
Ask a search engine about consumer refund rights, tax deadlines, drug names, plug types, or streaming availability, and the results depend heavily on where the request came from. This is invisible during development because you’re testing from one place and the answers look fine.
Then a user in Germany asks your agent about statutory warranty periods and gets an article about US state law.
The header
Serply exposes region selection through a request header, X-Proxy-Location, on the search endpoints:
import os
import requests
from urllib.parse import quote_plus
API_KEY = os.environ["SERPLY_API_KEY"]
def search(query: str, region: str = "US", num: int = 10) -> list[dict]:
resp = requests.get(
f"https://api.serply.io/v1/search/q={quote_plus(query)}&num={num}",
headers={
"X-Api-Key": API_KEY,
"X-Proxy-Location": region,
"X-User-Agent": "desktop",
},
timeout=30,
)
resp.raise_for_status()
return resp.json().get("results", [])
The documented values are US, CA, GB, IE, EU, FR, DE, SE, IN, JP, KR, SG, AU, and BR. The response echoes back a device_region field so you can confirm what actually got used.
Seeing the difference
Worth running once, on your own domain, to calibrate how much this matters:
def compare(query: str, regions: list[str]) -> None:
for region in regions:
results = search(query, region=region, num=5)
print(f"\n--- {region} ---")
for r in results:
display = (r.get("metadata") or {}).get("display_url", "")
print(f" {r.get('position')}. {display:<28} {r.get('title', '')[:60]}")
compare("statutory warranty period consumer goods", ["US", "GB", "DE", "AU"])
For queries with a legal or regulatory dimension the overlap between regions is often near zero. For “how does TCP work” it’s near total. Knowing which of your users’ questions fall into which bucket is the actual design work.
Making it a tool parameter
The simplest correct approach is to let the agent choose, with a description that explains when to bother:
{
"name": "web_search",
"description": (
"Search the live web. Returns titles, URLs, and snippets."
),
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query, keyword style."},
"region": {
"type": "string",
"enum": ["US", "CA", "GB", "IE", "EU", "FR", "DE", "SE",
"IN", "JP", "KR", "SG", "AU", "BR"],
"description": (
"Country to search from. Results are geo-personalized, so set "
"this when the question involves law, regulation, taxes, pricing, "
"product availability, healthcare, or local services. Default US."
),
"default": "US",
},
},
"required": ["query"],
},
}
Enumerating the situations in the parameter description is what makes this work. Given only “country to search from,” models leave it at the default essentially always.
Defaulting from user context
Better still, don’t make the model guess when you already know:
from dataclasses import dataclass
@dataclass
class UserContext:
region: str = "US"
locale: str = "en-US"
def build_search_tool(ctx: UserContext):
def web_search(query: str, region: str | None = None, num: int = 10) -> str:
results = search(query, region=region or ctx.region, num=num)
if not results:
return f"No results for '{query}'."
return "\n\n".join(
f"[{r.get('position')}] {r.get('title')}\n{r.get('link')}\n{r.get('description', '')}"
for r in results
)
return web_search
The model can still override for a question that’s explicitly about somewhere else — “what are the rules in Japan?” — while everything else inherits the user’s actual region.
Multi-region for comparison questions
Some questions are genuinely comparative, and one region can’t answer them:
from concurrent.futures import ThreadPoolExecutor
def multi_region_search(query: str, regions: list[str], num: int = 5) -> dict:
with ThreadPoolExecutor(max_workers=min(len(regions), 5)) as pool:
results = list(pool.map(lambda r: search(query, region=r, num=num), regions))
return dict(zip(regions, results))
data = multi_region_search("data localization requirements", ["US", "DE", "IN", "BR"])
Run them concurrently — four sequential searches at a second each is four seconds of latency for something that should take one. Keep the region count small; this multiplies your call volume directly, and x-ratelimit-requests-remaining on the responses is the number to watch.
Device matters too
The companion header, X-User-Agent, takes desktop or mobile and defaults to desktop. Mobile SERPs are laid out differently and surface different features, which matters if your agent is answering questions about search results rather than using them as a source:
headers={"X-Api-Key": API_KEY, "X-Proxy-Location": "US", "X-User-Agent": "mobile"}
For an SEO or rank-tracking agent this is essential — reporting a desktop position to someone whose traffic is 70% mobile is reporting the wrong number. For a general research agent, leave it on desktop and don’t think about it again.
What to actually do
If you build one thing from this, make it the default: thread the user’s region through to the header instead of hard-coding US. Most geo bugs in agent products aren’t subtle ranking differences — they’re an agent in one country confidently citing another country’s rules, and the user has no way to tell.