Powered by AppSignal & Oban Pro

Groq instructor

livebook/groq-instructor.livemd

Groq instructor

Mix.install([
  {:groq, "~> 0.1"},
  {:hackney, "~> 1.18"},
  {:jason, "~> 1.4"},
  {:phoenix_live_view, "~> 0.18.3"},
  {:kino, "~> 0.9.0"}
])

Section

Mix.install([
  {:groq, "~> 0.1"},
  {:hackney, "~> 1.18"},
  {:jason, "~> 1.4"},
  {:phoenix_live_view, "~> 0.18.3"},
  {:kino, "~> 0.9.0"}
])
json_library = if Code.ensure_loaded?(Jason), do: Jason, else: :error

Application.put_env(:groq, :json_library, json_library)

System.fetch_env!("GROQ_API_KEY")

# Start the Groq application
case Application.ensure_all_started(:groq) do
  {:ok, _} -> IO.puts("Groq application started successfully")
  {:error, error} -> IO.puts("Failed to start Groq application: #{inspect(error)}")
end
Application.ensure_all_started(:hackney)
Application.ensure_all_started(:groq)
defmodule MathAgent do
  def calculate(expression) do
    try do
      result = Code.eval_string(expression) |> elem(0)
      Jason.encode!(%{result: result})
    rescue
      _ -> Jason.encode!(%{error: "Invalid expression"})
    end
  end

  def function_properties do
    [
      %{
        "type" => "function",
        "function" => %{
          "name" => "calculate",
          "description" => "Evaluate a mathematical expression",
          "parameters" => %{
            "type" => "object",
            "properties" => %{
              "expression" => %{
                "type" => "string",
                "description" => "The mathematical expression to evaluate"
              }
            },
            "required" => ["expression"]
          }
        }
      }
    ]
  end

  def create_chat_completion(messages) do
    Groq.ChatCompletion.create(%{
      "model" => "llama3-groq-70b-8192-tool-use-preview",
      "messages" => messages,
      "tools" => function_properties(),
      "tool_choice" => "auto",
      "max_tokens" => 4096
    })
  end

  def handle_response({:ok, result}) do
    case result do
      %{"choices" => choices} when is_list(choices) and length(choices) > 0 ->
        first_choice = Enum.at(choices, 0)
        handle_message(first_choice["message"])

      _ ->
        {:error, "Unexpected response structure: #{inspect(result)}"}
    end
  end

  def handle_response({:error, error}) do
    {:error, "Error: #{inspect(error)}"}
  end

  defp handle_message(%{"tool_calls" => [tool_call | _]} = message) do
    IO.puts("\nModel is using a tool:")
    IO.inspect(message, label: "Full message")

    %{"function" => %{"name" => function_name, "arguments" => arguments}} = tool_call

    case function_name do
      "calculate" ->
        args = Jason.decode!(arguments)
        IO.puts("Calling calculate function with expression: #{args["expression"]}")
        result = calculate(args["expression"])
        IO.puts("Calculate function result: #{result}")
        {:tool_call, tool_call["id"], function_name, result}

      _ ->
        {:error, "Unknown function: #{function_name}"}
    end
  end

  defp handle_message(message) do
    IO.puts("\nModel is not using a tool:")
    IO.inspect(message, label: "Full message")
    {:ok, message["content"]}
  end

  def run_conversation(user_prompt) do
    IO.puts("Starting conversation with user prompt: #{user_prompt}")

    initial_messages = [
      %{
        "role" => "system",
        "content" => "Another fake answer!"
      },
      %{
        "role" => "user",
        "content" => user_prompt
      }
    ]

    case create_chat_completion(initial_messages) do
      {:ok, result} ->
        IO.puts("\nReceived initial response from Groq API")

        case handle_response({:ok, result}) do
          {:tool_call, id, name, content} ->
            IO.puts("\nProcessing tool call result")

            tool_message = %{
              "tool_call_id" => id,
              "role" => "tool",
              "name" => name,
              "content" => content
            }

            first_choice = Enum.at(result["choices"], 0)
            new_messages = initial_messages ++ [first_choice["message"], tool_message]
            IO.puts("\nSending follow-up request to Groq API")

            case create_chat_completion(new_messages) do
              {:ok, final_result} ->
                IO.puts("\nReceived final response from Groq API")
                handle_response({:ok, final_result})

              error ->
                error
            end

          other ->
            other
        end

      error ->
        error
    end
  end
end
user_input_no_tool = "What's the capital of France?"

IO.puts(
  "\n\nRunning conversation with input that might not use the tool: #{user_input_no_tool}\n"
)

case MathAgent.run_conversation(user_input_no_tool) do
  {:ok, response} -> IO.puts("\nFinal Response: #{response}")
  {:error, error} -> IO.puts("\nError: #{error}")
end