Spaces:
Sleeping
Sleeping
<!-- livebook:{"app_settings":{"access_type":"public","show_source":true,"slug":"groq-agent"}} --> | |
# 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 | |
<!-- livebook:{"break_markdown":true} --> | |
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 | |
]) | |
``` | |
<!-- livebook:{"offset":13926,"stamp":{"token":"XCP.uif9L-BcwPrb46PT8dWaX-PK1L_knFhTLHIWcWPSMch2yDch8FA8yfOYF8Se-7uQ0JK-TJ0aFK7cW-wXCB9Y_hnMp5M3nT0n1HXBTRz3HNLcOqbTUN30","version":2}} --> | |