joselobenitezg commited on
Commit
fc00f58
1 Parent(s): 0a362dd

Upload groq.livemd

Browse files
Files changed (1) hide show
  1. public-apps/groq.livemd +480 -0
public-apps/groq.livemd ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- livebook:{"app_settings":{"access_type":"public","show_source":true,"slug":"groq-agent"}} -->
2
+
3
+ # Elixir groq
4
+
5
+ ```elixir
6
+ Mix.install([
7
+ {:groq, "~> 0.1"},
8
+ {:hackney, "~> 1.18"},
9
+ {:jason, "~> 1.4"},
10
+ {:phoenix_live_view, "~> 0.18.3"},
11
+ {:kino, "~> 0.9.0"}
12
+ ])
13
+
14
+ # Check if Jason is available
15
+ json_library = if Code.ensure_loaded?(Jason), do: Jason, else: :error
16
+
17
+ # Configure Groq to use the available JSON library
18
+ Application.put_env(:groq, :json_library, json_library)
19
+
20
+ System.put_env("GROQ_API_KEY", System.fetch_env!("LB_GROQ_API_KEY"))
21
+
22
+ # Start the Groq application
23
+ case Application.ensure_all_started(:groq) do
24
+ {:ok, _} -> IO.puts("Groq application started successfully")
25
+ {:error, error} -> IO.puts("Failed to start Groq application: #{inspect(error)}")
26
+ end
27
+ ```
28
+
29
+ ## Using groq and tool call in liveview
30
+
31
+ We are gonna use the client from https://github.com/connorjacobsen/groq-elixir/tree/main
32
+
33
+ Basically is Groq's endpoint wrapper
34
+
35
+ ```elixir
36
+ # Ensure all dependencies are started
37
+ Application.ensure_all_started(:hackney)
38
+ Application.ensure_all_started(:groq)
39
+ ```
40
+
41
+ ```elixir
42
+ response = Groq.ChatCompletion.create(%{
43
+ "model" => "mixtral-8x7b-32768",
44
+ "messages" => [
45
+ %{
46
+ "role" => "user",
47
+ "content" => "Explain the importance of fast language models"
48
+ }
49
+ ]
50
+ })
51
+
52
+ case response do
53
+ {:ok, result} ->
54
+ IO.puts("Response content:")
55
+ case result do
56
+ %{"choices" => [%{"message" => %{"content" => content}} | _]} ->
57
+ IO.puts(content)
58
+ _ ->
59
+ IO.puts("Unexpected response structure: #{inspect(result)}")
60
+ end
61
+ {:error, error} ->
62
+ IO.puts("Error: #{inspect(error)}")
63
+ end
64
+ ```
65
+
66
+ ### Create a custom function to use as a tool
67
+
68
+ ```elixir
69
+ # Define a simple function to get the current time
70
+ defmodule TimeFunctions do
71
+ def get_current_time do
72
+ {{year, month, day}, {hour, minute, second}} = :calendar.local_time()
73
+ "Current time: #{year}-#{month}-#{day} #{hour}:#{minute}:#{second}"
74
+ end
75
+ end
76
+
77
+ # Define the function's properties
78
+ function_properties = [
79
+ %{
80
+ "name" => "get_current_time",
81
+ "description" => "Get the current time",
82
+ "parameters" => %{
83
+ "type" => "object",
84
+ "properties" => %{},
85
+ "required" => []
86
+ }
87
+ }
88
+ ]
89
+ ```
90
+
91
+ ### Let's call the function if the user need it
92
+
93
+ ```elixir
94
+ # Create a chat completion request with function calling
95
+ response = Groq.ChatCompletion.create(%{
96
+ "model" => "mixtral-8x7b-32768",
97
+ "messages" => [
98
+ %{
99
+ "role" => "user",
100
+ "content" => "What time is it?"
101
+ }
102
+ ],
103
+ "tools" => [
104
+ %{
105
+ "type" => "function",
106
+ "function" => Enum.at(function_properties, 0)
107
+ }
108
+ ],
109
+ "tool_choice" => "auto"
110
+ })
111
+
112
+ # Handle the response
113
+ case response do
114
+ {:ok, result} ->
115
+ case result do
116
+ %{"choices" => [%{"message" => message} | _]} ->
117
+ case message do
118
+ %{"tool_calls" => [%{"function" => %{"name" => "get_current_time"}} | _]} ->
119
+ time = TimeFunctions.get_current_time()
120
+ IO.puts("Assistant requested the current time. #{time}")
121
+ _ ->
122
+ IO.puts("Assistant response: #{message["content"]}")
123
+ end
124
+ _ ->
125
+ IO.puts("Unexpected response structure: #{inspect(result)}")
126
+ end
127
+ {:error, error} ->
128
+ IO.puts("Error: #{inspect(error)}")
129
+ end
130
+
131
+
132
+ ```
133
+
134
+ ### Let's build a Math Agent.
135
+
136
+ 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.
137
+
138
+ ```elixir
139
+ defmodule MathAgent do
140
+ def calculate(expression) do
141
+ try do
142
+ result = Code.eval_string(expression) |> elem(0)
143
+ Jason.encode!(%{result: result})
144
+ rescue
145
+ _ -> Jason.encode!(%{error: "Invalid expression"})
146
+ end
147
+ end
148
+
149
+ def function_properties do
150
+ [
151
+ %{
152
+ "type" => "function",
153
+ "function" => %{
154
+ "name" => "calculate",
155
+ "description" => "Evaluate a mathematical expression",
156
+ "parameters" => %{
157
+ "type" => "object",
158
+ "properties" => %{
159
+ "expression" => %{
160
+ "type" => "string",
161
+ "description" => "The mathematical expression to evaluate"
162
+ }
163
+ },
164
+ "required" => ["expression"]
165
+ }
166
+ }
167
+ }
168
+ ]
169
+ end
170
+
171
+ def create_chat_completion(messages) do
172
+ Groq.ChatCompletion.create(%{
173
+ "model" => "llama3-groq-70b-8192-tool-use-preview",
174
+ "messages" => messages,
175
+ "tools" => function_properties(),
176
+ "tool_choice" => "auto",
177
+ "max_tokens" => 4096
178
+ })
179
+ end
180
+
181
+ def handle_response({:ok, result}) do
182
+ case result do
183
+ %{"choices" => choices} when is_list(choices) and length(choices) > 0 ->
184
+ first_choice = Enum.at(choices, 0)
185
+ handle_message(first_choice["message"])
186
+ _ ->
187
+ {:error, "Unexpected response structure: #{inspect(result)}"}
188
+ end
189
+ end
190
+
191
+ def handle_response({:error, error}) do
192
+ {:error, "Error: #{inspect(error)}"}
193
+ end
194
+
195
+ defp handle_message(%{"tool_calls" => [tool_call | _]} = message) do
196
+ IO.puts("\nModel is using a tool:")
197
+ IO.inspect(message, label: "Full message")
198
+
199
+ %{"function" => %{"name" => function_name, "arguments" => arguments}} = tool_call
200
+ case function_name do
201
+ "calculate" ->
202
+ args = Jason.decode!(arguments)
203
+ IO.puts("Calling calculate function with expression: #{args["expression"]}")
204
+ result = calculate(args["expression"])
205
+ IO.puts("Calculate function result: #{result}")
206
+ {:tool_call, tool_call["id"], function_name, result}
207
+ _ ->
208
+ {:error, "Unknown function: #{function_name}"}
209
+ end
210
+ end
211
+
212
+ defp handle_message(message) do
213
+ IO.puts("\nModel is not using a tool:")
214
+ IO.inspect(message, label: "Full message")
215
+ {:ok, message["content"]}
216
+ end
217
+
218
+ def run_conversation(user_prompt) do
219
+ IO.puts("Starting conversation with user prompt: #{user_prompt}")
220
+
221
+ initial_messages = [
222
+ %{
223
+ "role" => "system",
224
+ "content" => "You are a calculator assistant.
225
+ Use the calculate function to perform mathematical operations and provide the results.
226
+ If the answer is not about Math, you will respond: Eee locoo, aguante Cristinaaaa 🇦🇷!"
227
+ },
228
+ %{
229
+ "role" => "user",
230
+ "content" => user_prompt
231
+ }
232
+ ]
233
+
234
+ case create_chat_completion(initial_messages) do
235
+ {:ok, result} ->
236
+ IO.puts("\nReceived initial response from Groq API")
237
+ case handle_response({:ok, result}) do
238
+ {:tool_call, id, name, content} ->
239
+ IO.puts("\nProcessing tool call result")
240
+ tool_message = %{
241
+ "tool_call_id" => id,
242
+ "role" => "tool",
243
+ "name" => name,
244
+ "content" => content
245
+ }
246
+ first_choice = Enum.at(result["choices"], 0)
247
+ new_messages = initial_messages ++ [first_choice["message"], tool_message]
248
+ IO.puts("\nSending follow-up request to Groq API")
249
+ case create_chat_completion(new_messages) do
250
+ {:ok, final_result} ->
251
+ IO.puts("\nReceived final response from Groq API")
252
+ handle_response({:ok, final_result})
253
+ error -> error
254
+ end
255
+ other -> other
256
+ end
257
+ error -> error
258
+ end
259
+ end
260
+ end
261
+ ```
262
+
263
+ ### Let's try with user input
264
+
265
+ Trye
266
+
267
+ ```elixir
268
+ user_input = "What is 25 * 4 + 10?"
269
+
270
+ IO.puts("Running conversation with input: #{user_input}\n")
271
+ case MathAgent.run_conversation(user_input) do
272
+ {:ok, response} -> IO.puts("\nFinal Response: #{response}")
273
+ {:error, error} -> IO.puts("\nError: #{error}")
274
+ end
275
+ ```
276
+
277
+ #### 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
278
+
279
+ ```elixir
280
+ # Example of a query that might not use the tool
281
+ user_input_no_tool = "What's the capital of France?"
282
+
283
+ IO.puts("\n\nRunning conversation with input that might not use the tool: #{user_input_no_tool}\n")
284
+ case MathAgent.run_conversation(user_input_no_tool) do
285
+ {:ok, response} -> IO.puts("\nFinal Response: #{response}")
286
+ {:error, error} -> IO.puts("\nError: #{error}")
287
+ end
288
+ ```
289
+
290
+ ### Use Case: Retrieve information from "database" using natural lenguage as a query
291
+
292
+ <!-- livebook:{"break_markdown":true} -->
293
+
294
+ Let's create a fake 50.000 user database
295
+
296
+ ```elixir
297
+ fake_database = Enum.map(1..50_000, fn _ ->
298
+ user_id = :crypto.strong_rand_bytes(16) |> Base.encode16(case: :lower)
299
+ minutes = :rand.uniform(1000)
300
+ {user_id, minutes}
301
+ end)
302
+
303
+ IO.puts("Sample of fake data (first 10 entries):")
304
+ fake_database |> Enum.take(10) |> IO.inspect()
305
+
306
+ defmodule DataStore do
307
+ def get_fake_database do
308
+ unquote(Macro.escape(fake_database))
309
+ end
310
+ end
311
+
312
+ IO.puts("Fake database generated and stored in DataStore module.")
313
+ ```
314
+
315
+ ###### Le'ts create a GroqChat module and create a function as a tool with the properties and call the client.
316
+
317
+ ###### We are going to use Kino to create a funny interface :)
318
+
319
+ ```elixir
320
+ defmodule GroqChat do
321
+ # Use the fake database from the DataStore module
322
+ @fake_database DataStore.get_fake_database()
323
+
324
+ def get_top_users(n) do
325
+ @fake_database
326
+ |> Enum.sort_by(fn {_, minutes} -> minutes end, :desc)
327
+ |> Enum.take(n)
328
+ |> Enum.map(fn {user_id, minutes} -> %{user_id: user_id, minutes: minutes} end)
329
+ |> Jason.encode!()
330
+ end
331
+
332
+ def function_properties do
333
+ [
334
+ %{
335
+ "type" => "function",
336
+ "function" => %{
337
+ "name" => "get_top_users",
338
+ "description" => "Get the top N users with the most system usage time",
339
+ "parameters" => %{
340
+ "type" => "object",
341
+ "properties" => %{
342
+ "n" => %{
343
+ "type" => "integer",
344
+ "description" => "Number of top users to retrieve"
345
+ }
346
+ },
347
+ "required" => ["n"]
348
+ }
349
+ }
350
+ }
351
+ ]
352
+ end
353
+
354
+ def create_chat_completion(messages) do
355
+ Groq.ChatCompletion.create(
356
+ %{
357
+ "model" => "llama3-groq-70b-8192-tool-use-preview",
358
+ "messages" => messages,
359
+ "tools" => function_properties(),
360
+ "tool_choice" => "auto",
361
+ "max_tokens" => 4096
362
+ },
363
+ recv_timeout: 30_000 # Increase timeout to 30 seconds
364
+ )
365
+ end
366
+
367
+ def handle_response({:ok, result}) do
368
+ case result do
369
+ %{"choices" => choices} when is_list(choices) and length(choices) > 0 ->
370
+ first_choice = Enum.at(choices, 0)
371
+ handle_message(first_choice["message"])
372
+ _ ->
373
+ {:error, "Unexpected response structure: #{inspect(result)}"}
374
+ end
375
+ end
376
+
377
+ def handle_response({:error, error}) do
378
+ {:error, "Error: #{inspect(error)}"}
379
+ end
380
+
381
+ defp handle_message(%{"tool_calls" => [tool_call | _]} = message) do
382
+ IO.puts("\nModel is using a tool:")
383
+ IO.inspect(message, label: "Full message")
384
+
385
+ %{"function" => %{"name" => function_name, "arguments" => arguments}} = tool_call
386
+ case function_name do
387
+ "get_top_users" ->
388
+ args = Jason.decode!(arguments)
389
+ IO.puts("Calling get_top_users function with n: #{args["n"]}")
390
+ result = get_top_users(args["n"])
391
+ IO.puts("Get top users function result: #{result}")
392
+ {:tool_call, tool_call["id"], function_name, result}
393
+ _ ->
394
+ {:error, "Unknown function: #{function_name}"}
395
+ end
396
+ end
397
+
398
+ defp handle_message(message) do
399
+ IO.puts("\nModel is not using a tool:")
400
+ IO.inspect(message, label: "Full message")
401
+ {:ok, message["content"]}
402
+ end
403
+
404
+ def run_conversation(user_prompt) do
405
+ IO.puts("Starting conversation with user prompt: #{user_prompt}")
406
+
407
+ initial_messages = [
408
+ %{
409
+ "role" => "system",
410
+ "content" => "You are an assistant capable of retrieving information about top system users.
411
+ Use the get_top_users function to retrieve information about users in database with
412
+ the most system usage time. If question is not about user or mintues info respond: Eyy guachoo, esto solo para database"
413
+ },
414
+ %{
415
+ "role" => "user",
416
+ "content" => user_prompt
417
+ }
418
+ ]
419
+
420
+ case create_chat_completion(initial_messages) do
421
+ {:ok, result} ->
422
+ IO.puts("\nReceived initial response from Groq API")
423
+ case handle_response({:ok, result}) do
424
+ {:tool_call, id, name, content} ->
425
+ IO.puts("\nProcessing tool call result")
426
+ tool_message = %{
427
+ "tool_call_id" => id,
428
+ "role" => "tool",
429
+ "name" => name,
430
+ "content" => content
431
+ }
432
+ first_choice = Enum.at(result["choices"], 0)
433
+ new_messages = initial_messages ++ [first_choice["message"], tool_message]
434
+ IO.puts("\nSending follow-up request to Groq API")
435
+ case create_chat_completion(new_messages) do
436
+ {:ok, final_result} ->
437
+ IO.puts("\nReceived final response from Groq API")
438
+ handle_response({:ok, final_result})
439
+ error -> error
440
+ end
441
+ other -> other
442
+ end
443
+ error -> error
444
+ end
445
+ end
446
+ end
447
+
448
+ # Create an input form with a submit option
449
+ form = Kino.Control.form(
450
+ [
451
+ query: Kino.Input.text("Enter your query. Example: list the 5 user with most used the system?")
452
+ ],
453
+ submit: "Send"
454
+ )
455
+
456
+ # Create a frame to display the response
457
+ frame = Kino.Frame.new()
458
+
459
+ # Set up the event stream for form submission
460
+ Kino.Control.stream(form)
461
+ |> Kino.listen(fn %{data: %{query: query}} ->
462
+ Kino.Frame.render(frame, Kino.Markdown.new("**Loading...**"))
463
+
464
+ # Make the API call
465
+ case GroqChat.run_conversation(query) do
466
+ {:ok, response} ->
467
+ Kino.Frame.render(frame, Kino.Markdown.new("**Response:**\n\n#{response}"))
468
+ {:error, error} ->
469
+ Kino.Frame.render(frame, Kino.Markdown.new("**Error:**\n\n#{error}"))
470
+ end
471
+ end)
472
+
473
+ # Render the UI elements
474
+ Kino.Layout.grid([
475
+ form,
476
+ frame
477
+ ])
478
+ ```
479
+
480
+ <!-- livebook:{"offset":13926,"stamp":{"token":"XCP.uif9L-BcwPrb46PT8dWaX-PK1L_knFhTLHIWcWPSMch2yDch8FA8yfOYF8Se-7uQ0JK-TJ0aFK7cW-wXCB9Y_hnMp5M3nT0n1HXBTRz3HNLcOqbTUN30","version":2}} -->