SDK quickstart: Elixir

The community openai_ex library targets the OpenAI shape and accepts a base URL override. Phoenix LiveView pairs particularly well with streaming responses.

Add to deps

# mix.exs
defp deps do
  [
    {:openai_ex, "~> 0.10"},
    {:jason, "~> 1.4"},
  ]
end

Hello world

config = OpenaiEx.new(
  System.fetch_env!("JC_API_KEY"),
  base_url: "https://api.greenjoules.cloud/v1"
)

response =
  config
  |> OpenaiEx.Chat.Completions.create!(%{
    model: "auto",
    messages: [%{role: "user", content: "hi"}]
  })

IO.puts(response["choices"] |> hd |> get_in(["message", "content"]))

Streaming + Phoenix LiveView

defmodule MyAppWeb.ChatLive do
  use MyAppWeb, :live_view

  def handle_event("ask", %{"q" => q}, socket) do
    parent = self()
    Task.start(fn ->
      config = OpenaiEx.new(System.get_env("JC_API_KEY"),
                             base_url: "https://api.greenjoules.cloud/v1")
      config
      |> OpenaiEx.Chat.Completions.create_stream!(%{
        model: "auto",
        messages: [%{role: "user", content: q}],
      })
      |> Enum.each(fn chunk ->
        text = get_in(chunk, ["choices", Access.at(0), "delta", "content"])
        if text, do: send(parent, {:token, text})
      end)
      send(parent, :stream_done)
    end)
    {:noreply, assign(socket, response: "")}
  end

  def handle_info({:token, t}, socket),
    do: {:noreply, update(socket, :response, &(&1 <> t))}
  def handle_info(:stream_done, socket), do: {:noreply, socket}
end

GenServer pool

For high-throughput callers, wrap calls in a GenServer pool (poolboy or NimblePool) so concurrent BEAM processes share a small set of HTTP connections. Keep :hackney pool size aligned with your concurrency budget.

Joule headers

OpenaiEx exposes the raw Req response via its lower-level functions:

{:ok, resp} = OpenaiEx.Chat.Completions.create_raw(config, params)
{_status, headers, _body} = resp
joules = List.keyfind(headers, "x-energy-joules", 0)

Errors

case OpenaiEx.Chat.Completions.create(config, params) do
  {:ok, body} -> body
  {:error, %{status: 402}} -> :insufficient_balance
  {:error, %{status: 429}} -> :energy_budget_hit
  {:error, e} -> raise e
end