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