66 Commits

Author SHA1 Message Date
b5a00bc694 update 2025-07-30 18:08:57 +07:00
4eb55537f7 update 2025-07-23 18:28:38 +07:00
narawat lamaiin
0a1032c545 update 2025-07-18 07:54:59 +07:00
narawat lamaiin
68a20b5080 update 2025-07-17 11:48:23 +07:00
narawat lamaiin
8a9c9606c7 update 2025-07-14 19:33:12 +07:00
narawat lamaiin
bad2ca35ed update 2025-07-14 08:54:51 +07:00
narawat lamaiin
f2b56640cc update 2025-06-17 12:53:32 +07:00
narawat lamaiin
5d552d96c4 add example 2025-06-17 12:52:00 +07:00
narawat lamaiin
e0dc7d29b2 update 2025-06-15 08:02:59 +07:00
narawat lamaiin
932611a439 update 2025-06-09 06:33:48 +07:00
narawat lamaiin
a5c6360b4e update 2025-06-03 10:08:54 +07:00
narawat lamaiin
03f50379c9 mark new version 2025-05-26 07:14:19 +07:00
ton
a7da0b8123 Merge pull request 'v0.3.0' (#5) from v0.3.0 into main
Reviewed-on: #5
2025-05-26 00:07:21 +00:00
narawat lamaiin
e524813021 update 2025-05-26 07:05:14 +07:00
narawat lamaiin
3444f00062 update 2025-05-19 21:10:04 +07:00
narawat lamaiin
919d8ec85e update 2025-05-18 17:21:51 +07:00
narawat lamaiin
3a88e0e7d4 update 2025-05-17 21:36:29 +07:00
narawat lamaiin
68c2c2f12b update 2025-05-17 12:18:25 +07:00
narawat lamaiin
3e79c0bfed update 2025-05-16 10:26:50 +07:00
narawat lamaiin
d0c26e52e8 update 2025-05-14 21:21:35 +07:00
narawat lamaiin
a0152a3c29 update 2025-05-04 20:56:17 +07:00
narawat lamaiin
1fc5dfe820 mark new version 2025-05-02 15:27:29 +07:00
ton
4b2575f4a4 Merge pull request 'v0.2.0' (#4) from v0.2.0 into main
Reviewed-on: #4
2025-05-02 08:21:05 +00:00
narawat lamaiin
a01a91e7b9 update 2025-05-01 12:05:59 +07:00
narawat lamaiin
aa8436c0ed update 2025-05-01 08:04:01 +07:00
narawat lamaiin
cccad676db update 2025-05-01 07:59:37 +07:00
narawat lamaiin
03de659c9b update companion 2025-04-30 12:58:32 +07:00
narawat lamaiin
affb96f0cf update 2025-04-29 18:45:52 +07:00
narawat lamaiin
f19f302bd9 update 2025-04-29 11:01:36 +07:00
narawat lamaiin
7ca4f5276d update 2025-04-26 06:20:09 +07:00
narawat lamaiin
44804041a3 update 2025-04-25 21:12:27 +07:00
narawat lamaiin
48a3704f6d update 2025-04-13 21:46:54 +07:00
8321a13afc update 2025-04-04 15:23:34 +07:00
b26ae31d4c mark new version 2025-04-04 15:23:11 +07:00
ton
b397bf7bdb Merge pull request 'v0.1.4' (#3) from v0.1.4 into main
Reviewed-on: #3
2025-04-04 08:14:57 +00:00
narawat lamaiin
c0edf7dadf update 2025-04-04 15:04:02 +07:00
narawat lamaiin
c21f943b12 update 2025-04-01 21:17:15 +07:00
narawat lamaiin
b8fd772a28 update 2025-03-31 21:30:14 +07:00
narawat lamaiin
883f581b2a update 2025-03-22 15:34:00 +07:00
narawat lamaiin
5a890860a6 update 2025-03-22 09:42:51 +07:00
7d5bc14a09 mark new version 2025-03-21 10:13:53 +07:00
ton
37ba3a9d31 Merge pull request 'v0.1.3-dev' (#2) from v0.1.3-dev into main
Reviewed-on: #2
2025-03-21 03:09:16 +00:00
bfadd53033 update 2025-03-21 10:03:08 +07:00
8fc3afe348 update 2025-03-20 16:15:38 +07:00
c60037226a update 2025-03-13 19:11:20 +07:00
narawat lamaiin
db6c9c5f2b update 2025-03-07 13:34:15 +07:00
narawat lamaiin
6504099959 update 2025-01-31 09:50:44 +07:00
724b092bdb update 2025-01-30 21:28:49 +07:00
c56c3d02b0 update 2025-01-29 12:16:01 +07:00
ton
a7f3e29e9c Merge pull request 'WIP v0.1.2-dev' (#1) from v0.1.2-dev into main
Reviewed-on: #1
2025-01-25 07:30:18 +00:00
narawat lamaiin
b8fc23b41e update 2025-01-25 14:21:37 +07:00
narawat lamaiin
cf4cd13b14 update 2025-01-25 13:31:23 +07:00
narawat lamaiin
29adc077d5 update 2025-01-23 19:34:13 +07:00
narawat lamaiin
d89d425885 update 2025-01-21 08:28:26 +07:00
narawat lamaiin
bb81b973d3 update 2025-01-20 18:19:38 +07:00
narawat lamaiin
4197625e57 update 2025-01-17 22:09:48 +07:00
narawat lamaiin
3fdc0adf99 update 2025-01-16 07:40:39 +07:00
narawat lamaiin
c7000f66b8 update 2025-01-15 08:35:25 +07:00
narawat lamaiin
2206831bab update 2025-01-15 06:13:18 +07:00
narawat lamaiin
a29e8049a7 update 2025-01-11 16:57:57 +07:00
narawat lamaiin
944d9eaf2b update 2025-01-10 18:08:21 +07:00
narawat lamaiin
616c159336 update 2025-01-10 08:06:01 +07:00
narawat lamaiin
022cb5caf0 update 2025-01-05 17:41:21 +07:00
cff0d31ae6 update 2025-01-04 16:10:23 +07:00
82167fe006 update 2025-01-04 16:07:18 +07:00
814a0ecc6a update 2024-12-27 20:53:15 +07:00
16 changed files with 4195 additions and 2129 deletions

View File

@@ -1,8 +1,8 @@
# This file is machine-generated - editing it directly is not advised
julia_version = "1.11.2"
julia_version = "1.11.5"
manifest_format = "2.0"
project_hash = "b483014657ef9f0fde60d7258585b291d6f0eeca"
project_hash = "9896e9d54d6cf4e2c3ae871a42f43f2f212ab1c9"
[[deps.AliasTables]]
deps = ["PtrArrays", "Random"]
@@ -27,6 +27,11 @@ git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d"
uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35"
version = "0.1.9"
[[deps.BufferedStreams]]
git-tree-sha1 = "6863c5b7fc997eadcabdbaf6c5f201dc30032643"
uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
version = "1.2.2"
[[deps.CEnum]]
git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc"
uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
@@ -40,26 +45,37 @@ version = "0.10.15"
[[deps.CodeTracking]]
deps = ["InteractiveUtils", "UUIDs"]
git-tree-sha1 = "7eee164f122511d3e4e1ebadb7956939ea7e1c77"
git-tree-sha1 = "062c5e1a5bf6ada13db96a4ae4749a4c2234f521"
uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
version = "1.3.6"
version = "1.3.9"
[[deps.CodecBase]]
deps = ["TranscodingStreams"]
git-tree-sha1 = "40956acdbef3d8c7cc38cba42b56034af8f8581a"
uuid = "6c391c72-fb7b-5838-ba82-7cfb1bcfecbf"
version = "0.3.4"
[[deps.CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759"
git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.6"
version = "0.7.8"
[[deps.Compat]]
deps = ["TOML", "UUIDs"]
git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215"
git-tree-sha1 = "3a3dfb30697e96a440e4149c8c51bf32f818c0f3"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "4.16.0"
version = "4.17.0"
weakdeps = ["Dates", "LinearAlgebra"]
[deps.Compat.extensions]
CompatLinearAlgebraExt = "LinearAlgebra"
[[deps.Compiler]]
git-tree-sha1 = "382d79bfe72a406294faca39ef0c3cef6e6ce1f1"
uuid = "807dbc54-b67e-4c79-8afb-eafe4df6f2e1"
version = "0.1.1"
[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
@@ -67,9 +83,9 @@ version = "1.1.1+0"
[[deps.ConcurrentUtilities]]
deps = ["Serialization", "Sockets"]
git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1"
git-tree-sha1 = "d9d26935a0bcffc87d2613ce14c527c99fc543fd"
uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
version = "2.4.2"
version = "2.5.0"
[[deps.Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
@@ -94,9 +110,9 @@ version = "1.7.0"
[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82"
git-tree-sha1 = "4e1fe97fdaed23e9dc21d4d664bea76b65fc50a0"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.20"
version = "0.18.22"
[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
@@ -120,9 +136,9 @@ version = "1.11.0"
[[deps.Distributions]]
deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
git-tree-sha1 = "3101c32aab536e7a27b1763c0797dba151b899ad"
git-tree-sha1 = "3e6d038b77f22791b8e3472b7c633acea1ecac06"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
version = "0.25.113"
version = "0.25.120"
[deps.Distributions.extensions]
DistributionsChainRulesCoreExt = "ChainRulesCore"
@@ -135,10 +151,9 @@ version = "0.25.113"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d"
git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.9.3"
version = "0.9.5"
[[deps.Downloads]]
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
@@ -158,9 +173,9 @@ version = "0.1.10"
[[deps.FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "2dd20384bf8c6d411b5c7370865b1e9b26cb2ea3"
git-tree-sha1 = "b66970a70db13f45b7e57fbda1736e1cf72174ea"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.16.6"
version = "1.17.0"
weakdeps = ["HTTP"]
[deps.FileIO.extensions]
@@ -168,9 +183,9 @@ weakdeps = ["HTTP"]
[[deps.FilePathsBase]]
deps = ["Compat", "Dates"]
git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361"
git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2"
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
version = "0.9.22"
version = "0.9.24"
weakdeps = ["Mmap", "Test"]
[deps.FilePathsBase.extensions]
@@ -199,30 +214,33 @@ uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"
version = "1.11.0"
[[deps.GeneralUtils]]
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "JSON3", "MQTTClient", "PrettyPrinting", "Random", "SHA", "UUIDs"]
git-tree-sha1 = "978d9a5c3fc30205dd72d4a2a2ed4fa85ebee5cf"
repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/GeneralUtils"
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "JSON3", "MQTTClient", "NATS", "PrettyPrinting", "Random", "SHA", "UUIDs"]
path = "/appfolder/app/dev/GeneralUtils"
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
version = "0.1.0"
version = "0.3.1"
[[deps.HTTP]]
deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
git-tree-sha1 = "6c22309e9a356ac1ebc5c8a217045f9bae6f8d9a"
git-tree-sha1 = "ed5e9c58612c4e081aecdb6e1a479e18462e041e"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "1.10.13"
version = "1.10.17"
[[deps.HashArrayMappedTries]]
git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae"
uuid = "076d061b-32b6-4027-95e0-9a2c6f6d7e74"
version = "0.2.0"
[[deps.HypergeometricFunctions]]
deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
git-tree-sha1 = "b1c2585431c382e3fe5805874bda6aea90a95de9"
git-tree-sha1 = "68c173f4f449de5b438ee67ed0c9c748dc31a2ec"
uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
version = "0.3.25"
version = "0.3.28"
[[deps.ICU_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "20b6765a3016e1fca0c9c93c80d50061b94218b7"
deps = ["Artifacts", "JLLWrappers", "Libdl"]
git-tree-sha1 = "b3d8be712fbf9237935bde0ce9b5a736ae38fc34"
uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b"
version = "69.1.0+0"
version = "76.2.0+0"
[[deps.Infinity]]
deps = ["Dates", "Random", "Requires"]
@@ -231,9 +249,9 @@ uuid = "a303e19e-6eb4-11e9-3b09-cd9505f79100"
version = "0.2.4"
[[deps.InlineStrings]]
git-tree-sha1 = "45521d31238e87ee9f9732561bfee12d4eebd52d"
git-tree-sha1 = "8594fac023c5ce1ef78260f24d1ad18b4327b420"
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
version = "1.4.2"
version = "1.4.4"
[deps.InlineStrings.extensions]
ArrowTypesExt = "ArrowTypes"
@@ -255,14 +273,14 @@ uuid = "d8418881-c3e1-53bb-8760-2df7ec849ed5"
version = "1.10.0"
[[deps.InvertedIndices]]
git-tree-sha1 = "0dc7b50b8d436461be01300fd8cd45aa0274b038"
git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.3.0"
version = "1.3.1"
[[deps.IrrationalConstants]]
git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2"
git-tree-sha1 = "e2222959fbc6c19554dc15174c81bf7bf3aa691c"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.2.2"
version = "0.2.4"
[[deps.IterTools]]
git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023"
@@ -276,15 +294,15 @@ version = "1.0.0"
[[deps.JLLWrappers]]
deps = ["Artifacts", "Preferences"]
git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b"
git-tree-sha1 = "a007feb38b422fbdab534406aeca1b86823cb4d6"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.6.1"
version = "1.7.0"
[[deps.JSON3]]
deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"]
git-tree-sha1 = "1d322381ef7b087548321d3f878cb4c9bd8f8f9b"
git-tree-sha1 = "411eccfe8aba0814ffa0fdf4860913ed09c34975"
uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
version = "1.14.1"
version = "1.14.3"
[deps.JSON3.extensions]
JSON3ArrowExt = ["ArrowTypes"]
@@ -294,23 +312,21 @@ version = "1.14.1"
[[deps.JuliaInterpreter]]
deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"]
git-tree-sha1 = "10da5154188682e5c0726823c2b5125957ec3778"
git-tree-sha1 = "6ac9e4acc417a5b534ace12690bc6973c25b862f"
uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"
version = "0.9.38"
version = "0.10.3"
[[deps.Kerberos_krb5_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "60274b4ab38e8d1248216fe6b6ace75ae09b0502"
deps = ["Artifacts", "JLLWrappers", "Libdl"]
git-tree-sha1 = "0f2899fdadaab4b8f57db558ba21bdb4fb52f1f0"
uuid = "b39eb1a6-c29a-53d7-8c32-632cd16f18da"
version = "1.19.3+0"
version = "1.21.3+0"
[[deps.LLMMCTS]]
deps = ["GeneralUtils", "JSON3"]
git-tree-sha1 = "d8c653b8fafbd3757b7332985efaf1fdb8b6fe97"
repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/LLMMCTS"
deps = ["GeneralUtils", "JSON3", "PrettyPrinting"]
path = "/appfolder/app/dev/LLMMCTS"
uuid = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
version = "0.1.2"
version = "0.1.4"
[[deps.LaTeXStrings]]
git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
@@ -350,9 +366,9 @@ version = "1.18.0"
[[deps.LibPQ_jll]]
deps = ["Artifacts", "ICU_jll", "JLLWrappers", "Kerberos_krb5_jll", "Libdl", "OpenSSL_jll", "Zstd_jll"]
git-tree-sha1 = "09163f837936c8cc44f4691cb41d805eb1769642"
git-tree-sha1 = "7757f54f007cc0eb516a5000fb9a6fc19a49da7e"
uuid = "08be9ffa-1c94-5ee5-a977-46a84ec9b350"
version = "16.0.0+0"
version = "16.8.0+0"
[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
@@ -370,9 +386,9 @@ version = "1.11.0"
[[deps.LogExpFunctions]]
deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea"
git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.28"
version = "0.3.29"
[deps.LogExpFunctions.extensions]
LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
@@ -395,10 +411,10 @@ uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36"
version = "1.1.0"
[[deps.LoweredCodeUtils]]
deps = ["JuliaInterpreter"]
git-tree-sha1 = "688d6d9e098109051ae33d126fcfc88c4ce4a021"
deps = ["Compiler", "JuliaInterpreter"]
git-tree-sha1 = "bc54ba0681bb71e56043a1b923028d652e78ee42"
uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
version = "3.1.0"
version = "3.4.1"
[[deps.MQTTClient]]
deps = ["Distributed", "Random", "Sockets"]
@@ -452,14 +468,26 @@ version = "0.8.1"
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
version = "2023.12.12"
[[deps.NATS]]
deps = ["Base64", "BufferedStreams", "CodecBase", "Dates", "DocStringExtensions", "JSON3", "MbedTLS", "NanoDates", "Random", "ScopedValues", "Sockets", "Sodium", "StructTypes", "URIs"]
git-tree-sha1 = "d9d9a189fb9155a460e6b5e8966bf6a66737abf8"
uuid = "55e73f9c-eeeb-467f-b4cc-a633fde63d2a"
version = "0.1.0"
[[deps.NanoDates]]
deps = ["Dates", "Parsers"]
git-tree-sha1 = "850a0557ae5934f6e67ac0dc5ca13d0328422d1f"
uuid = "46f1a544-deae-4307-8689-c12aa3c955c6"
version = "1.0.3"
[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"
[[deps.OffsetArrays]]
git-tree-sha1 = "39d000d9c33706b8364817d8894fae1548f40295"
git-tree-sha1 = "117432e406b5c023f665fa73dc26e79ec3630151"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.14.2"
version = "1.17.0"
[deps.OffsetArrays.extensions]
OffsetArraysAdaptExt = "Adapt"
@@ -475,42 +503,42 @@ version = "0.3.27+1"
[[deps.OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
version = "0.8.1+2"
version = "0.8.5+0"
[[deps.OpenSSL]]
deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
git-tree-sha1 = "38cb508d080d21dc1128f7fb04f20387ed4c0af4"
git-tree-sha1 = "f1a7e086c677df53e064e0fdd2c9d0b0833e3f6e"
uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c"
version = "1.4.3"
version = "1.5.0"
[[deps.OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10"
git-tree-sha1 = "87510f7292a2b21aeff97912b0898f9553cc5c2c"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "3.0.15+1"
version = "3.5.1+0"
[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"
version = "0.5.6+0"
[[deps.OrderedCollections]]
git-tree-sha1 = "12f1439c4f986bb868acda6ea33ebc78e19b95ad"
git-tree-sha1 = "05868e21324cede2207c6f0f466b4bfef6d5e7ee"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.7.0"
version = "1.8.1"
[[deps.PDMats]]
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
git-tree-sha1 = "949347156c25054de2db3b166c52ac4728cbad65"
git-tree-sha1 = "f07c06228a1c670ae4c87d1276b92c7c597fdda0"
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
version = "0.11.31"
version = "0.11.35"
[[deps.Parsers]]
deps = ["Dates", "PrecompileTools", "UUIDs"]
git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821"
git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.8.1"
version = "2.8.3"
[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"]
@@ -556,15 +584,15 @@ uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
version = "1.11.0"
[[deps.PtrArrays]]
git-tree-sha1 = "77a42d78b6a92df47ab37e177b2deac405e1c88f"
git-tree-sha1 = "1d36ef11a9aaf1e8b74dacc6a731dd1de8fd493d"
uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
version = "1.2.1"
version = "1.3.0"
[[deps.QuadGK]]
deps = ["DataStructures", "LinearAlgebra"]
git-tree-sha1 = "cda3b045cf9ef07a08ad46731f5a3165e56cf3da"
git-tree-sha1 = "9da16da70037ba9d701192e27befedefb91ec284"
uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
version = "2.11.1"
version = "2.11.2"
[deps.QuadGK.extensions]
QuadGKEnzymeExt = "Enzyme"
@@ -595,15 +623,19 @@ version = "1.2.2"
[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.0"
version = "1.3.1"
[[deps.Revise]]
deps = ["CodeTracking", "Distributed", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "REPL", "Requires", "UUIDs", "Unicode"]
git-tree-sha1 = "470f48c9c4ea2170fd4d0f8eb5118327aada22f5"
deps = ["CodeTracking", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "REPL", "Requires", "UUIDs", "Unicode"]
git-tree-sha1 = "f6f7d30fb0d61c64d0cfe56cf085a7c9e7d5bc80"
uuid = "295af30f-e4ad-537b-8983-00126c2a3abe"
version = "3.6.4"
version = "3.8.0"
weakdeps = ["Distributed"]
[deps.Revise.extensions]
DistributedExt = "Distributed"
[[deps.Rmath]]
deps = ["Random", "Rmath_jll"]
@@ -623,28 +655,32 @@ version = "0.7.0"
[[deps.SQLLLM]]
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON3", "LLMMCTS", "LibPQ", "PrettyPrinting", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"]
git-tree-sha1 = "45e660e44de0950a5e5f92d467298d8b768b6023"
repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/SQLLLM"
path = "/appfolder/app/dev/SQLLLM"
uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
version = "0.2.0"
version = "0.2.4"
[[deps.SQLStrings]]
git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c"
uuid = "af517c2e-c243-48fa-aab8-efac3db270f5"
version = "0.1.0"
[[deps.ScopedValues]]
deps = ["HashArrayMappedTries", "Logging"]
git-tree-sha1 = "1147f140b4c8ddab224c94efa9569fc23d63ab44"
uuid = "7e506255-f358-4e82-b7e4-beb19740aa63"
version = "1.3.0"
[[deps.Scratch]]
deps = ["Dates"]
git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386"
git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109"
uuid = "6c6a2e73-6563-6170-7368-637461726353"
version = "1.2.1"
version = "1.3.0"
[[deps.SentinelArrays]]
deps = ["Dates", "Random"]
git-tree-sha1 = "d0553ce4031a081cc42387a9b9c8441b7d99f32d"
git-tree-sha1 = "712fb0231ee6f9120e005ccd56297abbc053e7e0"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.4.7"
version = "1.4.8"
[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
@@ -659,6 +695,12 @@ version = "1.2.0"
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
version = "1.11.0"
[[deps.Sodium]]
deps = ["Base64", "libsodium_jll"]
git-tree-sha1 = "907703e0d50846f300650d7225bdcab145b7bca9"
uuid = "4f5b5e99-b0ad-42cd-b47a-334e172ec8bd"
version = "1.1.2"
[[deps.SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085"
@@ -672,9 +714,9 @@ version = "1.11.0"
[[deps.SpecialFunctions]]
deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "2f5d4697f21388cbe1ff299430dd169ef97d7e14"
git-tree-sha1 = "41852b8679f78c8d8961eeadc8f62cef861a52e3"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "2.4.0"
version = "2.5.1"
[deps.SpecialFunctions.extensions]
SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
@@ -694,21 +736,21 @@ weakdeps = ["SparseArrays"]
[[deps.StatsAPI]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed"
git-tree-sha1 = "9d72a13a3f4dd3795a195ac5a44d7d6ff5f552ff"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.7.0"
version = "1.7.1"
[[deps.StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21"
deps = ["AliasTables", "DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "b81c5035922cc89c2d9523afc6c54be512411466"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.34.3"
version = "0.34.5"
[[deps.StatsFuns]]
deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
git-tree-sha1 = "b423576adc27097764a90e163157bcfc9acf0f46"
git-tree-sha1 = "8e45cecc66f3b42633b8ce14d431e8e57a3e242e"
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
version = "1.3.2"
version = "1.5.0"
[deps.StatsFuns.extensions]
StatsFunsChainRulesCoreExt = "ChainRulesCore"
@@ -720,9 +762,9 @@ version = "1.3.2"
[[deps.StringManipulation]]
deps = ["PrecompileTools"]
git-tree-sha1 = "a6b1675a536c5ad1a60e5a5153e1fee12eb146e3"
git-tree-sha1 = "725421ae8e530ec29bcbdddbe91ff8053421d023"
uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e"
version = "0.4.0"
version = "0.4.1"
[[deps.StructTypes]]
deps = ["Dates", "UUIDs"]
@@ -750,9 +792,9 @@ version = "1.0.3"
[[deps.TZJData]]
deps = ["Artifacts"]
git-tree-sha1 = "36b40607bf2bf856828690e097e1c799623b0602"
git-tree-sha1 = "72df96b3a595b7aab1e101eb07d2a435963a97e2"
uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7"
version = "1.3.0+2024b"
version = "1.5.0+2025b"
[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
@@ -762,9 +804,9 @@ version = "1.0.1"
[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
git-tree-sha1 = "598cd7c1f68d1e205689b1c2fe65a9f85846f297"
git-tree-sha1 = "f2c1efbc8f3a609aadf318094f8fc5204bdaf344"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.12.0"
version = "1.12.1"
[[deps.Tar]]
deps = ["ArgTools", "SHA"]
@@ -777,10 +819,10 @@ uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
version = "1.11.0"
[[deps.TimeZones]]
deps = ["Dates", "Downloads", "InlineStrings", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"]
git-tree-sha1 = "33c771f2157712ff4c85931186a4984efbe58934"
deps = ["Artifacts", "Dates", "Downloads", "InlineStrings", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"]
git-tree-sha1 = "2c705e96825b66c4a3f25031a683c06518256dd3"
uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53"
version = "1.19.0"
version = "1.21.3"
weakdeps = ["RecipesBase"]
[deps.TimeZones.extensions]
@@ -792,9 +834,9 @@ uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.11.3"
[[deps.URIs]]
git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b"
git-tree-sha1 = "bef26fb046d031353ef97a82e3fdb6afe7f21b1a"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.5.1"
version = "1.6.1"
[[deps.UTCDateTimes]]
deps = ["Dates", "TimeZones"]
@@ -829,15 +871,21 @@ version = "1.2.13+1"
[[deps.Zstd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b"
git-tree-sha1 = "446b23e73536f84e8037f5dce465e92275f6a308"
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
version = "1.5.6+1"
version = "1.5.7+1"
[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.11.0+0"
[[deps.libsodium_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
git-tree-sha1 = "011b0a7331b41c25524b64dc42afc9683ee89026"
uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8"
version = "1.0.21+0"
[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

View File

@@ -1,9 +1,10 @@
name = "YiemAgent"
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
authors = ["narawat lamaiin <narawat@outlook.com>"]
version = "0.1.1"
version = "0.4.0"
[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
@@ -12,6 +13,7 @@ HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
LLMMCTS = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
NATS = "55e73f9c-eeeb-467f-b4cc-a633fde63d2a"
PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
@@ -21,7 +23,6 @@ URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[compat]
CSV = "0.10.15"
DataFrames = "1.7.0"
GeneralUtils = "0.1.0"
LLMMCTS = "0.1.2"
SQLLLM = "0.2.0"
NATS = "0.1.0"

View File

@@ -0,0 +1,585 @@
using Revise
using JSON, JSON3, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames, DataStructures
using YiemAgent, GeneralUtils
using Base.Threads
# ---------------------------------------------- 100 --------------------------------------------- #
# load config
config = JSON3.read("/appfolder/app/dev/YiemAgent/test/config.json")
# config = copy(JSON3.read("../mountvolume/config.json"))
function executeSQL(sql::T) where {T<:AbstractString}
host = config[:externalservice][:wineDB][:host]
port = config[:externalservice][:wineDB][:port]
dbname = config[:externalservice][:wineDB][:dbname]
user = config[:externalservice][:wineDB][:user]
password = config[:externalservice][:wineDB][:password]
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
result = LibPQ.execute(DBconnection, sql)
close(DBconnection)
return result
end
function executeSQLVectorDB(sql)
host = config[:externalservice][:SQLVectorDB][:host]
port = config[:externalservice][:SQLVectorDB][:port]
dbname = config[:externalservice][:SQLVectorDB][:dbname]
user = config[:externalservice][:SQLVectorDB][:user]
password = config[:externalservice][:SQLVectorDB][:password]
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
result = LibPQ.execute(DBconnection, sql)
close(DBconnection)
return result
end
function text2textInstructLLM(prompt::String; maxattempt::Integer=10, modelsize::String="medium",
senderId=GeneralUtils.uuid4snakecase(), timeout=90,
llmkwargs=Dict(
:num_ctx => 32768,
:temperature => 0.5,
)
)
msgMeta = GeneralUtils.generate_msgMeta(
config[:externalservice][:loadbalancer][:mqtttopic];
msgPurpose="inference",
senderName="yiemagent",
senderId=senderId,
receiverName="text2textinstruct_$modelsize",
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict(
:text => prompt,
:kwargs => llmkwargs
)
)
response = nothing
for attempts in 1:maxattempt
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; responsetimeout=timeout, responsemaxattempt=maxattempt)
payload = _response[:response]
if _response[:success] && payload[:text] !== nothing
response = _response[:response][:text]
break
else
println("\n<text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(outgoingMsg)
println("</text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
sleep(3)
end
end
return response
end
# get text embedding from a LLM service
function getEmbedding(text::T) where {T<:AbstractString}
msgMeta = GeneralUtils.generate_msgMeta(
config[:externalservice][:loadbalancer][:mqtttopic];
msgPurpose="embedding",
senderName="yiemagent",
senderId=sessionId,
receiverName="textembedding",
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict(
:text => [text] # must be a vector of string
)
)
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; responsetimeout=120, responsemaxattempt=3)
embedding = response[:response][:embeddings]
return embedding
end
function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
vectorDB::Function; limit::Integer=1
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
# get embedding from LLM service
embedding = getEmbedding(text)[1]
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
sql = """
SELECT *, $embeddingColumnName <-> '$embedding' as distance
FROM $tablename
ORDER BY distance LIMIT $limit;
"""
response = vectorDB(sql)
df = DataFrame(response)
return df
end
function similarSQLVectorDB(query; maxdistance::Integer=100)
tablename = "sqlllm_decision_repository"
# get embedding of the query
df = findSimilarTextFromVectorDB(query, tablename,
"function_input_embedding", executeSQLVectorDB)
# println(df[1, [:id, :function_output]])
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
# distance = 100 # CHANGE this is for testing only
if row != 0 && distance < maxdistance
# if there is usable SQL, return it.
output_b64 = df[1, :function_output_base64] # pick the closest match
output_str = String(base64decode(output_b64))
rowid = df[1, :id]
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
return (dict=output_str, distance=distance)
else
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
return (dict=nothing, distance=nothing)
end
end
function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=3) where {T1<:AbstractString, T2<:AbstractString}
tablename = "sqlllm_decision_repository"
# get embedding of the query
# query = state[:thoughtHistory][:question]
df = findSimilarTextFromVectorDB(query, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
query_embedding = getEmbedding(query)[1]
query = replace(query, "'" => "")
sql_base64 = base64encode(SQL)
sql_ = replace(SQL, "'" => "")
sql = """
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
"""
# println("\n~~~ added new decision to vectorDB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# println(sql)
_ = executeSQLVectorDB(sql)
end
end
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=3
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
tablename = "sommelier_decision_repository"
# find similar
println("\n~~~ search vectorDB for this: $recentevents ", @__FILE__, " ", @__LINE__)
df = findSimilarTextFromVectorDB(recentevents, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row != 0 && distance < maxdistance
# if there is usable decision, return it.
rowid = df[1, :id]
println("\n~~~ found similar decision. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
output_b64 = df[1, :function_output_base64] # pick the closest match
_output_str = String(base64decode(output_b64))
output = copy(JSON3.read(_output_str))
return output
else
println("\n~~~ similar decision not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
return nothing
end
end
function insertSommelierDecision(recentevents::T1, decision::T2; maxdistance::Integer=5
) where {T1<:AbstractString, T2<:AbstractDict}
tablename = "sommelier_decision_repository"
# find similar
df = findSimilarTextFromVectorDB(recentevents, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
recentevents_embedding = getEmbedding(recentevents)[1]
recentevents = replace(recentevents, "'" => "")
decision_json = JSON3.write(decision)
decision_base64 = base64encode(decision_json)
decision = replace(decision_json, "'" => "")
sql = """
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$recentevents', '$decision', '$decision_base64', '$recentevents_embedding');
"""
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
println(sql)
_ = executeSQLVectorDB(sql)
else
println("~~~ similar decision previously cached, distance $distance ", @__FILE__, " ", @__LINE__)
end
end
sessionId = GeneralUtils.uuid4snakecase()
externalFunction = (
getEmbedding=getEmbedding,
text2textInstructLLM=text2textInstructLLM,
executeSQL=executeSQL,
similarSQLVectorDB=similarSQLVectorDB,
insertSQLVectorDB=insertSQLVectorDB,
similarSommelierDecision=similarSommelierDecision,
insertSommelierDecision=insertSommelierDecision,
)
# s = "full-bodied red wine, budget 1500 USD"
# r = YiemAgent.extractWineAttributes_1(agent, s)
# println(r)
# --------------------------- generating scenario and customer profile --------------------------- #
function rolegenerator()
rolegenerator_systemmsg =
"""
Your role:
- You are a helpful assistant
Your mission:
- Create one random role of a potential customer of an internet wine store.
You must follow the following guidelines:
- the user only need the role, do not add your own words.
- the role should be detailed and realistic.
You should then respond to the user with:
Name: a name of the potential customer
Situation: a situation that the potential customer may be facing
Mission: a mission of the potential customer
Profile: a profile of the potential customer, including their age, gender, occupation, and other relevant information
You should only respond in format as described below:
Name: ...
Situation: ...
Mission: ...
Profile: ...
Additional_information: ...
Here are some examples:
Name: Jimmy
Situation:
- Your relationship with your boss is not that good. You need to improve your relationship with your boss.
- Your boss's wedding anniversary is coming up.
- You are at a wine store and start talking with the store's sommelier.
Mission:
- Ask the sommelier to provide multiple wine options, and subsequently choose one option from the presented list.
Profile:
- You are a young professional in a big company.
- You are avid party goer
- You like beer.
- You know nothing about wine.
- You have a budget of 1500usd.
Additional_information:
- your boss like spicy food.
- your boss is a middle-aged man.
- your boss likes Australian wine.
Name: Kate
Situation:
- Your husband asked you to get him a bottle of wine. He will gift the wine to his business client while dining at a German restaurant.
- Your husband is a business client and he will gift the wine to his business
- You are at a wine store and start talking with the store's sommelier.
Mission:
- Ask the sommelier to provide multiple wine options, and subsequently choose one option from the presented list.
Profile:
- You are a CEO in a startup company.
- You are a nerd
- You don't like alcohol.
- You have a budget of 150usd.
- You don't care about organic, sulfite, gluten-free, or sustainability certified wines
Additional_information:
- your husband like spicy food.
- your husband is a middle-aged man.
Name: John
Situation:
- A local newspaper club wants to have a scoop about wine with local food in the U.S.
- You are at a wine store and start talking with the store's sommelier.
Mission:
- Ask the sommelier to provide multiple wine options, and subsequently choose one option from the presented list.
Profile:
- I'm a young guy.
- I prefer to express my ideas in a succinct and clear manner.
Additional_information:
- N/A
Name: Jane
Situation:
- You have catering a dinner party with French cuisine.
- You want to serve wine with your guests.
- You are at a wine store and start talking with the store's sommelier.
Mission:
- Ask the sommelier to provide multiple wine options, and subsequently choose one option from the presented list.
Profile:
- You are a young French restaurant owner.
- You like dry, full-bodied red wine with high tannin
- You don't care about organic, sulfite, gluten-free, or sustainability certified wines.
- You have a budget of 200 usd.
Additional_information:
- N/A
Let's begin!
"""
header = ["Name:", "Situation:", "Mission:", "Profile:", "Additional_information:"]
dictkey = ["name", "situation", "mission", "profile", "additional_information"]
errornote = "N/A"
for attempt in 1:10
_prompt =
[
Dict(:name => "system", :text => rolegenerator_systemmsg),
]
prompt = GeneralUtils.formatLLMtext(_prompt, "qwen3")
response = text2textInstructLLM(prompt) # generated role
response = GeneralUtils.deFormatLLMtext(response, "qwen3")
think, response = GeneralUtils.extractthink(response)
# check whether response has all header
detected_kw = GeneralUtils.detect_keyword(header, response)
kwvalue = [i for i in values(detected_kw)]
zeroind = findall(x -> x == 0, kwvalue)
missingkeys = [header[i] for i in zeroind]
if 0 values(detected_kw)
errornote = "$missingkeys are missing from your previous response"
println("\nERROR YiemAgent rolegenerator() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue
elseif sum(values(detected_kw)) > length(header)
errornote = "\nYour previous attempt has duplicated points according to the required response format"
println("\nERROR YiemAgent rolegenerator() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue
end
responsedict = GeneralUtils.textToDict(response, header;
dictKey=dictkey, symbolkey=true)
responsedict[:id] = GeneralUtils.uuid4snakecase()
responsedict[:systemmsg] =
"""
You are role playing as a CUSTOMER of a wine store and you are currently talking with a sommelier of a wine store.
Your profile is as follows:
Situation: $(responsedict[:situation])
Mission: $(responsedict[:mission])
Profile: $(responsedict[:profile])
Additional_information: $(responsedict[:additional_information])
You should follow the following guidelines:
- Focus on the lastest conversation
- Your like to be short and concise
- If you don't know an answer to sommelier's question, you should say: I don't know.
- If you think the store can't provide what you seek, you can leave.
You should then respond to the user with:
Dialogue: what you want to say to the user
Role: Verify that the dialogue is intended for the customer of a wine store. Can be "yes" or "no"
You should only respond in format as described below:
Dialogue: ...
Role: ...
Let's begin!
"""
println("\nrolegenerator() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
println(responsedict)
return responsedict
end
error("ERROR rolegenerator() failed to generate customer role: ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
end
# Define the external functions for the customer agent in named tuple format
customer_externalFunction = (
text2textInstructLLM=text2textInstructLLM,
)
function main()
agent = YiemAgent.sommelier(
externalFunction;
name="Jane",
id=sessionId, # agent instance id
retailername="Yiem",
llmFormatName="qwen3"
)
customerDict = rolegenerator()
customer = YiemAgent.virtualcustomer(
customer_externalFunction;
systemmsg=customerDict[:systemmsg],
name=customerDict[:name],
id=sessionId, # agent instance id
llmFormatName="qwen3"
)
# customer_chat = "hello"
# YiemAgent.addNewMessage(customer, "assistant", customer_chat)
# # add user activity to events memory
# push!(customer.memory[:events],
# YiemAgent.eventdict(;
# event_description="the assistant talks to the user.",
# timestamp=Dates.now(),
# subject="assistant",
# actionname="CHATBOX",
# action_input=customer_chat,
# )
# )
# println("\ncustomer respond:\n $customer_chat")
agent_response = YiemAgent.conversation(agent; maximumMsg=50)
println("\nagent respond:\n $agent_response")
while true
customer_chat = nothing
while customer_chat === nothing
customer_response = YiemAgent.conversation(customer, Dict(:text=> agent_response);
converPartnerName=agent.name,
maximumMsg=50)
customer_response = GeneralUtils.deFormatLLMtext(customer_response, customer.llmFormatName)
customer_chat = customer_response
#[WORKING] check whether customer response the same before
end
println("\ncustomer respond:\n $customer_chat")
agent_response = YiemAgent.conversation(agent;
userinput=Dict(:text=> customer_chat),
maximumMsg=50)
println("\nagent respond:\n $agent_response")
if haskey(agent.memory[:events][end], :thought)
lastAssistantAction = agent.memory[:events][end][:thought][:actionname]
if lastAssistantAction == "ENDCONVERSATION" # store thoughtDict
# save a.memory[:shortmem][:decisionlog] to disk using JSON3
println("\nsaving agent.memory[:shortmem][:decisionlog] to disk")
date = "$(Dates.now())"
date = replace(date, ':'=>'.')
filename = "agent_decision_log_$(date)_$(agent.id).json"
filepath = "/appfolder/mountvolume/appdata/log/$filename"
open(filepath, "w") do io
JSON3.pretty(io, agent.memory[:shortmem][:decisionlog])
end
# check how many file in /appfolder/mountvolume/appdata/log/ folder now
logfilesnumber = length(readdir("/appfolder/mountvolume/appdata/log/"))
println("\nCaching conversation process done. Total $logfilesnumber files in /appfolder/mountvolume/appdata/log/ folder now.\n")
break
end
end
end
end
for i in 1:100
main()
println("\n Round $i/100 done.")
end
println("done")
# prompt =
# """
# <|im_start|>system
# You are a role playing agent acting as:
# Name: Emily
# Situation: - Emily is planning her upcoming birthday party and wants to make it extra special. She has invited close friends and family, and she's looking for a unique wine that will impress them.
# Mission: - Emily needs to find a rare and high-quality wine that matches the theme of her party, which is a mix of classic and modern flavors. She also wants to ensure that the wine is not too expensive so that it won't break her budget.
# Profile: - Emily is in her late 20s, works as a marketing executive for a tech company, and has a passion for trying new things. She's organized and detail-oriented but can be spontaneous when it comes to planning events.
# Additional_information: - Emily loves experimenting with different types of food and wine pairings.
# Your are currently talking with a sommelier.
# You should follow the following guidelines:
# - Focus on the lastest conversation
# - If you satisfy with the sommelier's recommendation for bottle of wine(s), you should say: Thanks for you help. I will buy the wine you recommended.
# - If you don't satisfy with the sommelier's questions or can't get a good wine recommendation, you can continue the conversation.
# Let's begin!
# <|im_end|>
# <|im_start|>Jane
# Hello! Welcome to Yiem's Wine Store. I'm Jane, your friendly sommelier. How can I assist you today? What type of wine are you in the mood for, and is there a special occasion or event on your mind?
# <|im_end|>
# <|im_start|>Emily
# Hi Jane! Thank you so much for welcoming me. For my birthday party, I'm looking for something that combines classic and modern flavors. It's a mix of guests who enjoy both traditional tastes and more contemporary ones. Also, I want to make sure it won't break the bank. Any suggestions?
# <|im_end|>
# <|im_start|>Jane
# Thank you for sharing your preferences, Jane! To better assist you, could you please let me know if there are any specific characteristics of wine you're looking for, such as tannin, sweetness, intensity, or acidity? Additionally, do you have any food items in mind that this wine should pair well with?
# <|im_end|>
# <|im_start|>Emily
# """
# llmkwargs=Dict(
# :num_ctx => 32768,
# :temperature => 0.3,
# )
# r = text2textInstructLLM(prompt, llmkwargs=llmkwargs)
# println(r)
# println(555)
# response = YiemAgent.conversation(agent, Dict(:text=> "I want to get a French red wine under 100."))
# while true
# println("your respond: ")
# user_answer = readline()
# response = YiemAgent.conversation(agent, Dict(:text=> user_answer))
# println("\n$response")
# end
# """
# Hello
# I would like to get a bottle of wine for my boss but I don't know much about wine. Can you help me?
# well actually, my boss is going to offer the wine to his client as a gift in a business meeting. All I know is his client like spicy food and French wine. I have a budget about 1000.
# """
# input = "French wine, bordeaux, under USD100, pairs with spicy food"
# r = YiemAgent.extractWineAttributes_1(a, input)
# inventory_order = "French Syrah, Viognier, full bodied, under 100"
# r = YiemAgent.extractWineAttributes_2(a, inventory_order)
# pprintln(r)
# cron job
# @reboot sleep 50 && nvidia-smi -pm 1
# @reboot sleep 51 && nvidia-smi -i 0 -pl 150
# @reboot sleep 52 && nvidia-smi -i 1 -pl 150
# @reboot sleep 53 && nvidia-smi -i 2 -pl 150
# @reboot sleep 54 && nvidia-smi -i 3 -pl 150
# @reboot sleep 55 && julia -t 2 /home/ton/work/restartContainer/main.jl
# using GeneralUtils
# msgMeta = GeneralUtils.generate_msgMeta(
# "/tonpc_containerServices",
# senderName= "somename",
# senderId= "1230",
# mqttBrokerAddress= "mqtt.yiem.cc",
# mqttBrokerPort= 1883,
# )
# outgoingMsg = Dict(
# :msgMeta=> msgMeta,
# :payload=> "docker container restart playground-app",
# )
# GeneralUtils.sendMqttMsg(outgoingMsg)

76
example/config.json Normal file
View File

@@ -0,0 +1,76 @@
{
"mqttServerInfo": {
"description": "mqtt server info",
"port": 1883,
"broker": "mqtt.yiem.cc"
},
"testingOrProduction": {
"value": "testing",
"description": "agent status, couldbe testing or production"
},
"agentid": {
"value": "2b74b87a-5413-4fe2-a4d3-405891051680",
"description": "a unique id for this agent"
},
"agentCentralConfigTopic": {
"mqtttopic": "/yiem_branch_1/agent/sommelier/backend/config/api/v1.1",
"description": "a central agent server's topic to get this agent config"
},
"servicetopic": {
"mqtttopic": [
"/yiem/hq/agent/sommelier/backend/prompt/api_v1/testing"
],
"description": "a topic this agent are waiting for service request"
},
"role": {
"value": "sommelier",
"description": "agent role"
},
"organization": {
"value": "yiem_branch_1",
"description": "organization name"
},
"externalservice": {
"loadbalancer": {
"mqtttopic": "/loadbalancer/requestingservice",
"description": "text to text service with instruct LLM"
},
"text2textinstruct": {
"mqtttopic": "/loadbalancer/requestingservice",
"description": "text to text service with instruct LLM",
"llminfo": {
"name": "llama3instruct"
}
},
"virtualWineCustomer_1": {
"mqtttopic": "/virtualenvironment/winecustomer",
"description": "text to text service with instruct LLM that act as wine customer",
"llminfo": {
"name": "llama3instruct"
}
},
"text2textchat": {
"mqtttopic": "/loadbalancer/requestingservice",
"description": "text to text service with instruct LLM",
"llminfo": {
"name": "llama3instruct"
}
},
"wineDB" : {
"description": "A wine database connection info for LibPQ client",
"host": "192.168.88.12",
"port": 10201,
"dbname": "wineDB",
"user": "yiemtechnologies",
"password": "yiemtechnologies@Postgres_0.0"
},
"SQLVectorDB" : {
"description": "A wine database connection info for LibPQ client",
"host": "192.168.88.12",
"port": 10203,
"dbname": "SQLVectorDB",
"user": "yiemtechnologies",
"password": "yiemtechnologies@Postgres_0.0"
}
}
}

706
example/main.jl Normal file
View File

@@ -0,0 +1,706 @@
using JSON, JSON3, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames, DataStructures
using YiemAgent, GeneralUtils
using Base.Threads
# ---------------------------------------------- 100 --------------------------------------------- #
""" Expected incomming MQTT message format for this service:
{
"msgMeta": {
"msgPurpose": "updateStatus",
"requestresponse": "request",
"timestamp": "2024-03-29T05:8:48.362",
"replyToMsgId": null,
"receiverId": null,
"getpost": "get",
"msgId": "e5c09bd8-7100-4e4e-bb43-05bee589a22c",
"acknowledgestatus": null,
"sendTopic": "/agent/wine/backend/chat/api/v1/prompt",
"receiverName": "agent-wine-backend",
"replyTopic": "/agent/wine/frontend/chat/api/v1/txt/receive",
"senderName": "agent-wine-frontend-chat",
"senderId": "0938a757-e0ee-40a9-8355-5e24906a87cd"
},
"payload" : {
"text": "hello"
}
}
"""
# load config
config = copy(JSON3.read("../mountvolume/config/config.json"))
""" Instantiate an agent. One need to specify startmessage and one of gpu location info,
Mqtt or Rest. start message must be comply with GeneralUtils's message format
Arguments\n
-----
channel::Channel
communication channel
sessionId::String
sesstion ID of the agent
agentName::String
Name of the agent
mqttBroker::String
mqtt broker e.g. "tcp://127.0.0.1:1883"
agentConfigTopic::String
main communication topic for an agent to ask for config
timeout::Int64
inactivity timeout in minutes. If timeout is reached, an agent will be terminated.
Return\n
-----
a task represent an agent
Example\n
-----
```jldoctest
julia> using YiemAgent, GeneralUtils
julia> msg = GeneralUtils.generate_msgMeta("/agent")
julia> incoming_msg = msg # assuming 1st msg was sent from other app
julia> agentConfigTopic = "/agent/wine/backend/config"
julia> task = runAgentInstance(incoming_msg, mqttBroker, agentConfigTopic, 60)
```
TODO\n
-----
[] update docstringLAMA_CONTEXT_LENGTH=40960 since the default size is 2048 as you can see in your debug log:
[] change how to get result of YiemAgent from let YiemAgent send msg directly to frontend,
to
response = YiemAgent.conversation()
then send response to frontend
Signature\n
-----
"""
function runAgentInstance(
receiveUserMsgChannel::Channel,
outputchannel::Channel,
sessionId::String,
config::Dict,
timeout::Int64,
)
function executeSQL(sql::T) where {T<:AbstractString}
host = config[:externalservice][:wineDB][:host]
port = config[:externalservice][:wineDB][:port]
dbname = config[:externalservice][:wineDB][:dbname]
user = config[:externalservice][:wineDB][:user]
password = config[:externalservice][:wineDB][:password]
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
result = LibPQ.execute(DBconnection, sql)
close(DBconnection)
return result
end
function executeSQLVectorDB(sql)
host = config[:externalservice][:SQLVectorDB][:host]
port = config[:externalservice][:SQLVectorDB][:port]
dbname = config[:externalservice][:SQLVectorDB][:dbname]
user = config[:externalservice][:SQLVectorDB][:user]
password = config[:externalservice][:SQLVectorDB][:password]
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
result = LibPQ.execute(DBconnection, sql)
close(DBconnection)
return result
end
function text2textInstructLLM(prompt::String; maxattempt::Integer=3, modelsize::String="medium",
senderId=GeneralUtils.uuid4snakecase(), timeout=180,
llmkwargs=Dict(
:num_ctx => 32768,
:temperature => 0.5,
))
msgMeta = GeneralUtils.generate_msgMeta(
config[:externalservice][:loadbalancer][:mqtttopic];
msgPurpose="inference",
senderName="yiemagent",
senderId=senderId,
receiverName="text2textinstruct_$modelsize",
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict(
:text => prompt,
:kwargs => llmkwargs
)
)
response = nothing
for attempts in 1:maxattempt
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=timeout, maxattempt=maxattempt)
payload = _response[:response]
if _response[:success] && payload[:text] !== nothing
response = _response[:response][:text]
break
else
println("\n<text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(outgoingMsg)
println("</text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
sleep(3)
end
end
return response
end
# get text embedding from a LLM service
function getEmbedding(text::T) where {T<:AbstractString}
msgMeta = GeneralUtils.generate_msgMeta(
config[:externalservice][:loadbalancer][:mqtttopic];
msgPurpose="embedding",
senderName="yiemagent",
senderId=sessionId,
receiverName="textembedding",
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict(
:text => [text] # must be a vector of string
)
)
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120, maxattempt=3)
embedding = response[:response][:embeddings]
return embedding
end
function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
vectorDB::Function; limit::Integer=1
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
# get embedding from LLM service
embedding = getEmbedding(text)[1]
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
sql = """
SELECT *, $embeddingColumnName <-> '$embedding' as distance
FROM $tablename
ORDER BY distance LIMIT $limit;
"""
response = vectorDB(sql)
df = DataFrame(response)
return df
end
function similarSQLVectorDB(query; maxdistance::Integer=100)
tablename = "sqlllm_decision_repository"
# get embedding of the query
df = findSimilarTextFromVectorDB(query, tablename,
"function_input_embedding", executeSQLVectorDB)
# println(df[1, [:id, :function_output]])
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
# distance = 100 # CHANGE this is for testing only
if row != 0 && distance < maxdistance
# if there is usable SQL, return it.
output_b64 = df[1, :function_output_base64] # pick the closest match
output_str = String(base64decode(output_b64))
rowid = df[1, :id]
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
return (dict=output_str, distance=distance)
else
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
return (dict=nothing, distance=nothing)
end
end
function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=3) where {T1<:AbstractString, T2<:AbstractString}
tablename = "sqlllm_decision_repository"
# get embedding of the query
# query = state[:thoughtHistory][:question]
df = findSimilarTextFromVectorDB(query, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
query_embedding = getEmbedding(query)[1]
query = replace(query, "'" => "")
sql_base64 = base64encode(SQL)
sql_ = replace(SQL, "'" => "")
sql = """
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
"""
# println("\n~~~ added new decision to vectorDB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# println(sql)
_ = executeSQLVectorDB(sql)
end
end
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=3
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
tablename = "sommelier_decision_repository"
# find similar
println("\n~~~ search vectorDB for this: $recentevents ", @__FILE__, " ", @__LINE__)
df = findSimilarTextFromVectorDB(recentevents, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row != 0 && distance < maxdistance
# if there is usable decision, return it.
rowid = df[1, :id]
println("\n~~~ found similar decision. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
output_b64 = df[1, :function_output_base64] # pick the closest match
_output_str = String(base64decode(output_b64))
output = copy(JSON3.read(_output_str))
return output
else
println("\n~~~ similar decision not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
return nothing
end
end
function insertSommelierDecision(recentevents::T1, decision::T2; maxdistance::Integer=5
) where {T1<:AbstractString, T2<:AbstractDict}
tablename = "sommelier_decision_repository"
# find similar
df = findSimilarTextFromVectorDB(recentevents, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
recentevents_embedding = getEmbedding(recentevents)[1]
recentevents = replace(recentevents, "'" => "")
decision_json = JSON3.write(decision)
decision_base64 = base64encode(decision_json)
decision = replace(decision_json, "'" => "")
sql =
"""
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$recentevents', '$decision', '$decision_base64', '$recentevents_embedding');
"""
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
println(sql)
_ = executeSQLVectorDB(sql)
else
println("~~~ similar decision previously cached, distance $distance ", @__FILE__, " ", @__LINE__)
end
end
# keepaliveChannel_2::Channel{Dict} = Channel{Dict}(8)
latestUserMsgTimeStamp::DateTime = Dates.now()
externalFunction = (
getEmbedding=getEmbedding,
text2textInstructLLM=text2textInstructLLM,
executeSQL=executeSQL,
similarSQLVectorDB=similarSQLVectorDB,
insertSQLVectorDB=insertSQLVectorDB,
similarSommelierDecision=similarSommelierDecision,
insertSommelierDecision=insertSommelierDecision,
)
agent = YiemAgent.sommelier(
externalFunction;
name="Jane",
id=sessionId, # agent instance id
retailername="Yiem",
llmFormatName="qwen3"
)
# user chat loop
while true
# check for new user message
if isready(receiveUserMsgChannel)
incomingMsg = take!(receiveUserMsgChannel)
incoming_msgMeta = incomingMsg[:msgMeta]
incomingPayload = incomingMsg[:payload]
latestUserMsgTimeStamp = Dates.now()
# make sure the message has :text key because YiemAgent use this key for incoming user msg
if haskey(incomingPayload, :text)
# skip, msg already has correct key name
elseif haskey(incomingPayload, :txt)
# change key name to text
incomingPayload[:text] = incomingPayload[:txt]
else
error("\n no :txt or :text key in the message.")
end
# reset agent
if occursin("newtopic", incomingPayload[:text]) ||
occursin("Newtopic", incomingPayload[:text]) ||
occursin("New topic", incomingPayload[:text]) ||
occursin("new topic", incomingPayload[:text])
# YiemAgent.clearhistory(agent)
agent = YiemAgent.sommelier(
externalFunction;
name="Janie",
id=sessionId, # agent instance id
retailername="Yiem",
)
# sending msg back to sender i.e. LINE
msgMeta = GeneralUtils.generate_msgMeta(
incomingMsg[:msgMeta][:replyTopic];
senderName="wine_assistant_backend",
senderId=sessionId,
replyToMsgId=incomingMsg[:msgMeta][:msgId],
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict(
:alias => agent.name, # will be shown in frontend as agent name
:text => "Okay. What shall we talk about?"
)
)
_ = GeneralUtils.sendMqttMsg(outgoingMsg)
println("--> outgoingMsg ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(outgoingMsg)
else
usermsg = incomingPayload
if incoming_msgMeta[:msgPurpose] == "initialize"
println("\n-- Initializing... ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
end
# send prompt
result = YiemAgent.conversation(agent;
userinput=usermsg,
maximumMsg=50)
# Ken's bot use [br] for newline character '\n'
# result = replace(result, '\n'=>"[br]")
if incoming_msgMeta[:msgPurpose] == "initialize"
println("\n-- Initialized. Ready! waiting for request at:\n$(config[:servicetopic][:mqtttopic]) ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue
end
msgMeta = GeneralUtils.generate_msgMeta(
incomingMsg[:msgMeta][:replyTopic];
senderName="wine_assistant_backend",
senderId=string(uuid4()),
replyToMsgId=incomingMsg[:msgMeta][:msgId],
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict(
:alias => agent.name, # will be shown in frontend as agent name
:text => result
)
)
_ = GeneralUtils.sendMqttMsg(outgoingMsg)
println("\n--> outgoingMsg ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(outgoingMsg)
# jpg_as_juliaStr = nothing
# prompt = nothing
# if haskey(payload, "img")
# url_or_base64 = payload["img"]
# if startswith(url_or_base64, "http")
# # img in http
# julia_rgb_img, cv2_bgr_img = ImageUtils.url_to_cv2_image(url_or_base64)
# _, buffer = cv2.imencode(".jpg", cv2_bgr_img)
# jpg_as_pyStr = base64.b64encode(buffer).decode("utf-8")
# jpg_as_juliaStr = pyconvert(String, jpg_as_pyStr)
# else
# # img in base64
# cv2_bgr_img = payload["img"]
# jpg_as_juliaStr = pyconvert(String, jpg_as_pyStr)
# end
# end
end
else
# println("\n no msg")
end
if haskey(agent.memory[:events][end], :thought)
lastAssistantAction = agent.memory[:events][end][:thought][:action_name]
if lastAssistantAction == "ENDCONVERSATION" # store thoughtDict
# save a.memory[:shortmem][:decisionlog] to disk using JSON3
println("\nsaving agent.memory[:shortmem][:decisionlog] to disk")
filename = "agent_decision_log_$(Dates.now())_$(agent.id).json"
filepath = "/appfolder/app/log/$filename"
open(filepath, "w") do io
JSON3.pretty(io, agent.memory[:shortmem][:decisionlog])
end
# for (i, event) in enumerate(agent.memory[:events])
# if event[:subject] == "assistant"
# # create timeline of the last 3 conversation except the last one.
# # The former will be used as caching key and the latter will be the caching target
# # in vector database
# all_recapkeys = keys(agent.memory[:recap]) #[TESTING] recap as caching
# all_recapkeys_vec = [r for r in all_recapkeys] # convert to a vector
# # select from 1 to 2nd-to-lase event (i.e. excluding the latest which is assistant's response)
# _recapkeys_vec = all_recapkeys_vec[1:i-1]
# # select only previous 3 recaps
# recapkeys_vec =
# if length(_recapkeys_vec) <= 3 # 1st message is a user's hello msg
# _recapkeys_vec # choose all
# else
# _recapkeys_vec[end-2:end]
# end
# #[PENDING] if there is specific data such as number, donot store in database
# tempmem = DataStructures.OrderedDict()
# for k in recapkeys_vec
# tempmem[k] = agent.memory[:recap][k]
# end
# recap = GeneralUtils.dictToString_noKey(tempmem)
# thoughtDict = agent.memory[:events][i][:thought] # latest assistant thoughtDict
# insertSommelierDecision(recap, thoughtDict)
# else
# # skip
# end
# end
println("\nCaching conversation process done")
break
end
end
# self terminate if too long inactivity
timediff = GeneralUtils.timedifference(latestUserMsgTimeStamp, Dates.now(), "minutes")
if timediff > timeout
result = Dict(:exitreason => "timeout", :timestamp => Dates.now())
put!(outputchannel, result)
println("Agent ID $(agent.id) timeout has been reached $timediff/$timeout minutes Send delete session msg ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# send "delete session" message to inform the main loop that this session can be deleted
sendto =
if typeof(config[:servicetopic][:mqtttopic]) <: Array
config[:servicetopic][:mqtttopic][1]
else
config[:servicetopic][:mqtttopic]
end
msgMeta = GeneralUtils.generate_msgMeta(
sendto;
senderName="session",
senderId=sessionId,
msgPurpose="delete session",
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => nothing
)
_ = GeneralUtils.sendMqttMsg(outgoingMsg)
try disconnect(agent.mqttClient) catch end
break
end
sleep(1) # allowing on_msg_2, asyncmove above and other process to run
end
end
sessionDict = Dict{String,Any}()
incomingMsgChannel = (ch1=Channel(8),) # store msg that coming into servicetopic
# incommingInternalMsg = [] # st ore msg that coming into servicetopic internal management
keepaliveChannel::Channel{Dict} = Channel{Dict}(8)
# Define the callback for receiving messages.
function onMsgCallback_1(topic, payload)
jobj = JSON3.read(String(payload))
incomingMqttMsg = copy(jobj) # convert json object into julia dictionary recursively
if occursin("keepalive", topic)
put!(keepaliveChannel, incomingMqttMsg)
else
put!(incomingMsgChannel[:ch1], incomingMqttMsg)
end
end
mqttInstance = GeneralUtils.mqttClientInstance_v2(
config[:mqttServerInfo][:broker],
config[:servicetopic][:mqtttopic],
incomingMsgChannel,
keepaliveChannel,
onMsgCallback_1
)
# ------------------------------------------------------------------------------------------------ #
# this service main loop #
# ------------------------------------------------------------------------------------------------ #
function main()
sessiontimeout = 1 * 1 * 60 # timeout in minute for each instance (day * hour * minute)
initializing = false
while true
# check if mqtt connection is still up
_ = GeneralUtils.checkMqttConnection!(mqttInstance; keepaliveCheckInterval=30)
# initialize session 0
if initializing == false # send init msg
sendto =
if typeof(config[:servicetopic][:mqtttopic]) <: Array
config[:servicetopic][:mqtttopic][1]
else
config[:servicetopic][:mqtttopic]
end
msgMeta = GeneralUtils.generate_msgMeta(
sendto;
msgPurpose="initialize",
senderName="initializer",
senderId="0",
msgId= "initMsg",
replyTopic=sendto,
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict( # will be shown in frontend as agent name
:text => "Do you have full-bodied red wines under 100 USD. I don't have any other preferences."
)
)
_ = GeneralUtils.sendMqttMsg(outgoingMsg)
initializing = true
println("\n--> Initializing msg sent ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
end
# check for new message
if !isempty(incomingMsgChannel[:ch1])
msg = popfirst!(incomingMsgChannel[:ch1])
println("\n<-- incomingMsg ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(msg)
# @spawn new runAgentInstance and store it in sessionDict
# use agent's frontend id because 1 backend agent per 1 frontend session
sessionId = msg[:msgMeta][:senderId]
sessionId = replace(sessionId, "-" => "_") # julia can't use "-" in a dict key
# check for delete session msg
if msg[:msgMeta][:msgPurpose] == "delete session"
delete!(sessionDict, sessionId)
println("sessionId $(sessionId) has been terminated ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# no session yet, create new session
elseif sessionId keys(sessionDict)
inputch = Channel{Dict}(8)
outputch = Channel{Dict}(8)
process = @spawn runAgentInstance(inputch, outputch, sessionId, config, sessiontimeout)
# process = runAgentInstance(inputch, outputch, sessionId, config, sessiontimeout) #XXX use spawn version
println("\ninstantiate agent success ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# call runAgentInstance() and store it in sessionDict to be able to check on it later
sessionDict[sessionId] = Dict(
:inputchannel => inputch,
:outputchannel => outputch,
:process => process,
)
put!(sessionDict[sessionId][:inputchannel], msg)
# ongoing session
else
println("sessionId $(sessionId) existing session ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
put!(sessionDict[sessionId][:inputchannel], msg)
end
end
# sleep is needed because MQTTClient use async. "while true" loop leave no
# chance for control to switch to on_msg()
sleep(1)
end
end
main()

View File

@@ -0,0 +1,72 @@
To make **LLM-driven inference** fast while maintaining its dynamic capabilities, there are a few practices or approaches to avoid, as they could lead to performance bottlenecks or inefficiencies. Here's what *not* to do:
---
### **1. Avoid Using Overly Large Models for Every Query**
While larger LLMs like GPT-4 provide high accuracy and nuanced responses, they may slow down real-time processing due to their computational complexity. Instead:
- Use distilled or smaller models (e.g., GPT-3.5 Turbo or fine-tuned versions) for faster inference without compromising much on quality.
---
### **2. Avoid Excessive Entity Preprocessing**
Dont rely on overly complicated preprocessing steps (like advanced NER models or regex-heavy pipelines) to extract entities from the query before invoking the LLM. This could add latency. Instead:
- Design efficient prompts that allow the LLM to extract entities and generate responses simultaneously.
---
### **3. Avoid Asking the LLM Multiple Separate Questions**
Running the LLM for multiple subtasks—for example, entity extraction first and response generation second—can significantly slow down the pipeline. Instead:
- Create prompts that combine tasks into one pass, e.g., *"Identify the city name and generate a weather response for this query: 'What's the weather in London?'"*.
---
### **4. Dont Overload the LLM with Context History**
Excessively lengthy conversation history or irrelevant context in your prompts can slow down inference times. Instead:
- Provide only the relevant context for each query, trimming unnecessary parts of the conversation.
---
### **5. Avoid Real-Time Dependence on External APIs**
Using external APIs to fetch supplementary data (e.g., weather details or location info) during every query can introduce latency. Instead:
- Pre-fetch API data asynchronously and use the LLM to integrate it dynamically into responses.
---
### **6. Avoid Running LLM on Underpowered Hardware**
Running inference on CPUs or low-spec GPUs will result in slower response times. Instead:
- Deploy the LLM on optimized infrastructure (e.g., high-performance GPUs like NVIDIA A100 or cloud platforms like Azure AI) to reduce latency.
---
### **7. Skip Lengthy Generative Prompts**
Avoid prompts that encourage the LLM to produce overly detailed or verbose responses, as these take longer to process. Instead:
- Use concise prompts that focus on generating actionable or succinct answers.
---
### **8. Dont Ignore Optimization Techniques**
Failing to optimize your LLM setup can drastically impact performance. For example:
- Avoid skipping techniques like model quantization (reducing numerical precision to speed up inference) or distillation (training smaller models).
---
### **9. Dont Neglect Response Caching**
While you may not want a full caching system to avoid sunk costs, dismissing lightweight caching entirely can impact speed. Instead:
- Use temporary session-based caching for very frequent queries, without committing to a full-fledged cache infrastructure.
---
### **10. Avoid One-Size-Fits-All Solutions**
Applying the same LLM inference method to all queries—whether simple or complex—will waste processing resources. Instead:
- Route basic queries to faster, specialized models and use the LLM for nuanced or multi-step queries only.
---
### Summary: Focus on Efficient Design
By avoiding these pitfalls, you can ensure that LLM-driven inference remains fast and responsive:
- Optimize prompts.
- Use smaller models for simpler queries.
- Run the LLM on high-performance hardware.
- Trim unnecessary preprocessing or contextual steps.
Would you like me to help refine a prompt or suggest specific tools to complement your implementation? Let me know!

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,65 @@
module type
export agent, sommelier, companion
export agent, sommelier, companion, virtualcustomer, appcontext
using Dates, UUIDs, DataStructures, JSON3
using Dates, UUIDs, DataStructures, JSON3, NATS
using GeneralUtils
# ---------------------------------------------- 100 --------------------------------------------- #
abstract type agent end
mutable struct appcontext
const connection::NATS.Connection
const text2textInstructLLMServiceSubject::String
getTextEmbedding::Function
text2textInstructLLM::Function
executeSQL::Function
similarSQLVectorDB::Function
insertSQLVectorDB::Function
similarSommelierDecision::Function
insertSommelierDecision::Function
end
abstract type agent end
mutable struct companion <: agent
name::String # agent name
id::String # agent id
systemmsg::String # system message
tools::Dict # tools
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
chathistory::Vector{Dict{Symbol, Any}}
memory::Dict{Symbol, Any}
context::NamedTuple # NamedTuple of functions
llmFormatName::String
end
function companion(
context::appcontext # NamedTuple of functions
;
name::String= "Assistant",
id::String= GeneralUtils.uuid4snakecase(),
maxHistoryMsg::Integer= 20,
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
llmFormatName::String= "granite3",
systemmsg::String=
"""
Your name: $name
Your sex: Female
Your role: You are a helpful assistant.
You should follow the following guidelines:
- Focus on the latest conversation.
- Your like to be short and concise.
Let's begin!
""",
)
tools = Dict( # update input format
"CHATBOX"=> Dict(
:description => "- CHATBOX which you can use to talk with the user. The input is your intentions for the dialogue. Be specific.",
),
)
""" Memory
Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
@@ -22,38 +68,23 @@ mutable struct companion <: agent
Dict(:name=>"user", :text=> "Wassup!", :timestamp=> Dates.now()),
Dict(:name=>"assistant", :text=> "Hi I'm your assistant.", :timestamp=> Dates.now()),
]
"""
chathistory::Vector{Dict{Symbol, Any}}
memory::Dict{Symbol, Any}
# communication function
text2textInstructLLM::Function
end
function companion(
text2textInstructLLM::Function
;
name::String= "Assistant",
id::String= string(uuid4()),
maxHistoryMsg::Integer= 20,
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
)
memory = Dict{Symbol, Any}(
:chatbox=> "",
:shortmem=> OrderedDict{Symbol, Any}(),
:events=> Vector{Dict{Symbol, Any}}(),
:state=> Dict{Symbol, Any}(),
)
:events=> Vector{Dict{Symbol, Any}}(),
:state=> Dict{Symbol, Any}(), # state of the agent
:recap=> OrderedDict{Symbol, Any}(), # recap summary of the conversation
)
newAgent = companion(
name,
id,
systemmsg,
tools,
maxHistoryMsg,
chathistory,
memory,
text2textInstructLLM
context,
llmFormatName
)
return newAgent
@@ -61,6 +92,7 @@ end
""" A sommelier agent.
# Arguments
@@ -134,30 +166,21 @@ mutable struct sommelier <: agent
retailername::String
tools::Dict
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
""" Memory
Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
NO "system" message in chathistory because I want to add it at the inference time
chathistory= [
Dict(:name=>"user", :text=> "Wassup!", :timestamp=> Dates.now()),
Dict(:name=>"assistant", :text=> "Hi I'm your assistant.", :timestamp=> Dates.now()),
]
"""
chathistory::Vector{Dict{Symbol, Any}}
memory::Dict{Symbol, Any}
func # NamedTuple of functions
context # NamedTuple of functions
llmFormatName::String
end
function sommelier(
func, # NamedTuple of functions
context::appcontext, # app context
;
name::String= "Assistant",
id::String= string(uuid4()),
retailername::String= "retailer_name",
maxHistoryMsg::Integer= 20,
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
llmFormatName::String= "granite3"
)
tools = Dict( # update input format
@@ -171,22 +194,27 @@ function sommelier(
:input => """<input>Input is a JSON-formatted string that contains a detailed and precise search query.</input><input example>{\"wine type\": \"rose\", \"price\": \"max 35\", \"sweetness level\": \"sweet\", \"intensity level\": \"light bodied\", \"Tannin level\": \"low\", \"Acidity level\": \"low\"}</input example>""",
:output => """<output>Output are wines that match the search query in JSON format.""",
),
# "finalanswer"=> Dict(
# :description => "<tool description>Useful for when you are ready to recommend wines to the user.</tool description>",
# :input => """<input format>{\"finalanswer\": \"some text\"}.</input format><input example>{\"finalanswer\": \"I recommend Zena Crown Vista\"}</input example>""",
# :output => "" ,
# :func => nothing,
# ),
)
memory = Dict{Symbol, Any}(
:chatbox=> "",
:shortmem=> OrderedDict{Symbol, Any}(),
:events=> Vector{Dict{Symbol, Any}}(),
:state=> Dict{Symbol, Any}(
:wine_presented_to_user=> "None",
),
)
""" Memory
Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
NO "system" message in chathistory because I want to add it at the inference time
chathistory= [
Dict(:name=>"user", :text=> "Wassup!", :timestamp=> Dates.now()),
Dict(:name=>"assistant", :text=> "Hi I'm your assistant.", :timestamp=> Dates.now()),
]
"""
memory = Dict{Symbol, Any}(
:shortmem=> OrderedDict{Symbol, Any}(
:db_search_result=> Any[],
:scratchpad=> "", #[PENDING] should be a dict e.g. Dict(:database_search_result=>Dict(:wines=> "", :search_query=> ""))
),
:events=> Vector{Dict{Symbol, Any}}(),
:state=> Dict{Symbol, Any}(
),
:recap=> OrderedDict{Symbol, Any}(),
)
newAgent = sommelier(
name,
@@ -196,7 +224,82 @@ function sommelier(
maxHistoryMsg,
chathistory,
memory,
func
context,
llmFormatName
)
return newAgent
end
mutable struct virtualcustomer <: agent
name::String # agent name
id::String # agent id
systemmsg::String # system message
tools::Dict
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
chathistory::Vector{Dict{Symbol, Any}}
memory::Dict{Symbol, Any}
context # NamedTuple of functions
llmFormatName::String
end
function virtualcustomer(
context, # NamedTuple of functions
;
name::String= "Assistant",
id::String= string(uuid4()),
maxHistoryMsg::Integer= 20,
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
llmFormatName::String= "granite3",
systemmsg::String=
"""
Your name: $name
Your sex: Female
Your role: You are a helpful assistant.
You should follow the following guidelines:
- Focus on the latest conversation.
- Your like to be short and concise.
Let's begin!
""",
)
tools = Dict( # update input format
"chatbox"=> Dict(
:description => "<askbox tool description>Useful for when you need to ask the user for more context. Do not ask the user their own question.</askbox tool description>",
:input => """<input>Input is a text in JSON format.</input><input example>{\"Q1\": \"How are you doing?\", \"Q2\": \"How may I help you?\"}</input example>""",
:output => "" ,
),
)
""" Memory
Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
NO "system" message in chathistory because I want to add it at the inference time
chathistory= [
Dict(:name=>"user", :text=> "Wassup!", :timestamp=> Dates.now()),
Dict(:name=>"assistant", :text=> "Hi I'm your assistant.", :timestamp=> Dates.now()),
]
"""
memory = Dict{Symbol, Any}(
:shortmem=> OrderedDict{Symbol, Any}(
),
:events=> Vector{Dict{Symbol, Any}}(),
:state=> Dict{Symbol, Any}(
),
:recap=> OrderedDict{Symbol, Any}(),
)
newAgent = virtualcustomer(
name,
id,
systemmsg,
tools,
maxHistoryMsg,
chathistory,
memory,
context,
llmFormatName
)
return newAgent

View File

@@ -1,6 +1,8 @@
module util
export clearhistory, addNewMessage, vectorOfDictToText, eventdict, noises
export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
availableWineToText, createEventsLog, createChatLog, checkAgentResponse_JSON,
checkAgentResponse_text
using UUIDs, Dates, DataStructures, HTTP, JSON3
using GeneralUtils
@@ -101,12 +103,12 @@ end
-----
"""
function addNewMessage(a::T1, name::String, text::T2;
maximumMsg::Integer=20) where {T1<:agent, T2<:AbstractString}
maximumMsg::Integer=30) where {T1<:agent, T2<:AbstractString}
if name ["system", "user", "assistant"] # guard against typo
error("name is not in agent.availableRole $(@__LINE__)")
end
#[] summarize the oldest 10 message
#[PENDING] summarize the oldest 10 message
if length(a.chathistory) > maximumMsg
summarize(a.chathistory)
else
@@ -121,47 +123,53 @@ This function takes in a vector of dictionaries and outputs a single string wher
# Arguments
- `vecd::Vector`
a vector of dictionaries
A vector of dictionaries containing chat messages
- `withkey::Bool`
whether to include the key in the output text. Default is true
Whether to include the name as a prefix in the output text. Default is true
- `range::Union{Nothing,UnitRange,Int}`
Optional range of messages to include. If nothing, includes all messages
# Return
a string with the formatted dictionaries
# Returns
A formatted string where each line contains either:
- If withkey=true: "name> message\n"
- If withkey=false: "message\n"
# Example
```jldoctest
julia> using Revise
julia> using GeneralUtils
julia> vecd = [Dict(:name => "John", :text => "Hello"), Dict(:name => "Jane", :text => "Goodbye")]
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
"John> Hello\nJane> Goodbye\n"
```
# Signature
"""
function vectorOfDictToText(vecd::Vector; withkey=true)::String
function chatHistoryToText(vecd::Vector; withkey=true, range=nothing)::String
# Initialize an empty string to hold the final text
text = ""
# Get the elements within the specified range, or all elements if no range provided
elements = isnothing(range) ? vecd : vecd[range]
# Determine whether to include the key in the output text or not
if withkey
# Loop through each dictionary in the input vector
for d in vecd
# Extract the 'name' and 'text' keys from the dictionary
name = d[:name]
_text = d[:text]
# Append the formatted string to the text variable
text *= "$name> $_text \n"
# Loop through each dictionary in the input vector
for d in elements
# Extract the 'name' and 'text' keys from the dictionary
name = titlecase(d[:name])
_text = d[:text]
# Append the formatted string to the text variable
text *= "$name> $_text \n"
end
else
# Loop through each dictionary in the input vector
for d in vecd
# Iterate over all key-value pairs in the dictionary
for (k, v) in d
# Append the formatted string to the text variable
text *= "$v \n"
end
end
# Loop through each dictionary in the input vector
for d in elements
# Iterate over all key-value pairs in the dictionary
for (k, v) in d
# Append the formatted string to the text variable
text *= "$v \n"
end
end
end
# Return the final text
@@ -169,255 +177,280 @@ function vectorOfDictToText(vecd::Vector; withkey=true)::String
end
function eventdict(;
event_description::Union{String, Nothing}=nothing,
timestamp::Union{DateTime, Nothing}=nothing,
subject::Union{String, Nothing}=nothing,
action_or_dialogue::Union{String, Nothing}=nothing,
location::Union{String, Nothing}=nothing,
equipment_used::Union{String, Nothing}=nothing,
material_used::Union{String, Nothing}=nothing,
outcome::Union{String, Nothing}=nothing,
note::Union{String, Nothing}=nothing,
)
return Dict{Symbol, Any}(
:event_description=> event_description,
:timestamp=> timestamp,
:subject=> subject,
:action_or_dialogue=> action_or_dialogue,
:location=> location,
:equipment_used=> equipment_used,
:material_used=> material_used,
:outcome=> outcome,
:note=> note,
)
function availableWineToText(vecd::Vector)::String
# Initialize an empty string to hold the final text
rowtext = ""
# Loop through each dictionary in the input vector
for (i, d) in enumerate(vecd)
# Iterate over all key-value pairs in the dictionary
temp = []
for (k, v) in d
# Append the formatted string to the text variable
t = "$k:$v"
push!(temp, t)
end
_rowtext = join(temp, ',')
rowtext *= "$i) $_rowtext "
end
return rowtext
end
# """ Convert a single chat dictionary into LLM model instruct format.
""" Create a dictionary representing an event with optional details.
# # Llama 3 instruct format example
# <|system|>
# You are a helpful AI assistant.<|end|>
# <|user|>
# I am going to Paris, what should I see?<|end|>
# <|assistant|>
# Paris, the capital of France, is known for its stunning architecture, art museums."<|end|>
# <|user|>
# What is so great about #1?<|end|>
# <|assistant|>
# Arguments
- `event_description::Union{String, Nothing}`
A description of the event
- `timestamp::Union{DateTime, Nothing}`
The time when the event occurred
- `subject::Union{String, Nothing}`
The subject or entity associated with the event
- `thought::Union{AbstractDict, Nothing}`
Any associated thoughts or metadata
- `actionname::Union{String, Nothing}`
The name of the action performed (e.g., "CHAT", "CHECKINVENTORY")
- `actioninput::Union{String, Nothing}`
Input or parameters for the action
- `location::Union{String, Nothing}`
Where the event took place
- `equipment_used::Union{String, Nothing}`
Equipment involved in the event
- `material_used::Union{String, Nothing}`
Materials used during the event
- `outcome::Union{String, Nothing}`
The result or consequence of the event after action execution
- `note::Union{String, Nothing}`
Additional notes or comments
# Returns
A dictionary with event details as symbol-keyed key-value pairs
"""
function eventdict(;
event_description::Union{String, Nothing}=nothing,
timestamp::Union{DateTime, Nothing}=nothing,
subject::Union{String, Nothing}=nothing,
thought::Union{AbstractDict, Nothing}=nothing,
actionname::Union{String, Nothing}=nothing, # "CHAT", "CHECKINVENTORY", "PRESENTBOX", etc
actioninput::Union{String, Nothing}=nothing,
location::Union{String, Nothing}=nothing,
equipment_used::Union{String, Nothing}=nothing,
material_used::Union{String, Nothing}=nothing,
observation::Union{String, Nothing}=nothing,
note::Union{String, Nothing}=nothing,
)
d = Dict{Symbol, Any}(
:event_description=> event_description,
:timestamp=> timestamp,
:subject=> subject,
:thought=> thought,
:actionname=> actionname,
:actioninput=> actioninput,
:location=> location,
:equipment_used=> equipment_used,
:material_used=> material_used,
:observation=> observation,
:note=> note,
)
return d
end
# # Arguments
# - `name::T`
# message owner name e.f. "system", "user" or "assistant"
# - `text::T`
""" Create a formatted timeline string from a sequence of events.
# # Return
# - `formattedtext::String`
# text formatted to model format
# Arguments
- `events::T1`
Vector of event dictionaries containing subject, actioninput and optional outcome fields
Each event dictionary should have the following keys:
- :subject - The subject or entity performing the action
- :actioninput - The action or input performed by the subject
- :observation - (Optional) The result or outcome of the action
# # Example
# ```jldoctest
# julia> using Revise
# julia> using YiemAgent
# julia> d = Dict(:name=> "system",:text=> "You are a helpful, respectful and honest assistant.",)
# julia> formattedtext = YiemAgent.formatLLMtext_phi3instruct(d[:name], d[:text])
# Returns
- `timeline::String`
A formatted string representing the events with their subjects, actions, and optional outcomes
Format: "{index}) {subject}> {actioninput} {outcome}\n" for each event
# ```
# Example
# Signature
# """
# function formatLLMtext_phi3instruct(name::T, text::T) where {T<:AbstractString}
# formattedtext =
# """
# <|$name|>
# $text<|end|>\n
# """
events = [
Dict(:subject => "User", :actioninput => "Hello", :observation => nothing),
Dict(:subject => "Assistant", :actioninput => "Hi there!", :observation => "with a smile")
]
timeline = createTimeline(events)
# 1) User> Hello
# 2) Assistant> Hi there! with a smile
# return formattedtext
# end
"""
function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
) where {T1<:AbstractVector}
# Initialize empty timeline string
timeline = ""
# Determine which indices to use - either provided range or full length
ind =
if eventindex !== nothing
[eventindex...]
else
1:length(events)
end
# Iterate through events and format each one
for i in ind
event = events[i]
# If no outcome exists, format without outcome
# if event[:actionname] == "CHATBOX"
# timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput])\n"
# elseif event[:actionname] == "CHECKINVENTORY" && event[:observation] === nothing
# timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput]), observation: Not done yet.\n"
# If outcome exists, include it in formatting
if event[:actionname] == "CHECKWINE"
timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput]), observation: $(event[:observation])\n"
else
timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput])\n"
end
end
# """ Convert a single chat dictionary into LLM model instruct format.
# Return formatted timeline string
return timeline
end
# function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
# ) where {T1<:AbstractVector}
# # Initialize empty timeline string
# timeline = ""
# # Determine which indices to use - either provided range or full length
# ind =
# if eventindex !== nothing
# [eventindex...]
# else
# 1:length(events)
# end
# # Llama 3 instruct format example
# <|begin_of_text|>
# <|start_header_id|>system<|end_header_id|>
# You are a helpful assistant.
# <|eot_id|>
# <|start_header_id|>user<|end_header_id|>
# Get me an icecream.
# <|eot_id|>
# <|start_header_id|>assistant<|end_header_id|>
# Go buy it yourself at 7-11.
# <|eot_id|>
# # Arguments
# - `name::T`
# message owner name e.f. "system", "user" or "assistant"
# - `text::T`
# # Return
# - `formattedtext::String`
# text formatted to model format
# # Example
# ```jldoctest
# julia> using Revise
# julia> using YiemAgent
# julia> d = Dict(:name=> "system",:text=> "You are a helpful, respectful and honest assistant.",)
# julia> formattedtext = YiemAgent.formatLLMtext_llama3instruct(d[:name], d[:text])
# "<|begin_of_text|>\n <|start_header_id|>system<|end_header_id|>\n You are a helpful, respectful and honest assistant.\n <|eot_id|>\n"
# ```
# Signature
# """
# function formatLLMtext_llama3instruct(name::T, text::T) where {T<:AbstractString}
# formattedtext =
# if name == "system"
# """
# <|begin_of_text|>
# <|start_header_id|>$name<|end_header_id|>
# $text
# <|eot_id|>
# """
# else
# """
# <|start_header_id|>$name<|end_header_id|>
# $text
# <|eot_id|>
# """
# end
# return formattedtext
# end
# """ Convert a chat messages in vector of dictionary into LLM model instruct format.
# # Arguments
# - `messages::Vector{Dict{Symbol, T}}`
# message owner name e.f. "system", "user" or "assistant"
# - `formatname::T`
# format name to be used
# # Return
# - `formattedtext::String`
# text formatted to model format
# # Example
# ```jldoctest
# julia> using Revise
# julia> using YiemAgent
# julia> chatmessage = [
# Dict(:name=> "system",:text=> "You are a helpful, respectful and honest assistant.",),
# Dict(:name=> "user",:text=> "list me all planets in our solar system.",),
# Dict(:name=> "assistant",:text=> "I'm sorry. I don't know. You tell me.",),
# ]
# julia> formattedtext = YiemAgent.formatLLMtext(chatmessage, "llama3instruct")
# "<|begin_of_text|>\n <|start_header_id|>system<|end_header_id|>\n You are a helpful, respectful and honest assistant.\n <|eot_id|>\n <|start_header_id|>user<|end_header_id|>\n list me all planets in our solar system.\n <|eot_id|>\n <|start_header_id|>assistant<|end_header_id|>\n I'm sorry. I don't know. You tell me.\n <|eot_id|>\n"
# ```
# # Signature
# """
# function formatLLMtext(messages::Vector{Dict{Symbol, T}},
# formatname::String="llama3instruct") where {T<:Any}
# f = if formatname == "llama3instruct"
# formatLLMtext_llama3instruct
# elseif formatname == "mistral"
# # not define yet
# elseif formatname == "phi3instruct"
# formatLLMtext_phi3instruct
# else
# error("$formatname template not define yet")
# end
# str = ""
# for t in messages
# str *= f(t[:name], t[:text])
# end
# # add <|assistant|> so that the model don't generate it and I don't need to clean it up later
# if formatname == "phi3instruct"
# str *= "<|assistant|>\n"
# end
# return str
# end
# """
# Arguments\n
# -----
# Return\n
# -----
# Example\n
# -----
# ```jldoctest
# julia>
# ```
# TODO\n
# -----
# [] update docstring
# [PENDING] implement the function
# Signature\n
# -----
# """
# function iterativeprompting(a::T, prompt::String, verification::Function) where {T<:agent}
# msgMeta = GeneralUtils.generate_msgMeta(
# a.config[:externalService][:text2textinstruct],
# senderName= "iterativeprompting",
# senderId= a.id,
# receiverName= "text2textinstruct",
# )
# outgoingMsg = Dict(
# :msgMeta=> msgMeta,
# :payload=> Dict(
# :text=> prompt,
# )
# )
# success = nothing
# result = nothing
# critique = ""
# # iteration loop
# while true
# # send prompt to LLM
# response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
# error("--> iterativeprompting")
# # check for correctness and get feedback
# success, _critique = verification(response)
# if success
# result = response
# break
# else
# # add critique to prompt
# critique *= _critique * "\n"
# replace!(prompt, "Critique: ..." => "Critique: $critique")
# # Iterate through events and format each one
# for i in ind
# event = events[i]
# # If no outcome exists, format without outcome
# if event[:observation] === nothing
# timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput]), observation: Not done yet.\n"
# # If outcome exists, include it in formatting
# else
# timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput]), observation: $(event[:observation])\n"
# end
# end
# return (success=success, result=result)
# # Return formatted timeline string
# return timeline
# end
function createEventsLog(events::T1; index::Union{UnitRange, Nothing}=nothing
) where {T1<:AbstractVector}
# Initialize empty log array
log = Dict{Symbol, String}[]
# Determine which indices to use - either provided range or full length
ind =
if index !== nothing
[index...]
else
1:length(events)
end
# Iterate through events and format each one
for i in ind
event = events[i]
# If no outcome exists, format without outcome
if event[:observation] === nothing
subject = event[:subject]
actionname = event[:actionname]
actioninput = event[:actioninput]
str = "actionname: $actionname, actioninput: $actioninput"
d = Dict{Symbol, String}(:name=>subject, :text=>str)
push!(log, d)
else
subject = event[:subject]
actionname = event[:actionname]
actioninput = event[:actioninput]
observation = event[:observation]
str = "actionname: $actionname, actioninput: $actioninput, observation: $observation"
d = Dict{Symbol, String}(:name=>subject, :text=>str)
push!(log, d)
end
end
return log
end
function createChatLog(chatdict::T1; index::Union{UnitRange, Nothing}=nothing
) where {T1<:AbstractVector}
# Initialize empty log array
log = Dict{Symbol, String}[]
# Determine which indices to use - either provided range or full length
ind =
if index !== nothing
[index...]
else
1:length(chatdict)
end
# Iterate through events and format each one
for i in ind
event = chatdict[i]
subject = event[:name]
text = event[:text]
d = Dict{Symbol, String}(:name=>subject, :text=>text)
push!(log, d)
end
return log
end
function checkAgentResponse_text(response::String, requiredHeader::T
)::Tuple where {T<:Array{String}}
detected_kw = GeneralUtils.detectKeywordVariation(requiredHeader, response)
missingkeys = [k for (k, v) in detected_kw if v === nothing]
ispass = false
errormsg = nothing
if !isempty(missingkeys)
errormsg = "$missingkeys are missing from your previous response"
ispass = false
elseif sum([length(i) for i in values(detected_kw)]) > length(requiredHeader)
errormsg = "Your previous attempt has duplicated points according to the required response format"
ispass = false
else
ispass = true
end
return (ispass, errormsg)
end
function checkAgentResponse_JSON(responsedict::Dict, requiredKeys::T
)::Tuple where {T<:Array{Symbol}}
_responsedictKey = keys(responsedict)
responsedictKey = [i for i in _responsedictKey] # convert into a list
is_requiredKeys_in_responsedictKey = [i responsedictKey for i in requiredKeys]
ispass = false
errormsg = nothing
if length(is_requiredKeys_in_responsedictKey) > length(requiredKeys)
errormsg = "Your previous attempt has duplicated points according to the required response format"
ispass = false
elseif !all(is_requiredKeys_in_responsedictKey)
zeroind = findall(x -> x == 0, is_requiredKeys_in_responsedictKey)
missingkeys = [requiredKeys[i] for i in zeroind]
errormsg = "$missingkeys are missing from your previous response"
ispass = false
else
ispass = true
end
return (ispass, errormsg)
end

41
test/Manifest.toml Normal file
View File

@@ -0,0 +1,41 @@
# This file is machine-generated - editing it directly is not advised
julia_version = "1.11.4"
manifest_format = "2.0"
project_hash = "71d91126b5a1fb1020e1098d9d492de2a4438fd2"
[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
version = "1.11.0"
[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
version = "1.11.0"
[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
version = "1.11.0"
[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
version = "1.11.0"
[[deps.Random]]
deps = ["SHA"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
version = "1.11.0"
[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"
[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
version = "1.11.0"
[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
version = "1.11.0"

2
test/Project.toml Normal file
View File

@@ -0,0 +1,2 @@
[deps]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

View File

@@ -1,272 +1,296 @@
using Revise
using JSON, JSON3, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames
using YiemAgent, GeneralUtils
using Base.Threads
# ---------------------------------------------- 100 --------------------------------------------- #
# load config
config = JSON3.read("./test/config.json")
# config = copy(JSON3.read("../mountvolume/config.json"))
function executeSQL(sql::T) where {T<:AbstractString}
DBconnection = LibPQ.Connection("host=192.168.88.12 port=10201 dbname=wineDB user=yiemtechnologies password=yiemtechnologies@Postgres_0.0")
result = LibPQ.execute(DBconnection, sql)
close(DBconnection)
return result
end
function executeSQLVectorDB(sql)
DBconnection = LibPQ.Connection("host=192.168.88.12 port=10203 dbname=SQLVectorDB user=yiemtechnologies password=yiemtechnologies@Postgres_0.0")
result = LibPQ.execute(DBconnection, sql)
close(DBconnection)
return result
end
function text2textInstructLLM(prompt::String)
msgMeta = GeneralUtils.generate_msgMeta(
config[:externalservice][:text2textinstruct][:mqtttopic];
msgPurpose="inference",
senderName="yiemagent",
senderId=string(uuid4()),
receiverName="text2textinstruct",
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict(
:text => prompt,
:kwargs => Dict(
:num_ctx => 16384,
:temperature => 0.2,
)
)
)
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=6000)
response = _response[:response][:text]
return response
end
# get text embedding from a LLM service
function getEmbedding(text::T) where {T<:AbstractString}
msgMeta = GeneralUtils.generate_msgMeta(
config[:externalservice][:text2textinstruct][:mqtttopic];
msgPurpose="embedding",
senderName="yiemagent",
senderId=string(uuid4()),
receiverName="text2textinstruct",
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict(
:text => [text] # must be a vector of string
)
)
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=6000)
embedding = response[:response][:embeddings]
return embedding
end
function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
vectorDB::Function; limit::Integer=1
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
# get embedding from LLM service
embedding = getEmbedding(text)[1]
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
sql = """
SELECT *, $embeddingColumnName <-> '$embedding' as distance
FROM $tablename
ORDER BY distance LIMIT $limit;
"""
response = vectorDB(sql)
df = DataFrame(response)
return df
end
function similarSQLVectorDB(query; maxdistance::Integer=100)
tablename = "sqlllm_decision_repository"
# get embedding of the query
df = findSimilarTextFromVectorDB(query, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row != 0 && distance < maxdistance
# if there is usable SQL, return it.
output_b64 = df[1, :function_output_base64] # pick the closest match
output_str = String(base64decode(output_b64))
rowid = df[1, :id]
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
return (dict=output_str, distance=distance)
else
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
return (dict=nothing, distance=nothing)
end
end
function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=1) where {T1<:AbstractString, T2<:AbstractString}
tablename = "sqlllm_decision_repository"
# get embedding of the query
# query = state[:thoughtHistory][:question]
df = findSimilarTextFromVectorDB(query, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
query_embedding = getEmbedding(query)[1]
query = replace(query, "'" => "")
sql_base64 = base64encode(SQL)
sql_ = replace(SQL, "'" => "")
sql = """
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
"""
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
println(sql)
_ = executeSQLVectorDB(sql)
end
end
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=5
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
tablename = "sommelier_decision_repository"
# find similar
println("\n~~~ search vectorDB for this: $recentevents ", @__FILE__, " ", @__LINE__)
df = findSimilarTextFromVectorDB(recentevents, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row != 0 && distance < maxdistance
# if there is usable decision, return it.
rowid = df[1, :id]
println("\n~~~ found similar decision. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
output_b64 = df[1, :function_output_base64] # pick the closest match
_output_str = String(base64decode(output_b64))
output = copy(JSON3.read(_output_str))
return output
else
println("\n~~~ similar decision not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
return nothing
end
end
function insertSommelierDecision(recentevents::T1, decision::T2; maxdistance::Integer=5
) where {T1<:AbstractString, T2<:AbstractDict}
tablename = "sommelier_decision_repository"
# find similar
df = findSimilarTextFromVectorDB(recentevents, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
recentevents_embedding = a.func[:getEmbedding](recentevents)[1]
recentevents = replace(recentevents, "'" => "")
decision_json = JSON3.write(decision)
decision_base64 = base64encode(decision_json)
decision = replace(decision_json, "'" => "")
sql = """
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$recentevents', '$decision', '$decision_base64', '$recentevents_embedding');
"""
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
println(sql)
_ = executeSQLVectorDB(sql)
else
println("~~~ similar decision previously cached, distance $distance ", @__FILE__, " ", @__LINE__)
end
end
sessionId = "12345"
externalFunction = (
getEmbedding=getEmbedding,
text2textInstructLLM=text2textInstructLLM,
executeSQL=executeSQL,
similarSQLVectorDB=similarSQLVectorDB,
insertSQLVectorDB=insertSQLVectorDB,
similarSommelierDecision=similarSommelierDecision,
insertSommelierDecision=insertSommelierDecision,
)
a = YiemAgent.sommelier(
externalFunction;
name="Ton",
id=sessionId, # agent instance id
retailername="Yiem",
)
while true
println("your respond: ")
user_answer = readline()
response = YiemAgent.conversation(a, Dict(:text=> user_answer))
println("\n$response")
end
# response = YiemAgent.conversation(a, Dict(:text=> "I want to get a French red wine under 100."))
using Revise
using JSON, JSON3, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames
using YiemAgent, GeneralUtils
using Base.Threads
# ---------------------------------------------- 100 --------------------------------------------- #
# load config
config = JSON3.read("/appfolder/app/dev/YiemAgent/test/config.json")
# config = copy(JSON3.read("../mountvolume/config.json"))
function executeSQL(sql::T) where {T<:AbstractString}
host = config[:externalservice][:wineDB][:host]
port = config[:externalservice][:wineDB][:port]
dbname = config[:externalservice][:wineDB][:dbname]
user = config[:externalservice][:wineDB][:user]
password = config[:externalservice][:wineDB][:password]
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
result = LibPQ.execute(DBconnection, sql)
close(DBconnection)
return result
end
function executeSQLVectorDB(sql)
host = config[:externalservice][:SQLVectorDB][:host]
port = config[:externalservice][:SQLVectorDB][:port]
dbname = config[:externalservice][:SQLVectorDB][:dbname]
user = config[:externalservice][:SQLVectorDB][:user]
password = config[:externalservice][:SQLVectorDB][:password]
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
result = LibPQ.execute(DBconnection, sql)
close(DBconnection)
return result
end
function text2textInstructLLM(prompt::String; maxattempt::Integer=3, modelsize::String="medium",
llmkwargs=Dict(
:num_ctx => 32768,
:temperature => 0.1,
)
)
msgMeta = GeneralUtils.generate_msgMeta(
config[:externalservice][:loadbalancer][:mqtttopic];
msgPurpose="inference",
senderName="yiemagent",
senderId=sessionId,
receiverName="text2textinstruct_$modelsize",
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict(
:text => prompt,
:kwargs => llmkwargs
)
)
response = nothing
for attempts in 1:maxattempt
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=180, maxattempt=maxattempt)
payload = _response[:response]
if _response[:success] && payload[:text] !== nothing
response = _response[:response][:text]
break
else
println("\n<text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(outgoingMsg)
println("</text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
sleep(3)
end
end
return response
end
# get text embedding from a LLM service
function getEmbedding(text::T) where {T<:AbstractString}
msgMeta = GeneralUtils.generate_msgMeta(
config[:externalservice][:loadbalancer][:mqtttopic];
msgPurpose="embedding",
senderName="yiemagent",
senderId=sessionId,
receiverName="textembedding",
mqttBrokerAddress=config[:mqttServerInfo][:broker],
mqttBrokerPort=config[:mqttServerInfo][:port],
)
outgoingMsg = Dict(
:msgMeta => msgMeta,
:payload => Dict(
:text => [text] # must be a vector of string
)
)
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120, maxattempt=3)
embedding = response[:response][:embeddings]
return embedding
end
function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
vectorDB::Function; limit::Integer=1
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
# get embedding from LLM service
embedding = getEmbedding(text)[1]
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
sql = """
SELECT *, $embeddingColumnName <-> '$embedding' as distance
FROM $tablename
ORDER BY distance LIMIT $limit;
"""
response = vectorDB(sql)
df = DataFrame(response)
return df
end
function similarSQLVectorDB(query; maxdistance::Integer=100)
tablename = "sqlllm_decision_repository"
# get embedding of the query
df = findSimilarTextFromVectorDB(query, tablename,
"function_input_embedding", executeSQLVectorDB)
# println(df[1, [:id, :function_output]])
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
# distance = 100 # CHANGE this is for testing only
if row != 0 && distance < maxdistance
# if there is usable SQL, return it.
output_b64 = df[1, :function_output_base64] # pick the closest match
output_str = String(base64decode(output_b64))
rowid = df[1, :id]
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
return (dict=output_str, distance=distance)
else
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
return (dict=nothing, distance=nothing)
end
end
function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=3) where {T1<:AbstractString, T2<:AbstractString}
tablename = "sqlllm_decision_repository"
# get embedding of the query
# query = state[:thoughtHistory][:question]
df = findSimilarTextFromVectorDB(query, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
query_embedding = getEmbedding(query)[1]
query = replace(query, "'" => "")
sql_base64 = base64encode(SQL)
sql_ = replace(SQL, "'" => "")
sql = """
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
"""
# println("\n~~~ added new decision to vectorDB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# println(sql)
_ = executeSQLVectorDB(sql)
end
end
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=3
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
tablename = "sommelier_decision_repository"
# find similar
println("\n~~~ search vectorDB for this: $recentevents ", @__FILE__, " ", @__LINE__)
df = findSimilarTextFromVectorDB(recentevents, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row != 0 && distance < maxdistance
# if there is usable decision, return it.
rowid = df[1, :id]
println("\n~~~ found similar decision. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
output_b64 = df[1, :function_output_base64] # pick the closest match
_output_str = String(base64decode(output_b64))
output = copy(JSON3.read(_output_str))
return output
else
println("\n~~~ similar decision not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
return nothing
end
end
function insertSommelierDecision(recentevents::T1, decision::T2; maxdistance::Integer=5
) where {T1<:AbstractString, T2<:AbstractDict}
tablename = "sommelier_decision_repository"
# find similar
df = findSimilarTextFromVectorDB(recentevents, tablename,
"function_input_embedding", executeSQLVectorDB)
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
recentevents_embedding = getEmbedding(recentevents)[1]
recentevents = replace(recentevents, "'" => "")
decision_json = JSON3.write(decision)
decision_base64 = base64encode(decision_json)
decision = replace(decision_json, "'" => "")
sql = """
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$recentevents', '$decision', '$decision_base64', '$recentevents_embedding');
"""
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
println(sql)
_ = executeSQLVectorDB(sql)
else
println("~~~ similar decision previously cached, distance $distance ", @__FILE__, " ", @__LINE__)
end
end
sessionId = "12345"
externalFunction = (
getEmbedding=getEmbedding,
text2textInstructLLM=text2textInstructLLM,
executeSQL=executeSQL,
similarSQLVectorDB=similarSQLVectorDB,
insertSQLVectorDB=insertSQLVectorDB,
similarSommelierDecision=similarSommelierDecision,
insertSommelierDecision=insertSommelierDecision,
)
a = YiemAgent.sommelier(
externalFunction;
name="Ton",
id=sessionId, # agent instance id
retailername="Yiem",
)
while true
print("\nyour respond: ")
user_answer = readline()
response = YiemAgent.conversation(agent;
userinput=Dict(:text=> user_answer),
maximumMsg=50)
println("\n$response")
end
# response = YiemAgent.conversation(a, Dict(:text=> "I want to get a French red wine under 100."))
"""
hello I want to get a bottle of red wine for my boss. I have a budget around 50 dollars. Show me some options.
I have no idea about his wine taste but he likes spicy food.
"""

View File

@@ -27,30 +27,50 @@
"description": "agent role"
},
"organization": {
"value": "yiem_hq",
"value": "yiem_branch_1",
"description": "organization name"
},
"externalservice": {
"text2textinstruct": {
"mqtttopic": "/loadbalancer/requestingservice",
"description": "text to text service with instruct LLM",
"llminfo": {
"name": "llama3instruct"
}
},
"virtualWineCustomer_1": {
"mqtttopic": "/virtualenvironment/winecustomer",
"description": "text to text service with instruct LLM that act as wine customer",
"llminfo": {
"name": "llama3instruct"
}
},
"text2textchat": {
"mqtttopic": "/loadbalancer/requestingservice",
"description": "text to text service with instruct LLM",
"llminfo": {
"name": "llama3instruct"
}
}
"loadbalancer": {
"mqtttopic": "/loadbalancer/requestingservice",
"description": "text to text service with instruct LLM"
},
"text2textinstruct": {
"mqtttopic": "/loadbalancer/requestingservice",
"description": "text to text service with instruct LLM",
"llminfo": {
"name": "llama3instruct"
}
},
"virtualWineCustomer_1": {
"mqtttopic": "/virtualenvironment/winecustomer",
"description": "text to text service with instruct LLM that act as wine customer",
"llminfo": {
"name": "llama3instruct"
}
},
"text2textchat": {
"mqtttopic": "/loadbalancer/requestingservice",
"description": "text to text service with instruct LLM",
"llminfo": {
"name": "llama3instruct"
}
},
"wineDB" : {
"description": "A wine database connection info for LibPQ client",
"host": "192.168.88.12",
"port": 10201,
"dbname": "wineDB",
"user": "yiemtechnologies",
"password": "yiemtechnologies@Postgres_0.0"
},
"SQLVectorDB" : {
"description": "A wine database connection info for LibPQ client",
"host": "192.168.88.12",
"port": 10203,
"dbname": "SQLVectorDB",
"user": "yiemtechnologies",
"password": "yiemtechnologies@Postgres_0.0"
}
}
}

View File

@@ -1,9 +0,0 @@
using GeneralUtils
response = "trajectory_evaluation:\nThe trajectory is correct so far. The thought accurately reflects the user's question, and the action taken is a valid attempt to retrieve data from the database that matches the specified criteria.\n\nanswer_evaluation:\nThe observation provides information about two red wines from Bordeaux rive droite in France, which partially answers the question. However, it does not provide a complete answer as it only lists the wine names and characteristics, but does not explicitly state whether there are any other wines that match the criteria.\n\naccepted_as_answer: No\n\nscore: 6\nThe trajectory is mostly correct, but the observation does not fully address the question.\n\nsuggestion: Consider adding more filters or parameters to the database query to retrieve a complete list of wines that match the specified criteria."
responsedict = GeneralUtils.textToDict(response,
["trajectory_evaluation", "answer_evaluation", "accepted_as_answer", "score", "suggestion"],
rightmarker=":", symbolkey=true)

0
test/runtests.jl Normal file
View File