Skip to main content
  1. Portfolios/

Integrating Sonnet 3.5 with Phoenix Liveview

·5 mins· loading · loading · ·
Ch Virinchi
Author
Ch Virinchi
Elixir programmer, rocket propulsion enthusiast. Web Application developer.
Table of Contents

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)

Creating an API key at Anthropic

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.

Set your JSON exactly as per the fields set in your schema!
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

Listing Colleges
College list inserted into database
And that’s it! Refresh the page to see the updated tables! Cool right?

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

Related

Adikailash trip
·15 mins· loading · loading
Tip: Be sure to turn on ZenMode using the button on right side of the like button for a better reading experience #Day 0 27th May Post my JEE exam on 26th, we set off from Vizag on 27th afternoon, headed to our base camp, Vizianagaram.