Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

AgentRestCountries

livebooks/agentRestCountries.livemd

AgentRestCountries

Mix.install([
  {:ollama, "~> 0.8.0"},
  {:kino, "~> 0.15.0"}
])

Initial Setup

Background

In this work book we will create an AI Agent for RestCountries.

A free and opensource project wich provides REST API’s to know more about the details of any given country.

You can access API through https://restcountries.com/v3.1/all but in order to get a faster response, you should filter the results by the fields you need. Like

https://restcountries.com/v3.1/all?fields=name,flags

What you need

  1. How to locally run Ollama setup?
  2. Livebook to run this notebook

Accessing Ollama in Livebook

Install the ollama dependencies.

Mix.install([
  {:ollama, "~> 0.8.0"}
])

Make Ollama Accessible from LAN

OLLAMA_HOST=0.0.0.0:11434 ollama serve

baseUrl="http://192.168.29.19:11434/api/"
client = Ollama.init(baseUrl)
prompt= "Why sky is blue?"
model = "gemma3"
{:ok, response} = Ollama.completion(client, [model: model, prompt: prompt])

Here is the Response from the Ollama Model.

response["response"]

REST Countries Details

name = "India"
{:ok, req_resp} = Req.get("https://restcountries.com/v3.1/name/#{name}")
Enum.map(req_resp.body, fn a -> a["name"]["common"] end)
cca3="KEN"
{:ok, cca3_resp} = Req.get("https://restcountries.com/v3.1/alpha?codes=#{cca3}")
  Enum.map(cca3_resp.body, fn b ->
      region = b["region"]
      idd = b["idd"]["root"] <> List.first(b["idd"]["suffixes"])
      name = b["name"]["common"] 
      population = b["population"]
      subregion = b["subregion"]
      coatOfArms = b["coatOfArms"]
      emoji_flag = b["flag"]
      flags = b["flags"]
      latlang = b["latlng"]
      borders = b["borders"]
      currencies = b["currencies"]
      timezones = b["timezones"]
      area = b["area"]
      tld = b["tld"]
      continents = b["continents"] 
      independent = b["independent"]
      unMember = b["unMember"]
      
      %{region: region, idd: idd, name: name, population: population,
        subregion: subregion, 
        coatOfArms: coatOfArms,
        emoji_flag: emoji_flag,
        flags: flags,
        latlang: latlang,
        borders: borders,
        currencies: currencies,
        timezones: timezones,
        area: area,
        tld: tld,
        continents: continents,
        independent: independent,
        unMember: unMember
        
      }
      end)

Put it all together as a module

defmodule AgentRestCountries.Info do

  defstruct baseUrl: String, version: String, query: String, url: String

  def default_init() do
     baseUrl = "https://restcountries.com"
     version = "v3.1" 
     query = "alpha?codes="

     init(%{baseUrl: baseUrl, version: version, query: query})
  end

  def init(
    %{ baseUrl: baseUrl ,
       version: version , 
       query: query }) do
    
      %__MODULE__{ 
        baseUrl: baseUrl,
         version: version, 
         query: query, 
         url: baseUrl <> "/" <> version <> "/" <> query
       }
   end
  

  def country(module, cca3) when is_binary(cca3) do
     Req.get(module.url <> "#{cca3}")
  end
  
  def details(body) when is_list(body) do
    Enum.map(body, fn b ->
      region = b["region"]
      idd = b["idd"]["root"] <> List.first(b["idd"]["suffixes"])
      name = b["name"]["common"] 
      population = b["population"]
      subregion = b["subregion"]
      coatOfArms = b["coatOfArms"]
      emoji_flag = b["flag"]
      flags = b["flags"]
      latlang = b["latlng"]
      borders = b["borders"]
      currencies = b["currencies"]
      timezones = b["timezones"]
      area = b["area"]
      tld = b["tld"]
      continents = b["continents"]
      independent = b["independent"]
      unMember = b["unMember"]
      
      %{region: region, idd: idd, name: name, population: population,
        subregion: subregion, 
        coatOfArms: coatOfArms,
        emoji_flag: emoji_flag,
        flags: flags,
        latlang: latlang,
        borders: borders,
        currencies: currencies,
        timezones: timezones,
        area: area,
        tld: tld,
        continents: continents,
        independent: independent,
        unMember: unMember}
      end)
  end
end
m = AgentRestCountries.Info.default_init()
AgentRestCountries.Info.country(m, "KEN")

Alpha-3 codes

ISO Alpha-3 Country Codes

