pedrogrisi commited on
Commit
c79b921
·
verified ·
1 Parent(s): 8bc224c

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ memory.db-wal filter=lfs diff=lfs merge=lfs -text
1_lab1.ipynb ADDED
@@ -0,0 +1,534 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "## Welcome back to Python Notebooks!\n",
8
+ "\n",
9
+ "Didja miss me??\n",
10
+ "\n",
11
+ "### And welcome to Week 4, Day 2 - introducing LangGraph!"
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "code",
16
+ "execution_count": 2,
17
+ "metadata": {},
18
+ "outputs": [],
19
+ "source": [
20
+ "from typing import Annotated\n",
21
+ "from langgraph.graph import StateGraph, START, END\n",
22
+ "from langgraph.graph.message import add_messages\n",
23
+ "from dotenv import load_dotenv\n",
24
+ "from IPython.display import Image, display\n",
25
+ "import gradio as gr\n",
26
+ "from langgraph.graph import StateGraph\n",
27
+ "from langgraph.graph.message import add_messages\n",
28
+ "from langchain_openai import ChatOpenAI\n",
29
+ "from pydantic import BaseModel\n",
30
+ "import random\n"
31
+ ]
32
+ },
33
+ {
34
+ "cell_type": "code",
35
+ "execution_count": 3,
36
+ "metadata": {},
37
+ "outputs": [],
38
+ "source": [
39
+ "# Some useful constants\n",
40
+ "\n",
41
+ "nouns = [\"Cabbages\", \"Unicorns\", \"Toasters\", \"Penguins\", \"Bananas\", \"Zombies\", \"Rainbows\", \"Eels\", \"Pickles\", \"Muffins\"]\n",
42
+ "adjectives = [\"outrageous\", \"smelly\", \"pedantic\", \"existential\", \"moody\", \"sparkly\", \"untrustworthy\", \"sarcastic\", \"squishy\", \"haunted\"]"
43
+ ]
44
+ },
45
+ {
46
+ "cell_type": "code",
47
+ "execution_count": 4,
48
+ "metadata": {},
49
+ "outputs": [
50
+ {
51
+ "data": {
52
+ "text/plain": [
53
+ "True"
54
+ ]
55
+ },
56
+ "execution_count": 4,
57
+ "metadata": {},
58
+ "output_type": "execute_result"
59
+ }
60
+ ],
61
+ "source": [
62
+ "# Our favorite first step! Crew was doing this for us, by the way.\n",
63
+ "load_dotenv(override=True)\n"
64
+ ]
65
+ },
66
+ {
67
+ "cell_type": "code",
68
+ "execution_count": 5,
69
+ "metadata": {},
70
+ "outputs": [
71
+ {
72
+ "name": "stdout",
73
+ "output_type": "stream",
74
+ "text": [
75
+ "HELLO\n"
76
+ ]
77
+ },
78
+ {
79
+ "data": {
80
+ "text/plain": [
81
+ "'HELLO'"
82
+ ]
83
+ },
84
+ "execution_count": 5,
85
+ "metadata": {},
86
+ "output_type": "execute_result"
87
+ }
88
+ ],
89
+ "source": [
90
+ "def shout(text: Annotated[str, \"something to be shouted\"]) -> str:\n",
91
+ " print(text.upper())\n",
92
+ " return text.upper()\n",
93
+ "\n",
94
+ "shout(\"hello\")"
95
+ ]
96
+ },
97
+ {
98
+ "cell_type": "markdown",
99
+ "metadata": {},
100
+ "source": [
101
+ "### A word about \"Annotated\"\n",
102
+ "\n",
103
+ "You probably know this; type hinting is a feature in Python that lets you specify the type of something:\n",
104
+ "\n",
105
+ "`my_favorite_things: List`\n",
106
+ "\n",
107
+ "But you may not know this:\n",
108
+ "\n",
109
+ "You can also use something called \"Annotated\" to add extra information that somebody else might find useful:\n",
110
+ "\n",
111
+ "`my_favorite_things: Annotated[List, \"these are a few of mine\"]`\n",
112
+ "\n",
113
+ "LangGraph needs us to use this feature when we define our State object.\n",
114
+ "\n",
115
+ "It wants us to tell it what function it should call to update the State with a new value.\n",
116
+ "\n",
117
+ "This function is called a **reducer**.\n",
118
+ "\n",
119
+ "LangGraph provides a default reducer called `add_messages` which takes care of the most common case.\n",
120
+ "\n",
121
+ "And that hopefully explains why the State looks like this.\n",
122
+ "\n",
123
+ "\n"
124
+ ]
125
+ },
126
+ {
127
+ "cell_type": "markdown",
128
+ "metadata": {},
129
+ "source": [
130
+ "### Step 1: Define the State object\n",
131
+ "\n",
132
+ "You can use any python object; but it's most common to use a TypedDict or a Pydantic BaseModel."
133
+ ]
134
+ },
135
+ {
136
+ "cell_type": "code",
137
+ "execution_count": 6,
138
+ "metadata": {},
139
+ "outputs": [],
140
+ "source": [
141
+ "\n",
142
+ "class State(BaseModel):\n",
143
+ " \n",
144
+ " messages: Annotated[list, add_messages]\n"
145
+ ]
146
+ },
147
+ {
148
+ "cell_type": "markdown",
149
+ "metadata": {},
150
+ "source": [
151
+ "### Step 2: Start the Graph Builder with this State class"
152
+ ]
153
+ },
154
+ {
155
+ "cell_type": "code",
156
+ "execution_count": 7,
157
+ "metadata": {},
158
+ "outputs": [],
159
+ "source": [
160
+ "graph_builder = StateGraph(State)"
161
+ ]
162
+ },
163
+ {
164
+ "cell_type": "markdown",
165
+ "metadata": {},
166
+ "source": [
167
+ "### Step 3: Create a Node\n",
168
+ "\n",
169
+ "A node can be any python function.\n",
170
+ "\n",
171
+ "The reducer that we set before gets automatically called to combine this response with previous responses\n"
172
+ ]
173
+ },
174
+ {
175
+ "cell_type": "code",
176
+ "execution_count": 8,
177
+ "metadata": {},
178
+ "outputs": [
179
+ {
180
+ "data": {
181
+ "text/plain": [
182
+ "<langgraph.graph.state.StateGraph at 0x12323bd70>"
183
+ ]
184
+ },
185
+ "execution_count": 8,
186
+ "metadata": {},
187
+ "output_type": "execute_result"
188
+ }
189
+ ],
190
+ "source": [
191
+ "def our_first_node(old_state: State) -> State:\n",
192
+ "\n",
193
+ " reply = f\"{random.choice(nouns)} are {random.choice(adjectives)}\"\n",
194
+ " messages = [{\"role\": \"assistant\", \"content\": reply}]\n",
195
+ "\n",
196
+ " new_state = State(messages=messages)\n",
197
+ "\n",
198
+ " return new_state\n",
199
+ "\n",
200
+ "graph_builder.add_node(\"first_node\", our_first_node)"
201
+ ]
202
+ },
203
+ {
204
+ "cell_type": "markdown",
205
+ "metadata": {},
206
+ "source": [
207
+ "### Step 4: Create Edges"
208
+ ]
209
+ },
210
+ {
211
+ "cell_type": "code",
212
+ "execution_count": 9,
213
+ "metadata": {},
214
+ "outputs": [
215
+ {
216
+ "data": {
217
+ "text/plain": [
218
+ "<langgraph.graph.state.StateGraph at 0x12323bd70>"
219
+ ]
220
+ },
221
+ "execution_count": 9,
222
+ "metadata": {},
223
+ "output_type": "execute_result"
224
+ }
225
+ ],
226
+ "source": [
227
+ "graph_builder.add_edge(START, \"first_node\")\n",
228
+ "graph_builder.add_edge(\"first_node\", END)"
229
+ ]
230
+ },
231
+ {
232
+ "cell_type": "markdown",
233
+ "metadata": {},
234
+ "source": [
235
+ "### Step 5: Compile the Graph"
236
+ ]
237
+ },
238
+ {
239
+ "cell_type": "code",
240
+ "execution_count": 10,
241
+ "metadata": {},
242
+ "outputs": [],
243
+ "source": [
244
+ "graph = graph_builder.compile()"
245
+ ]
246
+ },
247
+ {
248
+ "cell_type": "code",
249
+ "execution_count": 11,
250
+ "metadata": {},
251
+ "outputs": [
252
+ {
253
+ "data": {
254
+ "image/png": "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",
255
+ "text/plain": [
256
+ "<IPython.core.display.Image object>"
257
+ ]
258
+ },
259
+ "metadata": {},
260
+ "output_type": "display_data"
261
+ }
262
+ ],
263
+ "source": [
264
+ "display(Image(graph.get_graph().draw_mermaid_png()))"
265
+ ]
266
+ },
267
+ {
268
+ "cell_type": "markdown",
269
+ "metadata": {},
270
+ "source": [
271
+ "### That's it! Showtime!"
272
+ ]
273
+ },
274
+ {
275
+ "cell_type": "code",
276
+ "execution_count": 12,
277
+ "metadata": {},
278
+ "outputs": [
279
+ {
280
+ "name": "stdout",
281
+ "output_type": "stream",
282
+ "text": [
283
+ "* Running on local URL: http://127.0.0.1:7860\n",
284
+ "* To create a public link, set `share=True` in `launch()`.\n"
285
+ ]
286
+ },
287
+ {
288
+ "data": {
289
+ "text/html": [
290
+ "<div><iframe src=\"http://127.0.0.1:7860/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
291
+ ],
292
+ "text/plain": [
293
+ "<IPython.core.display.HTML object>"
294
+ ]
295
+ },
296
+ "metadata": {},
297
+ "output_type": "display_data"
298
+ },
299
+ {
300
+ "data": {
301
+ "text/plain": []
302
+ },
303
+ "execution_count": 12,
304
+ "metadata": {},
305
+ "output_type": "execute_result"
306
+ },
307
+ {
308
+ "name": "stdout",
309
+ "output_type": "stream",
310
+ "text": [
311
+ "{'messages': [HumanMessage(content='Ola', additional_kwargs={}, response_metadata={}, id='7fa75890-b14d-4046-bcaa-fab8f767501e'), AIMessage(content='Eels are smelly', additional_kwargs={}, response_metadata={}, id='8b7773a3-39ac-4f9a-8c73-0f57b10f380f')]}\n",
312
+ "{'messages': [HumanMessage(content='is that so?', additional_kwargs={}, response_metadata={}, id='f8947432-be24-479d-bf75-d56e718a846d'), AIMessage(content='Penguins are squishy', additional_kwargs={}, response_metadata={}, id='e032eb66-f503-4e94-97dc-69d0afd9e46a')]}\n",
313
+ "{'messages': [HumanMessage(content=\"you don't say\", additional_kwargs={}, response_metadata={}, id='4dbee382-5080-4dec-a82e-bc19293a5f51'), AIMessage(content='Penguins are sparkly', additional_kwargs={}, response_metadata={}, id='943d633d-dc2e-4cd7-b4a0-8302b23de6af')]}\n",
314
+ "{'messages': [HumanMessage(content='for real?', additional_kwargs={}, response_metadata={}, id='e84bd835-4f59-4d9b-8d96-ca76a07a6fdd'), AIMessage(content='Pickles are moody', additional_kwargs={}, response_metadata={}, id='8383e070-2852-4e97-b027-2ef66af3801d')]}\n",
315
+ "{'messages': [HumanMessage(content='no way', additional_kwargs={}, response_metadata={}, id='1f9e46f8-4d5a-4a8e-9e1b-5b234b3b8d1c'), AIMessage(content='Unicorns are sarcastic', additional_kwargs={}, response_metadata={}, id='52f0e0bc-2e99-4cd8-9ed7-14ef934891bd')]}\n",
316
+ "{'messages': [HumanMessage(content='a', additional_kwargs={}, response_metadata={}, id='0300d7cc-fa3a-49f7-92a1-87a6370375c9'), AIMessage(content='Pickles are squishy', additional_kwargs={}, response_metadata={}, id='f05acb5e-af43-41b2-8aae-9b803e1f1d6b')]}\n",
317
+ "{'messages': [HumanMessage(content='a', additional_kwargs={}, response_metadata={}, id='05b27fee-32b7-4825-982c-0b2bd72c1fe8'), AIMessage(content='Eels are moody', additional_kwargs={}, response_metadata={}, id='f6677907-f26a-4fe7-ab3f-d6a11d35878d')]}\n",
318
+ "{'messages': [HumanMessage(content='', additional_kwargs={}, response_metadata={}, id='fc4ef749-d210-411f-98bd-1c49cb3cb306'), AIMessage(content='Toasters are sparkly', additional_kwargs={}, response_metadata={}, id='7ea83e95-976c-4c99-9a54-71db9f25f00a')]}\n",
319
+ "{'messages': [HumanMessage(content='', additional_kwargs={}, response_metadata={}, id='888c2080-a378-4c3b-9683-845d1bb27a0e'), AIMessage(content='Toasters are pedantic', additional_kwargs={}, response_metadata={}, id='f841f525-4641-4426-8730-6bf64ea7c147')]}\n",
320
+ "{'messages': [HumanMessage(content='mannn', additional_kwargs={}, response_metadata={}, id='ed5a27e8-070b-4101-9c85-73748baa6760'), AIMessage(content='Bananas are existential', additional_kwargs={}, response_metadata={}, id='b753373d-0145-4514-a393-d59cd749bfdf')]}\n",
321
+ "{'messages': [HumanMessage(content='MAN WTF\\n\\\\\\\\', additional_kwargs={}, response_metadata={}, id='77e57d15-d486-4da9-9eae-7e1d7035ab74'), AIMessage(content='Pickles are sparkly', additional_kwargs={}, response_metadata={}, id='a0aec9ca-3d11-432a-ad23-23e128863311')]}\n"
322
+ ]
323
+ }
324
+ ],
325
+ "source": [
326
+ "def chat(user_input: str, history):\n",
327
+ " message = {\"role\": \"user\", \"content\": user_input}\n",
328
+ " messages = [message]\n",
329
+ " state = State(messages=messages)\n",
330
+ " result = graph.invoke(state)\n",
331
+ " print(result)\n",
332
+ " return result[\"messages\"][-1].content\n",
333
+ "\n",
334
+ "\n",
335
+ "gr.ChatInterface(chat, type=\"messages\").launch()"
336
+ ]
337
+ },
338
+ {
339
+ "cell_type": "markdown",
340
+ "metadata": {},
341
+ "source": [
342
+ "### But why did I show you that?\n",
343
+ "\n",
344
+ "To make the point that LangGraph is all about python functions - it doesn't need to involve LLMs!!\n",
345
+ "\n",
346
+ "Now we'll do the 5 steps again, but in 1 shot:"
347
+ ]
348
+ },
349
+ {
350
+ "cell_type": "code",
351
+ "execution_count": 13,
352
+ "metadata": {},
353
+ "outputs": [],
354
+ "source": [
355
+ "# Step 1: Define the State object\n",
356
+ "class State(BaseModel):\n",
357
+ " messages: Annotated[list, add_messages]\n"
358
+ ]
359
+ },
360
+ {
361
+ "cell_type": "code",
362
+ "execution_count": 14,
363
+ "metadata": {},
364
+ "outputs": [],
365
+ "source": [
366
+ "# Step 2: Start the Graph Builder with this State class\n",
367
+ "graph_builder = StateGraph(State)\n"
368
+ ]
369
+ },
370
+ {
371
+ "cell_type": "code",
372
+ "execution_count": 15,
373
+ "metadata": {},
374
+ "outputs": [
375
+ {
376
+ "data": {
377
+ "text/plain": [
378
+ "<langgraph.graph.state.StateGraph at 0x123f70560>"
379
+ ]
380
+ },
381
+ "execution_count": 15,
382
+ "metadata": {},
383
+ "output_type": "execute_result"
384
+ }
385
+ ],
386
+ "source": [
387
+ "# Step 3: Create a Node\n",
388
+ "\n",
389
+ "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
390
+ "\n",
391
+ "def chatbot_node(old_state: State) -> State:\n",
392
+ " response = llm.invoke(old_state.messages)\n",
393
+ " new_state = State(messages=[response])\n",
394
+ " return new_state\n",
395
+ "\n",
396
+ "graph_builder.add_node(\"chatbot\", chatbot_node)"
397
+ ]
398
+ },
399
+ {
400
+ "cell_type": "code",
401
+ "execution_count": 16,
402
+ "metadata": {},
403
+ "outputs": [
404
+ {
405
+ "data": {
406
+ "text/plain": [
407
+ "<langgraph.graph.state.StateGraph at 0x123f70560>"
408
+ ]
409
+ },
410
+ "execution_count": 16,
411
+ "metadata": {},
412
+ "output_type": "execute_result"
413
+ }
414
+ ],
415
+ "source": [
416
+ "# Step 4: Create Edges\n",
417
+ "graph_builder.add_edge(START, \"chatbot\")\n",
418
+ "graph_builder.add_edge(\"chatbot\", END)"
419
+ ]
420
+ },
421
+ {
422
+ "cell_type": "code",
423
+ "execution_count": 17,
424
+ "metadata": {},
425
+ "outputs": [
426
+ {
427
+ "data": {
428
+ "image/png": "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",
429
+ "text/plain": [
430
+ "<IPython.core.display.Image object>"
431
+ ]
432
+ },
433
+ "metadata": {},
434
+ "output_type": "display_data"
435
+ }
436
+ ],
437
+ "source": [
438
+ "# Step 5: Compile the Graph\n",
439
+ "graph = graph_builder.compile()\n",
440
+ "display(Image(graph.get_graph().draw_mermaid_png()))"
441
+ ]
442
+ },
443
+ {
444
+ "cell_type": "markdown",
445
+ "metadata": {},
446
+ "source": [
447
+ "### That's it! And, let's do this:"
448
+ ]
449
+ },
450
+ {
451
+ "cell_type": "code",
452
+ "execution_count": 18,
453
+ "metadata": {},
454
+ "outputs": [
455
+ {
456
+ "name": "stdout",
457
+ "output_type": "stream",
458
+ "text": [
459
+ "* Running on local URL: http://127.0.0.1:7861\n",
460
+ "* To create a public link, set `share=True` in `launch()`.\n"
461
+ ]
462
+ },
463
+ {
464
+ "data": {
465
+ "text/html": [
466
+ "<div><iframe src=\"http://127.0.0.1:7861/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
467
+ ],
468
+ "text/plain": [
469
+ "<IPython.core.display.HTML object>"
470
+ ]
471
+ },
472
+ "metadata": {},
473
+ "output_type": "display_data"
474
+ },
475
+ {
476
+ "data": {
477
+ "text/plain": []
478
+ },
479
+ "execution_count": 18,
480
+ "metadata": {},
481
+ "output_type": "execute_result"
482
+ },
483
+ {
484
+ "name": "stdout",
485
+ "output_type": "stream",
486
+ "text": [
487
+ "{'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='cc500b98-90b7-4002-a3de-1b2b003da18d'), AIMessage(content='Olá! Como posso ajudar você hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 8, 'prompt_tokens': 8, 'total_tokens': 16, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-Bqn7XcFoUceMCPb5YJh3dcTuyJosi', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--4550d66b-0846-4d03-8a32-aa69955b0c9b-0', usage_metadata={'input_tokens': 8, 'output_tokens': 8, 'total_tokens': 16, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}\n",
488
+ "{'messages': [HumanMessage(content='Como você pode me ajudar?', additional_kwargs={}, response_metadata={}, id='b4b97f08-0321-4d12-88bd-de036ee99668'), AIMessage(content='Posso ajudar de várias maneiras! Aqui estão algumas opções:\\n\\n1. **Responder perguntas**: Posso fornecer informações sobre uma ampla variedade de tópicos, incluindo ciência, história, cultura, tecnologia e muito mais.\\n\\n2. **Ajuda com estudos**: Posso explicar conceitos acadêmicos, ajudar com problemas de matemática, sugerir recursos de estudo e revisar textos.\\n\\n3. **Escrita e revisão**: Posso ajudar a escrever textos, como ensaios, artigos ou cartas, e também revisar e sugerir melhorias em textos que você já escreveu.\\n\\n4. **Dicas de aprendizado**: Posso oferecer dicas e técnicas para melhorar sua produtividade e habilidades de aprendizado.\\n\\n5. **Sugestões de leitura e entretenimento**: Posso recomendar livros, filmes, músicas e outros tipos de entretenimento com base nos seus interesses.\\n\\n6. **Conversas e discussões**: Se você quiser conversar ou debater sobre algum assunto, estou aqui para isso também!\\n\\n7. **Suporte emocional**: Embora eu não substitua um profissional de saúde mental, posso oferecer palavras de apoio e conselhos gerais sobre bem-estar.\\n\\nSe houver algo específico em que você gostaria de ajuda, é só me avisar!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 247, 'prompt_tokens': 13, 'total_tokens': 260, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-Bqn7hnRukyrRA4NC069XKuKeorzGW', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--3eeefff5-94f8-4e01-a2ef-e4bab4f26f01-0', usage_metadata={'input_tokens': 13, 'output_tokens': 247, 'total_tokens': 260, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}\n",
489
+ "{'messages': [HumanMessage(content='consegue interpretar imagens?', additional_kwargs={}, response_metadata={}, id='64cd56e0-a67c-42d3-8d13-1987fcfc73d9'), AIMessage(content='Não, eu não consigo interpretar imagens. No entanto, posso ajudar a responder perguntas ou fornecer informações com base em texto. Se você tiver uma descrição ou informações sobre uma imagem, posso ajudar a interpretar ou discutir o conteúdo!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 44, 'prompt_tokens': 12, 'total_tokens': 56, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_62a23a81ef', 'id': 'chatcmpl-Bqn80FV6U9VMJ0mKtvX8A2hWD4c7c', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--88ee1686-7493-487c-83b8-d9e4eee5c9c7-0', usage_metadata={'input_tokens': 12, 'output_tokens': 44, 'total_tokens': 56, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}\n",
490
+ "{'messages': [HumanMessage(content='o que eu disse nessa conversa?', additional_kwargs={}, response_metadata={}, id='b5d88fc7-b827-4a03-8ebd-367d609ee6b5'), AIMessage(content='Desculpe, mas não posso acessar ou recuperar informações de conversas anteriores. No entanto, posso ajudá-lo com informações ou perguntas que você tiver agora. Como posso ajudar?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 35, 'prompt_tokens': 14, 'total_tokens': 49, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-Bqn8YLflXq0DzQdCpn6fWZnO8hcTe', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--ddd55222-9e99-4416-80b5-cefe6a4f3e9b-0', usage_metadata={'input_tokens': 14, 'output_tokens': 35, 'total_tokens': 49, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}\n"
491
+ ]
492
+ }
493
+ ],
494
+ "source": [
495
+ "def chat(user_input: str, history):\n",
496
+ " initial_state = State(messages=[{\"role\": \"user\", \"content\": user_input}])\n",
497
+ " result = graph.invoke(initial_state)\n",
498
+ " print(result)\n",
499
+ " return result['messages'][-1].content\n",
500
+ "\n",
501
+ "\n",
502
+ "gr.ChatInterface(chat, type=\"messages\").launch()"
503
+ ]
504
+ },
505
+ {
506
+ "cell_type": "code",
507
+ "execution_count": null,
508
+ "metadata": {},
509
+ "outputs": [],
510
+ "source": []
511
+ }
512
+ ],
513
+ "metadata": {
514
+ "kernelspec": {
515
+ "display_name": ".venv",
516
+ "language": "python",
517
+ "name": "python3"
518
+ },
519
+ "language_info": {
520
+ "codemirror_mode": {
521
+ "name": "ipython",
522
+ "version": 3
523
+ },
524
+ "file_extension": ".py",
525
+ "mimetype": "text/x-python",
526
+ "name": "python",
527
+ "nbconvert_exporter": "python",
528
+ "pygments_lexer": "ipython3",
529
+ "version": "3.12.7"
530
+ }
531
+ },
532
+ "nbformat": 4,
533
+ "nbformat_minor": 2
534
+ }
2_lab2.ipynb ADDED
@@ -0,0 +1,668 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "### And welcome to Week 4, Day 3 - more LangGraph.."
8
+ ]
9
+ },
10
+ {
11
+ "cell_type": "code",
12
+ "execution_count": 2,
13
+ "metadata": {},
14
+ "outputs": [],
15
+ "source": [
16
+ "from typing import Annotated\n",
17
+ "from langgraph.graph import StateGraph, START, END\n",
18
+ "from langgraph.graph.message import add_messages\n",
19
+ "from dotenv import load_dotenv\n",
20
+ "from IPython.display import Image, display\n",
21
+ "import gradio as gr\n",
22
+ "from langgraph.prebuilt import ToolNode, tools_condition\n",
23
+ "import requests\n",
24
+ "import os\n",
25
+ "from langchain_openai import ChatOpenAI\n",
26
+ "from typing import TypedDict\n"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 3,
32
+ "metadata": {},
33
+ "outputs": [
34
+ {
35
+ "data": {
36
+ "text/plain": [
37
+ "True"
38
+ ]
39
+ },
40
+ "execution_count": 3,
41
+ "metadata": {},
42
+ "output_type": "execute_result"
43
+ }
44
+ ],
45
+ "source": [
46
+ "# Our favorite first step! Crew was doing this for us, by the way.\n",
47
+ "load_dotenv(override=True)\n"
48
+ ]
49
+ },
50
+ {
51
+ "cell_type": "markdown",
52
+ "metadata": {},
53
+ "source": [
54
+ "### First, let's go set up LangSmith!\n",
55
+ "\n",
56
+ "https://langsmith.com"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "markdown",
61
+ "metadata": {},
62
+ "source": [
63
+ "### Next, here is a useful function in LangChain community:"
64
+ ]
65
+ },
66
+ {
67
+ "cell_type": "code",
68
+ "execution_count": 4,
69
+ "metadata": {},
70
+ "outputs": [
71
+ {
72
+ "data": {
73
+ "text/plain": [
74
+ "'Paris is the capital and largest city of France. With an estimated population of 2,048,472 in January 2025 in an area of more than 105 km2 (41 sq mi), ... Paris is the capital and most populous city of France. Situated on the Seine River, in the north of the country, it is in the centre of the Île-de-France ... Paris, city and capital of France, located along the Seine River, in the north-central part of the country. Paris is one of the world\\'s most important and ... Paris is the capital of France, the largest country of Europe with 550 000 km2 (65 millions inhabitants). Paris has 2.234 million inhabitants end 2011. The capital of France has been Paris since its liberation in 1944. Paris is the city of romance par excellence, the fashion capital and the best example of French art de vivre. Exploring Paris is an essential rite of passage ... In 987 A.D., Paris became the capital of France. As the city grew, the Left Bank earned a reputation as the intellectual district while the Right Bank ... Dive into the charm of Paris, the capital of France! Explore its rich history, iconic attractions, and delectable cuisine in our guide. This stockshot provides illustrative images of Paris, capital of France, filmed in November 2020. It presents general views of the city, official buildings ... Paris, the capital of France, is often referred to as the \"City of Light\" and is renowned for its art, culture, and history. It is home to ...'"
75
+ ]
76
+ },
77
+ "execution_count": 4,
78
+ "metadata": {},
79
+ "output_type": "execute_result"
80
+ }
81
+ ],
82
+ "source": [
83
+ "from langchain_community.utilities import GoogleSerperAPIWrapper\n",
84
+ "\n",
85
+ "serper = GoogleSerperAPIWrapper()\n",
86
+ "serper.run(\"What is the capital of France?\")"
87
+ ]
88
+ },
89
+ {
90
+ "cell_type": "markdown",
91
+ "metadata": {},
92
+ "source": [
93
+ "### Now here is a LangChain wrapper class for converting functions into Tools"
94
+ ]
95
+ },
96
+ {
97
+ "cell_type": "code",
98
+ "execution_count": 5,
99
+ "metadata": {},
100
+ "outputs": [],
101
+ "source": [
102
+ "from langchain.agents import Tool\n",
103
+ "\n",
104
+ "tool_search =Tool(\n",
105
+ " name=\"search\",\n",
106
+ " func=serper.run,\n",
107
+ " description=\"Useful for when you need more information from an online search\"\n",
108
+ " )\n",
109
+ "\n"
110
+ ]
111
+ },
112
+ {
113
+ "cell_type": "markdown",
114
+ "metadata": {},
115
+ "source": [
116
+ "### Now we can try out the tool the langchain way"
117
+ ]
118
+ },
119
+ {
120
+ "cell_type": "code",
121
+ "execution_count": 6,
122
+ "metadata": {},
123
+ "outputs": [
124
+ {
125
+ "data": {
126
+ "text/plain": [
127
+ "'Paris is the capital and largest city of France. With an estimated population of 2,048,472 in January 2025 in an area of more than 105 km2 (41 sq mi), ... Paris is the capital and most populous city of France. Situated on the Seine River, in the north of the country, it is in the centre of the Île-de-France ... Paris, city and capital of France, located along the Seine River, in the north-central part of the country. Paris is one of the world\\'s most important and ... Paris is the capital of France, the largest country of Europe with 550 000 km2 (65 millions inhabitants). Paris has 2.234 million inhabitants end 2011. The capital of France has been Paris since its liberation in 1944. In 987 A.D., Paris became the capital of France. As the city grew, the Left Bank earned a reputation as the intellectual district while the Right Bank ... Paris is the city of romance par excellence, the fashion capital and the best example of French art de vivre. Exploring Paris is an essential rite of passage ... This stockshot provides illustrative images of Paris, capital of France, filmed in November 2020. It presents general views of the city, official buildings ... Paris, the capital of France, is often referred to as the \"City of Light\" and is renowned for its art, culture, and history. It is home to ... Dive into the charm of Paris, the capital of France! Explore its rich history, iconic attractions, and delectable cuisine in our guide.'"
128
+ ]
129
+ },
130
+ "execution_count": 6,
131
+ "metadata": {},
132
+ "output_type": "execute_result"
133
+ }
134
+ ],
135
+ "source": [
136
+ "tool_search.invoke(\"What is the capital of France?\")"
137
+ ]
138
+ },
139
+ {
140
+ "cell_type": "markdown",
141
+ "metadata": {},
142
+ "source": [
143
+ "### And now let's write a tool ourselves\n",
144
+ "\n",
145
+ "We'll pick a familiar one"
146
+ ]
147
+ },
148
+ {
149
+ "cell_type": "code",
150
+ "execution_count": 7,
151
+ "metadata": {},
152
+ "outputs": [],
153
+ "source": [
154
+ "pushover_token = os.getenv(\"PUSHOVER_TOKEN\")\n",
155
+ "pushover_user = os.getenv(\"PUSHOVER_USER\")\n",
156
+ "pushover_url = \"https://api.pushover.net/1/messages.json\"\n",
157
+ "\n",
158
+ "def push(text: str):\n",
159
+ " \"\"\"Send a push notification to the user\"\"\"\n",
160
+ " requests.post(pushover_url, data = {\"token\": pushover_token, \"user\": pushover_user, \"message\": text})\n",
161
+ " "
162
+ ]
163
+ },
164
+ {
165
+ "cell_type": "code",
166
+ "execution_count": 8,
167
+ "metadata": {},
168
+ "outputs": [],
169
+ "source": [
170
+ "tool_push = Tool(\n",
171
+ " name=\"send_push_notification\",\n",
172
+ " func=push,\n",
173
+ " description=\"useful for when you want to send a push notification\"\n",
174
+ " )\n",
175
+ "\n",
176
+ "tool_push.invoke(\"Hello, me\")"
177
+ ]
178
+ },
179
+ {
180
+ "cell_type": "markdown",
181
+ "metadata": {},
182
+ "source": [
183
+ "### Back to the Graph from yesterday\n",
184
+ "\n",
185
+ "One small change - using TypedDict instead of BaseModel for the State object\n",
186
+ "\n",
187
+ "When we implement tools, we always need to make 2 changes to the code:\n",
188
+ "\n",
189
+ "1. Changes to provide the tools to OpenAI in json when we make the call\n",
190
+ "\n",
191
+ "2. Changes to handle the results back: look for the model staying that the finish_reason==\"tool_calls\" and then retrieve the call, run the function, provide the results."
192
+ ]
193
+ },
194
+ {
195
+ "cell_type": "markdown",
196
+ "metadata": {},
197
+ "source": [
198
+ "### Bring them together"
199
+ ]
200
+ },
201
+ {
202
+ "cell_type": "code",
203
+ "execution_count": 9,
204
+ "metadata": {},
205
+ "outputs": [],
206
+ "source": [
207
+ "tools = [tool_search, tool_push]"
208
+ ]
209
+ },
210
+ {
211
+ "cell_type": "code",
212
+ "execution_count": 10,
213
+ "metadata": {},
214
+ "outputs": [],
215
+ "source": [
216
+ "# Step 1: Define the State object\n",
217
+ "class State(TypedDict):\n",
218
+ " messages: Annotated[list, add_messages]"
219
+ ]
220
+ },
221
+ {
222
+ "cell_type": "code",
223
+ "execution_count": 11,
224
+ "metadata": {},
225
+ "outputs": [],
226
+ "source": [
227
+ "# Step 2: Start the Graph Builder with this State class\n",
228
+ "graph_builder = StateGraph(State)"
229
+ ]
230
+ },
231
+ {
232
+ "cell_type": "code",
233
+ "execution_count": 12,
234
+ "metadata": {},
235
+ "outputs": [],
236
+ "source": [
237
+ "# This is different:\n",
238
+ "\n",
239
+ "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
240
+ "llm_with_tools = llm.bind_tools(tools)"
241
+ ]
242
+ },
243
+ {
244
+ "cell_type": "code",
245
+ "execution_count": 15,
246
+ "metadata": {},
247
+ "outputs": [
248
+ {
249
+ "ename": "ValueError",
250
+ "evalue": "Node `chatbot` already present.",
251
+ "output_type": "error",
252
+ "traceback": [
253
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
254
+ "\u001b[31mValueError\u001b[39m Traceback (most recent call last)",
255
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[15]\u001b[39m\u001b[32m, line 7\u001b[39m\n\u001b[32m 4\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mchatbot\u001b[39m(state: State):\n\u001b[32m 5\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m {\u001b[33m\"\u001b[39m\u001b[33mmessages\u001b[39m\u001b[33m\"\u001b[39m: [llm_with_tools.invoke(state[\u001b[33m\"\u001b[39m\u001b[33mmessages\u001b[39m\u001b[33m\"\u001b[39m])]}\n\u001b[32m----> \u001b[39m\u001b[32m7\u001b[39m \u001b[43mgraph_builder\u001b[49m\u001b[43m.\u001b[49m\u001b[43madd_node\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mchatbot\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mchatbot\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 8\u001b[39m graph_builder.add_node(\u001b[33m\"\u001b[39m\u001b[33mtools\u001b[39m\u001b[33m\"\u001b[39m, ToolNode(tools=tools))\n",
256
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/projects/agents/.venv/lib/python3.12/site-packages/langgraph/graph/state.py:375\u001b[39m, in \u001b[36mStateGraph.add_node\u001b[39m\u001b[34m(self, node, action, defer, metadata, input, retry, cache_policy, destinations)\u001b[39m\n\u001b[32m 373\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m\n\u001b[32m 374\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m node \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m.nodes:\n\u001b[32m--> \u001b[39m\u001b[32m375\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mNode `\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mnode\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m` already present.\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 376\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m node == END \u001b[38;5;129;01mor\u001b[39;00m node == START:\n\u001b[32m 377\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mNode `\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mnode\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m` is reserved.\u001b[39m\u001b[33m\"\u001b[39m)\n",
257
+ "\u001b[31mValueError\u001b[39m: Node `chatbot` already present."
258
+ ]
259
+ }
260
+ ],
261
+ "source": [
262
+ "# Step 3: Create a Node\n",
263
+ "\n",
264
+ "\n",
265
+ "def chatbot(state: State):\n",
266
+ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
267
+ "\n",
268
+ "graph_builder.add_node(\"chatbot\", chatbot)\n",
269
+ "graph_builder.add_node(\"tools\", ToolNode(tools=tools))"
270
+ ]
271
+ },
272
+ {
273
+ "cell_type": "code",
274
+ "execution_count": 16,
275
+ "metadata": {},
276
+ "outputs": [
277
+ {
278
+ "data": {
279
+ "text/plain": [
280
+ "<langgraph.graph.state.StateGraph at 0x12a0ca4b0>"
281
+ ]
282
+ },
283
+ "execution_count": 16,
284
+ "metadata": {},
285
+ "output_type": "execute_result"
286
+ }
287
+ ],
288
+ "source": [
289
+ "# Step 4: Create Edges\n",
290
+ "\n",
291
+ "\n",
292
+ "graph_builder.add_conditional_edges( \"chatbot\", tools_condition, \"tools\")\n",
293
+ "\n",
294
+ "# Any time a tool is called, we return to the chatbot to decide the next step\n",
295
+ "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
296
+ "graph_builder.add_edge(START, \"chatbot\")"
297
+ ]
298
+ },
299
+ {
300
+ "cell_type": "code",
301
+ "execution_count": 17,
302
+ "metadata": {},
303
+ "outputs": [
304
+ {
305
+ "data": {
306
+ "image/png": "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",
307
+ "text/plain": [
308
+ "<IPython.core.display.Image object>"
309
+ ]
310
+ },
311
+ "metadata": {},
312
+ "output_type": "display_data"
313
+ }
314
+ ],
315
+ "source": [
316
+ "# Step 5: Compile the Graph\n",
317
+ "graph = graph_builder.compile()\n",
318
+ "display(Image(graph.get_graph().draw_mermaid_png()))"
319
+ ]
320
+ },
321
+ {
322
+ "cell_type": "markdown",
323
+ "metadata": {},
324
+ "source": [
325
+ "### That's it! And, let's do this:"
326
+ ]
327
+ },
328
+ {
329
+ "cell_type": "code",
330
+ "execution_count": 18,
331
+ "metadata": {},
332
+ "outputs": [
333
+ {
334
+ "name": "stdout",
335
+ "output_type": "stream",
336
+ "text": [
337
+ "* Running on local URL: http://127.0.0.1:7860\n",
338
+ "* To create a public link, set `share=True` in `launch()`.\n"
339
+ ]
340
+ },
341
+ {
342
+ "data": {
343
+ "text/html": [
344
+ "<div><iframe src=\"http://127.0.0.1:7860/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
345
+ ],
346
+ "text/plain": [
347
+ "<IPython.core.display.HTML object>"
348
+ ]
349
+ },
350
+ "metadata": {},
351
+ "output_type": "display_data"
352
+ },
353
+ {
354
+ "data": {
355
+ "text/plain": []
356
+ },
357
+ "execution_count": 18,
358
+ "metadata": {},
359
+ "output_type": "execute_result"
360
+ }
361
+ ],
362
+ "source": [
363
+ "def chat(user_input: str, history):\n",
364
+ " result = graph.invoke({\"messages\": [{\"role\": \"user\", \"content\": user_input}]})\n",
365
+ " return result[\"messages\"][-1].content\n",
366
+ "\n",
367
+ "\n",
368
+ "gr.ChatInterface(chat, type=\"messages\").launch()"
369
+ ]
370
+ },
371
+ {
372
+ "cell_type": "markdown",
373
+ "metadata": {},
374
+ "source": [
375
+ "## OK it's time to add Memory!\n",
376
+ "\n",
377
+ "### BUT WAIT!\n",
378
+ "\n",
379
+ "We have this whole Graph maintaining the state and appending to the state.\n",
380
+ "\n",
381
+ "Why isn't this handling memory?\n",
382
+ "\n",
383
+ "### This is a crucial point for understanding LangGraph\n",
384
+ "\n",
385
+ "> A super-step can be considered a single iteration over the graph nodes. Nodes that run in parallel are part of the same super-step, while nodes that run sequentially belong to separate super-steps.\n",
386
+ "\n",
387
+ "\n",
388
+ "One \"Super-Step\" of the graph represents one invocation of passing messages between agents.\n",
389
+ "\n",
390
+ "In idomatic LangGraph, you call invoke to run your graph for each super-step; for each interaction.\n",
391
+ "\n",
392
+ "The reducer handles state updates automatically within one super-step, but not between them.\n",
393
+ "\n",
394
+ "That is what checkpointing achieves."
395
+ ]
396
+ },
397
+ {
398
+ "cell_type": "code",
399
+ "execution_count": 19,
400
+ "metadata": {},
401
+ "outputs": [],
402
+ "source": [
403
+ "from langgraph.checkpoint.memory import MemorySaver\n",
404
+ "\n",
405
+ "memory = MemorySaver()"
406
+ ]
407
+ },
408
+ {
409
+ "cell_type": "code",
410
+ "execution_count": 20,
411
+ "metadata": {},
412
+ "outputs": [
413
+ {
414
+ "data": {
415
+ "image/png": "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",
416
+ "text/plain": [
417
+ "<IPython.core.display.Image object>"
418
+ ]
419
+ },
420
+ "metadata": {},
421
+ "output_type": "display_data"
422
+ }
423
+ ],
424
+ "source": [
425
+ "# Steps 1 and 2\n",
426
+ "graph_builder = StateGraph(State)\n",
427
+ "\n",
428
+ "\n",
429
+ "# Step 3\n",
430
+ "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
431
+ "llm_with_tools = llm.bind_tools(tools)\n",
432
+ "\n",
433
+ "def chatbot(state: State):\n",
434
+ " print(state)\n",
435
+ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
436
+ "\n",
437
+ "graph_builder.add_node(\"chatbot\", chatbot)\n",
438
+ "graph_builder.add_node(\"tools\", ToolNode(tools=tools))\n",
439
+ "\n",
440
+ "# Step 4\n",
441
+ "graph_builder.add_conditional_edges( \"chatbot\", tools_condition, \"tools\")\n",
442
+ "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
443
+ "graph_builder.add_edge(START, \"chatbot\")\n",
444
+ "\n",
445
+ "# Step 5\n",
446
+ "graph = graph_builder.compile(checkpointer=memory)\n",
447
+ "display(Image(graph.get_graph().draw_mermaid_png()))"
448
+ ]
449
+ },
450
+ {
451
+ "cell_type": "code",
452
+ "execution_count": 21,
453
+ "metadata": {},
454
+ "outputs": [
455
+ {
456
+ "name": "stdout",
457
+ "output_type": "stream",
458
+ "text": [
459
+ "* Running on local URL: http://127.0.0.1:7861\n",
460
+ "* To create a public link, set `share=True` in `launch()`.\n"
461
+ ]
462
+ },
463
+ {
464
+ "data": {
465
+ "text/html": [
466
+ "<div><iframe src=\"http://127.0.0.1:7861/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
467
+ ],
468
+ "text/plain": [
469
+ "<IPython.core.display.HTML object>"
470
+ ]
471
+ },
472
+ "metadata": {},
473
+ "output_type": "display_data"
474
+ },
475
+ {
476
+ "data": {
477
+ "text/plain": []
478
+ },
479
+ "execution_count": 21,
480
+ "metadata": {},
481
+ "output_type": "execute_result"
482
+ },
483
+ {
484
+ "name": "stdout",
485
+ "output_type": "stream",
486
+ "text": [
487
+ "{'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182')]}\n",
488
+ "{'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182'), AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='meu nome é Pedro', additional_kwargs={}, response_metadata={}, id='6460e680-2716-4ba7-879e-3f7f5217e843')]}\n",
489
+ "{'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182'), AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='meu nome é Pedro', additional_kwargs={}, response_metadata={}, id='6460e680-2716-4ba7-879e-3f7f5217e843'), AIMessage(content='Prazer em conhecê-lo, Pedro! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 110, 'total_tokens': 127, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAm7xOqflg4weELosLScIRABuDJm', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--d007ba52-31fd-451b-b9ce-d8bb5fb9d663-0', usage_metadata={'input_tokens': 110, 'output_tokens': 17, 'total_tokens': 127, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='Qual é o meu nome?', additional_kwargs={}, response_metadata={}, id='b668ac3c-a0aa-4e77-b67e-cee502d422dc')]}\n"
490
+ ]
491
+ }
492
+ ],
493
+ "source": [
494
+ "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
495
+ "\n",
496
+ "def chat(user_input: str, history):\n",
497
+ " result = graph.invoke({\"messages\": [{\"role\": \"user\", \"content\": user_input}]}, config=config)\n",
498
+ " return result[\"messages\"][-1].content\n",
499
+ "\n",
500
+ "\n",
501
+ "gr.ChatInterface(chat, type=\"messages\").launch()"
502
+ ]
503
+ },
504
+ {
505
+ "cell_type": "code",
506
+ "execution_count": 22,
507
+ "metadata": {},
508
+ "outputs": [
509
+ {
510
+ "data": {
511
+ "text/plain": [
512
+ "StateSnapshot(values={'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182'), AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='meu nome é Pedro', additional_kwargs={}, response_metadata={}, id='6460e680-2716-4ba7-879e-3f7f5217e843'), AIMessage(content='Prazer em conhecê-lo, Pedro! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 110, 'total_tokens': 127, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAm7xOqflg4weELosLScIRABuDJm', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--d007ba52-31fd-451b-b9ce-d8bb5fb9d663-0', usage_metadata={'input_tokens': 110, 'output_tokens': 17, 'total_tokens': 127, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='Qual é o meu nome?', additional_kwargs={}, response_metadata={}, id='b668ac3c-a0aa-4e77-b67e-cee502d422dc'), AIMessage(content='Seu nome é Pedro. Como posso ajudá-lo?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 140, 'total_tokens': 151, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAmIe267HDFQr4LuOl1BVtUd1p6S', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--6f872f8c-5bbb-4a27-8dfa-cfb8a541177c-0', usage_metadata={'input_tokens': 140, 'output_tokens': 11, 'total_tokens': 151, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c481-7982-6b20-8007-f31034093660'}}, metadata={'source': 'loop', 'writes': {'chatbot': {'messages': [AIMessage(content='Seu nome é Pedro. Como posso ajudá-lo?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 140, 'total_tokens': 151, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAmIe267HDFQr4LuOl1BVtUd1p6S', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--6f872f8c-5bbb-4a27-8dfa-cfb8a541177c-0', usage_metadata={'input_tokens': 140, 'output_tokens': 11, 'total_tokens': 151, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}}, 'step': 7, 'parents': {}, 'thread_id': '1'}, created_at='2025-07-08T22:08:35.173448+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c481-5cc7-63b4-8006-ea5b391de267'}}, tasks=(), interrupts=())"
513
+ ]
514
+ },
515
+ "execution_count": 22,
516
+ "metadata": {},
517
+ "output_type": "execute_result"
518
+ }
519
+ ],
520
+ "source": [
521
+ "graph.get_state(config)"
522
+ ]
523
+ },
524
+ {
525
+ "cell_type": "code",
526
+ "execution_count": 23,
527
+ "metadata": {},
528
+ "outputs": [
529
+ {
530
+ "data": {
531
+ "text/plain": [
532
+ "[StateSnapshot(values={'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182'), AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='meu nome é Pedro', additional_kwargs={}, response_metadata={}, id='6460e680-2716-4ba7-879e-3f7f5217e843'), AIMessage(content='Prazer em conhecê-lo, Pedro! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 110, 'total_tokens': 127, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAm7xOqflg4weELosLScIRABuDJm', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--d007ba52-31fd-451b-b9ce-d8bb5fb9d663-0', usage_metadata={'input_tokens': 110, 'output_tokens': 17, 'total_tokens': 127, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='Qual é o meu nome?', additional_kwargs={}, response_metadata={}, id='b668ac3c-a0aa-4e77-b67e-cee502d422dc'), AIMessage(content='Seu nome é Pedro. Como posso ajudá-lo?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 140, 'total_tokens': 151, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAmIe267HDFQr4LuOl1BVtUd1p6S', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--6f872f8c-5bbb-4a27-8dfa-cfb8a541177c-0', usage_metadata={'input_tokens': 140, 'output_tokens': 11, 'total_tokens': 151, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c481-7982-6b20-8007-f31034093660'}}, metadata={'source': 'loop', 'writes': {'chatbot': {'messages': [AIMessage(content='Seu nome é Pedro. Como posso ajudá-lo?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 140, 'total_tokens': 151, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAmIe267HDFQr4LuOl1BVtUd1p6S', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--6f872f8c-5bbb-4a27-8dfa-cfb8a541177c-0', usage_metadata={'input_tokens': 140, 'output_tokens': 11, 'total_tokens': 151, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}}, 'step': 7, 'parents': {}, 'thread_id': '1'}, created_at='2025-07-08T22:08:35.173448+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c481-5cc7-63b4-8006-ea5b391de267'}}, tasks=(), interrupts=()),\n",
533
+ " StateSnapshot(values={'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182'), AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='meu nome é Pedro', additional_kwargs={}, response_metadata={}, id='6460e680-2716-4ba7-879e-3f7f5217e843'), AIMessage(content='Prazer em conhecê-lo, Pedro! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 110, 'total_tokens': 127, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAm7xOqflg4weELosLScIRABuDJm', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--d007ba52-31fd-451b-b9ce-d8bb5fb9d663-0', usage_metadata={'input_tokens': 110, 'output_tokens': 17, 'total_tokens': 127, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='Qual é o meu nome?', additional_kwargs={}, response_metadata={}, id='b668ac3c-a0aa-4e77-b67e-cee502d422dc')]}, next=('chatbot',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c481-5cc7-63b4-8006-ea5b391de267'}}, metadata={'source': 'loop', 'writes': None, 'step': 6, 'parents': {}, 'thread_id': '1'}, created_at='2025-07-08T22:08:32.160650+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c481-5cc4-6a06-8005-a5a5447acea4'}}, tasks=(PregelTask(id='db604ec6-a937-d1d4-ef31-32fbdd600cc2', name='chatbot', path=('__pregel_pull', 'chatbot'), error=None, interrupts=(), state=None, result={'messages': [AIMessage(content='Seu nome é Pedro. Como posso ajudá-lo?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 140, 'total_tokens': 151, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAmIe267HDFQr4LuOl1BVtUd1p6S', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--6f872f8c-5bbb-4a27-8dfa-cfb8a541177c-0', usage_metadata={'input_tokens': 140, 'output_tokens': 11, 'total_tokens': 151, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}),), interrupts=()),\n",
534
+ " StateSnapshot(values={'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182'), AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='meu nome é Pedro', additional_kwargs={}, response_metadata={}, id='6460e680-2716-4ba7-879e-3f7f5217e843'), AIMessage(content='Prazer em conhecê-lo, Pedro! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 110, 'total_tokens': 127, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAm7xOqflg4weELosLScIRABuDJm', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--d007ba52-31fd-451b-b9ce-d8bb5fb9d663-0', usage_metadata={'input_tokens': 110, 'output_tokens': 17, 'total_tokens': 127, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}, next=('__start__',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c481-5cc4-6a06-8005-a5a5447acea4'}}, metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': 'Qual é o meu nome?'}]}}, 'step': 5, 'parents': {}, 'thread_id': '1'}, created_at='2025-07-08T22:08:32.159570+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c481-0dfa-69a2-8004-60dce29fbdb1'}}, tasks=(PregelTask(id='57e76031-affd-c079-be75-5512d31eac5c', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': 'Qual é o meu nome?'}]}),), interrupts=()),\n",
535
+ " StateSnapshot(values={'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182'), AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='meu nome é Pedro', additional_kwargs={}, response_metadata={}, id='6460e680-2716-4ba7-879e-3f7f5217e843'), AIMessage(content='Prazer em conhecê-lo, Pedro! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 110, 'total_tokens': 127, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAm7xOqflg4weELosLScIRABuDJm', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--d007ba52-31fd-451b-b9ce-d8bb5fb9d663-0', usage_metadata={'input_tokens': 110, 'output_tokens': 17, 'total_tokens': 127, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c481-0dfa-69a2-8004-60dce29fbdb1'}}, metadata={'source': 'loop', 'writes': {'chatbot': {'messages': [AIMessage(content='Prazer em conhecê-lo, Pedro! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 110, 'total_tokens': 127, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAm7xOqflg4weELosLScIRABuDJm', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--d007ba52-31fd-451b-b9ce-d8bb5fb9d663-0', usage_metadata={'input_tokens': 110, 'output_tokens': 17, 'total_tokens': 127, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}}, 'step': 4, 'parents': {}, 'thread_id': '1'}, created_at='2025-07-08T22:08:23.897940+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c480-ffde-6fb2-8003-7fca9a9487fe'}}, tasks=(), interrupts=()),\n",
536
+ " StateSnapshot(values={'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182'), AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='meu nome é Pedro', additional_kwargs={}, response_metadata={}, id='6460e680-2716-4ba7-879e-3f7f5217e843')]}, next=('chatbot',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c480-ffde-6fb2-8003-7fca9a9487fe'}}, metadata={'source': 'loop', 'writes': None, 'step': 3, 'parents': {}, 'thread_id': '1'}, created_at='2025-07-08T22:08:22.418621+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c480-ffdc-6370-8002-9ec129d49532'}}, tasks=(PregelTask(id='3d95d457-c987-d82f-0581-7e68c74293db', name='chatbot', path=('__pregel_pull', 'chatbot'), error=None, interrupts=(), state=None, result={'messages': [AIMessage(content='Prazer em conhecê-lo, Pedro! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 110, 'total_tokens': 127, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAm7xOqflg4weELosLScIRABuDJm', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--d007ba52-31fd-451b-b9ce-d8bb5fb9d663-0', usage_metadata={'input_tokens': 110, 'output_tokens': 17, 'total_tokens': 127, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}),), interrupts=()),\n",
537
+ " StateSnapshot(values={'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182'), AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}, next=('__start__',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c480-ffdc-6370-8002-9ec129d49532'}}, metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': 'meu nome é Pedro'}]}}, 'step': 2, 'parents': {}, 'thread_id': '1'}, created_at='2025-07-08T22:08:22.417476+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c480-c17e-6560-8001-1db67eb021e5'}}, tasks=(PregelTask(id='1d1f077f-c2ac-f10f-7177-d5a12991785d', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': 'meu nome é Pedro'}]}),), interrupts=()),\n",
538
+ " StateSnapshot(values={'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182'), AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c480-c17e-6560-8001-1db67eb021e5'}}, metadata={'source': 'loop', 'writes': {'chatbot': {'messages': [AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}}, 'step': 1, 'parents': {}, 'thread_id': '1'}, created_at='2025-07-08T22:08:15.877852+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c480-aa90-6c54-8000-fa6a76e348f1'}}, tasks=(), interrupts=()),\n",
539
+ " StateSnapshot(values={'messages': [HumanMessage(content='ola', additional_kwargs={}, response_metadata={}, id='e1e436b1-5203-4693-afa4-d4fd4c377182')]}, next=('chatbot',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c480-aa90-6c54-8000-fa6a76e348f1'}}, metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}, 'thread_id': '1'}, created_at='2025-07-08T22:08:13.473681+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c480-aa88-6e28-bfff-c7a662524363'}}, tasks=(PregelTask(id='ced35c81-d43e-2a98-61eb-e3e00cd63c2c', name='chatbot', path=('__pregel_pull', 'chatbot'), error=None, interrupts=(), state=None, result={'messages': [AIMessage(content='Olá! Como posso ajudá-lo hoje?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 89, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrAlzTfXnPoO2N2jkBUEXRcFqAhzC', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--c43ad2f7-5178-4cc6-bdd8-56df3167dc85-0', usage_metadata={'input_tokens': 89, 'output_tokens': 9, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}),), interrupts=()),\n",
540
+ " StateSnapshot(values={'messages': []}, next=('__start__',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f05c480-aa88-6e28-bfff-c7a662524363'}}, metadata={'source': 'input', 'writes': {'__start__': {'messages': [{'role': 'user', 'content': 'ola'}]}}, 'step': -1, 'parents': {}, 'thread_id': '1'}, created_at='2025-07-08T22:08:13.470460+00:00', parent_config=None, tasks=(PregelTask(id='35c3c5a1-630a-3692-eeae-7cf3db5b826f', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [{'role': 'user', 'content': 'ola'}]}),), interrupts=())]"
541
+ ]
542
+ },
543
+ "execution_count": 23,
544
+ "metadata": {},
545
+ "output_type": "execute_result"
546
+ }
547
+ ],
548
+ "source": [
549
+ "# Most recent first\n",
550
+ "\n",
551
+ "list(graph.get_state_history(config))"
552
+ ]
553
+ },
554
+ {
555
+ "cell_type": "markdown",
556
+ "metadata": {},
557
+ "source": [
558
+ "### LangGraph gives you tools to set the state back to a prior point in time, to branch off:\n",
559
+ "\n",
560
+ "```\n",
561
+ "config = {\"configurable\": {\"thread_id\": \"1\", \"checkpoint_id\": ...}}\n",
562
+ "graph.invoke(None, config=config)\n",
563
+ "```\n",
564
+ "\n",
565
+ "And this allows you to build stable systems that can be recovered and rerun from any prior checkpoint."
566
+ ]
567
+ },
568
+ {
569
+ "cell_type": "markdown",
570
+ "metadata": {},
571
+ "source": [
572
+ "### And now let's store in SQL\n",
573
+ "\n",
574
+ "### And this is the power of LangGraph."
575
+ ]
576
+ },
577
+ {
578
+ "cell_type": "code",
579
+ "execution_count": 24,
580
+ "metadata": {},
581
+ "outputs": [],
582
+ "source": [
583
+ "import sqlite3\n",
584
+ "from langgraph.checkpoint.sqlite import SqliteSaver\n",
585
+ "\n",
586
+ "db_path = \"memory.db\"\n",
587
+ "conn = sqlite3.connect(db_path, check_same_thread=False)\n",
588
+ "sql_memory = SqliteSaver(conn)"
589
+ ]
590
+ },
591
+ {
592
+ "cell_type": "code",
593
+ "execution_count": null,
594
+ "metadata": {},
595
+ "outputs": [],
596
+ "source": [
597
+ "# Steps 1 and 2\n",
598
+ "graph_builder = StateGraph(State)\n",
599
+ "\n",
600
+ "\n",
601
+ "# Step 3\n",
602
+ "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
603
+ "llm_with_tools = llm.bind_tools(tools)\n",
604
+ "\n",
605
+ "def chatbot(state: State):\n",
606
+ " print(state)\n",
607
+ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
608
+ "\n",
609
+ "graph_builder.add_node(\"chatbot\", chatbot)\n",
610
+ "graph_builder.add_node(\"tools\", ToolNode(tools=tools))\n",
611
+ "\n",
612
+ "# Step 4\n",
613
+ "graph_builder.add_conditional_edges( \"chatbot\", tools_condition, \"tools\")\n",
614
+ "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
615
+ "graph_builder.add_edge(START, \"chatbot\")\n",
616
+ "\n",
617
+ "# Step 5\n",
618
+ "graph = graph_builder.compile(checkpointer=sql_memory)\n",
619
+ "display(Image(graph.get_graph().draw_mermaid_png()))\n",
620
+ " "
621
+ ]
622
+ },
623
+ {
624
+ "cell_type": "code",
625
+ "execution_count": null,
626
+ "metadata": {},
627
+ "outputs": [],
628
+ "source": [
629
+ "config = {\"configurable\": {\"thread_id\": \"3\"}}\n",
630
+ "\n",
631
+ "def chat(user_input: str, history):\n",
632
+ " result = graph.invoke({\"messages\": [{\"role\": \"user\", \"content\": user_input}]}, config=config)\n",
633
+ " return result[\"messages\"][-1].content\n",
634
+ "\n",
635
+ "\n",
636
+ "gr.ChatInterface(chat, type=\"messages\").launch()"
637
+ ]
638
+ },
639
+ {
640
+ "cell_type": "code",
641
+ "execution_count": null,
642
+ "metadata": {},
643
+ "outputs": [],
644
+ "source": []
645
+ }
646
+ ],
647
+ "metadata": {
648
+ "kernelspec": {
649
+ "display_name": ".venv",
650
+ "language": "python",
651
+ "name": "python3"
652
+ },
653
+ "language_info": {
654
+ "codemirror_mode": {
655
+ "name": "ipython",
656
+ "version": 3
657
+ },
658
+ "file_extension": ".py",
659
+ "mimetype": "text/x-python",
660
+ "name": "python",
661
+ "nbconvert_exporter": "python",
662
+ "pygments_lexer": "ipython3",
663
+ "version": "3.12.7"
664
+ }
665
+ },
666
+ "nbformat": 4,
667
+ "nbformat_minor": 2
668
+ }
3_lab3.ipynb ADDED
@@ -0,0 +1,649 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "## Welcome to Week 4, Day 4\n",
8
+ "\n",
9
+ "This is the start of an AWESOME project! Really simple and very effective."
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "markdown",
14
+ "metadata": {},
15
+ "source": [
16
+ "### First - a heads up for Windows PC users\n",
17
+ "\n",
18
+ "While executing this notebook, you might hit a problem with the Playwright browser raising a NotImplementedError.\n",
19
+ "\n",
20
+ "This should work when we move to python modules, but it can cause problems in Windows in a notebook.\n",
21
+ "\n",
22
+ "If you it this error and would like to run the notebook, you need to make a small change which seems quite hacky!\n",
23
+ "\n",
24
+ "1. Right click in `.venv` in the File Explorer on the left and select \"Find in folder\"\n",
25
+ "2. Search for `asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())` \n",
26
+ "3. That code should be found in a line of code in a file called `kernelapp.py`\n",
27
+ "4. Comment out that line of code in that file! And save the file. (And in fact, student William Lapa tells me that he needed to comment out the entire else statement that this line is part of.)\n",
28
+ "5. Restart the kernel by pressing the \"Restart\" button above\n",
29
+ "\n",
30
+ "Thank you to student Nicolas for finding this, and to Yaki, Zibin and Bhaskar for confirming that this worked for them!"
31
+ ]
32
+ },
33
+ {
34
+ "cell_type": "code",
35
+ "execution_count": 1,
36
+ "metadata": {},
37
+ "outputs": [],
38
+ "source": [
39
+ "from typing import Annotated\n",
40
+ "from typing_extensions import TypedDict\n",
41
+ "from langgraph.graph import StateGraph, START\n",
42
+ "from langgraph.graph.message import add_messages\n",
43
+ "from dotenv import load_dotenv\n",
44
+ "from IPython.display import Image, display\n",
45
+ "import gradio as gr\n",
46
+ "from langgraph.prebuilt import ToolNode, tools_condition\n",
47
+ "import requests\n",
48
+ "import os\n",
49
+ "from langchain.agents import Tool\n",
50
+ "\n",
51
+ "from langchain_openai import ChatOpenAI\n",
52
+ "from langgraph.checkpoint.memory import MemorySaver"
53
+ ]
54
+ },
55
+ {
56
+ "cell_type": "code",
57
+ "execution_count": 2,
58
+ "metadata": {},
59
+ "outputs": [
60
+ {
61
+ "data": {
62
+ "text/plain": [
63
+ "True"
64
+ ]
65
+ },
66
+ "execution_count": 2,
67
+ "metadata": {},
68
+ "output_type": "execute_result"
69
+ }
70
+ ],
71
+ "source": [
72
+ "load_dotenv(override=True)"
73
+ ]
74
+ },
75
+ {
76
+ "cell_type": "markdown",
77
+ "metadata": {},
78
+ "source": [
79
+ "### Asynchronous LangGraph\n",
80
+ "\n",
81
+ "To run a tool: \n",
82
+ "Sync: `tool.run(inputs)` \n",
83
+ "Async: `await tool.arun(inputs)`\n",
84
+ "\n",
85
+ "To invoke the graph: \n",
86
+ "Sync: `graph.invoke(state)` \n",
87
+ "Async: `await graph.ainvoke(state)`"
88
+ ]
89
+ },
90
+ {
91
+ "cell_type": "code",
92
+ "execution_count": 3,
93
+ "metadata": {},
94
+ "outputs": [],
95
+ "source": [
96
+ "class State(TypedDict):\n",
97
+ " \n",
98
+ " messages: Annotated[list, add_messages]\n",
99
+ "\n",
100
+ "\n",
101
+ "graph_builder = StateGraph(State)"
102
+ ]
103
+ },
104
+ {
105
+ "cell_type": "code",
106
+ "execution_count": 4,
107
+ "metadata": {},
108
+ "outputs": [],
109
+ "source": [
110
+ "pushover_token = os.getenv(\"PUSHOVER_TOKEN\")\n",
111
+ "pushover_user = os.getenv(\"PUSHOVER_USER\")\n",
112
+ "pushover_url = \"https://api.pushover.net/1/messages.json\"\n",
113
+ "\n",
114
+ "def push(text: str):\n",
115
+ " \"\"\"Send a push notification to the user\"\"\"\n",
116
+ " requests.post(pushover_url, data = {\"token\": pushover_token, \"user\": pushover_user, \"message\": text})\n",
117
+ "\n",
118
+ "tool_push = Tool(\n",
119
+ " name=\"send_push_notification\",\n",
120
+ " func=push,\n",
121
+ " description=\"useful for when you want to send a push notification\"\n",
122
+ " )"
123
+ ]
124
+ },
125
+ {
126
+ "cell_type": "markdown",
127
+ "metadata": {},
128
+ "source": [
129
+ "### Next: Install Playwright\n",
130
+ "\n",
131
+ "On Windows and MacOS: \n",
132
+ "`playwright install`\n",
133
+ "\n",
134
+ "On Linux: \n",
135
+ "`playwright install —with-reps chromium`"
136
+ ]
137
+ },
138
+ {
139
+ "cell_type": "markdown",
140
+ "metadata": {},
141
+ "source": [
142
+ "### Introducing nest_asyncio\n",
143
+ "\n",
144
+ "Python async code only allows for one \"event loop\" processing aynchronous events.\n",
145
+ "\n",
146
+ "The `nest_asyncio` library patches this, and is used for special situations, if you need to run a nested event loop.\n",
147
+ "\n"
148
+ ]
149
+ },
150
+ {
151
+ "cell_type": "code",
152
+ "execution_count": 5,
153
+ "metadata": {},
154
+ "outputs": [],
155
+ "source": [
156
+ "import nest_asyncio\n",
157
+ "nest_asyncio.apply()"
158
+ ]
159
+ },
160
+ {
161
+ "cell_type": "markdown",
162
+ "metadata": {},
163
+ "source": [
164
+ "### The LangChain community\n",
165
+ "\n",
166
+ "One of the remarkable things about LangChain is the rich community around it.\n",
167
+ "\n",
168
+ "Check this out:\n"
169
+ ]
170
+ },
171
+ {
172
+ "cell_type": "code",
173
+ "execution_count": 6,
174
+ "metadata": {},
175
+ "outputs": [],
176
+ "source": [
177
+ "from langchain_community.agent_toolkits import PlayWrightBrowserToolkit\n",
178
+ "from langchain_community.tools.playwright.utils import create_async_playwright_browser\n",
179
+ "\n",
180
+ "async_browser = create_async_playwright_browser(headless=False) # headful mode\n",
181
+ "toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=async_browser)\n",
182
+ "tools = toolkit.get_tools()"
183
+ ]
184
+ },
185
+ {
186
+ "cell_type": "code",
187
+ "execution_count": 7,
188
+ "metadata": {},
189
+ "outputs": [
190
+ {
191
+ "name": "stdout",
192
+ "output_type": "stream",
193
+ "text": [
194
+ "click_element=async_browser=<Browser type=<BrowserType name=chromium executable_path=/Users/pedrogrisi/Library/Caches/ms-playwright/chromium-1169/chrome-mac/Chromium.app/Contents/MacOS/Chromium> version=136.0.7103.25>\n",
195
+ "navigate_browser=async_browser=<Browser type=<BrowserType name=chromium executable_path=/Users/pedrogrisi/Library/Caches/ms-playwright/chromium-1169/chrome-mac/Chromium.app/Contents/MacOS/Chromium> version=136.0.7103.25>\n",
196
+ "previous_webpage=async_browser=<Browser type=<BrowserType name=chromium executable_path=/Users/pedrogrisi/Library/Caches/ms-playwright/chromium-1169/chrome-mac/Chromium.app/Contents/MacOS/Chromium> version=136.0.7103.25>\n",
197
+ "extract_text=async_browser=<Browser type=<BrowserType name=chromium executable_path=/Users/pedrogrisi/Library/Caches/ms-playwright/chromium-1169/chrome-mac/Chromium.app/Contents/MacOS/Chromium> version=136.0.7103.25>\n",
198
+ "extract_hyperlinks=async_browser=<Browser type=<BrowserType name=chromium executable_path=/Users/pedrogrisi/Library/Caches/ms-playwright/chromium-1169/chrome-mac/Chromium.app/Contents/MacOS/Chromium> version=136.0.7103.25>\n",
199
+ "get_elements=async_browser=<Browser type=<BrowserType name=chromium executable_path=/Users/pedrogrisi/Library/Caches/ms-playwright/chromium-1169/chrome-mac/Chromium.app/Contents/MacOS/Chromium> version=136.0.7103.25>\n",
200
+ "current_webpage=async_browser=<Browser type=<BrowserType name=chromium executable_path=/Users/pedrogrisi/Library/Caches/ms-playwright/chromium-1169/chrome-mac/Chromium.app/Contents/MacOS/Chromium> version=136.0.7103.25>\n"
201
+ ]
202
+ }
203
+ ],
204
+ "source": [
205
+ "for tool in tools:\n",
206
+ " print(f\"{tool.name}={tool}\")"
207
+ ]
208
+ },
209
+ {
210
+ "cell_type": "code",
211
+ "execution_count": 8,
212
+ "metadata": {},
213
+ "outputs": [],
214
+ "source": [
215
+ "tool_dict = {tool.name:tool for tool in tools}\n",
216
+ "\n",
217
+ "navigate_tool = tool_dict.get(\"navigate_browser\")\n",
218
+ "extract_text_tool = tool_dict.get(\"extract_text\")\n",
219
+ "\n",
220
+ " \n",
221
+ "await navigate_tool.arun({\"url\": \"https://www.cnn.com\"})\n",
222
+ "text = await extract_text_tool.arun({})"
223
+ ]
224
+ },
225
+ {
226
+ "cell_type": "code",
227
+ "execution_count": 9,
228
+ "metadata": {},
229
+ "outputs": [
230
+ {
231
+ "name": "stdout",
232
+ "output_type": "stream",
233
+ "text": [
234
+ "Breaking News, Latest News and Videos | CNN CNN values your feedback\n",
235
+ "1. How relevant is this ad to you? 2. Did you encounter any technical\n",
236
+ "issues? No Video player was slow to load content Video content never\n",
237
+ "loaded Ad froze or did not finish loading Video content did not start\n",
238
+ "after ad Audio on ad was too loud Other issues Ad never loaded Ad\n",
239
+ "prevented/slowed the page from loading Content moved around while ad\n",
240
+ "loaded Ad was repetitive to ads I've seen previously Other issues\n",
241
+ "Cancel Submit Thank You! Your effort and contribution in providing\n",
242
+ "this feedback is much\n",
243
+ "appreciated. Close Ad Feedback Close icon US World Politics Business\n",
244
+ "Health Entertainment Style Travel Sports Science Climate Weather\n",
245
+ "Ukraine-Russia War Israel-Hamas War Games More US World Politics\n",
246
+ "Business Health Entertainment Style Travel Sports Science Climate\n",
247
+ "Weather Ukraine-Russia War Israel-Hamas War Games Watch Listen Live TV\n",
248
+ "Subscribe Sign in My Account Settings Newsletters Topics you follow\n",
249
+ "Sign out Your CNN account Sign in to your CNN account Sign in My\n",
250
+ "Account Settings Newsletters Topics you follow Sign out Your CNN\n",
251
+ "account Sign in to your CNN account Live TV Listen Watch Edition US\n",
252
+ "International Arabic Español Edition US International Arabic Español\n",
253
+ "World Africa Americas Asia Australia China Europe India Middle East\n",
254
+ "United Kingdom US Politics Trump Facts First CNN Polls 2025 Elections\n",
255
+ "Business Tech Media Calculators Videos Markets Pre-markets After-Hours\n",
256
+ "Fear & Greed Investing Markets Now Nightcap Health Life, But Better\n",
257
+ "Fitness Food Sleep Mindfulness Relationships Entertainment Movies\n",
258
+ "Television Celebrity Tech Innovate Foreseeable Future Mission: Ahead\n",
259
+ "Work Transformed Innovative Cities Style Arts Design Fashion\n",
260
+ "Architecture Luxury Beauty Video Travel Destinations Food & Drink Stay\n",
261
+ "News Videos Sports Football Tennis Golf Motorsport US Sports Olympics\n",
262
+ "Climbing Esports Hockey Science Space Life Unearthed Climate Solutions\n",
263
+ "Weather Weather Video Climate Ukraine-Russia War Israel-Hamas War\n",
264
+ "Features As Equals Call to Earth Freedom Project Impact Your World\n",
265
+ "Inside Africa CNN Heroes Watch Live TV CNN Fast Shows A-Z CNN10 CNN\n",
266
+ "Max CNN TV Schedules Listen CNN 5 Things Chasing Life with Dr. Sanjay\n",
267
+ "Gupta The Assignment with Audie Cornish One Thing Tug of War CNN\n",
268
+ "Political Briefing The Axe Files All There Is with Anderson Cooper All\n",
269
+ "CNN Audio podcasts Games Daily Crossword Jumble Crossword Sudoblock\n",
270
+ "Sudoku 5 Things Quiz About CNN Photos Investigations CNN Profiles CNN\n",
271
+ "Leadership CNN Newsletters Work for CNN Follow CNN Texas flooding\n",
272
+ "Russia launches record drone attack on Ukraine Christian Horner Design\n",
273
+ "for women, by women Extreme heat Wimbledon whites Club World Cup •\n",
274
+ "Analysis Analysis Getty Images Analysis Trump seems to really be\n",
275
+ "losing patience with Putin. But why now? • Analysis Analysis Mingson\n",
276
+ "Lau/AP Analysis How a tied shoelace will mark a turning point in US\n",
277
+ "history • Live Updates Live Updates Brandon Bell/Getty Images Live\n",
278
+ "Updates At least 111 dead and more than 170 still missing in\n",
279
+ "catastrophic Texas flooding, officials say Hegseth authorized Ukraine\n",
280
+ "weapons pause before informing White House, sources say Show all Kevin\n",
281
+ "Dietsch/Getty Images The defense secretary’s actions set off a\n",
282
+ "scramble inside the US administration, with Trump suggesting he was\n",
283
+ "not responsible Russia launches record drone attack on Ukraine after\n",
284
+ "Trump criticizes Putin Video See moment Trump vents his anger about\n",
285
+ "Putin during cabinet meeting 0:30 CNN Exclusive Trump said he\n",
286
+ "threatened to bomb Moscow if Putin attacked Ukraine, 2024 fundraiser\n",
287
+ "tapes show Analysis Trump’s sudden shift on weapons for Ukraine takes\n",
288
+ "the war back to square one Video Kaitlan Collins presses Trump on who\n",
289
+ "paused Ukraine weapons 0:28 Play Catch up on today's global news -\n",
290
+ "Source: CNN Video Catch up on today’s global news Bradley\n",
291
+ "Collyer/Press Association/AP/File Red Bull team principal Christian\n",
292
+ "Horner fired after 20 years with team Clive Brunskill/Getty Images\n",
293
+ "Wimbledon blames ball boy for latest electronic line call blunder Alex\n",
294
+ "Grimm/Getty Images After scoring the goal of his life, meet the\n",
295
+ "teacher who gave stars at the Club World Cup a lesson they’ll never\n",
296
+ "forget Courtesy On Why an inch of color can get you fined at Wimbledon\n",
297
+ "Ad Feedback More top stories Dr Fadel Naim/X Desperate Gaza doctors\n",
298
+ "cram several babies into one incubator as fuel crisis reaches critical\n",
299
+ "point First malaria drug for newborns and young infants expected to be\n",
300
+ "approved in Africa within weeks Trump wants to talk business with\n",
301
+ "Africa in hopes of countering China. But a US summit excludes Africa’s\n",
302
+ "big players Frustration grows inside the White House after DOJ’s\n",
303
+ "Epstein review comes up empty Video Disc-shaped UAP captured on video\n",
304
+ "2:21 Trump can carry out mass firings, Supreme Court says Youtube\n",
305
+ "/Alexis Stone / Mateusz Sitek He has transformed into at least 250\n",
306
+ "celebrities. This one might be his last • Interactive Interactive Tim\n",
307
+ "Mosenfelder/Getty Images Interactive 10 objects that challenge the\n",
308
+ "inherent gender bias in product design Mike Kemp/In Pictures/Getty\n",
309
+ "Images/File Giant, flightless bird is next target for de-extinction\n",
310
+ "company Colossal Biosciences Ad Feedback Andrew Harnik/Getty Images\n",
311
+ "Trump promised 200 deals by now. He’s gotten 3, and 1 more is getting\n",
312
+ "very close Jul 9, 2025 Callaghan O'Hare/Bloomberg/Getty Images\n",
313
+ "Analysis Trump has delayed his monster tariffs. Here’s why you should\n",
314
+ "care Jul 9, 2025 Kevin Lamarque/Reuters Trump promises more tariff\n",
315
+ "letters and warns BRICS of what’s coming Jul 9, 2025 CNN Video Trump\n",
316
+ "gets upset with reporter who asked about Epstein 1:25 Jul 8, 2025 Ad\n",
317
+ "Feedback Bryan Dozier/NurPhoto/Shutterstock The Trump administration\n",
318
+ "said ‘many Jewish groups’ support a controversial nominee – some have\n",
319
+ "never heard of him CNN Disillusioned with life in the UK, arson-attack\n",
320
+ "instigator turned to Russian mercenary group, prosecutors say Sakis\n",
321
+ "Mitrolidis/AFP/Getty Images Extreme heat is a killer. A recent heat\n",
322
+ "wave shows how much more deadly it’s becoming • Video 0:35 White\n",
323
+ "helmets Video Wildfires spread in northeastern Syria 0:35 Ocean\n",
324
+ "Exploration Trust Coconut logs: Discovery shines new light on US Navy\n",
325
+ "ship’s ingenious way to get back into battle Heshi Peixin\n",
326
+ "Kindergarten/WeChat More than 200 children found with high lead levels\n",
327
+ "after kindergarten in China uses paint as food coloring, authorities\n",
328
+ "say Air Zermatt/AP He fell into a crevasse in Switzerland. Then his\n",
329
+ "Chihuahua saved the day Thibaud Moritz/AFP/Getty Images Nvidia beats\n",
330
+ "Apple and Microsoft to become the world’s first $4 trillion public\n",
331
+ "company • CNN Investigates CNN Investigates Eyad Baba/AFP/Getty Images\n",
332
+ "CNN Investigates USAID review raised ‘critical concerns’ over Gaza aid\n",
333
+ "group days before $30 million US grant Vincent Carchietta/Imagn\n",
334
+ "Images/Reuters ‘I’m sorry’: This player scored twice against his\n",
335
+ "former side to book spot in Club World Cup final • Video 2:56 CNN\n",
336
+ "Video This legend crosses time zones with just 6 pounds of gear. Here\n",
337
+ "are his favorites 2:56 Ad Feedback Featured Sections Space and science\n",
338
+ "C. Tortora/INSPIRE/VST/ESO/LBT Astronomers discover ‘fossil galaxy’ 3\n",
339
+ "billion light-years away Ancient Peruvian city more than 3,000 years\n",
340
+ "old unveiled These ultra-thin bendy solar panels are so light you can\n",
341
+ "wear them The first genome sequenced from ancient Egypt reveals\n",
342
+ "surprising ancestry, scientists say Global Travel Show all Ariel Kang\n",
343
+ "Solo female traveler recaps dramatic 104-hour exit from Iran 20 of\n",
344
+ "Canada’s best foods (Sorry, Hawaiian pizza did not make the list) The\n",
345
+ "changes coming to European hand-luggage rules Why Britain’s royal\n",
346
+ "train is coming to the end of the line after 180 years of service Ad\n",
347
+ "Feedback Global Business • Video 1:22 Fintech Meetup Video Why this\n",
348
+ "CEO just killed unlimited vacation days at his company 1:22 Analysis\n",
349
+ "Wall Street’s Trump TACO trade has a chicken and an egg problem Trump\n",
350
+ "says 50% tariff on copper imports is coming and threatens 200% on\n",
351
+ "pharmaceuticals Tesla is in deeper trouble than you think Art and\n",
352
+ "Style Courtesy Celine Naomi Watts, BTS’ Kim Taehyung and more turn out\n",
353
+ "for Celine fashion show Look of the Week: Marc Jacobs totes a boxed\n",
354
+ "Labubu What to expect from Paris Haute Couture Week An ambitious\n",
355
+ "vision of a city built from lava This pinecone-sized device could\n",
356
+ "transform the fight against wildfires SPORT Show all Andy Lyons/Getty\n",
357
+ "Images Caitlin Clark picks Indiana Fever teammate Aliyah Boston with\n",
358
+ "the No. 1 pick in WNBA All-Star Game draft Three-time MVP Nikola Jokić\n",
359
+ "will delay signing extension with Nuggets this summer, AP source says\n",
360
+ "Why tennis players grunt during matches Ad Feedback US Politics •\n",
361
+ "Video 1:06 cnn Video Hear Kristi Noem’s announcement about change in\n",
362
+ "shoe removal policy at airport security checkpoints 1:06 Why an\n",
363
+ "obscure provision in Trump’s big agenda bill has gamblers crying foul\n",
364
+ "Justice Ketanji Brown Jackson emerges as a leading dissenter in an era\n",
365
+ "of Trump health and wellness Tatiana Maksimova/Moment RF/Getty Images\n",
366
+ "Women are actually tougher than men How to tell if you have a\n",
367
+ "‘dopamine deficit’ and what you can do to reset ‘Cool’ people tend to\n",
368
+ "have these six things in common, study finds This overlooked aspect of\n",
369
+ "fitness affects everything Tech Lisa Eadicicco/CNN Samsung’s bet on\n",
370
+ "the future of smartphones is something Apple doesn’t have an answer to\n",
371
+ "yet Elon Musk’s AI chatbot is suddenly posting antisemitic tropes A\n",
372
+ "new TikTok app may be coming. Here’s what we know so far More from CNN\n",
373
+ "African Voices Kelvin Okafor These hyper-realistic pencil portraits\n",
374
+ "are raising awareness of visible skin differences • Video 4:53 Keith\n",
375
+ "Bernstein/Paramount Pictures Video When Hollywood comes to South\n",
376
+ "Africa: CEO of film production company on journey to the big screens\n",
377
+ "4:53 Abi Morocco Photos/Lagos Studio Archives These artists are racing\n",
378
+ "to save forgotten photos of Lagos life Ad Feedback In Case You Missed\n",
379
+ "It Caral Archaeological Zone/Handout/Reuters Ancient Peruvian city\n",
380
+ "more than 3,000 years old uncovered This record serve at Wimbledon is\n",
381
+ "a sign of things to come Denmark has long been Euroskeptic. Donald\n",
382
+ "Trump helped change that China tells EU it can’t accept Russia losing\n",
383
+ "its war against Ukraine, official says Wildfires sweep through forests\n",
384
+ "in drought-hit Syrian coast in major test for new government Elephant\n",
385
+ "kills two tourists from the UK and New Zealand in Zambian national\n",
386
+ "park Calls are mounting to ban Germany’s far-right AfD party – despite\n",
387
+ "it being more popular than ever Man with rifle killed at US Border\n",
388
+ "Patrol facility after exchanging fire with officers Israel bombs\n",
389
+ "Houthis in Yemen after rebels attack commercial ship for first time in\n",
390
+ "months Subscribe Sign in My Account Settings Newsletters Topics you\n",
391
+ "follow Sign out Your CNN account Sign in to your CNN account Live TV\n",
392
+ "Listen Watch World Africa Americas Asia Australia China Europe India\n",
393
+ "Middle East United Kingdom US Politics Trump Facts First CNN Polls\n",
394
+ "2025 Elections Business Tech Media Calculators Videos Markets Pre-\n",
395
+ "markets After-Hours Fear & Greed Investing Markets Now Nightcap Health\n",
396
+ "Life, But Better Fitness Food Sleep Mindfulness Relationships\n",
397
+ "Entertainment Movies Television Celebrity Tech Innovate Foreseeable\n",
398
+ "Future Mission: Ahead Work Transformed Innovative Cities Style Arts\n",
399
+ "Design Fashion Architecture Luxury Beauty Video Travel Destinations\n",
400
+ "Food & Drink Stay News Videos Sports Football Tennis Golf Motorsport\n",
401
+ "US Sports Olympics Climbing Esports Hockey Science Space Life\n",
402
+ "Unearthed Climate Solutions Weather Weather Video Climate Ukraine-\n",
403
+ "Russia War Israel-Hamas War Features As Equals Call to Earth Freedom\n",
404
+ "Project Impact Your World Inside Africa CNN Heroes Watch Live TV CNN\n",
405
+ "Fast Shows A-Z CNN10 CNN Max CNN TV Schedules Listen CNN 5 Things\n",
406
+ "Chasing Life with Dr. Sanjay Gupta The Assignment with Audie Cornish\n",
407
+ "One Thing Tug of War CNN Political Briefing The Axe Files All There Is\n",
408
+ "with Anderson Cooper All CNN Audio podcasts Games Daily Crossword\n",
409
+ "Jumble Crossword Sudoblock Sudoku 5 Things Quiz About CNN Photos\n",
410
+ "Investigations CNN Profiles CNN Leadership CNN Newsletters Work for\n",
411
+ "CNN Watch Listen Live TV Follow CNN Subscribe Sign in My Account\n",
412
+ "Settings Newsletters Topics you follow Sign out Your CNN account Sign\n",
413
+ "in to your CNN account Terms of Use Privacy Policy Cookie Settings Ad\n",
414
+ "Choices Accessibility & CC About Newsletters Transcripts © 2025 Cable\n",
415
+ "News Network. A Warner Bros. Discovery Company. All Rights Reserved.\n",
416
+ "CNN Sans ™ & © 2016 Cable News Network. Manage Cookies+ When you visit\n",
417
+ "any website, it may store or retrieve information on your browser,\n",
418
+ "mostly in the form of cookies. This information might be about you,\n",
419
+ "your preferences or your device and is mostly used to make the site\n",
420
+ "work as you expect it to. The information does not usually directly\n",
421
+ "identify you, but it can give you a more personalized web experience.\n",
422
+ "Because we respect your right to privacy, you can choose not to allow\n",
423
+ "some types of cookies. Click on the different category headings to\n",
424
+ "find out more and change our default settings. However, blocking some\n",
425
+ "types of cookies may impact your experience of the site and the\n",
426
+ "services we are able to offer. Privacy Policy Allow All Manage Consent\n",
427
+ "Preferences Use limited data to select advertising Use limited data to\n",
428
+ "select advertising Advertising presented to you on this service can be\n",
429
+ "based on limited data, such as the website or app you are using, your\n",
430
+ "non-precise location, your device type or which content you are (or\n",
431
+ "have been) interacting with (for example, to limit the number of times\n",
432
+ "an ad is presented to you). Use profiles to select personalised\n",
433
+ "advertising Use profiles to select personalised advertising\n",
434
+ "Advertising presented to you on this service can be based on your\n",
435
+ "advertising profiles, which can reflect your activity on this service\n",
436
+ "or other websites or apps (like the forms you submit, content you look\n",
437
+ "at), possible interests and personal aspects. Create profiles for\n",
438
+ "personalised advertising Create profiles for personalised advertising\n",
439
+ "Information about your activity on this service (such as forms you\n",
440
+ "submit, content you look at) can be stored and combined with other\n",
441
+ "information about you (for example, information from your previous\n",
442
+ "activity on this service and other websites or apps) or similar users.\n",
443
+ "This is then used to build or improve a profile about you (that might\n",
444
+ "include possible interests and personal aspects). Your profile can be\n",
445
+ "used (also later) to present advertising that appears more relevant\n",
446
+ "based on your possible interests by this and other entities. Use\n",
447
+ "profiles to select personalised content Use profiles to select\n",
448
+ "personalised content Content presented to you on this service can be\n",
449
+ "based on your content personalisation profiles, which can reflect your\n",
450
+ "activity on this or other services (for instance, the forms you\n",
451
+ "submit, content you look at), possible interests and personal aspects.\n",
452
+ "This can for example be used to adapt the order in which content is\n",
453
+ "shown to you, so that it is even easier for you to find (non-\n",
454
+ "advertising) content that matches your interests. Create profiles to\n",
455
+ "personalise content Create profiles to personalise content Information\n",
456
+ "about your activity on this service (for instance, forms you submit,\n",
457
+ "non-advertising content you look at) can be stored and combined with\n",
458
+ "other information about you (such as your previous activity on this\n",
459
+ "service or other websites or apps) or similar users. This is then used\n",
460
+ "to build or improve a profile about you (which might for example\n",
461
+ "include possible interests and personal aspects). Your profile can be\n",
462
+ "used (also later) to present content that appears more relevant based\n",
463
+ "on your possible interests, such as by adapting the order in which\n",
464
+ "content is shown to you, so that it is even easier for you to find\n",
465
+ "content that matches your interests. Measure advertising performance\n",
466
+ "Measure advertising performance Information regarding which\n",
467
+ "advertising is presented to you and how you interact with it can be\n",
468
+ "used to determine how well an advert has worked for you or other users\n",
469
+ "and whether the goals of the advertising were reached. For instance,\n",
470
+ "whether you saw an ad, whether you clicked on it, whether it led you\n",
471
+ "to buy a product or visit a website, etc. This is very helpful to\n",
472
+ "understand the relevance of advertising campaigns. Measure content\n",
473
+ "performance Measure content performance Information regarding which\n",
474
+ "content is presented to you and how you interact with it can be used\n",
475
+ "to determine whether the (non-advertising) content e.g. reached its\n",
476
+ "intended audience and matched your interests. For instance, whether\n",
477
+ "you read an article, watch a video, listen to a podcast or look at a\n",
478
+ "product description, how long you spent on this service and the web\n",
479
+ "pages you visit etc. This is very helpful to understand the relevance\n",
480
+ "of (non-advertising) content that is shown to you. Understand\n",
481
+ "audiences through statistics or combinations of data from different\n",
482
+ "sources Understand audiences through statistics or combinations of\n",
483
+ "data from different sources Reports can be generated based on the\n",
484
+ "combination of data sets (like user profiles, statistics, market\n",
485
+ "research, analytics data) regarding your interactions and those of\n",
486
+ "other users with advertising or (non-advertising) content to identify\n",
487
+ "common characteristics (for instance, to determine which target\n",
488
+ "audiences are more receptive to an ad campaign or to certain\n",
489
+ "contents). Develop and improve services Develop and improve services\n",
490
+ "Information about your activity on this service, such as your\n",
491
+ "interaction with ads or content, can be very helpful to improve\n",
492
+ "products and services and to build new products and services based on\n",
493
+ "user interactions, the type of audience, etc. This specific purpose\n",
494
+ "does not include the development or improvement of user profiles and\n",
495
+ "identifiers. Ensure security, prevent and detect fraud, and fix errors\n",
496
+ "Always Active Your data can be used to monitor for and prevent unusual\n",
497
+ "and possibly fraudulent activity (for example, regarding advertising,\n",
498
+ "ad clicks by bots), and ensure systems and processes work properly and\n",
499
+ "securely. It can also be used to correct any problems you, the\n",
500
+ "publisher or the advertiser may encounter in the delivery of content\n",
501
+ "and ads and in your interaction with them. Deliver and present\n",
502
+ "advertising and content Always Active Certain information (like an IP\n",
503
+ "address or device capabilities) is used to ensure the technical\n",
504
+ "compatibility of the content or advertising, and to facilitate the\n",
505
+ "transmission of the content or ad to your device. Strictly Necessary\n",
506
+ "Cookies Always Active These cookies are necessary for the website to\n",
507
+ "function and cannot be switched off in our systems. You can set your\n",
508
+ "browser to block or alert you about these cookies, but some parts of\n",
509
+ "the site will not work. Back Button Cookie List Search Icon Filter\n",
510
+ "Icon Clear checkbox label label Apply Cancel Consent Leg.Interest\n",
511
+ "checkbox label label checkbox label label checkbox label label\n",
512
+ "Essential Cookies Only Confirm My Choices\n"
513
+ ]
514
+ }
515
+ ],
516
+ "source": [
517
+ "import textwrap\n",
518
+ "print(textwrap.fill(text))"
519
+ ]
520
+ },
521
+ {
522
+ "cell_type": "code",
523
+ "execution_count": 10,
524
+ "metadata": {},
525
+ "outputs": [],
526
+ "source": [
527
+ "all_tools = tools + [tool_push]"
528
+ ]
529
+ },
530
+ {
531
+ "cell_type": "code",
532
+ "execution_count": 11,
533
+ "metadata": {},
534
+ "outputs": [],
535
+ "source": [
536
+ "\n",
537
+ "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
538
+ "llm_with_tools = llm.bind_tools(all_tools)\n",
539
+ "\n",
540
+ "\n",
541
+ "def chatbot(state: State):\n",
542
+ " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n"
543
+ ]
544
+ },
545
+ {
546
+ "cell_type": "code",
547
+ "execution_count": 12,
548
+ "metadata": {},
549
+ "outputs": [
550
+ {
551
+ "data": {
552
+ "image/png": "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",
553
+ "text/plain": [
554
+ "<IPython.core.display.Image object>"
555
+ ]
556
+ },
557
+ "metadata": {},
558
+ "output_type": "display_data"
559
+ }
560
+ ],
561
+ "source": [
562
+ "\n",
563
+ "graph_builder = StateGraph(State)\n",
564
+ "graph_builder.add_node(\"chatbot\", chatbot)\n",
565
+ "graph_builder.add_node(\"tools\", ToolNode(tools=all_tools))\n",
566
+ "graph_builder.add_conditional_edges( \"chatbot\", tools_condition, \"tools\")\n",
567
+ "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
568
+ "graph_builder.add_edge(START, \"chatbot\")\n",
569
+ "\n",
570
+ "memory = MemorySaver()\n",
571
+ "graph = graph_builder.compile(checkpointer=memory)\n",
572
+ "display(Image(graph.get_graph().draw_mermaid_png()))"
573
+ ]
574
+ },
575
+ {
576
+ "cell_type": "code",
577
+ "execution_count": 13,
578
+ "metadata": {},
579
+ "outputs": [
580
+ {
581
+ "name": "stdout",
582
+ "output_type": "stream",
583
+ "text": [
584
+ "* Running on local URL: http://127.0.0.1:7860\n",
585
+ "* To create a public link, set `share=True` in `launch()`.\n"
586
+ ]
587
+ },
588
+ {
589
+ "data": {
590
+ "text/html": [
591
+ "<div><iframe src=\"http://127.0.0.1:7860/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
592
+ ],
593
+ "text/plain": [
594
+ "<IPython.core.display.HTML object>"
595
+ ]
596
+ },
597
+ "metadata": {},
598
+ "output_type": "display_data"
599
+ },
600
+ {
601
+ "data": {
602
+ "text/plain": []
603
+ },
604
+ "execution_count": 13,
605
+ "metadata": {},
606
+ "output_type": "execute_result"
607
+ }
608
+ ],
609
+ "source": [
610
+ "config = {\"configurable\": {\"thread_id\": \"10\"}}\n",
611
+ "\n",
612
+ "async def chat(user_input: str, history):\n",
613
+ " result = await graph.ainvoke({\"messages\": [{\"role\": \"user\", \"content\": user_input}]}, config=config)\n",
614
+ " return result[\"messages\"][-1].content\n",
615
+ "\n",
616
+ "\n",
617
+ "gr.ChatInterface(chat, type=\"messages\").launch()"
618
+ ]
619
+ },
620
+ {
621
+ "cell_type": "code",
622
+ "execution_count": null,
623
+ "metadata": {},
624
+ "outputs": [],
625
+ "source": []
626
+ }
627
+ ],
628
+ "metadata": {
629
+ "kernelspec": {
630
+ "display_name": ".venv",
631
+ "language": "python",
632
+ "name": "python3"
633
+ },
634
+ "language_info": {
635
+ "codemirror_mode": {
636
+ "name": "ipython",
637
+ "version": 3
638
+ },
639
+ "file_extension": ".py",
640
+ "mimetype": "text/x-python",
641
+ "name": "python",
642
+ "nbconvert_exporter": "python",
643
+ "pygments_lexer": "ipython3",
644
+ "version": "3.12.7"
645
+ }
646
+ },
647
+ "nbformat": 4,
648
+ "nbformat_minor": 2
649
+ }
4_lab4.ipynb ADDED
@@ -0,0 +1,464 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "## Week 4 Day 4 - preparing the big project!\n",
8
+ "\n",
9
+ "# The Sidekick\n",
10
+ "\n",
11
+ "It's time to introduce:\n",
12
+ "\n",
13
+ "1. Structured Outputs\n",
14
+ "2. A multi-agent flow"
15
+ ]
16
+ },
17
+ {
18
+ "cell_type": "code",
19
+ "execution_count": 1,
20
+ "metadata": {},
21
+ "outputs": [],
22
+ "source": [
23
+ "from typing import Annotated, TypedDict, List, Dict, Any, Optional\n",
24
+ "from typing_extensions import TypedDict\n",
25
+ "from langchain_core.messages import AIMessage, HumanMessage, SystemMessage\n",
26
+ "from langchain_openai import ChatOpenAI\n",
27
+ "from langchain_community.agent_toolkits import PlayWrightBrowserToolkit\n",
28
+ "from langchain_community.tools.playwright.utils import create_async_playwright_browser\n",
29
+ "from langgraph.graph import StateGraph, START, END\n",
30
+ "from langgraph.checkpoint.memory import MemorySaver\n",
31
+ "from langgraph.prebuilt import ToolNode, tools_condition\n",
32
+ "from langgraph.graph.message import add_messages\n",
33
+ "from pydantic import BaseModel, Field\n",
34
+ "from IPython.display import Image, display\n",
35
+ "import gradio as gr\n",
36
+ "import uuid\n",
37
+ "from dotenv import load_dotenv"
38
+ ]
39
+ },
40
+ {
41
+ "cell_type": "code",
42
+ "execution_count": 2,
43
+ "metadata": {},
44
+ "outputs": [
45
+ {
46
+ "data": {
47
+ "text/plain": [
48
+ "True"
49
+ ]
50
+ },
51
+ "execution_count": 2,
52
+ "metadata": {},
53
+ "output_type": "execute_result"
54
+ }
55
+ ],
56
+ "source": [
57
+ "load_dotenv(override=True)"
58
+ ]
59
+ },
60
+ {
61
+ "cell_type": "markdown",
62
+ "metadata": {},
63
+ "source": [
64
+ "### For structured outputs, we define a Pydantic object for the Schema"
65
+ ]
66
+ },
67
+ {
68
+ "cell_type": "code",
69
+ "execution_count": 3,
70
+ "metadata": {},
71
+ "outputs": [],
72
+ "source": [
73
+ "# First define a structured output\n",
74
+ "\n",
75
+ "class EvaluatorOutput(BaseModel):\n",
76
+ " feedback: str = Field(description=\"Feedback on the assistant's response\")\n",
77
+ " success_criteria_met: bool = Field(description=\"Whether the success criteria have been met\")\n",
78
+ " user_input_needed: bool = Field(description=\"True if more input is needed from the user, or clarifications, or the assistant is stuck\")\n"
79
+ ]
80
+ },
81
+ {
82
+ "cell_type": "markdown",
83
+ "metadata": {},
84
+ "source": [
85
+ "### And for the State, we'll use TypedDict again\n",
86
+ "\n",
87
+ "But now we have some real information to maintain!\n",
88
+ "\n",
89
+ "The messages uses the reducer. The others are simply values that we overwrite with any state change."
90
+ ]
91
+ },
92
+ {
93
+ "cell_type": "code",
94
+ "execution_count": 4,
95
+ "metadata": {},
96
+ "outputs": [],
97
+ "source": [
98
+ "# The state\n",
99
+ "\n",
100
+ "class State(TypedDict):\n",
101
+ " messages: Annotated[List[Any], add_messages]\n",
102
+ " success_criteria: str\n",
103
+ " feedback_on_work: Optional[str]\n",
104
+ " success_criteria_met: bool\n",
105
+ " user_input_needed: bool"
106
+ ]
107
+ },
108
+ {
109
+ "cell_type": "code",
110
+ "execution_count": 5,
111
+ "metadata": {},
112
+ "outputs": [],
113
+ "source": [
114
+ "# Get our async Playwright tools\n",
115
+ "\n",
116
+ "import nest_asyncio\n",
117
+ "nest_asyncio.apply()\n",
118
+ "async_browser = create_async_playwright_browser(headless=False) # headful mode\n",
119
+ "toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=async_browser)\n",
120
+ "tools = toolkit.get_tools()"
121
+ ]
122
+ },
123
+ {
124
+ "cell_type": "code",
125
+ "execution_count": 6,
126
+ "metadata": {},
127
+ "outputs": [],
128
+ "source": [
129
+ "# Initialize the LLMs\n",
130
+ "\n",
131
+ "worker_llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
132
+ "worker_llm_with_tools = worker_llm.bind_tools(tools)\n",
133
+ "\n",
134
+ "evaluator_llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
135
+ "evaluator_llm_with_output = evaluator_llm.with_structured_output(EvaluatorOutput)"
136
+ ]
137
+ },
138
+ {
139
+ "cell_type": "code",
140
+ "execution_count": 7,
141
+ "metadata": {},
142
+ "outputs": [],
143
+ "source": [
144
+ "# The worker node\n",
145
+ "\n",
146
+ "def worker(state: State) -> Dict[str, Any]:\n",
147
+ " system_message = f\"\"\"You are a helpful assistant that can use tools to complete tasks.\n",
148
+ "You keep working on a task until either you have a question or clarification for the user, or the success criteria is met.\n",
149
+ "This is the success criteria:\n",
150
+ "{state['success_criteria']}\n",
151
+ "You should reply either with a question for the user about this assignment, or with your final response.\n",
152
+ "If you have a question for the user, you need to reply by clearly stating your question. An example might be:\n",
153
+ "\n",
154
+ "Question: please clarify whether you want a summary or a detailed answer\n",
155
+ "\n",
156
+ "If you've finished, reply with the final answer, and don't ask a question; simply reply with the answer.\n",
157
+ "\"\"\"\n",
158
+ " \n",
159
+ " if state.get(\"feedback_on_work\"):\n",
160
+ " system_message += f\"\"\"\n",
161
+ "Previously you thought you completed the assignment, but your reply was rejected because the success criteria was not met.\n",
162
+ "Here is the feedback on why this was rejected:\n",
163
+ "{state['feedback_on_work']}\n",
164
+ "With this feedback, please continue the assignment, ensuring that you meet the success criteria or have a question for the user.\"\"\"\n",
165
+ " \n",
166
+ " # Add in the system message\n",
167
+ "\n",
168
+ " found_system_message = False\n",
169
+ " messages = state[\"messages\"]\n",
170
+ " for message in messages:\n",
171
+ " if isinstance(message, SystemMessage):\n",
172
+ " message.content = system_message\n",
173
+ " found_system_message = True\n",
174
+ " \n",
175
+ " if not found_system_message:\n",
176
+ " messages = [SystemMessage(content=system_message)] + messages\n",
177
+ " \n",
178
+ " # Invoke the LLM with tools\n",
179
+ " response = worker_llm_with_tools.invoke(messages)\n",
180
+ " \n",
181
+ " # Return updated state\n",
182
+ " return {\n",
183
+ " \"messages\": [response],\n",
184
+ " }"
185
+ ]
186
+ },
187
+ {
188
+ "cell_type": "code",
189
+ "execution_count": 8,
190
+ "metadata": {},
191
+ "outputs": [],
192
+ "source": [
193
+ "def worker_router(state: State) -> str:\n",
194
+ " last_message = state[\"messages\"][-1]\n",
195
+ " \n",
196
+ " if hasattr(last_message, \"tool_calls\") and last_message.tool_calls:\n",
197
+ " return \"tools\"\n",
198
+ " else:\n",
199
+ " return \"evaluator\""
200
+ ]
201
+ },
202
+ {
203
+ "cell_type": "code",
204
+ "execution_count": 9,
205
+ "metadata": {},
206
+ "outputs": [],
207
+ "source": [
208
+ "def format_conversation(messages: List[Any]) -> str:\n",
209
+ " conversation = \"Conversation history:\\n\\n\"\n",
210
+ " for message in messages:\n",
211
+ " if isinstance(message, HumanMessage):\n",
212
+ " conversation += f\"User: {message.content}\\n\"\n",
213
+ " elif isinstance(message, AIMessage):\n",
214
+ " text = message.content or \"[Tools use]\"\n",
215
+ " conversation += f\"Assistant: {text}\\n\"\n",
216
+ " return conversation"
217
+ ]
218
+ },
219
+ {
220
+ "cell_type": "code",
221
+ "execution_count": 10,
222
+ "metadata": {},
223
+ "outputs": [],
224
+ "source": [
225
+ "def evaluator(state: State) -> State:\n",
226
+ " last_response = state[\"messages\"][-1].content\n",
227
+ "\n",
228
+ " system_message = f\"\"\"You are an evaluator that determines if a task has been completed successfully by an Assistant.\n",
229
+ "Assess the Assistant's last response based on the given criteria. Respond with your feedback, and with your decision on whether the success criteria has been met,\n",
230
+ "and whether more input is needed from the user.\"\"\"\n",
231
+ " \n",
232
+ " user_message = f\"\"\"You are evaluating a conversation between the User and Assistant. You decide what action to take based on the last response from the Assistant.\n",
233
+ "\n",
234
+ "The entire conversation with the assistant, with the user's original request and all replies, is:\n",
235
+ "{format_conversation(state['messages'])}\n",
236
+ "\n",
237
+ "The success criteria for this assignment is:\n",
238
+ "{state['success_criteria']}\n",
239
+ "\n",
240
+ "And the final response from the Assistant that you are evaluating is:\n",
241
+ "{last_response}\n",
242
+ "\n",
243
+ "Respond with your feedback, and decide if the success criteria is met by this response.\n",
244
+ "Also, decide if more user input is required, either because the assistant has a question, needs clarification, or seems to be stuck and unable to answer without help.\n",
245
+ "\"\"\"\n",
246
+ " if state[\"feedback_on_work\"]:\n",
247
+ " user_message += f\"Also, note that in a prior attempt from the Assistant, you provided this feedback: {state['feedback_on_work']}\\n\"\n",
248
+ " user_message += \"If you're seeing the Assistant repeating the same mistakes, then consider responding that user input is required.\"\n",
249
+ " \n",
250
+ " evaluator_messages = [SystemMessage(content=system_message), HumanMessage(content=user_message)]\n",
251
+ "\n",
252
+ " eval_result = evaluator_llm_with_output.invoke(evaluator_messages)\n",
253
+ " new_state = {\n",
254
+ " \"messages\": [{\"role\": \"assistant\", \"content\": f\"Evaluator Feedback on this answer: {eval_result.feedback}\"}],\n",
255
+ " \"feedback_on_work\": eval_result.feedback,\n",
256
+ " \"success_criteria_met\": eval_result.success_criteria_met,\n",
257
+ " \"user_input_needed\": eval_result.user_input_needed\n",
258
+ " }\n",
259
+ " return new_state"
260
+ ]
261
+ },
262
+ {
263
+ "cell_type": "code",
264
+ "execution_count": 11,
265
+ "metadata": {},
266
+ "outputs": [],
267
+ "source": [
268
+ "def route_based_on_evaluation(state: State) -> str:\n",
269
+ " if state[\"success_criteria_met\"] or state[\"user_input_needed\"]:\n",
270
+ " return \"END\"\n",
271
+ " else:\n",
272
+ " return \"worker\""
273
+ ]
274
+ },
275
+ {
276
+ "cell_type": "code",
277
+ "execution_count": 12,
278
+ "metadata": {},
279
+ "outputs": [],
280
+ "source": [
281
+ "# Set up Graph Builder with State\n",
282
+ "graph_builder = StateGraph(State)\n",
283
+ "\n",
284
+ "# Add nodes\n",
285
+ "graph_builder.add_node(\"worker\", worker)\n",
286
+ "graph_builder.add_node(\"tools\", ToolNode(tools=tools))\n",
287
+ "graph_builder.add_node(\"evaluator\", evaluator)\n",
288
+ "\n",
289
+ "# Add edges\n",
290
+ "graph_builder.add_conditional_edges(\"worker\", worker_router, {\"tools\": \"tools\", \"evaluator\": \"evaluator\"})\n",
291
+ "graph_builder.add_edge(\"tools\", \"worker\")\n",
292
+ "graph_builder.add_conditional_edges(\"evaluator\", route_based_on_evaluation, {\"worker\": \"worker\", \"END\": END})\n",
293
+ "graph_builder.add_edge(START, \"worker\")\n",
294
+ "\n",
295
+ "# Compile the graph\n",
296
+ "memory = MemorySaver()\n",
297
+ "graph = graph_builder.compile(checkpointer=memory)"
298
+ ]
299
+ },
300
+ {
301
+ "cell_type": "code",
302
+ "execution_count": 13,
303
+ "metadata": {},
304
+ "outputs": [
305
+ {
306
+ "data": {
307
+ "image/png": "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",
308
+ "text/plain": [
309
+ "<IPython.core.display.Image object>"
310
+ ]
311
+ },
312
+ "metadata": {},
313
+ "output_type": "display_data"
314
+ }
315
+ ],
316
+ "source": [
317
+ "display(Image(graph.get_graph().draw_mermaid_png()))"
318
+ ]
319
+ },
320
+ {
321
+ "cell_type": "markdown",
322
+ "metadata": {},
323
+ "source": [
324
+ "### Next comes the gradio Callback to kick off a super-step"
325
+ ]
326
+ },
327
+ {
328
+ "cell_type": "code",
329
+ "execution_count": 14,
330
+ "metadata": {},
331
+ "outputs": [],
332
+ "source": [
333
+ "def make_thread_id() -> str:\n",
334
+ " return str(uuid.uuid4())\n",
335
+ "\n",
336
+ "\n",
337
+ "async def process_message(message, success_criteria, history, thread):\n",
338
+ "\n",
339
+ " config = {\"configurable\": {\"thread_id\": thread}}\n",
340
+ "\n",
341
+ " state = {\n",
342
+ " \"messages\": message,\n",
343
+ " \"success_criteria\": success_criteria,\n",
344
+ " \"feedback_on_work\": None,\n",
345
+ " \"success_criteria_met\": False,\n",
346
+ " \"user_input_needed\": False\n",
347
+ " }\n",
348
+ " result = await graph.ainvoke(state, config=config)\n",
349
+ " user = {\"role\": \"user\", \"content\": message}\n",
350
+ " reply = {\"role\": \"assistant\", \"content\": result[\"messages\"][-2].content}\n",
351
+ " feedback = {\"role\": \"assistant\", \"content\": result[\"messages\"][-1].content}\n",
352
+ " return history + [user, reply, feedback]\n",
353
+ "\n",
354
+ "async def reset():\n",
355
+ " return \"\", \"\", None, make_thread_id()\n",
356
+ "\n"
357
+ ]
358
+ },
359
+ {
360
+ "cell_type": "markdown",
361
+ "metadata": {},
362
+ "source": [
363
+ "### And now launch our Sidekick UI"
364
+ ]
365
+ },
366
+ {
367
+ "cell_type": "code",
368
+ "execution_count": 15,
369
+ "metadata": {},
370
+ "outputs": [
371
+ {
372
+ "name": "stdout",
373
+ "output_type": "stream",
374
+ "text": [
375
+ "* Running on local URL: http://127.0.0.1:7860\n",
376
+ "* To create a public link, set `share=True` in `launch()`.\n"
377
+ ]
378
+ },
379
+ {
380
+ "data": {
381
+ "text/html": [
382
+ "<div><iframe src=\"http://127.0.0.1:7860/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
383
+ ],
384
+ "text/plain": [
385
+ "<IPython.core.display.HTML object>"
386
+ ]
387
+ },
388
+ "metadata": {},
389
+ "output_type": "display_data"
390
+ },
391
+ {
392
+ "data": {
393
+ "text/plain": []
394
+ },
395
+ "execution_count": 15,
396
+ "metadata": {},
397
+ "output_type": "execute_result"
398
+ }
399
+ ],
400
+ "source": [
401
+ "\n",
402
+ "with gr.Blocks(theme=gr.themes.Default(primary_hue=\"emerald\")) as demo:\n",
403
+ " gr.Markdown(\"## Sidekick Personal Co-worker\")\n",
404
+ " thread = gr.State(make_thread_id())\n",
405
+ " \n",
406
+ " with gr.Row():\n",
407
+ " chatbot = gr.Chatbot(label=\"Sidekick\", height=300, type=\"messages\")\n",
408
+ " with gr.Group():\n",
409
+ " with gr.Row():\n",
410
+ " message = gr.Textbox(show_label=False, placeholder=\"Your request to your sidekick\")\n",
411
+ " with gr.Row():\n",
412
+ " success_criteria = gr.Textbox(show_label=False, placeholder=\"What are your success critiera?\")\n",
413
+ " with gr.Row():\n",
414
+ " reset_button = gr.Button(\"Reset\", variant=\"stop\")\n",
415
+ " go_button = gr.Button(\"Go!\", variant=\"primary\")\n",
416
+ " message.submit(process_message, [message, success_criteria, chatbot, thread], [chatbot])\n",
417
+ " success_criteria.submit(process_message, [message, success_criteria, chatbot, thread], [chatbot])\n",
418
+ " go_button.click(process_message, [message, success_criteria, chatbot, thread], [chatbot])\n",
419
+ " reset_button.click(reset, [], [message, success_criteria, chatbot, thread])\n",
420
+ "\n",
421
+ " \n",
422
+ "demo.launch()"
423
+ ]
424
+ },
425
+ {
426
+ "cell_type": "markdown",
427
+ "metadata": {},
428
+ "source": [
429
+ "<table style=\"margin: 0; text-align: left; width:100%\">\n",
430
+ " <tr>\n",
431
+ " <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
432
+ " <img src=\"../assets/thanks.png\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
433
+ " </td>\n",
434
+ " <td>\n",
435
+ " <h2 style=\"color:#00cc00;\">Congratulations on making the first version of Sidekick!</h2>\n",
436
+ " <span style=\"color:#00cc00;\">This is a pretty epic moment in the course. You've made the start of something very powerful. And you've upskilled on an impressive Agent framework in LangGraph. Maybe like me you're being converted from a LangGraph skeptic to a LangGraph fan..<br/><br/>My editor would kill me if I didn't mention again: if you're able to rate the course on Udemy, I'd be so very grateful: it's the main way that Udemy decides whether to show the course to others and it makes a massive difference.<br/><br/>And another reminder that I love <a href=\"https://www.linkedin.com/in/eddonner/\">connecting on LinkedIn</a> if you haven't yet! If you wanted to post about your progress on the course, please tag me and I'll weigh in to increase your exposure.\n",
437
+ " </span>\n",
438
+ " </td>\n",
439
+ " </tr>"
440
+ ]
441
+ }
442
+ ],
443
+ "metadata": {
444
+ "kernelspec": {
445
+ "display_name": ".venv",
446
+ "language": "python",
447
+ "name": "python3"
448
+ },
449
+ "language_info": {
450
+ "codemirror_mode": {
451
+ "name": "ipython",
452
+ "version": 3
453
+ },
454
+ "file_extension": ".py",
455
+ "mimetype": "text/x-python",
456
+ "name": "python",
457
+ "nbconvert_exporter": "python",
458
+ "pygments_lexer": "ipython3",
459
+ "version": "3.12.7"
460
+ }
461
+ },
462
+ "nbformat": 4,
463
+ "nbformat_minor": 2
464
+ }
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
  title: Sidekick
3
- emoji: 😻
4
- colorFrom: red
5
- colorTo: gray
6
- sdk: gradio
7
- sdk_version: 5.37.0
8
  app_file: app.py
9
- pinned: false
 
10
  ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Sidekick
 
 
 
 
 
3
  app_file: app.py
4
+ sdk: gradio
5
+ sdk_version: 5.33.1
6
  ---
 
 
__pycache__/sidekick.cpython-312.pyc ADDED
Binary file (12.1 kB). View file
 
__pycache__/sidekick_tools.cpython-312.pyc ADDED
Binary file (2.97 kB). View file
 
app.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from sidekick import Sidekick
3
+
4
+
5
+ async def setup():
6
+ sidekick = Sidekick()
7
+ await sidekick.setup()
8
+ return sidekick
9
+
10
+ async def process_message(sidekick, message, success_criteria, history):
11
+ results = await sidekick.run_superstep(message, success_criteria, history)
12
+ return results, sidekick
13
+
14
+ async def reset():
15
+ new_sidekick = Sidekick()
16
+ await new_sidekick.setup()
17
+ return "", "", None, new_sidekick
18
+
19
+ def free_resources(sidekick):
20
+ print("Cleaning up")
21
+ try:
22
+ if sidekick:
23
+ sidekick.free_resources()
24
+ except Exception as e:
25
+ print(f"Exception during cleanup: {e}")
26
+
27
+
28
+ with gr.Blocks(title="Sidekick", theme=gr.themes.Default(primary_hue="emerald")) as ui:
29
+ gr.Markdown("## Sidekick Personal Co-Worker")
30
+ sidekick = gr.State(delete_callback=free_resources)
31
+
32
+ with gr.Row():
33
+ chatbot = gr.Chatbot(label="Sidekick", height=300, type="messages")
34
+ with gr.Group():
35
+ with gr.Row():
36
+ message = gr.Textbox(show_label=False, placeholder="Your request to the Sidekick")
37
+ with gr.Row():
38
+ success_criteria = gr.Textbox(show_label=False, placeholder="What are your success critiera?")
39
+ with gr.Row():
40
+ reset_button = gr.Button("Reset", variant="stop")
41
+ go_button = gr.Button("Go!", variant="primary")
42
+
43
+ ui.load(setup, [], [sidekick])
44
+ message.submit(process_message, [sidekick, message, success_criteria, chatbot], [chatbot, sidekick])
45
+ success_criteria.submit(process_message, [sidekick, message, success_criteria, chatbot], [chatbot, sidekick])
46
+ go_button.click(process_message, [sidekick, message, success_criteria, chatbot], [chatbot, sidekick])
47
+ reset_button.click(reset, [], [message, success_criteria, chatbot, sidekick])
48
+
49
+
50
+ ui.launch(inbrowser=True)
community_contributions/SideKick(Ugraded)/app.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ load_dotenv(override=True)
4
+
5
+ import gradio as gr
6
+ from sidekick_tools import create_calendar_event, list_upcoming_events
7
+ from sidekick import Sidekick
8
+
9
+ async def setup():
10
+ sidekick = Sidekick()
11
+ await sidekick.setup()
12
+ return sidekick
13
+
14
+ async def process_message(sidekick, message, success_criteria, history):
15
+ results = await sidekick.run_superstep(message, success_criteria, history)
16
+ return results, sidekick
17
+
18
+ async def reset():
19
+ new_sidekick = Sidekick()
20
+ await new_sidekick.setup()
21
+ return "", "", None, new_sidekick
22
+
23
+
24
+ def free_resources(sidekick):
25
+ print("Cleaning up")
26
+ try:
27
+ if sidekick:
28
+ sidekick.cleanup()
29
+ except Exception as e:
30
+ print(f"Exception during cleanup: {e}")
31
+
32
+ # Gradio UI
33
+ with gr.Blocks(title="Sidekick", theme=gr.themes.Default(primary_hue="emerald")) as ui:
34
+ gr.Markdown("## Sidekick Personal Co-Worker")
35
+ sidekick = gr.State(delete_callback=free_resources)
36
+
37
+ with gr.Row():
38
+ chatbot = gr.Chatbot(label="Sidekick", height=300, type="messages")
39
+ with gr.Group():
40
+ with gr.Row():
41
+ message = gr.Textbox(show_label=False, placeholder="Your request to the Sidekick")
42
+ with gr.Row():
43
+ success_criteria = gr.Textbox(show_label=False, placeholder="What are your success criteria?")
44
+ with gr.Row():
45
+ reset_button = gr.Button("Reset", variant="stop")
46
+ go_button = gr.Button("Go!", variant="primary")
47
+
48
+ # Calendar Accordion
49
+ with gr.Accordion("📆 Calendar", open=False):
50
+ cal_summary = gr.Textbox(label="Event Title")
51
+ cal_start = gr.Textbox(label="Start (RFC3339)", placeholder="2025-05-20T15:00:00+05:30")
52
+ cal_end = gr.Textbox(label="End (RFC3339)", placeholder="2025-05-20T16:00:00+05:30")
53
+ cal_description = gr.Textbox(label="Description (optional)")
54
+ add_event_btn = gr.Button("Add Event")
55
+ list_events_btn = gr.Button("List Upcoming Events")
56
+ cal_output = gr.Textbox(label="Calendar Output", interactive=False)
57
+
58
+ # Bind main functions
59
+ ui.load(setup, [], [sidekick])
60
+ message.submit(process_message, [sidekick, message, success_criteria, chatbot], [chatbot, sidekick])
61
+ success_criteria.submit(process_message, [sidekick, message, success_criteria, chatbot], [chatbot, sidekick])
62
+ go_button.click(process_message, [sidekick, message, success_criteria, chatbot], [chatbot, sidekick])
63
+ reset_button.click(reset, [], [message, success_criteria, chatbot, sidekick])
64
+
65
+ # Bind calendar tools
66
+ add_event_btn.click(
67
+ fn=lambda summary, start, end, desc: create_calendar_event(
68
+ summary, start, end, desc, os.getenv("GOOGLE_CALENDAR_ID", "primary")
69
+ ),
70
+ inputs=[cal_summary, cal_start, cal_end, cal_description],
71
+ outputs=cal_output
72
+ )
73
+ list_events_btn.click(
74
+ fn=lambda: list_upcoming_events(os.getenv("GOOGLE_CALENDAR_ID", "primary")),
75
+ outputs=cal_output
76
+ )
77
+
78
+ ui.launch(inbrowser=True)
community_contributions/SideKick(Ugraded)/readme.md ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Sidekick: Your Personal Co-Worker AI Agent
2
+
3
+ Sidekick is an agentic AI assistant built with **Langgraph**, **LangChain**, and **Gradio**. It can:
4
+
5
+ - **Google Calendar Integration**: Create and list events via the Calendar API.
6
+ - **Multi-Agent Orchestration**:
7
+ - **PlannerAgent**: Decomposes tasks into subtasks.
8
+ - **ResearchAgent**: Performs web searches & fetches summaries.
9
+ - **CodeAgent**: Writes and debugs code.
10
+ - **Evaluator Loop**: Ensures responses meet success criteria or asks clarifying questions.
11
+ - **Push Notifications**: (Optional) Send reminders via Pushover.
12
+
13
+ ---
14
+
15
+ ## 🛠️ Prerequisites
16
+
17
+ - **Python 3.8+**
18
+ - **Google Cloud Project** with Calendar API enabled
19
+ - **OAuth 2.0 Client Credentials** (`credentials.json`)
20
+ - **Pushover** account & tokens (optional)
21
+ - **Virtualenv** or Conda environment
22
+
23
+ ---
24
+
25
+ ## 🚀 Installation
26
+
27
+ 1. **Clone the repository**
28
+
29
+ ```bash
30
+ git clone https://github.com/yourusername/sidekick-agent.git
31
+ cd sidekick-agent
32
+ ```
33
+
34
+ 2. **Create & activate a virtual environment**
35
+
36
+ ```bash
37
+ python3 -m venv .venv
38
+ source .venv/bin/activate # macOS/Linux
39
+ .venv\Scripts\activate # Windows
40
+ ```
41
+
42
+ 3. **Install dependencies**
43
+
44
+ ```bash
45
+ pip install --upgrade pip
46
+ pip install \
47
+ python-dotenv \
48
+ gradio \
49
+ playwright \
50
+ google-api-python-client \
51
+ google-auth-httplib2 \
52
+ google-auth-oauthlib \
53
+ langchain \
54
+ langchain-community \
55
+ langchain-experimental \
56
+ langchain-openai \
57
+ langgraph \
58
+ pydantic \
59
+ requests
60
+ playwright install chromium
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 🔑 Configuration
66
+
67
+ Create a `.env` file in the project root:
68
+
69
+ ```ini
70
+ # OpenAI
71
+ OPENAI_API_KEY=sk-…
72
+
73
+ # Pushover (optional)
74
+ PUSHOVER_TOKEN=your_pushover_app_token
75
+ PUSHOVER_USER=your_pushover_user_key
76
+
77
+ # Google Calendar
78
+ GOOGLE_TOKEN_PATH=token.json
79
+ GOOGLE_CALENDAR_ID=primary
80
+
81
+ # File-Toolkit root (optional)
82
+ FILE_TOOL_ROOT=./sandbox
83
+
84
+ # Timezone for RFC3339 formatting
85
+ TIMEZONE_OFFSET=+05:30
86
+ ```
87
+
88
+ ---
89
+
90
+ ## 🔑 OAuth2 Quickstart (Generate `token.json`)
91
+
92
+ 1. **Enable the Calendar API** in Google Cloud Console.
93
+ 2. Download your OAuth **client_secrets** file and save as `credentials.json` in the root.
94
+ 3. Run this script once to authorize and generate `token.json`:
95
+ ```python
96
+ from google_auth_oauthlib.flow import InstalledAppFlow
97
+
98
+ SCOPES = ["https://www.googleapis.com/auth/calendar"]
99
+ flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
100
+ creds = flow.run_local_server(port=0)
101
+ with open("token.json", "w") as token:
102
+ token.write(creds.to_json())
103
+ ```
104
+ 4. Confirm `token.json` is present alongside `credentials.json`.
105
+
106
+ ---
107
+
108
+ ## 🔧 Usage
109
+
110
+ Activate the environment and launch the Gradio app:
111
+
112
+ ```bash
113
+ # Activate venv
114
+ source .venv/bin/activate # macOS/Linux
115
+ .venv\Scripts\activate # Windows
116
+
117
+ # Run the app
118
+ python app.py
119
+ ```
120
+
121
+ - Open the displayed local URL in your browser.
122
+ - Chat with Sidekick!
123
+ - Expand the “📆 Calendar” accordion to create or list events.
124
+ - Provide success criteria to guide the assistant.
125
+ - Use “Reset” to start a new session.
126
+
127
+ ---
128
+
129
+ ## ❓ Troubleshooting
130
+
131
+ - **403 Insufficient Permission**: Delete `token.json` and re-run the OAuth quickstart to grant the full calendar scope.
132
+ - **ModuleNotFoundError**: Ensure you installed packages inside the activated venv with `python -m pip install ...`.
133
+ - **Playwright errors**: Run `playwright install chromium` again in your environment.
134
+
135
+ ---
136
+
137
+ ## 📁 Project Structure
138
+
139
+ ```
140
+ .
141
+ ├── app.py
142
+ ├── sidekick.py
143
+ ├── sidekick_tools.py
144
+ ├── credentials.json # OAuth client secrets
145
+ ├── token.json # OAuth tokens (auto-generated)
146
+ ├── .env
147
+ ├── sandbox/ # (optional) for file-toolkit writes
148
+ └── test_insert_event.py # Quickstart test for Calendar write
149
+ ```
150
+
151
+ ---
152
+
153
+ ## 📄 License
154
+
155
+ Released under the **MIT License**. Feel free to fork, tweak, and contribute!
community_contributions/SideKick(Ugraded)/sidekick.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+ import asyncio
4
+ from datetime import datetime
5
+ from typing import List, Any, Optional, Dict, Annotated
6
+ from typing_extensions import TypedDict
7
+ from pydantic import BaseModel, Field
8
+ from dotenv import load_dotenv
9
+ load_dotenv(override=True)
10
+
11
+ from langchain_openai import ChatOpenAI
12
+ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
13
+ from langgraph.graph import StateGraph, START, END
14
+ from langgraph.prebuilt import ToolNode
15
+ from langgraph.graph.message import add_messages
16
+ from langgraph.checkpoint.memory import MemorySaver
17
+
18
+ from sidekick_tools import playwright_tools, other_tools, calendar_tools
19
+
20
+ class State(TypedDict):
21
+ messages: Annotated[List[Any], add_messages]
22
+ success_criteria: str
23
+ feedback_on_work: Optional[str]
24
+ success_criteria_met: bool
25
+ user_input_needed: bool
26
+ subtasks: Optional[List[str]]
27
+
28
+ class EvaluatorOutput(BaseModel):
29
+ feedback: str = Field(description="Feedback on the assistant's response")
30
+ success_criteria_met: bool = Field(description="Whether success criteria met")
31
+ user_input_needed: bool = Field(description="Whether more input is needed from the user")
32
+
33
+ class Sidekick:
34
+ def __init__(self):
35
+ self.tools = []
36
+ self.worker_llm_with_tools = None
37
+ self.planner = None
38
+ self.research = None
39
+ self.code = None
40
+ self.evaluator_llm_with_output = None
41
+ self.graph = None
42
+ self.memory = MemorySaver()
43
+ self.browser = None
44
+ self.playwright = None
45
+ self.sidekick_id = str(uuid.uuid4())
46
+
47
+ async def setup(self):
48
+ self.tools, self.browser, self.playwright = await playwright_tools()
49
+ self.tools += await other_tools()
50
+ self.tools += calendar_tools()
51
+
52
+ worker_llm = ChatOpenAI(model="gpt-4o-mini")
53
+ self.worker_llm_with_tools = worker_llm.bind_tools(self.tools)
54
+
55
+ planner_llm = ChatOpenAI(model="gpt-4o-mini", system_message="You are PlannerAgent: decompose tasks into subtasks.")
56
+ research_llm = ChatOpenAI(model="gpt-4o-mini", system_message="You are ResearchAgent: retrieve facts and summaries.")
57
+ code_llm = ChatOpenAI(model="gpt-4o-mini", system_message="You are CodeAgent: write and debug code.")
58
+ self.planner = planner_llm.bind_tools(self.tools)
59
+ self.research = research_llm.bind_tools(self.tools)
60
+ self.code = code_llm.bind_tools(self.tools)
61
+
62
+ evaluator_llm = ChatOpenAI(model="gpt-4o-mini")
63
+ self.evaluator_llm_with_output = evaluator_llm.with_structured_output(EvaluatorOutput)
64
+
65
+ await self.build_graph()
66
+
67
+
68
+ def worker(self, state: State) -> Dict[str, Any]:
69
+ system_message = f"""You are a helpful assistant that can use tools to complete tasks.
70
+ You keep working on a task until either you have a question or clarification for the user, or the success criteria is met.
71
+ You have many tools to help you, including tools to browse the internet, navigating and retrieving web pages.
72
+ You have a tool to run python code, but note that you would need to include a print() statement if you wanted to receive output.
73
+ The current date and time is {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
74
+
75
+ This is the success criteria:
76
+ {state['success_criteria']}
77
+ You should reply either with a question for the user about this assignment, or with your final response.
78
+ If you have a question for the user, you need to reply by clearly stating your question. An example might be:
79
+
80
+ Question: please clarify whether you want a summary or a detailed answer
81
+
82
+ If you've finished, reply with the final answer, and don't ask a question; simply reply with the answer.
83
+ """
84
+
85
+ if state.get("feedback_on_work"):
86
+ system_message += f"""
87
+ Previously you thought you completed the assignment, but your reply was rejected because the success criteria was not met.
88
+ Here is the feedback on why this was rejected:
89
+ {state['feedback_on_work']}
90
+ With this feedback, please continue the assignment, ensuring that you meet the success criteria or have a question for the user."""
91
+
92
+ # Add in the system message
93
+
94
+ found_system_message = False
95
+ messages = state["messages"]
96
+ for message in messages:
97
+ if isinstance(message, SystemMessage):
98
+ message.content = system_message
99
+ found_system_message = True
100
+
101
+ if not found_system_message:
102
+ messages = [SystemMessage(content=system_message)] + messages
103
+
104
+ # Invoke the LLM with tools
105
+ response = self.worker_llm_with_tools.invoke(messages)
106
+
107
+ # Return updated state
108
+ return {
109
+ "messages": [response],
110
+ }
111
+
112
+
113
+ def worker_router(self, state: State) -> str:
114
+ last_message = state["messages"][-1]
115
+
116
+ if hasattr(last_message, "tool_calls") and last_message.tool_calls:
117
+ return "tools"
118
+ else:
119
+ return "evaluator"
120
+
121
+ def format_conversation(self, messages: List[Any]) -> str:
122
+ conversation = "Conversation history:\n\n"
123
+ for message in messages:
124
+ if isinstance(message, HumanMessage):
125
+ conversation += f"User: {message.content}\n"
126
+ elif isinstance(message, AIMessage):
127
+ text = message.content or "[Tools use]"
128
+ conversation += f"Assistant: {text}\n"
129
+ return conversation
130
+
131
+ def evaluator(self, state: State) -> State:
132
+ last_response = state["messages"][-1].content
133
+
134
+ system_message = f"""You are an evaluator that determines if a task has been completed successfully by an Assistant.
135
+ Assess the Assistant's last response based on the given criteria. Respond with your feedback, and with your decision on whether the success criteria has been met,
136
+ and whether more input is needed from the user."""
137
+
138
+ user_message = f"""You are evaluating a conversation between the User and Assistant. You decide what action to take based on the last response from the Assistant.
139
+
140
+ The entire conversation with the assistant, with the user's original request and all replies, is:
141
+ {self.format_conversation(state['messages'])}
142
+
143
+ The success criteria for this assignment is:
144
+ {state['success_criteria']}
145
+
146
+ And the final response from the Assistant that you are evaluating is:
147
+ {last_response}
148
+
149
+ Respond with your feedback, and decide if the success criteria is met by this response.
150
+ Also, decide if more user input is required, either because the assistant has a question, needs clarification, or seems to be stuck and unable to answer without help.
151
+
152
+ The Assistant has access to a tool to write files. If the Assistant says they have written a file, then you can assume they have done so.
153
+ Overall you should give the Assistant the benefit of the doubt if they say they've done something. But you should reject if you feel that more work should go into this.
154
+
155
+ """
156
+ if state["feedback_on_work"]:
157
+ user_message += f"Also, note that in a prior attempt from the Assistant, you provided this feedback: {state['feedback_on_work']}\n"
158
+ user_message += "If you're seeing the Assistant repeating the same mistakes, then consider responding that user input is required."
159
+
160
+ evaluator_messages = [SystemMessage(content=system_message), HumanMessage(content=user_message)]
161
+
162
+ eval_result = self.evaluator_llm_with_output.invoke(evaluator_messages)
163
+ new_state = {
164
+ "messages": [{"role": "assistant", "content": f"Evaluator Feedback on this answer: {eval_result.feedback}"}],
165
+ "feedback_on_work": eval_result.feedback,
166
+ "success_criteria_met": eval_result.success_criteria_met,
167
+ "user_input_needed": eval_result.user_input_needed
168
+ }
169
+ return new_state
170
+
171
+ def route_based_on_evaluation(self, state: State) -> str:
172
+ if state["success_criteria_met"] or state["user_input_needed"]:
173
+ return "END"
174
+ else:
175
+ return "worker"
176
+
177
+
178
+ async def build_graph(self):
179
+ # Set up Graph Builder with State
180
+ graph_builder = StateGraph(State)
181
+
182
+ # Add nodes
183
+ graph_builder.add_node("worker", self.worker)
184
+ graph_builder.add_node("tools", ToolNode(tools=self.tools))
185
+ graph_builder.add_node("evaluator", self.evaluator)
186
+
187
+ # Add edges
188
+ graph_builder.add_conditional_edges("worker", self.worker_router, {"tools": "tools", "evaluator": "evaluator"})
189
+ graph_builder.add_edge("tools", "worker")
190
+ graph_builder.add_conditional_edges("evaluator", self.route_based_on_evaluation, {"worker": "worker", "END": END})
191
+ graph_builder.add_edge(START, "worker")
192
+
193
+ # Compile the graph
194
+ self.graph = graph_builder.compile(checkpointer=self.memory)
195
+
196
+ async def run_superstep(self, message, success_criteria, history):
197
+ config = {"configurable": {"thread_id": self.sidekick_id}}
198
+
199
+ state = {
200
+ "messages": message,
201
+ "success_criteria": success_criteria or "The answer should be clear and accurate",
202
+ "feedback_on_work": None,
203
+ "success_criteria_met": False,
204
+ "user_input_needed": False
205
+ }
206
+ result = await self.graph.ainvoke(state, config=config)
207
+ user = {"role": "user", "content": message}
208
+ reply = {"role": "assistant", "content": result["messages"][-2].content}
209
+ feedback = {"role": "assistant", "content": result["messages"][-1].content}
210
+ return history + [user, reply, feedback]
211
+
212
+ def cleanup(self):
213
+ if self.browser:
214
+ try:
215
+ loop = asyncio.get_running_loop()
216
+ loop.create_task(self.browser.close())
217
+ if self.playwright:
218
+ loop.create_task(self.playwright.stop())
219
+ except RuntimeError:
220
+ # If no loop is running, do a direct run
221
+ asyncio.run(self.browser.close())
222
+ if self.playwright:
223
+ asyncio.run(self.playwright.stop())
community_contributions/SideKick(Ugraded)/sidekick_tools.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ from datetime import datetime
4
+ from dotenv import load_dotenv
5
+ load_dotenv(override=True)
6
+
7
+ from playwright.async_api import async_playwright
8
+ from google.oauth2.credentials import Credentials
9
+ from googleapiclient.discovery import build
10
+ from langchain.agents import Tool
11
+ from langchain_community.agent_toolkits import PlayWrightBrowserToolkit, FileManagementToolkit
12
+ from langchain_community.utilities import GoogleSerperAPIWrapper
13
+ from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
14
+ from langchain_community.tools.wikipedia.tool import WikipediaQueryRun
15
+ from langchain_experimental.tools import PythonREPLTool
16
+
17
+ # Pushover setup via .env
18
+ pushover_token = os.getenv("PUSHOVER_TOKEN")
19
+ pushover_user = os.getenv("PUSHOVER_USER")
20
+ pushover_url = "https://api.pushover.net/1/messages.json"
21
+ serper = GoogleSerperAPIWrapper()
22
+
23
+ async def playwright_tools():
24
+ playwright = await async_playwright().start()
25
+ browser = await playwright.chromium.launch(headless=False)
26
+ toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=browser)
27
+ return toolkit.get_tools(), browser, playwright
28
+
29
+
30
+ def push(text: str) -> str:
31
+ """Send a push notification via Pushover"""
32
+ requests.post(pushover_url, data={"token": pushover_token, "user": pushover_user, "message": text})
33
+ return "success"
34
+
35
+
36
+ def get_file_tools():
37
+ toolkit = FileManagementToolkit(root_dir=os.getenv("FILE_TOOL_ROOT", "sandbox"))
38
+ return toolkit.get_tools()
39
+
40
+ async def other_tools() -> list[Tool]:
41
+ push_tool = Tool(
42
+ name="send_push_notification",
43
+ func=push,
44
+ description="Send a push notification via Pushover"
45
+ )
46
+ file_tools = get_file_tools()
47
+ search_tool = Tool(
48
+ name="search",
49
+ func=serper.run,
50
+ description="Run a Google Serper web search"
51
+ )
52
+ wikipedia = WikipediaAPIWrapper()
53
+ wiki_tool = WikipediaQueryRun(api_wrapper=wikipedia)
54
+ python_repl = PythonREPLTool()
55
+ return file_tools + [push_tool, search_tool, python_repl, wiki_tool]
56
+
57
+ # --- Google Calendar integration ---
58
+
59
+ def _get_calendar_service():
60
+ creds = Credentials.from_authorized_user_file(
61
+ os.getenv("GOOGLE_TOKEN_PATH", "token.json"),
62
+ scopes=["https://www.googleapis.com/auth/calendar"]
63
+ )
64
+ return build("calendar", "v3", credentials=creds)
65
+
66
+
67
+ def create_calendar_event(summary: str, start_iso: str, end_iso: str, description: str = "", calendar_id: str = None) -> str:
68
+ cal_id = calendar_id or os.getenv("GOOGLE_CALENDAR_ID", "primary")
69
+ service = _get_calendar_service()
70
+ event = {
71
+ "summary": summary,
72
+ "description": description,
73
+ "start": {"dateTime": start_iso},
74
+ "end": {"dateTime": end_iso},
75
+ }
76
+ created = service.events().insert(calendarId=cal_id, body=event).execute()
77
+ return f"Event created: {created.get('htmlLink')}"
78
+
79
+
80
+ def list_upcoming_events(calendar_id: str = None, max_results: int = 5) -> str:
81
+ cal_id = calendar_id or os.getenv("GOOGLE_CALENDAR_ID", "primary")
82
+ service = _get_calendar_service()
83
+ now = datetime.utcnow().isoformat() + "Z"
84
+ events_result = (
85
+ service.events()
86
+ .list(calendarId=cal_id, timeMin=now, maxResults=max_results, singleEvents=True, orderBy="startTime")
87
+ .execute()
88
+ )
89
+ events = events_result.get("items", [])
90
+ if not events:
91
+ return "No upcoming events found."
92
+ lines = []
93
+ for evt in events:
94
+ start = evt["start"].get("dateTime", evt["start"].get("date"))
95
+ lines.append(f"{start} — {evt['summary']}")
96
+ return "\n".join(lines)
97
+
98
+
99
+ def calendar_tools() -> list[Tool]:
100
+ return [
101
+ Tool(
102
+ name="create_calendar_event",
103
+ func=create_calendar_event,
104
+ description="Schedule an event: summary, start_iso (RFC3339), end_iso (RFC3339), [description], [calendar_id]"
105
+ ),
106
+ Tool(
107
+ name="list_upcoming_events",
108
+ func=list_upcoming_events,
109
+ description="List upcoming events on the specified or primary calendar."
110
+ ),
111
+ ]
community_contributions/community.ipynb ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Community contributions\n",
8
+ "\n",
9
+ "Thank you for considering contributing your work to the repo!\n",
10
+ "\n",
11
+ "Please add your code (modules or notebooks) to this directory and send me a PR, per the instructions in the guides.\n",
12
+ "\n",
13
+ "I'd love to share your progress with other students, so everyone can benefit from your projects.\n"
14
+ ]
15
+ },
16
+ {
17
+ "cell_type": "markdown",
18
+ "metadata": {},
19
+ "source": []
20
+ }
21
+ ],
22
+ "metadata": {
23
+ "language_info": {
24
+ "name": "python"
25
+ }
26
+ },
27
+ "nbformat": 4,
28
+ "nbformat_minor": 2
29
+ }
memory.db ADDED
Binary file (4.1 kB). View file
 
memory.db-shm ADDED
Binary file (32.8 kB). View file
 
memory.db-wal ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fb74252aa25ee063ccba0d2f503496afb3b42d28d27fbfb3f589566cf4ce39d5
3
+ size 889952
sandbox/dinner.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dinner Report: Le Bernardin
2
+
3
+ ## Restaurant Overview
4
+ - **Name**: Le Bernardin
5
+ - **Address**: 155 W 51st St, New York, NY 10019
6
+ - **Phone**: (212) 554-1515
7
+ - **Website**: [le-bernardin.com](http://le-bernardin.com)
8
+
9
+ ## Cuisine
10
+ Le Bernardin specializes in refined seafood dishes, crafted with the utmost respect for the ingredients. The menu includes a variety of seafood preparations, with an emphasis on freshness and simplicity.
11
+
12
+ ## Menu Highlights
13
+ - **Tuna Tartare**: Diced raw tuna, served with toasted sesame and avocado.
14
+ - **Wild Salmon**: Lightly cooked, served with a warm ginger-soy emulsion and bok choy.
15
+ - **Poached Lobster**: Accompanied by truffle butter and a delicate sauce.
16
+ - **Chocolate Soufflé**: A classic dessert, rich and airy.
17
+
18
+ ## Ambiance
19
+ The atmosphere at Le Bernardin is elegant and serene, characterized by minimalist decor and a focus on the dining experience.
20
+
21
+ ## Summary of Reviews
22
+ Le Bernardin has consistently received rave reviews for its exceptional cuisine and impeccable service. It is a three-Michelin star restaurant, recognized as one of the best seafood restaurants not only in New York but also in the world. Diners emphasize the harmonious flavors and the artistry of the dishes, making it a top choice for special occasions.
23
+
24
+ **In conclusion**, Le Bernardin represents the pinnacle of fine dining, offering diners an unforgettable experience centered around seafood excellence.
sandbox/jantar.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Restaurantes Franceses em Brasília
2
+
3
+ ## 1. Saveur Bistrot
4
+ - **Endereço:** Não especificado
5
+ - **Telefone:** Não especificado
6
+ - **Menu:** Não especificado
7
+ - **Avaliações:** 4,8 (1.164 avaliações)
8
+
9
+ ## 2. La Fleur Bistrô
10
+ - **Endereço:** Não especificado
11
+ - **Telefone:** Não especificado
12
+ - **Menu:** Não especificado
13
+ - **Avaliações:** 4,8 (305 avaliações)
14
+
15
+ ## 3. Lake's Restaurante
16
+ - **Endereço:** Não especificado
17
+ - **Telefone:** Não especificado
18
+ - **Menu:** Não especificado
19
+ - **Avaliações:** 4,5 (491 avaliações)
20
+
21
+ ## 4. L'Entrecôte de Paris
22
+ - **Endereço:** Não especificado
23
+ - **Telefone:** Não especificado
24
+ - **Menu:** Não especificado
25
+ - **Avaliações:** 4,0 (882 avaliações)
26
+
27
+ ## 5. Crepe Royale
28
+ - **Endereço:** Não especificado
29
+ - **Telefone:** Não especificado
30
+ - **Menu:** Não especificado
31
+ - **Avaliações:** 4,4 (252 avaliações)
32
+
33
+ Observação: Os detalhes dos restaurantes podem não estar completos devido a falhas na captura das informações.
sandbox/lafleur_bistro.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # La Fleur Bistrô
2
+
3
+ - **Endereço:** CLSW 302 Bloco A, Lj 8, Brasília, DF 70673-611
4
+ - **Telefone:** +55 61 3978-2200
5
+ - **Horário de Funcionamento:**
6
+ - Segunda a Quinta: 12h - 00h
7
+ - Sexta e Sábado: 12h - 1h
8
+ - Domingo: 16h - 00h
9
+ - **Ambiente:** Elegantemente decorado e aconchegante, inspirado nos cafês e bistrôs parisienses.
10
+ - **Especialidades:** Conhecido pelo melhor crepe de Brasília e uma experiência culinária memorável. O local é descrito como aconchegante, romântico e acessível.
11
+ - **Preço:** Classificado como $$$$,
12
+ - **Serviços Disponíveis:** Take out pelo telefone e pelo iFood.
13
+
14
+ Venha conhecer e vivenciar uma experiência única no La Fleur Bistrô, onde cada caloria vale a pena!
sidekick.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated
2
+ from typing_extensions import TypedDict
3
+ from langgraph.graph import StateGraph, START, END
4
+ from langgraph.graph.message import add_messages
5
+ from dotenv import load_dotenv
6
+ from langgraph.prebuilt import ToolNode
7
+ from langchain_openai import ChatOpenAI
8
+ from langgraph.checkpoint.memory import MemorySaver
9
+ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
10
+ from typing import List, Any, Optional, Dict
11
+ from pydantic import BaseModel, Field
12
+ from sidekick_tools import playwright_tools, other_tools
13
+ import uuid
14
+ import asyncio
15
+ from datetime import datetime
16
+
17
+ load_dotenv(override=True)
18
+
19
+ class State(TypedDict):
20
+ messages: Annotated[List[Any], add_messages]
21
+ success_criteria: str
22
+ feedback_on_work: Optional[str]
23
+ success_criteria_met: bool
24
+ user_input_needed: bool
25
+
26
+
27
+ class EvaluatorOutput(BaseModel):
28
+ feedback: str = Field(description="Feedback on the assistant's response")
29
+ success_criteria_met: bool = Field(description="Whether the success criteria have been met")
30
+ user_input_needed: bool = Field(description="True if more input is needed from the user, or clarifications, or the assistant is stuck")
31
+
32
+
33
+ class Sidekick:
34
+ def __init__(self):
35
+ self.worker_llm_with_tools = None
36
+ self.evaluator_llm_with_output = None
37
+ self.tools = None
38
+ self.llm_with_tools = None
39
+ self.graph = None
40
+ self.sidekick_id = str(uuid.uuid4())
41
+ self.memory = MemorySaver()
42
+ self.browser = None
43
+ self.playwright = None
44
+
45
+ async def setup(self):
46
+ self.tools, self.browser, self.playwright = await playwright_tools()
47
+ self.tools += await other_tools()
48
+ worker_llm = ChatOpenAI(model="gpt-4o-mini")
49
+ self.worker_llm_with_tools = worker_llm.bind_tools(self.tools)
50
+ evaluator_llm = ChatOpenAI(model="gpt-4o-mini")
51
+ self.evaluator_llm_with_output = evaluator_llm.with_structured_output(EvaluatorOutput)
52
+ await self.build_graph()
53
+
54
+ def worker(self, state: State) -> Dict[str, Any]:
55
+ system_message = f"""You are a helpful assistant that can use tools to complete tasks.
56
+ You keep working on a task until either you have a question or clarification for the user, or the success criteria is met.
57
+ You have many tools to help you, including tools to browse the internet, navigating and retrieving web pages.
58
+ You have a tool to run python code, but note that you would need to include a print() statement if you wanted to receive output.
59
+ The current date and time is {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
60
+
61
+ This is the success criteria:
62
+ {state['success_criteria']}
63
+ You should reply either with a question for the user about this assignment, or with your final response.
64
+ If you have a question for the user, you need to reply by clearly stating your question. An example might be:
65
+
66
+ Question: please clarify whether you want a summary or a detailed answer
67
+
68
+ If you've finished, reply with the final answer, and don't ask a question; simply reply with the answer.
69
+ """
70
+
71
+ if state.get("feedback_on_work"):
72
+ system_message += f"""
73
+ Previously you thought you completed the assignment, but your reply was rejected because the success criteria was not met.
74
+ Here is the feedback on why this was rejected:
75
+ {state['feedback_on_work']}
76
+ With this feedback, please continue the assignment, ensuring that you meet the success criteria or have a question for the user."""
77
+
78
+ # Add in the system message
79
+
80
+ found_system_message = False
81
+ messages = state["messages"]
82
+ for message in messages:
83
+ if isinstance(message, SystemMessage):
84
+ message.content = system_message
85
+ found_system_message = True
86
+
87
+ if not found_system_message:
88
+ messages = [SystemMessage(content=system_message)] + messages
89
+
90
+ # Invoke the LLM with tools
91
+ response = self.worker_llm_with_tools.invoke(messages)
92
+
93
+ # Return updated state
94
+ return {
95
+ "messages": [response],
96
+ }
97
+
98
+
99
+ def worker_router(self, state: State) -> str:
100
+ last_message = state["messages"][-1]
101
+
102
+ if hasattr(last_message, "tool_calls") and last_message.tool_calls:
103
+ return "tools"
104
+ else:
105
+ return "evaluator"
106
+
107
+ def format_conversation(self, messages: List[Any]) -> str:
108
+ conversation = "Conversation history:\n\n"
109
+ for message in messages:
110
+ if isinstance(message, HumanMessage):
111
+ conversation += f"User: {message.content}\n"
112
+ elif isinstance(message, AIMessage):
113
+ text = message.content or "[Tools use]"
114
+ conversation += f"Assistant: {text}\n"
115
+ return conversation
116
+
117
+ def evaluator(self, state: State) -> State:
118
+ last_response = state["messages"][-1].content
119
+
120
+ system_message = f"""You are an evaluator that determines if a task has been completed successfully by an Assistant.
121
+ Assess the Assistant's last response based on the given criteria. Respond with your feedback, and with your decision on whether the success criteria has been met,
122
+ and whether more input is needed from the user."""
123
+
124
+ user_message = f"""You are evaluating a conversation between the User and Assistant. You decide what action to take based on the last response from the Assistant.
125
+
126
+ The entire conversation with the assistant, with the user's original request and all replies, is:
127
+ {self.format_conversation(state['messages'])}
128
+
129
+ The success criteria for this assignment is:
130
+ {state['success_criteria']}
131
+
132
+ And the final response from the Assistant that you are evaluating is:
133
+ {last_response}
134
+
135
+ Respond with your feedback, and decide if the success criteria is met by this response.
136
+ Also, decide if more user input is required, either because the assistant has a question, needs clarification, or seems to be stuck and unable to answer without help.
137
+
138
+ The Assistant has access to a tool to write files. If the Assistant says they have written a file, then you can assume they have done so.
139
+ Overall you should give the Assistant the benefit of the doubt if they say they've done something. But you should reject if you feel that more work should go into this.
140
+
141
+ """
142
+ if state["feedback_on_work"]:
143
+ user_message += f"Also, note that in a prior attempt from the Assistant, you provided this feedback: {state['feedback_on_work']}\n"
144
+ user_message += "If you're seeing the Assistant repeating the same mistakes, then consider responding that user input is required."
145
+
146
+ evaluator_messages = [SystemMessage(content=system_message), HumanMessage(content=user_message)]
147
+
148
+ eval_result = self.evaluator_llm_with_output.invoke(evaluator_messages)
149
+ new_state = {
150
+ "messages": [{"role": "assistant", "content": f"Evaluator Feedback on this answer: {eval_result.feedback}"}],
151
+ "feedback_on_work": eval_result.feedback,
152
+ "success_criteria_met": eval_result.success_criteria_met,
153
+ "user_input_needed": eval_result.user_input_needed
154
+ }
155
+ return new_state
156
+
157
+ def route_based_on_evaluation(self, state: State) -> str:
158
+ if state["success_criteria_met"] or state["user_input_needed"]:
159
+ return "END"
160
+ else:
161
+ return "worker"
162
+
163
+
164
+ async def build_graph(self):
165
+ # Set up Graph Builder with State
166
+ graph_builder = StateGraph(State)
167
+
168
+ # Add nodes
169
+ graph_builder.add_node("worker", self.worker)
170
+ graph_builder.add_node("tools", ToolNode(tools=self.tools))
171
+ graph_builder.add_node("evaluator", self.evaluator)
172
+
173
+ # Add edges
174
+ graph_builder.add_conditional_edges("worker", self.worker_router, {"tools": "tools", "evaluator": "evaluator"})
175
+ graph_builder.add_edge("tools", "worker")
176
+ graph_builder.add_conditional_edges("evaluator", self.route_based_on_evaluation, {"worker": "worker", "END": END})
177
+ graph_builder.add_edge(START, "worker")
178
+
179
+ # Compile the graph
180
+ self.graph = graph_builder.compile(checkpointer=self.memory)
181
+
182
+ async def run_superstep(self, message, success_criteria, history):
183
+ config = {"configurable": {"thread_id": self.sidekick_id}}
184
+
185
+ state = {
186
+ "messages": message,
187
+ "success_criteria": success_criteria or "The answer should be clear and accurate",
188
+ "feedback_on_work": None,
189
+ "success_criteria_met": False,
190
+ "user_input_needed": False
191
+ }
192
+ result = await self.graph.ainvoke(state, config=config)
193
+ user = {"role": "user", "content": message}
194
+ reply = {"role": "assistant", "content": result["messages"][-2].content}
195
+ feedback = {"role": "assistant", "content": result["messages"][-1].content}
196
+ return history + [user, reply, feedback]
197
+
198
+ def cleanup(self):
199
+ if self.browser:
200
+ try:
201
+ loop = asyncio.get_running_loop()
202
+ loop.create_task(self.browser.close())
203
+ if self.playwright:
204
+ loop.create_task(self.playwright.stop())
205
+ except RuntimeError:
206
+ # If no loop is running, do a direct run
207
+ asyncio.run(self.browser.close())
208
+ if self.playwright:
209
+ asyncio.run(self.playwright.stop())
sidekick_tools.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from playwright.async_api import async_playwright
2
+ from langchain_community.agent_toolkits import PlayWrightBrowserToolkit
3
+ from dotenv import load_dotenv
4
+ import os
5
+ import requests
6
+ from langchain.agents import Tool
7
+ from langchain_community.agent_toolkits import FileManagementToolkit
8
+ from langchain_community.tools.wikipedia.tool import WikipediaQueryRun
9
+ from langchain_experimental.tools import PythonREPLTool
10
+ from langchain_community.utilities import GoogleSerperAPIWrapper
11
+ from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
12
+
13
+
14
+
15
+ load_dotenv(override=True)
16
+ pushover_token = os.getenv("PUSHOVER_TOKEN")
17
+ pushover_user = os.getenv("PUSHOVER_USER")
18
+ pushover_url = "https://api.pushover.net/1/messages.json"
19
+ serper = GoogleSerperAPIWrapper()
20
+
21
+ async def playwright_tools():
22
+ playwright = await async_playwright().start()
23
+ browser = await playwright.chromium.launch(headless=False)
24
+ toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=browser)
25
+ return toolkit.get_tools(), browser, playwright
26
+
27
+
28
+ def push(text: str):
29
+ """Send a push notification to the user"""
30
+ requests.post(pushover_url, data = {"token": pushover_token, "user": pushover_user, "message": text})
31
+ return "success"
32
+
33
+
34
+ def get_file_tools():
35
+ toolkit = FileManagementToolkit(root_dir="sandbox")
36
+ return toolkit.get_tools()
37
+
38
+
39
+ async def other_tools():
40
+ push_tool = Tool(name="send_push_notification", func=push, description="Use this tool when you want to send a push notification")
41
+ file_tools = get_file_tools()
42
+
43
+ tool_search =Tool(
44
+ name="search",
45
+ func=serper.run,
46
+ description="Use this tool when you want to get the results of an online web search"
47
+ )
48
+
49
+ wikipedia = WikipediaAPIWrapper()
50
+ wiki_tool = WikipediaQueryRun(api_wrapper=wikipedia)
51
+
52
+ python_repl = PythonREPLTool()
53
+
54
+ return file_tools + [push_tool, tool_search, python_repl, wiki_tool]
55
+