AI Prompt (Multi-Provider)
Full Screen DemoA single prompt endpoint that talks to OpenAI, Anthropic, Google or Perplexity by changing one string.
The trick: ReqLLM.generate_text/3 accepts a provider:model spec like openai:gpt-4o-mini or anthropic:claude-haiku-4-5-20251001. Swap providers without rewriting the call site.
The demo below is rude on purpose. Channel your inner 10 year old, then read the code.
NI
Nic
1 developer has viewed this recipe
defmodule MyApp.AI do
@moduledoc """
One function. Any provider. Change the model spec, change the brain.
MyApp.AI.chat(messages, model: "openai:gpt-4o-mini")
MyApp.AI.chat(messages, model: "anthropic:claude-haiku-4-5-20251001")
MyApp.AI.chat(messages, model: "google:gemini-2.5-flash")
"""
def chat(messages, opts \\ []) do
model = Keyword.get(opts, :model, "openai:gpt-4o-mini")
temperature = Keyword.get(opts, :temperature, 0.7)
put_provider_key(model)
case ReqLLM.generate_text(model, messages, temperature: temperature) do
{:ok, response} -> {:ok, ReqLLM.Response.text(response)}
{:error, error} -> {:error, format_error(error)}
end
end
defp put_provider_key("openai:" <> _), do: ReqLLM.put_key(:openai_api_key, System.get_env("OPENAI_API_KEY"))
defp put_provider_key("anthropic:" <> _), do: ReqLLM.put_key(:anthropic_api_key, System.get_env("ANTHROPIC_API_KEY"))
defp put_provider_key("google:" <> _), do: ReqLLM.put_key(:google_api_key, System.get_env("GOOGLE_GEMINI_API_KEY"))
defp put_provider_key("perplexity:" <> _), do: ReqLLM.put_key(:perplexity_api_key, System.get_env("PERPLEXITY_API_KEY"))
defp format_error(%{message: msg}), do: msg
defp format_error(other), do: inspect(other)
end
defmodule MyAppWeb.CartmanLive do
use MyAppWeb, :live_view
@system_prompt """
You are Eric Cartman from South Park. Answer in character.
Keep it under 100 words. No real slurs, but everything else is fair game.
"""
def mount(_params, _session, socket) do
{:ok, assign(socket, form: to_form(%{}, as: :form), response: nil, model: "openai:gpt-4o-mini")}
end
def handle_event("submit", %{"form" => %{"prompt" => prompt, "model" => model}}, socket) do
messages = [
%{role: "system", content: @system_prompt},
%{role: "user", content: prompt}
]
case MyApp.AI.chat(messages, model: model, temperature: 0.9) do
{:ok, response} ->
{:noreply, assign(socket, response: response, model: model,
form: to_form(%{"prompt" => prompt}, as: :form))}
{:error, error} ->
{:noreply, put_flash(socket, :error, error)}
end
end
end
<.container class="my-20">
<.h3>Ask Cartman (any model)</.h3>
<.form for={@form} phx-submit="submit">
<.field
field={@form[:model]}
type="select"
label="Model"
options={[
{"OpenAI GPT-4o mini", "openai:gpt-4o-mini"},
{"Anthropic Haiku 4.5", "anthropic:claude-haiku-4-5-20251001"},
{"Google Gemini 2.5 Flash", "google:gemini-2.5-flash"}
]}
/>
<.field field={@form[:prompt]} placeholder="Best life advice?" />
<div class="flex justify-end">
<.button color="success" phx-disable-with="Thinking...">Ask Cartman</.button>
</div>
</.form>
<div :if={@response} class="mt-10 p-5 bg-orange-500 text-white rounded">
<p class="text-xs opacity-75 mb-2">via {@model}</p>
{@response}
</div>
</.container>
# No code needed. ReqLLM reads keys via `ReqLLM.put_key/2` at call time.
# Set the env vars you actually plan to use:
#
# export OPENAI_API_KEY=...
# export ANTHROPIC_API_KEY=...
# export GOOGLE_GEMINI_API_KEY=...
# export PERPLEXITY_API_KEY=...
def deps do
[
{:req, "~> 0.5"},
{:req_llm, "~> 1.0"}
]
end
Why this matters
-
One call site. Any LLM. Swap
openai:gpt-4o-miniforanthropic:claude-haiku-4-5-20251001without touching the call. - A/B test models in production by toggling a config value, which is useful when a provider has an outage.
-
Wraps cleanly into a
Jido.Actionif you want tool calling. See the tool calling recipe.