S.No. Alpha-3 Country Name
1 AFG Afghanistan
2 ALB Albania
3 DZA Algeria
4 AND Andorra
5 AGO Angola
6 ATG Antigua and Barbuda
7 ARG Argentina
8 ARM Armenia

List of countries in the Elixir map format

%{
  "Name" => "Afghanistan",
  "CCA3" => "AFG"
}
defmodule AgentRestCountries.CCA3 do
  def list_countries() do
    [
      %{"Name" => "Afghanistan", "CCA3" => "AFG"},
      %{"Name" => "Albania", "CCA3" => "ALB"},
      %{"Name" => "Algeria", "CCA3" => "DZA"},
      %{"Name" => "Andorra", "CCA3" => "AND"},
      %{"Name" => "Angola", "CCA3" => "AGO"},
      %{"Name" => "Antigua and Barbuda", "CCA3" => "ATG"},
      %{"Name" => "Argentina", "CCA3" => "ARG"},
      %{"Name" => "Armenia", "CCA3" => "ARM"},
      %{"Name" => "Australia", "CCA3" => "AUS"},
      %{"Name" => "Austria", "CCA3" => "AUT"},
      %{"Name" => "Azerbaijan", "CCA3" => "AZE"},
      %{"Name" => "Bahamas", "CCA3" => "BHS"},
      %{"Name" => "Bahrain", "CCA3" => "BHR"},
      %{"Name" => "Bangladesh", "CCA3" => "BGD"},
      %{"Name" => "Barbados", "CCA3" => "BRB"},
      %{"Name" => "Belarus", "CCA3" => "BLR"},
      %{"Name" => "Belgium", "CCA3" => "BEL"},
      %{"Name" => "Belize", "CCA3" => "BLZ"},
      %{"Name" => "Benin", "CCA3" => "BEN"},
      %{"Name" => "Bhutan", "CCA3" => "BTN"},
      %{"Name" => "Bolivia", "CCA3" => "BOL"},
      %{"Name" => "Bosnia and Herzegovina", "CCA3" => "BIH"},
      %{"Name" => "Botswana", "CCA3" => "BWA"},
      %{"Name" => "Brazil", "CCA3" => "BRA"},
      %{"Name" => "Brunei Darussalam", "CCA3" => "BRN"},
      %{"Name" => "Bulgaria", "CCA3" => "BGR"},
      %{"Name" => "Burkina Faso", "CCA3" => "BFA"},
      %{"Name" => "Burundi", "CCA3" => "BDI"},
      %{"Name" => "Cabo Verde", "CCA3" => "CPV"},
      %{"Name" => "Cambodia", "CCA3" => "KHM"},
      %{"Name" => "Cameroon", "CCA3" => "CMR"},
      %{"Name" => "Canada", "CCA3" => "CAN"},
      %{"Name" => "Central African Republic", "CCA3" => "CAF"},
      %{"Name" => "Chad", "CCA3" => "TCD"},
      %{"Name" => "Chile", "CCA3" => "CHL"},
      %{"Name" => "China", "CCA3" => "CHN"},
      %{"Name" => "Colombia", "CCA3" => "COL"},
      %{"Name" => "Comoros", "CCA3" => "COM"},
      %{"Name" => "Congo", "CCA3" => "COG"},
      %{"Name" => "Congo, Democratic Republic of the", "CCA3" => "COD"},
      %{"Name" => "Costa Rica", "CCA3" => "CRI"},
      %{"Name" => "Côte d'Ivoire", "CCA3" => "CIV"},
      %{"Name" => "Croatia", "CCA3" => "HRV"},
      %{"Name" => "Cuba", "CCA3" => "CUB"},
      %{"Name" => "Cyprus", "CCA3" => "CYP"},
      %{"Name" => "Czechia", "CCA3" => "CZE"},
      %{"Name" => "Denmark", "CCA3" => "DNK"},
      %{"Name" => "Djibouti", "CCA3" => "DJI"},
      %{"Name" => "Dominica", "CCA3" => "DMA"},
      %{"Name" => "Dominican Republic", "CCA3" => "DOM"},
      %{"Name" => "Ecuador", "CCA3" => "ECU"},
      %{"Name" => "Egypt", "CCA3" => "EGY"},
      %{"Name" => "El Salvador", "CCA3" => "SLV"},
      %{"Name" => "Equatorial Guinea", "CCA3" => "GNQ"},
      %{"Name" => "Eritrea", "CCA3" => "ERI"},
      %{"Name" => "Estonia", "CCA3" => "EST"},
      %{"Name" => "Eswatini", "CCA3" => "SWZ"},
      %{"Name" => "Ethiopia", "CCA3" => "ETH"},
      %{"Name" => "Fiji", "CCA3" => "FJI"},
      %{"Name" => "Finland", "CCA3" => "FIN"},
      %{"Name" => "France", "CCA3" => "FRA"},
      %{"Name" => "Gabon", "CCA3" => "GAB"},
      %{"Name" => "Gambia", "CCA3" => "GMB"},
      %{"Name" => "Georgia", "CCA3" => "GEO"},
      %{"Name" => "Germany", "CCA3" => "DEU"},
      %{"Name" => "Ghana", "CCA3" => "GHA"},
      %{"Name" => "Greece", "CCA3" => "GRC"},
      %{"Name" => "Grenada", "CCA3" => "GRD"},
      %{"Name" => "Guatemala", "CCA3" => "GTM"},
      %{"Name" => "Guinea", "CCA3" => "GIN"},
      %{"Name" => "Guinea-Bissau", "CCA3" => "GNB"},
      %{"Name" => "Guyana", "CCA3" => "GUY"},
      %{"Name" => "Haiti", "CCA3" => "HTI"},
      %{"Name" => "Honduras", "CCA3" => "HND"},
      %{"Name" => "Hungary", "CCA3" => "HUN"},
      %{"Name" => "Iceland", "CCA3" => "ISL"},
      %{"Name" => "India", "CCA3" => "IND"},
      %{"Name" => "Indonesia", "CCA3" => "IDN"},
      %{"Name" => "Iran", "CCA3" => "IRN"},
      %{"Name" => "Iraq", "CCA3" => "IRQ"},
      %{"Name" => "Ireland", "CCA3" => "IRL"},
      %{"Name" => "Israel", "CCA3" => "ISR"},
      %{"Name" => "Italy", "CCA3" => "ITA"},
      %{"Name" => "Jamaica", "CCA3" => "JAM"},
      %{"Name" => "Japan", "CCA3" => "JPN"},
      %{"Name" => "Jordan", "CCA3" => "JOR"},
      %{"Name" => "Kazakhstan", "CCA3" => "KAZ"},
      %{"Name" => "Kenya", "CCA3" => "KEN"},
      %{"Name" => "Kiribati", "CCA3" => "KIR"},
      %{"Name" => "Korea, Democratic People's Republic of", "CCA3" => "PRK"},
      %{"Name" => "Korea, Republic of", "CCA3" => "KOR"},
      %{"Name" => "Kuwait", "CCA3" => "KWT"},
      %{"Name" => "Kyrgyzstan", "CCA3" => "KGZ"},
      %{"Name" => "Lao People's Democratic Republic", "CCA3" => "LAO"},
      %{"Name" => "Latvia", "CCA3" => "LVA"},
      %{"Name" => "Lebanon", "CCA3" => "LBN"},
      %{"Name" => "Lesotho", "CCA3" => "LSO"},
      %{"Name" => "Liberia", "CCA3" => "LBR"},
      %{"Name" => "Libya", "CCA3" => "LBY"},
      %{"Name" => "Liechtenstein", "CCA3" => "LIE"},
      %{"Name" => "Lithuania", "CCA3" => "LTU"},
      %{"Name" => "Luxembourg", "CCA3" => "LUX"},
      %{"Name" => "Madagascar", "CCA3" => "MDG"},
      %{"Name" => "Malawi", "CCA3" => "MWI"},
      %{"Name" => "Malaysia", "CCA3" => "MYS"},
      %{"Name" => "Maldives", "CCA3" => "MDV"},
      %{"Name" => "Mali", "CCA3" => "MLI"},
      %{"Name" => "Malta", "CCA3" => "MLT"},
      %{"Name" => "Marshall Islands", "CCA3" => "MHL"},
      %{"Name" => "Mauritania", "CCA3" => "MRT"},
      %{"Name" => "Mauritius", "CCA3" => "MUS"},
      %{"Name" => "Mexico", "CCA3" => "MEX"},
      %{"Name" => "Micronesia", "CCA3" => "FSM"},
      %{"Name" => "Moldova", "CCA3" => "MDA"},
      %{"Name" => "Monaco", "CCA3" => "MCO"},
      %{"Name" => "Mongolia", "CCA3" => "MNG"},
      %{"Name" => "Montenegro", "CCA3" => "MNE"},
      %{"Name" => "Morocco", "CCA3" => "MAR"},
      %{"Name" => "Mozambique", "CCA3" => "MOZ"},
      %{"Name" => "Myanmar", "CCA3" => "MMR"},
      %{"Name" => "Namibia", "CCA3" => "NAM"},
      %{"Name" => "Nauru", "CCA3" => "NRU"},
      %{"Name" => "Nepal", "CCA3" => "NPL"},
      %{"Name" => "Netherlands", "CCA3" => "NLD"},
      %{"Name" => "New Zealand", "CCA3" => "NZL"},
      %{"Name" => "Nicaragua", "CCA3" => "NIC"},
      %{"Name" => "Niger", "CCA3" => "NER"},
      %{"Name" => "Nigeria", "CCA3" => "NGA"},
      %{"Name" => "North Macedonia", "CCA3" => "MKD"},
      %{"Name" => "Norway", "CCA3" => "NOR"},
      %{"Name" => "Oman", "CCA3" => "OMN"},
      %{"Name" => "Pakistan", "CCA3" => "PAK"},
      %{"Name" => "Palau", "CCA3" => "PLW"},
      %{"Name" => "Palestine, State of", "CCA3" => "PSE"},
      %{"Name" => "Panama", "CCA3" => "PAN"},
      %{"Name" => "Papua New Guinea", "CCA3" => "PNG"},
      %{"Name" => "Paraguay", "CCA3" => "PRY"},
      %{"Name" => "Peru", "CCA3" => "PER"},
      %{"Name" => "Philippines", "CCA3" => "PHL"},
      %{"Name" => "Poland", "CCA3" => "POL"},
      %{"Name" => "Portugal", "CCA3" => "PRT"},
      %{"Name" => "Qatar", "CCA3" => "QAT"},
      %{"Name" => "Romania", "CCA3" => "ROU"},
      %{"Name" => "Russian Federation", "CCA3" => "RUS"},
      %{"Name" => "Rwanda", "CCA3" => "RWA"},
      %{"Name" => "Saint Kitts and Nevis", "CCA3" => "KNA"},
      %{"Name" => "Saint Lucia", "CCA3" => "LCA"},
      %{"Name" => "Saint Vincent and the Grenadines", "CCA3" => "VCT"},
      %{"Name" => "Samoa", "CCA3" => "WSM"},
      %{"Name" => "San Marino", "CCA3" => "SMR"},
      %{"Name" => "Sao Tome and Principe", "CCA3" => "STP"},
      %{"Name" => "Saudi Arabia", "CCA3" => "SAU"},
      %{"Name" => "Senegal", "CCA3" => "SEN"},
      %{"Name" => "Serbia", "CCA3" => "SRB"},
      %{"Name" => "Seychelles", "CCA3" => "SYC"},
      %{"Name" => "Sierra Leone", "CCA3" => "SLE"},
      %{"Name" => "Singapore", "CCA3" => "SGP"},
      %{"Name" => "Slovakia", "CCA3" => "SVK"},
      %{"Name" => "Slovenia", "CCA3" => "SVN"},
      %{"Name" => "Solomon Islands", "CCA3" => "SLB"},
      %{"Name" => "Somalia", "CCA3" => "SOM"},
      %{"Name" => "South Africa", "CCA3" => "ZAF"},
      %{"Name" => "South Sudan", "CCA3" => "SSD"},
      %{"Name" => "Spain", "CCA3" => "ESP"},
      %{"Name" => "Sri Lanka", "CCA3" => "LKA"},
      %{"Name" => "Sudan", "CCA3" => "SDN"},
      %{"Name" => "Suriname", "CCA3" => "SUR"},
      %{"Name" => "Sweden", "CCA3" => "SWE"},
      %{"Name" => "Switzerland", "CCA3" => "CHE"},
      %{"Name" => "Syrian Arab Republic", "CCA3" => "SYR"},
      %{"Name" => "Tajikistan", "CCA3" => "TJK"},
      %{"Name" => "Tanzania, United Republic of", "CCA3" => "TZA"},
      %{"Name" => "Thailand", "CCA3" => "THA"},
      %{"Name" => "Timor-Leste", "CCA3" => "TLS"},
      %{"Name" => "Togo", "CCA3" => "TGO"},
      %{"Name" => "Tonga", "CCA3" => "TON"},
      %{"Name" => "Trinidad and Tobago", "CCA3" => "TTO"},
      %{"Name" => "Tunisia", "CCA3" => "TUN"},
      %{"Name" => "Turkey", "CCA3" => "TUR"},
      %{"Name" => "Turkmenistan", "CCA3" => "TKM"},
      %{"Name" => "Tuvalu", "CCA3" => "TUV"},
      %{"Name" => "Uganda", "CCA3" => "UGA"},
      %{"Name" => "Ukraine", "CCA3" => "UKR"},
      %{"Name" => "United Arab Emirates", "CCA3" => "ARE"},
      %{"Name" => "United Kingdom", "CCA3" => "GBR"},
      %{"Name" => "United States of America", "CCA3" => "USA"},
      %{"Name" => "Uruguay", "CCA3" => "URY"},
      %{"Name" => "Uzbekistan", "CCA3" => "UZB"},
      %{"Name" => "Vanuatu", "CCA3" => "VUT"},
      %{"Name" => "Vatican City (Holy See)", "CCA3" => "VAT"},
      %{"Name" => "Venezuela", "CCA3" => "VEN"},
      %{"Name" => "Viet Nam", "CCA3" => "VNM"},
      %{"Name" => "Yemen", "CCA3" => "YEM"},
      %{"Name" => "Zambia", "CCA3" => "ZMB"},
      %{"Name" => "Zimbabwe", "CCA3" => "ZWE"}
    ]
  end

  def list_by(:cca3, items) do
    items |> Enum.map(fn a -> a["CCA3"] end)
  end

  def list_by(:name, items) do
    items |> Enum.map(fn a -> a["Name"] end)
  end

  defp error(code) do
    {:error,
     "Looks like #{code} is not a valid Alpha-3 code for country. `https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3`"}
  end

  def get_country_by(:cca3, cca3) when is_binary(cca3) do
    case filter(list_countries(), fn a -> a["CCA3"] == cca3 end) do
      [] -> error(cca3)
      [a] -> {:ok, a}
    end
  end

  def get_country_by(:name, name) when is_binary(name) do
    case filter(list_countries(), fn a -> String.upcase(a["Name"]) == String.upcase(name) end) do
      [] -> error(name)
      [a] -> {:ok, a}
    end
  end

  defp filter(items, predicate) when is_list(items) and is_function(predicate) do
    Enum.filter(items, predicate)
  end
