# Elixir groq ```elixir Mix.install([ {:groq, "~> 0.1"}, {:hackney, "~> 1.18"}, {:jason, "~> 1.4"}, {:phoenix_live_view, "~> 0.18.3"}, {:kino, "~> 0.9.0"} ]) # Check if Jason is available json_library = if Code.ensure_loaded?(Jason), do: Jason, else: :error # Configure Groq to use the available JSON library Application.put_env(:groq, :json_library, json_library) System.put_env("GROQ_API_KEY", System.fetch_env!("LB_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 ``` ## Using groq and tool call in liveview We are gonna use the client from https://github.com/connorjacobsen/groq-elixir/tree/main Basically is Groq's endpoint wrapper ```elixir # Ensure all dependencies are started Application.ensure_all_started(:hackney) Application.ensure_all_started(:groq) ``` ```elixir response = Groq.ChatCompletion.create(%{ "model" => "mixtral-8x7b-32768", "messages" => [ %{ "role" => "user", "content" => "Explain the importance of fast language models" } ] }) case response do {:ok, result} -> IO.puts("Response content:") case result do %{"choices" => [%{"message" => %{"content" => content}} | _]} -> IO.puts(content) _ -> IO.puts("Unexpected response structure: #{inspect(result)}") end {:error, error} -> IO.puts("Error: #{inspect(error)}") end ``` ### Create a custom function to use as a tool ```elixir # Define a simple function to get the current time defmodule TimeFunctions do def get_current_time do {{year, month, day}, {hour, minute, second}} = :calendar.local_time() "Current time: #{year}-#{month}-#{day} #{hour}:#{minute}:#{second}" end end # Define the function's properties function_properties = [ %{ "name" => "get_current_time", "description" => "Get the current time", "parameters" => %{ "type" => "object", "properties" => %{}, "required" => [] } } ] ``` ### Let's call the function if the user need it ```elixir # Create a chat completion request with function calling response = Groq.ChatCompletion.create(%{ "model" => "mixtral-8x7b-32768", "messages" => [ %{ "role" => "user", "content" => "What time is it?" } ], "tools" => [ %{ "type" => "function", "function" => Enum.at(function_properties, 0) } ], "tool_choice" => "auto" }) # Handle the response case response do {:ok, result} -> case result do %{"choices" => [%{"message" => message} | _]} -> case message do %{"tool_calls" => [%{"function" => %{"name" => "get_current_time"}} | _]} -> time = TimeFunctions.get_current_time() IO.puts("Assistant requested the current time. #{time}") _ -> IO.puts("Assistant response: #{message["content"]}") end _ -> IO.puts("Unexpected response structure: #{inspect(result)}") end {:error, error} -> IO.puts("Error: #{inspect(error)}") end ``` ### Let's build a Math Agent. Since the models can operate mathematical operations we are going to create an Ange to understand user query and if there any mathematical expresion we are going to use a 'calculate' function to process it. ```elixir 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" => "You are a calculator assistant. Use the calculate function to perform mathematical operations and provide the results. If the answer is not about Math, you will respond: Eee locoo, aguante Cristinaaaa 🇦🇷!" }, %{ "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 ``` ### Let's try with user input Trye ```elixir user_input = "What is 25 * 4 + 10?" IO.puts("Running conversation with input: #{user_input}\n") case MathAgent.run_conversation(user_input) do {:ok, response} -> IO.puts("\nFinal Response: #{response}") {:error, error} -> IO.puts("\nError: #{error}") end ``` #### If we query some question that is not related about math we are going to receive according of what we have in our system prompt on how the model have to behave ```elixir # Example of a query that might not use the tool 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 ``` ### Use Case: Retrieve information from "database" using natural lenguage as a query Let's create a fake 50.000 user database ```elixir fake_database = Enum.map(1..50_000, fn _ -> user_id = :crypto.strong_rand_bytes(16) |> Base.encode16(case: :lower) minutes = :rand.uniform(1000) {user_id, minutes} end) IO.puts("Sample of fake data (first 10 entries):") fake_database |> Enum.take(10) |> IO.inspect() defmodule DataStore do def get_fake_database do unquote(Macro.escape(fake_database)) end end IO.puts("Fake database generated and stored in DataStore module.") ``` ###### Le'ts create a GroqChat module and create a function as a tool with the properties and call the client. ###### We are going to use Kino to create a funny interface :) ```elixir defmodule GroqChat do # Use the fake database from the DataStore module @fake_database DataStore.get_fake_database() def get_top_users(n) do @fake_database |> Enum.sort_by(fn {_, minutes} -> minutes end, :desc) |> Enum.take(n) |> Enum.map(fn {user_id, minutes} -> %{user_id: user_id, minutes: minutes} end) |> Jason.encode!() end def function_properties do [ %{ "type" => "function", "function" => %{ "name" => "get_top_users", "description" => "Get the top N users with the most system usage time", "parameters" => %{ "type" => "object", "properties" => %{ "n" => %{ "type" => "integer", "description" => "Number of top users to retrieve" } }, "required" => ["n"] } } } ] 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 }, recv_timeout: 30_000 # Increase timeout to 30 seconds ) 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 "get_top_users" -> args = Jason.decode!(arguments) IO.puts("Calling get_top_users function with n: #{args["n"]}") result = get_top_users(args["n"]) IO.puts("Get top users 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" => "You are an assistant capable of retrieving information about top system users. Use the get_top_users function to retrieve information about users in database with the most system usage time. If question is not about user or mintues info respond: Eyy guachoo, esto solo para database" }, %{ "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 # Create an input form with a submit option form = Kino.Control.form( [ query: Kino.Input.text("Enter your query. Example: list the 5 user with most used the system?") ], submit: "Send" ) # Create a frame to display the response frame = Kino.Frame.new() # Set up the event stream for form submission Kino.Control.stream(form) |> Kino.listen(fn %{data: %{query: query}} -> Kino.Frame.render(frame, Kino.Markdown.new("**Loading...**")) # Make the API call case GroqChat.run_conversation(query) do {:ok, response} -> Kino.Frame.render(frame, Kino.Markdown.new("**Response:**\n\n#{response}")) {:error, error} -> Kino.Frame.render(frame, Kino.Markdown.new("**Error:**\n\n#{error}")) end end) # Render the UI elements Kino.Layout.grid([ form, frame ]) ```