thinwrap_llm
OpenAI, Azure OpenAI, Anthropic, Google Gemini, AWS Bedrock, Mistral, +12
Thinwrap publishes three Erlang packages on Hex. The claims below hold for Erlang only — they're the developer-honest version of the umbrella "zero deps, BYO HTTP" pitch, narrowed to what's true on OTP alone. The same packages work from Elixir andGleam, which have their own pages because the setup and the code differ.
Add the packages you need to rebar.config. There is nothing to start or configure: the default transport startsinets and ssl itself if your release hasn't.
{deps, [
{thinwrap_location, "~> 1.3"},
{thinwrap_notifications, "~> 1.2"},
{thinwrap_llm, "~> 1.1"}
]}.Every operation is a function of a config map and an input map, both with atom keys and binary values. The config's providerpicks the connector; switching providers changes the config, not the input or the result.
%% 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}, % Berlin
#{lat => 52.3906, lng => 13.0645}]}, % Potsdam
case thinwrap_location:route(Config, Input) of
{ok, #{total_distance_meters := M, total_duration_seconds := S}} ->
io:format("~.1f km, ~B min~n", [M / 1000, round(S / 60)]);
{error, #{code := Code, message := Msg}} ->
io:format("~p: ~ts~n", [Code, Msg])
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 map (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 plain map with a code atom (auth_failed,rate_limited, invalid_request, …) and amessage. Misusing the API — an unknown key, a wrong type, a provider that doesn't serve the operation — raiseserror({badarg, Description}) before any request is sent. The wrapper holds no state and performs no automatic retry: the raw Retry-After header and its parsed seconds ride incause, and there is no top-level retry key — by design. Retries, token caching and connection pooling are yours; pass any module implementing request/2 astransport => {Module, Opts}.