In the rapidly evolving backdrop of web development, LLMs have emerged as game-changers, offering unprecedented possibilities for creating dynamic and cutting-edge applications. The possibilities are endless. Anthropic & OpenAI dont offer Client Side SDKs for Elixir. This leaves Phoenix developers at a disadvantage of not being able to use LLMs in their applications. In reality, having a different SDK for each language even complicates things. With different documentation & environments, developers will be confused to choosing an API
Enter the REST APIs. We can do a get/post request to them from any language, and the response will be a JSON
. JSON
being the cousin of all languages, we can utilise that seamlessly.
In this example, we’ll be using req to manage our requests. We’ll demonstrate this process within a Phoenix LiveView context, showcasing how effortlessly you can fetch large amounts of data and populate your application’s database with AI-generated content. Let’s get started!
Setting Up
Set up the boilerplate Phoenix application. To create the table & changeset, let’s use a generator and say
mix phx.gen.live Colleges College colleges name established_year:integer location
Run the migrations. Now you should have a running boilerplate application.
Create a new file called claude.ex
in lib/yourApp
. Now would be a good time to head over to mix.exs
and add req
as a dependency. Simply, include the following line & fetch your deps.
{:req, "~> 0.5.0"}
Also, head over to Anthropic Console
and create your API key. (They offer 20,000 tokens per minute free)
Code Outline
We want to structure our code with a singular function that takes a prompt, runs it through claude & returns the result. So that function can be called with different prompts to build different tables.
defmodule AnthropicApi do
def call_claude(prompt) do
# Call Anthropic API with Req & return the result
end
end
Unfortunately, there is no Anthropic interface for Phoenix/Elixir. Thankfully, they have a REST API which can be called with a URL. This gives us the perfect excuse to whip up Req which integrates seamlessly with Phoenix
Writing the call_claude function
Let’s declare the base URL up top
url = "https://api.anthropic.com/v1/messages"
According to their docs, Anthropic expects a bunch of headers to be passed every time we want to make an API call. This is where we’ll define the API key we created earlier
headers = [
{"x-api-key",
System.fetch_env!("ANTHROPIC_API_KEY")},
{"anthropic-version", "2023-06-01"},
{"content-type", "application/json"}]
Now comes the most important part, our prompt. This is to be defined under body
. You can use the prompt passed down from the call_claude() function. For the sake of this example, I’m using hardcoded prompts.
body = %{
model: "claude-3-5-sonnet-20240620",
max_tokens: 500,
tools: [
%{ name: "Colleges",
description: "Return the College's details in a well-structured JSON.",
input_schema: %{
type: "object",
properties: %{
name: %{type: "string", description: "Name of the college"},
established_year: %{type: "integer", description: "Year that the college was established in"},
location: %{type: "string", description: "Location of the college. Only city & state will do"}},
required: ["name", "established", "location"]}}],
You can change the max_tokens parameter to change the length of Claude’s reply. Remember that it sets the max-ceiling and doesn’t dictate how many tokens Claude actually uses. More details about tokens can be found in docs. Be careful not to use up all your 300k tokens in one go!
A general prompt-reply will generate a few paragraphs which isn’t of much use to us. Enter tools. You can force Claude to use a specific tool. In our case, we defined a JSON and declared that we wanted our reply to be in that particular JSON format.
tool_choice: %{type: "tool", name: "Colleges"},
messages: [
%{role: "user",
content: [%{
type: "text",
text: "Give me details of the top 5 Colleges participating in JOSAA counselling"}]}]
You can add more tools & change this setting under tool_choice. Do refer to the tool_choice docs here.
Finally, we can make the request using Req.
case Req.post(url, headers: headers, json: body) do
{:ok, response} -> response
{:error, error} ->
IO.puts("Error: #{inspect(error)}")
end
Req gives {:ok response}
or {:error, error}
tuples which have been handled accordingly. So now, calling this function will just return the ‘response’ if all is well. And that’s it for our call_claude function!
Inserting into the table
Head over to index.html.heex & include a button
<button phx-click="create-college">Create-College</button>
We’ll insert data into the database every time this button’s clicked.
Let’s handle the event in the corresponding live view
def handle_event("create-college", _, socket) do
for x <- AnthropicApi.call_claude().body["content"] do
case Colleges.create_college(x["input"]) do
{:ok, college} ->
IO.puts("College created: #{college.name}")
{:error, changeset} ->
IO.puts("Error creating college: #{inspect(changeset.errors)}")
end
end
{:noreply, socket}
end
Conclusion
Since Anthropic offers 300k free tokens per day, anyone can whip this up in a few minutes & populate their database with a large number of fields. Paired with Phoenix’s Stream & Pubsub features, this becomes a really powerful tool in your arsenal. Do let me know of other use cases you could come up with this!
Final Code-
# /lib/yourApp/claude.ex
defmodule AnthropicApi do
def call_claude do
url = "https://api.anthropic.com/v1/messages"
headers = [
{"x-api-key",
"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"},
{"anthropic-version", "2023-06-01"},
{"content-type", "application/json"}]
body = %{
model: "claude-3-5-sonnet-20240620",
max_tokens: 2048,
tools: [
%{
name: "Colleges",
description: "Return the College's details in a well-structured JSON.",
input_schema: %{
type: "object",
properties: %{
name: %{
type: "string",
description: "Name of the college"},
established_year: %{
type: "integer",
description: "Year that the college was established in"
},
location: %{
type: "string",
description: "Location of the college. Only city & state will do"
}
},
required: ["name", "established", "location"]
}
}
],
tool_choice: %{type: "tool", name: "Colleges"},
messages: [
%{
role: "user",
content: [
%{
type: "text",
text: "Give me details of the top 5 Colleges participating in JOSAA counselling"
}
]
}
]
}
case Req.post(url, headers: headers, json: body) do
{:ok, response} ->
response
{:error, error} ->
IO.puts("Error: #{inspect(error)}")
end
end
end
# /lib/yourApp_web/live/college_live/claude.ex
def handle_event("create-college", _, socket) do
for x <- AnthropicApi.call_claude().body["content"] do
case Colleges.create_college(x["input"]) do
{:ok, college} ->
IO.puts("College created: #{college.name}")
{:error, changeset} ->
IO.puts("Error creating college: #{inspect(changeset.errors)}")
end
end
{:noreply, socket}
end