spagestic commited on
Commit
425ab31
·
1 Parent(s): 8aebde4

Removed the outdated structured-data tools and wired everything to web search instead.

Browse files
README.md CHANGED
@@ -27,7 +27,7 @@ Immigration research is fragmented across government sites, forums, and spreadsh
27
  1. **Describe yourself** — citizenship, education, work history, languages, budget, and goals in everyday language.
28
  2. **Get a shortlist** — the agent reasons over your profile and surfaces destination countries that fit.
29
  3. **Explore on a 3D globe** — shortlisted countries appear on an interactive MapLibre globe beside the chat.
30
- 4. **Dig into the details** — visa pathways, required documents, realistic timelines, and live economic context from the World Bank.
31
 
32
  No forms to decode. No keyword guessing. Just a research session that meets you where you are.
33
 
@@ -37,15 +37,12 @@ Borderless is a **Gradio agent** powered by **[Qwen/Qwen3.6-27B](https://hugging
37
 
38
  | Tool | What it fetches |
39
  |------|-----------------|
40
- | `get_visa_requirement` | Visa rules between your passport country and a destination |
41
- | `get_passport_stats` | Aggregate visa-free / visa-on-arrival access for a passport |
42
- | `get_economic_indicator` | World Bank time series (GDP, inflation, trade, and more) |
43
  | `search_immigration_info` | Web search for official immigration pages, policies, and pathways (Exa) |
44
  | `scrape_web_page` | Markdown content from a specific official government or embassy URL (Firecrawl) |
45
  | `crawl_web_site` | Multiple pages from an official immigration website section (Firecrawl) |
46
  | `update_globe` | Marks, highlights, and flies to countries on the MapLibre globe |
47
 
48
- Tool calls stream in the chat so you can follow the agent's reasoning. Globe updates are also tool-driven: when the agent recommends destinations or the user asks to mark countries, it sends ISO country codes to the map and the UI renders markers, highlights, and camera movements. After up to five tool rounds, it synthesizes a clear answer with pathways, documents, timelines, and economic snapshots.
49
 
50
  Sign in with your Hugging Face account to run inference through the Inference API.
51
 
@@ -53,9 +50,7 @@ Sign in with your Hugging Face account to run inference through the Inference AP
53
 
54
  - **Agentic research** — multi-turn tool use, not a single-shot prompt
55
  - **Tool-driven 3D globe** — MapLibre GL globe projection with markers, highlights, fly-to camera moves, drag, rotate, and zoom
56
- - **Live visa data** — passport strength and country-pair requirements
57
- - **Economic context** — World Bank indicators to compare destinations
58
- - **Web search** — Exa discovers official immigration pages and policy sources
59
  - **Official page scraping** — Firecrawl extracts markdown from government immigration sites
60
  - **Country metadata** — REST Countries powers ISO-2 / ISO-3 lookup and map coordinates
61
  - **Transparent traces** — every tool call and result visible in the chat
@@ -74,9 +69,7 @@ Sign in with your Hugging Face account to run inference through the Inference AP
74
  - **[Qwen3.6-27B](https://huggingface.co/Qwen/Qwen3.6-27B)** — reasoning and tool planning via Hugging Face Inference API
75
  - **[huggingface_hub](https://huggingface.co/docs/huggingface_hub)** — `InferenceClient` with streaming and function calling
76
  - **[MapLibre GL JS](https://maplibre.org/)** — interactive 3D globe
77
- - **[World Bank Open Data API](http://api.worldbank.org/)** — economic indicators
78
  - **[REST Countries](https://restcountries.com/)** — country names, ISO codes, regions, capitals, flags, area, and map coordinates
79
- - **Passport Visa API** — visa requirements and passport mobility stats
80
  - **[Exa](https://exa.ai)** — neural web search for discovering immigration sources
81
  - **[Firecrawl](https://firecrawl.dev)** — scrape and crawl official web pages for immigration details
82
 
@@ -92,8 +85,6 @@ ui/
92
  globe_commands.py # Globe marker/highlight/fly-to state updates
93
  country_coords.py # Country lookup for globe coordinates
94
  apis/
95
- world_bank.py # World Bank indicator client
96
- passport_visa.py # Visa & passport stats client
97
  rest_countries.py # REST Countries metadata client
98
  exa.py # Exa web search client
99
  firecrawl.py # Firecrawl scrape/crawl client
@@ -128,7 +119,7 @@ For web research tools, set API keys from [dashboard.exa.ai](https://dashboard.e
128
  | `EXA_API_KEY` | `search_immigration_info` |
129
  | `FIRECRAWL_API_KEY` | `scrape_web_page`, `crawl_web_site` |
130
 
131
- On Hugging Face Spaces, add both as **Space secrets** (Settings → Secrets). Without keys, structured visa and economic tools still work; web tools return a clear error. The agent uses Exa to discover URLs, then Firecrawl to fetch full official page content.
132
 
133
  ## License
134
 
 
27
  1. **Describe yourself** — citizenship, education, work history, languages, budget, and goals in everyday language.
28
  2. **Get a shortlist** — the agent reasons over your profile and surfaces destination countries that fit.
29
  3. **Explore on a 3D globe** — shortlisted countries appear on an interactive MapLibre globe beside the chat.
30
+ 4. **Dig into the details** — visa pathways, required documents, realistic timelines, and economic context from official sources.
31
 
32
  No forms to decode. No keyword guessing. Just a research session that meets you where you are.
33
 
 
37
 
38
  | Tool | What it fetches |
39
  |------|-----------------|
 
 
 
40
  | `search_immigration_info` | Web search for official immigration pages, policies, and pathways (Exa) |
41
  | `scrape_web_page` | Markdown content from a specific official government or embassy URL (Firecrawl) |
42
  | `crawl_web_site` | Multiple pages from an official immigration website section (Firecrawl) |
43
  | `update_globe` | Marks, highlights, and flies to countries on the MapLibre globe |
44
 
45
+ Tool calls stream in the chat so you can follow the agent's reasoning. Globe updates are also tool-driven: when the agent recommends destinations or the user asks to mark countries, it sends ISO country codes to the map and the UI renders markers, highlights, and camera movements. After up to five tool rounds, it synthesizes a clear answer with pathways, documents, timelines, and cited sources.
46
 
47
  Sign in with your Hugging Face account to run inference through the Inference API.
48
 
 
50
 
51
  - **Agentic research** — multi-turn tool use, not a single-shot prompt
52
  - **Tool-driven 3D globe** — MapLibre GL globe projection with markers, highlights, fly-to camera moves, drag, rotate, and zoom
53
+ - **Web search** — Exa discovers official immigration pages, visa rules, and policy sources
 
 
54
  - **Official page scraping** — Firecrawl extracts markdown from government immigration sites
55
  - **Country metadata** — REST Countries powers ISO-2 / ISO-3 lookup and map coordinates
56
  - **Transparent traces** — every tool call and result visible in the chat
 
69
  - **[Qwen3.6-27B](https://huggingface.co/Qwen/Qwen3.6-27B)** — reasoning and tool planning via Hugging Face Inference API
70
  - **[huggingface_hub](https://huggingface.co/docs/huggingface_hub)** — `InferenceClient` with streaming and function calling
71
  - **[MapLibre GL JS](https://maplibre.org/)** — interactive 3D globe
 
72
  - **[REST Countries](https://restcountries.com/)** — country names, ISO codes, regions, capitals, flags, area, and map coordinates
 
73
  - **[Exa](https://exa.ai)** — neural web search for discovering immigration sources
74
  - **[Firecrawl](https://firecrawl.dev)** — scrape and crawl official web pages for immigration details
75
 
 
85
  globe_commands.py # Globe marker/highlight/fly-to state updates
86
  country_coords.py # Country lookup for globe coordinates
87
  apis/
 
 
88
  rest_countries.py # REST Countries metadata client
89
  exa.py # Exa web search client
90
  firecrawl.py # Firecrawl scrape/crawl client
 
119
  | `EXA_API_KEY` | `search_immigration_info` |
120
  | `FIRECRAWL_API_KEY` | `scrape_web_page`, `crawl_web_site` |
121
 
122
+ On Hugging Face Spaces, add both as **Space secrets** (Settings → Secrets). Without keys, web tools return a clear error. The agent uses Exa to discover URLs, then Firecrawl to fetch full official page content.
123
 
124
  ## License
125
 
apis/__init__.py CHANGED
@@ -1,14 +1,9 @@
1
  # apis/__init__.py
2
  from apis.exa import search_immigration
3
  from apis.firecrawl import crawl_site, scrape_page
4
- from apis.passport_visa import passport_stats, visa_requirement
5
- from apis.world_bank import get_indicator_series
6
 
7
  __all__ = [
8
  "crawl_site",
9
- "get_indicator_series",
10
- "passport_stats",
11
  "scrape_page",
12
  "search_immigration",
13
- "visa_requirement",
14
  ]
 
1
  # apis/__init__.py
2
  from apis.exa import search_immigration
3
  from apis.firecrawl import crawl_site, scrape_page
 
 
4
 
5
  __all__ = [
6
  "crawl_site",
 
 
7
  "scrape_page",
8
  "search_immigration",
 
9
  ]
apis/passport_visa.py DELETED
@@ -1,46 +0,0 @@
1
- # apis/passport_visa.py
2
- """Passport Visa API client (community visa requirements service)."""
3
-
4
- from __future__ import annotations
5
-
6
- from typing import Any
7
-
8
- import requests
9
-
10
- BASE_URL = "https://rough-sun-2523.fly.dev"
11
-
12
-
13
- def visa_requirement(
14
- passport_iso2: str,
15
- destination_iso2: str,
16
- *,
17
- timeout: float = 10,
18
- ) -> dict[str, Any]:
19
- """
20
- Get visa requirement between two countries.
21
-
22
- Args:
23
- passport_iso2: Issuing passport country (ISO-2, e.g. ``HK``).
24
- destination_iso2: Destination country (ISO-2, e.g. ``GB``).
25
- """
26
- url = f"{BASE_URL}/visa/{passport_iso2}/{destination_iso2}"
27
- resp = requests.get(url, timeout=timeout)
28
- resp.raise_for_status()
29
- return resp.json()
30
-
31
-
32
- def passport_stats(
33
- passport_iso2: str,
34
- *,
35
- timeout: float = 10,
36
- ) -> dict[str, Any]:
37
- """
38
- Get aggregate visa stats for a passport (VR, VOA, EV, VF, NA counts).
39
-
40
- Args:
41
- passport_iso2: Passport country (ISO-2, e.g. ``HK``).
42
- """
43
- url = f"{BASE_URL}/country/{passport_iso2}"
44
- resp = requests.get(url, timeout=timeout)
45
- resp.raise_for_status()
46
- return resp.json()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apis/world_bank.py DELETED
@@ -1,49 +0,0 @@
1
- # apis/world_bank.py
2
- """World Bank API client for economic indicators."""
3
-
4
- from __future__ import annotations
5
-
6
- from typing import Any
7
-
8
- import requests
9
-
10
- BASE_URL = "http://api.worldbank.org/v2"
11
-
12
-
13
- def get_indicator_series(
14
- indicator: str,
15
- country: str,
16
- *,
17
- per_page: int = 100,
18
- mrv: int | None = None,
19
- timeout: float = 30,
20
- ) -> list[dict[str, Any]]:
21
- """
22
- Fetch a time series for a World Bank indicator and country.
23
-
24
- Args:
25
- indicator: World Bank indicator code (e.g. ``NY.GDP.MKTP.CD``).
26
- country: ISO-3 country code (e.g. ``HKG``).
27
- per_page: Max observations per request (increase for longer histories).
28
- mrv: If set, return only the *m* most recent values.
29
- timeout: Request timeout in seconds.
30
-
31
- Returns:
32
- List of observation dicts with ``date``, ``value``, ``country``, etc.
33
- """
34
- url = f"{BASE_URL}/country/{country}/indicator/{indicator}"
35
- params: dict[str, str | int] = {"format": "json", "per_page": per_page}
36
- if mrv is not None:
37
- params["mrv"] = mrv
38
-
39
- resp = requests.get(url, params=params, timeout=timeout)
40
- resp.raise_for_status()
41
- data = resp.json()
42
-
43
- if not isinstance(data, list) or len(data) < 2:
44
- return []
45
-
46
- observations = data[1]
47
- if observations is None:
48
- return []
49
- return observations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/chat.py CHANGED
@@ -9,21 +9,18 @@ ADDITIONAL_INPUTS = [
9
  gr.Textbox(
10
  value=(
11
  "You are Borderless, an immigration research assistant with access to "
12
- "World Bank economic data, passport/visa information, web search, page "
13
- "scraping, and an interactive 3D globe. Use structured data tools first: "
14
- "get_visa_requirement and get_passport_stats for country-pair visa rules, "
15
- "get_economic_indicator for GDP, inflation, and trade data. For document "
16
- "lists, fees, processing times, or pathway details not covered by "
17
- "structured tools, use search_immigration_info to discover official "
18
- "government and embassy pages, then scrape_web_page on the best official "
19
- "URLs for full content. Prefer official .gov, immigration authority, and "
20
- "embassy domains; use include_domains when you know the right government "
21
- "site. Use crawl_web_site only when multiple pages from the same official "
22
- "site are needed; prefer scrape_web_page when one page is enough. Cite "
23
- "source URLs when answering from search or scraped content. When you "
24
- "recommend or compare destination countries, call update_globe with action "
25
- "show so the user can see them on the globe. When the user asks to mark, "
26
- "map, show, highlight, or fly to countries, call update_globe immediately "
27
  "instead of describing the tool call."
28
  ),
29
  label="System message",
 
9
  gr.Textbox(
10
  value=(
11
  "You are Borderless, an immigration research assistant with access to "
12
+ "web search, page scraping, and an interactive 3D globe. Use "
13
+ "search_immigration_info as your primary discovery tool for visa rules, "
14
+ "pathways, documents, fees, processing times, and economic context — "
15
+ "then scrape_web_page on the best official URLs for full content. Prefer "
16
+ "official .gov, immigration authority, and embassy domains; use "
17
+ "include_domains when you know the right government site. Use "
18
+ "crawl_web_site only when multiple pages from the same official site "
19
+ "are needed; prefer scrape_web_page when one page is enough. Cite source "
20
+ "URLs when answering from search or scraped content. When you recommend "
21
+ "or compare destination countries, call update_globe with action show so "
22
+ "the user can see them on the globe. When the user asks to mark, map, "
23
+ "show, highlight, or fly to countries, call update_globe immediately "
 
 
 
24
  "instead of describing the tool call."
25
  ),
26
  label="System message",
ui/inference/tool_schemas/__init__.py CHANGED
@@ -3,9 +3,6 @@ from __future__ import annotations
3
  from typing import Any
4
 
5
  from .crawl_web_site import SCHEMA as crawl_web_site
6
- from .get_economic_indicator import SCHEMA as get_economic_indicator
7
- from .get_passport_stats import SCHEMA as get_passport_stats
8
- from .get_visa_requirement import SCHEMA as get_visa_requirement
9
  from .scrape_web_page import SCHEMA as scrape_web_page
10
  from .search_immigration_info import SCHEMA as search_immigration_info
11
  from .think import SCHEMA as think
@@ -13,9 +10,6 @@ from .update_globe import SCHEMA as update_globe
13
 
14
  TOOL_SCHEMAS: list[dict[str, Any]] = [
15
  think,
16
- get_economic_indicator,
17
- get_visa_requirement,
18
- get_passport_stats,
19
  search_immigration_info,
20
  scrape_web_page,
21
  crawl_web_site,
 
3
  from typing import Any
4
 
5
  from .crawl_web_site import SCHEMA as crawl_web_site
 
 
 
6
  from .scrape_web_page import SCHEMA as scrape_web_page
7
  from .search_immigration_info import SCHEMA as search_immigration_info
8
  from .think import SCHEMA as think
 
10
 
11
  TOOL_SCHEMAS: list[dict[str, Any]] = [
12
  think,
 
 
 
13
  search_immigration_info,
14
  scrape_web_page,
15
  crawl_web_site,
ui/inference/tool_schemas/get_economic_indicator.py DELETED
@@ -1,30 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- SCHEMA: dict[str, Any] = {
6
- "type": "function",
7
- "function": {
8
- "name": "get_economic_indicator",
9
- "description": (
10
- "Fetch World Bank economic indicator time series for a country. "
11
- "Use ISO-3 country codes (e.g. IND for India, HKG, USA, CHN) and "
12
- "World Bank indicator codes (e.g. NY.GDP.MKTP.CD for GDP in "
13
- "current US dollars)."
14
- ),
15
- "parameters": {
16
- "type": "object",
17
- "properties": {
18
- "indicator": {
19
- "type": "string",
20
- "description": "World Bank indicator code, e.g. NY.GDP.MKTP.CD",
21
- },
22
- "country": {
23
- "type": "string",
24
- "description": "ISO-3 country code, e.g. IND, HKG, USA",
25
- },
26
- },
27
- "required": ["indicator", "country"],
28
- },
29
- },
30
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/inference/tool_schemas/get_passport_stats.py DELETED
@@ -1,24 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- SCHEMA: dict[str, Any] = {
6
- "type": "function",
7
- "function": {
8
- "name": "get_passport_stats",
9
- "description": (
10
- "Get aggregate visa access stats for a passport country. Use ISO-2 "
11
- "country codes."
12
- ),
13
- "parameters": {
14
- "type": "object",
15
- "properties": {
16
- "passport_iso2": {
17
- "type": "string",
18
- "description": "Passport issuing country ISO-2 code, e.g. HK, US, IN",
19
- },
20
- },
21
- "required": ["passport_iso2"],
22
- },
23
- },
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/inference/tool_schemas/get_visa_requirement.py DELETED
@@ -1,28 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- SCHEMA: dict[str, Any] = {
6
- "type": "function",
7
- "function": {
8
- "name": "get_visa_requirement",
9
- "description": (
10
- "Get visa requirement details for a passport country traveling to "
11
- "a destination country. Use ISO-2 country codes."
12
- ),
13
- "parameters": {
14
- "type": "object",
15
- "properties": {
16
- "passport_iso2": {
17
- "type": "string",
18
- "description": "Passport issuing country ISO-2 code, e.g. HK, US, IN",
19
- },
20
- "destination_iso2": {
21
- "type": "string",
22
- "description": "Destination country ISO-2 code, e.g. GB, JP, US",
23
- },
24
- },
25
- "required": ["passport_iso2", "destination_iso2"],
26
- },
27
- },
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ui/inference/tools.py CHANGED
@@ -9,8 +9,6 @@ from gradio import ChatMessage
9
 
10
  from apis.exa import search_immigration
11
  from apis.firecrawl import crawl_site, scrape_page
12
- from apis.passport_visa import passport_stats, visa_requirement
13
- from apis.world_bank import get_indicator_series
14
  from ui.globe_commands import apply_update_globe
15
 
16
  from .messages import assistant_message_dict
@@ -44,24 +42,6 @@ def _run_tool(
44
  ) -> tuple[Any, dict[str, Any] | None]:
45
  if name == "think":
46
  return {"ok": True}, globe_state
47
- if name == "get_economic_indicator":
48
- return (
49
- get_indicator_series(
50
- indicator=args["indicator"],
51
- country=args["country"],
52
- ),
53
- globe_state,
54
- )
55
- if name == "get_visa_requirement":
56
- return (
57
- visa_requirement(
58
- passport_iso2=args["passport_iso2"],
59
- destination_iso2=args["destination_iso2"],
60
- ),
61
- globe_state,
62
- )
63
- if name == "get_passport_stats":
64
- return passport_stats(passport_iso2=args["passport_iso2"]), globe_state
65
  if name == "search_immigration_info":
66
  return (
67
  search_immigration(
 
9
 
10
  from apis.exa import search_immigration
11
  from apis.firecrawl import crawl_site, scrape_page
 
 
12
  from ui.globe_commands import apply_update_globe
13
 
14
  from .messages import assistant_message_dict
 
42
  ) -> tuple[Any, dict[str, Any] | None]:
43
  if name == "think":
44
  return {"ok": True}, globe_state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  if name == "search_immigration_info":
46
  return (
47
  search_immigration(