File size: 14,115 Bytes
fc00f58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
<!-- 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}} -->