Golfn commited on
Commit
4f426e8
·
1 Parent(s): f4afeeb

test langgraph agent from Hugggingface

Browse files
Files changed (5) hide show
  1. agent_langraph_test.py +39 -0
  2. pdm.lock +249 -2
  3. pyproject.toml +1 -1
  4. setting_up_langraph.py +192 -0
  5. test.py +0 -1
agent_langraph_test.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from setting_up_langraph import compiled_graph
2
+
3
+
4
+ legitimate_email = {
5
+ "sender": "john.smith@example.com",
6
+ "subject": "Question about your services",
7
+ "body": "Dear Mr. Hugg, I was referred to you by a colleague and I'm interested in learning more about your consulting services. Could we schedule a call next week? Best regards, John Smith"
8
+ }
9
+
10
+ # Example spam email
11
+ spam_email = {
12
+ "sender": "winner@lottery-intl.com",
13
+ "subject": "YOU HAVE WON $5,000,000!!!",
14
+ "body": "CONGRATULATIONS! You have been selected as the winner of our international lottery! To claim your $5,000,000 prize, please send us your bank details and a processing fee of $100."
15
+ }
16
+
17
+ # Process the legitimate email
18
+ print("\nProcessing legitimate email...")
19
+ legitimate_result = compiled_graph.invoke({
20
+ "email": legitimate_email,
21
+ "is_spam": None,
22
+ "spam_reason": None,
23
+ "email_category": None,
24
+ "email_draft": None,
25
+ "messages": []
26
+ })
27
+
28
+ # Process the spam email
29
+ print("\nProcessing spam email...")
30
+ spam_result = compiled_graph.invoke({
31
+ "email": spam_email,
32
+ "is_spam": None,
33
+ "spam_reason": None,
34
+ "email_category": None,
35
+ "email_draft": None,
36
+ "messages": []
37
+ })
38
+
39
+ compiled_graph.get_graph().draw_mermaid_png()
pdm.lock CHANGED
@@ -5,7 +5,7 @@
5
  groups = ["default"]
6
  strategy = ["inherit_metadata"]
7
  lock_version = "4.5.0"
8
- content_hash = "sha256:1e0d6e2742f1bc8e6c26e5e055d7e6ad7229a9fe015ec6a2968f9472152e002a"
9
 
10
  [[metadata.targets]]
11
  requires_python = "==3.12.*"
