Elixir
Elixir has no first-party OpenAI SDK. This page uses openai_ex, the
community-maintained client most Elixir projects use. It is built by piping a
client struct through configuration functions, so routing through the firewall is
one extra pipe — with_base_url/2 — plus the Vulnetix key instead of a provider
key.
Install
# mix.exs
def deps do
[{:openai_ex, "~> 0.9"}]
end
Configure
alias OpenaiEx.Chat
alias OpenaiEx.ChatMessage
org = System.fetch_env!("VULNETIX_ORG_UUID")
openai =
System.fetch_env!("VULNETIX_API_KEY") # Vulnetix key, not a provider key
|> OpenaiEx.new()
|> OpenaiEx.with_base_url("https://guardrails.vulnetix.com/openai/#{org}/v1")
chat_req =
Chat.Completions.new(
model: "gpt-4o-mini",
messages: [ChatMessage.user("Hello")]
)
{:ok, response} = Chat.Completions.create(openai, chat_req)
IO.puts(get_in(response, ["choices", Access.at(0), "message", "content"]))
Swap openai in the path for any other provider slug and pass that provider’s own
model string — the gateway forwards model verbatim.
Streaming
Unchanged. The gateway relays the provider’s SSE stream; pass stream: true and
consume the chunk stream as you already do.
{:ok, chat_stream} = Chat.Completions.create(openai, chat_req, stream: true)
chat_stream.body_stream
|> Stream.flat_map(& &1)
|> Enum.each(fn %{data: d} ->
d |> get_in(["choices", Access.at(0), "delta", "content"]) |> IO.write()
end)
Handling a guardrail block
A request your policy refuses never reaches the provider. It returns an
OpenAI-shaped 403, which openai_ex returns as
{:error, %OpenaiEx.Error{}} — the struct carries status_code, code, and the
decoded body.
case Chat.Completions.create(openai, chat_req) do
{:ok, response} ->
handle(response)
{:error, %OpenaiEx.Error{status_code: 403, code: code, body: body}} ->
rule = get_in(body, ["error", "blocked_by"])
case code do
"request_blocked" ->
{:error, "AI firewall blocked this request (rule: #{rule})"}
# provider_denied, model_denied, model_not_allowed, provider_key_missing
_ ->
{:error, "AI firewall policy refused this call: #{code}"}
end
{:error, err} ->
{:error, err}
end
code is what tells your organisation refused this apart from the provider
refused this:
code | Meaning |
|---|---|
request_blocked | A content guardrail matched. blocked_by names the rule; violations lists every rule that matched. |
provider_denied | Policy does not allow this provider. |
model_denied / model_not_allowed | Policy does not allow this model. |
provider_key_missing | No provider key in the vault for this provider. |
{:ok, …}. A pii_redact rule rewrites the
matched spans in your prompt to the literal [REDACTED] and forwards the request.
There is no error tuple to match on; the completion simply concerns redacted text.Tool / function calling
Tools are forwarded untouched. Guardrails inspect the request you send, which includes the tool-result messages you add to the message list before the next call.
Gotchas
OpenaiEx.new/1takes your Vulnetix key. A provider key here is the most common401.with_base_url/2must include the/v1suffix.Chat.Completions.create!/2raises instead of returning a tuple — if you use the bang variant, rescueOpenaiEx.Errorand read the samestatus_codeandcodefields.- This is a community library. If you use a different one, the two settings are the
same: point the base URL at
…/{orgUuid}/v1and send the Vulnetix key.