end
l = AgentRestCountries.CCA3.list_countries()
v = AgentRestCountries.CCA3.list_by(:name, l)
vc = AgentRestCountries.CCA3.list_by(:cca3, l)
vb = AgentRestCountries.CCA3.get_country_by(:cca3, "IND")
vc = AgentRestCountries.CCA3.get_country_by(:name, "InDIa")

Building blocks for Ollama Tools

defmodule AgentRestCountries.OllamaWrapper do
  defstruct model: "gemma3",
            baseUrl: "http://localhost",
            port: 11434,
            endpoint: "/api",
            client: Ollama,
            stream: false,
            prompt: String,
            messages: [],
            composedUrl: String,
            tools: []

  def client(%__MODULE__{} = module) do
    ollama_url = module.baseUrl <> ":" <> "#{module.port}" <> module.endpoint
    IO.inspect(ollama_url, lable: "ollam_url")
    %{module | client: Ollama.init(ollama_url), composedUrl: ollama_url}
  end

  def with_tools(%__MODULE__{} = module, tool) when is_map(tool) do
    # TODO: ++ on lists are not optimal fix it.
    %{module | tools: module.tools ++ [tool]}
    end

  def with_tools(%__MODULE__{} = module, tools) when is_list(tools) do
    %{module | tools: module.tools ++ tools}
  end

  def with_tools(%__MODULE__{} = module, _tool), do: module

    def with_messages(%__MODULE__{} = module, _message), do: module

  def with_model(%__MODULE__{} = module, model) when is_binary(model) do
    %{module | model: model}
  end

  def with_model(%__MODULE__{} = module, _model), do: module

  def with_prompt(%__MODULE__{} = module, prompt) when is_binary(prompt) do
    %{module | prompt: prompt}
  end

  def with_prompt(%__MODULE__{} = module, _prompt), do: module

  def with_port(%__MODULE__{} = module, port) when is_integer(port) do
    %{module | port: port}
  end

  def with_port(%__MODULE__{} = module, _port), do: module

  def with_base_url(%__MODULE__{} = module, baseUrl) when is_binary(baseUrl) do
    %{module | baseUrl: baseUrl}
  end

  def with_base_url(%__MODULE__{} = module, _baseUrl), do: module

  def completion(%__MODULE__{} = module) do
    Ollama.completion(module.client,
      model: module.model,
      prompt: module.prompt,
      stream: module.stream
    )
  end

end

Create the Ollama client via OllamaWrapper.

ow = AgentRestCountries.OllamaWrapper.client(%AgentRestCountries.OllamaWrapper{})
ow.composedUrl
alias AgentRestCountries.OllamaWrapper


ollama_client_with_port_11444 = %AgentRestCountries.OllamaWrapper{}
|> AgentRestCountries.OllamaWrapper.with_base_url("http://192.168.29.140")
|> AgentRestCountries.OllamaWrapper.with_port(11444)