thinwrap_llm
OpenAI, Azure OpenAI, Anthropic, Google Gemini, AWS Bedrock, Mistral, +12
Thinwrap's three Hex packages are written in Erlang, so an Elixir project depends on them like any other Hex package and calls the Erlang modules directly. There is no Elixir wrapper and there are no structs: config, input and results are plain maps with atom keys and string values. The claims below hold for Elixir only.
Add the packages you need to mix.exs. Noextra_applications entry is needed: the packages declareinets and ssl themselves.
defp deps do
[
{:thinwrap_location, "~> 1.3"},
{:thinwrap_notifications, "~> 1.2"},
{:thinwrap_llm, "~> 1.1"}
]
endEvery operation is a function of a config map and an input map. The config's provider picks the connector; switching providers changes the config, not the input or the result. The result is {:ok, map} or {:error, map}, so it pattern-matches like any Elixir API.
# OSRM's public demo server needs no key, so this runs as-is.
config = %{provider: :osrm, base_url: "https://router.project-osrm.org"}
input = %{
waypoints: [
%{lat: 52.5200, lng: 13.4050},
%{lat: 52.3906, lng: 13.0645}
]
}
case :thinwrap_location.route(config, input) do
{:ok, %{total_distance_meters: m, total_duration_seconds: s}} ->
IO.puts("#{Float.round(m / 1000, 1)} km, #{round(s / 60)} min")
{:error, %{code: code, message: message}} ->
IO.puts("#{code}: #{message}")
end
# => 35.1 km, 39 minOpenAI, Azure OpenAI, Anthropic, Google Gemini, AWS Bedrock, Mistral, +12
Google Maps Platform, Mapbox, HERE, ESRI ArcGIS, TomTom, OSRM (self-hosted)
SendGrid, AWS SES, Mailgun, Postmark, Resend, Brevo (Sendinblue), +29
The unified surface includes only features that ≥90% of providers natively support. Sub-baseline fields stay accessible via thepassthrough key (body deep-merged into the request body; your values win), and the vendor's answer rides inraw. Cross-language parity with the other packages is a design goal — not a hard release gate.
An operation failure returns {:error, error}, a map with a :code atom (:auth_failed,:rate_limited, :invalid_request, …) and a:message. Misusing the API — an unknown key, a wrongly typed value, a provider that doesn't serve the operation — raises ArgumentError before any request is sent (catch :error, reason sees the original{:badarg, description}). The wrapper holds no state and performs no automatic retry: the raw Retry-After header and its parsed seconds ride in :cause, and there isno top-level retry key — by design. To bring your own HTTP client, write a module with @behaviour :thinwrap_location_transport and a request/2, and pass it as transport: {MyTransport, opts}.