@@ -113,6 +113,29 @@ files = [
113
  {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
114
  ]
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  [[package]]
117
  name = "filelock"
118
  version = "3.18.0"
@@ -229,6 +252,28 @@ files = [
229
  {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
230
  ]
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  [[package]]
233
  name = "jsonpatch"
234
  version = "1.33"
@@ -295,6 +340,22 @@ files = [
295
  {file = "langchain_core-0.3.60.tar.gz", hash = "sha256:63dd1bdf7939816115399522661ca85a2f3686a61440f2f46ebd86d1b028595b"},
296
  ]
297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  [[package]]
299
  name = "langchain-text-splitters"
300
  version = "0.3.8"
@@ -309,6 +370,72 @@ files = [
309
  {file = "langchain_text_splitters-0.3.8.tar.gz", hash = "sha256:116d4b9f2a22dda357d0b79e30acf005c5518177971c66a9f1ab0edfdb0f912e"},
310
  ]
311
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  [[package]]
313
  name = "langsmith"
314
  version = "0.3.42"
@@ -351,13 +478,34 @@ files = [
351
  {file = "numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291"},
352
  ]
353
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  [[package]]
355
  name = "orjson"
356
  version = "3.10.18"
357
  requires_python = ">=3.9"
358
  summary = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
359
  groups = ["default"]
360
- marker = "platform_python_implementation != \"PyPy\""
361
  files = [
362
  {file = "orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753"},
363
  {file = "orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17"},
@@ -377,6 +525,24 @@ files = [
377
  {file = "orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53"},
378
  ]
379
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  [[package]]
381
  name = "packaging"
382
  version = "24.2"
@@ -483,6 +649,17 @@ files = [
483
  {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
484
  ]
485
 
 
 
 
 
 
 
 
 
 
 
 
486
  [[package]]
487
  name = "pytz"
488
  version = "2025.2"
@@ -512,6 +689,31 @@ files = [
512
  {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
513
  ]
514
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
  [[package]]
516
  name = "requests"
517
  version = "2.32.3"
@@ -600,6 +802,26 @@ files = [
600
  {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"},
601
  ]
602
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
603
  [[package]]
604
  name = "tqdm"
605
  version = "4.67.1"
@@ -661,6 +883,31 @@ files = [
661
  {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"},
662
  ]
663
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
  [[package]]
665
  name = "zstandard"
666
  version = "0.23.0"
 
5
  groups = ["default"]
6
  strategy = ["inherit_metadata"]
7
  lock_version = "4.5.0"
8
+ content_hash = "sha256:dd76d2d0b451a1501d4c6609f4045fb2fb46aebf3f2f6c1d98fbc9462822accc"
9
 
10
  [[metadata.targets]]
11
  requires_python = "==3.12.*"
 
113
  {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
114
  ]
115
 
116
+ [[package]]
117
+ name = "distro"
118
+ version = "1.9.0"
119
+ requires_python = ">=3.6"
120
+ summary = "Distro - an OS platform information API"
121
+ groups = ["default"]
122
+ files = [
123
+ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"},
124
+ {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
125
+ ]
126
+
127
+ [[package]]
128
+ name = "dotenv"
129
+ version = "0.9.9"
130
+ summary = "Deprecated package"
131
+ groups = ["default"]
132
+ dependencies = [
133
+ "python-dotenv",
134
+ ]
135
+ files = [
136
+ {file = "dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9"},
137
+ ]
138
+
139
  [[package]]
140
  name = "filelock"
141
  version = "3.18.0"
 
252
  {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
253
  ]
254
 
255
+ [[package]]
256
+ name = "jiter"
257
+ version = "0.9.0"
258
+ requires_python = ">=3.8"
259
+ summary = "Fast iterable JSON parser."
260
+ groups = ["default"]
261
+ files = [
262
+ {file = "jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11"},
263
+ {file = "jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e"},
264
+ {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2"},
265
+ {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75"},
266
+ {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d"},
267
+ {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42"},
268
+ {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc"},
269
+ {file = "jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc"},
270
+ {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e"},
271
+ {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d"},
272
+ {file = "jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06"},
273
+ {file = "jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0"},
274
+ {file = "jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893"},
275
+ ]
276
+
277
  [[package]]
278
  name = "jsonpatch"
279
  version = "1.33"
 
340
  {file = "langchain_core-0.3.60.tar.gz", hash = "sha256:63dd1bdf7939816115399522661ca85a2f3686a61440f2f46ebd86d1b028595b"},
341
  ]
342
 
343
+ [[package]]
344
+ name = "langchain-openai"
345
+ version = "0.3.17"
346
+ requires_python = ">=3.9"
347
+ summary = "An integration package connecting OpenAI and LangChain"
348
+ groups = ["default"]
349
+ dependencies = [
350
+ "langchain-core<1.0.0,>=0.3.59",
351
+ "openai<2.0.0,>=1.68.2",
352
+ "tiktoken<1,>=0.7",
353
+ ]
354
+ files = [
355
+ {file = "langchain_openai-0.3.17-py3-none-any.whl", hash = "sha256:d4d9cf945e2453ee5895ccd12fd8a3ea9131a0f6130dcc21427c77cc2206b1c0"},
356
+ {file = "langchain_openai-0.3.17.tar.gz", hash = "sha256:10bcdfac3edb3dea4a8aabb12f01566e5ff8756634cc52aa169c62e4c4b73801"},
357
+ ]
358
+
359
  [[package]]
360
  name = "langchain-text-splitters"
361
  version = "0.3.8"
 
370
  {file = "langchain_text_splitters-0.3.8.tar.gz", hash = "sha256:116d4b9f2a22dda357d0b79e30acf005c5518177971c66a9f1ab0edfdb0f912e"},
371
  ]
372
 
373
+ [[package]]
374
+ name = "langgraph"
375
+ version = "0.4.5"
376
+ requires_python = ">=3.9"
377
+ summary = "Building stateful, multi-actor applications with LLMs"
378
+ groups = ["default"]
379
+ dependencies = [
380
+ "langchain-core>=0.1; python_version < \"4.0\"",
381
+ "langgraph-checkpoint<3.0.0,>=2.0.26",
382
+ "langgraph-prebuilt>=0.1.8; python_version < \"4.0\"",
383
+ "langgraph-sdk>=0.1.42; python_version < \"4.0\"",
384
+ "pydantic>=2.7.4",
385
+ "xxhash<4.0.0,>=3.5.0",
386
+ ]
387
+ files = [
388
+ {file = "langgraph-0.4.5-py3-none-any.whl", hash = "sha256:73f36caae55137c2bdb2a6c59661f0ae29c1516a0d1f4ad4975ad3862865a979"},
389
+ {file = "langgraph-0.4.5.tar.gz", hash = "sha256:08a8c6577b09cda4e0c16712e762927f00930dabbc7fe235562985ad85891349"},
390
+ ]
391
+
392
+ [[package]]
393
+ name = "langgraph-checkpoint"
394
+ version = "2.0.26"
395
+ requires_python = ">=3.9"
396
+ summary = "Library with base interfaces for LangGraph checkpoint savers."
397
+ groups = ["default"]
398
+ dependencies = [
399
+ "langchain-core>=0.2.38; python_version < \"4.0\"",
400
+ "ormsgpack<2.0.0,>=1.8.0",
401
+ ]
402
+ files = [
403
+ {file = "langgraph_checkpoint-2.0.26-py3-none-any.whl", hash = "sha256:ad4907858ed320a208e14ac037e4b9244ec1cb5aa54570518166ae8b25752cec"},
404
+ {file = "langgraph_checkpoint-2.0.26.tar.gz", hash = "sha256:2b800195532d5efb079db9754f037281225ae175f7a395523f4bf41223cbc9d6"},
405
+ ]
406
+
407
+ [[package]]
408
+ name = "langgraph-prebuilt"
409
+ version = "0.1.8"
410
+ requires_python = "<4.0.0,>=3.9.0"
411
+ summary = "Library with high-level APIs for creating and executing LangGraph agents and tools."
412
+ groups = ["default"]
413
+ marker = "python_version < \"4.0\""
414
+ dependencies = [
415
+ "langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43",
416
+ "langgraph-checkpoint<3.0.0,>=2.0.10",
417
+ ]
418
+ files = [
419
+ {file = "langgraph_prebuilt-0.1.8-py3-none-any.whl", hash = "sha256:ae97b828ae00be2cefec503423aa782e1bff165e9b94592e224da132f2526968"},
420
+ {file = "langgraph_prebuilt-0.1.8.tar.gz", hash = "sha256:4de7659151829b2b955b6798df6800e580e617782c15c2c5b29b139697491831"},
421
+ ]
422
+
423
+ [[package]]
424
+ name = "langgraph-sdk"
425
+ version = "0.1.69"
426
+ requires_python = ">=3.9"
427
+ summary = "SDK for interacting with LangGraph API"
428
+ groups = ["default"]
429
+ marker = "python_version < \"4.0\""
430
+ dependencies = [
431
+ "httpx>=0.25.2",
432
+ "orjson>=3.10.1",
433
+ ]
434
+ files = [
435
+ {file = "langgraph_sdk-0.1.69-py3-none-any.whl", hash = "sha256:0ed117bcdf67285a17c57f6265f1d94f2dbd71346cf48a8e1a5fa25e523eb6b8"},
436
+ {file = "langgraph_sdk-0.1.69.tar.gz", hash = "sha256:2e85d73b78a03f9606d0fafd62048b3060371149f6f9e61f07f087fd56c766fa"},
437
+ ]
438
+
439
  [[package]]
440
  name = "langsmith"
441
  version = "0.3.42"
 
478
  {file = "numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291"},
479
  ]
480
 
481
+ [[package]]
482
+ name = "openai"
483
+ version = "1.79.0"
484
+ requires_python = ">=3.8"
485
+ summary = "The official Python library for the openai API"
486
+ groups = ["default"]
487
+ dependencies = [
488
+ "anyio<5,>=3.5.0",
489
+ "distro<2,>=1.7.0",
490
+ "httpx<1,>=0.23.0",
491
+ "jiter<1,>=0.4.0",
492
+ "pydantic<3,>=1.9.0",
493
+ "sniffio",
494
+ "tqdm>4",
495
+ "typing-extensions<5,>=4.11",
496
+ ]
497
+ files = [
498
+ {file = "openai-1.79.0-py3-none-any.whl", hash = "sha256:d5050b92d5ef83f869cb8dcd0aca0b2291c3413412500eec40c66981b3966992"},
499
+ {file = "openai-1.79.0.tar.gz", hash = "sha256:e3b627aa82858d3e42d16616edc22aa9f7477ee5eb3e6819e9f44a961d899a4c"},
500
+ ]
501
+
502
  [[package]]
503
  name = "orjson"
504
  version = "3.10.18"
505
  requires_python = ">=3.9"
506
  summary = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
507
  groups = ["default"]
508
+ marker = "platform_python_implementation != \"PyPy\" or python_version < \"4.0\""
509
  files = [
510
  {file = "orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753"},
511
  {file = "orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17"},
 
525
  {file = "orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53"},
526
  ]
527
 
528
+ [[package]]
529
+ name = "ormsgpack"
530
+ version = "1.9.1"
531
+ requires_python = ">=3.9"
532
+ summary = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy"
533
+ groups = ["default"]
534
+ files = [
535
+ {file = "ormsgpack-1.9.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1ede445fc3fdba219bb0e0d1f289df26a9c7602016b7daac6fafe8fe4e91548f"},
536
+ {file = "ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db50b9f918e25b289114312ed775794d0978b469831b992bdc65bfe20b91fe30"},
537
+ {file = "ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c7d8fc58e4333308f58ec720b1ee6b12b2b3fe2d2d8f0766ab751cb351e8757"},
538
+ {file = "ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeee6d08c040db265cb8563444aba343ecb32cbdbe2414a489dcead9f70c6765"},
539
+ {file = "ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2fbb8181c198bdc413a4e889e5200f010724eea4b6d5a9a7eee2df039ac04aca"},
540
+ {file = "ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:16488f094ac0e2250cceea6caf72962614aa432ee11dd57ef45e1ad25ece3eff"},
541
+ {file = "ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:422d960bfd6ad88be20794f50ec7953d8f7a0f2df60e19d0e8feb994e2ed64ee"},
542
+ {file = "ormsgpack-1.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:e6e2f9eab527cf43fb4a4293e493370276b1c8716cf305689202d646c6a782ef"},
543
+ {file = "ormsgpack-1.9.1.tar.gz", hash = "sha256:3da6e63d82565e590b98178545e64f0f8506137b92bd31a2d04fd7c82baf5794"},
544
+ ]
545
+
546
  [[package]]
547
  name = "packaging"
548
  version = "24.2"
 
649
  {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
650
  ]
651
 
652
+ [[package]]
653
+ name = "python-dotenv"
654
+ version = "1.1.0"
655
+ requires_python = ">=3.9"
656
+ summary = "Read key-value pairs from a .env file and set them as environment variables"
657
+ groups = ["default"]
658
+ files = [
659
+ {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"},
660
+ {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"},
661
+ ]
662
+
663
  [[package]]
664
  name = "pytz"
665
  version = "2025.2"
 
689
  {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
690
  ]
691
 
692
+ [[package]]
693
+ name = "regex"
694
+ version = "2024.11.6"
695
+ requires_python = ">=3.8"
696
+ summary = "Alternative regular expression module, to replace re."
697
+ groups = ["default"]
698
+ files = [
699
+ {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"},
700
+ {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"},
701
+ {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"},
702
+ {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"},
703
+ {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"},
704
+ {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"},
705
+ {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"},
706
+ {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"},
707
+ {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"},
708
+ {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"},
709
+ {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"},
710
+ {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"},
711
+ {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"},
712
+ {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"},
713
+ {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"},
714
+ {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"},
715
+ ]
716
+
717
  [[package]]
718
  name = "requests"
719
  version = "2.32.3"
 
802
  {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"},
803
  ]
804
 
805
+ [[package]]
806
+ name = "tiktoken"
807
+ version = "0.9.0"
808
+ requires_python = ">=3.9"
809
+ summary = "tiktoken is a fast BPE tokeniser for use with OpenAI's models"
810
+ groups = ["default"]
811
+ dependencies = [
812
+ "regex>=2022.1.18",
813
+ "requests>=2.26.0",
814
+ ]
815
+ files = [
816
+ {file = "tiktoken-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e88f121c1c22b726649ce67c089b90ddda8b9662545a8aeb03cfef15967ddd03"},
817
+ {file = "tiktoken-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6600660f2f72369acb13a57fb3e212434ed38b045fd8cc6cdd74947b4b5d210"},
818
+ {file = "tiktoken-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95e811743b5dfa74f4b227927ed86cbc57cad4df859cb3b643be797914e41794"},
819
+ {file = "tiktoken-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99376e1370d59bcf6935c933cb9ba64adc29033b7e73f5f7569f3aad86552b22"},
820
+ {file = "tiktoken-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:badb947c32739fb6ddde173e14885fb3de4d32ab9d8c591cbd013c22b4c31dd2"},
821
+ {file = "tiktoken-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:5a62d7a25225bafed786a524c1b9f0910a1128f4232615bf3f8257a73aaa3b16"},
822
+ {file = "tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d"},
823
+ ]
824
+
825
  [[package]]
826
  name = "tqdm"
827
  version = "4.67.1"
 
883
  {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"},
884
  ]
885
 
886
+ [[package]]
887
+ name = "xxhash"
888
+ version = "3.5.0"
889
+ requires_python = ">=3.7"
890
+ summary = "Python binding for xxHash"
891
+ groups = ["default"]
892
+ files = [
893
+ {file = "xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00"},
894
+ {file = "xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9"},
895
+ {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84"},
896
+ {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793"},
897
+ {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be"},
898
+ {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6"},
899
+ {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90"},
900
+ {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27"},
901
+ {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2"},
902
+ {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d"},
903
+ {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab"},
904
+ {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e"},
905
+ {file = "xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8"},
906
+ {file = "xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e"},
907
+ {file = "xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2"},
908
+ {file = "xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f"},
909
+ ]
910
+
911
  [[package]]
912
  name = "zstandard"
913
  version = "0.23.0"
pyproject.toml CHANGED
@@ -5,7 +5,7 @@ description = "Default template for PDM package"
5
  authors = [
6
  {name = "Supphawit_Golf", email = "supphawit.n@gmail.com"},
7
  ]
8
- dependencies = ["langchain>=0.3.25", "huggingface-hub>=0.31.2", "pandas>=2.2.3"]
9
  requires-python = "==3.12.*"
10
  readme = "README.md"
11
  license = {text = "MIT"}
 
5
  authors = [
6
  {name = "Supphawit_Golf", email = "supphawit.n@gmail.com"},
7
  ]
8
+ dependencies = ["langchain>=0.3.25", "huggingface-hub>=0.31.2", "pandas>=2.2.3", "langgraph>=0.4.5", "langchain-openai>=0.3.17", "dotenv>=0.9.9"]
9
  requires-python = "==3.12.*"
10
  readme = "README.md"
11
  license = {text = "MIT"}
setting_up_langraph.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import TypedDict, List, Dict, Any, Optional
3
+ from langgraph.graph import StateGraph, START, END
4
+ from langchain_openai import ChatOpenAI
5
+ from langchain_core.messages import HumanMessage
6
+ from dotenv import load_dotenv
7
+ load_dotenv()
8
+
9
+ class EmailState(TypedDict):
10
+ # The email being processed
11
+ email: Dict[str, Any] # Contains subject, sender, body, etc.
12
+
13
+ # Category of the email (inquiry, complaint, etc.)
14
+ email_category: Optional[str]
15
+
16
+ # Reason why the email was marked as spam
17
+ spam_reason: Optional[str]
18
+
19
+ # Analysis and decisions
20
+ is_spam: Optional[bool]
21
+
22
+ # Response generation
23
+ email_draft: Optional[str]
24
+
25
+ # Processing metadata
26
+ messages: List[Dict[str, Any]] # Track conversation with LLM for analysis
27
+
28
+ # Initialize our LLM
29
+ model = ChatOpenAI(temperature=0
30
+ , model="gpt-4o-mini", openai_api_key=os.getenv("OPENAI_KEY"))
31
+
32
+ def read_email(state: EmailState):
33
+ """Alfred reads and logs the incoming email"""
34
+ email = state["email"]
35
+
36
+ # Here we might do some initial preprocessing
37
+ print(f"Alfred is processing an email from {email['sender']} with subject: {email['subject']}")
38
+
39
+ # No state changes needed here
40
+ return {}
41
+
42
+ def classify_email(state: EmailState):
43
+ """Alfred uses an LLM to determine if the email is spam or legitimate"""
44
+ email = state["email"]
45
+
46
+ # Prepare our prompt for the LLM
47
+ prompt = f"""
48
+ As Alfred the butler, analyze this email and determine if it is spam or legitimate.
49
+
50
+ Email:
51
+ From: {email['sender']}
52
+ Subject: {email['subject']}
53
+ Body: {email['body']}
54
+
55
+ First, determine if this email is spam. If it is spam, explain why.
56
+ If it is legitimate, categorize it (inquiry, complaint, thank you, etc.).
57
+ """
58
+
59
+ # Call the LLM
60
+ messages = [HumanMessage(content=prompt)]
61
+ response = model.invoke(messages)
62
+
63
+ # Simple logic to parse the response (in a real app, you'd want more robust parsing)
64
+ response_text = response.content.lower()
65
+ is_spam = "spam" in response_text and "not spam" not in response_text
66
+
67
+ # Extract a reason if it's spam
68
+ spam_reason = None
69
+ if is_spam and "reason:" in response_text:
70
+ spam_reason = response_text.split("reason:")[1].strip()
71
+
72
+ # Determine category if legitimate
73
+ email_category = None
74
+ if not is_spam:
75
+ categories = ["inquiry", "complaint", "thank you", "request", "information"]
76
+ for category in categories:
77
+ if category in response_text:
78
+ email_category = category
79
+ break
80
+
81
+ # Update messages for tracking
82
+ new_messages = state.get("messages", []) + [
83
+ {"role": "user", "content": prompt},
84
+ {"role": "assistant", "content": response.content}
85
+ ]
86
+
87
+ # Return state updates
88
+ return {
89
+ "is_spam": is_spam,
90
+ "spam_reason": spam_reason,
91
+ "email_category": email_category,
92
+ "messages": new_messages
93
+ }
94
+
95
+ def handle_spam(state: EmailState):
96
+ """Alfred discards spam email with a note"""
97
+ print(f"Alfred has marked the email as spam. Reason: {state['spam_reason']}")
98
+ print("The email has been moved to the spam folder.")
99
+
100
+ # We're done processing this email
101
+ return {}
102
+
103
+ def draft_response(state: EmailState):
104
+ """Alfred drafts a preliminary response for legitimate emails"""
105
+ email = state["email"]
106
+ category = state["email_category"] or "general"
107
+
108
+ # Prepare our prompt for the LLM
109
+ prompt = f"""
110
+ As Alfred the butler, draft a polite preliminary response to this email.
111
+
112
+ Email:
113
+ From: {email['sender']}
114
+ Subject: {email['subject']}
115
+ Body: {email['body']}
116
+
117
+ This email has been categorized as: {category}
118
+
119
+ Draft a brief, professional response that Mr. Hugg can review and personalize before sending.
120
+ """
121
+
122
+ # Call the LLM
123
+ messages = [HumanMessage(content=prompt)]
124
+ response = model.invoke(messages)
125
+
126
+ # Update messages for tracking
127
+ new_messages = state.get("messages", []) + [
128
+ {"role": "user", "content": prompt},
129
+ {"role": "assistant", "content": response.content}
130
+ ]
131
+
132
+ # Return state updates
133
+ return {
134
+ "email_draft": response.content,
135
+ "messages": new_messages
136
+ }
137
+
138
+ def notify_mr_hugg(state: EmailState):
139
+ """Alfred notifies Mr. Hugg about the email and presents the draft response"""
140
+ email = state["email"]
141
+
142
+ print("\n" + "="*50)
143
+ print(f"Sir, you've received an email from {email['sender']}.")
144
+ print(f"Subject: {email['subject']}")
145
+ print(f"Category: {state['email_category']}")
146
+ print("\nI've prepared a draft response for your review:")
147
+ print("-"*50)
148
+ print(state["email_draft"])
149
+ print("="*50 + "\n")
150
+
151
+ # We're done processing this email
152
+ return {}
153
+
154
+ def route_email(state: EmailState) -> str:
155
+ """Determine the next step based on spam classification"""
156
+ if state["is_spam"]:
157
+ return "spam"
158
+ else:
159
+ return "legitimate"
160
+
161
+ # Create the graph
162
+ email_graph = StateGraph(EmailState)
163
+
164
+ # Add nodes
165
+ email_graph.add_node("read_email", read_email)
166
+ email_graph.add_node("classify_email", classify_email)
167
+ email_graph.add_node("handle_spam", handle_spam)
168
+ email_graph.add_node("draft_response", draft_response)
169
+ email_graph.add_node("notify_mr_hugg", notify_mr_hugg)
170
+
171
+ # Start the edges
172
+ email_graph.add_edge(START, "read_email")
173
+ # Add edges - defining the flow
174
+ email_graph.add_edge("read_email", "classify_email")
175
+
176
+ # Add conditional branching from classify_email
177
+ email_graph.add_conditional_edges(
178
+ "classify_email",
179
+ route_email,
180
+ {
181
+ "spam": "handle_spam",
182
+ "legitimate": "draft_response"
183
+ }
184
+ )
185
+
186
+ # Add the final edges
187
+ email_graph.add_edge("handle_spam", END)
188
+ email_graph.add_edge("draft_response", "notify_mr_hugg")
189
+ email_graph.add_edge("notify_mr_hugg", END)
190
+
191
+ # Compile the graph
192
+ compiled_graph = email_graph.compile()
test.py DELETED
@@ -1 +0,0 @@
1
- print(f'Hello world')