Compare commits
41 Commits
v0.1.3
...
b5a00bc694
| Author | SHA1 | Date | |
|---|---|---|---|
| b5a00bc694 | |||
| 4eb55537f7 | |||
|
|
0a1032c545 | ||
|
|
68a20b5080 | ||
|
|
8a9c9606c7 | ||
|
|
bad2ca35ed | ||
|
|
f2b56640cc | ||
|
|
5d552d96c4 | ||
|
|
e0dc7d29b2 | ||
|
|
932611a439 | ||
|
|
a5c6360b4e | ||
|
|
03f50379c9 | ||
|
|
a7da0b8123 | ||
|
|
e524813021 | ||
|
|
3444f00062 | ||
|
|
919d8ec85e | ||
|
|
3a88e0e7d4 | ||
|
|
68c2c2f12b | ||
|
|
3e79c0bfed | ||
|
|
d0c26e52e8 | ||
|
|
a0152a3c29 | ||
|
|
1fc5dfe820 | ||
|
|
4b2575f4a4 | ||
|
|
a01a91e7b9 | ||
|
|
aa8436c0ed | ||
|
|
cccad676db | ||
|
|
03de659c9b | ||
|
|
affb96f0cf | ||
|
|
f19f302bd9 | ||
|
|
7ca4f5276d | ||
|
|
44804041a3 | ||
|
|
48a3704f6d | ||
| 8321a13afc | |||
| b26ae31d4c | |||
|
|
b397bf7bdb | ||
|
|
c0edf7dadf | ||
|
|
c21f943b12 | ||
|
|
b8fd772a28 | ||
|
|
883f581b2a | ||
|
|
5a890860a6 | ||
| 7d5bc14a09 |
278
Manifest.toml
278
Manifest.toml
@@ -1,8 +1,8 @@
|
|||||||
# This file is machine-generated - editing it directly is not advised
|
# 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"
|
manifest_format = "2.0"
|
||||||
project_hash = "b483014657ef9f0fde60d7258585b291d6f0eeca"
|
project_hash = "9896e9d54d6cf4e2c3ae871a42f43f2f212ab1c9"
|
||||||
|
|
||||||
[[deps.AliasTables]]
|
[[deps.AliasTables]]
|
||||||
deps = ["PtrArrays", "Random"]
|
deps = ["PtrArrays", "Random"]
|
||||||
@@ -27,6 +27,11 @@ git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d"
|
|||||||
uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35"
|
uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
|
|
||||||
|
[[deps.BufferedStreams]]
|
||||||
|
git-tree-sha1 = "6863c5b7fc997eadcabdbaf6c5f201dc30032643"
|
||||||
|
uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
|
||||||
|
version = "1.2.2"
|
||||||
|
|
||||||
[[deps.CEnum]]
|
[[deps.CEnum]]
|
||||||
git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc"
|
git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc"
|
||||||
uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
|
uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
|
||||||
@@ -40,26 +45,37 @@ version = "0.10.15"
|
|||||||
|
|
||||||
[[deps.CodeTracking]]
|
[[deps.CodeTracking]]
|
||||||
deps = ["InteractiveUtils", "UUIDs"]
|
deps = ["InteractiveUtils", "UUIDs"]
|
||||||
git-tree-sha1 = "7eee164f122511d3e4e1ebadb7956939ea7e1c77"
|
git-tree-sha1 = "062c5e1a5bf6ada13db96a4ae4749a4c2234f521"
|
||||||
uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
|
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.CodecZlib]]
|
||||||
deps = ["TranscodingStreams", "Zlib_jll"]
|
deps = ["TranscodingStreams", "Zlib_jll"]
|
||||||
git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759"
|
git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9"
|
||||||
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
|
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
|
||||||
version = "0.7.6"
|
version = "0.7.8"
|
||||||
|
|
||||||
[[deps.Compat]]
|
[[deps.Compat]]
|
||||||
deps = ["TOML", "UUIDs"]
|
deps = ["TOML", "UUIDs"]
|
||||||
git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215"
|
git-tree-sha1 = "3a3dfb30697e96a440e4149c8c51bf32f818c0f3"
|
||||||
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
|
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
|
||||||
version = "4.16.0"
|
version = "4.17.0"
|
||||||
weakdeps = ["Dates", "LinearAlgebra"]
|
weakdeps = ["Dates", "LinearAlgebra"]
|
||||||
|
|
||||||
[deps.Compat.extensions]
|
[deps.Compat.extensions]
|
||||||
CompatLinearAlgebraExt = "LinearAlgebra"
|
CompatLinearAlgebraExt = "LinearAlgebra"
|
||||||
|
|
||||||
|
[[deps.Compiler]]
|
||||||
|
git-tree-sha1 = "382d79bfe72a406294faca39ef0c3cef6e6ce1f1"
|
||||||
|
uuid = "807dbc54-b67e-4c79-8afb-eafe4df6f2e1"
|
||||||
|
version = "0.1.1"
|
||||||
|
|
||||||
[[deps.CompilerSupportLibraries_jll]]
|
[[deps.CompilerSupportLibraries_jll]]
|
||||||
deps = ["Artifacts", "Libdl"]
|
deps = ["Artifacts", "Libdl"]
|
||||||
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
|
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
|
||||||
@@ -67,9 +83,9 @@ version = "1.1.1+0"
|
|||||||
|
|
||||||
[[deps.ConcurrentUtilities]]
|
[[deps.ConcurrentUtilities]]
|
||||||
deps = ["Serialization", "Sockets"]
|
deps = ["Serialization", "Sockets"]
|
||||||
git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1"
|
git-tree-sha1 = "d9d26935a0bcffc87d2613ce14c527c99fc543fd"
|
||||||
uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
|
uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
|
||||||
version = "2.4.2"
|
version = "2.5.0"
|
||||||
|
|
||||||
[[deps.Crayons]]
|
[[deps.Crayons]]
|
||||||
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
|
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
|
||||||
@@ -94,9 +110,9 @@ version = "1.7.0"
|
|||||||
|
|
||||||
[[deps.DataStructures]]
|
[[deps.DataStructures]]
|
||||||
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
|
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
|
||||||
git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82"
|
git-tree-sha1 = "4e1fe97fdaed23e9dc21d4d664bea76b65fc50a0"
|
||||||
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
||||||
version = "0.18.20"
|
version = "0.18.22"
|
||||||
|
|
||||||
[[deps.DataValueInterfaces]]
|
[[deps.DataValueInterfaces]]
|
||||||
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
|
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
|
||||||
@@ -120,9 +136,9 @@ version = "1.11.0"
|
|||||||
|
|
||||||
[[deps.Distributions]]
|
[[deps.Distributions]]
|
||||||
deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
|
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"
|
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
|
||||||
version = "0.25.113"
|
version = "0.25.120"
|
||||||
|
|
||||||
[deps.Distributions.extensions]
|
[deps.Distributions.extensions]
|
||||||
DistributionsChainRulesCoreExt = "ChainRulesCore"
|
DistributionsChainRulesCoreExt = "ChainRulesCore"
|
||||||
@@ -135,10 +151,9 @@ version = "0.25.113"
|
|||||||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||||
|
|
||||||
[[deps.DocStringExtensions]]
|
[[deps.DocStringExtensions]]
|
||||||
deps = ["LibGit2"]
|
git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c"
|
||||||
git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d"
|
|
||||||
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
|
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
|
||||||
version = "0.9.3"
|
version = "0.9.5"
|
||||||
|
|
||||||
[[deps.Downloads]]
|
[[deps.Downloads]]
|
||||||
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
|
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
|
||||||
@@ -158,9 +173,9 @@ version = "0.1.10"
|
|||||||
|
|
||||||
[[deps.FileIO]]
|
[[deps.FileIO]]
|
||||||
deps = ["Pkg", "Requires", "UUIDs"]
|
deps = ["Pkg", "Requires", "UUIDs"]
|
||||||
git-tree-sha1 = "2dd20384bf8c6d411b5c7370865b1e9b26cb2ea3"
|
git-tree-sha1 = "b66970a70db13f45b7e57fbda1736e1cf72174ea"
|
||||||
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
|
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
|
||||||
version = "1.16.6"
|
version = "1.17.0"
|
||||||
weakdeps = ["HTTP"]
|
weakdeps = ["HTTP"]
|
||||||
|
|
||||||
[deps.FileIO.extensions]
|
[deps.FileIO.extensions]
|
||||||
@@ -168,9 +183,9 @@ weakdeps = ["HTTP"]
|
|||||||
|
|
||||||
[[deps.FilePathsBase]]
|
[[deps.FilePathsBase]]
|
||||||
deps = ["Compat", "Dates"]
|
deps = ["Compat", "Dates"]
|
||||||
git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361"
|
git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2"
|
||||||
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
|
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
|
||||||
version = "0.9.22"
|
version = "0.9.24"
|
||||||
weakdeps = ["Mmap", "Test"]
|
weakdeps = ["Mmap", "Test"]
|
||||||
|
|
||||||
[deps.FilePathsBase.extensions]
|
[deps.FilePathsBase.extensions]
|
||||||
@@ -199,30 +214,33 @@ uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"
|
|||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
||||||
[[deps.GeneralUtils]]
|
[[deps.GeneralUtils]]
|
||||||
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "JSON3", "MQTTClient", "PrettyPrinting", "Random", "SHA", "UUIDs"]
|
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "JSON3", "MQTTClient", "NATS", "PrettyPrinting", "Random", "SHA", "UUIDs"]
|
||||||
git-tree-sha1 = "978d9a5c3fc30205dd72d4a2a2ed4fa85ebee5cf"
|
path = "/appfolder/app/dev/GeneralUtils"
|
||||||
repo-rev = "main"
|
|
||||||
repo-url = "https://git.yiem.cc/ton/GeneralUtils"
|
|
||||||
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
||||||
version = "0.1.0"
|
version = "0.3.1"
|
||||||
|
|
||||||
[[deps.HTTP]]
|
[[deps.HTTP]]
|
||||||
deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
|
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"
|
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.HypergeometricFunctions]]
|
||||||
deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
|
deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
|
||||||
git-tree-sha1 = "b1c2585431c382e3fe5805874bda6aea90a95de9"
|
git-tree-sha1 = "68c173f4f449de5b438ee67ed0c9c748dc31a2ec"
|
||||||
uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
|
uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
|
||||||
version = "0.3.25"
|
version = "0.3.28"
|
||||||
|
|
||||||
[[deps.ICU_jll]]
|
[[deps.ICU_jll]]
|
||||||
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
|
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
||||||
git-tree-sha1 = "20b6765a3016e1fca0c9c93c80d50061b94218b7"
|
git-tree-sha1 = "b3d8be712fbf9237935bde0ce9b5a736ae38fc34"
|
||||||
uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b"
|
uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b"
|
||||||
version = "69.1.0+0"
|
version = "76.2.0+0"
|
||||||
|
|
||||||
[[deps.Infinity]]
|
[[deps.Infinity]]
|
||||||
deps = ["Dates", "Random", "Requires"]
|
deps = ["Dates", "Random", "Requires"]
|
||||||
@@ -231,9 +249,9 @@ uuid = "a303e19e-6eb4-11e9-3b09-cd9505f79100"
|
|||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|
||||||
[[deps.InlineStrings]]
|
[[deps.InlineStrings]]
|
||||||
git-tree-sha1 = "45521d31238e87ee9f9732561bfee12d4eebd52d"
|
git-tree-sha1 = "8594fac023c5ce1ef78260f24d1ad18b4327b420"
|
||||||
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
|
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
|
||||||
version = "1.4.2"
|
version = "1.4.4"
|
||||||
|
|
||||||
[deps.InlineStrings.extensions]
|
[deps.InlineStrings.extensions]
|
||||||
ArrowTypesExt = "ArrowTypes"
|
ArrowTypesExt = "ArrowTypes"
|
||||||
@@ -255,14 +273,14 @@ uuid = "d8418881-c3e1-53bb-8760-2df7ec849ed5"
|
|||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
|
|
||||||
[[deps.InvertedIndices]]
|
[[deps.InvertedIndices]]
|
||||||
git-tree-sha1 = "0dc7b50b8d436461be01300fd8cd45aa0274b038"
|
git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe"
|
||||||
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
|
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
|
||||||
version = "1.3.0"
|
version = "1.3.1"
|
||||||
|
|
||||||
[[deps.IrrationalConstants]]
|
[[deps.IrrationalConstants]]
|
||||||
git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2"
|
git-tree-sha1 = "e2222959fbc6c19554dc15174c81bf7bf3aa691c"
|
||||||
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
|
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
|
||||||
version = "0.2.2"
|
version = "0.2.4"
|
||||||
|
|
||||||
[[deps.IterTools]]
|
[[deps.IterTools]]
|
||||||
git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023"
|
git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023"
|
||||||
@@ -276,15 +294,15 @@ version = "1.0.0"
|
|||||||
|
|
||||||
[[deps.JLLWrappers]]
|
[[deps.JLLWrappers]]
|
||||||
deps = ["Artifacts", "Preferences"]
|
deps = ["Artifacts", "Preferences"]
|
||||||
git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b"
|
git-tree-sha1 = "a007feb38b422fbdab534406aeca1b86823cb4d6"
|
||||||
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
|
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
|
||||||
version = "1.6.1"
|
version = "1.7.0"
|
||||||
|
|
||||||
[[deps.JSON3]]
|
[[deps.JSON3]]
|
||||||
deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"]
|
deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"]
|
||||||
git-tree-sha1 = "1d322381ef7b087548321d3f878cb4c9bd8f8f9b"
|
git-tree-sha1 = "411eccfe8aba0814ffa0fdf4860913ed09c34975"
|
||||||
uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
|
uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
|
||||||
version = "1.14.1"
|
version = "1.14.3"
|
||||||
|
|
||||||
[deps.JSON3.extensions]
|
[deps.JSON3.extensions]
|
||||||
JSON3ArrowExt = ["ArrowTypes"]
|
JSON3ArrowExt = ["ArrowTypes"]
|
||||||
@@ -294,23 +312,21 @@ version = "1.14.1"
|
|||||||
|
|
||||||
[[deps.JuliaInterpreter]]
|
[[deps.JuliaInterpreter]]
|
||||||
deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"]
|
deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"]
|
||||||
git-tree-sha1 = "10da5154188682e5c0726823c2b5125957ec3778"
|
git-tree-sha1 = "6ac9e4acc417a5b534ace12690bc6973c25b862f"
|
||||||
uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"
|
uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"
|
||||||
version = "0.9.38"
|
version = "0.10.3"
|
||||||
|
|
||||||
[[deps.Kerberos_krb5_jll]]
|
[[deps.Kerberos_krb5_jll]]
|
||||||
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
|
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
||||||
git-tree-sha1 = "60274b4ab38e8d1248216fe6b6ace75ae09b0502"
|
git-tree-sha1 = "0f2899fdadaab4b8f57db558ba21bdb4fb52f1f0"
|
||||||
uuid = "b39eb1a6-c29a-53d7-8c32-632cd16f18da"
|
uuid = "b39eb1a6-c29a-53d7-8c32-632cd16f18da"
|
||||||
version = "1.19.3+0"
|
version = "1.21.3+0"
|
||||||
|
|
||||||
[[deps.LLMMCTS]]
|
[[deps.LLMMCTS]]
|
||||||
deps = ["GeneralUtils", "JSON3"]
|
deps = ["GeneralUtils", "JSON3", "PrettyPrinting"]
|
||||||
git-tree-sha1 = "d8c653b8fafbd3757b7332985efaf1fdb8b6fe97"
|
path = "/appfolder/app/dev/LLMMCTS"
|
||||||
repo-rev = "main"
|
|
||||||
repo-url = "https://git.yiem.cc/ton/LLMMCTS"
|
|
||||||
uuid = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
|
uuid = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
|
||||||
version = "0.1.2"
|
version = "0.1.4"
|
||||||
|
|
||||||
[[deps.LaTeXStrings]]
|
[[deps.LaTeXStrings]]
|
||||||
git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
|
git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
|
||||||
@@ -350,9 +366,9 @@ version = "1.18.0"
|
|||||||
|
|
||||||
[[deps.LibPQ_jll]]
|
[[deps.LibPQ_jll]]
|
||||||
deps = ["Artifacts", "ICU_jll", "JLLWrappers", "Kerberos_krb5_jll", "Libdl", "OpenSSL_jll", "Zstd_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"
|
uuid = "08be9ffa-1c94-5ee5-a977-46a84ec9b350"
|
||||||
version = "16.0.0+0"
|
version = "16.8.0+0"
|
||||||
|
|
||||||
[[deps.LibSSH2_jll]]
|
[[deps.LibSSH2_jll]]
|
||||||
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
|
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
|
||||||
@@ -370,9 +386,9 @@ version = "1.11.0"
|
|||||||
|
|
||||||
[[deps.LogExpFunctions]]
|
[[deps.LogExpFunctions]]
|
||||||
deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
|
deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
|
||||||
git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea"
|
git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f"
|
||||||
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
|
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
|
|
||||||
[deps.LogExpFunctions.extensions]
|
[deps.LogExpFunctions.extensions]
|
||||||
LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
|
LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
|
||||||
@@ -395,10 +411,10 @@ uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36"
|
|||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
||||||
[[deps.LoweredCodeUtils]]
|
[[deps.LoweredCodeUtils]]
|
||||||
deps = ["JuliaInterpreter"]
|
deps = ["Compiler", "JuliaInterpreter"]
|
||||||
git-tree-sha1 = "688d6d9e098109051ae33d126fcfc88c4ce4a021"
|
git-tree-sha1 = "bc54ba0681bb71e56043a1b923028d652e78ee42"
|
||||||
uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
|
uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
|
||||||
version = "3.1.0"
|
version = "3.4.1"
|
||||||
|
|
||||||
[[deps.MQTTClient]]
|
[[deps.MQTTClient]]
|
||||||
deps = ["Distributed", "Random", "Sockets"]
|
deps = ["Distributed", "Random", "Sockets"]
|
||||||
@@ -452,14 +468,26 @@ version = "0.8.1"
|
|||||||
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
|
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
|
||||||
version = "2023.12.12"
|
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]]
|
[[deps.NetworkOptions]]
|
||||||
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
|
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
||||||
[[deps.OffsetArrays]]
|
[[deps.OffsetArrays]]
|
||||||
git-tree-sha1 = "39d000d9c33706b8364817d8894fae1548f40295"
|
git-tree-sha1 = "117432e406b5c023f665fa73dc26e79ec3630151"
|
||||||
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
|
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
|
||||||
version = "1.14.2"
|
version = "1.17.0"
|
||||||
|
|
||||||
[deps.OffsetArrays.extensions]
|
[deps.OffsetArrays.extensions]
|
||||||
OffsetArraysAdaptExt = "Adapt"
|
OffsetArraysAdaptExt = "Adapt"
|
||||||
@@ -475,42 +503,42 @@ version = "0.3.27+1"
|
|||||||
[[deps.OpenLibm_jll]]
|
[[deps.OpenLibm_jll]]
|
||||||
deps = ["Artifacts", "Libdl"]
|
deps = ["Artifacts", "Libdl"]
|
||||||
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
|
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
|
||||||
version = "0.8.1+2"
|
version = "0.8.5+0"
|
||||||
|
|
||||||
[[deps.OpenSSL]]
|
[[deps.OpenSSL]]
|
||||||
deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
|
deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
|
||||||
git-tree-sha1 = "38cb508d080d21dc1128f7fb04f20387ed4c0af4"
|
git-tree-sha1 = "f1a7e086c677df53e064e0fdd2c9d0b0833e3f6e"
|
||||||
uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c"
|
uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c"
|
||||||
version = "1.4.3"
|
version = "1.5.0"
|
||||||
|
|
||||||
[[deps.OpenSSL_jll]]
|
[[deps.OpenSSL_jll]]
|
||||||
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
||||||
git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10"
|
git-tree-sha1 = "87510f7292a2b21aeff97912b0898f9553cc5c2c"
|
||||||
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
|
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
|
||||||
version = "3.0.15+1"
|
version = "3.5.1+0"
|
||||||
|
|
||||||
[[deps.OpenSpecFun_jll]]
|
[[deps.OpenSpecFun_jll]]
|
||||||
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
|
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
|
||||||
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
|
git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335"
|
||||||
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
|
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
|
||||||
version = "0.5.5+0"
|
version = "0.5.6+0"
|
||||||
|
|
||||||
[[deps.OrderedCollections]]
|
[[deps.OrderedCollections]]
|
||||||
git-tree-sha1 = "12f1439c4f986bb868acda6ea33ebc78e19b95ad"
|
git-tree-sha1 = "05868e21324cede2207c6f0f466b4bfef6d5e7ee"
|
||||||
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
||||||
version = "1.7.0"
|
version = "1.8.1"
|
||||||
|
|
||||||
[[deps.PDMats]]
|
[[deps.PDMats]]
|
||||||
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
|
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
|
||||||
git-tree-sha1 = "949347156c25054de2db3b166c52ac4728cbad65"
|
git-tree-sha1 = "f07c06228a1c670ae4c87d1276b92c7c597fdda0"
|
||||||
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
|
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
|
||||||
version = "0.11.31"
|
version = "0.11.35"
|
||||||
|
|
||||||
[[deps.Parsers]]
|
[[deps.Parsers]]
|
||||||
deps = ["Dates", "PrecompileTools", "UUIDs"]
|
deps = ["Dates", "PrecompileTools", "UUIDs"]
|
||||||
git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821"
|
git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810"
|
||||||
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
|
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
|
||||||
version = "2.8.1"
|
version = "2.8.3"
|
||||||
|
|
||||||
[[deps.Pkg]]
|
[[deps.Pkg]]
|
||||||
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"]
|
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"
|
version = "1.11.0"
|
||||||
|
|
||||||
[[deps.PtrArrays]]
|
[[deps.PtrArrays]]
|
||||||
git-tree-sha1 = "77a42d78b6a92df47ab37e177b2deac405e1c88f"
|
git-tree-sha1 = "1d36ef11a9aaf1e8b74dacc6a731dd1de8fd493d"
|
||||||
uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
|
uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
|
||||||
version = "1.2.1"
|
version = "1.3.0"
|
||||||
|
|
||||||
[[deps.QuadGK]]
|
[[deps.QuadGK]]
|
||||||
deps = ["DataStructures", "LinearAlgebra"]
|
deps = ["DataStructures", "LinearAlgebra"]
|
||||||
git-tree-sha1 = "cda3b045cf9ef07a08ad46731f5a3165e56cf3da"
|
git-tree-sha1 = "9da16da70037ba9d701192e27befedefb91ec284"
|
||||||
uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
|
uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
|
||||||
version = "2.11.1"
|
version = "2.11.2"
|
||||||
|
|
||||||
[deps.QuadGK.extensions]
|
[deps.QuadGK.extensions]
|
||||||
QuadGKEnzymeExt = "Enzyme"
|
QuadGKEnzymeExt = "Enzyme"
|
||||||
@@ -595,15 +623,19 @@ version = "1.2.2"
|
|||||||
|
|
||||||
[[deps.Requires]]
|
[[deps.Requires]]
|
||||||
deps = ["UUIDs"]
|
deps = ["UUIDs"]
|
||||||
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
|
git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64"
|
||||||
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
|
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
|
||||||
version = "1.3.0"
|
version = "1.3.1"
|
||||||
|
|
||||||
[[deps.Revise]]
|
[[deps.Revise]]
|
||||||
deps = ["CodeTracking", "Distributed", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "REPL", "Requires", "UUIDs", "Unicode"]
|
deps = ["CodeTracking", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "REPL", "Requires", "UUIDs", "Unicode"]
|
||||||
git-tree-sha1 = "470f48c9c4ea2170fd4d0f8eb5118327aada22f5"
|
git-tree-sha1 = "f6f7d30fb0d61c64d0cfe56cf085a7c9e7d5bc80"
|
||||||
uuid = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
uuid = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
||||||
version = "3.6.4"
|
version = "3.8.0"
|
||||||
|
weakdeps = ["Distributed"]
|
||||||
|
|
||||||
|
[deps.Revise.extensions]
|
||||||
|
DistributedExt = "Distributed"
|
||||||
|
|
||||||
[[deps.Rmath]]
|
[[deps.Rmath]]
|
||||||
deps = ["Random", "Rmath_jll"]
|
deps = ["Random", "Rmath_jll"]
|
||||||
@@ -623,28 +655,32 @@ version = "0.7.0"
|
|||||||
|
|
||||||
[[deps.SQLLLM]]
|
[[deps.SQLLLM]]
|
||||||
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON3", "LLMMCTS", "LibPQ", "PrettyPrinting", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"]
|
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON3", "LLMMCTS", "LibPQ", "PrettyPrinting", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"]
|
||||||
git-tree-sha1 = "45e660e44de0950a5e5f92d467298d8b768b6023"
|
path = "/appfolder/app/dev/SQLLLM"
|
||||||
repo-rev = "main"
|
|
||||||
repo-url = "https://git.yiem.cc/ton/SQLLLM"
|
|
||||||
uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
|
uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
|
||||||
version = "0.2.0"
|
version = "0.2.4"
|
||||||
|
|
||||||
[[deps.SQLStrings]]
|
[[deps.SQLStrings]]
|
||||||
git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c"
|
git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c"
|
||||||
uuid = "af517c2e-c243-48fa-aab8-efac3db270f5"
|
uuid = "af517c2e-c243-48fa-aab8-efac3db270f5"
|
||||||
version = "0.1.0"
|
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.Scratch]]
|
||||||
deps = ["Dates"]
|
deps = ["Dates"]
|
||||||
git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386"
|
git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109"
|
||||||
uuid = "6c6a2e73-6563-6170-7368-637461726353"
|
uuid = "6c6a2e73-6563-6170-7368-637461726353"
|
||||||
version = "1.2.1"
|
version = "1.3.0"
|
||||||
|
|
||||||
[[deps.SentinelArrays]]
|
[[deps.SentinelArrays]]
|
||||||
deps = ["Dates", "Random"]
|
deps = ["Dates", "Random"]
|
||||||
git-tree-sha1 = "d0553ce4031a081cc42387a9b9c8441b7d99f32d"
|
git-tree-sha1 = "712fb0231ee6f9120e005ccd56297abbc053e7e0"
|
||||||
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
|
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
|
||||||
version = "1.4.7"
|
version = "1.4.8"
|
||||||
|
|
||||||
[[deps.Serialization]]
|
[[deps.Serialization]]
|
||||||
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
||||||
@@ -659,6 +695,12 @@ version = "1.2.0"
|
|||||||
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
|
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
|
||||||
version = "1.11.0"
|
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.SortingAlgorithms]]
|
||||||
deps = ["DataStructures"]
|
deps = ["DataStructures"]
|
||||||
git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085"
|
git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085"
|
||||||
@@ -672,9 +714,9 @@ version = "1.11.0"
|
|||||||
|
|
||||||
[[deps.SpecialFunctions]]
|
[[deps.SpecialFunctions]]
|
||||||
deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
|
deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
|
||||||
git-tree-sha1 = "2f5d4697f21388cbe1ff299430dd169ef97d7e14"
|
git-tree-sha1 = "41852b8679f78c8d8961eeadc8f62cef861a52e3"
|
||||||
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
|
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
|
||||||
version = "2.4.0"
|
version = "2.5.1"
|
||||||
|
|
||||||
[deps.SpecialFunctions.extensions]
|
[deps.SpecialFunctions.extensions]
|
||||||
SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
|
SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
|
||||||
@@ -694,21 +736,21 @@ weakdeps = ["SparseArrays"]
|
|||||||
|
|
||||||
[[deps.StatsAPI]]
|
[[deps.StatsAPI]]
|
||||||
deps = ["LinearAlgebra"]
|
deps = ["LinearAlgebra"]
|
||||||
git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed"
|
git-tree-sha1 = "9d72a13a3f4dd3795a195ac5a44d7d6ff5f552ff"
|
||||||
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
|
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
|
||||||
version = "1.7.0"
|
version = "1.7.1"
|
||||||
|
|
||||||
[[deps.StatsBase]]
|
[[deps.StatsBase]]
|
||||||
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
|
deps = ["AliasTables", "DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
|
||||||
git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21"
|
git-tree-sha1 = "b81c5035922cc89c2d9523afc6c54be512411466"
|
||||||
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
|
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
|
||||||
version = "0.34.3"
|
version = "0.34.5"
|
||||||
|
|
||||||
[[deps.StatsFuns]]
|
[[deps.StatsFuns]]
|
||||||
deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
|
deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
|
||||||
git-tree-sha1 = "b423576adc27097764a90e163157bcfc9acf0f46"
|
git-tree-sha1 = "8e45cecc66f3b42633b8ce14d431e8e57a3e242e"
|
||||||
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
|
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
|
||||||
version = "1.3.2"
|
version = "1.5.0"
|
||||||
|
|
||||||
[deps.StatsFuns.extensions]
|
[deps.StatsFuns.extensions]
|
||||||
StatsFunsChainRulesCoreExt = "ChainRulesCore"
|
StatsFunsChainRulesCoreExt = "ChainRulesCore"
|
||||||
@@ -720,9 +762,9 @@ version = "1.3.2"
|
|||||||
|
|
||||||
[[deps.StringManipulation]]
|
[[deps.StringManipulation]]
|
||||||
deps = ["PrecompileTools"]
|
deps = ["PrecompileTools"]
|
||||||
git-tree-sha1 = "a6b1675a536c5ad1a60e5a5153e1fee12eb146e3"
|
git-tree-sha1 = "725421ae8e530ec29bcbdddbe91ff8053421d023"
|
||||||
uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e"
|
uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
|
|
||||||
[[deps.StructTypes]]
|
[[deps.StructTypes]]
|
||||||
deps = ["Dates", "UUIDs"]
|
deps = ["Dates", "UUIDs"]
|
||||||
@@ -750,9 +792,9 @@ version = "1.0.3"
|
|||||||
|
|
||||||
[[deps.TZJData]]
|
[[deps.TZJData]]
|
||||||
deps = ["Artifacts"]
|
deps = ["Artifacts"]
|
||||||
git-tree-sha1 = "36b40607bf2bf856828690e097e1c799623b0602"
|
git-tree-sha1 = "72df96b3a595b7aab1e101eb07d2a435963a97e2"
|
||||||
uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7"
|
uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7"
|
||||||
version = "1.3.0+2024b"
|
version = "1.5.0+2025b"
|
||||||
|
|
||||||
[[deps.TableTraits]]
|
[[deps.TableTraits]]
|
||||||
deps = ["IteratorInterfaceExtensions"]
|
deps = ["IteratorInterfaceExtensions"]
|
||||||
@@ -762,9 +804,9 @@ version = "1.0.1"
|
|||||||
|
|
||||||
[[deps.Tables]]
|
[[deps.Tables]]
|
||||||
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
|
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
|
||||||
git-tree-sha1 = "598cd7c1f68d1e205689b1c2fe65a9f85846f297"
|
git-tree-sha1 = "f2c1efbc8f3a609aadf318094f8fc5204bdaf344"
|
||||||
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
|
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
|
||||||
version = "1.12.0"
|
version = "1.12.1"
|
||||||
|
|
||||||
[[deps.Tar]]
|
[[deps.Tar]]
|
||||||
deps = ["ArgTools", "SHA"]
|
deps = ["ArgTools", "SHA"]
|
||||||
@@ -777,10 +819,10 @@ uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
|||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
||||||
[[deps.TimeZones]]
|
[[deps.TimeZones]]
|
||||||
deps = ["Dates", "Downloads", "InlineStrings", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"]
|
deps = ["Artifacts", "Dates", "Downloads", "InlineStrings", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"]
|
||||||
git-tree-sha1 = "33c771f2157712ff4c85931186a4984efbe58934"
|
git-tree-sha1 = "2c705e96825b66c4a3f25031a683c06518256dd3"
|
||||||
uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53"
|
uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53"
|
||||||
version = "1.19.0"
|
version = "1.21.3"
|
||||||
weakdeps = ["RecipesBase"]
|
weakdeps = ["RecipesBase"]
|
||||||
|
|
||||||
[deps.TimeZones.extensions]
|
[deps.TimeZones.extensions]
|
||||||
@@ -792,9 +834,9 @@ uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
|
|||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
|
|
||||||
[[deps.URIs]]
|
[[deps.URIs]]
|
||||||
git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b"
|
git-tree-sha1 = "bef26fb046d031353ef97a82e3fdb6afe7f21b1a"
|
||||||
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
|
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
|
||||||
version = "1.5.1"
|
version = "1.6.1"
|
||||||
|
|
||||||
[[deps.UTCDateTimes]]
|
[[deps.UTCDateTimes]]
|
||||||
deps = ["Dates", "TimeZones"]
|
deps = ["Dates", "TimeZones"]
|
||||||
@@ -829,15 +871,21 @@ version = "1.2.13+1"
|
|||||||
|
|
||||||
[[deps.Zstd_jll]]
|
[[deps.Zstd_jll]]
|
||||||
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
||||||
git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b"
|
git-tree-sha1 = "446b23e73536f84e8037f5dce465e92275f6a308"
|
||||||
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
|
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
|
||||||
version = "1.5.6+1"
|
version = "1.5.7+1"
|
||||||
|
|
||||||
[[deps.libblastrampoline_jll]]
|
[[deps.libblastrampoline_jll]]
|
||||||
deps = ["Artifacts", "Libdl"]
|
deps = ["Artifacts", "Libdl"]
|
||||||
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
|
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
|
||||||
version = "5.11.0+0"
|
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.nghttp2_jll]]
|
||||||
deps = ["Artifacts", "Libdl"]
|
deps = ["Artifacts", "Libdl"]
|
||||||
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
|
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
name = "YiemAgent"
|
name = "YiemAgent"
|
||||||
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
||||||
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
||||||
version = "0.1.3"
|
version = "0.4.0"
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
||||||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
||||||
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
||||||
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
|
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
|
||||||
@@ -12,6 +13,7 @@ HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
|
|||||||
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
|
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
|
||||||
LLMMCTS = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
|
LLMMCTS = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
|
||||||
LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
|
LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
|
||||||
|
NATS = "55e73f9c-eeeb-467f-b4cc-a633fde63d2a"
|
||||||
PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337"
|
PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337"
|
||||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||||
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
||||||
@@ -21,7 +23,6 @@ URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
|
|||||||
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
||||||
|
|
||||||
[compat]
|
[compat]
|
||||||
|
CSV = "0.10.15"
|
||||||
DataFrames = "1.7.0"
|
DataFrames = "1.7.0"
|
||||||
GeneralUtils = "0.1, 0.2"
|
NATS = "0.1.0"
|
||||||
LLMMCTS = "0.1.2"
|
|
||||||
SQLLLM = "0.2.0"
|
|
||||||
|
|||||||
585
example/agent_chat_virtualCustomer.jl
Normal file
585
example/agent_chat_virtualCustomer.jl
Normal 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
76
example/config.json
Normal 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
706
example/main.jl
Normal 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()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
72
fast_inference_guideline.txt
Normal file
72
fast_inference_guideline.txt
Normal 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**
|
||||||
|
Don’t 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. Don’t 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. Don’t 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. Don’t 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!
|
||||||
1997
src/interface.jl
1997
src/interface.jl
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
|||||||
module llmfunction
|
module llmfunction
|
||||||
|
|
||||||
export virtualWineUserChatbox, jsoncorrection, checkinventory, # recommendbox,
|
export virtualWineUserChatbox, jsoncorrection, checkwine, # recommendbox,
|
||||||
virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes_1,
|
virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes_1,
|
||||||
extractWineAttributes_2, paraphrase
|
extractWineAttributes_2, paraphrase
|
||||||
|
|
||||||
using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs, Dates
|
using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs, Dates, DataFrames
|
||||||
using GeneralUtils, SQLLLM
|
using GeneralUtils, SQLLLM
|
||||||
using ..type, ..util
|
using ..type, ..util
|
||||||
|
|
||||||
@@ -288,23 +288,48 @@ julia> result = checkinventory(agent, input)
|
|||||||
|
|
||||||
# Signature
|
# Signature
|
||||||
"""
|
"""
|
||||||
function checkinventory(a::T1, input::T2
|
function checkwine(a::T1, input::T2; maxattempt::Int=3
|
||||||
) where {T1<:agent, T2<:AbstractString}
|
) where {T1<:agent, T2<:AbstractString}
|
||||||
|
|
||||||
println("\n~~~ checkinventory order: $input ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
println("\ncheckinventory order: $input ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
wineattributes_1 = extractWineAttributes_1(a, input)
|
wineattributes_1 = extractWineAttributes_1(a, input)
|
||||||
wineattributes_2 = extractWineAttributes_2(a, input)
|
wineattributes_2 = extractWineAttributes_2(a, input)
|
||||||
|
|
||||||
_inventoryquery = "retailer name: $(a.retailername), $wineattributes_1, $wineattributes_2"
|
# placeholder
|
||||||
inventoryquery = "Retrieves winery, wine_name, vintage, region, country, wine_type, grape, serving_temperature, sweetness, intensity, tannin, acidity, tasting_notes, price and currency of wines that match the following criteria - {$_inventoryquery}"
|
textresult = nothing
|
||||||
println("~~~ checkinventory input: $inventoryquery ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
rawresponse = nothing
|
||||||
# add suppport for similarSQLVectorDB
|
|
||||||
textresult, rawresponse = SQLLLM.query(inventoryquery, a.func[:executeSQL],
|
|
||||||
a.func[:text2textInstructLLM],
|
|
||||||
insertSQLVectorDB=a.func[:insertSQLVectorDB],
|
|
||||||
similarSQLVectorDB=a.func[:similarSQLVectorDB])
|
|
||||||
|
|
||||||
println("\n~~~ checkinventory result ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
for i in 1:maxattempt
|
||||||
|
|
||||||
|
#CHANGE if you want to add retailer name
|
||||||
|
# _inventoryquery = "retailer name: $(a.retailername), $wineattributes_1, $wineattributes_2"
|
||||||
|
_inventoryquery = "$wineattributes_1, $wineattributes_2"
|
||||||
|
|
||||||
|
retrieve_attributes = ["winery", "wine_name", "wine_id", "vintage", "region", "country", "wine_type", "grape", "serving_temperature", "sweetness", "intensity", "tannin", "acidity", "tasting_notes", "price", "currency"]
|
||||||
|
inventoryquery = "Retrieves $retrieve_attributes of wines that match the following criteria - {$_inventoryquery}"
|
||||||
|
println("\ncheckinventory input: $inventoryquery ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
# add suppport for similarSQLVectorDB
|
||||||
|
textresult, rawresponse = SQLLLM.query(inventoryquery,
|
||||||
|
a.context.executeSQL,
|
||||||
|
a.context.text2textInstructLLM;
|
||||||
|
insertSQLVectorDB=a.context.insertSQLVectorDB,
|
||||||
|
similarSQLVectorDB=a.context.similarSQLVectorDB,
|
||||||
|
llmFormatName="qwen3")
|
||||||
|
# check if all of retrieve_attributes appears in textresult
|
||||||
|
isin = [occursin(x, textresult) for x in retrieve_attributes]
|
||||||
|
# check if rawresponse type is DataFrame so that I can check for column
|
||||||
|
if typeof(rawresponse) == DataFrame &&
|
||||||
|
!occursin("The resulting table has 0 row", textresult) &&
|
||||||
|
!all(isin)
|
||||||
|
|
||||||
|
errornote = "Not all of $retrieve_attributes appear in search result"
|
||||||
|
println("\nERROR YiemAgent checkwine() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
println("\ncheckinventory result ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
println(textresult)
|
println(textresult)
|
||||||
|
|
||||||
return (result=textresult, rawresponse=rawresponse, success=true, errormsg=nothing)
|
return (result=textresult, rawresponse=rawresponse, success=true, errormsg=nothing)
|
||||||
@@ -330,142 +355,218 @@ julia>
|
|||||||
|
|
||||||
# Signature
|
# Signature
|
||||||
"""
|
"""
|
||||||
function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
|
||||||
|
)::String where {T1<:agent, T2<:AbstractString}
|
||||||
|
|
||||||
systemmsg =
|
systemmsg =
|
||||||
"""
|
"""
|
||||||
As a helpful sommelier, your task is to extract the user information from the user's query as much as possible to fill out user's preference form.
|
As a helpful sommelier, your task is to extract the user information from the user's query as much as possible to fill out user's preference form.
|
||||||
|
|
||||||
At each round of conversation, the user will give you the following:
|
At each round of conversation, the user will give you the following:
|
||||||
User's query: ...
|
- The query: the query provided by the user.
|
||||||
|
|
||||||
You must follow the following guidelines:
|
You must follow the following guidelines:
|
||||||
- If specific information required in the preference form is not available in the query or there isn't any, mark with "NA" to indicate this.
|
- If specific information required in the preference form is not available in the query or there isn't any, mark with "N/A" to indicate this.
|
||||||
Additionally, words like 'any' or 'unlimited' mean no information is available.
|
Additionally, words like 'any' or 'unlimited' mean no information is available.
|
||||||
- Do not generate other comments.
|
- Do not generate other comments.
|
||||||
|
|
||||||
You should then respond to the user with:
|
You should then respond to the user with:
|
||||||
Comprehension: state your understanding of the current situation
|
wine_name: name of the wine
|
||||||
Wine_name: name of the wine
|
winery: name of the winery
|
||||||
Winery: name of the winery
|
vintage: the year of the wine
|
||||||
Vintage: the year of the wine
|
region: a region, such as Burgundy, Bordeaux, Champagne, Napa Valley, Tuscany, California, Oregon, etc. Use "or" if there are multiple regions.
|
||||||
Region: a region (NOT a country) where the wine is produced, such as Burgundy, Napa Valley, etc
|
country: a country where wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "United States". Use "or" if there are multiple countries.
|
||||||
Country: a country where the wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "United States"
|
wine_type: can be one of: "red", "white", "sparkling", "rose", "dessert" or "fortified"
|
||||||
Wine_type: can be one of: "red", "white", "sparkling", "rose", "dessert" or "fortified"
|
grape_varietal: the name of the primary grape used to make the wine
|
||||||
Grape_varietal: the name of the primary grape used to make the wine
|
tasting_notes: a word describe the wine's flavor, such as "butter", "oak", "fruity", "raspberry", "earthy", "floral", etc
|
||||||
Tasting_notes: a brief description of the wine's taste, such as "butter", "oak", "fruity", etc
|
wine_price_min: minimum price range of wine. Example: For wine price 20, wine_price_min will be 0. For wine price 10 to 100, wine_price_min will be 10.
|
||||||
Wine_price: price range of wine.
|
wine_price_max: maximum price range of wine. Example: For wine price 20, wine_price_max will be 20. For wine price 10 to 100, wine_price_max will be 100.
|
||||||
Occasion: the occasion the user is having the wine for
|
occasion: the occasion the user is having the wine for
|
||||||
Food_to_be_paired_with_wine: food that the user will be served with the wine such as poultry, fish, steak, etc
|
food_to_be_paired_with_wine: food that the user will be served with the wine such as poultry, fish, steak, etc
|
||||||
|
|
||||||
You should only respond in format as described below:
|
You should only respond in JSON format as described below:
|
||||||
Comprehension: ...
|
{
|
||||||
Wine_name: ...
|
"wine_name": "...",
|
||||||
Winery: ...
|
"winery": "...",
|
||||||
Vintage: ...
|
"vintage": "...",
|
||||||
Region: ...
|
"region": "...",
|
||||||
Country: ...
|
"country": "...",
|
||||||
Wine_type:
|
"wine_type": "...",
|
||||||
Grape_varietal: ...
|
"grape_varietal": "...",
|
||||||
Tasting_notes: ...
|
"tasting_notes": "...",
|
||||||
Wine_price: ...
|
"wine_price_min": "...",
|
||||||
Occasion: ...
|
"wine_price_max": "...",
|
||||||
Food_to_be_paired_with_wine: ...
|
"occasion": "...",
|
||||||
|
"food_to_be_paired_with_wine": "..."
|
||||||
|
}
|
||||||
|
|
||||||
Here are some example:
|
Here are some example:
|
||||||
User's query: red, Chenin Blanc, Riesling, 20 USD
|
|
||||||
{"reasoning": ..., "winery": "NA", "wine_name": "NA", "vintage": "NA", "region": "NA", "country": "NA", "wine_type": "red, white", "grape_varietal": "Chenin Blanc, Riesling", "tasting_notes": "NA", "wine_price": "0-20", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
|
||||||
|
|
||||||
User's query: Domaine du Collier Saumur Blanc 2019, France, white, Chenin Blanc
|
User's query: red, Chenin Blanc, Riesling, 20 USD from Tuscany, Italy or Napa Valley, USA
|
||||||
{"reasoning": ..., "winery": "Domaine du Collier", "wine_name": "Saumur Blanc", "vintage": "2019", "region": "Saumur", "country": "France", "wine_type": "white", "grape_varietal": "Chenin Blanc", "tasting_notes": "NA", "wine_price": "NA", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
{
|
||||||
|
"wine_name": "N/A",
|
||||||
|
"winery": "N/A",
|
||||||
|
"vintage": "N/A",
|
||||||
|
"region": "Tuscany or Napa Valley",
|
||||||
|
"country": "Italy or United States",
|
||||||
|
"wine_type": "red or white",
|
||||||
|
"grape_varietal": "Chenin Blanc or Riesling",
|
||||||
|
"tasting_notes": "citrus",
|
||||||
|
"wine_price_min": "0",
|
||||||
|
"wine_price_max": "20",
|
||||||
|
"occasion": "N/A",
|
||||||
|
"food_to_be_paired_with_wine": "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
Let's begin!
|
User's query: Domaine du Collier Saumur Blanc 2019, France, white, Merlot
|
||||||
|
{
|
||||||
|
"wine_name": "Saumur Blanc",
|
||||||
|
"winery": "Domaine du Collier",
|
||||||
|
"vintage": "2019",
|
||||||
|
"region": "Saumur",
|
||||||
|
"country": "France",
|
||||||
|
"wine_type": "white",
|
||||||
|
"grape_varietal": "Merlot",
|
||||||
|
"tasting_notes": "plum",
|
||||||
|
"wine_price_min": "N/A",
|
||||||
|
"wine_price_max": "N/A",
|
||||||
|
"occasion": "N/A",
|
||||||
|
"food_to_be_paired_with_wine": "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
header = ["Comprehension:", "Wine_name:", "Winery:", "Vintage:", "Region:", "Country:", "Wine_type:", "Grape_varietal:", "Tasting_notes:", "Wine_price:", "Occasion:", "Food_to_be_paired_with_wine:"]
|
requiredKeys = [:wine_name, :winery, :vintage, :region, :country, :wine_type, :grape_varietal, :tasting_notes, :wine_price_min, :wine_price_max, :occasion, :food_to_be_paired_with_wine]
|
||||||
dictkey = ["comprehension", "wine_name", "winery", "vintage", "region", "country", "wine_type", "grape_varietal", "tasting_notes", "wine_price", "occasion", "food_to_be_paired_with_wine"]
|
errornote = "N/A"
|
||||||
errornote = ""
|
|
||||||
|
|
||||||
for attempt in 1:5
|
for attempt in 1:maxattempt
|
||||||
usermsg =
|
usermsg =
|
||||||
"""
|
"""
|
||||||
User's query: $input
|
$input
|
||||||
$errornote
|
"""
|
||||||
"""
|
context =
|
||||||
|
"""
|
||||||
|
<context>
|
||||||
|
P.S. $errornote
|
||||||
|
</context>
|
||||||
|
/no_think
|
||||||
|
"""
|
||||||
|
|
||||||
_prompt =
|
unformatPrompt =
|
||||||
[
|
[
|
||||||
Dict(:name=> "system", :text=> systemmsg),
|
Dict(:name=> "system", :text=> systemmsg),
|
||||||
Dict(:name=> "user", :text=> usermsg)
|
Dict(:name=> "user", :text=> usermsg)
|
||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
|
||||||
response = a.func[:text2textInstructLLM](prompt)
|
# add info
|
||||||
|
prompt = prompt * context
|
||||||
|
|
||||||
|
response = a.context.text2textInstructLLM(prompt; modelsize="medium", senderId=a.id)
|
||||||
|
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
|
think, response = GeneralUtils.extractthink(response)
|
||||||
|
|
||||||
# check wheter all attributes are in the response
|
responsedict = nothing
|
||||||
checkFlag = false
|
try
|
||||||
for word in header
|
responsedict = copy(JSON3.read(response))
|
||||||
if !occursin(word, response)
|
catch
|
||||||
errornote = "$word attribute is missing in previous attempts"
|
println("\nERROR YiemAgent extractWineAttributes_1() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
||||||
checkFlag = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
checkFlag == true ? continue : nothing
|
|
||||||
|
|
||||||
# check whether response has all header
|
|
||||||
detected_kw = GeneralUtils.detect_keyword(header, response)
|
|
||||||
if sum(values(detected_kw)) < length(header)
|
|
||||||
errornote = "\nYiemAgent extractWineAttributes_1() response does not have all header"
|
|
||||||
continue
|
|
||||||
elseif sum(values(detected_kw)) > length(header)
|
|
||||||
errornote = "\nYiemAgent extractWineAttributes_1() response has duplicated header"
|
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
responsedict = GeneralUtils.textToDict(response, header;
|
|
||||||
dictKey=dictkey, symbolkey=true)
|
|
||||||
|
|
||||||
delete!(responsedict, :comprehension)
|
# check whether all answer's key points are in responsedict
|
||||||
|
_responsedictKey = keys(responsedict)
|
||||||
|
responsedictKey = [i for i in _responsedictKey] # convert into a list
|
||||||
|
is_requiredKeys_in_responsedictKey = [i ∈ responsedictKey for i in requiredKeys]
|
||||||
|
|
||||||
|
if length(is_requiredKeys_in_responsedictKey) > length(requiredKeys)
|
||||||
|
errornote = "Your previous attempt has more key points than answer's required key points."
|
||||||
|
println("\nERROR YiemAgent extractWineAttributes_1() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
continue
|
||||||
|
elseif !all(is_requiredKeys_in_responsedictKey)
|
||||||
|
zeroind = findall(x -> x == 0, is_requiredKeys_in_responsedictKey)
|
||||||
|
missingkeys = [requiredKeys[i] for i in zeroind]
|
||||||
|
errornote = "$missingkeys are missing from your previous response"
|
||||||
|
println("\nERROR YiemAgent extractWineAttributes_1() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
# # 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 decisionMaker() $errornote:\n$response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
# continue
|
||||||
|
# elseif sum(values(detected_kw)) > length(header)
|
||||||
|
# errornote = "Your previous attempt has duplicated points"
|
||||||
|
# println("\nERROR YiemAgent decisionMaker() $errornote:\n$response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
# continue
|
||||||
|
# end
|
||||||
|
|
||||||
|
# # check whether response has all answer's key points
|
||||||
|
# detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||||
|
# if 0 ∈ values(detected_kw)
|
||||||
|
# errornote = "In your previous attempts, the response does not have all answer's key points"
|
||||||
|
# println("\nYiemAgent extractWineAttributes_1() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
# continue
|
||||||
|
# elseif sum(values(detected_kw)) > length(header)
|
||||||
|
# errornote = "In your previous attempts, the response has duplicated answer's key points"
|
||||||
|
# println("\nYiemAgent extractWineAttributes_1() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
# println(response)
|
||||||
|
# continue
|
||||||
|
# end
|
||||||
|
# responsedict = GeneralUtils.textToDict(response, header;
|
||||||
|
# dictKey=dictkey, symbolkey=true)
|
||||||
|
|
||||||
|
removekeys = [:thought, :tasting_notes, :occasion, :food_to_be_paired_with_wine, :vintage]
|
||||||
|
for i in removekeys
|
||||||
|
delete!(responsedict, i)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
delete!(responsedict, :thought)
|
||||||
delete!(responsedict, :tasting_notes)
|
delete!(responsedict, :tasting_notes)
|
||||||
delete!(responsedict, :occasion)
|
delete!(responsedict, :occasion)
|
||||||
delete!(responsedict, :food_to_be_paired_with_wine)
|
delete!(responsedict, :food_to_be_paired_with_wine)
|
||||||
|
delete!(responsedict, :vintage)
|
||||||
println(@__FILE__, " ", @__LINE__)
|
|
||||||
pprintln(responsedict)
|
|
||||||
|
|
||||||
# check if winery, wine_name, region, country, wine_type, grape_varietal's value are in the query because sometime AI halucinates
|
# check if winery, wine_name, region, country, wine_type, grape_varietal's value are in the query because sometime AI halucinates
|
||||||
checkFlag = false
|
checkFlag = false
|
||||||
for i in dictkey
|
for i in requiredKeys
|
||||||
j = Symbol(i)
|
j = Symbol(i)
|
||||||
if j ∉ [:comprehension, :tasting_notes, :occasion, :food_to_be_paired_with_wine]
|
if j ∉ removekeys
|
||||||
# in case j is wine_price it needs to be checked differently because its value is ranged
|
# in case j is wine_price it needs to be checked differently because its value is ranged
|
||||||
if j == :wine_price
|
if j == :wine_price
|
||||||
if responsedict[:wine_price] != "NA"
|
if responsedict[:wine_price] != "N/A"
|
||||||
# check whether wine_price is in ranged number
|
# check whether wine_price is in ranged number
|
||||||
if !occursin('-', responsedict[:wine_price])
|
if !occursin("to", responsedict[:wine_price])
|
||||||
errornote = "wine_price must be a range number"
|
errornote = "In your previous attempt, the 'wine_price' was set to $(responsedict[:wine_price]) which is not a correct format. Please adjust it accordingly."
|
||||||
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
println("\nERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
checkFlag = true
|
checkFlag = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
# check whether max wine_price is in the input
|
# # check whether max wine_price is in the input
|
||||||
pricerange = split(responsedict[:wine_price], '-')
|
# pricerange = split(responsedict[:wine_price], '-')
|
||||||
minprice = pricerange[1]
|
# minprice = pricerange[1]
|
||||||
maxprice = pricerange[end]
|
# maxprice = pricerange[end]
|
||||||
if !occursin(maxprice, input)
|
# if !occursin(maxprice, input)
|
||||||
responsedict[:wine_price] = "NA"
|
# responsedict[:wine_price] = "N/A"
|
||||||
end
|
# end
|
||||||
# price range like 100-100 is not good
|
# # price range like 100-100 is not good
|
||||||
if minprice == maxprice
|
# if minprice == maxprice
|
||||||
errornote = "wine_price with minimum equals to maximum is not valid"
|
# errornote = "In your previous attempt, you inputted 'wine_price' with a 'minimum' value equaling the 'maximum', which is not valid."
|
||||||
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
# println("\nERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
checkFlag = true
|
# checkFlag = true
|
||||||
break
|
# break
|
||||||
end
|
# end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
content = responsedict[j]
|
content = responsedict[j]
|
||||||
@@ -478,14 +579,14 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
content = [content]
|
content = [content]
|
||||||
end
|
end
|
||||||
|
|
||||||
for x in content #check whether price are mentioned in the input
|
# for x in content #check whether price are mentioned in the input
|
||||||
if !occursin("NA", responsedict[j]) && !occursin(x, input)
|
# if !occursin("NA", responsedict[j]) && !occursin(x, input)
|
||||||
errornote = "$x is not mentioned in the user query, you must only use the info from the query."
|
# errornote = "$x is not mentioned in the user query, you must only use the info from the query."
|
||||||
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
# println("ERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
checkFlag == true
|
# checkFlag == true
|
||||||
break
|
# break
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -500,7 +601,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
result = ""
|
result = ""
|
||||||
for (k, v) in responsedict
|
for (k, v) in responsedict
|
||||||
# some time LLM generate text with "(some comment)". this line removes it
|
# some time LLM generate text with "(some comment)". this line removes it
|
||||||
if !occursin("NA", v) && v != "" && !occursin("none", v) && !occursin("None", v)
|
if !occursin("N/A", v) && v != "" && !occursin("none", v) && !occursin("None", v)
|
||||||
result *= "$k: $v, "
|
result *= "$k: $v, "
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -523,7 +624,7 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
|
|
||||||
conversiontable =
|
conversiontable =
|
||||||
"""
|
"""
|
||||||
<Conversion Table>
|
<conversion_table>
|
||||||
Intensity level:
|
Intensity level:
|
||||||
1 to 2: May correspond to "light-bodied" or a similar description.
|
1 to 2: May correspond to "light-bodied" or a similar description.
|
||||||
2 to 3: May correspond to "med light bodied", "medium light" or a similar description.
|
2 to 3: May correspond to "med light bodied", "medium light" or a similar description.
|
||||||
@@ -548,149 +649,177 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
3 to 4: May correspond to "medium acidity" or a similar description.
|
3 to 4: May correspond to "medium acidity" or a similar description.
|
||||||
4 to 5: May correspond to "semi high acidity" or a similar description.
|
4 to 5: May correspond to "semi high acidity" or a similar description.
|
||||||
4 to 5: May correspond to "high acidity" or a similar description.
|
4 to 5: May correspond to "high acidity" or a similar description.
|
||||||
</Conversion Table>
|
</conversion_table>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
systemmsg =
|
systemmsg =
|
||||||
"""
|
"""
|
||||||
As an helpful sommelier, your task is to fill out the user's preference form based on the corresponding words from the user's query.
|
As an helpful sommelier, your task is to fill out the user's preference form based on the corresponding words from the user's query.
|
||||||
|
|
||||||
At each round of conversation, the user will give you the current situation:
|
At each round of conversation, you will be given the following information:
|
||||||
Conversion Table: ...
|
conversion_table: a conversion table that maps descriptive words to their corresponding integer levels
|
||||||
User's query: ...
|
query: the words from the user's query that describe their preferences
|
||||||
|
|
||||||
The preference form requires the following information:
|
The preference form requires the following information:
|
||||||
sweetness, acidity, tannin, intensity
|
sweetness, acidity, tannin, intensity
|
||||||
|
|
||||||
<You must follow the following guidelines>
|
You must follow the following guidelines:
|
||||||
1) If specific information required in the preference form is not available in the query or there isn't any, mark with 'NA' to indicate this.
|
1) If specific information required in the preference form is not available in the query or there isn't any, mark with 'N/A' to indicate this.
|
||||||
Additionally, words like 'any' or 'unlimited' mean no information is available.
|
Additionally, words like 'any' or 'unlimited' mean no information is available.
|
||||||
2) Use the conversion table to convert the descriptive word level of sweetness, intensity, tannin, and acidity into a corresponding integer.
|
2) Use the conversion table to convert the descriptive word level of sweetness, intensity, tannin, and acidity into a corresponding integer.
|
||||||
3) Do not generate other comments.
|
3) Do not generate other comments.
|
||||||
</You must follow the following guidelines>
|
You should then respond to the user with:
|
||||||
|
sweetness_keyword: The exact keywords in the user's query describing the sweetness level of the wine.
|
||||||
|
sweetness: ( S ), where ( S ) represents integers indicating the range of sweetness levels. Example: 1-2
|
||||||
|
acidity_keyword: The exact keywords in the user's query describing the acidity level of the wine.
|
||||||
|
acidity: ( A ), where ( A ) represents integers indicating the range of acidity level. Example: 3-5
|
||||||
|
tannin_keyword: The exact keywords in the user's query describing the tannin level of the wine.
|
||||||
|
tannin: ( T ), where ( T ) represents integers indicating the range of tannin level. Example: 1-3
|
||||||
|
intensity_keyword: The exact keywords in the user's query describing the intensity level of the wine.
|
||||||
|
intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
|
||||||
|
You should only respond in JSON format as described below:
|
||||||
|
{
|
||||||
|
"sweetness_keyword": "...",
|
||||||
|
"sweetness": "...",
|
||||||
|
"acidity_keyword": "...",
|
||||||
|
"acidity": "...",
|
||||||
|
"tannin_keyword": "...",
|
||||||
|
"tannin": "...",
|
||||||
|
"intensity_keyword": "...",
|
||||||
|
"intensity": "..."
|
||||||
|
}
|
||||||
|
|
||||||
<You should then respond to the user with>
|
Here are some examples:
|
||||||
Sweetness_keyword: The exact keywords in the user's query describing the sweetness level of the wine.
|
|
||||||
Sweetness: ( S ), where ( S ) represents integers indicating the range of sweetness levels. Example: 1-2
|
|
||||||
Acidity_keyword: The exact keywords in the user's query describing the acidity level of the wine.
|
|
||||||
Acidity: ( A ), where ( A ) represents integers indicating the range of acidity level. Example: 3-5
|
|
||||||
Tannin_keyword: The exact keywords in the user's query describing the tannin level of the wine.
|
|
||||||
Tannin: ( T ), where ( T ) represents integers indicating the range of tannin level. Example: 1-3
|
|
||||||
Intensity_keyword: The exact keywords in the user's query describing the intensity level of the wine.
|
|
||||||
Intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
|
|
||||||
</You should then respond to the user with>
|
|
||||||
|
|
||||||
<You should only respond in format as described below>
|
|
||||||
Sweetness_keyword: ...
|
|
||||||
Sweetness: ...
|
|
||||||
Acidity_keyword: ...
|
|
||||||
Acidity: ...
|
|
||||||
Tannin_keyword: ...
|
|
||||||
Tannin: ...
|
|
||||||
Intensity_keyword: ...
|
|
||||||
Intensity: ...
|
|
||||||
</You should only respond in format as described below>
|
|
||||||
|
|
||||||
<Here are some examples>
|
|
||||||
User's query: I want a wine with a medium-bodied, low acidity, medium tannin.
|
User's query: I want a wine with a medium-bodied, low acidity, medium tannin.
|
||||||
Sweetness_keyword: NA
|
{
|
||||||
Sweetness: NA
|
"sweetness_keyword": "N/A",
|
||||||
Acidity_keyword: low acidity
|
"sweetness": "N/A",
|
||||||
Acidity: 1-2
|
"acidity_keyword": "low acidity",
|
||||||
Tannin_keyword: medium tannin
|
"acidity": "1-2",
|
||||||
Tannin: 3-4
|
"tannin_keyword": "medium tannin",
|
||||||
Intensity_keyword: medium-bodied
|
"tannin": "3-4",
|
||||||
Intensity: 3-4
|
"intensity_keyword": "medium-bodied",
|
||||||
|
"intensity": "3-4"
|
||||||
|
}
|
||||||
|
|
||||||
User's query: German red wine, under 100, pairs with spicy food
|
User's query: German red wine, under 100, pairs with spicy food
|
||||||
Sweetness_keyword: NA
|
{
|
||||||
Sweetness: NA
|
"sweetness_keyword": "N/A",
|
||||||
Acidity_keyword: NA
|
"sweetness": "N/A",
|
||||||
Acidity: NA
|
"acidity_keyword": "N/A",
|
||||||
Tannin_keyword: NA
|
"acidity": "N/A",
|
||||||
Tannin: NA
|
"tannin_keyword": "N/A",
|
||||||
Intensity_keyword: NA
|
"tannin": "N/A",
|
||||||
Intensity: NA
|
"intensity_keyword": "N/A",
|
||||||
</Here are some examples>
|
"intensity": "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
header = ["Sweetness_keyword:", "Sweetness:", "Acidity_keyword:", "Acidity:", "Tannin_keyword:", "Tannin:", "Intensity_keyword:", "Intensity:"]
|
requiredKeys = [:sweetness_keyword, :sweetness, :acidity_keyword, :acidity, :tannin_keyword, :tannin, :intensity_keyword, :intensity]
|
||||||
dictkey = ["sweetness_keyword", "sweetness", "acidity_keyword", "acidity", "tannin_keyword", "tannin", "intensity_keyword", "intensity"]
|
|
||||||
errornote = ""
|
# header = ["Sweetness_keyword:", "Sweetness:", "Acidity_keyword:", "Acidity:", "Tannin_keyword:", "Tannin:", "Intensity_keyword:", "Intensity:"]
|
||||||
|
# dictkey = ["sweetness_keyword", "sweetness", "acidity_keyword", "acidity", "tannin_keyword", "tannin", "intensity_keyword", "intensity"]
|
||||||
|
errornote = "N/A"
|
||||||
|
|
||||||
for attempt in 1:10
|
for attempt in 1:10
|
||||||
usermsg =
|
context =
|
||||||
"""
|
"""
|
||||||
$conversiontable
|
$conversiontable
|
||||||
User's query: $input
|
<query>
|
||||||
$errornote
|
$input
|
||||||
|
</query>
|
||||||
|
P.S. $errornote
|
||||||
|
/no_think
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_prompt =
|
unformatPrompt =
|
||||||
[
|
[
|
||||||
Dict(:name=> "system", :text=> systemmsg),
|
Dict(:name=> "system", :text=> systemmsg),
|
||||||
Dict(:name=> "user", :text=> usermsg)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
|
||||||
|
# add info
|
||||||
|
prompt = prompt * context
|
||||||
|
|
||||||
response = a.func[:text2textInstructLLM](prompt)
|
response = a.context.text2textInstructLLM(prompt; modelsize="medium", senderId=a.id)
|
||||||
|
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
||||||
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
|
think, response = GeneralUtils.extractthink(response)
|
||||||
|
|
||||||
# check whether response has all header
|
responsedict = nothing
|
||||||
detected_kw = GeneralUtils.detect_keyword(header, response)
|
try
|
||||||
if sum(values(detected_kw)) < length(header)
|
responsedict = copy(JSON3.read(response))
|
||||||
errornote = "\nYiemAgent extractWineAttributes_2() response does not have all header"
|
catch
|
||||||
continue
|
println("\nERROR YiemAgent extractWineAttributes_2() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
elseif sum(values(detected_kw)) > length(header)
|
|
||||||
errornote = "\nYiemAgent extractWineAttributes_2() response has duplicated header"
|
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
responsedict = GeneralUtils.textToDict(response, header;
|
# check whether all answer's key points are in responsedict
|
||||||
dictKey=dictkey, symbolkey=true)
|
_responsedictKey = keys(responsedict)
|
||||||
|
responsedictKey = [i for i in _responsedictKey] # convert into a list
|
||||||
|
is_requiredKeys_in_responsedictKey = [i ∈ responsedictKey for i in requiredKeys]
|
||||||
|
|
||||||
|
if length(is_requiredKeys_in_responsedictKey) > length(requiredKeys)
|
||||||
|
errornote = "Your previous attempt has more key points than answer's required key points."
|
||||||
|
println("\nERROR YiemAgent extractWineAttributes_2() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
continue
|
||||||
|
elseif !all(is_requiredKeys_in_responsedictKey)
|
||||||
|
zeroind = findall(x -> x == 0, is_requiredKeys_in_responsedictKey)
|
||||||
|
missingkeys = [requiredKeys[i] for i in zeroind]
|
||||||
|
errornote = "$missingkeys are missing from your previous response"
|
||||||
|
println("\nERROR YiemAgent extractWineAttributes_2() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
# check whether each describing keyword is in the input to prevent halucination
|
# check whether each describing keyword is in the input to prevent halucination
|
||||||
for i in ["sweetness", "acidity", "tannin", "intensity"]
|
for i in ["sweetness", "acidity", "tannin", "intensity"]
|
||||||
keyword = Symbol(i * "_keyword") # e.g. sweetness_keyword
|
keyword = Symbol(i * "_keyword") # e.g. sweetness_keyword
|
||||||
value = responsedict[keyword]
|
value = responsedict[keyword]
|
||||||
if value != "NA" && !occursin(value, input)
|
if value != "N/A" && !occursin(value, input)
|
||||||
errornote = "WARNING. Keyword $keyword: $value does not appear in the input. You must use information from the input only"
|
errornote = "In your previous attempt, keyword $keyword: $value does not appear in the input. You must use information from the input only"
|
||||||
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
println("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
# if value == "NA" then responsedict[i] = "NA"
|
# if value == "N/A" then responsedict[i] = "N/A"
|
||||||
# e.g. if sweetness_keyword == "NA" then sweetness = "NA"
|
# e.g. if sweetness_keyword == "N/A" then sweetness = "N/A"
|
||||||
if value == "NA"
|
if value == "N/A"
|
||||||
responsedict[Symbol(i)] = "NA"
|
responsedict[Symbol(i)] = "N/A"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# some time LLM not put integer range
|
# some time LLM not put integer range
|
||||||
for (k, v) in responsedict
|
for (k, v) in responsedict
|
||||||
if !occursin("keyword", string(k))
|
if !occursin("keyword", string(k))
|
||||||
if v !== "NA" && (!occursin('-', v) || length(v) > 5)
|
if v !== "N/A" && (!occursin('-', v) || length(v) > 5)
|
||||||
errornote = "WARNING: The non-range value {$k: $v} is not allowed. It should be specified in a range format, i.e. min-max."
|
errornote = "WARNING: The non-range value {$k: $v} is not allowed. It should be specified in a range format, i.e. min-max."
|
||||||
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
println("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# some time LLM says NA-2. Need to convert NA to 1
|
# some time LLM says N/A-2. Need to convert N/A to 1
|
||||||
for (k, v) in responsedict
|
for (k, v) in responsedict
|
||||||
if occursin("NA", v) && occursin("-", v)
|
if occursin("N/A", v) && occursin("-", v)
|
||||||
new_v = replace(v, "NA"=>"1")
|
new_v = replace(v, "N/A"=>"1")
|
||||||
responsedict[k] = new_v
|
responsedict[k] = new_v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# delete some key words from responsedict
|
||||||
|
for (k, v) in responsedict
|
||||||
|
if k ∈ [:sweetness_keyword, :acidity_keyword, :tannin_keyword, :intensity_keyword]
|
||||||
|
delete!(responsedict, k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
result = ""
|
result = ""
|
||||||
for (k, v) in responsedict
|
for (k, v) in responsedict
|
||||||
# some time LLM generate text with "(some comment)". this line removes it
|
# some time LLM generate text with "(some comment)". this line removes it
|
||||||
if !occursin("NA", v)
|
if !occursin("N/A", v)
|
||||||
result *= "$k: $v, "
|
result *= "$k: $v, "
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -731,22 +860,25 @@ function paraphrase(text2textInstructLLM::Function, text::String)
|
|||||||
- N/A
|
- N/A
|
||||||
|
|
||||||
You should then respond to the user with:
|
You should then respond to the user with:
|
||||||
1) Paraphrase: Paraphrased text
|
Paraphrase: Paraphrased text
|
||||||
|
|
||||||
You should only respond in format as described below:
|
You should only respond in format as described below:
|
||||||
Paraphrase: ...
|
Paraphrase: ...
|
||||||
|
|
||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
|
#[PENDING] use JSON3 the same as extractWineAttributes_1 is better. change this function to use the same format use decisionMaker
|
||||||
|
header = ["Paraphrase:"]
|
||||||
|
dictkey = ["paraphrase"]
|
||||||
|
|
||||||
errornote = ""
|
errornote = "N/A"
|
||||||
response = nothing # placeholder for show when error msg show up
|
response = nothing # placeholder for show when error msg show up
|
||||||
|
|
||||||
|
|
||||||
for attempt in 1:10
|
for attempt in 1:10
|
||||||
usermsg = """
|
usermsg = """
|
||||||
Text: $text
|
Text: $text
|
||||||
$errornote
|
P.S. $errornote
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_prompt =
|
_prompt =
|
||||||
@@ -756,17 +888,16 @@ function paraphrase(text2textInstructLLM::Function, text::String)
|
|||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
prompt = GeneralUtils.formatLLMtext(_prompt, a.llmFormatName)
|
||||||
prompt *= """
|
|
||||||
<|start_header_id|>assistant<|end_header_id|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
try
|
try
|
||||||
response = text2textInstructLLM(prompt)
|
response = text2textInstructLLM(prompt)
|
||||||
|
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
||||||
|
think, response = GeneralUtils.extractthink(response)
|
||||||
# sometime the model response like this "here's how I would respond: ..."
|
# sometime the model response like this "here's how I would respond: ..."
|
||||||
if occursin("respond:", response)
|
if occursin("respond:", response)
|
||||||
errornote = "You don't need to intro your response"
|
errornote = "You don't need to intro your response"
|
||||||
error("\n~~~ paraphrase() response contain : ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
error("\nparaphrase() response contain : ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
end
|
end
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
response = replace(response, '*'=>"")
|
response = replace(response, '*'=>"")
|
||||||
@@ -774,14 +905,22 @@ function paraphrase(text2textInstructLLM::Function, text::String)
|
|||||||
response = replace(response, '`' => "")
|
response = replace(response, '`' => "")
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
|
|
||||||
header = ["Paraphrase:"]
|
# check whether response has all answer's key points
|
||||||
dictkey = ["paraphrase"]
|
detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||||
|
if 0 ∈ values(detected_kw)
|
||||||
|
errornote = "\nYiemAgent paraphrase() response does not have all answer's key points"
|
||||||
|
continue
|
||||||
|
elseif sum(values(detected_kw)) > length(header)
|
||||||
|
errornote = "\nnYiemAgent paraphrase() response has duplicated answer's key points"
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
responsedict = GeneralUtils.textToDict(response, header;
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
dictKey=dictkey, symbolkey=true)
|
dictKey=dictkey, symbolkey=true)
|
||||||
|
|
||||||
for i ∈ [:paraphrase]
|
for i ∈ [:paraphrase]
|
||||||
if length(JSON3.write(responsedict[i])) == 0
|
if length(JSON3.write(responsedict[i])) == 0
|
||||||
error("$i is empty ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
error("$i is empty ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -793,7 +932,7 @@ function paraphrase(text2textInstructLLM::Function, text::String)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
println("\n~~~ paraphrase() ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
println("\nparaphrase() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
pprintln(Dict(responsedict))
|
pprintln(Dict(responsedict))
|
||||||
|
|
||||||
result = responsedict[:paraphrase]
|
result = responsedict[:paraphrase]
|
||||||
@@ -804,10 +943,10 @@ function paraphrase(text2textInstructLLM::Function, text::String)
|
|||||||
showerror(io, e)
|
showerror(io, e)
|
||||||
errorMsg = String(take!(io))
|
errorMsg = String(take!(io))
|
||||||
st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
|
st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
|
||||||
println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
error("generatechat failed to generate a response")
|
error("paraphrase() failed to generate a response")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -915,7 +1054,7 @@ end
|
|||||||
# “action_name” is the name of the action taken, which can be one of the following functions:
|
# “action_name” is the name of the action taken, which can be one of the following functions:
|
||||||
# 1) CHATBOX[text], which you can use to talk with the user. "text" is in verbal English.
|
# 1) CHATBOX[text], which you can use to talk with the user. "text" is in verbal English.
|
||||||
# 2) WINESTOCK[query], which you can use to find info about wine in your inventory. "query" is a search term in verbal English. The best query must includes "budget", "type of wine", "characteristics of wine" and "food pairing".
|
# 2) WINESTOCK[query], which you can use to find info about wine in your inventory. "query" is a search term in verbal English. The best query must includes "budget", "type of wine", "characteristics of wine" and "food pairing".
|
||||||
# "action_input" is the input to the action
|
# "actioninput" is the input to the action
|
||||||
# "observation" is result of the preceding immediate action.
|
# "observation" is result of the preceding immediate action.
|
||||||
|
|
||||||
# At each round of conversation, the user will give you:
|
# At each round of conversation, the user will give you:
|
||||||
@@ -970,7 +1109,7 @@ end
|
|||||||
# ]
|
# ]
|
||||||
|
|
||||||
# # put in model format
|
# # put in model format
|
||||||
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
# prompt = GeneralUtils.formatLLMtext(_prompt, "granite3")
|
||||||
# prompt *=
|
# prompt *=
|
||||||
# """
|
# """
|
||||||
# <|start_header_id|>assistant<|end_header_id|>
|
# <|start_header_id|>assistant<|end_header_id|>
|
||||||
@@ -1002,7 +1141,7 @@ end
|
|||||||
# state[:isterminal] = true
|
# state[:isterminal] = true
|
||||||
# state[:reward] = 1
|
# state[:reward] = 1
|
||||||
# end
|
# end
|
||||||
# println("--> 5 Evaluator ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
# println("--> 5 Evaluator ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
# pprintln(Dict(responsedict))
|
# pprintln(Dict(responsedict))
|
||||||
# return responsedict[:score]
|
# return responsedict[:score]
|
||||||
# catch e
|
# catch e
|
||||||
|
|||||||
211
src/type.jl
211
src/type.jl
@@ -1,19 +1,65 @@
|
|||||||
module type
|
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
|
using GeneralUtils
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
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
|
abstract type agent end
|
||||||
|
|
||||||
|
|
||||||
mutable struct companion <: agent
|
mutable struct companion <: agent
|
||||||
|
name::String # agent name
|
||||||
id::String # agent id
|
id::String # agent id
|
||||||
systemmsg::Union{String, Nothing}
|
systemmsg::String # system message
|
||||||
|
tools::Dict # tools
|
||||||
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
|
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
|
""" Memory
|
||||||
Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
|
Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
|
||||||
@@ -22,45 +68,31 @@ mutable struct companion <: agent
|
|||||||
Dict(:name=>"user", :text=> "Wassup!", :timestamp=> Dates.now()),
|
Dict(:name=>"user", :text=> "Wassup!", :timestamp=> Dates.now()),
|
||||||
Dict(:name=>"assistant", :text=> "Hi I'm your assistant.", :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
|
|
||||||
;
|
|
||||||
id::String= string(uuid4()),
|
|
||||||
systemmsg::Union{String, Nothing}= nothing,
|
|
||||||
maxHistoryMsg::Integer= 20,
|
|
||||||
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
|
|
||||||
)
|
|
||||||
|
|
||||||
memory = Dict{Symbol, Any}(
|
memory = Dict{Symbol, Any}(
|
||||||
:chatbox=> "",
|
:events=> Vector{Dict{Symbol, Any}}(),
|
||||||
:shortmem=> OrderedDict{Symbol, Any}(),
|
:state=> Dict{Symbol, Any}(), # state of the agent
|
||||||
:events=> Vector{Dict{Symbol, Any}}(),
|
:recap=> OrderedDict{Symbol, Any}(), # recap summary of the conversation
|
||||||
:state=> Dict{Symbol, Any}(),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
newAgent = companion(
|
newAgent = companion(
|
||||||
id,
|
name,
|
||||||
systemmsg,
|
id,
|
||||||
maxHistoryMsg,
|
systemmsg,
|
||||||
chathistory,
|
tools,
|
||||||
memory,
|
maxHistoryMsg,
|
||||||
text2textInstructLLM
|
chathistory,
|
||||||
)
|
memory,
|
||||||
|
context,
|
||||||
|
llmFormatName
|
||||||
|
)
|
||||||
|
|
||||||
return newAgent
|
return newAgent
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
""" A sommelier agent.
|
""" A sommelier agent.
|
||||||
|
|
||||||
# Arguments
|
# Arguments
|
||||||
@@ -134,29 +166,21 @@ mutable struct sommelier <: agent
|
|||||||
retailername::String
|
retailername::String
|
||||||
tools::Dict
|
tools::Dict
|
||||||
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
|
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}}
|
chathistory::Vector{Dict{Symbol, Any}}
|
||||||
memory::Dict{Symbol, Any}
|
memory::Dict{Symbol, Any}
|
||||||
func # NamedTuple of functions
|
context # NamedTuple of functions
|
||||||
|
llmFormatName::String
|
||||||
end
|
end
|
||||||
|
|
||||||
function sommelier(
|
function sommelier(
|
||||||
func, # NamedTuple of functions
|
context::appcontext, # app context
|
||||||
;
|
;
|
||||||
name::String= "Assistant",
|
name::String= "Assistant",
|
||||||
id::String= string(uuid4()),
|
id::String= string(uuid4()),
|
||||||
retailername::String= "retailer_name",
|
retailername::String= "retailer_name",
|
||||||
maxHistoryMsg::Integer= 20,
|
maxHistoryMsg::Integer= 20,
|
||||||
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
|
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
|
||||||
|
llmFormatName::String= "granite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
tools = Dict( # update input format
|
tools = Dict( # update input format
|
||||||
@@ -170,24 +194,26 @@ 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>""",
|
: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.""",
|
: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
|
||||||
|
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}(
|
memory = Dict{Symbol, Any}(
|
||||||
:chatbox=> "",
|
|
||||||
:shortmem=> OrderedDict{Symbol, Any}(
|
:shortmem=> OrderedDict{Symbol, Any}(
|
||||||
:available_wine=> [],
|
:db_search_result=> Any[],
|
||||||
:found_wine=> [], # used by decisionMaker(). This is to prevent decisionMaker() keep presenting the same wines
|
:scratchpad=> "", #[PENDING] should be a dict e.g. Dict(:database_search_result=>Dict(:wines=> "", :search_query=> ""))
|
||||||
),
|
),
|
||||||
:events=> Vector{Dict{Symbol, Any}}(),
|
:events=> Vector{Dict{Symbol, Any}}(),
|
||||||
:state=> Dict{Symbol, Any}(
|
:state=> Dict{Symbol, Any}(
|
||||||
),
|
),
|
||||||
:recap=> OrderedDict{Symbol, Any}(),
|
:recap=> OrderedDict{Symbol, Any}(),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
newAgent = sommelier(
|
newAgent = sommelier(
|
||||||
@@ -198,7 +224,82 @@ function sommelier(
|
|||||||
maxHistoryMsg,
|
maxHistoryMsg,
|
||||||
chathistory,
|
chathistory,
|
||||||
memory,
|
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
|
return newAgent
|
||||||
|
|||||||
504
src/util.jl
504
src/util.jl
@@ -1,7 +1,8 @@
|
|||||||
module util
|
module util
|
||||||
|
|
||||||
export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
|
export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
|
||||||
availableWineToText
|
availableWineToText, createEventsLog, createChatLog, checkAgentResponse_JSON,
|
||||||
|
checkAgentResponse_text
|
||||||
|
|
||||||
using UUIDs, Dates, DataStructures, HTTP, JSON3
|
using UUIDs, Dates, DataStructures, HTTP, JSON3
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
@@ -102,7 +103,7 @@ end
|
|||||||
-----
|
-----
|
||||||
"""
|
"""
|
||||||
function addNewMessage(a::T1, name::String, text::T2;
|
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
|
if name ∉ ["system", "user", "assistant"] # guard against typo
|
||||||
error("name is not in agent.availableRole $(@__LINE__)")
|
error("name is not in agent.availableRole $(@__LINE__)")
|
||||||
end
|
end
|
||||||
@@ -122,47 +123,53 @@ This function takes in a vector of dictionaries and outputs a single string wher
|
|||||||
|
|
||||||
# Arguments
|
# Arguments
|
||||||
- `vecd::Vector`
|
- `vecd::Vector`
|
||||||
a vector of dictionaries
|
A vector of dictionaries containing chat messages
|
||||||
- `withkey::Bool`
|
- `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
|
# Returns
|
||||||
a string with the formatted dictionaries
|
A formatted string where each line contains either:
|
||||||
|
- If withkey=true: "name> message\n"
|
||||||
|
- If withkey=false: "message\n"
|
||||||
|
|
||||||
# Example
|
# Example
|
||||||
```jldoctest
|
|
||||||
julia> using Revise
|
julia> using Revise
|
||||||
julia> using GeneralUtils
|
julia> using GeneralUtils
|
||||||
julia> vecd = [Dict(:name => "John", :text => "Hello"), Dict(:name => "Jane", :text => "Goodbye")]
|
julia> vecd = [Dict(:name => "John", :text => "Hello"), Dict(:name => "Jane", :text => "Goodbye")]
|
||||||
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
|
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
|
||||||
"John> Hello\nJane> Goodbye\n"
|
"John> Hello\nJane> Goodbye\n"
|
||||||
```
|
```
|
||||||
# Signature
|
|
||||||
"""
|
"""
|
||||||
function chatHistoryToText(vecd::Vector; withkey=true)::String
|
function chatHistoryToText(vecd::Vector; withkey=true, range=nothing)::String
|
||||||
# Initialize an empty string to hold the final text
|
# Initialize an empty string to hold the final text
|
||||||
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
|
# Determine whether to include the key in the output text or not
|
||||||
if withkey
|
if withkey
|
||||||
# Loop through each dictionary in the input vector
|
# Loop through each dictionary in the input vector
|
||||||
for d in vecd
|
for d in elements
|
||||||
# Extract the 'name' and 'text' keys from the dictionary
|
# Extract the 'name' and 'text' keys from the dictionary
|
||||||
name = d[:name]
|
name = titlecase(d[:name])
|
||||||
_text = d[:text]
|
_text = d[:text]
|
||||||
|
|
||||||
# Append the formatted string to the text variable
|
# Append the formatted string to the text variable
|
||||||
text *= "$name> $_text \n"
|
text *= "$name> $_text \n"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# Loop through each dictionary in the input vector
|
# Loop through each dictionary in the input vector
|
||||||
for d in vecd
|
for d in elements
|
||||||
# Iterate over all key-value pairs in the dictionary
|
# Iterate over all key-value pairs in the dictionary
|
||||||
for (k, v) in d
|
for (k, v) in d
|
||||||
# Append the formatted string to the text variable
|
# Append the formatted string to the text variable
|
||||||
text *= "$v \n"
|
text *= "$v \n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return the final text
|
# Return the final text
|
||||||
@@ -191,6 +198,35 @@ end
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
""" Create a dictionary representing an event with optional details.
|
||||||
|
|
||||||
|
# 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(;
|
function eventdict(;
|
||||||
event_description::Union{String, Nothing}=nothing,
|
event_description::Union{String, Nothing}=nothing,
|
||||||
timestamp::Union{DateTime, Nothing}=nothing,
|
timestamp::Union{DateTime, Nothing}=nothing,
|
||||||
@@ -201,266 +237,220 @@ function eventdict(;
|
|||||||
location::Union{String, Nothing}=nothing,
|
location::Union{String, Nothing}=nothing,
|
||||||
equipment_used::Union{String, Nothing}=nothing,
|
equipment_used::Union{String, Nothing}=nothing,
|
||||||
material_used::Union{String, Nothing}=nothing,
|
material_used::Union{String, Nothing}=nothing,
|
||||||
outcome::Union{String, Nothing}=nothing,
|
observation::Union{String, Nothing}=nothing,
|
||||||
note::Union{String, Nothing}=nothing,
|
note::Union{String, Nothing}=nothing,
|
||||||
)
|
)
|
||||||
return Dict{Symbol, Any}(
|
|
||||||
:event_description=> event_description,
|
d = Dict{Symbol, Any}(
|
||||||
:timestamp=> timestamp,
|
:event_description=> event_description,
|
||||||
:subject=> subject,
|
:timestamp=> timestamp,
|
||||||
:thought=> thought,
|
:subject=> subject,
|
||||||
:actionname=> actionname,
|
:thought=> thought,
|
||||||
:actioninput=> actioninput,
|
:actionname=> actionname,
|
||||||
:location=> location,
|
:actioninput=> actioninput,
|
||||||
:equipment_used=> equipment_used,
|
:location=> location,
|
||||||
:material_used=> material_used,
|
:equipment_used=> equipment_used,
|
||||||
:outcome=> outcome,
|
:material_used=> material_used,
|
||||||
:note=> note,
|
:observation=> observation,
|
||||||
)
|
:note=> note,
|
||||||
|
)
|
||||||
|
|
||||||
|
return d
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function createTimeline(memory::T1; skiprecent::Integer=0) where {T1<:AbstractVector}
|
""" Create a formatted timeline string from a sequence of events.
|
||||||
events = memory[1:end-skiprecent]
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
"""
|
||||||
|
function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
|
||||||
|
) where {T1<:AbstractVector}
|
||||||
|
# Initialize empty timeline string
|
||||||
timeline = ""
|
timeline = ""
|
||||||
for (i, event) in enumerate(events)
|
|
||||||
if event[:outcome] === nothing
|
# Determine which indices to use - either provided range or full length
|
||||||
timeline *= "$i) $(event[:subject])> $(event[:actioninput])\n"
|
ind =
|
||||||
|
if eventindex !== nothing
|
||||||
|
[eventindex...]
|
||||||
else
|
else
|
||||||
timeline *= "$i) $(event[:subject])> $(event[:actioninput]) $(event[:outcome])\n"
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Return formatted timeline string
|
||||||
return timeline
|
return timeline
|
||||||
end
|
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
|
||||||
# """ Convert a single chat dictionary into LLM model instruct format.
|
# [eventindex...]
|
||||||
|
|
||||||
# # 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
|
|
||||||
# - `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_phi3instruct(d[:name], d[:text])
|
|
||||||
|
|
||||||
# ```
|
|
||||||
|
|
||||||
# Signature
|
|
||||||
# """
|
|
||||||
# function formatLLMtext_phi3instruct(name::T, text::T) where {T<:AbstractString}
|
|
||||||
# formattedtext =
|
|
||||||
# """
|
|
||||||
# <|$name|>
|
|
||||||
# $text<|end|>\n
|
|
||||||
# """
|
|
||||||
|
|
||||||
# return formattedtext
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# """ Convert a single chat dictionary into LLM model instruct format.
|
|
||||||
|
|
||||||
# # 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
|
# else
|
||||||
# # add critique to prompt
|
# 1:length(events)
|
||||||
# critique *= _critique * "\n"
|
# end
|
||||||
# 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
|
||||||
# end
|
# end
|
||||||
|
|
||||||
# return (success=success, result=result)
|
# # Return formatted timeline string
|
||||||
|
# return timeline
|
||||||
# end
|
# 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
41
test/Manifest.toml
Normal 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
2
test/Project.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[deps]
|
||||||
|
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||||
@@ -36,13 +36,18 @@ function executeSQLVectorDB(sql)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
function text2textInstructLLM(prompt::String; maxattempt=3)
|
function text2textInstructLLM(prompt::String; maxattempt::Integer=3, modelsize::String="medium",
|
||||||
|
llmkwargs=Dict(
|
||||||
|
:num_ctx => 32768,
|
||||||
|
:temperature => 0.1,
|
||||||
|
)
|
||||||
|
)
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
msgMeta = GeneralUtils.generate_msgMeta(
|
||||||
config[:externalservice][:loadbalancer][:mqtttopic];
|
config[:externalservice][:loadbalancer][:mqtttopic];
|
||||||
msgPurpose="inference",
|
msgPurpose="inference",
|
||||||
senderName="yiemagent",
|
senderName="yiemagent",
|
||||||
senderId=sessionId,
|
senderId=sessionId,
|
||||||
receiverName="text2textinstruct_small",
|
receiverName="text2textinstruct_$modelsize",
|
||||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
mqttBrokerPort=config[:mqttServerInfo][:port],
|
||||||
)
|
)
|
||||||
@@ -51,16 +56,13 @@ function text2textInstructLLM(prompt::String; maxattempt=3)
|
|||||||
:msgMeta => msgMeta,
|
:msgMeta => msgMeta,
|
||||||
:payload => Dict(
|
:payload => Dict(
|
||||||
:text => prompt,
|
:text => prompt,
|
||||||
:kwargs => Dict(
|
:kwargs => llmkwargs
|
||||||
:num_ctx => 16384,
|
|
||||||
:temperature => 0.2,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
response = nothing
|
response = nothing
|
||||||
for attempts in 1:maxattempt
|
for attempts in 1:maxattempt
|
||||||
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=300, maxattempt=maxattempt)
|
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=180, maxattempt=maxattempt)
|
||||||
payload = _response[:response]
|
payload = _response[:response]
|
||||||
if _response[:success] && payload[:text] !== nothing
|
if _response[:success] && payload[:text] !== nothing
|
||||||
response = _response[:response][:text]
|
response = _response[:response][:text]
|
||||||
@@ -83,7 +85,7 @@ function getEmbedding(text::T) where {T<:AbstractString}
|
|||||||
msgPurpose="embedding",
|
msgPurpose="embedding",
|
||||||
senderName="yiemagent",
|
senderName="yiemagent",
|
||||||
senderId=sessionId,
|
senderId=sessionId,
|
||||||
receiverName="text2textinstruct_small",
|
receiverName="textembedding",
|
||||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
mqttBrokerPort=config[:mqttServerInfo][:port],
|
||||||
)
|
)
|
||||||
@@ -94,7 +96,8 @@ function getEmbedding(text::T) where {T<:AbstractString}
|
|||||||
:text => [text] # must be a vector of string
|
:text => [text] # must be a vector of string
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120)
|
|
||||||
|
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120, maxattempt=3)
|
||||||
embedding = response[:response][:embeddings]
|
embedding = response[:response][:embeddings]
|
||||||
return embedding
|
return embedding
|
||||||
end
|
end
|
||||||
@@ -161,7 +164,7 @@ function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=3) where {T1
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=5
|
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=3
|
||||||
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
|
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
|
||||||
tablename = "sommelier_decision_repository"
|
tablename = "sommelier_decision_repository"
|
||||||
# find similar
|
# find similar
|
||||||
@@ -194,7 +197,7 @@ function insertSommelierDecision(recentevents::T1, decision::T2; maxdistance::In
|
|||||||
row, col = size(df)
|
row, col = size(df)
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
distance = row == 0 ? Inf : df[1, :distance]
|
||||||
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
||||||
recentevents_embedding = a.func[:getEmbedding](recentevents)[1]
|
recentevents_embedding = getEmbedding(recentevents)[1]
|
||||||
recentevents = replace(recentevents, "'" => "")
|
recentevents = replace(recentevents, "'" => "")
|
||||||
decision_json = JSON3.write(decision)
|
decision_json = JSON3.write(decision)
|
||||||
decision_base64 = base64encode(decision_json)
|
decision_base64 = base64encode(decision_json)
|
||||||
@@ -234,9 +237,11 @@ a = YiemAgent.sommelier(
|
|||||||
)
|
)
|
||||||
|
|
||||||
while true
|
while true
|
||||||
println("your respond: ")
|
print("\nyour respond: ")
|
||||||
user_answer = readline()
|
user_answer = readline()
|
||||||
response = YiemAgent.conversation(a, Dict(:text=> user_answer))
|
response = YiemAgent.conversation(agent;
|
||||||
|
userinput=Dict(:text=> user_answer),
|
||||||
|
maximumMsg=50)
|
||||||
println("\n$response")
|
println("\n$response")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -244,14 +249,13 @@ end
|
|||||||
# response = YiemAgent.conversation(a, Dict(:text=> "I want to get a French red wine under 100."))
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"description": "agent role"
|
"description": "agent role"
|
||||||
},
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
"value": "yiem_hq",
|
"value": "yiem_branch_1",
|
||||||
"description": "organization name"
|
"description": "organization name"
|
||||||
},
|
},
|
||||||
"externalservice": {
|
"externalservice": {
|
||||||
|
|||||||
Reference in New Issue
Block a user