diff --git a/Manifest.toml b/Manifest.toml index 8eb5fef..53b840d 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -1,8 +1,32 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.11.3" +julia_version = "1.12.6" manifest_format = "2.0" -project_hash = "9e0d7dca51b949f2ffa5477b895b90988ec62529" +project_hash = "f82baf5953223c6185bd47af518fd540515402d1" + +[[deps.Accessors]] +deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"] +git-tree-sha1 = "7063ad1083578215c7c4bf410368150abe8d5524" +uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" +version = "0.1.45" + + [deps.Accessors.extensions] + AxisKeysExt = "AxisKeys" + IntervalSetsExt = "IntervalSets" + LinearAlgebraExt = "LinearAlgebra" + StaticArraysExt = "StaticArrays" + StructArraysExt = "StructArrays" + TestExt = "Test" + UnitfulExt = "Unitful" + + [deps.Accessors.weakdeps] + AxisKeys = "94b1ba4f-4ee9-5380-92f1-94cde586c3c5" + IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" + LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" + Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [[deps.AliasTables]] deps = ["PtrArrays", "Random"] @@ -14,6 +38,12 @@ version = "1.1.3" uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" version = "1.1.2" +[[deps.ArrowTypes]] +deps = ["Sockets", "UUIDs"] +git-tree-sha1 = "404265cd8128a2515a81d5eae16de90fdef05101" +uuid = "31f734f8-188a-4ce0-8406-c8a06bd891cd" +version = "2.3.0" + [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" version = "1.11.0" @@ -22,54 +52,92 @@ version = "1.11.0" uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" version = "1.11.0" -[[deps.BitFlags]] -git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d" -uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" -version = "0.1.9" +[[deps.BufferedStreams]] +git-tree-sha1 = "6863c5b7fc997eadcabdbaf6c5f201dc30032643" +uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d" +version = "1.2.2" [[deps.CEnum]] git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc" uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" version = "0.5.0" +[[deps.CRC32c]] +uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc" +version = "1.11.0" + [[deps.CSV]] deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"] -git-tree-sha1 = "deddd8725e5e1cc49ee205a1964256043720a6c3" +git-tree-sha1 = "8d8e0b0f350b8e1c91420b5e64e5de774c2f0f4d" uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -version = "0.10.15" +version = "0.10.16" [[deps.CodeTracking]] -deps = ["InteractiveUtils", "UUIDs"] -git-tree-sha1 = "7eee164f122511d3e4e1ebadb7956939ea7e1c77" +deps = ["InteractiveUtils", "REPL", "UUIDs"] +git-tree-sha1 = "cfb7a2e89e245a9d5016b70323db412b3a7438d5" uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" -version = "1.3.6" +version = "3.0.2" + +[[deps.CodecBase]] +deps = ["TranscodingStreams"] +git-tree-sha1 = "40956acdbef3d8c7cc38cba42b56034af8f8581a" +uuid = "6c391c72-fb7b-5838-ba82-7cfb1bcfecbf" +version = "0.3.4" [[deps.CodecZlib]] deps = ["TranscodingStreams", "Zlib_jll"] -git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759" +git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" -version = "0.7.6" +version = "0.7.8" + +[[deps.CommonSolve]] +git-tree-sha1 = "99ee296f88c12485402e37c2fd025f95ae097637" +uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +version = "0.2.9" [[deps.Compat]] deps = ["TOML", "UUIDs"] -git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215" +git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.16.0" +version = "4.18.1" weakdeps = ["Dates", "LinearAlgebra"] [deps.Compat.extensions] CompatLinearAlgebraExt = "LinearAlgebra" +[[deps.Compiler]] +git-tree-sha1 = "382d79bfe72a406294faca39ef0c3cef6e6ce1f1" +uuid = "807dbc54-b67e-4c79-8afb-eafe4df6f2e1" +version = "0.1.1" + [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.1.1+0" +version = "1.3.0+1" -[[deps.ConcurrentUtilities]] -deps = ["Serialization", "Sockets"] -git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1" -uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" -version = "2.4.2" +[[deps.CompositionsBase]] +git-tree-sha1 = "802bb88cd69dfd1509f6670416bd4434015693ad" +uuid = "a33af91c-f02d-484b-be07-31d278c5ca2b" +version = "0.1.2" +weakdeps = ["InverseFunctions"] + + [deps.CompositionsBase.extensions] + CompositionsBaseInverseFunctionsExt = "InverseFunctions" + +[[deps.ConstructionBase]] +git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb" +uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" +version = "1.6.0" + + [deps.ConstructionBase.extensions] + ConstructionBaseIntervalSetsExt = "IntervalSets" + ConstructionBaseLinearAlgebraExt = "LinearAlgebra" + ConstructionBaseStaticArraysExt = "StaticArrays" + + [deps.ConstructionBase.weakdeps] + IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" + LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" [[deps.Crayons]] git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15" @@ -88,15 +156,15 @@ version = "1.16.0" [[deps.DataFrames]] deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] -git-tree-sha1 = "fb61b4812c49343d7ef0b533ba982c46021938a6" +git-tree-sha1 = "5fab31e2e01e70ad66e3e24c968c264d1cf166d6" uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -version = "1.7.0" +version = "1.8.2" [[deps.DataStructures]] -deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" +deps = ["OrderedCollections"] +git-tree-sha1 = "6fb53a69613a0b2b68a0d12671717d307ab8b24e" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.20" +version = "0.19.5" [[deps.DataValueInterfaces]] git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" @@ -119,37 +187,37 @@ uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" version = "1.11.0" [[deps.Distributions]] -deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"] -git-tree-sha1 = "03aa5d44647eaec98e1920635cdfed5d5560a8b9" +deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "Roots", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"] +git-tree-sha1 = "cd3c5ac74cd3923c8945c6a81518c46abd0e73a3" uuid = "31c24e10-a181-5473-b8eb-7969acd0382f" -version = "0.25.117" +version = "0.25.129" [deps.Distributions.extensions] DistributionsChainRulesCoreExt = "ChainRulesCore" DistributionsDensityInterfaceExt = "DensityInterface" + DistributionsSparseConnectivityTracerExt = "SparseConnectivityTracer" DistributionsTestExt = "Test" [deps.Distributions.weakdeps] ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d" + SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [[deps.DocStringExtensions]] -deps = ["LibGit2"] -git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" +git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.9.3" +version = "0.9.5" [[deps.Downloads]] deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" -version = "1.6.0" +version = "1.7.0" -[[deps.ExceptionUnwrapping]] -deps = ["Test"] -git-tree-sha1 = "d36f682e590a83d63d1c7dbd287573764682d12a" -uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" -version = "0.1.11" +[[deps.EnumX]] +git-tree-sha1 = "c49898e8438c828577f04b92fc9368c388ac783c" +uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" +version = "1.0.7" [[deps.ExprTools]] git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec" @@ -158,19 +226,15 @@ version = "0.1.10" [[deps.FileIO]] deps = ["Pkg", "Requires", "UUIDs"] -git-tree-sha1 = "2dd20384bf8c6d411b5c7370865b1e9b26cb2ea3" +git-tree-sha1 = "91e0e5c68d02bcdaae76d3c8ceb4361e8f28d2e9" uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" -version = "1.16.6" -weakdeps = ["HTTP"] - - [deps.FileIO.extensions] - HTTPExt = "HTTP" +version = "1.16.5" [[deps.FilePathsBase]] deps = ["Compat", "Dates"] -git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361" +git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2" uuid = "48062228-2e41-5def-b9a4-89aafe57970f" -version = "0.9.22" +version = "0.9.24" weakdeps = ["Mmap", "Test"] [deps.FilePathsBase.extensions] @@ -183,44 +247,62 @@ version = "1.11.0" [[deps.FillArrays]] deps = ["LinearAlgebra"] -git-tree-sha1 = "6a70198746448456524cb442b8af316927ff3e1a" +git-tree-sha1 = "2f979084d1e13948a3352cf64a25df6bd3b4dca3" uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" -version = "1.13.0" -weakdeps = ["PDMats", "SparseArrays", "Statistics"] +version = "1.16.0" [deps.FillArrays.extensions] FillArraysPDMatsExt = "PDMats" FillArraysSparseArraysExt = "SparseArrays" + FillArraysStaticArraysExt = "StaticArrays" FillArraysStatisticsExt = "Statistics" + [deps.FillArrays.weakdeps] + PDMats = "90014a1f-27ba-587c-ab20-58faa44d9150" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + [[deps.Future]] deps = ["Random"] uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" version = "1.11.0" +[[deps.Gamma]] +git-tree-sha1 = "86f86b6168a016ed88e4ae4e64577b98c3b59e8e" +uuid = "a0844989-3bd2-4988-8bea-c9407ab0941b" +version = "1.1.0" + [[deps.GeneralUtils]] -deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "JSON3", "MQTTClient", "PrettyPrinting", "Random", "SHA", "UUIDs"] -path = "../GeneralUtils" +deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "HTTP", "JSON", "NATS", "PrettyPrinting", "Random", "Revise", "SHA", "UUIDs"] +git-tree-sha1 = "7c0600c166a5deb2c607018a491c04eb25969c2e" +repo-rev = "main" +repo-url = "https://git.yiem.cc/ton/GeneralUtils" uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe" -version = "0.2.2" +version = "0.4.9" [[deps.HTTP]] -deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] -git-tree-sha1 = "6c22309e9a356ac1ebc5c8a217045f9bae6f8d9a" +deps = ["Base64", "CodecZlib", "Dates", "EnumX", "PrecompileTools", "Random", "Reseau", "SHA", "URIs", "UUIDs", "Zlib_jll"] +git-tree-sha1 = "eda1d37cb55d90a17d0957c75841138c88b361a1" uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" -version = "1.10.13" +version = "2.5.4" + +[[deps.HashArrayMappedTries]] +git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae" +uuid = "076d061b-32b6-4027-95e0-9a2c6f6d7e74" +version = "0.2.0" [[deps.HypergeometricFunctions]] -deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"] -git-tree-sha1 = "2bd56245074fab4015b9174f24ceba8293209053" +deps = ["Gamma", "LinearAlgebra"] +git-tree-sha1 = "18d7deab5fb0440dc6a7b6993c5c27b25420de10" uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a" -version = "0.3.27" +version = "0.3.29" [[deps.ICU_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "20b6765a3016e1fca0c9c93c80d50061b94218b7" +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "b3d8be712fbf9237935bde0ce9b5a736ae38fc34" uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b" -version = "69.1.0+0" +version = "76.2.0+0" [[deps.Infinity]] deps = ["Dates", "Random", "Requires"] @@ -229,38 +311,45 @@ uuid = "a303e19e-6eb4-11e9-3b09-cd9505f79100" version = "0.2.4" [[deps.InlineStrings]] -git-tree-sha1 = "45521d31238e87ee9f9732561bfee12d4eebd52d" +git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d" uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" -version = "1.4.2" +version = "1.4.5" +weakdeps = ["ArrowTypes", "Parsers"] [deps.InlineStrings.extensions] ArrowTypesExt = "ArrowTypes" ParsersExt = "Parsers" - [deps.InlineStrings.weakdeps] - ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" - Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" - [[deps.InteractiveUtils]] deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" version = "1.11.0" [[deps.Intervals]] -deps = ["Dates", "Printf", "RecipesBase", "Serialization", "TimeZones"] -git-tree-sha1 = "ac0aaa807ed5eaf13f67afe188ebc07e828ff640" +deps = ["ArrowTypes", "Dates", "Printf", "RecipesBase", "Serialization", "TimeZones"] +git-tree-sha1 = "d6fe00b123e32ddd17231b35d69a6394e696fd5a" uuid = "d8418881-c3e1-53bb-8760-2df7ec849ed5" -version = "1.10.0" +version = "1.11.0" + +[[deps.InverseFunctions]] +git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb" +uuid = "3587e190-3f89-42d0-90ee-14403ec27112" +version = "0.1.17" +weakdeps = ["Dates", "Test"] + + [deps.InverseFunctions.extensions] + InverseFunctionsDatesExt = "Dates" + InverseFunctionsTestExt = "Test" [[deps.InvertedIndices]] -git-tree-sha1 = "0dc7b50b8d436461be01300fd8cd45aa0274b038" +git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe" uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" -version = "1.3.0" +version = "1.3.1" [[deps.IrrationalConstants]] -git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2" +git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758" uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" -version = "0.2.2" +version = "0.2.6" [[deps.IterTools]] git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023" @@ -274,39 +363,54 @@ version = "1.0.0" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] -git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b" +git-tree-sha1 = "7204148362dafe5fe6a273f855b8ccbe4df8173e" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.8.0" + +[[deps.JSON]] +deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"] +git-tree-sha1 = "c89d196f5ffb64bfbf80985b699ea913b0d2c211" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" version = "1.6.1" +weakdeps = ["ArrowTypes"] + + [deps.JSON.extensions] + JSONArrowExt = ["ArrowTypes"] [[deps.JSON3]] deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"] -git-tree-sha1 = "1d322381ef7b087548321d3f878cb4c9bd8f8f9b" +git-tree-sha1 = "411eccfe8aba0814ffa0fdf4860913ed09c34975" uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" -version = "1.14.1" +version = "1.14.3" +weakdeps = ["ArrowTypes"] [deps.JSON3.extensions] JSON3ArrowExt = ["ArrowTypes"] - [deps.JSON3.weakdeps] - ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" - [[deps.JuliaInterpreter]] deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"] -git-tree-sha1 = "10da5154188682e5c0726823c2b5125957ec3778" +git-tree-sha1 = "58927c485919bf17ea308d9d82156de1adf4b006" uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" -version = "0.9.38" +version = "0.10.12" + +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" +version = "1.12.0" [[deps.Kerberos_krb5_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "60274b4ab38e8d1248216fe6b6ace75ae09b0502" +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0f2899fdadaab4b8f57db558ba21bdb4fb52f1f0" uuid = "b39eb1a6-c29a-53d7-8c32-632cd16f18da" -version = "1.19.3+0" +version = "1.21.3+0" [[deps.LLMMCTS]] -deps = ["GeneralUtils", "JSON3", "PrettyPrinting"] -path = "../LLMMCTS" +deps = ["GeneralUtils", "JSON", "PrettyPrinting"] +git-tree-sha1 = "6b4f123b03c0fcce5b21c0dbcb947e8dd23f333a" +repo-rev = "main" +repo-url = "https://git.yiem.cc/ton/LLMMCTS" uuid = "d76c5a4d-449e-4835-8cc4-dd86ec44f241" -version = "0.1.3" +version = "0.1.5" [[deps.LaTeXStrings]] git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c" @@ -324,19 +428,19 @@ uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" version = "0.6.4" [[deps.LibCURL_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "nghttp2_jll"] uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" -version = "8.6.0+0" +version = "8.15.0+0" [[deps.LibGit2]] -deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" version = "1.11.0" [[deps.LibGit2_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll"] uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" -version = "1.7.2+0" +version = "1.9.0+0" [[deps.LibPQ]] deps = ["CEnum", "DBInterface", "Dates", "Decimals", "DocStringExtensions", "FileWatching", "Infinity", "Intervals", "IterTools", "LayerDicts", "LibPQ_jll", "Libdl", "Memento", "OffsetArrays", "SQLStrings", "Tables", "TimeZones", "UTCDateTimes"] @@ -346,14 +450,14 @@ version = "1.18.0" [[deps.LibPQ_jll]] deps = ["Artifacts", "ICU_jll", "JLLWrappers", "Kerberos_krb5_jll", "Libdl", "OpenSSL_jll", "Zstd_jll"] -git-tree-sha1 = "09163f837936c8cc44f4691cb41d805eb1769642" +git-tree-sha1 = "c692057e05ba6da348bc45d5dab8c7a2c88da518" uuid = "08be9ffa-1c94-5ee5-a977-46a84ec9b350" -version = "16.0.0+0" +version = "16.14.0+0" [[deps.LibSSH2_jll]] -deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +deps = ["Artifacts", "Libdl", "OpenSSL_jll"] uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" -version = "1.11.0+1" +version = "1.11.3+1" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" @@ -362,13 +466,13 @@ version = "1.11.0" [[deps.LinearAlgebra]] deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -version = "1.11.0" +version = "1.12.0" [[deps.LogExpFunctions]] deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea" +git-tree-sha1 = "bba2d9aa057d8f126415de240573e86a8f39d2a1" uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.28" +version = "1.0.1" [deps.LogExpFunctions.extensions] LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" @@ -384,49 +488,39 @@ version = "0.3.28" uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" version = "1.11.0" -[[deps.LoggingExtras]] -deps = ["Dates", "Logging"] -git-tree-sha1 = "f02b56007b064fbfddb4c9cd60161b6dd0f40df3" -uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" -version = "1.1.0" - [[deps.LoweredCodeUtils]] -deps = ["JuliaInterpreter"] -git-tree-sha1 = "688d6d9e098109051ae33d126fcfc88c4ce4a021" +deps = ["CodeTracking", "Compiler", "JuliaInterpreter"] +git-tree-sha1 = "3733419e9a71156b389f3e331672d2e95436783f" uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" -version = "3.1.0" +version = "3.6.2" -[[deps.MQTTClient]] -deps = ["Distributed", "Random", "Sockets"] -git-tree-sha1 = "f2597b290d4bf17b577346153cd2ddf9accb5c26" -uuid = "985f35cc-2c3d-4943-b8c1-f0931d5f0959" -version = "0.3.1" -weakdeps = ["PrecompileTools"] - - [deps.MQTTClient.extensions] - PrecompileMQTT = "PrecompileTools" +[[deps.MacroTools]] +git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.16" [[deps.Markdown]] -deps = ["Base64"] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" version = "1.11.0" [[deps.MbedTLS]] deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] -git-tree-sha1 = "c067a280ddc25f196b5e7df3877c6b226d390aaf" +git-tree-sha1 = "8785729fa736197687541f7053f6d8ab7fc44f92" uuid = "739be429-bea8-5141-9913-cc70e7f3736d" -version = "1.1.9" +version = "1.1.10" [[deps.MbedTLS_jll]] -deps = ["Artifacts", "Libdl"] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "ff69a2b1330bcb730b9ac1ab7dd680176f5896b8" uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.6+0" +version = "2.28.1010+0" [[deps.Memento]] deps = ["Dates", "Distributed", "Requires", "Serialization", "Sockets", "Test", "UUIDs"] -git-tree-sha1 = "bb2e8f4d9f400f6e90d57b34860f6abdc51398e5" +git-tree-sha1 = "e03a25cb3b6569623f8246d3d8b3faa7ce86f4ad" uuid = "f28f55f0-a522-5efc-85c2-fe41dfb9b2d9" -version = "1.4.1" +version = "1.5.0" [[deps.Missings]] deps = ["DataAPI"] @@ -446,16 +540,28 @@ version = "0.8.1" [[deps.MozillaCACerts_jll]] uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2023.12.12" +version = "2025.11.4" + +[[deps.NATS]] +deps = ["Base64", "BufferedStreams", "CodecBase", "Dates", "DocStringExtensions", "JSON3", "MbedTLS", "NanoDates", "Random", "ScopedValues", "Sockets", "Sodium", "StructTypes", "URIs"] +git-tree-sha1 = "a1cdf34ba90ee5cd2658e487d3277ffafee712ce" +uuid = "55e73f9c-eeeb-467f-b4cc-a633fde63d2a" +version = "0.1.1" + +[[deps.NanoDates]] +deps = ["Dates", "Parsers"] +git-tree-sha1 = "850a0557ae5934f6e67ac0dc5ca13d0328422d1f" +uuid = "46f1a544-deae-4307-8689-c12aa3c955c6" +version = "1.0.3" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" -version = "1.2.0" +version = "1.3.0" [[deps.OffsetArrays]] -git-tree-sha1 = "39d000d9c33706b8364817d8894fae1548f40295" +git-tree-sha1 = "117432e406b5c023f665fa73dc26e79ec3630151" uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.14.2" +version = "1.17.0" [deps.OffsetArrays.extensions] OffsetArraysAdaptExt = "Adapt" @@ -466,52 +572,49 @@ version = "1.14.2" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.27+1" +version = "0.3.29+0" [[deps.OpenLibm_jll]] deps = ["Artifacts", "Libdl"] uuid = "05823500-19ac-5b8b-9628-191a04bc5112" -version = "0.8.1+2" - -[[deps.OpenSSL]] -deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"] -git-tree-sha1 = "38cb508d080d21dc1128f7fb04f20387ed4c0af4" -uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c" -version = "1.4.3" +version = "0.8.7+0" [[deps.OpenSSL_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10" +deps = ["Artifacts", "Libdl"] uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.15+1" +version = "3.5.4+0" [[deps.OpenSpecFun_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1" +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" -version = "0.5.5+2" +version = "0.5.6+0" [[deps.OrderedCollections]] -git-tree-sha1 = "12f1439c4f986bb868acda6ea33ebc78e19b95ad" +git-tree-sha1 = "94ba93778373a53bfd5a0caaf7d809c445292ff4" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.7.0" +version = "1.8.2" [[deps.PDMats]] deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"] -git-tree-sha1 = "966b85253e959ea89c53a9abebbf2e964fbf593b" +git-tree-sha1 = "26766d4b5f1a410c218a19b85a672c6edb693c65" uuid = "90014a1f-27ba-587c-ab20-58faa44d9150" -version = "0.11.32" +version = "0.11.40" +weakdeps = ["StatsBase"] + + [deps.PDMats.extensions] + StatsBaseExt = "StatsBase" [[deps.Parsers]] deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" +git-tree-sha1 = "32a4e09c5f29402573d673901778a0e03b0807b9" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.8.1" +version = "2.8.6" [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -version = "1.11.0" +version = "1.12.1" weakdeps = ["REPL"] [deps.Pkg.extensions] @@ -525,15 +628,15 @@ version = "1.4.3" [[deps.PrecompileTools]] deps = ["Preferences"] -git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +git-tree-sha1 = "edbeefc7a4889f528644251bdb5fc9ab5348bc2c" uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.2.1" +version = "1.3.4" [[deps.Preferences]] deps = ["TOML"] -git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +git-tree-sha1 = "8b770b60760d4451834fe79dd483e318eee709c4" uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.4.3" +version = "1.5.2" [[deps.PrettyPrinting]] git-tree-sha1 = "142ee93724a9c5d04d78df7006670a93ed1b244e" @@ -541,10 +644,16 @@ uuid = "54e16d92-306c-5ea0-a30b-337be88ac337" version = "0.4.2" [[deps.PrettyTables]] -deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "Reexport", "StringManipulation", "Tables"] -git-tree-sha1 = "1101cd475833706e4d0e7b122218257178f48f34" +deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"] +git-tree-sha1 = "624de6279ab7d94fc9f672f0068107eb6619732c" uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -version = "2.4.0" +version = "3.3.2" + + [deps.PrettyTables.extensions] + PrettyTablesTypstryExt = "Typstry" + + [deps.PrettyTables.weakdeps] + Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e" [[deps.Printf]] deps = ["Unicode"] @@ -552,15 +661,15 @@ uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" version = "1.11.0" [[deps.PtrArrays]] -git-tree-sha1 = "1d36ef11a9aaf1e8b74dacc6a731dd1de8fd493d" +git-tree-sha1 = "4fbbafbc6251b883f4d2705356f3641f3652a7fe" uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" -version = "1.3.0" +version = "1.4.0" [[deps.QuadGK]] deps = ["DataStructures", "LinearAlgebra"] -git-tree-sha1 = "9da16da70037ba9d701192e27befedefb91ec284" +git-tree-sha1 = "5e8e8b0ab68215d7a2b14b9921a946fee794749e" uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" -version = "2.11.2" +version = "2.11.3" [deps.QuadGK.extensions] QuadGKEnzymeExt = "Enzyme" @@ -569,7 +678,7 @@ version = "2.11.2" Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" [[deps.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"] +deps = ["InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" version = "1.11.0" @@ -591,21 +700,31 @@ version = "1.2.2" [[deps.Requires]] deps = ["UUIDs"] -git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" uuid = "ae029012-a4dd-5104-9daa-d747884805df" -version = "1.3.0" +version = "1.3.1" + +[[deps.Reseau]] +deps = ["NetworkOptions", "OpenSSL_jll", "PrecompileTools", "Random", "SHA"] +git-tree-sha1 = "0eab6d95ed40c2ef3992255c1c71e4f9748932b5" +uuid = "802f3686-a58f-41ce-bb0c-3c43c75bba36" +version = "1.3.1" [[deps.Revise]] -deps = ["CodeTracking", "Distributed", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "REPL", "Requires", "UUIDs", "Unicode"] -git-tree-sha1 = "470f48c9c4ea2170fd4d0f8eb5118327aada22f5" +deps = ["CRC32c", "CodeTracking", "FileWatching", "InteractiveUtils", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Preferences", "REPL", "UUIDs"] +git-tree-sha1 = "27e3ee13fc8739a59b380d6163d6a82f52c03bd7" uuid = "295af30f-e4ad-537b-8983-00126c2a3abe" -version = "3.6.4" +version = "3.15.1" +weakdeps = ["Distributed"] + + [deps.Revise.extensions] + DistributedExt = "Distributed" [[deps.Rmath]] deps = ["Random", "Rmath_jll"] -git-tree-sha1 = "852bd0f55565a9e973fcfee83a84413270224dc4" +git-tree-sha1 = "5b3d50eb374cea306873b371d3f8d3915a018f0b" uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa" -version = "0.8.0" +version = "0.9.0" [[deps.Rmath_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -613,56 +732,91 @@ git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8" uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f" version = "0.5.1+0" +[[deps.Roots]] +deps = ["Accessors", "CommonSolve", "Printf"] +git-tree-sha1 = "ed45bcc7cf3c8887595b973f2b1efbe91dcc50ec" +uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665" +version = "3.0.1" + + [deps.Roots.extensions] + RootsChainRulesCoreExt = "ChainRulesCore" + RootsForwardDiffExt = "ForwardDiff" + RootsIntervalRootFindingExt = "IntervalRootFinding" + RootsSymPyExt = "SymPy" + RootsSymPyPythonCallExt = "SymPyPythonCall" + RootsUnitfulExt = "Unitful" + + [deps.Roots.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" + IntervalRootFinding = "d2bf35a9-74e0-55ec-b149-d360ff49b807" + SymPy = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6" + SymPyPythonCall = "bc8888f7-b21e-4b7c-a06a-5d9c9496438c" + Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" + [[deps.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" version = "0.7.0" +[[deps.SQLLLM]] +deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "PrettyPrinting", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"] +path = "." +uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3" +version = "0.2.4" + [[deps.SQLStrings]] git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c" uuid = "af517c2e-c243-48fa-aab8-efac3db270f5" version = "0.1.0" +[[deps.ScopedValues]] +deps = ["HashArrayMappedTries", "Logging"] +git-tree-sha1 = "67a144433c4ce877ee6d1ada69a124d6b1ecf7be" +uuid = "7e506255-f358-4e82-b7e4-beb19740aa63" +version = "1.6.2" + [[deps.Scratch]] deps = ["Dates"] -git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386" +git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" uuid = "6c6a2e73-6563-6170-7368-637461726353" -version = "1.2.1" +version = "1.3.0" [[deps.SentinelArrays]] deps = ["Dates", "Random"] -git-tree-sha1 = "d0553ce4031a081cc42387a9b9c8441b7d99f32d" +git-tree-sha1 = "084c47c7c5ce5cfecefa0a98dff69eb3646b5a80" uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" -version = "1.4.7" +version = "1.4.10" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" version = "1.11.0" -[[deps.SimpleBufferStream]] -git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1" -uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" -version = "1.2.0" - [[deps.Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" version = "1.11.0" +[[deps.Sodium]] +deps = ["Base64", "libsodium_jll"] +git-tree-sha1 = "907703e0d50846f300650d7225bdcab145b7bca9" +uuid = "4f5b5e99-b0ad-42cd-b47a-334e172ec8bd" +version = "1.1.2" + [[deps.SortingAlgorithms]] deps = ["DataStructures"] -git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085" +git-tree-sha1 = "13cd91cc9be159e3f4d95b857fa2aa383b53772a" uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" -version = "1.2.1" +version = "1.2.3" [[deps.SparseArrays]] deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -version = "1.11.0" +version = "1.12.0" [[deps.SpecialFunctions]] deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "64cca0c26b4f31ba18f13f6c12af7c85f478cfde" +git-tree-sha1 = "6547cbdd8ce32efba0d21c5a40fa96d1a3548f9f" uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.5.0" +version = "2.8.0" [deps.SpecialFunctions.extensions] SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" @@ -682,21 +836,21 @@ weakdeps = ["SparseArrays"] [[deps.StatsAPI]] deps = ["LinearAlgebra"] -git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed" +git-tree-sha1 = "178ed29fd5b2a2cfc3bd31c13375ae925623ff36" uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" -version = "1.7.0" +version = "1.8.0" [[deps.StatsBase]] -deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21" +deps = ["AliasTables", "DataAPI", "DataStructures", "IrrationalConstants", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "e4d7a1a0edc20af42689ea6f4f3587a2175d50ee" uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.34.3" +version = "0.34.12" [[deps.StatsFuns]] deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"] -git-tree-sha1 = "b423576adc27097764a90e163157bcfc9acf0f46" +git-tree-sha1 = "770240df9a3b8888065046948f7a09b4e0f997d5" uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c" -version = "1.3.2" +version = "2.2.0" [deps.StatsFuns.extensions] StatsFunsChainRulesCoreExt = "ChainRulesCore" @@ -708,9 +862,9 @@ version = "1.3.2" [[deps.StringManipulation]] deps = ["PrecompileTools"] -git-tree-sha1 = "a6b1675a536c5ad1a60e5a5153e1fee12eb146e3" +git-tree-sha1 = "d05693d339e37d6ab134c5ab53c29fce5ee5d7d5" uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" -version = "0.4.0" +version = "0.4.4" [[deps.StructTypes]] deps = ["Dates", "UUIDs"] @@ -718,6 +872,22 @@ git-tree-sha1 = "159331b30e94d7b11379037feeb9b690950cace8" uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" version = "1.11.0" +[[deps.StructUtils]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "82bee338d650aa515f31866c460cb7e3bcef90b8" +uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42" +version = "2.8.2" + + [deps.StructUtils.extensions] + StructUtilsMeasurementsExt = ["Measurements"] + StructUtilsStaticArraysCoreExt = ["StaticArraysCore"] + StructUtilsTablesExt = ["Tables"] + + [deps.StructUtils.weakdeps] + Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" + StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" + Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" + [[deps.StyledStrings]] uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" version = "1.11.0" @@ -729,7 +899,7 @@ uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" [[deps.SuiteSparse_jll]] deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "7.7.0+0" +version = "7.8.3+2" [[deps.TOML]] deps = ["Dates"] @@ -738,9 +908,9 @@ version = "1.0.3" [[deps.TZJData]] deps = ["Artifacts"] -git-tree-sha1 = "36b40607bf2bf856828690e097e1c799623b0602" +git-tree-sha1 = "72df96b3a595b7aab1e101eb07d2a435963a97e2" uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7" -version = "1.3.0+2024b" +version = "1.5.0+2025b" [[deps.TableTraits]] deps = ["IteratorInterfaceExtensions"] @@ -750,9 +920,9 @@ version = "1.0.1" [[deps.Tables]] deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] -git-tree-sha1 = "598cd7c1f68d1e205689b1c2fe65a9f85846f297" +git-tree-sha1 = "0f38a06c83f0007bbab3cf911262841c9a0f07e0" uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "1.12.0" +version = "1.13.0" [[deps.Tar]] deps = ["ArgTools", "SHA"] @@ -765,10 +935,10 @@ uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" version = "1.11.0" [[deps.TimeZones]] -deps = ["Dates", "Downloads", "InlineStrings", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"] -git-tree-sha1 = "33c771f2157712ff4c85931186a4984efbe58934" +deps = ["Artifacts", "Dates", "Downloads", "InlineStrings", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"] +git-tree-sha1 = "d422301b2a1e294e3e4214061e44f338cafe18a2" uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53" -version = "1.19.0" +version = "1.22.2" weakdeps = ["RecipesBase"] [deps.TimeZones.extensions] @@ -780,9 +950,9 @@ uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" version = "0.11.3" [[deps.URIs]] -git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b" +git-tree-sha1 = "bef26fb046d031353ef97a82e3fdb6afe7f21b1a" uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.5.1" +version = "1.6.1" [[deps.UTCDateTimes]] deps = ["Dates", "TimeZones"] @@ -801,9 +971,9 @@ version = "1.11.0" [[deps.WeakRefStrings]] deps = ["DataAPI", "InlineStrings", "Parsers"] -git-tree-sha1 = "b1be2855ed9ed8eac54e5caff2afcdb442d52c23" +git-tree-sha1 = "0716e01c3b40413de5dedbc9c5c69f27cddfddfc" uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" -version = "1.4.2" +version = "1.4.3" [[deps.WorkerUtilities]] git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7" @@ -813,25 +983,31 @@ version = "1.6.1" [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.13+1" +version = "1.3.1+2" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b" +git-tree-sha1 = "446b23e73536f84e8037f5dce465e92275f6a308" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.6+1" +version = "1.5.7+1" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.11.0+0" +version = "5.15.0+0" + +[[deps.libsodium_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "011b0a7331b41c25524b64dc42afc9683ee89026" +uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8" +version = "1.0.21+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.59.0+0" +version = "1.64.0+1" [[deps.p7zip_jll]] -deps = ["Artifacts", "Libdl"] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.4.0+2" +version = "17.7.0+0" diff --git a/Project.toml b/Project.toml index 568883e..cce2e81 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SQLLLM" uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3" +version = "0.2.5" authors = ["narawat lamaiin "] -version = "0.2.3" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" @@ -11,7 +11,7 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" GeneralUtils = "c6c72f09-b708-4ac8-ac7c-2084d70108fe" HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" -JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" LLMMCTS = "d76c5a4d-449e-4835-8cc4-dd86ec44f241" LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1" PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337" @@ -24,3 +24,6 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] Dates = "1.11.0" +GeneralUtils = "0.4.0 - 0.9.0" +JSON = "1.6.1" +LLMMCTS = "0.1.5" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e2ee34b --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core b/core deleted file mode 100644 index 1358e6e..0000000 Binary files a/core and /dev/null differ diff --git a/src/interface.jl b/src/interface.jl index 87b1c8d..0803f2f 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -2,7 +2,7 @@ module interface export decisionMaker, evaluator, reflector, transition, query -using LibPQ, DataStructures, JSON3, UUIDs, PrettyPrinting, Dates +using LibPQ, DataStructures, JSON, UUIDs, PrettyPrinting, Dates using GeneralUtils, LLMMCTS using ..util, ..llmfunction @@ -10,6 +10,8 @@ using ..util, ..llmfunction + + """ Think and choose action. # Arguments @@ -21,22 +23,22 @@ using ..util, ..llmfunction A function that handles communication to LLM service # Return - - `thoughtDict::Dict{Symbol, Any}` + - `thoughtDict::Dict{String, Any}` # Example ```jldoctest julia> using SQLLLM, GeneralUtils, UUIDs, DataStructures, PrettyPrinting julia> state = Dict( - :isterminal => false, - :lesson => nothing, - :reward => 0, - :evaluation => "None", - :accepted_as_answer => "No", - :thoughtHistory => OrderedDict{Symbol, Any}(:question => "How many wines do you have that can be paired with lamb?"), - :evaluationscore => 0, - :suggestion => "None" + "isterminal" => false, + "lesson" => nothing, + "reward" => 0, + "evaluation" => "None", + "accepted_as_answer" => "No", + "action_history" => OrderedDict{String, Any}("question" => "How many wines do you have that can be paired with lamb?"), + "evaluationscore" => 0, + "suggestion" => "None" ) -julia> context = Dict(:tablelist=> "None") +julia> context = Dict("tablelist"=> "None") julia> function text2textInstructLLM(prompt::String) config = Dict( :mqttServerInfo => Dict( @@ -100,245 +102,107 @@ Dict( # Signature """ -function decisionMaker(state::T1, context, text2textInstructLLM::Function, - ; querySQLVectorDBF::Union{T2, Nothing}=nothing - )::Dict{Symbol, Any} where {T1<:AbstractDict, T2<:Function} +function decisionMaker(state::T1, text2textInstructLLM::Function, llmFormatName::String + ; querySQLVectorDBF::Union{T2, Nothing}=nothing, maxattempt=10 + )::Dict{String, Any} where {T1<:AbstractDict, T2<:Function} - # lessonDict = - # if isfile("lesson.json") - # lessonDict = copy(JSON3.read("lesson.json")) - # else - # lessonDict = nothing - # end - - # lessonDict = nothing - - # lesson = - # if lessonDict === nothing - # "" - # else - # """ - # You have attempted to help the user before and failed, either because your reasoning for the - # recommendation was incorrect or your response did not exactly match the user expectation. - # The following lesson(s) give a plan to avoid failing to help the user in the same way you - # did previously. Use them to improve your strategy to help the user. - - # Here are some lessons in JSON format: - # $(JSON3.write(lessonDict)) - - # When providing the thought and action for the current trial, that into account these failed - # trajectories and make sure not to repeat the same mistakes and incorrect answers. - # """ - # end - - systemmsg = - """ - You are a helpful assistant that find the data from a database to satisfy the user's query. - You are also eager to improve your helpfulness. - - For your information: - - Observation: Result of the immediately preceding action - - At each round of conversation, the user will give you the current situation: - User Query: ... - Example: ... - Your Q&A: ... - Your work progress: ... - Evaluation: Evaluation of the immediately preceding action and observation - Suggestion: Suggestion for the immediately preceding action and observation - - You must follow the following guidelines: - - Keep SQL queries focused only on the provided information. - - You should follow the following guidelines: - - Do not create any table in the database - - A junction table can be used to link tables together. Another use case is for filtering data. - - If you can't find a single table that can be used to answer the user's query, try joining multiple tables to see if you can obtain the answer. - - If you are unable to find the requested information, kindly inform the user, "The current data in our database does not provide the specific answer to your query". - - Text information in the database usually stored in lower case. If your search returns empty, try using lower case to search. - - You should then respond to the user with interleaving Understanding, Reasoning, Plan, Action: - 1) Comprehension: - - State your comprehension about the current situation. - 2) Plan: Given the current circumstances, outline a detailed, step-by-step plan to accomplish the task. Be specific. - 3) Action_name (Must be aligned with your plan): Can be one of the following functions: - - GETDATA, which you can use to get the data from the database. Action_input for this function must be a single SQL query to be executed against the database. - For more effective text search, it's necessary to use case-insensitivity and the ILIKE operator. - Do not wrap the SQL as it will be executed against the database directly and SQL must be ended with ';'. - 4) Action_input: Input to the action - - You should only respond in format as described below: - Comprehension: ... - Plan: ... - Action_name: ... - Action_input: ... - - Let's begin! - """ - - workprogress = "" - for (k, v) in state[:thoughtHistory] - if k ∉ [:question] - workprogress *= "$k: $v\n" - end - end - - response = nothing # store for show when error msg show up + requiredKeys = ["plan", "action_name", "action_input"] errornote = "" - # provide similar sql only for the first attempt - similarSQL_ = "None" - if length(state[:thoughtHistory]) == 1 - sql, distance = querySQLVectorDBF(state[:thoughtHistory][:question]) - similarSQL_ = sql !== nothing ? sql : "None" + sql, distance = querySQLVectorDBF(state["question"]) + similarSQL_ = sql !== nothing ? sql : "None" + context = + """ + + $(GeneralUtils.dict_to_string_html(state["context"])) + + $similarSQL_ + + + $(GeneralUtils.dict_to_string_html(state["action_history"])) + + + $errornote + + + """ + + # add context to text of the latest message (in the front). + # use for loop because in openai format, each msg may contain both text and image. + + for d in state["chathistory"][end]["content"] + if d["type"] == "text" + d["text"] = context * d["text"] + break + end end + response = nothing # store for show when error msg show up - for attempt in 1:10 - QandA = generatequestion(state, context, text2textInstructLLM; similarSQL=similarSQL_) + for attempt in 1:maxattempt + msg = Dict( + "model" => "gemma-4-E4B-it-UD-Q4_K_XL", + "messages" => state["chathistory"], + "temperature" => 0.7 + ) + response = text2textInstructLLM("random_id", msg) + response = GeneralUtils.clean_json_response(response) - usermsg = - """ - $(context[:tablelist]) - User query: $(state[:thoughtHistory][:question]) - Example: $similarSQL_ - Your Q&A: $QandA - Your work progress: $workprogress - Evaluation: $(state[:evaluation]) - Suggestion: $(state[:suggestion]) - $errornote - """ - - _prompt = - [ - Dict(:name=> "system", :text=> systemmsg), - Dict(:name=> "user", :text=> usermsg) - ] - - # put in model format - prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen") - response = text2textInstructLLM(prompt) - - # LLM tends to generate observation given that it is in the input - response = - if occursin("observation:", response) - string(split(response, "observation:")[1]) - elseif occursin("Observation:", response) - string(split(response, "Observation:")[1]) - elseif occursin("observation_", response) - string(split(response, "observation_")[1]) - elseif occursin("Observation_", response) - string(split(response, "Observation_")[1]) - else - response - end - - # sometime LLM output something like **Comprehension**: which is not expected - response = replace(response, "**"=>"") - response = replace(response, "***"=>"") - - # some time LLM output Plan_1: so we need to detect and replace topic numbering - regex = r"_[0-1000]+:" - matches = collect(eachmatch(regex, response)) - for m in matches - response = replace(response, string(m.match)=>":") - end - - if occursin("NULL", response) - errornote = "\nSQL decisionMaker() NULL response is not allowed" - println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + think, response = GeneralUtils.extractthink(response) + responsedict = nothing + try + _responsedict = JSON.parse(response) + responsedict = GeneralUtils.dictify(_responsedict, keytype=String, sort_order=requiredKeys) + catch + println("\nERROR decisionMaker() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())") continue end - header = ["Comprehension:", "Plan:", "Action_name:", "Action_input:"] - dictkey = ["comprehension", "plan", "action_name", "action_input"] - - # detect if there are more than 1 key per categories - wordcount = GeneralUtils.countGivenWords(response, header) - duplicateKeywordFlag = false - for (i, v) in enumerate(wordcount) - keyword = header[i] - keywordNumber = v - if keywordNumber > 1 - errornote = "\nSQL query has duplicated keyword, $keyword" - println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") - duplicateKeywordFlag = true - break - end + # check whether all answer's key points are in responsedict + ispass, errormsg = GeneralUtils.checkAgentResponse_JSON(responsedict, requiredKeys) + if !ispass + errornote = errormsg + println("\nERROR YiemAgent decisionMaker() $errornote --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n") + continue end - duplicateKeywordFlag == true ? continue : nothing - - # check whether response has all header - kw = [] - # use for loop and detect_keyword function to get the exact variation of each keyword in the text then push to kw list - for keyword in header - detected = GeneralUtils.detect_keyword(keyword, response) - push!(kw, detected) - end - if nothing ∈ kw - println("Some keywords are missing, Required keywords=$header, Response keywords=$kw ", @__FILE__, ":", @__LINE__, " $(Dates.now())") - continue # try again next loop - end - - # textToDict() search for action_input - responsedict = GeneralUtils.textToDict(response, header; - dictKey=dictkey, symbolkey=true) - - delete!(responsedict, :observation) # remove backticks Error occurred: MethodError: no method matching occursin(::String, ::Vector{String}) - if occursin("```", responsedict[:action_input]) - sql = GeneralUtils.extract_triple_backtick_text(responsedict[:action_input])[1] + if occursin("```", responsedict["action_input"]) + sql = GeneralUtils.extract_triple_backtick_text(responsedict["action_input"])[1] if sql[1:4] == "sql\n" sql = sql[5:end] end sql = split(sql, ';') # some time there are comments in the sql sql = sql[1] * ';' - responsedict[:action_input] = sql + responsedict["action_input"] = sql end - toollist = ["TABLEINFO", "GETDATA"] - if responsedict[:action_name] ∉ toollist - errornote = "\nYou must only use the given functions" - println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + toollist = ["RUNSQL"] + if responsedict["action_name"] ∉ toollist + errornote = "Your previous attempt has action_name that is not in the tool list" + println("\nERROR SQLLLM decisionMaker(). Attempt $attempt/$maxattempt. $errornote --(not qualify response)--> $(responsedict["action_name"]) ", @__FILE__, ":", @__LINE__, " $(Dates.now())") continue end for i in toollist - if occursin(i, responsedict[:action_input]) - errornote = "\n action_name is in action_input which is not allowed." - println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + if occursin(i, responsedict["action_input"]) + errornote = "Your previous attempt has action_name in action_input which is not allowed" + println("\nERROR SQLLLM decisionMaker(). Attempt $attempt/$maxattempt. $errornote --(not qualify response)--> $(responsedict["action_input"]) ", @__FILE__, ":", @__LINE__, " $(Dates.now())") continue end end - - for i ∈ [:comprehension, :plan, :action_name, :action_input] - if length(JSON3.write(responsedict[i])) == 0 - errornote = "\n $i is empty" - println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") - continue - end - end - - # check if there are more than 1 key per categories - for i ∈ [:comprehension, :plan, :action_name, :action_input] - matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i) - if length(matchkeys) > 1 - errornote = "\n $i has more than one key" - println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") - continue - end - end - - state[:decisionMaker] = responsedict - + + # println("\nSQLLLM decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + # pprintln(responsedict) + # println("---") + return responsedict - end - error("DecisionMaker failed to generate a thought \n", response) + error("SQLLLM DecisionMaker() failed to generate a thought \n", response) end + """ Assigns a scalar value to each new child node to be used for selec- tion and backpropagation. This value effectively quantifies the agent's progress in task completion, serving as a heuristic to steer the search algorithm towards the most promising regions of the tree. @@ -359,156 +223,164 @@ julia> # Signature """ -function evaluator(state::T1, text2textInstructLLM::Function +function evaluator(state::T1, text2textInstructLLM::Function, llmFormatName::String; + maxattempt=10 ) where {T1<:AbstractDict} - systemmsg = + systemmsg = """ - You are a helpful assistant that analyzes agent's trajectory to find solutions and observations (i.e., the results of actions) to answer the user's questions. - - Definitions: - "question" is the user's question - "understanding" is agent's understanding about the current situation - "reasoning" is agent's step-by-step reasoning about the current situation - "plan" is agent's plan to complete the task from the current situation - "action_name" is the name of the action taken, which can be one of the following functions: - - GETDATA, which you can use to get the data from the database. Action_input for this function must be a single SQL query to be executed against the database. - For more effective text search, it's necessary to use case-insensitivity and the ILIKE operator. - Do not wrap the SQL as it will be executed against the database directly and SQL must be ended with ';'. - "action_input" is the input to the action - "observation" is result of the preceding immediate action - - - Trajectory: ... - Error_note: error note from your previous attempt - - - - - When the search returns no result, validate whether the SQL query makes sense before accepting it as a valid answer. - - - - 1) Trajectory_evaluation: Analyze the trajectory of a solution to answer the user's original question. - - Evaluate the correctness of each section and the overall trajectory based on the given question. - - Provide detailed reasoning and analysis, focusing on the latest thought, action, and observation. - - Incomplete trajectory are acceptable if the thoughts and actions up to that point are correct, even if the final answer isn't reached. - - Do not generate additional thoughts or actions. - 2) Answer_evaluation: - - Focus only on the matter mentioned in the question and comprehensively analyze how the latest observation's details addresses the question - 3) Accepted_as_answer: Decide whether the latest observation's content answers the question. Can be "Yes" or "No" - Bad example (The observation didn't answers the question): - question: Find cars with 4 wheels. - observation: There are an apple in the table. - Good example (The observation answers the question): - question: Find cars with a stereo. - observation: There are 1 cars in the table. 1) brand: Toyota, model: yaris, color: black. - 4) Score: Correctness score s where s is a single integer between 0 to 9. + + At each round of conversation, the user provides the following: + - customer question + - trajectory: A history of how an agent (you) worked on the question chronologically + + + Analyze and evaluate agent's trajectory to find solutions and the results of actions to answer the user's questions according to evaluation guidelines. + + + Fulfill the objective. + + + - When the search returns no result, it usually means 1) there is simply no data. or 2) SQL condition is not correct or 3) SQL is looking at the wrong tables. + - validate whether the SQL query makes sense before accepting it as a valid answer. + + + 1) Trajectory_evaluation: Analyze the trajectory of a solution to answer the user's original question. + - Evaluate the correctness of each section and the overall trajectory based on the given question. + - Provide detailed reasoning and analysis, focusing on the latest plan, action_name, action_input, and action_result. + - Incomplete trajectory are acceptable if the thoughts and actions up to that point are correct, even if the final answer isn't reached. + - Do not generate additional thoughts or actions. + 2) Answer_evaluation: + - Focus only on the matter mentioned in the question and comprehensively analyze how the latest action_input is appropriate. + 3) Accepted_as_answer: Decide whether the latest action_input is technically correct. Can be "yes" or "no" + Bad example: + question: Find cars with 4 wheels. + action_input: INSERT INTO employees + VALUES (5, 'Charlie', 'Green', '2026-06-01', 60000.00);. + Good example: + question: Find cars with a sunroof. + action_input: SELECT * FROM car_features + WHERE has_sunroof = TRUE; + 4) Score: Correctness score s where s is a single integer between 0 to 9. For example: - - 0 indicates that both the trajectory is incorrect, failed or errors and the observation is incorrect or failed - - 4 indicates that the trajectory are correct but the observation is incorrect or failed - - 5 indicates that the trajectory are correct, but no results are returned. - - 6 indicates that the trajectory are correct, but the observation's content doesn't directly answer the question - - 8 indicates that both the trajectory are correct, and the observation's content directly answers the question. - - 9 indicates a perfect perfomance. Both the trajectory are correct, and the observation's content directly answers the question, surpassing your expectations. - 5) Suggestion: if accepted_as_answer is "No", provide suggestion. - - - - Trajectory_evaluation: ... - Answer_evaluation: ... - Accepted_as_answer: ... - Score: ... - Suggestion: ... - - - Let's begin! + - 0 indicates that both the trajectory is incorrect, failed or errors and the action_result is incorrect or failed + - 4 indicates that the trajectory are correct, but no results are returned. + - 5 indicates that the trajectory are correct but the action_result is incorrect or failed + - 6 indicates that the trajectory are correct, but the action_result's content doesn't directly answer the question + - 8 indicates that both the trajectory are correct, and the action_result's content directly answers the question. + - 9 indicates a perfect perfomance. Both the trajectory are correct, and the action_result's content directly answers the question, surpassing your expectations. + 5) Suggestion: what are the possible reason of this outcome, what can one learn from it and what suggestion can made? + + + "trajectory_evaluation": "...", + "answer_evaluation": "...", + "accepted_as_answer": "...", + "score": "...", + "suggestion": "..." + + """ + requiredKeys = ["trajectory_evaluation", "answer_evaluation", "accepted_as_answer", "score", "suggestion"] + errornote = "" + usermsg = + """ + + $(state["context"]["table_schema"]) + + + $(state["chathistory"][2]["content"][1]["text"]) + + + $(GeneralUtils.dict_to_string_html(state["action_history"])) + """ - thoughthistory = "" - for (k, v) in state[:thoughtHistory] - thoughthistory *= "$k: $v\n" - end + msg = Dict( + "model" => "gemma-4-E4B-it-UD-Q4_K_XL", + "messages" => [ + Dict( + "role" => "system", + "content" => [ + Dict("type" => "text", "text" => systemmsg), + ] + ), + Dict( + "role" => "user", + "content" => [ + Dict("type" => "text", "text" => usermsg), + ] + ), + ], + "temperature" => 0.7 + ) + + for attempt in 1:maxattempt + response = text2textInstructLLM("random_id", msg) + response = GeneralUtils.clean_json_response(response) - errornote = "" - - for attempt in 1:10 - errorFlag = false - - usermsg = - """ - Trajectory: $thoughthistory - Error_note: $errornote - """ - - _prompt = - [ - Dict(:name=> "system", :text=> systemmsg), - Dict(:name=> "user", :text=> usermsg) - ] - - # put in model format - prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen") - - header = ["Trajectory_evaluation:", "Answer_evaluation:", "Accepted_as_answer:", "Score:", "Suggestion:"] - dictkey = ["trajectory_evaluation", "answer_evaluation", "accepted_as_answer", "score", "suggestion"] - - response = text2textInstructLLM(prompt) - - # sometime LLM output something like **Comprehension**: which is not expected - response = replace(response, "**"=>"") - response = replace(response, "***"=>"") - - # make sure every header is in the response - for i in header - detected = GeneralUtils.detect_keyword(i, response) - if detected === nothing - errornote = "Your previous response didn't provide $i" - errorFlag = true - end - end - if errorFlag - continue # skip to the next iteration - end - - responsedict = GeneralUtils.textToDict(response, header; - dictKey=dictkey, symbolkey=true) - - responsedict[:score] = responsedict[:score][1] # some time "6\nThe trajectories are incomplete" is generated but I only need the number. + response = GeneralUtils.remove_french_accents(response) + think, response = GeneralUtils.extractthink(response) + response = String(split(response, ", action_result")[1]) # in case LLM generate action_result key which it isn't supposed to + response = strip(response) + responsedict = nothing try - responsedict[:score] = parse(Int, responsedict[:score]) # convert string "5" into integer 5 + _responsedict = JSON.parse(response) + responsedict = GeneralUtils.dictify(_responsedict, keytype=String, sort_order=requiredKeys) catch + println("\nERROR SQLLLM evaluator() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())") continue end - accepted_as_answer::AbstractString = responsedict[:accepted_as_answer] + # check whether all answer's key points are in responsedict + ispass, errormsg = GeneralUtils.checkAgentResponse_JSON(responsedict, requiredKeys) + if !ispass + errornote = errormsg + println("\nERROR SQLLLM evaluator() $errornote --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n") + continue + end + + responsedict["score"] = responsedict["score"][1] # some time "6\nThe trajectories are incomplete" is generated but I only need the number. + try + responsedict["score"] = parse(Int, responsedict["score"]) # convert string "5" into integer 5 + catch + errornote = "Your previous attempt's score has wrong format" + println("\nERROR SQLLLM evaluator() Attempt $attempt/$maxattempt. $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + continue + end - if accepted_as_answer ∉ ["Yes", "No"] # [PENDING] add errornote into the prompt - error("generated accepted_as_answer has wrong format") + accepted_as_answer::AbstractString = responsedict["accepted_as_answer"] + + if accepted_as_answer ∉ ["Yes", "yes", "No", "no"] + errornote = "Your previous attempt's accepted_as_answer has wrong format" + println("\nERROR SQLLLM evaluator() Attempt $attempt/$maxattempt. $errornote --(not qualify response)--> $(responsedict["accepted_as_answer"]) ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + continue end # add to state here instead to in transition() because the latter causes julia extension crash (a bug in julia extension) - state[:evaluation] = "$(responsedict[:trajectory_evaluation]) $(responsedict[:answer_evaluation])" - state[:evaluationscore] = responsedict[:score] - state[:accepted_as_answer] = responsedict[:accepted_as_answer] - state[:suggestion] = responsedict[:suggestion] + state["evaluation"] = "$(responsedict["trajectory_evaluation"]) $(responsedict["answer_evaluation"])" + state["evaluationscore"] = responsedict["score"] + state["accepted_as_answer"] = responsedict["accepted_as_answer"] + state["suggestion"] = responsedict["suggestion"] # mark as terminal state when the answer is achieved - if accepted_as_answer == "Yes" + if accepted_as_answer ∈ ["Yes", "yes"] # mark the state as terminal state because the evaluation say so. - state[:isterminal] = true + state["isterminal"] = true # evaluation score as reward because different answers hold different value for the user. - state[:reward] = responsedict[:score] + state["reward"] = responsedict["score"] end - println("\n~~~ Evaluator() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") - pprintln(Dict(responsedict)) - return responsedict[:score] + println("\n--- SQLLLM evaluator() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + pprintln(responsedict) + println("---\n") + + return responsedict["score"] end error("Evaluator failed to generate an evaluation, Response: \n$response\n<|End of error|>") end + """ # Arguments @@ -594,7 +466,7 @@ function reflector(config::T1, state::T2)::String where {T1<:AbstractDict, T2<:A usermsg = """ - $(JSON3.write(state[:thoughtHistory])) + $(JSON.json(state[:action_history])) """ _prompt = @@ -604,7 +476,7 @@ function reflector(config::T1, state::T2)::String where {T1<:AbstractDict, T2<:A ] # put in model format - prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen") + prompt = GeneralUtils.formatLLMtext(_prompt, "granite3") externalService = config[:externalservice][:text2textinstruct] # apply LLM specific instruct format @@ -645,7 +517,7 @@ function reflector(config::T1, state::T2)::String where {T1<:AbstractDict, T2<:A error("Not valid JSON") end - reflectionDict = copy(JSON3.read(responseJsonStr)) + reflectionDict = copy(JSON.parse(responseJsonStr)) # check if dict has all required value dummya::AbstractString = reflectionDict[:reflection] @@ -680,67 +552,61 @@ end ```jldoctest julia> using SQLLLM, DataStructures julia> state = Dict( - :isterminal => false, - :lesson => nothing, - :reward => 0, - :evaluation => "None", - :accepted_as_answer => "No", - :thoughtHistory => OrderedDict{Symbol, Any}(:question => "How many wines do you have that can be paired with lamb?"), - :evaluationscore => 0, - :suggestion => "None" + "isterminal" => false, + "lesson" => nothing, + "reward" => 0, + "evaluation" => "None", + "accepted_as_answer" => "No", + "action_history" => OrderedDict{String, Any}("question" => "How many wines do you have that can be paired with lamb?"), + "evaluationscore" => 0, + "suggestion" => "None" ) ``` - -# TODO - - [] add embedding of newstate and store in newstate[:embedding] - - [PENDING] should getdata() return isterminal? -# Signature -""" + - add embedding of newstate and store in newstate[:embedding] + - should getdata() return isterminal? +""" function transition(state::T, args::NamedTuple )::NamedTuple{(:newNodeKey, :newstate, :progressvalue), Tuple{String, T, Integer}} where {T<:AbstractDict} decisionMakerF::Function = args[:decisionMaker] evaluatorF::Function = args[:evaluator] - reflector::Function = args[:reflector] - context = args[:context] + # reflector::Function = args[:reflector] executeSQL::Function = args[:executeSQL] text2textInstructLLM::Function = args[:text2textInstructLLM] - insertSQLVectorDB::Function = args[:insertSQLVectorDB] + # insertSQLVectorDB::Function = args[:insertSQLVectorDB] querySQLVectorDBF::Function = args[:querySQLVectorDB] + llmFormatName::String = args[:llmFormatName] # getting SQL from vectorDB - thoughtDict = decisionMakerF(state, context, text2textInstructLLM; querySQLVectorDBF) - + thoughtDict = decisionMakerF(state, text2textInstructLLM, llmFormatName; + querySQLVectorDBF) + # println("\n--- SQLLLM transition() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + # pprintln(thoughtDict) + # println("---") # map action and input() to llm function - response = - if thoughtDict[:action_name] == "listalltables" - # deepcopy(state[:virtualCustomerChatHistory]) because I want to keep it clean - # so that other simulation start from this same node is not contaminated with actioninput - listAllTable_json(executeSQL) - elseif thoughtDict[:action_name] == "TABLEINFO" - input = thoughtDict[:action_input] - tableinfo(executeSQL, input) - elseif thoughtDict[:action_name] == "GETDATA" - response = SQLexecution(executeSQL, thoughtDict[:action_input]) - if response[:success] - extracted = extractContent_dataframe(response[:result], text2textInstructLLM, thoughtDict[:action_input]) - (rawresponse=response[:result], result=extracted, errormsg=nothing, success=true) - else - (result=nothing, errormsg=response[:errormsg], success=false) - end - else - error("undefined LLM function. Requesting $(thoughtDict[:action_name])") - end - # this section allow LLM functions above to have different return values. - success::Bool = haskey(response, :success) ? response[:success] : false - result = success ? response[:result] : response[:errormsg] - rawresponse = haskey(response, :rawresponse) ? response[:rawresponse] : nothing - select = haskey(response, :select) ? response[:select] : nothing - reward::Integer = haskey(response, :reward) ? response[:reward] : 0 - isterminal::Bool = haskey(response, :isterminal) ? response[:isterminal] : false - newNodeKey, newstate = makeNewState(state, thoughtDict, rawresponse, JSON3.write(result), select, reward, isterminal) - progressvalue::Integer = evaluatorF(newstate, text2textInstructLLM) + response = nothing + if thoughtDict["action_name"] == "RUNSQL" + response = SQLexecution(executeSQL, thoughtDict["action_input"]) + # println("\n--- SQLLLM transition() response ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + # println(response) + # println("---") + else + error("undefined LLM function. Requesting $(thoughtDict["action_name"])") + end + + newNodeKey, newstate = makeNewState(state, thoughtDict, response) + progressvalue::Integer = + if response[:success] + 8 # for faster agent response. if success just skip evaluation + else + evaluatorF(newstate, text2textInstructLLM, llmFormatName) + end + + println("\n--- SQLLLM transition() thoughtDict ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + pprintln(thoughtDict) + println("---") + # error("SQLLLM transition() end") return (newNodeKey=newNodeKey, newstate=newstate, progressvalue=progressvalue) end @@ -749,15 +615,22 @@ end # Arguments - `query<:AbstractString` - a query + A natural language query in English - `executeSQL::Function` - a connection object to a database + A function that executes SQL queries against the database - `text2textInstructLLM::Function` - A function that handles communication to text2text instruct LLM service. - -# Return - - `resulttext::String` - The result of the query in English. + A function that handles communication with a text-to-text instruction-based language model + +# Keyword Arguments + - `insertSQLVectorDB::Union{Function, Nothing}=nothing` + Optional function to insert SQL queries into a vector database for future reference + - `similarSQLVectorDB::Union{Function, Nothing}=nothing` + Optional function to find similar SQL queries from a vector database + +# Returns + - `NamedTuple{(:text, :rawresponse), Tuple{Any, Any}}` + - `:text`: The query result in natural language + - `:rawresponse`: The raw database response # Example ```jldoctest @@ -820,83 +693,292 @@ julia> println(result) ``` # Signature -""" +""" function query(query::T, executeSQL::Function, text2textInstructLLM::Function; insertSQLVectorDB::Union{Function, Nothing}=nothing, similarSQLVectorDB::Union{Function, Nothing}=nothing, + llmFormatName="qwen3" ) where {T<:AbstractString} # use similarSQLVectorDB to find similar SQL for the query sql, distance = similarSQLVectorDB(query) + + # if sql is really match, immediately check database then return if sql !== nothing && distance <= 1 # query vector db to get wine response = SQLexecution(executeSQL, sql) if response[:success] - # intention = Dict(:intention=> "$(thoughtDict[:plan])") - extracted = extractContent_dataframe(response[:result], text2textInstructLLM, sql) - return (text=extracted, rawresponse=response[:result]) - end + return (result_str=response[:result_str], result_raw=response[:result_raw]) + else + error(response[:errormsg]) + end end + """ + chathistory= [ + Dict( + "role" => "system", + "content" => [ + Dict("type" => "text", "text" => "You are a helpful assistant"), + ] + ), + ] + """ + + systemmsg = + """ + + - RUNSQL, which you can use to execute SQL against the database. + action_input for this function must be a single SQL query to be executed against the database. + For more effective text search, it's necessary to use case-insensitivity and the ILIKE operator. + Do not wrap the SQL as it will be executed against the database directly and SQL must be ended with ';'. + + + At each round of conversation, you will be given the following: + - user question + You are working under your mentor supervision and you are also eager to improve your helpfulness. + + + Consult the database search guidelines. Then find the data from a database to satisfy the user's question. + + + Fulfill the objective. + + + - Keep SQL queries focused only on the provided information. + - Do not create any table in the database + - A junction table can be used to link tables together. Another use case is for filtering data. + - If you can't find a single table that can be used to answer the user's query, try joining multiple tables to see if you can obtain the answer. + - Text information in the database usually stored in lower case. If your search returns empty, try using lower case to search. + - If there is no search result from the database, remove the restrictive criteria until a search result is available, and proceed from there. + + + 1) plan: Based on the current situation, state a complete action plan to complete the task. Be specific. + 2) action_name: (Typically corresponds to the execution of the first step in your plan) Can be one of the available_actions name + 3) action_input: The input to the action you are about to perform according to your plan. + After the action is executed you gets "action_result". It is the output from the action you selected. + + + "plan": "...", + "action_name": "...", + "action_input": "..." + + """ + + + # do MCTS if no data in the database # add extra context for Evaluator so that it knows the observation is from seaching a database - initialstate = Dict{Symbol, Any}( - :reward=> 0, - :isterminal=> false, - :evaluation=> "None", - :evaluationscore=> 0, - :suggestion=> "None", - :accepted_as_answer=> "No", - :lesson=> nothing, - - # contain question, thought_1, action_1, observation_1, thought_2, ... - :thoughtHistory=> OrderedDict{Symbol, Any}( - #[] :recap=>, - :question=> query, - ), + initialstate = Dict{String, Any}( + "reward"=> 0, + "isterminal"=> false, + "evaluation"=> "None", + "evaluationscore"=> 0, + "suggestion"=> "None", + "accepted_as_answer"=> "No", + "chathistory"=> Vector{Dict{String, Any}}(), # store system, user and assistant msg + "question"=> query, + "context"=> Dict{String, Any}(), + "action_history"=> OrderedDict{String, Any}( + # "1"=> Dict("plan"=> "...", "action_name"=> "...", "action_input"=> "...", "action_result"=> "..."), + # "2"=> Dict("plan"=> "...", "action_name"=> "...", "action_input"=> "...", "action_result"=> "..."), + # ... + ), ) - context = Dict( - :tablelist => listAllTable_str(executeSQL)[:result] - ) + + systemmsg_dict = Dict( + "role" => "system", + "content" => [ + Dict("type" => "text", "text" => systemmsg), + ] + ) + usermsg = Dict( + "role" => "user", + "content" => [ + Dict("type" => "text", "text" => query), + ] + ) + push!(initialstate["chathistory"], systemmsg_dict) + push!(initialstate["chathistory"], usermsg) + + #XXX find a way to recreate the schema from a existing database + table_schema = + """ + create table customer ( + customer_id uuid primary key default gen_random_uuid (), + customer_firstname varchar(128), + customer_lastname varchar(128), + customer_displayname varchar(128) not null, + customer_username varchar(128), + customer_password varchar(128), + customer_gender varchar(128), + country varchar(128), + telephone varchar(128), + email varchar(128) not null, + customer_birthdate varchar(128), + note text, + + other_attributes jsonb, + created_time timestamptz default current_timestamp, + updated_time timestamptz default current_timestamp, + description text + ); + + create table retailer ( + retailer_id uuid primary key default gen_random_uuid (), + retailer_name varchar(128) not null, + retailer_username varchar(128) not null, + retailer_password varchar(128) not null, + retailer_address text not null, + country varchar(128) not null, + contact_person varchar(128) not null, + telephone varchar(128) not null, + email varchar(128) not null, + note text, + + other_attributes jsonb, + created_time timestamptz default current_timestamp, + updated_time timestamptz default current_timestamp, + description text + ); + + create table food ( + food_id uuid primary key default gen_random_uuid (), + food_name varchar(128) not null, + country varchar(128), + spiciness integer, + sweetness integer, + sourness integer, + savoriness integer, + bitterness integer, + serving_temperature integer, + image_url jsonb, + note text, + other_attributes jsonb, + + created_time timestamptz default current_timestamp, + updated_time timestamptz default current_timestamp, + description text + ); + + create table wine ( + wine_id uuid primary key default gen_random_uuid (), + seo_name varchar(128) not null, + wine_name varchar(128) not null, + winery varchar(128) not null, + vintage integer not null, + region varchar(128) not null, + country varchar(128) not null, + wine_type varchar(128) not null, + grape varchar(128) not null, + serving_temperature varchar(128) not null, + intensity integer, + sweetness integer, + tannin integer, + acidity integer, + fizziness integer, + tasting_notes text, + image_url jsonb, + manufacturer_sku text, + note text, + other_attributes jsonb, + + created_time timestamptz default current_timestamp, + updated_time timestamptz default current_timestamp, + description text + ); + + create table wine_food ( + wine_id uuid references wine(wine_id), + food_id uuid references food(food_id), + constraint wine_food_id primary key (wine_id, food_id), + + created_time timestamptz default current_timestamp, + updated_time timestamptz default current_timestamp + ); + + CREATE TABLE retailer_wine ( + retailer_id uuid references retailer(retailer_id), + wine_id uuid references wine(wine_id), + constraint retailer_wine_id primary key (retailer_id, wine_id), + price NUMERIC(10, 2), + currency varchar(3) not null, + + created_time timestamptz default current_timestamp, + updated_time timestamptz default current_timestamp + ); + + CREATE TABLE retailer_food ( + retailer_id uuid references retailer(retailer_id), + food_id uuid references food(food_id), + constraint retailer_food_id primary key (retailer_id, food_id), + price NUMERIC(10, 2), + currency varchar(3) not null, + + created_time timestamptz default current_timestamp, + updated_time timestamptz default current_timestamp + ); + """ + + # println("\n--- SQLLLM query() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + # println("---") + # error("SQLLLM query() end") + + initialstate["context"]["table_schema"] = table_schema + transitionargs = ( - decisionMaker=decisionMaker, - evaluator=evaluator, - reflector=reflector, - context=context, - executeSQL=executeSQL, - text2textInstructLLM=text2textInstructLLM, - querySQLVectorDB=similarSQLVectorDB, - insertSQLVectorDB=insertSQLVectorDB, + executeSQL=executeSQL, + decisionMaker=decisionMaker, + evaluator=evaluator, + reflector=reflector, + text2textInstructLLM=text2textInstructLLM, + querySQLVectorDB=similarSQLVectorDB, + insertSQLVectorDB=insertSQLVectorDB, + llmFormatName=llmFormatName ) - earlystop(state) = state[:reward] >= 8 ? true : false - - _, _, resultState = LLMMCTS.runMCTS(initialstate, transition, transitionargs; - horizontalSampleExpansionPhase=5, - horizontalSampleSimulationPhase=2, - maxSimulationDepth=5, - maxiterations=1, - explorationweight=1.0, - earlystop=earlystop, - saveSimulatedNode=true, - multithread=true) - latestKey, latestInd = GeneralUtils.findHighestIndexKey(resultState[:thoughtHistory], "observation") - action_input = Symbol("action_input_$latestInd") # latest sql - sql = resultState[:thoughtHistory][action_input] - extracted = resultState[:thoughtHistory][latestKey] - - # add to vectorDB only if the answer is achieved and the state is terminal - if insertSQLVectorDB !== nothing && resultState[:isterminal] == true && - resultState[:rawresponse] !== nothing - - insertSQLVectorDB(resultState[:thoughtHistory][:question], sql) + earlystop(state) = state["reward"] >= 8 ? true : false + + root, _, resultState, highValueState = + LLMMCTS.runMCTS(initialstate, transition, transitionargs; + horizontalSampleExpansionPhase=1, + horizontalSampleSimulationPhase=1, + maxSimulationDepth=1, + maxiterations=1, + explorationweight=1.0, + earlystop=earlystop, + saveSimulatedNode=true, + multithread=false) + + # error("SQLLLM query() end") + + # compare all high value state answer then select the best one + if length(highValueState) > 1 + selected = compareState(query, highValueState, text2textInstructLLM, llmFormatName) + resultState = highValueState[selected] end - if extracted === nothing - println("query() return nothing") + max_ind = + if length(resultState["action_history"]) == 0 + 0 + else + k = keys(resultState["action_history"]) + maximum(parse.(Int, k)) + end + latest_action = resultState["action_history"]["$max_ind"] + + #CHANGE add to vectorDB only if the answer is achieved and the state is terminal + sql = latest_action["action_input"] + if insertSQLVectorDB !== nothing && resultState["isterminal"] == true && + resultState["accepted_as_answer"] == "yes" + insertSQLVectorDB(resultState["question"], sql) end - return (text=extracted, rawresponse=resultState[:rawresponse]) + println("\n--- SQLLLM query() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + println(resultState["result_raw"]) + println("---\n") + + return (result_str=latest_action["action_result"], result_raw=resultState["result_raw"]) end @@ -913,46 +995,37 @@ julia> # Signature """ -function makeNewState(currentstate::T1, thoughtDict::T4, rawresponse, response::T2, select::Union{T3, Nothing}, - reward::T3, isterminal::Bool - )::NamedTuple{(:newNodeKey, :newstate), Tuple{String, Dict{Symbol, <:Any}}} where {T1<:AbstractDict, T2<:AbstractString, T3<:Number, T4<:AbstractDict} - - keys = [:comprehension, :action_name, :action_input, :observation] - # latestKeys = [] +function makeNewState(currentstate::T1, thoughtDict::T2, response::NamedTuple, + )::NamedTuple{(:newNodeKey, :newstate), Tuple{String, Dict{String, <:Any}}} where {T1<:AbstractDict, T2<:AbstractDict} - currentstate_latestKey, currentstate_latestIndice = - GeneralUtils.findHighestIndexKey(currentstate[:thoughtHistory], keys[1]) - nextindice = currentstate_latestKey !== nothing ? currentstate_latestIndice + 1 : 1 - # currentstate_latestKey == :NA ? 1 : currentstate_latestIndice + 1 - - currentstate_latestKey = makeNextKey.(keys, nextindice) - - # add Thought, action, observation to thoughtHistory - newstate = deepcopy(currentstate) - for (x, y) in zip(keys, currentstate_latestKey) - if x != :observation - newstate[:thoughtHistory][y] = thoughtDict[Symbol(x)] - else - newstate[:thoughtHistory][y] = response - end + if response[:success] + thoughtDict["action_result"] = response[:result_str] + else + error(response[:errormsg]) end - newstate[:reward] = reward - newstate[:select] = select - newstate[:isterminal] = isterminal - newstate[:rawresponse] = rawresponse # whatever return from action + newstate = deepcopy(currentstate) + max_ind = + if length(newstate["action_history"]) == 0 + 0 + else + k = keys(newstate["action_history"]) + maximum(parse.(Int, k)) + end + newstate["action_history"]["$(max_ind + 1)"] = thoughtDict + newstate["reward"] = haskey(response, :reward) ? response[:reward] : 0 + newstate["select"] = haskey(response, :select) ? response[:select] : nothing + newstate["isterminal"] = haskey(response, :isterminal) ? response[:isterminal] : false + newstate["result_raw"] = response[:result_raw] # whatever return from action newNodeKey = GeneralUtils.uuid4snakecase() return (newNodeKey=newNodeKey, newstate=newstate) end - -makeNextKey(key, indice) = Symbol("$(key)_$indice") - - -function generatequestion(state::T1, context, text2textInstructLLM::Function; - similarSQL::Union{T2, Nothing}=nothing +function generatequestion(state::T1, context, text2textInstructLLM::Function, + llmFormatName::String; + similarSQL::Union{T2, Nothing}=nothing, maxattempt=10, )::String where {T1<:AbstractDict, T2<:AbstractString} similarSQL = @@ -983,53 +1056,57 @@ function generatequestion(state::T1, context, text2textInstructLLM::Function; 4) Do not generate any question or comments at the end. You should follow the following guidelines: - - When querying data in the database, start with broad search terms and refine your query later for more precise results. + - If there is no search result from the database, remove the restrictive criteria until a search result is available, and proceed from there. You should then respond to the user with: - 1) Understanding: - - State your understanding about the current situation. - 2) Q: Given the situation, "ask yourself" about the situation at least five, but no more than ten, questions. - 3) A: Given the situation, "answer to yourself" the best you can. + 1) Q: Given the situation, "ask yourself" about the situation at least three, but no more than five, questions. + 2) A: Given the situation, "answer to yourself" the best you can. - Do not generate any text after the last answer. You must only respond in format as described below: - Understanding: ... Q1: ... A1: ... Q2: ... A2: ... - Q3: ... - A3: ... ... Here are some examples: Q: What information in the hints is not necessary based on the query? A: Country is not specified in the query thus it should not be included in an SQL - Q: How can I modify a SQL example to fit my specific query needs? A: ... + Q: Why the query failed? + A: ... + Q: What criteria become more restrictive as the search scope broadens and can be remove? + A: In the "2019 Toyota Camry hybrid" search query, "2019" represents the most restrictive criteria because it narrows the data scope to a specific year, whereas "Toyota" and "Camry" are broader categories that allow for more general results. + Q: What works and what not previously? + A: ... Let's begin! """ + + header = ["Q1:"] + dictkey = ["q1"] workprogress = "" - for (k, v) in state[:thoughtHistory] - if k ∉ [:query] + for (k, v) in state["action_history"] + if k ∉ ["query"] workprogress *= "$k: $v\n" end end response = nothing # store for show when error msg show up - errornote = "" + errornote = "N/A" - for attempt in 1:10 + for attempt in 1:maxattempt usermsg = """ - $(context[:tablelist]) - User query: $(state[:thoughtHistory][:question]) + $(context["tablelist"]) + User query: $(state["action_history"]["question"]) Example: $similarSQL Your work progress: $workprogress - $errornote + P.S. $errornote + /no_think """ _prompt = @@ -1039,37 +1116,29 @@ function generatequestion(state::T1, context, text2textInstructLLM::Function; ] # put in model format - prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen") + prompt = GeneralUtils.formatLLMtext(_prompt, llmFormatName) - try - response = text2textInstructLLM(prompt) - - # check if response is valid - q_number = count("Q", response) - if q_number < 1 - errornote = "too few question" - error("too few questions only $q_number questions are generated") - end - if occursin('`', response) - response = replace(response, '`'=>"") - end - - header = ["Understanding:", "Q1:"] - dictkey = ["understanding", "q1"] - - responsedict = GeneralUtils.textToDict(response, header; - dictKey=dictkey, symbolkey=true) - response = "Q1: " * responsedict[:q1] - println("\n~~~ SQLLLM generatequestion() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") - pprintln(Dict(responsedict)) - return response - catch e - io = IOBuffer() - showerror(io, e) - errorMsg = String(take!(io)) - st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) - println("\n~~~ SQLLLM generatequestion() Attempt $attempt. Error occurred: $errorMsg\n$st ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + response = text2textInstructLLM(prompt, modelsize="medium") + response = GeneralUtils.deFormatLLMtext(response, llmFormatName) + think, response = GeneralUtils.extractthink(response) + + # check if response is valid + q_number = count("Q", response) + if q_number < 1 + errornote = "Your previous attempt has too few question." + println("\nERROR YiemAgent generatequestion(). Attempt $attempt/$maxattempt. $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + continue end + if occursin('`', response) + response = replace(response, '`'=>"") + end + + responsedict = GeneralUtils.textToDict(response, header; + dictKey=dictkey, symbolkey=false) + response = "Q1: " * responsedict["q1"] + println("\nSQLLLM generatequestion() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + pprintln(Dict(responsedict)) + return response end error("generatequestion failed to generate a thought ", response) end @@ -1119,8 +1188,6 @@ end - - diff --git a/src/llmfunction.jl b/src/llmfunction.jl index 7688c86..0cf8f60 100644 --- a/src/llmfunction.jl +++ b/src/llmfunction.jl @@ -1,10 +1,10 @@ module llmfunction export listAllTable_json, listAllTable_str, tableinfo, getdata, finalAnswerBox, - getTableNameFromSQL, extractContent_dataframe, SQLexecution + getTableNameFromSQL, extractContent_dataframe, SQLexecution, compareState -using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs, LibPQ, Tables, DataFrames, CSV, - DataStructures, StatsBase +using HTTP, JSON, URIs, Random, PrettyPrinting, UUIDs, LibPQ, Tables, DataFrames, CSV, + DataStructures, StatsBase, Dates using GeneralUtils, LLMMCTS using ..util @@ -36,7 +36,7 @@ julia> result = response[:result] # Signature """ function listAllTable_json(executeSQL::Function -)::NamedTuple{(:result, :success),Tuple{DataFrame,Bool}} + )::NamedTuple{(:result, :success),Tuple{DataFrame,Bool}} sql = """ SELECT @@ -189,7 +189,7 @@ function tableinfo(executeSQL::Function, tablenames::T end end if !isempty(notExistingTable) - result = "Error, the following tables does not exist in the database: $(JSON3.write(notExistingTable))" + result = "Error, the following tables does not exist in the database: $(JSON.json(notExistingTable))" return (result=result,) end @@ -287,17 +287,17 @@ function getdata_transition(state::T, args::NamedTuple # decisionMaker::Function = args[:decisionMaker] # evaluator::Function = args[:evaluator] # reflector::Function = args[:reflector] - context = args[:context] - executeSQL::Function = args[:executeSQL] - text2textInstructLLM::Function = args[:text2textInstructLLM] +context = args["context"] + executeSQL::Function = args["executeSQL"] + text2textInstructLLM::Function = args["text2textInstructLLM"] - thought, sql = - if state[:code] !== nothing - result = getdata_decisionMaker(state, context, text2textInstructLLM) - result[:thought], result[:code] - else - nothing, state[:question] - end + thought, sql = + if state["code"] !== nothing + result = getdata_decisionMaker(state, context, text2textInstructLLM) + result["thought"], result["code"] + else + nothing, state["question"] + end # make new state newNodeKey = GeneralUtils.uuid4snakecase() @@ -314,15 +314,15 @@ function getdata_transition(state::T, args::NamedTuple isterminal=false) end println("getdata_transition() 1 ", @__FILE__, " ", @__LINE__) - newstate[:code] = sql - newstate[:response] = response - newstate[:errorexplain] = thought - newstate[:errormsg] = errormsg - newstate[:reward] = reward - newstate[:isterminal] = isterminal +newstate["code"] = sql + newstate["response"] = response + newstate["errorexplain"] = thought + newstate["errormsg"] = errormsg + newstate["reward"] = reward + newstate["isterminal"] = isterminal if response !== nothing extracted = extractContent_dataframe(response, context, text2textInstructLLM) - newstate[:response] = extracted + newstate["response"] = extracted end println("getdata_transition() 2 ", @__FILE__, " ", @__LINE__) stateevaluation = "None" @@ -347,8 +347,9 @@ end # Signature """ -function getdata_decisionMaker(state::Dict, context::Dict, text2textInstructLLM::Function -)::NamedTuple{(:thought, :code, :success, :errormsg),Tuple{Union{String,Nothing},Union{String,Nothing},Bool,Union{String,Nothing}}} +function getdata_decisionMaker(state::Dict, context::Dict, text2textInstructLLM::Function, + llmFormatName::String + )::NamedTuple{(:thought, :code, :success, :errormsg),Tuple{Union{String,Nothing},Union{String,Nothing},Bool,Union{String,Nothing}}} Hints = "None" @@ -366,17 +367,14 @@ function getdata_decisionMaker(state::Dict, context::Dict, text2textInstructLLM: - Text information in the database is sometimes stored in lower case. If your search returns empty, try using lower case to search. You should then respond to the user with: - 1) Comprehension: - - State your comprehension about the current situation. - 3) Plan: Step-by-step instructions of how to complete the task. + 1) Plan: Step-by-step instructions of how to complete the task. - Focus on improving the code from the last round. - Do not create any table in the database. - 4) Code: + 2) Code: - Write new improved code. - Do not wrap the code and no comment as it will be executed directly without any modification against the database. You should only respond in format as described below and nothing more: - Comprehension: ... Plan: 1) ... 2) ... @@ -391,10 +389,10 @@ function getdata_decisionMaker(state::Dict, context::Dict, text2textInstructLLM: for attempt in 1:10 usermsg = """ Context: - $(context[:mentionedTableInfo]) - User intention: $(context[:userintention]) - Code executed from the last round: $(state[:code]) - Execution error: $(state[:errormsg]) + $(context["mentionedTableInfo"]) + User intention: $(context["userintention"]) + Code executed from the last round: $(state["code"]) + Execution error: $(state["errormsg"]) $noise $note_flag """ @@ -406,21 +404,23 @@ function getdata_decisionMaker(state::Dict, context::Dict, text2textInstructLLM: ] # put in model format - prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen") + prompt = GeneralUtils.formatLLMtext(_prompt, llmFormatName) try - response = text2textInstructLLM(prompt) - - header = ["Comprehension:", "Plan:", "Code:"] - dictkey = ["comprehension", "plan", "code"] + response = text2textInstructLLM(prompt, modelsize="medium") + response = GeneralUtils.deFormatLLMtext(response, llmFormatName) + think, response = GeneralUtils.extractthink(response) + + header = ["Plan:", "Code:"] + dictkey = ["plan", "code"] responsedict = GeneralUtils.textToDict(response, header; - dictKey=dictkey, symbolkey=true) - _code = responsedict[:code] + dictKey=dictkey, symbolkey=false) + _code = responsedict["code"] code = strip(_code) if length(code) < 2 error("No code available.") - elseif code == state[:code] + elseif code == state["code"] error("generated code is the same as earlier.") else end @@ -440,7 +440,7 @@ function getdata_decisionMaker(state::Dict, context::Dict, text2textInstructLLM: println("\n~~~ getdata_decisionMaker() ", @__FILE__, " ", @__LINE__) pprintln(Dict(responsedict)) - return (thought=responsedict[:comprehension], code=code, success=true, errormsg=nothing) + return (thought=responsedict["comprehension"], code=code, success=true, errormsg=nothing) catch e io = IOBuffer() showerror(io, e) @@ -481,20 +481,9 @@ julia> response = SQLLLM.SQLexecution(executeSQL, sql) # Signature """ function SQLexecution(executeSQL::Function, sql::T -) where {T<:AbstractString} + )::NamedTuple where {T<:AbstractString} try - #XXX dummy SQL. use for testing - # sql = "SELECT w.wine_name FROM wine w JOIN wine_food wf ON w.wine_id = wf.wine_id JOIN food f ON wf.food_id = f.food_id WHERE f.\"food_name\" = 'lamb';" - # sql = " SELECT w.wine_name FROM wine w JOIN food f ON f.food_name = 'lamb' JOIN wine_food wf ON w.wine_id = wf.wine_id AND f.food_id = wf.food_id GROUP BY w.wine_name ORDER BY COUNT(DISTINCT w.wine_id) DESC;" - # sql = " SELECT COUNT(DISTINCT wf.wine_id) FROM wine w JOIN wine_food wf ON w.wine_id = wf.wine_id JOIN food f ON wf.food_id = f.food_id WHERE f.food_name ILIKE '%lamb%'" - - #XXX use for package testing, remove when done - # ans = "1.schilfwein zweigelt 2.cabernet sauvignon reserve limited edition" - # ans = "There are 1500 wines that can be paired with lamb." - # ans = "1500" - # return (response=ans, errormsg=nothing, reward=1, isterminal=true) - # add LIMIT to the SQL to prevent loading large data sql = strip(sql) @@ -508,39 +497,36 @@ function SQLexecution(executeSQL::Function, sql::T else sql = sql * ";" end - println("\n~~~ SQLexecution() SQL: ", @__FILE__, " ", @__LINE__) - println(sql) - result = executeSQL(sql) df = DataFrame(result) - tablesize = size(df) row, column = tablesize if row == 0 - error("The resulting table has 0 row. Possible causes: 1) Your search criteria might be too specific. Relaxing some conditions could yield better results. Remember, you can always refine your search later. 2) There could be a typo in your search query. 3) You might be searching in the wrong place.") + return (result_str="The resulting table has 0 row.", result_raw=df, success=true, errormsg=nothing) elseif column > 30 - error("SQL execution failed. An unexpected error occurred. Please try again.") + return (result_str="There are more than 30 columns. Please be more specific.", result_raw=df, success=true, errormsg=nothing) + else + df1 = + if row > 2 + # ramdom row to pick + df[sample(1:nrow(df), 2, replace=false), :] # random select 2 rows from df + else + df + end + result = GeneralUtils.dfToString(df1) + # println("\n~~~ SQLexecution() result: ", @__FILE__, " ", @__LINE__) + # println(sql) + # println(df1) + # println("\n") + return (result_str=result, result_raw=df1, success=true, errormsg=nothing) end - - df1 = - if row > 2 - # ramdom row to pick - df[sample(1:nrow(df), 2, replace=false), :] # random select 2 rows from df - else - df - end - - println("\n~~~ SQLexecution() result: ", @__FILE__, " ", @__LINE__) - println(df1) - return (result=df1, success=true, errormsg=nothing) catch e io = IOBuffer() showerror(io, e) errorMsg = String(take!(io)) st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) println(errorMsg) - response = (result=nothing, success=false, errormsg=errorMsg) - return response + return (result_str=nothing, result_raw=nothing, success=false, errormsg=errorMsg) end end @@ -559,9 +545,10 @@ end - `result::String` # Signature -""" -function extractContent_dataframe(df::DataFrame, text2textInstructLLM::Function, action::String -)::String +""" #WORKING +function extractContent_dataframe(df::DataFrame, text2textInstructLLM::Function, action::String, + llmFormatName::String + )::String tablesize = size(df) row = tablesize[1] column = tablesize[2] @@ -627,31 +614,34 @@ function extractContent_dataframe(df::DataFrame, text2textInstructLLM::Function, ] # put in model format - prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen") + prompt = GeneralUtils.formatLLMtext(_prompt, llmFormatName) header = ["About_resulting_table:", "Search_summary:"] dictkey = ["about_resulting_table", "search_summary"] for i in 1:5 - response = text2textInstructLLM(prompt) + response = text2textInstructLLM("ramdom_id", prompt) + response = GeneralUtils.deFormatLLMtext(response, llmFormatName) + think, response = GeneralUtils.extractthink(response) - kw = [] - # use for loop and detect_keyword function to get the exact variation of each keyword in the text then push to kw list - for keyword in header - detected = GeneralUtils.detect_keyword(keyword, response) - push!(kw, detected) - end - if nothing ∈ kw - println("Some keywords are missing, Required keywords=$header, Response keywords=$kw ", @__FILE__, ":", @__LINE__, " $(Dates.now())") - continue # try again next loop + # check whether response has all header + detected_kw = GeneralUtils.detectKeywordVariation(header, response) + missingkeys = [k for (k, v) in detected_kw if v === nothing] + if !isempty(missingkeys) + errornote = "$missingkeys are missing from your previous response" + println("\nERROR SQLLLM extractContent_dataframe() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + continue + elseif sum([length(i) for i in values(detected_kw)]) > length(header) + errornote = "\nYour previous attempt has duplicated points according to the required response format" + println("\nERROR SQLLLM extractContent_dataframe() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + continue end responsedict = GeneralUtils.textToDict(response, header; - dictKey=dictkey, symbolkey=true) + dictKey=dictkey, symbolkey=false) - # result = dfstr result = """ - Summary: $(responsedict[:search_summary]) + Summary: $(responsedict["search_summary"]) More details: $dfstr """ @@ -734,7 +724,9 @@ julia> result = SQLLLM.getTableNameFromSQL(sql, text2textInstructLLM) # Signature """ -function getTableNameFromSQL(sql::T, text2textInstructLLM::Function)::Vector{String} where {T<:AbstractString} +function getTableNameFromSQL(sql::T, text2textInstructLLM::Function, + llmFormatName::String + )::Vector{String} where {T<:AbstractString} systemmsg = """ Extract table name out of the user query. @@ -762,16 +754,17 @@ function getTableNameFromSQL(sql::T, text2textInstructLLM::Function)::Vector{Str ] # put in model format - prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen") + prompt = GeneralUtils.formatLLMtext(_prompt, llmFormatName) header = ["Table_name:"] dictkey = ["table_name"] for attempt in 1:5 try - response = text2textInstructLLM(prompt) + response = text2textInstructLLM(prompt, modelsize="medium") + response = GeneralUtils.deFormatLLMtext(response, llmFormatName) responsedict = GeneralUtils.textToDict(response, header; - dictKey=dictkey, symbolkey=true) - response = copy(JSON3.read(responsedict[:table_name])) + dictKey=dictkey, symbolkey=false) + response = copy(JSON.parse(responsedict["table_name"])) return response catch e @@ -788,6 +781,174 @@ function getTableNameFromSQL(sql::T, text2textInstructLLM::Function)::Vector{Str end +""" Compare multiple solution attempts and select the most accurate one. + +This function evaluates multiple solution attempts for a given question and determines which attempt +provides the most accurate and relevant response. It uses an LLM to analyze and compare the attempts, +considering their actions and observations. + +# Arguments +- `question::String` + The original question or task that was attempted to be solved +- `highValueStateList::Vector{Dict}` + List of states containing different solution attempts and their results +- `text2textInstructLLM::Function` + A function that handles communication to LLM service + +# Returns +- `Integer` + The index of the selected best response (1-based indexing) + +# Example +```jldoctest +julia> +``` + +# Notes +- The function makes up to 10 attempts to get a valid response from the LLM +- Each state in highValueStateList should contain a action_history with action_input and observation +- The LLM evaluates attempts based on accuracy and relevance to the original question +""" +function compareState(question::String, highValueStateList::Vector{T}, + text2textInstructLLM::Function, llmFormatName::String + )::Integer where {T<:AbstractDict} + + systemmsg = + """ + Your profile: + - You are a helpful assistant + Situation: + - The user has made multiple attempts to solve the question, resulting in various answers + Your mission: + - Identify and select the most accurate and relevant response from these multiple results for the user + At each round of conversation, you will be given the following: + Question: the question the user is trying to answer + Attempt: the user's attempted actions and their corresponding results + You should then respond to the user with the following: + Comparison: detailed comparison of all results from all attempts from various aspects. + Rationale: a brief explanation of why the selected response is the most accurate and relevant + Selected_response_number: the number the selected response in the list of results (e.g., 1, 2, 3, ...) + You should only respond in format as described below: + Comparison: ... + Rationale: ... + Selected_response_number: ... + Here are some examples: + User's question: "How many German wines do you have?" + Attempt 1) + Action: SELECT COUNT(*) FROM wines WHERE country = 'Germany' + Result: 100 wines + Attempt 2) + Action: SELECT COUNT(*) FROM wines WHERE country = 'Germany' AND type = 'Red' + Result: 50 red wines + Comparison: The second attempt counts only German red wines while the first attempt includes all German wines. + Rationale: The user is asking for the number of German wines without specifying a type, so the most accurate response is the first attempt because it includes all German wines. + Selected_response_number:1 + + Let's begin! + """ + +potentialSolution = [] + keys = ["action_input", "observation"] + # extract the last action_name, action_input, observation of each state in highValueStateList and store them in a dictionary then push into potentialSolution + for state in highValueStateList + action_history = state["action_history"] + _, currentstate_latestIndice = + GeneralUtils.findHighestIndexKey(action_history, keys[1]) + latestKeys = makekey.(keys, currentstate_latestIndice) + d = Dict() + # get the last action_name, action_input, observation of currentstate + for (i,v) in enumerate(keys) + d[v] = action_history[latestKeys[i]] + end + push!(potentialSolution, d) + end + + """ + # put potential solutions from potentialSolution into the following form + Attempt 1) + action_name: + action_input: + observation: + Attempt 2) + action_name:` + action_input: + observation:` + ... + """ + potentialSolutionStr = "" + for (i, state) in enumerate(potentialSolution) + potentialSolutionStr *= "Attempt $i)\n" + for k in keys + potentialSolutionStr *= "$k: $(state[k])\n" + println("") + end + end + + errornote = "N/A" + + for attempt in 1:10 + errorFlag = false + + usermsg = + """ + Question: $question + Attempts: $potentialSolutionStr + P.S. $errornote + """ + + _prompt = + [ + Dict(:name=> "system", :text=> systemmsg), + Dict(:name=> "user", :text=> usermsg) + ] + + # put in model format + prompt = GeneralUtils.formatLLMtext(_prompt, llmFormatName) + + header = ["Comparison:", "Rationale:", "Selected_response_number:"] + dictkey = ["comparison", "rationale", "selected_response_number"] + + response = text2textInstructLLM(prompt, modelsize="medium") + + # sometime LLM output something like **Comprehension**: which is not expected + response = replace(response, "**"=>"") + response = replace(response, "***"=>"") + response = GeneralUtils.deFormatLLMtext(response, llmFormatName) + think, response = GeneralUtils.extractthink(response) + + # check whether response has all header + detected_kw = GeneralUtils.detectKeywordVariation(header, response) + missingkeys = [k for (k, v) in detected_kw if v === nothing] + if !isempty(missingkeys) + errornote = "$missingkeys are missing from your previous response" + println("\nERROR SQLLLM extractContent_dataframe() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + continue + elseif sum([length(i) for i in values(detected_kw)]) > length(header) + errornote = "\nYour previous attempt has duplicated points according to the required response format" + println("\nERROR SQLLLM extractContent_dataframe() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + continue + end + + responsedict = GeneralUtils.textToDict(response, header; dictKey=dictkey, symbolkey=false) + +responsedict["selected_response_number"] = responsedict["selected_response_number"][1] # some time "6\nThe trajectories are incomplete" is generated but I only need the number. + try + responsedict["selected_response_number"] = parse(Int, responsedict["selected_response_number"]) # convert string "5" into integer 5 + catch + errornote = "In your previous attempt, Selected_response_number was not a number. It must be a number." + println("\nERROR SQLLLM compareState() Attempt $attempt. $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + continue + end + + println("\n~~~ compareState() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") + pprintln(Dict(responsedict)) + + return responsedict["selected_response_number"] + end + error("compareState() failed to generate an evaluation, Response: \n$response\n<|End of error|>", @__FILE__, ":", @__LINE__, " $(Dates.now())") +end + + @@ -824,8 +985,6 @@ end - - diff --git a/src/util.jl b/src/util.jl index a2bfb36..e8640f6 100644 --- a/src/util.jl +++ b/src/util.jl @@ -1,6 +1,8 @@ module util +export makekey +makekey(key, indice) = "$(key)_$indice" diff --git a/system_prompt_template.jl b/system_prompt_template.jl new file mode 100644 index 0000000..22f6d77 --- /dev/null +++ b/system_prompt_template.jl @@ -0,0 +1,160 @@ +""" +# -------------------------------- Default system message template ------------------------------- # + + + - You are a helpful assistant + + + - Describe the current situation + Ex. The world use enormous energy from non-sustainable sources. This leads to climate change. + + + - state your vision of how the situation will evolve, what would you want the situation to evolve into + Ex. To be the leading innovator in sustainable technology by 2030, transforming global energy systems. + + + - state the goal + Ex. Empowering communities through clean energy solutions to create a sustainable future. + + + - Specific, measurable, and time-bound goals that directly support the mission. + Ex. Launch 50 solar-powered water purification systems in 3 regions by 2025. + + + - state the mini goals that fall under your responsibility + + + - + + + - + + + - + + + - + + + Comprehension: State your comprehension about the current situation. + Plan: Given the current circumstances, outline a detailed, step-by-step plan to accomplish the task. Be specific. + Action_name: (Typically corresponds to the execution of the first step in your plan) Can be one of the following function names: + - CHATBOX which you can use to talk with the user. The input is your intentions for the dialogue. Be specific. + - CHECKRESOURCES which you can use to check resources + - IMPLEMENT which you can use to implement the solution + Action_input: Detail the input for the action. + + + Comprehension: ... + Plan: ... + Action_name: ... + Action_input: ... + + + + + +Let's begin! + + + + + + + + + +# ------------------------------------------- Example: ------------------------------------------- # + + +- You are a founder of a tech startup + + +- The global rise in bedridden patients, driven by an aging population, presents significant challenges for caregivers. Family members often become primary caretakers, leading to physical and emotional strain. This situation frequently forces caregivers to make difficult choices, including leaving their careers to provide full-time care, which impacts both family finances and personal well-being. + + + - We want to develop a system that can help people with bedridden patients and their families so that they could go on with their lives. + + + - To create an innovative caregiving support platform that reduces the physical and emotional burden on family caregivers while ensuring quality care for bedridden patients + + + - Develop smart monitoring systems for patient safety + - Create automated alert mechanisms for critical situations + - Design user-friendly interfaces for remote patient monitoring + - Implement AI-driven predictive care recommendations + - Build a support network connecting caregivers with healthcare professionals + - Establish training modules for family caregivers + + + - Lead product vision and strategy development + - Oversee technical implementation and system architecture + - Coordinate with healthcare experts for medical validation + - Ensure compliance with healthcare regulations + - Manage stakeholder relationships + - Drive fundraising and business development + + + Challenges: user's specific caregiving challenges + Context: context and severity of the situation + Feedback: comments from family caregivers + Solutions: potential solution based on immediate and long-term impact + + + - Always prioritize patient safety and well-being + - Maintain empathy and understanding in all interactions + - Focus on practical, implementable solutions + - Consider both immediate needs and long-term sustainability + - Respect privacy and confidentiality of all stakeholders + - Follow healthcare regulations and best practices + + + Comprehension: State your comprehension about the current situation. + Plan: Given the current circumstances, outline a detailed, step-by-step plan to accomplish the task. Be specific. + Action_name: (Typically corresponds to the execution of the first step in your plan) + Can be one of the following function names: + - CHATBOX which you can use to talk with the user. The input is your intentions for the dialogue. Be specific. + - CHECKRESOURCES which you can use to check resources + - IMPLEMENT which you can use to implement the solution + Action_input: Detail the input for the action. + + + Comprehension: ... + Plan: ... + Action_name: ... + Action_input: ... + + + Example 1: + Challenges: "My mother needs constant monitoring at night, but I'm exhausted from lack of sleep." + Context: Elderly patient with dementia, requires 24/7 supervision + Feedback: "Need urgent solution for night monitoring" + Solutions: Smart monitoring system with motion sensors and alerts + + Comprehension: The caregiver is experiencing severe sleep deprivation due to nighttime monitoring requirements + Plan: + 1. Assess current monitoring needs + 2. Propose smart monitoring system installation + 3. Set up emergency alert system + 4. Train family on system usage + Action_name: CHATBOX + Action_input: Discuss specific nighttime behaviors and incidents to determine optimal sensor placement and alert thresholds + + Example 2: + Challenges: "Managing medication schedules is becoming overwhelming" + Context: Patient on multiple medications with complex timing requirements + Feedback: "Need help with medication management" + Solutions: Automated medication reminder and tracking system + + Comprehension: Caregiver struggling with complex medication management tasks + Plan: + 1. Review current medication schedule + 2. Implement automated reminder system + 3. Set up medication tracking log + 4. Connect with pharmacy for refill automation + Action_name: IMPLEMENT + Action_input: Deploy medication management module with smart alerts and compliance tracking + + +Let's begin! +""" diff --git a/test/Manifest.toml b/test/Manifest.toml new file mode 100644 index 0000000..83f035b --- /dev/null +++ b/test/Manifest.toml @@ -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" diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 0000000..0c36332 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,2 @@ +[deps] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/runtest.jl b/test/runtests.jl similarity index 68% rename from test/runtest.jl rename to test/runtests.jl index 31b0b20..8498517 100644 --- a/test/runtest.jl +++ b/test/runtests.jl @@ -1,9 +1,9 @@ using Revise -using LibPQ, Dates, JSON3, PrettyPrinting, UUIDs, DataFrames, DataStructures, Base64 +using LibPQ, Dates, JSON, PrettyPrinting, UUIDs, DataFrames, DataStructures, Base64 using GeneralUtils, SQLLLM -config = copy(JSON3.read("/appfolder/mountvolume/appdata/config.json")) +config = JSON.parse("/appfolder/app/dev/YiemAgent/test/config.json") function executeSQL(sql::T) where {T<:AbstractString} host = config[:externalservice][:wineDB][:host] @@ -29,13 +29,19 @@ function executeSQLVectorDB(sql) return result end -function text2textInstructLLM(prompt::String; maxattempt=3) +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=sessionId, - receiverName="text2textinstruct_small", + senderId=senderId, + receiverName="text2textinstruct_$modelsize", mqttBrokerAddress=config[:mqttServerInfo][:broker], mqttBrokerPort=config[:mqttServerInfo][:port], ) @@ -44,18 +50,16 @@ function text2textInstructLLM(prompt::String; maxattempt=3) :msgMeta => msgMeta, :payload => Dict( :text => prompt, - :kwargs => Dict( - :num_ctx => 16384, - :temperature => 0.2, - ) + :kwargs => llmkwargs ) ) response = nothing for attempts in 1:maxattempt - _response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=180, maxattempt=2) - response = _response[:response][:text] - if response !== nothing + _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 attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())") @@ -75,7 +79,7 @@ function getEmbedding(text::T) where {T<:AbstractString} msgPurpose="embedding", senderName="yiemagent", senderId=sessionId, - receiverName="text2textinstruct_small", + receiverName="textembedding", mqttBrokerAddress=config[:mqttServerInfo][:broker], mqttBrokerPort=config[:mqttServerInfo][:port], ) @@ -86,7 +90,8 @@ function getEmbedding(text::T) where {T<:AbstractString} :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] return embedding end @@ -107,12 +112,12 @@ function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnNam 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 @@ -129,11 +134,10 @@ function similarSQLVectorDB(query; maxdistance::Integer=100) 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] + # query = state[:action_history][:question] df = findSimilarTextFromVectorDB(query, tablename, "function_input_embedding", executeSQLVectorDB) row, col = size(df) @@ -153,12 +157,77 @@ function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=3) where {T1 end end -sessionId = "555" + +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(JSON.parse(_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 = JSON.json(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() +d = Dict(:id => sessionId) +filepath = "/appfolder/app/sessionid.json" +open(filepath, "w") do io + JSON.pretty(io, d) +end + + + + +# query = "How many German wines do you have?" +# highValueStateList = copy(JSON.parse("/appfolder/app/highValueState_1.json")) +# selectedState = SQLLLM.compareState(query, highValueStateList, text2textInstructLLM) + # query = Dict(:text=> "How many wines from France do you have that can be paired with lamb?") -query = "How many French wines do you have?" +# query = "How many French wines from Yiem store under 100 dollars do you have?" # query = "retailer: Yiem, wine_type: red, sweetness: 1-2, intensity: 4-5, wine price: 20-40" +query = "from Yiem retailer, red wine from France. price 100 to 1000 USD. sweetness: 1-2, intensity: 4-5" # query = "wine_type: white, country: United States, sweetness: 1-2, tannin: 3, food to be served with wine: pizza" # query = "wine_type: white, country: Austria, food to be served with wine: pork" # query = "wine price: less than 25, wine_type: rose, country: France, sweetness: 2, tannin: 3, food to be served with wine: pizza" @@ -283,8 +352,8 @@ SELECT * FROM wine WHERE wine_type = 'red' AND country = 'France' AND sweetness # :evaluation => # "The user's question is to search the database for wines that have a type of \"white\", are from \"France\", and have a sweetness level of 1. The thought is correct in identifying the conditions needed to filter the wine table. The action taken is to execute a SQL query to retrieve the desired data, which is also correct. The observation provides a search summary and two search results that match the user's question. Each result includes details about the wine such as ID, name, brand, manufacturer, region, country, type, grape variety, serving temperature, intensity, sweetness, tannin, and acidity.", # :accepted_as_answer => "Yes", -# :thoughtHistory => -# OrderedDict{Symbol, Any}(:question => "Search the database for wine_type: white, country: France, sweetness: 1", :thought_1 => "The user wants to search the database for wines that have a type of \"white\", are from \"France\", and have a sweetness level of 1. To achieve this, we need to filter the wine table based on these conditions.", :action_name_1 => "GETDATA", :action_input_1 => "SELECT * FROM wine WHERE wine.wine_type = 'white' AND wine.country = 'France' AND wine.sweetness = 1;", :observation_1 => "\"Search summary: The resulting table represents wines.\\nSearch result: 1) wine_id: 5b6b6df9-d87c-4f33-8995-7249c2ecc917, wine_name: corton-charlemagne grand cru, brand: domaine des croix, manufacturer: domaine des croix, region: bourgogne, country: France, wine_type: white, grape_variety: cote de beaune blanc, serving_temperature: 11 to 13 Celsius, intensity: 4, sweetness: 1, tannin: missing, acidity: 3, fizziness: missing\\n2) wine_id: 1ad27d16-ef64-4907-acf1-40631630c143, wine_name: puligny-montrachet 1er cru 'les demoiselles', brand: amiot guy, manufacturer: amiot guy, region: bourgogne, country: France, wine_type: white, grape_variety: cote de beaune blanc, serving_temperature: 11 to 13 Celsius, intensity: 4, sweetness: 1, tannin: missing, acidity: 3, fizziness: missing\\n\\n\""), +# :action_history => +# OrderedDict{String, Any}("question" => "Search the database for wine_type: white, country: France, sweetness: 1", "thought_1" => "The user wants to search the database for wines that have a type of \"white\", are from \"France\", and have a sweetness level of 1. To achieve this, we need to filter the wine table based on these conditions.", "action_name_1" => "GETDATA", "action_input_1" => "SELECT * FROM wine WHERE wine.wine_type = 'white' AND wine.country = 'France' AND wine.sweetness = 1;", "observation_1" => "\"Search summary: The resulting table represents wines.\\nSearch result: 1) wine_id: 5b6b6df9-d87c-4f33-8995-7249c2ecc917, wine_name: corton-charlemagne grand cru, brand: domaine des croix, manufacturer: domaine des croix, region: bourgogne, country: France, wine_type: white, grape_variety: cote de beaune blanc, serving_temperature: 11 to 13 Celsius, intensity: 4, sweetness: 1, tannin: missing, acidity: 3, fizziness: missing\\n2) wine_id: 1ad27d16-ef64-4907-acf1-40631630c143, wine_name: puligny-montrachet 1er cru 'les demoiselles', brand: amiot guy, manufacturer: amiot guy, region: bourgogne, country: France, wine_type: white, grape_variety: cote de beaune blanc, serving_temperature: 11 to 13 Celsius, intensity: 4, sweetness: 1, tannin: missing, acidity: 3, fizziness: missing\\n\\n\""), # :evaluationscore => 9, # :select => nothing, # :suggestion => "None") diff --git a/test/test_1.jl b/test/test_1.jl index e14d85b..43299e3 100644 --- a/test/test_1.jl +++ b/test/test_1.jl @@ -1,5 +1,5 @@ using Revise -using LibPQ, JSON3, PrettyPrinting, UUIDs, DataFrames, DataStructures, Dates, MQTTClient, Random +using LibPQ, JSON, PrettyPrinting, UUIDs, DataFrames, DataStructures, Dates, MQTTClient, Random using SQLLLM, GeneralUtils @@ -39,7 +39,7 @@ df = DataFrame(result) -config = copy(JSON3.read("config.json")) +config = copy(JSON.parse("config.json")) msgMeta = GeneralUtils.generate_msgMeta( config[:externalservice][:text2textinstruct][:mqtttopic]; diff --git a/test/test_2.jl b/test/test_2.jl index a099cf6..0bb78dc 100644 --- a/test/test_2.jl +++ b/test/test_2.jl @@ -1,16 +1,70 @@ -using Revise +# using Revise +# using SQLLLM, LLMMCTS, DataStructures, JSON + +# query = "How many German wines do you have?" +# highValueStateList = copy(JSON.parse("/appfolder/app/highValueState_1.json")) +# selectedState = SQLLLM.compareState(query, highValueStateList) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -function testf(a)::NamedTuple{(:a, :b), Tuple{Union{Nothing, Int}, Int}} - if a == 1 - return (a=nothing, b=5) - else - return (a=5, b=5) - end -end - - -q = testf(1) -w = testf(2) diff --git a/testfile.csv b/testfile.csv deleted file mode 100644 index 6ab242b..0000000 --- a/testfile.csv +++ /dev/null @@ -1,8 +0,0 @@ -table_name,comment -customer,"The customer table stores information about customers. It includes details such as first name, last name, display name, username, password, gender, country, telephone number, email, birthdate, additional_search_term, other attributes (in JSON format) and a description." -wine,"The wine table stores information about different wines. It includes details namely id, name, brand, manufacturer, region, country, wine_type, grape_variety, serving_temperature, intensity, sweetness, tannin, acidity, fizziness, additional_search_term, other attributes (in JSON format) and a description." -wine_food,"The wine_food table represents the association between wines and food items. It establishes a many-to-many relationship, allowing us to link specific wines with various food items." -food,"The food table represents various food items. It stores information related to food names, country of origin, taste attributes (spiciness, sweetness, sourness, savoriness, and bitterness), serving temperature, additional_search_term, other attributes (in JSON format) and a description." -retailer,"The retailer table stores information about different retailers. It includes details related to retailer names, usernames, passwords, addresses, contact persons, telephone numbers, email addresses, additional_search_term, other attributes (in JSON format) and a description." -retailer_wine,"The retailer_wine table represents the relationship between retailers and wines. It stores information about the wines available from which retailers, including vintage, their price, and the currency." -retailer_food,"The retailer_food table represents the relationship between retailers and food items. It stores information about the food items available from which retailers, including their price and the currency."