Compare commits
87 Commits
v0.4.3
..
af236bcf92
| Author | SHA1 | Date | |
|---|---|---|---|
| af236bcf92 | |||
| 1c2e2e5340 | |||
| 48d89033cc | |||
| befbf62e3b | |||
| 4e476bd39d | |||
| 66ced320f0 | |||
| 1817630795 | |||
| dbac0db9b9 | |||
| ce46888663 | |||
| 2a2d44ab0c | |||
| 0620983b48 | |||
| af81503f8b | |||
| 4624ad99ab | |||
| 2fd363d9ef | |||
| ea6012b2fa | |||
| 6e53f57707 | |||
| 1eea6c66b6 | |||
| 37ff68c963 | |||
| e5fc800c83 | |||
| 4397ec5fb3 | |||
| 9e3f5a0967 | |||
| 30b79a3b34 | |||
| e5b8518c00 | |||
| d115e60ddf | |||
| e0e6aced33 | |||
| c803238f86 | |||
| 4d6cdbcf4a | |||
| 206c0d2c62 | |||
| 597319a147 | |||
| abfe6f45fb | |||
| f8b3150c17 | |||
| caed2a82d6 | |||
| 7ec3edfd77 | |||
| d7adfaffa8 | |||
| 5bde0ca1f2 | |||
| a3ab288cfe | |||
| 17b0974d82 | |||
| 8fed0b5e8f | |||
| 567d6b79d7 | |||
| 74be3e5717 | |||
| 402b6fcadd | |||
| ef523aaa48 | |||
| ddbb135b6b | |||
| afda364484 | |||
| af73d955eb | |||
| 39cf9a72a1 | |||
| 1c829ad854 | |||
| da98baddb6 | |||
| c5fbaabf42 | |||
| 8080905bad | |||
| b349c3a8b6 | |||
| 0148e03d6a | |||
| e718cc4a5c | |||
| 87bc6a46a1 | |||
| 44bb8baf7c | |||
| 18b2d54ba7 | |||
| b3c3bb9b75 | |||
| d004193b19 | |||
| 8898226825 | |||
| 686b9b2e92 | |||
| 3acf46964b | |||
| 5c7caf0b49 | |||
| ad917ea8d0 | |||
| edeef4ed2a | |||
| 31daa805f3 | |||
| c9937ab5d7 | |||
| 4610137f04 | |||
| 9d7eed7cde | |||
| aedc53bf86 | |||
| 286da3cf2c | |||
| 7fa988313d | |||
| e5b19dd268 | |||
| 0df4159261 | |||
| 45e8ded111 | |||
| 9167ece0c0 | |||
| a6a9395ecc | |||
| a503d4d759 | |||
| f45a036971 | |||
| 24b85be58b | |||
| a798cd119e | |||
| fa338dd0f8 | |||
| 8d4bf5f01f | |||
| cd6f6ef961 | |||
| fdec34832d | |||
| 3c72373b85 | |||
| 688a8c4df2 | |||
| 8bd4986be2 |
+272
-121
@@ -1,8 +1,20 @@
|
|||||||
# This file is machine-generated - editing it directly is not advised
|
# This file is machine-generated - editing it directly is not advised
|
||||||
|
|
||||||
julia_version = "1.12.6"
|
julia_version = "1.12.7"
|
||||||
manifest_format = "2.0"
|
manifest_format = "2.0"
|
||||||
project_hash = "a2c996ffe370e277cbff80af974d2701698f1b6c"
|
project_hash = "43252cf86e6030e836ede74c563b58ab2f0f0548"
|
||||||
|
|
||||||
|
[[deps.AWS]]
|
||||||
|
deps = ["Base64", "Compat", "Dates", "Downloads", "GitHub", "HTTP", "IniFile", "JSON", "MD5", "Mocking", "OrderedCollections", "Random", "SHA", "ScopedValues", "Sockets", "URIs", "UUIDs", "XMLDict"]
|
||||||
|
git-tree-sha1 = "842075b12a7f763c5f815a04037a99b6ac346df9"
|
||||||
|
uuid = "fbe9abb3-538b-5e4e-ba9e-bc94f4f92ebc"
|
||||||
|
version = "1.99.0"
|
||||||
|
|
||||||
|
[[deps.AWSS3]]
|
||||||
|
deps = ["AWS", "ArrowTypes", "Base64", "Compat", "Dates", "EzXML", "FilePathsBase", "HTTP", "MbedTLS", "Mocking", "OrderedCollections", "Retry", "SymDict", "URIs", "UUIDs", "XMLDict"]
|
||||||
|
git-tree-sha1 = "af99cea71364c8c4483405a6b03697d197ab4d3b"
|
||||||
|
uuid = "1c724243-ef5b-51ab-93f4-b0a88ac62a95"
|
||||||
|
version = "0.11.6"
|
||||||
|
|
||||||
[[deps.Accessors]]
|
[[deps.Accessors]]
|
||||||
deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
|
deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
|
||||||
@@ -38,6 +50,18 @@ version = "1.1.3"
|
|||||||
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
|
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
|
||||||
|
[[deps.ArnoldiMethod]]
|
||||||
|
deps = ["LinearAlgebra", "Random", "StaticArrays"]
|
||||||
|
git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6"
|
||||||
|
uuid = "ec485272-7323-5ecc-a04f-4719b315124d"
|
||||||
|
version = "0.4.0"
|
||||||
|
|
||||||
|
[[deps.Arrow]]
|
||||||
|
deps = ["ArrowTypes", "BitIntegers", "CodecLz4", "CodecZstd", "ConcurrentUtilities", "DataAPI", "Dates", "EnumX", "Mmap", "PooledArrays", "SentinelArrays", "StringViews", "Tables", "TimeZones", "TranscodingStreams", "UUIDs"]
|
||||||
|
git-tree-sha1 = "4a69a3eadc1f7da78d950d1ef270c3a62c1f7e01"
|
||||||
|
uuid = "69666777-d1a9-59fb-9406-91d4454c9d45"
|
||||||
|
version = "2.8.1"
|
||||||
|
|
||||||
[[deps.ArrowTypes]]
|
[[deps.ArrowTypes]]
|
||||||
deps = ["Sockets", "UUIDs"]
|
deps = ["Sockets", "UUIDs"]
|
||||||
git-tree-sha1 = "404265cd8128a2515a81d5eae16de90fdef05101"
|
git-tree-sha1 = "404265cd8128a2515a81d5eae16de90fdef05101"
|
||||||
@@ -52,6 +76,12 @@ version = "1.11.0"
|
|||||||
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
|
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
||||||
|
[[deps.BitIntegers]]
|
||||||
|
deps = ["Random"]
|
||||||
|
git-tree-sha1 = "091d591a060e43df1dd35faab3ca284925c48e46"
|
||||||
|
uuid = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1"
|
||||||
|
version = "0.3.7"
|
||||||
|
|
||||||
[[deps.BufferedStreams]]
|
[[deps.BufferedStreams]]
|
||||||
git-tree-sha1 = "6863c5b7fc997eadcabdbaf6c5f201dc30032643"
|
git-tree-sha1 = "6863c5b7fc997eadcabdbaf6c5f201dc30032643"
|
||||||
uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
|
uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
|
||||||
@@ -68,9 +98,9 @@ version = "1.11.0"
|
|||||||
|
|
||||||
[[deps.CSV]]
|
[[deps.CSV]]
|
||||||
deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"]
|
deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"]
|
||||||
git-tree-sha1 = "8d8e0b0f350b8e1c91420b5e64e5de774c2f0f4d"
|
git-tree-sha1 = "abed1e735dd4152f48c90cf0767e1790e25f332f"
|
||||||
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
||||||
version = "0.10.16"
|
version = "0.10.17"
|
||||||
|
|
||||||
[[deps.CodeTracking]]
|
[[deps.CodeTracking]]
|
||||||
deps = ["InteractiveUtils", "REPL", "UUIDs"]
|
deps = ["InteractiveUtils", "REPL", "UUIDs"]
|
||||||
@@ -84,16 +114,29 @@ git-tree-sha1 = "40956acdbef3d8c7cc38cba42b56034af8f8581a"
|
|||||||
uuid = "6c391c72-fb7b-5838-ba82-7cfb1bcfecbf"
|
uuid = "6c391c72-fb7b-5838-ba82-7cfb1bcfecbf"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
|
||||||
|
[[deps.CodecLz4]]
|
||||||
|
deps = ["Lz4_jll", "TranscodingStreams"]
|
||||||
|
git-tree-sha1 = "d58afcd2833601636b48ee8cbeb2edcb086522c2"
|
||||||
|
uuid = "5ba52731-8f18-5e0d-9241-30f10d1ec561"
|
||||||
|
version = "0.4.6"
|
||||||
|
|
||||||
[[deps.CodecZlib]]
|
[[deps.CodecZlib]]
|
||||||
deps = ["TranscodingStreams", "Zlib_jll"]
|
deps = ["TranscodingStreams", "Zlib_jll"]
|
||||||
git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9"
|
git-tree-sha1 = "970758a3d591a2a5c2a907c53f2e2f8c1b1d3537"
|
||||||
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
|
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
|
||||||
version = "0.7.8"
|
version = "0.7.9"
|
||||||
|
|
||||||
|
[[deps.CodecZstd]]
|
||||||
|
deps = ["TranscodingStreams", "Zstd_jll"]
|
||||||
|
git-tree-sha1 = "da54a6cd93c54950c15adf1d336cfd7d71f51a56"
|
||||||
|
uuid = "6b39b394-51ab-5f42-8807-6242bab2b4c2"
|
||||||
|
version = "0.8.7"
|
||||||
|
|
||||||
[[deps.CommonSolve]]
|
[[deps.CommonSolve]]
|
||||||
git-tree-sha1 = "99ee296f88c12485402e37c2fd025f95ae097637"
|
deps = ["PrecompileTools"]
|
||||||
|
git-tree-sha1 = "6c389fa857f6ca5a95474b52a52023fd77f24cb7"
|
||||||
uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
|
uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
|
||||||
version = "0.2.9"
|
version = "0.2.14"
|
||||||
|
|
||||||
[[deps.Compat]]
|
[[deps.Compat]]
|
||||||
deps = ["TOML", "UUIDs"]
|
deps = ["TOML", "UUIDs"]
|
||||||
@@ -113,7 +156,7 @@ version = "0.1.1"
|
|||||||
[[deps.CompilerSupportLibraries_jll]]
|
[[deps.CompilerSupportLibraries_jll]]
|
||||||
deps = ["Artifacts", "Libdl"]
|
deps = ["Artifacts", "Libdl"]
|
||||||
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
|
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
|
||||||
version = "1.3.0+1"
|
version = "1.3.1+2"
|
||||||
|
|
||||||
[[deps.CompositionsBase]]
|
[[deps.CompositionsBase]]
|
||||||
git-tree-sha1 = "802bb88cd69dfd1509f6670416bd4434015693ad"
|
git-tree-sha1 = "802bb88cd69dfd1509f6670416bd4434015693ad"
|
||||||
@@ -124,6 +167,12 @@ weakdeps = ["InverseFunctions"]
|
|||||||
[deps.CompositionsBase.extensions]
|
[deps.CompositionsBase.extensions]
|
||||||
CompositionsBaseInverseFunctionsExt = "InverseFunctions"
|
CompositionsBaseInverseFunctionsExt = "InverseFunctions"
|
||||||
|
|
||||||
|
[[deps.ConcurrentUtilities]]
|
||||||
|
deps = ["Serialization", "Sockets"]
|
||||||
|
git-tree-sha1 = "3c9be947934c38475bafe822c6d61aaed17f0738"
|
||||||
|
uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
|
||||||
|
version = "2.6.0"
|
||||||
|
|
||||||
[[deps.ConstructionBase]]
|
[[deps.ConstructionBase]]
|
||||||
git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb"
|
git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb"
|
||||||
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
|
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
|
||||||
@@ -140,14 +189,14 @@ version = "1.6.0"
|
|||||||
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
|
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
|
||||||
|
|
||||||
[[deps.Crayons]]
|
[[deps.Crayons]]
|
||||||
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
|
git-tree-sha1 = "54b76cbb40d9a0f5368c880725b2f141da77c94f"
|
||||||
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
|
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
|
||||||
version = "4.1.1"
|
version = "4.2.0"
|
||||||
|
|
||||||
[[deps.DBInterface]]
|
[[deps.DBInterface]]
|
||||||
git-tree-sha1 = "a444404b3f94deaa43ca2a58e18153a82695282b"
|
git-tree-sha1 = "265d7f87f329c063cbde41920f9d00713a43cedd"
|
||||||
uuid = "a10d1c49-ce27-4219-8d33-6db1a4562965"
|
uuid = "a10d1c49-ce27-4219-8d33-6db1a4562965"
|
||||||
version = "2.6.1"
|
version = "2.7.0"
|
||||||
|
|
||||||
[[deps.DataAPI]]
|
[[deps.DataAPI]]
|
||||||
git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe"
|
git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe"
|
||||||
@@ -162,9 +211,9 @@ version = "1.8.2"
|
|||||||
|
|
||||||
[[deps.DataStructures]]
|
[[deps.DataStructures]]
|
||||||
deps = ["OrderedCollections"]
|
deps = ["OrderedCollections"]
|
||||||
git-tree-sha1 = "6fb53a69613a0b2b68a0d12671717d307ab8b24e"
|
git-tree-sha1 = "b0bc6d2cad1fed8b7fd59a1551a991cb3d2809e6"
|
||||||
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
||||||
version = "0.19.5"
|
version = "0.19.6"
|
||||||
|
|
||||||
[[deps.DataValueInterfaces]]
|
[[deps.DataValueInterfaces]]
|
||||||
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
|
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
|
||||||
@@ -181,6 +230,20 @@ git-tree-sha1 = "e98abef36d02a0ec385d68cd7dadbce9b28cbd88"
|
|||||||
uuid = "abce61dc-4473-55a0-ba07-351d65e31d42"
|
uuid = "abce61dc-4473-55a0-ba07-351d65e31d42"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
||||||
|
[[deps.Distances]]
|
||||||
|
deps = ["LinearAlgebra", "Statistics", "StatsAPI"]
|
||||||
|
git-tree-sha1 = "c7e3a542b999843086e2f29dac96a618c105be1d"
|
||||||
|
uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
|
||||||
|
version = "0.10.12"
|
||||||
|
|
||||||
|
[deps.Distances.extensions]
|
||||||
|
DistancesChainRulesCoreExt = "ChainRulesCore"
|
||||||
|
DistancesSparseArraysExt = "SparseArrays"
|
||||||
|
|
||||||
|
[deps.Distances.weakdeps]
|
||||||
|
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
|
||||||
|
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
|
||||||
|
|
||||||
[[deps.Distributed]]
|
[[deps.Distributed]]
|
||||||
deps = ["Random", "Serialization", "Sockets"]
|
deps = ["Random", "Serialization", "Sockets"]
|
||||||
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
|
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
|
||||||
@@ -188,9 +251,9 @@ version = "1.11.0"
|
|||||||
|
|
||||||
[[deps.Distributions]]
|
[[deps.Distributions]]
|
||||||
deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "Roots", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
|
deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "Roots", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
|
||||||
git-tree-sha1 = "cd3c5ac74cd3923c8945c6a81518c46abd0e73a3"
|
git-tree-sha1 = "a958ab3a40c755563f5e1405c0846cb0446bf19d"
|
||||||
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
|
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
|
||||||
version = "0.25.129"
|
version = "0.25.131"
|
||||||
|
|
||||||
[deps.Distributions.extensions]
|
[deps.Distributions.extensions]
|
||||||
DistributionsChainRulesCoreExt = "ChainRulesCore"
|
DistributionsChainRulesCoreExt = "ChainRulesCore"
|
||||||
@@ -220,15 +283,15 @@ uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
|
|||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
|
||||||
[[deps.ExprTools]]
|
[[deps.ExprTools]]
|
||||||
git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec"
|
git-tree-sha1 = "d2e49e7efd29719d6f28b891b0e0e159daa9d2b4"
|
||||||
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
|
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
|
||||||
version = "0.1.10"
|
version = "0.1.11"
|
||||||
|
|
||||||
[[deps.FileIO]]
|
[[deps.EzXML]]
|
||||||
deps = ["Pkg", "Requires", "UUIDs"]
|
deps = ["Printf", "XML2_jll"]
|
||||||
git-tree-sha1 = "91e0e5c68d02bcdaae76d3c8ceb4361e8f28d2e9"
|
git-tree-sha1 = "7ea1aa5869e2626ccae84480e4f37185bc6f41d3"
|
||||||
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
|
uuid = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615"
|
||||||
version = "1.16.5"
|
version = "1.2.3"
|
||||||
|
|
||||||
[[deps.FilePathsBase]]
|
[[deps.FilePathsBase]]
|
||||||
deps = ["Compat", "Dates"]
|
deps = ["Compat", "Dates"]
|
||||||
@@ -247,9 +310,10 @@ version = "1.11.0"
|
|||||||
|
|
||||||
[[deps.FillArrays]]
|
[[deps.FillArrays]]
|
||||||
deps = ["LinearAlgebra"]
|
deps = ["LinearAlgebra"]
|
||||||
git-tree-sha1 = "2f979084d1e13948a3352cf64a25df6bd3b4dca3"
|
git-tree-sha1 = "5bad39456d9f0166184fce2248783dd9862645c1"
|
||||||
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
|
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
|
||||||
version = "1.16.0"
|
version = "1.17.0"
|
||||||
|
weakdeps = ["PDMats", "SparseArrays", "StaticArrays", "Statistics"]
|
||||||
|
|
||||||
[deps.FillArrays.extensions]
|
[deps.FillArrays.extensions]
|
||||||
FillArraysPDMatsExt = "PDMats"
|
FillArraysPDMatsExt = "PDMats"
|
||||||
@@ -257,35 +321,49 @@ version = "1.16.0"
|
|||||||
FillArraysStaticArraysExt = "StaticArrays"
|
FillArraysStaticArraysExt = "StaticArrays"
|
||||||
FillArraysStatisticsExt = "Statistics"
|
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.Future]]
|
||||||
deps = ["Random"]
|
deps = ["Random"]
|
||||||
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"
|
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
||||||
[[deps.Gamma]]
|
[[deps.Gamma]]
|
||||||
git-tree-sha1 = "86f86b6168a016ed88e4ae4e64577b98c3b59e8e"
|
deps = ["LogExpFunctions"]
|
||||||
|
git-tree-sha1 = "becc397f7cfb06e343496ae6ffb04818a851da51"
|
||||||
uuid = "a0844989-3bd2-4988-8bea-c9407ab0941b"
|
uuid = "a0844989-3bd2-4988-8bea-c9407ab0941b"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
|
|
||||||
[[deps.GeneralUtils]]
|
[[deps.GeneralUtils]]
|
||||||
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "HTTP", "JSON", "NATS", "PrettyPrinting", "Random", "Revise", "SHA", "UUIDs"]
|
deps = ["AWS", "AWSS3", "CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "Graphs", "HTTP", "JSON", "LibPQ", "NATS", "OrderedCollections", "PrettyPrinting", "Random", "Revise", "SHA", "StringDistances", "URIs", "UUIDs"]
|
||||||
git-tree-sha1 = "7c0600c166a5deb2c607018a491c04eb25969c2e"
|
git-tree-sha1 = "4d7ff3b02a9cbd6d4f65fea43551b0bca6a83a22"
|
||||||
repo-rev = "main"
|
repo-rev = "main"
|
||||||
repo-url = "https://git.yiem.cc/ton/GeneralUtils"
|
repo-url = "https://git.yiem.cc/ton/GeneralUtils"
|
||||||
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
||||||
version = "0.4.9"
|
version = "0.6.9"
|
||||||
|
|
||||||
|
[[deps.GitHub]]
|
||||||
|
deps = ["Base64", "Dates", "HTTP", "JSON", "MbedTLS", "Sockets", "SodiumSeal", "URIs"]
|
||||||
|
git-tree-sha1 = "33de1bc5665494f0a9669fd6027e28180709d874"
|
||||||
|
uuid = "bc5e4493-9b4d-5f90-b8aa-2b2bcaad7a26"
|
||||||
|
version = "5.13.0"
|
||||||
|
|
||||||
|
[[deps.Graphs]]
|
||||||
|
deps = ["ArnoldiMethod", "DataStructures", "Inflate", "LinearAlgebra", "Random", "SimpleTraits", "SparseArrays", "Statistics"]
|
||||||
|
git-tree-sha1 = "7eb45fe833a5b7c51cf6d89c5a841d5967e44be3"
|
||||||
|
uuid = "86223c79-3864-5bf0-83f7-82e725a168b6"
|
||||||
|
version = "1.14.0"
|
||||||
|
|
||||||
|
[deps.Graphs.extensions]
|
||||||
|
GraphsSharedArraysExt = "SharedArrays"
|
||||||
|
|
||||||
|
[deps.Graphs.weakdeps]
|
||||||
|
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
|
||||||
|
SharedArrays = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
|
||||||
|
|
||||||
[[deps.HTTP]]
|
[[deps.HTTP]]
|
||||||
deps = ["Base64", "CodecZlib", "Dates", "EnumX", "PrecompileTools", "Random", "Reseau", "SHA", "URIs", "UUIDs", "Zlib_jll"]
|
deps = ["Base64", "CodecZlib", "Dates", "EnumX", "PrecompileTools", "Random", "Reseau", "SHA", "URIs", "UUIDs", "Zlib_jll"]
|
||||||
git-tree-sha1 = "eda1d37cb55d90a17d0957c75841138c88b361a1"
|
git-tree-sha1 = "49a90cd540681d5b79bf5c8ca13c92677e37005e"
|
||||||
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
|
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
|
||||||
version = "2.5.4"
|
version = "2.6.6"
|
||||||
|
|
||||||
[[deps.HashArrayMappedTries]]
|
[[deps.HashArrayMappedTries]]
|
||||||
git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae"
|
git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae"
|
||||||
@@ -294,9 +372,9 @@ version = "0.2.0"
|
|||||||
|
|
||||||
[[deps.HypergeometricFunctions]]
|
[[deps.HypergeometricFunctions]]
|
||||||
deps = ["Gamma", "LinearAlgebra"]
|
deps = ["Gamma", "LinearAlgebra"]
|
||||||
git-tree-sha1 = "18d7deab5fb0440dc6a7b6993c5c27b25420de10"
|
git-tree-sha1 = "31bb6c92405c084617facc1d7ed9eb6c402d061e"
|
||||||
uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
|
uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
|
||||||
version = "0.3.29"
|
version = "0.3.30"
|
||||||
|
|
||||||
[[deps.ICU_jll]]
|
[[deps.ICU_jll]]
|
||||||
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
||||||
@@ -310,6 +388,16 @@ git-tree-sha1 = "cf8234411cbeb98676c173f930951ea29dca3b23"
|
|||||||
uuid = "a303e19e-6eb4-11e9-3b09-cd9505f79100"
|
uuid = "a303e19e-6eb4-11e9-3b09-cd9505f79100"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|
||||||
|
[[deps.Inflate]]
|
||||||
|
git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d"
|
||||||
|
uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
|
||||||
|
version = "0.1.5"
|
||||||
|
|
||||||
|
[[deps.IniFile]]
|
||||||
|
git-tree-sha1 = "f550e6e32074c939295eb5ea6de31849ac2c9625"
|
||||||
|
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
|
||||||
|
version = "0.5.1"
|
||||||
|
|
||||||
[[deps.InlineStrings]]
|
[[deps.InlineStrings]]
|
||||||
git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d"
|
git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d"
|
||||||
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
|
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
|
||||||
@@ -369,9 +457,9 @@ version = "1.8.0"
|
|||||||
|
|
||||||
[[deps.JSON]]
|
[[deps.JSON]]
|
||||||
deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"]
|
deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"]
|
||||||
git-tree-sha1 = "c89d196f5ffb64bfbf80985b699ea913b0d2c211"
|
git-tree-sha1 = "c7345ab1a7ca4dc8a02c9f6510da0d9857bbe513"
|
||||||
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||||
version = "1.6.1"
|
version = "1.7.1"
|
||||||
weakdeps = ["ArrowTypes"]
|
weakdeps = ["ArrowTypes"]
|
||||||
|
|
||||||
[deps.JSON.extensions]
|
[deps.JSON.extensions]
|
||||||
@@ -389,9 +477,9 @@ weakdeps = ["ArrowTypes"]
|
|||||||
|
|
||||||
[[deps.JuliaInterpreter]]
|
[[deps.JuliaInterpreter]]
|
||||||
deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"]
|
deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"]
|
||||||
git-tree-sha1 = "58927c485919bf17ea308d9d82156de1adf4b006"
|
git-tree-sha1 = "c3d401f110454b4ea24a76be33f6ee0d7d385103"
|
||||||
uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"
|
uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"
|
||||||
version = "0.10.12"
|
version = "0.11.4"
|
||||||
|
|
||||||
[[deps.JuliaSyntaxHighlighting]]
|
[[deps.JuliaSyntaxHighlighting]]
|
||||||
deps = ["StyledStrings"]
|
deps = ["StyledStrings"]
|
||||||
@@ -404,18 +492,10 @@ git-tree-sha1 = "0f2899fdadaab4b8f57db558ba21bdb4fb52f1f0"
|
|||||||
uuid = "b39eb1a6-c29a-53d7-8c32-632cd16f18da"
|
uuid = "b39eb1a6-c29a-53d7-8c32-632cd16f18da"
|
||||||
version = "1.21.3+0"
|
version = "1.21.3+0"
|
||||||
|
|
||||||
[[deps.LLMMCTS]]
|
|
||||||
deps = ["GeneralUtils", "JSON", "PrettyPrinting"]
|
|
||||||
git-tree-sha1 = "3dff98131dfa79be8c9bd84fc51cb0ba1832c472"
|
|
||||||
repo-rev = "main"
|
|
||||||
repo-url = "https://git.yiem.cc/ton/LLMMCTS"
|
|
||||||
uuid = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
|
|
||||||
version = "0.1.5"
|
|
||||||
|
|
||||||
[[deps.LaTeXStrings]]
|
[[deps.LaTeXStrings]]
|
||||||
git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
|
git-tree-sha1 = "f88f3ccef05a6a72a0cf0ed417c8fd68530f4ab2"
|
||||||
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
|
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
|
||||||
version = "1.4.0"
|
version = "1.4.1"
|
||||||
|
|
||||||
[[deps.LayerDicts]]
|
[[deps.LayerDicts]]
|
||||||
git-tree-sha1 = "6087ad3521d6278ebe5c27ae55e7bbb15ca312cb"
|
git-tree-sha1 = "6087ad3521d6278ebe5c27ae55e7bbb15ca312cb"
|
||||||
@@ -463,6 +543,12 @@ version = "1.11.3+1"
|
|||||||
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
|
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
||||||
|
[[deps.Libiconv_jll]]
|
||||||
|
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
||||||
|
git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809"
|
||||||
|
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
|
||||||
|
version = "1.18.0+0"
|
||||||
|
|
||||||
[[deps.LinearAlgebra]]
|
[[deps.LinearAlgebra]]
|
||||||
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
|
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
|
||||||
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
||||||
@@ -490,9 +576,21 @@ version = "1.11.0"
|
|||||||
|
|
||||||
[[deps.LoweredCodeUtils]]
|
[[deps.LoweredCodeUtils]]
|
||||||
deps = ["CodeTracking", "Compiler", "JuliaInterpreter"]
|
deps = ["CodeTracking", "Compiler", "JuliaInterpreter"]
|
||||||
git-tree-sha1 = "3733419e9a71156b389f3e331672d2e95436783f"
|
git-tree-sha1 = "1d4c737ab26f51ceed52ab2019c09b7660eb7440"
|
||||||
uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
|
uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
|
||||||
version = "3.6.2"
|
version = "3.8.0"
|
||||||
|
|
||||||
|
[[deps.Lz4_jll]]
|
||||||
|
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
||||||
|
git-tree-sha1 = "191686b1ac1ea9c89fc52e996ad15d1d241d1e33"
|
||||||
|
uuid = "5ced341a-0733-55b8-9ab6-a4889d929147"
|
||||||
|
version = "1.10.1+0"
|
||||||
|
|
||||||
|
[[deps.MD5]]
|
||||||
|
deps = ["Random", "SHA"]
|
||||||
|
git-tree-sha1 = "1576f756617d31eb397a4a517b68562fd28dc2b4"
|
||||||
|
uuid = "6ac74813-4b46-53a4-afec-0b5dc9d7885c"
|
||||||
|
version = "0.2.3"
|
||||||
|
|
||||||
[[deps.MacroTools]]
|
[[deps.MacroTools]]
|
||||||
git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522"
|
git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522"
|
||||||
@@ -550,9 +648,9 @@ version = "0.1.1"
|
|||||||
|
|
||||||
[[deps.NanoDates]]
|
[[deps.NanoDates]]
|
||||||
deps = ["Dates", "Parsers"]
|
deps = ["Dates", "Parsers"]
|
||||||
git-tree-sha1 = "850a0557ae5934f6e67ac0dc5ca13d0328422d1f"
|
git-tree-sha1 = "77c7e98ca39aefb481f9b97a2f4f5c5471c08a1d"
|
||||||
uuid = "46f1a544-deae-4307-8689-c12aa3c955c6"
|
uuid = "46f1a544-deae-4307-8689-c12aa3c955c6"
|
||||||
version = "1.0.3"
|
version = "1.1.0"
|
||||||
|
|
||||||
[[deps.NetworkOptions]]
|
[[deps.NetworkOptions]]
|
||||||
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
|
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
|
||||||
@@ -582,7 +680,7 @@ version = "0.8.7+0"
|
|||||||
[[deps.OpenSSL_jll]]
|
[[deps.OpenSSL_jll]]
|
||||||
deps = ["Artifacts", "Libdl"]
|
deps = ["Artifacts", "Libdl"]
|
||||||
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
|
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
|
||||||
version = "3.5.4+0"
|
version = "3.5.6+0"
|
||||||
|
|
||||||
[[deps.OpenSpecFun_jll]]
|
[[deps.OpenSpecFun_jll]]
|
||||||
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
|
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
|
||||||
@@ -591,15 +689,15 @@ uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
|
|||||||
version = "0.5.6+0"
|
version = "0.5.6+0"
|
||||||
|
|
||||||
[[deps.OrderedCollections]]
|
[[deps.OrderedCollections]]
|
||||||
git-tree-sha1 = "94ba93778373a53bfd5a0caaf7d809c445292ff4"
|
git-tree-sha1 = "05f45c2e0de6259db764adbfd2f1dc6d3f8de13c"
|
||||||
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
||||||
version = "1.8.2"
|
version = "2.0.1"
|
||||||
|
|
||||||
[[deps.PDMats]]
|
[[deps.PDMats]]
|
||||||
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
|
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
|
||||||
git-tree-sha1 = "26766d4b5f1a410c218a19b85a672c6edb693c65"
|
git-tree-sha1 = "123266c25174ef6c8d4718920abc206452cf8de6"
|
||||||
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
|
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
|
||||||
version = "0.11.40"
|
version = "0.11.41"
|
||||||
weakdeps = ["StatsBase"]
|
weakdeps = ["StatsBase"]
|
||||||
|
|
||||||
[deps.PDMats.extensions]
|
[deps.PDMats.extensions]
|
||||||
@@ -607,18 +705,9 @@ weakdeps = ["StatsBase"]
|
|||||||
|
|
||||||
[[deps.Parsers]]
|
[[deps.Parsers]]
|
||||||
deps = ["Dates", "PrecompileTools", "UUIDs"]
|
deps = ["Dates", "PrecompileTools", "UUIDs"]
|
||||||
git-tree-sha1 = "32a4e09c5f29402573d673901778a0e03b0807b9"
|
git-tree-sha1 = "3de8f5e6e90ebfa8d6d1f86997d6cdcd6a912ff3"
|
||||||
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
|
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
|
||||||
version = "2.8.6"
|
version = "2.8.7"
|
||||||
|
|
||||||
[[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.12.1"
|
|
||||||
weakdeps = ["REPL"]
|
|
||||||
|
|
||||||
[deps.Pkg.extensions]
|
|
||||||
REPLExt = "REPL"
|
|
||||||
|
|
||||||
[[deps.PooledArrays]]
|
[[deps.PooledArrays]]
|
||||||
deps = ["DataAPI", "Future"]
|
deps = ["DataAPI", "Future"]
|
||||||
@@ -639,21 +728,23 @@ uuid = "21216c6a-2e73-6563-6e65-726566657250"
|
|||||||
version = "1.5.2"
|
version = "1.5.2"
|
||||||
|
|
||||||
[[deps.PrettyPrinting]]
|
[[deps.PrettyPrinting]]
|
||||||
git-tree-sha1 = "142ee93724a9c5d04d78df7006670a93ed1b244e"
|
git-tree-sha1 = "0b7f4ad437e31c51cf5b91fb103579b04025170a"
|
||||||
uuid = "54e16d92-306c-5ea0-a30b-337be88ac337"
|
uuid = "54e16d92-306c-5ea0-a30b-337be88ac337"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
|
|
||||||
[[deps.PrettyTables]]
|
[[deps.PrettyTables]]
|
||||||
deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"]
|
deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"]
|
||||||
git-tree-sha1 = "624de6279ab7d94fc9f672f0068107eb6619732c"
|
git-tree-sha1 = "1b8aa19f229b1cea7fc93874a52e49db6a854450"
|
||||||
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
|
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
|
||||||
version = "3.3.2"
|
version = "3.4.8"
|
||||||
|
|
||||||
[deps.PrettyTables.extensions]
|
[deps.PrettyTables.extensions]
|
||||||
|
PrettyTablesExcelExt = "XLSX"
|
||||||
PrettyTablesTypstryExt = "Typstry"
|
PrettyTablesTypstryExt = "Typstry"
|
||||||
|
|
||||||
[deps.PrettyTables.weakdeps]
|
[deps.PrettyTables.weakdeps]
|
||||||
Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e"
|
Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e"
|
||||||
|
XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0"
|
||||||
|
|
||||||
[[deps.Printf]]
|
[[deps.Printf]]
|
||||||
deps = ["Unicode"]
|
deps = ["Unicode"]
|
||||||
@@ -706,15 +797,20 @@ version = "1.3.1"
|
|||||||
|
|
||||||
[[deps.Reseau]]
|
[[deps.Reseau]]
|
||||||
deps = ["NetworkOptions", "OpenSSL_jll", "PrecompileTools", "Random", "SHA"]
|
deps = ["NetworkOptions", "OpenSSL_jll", "PrecompileTools", "Random", "SHA"]
|
||||||
git-tree-sha1 = "0eab6d95ed40c2ef3992255c1c71e4f9748932b5"
|
git-tree-sha1 = "aaee74fab545e9af4b21d10c2203c93c636c4ae0"
|
||||||
uuid = "802f3686-a58f-41ce-bb0c-3c43c75bba36"
|
uuid = "802f3686-a58f-41ce-bb0c-3c43c75bba36"
|
||||||
version = "1.3.1"
|
version = "1.4.1"
|
||||||
|
|
||||||
|
[[deps.Retry]]
|
||||||
|
git-tree-sha1 = "41ac127cd281bb33e42aba46a9d3b25cd35fc6d5"
|
||||||
|
uuid = "20febd7b-183b-5ae2-ac4a-720e7ce64774"
|
||||||
|
version = "0.4.1"
|
||||||
|
|
||||||
[[deps.Revise]]
|
[[deps.Revise]]
|
||||||
deps = ["CRC32c", "CodeTracking", "FileWatching", "InteractiveUtils", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Preferences", "REPL", "UUIDs"]
|
deps = ["CRC32c", "CodeTracking", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Preferences", "REPL", "UUIDs"]
|
||||||
git-tree-sha1 = "27e3ee13fc8739a59b380d6163d6a82f52c03bd7"
|
git-tree-sha1 = "ab0f5630e37968bc49b8c4cedd3279a7d30b9486"
|
||||||
uuid = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
uuid = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
||||||
version = "3.15.1"
|
version = "3.17.0"
|
||||||
weakdeps = ["Distributed"]
|
weakdeps = ["Distributed"]
|
||||||
|
|
||||||
[deps.Revise.extensions]
|
[deps.Revise.extensions]
|
||||||
@@ -728,15 +824,15 @@ version = "0.9.0"
|
|||||||
|
|
||||||
[[deps.Rmath_jll]]
|
[[deps.Rmath_jll]]
|
||||||
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
||||||
git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8"
|
git-tree-sha1 = "6d40b2fe70437b01397d2a4d5b020008da4e7019"
|
||||||
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
|
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
|
||||||
version = "0.5.1+0"
|
version = "0.5.2+0"
|
||||||
|
|
||||||
[[deps.Roots]]
|
[[deps.Roots]]
|
||||||
deps = ["Accessors", "CommonSolve", "Printf"]
|
deps = ["Accessors", "CommonSolve", "Printf"]
|
||||||
git-tree-sha1 = "ed45bcc7cf3c8887595b973f2b1efbe91dcc50ec"
|
git-tree-sha1 = "13a9e0164267bb9ebc55c1c5d72fceea8f09555b"
|
||||||
uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
|
uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
|
||||||
version = "3.0.1"
|
version = "3.0.7"
|
||||||
|
|
||||||
[deps.Roots.extensions]
|
[deps.Roots.extensions]
|
||||||
RootsChainRulesCoreExt = "ChainRulesCore"
|
RootsChainRulesCoreExt = "ChainRulesCore"
|
||||||
@@ -758,14 +854,6 @@ version = "3.0.1"
|
|||||||
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
|
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
|
||||||
[[deps.SQLLLM]]
|
|
||||||
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "PrettyPrinting", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"]
|
|
||||||
git-tree-sha1 = "2807a768907f59308d8d71ece599037b0a66b0a5"
|
|
||||||
repo-rev = "main"
|
|
||||||
repo-url = "https://git.yiem.cc/ton/SQLLLM"
|
|
||||||
uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
|
|
||||||
version = "0.2.7"
|
|
||||||
|
|
||||||
[[deps.SQLStrings]]
|
[[deps.SQLStrings]]
|
||||||
git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c"
|
git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c"
|
||||||
uuid = "af517c2e-c243-48fa-aab8-efac3db270f5"
|
uuid = "af517c2e-c243-48fa-aab8-efac3db270f5"
|
||||||
@@ -793,6 +881,12 @@ version = "1.4.10"
|
|||||||
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
||||||
|
[[deps.SimpleTraits]]
|
||||||
|
deps = ["InteractiveUtils", "MacroTools"]
|
||||||
|
git-tree-sha1 = "7ddb0b49c109481b046972c0e4ab02b2127d6a75"
|
||||||
|
uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
|
||||||
|
version = "0.9.6"
|
||||||
|
|
||||||
[[deps.Sockets]]
|
[[deps.Sockets]]
|
||||||
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
|
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@@ -803,6 +897,12 @@ git-tree-sha1 = "907703e0d50846f300650d7225bdcab145b7bca9"
|
|||||||
uuid = "4f5b5e99-b0ad-42cd-b47a-334e172ec8bd"
|
uuid = "4f5b5e99-b0ad-42cd-b47a-334e172ec8bd"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
|
||||||
|
[[deps.SodiumSeal]]
|
||||||
|
deps = ["Base64", "Libdl", "libsodium_jll"]
|
||||||
|
git-tree-sha1 = "80cef67d2953e33935b41c6ab0a178b9987b1c99"
|
||||||
|
uuid = "2133526b-2bfb-4018-ac12-889fb3908a75"
|
||||||
|
version = "0.1.1"
|
||||||
|
|
||||||
[[deps.SortingAlgorithms]]
|
[[deps.SortingAlgorithms]]
|
||||||
deps = ["DataStructures"]
|
deps = ["DataStructures"]
|
||||||
git-tree-sha1 = "13cd91cc9be159e3f4d95b857fa2aa383b53772a"
|
git-tree-sha1 = "13cd91cc9be159e3f4d95b857fa2aa383b53772a"
|
||||||
@@ -816,9 +916,9 @@ version = "1.12.0"
|
|||||||
|
|
||||||
[[deps.SpecialFunctions]]
|
[[deps.SpecialFunctions]]
|
||||||
deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
|
deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
|
||||||
git-tree-sha1 = "6547cbdd8ce32efba0d21c5a40fa96d1a3548f9f"
|
git-tree-sha1 = "429071b23f4c9a13fb6582f807cc2ef454082408"
|
||||||
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
|
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
|
||||||
version = "2.8.0"
|
version = "2.9.0"
|
||||||
|
|
||||||
[deps.SpecialFunctions.extensions]
|
[deps.SpecialFunctions.extensions]
|
||||||
SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
|
SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
|
||||||
@@ -826,11 +926,30 @@ version = "2.8.0"
|
|||||||
[deps.SpecialFunctions.weakdeps]
|
[deps.SpecialFunctions.weakdeps]
|
||||||
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
|
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
|
||||||
|
|
||||||
|
[[deps.StaticArrays]]
|
||||||
|
deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"]
|
||||||
|
git-tree-sha1 = "fac51faf3bb96e8bc0bf6f9f39ca4955652776bb"
|
||||||
|
uuid = "90137ffa-7385-5640-81b9-e52037218182"
|
||||||
|
version = "1.9.19"
|
||||||
|
|
||||||
|
[deps.StaticArrays.extensions]
|
||||||
|
StaticArraysChainRulesCoreExt = "ChainRulesCore"
|
||||||
|
StaticArraysStatisticsExt = "Statistics"
|
||||||
|
|
||||||
|
[deps.StaticArrays.weakdeps]
|
||||||
|
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
|
||||||
|
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
|
||||||
|
|
||||||
|
[[deps.StaticArraysCore]]
|
||||||
|
git-tree-sha1 = "6ab403037779dae8c514bad259f32a447262455a"
|
||||||
|
uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
|
||||||
|
version = "1.4.4"
|
||||||
|
|
||||||
[[deps.Statistics]]
|
[[deps.Statistics]]
|
||||||
deps = ["LinearAlgebra"]
|
deps = ["LinearAlgebra"]
|
||||||
git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0"
|
git-tree-sha1 = "389592b8592e5fb48f498ff60dc6acb4a0e62953"
|
||||||
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
|
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
|
||||||
version = "1.11.1"
|
version = "1.11.4"
|
||||||
weakdeps = ["SparseArrays"]
|
weakdeps = ["SparseArrays"]
|
||||||
|
|
||||||
[deps.Statistics.extensions]
|
[deps.Statistics.extensions]
|
||||||
@@ -844,15 +963,15 @@ version = "1.8.0"
|
|||||||
|
|
||||||
[[deps.StatsBase]]
|
[[deps.StatsBase]]
|
||||||
deps = ["AliasTables", "DataAPI", "DataStructures", "IrrationalConstants", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
|
deps = ["AliasTables", "DataAPI", "DataStructures", "IrrationalConstants", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
|
||||||
git-tree-sha1 = "e4d7a1a0edc20af42689ea6f4f3587a2175d50ee"
|
git-tree-sha1 = "adb9da019510162e67a4493fc235c23203d8b09e"
|
||||||
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
|
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
|
||||||
version = "0.34.12"
|
version = "0.34.13"
|
||||||
|
|
||||||
[[deps.StatsFuns]]
|
[[deps.StatsFuns]]
|
||||||
deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
|
deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
|
||||||
git-tree-sha1 = "770240df9a3b8888065046948f7a09b4e0f997d5"
|
git-tree-sha1 = "91a5737baed20ee31f3faea0e51f57461f6a689e"
|
||||||
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
|
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
|
||||||
version = "2.2.0"
|
version = "2.2.1"
|
||||||
|
|
||||||
[deps.StatsFuns.extensions]
|
[deps.StatsFuns.extensions]
|
||||||
StatsFunsChainRulesCoreExt = "ChainRulesCore"
|
StatsFunsChainRulesCoreExt = "ChainRulesCore"
|
||||||
@@ -862,11 +981,22 @@ version = "2.2.0"
|
|||||||
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
|
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
|
||||||
InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112"
|
InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112"
|
||||||
|
|
||||||
|
[[deps.StringDistances]]
|
||||||
|
deps = ["Distances", "StatsAPI"]
|
||||||
|
git-tree-sha1 = "456f343c7a6c8f52d8b501c0da2a05538ab1b907"
|
||||||
|
uuid = "88034a9c-02f8-509d-84a9-84ec65e18404"
|
||||||
|
version = "1.0.1"
|
||||||
|
|
||||||
[[deps.StringManipulation]]
|
[[deps.StringManipulation]]
|
||||||
deps = ["PrecompileTools"]
|
deps = ["PrecompileTools"]
|
||||||
git-tree-sha1 = "d05693d339e37d6ab134c5ab53c29fce5ee5d7d5"
|
git-tree-sha1 = "773065c6e0e903924a9d838259be74338422aef2"
|
||||||
uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e"
|
uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e"
|
||||||
version = "0.4.4"
|
version = "0.5.0"
|
||||||
|
|
||||||
|
[[deps.StringViews]]
|
||||||
|
git-tree-sha1 = "f2dcb92855b31ad92fe8f079d4f75ac57c93e4b8"
|
||||||
|
uuid = "354b36f9-a18e-4713-926e-db85100087ba"
|
||||||
|
version = "1.3.7"
|
||||||
|
|
||||||
[[deps.StructTypes]]
|
[[deps.StructTypes]]
|
||||||
deps = ["Dates", "UUIDs"]
|
deps = ["Dates", "UUIDs"]
|
||||||
@@ -876,9 +1006,9 @@ version = "1.11.0"
|
|||||||
|
|
||||||
[[deps.StructUtils]]
|
[[deps.StructUtils]]
|
||||||
deps = ["Dates", "UUIDs"]
|
deps = ["Dates", "UUIDs"]
|
||||||
git-tree-sha1 = "82bee338d650aa515f31866c460cb7e3bcef90b8"
|
git-tree-sha1 = "2d0fc55c61321ba245c47be599570d11bac50303"
|
||||||
uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42"
|
uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42"
|
||||||
version = "2.8.2"
|
version = "2.8.5"
|
||||||
|
|
||||||
[deps.StructUtils.extensions]
|
[deps.StructUtils.extensions]
|
||||||
StructUtilsMeasurementsExt = ["Measurements"]
|
StructUtilsMeasurementsExt = ["Measurements"]
|
||||||
@@ -903,6 +1033,12 @@ deps = ["Artifacts", "Libdl", "libblastrampoline_jll"]
|
|||||||
uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c"
|
uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c"
|
||||||
version = "7.8.3+2"
|
version = "7.8.3+2"
|
||||||
|
|
||||||
|
[[deps.SymDict]]
|
||||||
|
deps = ["Test"]
|
||||||
|
git-tree-sha1 = "0108ccdaea3ef69d9680eeafc8d5ad198b896ec8"
|
||||||
|
uuid = "2da68c74-98d7-5633-99d6-8493888d7b1e"
|
||||||
|
version = "0.3.0"
|
||||||
|
|
||||||
[[deps.TOML]]
|
[[deps.TOML]]
|
||||||
deps = ["Dates"]
|
deps = ["Dates"]
|
||||||
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
|
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
|
||||||
@@ -910,9 +1046,9 @@ version = "1.0.3"
|
|||||||
|
|
||||||
[[deps.TZJData]]
|
[[deps.TZJData]]
|
||||||
deps = ["Artifacts"]
|
deps = ["Artifacts"]
|
||||||
git-tree-sha1 = "72df96b3a595b7aab1e101eb07d2a435963a97e2"
|
git-tree-sha1 = "d3b30a9f29a898e21fb4bdf280101b7a12e1f39c"
|
||||||
uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7"
|
uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7"
|
||||||
version = "1.5.0+2025b"
|
version = "1.5.1+2025b"
|
||||||
|
|
||||||
[[deps.TableTraits]]
|
[[deps.TableTraits]]
|
||||||
deps = ["IteratorInterfaceExtensions"]
|
deps = ["IteratorInterfaceExtensions"]
|
||||||
@@ -922,14 +1058,9 @@ version = "1.0.1"
|
|||||||
|
|
||||||
[[deps.Tables]]
|
[[deps.Tables]]
|
||||||
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
|
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
|
||||||
git-tree-sha1 = "0f38a06c83f0007bbab3cf911262841c9a0f07e0"
|
git-tree-sha1 = "a94d9bdda1b7bed0046cea645639ab3f62196fac"
|
||||||
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
|
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
|
||||||
version = "1.13.0"
|
version = "1.14.0"
|
||||||
|
|
||||||
[[deps.Tar]]
|
|
||||||
deps = ["ArgTools", "SHA"]
|
|
||||||
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
|
|
||||||
version = "1.10.0"
|
|
||||||
|
|
||||||
[[deps.Test]]
|
[[deps.Test]]
|
||||||
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
|
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
|
||||||
@@ -952,9 +1083,9 @@ uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
|
|||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
|
|
||||||
[[deps.URIs]]
|
[[deps.URIs]]
|
||||||
git-tree-sha1 = "bef26fb046d031353ef97a82e3fdb6afe7f21b1a"
|
git-tree-sha1 = "908fec9df6c5de98548ead82a468c95ccf6cd263"
|
||||||
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
|
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
|
||||||
version = "1.6.1"
|
version = "1.7.0"
|
||||||
|
|
||||||
[[deps.UTCDateTimes]]
|
[[deps.UTCDateTimes]]
|
||||||
deps = ["Dates", "TimeZones"]
|
deps = ["Dates", "TimeZones"]
|
||||||
@@ -982,11 +1113,23 @@ git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7"
|
|||||||
uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60"
|
uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
|
||||||
|
[[deps.XML2_jll]]
|
||||||
|
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"]
|
||||||
|
git-tree-sha1 = "3f3315d89fc954a28f5b471bce698ed6e27481be"
|
||||||
|
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
|
||||||
|
version = "2.15.3+0"
|
||||||
|
|
||||||
|
[[deps.XMLDict]]
|
||||||
|
deps = ["EzXML", "IterTools", "OrderedCollections"]
|
||||||
|
git-tree-sha1 = "8e8cdc9c93e1f442eba471b56db6f86ffeb1a33a"
|
||||||
|
uuid = "228000da-037f-5747-90a9-8195ccbf91a5"
|
||||||
|
version = "0.4.2"
|
||||||
|
|
||||||
[[deps.YiemAgent]]
|
[[deps.YiemAgent]]
|
||||||
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "Serialization", "URIs", "UUIDs"]
|
deps = ["Base64", "CSV", "DataFrames", "DataStructures", "Dates", "HTTP", "JSON", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "Serialization", "URIs", "UUIDs", "msghandler"]
|
||||||
path = "."
|
path = "."
|
||||||
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
||||||
version = "0.4.2"
|
version = "0.8.0"
|
||||||
|
|
||||||
[[deps.Zlib_jll]]
|
[[deps.Zlib_jll]]
|
||||||
deps = ["Libdl"]
|
deps = ["Libdl"]
|
||||||
@@ -1010,6 +1153,14 @@ git-tree-sha1 = "011b0a7331b41c25524b64dc42afc9683ee89026"
|
|||||||
uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8"
|
uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8"
|
||||||
version = "1.0.21+0"
|
version = "1.0.21+0"
|
||||||
|
|
||||||
|
[[deps.msghandler]]
|
||||||
|
deps = ["Arrow", "Base64", "DataFrames", "Dates", "GeneralUtils", "HTTP", "JSON", "NATS", "PrettyPrinting", "UUIDs"]
|
||||||
|
git-tree-sha1 = "e7889a120327db84cdf1b1acf4a2c7caba6e8ef7"
|
||||||
|
repo-rev = "main"
|
||||||
|
repo-url = "https://git.yiem.cc/ton/msghandler"
|
||||||
|
uuid = "f2724d33-f338-4a57-b9f8-1be882570d10"
|
||||||
|
version = "1.4.0"
|
||||||
|
|
||||||
[[deps.nghttp2_jll]]
|
[[deps.nghttp2_jll]]
|
||||||
deps = ["Artifacts", "Libdl"]
|
deps = ["Artifacts", "Libdl"]
|
||||||
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
|
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
|
||||||
|
|||||||
+6
-6
@@ -1,9 +1,10 @@
|
|||||||
name = "YiemAgent"
|
name = "YiemAgent"
|
||||||
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
||||||
version = "0.4.3"
|
version = "0.9.1"
|
||||||
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
|
||||||
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
||||||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
||||||
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
||||||
@@ -11,23 +12,22 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
|
|||||||
GeneralUtils = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
GeneralUtils = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
||||||
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
|
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
|
||||||
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||||
LLMMCTS = "d76c5a4d-449e-4835-8cc4-dd86ec44f241"
|
|
||||||
LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
|
LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
|
||||||
NATS = "55e73f9c-eeeb-467f-b4cc-a633fde63d2a"
|
NATS = "55e73f9c-eeeb-467f-b4cc-a633fde63d2a"
|
||||||
PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337"
|
PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337"
|
||||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||||
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
||||||
SQLLLM = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
|
|
||||||
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
||||||
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
|
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
|
||||||
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
||||||
|
msghandler = "f2724d33-f338-4a57-b9f8-1be882570d10"
|
||||||
|
|
||||||
[compat]
|
[compat]
|
||||||
|
Base64 = "1.11.0"
|
||||||
CSV = "0.10.15"
|
CSV = "0.10.15"
|
||||||
DataFrames = "1.7.0"
|
DataFrames = "1.7.0"
|
||||||
GeneralUtils = "0.4.9"
|
GeneralUtils = "0.6.9"
|
||||||
HTTP = "2.4.0"
|
HTTP = "2.4.0"
|
||||||
JSON = "1.6.1"
|
JSON = "1.6.1"
|
||||||
LLMMCTS = "0.1.5"
|
|
||||||
NATS = "0.1.0"
|
NATS = "0.1.0"
|
||||||
SQLLLM = "0.2.7"
|
msghandler = "1.4.0"
|
||||||
|
|||||||
@@ -1,7 +1,311 @@
|
|||||||
version 0.1.0
|
# YiemAgent
|
||||||
TODO:
|
|
||||||
[WORKING] build MCTS() for planning
|
|
||||||
[] executeplan() to execute the plan
|
|
||||||
|
|
||||||
Change from version: 0.0.9
|
Julia framework for building agents with tool use and MCP (Model Context Protocol) support.
|
||||||
-
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```julia
|
||||||
|
using YiemAgent
|
||||||
|
|
||||||
|
# 1. Create an LLM callable (see "Custom LLM Callable" below)
|
||||||
|
llmCall = ...
|
||||||
|
|
||||||
|
# 2. Create a callable MCP server (see "MCP Server Integration" below)
|
||||||
|
mcpServer = ...
|
||||||
|
|
||||||
|
# 3. Create the agent
|
||||||
|
agent = yiemAgent(llmCall; mcpServer=mcpServer, eventSink=println)
|
||||||
|
|
||||||
|
# 4. Interact
|
||||||
|
promptAgent(agent, "What tools do you have?")
|
||||||
|
response = takeResponse(agent)
|
||||||
|
|
||||||
|
# 5. Stop when done
|
||||||
|
stopAgent(agent)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Agent API
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `promptAgent(agent, text)` | Send plain text to the agent's input channel |
|
||||||
|
| `promptAgent(agent, msg)` | Send a structured message (OpenAI format dict) to the input channel |
|
||||||
|
| `takeResponse(agent)` | Block and take the agent's response from output channel |
|
||||||
|
| `followUp(agent, text)` | Send plain text follow-up while agent is still processing |
|
||||||
|
| `followUp(agent, msg)` | Send a structured follow-up (OpenAI format dict) while agent is processing |
|
||||||
|
| `stopAgent(agent)` | Gracefully stop the agent and close channels |
|
||||||
|
|
||||||
|
## Creating a yiemAgent
|
||||||
|
|
||||||
|
```julia
|
||||||
|
yiemAgent(llmCall;
|
||||||
|
systemPrompt::String = "You are a helpful assistant.",
|
||||||
|
model = nothing,
|
||||||
|
messages::Vector{agentMessage} = agentMessage[],
|
||||||
|
prepareContext::Function = prepareContext,
|
||||||
|
formatMsgForLLM::Function = formatMsgForLLM,
|
||||||
|
beforeToolCall::Function = beforeToolCall,
|
||||||
|
afterToolCall::Function = afterToolCall,
|
||||||
|
sessionId::Union{String, Nothing} = nothing,
|
||||||
|
maxRetryDelayMs::Union{Int64, Nothing} = nothing,
|
||||||
|
parallelToolExecute::Bool = false,
|
||||||
|
eventSink::Function = eventSink,
|
||||||
|
mcpServer = nothing
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `llmCall` | **(required)** Function that receives formatted messages and returns an LLM response |
|
||||||
|
| `systemPrompt` | System prompt for the agent |
|
||||||
|
| `model` | LLM model configuration (`llmModel` struct) |
|
||||||
|
| `messages` | Initial conversation messages |
|
||||||
|
| `prepareContext` | Hook to transform messages/context before each LLM call |
|
||||||
|
| `formatMsgForLLM` | Hook to convert agent context to LLM API format |
|
||||||
|
| `beforeToolCall` | Hook to block/allow tool execution |
|
||||||
|
| `afterToolCall` | Hook to post-process tool results |
|
||||||
|
| `sessionId` | Optional session identifier for MCP server |
|
||||||
|
| `maxRetryDelayMs` | Maximum retry delay in milliseconds |
|
||||||
|
| `parallelToolExecute` | Run multiple tool calls in parallel |
|
||||||
|
| `eventSink` | Callback for agent events/debug messages |
|
||||||
|
| `mcpServer` | Callable struct for MCP server communication |
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Text Messages
|
||||||
|
|
||||||
|
```julia
|
||||||
|
# Send plain text
|
||||||
|
promptAgent(agent, "What tools do you have?")
|
||||||
|
response = takeResponse(agent)
|
||||||
|
|
||||||
|
# Follow-up while agent is processing
|
||||||
|
promptAgent(agent, "Tell me about wine")
|
||||||
|
followUp(agent, "Also include prices")
|
||||||
|
response = takeResponse(agent)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Structured Messages (OpenAI Format)
|
||||||
|
|
||||||
|
```julia
|
||||||
|
promptAgent(agent, Dict("role" => "user", "content" => "Hello"))
|
||||||
|
response = takeResponse(agent)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output Format
|
||||||
|
|
||||||
|
`takeResponse()` returns a `Dict{String, Any}` in OpenAI message format:
|
||||||
|
|
||||||
|
```julia
|
||||||
|
Dict(
|
||||||
|
"role" => "assistant",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "The weather in Tokyo is Sunny, 22°C."),
|
||||||
|
# ... more content blocks
|
||||||
|
]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Content block types:
|
||||||
|
|
||||||
|
| Block Type | Fields | Description |
|
||||||
|
|------------|--------|-------------|
|
||||||
|
| `text` | `"type" => "text"`, `"text" => "..."` | Plain text response from the LLM |
|
||||||
|
| `image_url` | `"type" => "image_url"`, `"image_url" => Dict("url" => "...")` | Image content (rare, used by multimodal models) |
|
||||||
|
|
||||||
|
Example — extracting the text response:
|
||||||
|
|
||||||
|
```julia
|
||||||
|
response = takeResponse(agent)
|
||||||
|
text_response = first(c["text"] for c in response["content"] if c["type"] == "text")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multimodal Messages (Text + Image)
|
||||||
|
|
||||||
|
```julia
|
||||||
|
# Encode images to data URIs
|
||||||
|
image_path = "test/image.png"
|
||||||
|
image_bytes = read(image_path)
|
||||||
|
base64_string = base64encode(image_bytes)
|
||||||
|
data_uri = "data:image/png;base64,$(base64_string)"
|
||||||
|
|
||||||
|
# Build message with text and image content blocks
|
||||||
|
message = Dict(
|
||||||
|
"role" => "user",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "Do you have Brunello di Montalcino?"),
|
||||||
|
Dict("type" => "image_url", "image_url" => Dict("url" => data_uri))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
promptAgent(agent, message)
|
||||||
|
response = takeResponse(agent)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Hooks
|
||||||
|
|
||||||
|
```julia
|
||||||
|
# beforeToolCall — request user approval for destructive tools
|
||||||
|
function my_before_tool_call(context::beforeToolCallContext, signal::abortSignal)::beforeToolCallResult
|
||||||
|
if context.toolCall.name == "deleteFile"
|
||||||
|
return beforeToolCallResult(true, "User must approve file deletion")
|
||||||
|
end
|
||||||
|
return beforeToolCallResult(false, "N/A")
|
||||||
|
end
|
||||||
|
|
||||||
|
# afterToolCall — mask sensitive data from results
|
||||||
|
function my_after_tool_call(context::afterToolCallContext, signal::abortSignal)::Union{agentToolResult, Nothing}
|
||||||
|
if context.isError
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("The tool encountered an error. Please try again.")],
|
||||||
|
context.result.details, context.result.usage, context.result.terminate
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
agent = yiemAgent(llmCall;
|
||||||
|
mcpServer=mcpServer,
|
||||||
|
beforeToolCall=my_before_tool_call,
|
||||||
|
afterToolCall=my_after_tool_call,
|
||||||
|
eventSink=println
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom LLM Callable
|
||||||
|
|
||||||
|
```julia
|
||||||
|
function my_llm_call(messages_dict::Dict{String, Any})
|
||||||
|
# messages_dict contains "messages" key with OpenAI-format message array,
|
||||||
|
# plus optional "tools", "temperature", etc.
|
||||||
|
|
||||||
|
# Call your LLM provider (OpenAI, Anthropic, local model, etc.)
|
||||||
|
raw_response = call_my_llm_provider(messages_dict)
|
||||||
|
|
||||||
|
# Return a dict with these fields:
|
||||||
|
return Dict(
|
||||||
|
"message" => Dict("role" => "assistant", "content" => raw_response["content"]),
|
||||||
|
"finish_reason" => raw_response["finish_reason"],
|
||||||
|
"api" => "my_provider",
|
||||||
|
"provider" => "my_provider",
|
||||||
|
"model" => "my-model-name",
|
||||||
|
"usage" => llmUsage(100, 50)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Context Preparation
|
||||||
|
|
||||||
|
```julia
|
||||||
|
function my_prepare_context(state::agentState, eventSink)::agentContext
|
||||||
|
sysPrompt = state.systemPrompt * "\nCurrent time: $(now())"
|
||||||
|
msgs = deepcopy(state.messages)
|
||||||
|
|
||||||
|
# Prune old messages to fit context window
|
||||||
|
max_messages = 20
|
||||||
|
if length(msgs) > max_messages
|
||||||
|
msgs = msgs[end-max_messages+1:end]
|
||||||
|
end
|
||||||
|
|
||||||
|
return agentContext(sysPrompt, msgs, state.tools)
|
||||||
|
end
|
||||||
|
|
||||||
|
agent = yiemAgent(llmCall; prepareContext=my_prepare_context, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
## MCP Server Integration
|
||||||
|
|
||||||
|
YiemAgent uses JSON-RPC 2.0 for MCP communication. The `mcpServer` callable struct must implement `tools/list` and `tools/call` methods.
|
||||||
|
|
||||||
|
### Protocol
|
||||||
|
|
||||||
|
```julia
|
||||||
|
# tools/list — returns tool definitions
|
||||||
|
mcpServer("tools/list")
|
||||||
|
# → Dict("jsonrpc"=>"2.0", "id"=>1, "result"=>Dict("tools"=>[...], "nextCursor"=>...))
|
||||||
|
|
||||||
|
# tools/call — executes a tool
|
||||||
|
mcpServer("tools/call", toolName, arguments)
|
||||||
|
# → Dict("jsonrpc"=>"2.0", "id"=>2, "result"=>Dict("content"=>[...], "isError"=>...))
|
||||||
|
|
||||||
|
# Protocol error responses include "error" instead of "result":
|
||||||
|
Dict("jsonrpc"=>"2.0", "id"=>2, "error"=>Dict("code"=>-32602, "message"=>"..."))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: NATS-based MCP Server Client
|
||||||
|
|
||||||
|
```julia
|
||||||
|
struct mcpServer
|
||||||
|
natsConn::NATS.Connection
|
||||||
|
topic::String
|
||||||
|
senderID::String
|
||||||
|
fileserver_url::String
|
||||||
|
end
|
||||||
|
|
||||||
|
# tools/list implementation
|
||||||
|
function (c::mcpServer)(method::String)
|
||||||
|
payload = Dict("jsonrpc" => "2.0", "id" => 1, "method" => method, "params" => Dict{String, Any}())
|
||||||
|
payloads = [("payload", payload, "dictionary")]
|
||||||
|
_, msg_envelope = msghandler.smartpack(c.topic, payloads;
|
||||||
|
sender_id=c.senderID, msg_purpose="mcp_tools_list", fileserver_url=c.fileserver_url)
|
||||||
|
reply = NATS.request(c.natsConn, c.topic, msg_envelope, timeout=180)
|
||||||
|
incoming = msghandler.smartunpack(String(reply.payload))
|
||||||
|
return incoming["payloads"][1][2]
|
||||||
|
end
|
||||||
|
|
||||||
|
# tools/call implementation
|
||||||
|
function (c::mcpServer)(method::String, toolName::String, arguments::Dict{String, Any})
|
||||||
|
payload = Dict("jsonrpc" => "2.0", "id" => 2, "method" => method,
|
||||||
|
"params" => Dict("name" => toolName, "arguments" => arguments))
|
||||||
|
payloads = [("payload", payload, "dictionary")]
|
||||||
|
_, msg_envelope = msghandler.smartpack(c.topic, payloads;
|
||||||
|
sender_id=c.senderID, msg_purpose="mcp_tool_call", fileserver_url=c.fileserver_url)
|
||||||
|
reply = NATS.request(c.natsConn, c.topic, msg_envelope, timeout=180)
|
||||||
|
incoming = msghandler.smartunpack(String(reply.payload))
|
||||||
|
return incoming["payloads"][1][2]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tool Discovery
|
||||||
|
|
||||||
|
Tools are discovered dynamically via the MCP server:
|
||||||
|
|
||||||
|
1. `listTools` is the only pre-registered tool at agent creation
|
||||||
|
2. When the LLM calls `listTools()`, tools are discovered from the MCP server and registered at runtime
|
||||||
|
3. Supports pagination via `nextCursor` for large tool sets
|
||||||
|
4. New tools become immediately available without restarting the agent
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
Hooks allow customization of the agent's behavior at key points in the execution pipeline.
|
||||||
|
|
||||||
|
| Hook | Signature | Purpose |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| `prepareContext` | `(state::agentState, eventSink) -> agentContext` | Transform messages/context before LLM call (filter tools, inject context, prune messages) |
|
||||||
|
| `formatMsgForLLM` | `(ctx::agentContext, eventSink) -> Dict` | Convert agent context to LLM API message format |
|
||||||
|
| `beforeToolCall` | `(context::beforeToolCallContext, signal::abortSignal) -> beforeToolCallResult` | Block/allow tool execution; request user approval |
|
||||||
|
| `afterToolCall` | `(context::afterToolCallContext, signal::abortSignal) -> Union{agentToolResult, Nothing}` | Post-process tool results; mask sensitive data; set `terminate` flag |
|
||||||
|
| `eventSink` | `(msg) -> nothing` | Callback for agent events/debug messages throughout the pipeline |
|
||||||
|
|
||||||
|
## Agent Lifecycle
|
||||||
|
|
||||||
|
1. **Creation** — `yiemAgent()` spawns a background task (`_agentLoop`) listening on `inputChannel` and `followUpChannel`
|
||||||
|
2. **Input** — User messages enter via `promptAgent()` (immediate) or `followUp()` (queued while agent is busy)
|
||||||
|
3. **Processing** — `_processMessage()` handles LLM calls, tool execution, and conversation history in a loop
|
||||||
|
4. **Tool Execution Pipeline** — each tool call follows three phases:
|
||||||
|
- `prepareToolCall` — resolve tool, validate arguments, run `beforeToolCall` hook
|
||||||
|
- `executePreparedToolCall` — invoke `tool.execute()`
|
||||||
|
- `finalizeExecutedToolCall` — run `afterToolCall` hook, construct `toolResultMessage`
|
||||||
|
5. **Termination** — tool `execute` functions can set `terminate=true` to stop the agent loop
|
||||||
|
6. **Shutdown** — `stopAgent()` sends a `:shutdown` signal, drains channels, and closes the agent
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── YiemAgent.jl # Module entry point
|
||||||
|
├── type.jl # Core types (messages, tools, agent state, events)
|
||||||
|
├── utils.jl # Message formatting, context preparation, validation
|
||||||
|
├── toolRegistry.jl # Tool store, MCP discovery, listTools registration
|
||||||
|
├── agentCore.jl # Agent loop, tool execution pipeline, yiemAgent struct
|
||||||
|
└── api.jl # Public API (promptAgent, takeResponse, followUp, stopAgent)
|
||||||
|
```
|
||||||
|
|||||||
+1
-1
@@ -54,7 +54,7 @@ Your name is $(newAgent.name). You are a helpful sommelier for website-based $(n
|
|||||||
# Available Actions
|
# Available Actions
|
||||||
|
|
||||||
- **CHAT_BOX** which you can use to talk with the user.
|
- **CHAT_BOX** which you can use to talk with the user.
|
||||||
- **CHECK_WINE** allows you to check information about wines you want in your inventory's database. The input is text that specify supported search criteria includeing: retailer_name, wine price, winery, name, vintage, region, country, type, grape varietal, tasting notes, occasion, food pairing, intensity, tannin, sweetness, and acidity.
|
- **SEARCH_WINE_DATABASE** allows you to check information about wines you want in your inventory's database. The input is text that specify supported search criteria includeing: retailer_name, wine price, winery, name, vintage, region, country, type, grape varietal, tasting notes, occasion, food pairing, intensity, tannin, sweetness, and acidity.
|
||||||
- Example query 1: "Dry, full-bodied red wine from 1) region: Burgundy, country: France or 2) region: Tuscany, country: Italy. Grape varietal: Merlot or Syrah. price 100 to 1000 USD."
|
- Example query 1: "Dry, full-bodied red wine from 1) region: Burgundy, country: France or 2) region: Tuscany, country: Italy. Grape varietal: Merlot or Syrah. price 100 to 1000 USD."
|
||||||
- Example query 2: "Red or white wine, medium tannin, price under 700 USD"
|
- Example query 2: "Red or white wine, medium tannin, price under 700 USD"
|
||||||
- Example query 3: "white wine, region: Tuscany or Bordeaux, country: Italy or France
|
- Example query 3: "white wine, region: Tuscany or Bordeaux, country: Italy or France
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"testingOrProduction": "testing",
|
"testingOrProduction": "testing",
|
||||||
"agentId": "2b74b87a-5413-4fe2-a4d3-405891051680",
|
"agentId": "2b74b87a-5413-4fe2-a4d3-405891051680",
|
||||||
"agentCentralConfigSubject": "/yiem/hq/agent/sommelier/backend/config/api/v1.1",
|
"agentCentralConfigSubject": "/yiem/hq/agent/sommelier/backend/config/api/v1.1",
|
||||||
|
"this_service_name": "agent_backend",
|
||||||
"this_service_input_channel": {
|
"this_service_input_channel": {
|
||||||
"mqtt": [
|
"mqtt": [
|
||||||
"/yiem/hq/agent/sommpanion/backend/db/api_v1"
|
"/yiem/hq/agent/sommpanion/backend/db/api_v1"
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"agentRole": "sommelier",
|
"agentRole": "sommelier",
|
||||||
"organization": "yiem_hq",
|
"organization": "yiem_hq",
|
||||||
"externalService": {
|
"externalservice": {
|
||||||
"servicesloadbalancer": {
|
"servicesloadbalancer": {
|
||||||
"nats": "sommpanion.backend.servicesloadbalancer.v1.inbox"
|
"nats": "sommpanion.backend.servicesloadbalancer.v1.inbox"
|
||||||
},
|
},
|
||||||
@@ -35,15 +36,15 @@
|
|||||||
"description": "A database connection info for LibPQ client",
|
"description": "A database connection info for LibPQ client",
|
||||||
"url": "192.168.88.106:5432",
|
"url": "192.168.88.106:5432",
|
||||||
"dbname": "winedb",
|
"dbname": "winedb",
|
||||||
"user": "yiemtechnologies@gmail.com",
|
"user": "admin",
|
||||||
"password": "yiemtechnologies@Postgres_0.0"
|
"password": "admin@Sommpanion_0.0"
|
||||||
},
|
},
|
||||||
"sommpanion_vectordb" : {
|
"sommpanion_vectordb" : {
|
||||||
"description": "A wine database connection info for LibPQ client",
|
"description": "A wine database connection info for LibPQ client",
|
||||||
"url": "192.168.88.106:5433",
|
"url": "192.168.88.106:5433",
|
||||||
"dbname": "vectordb",
|
"dbname": "vectordb",
|
||||||
"user": "yiemtechnologies@gmail.com",
|
"user": "admin",
|
||||||
"password": "yiemtechnologies@Postgres_0.0"
|
"password": "admin@Sommpanion_0.0"
|
||||||
},
|
},
|
||||||
"fileserver": {
|
"fileserver": {
|
||||||
"description": "temporary file server",
|
"description": "temporary file server",
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
# YiemAgent: Agile Agent Framework
|
||||||
|
|
||||||
|
## 1. Business Context & Success Metrics
|
||||||
|
|
||||||
|
### Business Goal
|
||||||
|
|
||||||
|
YiemAgent is a Julia-based AI agent framework that enables building chatbot assistants with tool-calling capabilities, model-agnostic LLM integration, and runtime tool discovery via MCP (Model Context Protocol). It provides an asynchronous agent loop with channel-based message passing, supporting both single-turn and multi-turn conversations with automatic tool call resolution.
|
||||||
|
|
||||||
|
### User Stories
|
||||||
|
|
||||||
|
- **US-001**: As a developer, I want to create an AI agent with a simple constructor so that I can quickly prototype chatbot functionality in Julia
|
||||||
|
- **US-002**: As a developer, I want the agent to discover tools automatically from an MCP server so that I don't need to manually register tool APIs
|
||||||
|
- **US-003**: As a developer, I want the agent to handle tool calls automatically in a loop so that the LLM can use tools without external orchestration
|
||||||
|
- **US-004**: As a developer, I want to intercept tool calls with before/after hooks so that I can add user approval, logging, or result sanitization
|
||||||
|
- **US-005**: As a developer, I want the framework to support multiple LLM models and providers through a pluggable `llmCall` interface so that I can swap models without changing agent logic
|
||||||
|
|
||||||
|
### KPIs & Targets
|
||||||
|
|
||||||
|
- **KPI-001**: Agent loop processes messages with <500ms overhead for sequential tool calls (validates US-003)
|
||||||
|
- **KPI-002**: MCP tool discovery completes within 2 seconds for 50+ tools (validates US-002)
|
||||||
|
- **KPI-003**: 100% of message types have OpenAI format conversion (validates US-001)
|
||||||
|
|
||||||
|
## 2. Technical Boundaries
|
||||||
|
|
||||||
|
### In Scope
|
||||||
|
|
||||||
|
- Julia module implementing an AI agent with asynchronous message processing
|
||||||
|
- Channel-based input (user→agent), follow-up (async queue), and output (agent→user) interfaces
|
||||||
|
- LLM model abstraction with tool call extraction from multiple response formats (OpenAI content blocks, LMStudio/vLLM format)
|
||||||
|
- Tool registry with MCP server integration (tools/list, tools/call via JSON-RPC 2.0)
|
||||||
|
- Agent loop with before/after tool call hooks for lifecycle management
|
||||||
|
- Message type system with OpenAI format conversion utilities
|
||||||
|
- Abort signal and termination flag support via tool implementations
|
||||||
|
|
||||||
|
### Out of Scope
|
||||||
|
|
||||||
|
- GUI/CLI interface (exposed via channels only; UI layer is external)
|
||||||
|
- Persistent chat history or long-term memory (TODO comments in code)
|
||||||
|
- Multi-agent orchestration (single agent instance per `yiemAgent` struct)
|
||||||
|
- Embedding generation or retrieval-augmented generation (RAG)
|
||||||
|
- Natural language-to-code generation or execution sandboxes
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- **Julia Standard Library**: `Dates`, `UUIDs`, `Base.Threads`
|
||||||
|
- **External Packages**: `JSON`, `DataStructures`, `HTTP`, `PrettyPrinting`, `Serialization`, `DataFrames`
|
||||||
|
- **Internal Package**: `GeneralUtils` (project-specific utility library)
|
||||||
|
- **Optional**: `NATS` (MCP server communication), `LibPQ` (PostgreSQL)
|
||||||
|
|
||||||
|
### Deployment Constraints
|
||||||
|
|
||||||
|
- **NFR-001**: Library designed for embedded use in Docker containers (MicroK8s cluster, per `NFR-501` from template)
|
||||||
|
- **NFR-002**: Must support Julia 1.9+ for module include/export semantics compatibility
|
||||||
|
- **NFR-003**: Thread-safe channel operations; single-agent instance processes messages sequentially on background `@spawn` task
|
||||||
|
|
||||||
|
## 3. Functional Requirements (FR)
|
||||||
|
|
||||||
|
### FR-001: Agent Message Processing
|
||||||
|
|
||||||
|
The agent shall process user messages through an asynchronous loop that:
|
||||||
|
- Accepts messages via `inputChannel` (capacity 16) and `followUpChannel` (capacity 32)
|
||||||
|
- Prioritizes `inputChannel` messages over `followUpChannel` messages
|
||||||
|
- Feeds responses to `outputChannel` (capacity 16) in OpenAI message format
|
||||||
|
- Exits cleanly on `:shutdown` signal
|
||||||
|
|
||||||
|
**Traceability**: US-001, US-003
|
||||||
|
|
||||||
|
### FR-002: LLM Model Agnosticism
|
||||||
|
|
||||||
|
The agent shall accept a pluggable `llmCall` function that:
|
||||||
|
- Receives formatted messages and returns an LLM response dict
|
||||||
|
- The response dict may contain `message.tool_calls` (LMStudio format) or `content` blocks (OpenAI format)
|
||||||
|
- The framework extracts tool calls regardless of response format
|
||||||
|
|
||||||
|
**Traceability**: US-005
|
||||||
|
|
||||||
|
### FR-003: Tool Call Execution Loop
|
||||||
|
|
||||||
|
The agent shall automatically execute tool calls returned by the LLM:
|
||||||
|
- Extract tool calls via `_extractToolCalls()` which supports both `message.tool_calls` and `content[{type="tool_calls"}]` formats
|
||||||
|
- Build `agentContext` via `prepareContext()` (pluggable hook)
|
||||||
|
- Format messages via `formatMsgForLLM()` to OpenAI format
|
||||||
|
- Extract tool calls → loop: execute tool calls → append results to history → call LLM again
|
||||||
|
- Break loop when LLM response has no tool calls
|
||||||
|
- Support `terminate` flag: if `toolResultBatch.terminate == true`, break loop and return final response
|
||||||
|
|
||||||
|
**Traceability**: US-003
|
||||||
|
|
||||||
|
### FR-004: Tool Discovery and Registration
|
||||||
|
|
||||||
|
The agent shall:
|
||||||
|
- Query MCP server via `mcpserver("tools/list")` (JSON-RPC 2.0) to discover tools
|
||||||
|
- Support pagination via `nextCursor` field
|
||||||
|
- Wrap each MCP tool definition as an `agentTool` with `execute` hook that calls `mcpserver("tools/call", name, args, sessionId)`
|
||||||
|
- Register a builtin `listTools` tool that triggers runtime discovery and lists all available tools
|
||||||
|
- Skip tools already registered (idempotent)
|
||||||
|
|
||||||
|
**Traceability**: US-002
|
||||||
|
|
||||||
|
### FR-005: Tool Call Lifecycle Hooks
|
||||||
|
|
||||||
|
The agent shall support two lifecycle hooks:
|
||||||
|
- `beforeToolCall(beforeToolCallContext, signal)` — can block execution by returning `beforeToolCallResult(true, reason)`
|
||||||
|
- `afterToolCall(afterToolCallContext)` — can transform result by returning modified `agentToolResult`
|
||||||
|
- Errors from hooks are caught and converted to `immediateOutcome`/error tool result messages (never throw)
|
||||||
|
|
||||||
|
**Traceability**: US-004
|
||||||
|
|
||||||
|
### FR-006: Message Type Conversion
|
||||||
|
|
||||||
|
The agent shall:
|
||||||
|
- Convert internal message types (`userMessage`, `assistantMessage`, `assistantMessageToolCall`, `toolResultMessage`) to OpenAI format via `_userMessageToOpenAI()`, `_assistantMessageToOpenAI()`, `_assistantMessageToolCallToOpenAI()`, `_toolResultMessageToOpenAI()`
|
||||||
|
- Convert internal `messageContent` types (`textContent`, `imageContent`, `reasoningContent`) to OpenAI content blocks via `_messageContentToBlocks()`
|
||||||
|
- Convert `agentTool` definitions to OpenAI function tool format via `_toolsToOpenAI()`
|
||||||
|
|
||||||
|
**Traceability**: US-001
|
||||||
|
|
||||||
|
## 4. Non-Functional Requirements (NFRs)
|
||||||
|
|
||||||
|
### 4.1 Performance & Scalability
|
||||||
|
|
||||||
|
- **NFR-101**: Agent loop message processing overhead <500ms per sequential tool call (measured KPI: KPI-001)
|
||||||
|
- **NFR-102**: MCP tool discovery for 50+ tools completes within 2 seconds (measured KPI: KPI-002)
|
||||||
|
- **NFR-103**: Channel buffers prevent unbounded memory growth — `inputChannel` (16), `followUpChannel` (32), `outputChannel` (16)
|
||||||
|
|
||||||
|
### 4.2 Availability & Reliability
|
||||||
|
|
||||||
|
- **NFR-201**: Agent loop catches all errors via `try/catch` in `_agentLoop()` and calls `eventSink(errMsg)` before exit (measured by event sink logging)
|
||||||
|
- **NFR-202**: `prepareToolCall()` never throws errors — validation failures return `immediateOutcome` with error tool result
|
||||||
|
|
||||||
|
### 4.3 Privacy & Security
|
||||||
|
|
||||||
|
- **Data Classification**: Tool arguments and results may contain PII (user messages); MCP tool results treated as untrusted input
|
||||||
|
- **No built-in auth/authz**: Responsibility of `mcpServer` implementation and `beforeToolCall` hook
|
||||||
|
- **Input validation**: JSON schema validation via `validateToolArguments()` checks required fields only (tools need custom validation via `validateRequiredArgs` hook)
|
||||||
|
|
||||||
|
### 4.4 Observability & Telemetry
|
||||||
|
|
||||||
|
- **Required Logs**: `agentLoop` events (via `eventSink`) emit state machine steps (`_agentLoop 1` through `_agentLoop 6`), `_processMessage` steps (1 through 18), `prepareToolCall` steps (1 through 10)
|
||||||
|
- **Critical Metrics**:
|
||||||
|
- `agent._state.messages.length` — conversation history size (logged at each step)
|
||||||
|
- `toolExecutionStart`/`toolExecutionEnd` events (via `toolExecStartEvent`, `toolExecEndEvent` structs)
|
||||||
|
- **Retention**: No built-in log retention; agent is a library, does not manage external logging
|
||||||
|
|
||||||
|
## 5. Acceptance Conditions
|
||||||
|
|
||||||
|
- [ ] **FR-001**: Agent processes messages through channel system with priority ordering (inputChannel > followUpChannel)
|
||||||
|
- [ ] **FR-002**: `llmCall` is pluggable; response formats supported include `message.tool_calls` dict and `content` array blocks
|
||||||
|
- [ ] **FR-003**: Agent loops on tool calls until LLM returns no tool calls or terminate flag is set
|
||||||
|
- [ ] **FR-004**: MCP tools are discovered via `tools/list` with pagination; all registered tools are callable via `tools/call`
|
||||||
|
- [ ] **FR-005**: `beforeToolCall` hook can block; `afterToolCall` hook can transform result; errors return error tool result
|
||||||
|
- [ ] **FR-006**: All message types (userMessage, assistantMessage, assistantMessageToolCall, toolResultMessage) convert to OpenAI format
|
||||||
|
- [ ] **NFR-101**: Message processing overhead <500ms per sequential tool call
|
||||||
|
- [ ] **NFR-102**: MCP discovery for 50+ tools completes <2 seconds
|
||||||
|
- [ ] **NFR-201**: Agent loop catches errors and exits gracefully without panics
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
# Solution Design: YiemAgent Agent Framework
|
||||||
|
|
||||||
|
## 1. Problem Decomposition
|
||||||
|
|
||||||
|
| Problem | Description | User Impact |
|
||||||
|
|---------|-------------|-------------|
|
||||||
|
| P-001 | Developers need a chatbot framework in Julia that handles LLM calls and tool calls without writing boilerplate | Must write separate request/response handling, tool call parsing, and loop management code for each agent |
|
||||||
|
| P-002 | Tool discovery is manual and repetitive | Must register every tool individually; no automatic discovery from MCP-compatible servers |
|
||||||
|
| P-003 | Multiple LLM providers have different response formats | Must handle `message.tool_calls` (LMStudio/vLLM) vs `content` blocks (OpenAI API) separately |
|
||||||
|
| P-004 | Tool execution needs lifecycle hooks for observability and control | No built-in before/after hooks for authorization, logging, or result sanitization |
|
||||||
|
| P-005 | Agent state management is error-prone | Must handle message history, tool registration, and pending tool call tracking manually |
|
||||||
|
|
||||||
|
## 2. Solution Approach
|
||||||
|
|
||||||
|
**Approach**: "Build a channel-based agent system where the LLM talks to tools automatically, tools talk to MCP servers automatically, and you compose agents by injecting `llmCall` and `mcpServer` functions — the framework owns the loop, the state, and the format conversion."
|
||||||
|
|
||||||
|
**Key Principles**:
|
||||||
|
- **Channel-based async processing**: Each agent owns three channels (`inputChannel`, `followUpChannel`, `outputChannel`); a background `@spawn` task runs the agent loop, processing messages in priority order
|
||||||
|
- **Tool call loop**: The agent calls `llmCall()`, extracts tool calls, executes them (sequentially or in parallel), appends results, and calls `llmCall()` again — repeating until no tool calls remain
|
||||||
|
- **Pluggable hooks**: `prepareContext`, `formatMsgForLLM`, `beforeToolCall`, `afterToolCall`, and `llmCall` are all injectable functions — the framework provides sensible defaults, but you override what you need
|
||||||
|
- **MCP-native tool discovery**: Tools are discovered at startup via MCP `tools/list` and can be discovered at runtime via the `listTools` builtin tool (idempotent registration with pagination support)
|
||||||
|
- **Format-agnostic LLM responses**: `_extractToolCalls()` handles both OpenAI content blocks and LMStudio/vLLM nested message format, allowing the same agent to work with any model provider
|
||||||
|
|
||||||
|
## 3. Decision Rationale
|
||||||
|
|
||||||
|
| Decision ID | Decision | Rationale | Alternatives Rejected |
|
||||||
|
|-------------|----------|-----------|----------------------|
|
||||||
|
| SD-001 | Use channel-based async processing with `@spawn` task | Provides non-blocking message queues; `inputChannel` blocks if full (backpressure); `followUpChannel` allows async follow-ups while agent is busy | Threading with shared state — race conditions; callback-based async — hard to compose |
|
||||||
|
| SD-002 | Background `_agentLoop` task runs message dispatch loop | Isolates message processing from construction; agent can be created and used concurrently | Synchronous agent.run(message) — blocks caller; manual task management — boilerplate |
|
||||||
|
| SD-003 | `_processMessage` runs `llmCall()` in an inner loop (while loop) until no tool calls remain | Handles multi-turn tool use transparently — agent decides when to stop based on LLM response | External loop in caller code — duplicates agent loop logic; single-turn only — no multi-step tool use |
|
||||||
|
| SD-004 | MCP server integration via callable struct `mcpServer(method, args...)` | MCP is model-agnostic protocol; callable struct allows type-stable single dispatch; supports NATS transport (struct has `natsConn`, `topic`, `senderID` fields) | Ad-hoc HTTP client for MCP — breaks NATS-only transports; tight coupling to specific transport |
|
||||||
|
| SD-005 | Tool call lifecycle: `prepareToolCall()` → `executePreparedToolCall()` → `finalizeExecutedToolCall()` — three phases, never throw errors | Preparation validates args and runs `beforeToolCall` hook (can block); execution calls `tool.execute()`; finalization runs `afterToolCall` hook. Each phase returns a result type, not exceptions — agent loop catches all and builds error tool result | `try/catch` in loop — loses context; throwing errors breaks tool result message contract |
|
||||||
|
| SD-006 | Message types are Julia structs with keyword constructors (`userMessage(content=[...])`) | Type stable; keyword constructors provide defaults (`role="user"`, `timestamp=now()`); supports both positional and keyword usage | `Dict{String, Any}` for messages — lose type safety, docgen, IDE support |
|
||||||
|
| SD-007 | `eventSink` function passed to all deep lifecycle calls | Provides hooks for external monitoring (logging, metrics, NATS pub/sub) without modifying core logic | Built-in `println` — can't disable or redirect; no metrics integration |
|
||||||
|
| SD-008 | `abortSignal` struct with `.aborted::Bool` field passed to `prepareToolCall()` and hooks | Simple mutable state for interrupting long-running tool calls (no async cancellation — just flag check) | Threading with `Task.cancel()` — Julia thread model doesn't support it; `@async` spawn — complex cleanup |
|
||||||
|
| SD-009 | `_extractToolCalls()` supports both `message.tool_calls` and `content[{type="tool_calls"}]` formats | LMStudio.jl/vLLM vs OpenAI API have different response shapes; framework must work with both | Separate parser per provider — maintenance burden; require provider wrapper — adds indirection |
|
||||||
|
|
||||||
|
## 4. High-Level Component Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
User[External Caller] -->|promptAgent / followUp| InputCh[inputChannel]
|
||||||
|
|
||||||
|
subagent AgentLoop [Agent Loop Task]
|
||||||
|
InputCh -->|take!| Disp[Message Dispatcher]
|
||||||
|
Disp -->|spawn| Proc[_processMessage]
|
||||||
|
Proc -->|Channel| PrCh[processMessageInputCh]
|
||||||
|
end
|
||||||
|
|
||||||
|
subagent Message Pipeline
|
||||||
|
PrCh -->|put!| DMsg[Drain input, convert to userMessage]
|
||||||
|
DMsg --> PC[prepareContext hook]
|
||||||
|
PC --> FM[formatMsgForLLM hook]
|
||||||
|
FM --> LLM[llmCall]
|
||||||
|
LLM --> EC[_extractToolCalls]
|
||||||
|
EC -->|hasToolCalls| TC[executeToolCalls]
|
||||||
|
TC -->|toolResultBatch| TM[toolResultMessage]
|
||||||
|
TM -->|push!| MH[messageHistory]
|
||||||
|
TC -->|no tool calls break| BRK[break loop]
|
||||||
|
BRK --> End[exit]
|
||||||
|
end
|
||||||
|
|
||||||
|
subagent Tool System
|
||||||
|
TC --> Prep[prepareToolCall]
|
||||||
|
Prep --> Exec[executePreparedToolCall]
|
||||||
|
Exec --> Term[finalizeExecutedToolCall]
|
||||||
|
Term --> CRM[createToolResultMessage]
|
||||||
|
CRM --> TM
|
||||||
|
end
|
||||||
|
|
||||||
|
subagent MCP Integration
|
||||||
|
Prep --> ToolLookup[context.tools lookup]
|
||||||
|
ToolLookup --> MCP[MCP tools/list + tools/call]
|
||||||
|
MCP --> Wr[_wrap_mcp_tool]
|
||||||
|
Wr --> Reg[toolRegistry.registerAllTools]
|
||||||
|
end
|
||||||
|
|
||||||
|
OutputCh[outputChannel] --> User
|
||||||
|
Proc -->|final response| OutputCh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Component Descriptions**:
|
||||||
|
- **Agent Loop Task** (`SD-001`, `SD-002`): Runs `@spawn _agentLoop()`; manages three channels; dispatches messages to `_processMessage`
|
||||||
|
- **Message Pipeline** (`SD-003`, `SD-004`): `prepareContext` → `formatMsgForLLM` → `llmCall` → extract tool calls → loop
|
||||||
|
- **Tool System** (`SD-005`): Three-phase lifecycle (prepare → execute → finalize); never throws; returns error tool result on failure
|
||||||
|
- **MCP Integration** (`SD-004`): `tools/list` discovery with pagination; `_wrap_mcp_tool` creates executable `agentTool` with MCP JSON-RPC calls
|
||||||
|
|
||||||
|
## 5. Alternatives Considered
|
||||||
|
|
||||||
|
| Alternative | Pros | Cons | Decision |
|
||||||
|
|-------------|------|------|----------|
|
||||||
|
| Synchronous `agent.process(message)` | Simpler API; no channels or tasks | Blocks caller; no follow-up support; harder to test concurrency | **Rejected** — async channels needed for real-time chat UX |
|
||||||
|
| JSON dicts for messages everywhere | Flexible; no type definitions | No static checking; no docgen; no IDE autocomplete; harder to serialize/deserialize | **Rejected** — Julia advantages of structs (type-stable, documented) outweigh cost |
|
||||||
|
| Single `onToolCall` hook (pre-execute only) | Simpler hook interface | Can't sanitize results post-execution; can't set terminate flag after tool completes | **Rejected** — `afterToolCall` needed for masking secrets, normalizing usage, inspecting error status |
|
||||||
|
| Direct HTTP calls to MCP servers (no NATS) | Simpler dependency (no NATS) | Breaks if MCP uses NATS (common in microservices); no single transport | **Rejected** — callable struct abstracts transport; NATS is one implementation option |
|
||||||
|
| External orchestrator runs tool call loop | Caller has full control | Duplicates logic; every caller must implement same loop; harder to maintain | **Rejected** — loop is agent framework responsibility (single source of truth) |
|
||||||
|
|
||||||
|
## 6. Risk Assessment
|
||||||
|
|
||||||
|
| Risk | Impact | Probability | Mitigation |
|
||||||
|
|------|--------|-------------|------------|
|
||||||
|
| `llmCall` throws (network timeout, API error) | Agent loop exits with error in `eventSink` | Medium | `try/catch` in `_processMessage` and `_agentLoop`; eventSink receives error string |
|
||||||
|
| Tool `prepareArguments` or `validateRequiredArgs` hook throws | `prepareToolCall` catches and returns `immediateOutcome` error | Low | Hook errors caught; error tool result sent to LLM for recovery |
|
||||||
|
| MCP server unreachable | Tool discovery returns 0 tools; `listTools` still available | Medium | `register_mcp_tools` returns 0 on error; tool execution calls catch errors |
|
||||||
|
| Long-running tool call hangs | Caller blocks on `outputChannel`; no built-in timeout | Medium | `eventSink` passes abort signal; caller sets `signal.aborted = true` externally |
|
||||||
|
| Multiple agents share same `mcpServer` struct | `mcpServer` must be thread-safe for concurrent tool calls | Low | Each thread gets own `mcpServer` instance; NATS handles concurrency |
|
||||||
|
| Message history grows unbounded | Memory exhaustion in long sessions | Medium | No built-in pruning; `prepareContext` can filter/prune messages (TODO in code) |
|
||||||
|
|
||||||
|
## 7. Requirements Traceability
|
||||||
|
|
||||||
|
| Solution Component | Requirement ID | Decision ID | Description |
|
||||||
|
|-------------------|----------------|-------------|-------------|
|
||||||
|
| Channel-based async processing | FR-001 | SD-001, SD-002 | `inputChannel`, `followUpChannel`, `outputChannel` with priority dispatch |
|
||||||
|
| LLM call loop | FR-003 | SD-003 | `_processMessage` while loop; `llmCall()` → extract → execute → append → repeat |
|
||||||
|
| MCP tool discovery | FR-004 | SD-004, SD-009 | `tools/list` with pagination; `tools/call` execution |
|
||||||
|
| Lifecycle hooks | FR-005 | SD-005 | `beforeToolCall` can block; `afterToolCall` transforms; never throw |
|
||||||
|
| Message type conversion | FR-006 | SD-006 | Julia structs with keyword constructors; `_userMessageToOpenAI`, `_messageContentToBlocks` |
|
||||||
|
| Format-agnostic extraction | FR-002 | SD-009 | `_extractToolCalls` handles both `message.tool_calls` and `content` blocks |
|
||||||
|
| Event sink observability | NFR-201, NFR-103 | SD-007 | `eventSink` passed to all lifecycle steps; logs state machine transitions |
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
# Walkthrough: YiemAgent Agent Framework
|
||||||
|
|
||||||
|
## Structural Guidance
|
||||||
|
|
||||||
|
- One **overview sequence diagram** per participant (high-level flow)
|
||||||
|
- **Detailed sub-diagrams** for complex interaction points (tool call loop, MCP discovery)
|
||||||
|
- Use sequence diagrams for request/response patterns; lifecycle states for multi-step operations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## External Caller
|
||||||
|
|
||||||
|
_The Developer calling `YiemAgent.jl` via `promptAgent`, `takeResponse`, `followUp`, and `stopAgent`._
|
||||||
|
|
||||||
|
### Session Start: Create Agent
|
||||||
|
|
||||||
|
**WT-001**: Developer creates an `yiemAgent` instance with `llmCall` and `mcpServer`
|
||||||
|
|
||||||
|
> Cross-reference: `2-solution_design.md:SD-001` (channel-based async), `SD-004` (MCP struct)
|
||||||
|
|
||||||
|
**Details**:
|
||||||
|
```julia
|
||||||
|
mcp = mcpServer(conn, topic, senderID)
|
||||||
|
agent = yiemAgent(llmCall, mcpServer=mcp, systemPrompt="You are a helpful assistant")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Dev as Developer
|
||||||
|
part agent YiemAgent Constructor
|
||||||
|
Dev->>agent: yiemAgent(llmCall, ; mcpServer=mcp)
|
||||||
|
agent->>agent: create channels (input=16, followUp=32, output=16)
|
||||||
|
agent->>agent: registerAllTools(tools, mcp)
|
||||||
|
agent->>mcp: mcp("tools/list", sessionId)
|
||||||
|
mcp-->>agent: response (tools array + nextCursor)
|
||||||
|
agent->>agent: _wrap_mcp_tool for each tool
|
||||||
|
agent->>agent: registerTool(tools, listTools_tool)
|
||||||
|
agent->>agent: println("[toolRegistry] Discovered N MCP tools")
|
||||||
|
agent->>agent: yiemAgent struct with placeholder task
|
||||||
|
agent->>agent: @spawn _agentLoop(agent)
|
||||||
|
agent-->>Dev: yiemAgent instance
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-004 (tool discovery), US-001 (agent creation)
|
||||||
|
|
||||||
|
### Send Message: User Prompt
|
||||||
|
|
||||||
|
**WT-002**: Developer sends a user message to the agent
|
||||||
|
|
||||||
|
> Cross-reference: `2-solution_design.md:SD-002` (background task dispatch)
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Dev as Developer
|
||||||
|
part agent AgentLoop
|
||||||
|
Dev->>agent: promptAgent(agent, text="What's the weather?")
|
||||||
|
agent->>agent: put!(inputChannel, msg_dict)
|
||||||
|
Dev->>agent: takeResponse(agent) # blocks
|
||||||
|
agent->>agent: inputChannel isready
|
||||||
|
agent->>agent: take!(inputChannel)
|
||||||
|
agent->>Dev: (returns agent for chaining)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-001 (channel processing), PR-001, SD-001, SD-002
|
||||||
|
|
||||||
|
### Receive Response
|
||||||
|
|
||||||
|
**WT-003**: Agent returns response on `outputChannel`
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Dev as Developer
|
||||||
|
part agent AgentLoop
|
||||||
|
AgentLoop->>AgentLoop: _processMessage runs
|
||||||
|
AgentLoop->>AgentLoop: while loop, llmCall, tool calls...
|
||||||
|
AgentLoop->>AgentLoop: no more tool calls → exit loop
|
||||||
|
AgentLoop->>AgentLoop: filter reasoningContent from final result
|
||||||
|
AgentLoop->>AgentLoop: _assistantMessageToOpenAI(result)
|
||||||
|
AgentLoop->>AgentLoop: put!(outputChannel, respondToUI)
|
||||||
|
AgentLoop->>Dev: take!(outputChannel) # unblocks
|
||||||
|
Dev->>Dev: receive OpenAI dict response
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-001, FR-003, FR-006, PR-001
|
||||||
|
|
||||||
|
### Send Follow-up While Agent Busy
|
||||||
|
|
||||||
|
**WT-004**: Developer sends a follow-up message while agent is processing a tool call
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Dev as Developer
|
||||||
|
part agent AgentLoop
|
||||||
|
Dev->>agent: promptAgent(text="What's the weather in Tokyo?") # agent starts processing
|
||||||
|
AgentLoop->>AgentLoop: _processMessage started (processingTask = Task)
|
||||||
|
Dev->>agent: followUp(text="Also check Seoul") # while busy
|
||||||
|
agent->>agent: put!(followUpChannel, msg_dict)
|
||||||
|
AgentLoop->>AgentLoop: _processMessage completes (tool calls done)
|
||||||
|
AgentLoop->>AgentLoop: outputChannel has message → put!(outputChannel, response), take!(next msg)
|
||||||
|
AgentLoop->>AgentLoop: no inputChannel → check followUpChannel
|
||||||
|
AgentLoop->>AgentLoop: isready(followUpChannel) → take!(followUpMsg), put!(inputChannel, followUpMsg)
|
||||||
|
AgentLoop->>AgentLoop: processingTask = nothing → reset
|
||||||
|
AgentLoop->>AgentLoop: loop back → take!(inputChannel) from followUp
|
||||||
|
Dev->>agent: takeResponse() → blocks
|
||||||
|
AgentLoop->>Dev: next response via outputChannel
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-001 (follow-up priority), PR-001, SD-001, SD-002
|
||||||
|
|
||||||
|
### Stop Agent
|
||||||
|
|
||||||
|
**WT-005**: Developer stops the agent gracefully
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Dev as Developer
|
||||||
|
part agent AgentLoop
|
||||||
|
Dev->>agent: stopAgent(agent)
|
||||||
|
agent->>agent: put!(inputChannel, :shutdown)
|
||||||
|
AgentLoop->>AgentLoop: check newUserMsg === :shutdown
|
||||||
|
AgentLoop->>AgentLoop: drain inputChannel (while isready)
|
||||||
|
AgentLoop->>AgentLoop: drain followUpChannel (while isready)
|
||||||
|
AgentLoop->>AgentLoop: break, catch, fetch(_agentLoop)
|
||||||
|
Dev->>agent: close(inputChannel, outputChannel, followUpChannel)
|
||||||
|
Dev-->>Dev: nothing (agent destroyed)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-001, NFR-201 (graceful exit), PR-001
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## LLM Provider
|
||||||
|
|
||||||
|
_The external LLM API (e.g. Anthropic, OpenAI, LMStudio) receiving formatted messages and returning responses._
|
||||||
|
|
||||||
|
### Receive Formatted Message
|
||||||
|
|
||||||
|
**WT-010**: Agent sends LLM request in OpenAI format
|
||||||
|
|
||||||
|
> Cross-reference: `2-solution_design.md:SD-003` (formatMsgForLLM)
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
part agent AgentLoop
|
||||||
|
AgentLoop->>AgentLoop: preparedContext = prepareContext(state)
|
||||||
|
AgentLoop->>AgentLoop: formattedMessages = formatMsgForLLM(preparedContext)
|
||||||
|
Note over AgentLoop: messages: [system, user, assistant, tool, ...]<br/>tools: [{"function": {"name": "getWeather", "parameters": {...}}}]
|
||||||
|
agentLoop->>llmCall: llmCall(formattedMessages, eventSink=eventSink)
|
||||||
|
llmCall->>Provider: HTTP POST /v1/chat/completions (JSON)
|
||||||
|
Provider-->>llmCall: JSON response
|
||||||
|
llmCall-->>AgentLoop: response = JSON.Object{String, Any}(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-002, SD-003, SD-009
|
||||||
|
|
||||||
|
### Receive Response: No Tool Calls
|
||||||
|
|
||||||
|
**WT-11**: Agent receives text-only response
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
llmCall-->>AgentLoop: response = {finish_reason=> "stop", message=> {role=> "assistant", content=> "It's 22°C in Bangkok"}}
|
||||||
|
AgentLoop->>AgentLoop: _extractToolCalls(response)
|
||||||
|
AgentLoop->>AgentLoop: hasToolCalls = false
|
||||||
|
AgentLoop->>AgentLoop: assistant_msg = assistantMessage(content=[textContent("It's 22°C")], ...)
|
||||||
|
AgentLoop->>AgentLoop: push!(agentMsgHistory, assistant_msg)
|
||||||
|
AgentLoop->>AgentLoop: break (no tool calls)
|
||||||
|
AgentLoop->>AgentLoop: filter reasoningContent (if any)
|
||||||
|
AgentLoop->>AgentLoop: create final assistantMessage
|
||||||
|
AgentLoop->>AgentLoop: put!(outputChannel, OpenAI format dict)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-003 (loop exit), FR-006 (OpenAI conversion), PR-003
|
||||||
|
|
||||||
|
### Receive Response: With Tool Calls
|
||||||
|
|
||||||
|
**WT-012**: Agent receives tool call response
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
llmCall-->>AgentLoop: response = {finish_reason=> "tool_calls",<br/>message=> {role=> "assistant",<br/>reasoning_content=> "I need to call getWeather...",<br/>tool_calls=> [{"id"=>"call_1", "function"=>{name=>"getWeather", arguments=>"{\"city\":\"Bangkok\"}"}}, ...]}}
|
||||||
|
AgentLoop->>AgentLoop: _extractToolCalls(response)
|
||||||
|
AgentLoop->>AgentLoop: hasToolCalls = true<br/>toolCallList = [agentToolCall("getWeather", ...)]
|
||||||
|
AgentLoop->>AgentLoop: assistant_msg = assistantMessageToolCall(toolCalls, reasoningContent, ...)
|
||||||
|
AgentLoop->>AgentLoop: push!(agentMsgHistory, assistant_msg)
|
||||||
|
AgentLoop->>AgentLoop: config = agentLoopConfig(beforeToolCall, afterToolCall, parallel="sequential")
|
||||||
|
AgentLoop->>AgentLoop: toolResultBatch = executeToolCalls(...)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-002, FR-003, PR-003, SD-003, SD-005, SD-009
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MCP Server
|
||||||
|
|
||||||
|
_The Model Context Protocol server providing tools via JSON-RPC 2.0 (transport via NATS or HTTP).
|
||||||
|
|
||||||
|
### Receive tools/list Request
|
||||||
|
|
||||||
|
**WT-020**: Agent discovers tools on startup
|
||||||
|
|
||||||
|
> Cross-reference: `2-solution_design.md:SD-004` (MCP integration)
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
part agent AgentLoop
|
||||||
|
agent->>mcp: mcp("tools/list", sessionId; eventSink=eventSink)
|
||||||
|
Note over mcp: JSON-RPC 2.0: {"jsonrpc"=>"2.0", method=>"tools/list", "id"=>..., "params"=>{...}}
|
||||||
|
MCP_server-->>agent: response = {<br/> "result" => {<br/> "tools" => [name, label, description, inputSchema], ...<br/> , "nextCursor" => 123<br/> }<br/>}
|
||||||
|
agent->>agent: for tool_def in tools_array<br/> _wrap_mcp_tool(mcp, tool_def)
|
||||||
|
agent->>agent: while cursor !== nothing → paginate
|
||||||
|
agent->>agent: println("[toolRegistry] Discovered N MCP tools. Total: $(length(tools))")
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-004 (MCP discovery), PR-004
|
||||||
|
|
||||||
|
### Receive tools/call Request
|
||||||
|
|
||||||
|
**WT-021**: Agent executes tool via MCP
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
part agent Tool System
|
||||||
|
agent->>mcp: mcp("tools/call", "getWeather", args={"city" => "Bangkok"}, sessionId; eventSink=eventSink)
|
||||||
|
Note over mcp: JSON-RPC 2.0: {"jsonrpc"=>"2.0", method=>"tools/call", "params"=>{name, arguments, sessionId}}
|
||||||
|
MCP_server-->>agent: response = {<br/> "result" => {<br/> "content" => [{"type"=>"text", "text"=>"Sunny, 22°C"}],<br/> "isError" => false<br/> }<br/>}
|
||||||
|
agent->>agent: check haskey(response, "error") → no
|
||||||
|
agent->>agent: result_data = response["result"]
|
||||||
|
agent->>agent: content_text = _extract_text_content(result_data)
|
||||||
|
agent->>agent: return agentToolResult([textContent(content_text)], details={isError=>false}, nothing, false)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-004 (tool execution), PR-004, SD-004, SD-005
|
||||||
|
|
||||||
|
### Receive Error from MCP Server
|
||||||
|
|
||||||
|
**WT-022**: MCP server returns error
|
||||||
|
|
||||||
|
**Sequence**:
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
MCP_server-->>agent: response = {<br/> "error" => {<br/> "code" => -32601,<br/> "message" => "Method not found"<br/> }<br/>}
|
||||||
|
agent->>agent: haskey(response, "error") → true
|
||||||
|
agent->>agent: return agentToolResult(<br/> [textContent("MCP error: Method not found")],<br/> details={"error" => "Method not found"},<br/> nothing, false<br/>)
|
||||||
|
agent->>AgentLoop: finalOutcome(error=true)
|
||||||
|
agent->>AgentLoop: push!(agentMsgHistory, toolResultMessage(...))
|
||||||
|
agent->>AgentLoop: next LLM turn receives error result
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Traceability**: FR-004 (error handling), NFR-202 (never throw), PR-004, SD-005
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Paths and Edge Cases
|
||||||
|
|
||||||
|
**WT-030**: LLM times out (network error)
|
||||||
|
|
||||||
|
> Same participant POV — from caller's perspective, they block on `takeResponse()` until task exits
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Dev as Developer
|
||||||
|
part agent AgentLoop
|
||||||
|
Dev->>agent: takeResponse(agent) # blocks
|
||||||
|
agent->>llmCall: llmCall(messages) # calls external API
|
||||||
|
lltimout
|
||||||
|
LLM provider-->>AgentLoop: ERROR (timeout)
|
||||||
|
AgentLoop->>AgentLoop: @error "Agent loop failed"
|
||||||
|
AgentLoop->>AgentLoop: eventSink("error: ...")
|
||||||
|
AgentLoop->>Dev: fetch(_agentLoop) throws TaskFailedException
|
||||||
|
Dev->>agent: stopAgent(agent) # cleanup channels
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**: PR-001, NFR-201 (catch error and exit), SD-001
|
||||||
|
|
||||||
|
**WT-031**: Tool execution throws exception
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
agent->>agent: executePreparedToolCall
|
||||||
|
agent->>tool: tool.execute(toolCallId, args, signal, eventSink)
|
||||||
|
note over tool: tool raises exception (e.g., network error, invalid JSON)
|
||||||
|
tool-->>agent: throw Exception
|
||||||
|
agent->>agent: catch e
|
||||||
|
agent->>agent: executedOutcome(createErrorToolResult("error message"), true)
|
||||||
|
agent->>AgentLoop: finalizeExecutedToolCall
|
||||||
|
AgentLoop->>AgentLoop: createToolResultMessage(error=true)
|
||||||
|
AgentLoop->>AgentLoop: push!(history, toolResultMessage)
|
||||||
|
AgentLoop->>AgentLoop: next llmCall with error in message history
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**: FR-005 (hook errors caught), NFR-202 (never throw), SD-005
|
||||||
|
|
||||||
|
**WT-032**: beforeToolCall hook blocks execution
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
agent->>agent: beforeToolCall(beforeCtx, signal, eventSink)
|
||||||
|
agent->>agent: returns beforeToolCallResult(true, "User must approve")
|
||||||
|
agent->>agent: return immediateOutcome(createErrorToolResult("Tool blocked"), true)
|
||||||
|
agent->>AgentLoop: executeToolCalls → immediateOutcome
|
||||||
|
AgentLoop->>AgentLoop: finalizeExecutedToolCall
|
||||||
|
AgentLoop->>AgentLoop: createToolResultMessage(error=true)
|
||||||
|
AgentLoop->>AgentLoop: push! to history
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**: FR-005 (beforeToolCall can block), SD-005
|
||||||
@@ -0,0 +1,518 @@
|
|||||||
|
# Specification: YiemAgent Agent Framework
|
||||||
|
|
||||||
|
## 1. Message Type System (`src/type.jl`)
|
||||||
|
|
||||||
|
### SP-001: userMessage
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `role::String` | `String` | Yes | Always `"user"` |
|
||||||
|
| `content::Vector{messageContent}` | `Vector{messageContent}` | No | Plain text and/or image content |
|
||||||
|
| `timestamp::Timestamp` | `DateTime` | Yes | When the message was sent |
|
||||||
|
|
||||||
|
**Keyword Constructor**: `userMessage(; role="user", content=[...], timestamp=now())`
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-006 (message type conversion)
|
||||||
|
- Solution Design: SD-006 (Julia structs for messages)
|
||||||
|
|
||||||
|
### SP-002: assistantMessage
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `role::String` | `String` | Yes | Always `"assistant"` |
|
||||||
|
| `content::Vector{messageContent}` | `Vector{messageContent}` | Yes | Text and/or image content |
|
||||||
|
| `api::String` | `String` | Yes | API name used (e.g., `"openai"`) |
|
||||||
|
| `provider::String` | `String` | Yes | Provider name (e.g., `"anthropic"`) |
|
||||||
|
| `model::String` | `String` | Yes | Model identifier |
|
||||||
|
| `usage::llmUsage` | `llmUsage` | Yes | Token usage (in/out) |
|
||||||
|
| `stopReason::String` | `String` | Yes | Why generation stopped (e.g., `"end_turn"`) |
|
||||||
|
| `errorMessage::Union{String, Nothing}` | `Union{String, Nothing}` | Yes | Error if generation failed |
|
||||||
|
| `timestamp::Timestamp` | `DateTime` | Yes | When the message was received |
|
||||||
|
|
||||||
|
**Keyword Constructor**: `assistantMessage(; role="assistant", content=[...], api="", provider="", model=nothing, usage=llmUsage(0,0), stopReason="end_turn", errorMessage=nothing, timestamp=now())`
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-006 (message type conversion)
|
||||||
|
- Solution Design: SD-006
|
||||||
|
|
||||||
|
### SP-003: assistantMessageToolCall
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `role::String` | `String` | Yes | Always `"assistant"` |
|
||||||
|
| `toolCalls::Vector{agentToolCall}` | `Vector{agentToolCall}` | Yes | Tool calls to execute |
|
||||||
|
| `content::Vector{messageContent}` | `Vector{messageContent}` | Yes | Reasoning/thinking content blocks |
|
||||||
|
| `api::String` | `String` | Yes | API name used |
|
||||||
|
| `provider::String` | `String` | Yes | Provider name |
|
||||||
|
| `model::String` | `String` | Yes | Model identifier |
|
||||||
|
| `usage::llmUsage` | `llmUsage` | Yes | Token usage |
|
||||||
|
| `stopReason::String` | `String` | Yes | Why generation stopped (e.g., `"tool_calls"`) |
|
||||||
|
| `errorMessage::Union{String, Nothing}` | `Union{String, Nothing}` | Yes | Error if generation failed |
|
||||||
|
| `timestamp::Timestamp` | `DateTime` | Yes | When the message was received |
|
||||||
|
|
||||||
|
**Keyword Constructor**: `assistantMessageToolCall(; role="assistant", toolCalls=[...], content=[], api="", provider="", model=nothing, usage=llmUsage(0,0), stopReason="tool_calls", errorMessage=nothing, timestamp=now())`
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-003 (tool call loop)
|
||||||
|
- Solution Design: SD-005 (lifecycle phases), SD-009 (format-agnostic LLM responses)
|
||||||
|
|
||||||
|
### SP-004: toolResultMessage
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `role::String` | `String` | Yes | Always `"toolResult"` (note: `"tool"` in example) |
|
||||||
|
| `toolCallId::String` | `String` | Yes | ID matching the tool call |
|
||||||
|
| `toolName::String` | `String` | Yes | Name of the executed tool |
|
||||||
|
| `content::Vector{messageContent}` | `Vector{messageContent}` | Yes | Tool output content |
|
||||||
|
| `details::Any` | `Any` | Yes | Additional tool-specific details |
|
||||||
|
| `usage::Union{llmUsage, Nothing}` | `Union{llmUsage, Nothing}` | Yes | Token usage if applicable |
|
||||||
|
| `addedToolNames::Union{Vector{String}, Nothing}` | `Union{Vector{String}, Nothing}` | Yes | Tools added during execution |
|
||||||
|
| `isError::Bool` | `Bool` | Yes | Whether the tool call resulted in an error |
|
||||||
|
| `timestamp::Timestamp` | `DateTime` | Yes | When the result was recorded |
|
||||||
|
|
||||||
|
**Keyword Constructor**: `toolResultMessage(; role="tool", toolCallId="", toolName="", content=[], details=nothing, usage=nothing, addedToolNames=nothing, isError=false, timestamp=now())`
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-005 (tool lifecycle)
|
||||||
|
- Solution Design: SD-005
|
||||||
|
|
||||||
|
### SP-005: agentToolCall
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `type::String` | `String` | Yes | Always `"function"` |
|
||||||
|
| `id::String` | `String` | Yes | Unique tool call identifier |
|
||||||
|
| `name::String` | `String` | Yes | Tool name |
|
||||||
|
| `arguments::Dict{String, Any}` | `Dict{String, Any}` | Yes | Parsed tool arguments |
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-003 (tool call extraction)
|
||||||
|
- Solution Design: SD-009 (extract tool calls from LLM response)
|
||||||
|
|
||||||
|
### SP-006: messageContent Types
|
||||||
|
|
||||||
|
**textContent** (`SP-006.1`):
|
||||||
|
```julia
|
||||||
|
struct textContent <: messageContent
|
||||||
|
text::String
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
**imageContent** (`SP-006.2`):
|
||||||
|
```julia
|
||||||
|
struct imageContent <: messageContent
|
||||||
|
data::String # Base64-encoded image data
|
||||||
|
mimeType::String # MIME type (e.g., "image/png")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
**reasoningContent** (`SP-006.3`):
|
||||||
|
```julia
|
||||||
|
struct reasoningContent <: messageContent
|
||||||
|
text::String
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-006 (message content conversion)
|
||||||
|
- Solution Design: SD-006
|
||||||
|
|
||||||
|
## 2. LLM Model Configuration (`src/type.jl`)
|
||||||
|
|
||||||
|
### SP-007: llmModel
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `id::String` | `String` | Yes | Unique model identifier |
|
||||||
|
| `name::String` | `String` | Yes | Human-readable model name |
|
||||||
|
| `provider::String` | `String` | Yes | Provider name (e.g., `"anthropic"`, `"openai"`) |
|
||||||
|
| `baseUrl::String` | `String` | Yes | API endpoint base URL |
|
||||||
|
| `reasoning::Bool` | `Bool` | Yes | Whether the model supports chain-of-thought |
|
||||||
|
| `input::Vector{String}` | `Vector{String}` | Yes | Supported input modalities (e.g., `"text"`, `"image"`) |
|
||||||
|
| `cost::modelCost` | `modelCost` | Yes | Pricing information |
|
||||||
|
| `contextWindow::Int64` | `Int64` | Yes | Maximum context length in tokens |
|
||||||
|
| `maxTokens::Int64` | `Int64` | Yes | Maximum output tokens per completion |
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-002 (LLM agnosticism)
|
||||||
|
- Solution Design: SD-003
|
||||||
|
|
||||||
|
### SP-008: modelCost
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `input::Float64` | `Float64` | Yes | Price per 1M input tokens |
|
||||||
|
| `output::Float64` | `Float64` | Yes | Price per 1M output tokens |
|
||||||
|
| `cache_read::Float64` | `Float64` | Yes | Price per 1M cached read tokens |
|
||||||
|
| `cache_write::Float64` | `Float64` | Yes | Price per 1M cache write tokens |
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: N/A (internal tracking data)
|
||||||
|
- Solution Design: SD-003
|
||||||
|
|
||||||
|
## 3. MCP Tool Integration (`src/toolRegistry.jl`)
|
||||||
|
|
||||||
|
### SP-009: tools/list Response Format
|
||||||
|
|
||||||
|
**Request**: `mcp("tools/list", sessionId; eventSink=eventSink)`
|
||||||
|
|
||||||
|
**Response** (JSON-RPC 2.0):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": {
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "tool_name",
|
||||||
|
"title"?: "Human-readable title",
|
||||||
|
"label"?: "Fallback title",
|
||||||
|
"description": "What the tool does",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"field": { "type": "string", "description": "..." }
|
||||||
|
},
|
||||||
|
"required": ["field"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nextCursor"?: "pagination_cursor_or_empty"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response error**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": -32601,
|
||||||
|
"message": "Method not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-004 (MCP discovery)
|
||||||
|
- Solution Design: SD-004
|
||||||
|
|
||||||
|
### SP-010: tools/call Request/Response Format
|
||||||
|
|
||||||
|
**Request**: `mcp("tools/call", toolName, args, sessionId; eventSink=eventSink)`
|
||||||
|
|
||||||
|
**Response** (JSON-RPC 2.0):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": {
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "Tool output text"}
|
||||||
|
],
|
||||||
|
"isError": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response error**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": {
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "Error message"}
|
||||||
|
],
|
||||||
|
"isError": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-004 (tool execution)
|
||||||
|
- Solution Design: SD-004 (`_wrap_mcp_tool`)
|
||||||
|
|
||||||
|
## 4. OpenAI Message Format (`src/utils.jl`)
|
||||||
|
|
||||||
|
### SP-011: system message format
|
||||||
|
|
||||||
|
```julia
|
||||||
|
Dict(
|
||||||
|
"role" => "system",
|
||||||
|
"content" => [Dict("type" => "text", "text" => ctx.systemPrompt)]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-006
|
||||||
|
- Solution Design: SD-006
|
||||||
|
|
||||||
|
### SP-012: userMessage → OpenAI format
|
||||||
|
|
||||||
|
```julia
|
||||||
|
Dict(
|
||||||
|
"role" => "user",
|
||||||
|
"content" => _messageContentToBlocks(msg.content)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `_messageContentToBlocks` converts:
|
||||||
|
- `textContent(t)` → `Dict("type" => "text", "text" => t)`
|
||||||
|
- `imageContent(data, mime)` → `Dict("type" => "image_url", "image_url" => Dict("url" => "data:$mime;base64,$data"))`
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-006
|
||||||
|
- Solution Design: SD-006
|
||||||
|
|
||||||
|
### SP-013: assistantMessageToolCall → OpenAI format
|
||||||
|
|
||||||
|
```julia
|
||||||
|
Dict(
|
||||||
|
"role" => "assistant",
|
||||||
|
"content" => nothing,
|
||||||
|
"tool_calls" => [
|
||||||
|
Dict(
|
||||||
|
"id" => tc.id,
|
||||||
|
"type" => tc.type,
|
||||||
|
"function" => Dict(
|
||||||
|
"name" => tc.name,
|
||||||
|
"arguments" => JSON.json(tc.arguments) # serialized as JSON string
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-003
|
||||||
|
- Solution Design: SD-009
|
||||||
|
|
||||||
|
### SP-014: assistantMessage → OpenAI format
|
||||||
|
|
||||||
|
```julia
|
||||||
|
Dict(
|
||||||
|
"role" => "assistant",
|
||||||
|
"content" => _messageContentToBlocks(msg.content)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-006
|
||||||
|
- Solution Design: SD-006
|
||||||
|
|
||||||
|
### SP-015: toolResultMessage → OpenAI format
|
||||||
|
|
||||||
|
```julia
|
||||||
|
Dict(
|
||||||
|
"role" => "tool",
|
||||||
|
"tool_call_id" => msg.toolCallId,
|
||||||
|
"content" => _messageContentToBlocks(msg.content)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-005, FR-006
|
||||||
|
- Solution Design: SD-005
|
||||||
|
|
||||||
|
### SP-016: agentTool → OpenAI tools format
|
||||||
|
|
||||||
|
```julia
|
||||||
|
Dict(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict(
|
||||||
|
"name" => tool.name,
|
||||||
|
"description" => tool.description,
|
||||||
|
"parameters" => tool.inputSchema
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-004
|
||||||
|
- Solution Design: SD-004, SD-006
|
||||||
|
|
||||||
|
## 5. LLM Response Format Agnostic Extraction (`src/agentCore.jl`)
|
||||||
|
|
||||||
|
### SP-017: format 1 — LMStudio.jl / vLLM style (dict with nested `message.tool_calls`)
|
||||||
|
|
||||||
|
**Response dict** (key: `message::Dict`):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"finish_reason": "tool_calls",
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "",
|
||||||
|
"reasoning_content": "I need to call getWeather...",
|
||||||
|
"tool_calls": [
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "getWeather",
|
||||||
|
"arguments": "{\"city\":\"Bangkok\"}" // JSON string
|
||||||
|
},
|
||||||
|
"id": "call_1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**When no tool calls**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "It's 22°C in Bangkok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-002
|
||||||
|
- Solution Design: SD-009
|
||||||
|
|
||||||
|
### SP-018: format 2 — OpenAI API style (content blocks array)
|
||||||
|
|
||||||
|
**Content blocks with tool calls** (`SP-018.1`):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "tool_calls",
|
||||||
|
"tool_calls": [
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "getWeather",
|
||||||
|
"arguments": {"city": "Bangkok"} // already parsed dict
|
||||||
|
},
|
||||||
|
"id": "call_1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Content blocks with tool_call (already parsed)** (`SP-018.2`):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "tool_call",
|
||||||
|
"name": "getWeather",
|
||||||
|
"arguments": {"city": "Bangkok"},
|
||||||
|
"id": "call_1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Content blocks with text only** (`SP-018.3`):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "It's 22°C in Bangkok"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response dict extras** (common across all formats, `SP-018.4`):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"api": "openai",
|
||||||
|
"provider": "anthropic",
|
||||||
|
"model": "gpt-4",
|
||||||
|
"usage": {"inputTokens": 100, "outputTokens": 50},
|
||||||
|
"error_message": "Optional error"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-002
|
||||||
|
- Solution Design: SD-009
|
||||||
|
|
||||||
|
## 6. Agent Loop Configuration (`src/type.jl` + `src/agentCore.jl`)
|
||||||
|
|
||||||
|
### SP-019: agentContext
|
||||||
|
|
||||||
|
```julia
|
||||||
|
struct agentContext
|
||||||
|
systemPrompt::String
|
||||||
|
messages::Vector{agentMessage}
|
||||||
|
tools::Union{OrderedDict{String, agentTool}, Nothing} # O(1) lookup by name
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-003 (context preparation)
|
||||||
|
- Solution Design: SD-003, SD-004
|
||||||
|
|
||||||
|
### SP-020: agentState
|
||||||
|
|
||||||
|
```julia
|
||||||
|
mutable struct agentState
|
||||||
|
systemPrompt::String
|
||||||
|
model::Union{llmModel, Nothing}
|
||||||
|
tools::OrderedDict{String, agentTool} # insertion-ordered
|
||||||
|
messages::Vector{agentMessage} # history (no system prompt)
|
||||||
|
pendingToolCalls::Vector{String} # tool call IDs waiting for results
|
||||||
|
errorMessage::Union{String, Nothing} # last error message
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-001 (agent state management)
|
||||||
|
- Solution Design: SD-006
|
||||||
|
|
||||||
|
### SP-021: agentLoopConfig
|
||||||
|
|
||||||
|
```julia
|
||||||
|
struct agentLoopConfig
|
||||||
|
beforeToolCall::Union{Function, Nothing}
|
||||||
|
afterToolCall::Union{Function, Nothing}
|
||||||
|
toolExecution::String # "sequential" or "parallel"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-005 (lifecycle hooks)
|
||||||
|
- Solution Design: SD-005
|
||||||
|
|
||||||
|
### SP-022: abortSignal
|
||||||
|
|
||||||
|
```julia
|
||||||
|
struct abortSignal
|
||||||
|
aborted::Bool
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: NFR-101 (timeout handling)
|
||||||
|
- Solution Design: SD-008
|
||||||
|
|
||||||
|
## 7. Tool Validation Hooks (`src/utils.jl`)
|
||||||
|
|
||||||
|
### SP-023: validateRequiredArgs (default hook)
|
||||||
|
|
||||||
|
**Signature**: `validateRequiredArgs(args::AbstractDict{String, Any}, inputSchema::AbstractDict{String, Any})::Union{Nothing, String}`
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
- Extract `required = get(inputSchema, "required", Any[])`
|
||||||
|
- Check each required field exists in `keys(args)`
|
||||||
|
- Return `nothing` if all present
|
||||||
|
- Return `"Missing required arguments: $(join(missing, ", "))"` if any missing
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: NFR-301 (input validation)
|
||||||
|
- Solution Design: SD-005
|
||||||
|
|
||||||
|
### SP-024: validateToolArguments (wrapper hook)
|
||||||
|
|
||||||
|
**Signature**: `validateToolArguments(tool::agentTool, prepared::agentToolCall)::AbstractDict{String, Any}`
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
- If `tool.validateRequiredArgs !== nothing`: call `tool.validateRequiredArgs(prepared.arguments)`
|
||||||
|
- Else: call `validateRequiredArgs(prepared.arguments, tool.inputSchema)`
|
||||||
|
- If returns `!nothing`: throw `ArgumentError(result)`
|
||||||
|
- Caller (`prepareToolCall`) catches and returns `immediateOutcome`
|
||||||
|
|
||||||
|
**Traceability**:
|
||||||
|
- Requirement: FR-005 (tool validation)
|
||||||
|
- Solution Design: SD-005
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
# Validation: YiemAgent Agent Framework
|
||||||
|
|
||||||
|
## Test Architecture Overview
|
||||||
|
|
||||||
|
Because YiemAgent is a Julia library (not a web service), validation tests focus on:
|
||||||
|
- **Unit tests**: message type conversion, tool execution logic, format extraction
|
||||||
|
- **Integration tests**: full agent loop with stubbed LLM and MCP
|
||||||
|
- **Contract tests**: response format compatibility with OpenAI/LMStudio specs
|
||||||
|
- **Performance tests**: agent loop overhead for sequential tool calls
|
||||||
|
|
||||||
|
Every specification rule (SP-XXX), walkthrough journey (WT-XXX), and requirement (FR-XXX) must have a corresponding test.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Types Mapping to Source Documents
|
||||||
|
|
||||||
|
| Test Type | Source Document | Example Coverage |
|
||||||
|
|-----------|-----------------|------------------|
|
||||||
|
| **Unit tests** | `4-specification.md` (SP-001..SP-024), `src/utils.jl` (converters) | Message type → OpenAI format conversion; tool argument validation |
|
||||||
|
| **Integration tests** | `3-walkthrough.md` (WT-001..WT-032) | Full agent loop: prompt → llmCall → tool extract → execute → response |
|
||||||
|
| **Contract tests** | `4-specification.md` SP-017, SP-018 | Response format 1 (LMStudio/vLLM) and format 2 (OpenAI) parsed correctly |
|
||||||
|
| **Performance tests** | `1-requirements.md` FR-003, NFR-101 | Agent loop overhead <500ms per sequential tool call |
|
||||||
|
| **Penetration tests** | `1-requirements.md` NFR-301 | Tool argument injection; MCP spoofing |
|
||||||
|
| **Schema validation** | `4-specification.md` SP-023, SP-024 | Required fields, type checking on tool arguments |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Specification-to-Test Execution Trace
|
||||||
|
|
||||||
|
Every traceable item — specification rules (SP-XXX), walkthrough journeys (WT-XXX), and requirements (FR-XXX) — must have a runnable test. This trace maps each item to its corresponding test file and test function.
|
||||||
|
|
||||||
|
**Format**:
|
||||||
|
|
||||||
|
| Requirement/Spec ID | Participant | Source Path | Unit Test File | Test Function | Additional Test File(s) | CI Gate | Status |
|
||||||
|
|---------------------|-------------|-------------|----------------|---------------|------------------------|---------|--------|
|
||||||
|
| FR-001 | — | `1-requirements.md:3.1` | `tests/unit/channel-handling.jl` | `test_input_channel_priority` | `tests/integration/full-agent-loop.jl` | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| FR-002 | — | `1-requirements.md:3.2` | `tests/unit/llm-format-extraction.jl` | `test_format1_tool_calls` | `tests/contract/response-format.jl` | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| FR-003 | — | `1-requirements.md:3.3` | `tests/integration/tool-call-loop.jl` | `test_loop_exits_on_no_tool_calls` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| FR-004 | — | `1-requirements.md:3.4` | `tests/unit/mcp-tool-discovery.jl` | `test_tools_list_with_pagination` | `tests/contract/mcp-json-rpc.jl` | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| FR-005 | — | `1-requirements.md:3.5` | `tests/unit/tool-hooks.jl` | `test_beforeToolCall_blocks` | `tests/integration/agent-loop.jl` | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| FR-006 | — | `1-requirements.md:3.6` | `tests/unit/message-conversion.jl` | `test_userMessage_to_OpenAI` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| WT-001 | Caller | `3-walkthrough.md:#agent-creation` | `integration/agent-creation.jl` | `test_agent_construction_and_lifecycle` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| WT-002 | Caller | `3-walkthrough.md:#send-message` | `integration/message-flow.jl` | `test_promptAgent_and_takeResponse` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| WT-003 | Caller | `3-walkthrough.md:#receive-response` | `integration/response-format.jl` | `test_output_channel_delivery` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| WT-004 | Caller | `3-walkthrough.md:#follow-up` | `integration/followup.jl` | `test_followUp_while_busy` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| WT-005 | Caller | `3-walkthrough.md:#stop-agent` | `integration/shutdown.jl` | `test_stopAgent_channel_cleanup` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| WT-010 | LLM Provider | `3-walkthrough.md:#receive-formatted` | `integration/llm-call.jl` | `test_formatMsgForLLM_output_structure` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| WT-011 | LLM Provider | `3-walkthrough.md:#response-no-tool-calls` | `integration/non-tool-response.jl` | `test_lmm_response_no_tool_calls_breaks_loop` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| WT-012 | LLM Provider | `3-walkthrough.md:#response-with-tool` | `integration/tool-response.jl` | `test_lmm_response_with_tool_calls` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| WT-020 | MCP Server | `3-walkthrough.md:#discover-tools` | `unit/mcp-discovery.jl` | `test_mcp_tools_list_parsed` | `contract/mcp-json-rpc.jl` | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| WT-021 | MCP Server | `3-walkthrough.md:#execute-tool` | `unit/mcp-tool-execute.jl` | `test_mcp_tools_call_response_parsed` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| WT-022 | MCP Server | `3-walkthrough.md:#mcp-error` | `unit/mcp-error-handling.jl` | `test_mcp_response_error_returned` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| WT-030 | Error | `3-walkthrough.md:#llm-timeout` | `integration/llm-timeout.jl` | `test_LLM_timeout_agent_loop_exits` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| WT-031 | Error | `3-walkthrough.md:#tool-throw` | `integration/tool-error.jl` | `test_tool_execute_throws_returns_error_result` | — | `ci/julia-integration.yaml` | **TODO** |
|
||||||
|
| WT-032 | Error | `3-walkthrough.md:#beforeToolCall-block` | `unit/beforeToolCall-block.jl` | `test_beforeToolCall_blocks_execution` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| SP-001 | — | `4-specification.md:#SP-001:userMessage` | `unit/message-types.jl` | `test_userMessage_struct_default_roles` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| SP-002 | — | `4-specification.md:#SP-002:assistantMessage` | `unit/message-types.jl` | `test_assistantMessage_struct_default_stopReason_end_turn` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| SP-003 | — | `4-specification.md:#SP-003:assistantMessageToolCall` | `unit/message-types.jl` | `test_assistantMessageToolCall_struct_default_stopReason_tool_calls` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| SP-004 | — | `4-specification.md:#SP-004:toolResultMessage` | `unit/message-types.jl` | `test_toolResultMessage_struct_defaults` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| SP-005 | — | `4-specification.md:#SP-005:agentToolCall` | `unit/message-types.jl` | `test_agentToolCall_type_is_always_function` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| SP-006.1,2,3 | — | `4-specification.md:#SP-006:content-types` | `unit/content-types.jl` | `test_textContent_imageContent_reasoningContent` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| SP-017 | — | `4-specification.md:#SP-017` | `contract/response-format.jl` | `test_lmStudio_vLLM_format_parsed` | — | `ci/julia-contract.yaml` | **TODO** |
|
||||||
|
| SP-018.1,2,3,4 | — | `4-specification.md:#SP-018` | `contract/response-format.jl` | `test_openAI_format_blocks_parsed` | — | `ci/julia-contract.yaml` | **TODO** |
|
||||||
|
| SP-023 | — | `4-specification.md:#SP-023` | `unit/tool-validation.jl` | `test_validateRequiredArgs_missing_fields` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| SP-024 | — | `4-specification.md:#SP-024` | `unit/tool-validation.jl` | `test_validateToolArguments_throws_on_missing` | — | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
| NFR-101 | — | `1-requirements.md:4.1` | `perf/agent-loop.jl` | `perf_agent_loop_overhead_<500ms` | `tests/perf/concurrency.jl` | `ci/julia-perf.yaml` | **TODO** |
|
||||||
|
| NFR-102 | — | `1-requirements.md:4.1` | `perf/mcp-discovery.jl` | `perf_mcp_discovery_50_tools_<2s` | — | `ci/julia-perf.yaml` | **TODO** |
|
||||||
|
| NFR-201 | — | `1-requirements.md:4.2` | `unit/error-handling.jl` | `test_agent_loop_catches_and_logs_errors` | `integration/llm-timeout.jl` | `ci/julia-unit.yaml` | **TODO** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Trace Rules
|
||||||
|
|
||||||
|
### Unit Test Rules
|
||||||
|
|
||||||
|
- **Every specification struct** (SP-001..SP-008, SP-019..SP-022) must have a unit test validating:
|
||||||
|
- Default values for keyword arguments are set correctly
|
||||||
|
- Type field matches spec (e.g., `agentToolCall.type` is always `"function"`)
|
||||||
|
- Constructor works with both positional and keyword args
|
||||||
|
- **Every converter function** (`_userMessageToOpenAI`, `_assistantMessageToOpenAI`, etc. SP-011..SP-016) must have a unit test:
|
||||||
|
- Input Julia struct → output `Dict{String, Any}` matching OpenAI spec
|
||||||
|
- Round-trip conversion preserves data (e.g., `textContent` text is preserved)
|
||||||
|
- **Every tool validation function** (SP-023, SP-024) must have unit tests:
|
||||||
|
- Missing required field → returns error string (not throws)
|
||||||
|
- All required present → returns `nothing`
|
||||||
|
- Custom `validateRequiredArgs` hook on tool takes precedence over default
|
||||||
|
|
||||||
|
### Integration Test Rules
|
||||||
|
|
||||||
|
- **Every walkthrough journey** (WT-XXX) must have at least one integration test:
|
||||||
|
- `_agentLoop` processes messages through the full pipeline with stubbed LLM/MCP
|
||||||
|
- `promptAgent` → `takeResponse` round-trip works
|
||||||
|
- `followUp` while busy queues to `followUpChannel`, processes after current work
|
||||||
|
- `stopAgent` sends `:shutdown`, drains channels, closes all 3 channels
|
||||||
|
- **Tool call lifecycle** (FR-003, FR-005):
|
||||||
|
- `llmCall` returns tool calls → loop calls `executeToolCalls` → results appended
|
||||||
|
- Tool calls → no tool calls on next `llmCall` → loop exits
|
||||||
|
- `toolResultBatch.terminate == true` → loop exits immediately
|
||||||
|
- **Hook tests**:
|
||||||
|
- `beforeToolCall` returning `beforeToolCallResult(true, reason)` blocks execution → error tool result
|
||||||
|
- `afterToolCall` returning modified `agentToolResult` replaces result
|
||||||
|
|
||||||
|
### Contract Test Rules
|
||||||
|
|
||||||
|
- **Every response format** (SP-017, SP-018) must be parsed:
|
||||||
|
- Format 1: `_extractToolCalls({message => {tool_calls => [...]}})` extracts `assistantMessageToolCall`
|
||||||
|
- Format 2: `_extractToolCalls({content => [{type=>"tool_calls", tool_calls=[...]}}])` extracts `assistantMessageToolCall`
|
||||||
|
- Format 2 (no tools): `_extractToolCalls({content => [{type=>"text", text=>...}]})` extracts `assistantMessage`
|
||||||
|
- `reasoning_content` field → added to `assistantMessageToolCall.content`
|
||||||
|
- `api`, `provider`, `model`, `usage` → copied to message fields
|
||||||
|
|
||||||
|
### Performance Test Rules
|
||||||
|
|
||||||
|
- **NFR-101**: Agent loop overhead measurement:
|
||||||
|
- Stub LLM returns tool call → execute tool (1ms) → stub returns no tool calls → measure elapsed
|
||||||
|
- Subtract stub costs (llm call 1ms + tool 1ms)
|
||||||
|
- Overhead = elapsed - stub costs
|
||||||
|
- Must be <500ms per tool call (p95)
|
||||||
|
- **NFR-102**: MCP discovery performance:
|
||||||
|
- Mock MCP server returns 200 tools (simulated, no real network)
|
||||||
|
- Measure `_wrap_mcp_tool` + `registerTool` for all 200 tools
|
||||||
|
- Must complete <2 seconds
|
||||||
|
|
||||||
|
### Security/Validation Rules
|
||||||
|
|
||||||
|
- **NFR-301 (input validation)**:
|
||||||
|
- Tool arguments with injection patterns (e.g., `{"city" => "; rm -rf /"}`) → passed through to MCP
|
||||||
|
- `beforeToolCall` can block dangerous tools (e.g., `toolCall.name == "delete_file"`)
|
||||||
|
- **Penetration test**: MCP server spoofing
|
||||||
|
- Attacker sends `{"result" => {"tools" => [{"name" => "malicious", ...}]}}` from untrusted MCP endpoint
|
||||||
|
- Agent executes tool via MCP `tools/call` with attacker-controlled arguments
|
||||||
|
- Mitigation: `beforeToolCall` hook can block unknown tools; app should validate MCP server URL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gap Check (Validation Review)
|
||||||
|
|
||||||
|
Per ASG Framework §6, verify:
|
||||||
|
|
||||||
|
- ✓ Every `SP-XXX` specification item has at least one unit test (listed in trace table above)
|
||||||
|
- ✓ Every `WT-XXX` walkthrough step has at least one integration test (listed in trace table above)
|
||||||
|
- ✓ Every NFR threshold has at least one performance test (NFR-101, NFR-102 in trace table)
|
||||||
|
- ✓ Security NFRs have penetration test scenarios (NFR-301 in trace rules above)
|
||||||
|
- ✓ Every `SP-017, SP-018` response format has contract tests (listed in trace table)
|
||||||
|
- ✓ Every `SP-011..SP-016, SP-023, SP-024` converter/validation rule has unit tests (listed in trace table)
|
||||||
|
|
||||||
|
**Status**: All rows have **TODO** status. Tests must be written before integration into production CI pipeline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CI Pipeline
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# ci/julia-pipeline.yaml (placeholder)
|
||||||
|
name: Julia YiemAgent CI
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
unit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: julia-actions/julia-buildpkg@latest
|
||||||
|
- run: julia --project=. -e 'using Pkg; Pkg.instantiate()'
|
||||||
|
- run: julia --project=./tests -e 'using Test; Test.@testset "Unit tests" # include tests/unit/run_tests.jl"'
|
||||||
|
|
||||||
|
integration:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: unit
|
||||||
|
steps:
|
||||||
|
- run: julia --project=./tests -e 'using Test; Test.@testset "Integration" # include tests/integration/run_tests.jl"'
|
||||||
|
|
||||||
|
contract:
|
||||||
|
runs-on: ubuntu-fail
|
||||||
|
needs: unit
|
||||||
|
steps:
|
||||||
|
- run: julia --project=./tests -e 'using Test; Test.@testset "Contract" # include tests/contract/run_tests.jl"'
|
||||||
|
|
||||||
|
perf:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [unit, integration]
|
||||||
|
steps:
|
||||||
|
- run: julia --project=./tests -e 'using Test; Test.@testset "Performance" # include tests/perf/run_tests.jl"'
|
||||||
|
# Fail if NFR-101 or NFR-102 thresholds breached
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: julia --eval 'using Aqua; Aqua.test_all(YiemAgent)'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gap Check**: Per ASG §5, every spec item maps to a CI gate. If a `SP-XXX` or `WT-XXX` row has no test file/function, the trace table row must be filled before merging.
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
# Implementation Plan: YiemAgent Agent Framework
|
||||||
|
|
||||||
|
## 1. Implementation Phases
|
||||||
|
|
||||||
|
### Phase 1: Type System & Core Structures (COMPLETED)
|
||||||
|
|
||||||
|
**Files**: `src/type.jl`
|
||||||
|
|
||||||
|
**Completed Tasks**:
|
||||||
|
- `IMP-001`: Define Julia struct types for messages (`userMessage`, `assistantMessage`, `assistantMessageToolCall`, `toolResultMessage`) with keyword constructors — **COMPLETED** (see `src/type.jl:95-249`)
|
||||||
|
- `IMP-002`: Define message content types (`textContent`, `imageContent`, `reasoningContent`) with abstract `messageContent` base type — **COMPLETED** (see `src/type.jl:74-87`)
|
||||||
|
- `IMP-003`: Define tool types (`agentTool`, `agentToolCall`) with lifecycle hooks (`prepareArguments`, `validateRequiredArgs`) — **COMPLETED** (see `src/type.jl:319-328`)
|
||||||
|
- `IMP-004`: Define agent context/state types (`agentContext`, `agentState`) — **COMPLETED** (see `src/type.jl:357-415`)
|
||||||
|
- `IMP-005`: Define LLM model/usage/cost types (`llmModel`, `modelCost`, `llmUsage`) — **COMPLETED** (see `src/type.jl:45-67`)
|
||||||
|
- `IMP-006`: Define tool call lifecycle result types (`preparedToolCall`, `immediateOutcome`, `executedOutcome`, `finalizedOutcome`) — **COMPLETED** (see `src/type.jl:600-710`)
|
||||||
|
- `IMP-007`: Define agent loop config and event types (`agentLoopConfig`, `abortSignal`, `agentToolResult`, `toolExecStartEvent`, etc.) — **COMPLETED** (see `src/type.jl:450-567`)
|
||||||
|
- `IMP-008`: Export all types from `type` module — **COMPLETED** (see `src/type.jl:2-26`)
|
||||||
|
|
||||||
|
**Validation Tests**: Unit tests for struct construction with keyword args (SP-001..SP-008, SP-019..SP-022)
|
||||||
|
|
||||||
|
**Traceability**: FR-001, FR-002, FR-003, FR-004, FR-005, FR-006
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 2: Utility & Formatting Functions (COMPLETED)
|
||||||
|
|
||||||
|
**Files**: `src/utils.jl`
|
||||||
|
|
||||||
|
**Completed Tasks**:
|
||||||
|
- `IMP-010`: Implement `prepareContext(state, eventSink) -> agentContext` — **COMPLETED** (see `src/utils.jl:112-126`)
|
||||||
|
- TODO: Filter tools based on user intent and tool description (`src/utils.jl:114`)
|
||||||
|
- TODO: Inject system prompt context (e.g., time, session state) (`src/utils.jl:118`)
|
||||||
|
- TODO: Prune/reorder messages (`src/utils.jl:121`)
|
||||||
|
- `IMP-011`: Implement `formatMsgForLLM(ctx, eventSink) -> Dict{String, Any}` — **COMPLETED** (see `src/utils.jl:159-244`)
|
||||||
|
- Converts system prompt to system message
|
||||||
|
- Converts conversation messages via `_userMessageToOpenAI`, `_assistantMessageToOpenAI`, `_assistantMessageToolCallToOpenAI`, `_toolResultMessageToOpenAI`
|
||||||
|
- Converts `ctx.tools` to OpenAI function tool format via `_toolsToOpenAI`
|
||||||
|
- `IMP-012`: Implement message converters (`_userMessageToOpenAI`, `_assistantMessageToOpenAI`, `_assistantMessageToolCallToOpenAI`, `_toolResultMessageToOpenAI`) — **COMPLETED** (see `src/utils.jl:321-387`)
|
||||||
|
- `IMP-013`: Implement `_messageContentToBlocks` — converts `textContent`/`imageContent` to OpenAI content blocks — **COMPLETED** (see `src/utils.jl:396-413`)
|
||||||
|
- `IMP-014`: Implement `_toolsToOpenAI` — converts `OrderedDict{String, agentTool}` to OpenAI tools array — **COMPLETED** (see `src/utils.jl:429-447`)
|
||||||
|
- `IMP-015`: Implement `beforeToolCall` hook default (returns `beforeToolCallResult(false, "N/A")`) — **COMPLETED** (see `src/utils.jl:278-284`)
|
||||||
|
- `IMP-016`: Implement `afterToolCall` hook default (returns `nothing`) — **COMPLETED** (see `src/utils.jl:310-316`)
|
||||||
|
- `IMP-017`: Implement `validateRequiredArgs(args, inputSchema)` — validates required fields in tool arguments — **COMPLETED** (see `src/utils.jl:479-497`)
|
||||||
|
- `IMP-018`: Implement `validateToolArguments(tool, prepared)` — wrapper that calls tool-specific or default hook — **COMPLETED** (see `src/utils.jl:533-546`)
|
||||||
|
|
||||||
|
**Validation Tests**: Unit tests for converters, validation hooks, and OpenAI format output structure (SP-011..SP-016, SP-023, SP-024)
|
||||||
|
|
||||||
|
**Traceability**: FR-001, FR-002, FR-003, FR-004, FR-005, FR-006
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 3: Tool Registry & MCP Integration (COMPLETED)
|
||||||
|
|
||||||
|
**Files**: `src/toolRegistry.jl`
|
||||||
|
|
||||||
|
**Completed Tasks**:
|
||||||
|
- `IMP-020`: Implement `_extract_text_content(result)` — extracts text from MCP tool result content array — **COMPLETED** (see `src/toolRegistry.jl:19-33`)
|
||||||
|
- `IMP-021`: Implement `_wrap_mcp_tool(mcpserver, tool_def)` — wraps MCP tool as `agentTool` with `execute` calling `mcpserver("tools/call", ...)` — **COMPLETED** (see `src/toolRegistry.jl:41-106`)
|
||||||
|
- Handles MCP error responses (`haskey(response, "error")`)
|
||||||
|
- Extracts text and `isError` from result
|
||||||
|
- `IMP-022`: Implement `register_mcp_tools(tools, mcpserver)` — queries `tools/list` with pagination (`nextCursor`) — **COMPLETED** (see `src/toolRegistry.jl:118-186`)
|
||||||
|
- Handles both `result.tools` and `result.error` response shapes
|
||||||
|
- Skips already registered tools (idempotent)
|
||||||
|
- `IMP-023`: Implement `registerTool(tools, tool)` — adds tool to OrderedDict — **COMPLETED** (see `src/toolRegistry.jl:200-205`)
|
||||||
|
- `IMP-024`: Implement `listTool(tools, mcpserver)` — builtin tool that triggers runtime discovery and lists all tools — **COMPLETED** (see `src/toolRegistry.jl:235-271`)
|
||||||
|
- `IMP-025`: Implement `registerAllTools(tools, mcpserver)` — discovers existing tools + registers `listTools` — **COMPLETED** (see `src/toolRegistry.jl:286-289`)
|
||||||
|
|
||||||
|
**Validation Tests**: Unit tests for MCP tools/list parsing, pagination, tool registration idempotency; contract tests for JSON-RPC 2.0 format (SP-009, SP-010)
|
||||||
|
|
||||||
|
**Traceability**: FR-002, FR-004
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 4: Agent Core & Loop (COMPLETED)
|
||||||
|
|
||||||
|
**Files**: `src/agentCore.jl`
|
||||||
|
|
||||||
|
**Completed Tasks**:
|
||||||
|
- `IMP-030`: Define `yiemAgent` struct with channels, callbacks, MCP server — **COMPLETED** (see `src/agentCore.jl:17-95`)
|
||||||
|
- `IMP-031`: Implement `yiemAgent` constructor — creates channels, discovers MCP tools, spawns `_agentLoop` task — **COMPLETED** (see `src/agentCore.jl:126-199`)
|
||||||
|
- `IMP-032`: Implement `_agentLoop(agent)` — background `@spawn` task managing `inputChannel`/`followUpChannel` priority dispatch — **COMPLETED** (see `src/agentCore.jl:225-364`)
|
||||||
|
- Handles shutdown signal: drains all channels, breaks loop
|
||||||
|
- On error: catches, calls `eventSink(errMsg)`, exits loop
|
||||||
|
- `IMP-033`: Implement `_processMessage(...)` — drains input, calls `llmCall`, extracts tool calls, executes, loops until no tool calls — **COMPLETED** (see `src/agentCore.jl:394-544`)
|
||||||
|
- `while true` loop: `llmCall()` → `_extractToolCalls()` → if `hasToolCalls && length > 0` execute → append results → repeat
|
||||||
|
- On `toolResultBatch.terminate == true`: build final response and break
|
||||||
|
- Catches all errors and calls `eventSink(err_msg)`
|
||||||
|
- `IMP-034`: Implement `_extractToolCalls(response)` — handles **both** formats:
|
||||||
|
- Format 1: `response["message"]["tool_calls"]` (LMStudio/vLLM style) — **COMPLETED** (see `src/agentCore.jl:671-683`)
|
||||||
|
- Format 2: `response.content` blocks with `type => "tool_calls"` or `type => "tool_call"` (OpenAI API style) — **COMPLETED** (see `src/agentCore.jl:685-719`)
|
||||||
|
- Extracts `reasoning_content`, `content` text blocks, `api`, `provider`, `model`, `usage`, `finish_reason` — **COMPLETED** (see `src/agentCore.jl:723-797`)
|
||||||
|
- Returns `(hasToolCalls, toolCallList, assistant_message)` — **COMPLETED** (see `src/agentCore.jl:799-827`)
|
||||||
|
- `IMP-035`: Implement `prepareToolCall(context, assistantMsg, toolCall, config, signal, eventSink)` — resolves tool, validates args, runs `beforeToolCall` hook, never throws — **COMPLETED** (see `src/agentCore.jl:974-1035`)
|
||||||
|
- `IMP-036`: Implement `executePreparedToolCall(prep, signal, eventSink)` — calls `tool.execute(toolCallId, args, ...)` — **COMPLETED** (see `src/agentCore.jl:1078-1100`)
|
||||||
|
- `IMP-037`: Implement `finalizeExecutedToolCall(context, msg, prep, executed, config, signal, eventSink)` — runs `afterToolCall` hook, catches errors — **COMPLETED** (see `src/agentCore.jl:1159-1198`)
|
||||||
|
- `IMP-038`: Implement `prepareToolCallArguments(tool, toolCall)` — calls `tool.prepareArguments` if present — **COMPLETED** (see `src/agentCore.jl:908-917`)
|
||||||
|
- `IMP-039`: Implement `createErrorToolResult(msg)` — builds `agentToolResult` with error content — **COMPLETED** (see `src/agentCore.jl:572-574`)
|
||||||
|
- `IMP-040`: Implement `createToolResultMessage(f)` — builds `toolResultMessage` from `finalizedOutcome` — **COMPLETED** (see `src/agentCore.jl:606-612`)
|
||||||
|
|
||||||
|
**Note**: Sequential and parallel execution implementations continue beyond line 1207 of `src/agentCore.jl` — implementation incomplete, requires review to ensure execution methods properly call hooks and emit lifecycle events. See IMP-041.
|
||||||
|
|
||||||
|
**Validation Tests**: Integration tests for full agent loop with mocked LLM and MCP; contract tests for response format extraction (WT-001..WT-032, SP-017, SP-018)
|
||||||
|
|
||||||
|
**Traceability**: FR-001, FR-002, FR-003, FR-004, FR-005
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 5: Public API (COMPLETED)
|
||||||
|
|
||||||
|
**Files**: `src/api.jl`
|
||||||
|
|
||||||
|
**Completed Tasks**:
|
||||||
|
- `IMP-050`: Implement `promptAgent(agent, msg::AbstractDict)` — put to `inputChannel`, returns agent for chaining — **COMPLETED** (see `src/api.jl:36-39`)
|
||||||
|
- `IMP-051`: Implement `promptAgent(agent, text::String)` — wraps text as OpenAI `Dict` and put to `inputChannel` — **COMPLETED** (see `src/api.jl:41-44`)
|
||||||
|
- `IMP-052`: Implement `takeResponse(agent)` — blocks on `outputChannel` `take!`, returns OpenAI dict response — **COMPLETED** (see `src/api.jl:66-68`)
|
||||||
|
- `IMP-053`: Implement `followUp(agent, msg::AbstractDict)` — put to `followUpChannel`, returns agent — **COMPLETED** (see `src/api.jl:94-97`)
|
||||||
|
- `IMP-054`: Implement `followUp(agent, text::String)` — wraps text as OpenAI `Dict` and put to `followUpChannel` — **COMPLETED** (see `src/api.jl:99-102`)
|
||||||
|
- `IMP-055`: Implement `stopAgent(agent)` — puts `:shutdown`, `fetch(_agentLoop)`, closes all channels — **COMPLETED** (see `src/api.jl:126-139`)
|
||||||
|
|
||||||
|
**Validation Tests**: Integration tests for send/receive/stop lifecycle (WT-001..WT-005)
|
||||||
|
|
||||||
|
**Traceability**: FR-001, FR-006
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Module Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── YiemAgent.jl # Parent module, includes submodules in dependency order
|
||||||
|
├── type.jl # All Julia struct types (exports 30+ types)
|
||||||
|
├── utils.jl # Formatting, validation, hooks (default implementations)
|
||||||
|
├── toolRegistry.jl # MCP tool discovery, registration, listTools builtin
|
||||||
|
├── agentCore.jl # yiemAgent struct, _agentLoop, _processMessage, extractToolCalls
|
||||||
|
└── api.jl # Public: promptAgent, takeResponse, followUp, stopAgent
|
||||||
|
```
|
||||||
|
|
||||||
|
**Include order** (per `src/YiemAgent.jl` lines 6-19):
|
||||||
|
1. `type.jl` (no dependencies)
|
||||||
|
2. `utils.jl` (depends on `type`)
|
||||||
|
3. `toolRegistry.jl` (depends on `type`)
|
||||||
|
4. `agentCore.jl` (depends on `type`, `utils`, `toolRegistry`)
|
||||||
|
5. `api.jl` (depends on `type`, `utils`, `agentCore`, `toolRegistry`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Task Dependencies
|
||||||
|
|
||||||
|
| Task ID | Title | Depends On | Status |
|
||||||
|
|---------|-------|------------|--------|
|
||||||
|
| IMP-001..IMP-008 | Type system | — | **COMPLETED** |
|
||||||
|
| IMP-010..IMP-018 | Utils (formatMsgForLLM, converters, hooks) | IMP-001..IMP-008 | **COMPLETED** |
|
||||||
|
| IMP-020..IMP-025 | Tool registry (MCP discovery) | IMP-003, IMP-017 | **COMPLETED** |
|
||||||
|
| IMP-030..IMP-040 | Agent core (agent loop, tool call execution) | IMP-001..IMP-008, IMP-010..IMP-018, IMP-020..IMP-025 | **INCOMPLETE*** (see note) |
|
||||||
|
| IMP-050..IMP-055 | Public API | IMP-030..IMP-040 | **COMPLETED** |
|
||||||
|
|
||||||
|
**NOTE on IMP-030..IMP-040**: The sequential and parallel execution functions (`executeToolCallsSequential`, `executeToolCallsParallel`, `executeToolCalls`) begin after line 1207 of `src/agentCore.jl` but the full implementation is truncated in file reads and requires review. The exported functions (`executePreparedToolCall`, `prepareToolCall`, `executeToolCallsSequential`, `executeToolCallsParallel`, `executeToolCalls`) are declared (`src/agentCore.jl:4-5`) but their full bodies may be incomplete. **Requires manual validation.**
|
||||||
|
|
||||||
|
**Traceability**: IMP-001..IMP-008 → SP-001..SP-008, IMP-020..IMP-025 → SP-009, SP-010; IMP-010..IMP-018 → SP-011..SP-016, SP-023, SP-024; IMP-030..IMP-040 → SP-017, SP-018, SP-019..SP-022
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Remaining Work (TODOs)
|
||||||
|
|
||||||
|
| File | Line | Description | Priority |
|
||||||
|
|------|------|-------------|----------|
|
||||||
|
| `src/utils.jl` | 114 | Filter tools from `state.tools` based on user intent and tool description | High |
|
||||||
|
| `src/utils.jl` | 118 | Add system prompt context: time, session state, user preferences | Medium |
|
||||||
|
| `src/utils.jl` | 121 | Prune/reorder/conversation messages before sending to LLM | Medium |
|
||||||
|
| `src/agentCore.jl` | post-1207 | Complete and validate `executeToolCallsSequential`, `executeToolCallsParallel`, `executeToolCalls` implementations (currently exported but body may be incomplete) | **Critical** |
|
||||||
|
| `tests/unit/...` | — | Write unit tests for all struct types (IMP-001..IMP-008) and converters (SP-011..SP-016) | High |
|
||||||
|
| `tests/unit/...` | — | Write unit tests for tool validation hooks (SP-023, SP-024) | High |
|
||||||
|
| `tests/integration/...` | — | Write integration tests for agent loop with mocked LLM/MCP (WT-001..WT-032) | High |
|
||||||
|
| `tests/contract/...` | — | Write contract tests for response formats (SP-017, SP-018) | Medium |
|
||||||
|
| `tests/perf/...` | — | Write performance tests (NFR-101, NFR-102) | Medium |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Build & Deployment
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/ton/docker-apps/sommpanion/YiemAgent
|
||||||
|
julia --project=. -e 'using Pkg; Pkg.instantiate(); using Pkg; Pkg.precompile()'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
julia --project=./tests -e 'using Pkg; Pkg.instantiate()'
|
||||||
|
julia --project=./tests -e 'using Test; Test.@testset "Unit" begin include("tests/unit/run_tests.jl") end'
|
||||||
|
julia --project=./tests -e 'using Test; Test.@testset "Integration" begin include("tests/integration/run_tests.jl") end'
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD Preparation
|
||||||
|
|
||||||
|
- Add `.github/workflows/julia.yaml` referencing `ci/julia-pipeline.yaml` steps
|
||||||
|
- Add `Project.toml` dependencies:
|
||||||
|
```toml
|
||||||
|
[deps]
|
||||||
|
JSON = "682c8566-3430-5439-8f4e-b64832e8aaba"
|
||||||
|
DataStructures = "892a3cad-33e6-51e5-808c-f3d90072be70"
|
||||||
|
HTTP = "cd6eee30-4dd5-5329-8bad-3259ec48d0f4"
|
||||||
|
# plus: UUIDs, Dates, PrettyPrinting, Serialization, DataFrames, NATS, LibPQ
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Risk Mitigation
|
||||||
|
|
||||||
|
| Risk | Mitigation |
|
||||||
|
|------|------------|
|
||||||
|
| Line 1207+ of `agentCore.jl` (sequential/parallel execution) is incomplete or broken | Manually review and validate `executeToolCallsSequential`, `executeToolCallsParallel`, `executeToolCalls` against spec SP-021 (agentLoopConfig) and trace to test `test_tool_call_execution_sequence` |
|
||||||
|
| `GeneralUtils` internal package unavailable in CI | Ensure `GeneralUtils` is installed as dependency (via `Pkg.develop` or registry) |
|
||||||
|
| NATS `mcpServer` struct is abstract — no concrete implementation | Provide at least one mock `mcpServer` for tests that returns canned JSON responses |
|
||||||
|
| Message history grows unbounded | Implement `prepareContext` pruning (TODO in code); add NFR-104 for max history tokens |
|
||||||
@@ -0,0 +1,445 @@
|
|||||||
|
# Runbook: YiemAgent Agent Framework
|
||||||
|
|
||||||
|
**Severity**: N/A (library — not a deployed service)
|
||||||
|
**Estimated Time**: N/A
|
||||||
|
|
||||||
|
This runbook documents operational guidance for developers working with the YiemAgent agent framework. It is not an operations runbook for a production service, but a developer handbook for debugging, troubleshooting, and extending the framework.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Agent Lifecycle
|
||||||
|
|
||||||
|
### 1.1: Create an Agent
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
```julia
|
||||||
|
using YiemAgent
|
||||||
|
|
||||||
|
# 1. Implement llmCall function
|
||||||
|
function my_llmCall(messages; eventSink=nothing)
|
||||||
|
# Call LLM API (e.g., OpenAI, Anthropic, LMStudio)
|
||||||
|
# Return dict with keys: "message", "content", "finish_reason", "api", "provider", "model", "usage"
|
||||||
|
eventSink("my_llmCall: sending $(length(messages["messages"])) messages"; publish=true)
|
||||||
|
# ... API call ...
|
||||||
|
return response_dict
|
||||||
|
end
|
||||||
|
|
||||||
|
# 2. Implement mcpServer callable struct
|
||||||
|
struct MyMCPClient
|
||||||
|
conn::NATS.Connection # or HTTP.Client
|
||||||
|
topic::String
|
||||||
|
senderID::String
|
||||||
|
end
|
||||||
|
|
||||||
|
function (mcp::MyMCPClient)(method::String, args...; eventSink=nothing)
|
||||||
|
# JSON-RPC 2.0 calls:
|
||||||
|
# mcp("tools/list", sessionId; eventSink=eventSink)
|
||||||
|
# mcp("tools/call", toolName, args, sessionId; eventSink=eventSink)
|
||||||
|
# ... NATS/HTTP call ...
|
||||||
|
return jsonrpc_response
|
||||||
|
end
|
||||||
|
|
||||||
|
# 3. Create agent
|
||||||
|
mcp = MyMCPClient(nats_conn, "yiemagent:tools", "agent_1")
|
||||||
|
agent = yiemAgent(
|
||||||
|
my_llmCall;
|
||||||
|
systemPrompt="You are a wine assistant.\nYou have tools for searching wine inventory.",
|
||||||
|
tools=mcp_tools, # optional: pass pre-discovered tools
|
||||||
|
prepareContext=my_prepareContext, # optional: custom context preparation
|
||||||
|
formatMsgForLLM=my_formatMsgForLLM, # optional: custom formatting for provider
|
||||||
|
beforeToolCall=my_beforeToolCall, # optional: approval gate
|
||||||
|
afterToolCall=my_afterToolCall, # optional: result sanitization
|
||||||
|
mcpServer=mcp,
|
||||||
|
parallelToolExecute=false,
|
||||||
|
eventSink=my_eventSink,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Reference**: `2-solution_design.md:SD-001` (channel-based async), `SD-002` (background task), `SD-004` (MCP struct)
|
||||||
|
|
||||||
|
### 1.2: Send a Message
|
||||||
|
|
||||||
|
```julia
|
||||||
|
# Option 1: Send a string message (automatically wrapped as OpenAI dict)
|
||||||
|
promptAgent(agent, "What wines have notes of oak and vanilla?")
|
||||||
|
|
||||||
|
# Option 2: Send a full OpenAI-format dict with images
|
||||||
|
msg = Dict(
|
||||||
|
"role" => "user",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "What wines match these notes?"),
|
||||||
|
Dict("type" => "image_url", "image_url" => Dict("url" => "data:image/png;base64,..."))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
promptAgent(agent, msg)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3: Receive a Response
|
||||||
|
|
||||||
|
```julia
|
||||||
|
# Blocks until agent finishes processing (no tool calls or tool calls complete)
|
||||||
|
response = takeResponse(agent)
|
||||||
|
# response is a Dict in OpenAI format:
|
||||||
|
# Dict("role" => "assistant", "content" => [Dict("type" => "text", "text" => "...")])
|
||||||
|
|
||||||
|
# For text-only response:
|
||||||
|
if response["content"][1]["type"] == "text"
|
||||||
|
println(response["content"][1]["text"])
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4: Send Follow-up While Agent is Busy
|
||||||
|
|
||||||
|
```julia
|
||||||
|
# While the agent is processing tool calls (between promptAgent(...) and takeResponse(...))
|
||||||
|
followUp(agent, "Also check European wines")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5: Stop the Agent
|
||||||
|
|
||||||
|
```julia
|
||||||
|
stopAgent(agent)
|
||||||
|
# Sends :shutdown, closes all channels. Agent object is destroyed.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Troubleshooting
|
||||||
|
|
||||||
|
### 2.1: Agent Loop Exits Silently
|
||||||
|
|
||||||
|
**Symptoms**:
|
||||||
|
- `takeResponse(agent)` hangs indefinitely
|
||||||
|
- No output in eventSink
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Check if `eventSink` is receiving error messages:
|
||||||
|
```julia
|
||||||
|
global error_count = 0
|
||||||
|
my_eventSink(msg; publish=false) = begin
|
||||||
|
if occursin("error", lowercase(msg)) || occursin("failed", lowercase(msg))
|
||||||
|
println("ERROR EVENT: $msg")
|
||||||
|
global error_count += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
2. Check network connectivity to LLM API endpoint
|
||||||
|
3. Verify `formatMsgForLLM` produces valid OpenAI-format message structure (`4-specification.md:SP-011..SP-016`)
|
||||||
|
|
||||||
|
> **Reference**: `4-specification.md` (OpenAI format specs), `3-walkthrough.md:WT-030` (LLM timeout)
|
||||||
|
|
||||||
|
### 2.2: Tool Call Not Executing
|
||||||
|
|
||||||
|
**Symptoms**:
|
||||||
|
- LLM returns `tool_calls` but agent returns text response immediately
|
||||||
|
- `beforeToolCall` hook is missing or incorrectly implemented
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Verify `prepareContext` returns `agentContext` with `tools` field (not `nothing`) (`src/utils.jl:123`)
|
||||||
|
2. Check `beforeToolCall` hook is not blocking execution:
|
||||||
|
```julia
|
||||||
|
function my_beforeToolCall(ctx, signal, eventSink)
|
||||||
|
# Returning nothing → allows execution
|
||||||
|
# Returning beforeToolCallResult(true, "blocked") → blocks
|
||||||
|
eventSink("beforeToolCall for $(ctx.toolCall.name)"; publish=true)
|
||||||
|
nothing # ← must return nothing (not beforeToolCallResult(false, "N/A"))
|
||||||
|
end
|
||||||
|
```
|
||||||
|
3. Verify tool exists in `context.tools[toolCall.name]` (`src/agentCore.jl:985`)
|
||||||
|
4. Check `validateToolArguments` is not throwing (catches and returns error tool result) (`src/utils.jl:533`)
|
||||||
|
|
||||||
|
> **Reference**: `4-specification.md:SP-021` (agentLoopConfig with beforeToolCall hook), `3-walkthrough.md:WT-032` (beforeToolCall blocks)
|
||||||
|
|
||||||
|
### 2.3: MCP Server Returns No Tools
|
||||||
|
|
||||||
|
**Symptoms**:
|
||||||
|
- `println("[toolRegistry] Discovered 0 MCP tools."` on startup
|
||||||
|
- Agent has no tools available at all times
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Verify `mcpServer` callable struct correctly handles `mcp("tools/list", sessionId; eventSink)`
|
||||||
|
2. Check JSON-RPC 2.0 response shape matches `4-specification.md:SP-009`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": {
|
||||||
|
"tools": [{"name": "...", ...}],
|
||||||
|
"nextCursor": "" # empty string = no more pages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. Verify `tools/list` error response uses `haskey(response, "error")` check (`src/toolRegistry.jl:128`)
|
||||||
|
|
||||||
|
> **Reference**: `4-specification.md:SP-009` (tools/list format), `3-walkthrough.md:WT-020` (MCP discovery)
|
||||||
|
|
||||||
|
### 2.4: LLM Response Format Not Parsed
|
||||||
|
|
||||||
|
**Symptoms**:
|
||||||
|
- Agent always treats LLM response as text, never extracts tool calls
|
||||||
|
- LLM correctly returns `tool_calls` but agent ignores them
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Verify `llmCall` returns a dict containing `message` (LMStudio/vLLM format, `4-specification.md:SP-017`) or `content` (OpenAI format, `4-specification.md:SP-018`)
|
||||||
|
2. Check for both format variations:
|
||||||
|
- Format 1: `response["message"]["tool_calls"]` (LMStudio style, see `src/agentCore.jl:671-683`)
|
||||||
|
- Format 2: `response.content[{type => "tool_calls"}]` (OpenAI style, see `src/agentCore.jl:700-705`)
|
||||||
|
3. Verify `finish_reason` field matches: `"tool_calls"` triggers tool execution, `"stop"`/`"end_turn"` breaks loop (`src/agentCore.jl:723-724`)
|
||||||
|
|
||||||
|
> **Reference**: `4-specification.md:SP-017, SP-018`, `3-walkthrough.md:WT-011, WT-012` (LLM response handling)
|
||||||
|
|
||||||
|
### 2.5: Tool Execution Hangs
|
||||||
|
|
||||||
|
**Symptoms**:
|
||||||
|
- `tool.execute(...)` starts but never returns
|
||||||
|
- EventSink receives `toolExecutionStart` but never `toolExecutionEnd`
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Implement `abortSignal` checking in tool execute function:
|
||||||
|
```julia
|
||||||
|
function my_tool_execute(toolCallId, args, signal, eventSink)
|
||||||
|
while !signal.aborted && !all_conditions_met
|
||||||
|
# Long-running operation with periodic abort checks
|
||||||
|
sleep(0.1)
|
||||||
|
end
|
||||||
|
if signal.aborted
|
||||||
|
return agentToolResult([textContent("Cancelled")], Dict(), nothing, false)
|
||||||
|
end
|
||||||
|
return agentToolResult([textContent("Done")], Dict(), nothing, false)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
2. External caller sets `signal.aborted = true` (if `abortSignal` is shared via closure)
|
||||||
|
3. Add timeout logic to tool execute function:
|
||||||
|
```julia
|
||||||
|
start_time = time()
|
||||||
|
timeout_seconds = 30.0
|
||||||
|
while time() - start_time < timeout_seconds && !signal.aborted
|
||||||
|
# operation...
|
||||||
|
sleep(0.5)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Reference**: `4-specification.md:SP-022` (abortSignal), `3-walkthrough.md:WT-030` (LLM timeout)
|
||||||
|
|
||||||
|
### 2.6: Message History Grows Unbounded
|
||||||
|
|
||||||
|
**Symptoms**:
|
||||||
|
- Memory usage grows linearly with conversation length
|
||||||
|
- LLM context window exceeded for long sessions
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Implement pruning in `prepareContext` (`src/utils.jl:112-126`):
|
||||||
|
```julia
|
||||||
|
function my_prepareContext(state::agentState, eventSink)
|
||||||
|
# Prune old messages, keep last N turns
|
||||||
|
max_turns = 20
|
||||||
|
recent_msgs = isempty(state.messages) ? state.messages :
|
||||||
|
state.messages[max(1, end-2*max_turns):end]
|
||||||
|
|
||||||
|
# Optionally inject system context (time, user prefs)
|
||||||
|
preparedMessages = recent_msgs
|
||||||
|
preparedSystemPrompt = state.systemPrompt * "\nCurrent time: $(now())"
|
||||||
|
|
||||||
|
# Filter tools (optional: keep only relevant subset)
|
||||||
|
filteredTools = state.tools # or filter by intent...
|
||||||
|
|
||||||
|
return agentContext(preparedSystemPrompt, preparedMessages, filteredTools)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
2. Add hard limit in `_agentLoop`:
|
||||||
|
```julia
|
||||||
|
if length(agent._state.messages) > 200
|
||||||
|
# Truncate oldest messages
|
||||||
|
agent._state.messages = agent._state.messages[end-100:end]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Reference**: `3-walkthrough.md` (agent message flow), `1-requirements.md:NFR-103` (channel buffer limits)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Extending the Framework
|
||||||
|
|
||||||
|
### 3.1: Add a Custom LLM Provider
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Implement `llmCall` function that accepts `FormattedMessages::Dict{String, Any}` and returns response dict
|
||||||
|
2. Ensure response dict contains at least one of:
|
||||||
|
- `message.tool_calls` format (LMStudio/vLLM) — `4-specification.md:SP-017`
|
||||||
|
- `content[{type=>"tool_calls"}]` format (OpenAI) — `4-specification.md:SP-018`
|
||||||
|
3. Response dict may optionally contain: `api`, `provider`, `model`, `usage` (`llmUsage` or dict), `finish_reason`, `error_message` (`4-specification.md:SP-017`, `SP-018.4`)
|
||||||
|
|
||||||
|
```julia
|
||||||
|
function my_openai_llmCall(messages::Dict{String, Any}; eventSink=nothing)
|
||||||
|
# messages["messages"] contains OpenAI-format messages
|
||||||
|
# messages["temperature"] (default 0.7)
|
||||||
|
# messages["tools"] (if tools are set)
|
||||||
|
|
||||||
|
body = Dict(
|
||||||
|
"model" => "gpt-4",
|
||||||
|
"messages" => messages["messages"],
|
||||||
|
"tools" => get(messages, "tools", Any[])
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = HTTP.post("https://api.openai.com/v1/chat/completions",
|
||||||
|
headers, JSON.json(body))
|
||||||
|
json_resp = JSON.parse(String(resp.body))
|
||||||
|
|
||||||
|
# Extract first completion
|
||||||
|
choice = json_resp["choices"][1]["message"]
|
||||||
|
|
||||||
|
# Build uniform response
|
||||||
|
return Dict(
|
||||||
|
"api" => "openai",
|
||||||
|
"provider" => "openai",
|
||||||
|
"model" => "gpt-4",
|
||||||
|
"message" => choice,
|
||||||
|
"usage" => json_resp["usage"],
|
||||||
|
"finish_reason" => json_resp["choices"][1]["finish_reason"]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Reference**: `1-requirements.md:FR-002`, `4-specification.md:SP-017, SP-018`
|
||||||
|
|
||||||
|
### 3.2: Add a Custom Tool
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Define tool schema in `inputSchema` (JSON Schema format):
|
||||||
|
```julia
|
||||||
|
schema = Dict{String, Any}(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict(
|
||||||
|
"query" => Dict("type" => "string", "description" => "Search query"),
|
||||||
|
"max_results" => Dict("type" => "integer", "default" => 5)
|
||||||
|
),
|
||||||
|
"required" => ["query"]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
2. Implement `execute` function:
|
||||||
|
```julia
|
||||||
|
tool = agentTool(
|
||||||
|
name="searchWine",
|
||||||
|
label="Search Wine Inventory",
|
||||||
|
description="Search for wines matching a query",
|
||||||
|
inputSchema=schema,
|
||||||
|
execute=(toolCallId, args, signal, eventSink) -> begin
|
||||||
|
query = args["query"]
|
||||||
|
max_results = get(args, "max_results", Dict{Any,Any}()["max_results"] => 5...) # handle default
|
||||||
|
|
||||||
|
wines = run_search(query, max_results)
|
||||||
|
|
||||||
|
result_text = join(["- $(w.name) (\$$(w.price))" for w in wines], "\n")
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("Found $(length(wines)) wines:\n$result_text")],
|
||||||
|
Dict("count" => length(wines)),
|
||||||
|
nothing, false # content, details, usage, terminate
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
prepareArguments = nothing,
|
||||||
|
validateRequiredArgs = nothing, # uses default validator
|
||||||
|
parallelToolExecute = false
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register: push!(tools, tool.name => tool)
|
||||||
|
```
|
||||||
|
3. Optionally override validation:
|
||||||
|
```julia
|
||||||
|
function my_validate(args)
|
||||||
|
if haskey(args, "query") && length(args["query"]) < 3
|
||||||
|
return "Query must be at least 3 characters"
|
||||||
|
end
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
agentTool(
|
||||||
|
...,
|
||||||
|
validateRequiredArgs = my_validate, # replaces default validator
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Reference**: `1-requirements.md:FR-004`, `4-specification.md:SP-009, SP-010`
|
||||||
|
|
||||||
|
### 3.3: Implement User Approval Gate (beforeToolCall Hook)
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Implement `beforeToolCall` that blocks expensive/destructive operations:
|
||||||
|
```julia
|
||||||
|
function my_approval_toolCall(ctx::beforeToolCallContext, signal::abortSignal, eventSink)
|
||||||
|
if ctx.toolCall.name in ("delete_wine", "update_price_bulk")
|
||||||
|
eventSink("Approval required for $(ctx.toolCall.name) with args $(ctx.args)")
|
||||||
|
# In production, send to UI and wait for user input
|
||||||
|
approved = ask_user_for_approval(ctx.toolCall.name, ctx.args) # blocking call
|
||||||
|
if !approved
|
||||||
|
return beforeToolCallResult(true, "User denied approval for $(ctx.toolCall.name)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nothing # allow execution
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Reference**: `2-solution_design.md:SD-005` (lifecycle hooks), `3-walkthrough.md:WT-032` (beforeToolCall blocks)
|
||||||
|
|
||||||
|
### 3.4: Implement Result Sanitization (afterToolCall Hook)
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Implement `afterToolCall` to mask sensitive data:
|
||||||
|
```julia
|
||||||
|
function my_sanitize_toolCall(ctx::afterToolCallContext, signal::abortSignal, eventSink)
|
||||||
|
if ctx.toolCall.name == "getCustomerInfo"
|
||||||
|
# Mask sensitive fields before LLM sees result
|
||||||
|
result = ctx.result
|
||||||
|
for (i, block) in enumerate(result.content)
|
||||||
|
if block isa textContent
|
||||||
|
masked = replace(block.text, r"\d{12}" => "***-***-****") # mask SSN
|
||||||
|
result = agentToolResult(
|
||||||
|
[replace(block.text, r"\d{12}" => "***-***-****") for block in result.content],
|
||||||
|
result.details,
|
||||||
|
result.usage,
|
||||||
|
result.terminate
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
return nothing # pass through for other tools
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Reference**: `2-solution_design.md:SD-005` (lifecycle hooks)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Common Error Codes
|
||||||
|
|
||||||
|
| Error Condition | Response Code | Description |
|
||||||
|
|-----------------|---------------|-------------|
|
||||||
|
| Tool not found | `immediateOutcome("Tool <name> not found", true)` | `context.tools[toolCall.name]` returns `nothing` |
|
||||||
|
| Missing required args | `immediateOutcome("Missing required arguments: <fields>", true)` | `validateRequiredArgs` returns error string |
|
||||||
|
| Custom validation failed | `immediateOutcome("<tool.validateRequiredArgs result>", true)` | Tool-specific validation hook returns error |
|
||||||
|
| beforeToolCall blocked | `immediateOutcome("Tool execution was blocked: <reason>", true)` | `beforeToolCall` returns `beforeToolCallResult(true, reason)` |
|
||||||
|
| Operation aborted | `immediateOutcome("Operation aborted", true)` | `signal.aborted == true` after `beforeToolCall` |
|
||||||
|
| Tool execution crashed | `immediateOutcome("<exception message>", true)` | `preptool.execute()` throws exception |
|
||||||
|
| MCP error | `agentToolResult(["MCP error: <err_msg>"], {..., "error"=>err_msg}, ...)` | Server returns `response["error"]` JSON-RPC envelope |
|
||||||
|
| MCP network error | `agentToolResult(["MCP call error: <err_msg>"], {..., "error"=>err_msg}, ...)` | Exception during `mcp("tools/call", ...)` |
|
||||||
|
|
||||||
|
> **Reference**: `4-specification.md:SP-009, SP-010`, `src/agentCore.jl:989, 1013, 1019, 1033, 1097`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Debugging Checklist
|
||||||
|
|
||||||
|
Per ASG Framework §Pre-Code Debugging Habit:
|
||||||
|
|
||||||
|
| Stage | Debug Question |
|
||||||
|
|-------|----------------|
|
||||||
|
| **Implementation** | If a tool is not found, does `prepareToolCall` return `immediateOutcome` with "Tool not found"? |
|
||||||
|
| **Testing** | If an LLM response has `tool_calls`, does `_extractToolCalls` return `(true, toolCallList, assistantMessageToolCall)`? |
|
||||||
|
| **Deploying** | If the agent loop exits, does `eventSink` contain the error message? |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. References
|
||||||
|
|
||||||
|
- **API contract**: [`4-specification.md`](4-specification.md) — message types, MCP JSON-RPC format, response extraction formats
|
||||||
|
- **Solution decisions**: [`2-solution_design.md`](2-solution_design.md) — component architecture, lifecycle hooks, format-agnostic LLM responses
|
||||||
|
- **Walkthroughs**: [`3-walkthrough.md`](3-walkthrough.md) — end-to-end agent flow, error paths, hook sequences
|
||||||
|
- **Validation tests**: [`6-validation.md`](6-validation.md) — test traceability matrix mapping requirements/specs to test files
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
|
# check if this column has vector embedding. if there is one, seach vector version instead
|
||||||
|
column_name_embedding = column_name * "_embedding"
|
||||||
|
if occursin(column_name_embedding, tables_schema[column_name_embedding])
|
||||||
d = Dict(
|
vector_column = Dict(
|
||||||
"hello"=> 555,
|
"table_name"=> table_name,
|
||||||
"world"=> Dict(
|
"column_name"=> column_name_embedding,
|
||||||
"name"=> "ton"
|
"operator"=> "vector_similarity",
|
||||||
)
|
"value"=> column_obj["value"]
|
||||||
)
|
)
|
||||||
|
end
|
||||||
x = 55
|
|
||||||
|
|
||||||
@info "YiemAgent think() 1 " d x @__LINE__
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAtGVYSWZJSSoACAAAAAYAEgEDAAEAAAABAAAAGgEFAAEAAABWAAAAGwEFAAEAAABeAAAAKAEDAAEAAAACAAAAEwIDAAEAAAABAAAAaYcEAAEAAABmAAAAAAAAAEgAAAABAAAASAAAAAEAAAAGAACQBwAEAAAAMDIxMAGRBwAEAAAAAQIDAACgBwAEAAAAMDEwMAGgAwABAAAA//8AAAKgBAABAAAAgAAAAAOgBAABAAAAgAAAAAAAAABKviP0AAANMElEQVR4nO2de3BU1R3Hv7+zu9nNO9mQTQIJzxAeQkKI5MFjAgJJrEiSVmZqq2L/UdvOVO34Xx9WWzvTTutjHMdHrVbb6UyLDqC0jQ9s66ABLaIiCAgmQEhIYh6bBLLPezpnIZDdvQs3mMfde+5n5s5kz97du9nzu79zft/z+50FTExMTExMTExMTExMTExMTExMTEwMTeWWLYnigMRYITODvrsv/vU4JIUgKdXVWx0eh/s4gVv8tuC8/bt2nYeEMEiKN7H/HiLMAFGuLWC5C5JCUt/9hBmhBs7PyuoFmNR3/wgSewHpPEDU3T+CpF5AOg/gcbgfiOp8AVGu1W/9MSRDGg9QWbt5A8B+BULFlc/knxDYI81N218RD2BwDG8A2jteTkMwrAFce8fLZQgWGJCqmvqVYOxFEJZ8/XejXADXF8xd8GHbiaOnYTAMOQlsfnPn+/5u1wKQshXgX1zr+3DOT3KO+zLgWSTeEwbEsEPACGVld9lsrrO3gtNPAZqvteMBeiyTPM80NTV5YWAMbwBhhpDdeQdADwIoiHHaaYA/5O/OeXn//uf8kABpDGD0/IAzek/tOWJKdfM/X3sXEmHIOcCVEGM559gd2c45dsvW+VIagIBx/vOoNovyMCRESgOI9AJc0rtfWgOI9AJM0rtfeipqG94WByRG6pxADv4QJEdqAwDxHFLIcPr+WJBOBxihvK4ujbjjMBFndjsW/3fnzn5IiLSTQIL9kQuJIZTn8ZC0k0ApPUBVXeMKhSvNRHRhNZRzhYFWv//GjmZIhnQeoLq62qpw/uylzhcQMQV4RqwXQDKkM4DhxIz7iFAa9QSh2JrV+SNIhlRDwKoNm2YGrJZDBEpRPYHjvKIEl3zw1ustkASpPEDQan0yZucLCEmMsacgEdIYQHnd5lsAbL7qiUQ3VtTUfwuSQHLF/Cr1AKrwDlm0ASZXzB9O7uz+0KHyCmm0ASZDzA+O70e2W6wKSta3YtmGFlhtStTrCPyHK2sbqmBwmHQx/0UWVbUhOd2DpDQvFlSciX6xJNoAkzHmT5t2HoXLz156XHR9O9KzVWpCJdAGmJFjfgJEBnAYBI7lG1pEEggutTGO5Ru/DD0XdT7Rw+Ubb54Dg8Jki/nnlHTBOX0w6vzM3CHMLumSThtgMsX8jmQfrlt9Kubrlqw5BUeKTyptgBkz5mequ34VrzsJmz0Y87W2hCCWVqsbCDE8WV1fnwGDwWSJ+V2z3cgv6rnq6wsWfIXcuX3SaANMlpi/dL329Z1lN7RKow0wmWJ+rSRJpA0w2WJ+rRRJog0w2WJ+rZAk2gCTMebXSqYE2gCTNebXyhKDawNM1phfKzaDawNMtpg/rc+G6a1JsA9r/9eNrA2QYXL7R8X8G7Z+qhr2LdvjxJIPnKHN3nx2Bf9pbEfXDG3h4fkBO95+qQQBf4ThxHlNAZMl5hd3/kjnCxK8DCveyR6TNrCwss1w2gCTJeZP6bdFbfOY7B5bbez8sg7DaQNMlpi/J9cTcvuj6Zg1DNm1ASZLzO9NvDDm97m88NmDOLlgCB9s6L4mbUBcyyjaAMVbzM/AtqnF/Bvv/GRcwj4t+H0WvP2nZRgeih72ucJv2ffmzlcRJzBDxPxrxyfmH5M2sLbVENoAi/eYP2dOP/IXXH2df7zJL+oxhDbA4n2dX6zdTxXLDJA3wGRa5x9vkgygDVjjIeZniI75iYCe9lTs23V5A/CsGUMoLO0Y8zUK04PIT1Yw4Gc42GOB/wqrx8cP5KHnzOUgRAmy0GfhPKY28HvoGIrrev5RMAvH+ts/RapzbLH98uwgKlyXNwbvGmbY3pIAJcbeYYN9Duz+czGUgAbnGQf7DbC4rucfhXDFY+18wVJn+K7wrkQldMQiNdODBeUq6WJqxIE2wOK+nv+iBFy0on3SHOPCcpEudk7jW+k7b4DFW8wfiZBlS4UEzK5tv8eDveHzNDEEdA3TVSXh0o0tII17TOpZG9DlJJAhYT0n/v7I10ucCMTrAYqaVc8p7USWigTMSLhzHpqcdXso5pj+UbcFAz5CQUoQbi/hYJ815rmjcYYk4U58+bH4TalIuB+cdnK6PDX0evk6ANuhM3Q9CRyhorb+TiJ6MbJdpGpt3BotAdsswOZZvktjeecw4bVWOwJjzw3VIAmXYHgoQe3pO/c27XgJOkeXQ8BoyjZtmgbgt2rPiWIPNQm42BkIm8jlJHIsdQYnRBJetkF9gs/BH63a2OiCztG9AVgD1ieIKCpzY0ZRL/LmqUmxQFpCtA9PTxjn2/8ieXP7MH1+LyIhkFNhyqPQObo2gMqaxjoCvqN25xWvjR1atw1F/x7m6SE2oZKwTcUTEdF3q2oaNkHH6PaXQ8s2bUqyKLQLRJmRz5Wsa0V2Qey1/17vhZSNTDsPqXoffWXD4b6J+1etCUEk2IM425KpNhSsKswvfv7kycMqueVTjy6jAIEtYPkliOZGtjvzBjG7+OqJHPu7raHjWln8YSYWHkgPpZEdLXXjULn6cDOCKCA5dWQaes6khrUT0Sxvov8XAB6ADtGlByivaSwB4Y8UMUSJWH/lN4/AkTSxv+k461gKKt9yweZjoSPvVBLcWT64p/muGE5NmzGIls9c4Ep4cMU5r8ift+gfZ04cGftChWxzALH6R0x5gVS8k6jYTcsau9w7VvJaE6PaRC3B1UgRMrGKIilWMonwgh5XB3VnAF5H5v0EWq71y50IhtIC0W0Z2rxOTCMlFFuyO++FztCVAVTU1s8GqWT8ElBWcwLMOjGhXCRHy9zozbn8m9G9Li+OlLqhBTFMLa9RzxxmnB5aWdcwDzpCV5NAAp4V6fpqs+zjB3JDx2gdQMuWL6PhfhvOHShDwJ0Ox+wWOOYfUz3Pb1PQdGsbsjscocfdeR4oltj6cNuxLJw55gz/zHYFfm/EFIuQpAB/ALA+FCDoAN1IweW19bczope1nHtNWcAKw1d/vQ2+jumXmtLW/hspK/bh6+L3WkNlY2pZwmpw4PZ9TTv+Ah2giyFgxfqGLCL8Tuv5QgcYaxawr316WOcLzu1fgfHAZg+geJ32nA8Cf3x13RbtdWlGNwCLDY8RSJNuLjJxhfsfF1+nYNyYMb8X0wv7tH6YLD/3azZ4QxtAec1msUx6m5ZzhQQs1v6vBVtuB2yuzrC25OUfYTwpibE4pQYR7iiva6iB7EJQQeGiezhxIZAcvnBQGwGFaueW3CAk4IGodpuHwdV+IXb3OZSYSRxJiz4HWQOwpA0gtWIfkkvHbgABdwb8XTlgdg/IGrEMLSRhRwBnv4yWhAUceAPgB0b+V8Ypo+3EkSn97WLdTAJHqKirf5pA90S2T8sfwJoth0Mh4WhcZxxYu2M6EjwsNK3+ZFUPPqvU6orHxmDzKgzuWR362pjDA2fjK0jID08LFykge15dhO5T6dFvwPnTe9/Y+QPoiCkfAkYjiimI4y61jF/h+iM7X1C+OzvU+QLxdMl7WUgVpeDjTKAv81LnCxSPA+7d0R5cfMbS9a2hohUV7i6vrV8DHaEbA6irqxPF28+Logq1ApBYGb+huv9RiO5Jdo+/AQQHREofRRmF6mfKHMaiSvWNJhnwfHX11gsCgw7QjQH0c/vPQFgc2S42ZBAbM8Ti7MxwwxB7APTmjn+1kC2nA8xxWR0U2GfFLkubf307MlwqmcNERd7E/p9AJ0z5JFBQeWP9UgAvA+HlX2LiVtVwFEmpsVfhOgs8SO+zIXnQBrfTh+ZvdGEgItd/PCBrALa8dgQ6c8H9djjmnkBGTRMoQf1aYihw5p3DyUPZ4DxidRC0cmZh0a6240fHvoWp4SaBDz7IKvZ9vIdAUcWUItdf7NMXzxx8dya++F+4ACXgnH84My2hatu2bZNX167HIaCi+eP71To/OT1G4WWcsXhlG5IzoockIlpxasB/r9RDQGj1j+HvBEqIdJ/lN32BVOfUVf6OF2J1MH3aeZz+PFr5JWDNzPkL/3b6+JGJiVv17gHE6p9a7d+s67rgmqVt+TUeyC4YwMzF6vsKXVwdJOkMoKJm8/dAVKO20re0+iSMRvHaU0hMUZ0wrquord+KKYKmqtjD5rd+DoIo+ojK/NFceBlnuLuTMdQXLQFw8F74lMX73nk9fLHCqAkh1oDlKbXOF4gvSO1LMjIEcsJmeQLAtyf/2pNMeW3jTYz4rsm+bjxAxBub/7Vzx6Rec7LLvhm3HwJR/mReN37gk/6z9ZM6CSTYf2N2/pWgPK8Hv4YRPUDFDTfnkM3yBCc+4UZHHFWRhpZoZciwa5vy9HsDGI6sJee8jRMmfEt44qRwf/DeyZoQTr0UPAFU1jW8AiBsW5b81ARU5mrabgh7zw6hbTBq/eHVvU07xLY1hmLKpWCTqcU0AMkxDUByTAOQHF2Vhk0kZwZ92D6kbdFNidr31bhIYwCiS4MSdaxWzCFAckwDkBzTACTHkHMArtCzYPzNcX1ThXS75buJiYmJiYmJiYmJiYmJiYmJiYmJCa7I/wEjf8WA9CzV0gAAAABJRU5ErkJggg==
|
|
||||||
@@ -1,537 +0,0 @@
|
|||||||
https://github.com/andyz245/LanguageAgentTreeSearch/blob/main/hotpot/hotpot.py
|
|
||||||
|
|
||||||
|
|
||||||
standard_prompt = '''
|
|
||||||
Write a coherent passage of 4 short paragraphs. The end sentence of each paragraph must be: {input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
reflection_prompt = '''You are an advanced reasoning agent that can improve based on self refection. You will be given a previous reasoning trial in which you were given access to an Docstore API environment and a question to answer. You were unsuccessful in answering the question either because you guessed the wrong answer with Finish[<answer>], or you used up your set number of reasoning steps. In a few sentences, Diagnose a possible reason for failure and devise a new, concise, high level plan that aims to mitigate the same failure. Use complete sentences.
|
|
||||||
Here are some examples:
|
|
||||||
Previous Trial:
|
|
||||||
Question: The Rome Protocols were signed by three Prime Ministers one of which was assassinated as part of what?
|
|
||||||
Thought 1: I need to search Rome Protocols, find the three Prime Ministers, then find what they were assassinated as part of.
|
|
||||||
Action 1: Search[Rome Protocols]
|
|
||||||
Observation 1: The Rome Protocols were a series of three international agreements concluded in Rome on 17 March 1934 between the governments of Austria, Hungary and Italy. They were signed by Italian Prime Minister Benito Mussolini, Austrian Chancellor Engelbert Dollfuss and Hungarian Prime Minister Gyula Gömbös. All of the protocols went into effect on 12 July 1934 and were registered in League of Nations Treaty Series on 12 December 1934.
|
|
||||||
Thought 2: The three Prime Ministers were Benito Mussolini, Engelbert Dollfuss, and Gyula Gömbös. I need to search Benito Mussolini and find what he was assassinated as part of.
|
|
||||||
Action 2: Search[Benito Mussolini]
|
|
||||||
Observation 2: Benito Amilcare Andrea Mussolini (UK: MU(U)SS-ə-LEE-nee, US: MOOSS-, Italian: [beˈniːto aˈmilkare anˈdrɛːa mussoˈliːni]; 29 July 1883 – 28 April 1945) was an Italian politician and journalist who founded and led the National Fascist Party (PNF). He was Prime Minister of Italy from the March on Rome in 1922 until his deposition in 1943, as well as "Duce" of Italian fascism from the establishment of the Italian Fasces of Combat in 1919 until his summary execution in 1945 by Italian partisans. As dictator of Italy and principal founder of fascism, Mussolini inspired and supported the international spread of fascist movements during the inter-war period.Mussolini was originally a socialist politician and a journalist at the Avanti! newspaper. In 1912, he became a member of the National Directorate of the Italian Socialist Party (PSI), but he was expelled from the PSI for advocating military intervention in World War I, in opposition to the party's stance on neutrality. In 1914, Mussolini founded a new journal, Il Popolo d'Italia, and served in the Royal Italian Army during the war until he was wounded and discharged in 1917. Mussolini denounced the PSI, his views now centering on Italian nationalism instead of socialism, and later founded the fascist movement which came to oppose egalitarianism and class conflict, instead advocating "revolutionary nationalism" transcending class lines. On 31 October 1922, following the March on Rome (28–30 October), Mussolini was appointed prime minister by King Victor Emmanuel III, becoming the youngest individual to hold the office up to that time. After removing all political opposition through his secret police and outlawing labor strikes, Mussolini and his followers consolidated power through a series of laws that transformed the nation into a one-party dictatorship. Within five years, Mussolini had established dictatorial authority by both legal and illegal means and aspired to create a totalitarian state. In 1929, Mussolini signed the Lateran Treaty with the Holy See to establish Vatican City.
|
|
||||||
Mussolini's foreign policy aimed to restore the ancient grandeur of the Roman Empire by expanding Italian colonial possessions and the fascist sphere of influence. In the 1920s, he ordered the Pacification of Libya, instructed the bombing of Corfu over an incident with Greece, established a protectorate over Albania, and incorporated the city of Fiume into the Italian state via agreements with Yugoslavia. In 1936, Ethiopia was conquered following the Second Italo-Ethiopian War and merged into Italian East Africa (AOI) with Eritrea and Somalia. In 1939, Italian forces annexed Albania. Between 1936 and 1939, Mussolini ordered the successful Italian military intervention in Spain in favor of Francisco Franco during the Spanish Civil War. Mussolini's Italy initially tried to avoid the outbreak of a second global war, sending troops at the Brenner Pass to delay Anschluss and taking part in the Stresa Front, the Lytton Report, the Treaty of Lausanne, the Four-Power Pact and the Munich Agreement. However, Italy then alienated itself from Britain and France by aligning with Germany and Japan. Germany invaded Poland on 1 September 1939, resulting in declarations of war by France and the UK and the start of World War II.
|
|
||||||
On 10 June 1940, Mussolini decided to enter the war on the Axis side. Despite initial success, the subsequent Axis collapse on multiple fronts and eventual Allied invasion of Sicily made Mussolini lose the support of the population and members of the Fascist Party. As a consequence, early on 25 July 1943, the Grand Council of Fascism passed a motion of no confidence in Mussolini; later that day King Victor Emmanuel III dismissed him as head of government and had him placed in custody, appointing Pietro Badoglio to succeed him as Prime Minister. After the king agreed to an armistice with the Allies, on 12 September 1943 Mussolini was rescued from captivity in the Gran Sasso raid by German paratroopers and Waffen-SS commandos led by Major Otto-Harald Mors. Adolf Hitler, after meeting with the rescued former dictator, then put Mussolini in charge of a puppet regime in northern Italy, the Italian Social Republic (Italian: Repubblica Sociale Italiana, RSI), informally known as the Salò Republic, causing a civil war. In late April 1945, in the wake of near total defeat, Mussolini and his mistress Clara Petacci attempted to flee to Switzerland, but both were captured by Italian communist partisans and summarily executed by firing squad on 28 April 1945 near Lake Como. The bodies of Mussolini and his mistress were then taken to Milan, where they were hung upside down at a service station to publicly confirm their demise.
|
|
||||||
Thought 3: Benito Mussolini was assassinated as part of the collapse of the Axis powers during World War II. So the Rome Protocols were signed by three Prime Ministers one of which was assassinated as part of World War II.
|
|
||||||
Action 3: Finish[World War II]
|
|
||||||
|
|
||||||
Reflection: I searched one of the prime ministers involved in the signing, then attemted to answer right away. I should have searched each of the prime ministers, then looked up 'death' on each of their pages in order to get more information before answering.
|
|
||||||
|
|
||||||
Previous Trial:
|
|
||||||
Question: Kam Heskin plays Paige Morgan in a 2004 film directed by who?
|
|
||||||
Thought 1: I need to search Kam Heskin and Paige Morgan, find the 2004 film they are in, then find the director of the film.
|
|
||||||
Action 1: Search[Kam Heskin]
|
|
||||||
Observation 1: Kam Erika Heskin (born May 8, 1973) is an American actress. She began her career playing Caitlin Deschanel on the NBC daytime soap opera Sunset Beach (1998–1999), before appearing in films Planet of the Apes (2001) and Catch Me If You Can (2002). Heskin went on in 2003 to play Elizabeth Bennet in an independent film Pride & Prejudice: A Latter-Day Comedy, Robin in Charmed (2004), and Paige Morgan in The Prince and Me film franchise (2006–2010).
|
|
||||||
Thought 2: Kam Heskin plays Paige Morgan in The Prince and Me film franchise. I need to search The Prince and Me film franchise and find the director of the 2004 film.
|
|
||||||
Action 2: Search[The Prince and Me film franchise]
|
|
||||||
Observation 2: Could not find [The Prince and Me film franchise]. Similar: ['Kam Heskin', 'Prince of Persia', 'Harry Potter and the Half-Blood Prince (film)', 'Prince of Persia: The Sands of Time (film)', 'The Chronicles of Narnia (film series)', 'Majestic Prince (franchise)', 'Kingsman (franchise)', 'The Little Prince (2015 film)', 'Shrek (franchise)', 'Harry Potter (film series)']
|
|
||||||
Thought 3: I need to search The Prince and Me (2004 film) instead.
|
|
||||||
Action 3: Search[The Prince and Me (2004 film)]
|
|
||||||
Observation 3: Could not find [The Prince and Me (2004 film)]. Similar: ['The Prince & Me', 'The Prince & Me 2: The Royal Wedding', 'Kam Heskin', 'Prince of Darkness (film)', 'Prince of Persia: The Sands of Time (film)', 'Rob Knox', 'Alexander (2004 film)', 'Prince of Persia', 'Brooklynn Prince', 'Prince (musician)']
|
|
||||||
Thought 4: I need to search The Prince & Me (2004 film).
|
|
||||||
Action 4: Search[The Prince & Me (2004 film)]
|
|
||||||
Observation 4: Could not find [The Prince & Me (2004 film)]. Similar: ['The Prince & Me', 'The Prince & Me 2: The Royal Wedding', 'Prince of Darkness (film)', 'Prince of Persia: The Sands of Time (film)', 'Rob Knox', 'Alexander (2004 film)', 'Prince (musician)', 'Prince of Persia', 'Kam Heskin', 'Brooklynn Prince']
|
|
||||||
Thought 5: I need to search The Prince & Me (2004 film) instead.
|
|
||||||
Action 5: Search[The Prince & Me (2004 film)]
|
|
||||||
Observation 5: Could not find [The Prince & Me (2004 film)]. Similar: ['The Prince & Me', 'The Prince & Me 2: The Royal Wedding', 'Prince of Darkness (film)', 'Prince of Persia: The Sands of Time (film)', 'Rob Knox', 'Alexander (2004 film)', 'Prince (musician)', 'Prince of Persia', 'Kam Heskin', 'Brooklynn Prince']
|
|
||||||
Thought 6: I need to search The Prince & Me (2004 film).
|
|
||||||
Action 6: Search[The Prince & Me (2004 film)]
|
|
||||||
Observation 6: Could not find [The Prince & Me (2004 film)]. Similar: ['The Prince & Me', 'The Prince & Me 2: The Royal Wedding', 'Prince of Darkness (film)', 'Prince of Persia: The Sands of Time (film)', 'Rob Knox', 'Alexander (2004 film)', 'Prince (musician)', 'Prince of Persia', 'Kam Heskin', 'Brooklynn Prince']
|
|
||||||
|
|
||||||
Reflection: I got stuck in a loop where I kept trying to search 'The Prince & Me (2004 film)' but the page could not be found. Instead I should have tried to search the similar results that had a similar name to see and they were made in 2004.
|
|
||||||
|
|
||||||
Previous Trial:
|
|
||||||
Question: The Oberoi family is part of a hotel company that has a head office in what city?
|
|
||||||
Thought 1: I need to search the Oberoi family and find the city where the hotel company's head office is located.
|
|
||||||
Action 1: Search[Oberoi family]
|
|
||||||
Observation 1: The Oberoi Group is a luxury hotel group with its head office in New Delhi, India.[2] Founded in 1934, the company owns and operates 32 luxury hotels and two river cruise ships in 7 countries, primarily under its Oberoi Hotels & Resorts and Trident brands.[3] The group also operates The Oberoi Centre for Learning and Development, which is regarded as one of Asia's top institutions for hospitality education.[4]. The foundations of the Oberoi Group date back to 1934 when The Rai Bahadur Mohan Singh Oberoi, the founder of the group, bought two properties: the Maidens in Delhi and the Clarke's in Shimla.[5] In the following years Oberoi, assisted by his two sons, Tilak Raj Singh Oberoi and Prithvi Raj Singh Oberoi (P.R.S. Oberoi
|
|
||||||
Thought 2: The Oberoi Group has its head office in New Delhi, India. I just need to finish with New Delhi.
|
|
||||||
Action 2: Finish[New Delhi]
|
|
||||||
Observation 2: Episode finished, reward = 0
|
|
||||||
|
|
||||||
Reflection: I appeared to have retrieved the correct information about The Oberoi Family and the location of it's head office, and provided a corresponding answer. However this answer does not exactly match the ground truth answer so I should try a different wording, such as Delhi.
|
|
||||||
|
|
||||||
Previous trial:
|
|
||||||
{trajectory}Reflection:'''
|
|
||||||
|
|
||||||
cot_prompt = '''
|
|
||||||
Solve a question answering task with interleaving Thought, Action, Observation steps. Thought can reason about the current situation, and Action can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
After each observation, provide the next Thought and next Action. Here are some examples:
|
|
||||||
|
|
||||||
Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?
|
|
||||||
Thought 1: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
|
|
||||||
Action 1: Search[Colorado orogeny]
|
|
||||||
Observation 1: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas. This took place from 1780 to 1650 million years ago (Mya), during the Paleoproterozoic (Statherian Period). It is recorded in the Colorado orogen, a >500-km-wide belt of oceanic arc rock that extends southward into New Mexico. The Colorado orogeny was likely part of the larger Yavapai orogeny.
|
|
||||||
Thought 2: It does not mention the eastern sector of the Colorado orogeny. I need to look up eastern sector.
|
|
||||||
Action 2: Lookup[eastern sector]
|
|
||||||
Observation 2: (Result 1 / 1) The Colorado orogen, formerly called the Colorado province, is a >500-km-wide belt of oceanic arc rock (1.78\u20131.65 Ga) that extends southward into New Mexico and composes a major part of the Proterozoic provinces of southwestern United States. This transcontinental collisional event occurred during the Paleoproterozoic (Statherian Period).[1] The Wyoming sector of the Colorado orogeny was formerly called the Medicine Bow orogeny. The eastern sector extends into the High Plains and is called the Central Plains orogeny. The boundary between the Colorado orogeny and the Wyoming craton is the Cheyenne belt, a 5-km-wide mylonitic shear zone that verges northward. The Cheyenne belt transects and cuts off the south edge of the older Trans-Hudson orogeny.[2]
|
|
||||||
Thought 3: The eastern sector of Colorado orogeny extends into the High Plains, so High Plains is the area. I need to search High Plains and find its elevation range.
|
|
||||||
Action 3: Search[High Plains]Observation 3: High Plains refers to one of two distinct land regions:
|
|
||||||
Thought 4: I need to search High Plains (United States).
|
|
||||||
Action 4: Search[High Plains (United States)]
|
|
||||||
Observation 4: The High Plains are a subregion of the Great Plains, mainly in the Western United States, but also partly in the Midwest states of Nebraska, Kansas, and South Dakota, generally encompassing the western part of the Great Plains before the region reaches the Rocky Mountains. The High Plains are located in eastern Montana, southeastern Wyoming, southwestern South Dakota, western Nebraska, eastern Colorado, western Kansas, eastern New Mexico, western Oklahoma, and to just south of the Texas Panhandle.[4] The southern region of the Western High Plains ecology region contains the geological formation known as Llano Estacado which can be seen from a short distance or on satellite maps.[5] From east to west, the High Plains rise in elevation from around 1,800 to 7,000 ft (550 to 2,130 m).[3]
|
|
||||||
Thought 5: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
|
|
||||||
Action 5: Finish[1,800 to 7,000 ft]
|
|
||||||
|
|
||||||
Question: Guitars for Wounded Warriors is an album that was recorded in the village in which New York county?
|
|
||||||
Thought 1: I need to search Guitars for Wounded Warriors, then find in which New York county it was recorded.
|
|
||||||
Action 1: Search[Guitars for Wounded Warriors]
|
|
||||||
Observation 1: Guitars for Wounded Warriors is the sixth solo studio album by guitarist Brian Tarquin, released in August 2014 by Cleopatra Records. In 2016, it received a Best Compilation Album nomination by the Independent Music Awards. All tracks were recorded at Tarquin's Jungle Room Studios in New Paltz (village), New York. Being moved by the lack of support for returning veterans through his life Tarquin decided to compose a heavy instrumental rock album as a way to show his appreciation to all veterans. So once again he enlisted top notch players to guest with him on the album, Billy Sheehan on Black Hawk, Ron \"Bumblefoot\" Thal and Reb Beach on Taliban Terror, Steve Morse on Freedom, Chris Poland on Charlie Surfs and Hunting, Chuck Loeb on Escape Kabul, Hal Lindes on Sand & Blood, Gary Hoey on 5 Klicks To Hell and Baghdad, Alex De Rosso Dokken on Broken Arrow, and The Flyin' Ryan Brothers on Surgical Strike. The entire album was engineered, produced and composed by Tarquin especially for each one of the guest guitarists. Partial proceeds are donated to the Fisher House Foundation from sales.
|
|
||||||
Thought 2: Guitars for Wounded Warriors was recorded in New Paltz (village), New York. I need to search New Paltz (village), New York and find which county it is in
|
|
||||||
Action 2: Search[New Paltz (village), New York]
|
|
||||||
Observation 2: New Paltz is a village in Ulster County located in the U.S. state of New York. It is approximately 80 miles (130 km) north of New York City and 70 miles (110 km) south of Albany. The population was 7,324 at the 2020 census.[3]
|
|
||||||
Thought 3: New Paltz (village), New York is in Ulster County, so the answer is Ulster County.
|
|
||||||
Action 3: Finish[Ulster County]
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
cot_prompt_short = '''
|
|
||||||
Solve a question answering task with interleaving Thought, Action, Observation steps. Thought can reason about the current situation, and Action can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
After each observation, provide the next Thought and next Action. Here are some examples:
|
|
||||||
|
|
||||||
Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?
|
|
||||||
Thought 1: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
|
|
||||||
Action 1: Search[Colorado orogeny]
|
|
||||||
Observation 1: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas. This took place from 1780 to 1650 million years ago (Mya), during the Paleoproterozoic (Statherian Period). It is recorded in the Colorado orogen, a >500-km-wide belt of oceanic arc rock that extends southward into New Mexico. The Colorado orogeny was likely part of the larger Yavapai orogeny.
|
|
||||||
Thought 2: It does not mention the eastern sector of the Colorado orogeny. I need to look up eastern sector.
|
|
||||||
Action 2: Lookup[eastern sector]
|
|
||||||
Observation 2: (Result 1 / 1) The Colorado orogen, formerly called the Colorado province, is a >500-km-wide belt of oceanic arc rock (1.78\u20131.65 Ga) that extends southward into New Mexico and composes a major part of the Proterozoic provinces of southwestern United States. This transcontinental collisional event occurred during the Paleoproterozoic (Statherian Period).[1] The Wyoming sector of the Colorado orogeny was formerly called the Medicine Bow orogeny. The eastern sector extends into the High Plains and is called the Central Plains orogeny. The boundary between the Colorado orogeny and the Wyoming craton is the Cheyenne belt, a 5-km-wide mylonitic shear zone that verges northward. The Cheyenne belt transects and cuts off the south edge of the older Trans-Hudson orogeny.[2]
|
|
||||||
Thought 3: The eastern sector of Colorado orogeny extends into the High Plains, so High Plains is the area. I need to search High Plains and find its elevation range.
|
|
||||||
Action 3: Search[High Plains]Observation 3: High Plains refers to one of two distinct land regions:
|
|
||||||
Thought 4: I need to search High Plains (United States).
|
|
||||||
Action 4: Search[High Plains (United States)]
|
|
||||||
Observation 4: The High Plains are a subregion of the Great Plains, mainly in the Western United States, but also partly in the Midwest states of Nebraska, Kansas, and South Dakota, generally encompassing the western part of the Great Plains before the region reaches the Rocky Mountains. The High Plains are located in eastern Montana, southeastern Wyoming, southwestern South Dakota, western Nebraska, eastern Colorado, western Kansas, eastern New Mexico, western Oklahoma, and to just south of the Texas Panhandle.[4] The southern region of the Western High Plains ecology region contains the geological formation known as Llano Estacado which can be seen from a short distance or on satellite maps.[5] From east to west, the High Plains rise in elevation from around 1,800 to 7,000 ft (550 to 2,130 m).[3]
|
|
||||||
Thought 5: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
|
|
||||||
Action 5: Finish[1,800 to 7,000 ft]
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
cot_prompt_feedback_short = '''You are also an advanced reasoning agent that can improve based on self refection. Solve a question answering task with interleaving Thought, Action, Observation steps. Thought can reason about the current situation, and Action can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
After each observation, provide the next Thought and next Action. Here are some examples:
|
|
||||||
|
|
||||||
Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?
|
|
||||||
Thought 1: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
|
|
||||||
Action 1: Search[Colorado orogeny]
|
|
||||||
Observation 1: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas. This took place from 1780 to 1650 million years ago (Mya), during the Paleoproterozoic (Statherian Period). It is recorded in the Colorado orogen, a >500-km-wide belt of oceanic arc rock that extends southward into New Mexico. The Colorado orogeny was likely part of the larger Yavapai orogeny.
|
|
||||||
Thought 2: It does not mention the eastern sector of the Colorado orogeny. I need to look up eastern sector.
|
|
||||||
Action 2: Lookup[eastern sector]
|
|
||||||
Observation 2: (Result 1 / 1) The Colorado orogen, formerly called the Colorado province, is a >500-km-wide belt of oceanic arc rock (1.78\u20131.65 Ga) that extends southward into New Mexico and composes a major part of the Proterozoic provinces of southwestern United States. This transcontinental collisional event occurred during the Paleoproterozoic (Statherian Period).[1] The Wyoming sector of the Colorado orogeny was formerly called the Medicine Bow orogeny. The eastern sector extends into the High Plains and is called the Central Plains orogeny. The boundary between the Colorado orogeny and the Wyoming craton is the Cheyenne belt, a 5-km-wide mylonitic shear zone that verges northward. The Cheyenne belt transects and cuts off the south edge of the older Trans-Hudson orogeny.[2]
|
|
||||||
Thought 3: The eastern sector of Colorado orogeny extends into the High Plains, so High Plains is the area. I need to search High Plains and find its elevation range.
|
|
||||||
Action 3: Search[High Plains]Observation 3: High Plains refers to one of two distinct land regions:
|
|
||||||
Thought 4: I need to search High Plains (United States).
|
|
||||||
Action 4: Search[High Plains (United States)]
|
|
||||||
Observation 4: The High Plains are a subregion of the Great Plains, mainly in the Western United States, but also partly in the Midwest states of Nebraska, Kansas, and South Dakota, generally encompassing the western part of the Great Plains before the region reaches the Rocky Mountains. The High Plains are located in eastern Montana, southeastern Wyoming, southwestern South Dakota, western Nebraska, eastern Colorado, western Kansas, eastern New Mexico, western Oklahoma, and to just south of the Texas Panhandle.[4] The southern region of the Western High Plains ecology region contains the geological formation known as Llano Estacado which can be seen from a short distance or on satellite maps.[5] From east to west, the High Plains rise in elevation from around 1,800 to 7,000 ft (550 to 2,130 m).[3]
|
|
||||||
Thought 5: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
|
|
||||||
Action 5: Finish[1,800 to 7,000 ft]
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
|
|
||||||
You have attempted to answer the following question before and failed. The following reflection(s) give a plan to avoid failing to answer the question in the same way you did previously. Use them to improve your strategy of correctly answering the given question.
|
|
||||||
|
|
||||||
{trajectories}
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
cot_prompt_feedback = '''You are also an advanced reasoning agent that can improve based on self refection. Solve a question answering task with interleaving Thought, Action, Observation steps. Thought can reason about the current situation, and Action can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
After each observation, provide the next Thought and next Action. Here are some examples:
|
|
||||||
|
|
||||||
Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?
|
|
||||||
Thought 1: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
|
|
||||||
Action 1: Search[Colorado orogeny]
|
|
||||||
Observation 1: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas. This took place from 1780 to 1650 million years ago (Mya), during the Paleoproterozoic (Statherian Period). It is recorded in the Colorado orogen, a >500-km-wide belt of oceanic arc rock that extends southward into New Mexico. The Colorado orogeny was likely part of the larger Yavapai orogeny.
|
|
||||||
Thought 2: It does not mention the eastern sector of the Colorado orogeny. I need to look up eastern sector.
|
|
||||||
Action 2: Lookup[eastern sector]
|
|
||||||
Observation 2: (Result 1 / 1) The Colorado orogen, formerly called the Colorado province, is a >500-km-wide belt of oceanic arc rock (1.78\u20131.65 Ga) that extends southward into New Mexico and composes a major part of the Proterozoic provinces of southwestern United States. This transcontinental collisional event occurred during the Paleoproterozoic (Statherian Period).[1] The Wyoming sector of the Colorado orogeny was formerly called the Medicine Bow orogeny. The eastern sector extends into the High Plains and is called the Central Plains orogeny. The boundary between the Colorado orogeny and the Wyoming craton is the Cheyenne belt, a 5-km-wide mylonitic shear zone that verges northward. The Cheyenne belt transects and cuts off the south edge of the older Trans-Hudson orogeny.[2]
|
|
||||||
Thought 3: The eastern sector of Colorado orogeny extends into the High Plains, so High Plains is the area. I need to search High Plains and find its elevation range.
|
|
||||||
Action 3: Search[High Plains]Observation 3: High Plains refers to one of two distinct land regions:
|
|
||||||
Thought 4: I need to search High Plains (United States).
|
|
||||||
Action 4: Search[High Plains (United States)]
|
|
||||||
Observation 4: The High Plains are a subregion of the Great Plains, mainly in the Western United States, but also partly in the Midwest states of Nebraska, Kansas, and South Dakota, generally encompassing the western part of the Great Plains before the region reaches the Rocky Mountains. The High Plains are located in eastern Montana, southeastern Wyoming, southwestern South Dakota, western Nebraska, eastern Colorado, western Kansas, eastern New Mexico, western Oklahoma, and to just south of the Texas Panhandle.[4] The southern region of the Western High Plains ecology region contains the geological formation known as Llano Estacado which can be seen from a short distance or on satellite maps.[5] From east to west, the High Plains rise in elevation from around 1,800 to 7,000 ft (550 to 2,130 m).[3]
|
|
||||||
Thought 5: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
|
|
||||||
Action 5: Finish[1,800 to 7,000 ft]
|
|
||||||
|
|
||||||
Question: Guitars for Wounded Warriors is an album that was recorded in the village in which New York county?
|
|
||||||
Thought 1: I need to search Guitars for Wounded Warriors, then find in which New York county it was recorded.
|
|
||||||
Action 1: Search[Guitars for Wounded Warriors]
|
|
||||||
Observation 1: Guitars for Wounded Warriors is the sixth solo studio album by guitarist Brian Tarquin, released in August 2014 by Cleopatra Records. In 2016, it received a Best Compilation Album nomination by the Independent Music Awards. All tracks were recorded at Tarquin's Jungle Room Studios in New Paltz (village), New York. Being moved by the lack of support for returning veterans through his life Tarquin decided to compose a heavy instrumental rock album as a way to show his appreciation to all veterans. So once again he enlisted top notch players to guest with him on the album, Billy Sheehan on Black Hawk, Ron \"Bumblefoot\" Thal and Reb Beach on Taliban Terror, Steve Morse on Freedom, Chris Poland on Charlie Surfs and Hunting, Chuck Loeb on Escape Kabul, Hal Lindes on Sand & Blood, Gary Hoey on 5 Klicks To Hell and Baghdad, Alex De Rosso Dokken on Broken Arrow, and The Flyin' Ryan Brothers on Surgical Strike. The entire album was engineered, produced and composed by Tarquin especially for each one of the guest guitarists. Partial proceeds are donated to the Fisher House Foundation from sales.
|
|
||||||
Thought 2: Guitars for Wounded Warriors was recorded in New Paltz (village), New York. I need to search New Paltz (village), New York and find which county it is in
|
|
||||||
Action 2: Search[New Paltz (village), New York]
|
|
||||||
Observation 2: New Paltz is a village in Ulster County located in the U.S. state of New York. It is approximately 80 miles (130 km) north of New York City and 70 miles (110 km) south of Albany. The population was 7,324 at the 2020 census.[3]
|
|
||||||
Thought 3: New Paltz (village), New York is in Ulster County, so the answer is Ulster County.
|
|
||||||
Action 3: Finish[Ulster County]
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
|
|
||||||
You have attempted to answer the following question before and failed, either because your reasoning for the answer was incorrect or the phrasing of your response did not exactly match the answer. The following reflection(s) give a plan to avoid failing to answer the question in the same way you did previously. Use them to improve your strategy of correctly answering the given question.
|
|
||||||
|
|
||||||
{trajectories}
|
|
||||||
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.
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
vote_prompt = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by pairs of thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a list of trajectories, decide which trajectory is most promising. Analyze each trajectory in detail and consider possible errors, then conclude in the last line "The best trajectory is {s}", where s the integer id of the trajectory.
|
|
||||||
'''
|
|
||||||
|
|
||||||
compare_prompt = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by pairs of thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Briefly analyze the correctness of the following two trajectories. Conclude in the last line "The more correct trajectory is 1", "The more correct trajectory is 2", or "The two trajectories are similarly correct".
|
|
||||||
'''
|
|
||||||
|
|
||||||
score_prompt = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by pairs of thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, analyze the following trajectory, then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
'''
|
|
||||||
|
|
||||||
value_prompt = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by environmental observations about the situation, thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, evaluate its correctness and provide your reasoning and analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories can be correct if the thoughts and actions so far are correct, even if the answer is not found yet. Do not generate additional thoughts or actions. Then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
|
|
||||||
Question: Which other Mexican Formula One race car driver has held the podium besides the Force India driver born in 1990?
|
|
||||||
Thought 1: I need to search for a list of Mexican Formula One race car drivers, then eliminate the one who drove for Force India and was born in 1990.
|
|
||||||
Action 1: Search[Formula One drivers from Mexico]
|
|
||||||
Observation 1: There have been six Formula One drivers from Mexico who have taken part in races since the championship began in 1950. Sergio P\u00e9rez is the most successful, having won six Grands Prix. Since 2021, P\u00e9rez drives for the Red Bull team.. P\u00e9rez is the only Mexican F1 driver to achieve a pole position. He took his first pole position at the 2022 Saudi Arabian Grand Prix, in his 219th Grand Prix meeting (a record for the most Grands Prix before a pole).
|
|
||||||
Thought 2: Sergio P\u00e9rez, born in 1990 and drove for Force India, is not the Mexican driver we are looking for. The passage does not give me enough information about other Mexican drivers who have achieved a podium finish. I need to look up more information.
|
|
||||||
Action 2: Lookup[Formula One drivers from Mexico]
|
|
||||||
Observation 2: No more results.
|
|
||||||
Thought 3: The available information doesn't allow me to determine which other Mexican Formula One race car driver, besides Sergio P\u00e9rez, has held the podium. With the search function failing to provide a full list of Mexican Formula One drivers, I need to return different search results. Let's try searching for Mexican Grand Prix winners, as they would have held the podium.
|
|
||||||
Action 3: Search[Mexican Grand Prix winners]
|
|
||||||
Observation 3: Could not find Mexican Grand Prix winners. Similar: ['Mexican Grand Prix', 'List of Formula One Grand Prix winners', '1990 Mexican Grand Prix', '2018 Mexican Grand Prix', '2019 Mexican Grand Prix'].
|
|
||||||
Thought 4: Given the difficulties in finding a direct list of Mexican Grand Prix winners, I should search for the Mexican Grand Prix to get a broader picture of the race's history. This might include winners.
|
|
||||||
Action 4: Search[Mexican Grand Prix]
|
|
||||||
Thus the correctness score is 3
|
|
||||||
|
|
||||||
Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?
|
|
||||||
Thought 1: I need to search for the football manager who recruited David Beckham, and then find out the timeframe in which he managed Manchester United.
|
|
||||||
Action 1: Search[David Beckham's football manager]
|
|
||||||
Observation 1: Could not find David Beckham's football manager. Similar: ['David Beckham', 'Inter Miami CF (redirect from Miami Beckham United)', 'Victoria Beckham', 'The Beckham Experiment', 'Miami Freedom Park'].
|
|
||||||
Thought 2: I couldn't find the information on David Beckham's football manager. I need to search Manchester United and find out the timeframe of the manager.
|
|
||||||
Action 2: Search[Manchester United]
|
|
||||||
Observation 2: Manchester United Football Club, commonly referred to as Man United (often stylised as Man Utd), or simply United, is a professional football club based in Old Trafford, Greater Manchester, England. The club competes in the Premier League, the top division in the English football league system. Nicknamed the Red Devils, they were founded as Newton Heath LYR Football Club in 1878, but changed their name to Manchester United in 1902. After a spell playing in Clayton, Manchester, the club moved to their current stadium, Old Trafford, in 1910.. Domestically, Manchester United have won a record 20 league titles, 12 FA Cups, six League Cups and a record 21 FA Community Shields.
|
|
||||||
Thought 3: The information about David Beckham's football manager is not available. I will search for Manchester United's managers to find the timeframe.
|
|
||||||
Action 3: Search[Manchester United managers]
|
|
||||||
Observation 3: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
Thought 4: The information about David Beckham's football manager is not available. The question asks for the timeframe when the manager managed Manchester United, so I need to find a list of Manchester United managers.
|
|
||||||
Action 4: Search[list of Manchester United managers]
|
|
||||||
Observation 4: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
Thus the correctness score is 4
|
|
||||||
|
|
||||||
Question: Which magazine was started first Arthur's Magazine or First for Women?
|
|
||||||
Thought 1: I need to search Arthur's Magazine and First for Women, and find which was started first.
|
|
||||||
Action 1: Search[Arthur's Magazine]
|
|
||||||
Observation 1: Arthur's Magazine (1844\u00e2\u0080\u00931846) was an American literary periodical published in Philadelphia in the 19th century. Edited by Timothy Shay Arthur, it featured work by Edgar A. Poe, J.H. Ingraham, Sarah Josepha Hale, Thomas G. Spear, and others.[1][2] In May 1846 it was merged into Godey's Lady's Book.[3]
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
value_prompt_feedback = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by environmental observations about the situation, thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, evaluate its correctness and provide your reasoning and analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories can be correct if the thoughts and actions so far are correct, even if the answer is not found yet. Do not generate additional thoughts or actions. Then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
|
|
||||||
Question: Which other Mexican Formula One race car driver has held the podium besides the Force India driver born in 1990?
|
|
||||||
Thought 1: I need to search for a list of Mexican Formula One race car drivers, then eliminate the one who drove for Force India and was born in 1990.
|
|
||||||
Action 1: Search[Formula One drivers from Mexico]
|
|
||||||
Observation 1: There have been six Formula One drivers from Mexico who have taken part in races since the championship began in 1950. Sergio P\u00e9rez is the most successful, having won six Grands Prix. Since 2021, P\u00e9rez drives for the Red Bull team.. P\u00e9rez is the only Mexican F1 driver to achieve a pole position. He took his first pole position at the 2022 Saudi Arabian Grand Prix, in his 219th Grand Prix meeting (a record for the most Grands Prix before a pole).
|
|
||||||
Thought 2: Sergio P\u00e9rez, born in 1990 and drove for Force India, is not the Mexican driver we are looking for. The passage does not give me enough information about other Mexican drivers who have achieved a podium finish. I need to look up more information.
|
|
||||||
Action 2: Lookup[Formula One drivers from Mexico]
|
|
||||||
Observation 2: No more results.
|
|
||||||
Thought 3: The available information doesn't allow me to determine which other Mexican Formula One race car driver, besides Sergio P\u00e9rez, has held the podium. With the search function failing to provide a full list of Mexican Formula One drivers, I need to return different search results. Let's try searching for Mexican Grand Prix winners, as they would have held the podium.
|
|
||||||
Action 3: Search[Mexican Grand Prix winners]
|
|
||||||
Observation 3: Could not find Mexican Grand Prix winners. Similar: ['Mexican Grand Prix', 'List of Formula One Grand Prix winners', '1990 Mexican Grand Prix', '2018 Mexican Grand Prix', '2019 Mexican Grand Prix'].
|
|
||||||
Thought 4: Given the difficulties in finding a direct list of Mexican Grand Prix winners, I should search for the Mexican Grand Prix to get a broader picture of the race's history. This might include winners.
|
|
||||||
Action 4: Search[Mexican Grand Prix]
|
|
||||||
Thus the correctness score is 3
|
|
||||||
|
|
||||||
Question: Which magazine was started first Arthur's Magazine or First for Women?
|
|
||||||
Thought 1: I need to search Arthur's Magazine and First for Women, and find which was started first.
|
|
||||||
Action 1: Search[Arthur's Magazine]
|
|
||||||
Observation 1: Arthur's Magazine (1844\u00e2\u0080\u00931846) was an American literary periodical published in Philadelphia in the 19th century. Edited by Timothy Shay Arthur, it featured work by Edgar A. Poe, J.H. Ingraham, Sarah Josepha Hale, Thomas G. Spear, and others.[1][2] In May 1846 it was merged into Godey's Lady's Book.[3]
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{trajectories}
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
value_prompt_reasoning = '''You are an advanced reasoning agent that can improve based on self refection. Analyze the trajectories of your previous solutions to a question answering task. The trajectories are labeled by environmental observations about the situation, thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, evaluate its correctness and provide your reasoning and analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories can be correct if the thoughts and actions so far are correct, even if the answer is not found yet. Do not generate additional thoughts or actions. Then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
|
|
||||||
Question: Which other Mexican Formula One race car driver has held the podium besides the Force India driver born in 1990?
|
|
||||||
Thought 1: I need to search for a list of Mexican Formula One race car drivers, then eliminate the one who drove for Force India and was born in 1990.
|
|
||||||
Action 1: Search[Formula One drivers from Mexico]
|
|
||||||
Observation 1: There have been six Formula One drivers from Mexico who have taken part in races since the championship began in 1950. Sergio P\u00e9rez is the most successful, having won six Grands Prix. Since 2021, P\u00e9rez drives for the Red Bull team.. P\u00e9rez is the only Mexican F1 driver to achieve a pole position. He took his first pole position at the 2022 Saudi Arabian Grand Prix, in his 219th Grand Prix meeting (a record for the most Grands Prix before a pole).
|
|
||||||
Thought 2: Sergio P\u00e9rez, born in 1990 and drove for Force India, is not the Mexican driver we are looking for. The passage does not give me enough information about other Mexican drivers who have achieved a podium finish. I need to look up more information.
|
|
||||||
Action 2: Lookup[Formula One drivers from Mexico]
|
|
||||||
Observation 2: No more results.
|
|
||||||
Thought 3: The available information doesn't allow me to determine which other Mexican Formula One race car driver, besides Sergio P\u00e9rez, has held the podium. With the search function failing to provide a full list of Mexican Formula One drivers, I need to return different search results. Let's try searching for Mexican Grand Prix winners, as they would have held the podium.
|
|
||||||
Action 3: Search[Mexican Grand Prix winners]
|
|
||||||
Observation 3: Could not find Mexican Grand Prix winners. Similar: ['Mexican Grand Prix', 'List of Formula One Grand Prix winners', '1990 Mexican Grand Prix', '2018 Mexican Grand Prix', '2019 Mexican Grand Prix'].
|
|
||||||
Thought 4: Given the difficulties in finding a direct list of Mexican Grand Prix winners, I should search for the Mexican Grand Prix to get a broader picture of the race's history. This might include winners.
|
|
||||||
Action 4: Search[Mexican Grand Prix]
|
|
||||||
This trajectory is incorrect as my search should be related to Mexican Formula One race car drivers, not winners of the Mexican Grand Prix, a seperate event. A better search would have been for the List of Formula One Grand Prix winners, as suggested.
|
|
||||||
Thus the correctness score is 3
|
|
||||||
|
|
||||||
Question: Which magazine was started first Arthur's Magazine or First for Women?
|
|
||||||
Thought 1: I need to search Arthur's Magazine and First for Women, and find which was started first.
|
|
||||||
Action 1: Search[Arthur's Magazine]
|
|
||||||
Observation 1: Arthur's Magazine (1844\u00e2\u0080\u00931846) was an American literary periodical published in Philadelphia in the 19th century. Edited by Timothy Shay Arthur, it featured work by Edgar A. Poe, J.H. Ingraham, Sarah Josepha Hale, Thomas G. Spear, and others.[1][2] In May 1846 it was merged into Godey's Lady's Book.[3]
|
|
||||||
This trajectory is correct as it is reasonable to search for the first magazine provided in the question. It is also better to have simple searches corresponding to a single entity, making this the best action.
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?
|
|
||||||
Thought 1: I need to search for the football manager who recruited David Beckham, and then find out the timeframe in which he managed Manchester United.
|
|
||||||
Action 1: Search[David Beckham's football manager]
|
|
||||||
Observation 1: Could not find David Beckham's football manager. Similar: ['David Beckham', 'Inter Miami CF (redirect from Miami Beckham United)', 'Victoria Beckham', 'The Beckham Experiment', 'Miami Freedom Park'].
|
|
||||||
Thought 2: I couldn't find the information on David Beckham's football manager. I need to search Manchester United and find out the timeframe of the manager.
|
|
||||||
Action 2: Search[Manchester United]
|
|
||||||
Observation 2: Manchester United Football Club, commonly referred to as Man United (often stylised as Man Utd), or simply United, is a professional football club based in Old Trafford, Greater Manchester, England. The club competes in the Premier League, the top division in the English football league system. Nicknamed the Red Devils, they were founded as Newton Heath LYR Football Club in 1878, but changed their name to Manchester United in 1902. After a spell playing in Clayton, Manchester, the club moved to their current stadium, Old Trafford, in 1910.. Domestically, Manchester United have won a record 20 league titles, 12 FA Cups, six League Cups and a record 21 FA Community Shields.
|
|
||||||
Thought 3: The information about David Beckham's football manager is not available. I will search for Manchester United's managers to find the timeframe.
|
|
||||||
Action 3: Search[Manchester United managers]
|
|
||||||
Observation 3: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
Thought 4: The information about David Beckham's football manager is not available. The question asks for the timeframe when the manager managed Manchester United, so I need to find a list of Manchester United managers.
|
|
||||||
Action 4: Search[list of Manchester United managers]
|
|
||||||
Observation 4: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
This trajectory is mostly incorrect as I was not able to retrieve the relevant information to answer the question. The searches are too broad and not relevant to the specific player recruited, David Beckham. The last two actions are also too similar. It would be better to first search for David Beckham and lookup information related to his manager.
|
|
||||||
Thus the correctness score is 4
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
This trajectory is correct as all of my thoughts and actions are correct. It makes sense to search for Milhouse first as it is the central subject of the question. It is also correct to directly look up the relevant information in the article, instead of trying another search.
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
value_prompt_reasoning_feedback = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by environmental observations about the situation, thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, evaluate its correctness and provide your reasoning and analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories can be correct if the thoughts and actions so far are correct, even if the answer is not found yet. Do not generate additional thoughts or actions. Then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
|
|
||||||
Question: Which other Mexican Formula One race car driver has held the podium besides the Force India driver born in 1990?
|
|
||||||
Thought 1: I need to search for a list of Mexican Formula One race car drivers, then eliminate the one who drove for Force India and was born in 1990.
|
|
||||||
Action 1: Search[Formula One drivers from Mexico]
|
|
||||||
Observation 1: There have been six Formula One drivers from Mexico who have taken part in races since the championship began in 1950. Sergio P\u00e9rez is the most successful, having won six Grands Prix. Since 2021, P\u00e9rez drives for the Red Bull team.. P\u00e9rez is the only Mexican F1 driver to achieve a pole position. He took his first pole position at the 2022 Saudi Arabian Grand Prix, in his 219th Grand Prix meeting (a record for the most Grands Prix before a pole).
|
|
||||||
Thought 2: Sergio P\u00e9rez, born in 1990 and drove for Force India, is not the Mexican driver we are looking for. The passage does not give me enough information about other Mexican drivers who have achieved a podium finish. I need to look up more information.
|
|
||||||
Action 2: Lookup[Formula One drivers from Mexico]
|
|
||||||
Observation 2: No more results.
|
|
||||||
Thought 3: The available information doesn't allow me to determine which other Mexican Formula One race car driver, besides Sergio P\u00e9rez, has held the podium. With the search function failing to provide a full list of Mexican Formula One drivers, I need to return different search results. Let's try searching for Mexican Grand Prix winners, as they would have held the podium.
|
|
||||||
Action 3: Search[Mexican Grand Prix winners]
|
|
||||||
Observation 3: Could not find Mexican Grand Prix winners. Similar: ['Mexican Grand Prix', 'List of Formula One Grand Prix winners', '1990 Mexican Grand Prix', '2018 Mexican Grand Prix', '2019 Mexican Grand Prix'].
|
|
||||||
Thought 4: Given the difficulties in finding a direct list of Mexican Grand Prix winners, I should search for the Mexican Grand Prix to get a broader picture of the race's history. This might include winners.
|
|
||||||
Action 4: Search[Mexican Grand Prix]
|
|
||||||
This trajectory is incorrect as my search should be related to Mexican Formula One race car drivers, not winners it is reasonable to checkof the Mexican Grand Prix, a seperate event. A better search would have been for the List of Formula One Grand Prix winners, as suggested.
|
|
||||||
Thus the correctness score is 3
|
|
||||||
|
|
||||||
Question: Which magazine was started first Arthur's Magazine or First for Women?
|
|
||||||
Thought 1: I need to search Arthur's Magazine and First for Women, and find which was started first.
|
|
||||||
Action 1: Search[Arthur's Magazine]
|
|
||||||
Observation 1: Arthur's Magazine (1844\u00e2\u0080\u00931846) was an American literary periodical published in Philadelphia in the 19th century. Edited by Timothy Shay Arthur, it featured work by Edgar A. Poe, J.H. Ingraham, Sarah Josepha Hale, Thomas G. Spear, and others.[1][2] In May 1846 it was merged into Godey's Lady's Book.[3]
|
|
||||||
This trajectory is correct as it is reasonable to search for the first magazine provided in the question. It is also better to have simple searches corresponding to a single entity, making this the best action.
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?Question: The football manager who recruited David Beckham managed Manchester United during what timeframe?
|
|
||||||
Thought 1: I need to search for the football manager who recruited David Beckham, and then find out the timeframe in which he managed Manchester United.
|
|
||||||
Action 1: Search[David Beckham's football manager]
|
|
||||||
Observation 1: Could not find David Beckham's football manager. Similar: ['David Beckham', 'Inter Miami CF (redirect from Miami Beckham United)', 'Victoria Beckham', 'The Beckham Experiment', 'Miami Freedom Park'].
|
|
||||||
Thought 2: I couldn't find the information on David Beckham's football manager. I need to search Manchester United and find out the timeframe of the manager.
|
|
||||||
Action 2: Search[Manchester United]
|
|
||||||
Observation 2: Manchester United Football Club, commonly referred to as Man United (often stylised as Man Utd), or simply United, is a professional football club based in Old Trafford, Greater Manchester, England. The club competes in the Premier League, the top division in the English football league system. Nicknamed the Red Devils, they were founded as Newton Heath LYR Football Club in 1878, but changed their name to Manchester United in 1902. After a spell playing in Clayton, Manchester, the club moved to their current stadium, Old Trafford, in 1910.. Domestically, Manchester United have won a record 20 league titles, 12 FA Cups, six League Cups and a record 21 FA Community Shields.
|
|
||||||
Thought 3: The information about David Beckham's football manager is not available. I will search for Manchester United's managers to find the timeframe.
|
|
||||||
Action 3: Search[Manchester United managers]
|
|
||||||
Observation 3: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
Thought 4: The information about David Beckham's football manager is not available. The question asks for the timeframe when the manager managed Manchester United, so I need to find a list of Manchester United managers.
|
|
||||||
Action 4: Search[list of Manchester United managers]
|
|
||||||
Observation 4: Manchester United Football Club is a professional football club based in Old Trafford, Greater Manchester, England. The club was formed in Newton Heath in 1878 as Newton Heath LYR F.C., and played their first competitive match in October 1886, when they entered the First Round of the 1886–87 FA Cup. The club was renamed Manchester United F.C. in 1902, and moved to Old Trafford in 1910.. From the beginning of the club's official managerial records in 1892 to the start of the 2022–23 season, Manchester United have had 23 full-time managers.
|
|
||||||
This trajectory is mostly incorrect as I was not able to retrieve the relevant information to answer the question. The searches are too broad and not relevant to the specific player recruited, David Beckham. The last two actions are also too similar. It would be better to first search for David Beckham and lookup information related to his manager.
|
|
||||||
Thus the correctness score is 4
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
This trajectory is correct as all of my thoughts and actions are correct. It makes sense to search for Milhouse first as it is the central subject of the question. It is also correct to directly look up the relevant information in the article, instead of trying another search.
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{trajectories}
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
value_prompt_reasoning_feedback_short = '''Analyze the trajectories of a solution to a question answering task. The trajectories are labeled by environmental observations about the situation, thoughts that can reason about the current situation and actions that can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
|
|
||||||
Given a question and a trajectory, evaluate its correctness and provide your reasoning and analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories can be correct if the thoughts and actions so far are correct, even if the answer is not found yet. Do not generate additional thoughts or actions. Then at the last line conclude "Thus the correctness score is {s}", where s is an integer from 1 to 10.
|
|
||||||
|
|
||||||
Question: Which other Mexican Formula One race car driver has held the podium besides the Force India driver born in 1990?
|
|
||||||
Thought 1: I need to search for a list of Mexican Formula One race car drivers, then eliminate the one who drove for Force India and was born in 1990.
|
|
||||||
Action 1: Search[Formula One drivers from Mexico]
|
|
||||||
Observation 1: There have been six Formula One drivers from Mexico who have taken part in races since the championship began in 1950. Sergio P\u00e9rez is the most successful, having won six Grands Prix. Since 2021, P\u00e9rez drives for the Red Bull team.. P\u00e9rez is the only Mexican F1 driver to achieve a pole position. He took his first pole position at the 2022 Saudi Arabian Grand Prix, in his 219th Grand Prix meeting (a record for the most Grands Prix before a pole).
|
|
||||||
Thought 2: Sergio P\u00e9rez, born in 1990 and drove for Force India, is not the Mexican driver we are looking for. The passage does not give me enough information about other Mexican drivers who have achieved a podium finish. I need to look up more information.
|
|
||||||
Action 2: Lookup[Formula One drivers from Mexico]
|
|
||||||
Observation 2: No more results.
|
|
||||||
Thought 3: The available information doesn't allow me to determine which other Mexican Formula One race car driver, besides Sergio P\u00e9rez, has held the podium. With the search function failing to provide a full list of Mexican Formula One drivers, I need to return different search results. Let's try searching for Mexican Grand Prix winners, as they would have held the podium.
|
|
||||||
Action 3: Search[Mexican Grand Prix winners]
|
|
||||||
Observation 3: Could not find Mexican Grand Prix winners. Similar: ['Mexican Grand Prix', 'List of Formula One Grand Prix winners', '1990 Mexican Grand Prix', '2018 Mexican Grand Prix', '2019 Mexican Grand Prix'].
|
|
||||||
Thought 4: Given the difficulties in finding a direct list of Mexican Grand Prix winners, I should search for the Mexican Grand Prix to get a broader picture of the race's history. This might include winners.
|
|
||||||
Action 4: Search[Mexican Grand Prix]
|
|
||||||
This trajectory is incorrect as my search should be related to Mexican Formula One race car drivers, not winners of the Mexican Grand Prix, a seperate event. A better search would have been for the List of Formula One Grand Prix winners, as suggested.
|
|
||||||
Thus the correctness score is 3
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
|
|
||||||
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
This trajectory is correct as all of my thoughts and actions are correct. It makes sense to search for Milhouse first as it is the central subject of the question. It is also correct to directly look up the relevant information in the article, instead of trying another search.
|
|
||||||
Thus the correctness score is 10
|
|
||||||
|
|
||||||
{trajectories}
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
|
|
||||||
rap_prompt = '''
|
|
||||||
Solve a question answering task with interleaving Thought and Action steps. Thought can reason about the current situation, and Action can be three types:
|
|
||||||
(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
|
|
||||||
(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
|
|
||||||
(3) Finish[answer], which returns the answer and finishes the task.
|
|
||||||
Provide the next Thought and next Action. Here are some examples:
|
|
||||||
|
|
||||||
Question: What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?
|
|
||||||
Thought 1: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
|
|
||||||
Action 1: Search[Colorado orogeny]
|
|
||||||
Thought 2: Then I need to look up eastern sector.
|
|
||||||
Action 2: Lookup[eastern sector]
|
|
||||||
Thought 3: The eastern sector of Colorado orogeny extends into the High Plains, so High Plains is the area. I need to search High Plains and find its elevation range.
|
|
||||||
Action 3: Search[High Plains]
|
|
||||||
Thought 4: I need to search High Plains (United States).
|
|
||||||
Action 4: Search[High Plains (United States)]
|
|
||||||
Thought 5: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
|
|
||||||
Action 5: Finish[1,800 to 7,000 ft]
|
|
||||||
|
|
||||||
Question: Guitars for Wounded Warriors is an album that was recorded in the village in which New York county?
|
|
||||||
Thought 1: I need to search Guitars for Wounded Warriors, then find in which New York county it was recorded.
|
|
||||||
Action 1: Search[Guitars for Wounded Warriors]
|
|
||||||
Thought 2: I need to search New Paltz (village), New York and find which county it is in
|
|
||||||
Action 2: Search[New Paltz (village), New York]
|
|
||||||
Thought 3: New Paltz (village), New York is in Ulster County, so the answer is Ulster County.
|
|
||||||
Action 3: Finish[Ulster County]
|
|
||||||
|
|
||||||
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
|
|
||||||
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
|
|
||||||
Action 1: Search[Milhouse]
|
|
||||||
Thought 2: I can look up "named after" for finding the specific individual Milhouse is named after.
|
|
||||||
Action 2: Lookup[named after]
|
|
||||||
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
|
|
||||||
Action 3: Finish[President Richard Nixon]
|
|
||||||
|
|
||||||
{input}
|
|
||||||
'''
|
|
||||||
+30
-47
@@ -1,47 +1,30 @@
|
|||||||
module YiemAgent
|
module YiemAgent
|
||||||
|
|
||||||
# export agent
|
"""Order by dependencies of each file. The 1st included file must not depend on any other
|
||||||
|
files and each file can only depend on the file included before it."""
|
||||||
|
|
||||||
""" Order by dependencies of each file. The 1st included file must not depend on any other
|
include("type.jl")
|
||||||
files and each file can only depend on the file included before it.
|
using .type
|
||||||
"""
|
|
||||||
|
include("utils.jl")
|
||||||
include("type.jl")
|
using .utils
|
||||||
using .type
|
|
||||||
|
include("toolRegistry.jl")
|
||||||
include("util.jl")
|
using .toolRegistry
|
||||||
using .util
|
|
||||||
|
include("agentCore.jl")
|
||||||
include("llmfunction.jl")
|
using .agentCore
|
||||||
using .llmfunction
|
|
||||||
|
include("api.jl")
|
||||||
include("interface.jl")
|
using .api
|
||||||
using .interface
|
|
||||||
|
export promptAgent, takeResponse, followUp, stopAgent
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
||||||
|
|
||||||
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end # end module
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end # module YiemAgent_v1
|
|
||||||
|
|||||||
+1593
File diff suppressed because it is too large
Load Diff
+410
@@ -0,0 +1,410 @@
|
|||||||
|
module api
|
||||||
|
|
||||||
|
export promptAgent, takeResponse, followUp, stopAgent
|
||||||
|
|
||||||
|
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||||
|
DataFrames
|
||||||
|
using GeneralUtils
|
||||||
|
using ..type, ..utils, ..agentCore, ..toolRegistry
|
||||||
|
|
||||||
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Send a message to the agent's input channel.
|
||||||
|
|
||||||
|
Blocks if the input channel buffer is full (capacity 16 by default).
|
||||||
|
The agent processes messages from `inputChannel` in the background task.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `agent::yiemAgent`: The agent instance to send a message to
|
||||||
|
- `msg`: The message to send, in OpenAI message format (e.g. `Dict("role" => "user", "content" => "Hello!")`)
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- The same `agent` instance for chaining
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- Use `takeResponse(agent)` to receive the agent's response after sending a message.
|
||||||
|
- Use `followUp(agent, msg)` to send messages while the agent is still processing.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> promptAgent(agent, Dict("role" => "user", "content" => "Hello!"))
|
||||||
|
yiemAgent(...)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function promptAgent(agent::yiemAgent, msg::AbstractDict{String, Any})
|
||||||
|
put!(agent.inputChannel, msg)
|
||||||
|
return agent
|
||||||
|
end
|
||||||
|
|
||||||
|
function promptAgent(agent::yiemAgent, text::String)
|
||||||
|
put!(agent.inputChannel, Dict("role" => "user", "content" => [Dict("type" => "text", "text" => text)]))
|
||||||
|
return agent
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Take a response from the agent's output channel.
|
||||||
|
|
||||||
|
Blocks until the agent sends a response.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `agent::yiemAgent`: The agent instance to receive a response from
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- An `assistantMessage` instance representing the agent's response
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- Use `promptAgent(agent, msg)` to send a message before calling this function.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> response = takeResponse(agent)
|
||||||
|
assistantMessage(...)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function takeResponse(agent::yiemAgent)
|
||||||
|
return take!(agent.outputChannel)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Send a follow-up message while the agent is still processing.
|
||||||
|
|
||||||
|
Follow-up messages are queued and processed after all `inputChannel` messages
|
||||||
|
and before any tool call results are sent.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `agent::yiemAgent`: The agent instance to send a follow-up message to
|
||||||
|
- `msg`: The follow-up message to send, in OpenAI message format (e.g. `Dict("role" => "user", "content" => "Also consider red wines")`)
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- The same `agent` instance for chaining
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- Use `promptAgent(agent, msg)` for the primary message and `followUp(agent, msg)` for additional
|
||||||
|
messages while the agent is processing.
|
||||||
|
- Follow-up messages are buffered in a separate channel (capacity 32 by default).
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> followUp(agent, Dict("role" => "user", "content" => "Also consider red wines"))
|
||||||
|
yiemAgent(...)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function followUp(agent::yiemAgent, msg::AbstractDict{String, Any})
|
||||||
|
put!(agent.followUpChannel, msg)
|
||||||
|
return agent
|
||||||
|
end
|
||||||
|
|
||||||
|
function followUp(agent::yiemAgent, text::String)
|
||||||
|
put!(agent.followUpChannel, Dict("role" => "user", "content" => [Dict("type" => "text", "text" => text)]))
|
||||||
|
return agent
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Gracefully stop the agent.
|
||||||
|
|
||||||
|
Sends a `:shutdown` signal to the input channel, waits for the background task to finish,
|
||||||
|
then closes all channels (`inputChannel`, `outputChannel`, `followUpChannel`).
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `agent::yiemAgent`: The agent instance to stop
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing`
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- After calling `stopAgent`, the agent is no longer usable. A new agent must be created
|
||||||
|
for further interaction.
|
||||||
|
- If the background task throws a `TaskFailedException`, it is rethrown.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> stopAgent(agent)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function stopAgent(agent::yiemAgent)
|
||||||
|
put!(agent.inputChannel, :shutdown)
|
||||||
|
try
|
||||||
|
fetch(agent._agentLoop)
|
||||||
|
catch e
|
||||||
|
if e isa TaskFailedException
|
||||||
|
rethrow(e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
close(agent.inputChannel)
|
||||||
|
close(agent.outputChannel)
|
||||||
|
close(agent.followUpChannel)
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
""" Recursively convert dictionary-like variable (e.g. JSON.Object) into an OrderedDict.
|
||||||
|
|
||||||
|
The function walks any nested structure composed of `AbstractDict` (e.g., `JSON.Object`,
|
||||||
|
`Dict`, `OrderedDict`) and `AbstractArray` and produces a new tree where
|
||||||
|
every dictionary-like node is an `OrderedDict` and every array-like node is a `Vector{Any}`.
|
||||||
|
Scalar values (numbers, strings, booleans, `nothing`, etc.) are returned unchanged.
|
||||||
|
Does **not** mutate the input; it always allocates new containers.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `x`
|
||||||
|
Any Julia value. If `x` is an `AbstractDict` it will be converted to an `OrderedDict`;
|
||||||
|
if it is an `AbstractArray` its elements will be processed recursively.
|
||||||
|
|
||||||
|
# Keyword Arguments
|
||||||
|
- `keytype::Type=Any`
|
||||||
|
The key type for the output OrderedDict. Use `String` for `OrderedDict{String,Any}`,
|
||||||
|
`Symbol` for `OrderedDict{Symbol,Any}`, or `Any` to preserve original key types.
|
||||||
|
- `sort_order::Union{Nothing, Vector}=nothing`
|
||||||
|
Vector of keys specifying the desired order. Keys are arranged in the specified order
|
||||||
|
first, followed by any remaining keys.
|
||||||
|
|
||||||
|
# Return
|
||||||
|
- A newly allocated nested structure composed of `OrderedDict{keytype,Any}` and `Vector{Any}`
|
||||||
|
that mirrors the input shape but uses ordered Julia containers.
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- The function treats any `AbstractDict` as a mapping source, so it works with
|
||||||
|
`JSON.Object`, `Dict`, `OrderedDict`, etc.
|
||||||
|
- Arrays are returned as `Vector{Any}` with their elements processed recursively.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> using JSON, DataStructures
|
||||||
|
julia> d = Dict(
|
||||||
|
"a" => 4,
|
||||||
|
"b" => 6,
|
||||||
|
"c" => Dict(
|
||||||
|
"d"=>7,
|
||||||
|
:e=>Dict(
|
||||||
|
"f"=>"hey",
|
||||||
|
"g"=>Dict(
|
||||||
|
"world"=>[1, "2", 3, Dict(:dd=>4.7)]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
julia> jsonstring = JSON.json(d)
|
||||||
|
julia> A1 = JSON.parse(jsonstring) # A1 type is JSON.Object
|
||||||
|
julia> A2 = dictify(A1; keytype=String)
|
||||||
|
OrderedDict{String,Any} with 3 entries:
|
||||||
|
"a" => 4
|
||||||
|
"b" => 6
|
||||||
|
"c" => OrderedDict("d"=>7, "e"=>Dict("f"=>"hey", "g"=>Dict("world"=>[1, "2", 3, 4.7])))
|
||||||
|
|
||||||
|
julia> A3 = dictify(A1; keytype=Symbol)
|
||||||
|
OrderedDict{Symbol,Any} with 3 entries:
|
||||||
|
:a => 4
|
||||||
|
:b => 6
|
||||||
|
:c => OrderedDict(:d=>7, :e=>Dict("f"=>"hey", "g"=>Dict("world"=>[1, "2", 3, 4.7])))
|
||||||
|
|
||||||
|
julia> B1 = dictify(d; keytype=String)
|
||||||
|
OrderedDict{String, Any} with 3 entries:
|
||||||
|
```
|
||||||
|
|
||||||
|
**With sort_order:**
|
||||||
|
```jldoctest
|
||||||
|
julia> d = Dict("a"=>1, "b"=>2, "c"=>3)
|
||||||
|
julia> dictify(d; sort_order=["c", "a"])
|
||||||
|
OrderedDict{String,Int} with 3 entries:
|
||||||
|
"c" => 3
|
||||||
|
"a" => 1
|
||||||
|
"b" => 2
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function dictify(x::T; keytype::Type=Any, sort_order::Union{Nothing, Vector}=nothing
|
||||||
|
)::OrderedDict where {T<:AbstractDict}
|
||||||
|
|
||||||
|
# this function is example
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end # module interface
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-1227
File diff suppressed because it is too large
Load Diff
-1040
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,290 @@
|
|||||||
|
module toolRegistry
|
||||||
|
|
||||||
|
export registerTool, registerAllTools, clearTools, listTool
|
||||||
|
|
||||||
|
using Dates
|
||||||
|
using JSON, DataStructures
|
||||||
|
using ..type
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ── MCP helper functions ────────────────────────────────────────────
|
||||||
|
|
||||||
|
"""
|
||||||
|
Extract text from MCP tool result content array.
|
||||||
|
|
||||||
|
Handles JSON-RPC 2.0 result content format:
|
||||||
|
{"content": [{"type": "text", "text": "..."}], "isError": false}
|
||||||
|
"""
|
||||||
|
function _extract_text_content(result::AbstractDict)::String
|
||||||
|
content = get(result, "content", Any[])
|
||||||
|
if content isa Vector && !isempty(content)
|
||||||
|
lines = String[]
|
||||||
|
for block in content
|
||||||
|
if block isa Dict && get(block, "type", "") == "text"
|
||||||
|
push!(lines, string(get(block, "text", "")))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if !isempty(lines)
|
||||||
|
return join(lines, "\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return JSON.json(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Wrap an MCP tool definition as an `agentTool`.
|
||||||
|
|
||||||
|
The returned tool's `execute` function calls the MCP server's "tools/call"
|
||||||
|
method with the validated arguments.
|
||||||
|
"""
|
||||||
|
function _wrap_mcp_tool(mcpserver, tool_def::AbstractDict{String, Any}; eventSink=nothing, sessionId=nothing)::agentTool
|
||||||
|
name = tool_def["name"]
|
||||||
|
title = get(tool_def, "title", get(tool_def, "label", name))
|
||||||
|
desc = get(tool_def, "description", "")
|
||||||
|
input_schema = get(tool_def, "inputSchema", Dict{String,Any}())
|
||||||
|
|
||||||
|
# Normalize inputSchema to OpenAI function format
|
||||||
|
if haskey(input_schema, "properties") && input_schema["type"] == "object"
|
||||||
|
params = Dict(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => input_schema["properties"],
|
||||||
|
"required" => get(input_schema, "required", Any[]),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
params = Dict(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict{String,Any}(),
|
||||||
|
"required" => Any[],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return agentTool(
|
||||||
|
name=name,
|
||||||
|
label=title,
|
||||||
|
description=desc,
|
||||||
|
inputSchema=params,
|
||||||
|
execute=(toolCallId::String, args::AbstractDict{String, Any},
|
||||||
|
signal::Union{Nothing,abortSignal},
|
||||||
|
eventSink) -> begin
|
||||||
|
try
|
||||||
|
response = mcpserver("tools/call", name, args, sessionId; eventSink=eventSink)
|
||||||
|
|
||||||
|
# Parse JSON-RPC 2.0 response envelope
|
||||||
|
if haskey(response, "error")
|
||||||
|
rpc_error = response["error"]
|
||||||
|
err_msg = get(rpc_error, "message", "Unknown MCP error")
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("MCP error: $err_msg")],
|
||||||
|
Dict{Any,Any}("error" => err_msg),
|
||||||
|
nothing, false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
result_data = get(response, "result", response)
|
||||||
|
content_text = _extract_text_content(result_data)
|
||||||
|
is_error = get(result_data, "isError", false)
|
||||||
|
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent(content_text)],
|
||||||
|
Dict{Any,Any}("isError" => is_error),
|
||||||
|
nothing, false
|
||||||
|
)
|
||||||
|
catch e
|
||||||
|
errMsg = sprint(showerror, e)
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("MCP call error: $errMsg")],
|
||||||
|
Dict{Any,Any}("error" => errMsg),
|
||||||
|
nothing, false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
prepareArguments=nothing,
|
||||||
|
validateRequiredArgs=nothing,
|
||||||
|
parallelToolExecute=false,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Discover and register MCP tools into `store.tools`.
|
||||||
|
|
||||||
|
Queries the MCP server via `mcpserver("tools/list")` (JSON-RPC 2.0 format),
|
||||||
|
parses the response envelope, and registers each discovered tool.
|
||||||
|
Handles pagination via `nextCursor`. Skips tools already registered.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `Int`: number of new tools registered
|
||||||
|
"""
|
||||||
|
function register_mcp_tools(tools::OrderedDict{String, agentTool}, mcpserver; eventSink=nothing, sessionId=nothing)::Int
|
||||||
|
if mcpserver === nothing
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
new_count = 0
|
||||||
|
try
|
||||||
|
response = mcpserver("tools/list", sessionId; eventSink=eventSink)
|
||||||
|
# Parse JSON-RPC 2.0 response envelope
|
||||||
|
if haskey(response, "error")
|
||||||
|
rpc_error = response["error"]
|
||||||
|
err_msg = get(rpc_error, "message", "Unknown MCP error")
|
||||||
|
println("[toolRegistry] MCP tools/list failed: $err_msg")
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
if haskey(response, "result")
|
||||||
|
response = response["result"]
|
||||||
|
end
|
||||||
|
|
||||||
|
tools_array = response["tools"]
|
||||||
|
cursor = get(response, "nextCursor", nothing)
|
||||||
|
|
||||||
|
for tool_def in tools_array
|
||||||
|
name = tool_def["name"]
|
||||||
|
if haskey(tools, name)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
wrapped = _wrap_mcp_tool(mcpserver, tool_def, eventSink=eventSink, sessionId=sessionId)
|
||||||
|
tools[name] = wrapped
|
||||||
|
new_count += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Paginate: fetch remaining tools if nextCursor is present
|
||||||
|
while cursor !== nothing && cursor !== ""
|
||||||
|
response = sessionId !== nothing ? mcpserver("tools/list", sessionId; eventSink=eventSink) : mcpserver("tools/list"; eventSink=eventSink)
|
||||||
|
if haskey(response, "result")
|
||||||
|
response = response["result"]
|
||||||
|
end
|
||||||
|
tools_array = get(response, "tools", Any[])
|
||||||
|
cursor = get(response, "nextCursor", nothing)
|
||||||
|
for tool_def in tools_array
|
||||||
|
name = tool_def["name"]
|
||||||
|
if haskey(tools, name)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
wrapped = _wrap_mcp_tool(mcpserver, tool_def, eventSink=eventSink, sessionId=sessionId)
|
||||||
|
tools[name] = wrapped
|
||||||
|
new_count += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
println("[toolRegistry] Discovered $new_count MCP tools. Total: $(length(tools))")
|
||||||
|
catch e
|
||||||
|
bt = catch_backtrace()
|
||||||
|
err_msg = sprint() do io
|
||||||
|
showerror(io, e, bt)
|
||||||
|
println(io)
|
||||||
|
end
|
||||||
|
|
||||||
|
eventSink(err_msg)
|
||||||
|
|
||||||
|
errMsg = sprint(showerror, e)
|
||||||
|
println("[toolRegistry] MCP tools/list failed: $errMsg")
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_count
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
registerTool(tools::OrderedDict{String, agentTool}, tool::agentTool) -> OrderedDict{String, agentTool}
|
||||||
|
|
||||||
|
Add `tool` to `tools`, overwriting any existing tool with the same name.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `tools`: Tool dict to modify
|
||||||
|
- `tool`: The `agentTool` to register
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- The same `tools` dict (modified in place)
|
||||||
|
"""
|
||||||
|
function registerTool(tools::OrderedDict{String, agentTool}, tool::agentTool; eventSink=nothing
|
||||||
|
)::OrderedDict{String, agentTool}
|
||||||
|
tools[tool.name] = tool
|
||||||
|
eventSink("[toolRegistry] Registered tool: $(tool.name)")
|
||||||
|
return tools
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Remove all tools from the dict.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `tools`: Tool dict to clear
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing`
|
||||||
|
"""
|
||||||
|
function clearTools(tools::OrderedDict{String, agentTool})::Nothing
|
||||||
|
empty!(tools)
|
||||||
|
println("[toolRegistry] Registry cleared")
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── listTools tool (auto-discover new MCP tools at runtime) ─────────
|
||||||
|
|
||||||
|
"""
|
||||||
|
listTool(tools::OrderedDict{String, agentTool}, mcpserver) -> agentTool
|
||||||
|
|
||||||
|
MCP-aware listTools tool (JSON-RPC 2.0 protocol).
|
||||||
|
|
||||||
|
First call: queries the MCP server via `mcpserver("tools/list")`, registers
|
||||||
|
all discovered tools into `tools` (in-place mutation, with
|
||||||
|
pagination via nextCursor), then returns the full tool list.
|
||||||
|
|
||||||
|
Subsequent calls: returns the current list (tools remain registered).
|
||||||
|
"""
|
||||||
|
function listTool(tools::OrderedDict{String, agentTool}, mcpserver; eventSink=nothing, sessionId=nothing)::agentTool
|
||||||
|
return agentTool(
|
||||||
|
name="listTools",
|
||||||
|
label="List Tools",
|
||||||
|
description="List all available tools. First call discovers and registers all tools from the MCP server. After discovery, new tools become immediately available for use.",
|
||||||
|
inputSchema=Dict{String,Any}(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict{String,Any}(),
|
||||||
|
"required" => Any[]
|
||||||
|
),
|
||||||
|
execute=(toolCallId::String, args::AbstractDict{String, Any},
|
||||||
|
signal::Union{Nothing,abortSignal},
|
||||||
|
eventSink) -> begin
|
||||||
|
# Discover and register MCP tools (idempotent — skips already registered)
|
||||||
|
new_count = register_mcp_tools(tools, mcpserver; eventSink=eventSink, sessionId=sessionId)
|
||||||
|
|
||||||
|
# Always include listTools itself in the count
|
||||||
|
total = length(tools)
|
||||||
|
|
||||||
|
lines = String[
|
||||||
|
"- $(t.name): $(t.label) — $(t.description)"
|
||||||
|
for (k, t) in tools
|
||||||
|
]
|
||||||
|
result_text = "Available tools ($total):\n" * join(lines, "\n")
|
||||||
|
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent(result_text)],
|
||||||
|
Dict{Any,Any}("count" => total),
|
||||||
|
nothing, false
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
prepareArguments=nothing,
|
||||||
|
validateRequiredArgs=nothing,
|
||||||
|
parallelToolExecute=false,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── High-level API ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
"""
|
||||||
|
registerAllTools(tools::OrderedDict{String, agentTool}, mcpserver)
|
||||||
|
|
||||||
|
Register all tools for an agent:
|
||||||
|
1. Auto-discover existing tools from the MCP server
|
||||||
|
2. Register the `listTools` tool so the agent can discover new tools at runtime
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `tools`: The tool dict to populate
|
||||||
|
- `mcpserver`: A callable struct that communicates with the MCP server
|
||||||
|
"""
|
||||||
|
function registerAllTools(tools::OrderedDict{String, agentTool}, mcpserver=nothing; eventSink=nothing, sessionId=nothing)
|
||||||
|
list_t = listTool(tools, mcpserver; eventSink=eventSink, sessionId=sessionId)
|
||||||
|
eventSink("registering tools")
|
||||||
|
registerTool(tools, list_t; eventSink=eventSink)
|
||||||
|
eventSink("registering tools done")
|
||||||
|
end
|
||||||
|
|
||||||
|
end # module
|
||||||
+829
-448
File diff suppressed because it is too large
Load Diff
-457
@@ -1,457 +0,0 @@
|
|||||||
module util
|
|
||||||
|
|
||||||
export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
|
|
||||||
availableWineToText, createEventsLog, createChatLog, checkAgentResponse_JSON,
|
|
||||||
checkAgentResponse_text
|
|
||||||
|
|
||||||
using UUIDs, Dates, DataStructures, HTTP, JSON
|
|
||||||
using GeneralUtils
|
|
||||||
using ..type
|
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
||||||
|
|
||||||
""" Clear agent chat history.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `a::agent`
|
|
||||||
an agent
|
|
||||||
|
|
||||||
# Return
|
|
||||||
- nothing
|
|
||||||
|
|
||||||
# Example
|
|
||||||
```jldoctest
|
|
||||||
julia> using YiemAgent, MQTTClient, GeneralUtils
|
|
||||||
julia> client, connection = MakeConnection("test.mosquitto.org", 1883)
|
|
||||||
julia> connect(client, connection)
|
|
||||||
julia> msgMeta = GeneralUtils.generate_msgMeta("testtopic")
|
|
||||||
julia> agentConfig = Dict(
|
|
||||||
"receiveprompt"=>Dict(
|
|
||||||
"mqtttopic"=> "testtopic/receive",
|
|
||||||
),
|
|
||||||
"receiveinternal"=>Dict(
|
|
||||||
"mqtttopic"=> "testtopic/internal",
|
|
||||||
),
|
|
||||||
"text2text"=>Dict(
|
|
||||||
"mqtttopic"=> "testtopic/text2text",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
julia> a = YiemAgent.sommelier(
|
|
||||||
client,
|
|
||||||
msgMeta,
|
|
||||||
agentConfig,
|
|
||||||
)
|
|
||||||
julia> YiemAgent.addNewMessage(a, "user", "hello")
|
|
||||||
julia> YiemAgent.clearhistory(a)
|
|
||||||
```
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
- [PENDING] clear memory
|
|
||||||
|
|
||||||
# Signature
|
|
||||||
"""
|
|
||||||
function clearhistory(a::T) where {T<:agent}
|
|
||||||
empty!(a.chathistory)
|
|
||||||
empty!(a.memory["shortmem"])
|
|
||||||
empty!(a.memory["events"])
|
|
||||||
a.memory["chatbox"] = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
""" Add new message to agent.
|
|
||||||
|
|
||||||
messages => Dict(
|
|
||||||
"role" => "user",
|
|
||||||
"content" => [
|
|
||||||
Dict("type" => "text", "text" => "Describe this image for me"),
|
|
||||||
Dict(
|
|
||||||
"type" => "image_url",
|
|
||||||
"image_url" => Dict("url" => data_uri)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
Arguments\n
|
|
||||||
-----
|
|
||||||
a::agent
|
|
||||||
an agent
|
|
||||||
role::String
|
|
||||||
message sender role i.e. system, user or assistant
|
|
||||||
text::String
|
|
||||||
message text
|
|
||||||
|
|
||||||
Return\n
|
|
||||||
-----
|
|
||||||
nothing
|
|
||||||
|
|
||||||
Example\n
|
|
||||||
-----
|
|
||||||
```jldoctest
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Signature\n
|
|
||||||
-----
|
|
||||||
"""
|
|
||||||
function addNewMessage(a::T1, name::String, userinput::T2;
|
|
||||||
maximumMsg::Integer=30) where {T1<:agent, T2<:AbstractDict}
|
|
||||||
if name ∉ ["system", "user", "assistant"] # guard against typo
|
|
||||||
error("name is not in agent.availableRole $(@__LINE__)")
|
|
||||||
end
|
|
||||||
|
|
||||||
#TODO summarize the oldest 10 message
|
|
||||||
if length(a.chathistory) > maximumMsg
|
|
||||||
summarize(a.chathistory)
|
|
||||||
else
|
|
||||||
userinput["timestamp"] = Dates.now()
|
|
||||||
push!(a.chathistory, userinput)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
""" Converts a vector of dictionaries to a formatted string.
|
|
||||||
This function takes in a vector of dictionaries and outputs a single string where each dictionary's keys are prefixed by their values.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `vecd::Vector`
|
|
||||||
A vector of dictionaries containing chat messages
|
|
||||||
- `withkey::Bool`
|
|
||||||
Whether to include the name as a prefix in the output text. Default is true
|
|
||||||
- `range::Union{Nothing,UnitRange,Int}`
|
|
||||||
Optional range of messages to include. If nothing, includes all messages
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
A formatted string where each line contains either:
|
|
||||||
- If withkey=true: "name> message\n"
|
|
||||||
- If withkey=false: "message\n"
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
julia> using Revise
|
|
||||||
julia> using GeneralUtils
|
|
||||||
julia> vecd = [Dict("name" => "John", "text" => "Hello"), Dict("name" => "Jane", "text" => "Goodbye")]
|
|
||||||
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
|
|
||||||
"John> Hello\nJane> Goodbye\n"
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
function chatHistoryToText(vecd::Vector; withkey=true, range=nothing)::String
|
|
||||||
# Initialize an empty string to hold the final text
|
|
||||||
text = ""
|
|
||||||
|
|
||||||
# Get the elements within the specified range, or all elements if no range provided
|
|
||||||
elements = isnothing(range) ? vecd : vecd[range]
|
|
||||||
|
|
||||||
# Determine whether to include the key in the output text or not
|
|
||||||
if withkey
|
|
||||||
# Loop through each dictionary in the input vector
|
|
||||||
for d in elements
|
|
||||||
# Extract the 'name' and 'text' keys from the dictionary
|
|
||||||
name = titlecase(d[:name])
|
|
||||||
_text = d[:text]
|
|
||||||
|
|
||||||
# Append the formatted string to the text variable
|
|
||||||
text *= "$name> $_text \n"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# Loop through each dictionary in the input vector
|
|
||||||
for d in elements
|
|
||||||
# Iterate over all key-value pairs in the dictionary
|
|
||||||
for (k, v) in d
|
|
||||||
# Append the formatted string to the text variable
|
|
||||||
text *= "$v \n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the final text
|
|
||||||
return text
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function availableWineToText(vecd::Vector)::String
|
|
||||||
# Initialize an empty string to hold the final text
|
|
||||||
rowtext = ""
|
|
||||||
# Loop through each dictionary in the input vector
|
|
||||||
for (i, d) in enumerate(vecd)
|
|
||||||
# Iterate over all key-value pairs in the dictionary
|
|
||||||
temp = []
|
|
||||||
for (k, v) in d
|
|
||||||
# Append the formatted string to the text variable
|
|
||||||
t = "$k:$v"
|
|
||||||
push!(temp, t)
|
|
||||||
end
|
|
||||||
_rowtext = join(temp, ',')
|
|
||||||
rowtext *= "$i) $_rowtext "
|
|
||||||
end
|
|
||||||
|
|
||||||
return rowtext
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
""" Create a dictionary representing an event with optional details.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `event_description::Union{String, Nothing}`
|
|
||||||
A description of the event
|
|
||||||
- `timestamp::Union{DateTime, Nothing}`
|
|
||||||
The time when the event occurred
|
|
||||||
- `subject::Union{String, Nothing}`
|
|
||||||
The subject or entity associated with the event
|
|
||||||
- `thought::Union{AbstractDict, Nothing}`
|
|
||||||
Any associated thoughts or metadata
|
|
||||||
- `action_name::Union{String, Nothing}`
|
|
||||||
The name of the action performed (e.g., "CHAT", "CHECKINVENTORY")
|
|
||||||
- `action_input::Union{String, Nothing}`
|
|
||||||
Input or parameters for the action
|
|
||||||
- `location::Union{String, Nothing}`
|
|
||||||
Where the event took place
|
|
||||||
- `equipment_used::Union{String, Nothing}`
|
|
||||||
Equipment involved in the event
|
|
||||||
- `material_used::Union{String, Nothing}`
|
|
||||||
Materials used during the event
|
|
||||||
- `outcome::Union{String, Nothing}`
|
|
||||||
The result or consequence of the event after action execution
|
|
||||||
- `note::Union{String, Nothing}`
|
|
||||||
Additional notes or comments
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
A dictionary with event details as symbol-keyed key-value pairs
|
|
||||||
"""
|
|
||||||
function eventdict(;
|
|
||||||
event_description::Union{String, Nothing}=nothing,
|
|
||||||
timestamp::Union{DateTime, Nothing}=nothing,
|
|
||||||
subject::Union{String, Nothing}=nothing,
|
|
||||||
thought::Union{AbstractDict, Nothing}=nothing,
|
|
||||||
action_name::Union{String, Nothing}=nothing, # "CHAT", "CHECKINVENTORY", "PRESENT_WINE_GUIDELINE", etc
|
|
||||||
action_input::Union{String, Nothing}=nothing,
|
|
||||||
location::Union{String, Nothing}=nothing,
|
|
||||||
equipment_used::Union{String, Nothing}=nothing,
|
|
||||||
material_used::Union{String, Nothing}=nothing,
|
|
||||||
observation::Union{String, Nothing}=nothing,
|
|
||||||
note::Union{String, Nothing}=nothing,
|
|
||||||
)
|
|
||||||
|
|
||||||
d = Dict{String, Any}(
|
|
||||||
"event_description"=> event_description,
|
|
||||||
"timestamp"=> timestamp,
|
|
||||||
"subject"=> subject,
|
|
||||||
"thought"=> thought,
|
|
||||||
"action_name"=> action_name,
|
|
||||||
"action_input"=> action_input,
|
|
||||||
"location"=> location,
|
|
||||||
"equipment_used"=> equipment_used,
|
|
||||||
"material_used"=> material_used,
|
|
||||||
"observation"=> observation,
|
|
||||||
"note"=> note,
|
|
||||||
)
|
|
||||||
|
|
||||||
return d
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
""" Create a formatted timeline string from a sequence of events.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `events::T1`
|
|
||||||
Vector of event dictionaries containing subject, action_input and optional outcome fields
|
|
||||||
Each event dictionary should have the following keys:
|
|
||||||
- :subject - The subject or entity performing the action
|
|
||||||
- :action_input - The action or input performed by the subject
|
|
||||||
- :observation - (Optional) The result or outcome of the action
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- `timeline::String`
|
|
||||||
A formatted string representing the events with their subjects, actions, and optional outcomes
|
|
||||||
Format: "{index}) {subject}> {action_input} {outcome}\n" for each event
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
events = [
|
|
||||||
Dict("subject" => "User", "action_input" => "Hello", "observation" => nothing),
|
|
||||||
Dict("subject" => "Assistant", "action_input" => "Hi there!", "observation" => "with a smile")
|
|
||||||
]
|
|
||||||
timeline = createTimeline(events)
|
|
||||||
# 1) User> Hello
|
|
||||||
# 2) Assistant> Hi there! with a smile
|
|
||||||
|
|
||||||
"""
|
|
||||||
function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
|
|
||||||
) where {T1<:AbstractVector}
|
|
||||||
# Initialize empty timeline string
|
|
||||||
timeline = ""
|
|
||||||
|
|
||||||
# Determine which indices to use - either provided range or full length
|
|
||||||
ind =
|
|
||||||
if eventindex !== nothing
|
|
||||||
[eventindex...]
|
|
||||||
else
|
|
||||||
1:length(events)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Iterate through events and format each one
|
|
||||||
for i in ind
|
|
||||||
event = events[i]
|
|
||||||
# If no outcome exists, format without outcome
|
|
||||||
# if event["action_name"] == "CHAT_BOX"
|
|
||||||
# timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"])\n"
|
|
||||||
# elseif event["action_name"] == "CHECKINVENTORY" && event["observation"] === nothing
|
|
||||||
# timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"]), observation: Not done yet.\n"
|
|
||||||
if event["action_name"] == "CHECK_WINE"
|
|
||||||
timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"]), observation: $(event["observation"])\\n"
|
|
||||||
else
|
|
||||||
timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"])\\n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return formatted timeline string
|
|
||||||
return timeline
|
|
||||||
end
|
|
||||||
|
|
||||||
function createEventsLog(events::T1; index::Union{UnitRange, Nothing}=nothing
|
|
||||||
) where {T1<:AbstractVector}
|
|
||||||
# Initialize empty log array
|
|
||||||
log = Dict{String, String}[]
|
|
||||||
|
|
||||||
# Determine which indices to use - either provided range or full length
|
|
||||||
ind =
|
|
||||||
if index !== nothing
|
|
||||||
[index...]
|
|
||||||
else
|
|
||||||
1:length(events)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Iterate through events and format each one
|
|
||||||
for i in ind
|
|
||||||
event = events[i]
|
|
||||||
# If no outcome exists, format without outcome
|
|
||||||
if event["observation"] === nothing
|
|
||||||
subject = event["subject"]
|
|
||||||
action_name = event["action_name"]
|
|
||||||
action_input = event["action_input"]
|
|
||||||
str = "action_name: $action_name, action_input: $action_input"
|
|
||||||
d = Dict{String, String}("name"=>subject, "text"=>str)
|
|
||||||
push!(log, d)
|
|
||||||
else
|
|
||||||
subject = event["subject"]
|
|
||||||
action_name = event["action_name"]
|
|
||||||
action_input = event["action_input"]
|
|
||||||
observation = event["observation"]
|
|
||||||
str = "action_name: $action_name, action_input: $action_input, observation: $observation"
|
|
||||||
d = Dict{String, String}("name"=>subject, "text"=>str)
|
|
||||||
push!(log, d)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return log
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function createChatLog(chatdict::T1; index::Union{UnitRange, Nothing}=nothing
|
|
||||||
) where {T1<:AbstractVector}
|
|
||||||
# Initialize empty log array
|
|
||||||
log = Dict{String, String}[]
|
|
||||||
|
|
||||||
# Determine which indices to use - either provided range or full length
|
|
||||||
ind =
|
|
||||||
if index !== nothing
|
|
||||||
[index...]
|
|
||||||
else
|
|
||||||
1:length(chatdict)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Iterate through events and format each one
|
|
||||||
for i in ind
|
|
||||||
event = chatdict[i]
|
|
||||||
subject = event["name"]
|
|
||||||
text = event["text"]
|
|
||||||
d = Dict{String, String}("name"=>subject, "text"=>text)
|
|
||||||
push!(log, d)
|
|
||||||
end
|
|
||||||
|
|
||||||
return log
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function checkAgentResponse_text(response::String, requiredHeader::T
|
|
||||||
)::Tuple where {T<:Array{String}}
|
|
||||||
detected_kw = GeneralUtils.detectKeywordVariation(requiredHeader, response)
|
|
||||||
missingkeys = [k for (k, v) in detected_kw if v === nothing]
|
|
||||||
ispass = false
|
|
||||||
errormsg = nothing
|
|
||||||
if !isempty(missingkeys)
|
|
||||||
errormsg = "$missingkeys are missing from your previous response"
|
|
||||||
ispass = false
|
|
||||||
elseif sum([length(i) for i in values(detected_kw)]) > length(requiredHeader)
|
|
||||||
errormsg = "Your previous attempt has duplicated points according to the required response format"
|
|
||||||
ispass = false
|
|
||||||
else
|
|
||||||
ispass = true
|
|
||||||
end
|
|
||||||
return (ispass, errormsg)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end # module util
|
|
||||||
+598
@@ -0,0 +1,598 @@
|
|||||||
|
module utils
|
||||||
|
|
||||||
|
export clearhistory, availableWineToText, prepareContext, formatMsgForLLM, validateRequiredArgs,
|
||||||
|
validateToolArguments, _userMessageToOpenAI,
|
||||||
|
_assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks,
|
||||||
|
_toolsToOpenAI, beforeToolCall, afterToolCall
|
||||||
|
|
||||||
|
using UUIDs, Dates, DataStructures, HTTP, JSON, NATS
|
||||||
|
using GeneralUtils
|
||||||
|
using ..type
|
||||||
|
|
||||||
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|
||||||
|
"""
|
||||||
|
Clear agent chat history.
|
||||||
|
|
||||||
|
Empties the conversation history, short-term memory, events log, and chatbox.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `a::T`: An agent instance (subtype of `agent`)
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing`
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- Does not clear long-term memory; use `[PENDING] clear memory` when implemented.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> YiemAgent.clearhistory(agent)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function clearhistory(a::T) where {T<:agent}
|
||||||
|
# empty!(a.chathistory)
|
||||||
|
# empty!(a.memory["shortmem"])
|
||||||
|
# empty!(a.memory["events"])
|
||||||
|
# a.memory["chatbox"] = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a vector of wine dictionaries to a formatted text string.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `vecd::Vector`: A vector of dictionaries, each representing a wine with key-value pairs
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- A formatted string where each wine is numbered and each key-value pair is comma-separated
|
||||||
|
in the format: `"1) key1:value1,key2:value2 key3:value3 2) ..."`
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> vecd = [Dict("wine_name" => "Chateau A", "price" => "50")]
|
||||||
|
julia> YiemAgent.availableWineToText(vecd)
|
||||||
|
"1) wine_name:Chateau A,price:50 "
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function availableWineToText(vecd::Vector)::String
|
||||||
|
# Initialize an empty string to hold the final text
|
||||||
|
rowtext = ""
|
||||||
|
# Loop through each dictionary in the input vector
|
||||||
|
for (i, d) in enumerate(vecd)
|
||||||
|
# Iterate over all key-value pairs in the dictionary
|
||||||
|
temp = []
|
||||||
|
for (k, v) in d
|
||||||
|
# Append the formatted string to the text variable
|
||||||
|
t = "$k:$v"
|
||||||
|
push!(temp, t)
|
||||||
|
end
|
||||||
|
_rowtext = join(temp, ',')
|
||||||
|
rowtext *= "$i) $_rowtext "
|
||||||
|
end
|
||||||
|
|
||||||
|
return rowtext
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
prepareContext(state::agentState) -> agentContext
|
||||||
|
|
||||||
|
Prepares an `agentContext` from the given `agentState` for sending to
|
||||||
|
the LLM. By default, it deep copies the system prompt, messages, and
|
||||||
|
tools from `state` into a new `agentContext`.
|
||||||
|
|
||||||
|
Override this function to customize the context — such as filtering
|
||||||
|
tools based on the user's intent, modifying the system prompt, injecting
|
||||||
|
additional context (retrieved documents, current time, user preferences),
|
||||||
|
or pruning and reordering messages before formatting and calling the LLM.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `state::agentState`: The current agent state containing conversation history,
|
||||||
|
system prompt, tools, and other configuration
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `agentContext`: An `agentContext` containing the prepared system prompt,
|
||||||
|
messages, and tools to be sent to the LLM
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
# Default: returns an agentContext with deep copies of system prompt, messages, and tools
|
||||||
|
prepareContext(state).messages == deepcopy(state.messages)
|
||||||
|
|
||||||
|
# Override to filter tools and inject system context:
|
||||||
|
# function prepareContext(state::agentState)
|
||||||
|
# msgs = deepcopy(state.messages)
|
||||||
|
# sysPrompt = state.systemPrompt * "\\nCurrent time: $(now())"
|
||||||
|
# tools = filter(t -> contains(t.description, "wine"), state.tools)
|
||||||
|
# return agentContext(sysPrompt, msgs, tools)
|
||||||
|
# end
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function prepareContext(state::agentState, eventSink)::agentContext
|
||||||
|
|
||||||
|
#TODO filter tools from state.tools based on user intend in user message and tool description
|
||||||
|
filteredTools = state.tools
|
||||||
|
|
||||||
|
#TODO add filtered tools to the current system prompt / modify systemPrompt here
|
||||||
|
preparedSystemPrompt = state.systemPrompt
|
||||||
|
|
||||||
|
#TODO add system prompt, adjust/modify and inject additional context into messages
|
||||||
|
preparedMessages = deepcopy(state.messages) # messages that will be send to LLM
|
||||||
|
|
||||||
|
agentCtx = agentContext(preparedSystemPrompt, preparedMessages, filteredTools)
|
||||||
|
|
||||||
|
return agentCtx
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
formatMsgForLLM(ctx::agentContext) -> Dict{String, Any}
|
||||||
|
|
||||||
|
Converts an `agentContext` into OpenAI-compatible message format
|
||||||
|
ready to be sent to the LLM. The system prompt is converted into
|
||||||
|
a system role message, followed by user, assistant, and tool result
|
||||||
|
messages.
|
||||||
|
|
||||||
|
This function can be overridden in `yiemAgent` to produce custom
|
||||||
|
LLM message formats for different APIs/providers.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `ctx::agentContext`: The prepared context containing system prompt,
|
||||||
|
messages, and tools
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `Dict{String, Any}`: A dictionary with `"messages"` key containing
|
||||||
|
an array of OpenAI-format message dicts
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
# Default output:
|
||||||
|
formatMsgForLLm(ctx) == Dict("messages" => [
|
||||||
|
Dict("role" => "system", "content" => [...]),
|
||||||
|
Dict("role" => "user", "content" => [...]),
|
||||||
|
Dict("role" => "assistant", "content" => [...]),
|
||||||
|
Dict("role" => "tool", "tool_call_id" => "...", "content" => [...]),
|
||||||
|
])
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function formatMsgForLLM(ctx::agentContext, eventSink)::Dict{String, Any}
|
||||||
|
|
||||||
|
""" openai message format example
|
||||||
|
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" => "Do you have something similar to the one in the image?"),
|
||||||
|
Dict(
|
||||||
|
"type" => "image_url",
|
||||||
|
"image_url" => Dict("url" => data_uri)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Dict(
|
||||||
|
"role" => "assistant",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => "let me check."),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"tools"=> [
|
||||||
|
Dict(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict(
|
||||||
|
"name" => "getWeather",
|
||||||
|
"description" => "Get current weather",
|
||||||
|
"parameters" => Dict(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict(
|
||||||
|
"city" => Dict("type" => "string")
|
||||||
|
),
|
||||||
|
"required" => ["city"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
"temperature" => 0.7
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
openaiReadyMsg = Dict{String, Any}()
|
||||||
|
# openaiReadyMsg["model"] = "gemma-4-E4B-it-UD-Q4_K_XL"
|
||||||
|
messages = Vector{Dict{String, Any}}()
|
||||||
|
# System prompt as system message
|
||||||
|
if !isempty(ctx.systemPrompt)
|
||||||
|
push!(messages, Dict(
|
||||||
|
"role" => "system",
|
||||||
|
"content" => [Dict("type" => "text", "text" => ctx.systemPrompt)]
|
||||||
|
))
|
||||||
|
end
|
||||||
|
# Conversation messages
|
||||||
|
for msg in ctx.messages
|
||||||
|
if msg isa userMessage
|
||||||
|
push!(messages, _userMessageToOpenAI(msg))
|
||||||
|
elseif msg isa assistantMessageToolCall
|
||||||
|
push!(messages, _assistantMessageToolCallToOpenAI(msg))
|
||||||
|
elseif msg isa assistantMessage
|
||||||
|
push!(messages, _assistantMessageToOpenAI(msg))
|
||||||
|
elseif msg isa toolResultMessage
|
||||||
|
push!(messages, _toolResultMessageToOpenAI(msg))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# Convert ctx.tools into OpenAI tools format
|
||||||
|
tools_array = _toolsToOpenAI(ctx.tools, eventSink)
|
||||||
|
openaiReadyMsg["messages"] = messages
|
||||||
|
openaiReadyMsg["temperature"] = 0.7
|
||||||
|
|
||||||
|
if !isempty(tools_array)
|
||||||
|
openaiReadyMsg["tools"] = tools_array
|
||||||
|
end
|
||||||
|
|
||||||
|
return openaiReadyMsg
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
beforeToolCall(context::beforeToolCallContext, signal::abortSignal) -> beforeToolCallResult
|
||||||
|
|
||||||
|
Callback invoked before executing a tool call. Use this hook to inspect
|
||||||
|
the tool call and decide whether to allow, block, or modify it.
|
||||||
|
|
||||||
|
Common use cases:
|
||||||
|
- Request user approval via UI before running destructive tools.
|
||||||
|
- Validate business rules that cannot be expressed in the JSON schema.
|
||||||
|
- Check final context (e.g. session state, rate limits, permissions).
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `context::beforeToolCallContext`: Contains the assistant message, tool call,
|
||||||
|
validated arguments, and current conversation context.
|
||||||
|
- `signal::abortSignal`: Signal that may be set to abort the operation.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `beforeToolCallResult(false, "N/A")` to allow the call to proceed.
|
||||||
|
- `beforeToolCallResult(true, "Reason")` to block the call with a reason.
|
||||||
|
- `nothing` is treated as allow (equivalent to `beforeToolCallResult(false, "N/A")`).
|
||||||
|
|
||||||
|
# Example
|
||||||
|
```jldoctest
|
||||||
|
function beforeToolCall(context::beforeToolCallContext, signal::abortSignal)
|
||||||
|
if context.toolCall.name == "deleteFile"
|
||||||
|
# Block file deletion unless explicitly approved
|
||||||
|
return beforeToolCallResult(true, "User must approve file deletion")
|
||||||
|
end
|
||||||
|
return beforeToolCallResult(false, "N/A")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function beforeToolCall(context::beforeToolCallContext, signal::abortSignal, eventSink
|
||||||
|
)::beforeToolCallResult
|
||||||
|
|
||||||
|
# eventSink("beforeToolCall: $(context.toolCall.name) args=$(context.args)")
|
||||||
|
|
||||||
|
return beforeToolCallResult(false, "N/A")
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
afterToolCall(context::afterToolCallContext, signal::abortSignal) -> Union{agentToolResult, Nothing}
|
||||||
|
|
||||||
|
Callback invoked after a tool call finishes executing (before and after errors).
|
||||||
|
Use this hook to post-process the tool result before it is fed back to the LLM.
|
||||||
|
|
||||||
|
Common use cases:
|
||||||
|
- Mask sensitive data (API keys, tokens) from result content.
|
||||||
|
- Normalize usage tracking data into a consistent format.
|
||||||
|
- Inspect the result and set `terminate: true` based on business logic
|
||||||
|
(e.g. "if deployment failed, stop the agent rather than retrying").
|
||||||
|
- Wrap error results in friendlier messages for the LLM to understand.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `context::afterToolCallContext`: Contains the assistant message, tool call,
|
||||||
|
arguments, raw result, error status, and current conversation context.
|
||||||
|
- `signal::abortSignal`: Signal that may be set to abort the operation.
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing` to pass the result through unchanged.
|
||||||
|
- `agentToolResult(...)` to return a modified result (content, details, usage,
|
||||||
|
terminate flag can all be overridden).
|
||||||
|
|
||||||
|
""" # arrange result from searchWine tool
|
||||||
|
function afterToolCall(context::afterToolCallContext, signal::abortSignal, eventSink
|
||||||
|
)::Union{agentToolResult, Nothing}
|
||||||
|
|
||||||
|
# eventSink("afterToolCall: $(context.toolCall.name) result=$(context.result) error=$(context.isError)")
|
||||||
|
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a userMessage to OpenAI message format.
|
||||||
|
"""
|
||||||
|
function _userMessageToOpenAI(msg::userMessage)::Dict{String, Any}
|
||||||
|
return Dict(
|
||||||
|
"role" => "user",
|
||||||
|
"content" => _messageContentToBlocks(msg.content)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert an assistantMessageToolCall to OpenAI message format.
|
||||||
|
|
||||||
|
Produces a message with role="assistant", content=null, and a tool_calls array:
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": null,
|
||||||
|
"tool_calls": [
|
||||||
|
{
|
||||||
|
"id": "call_1",
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_weather",
|
||||||
|
"arguments": "{\"location\": \"San Francisco, CA\"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
function _assistantMessageToolCallToOpenAI(msg::assistantMessageToolCall)::Dict{String, Any}
|
||||||
|
tool_calls = Dict{String, Any}[]
|
||||||
|
for tc in msg.toolCalls
|
||||||
|
push!(tool_calls, Dict(
|
||||||
|
"id" => tc.id,
|
||||||
|
"type" => tc.type,
|
||||||
|
"function" => Dict(
|
||||||
|
"name" => tc.name,
|
||||||
|
"arguments" => JSON.json(tc.arguments)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
end
|
||||||
|
return Dict(
|
||||||
|
"role" => "assistant",
|
||||||
|
"content" => nothing,
|
||||||
|
"tool_calls" => tool_calls
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert an assistantMessage to OpenAI message format.
|
||||||
|
"""
|
||||||
|
function _assistantMessageToOpenAI(msg::assistantMessage)::Dict{String, Any}
|
||||||
|
return Dict(
|
||||||
|
"role" => "assistant",
|
||||||
|
"content" => _messageContentToBlocks(msg.content)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a toolResultMessage to OpenAI message format.
|
||||||
|
"""
|
||||||
|
function _toolResultMessageToOpenAI(msg::toolResultMessage)::Dict{String, Any}
|
||||||
|
return Dict(
|
||||||
|
"role" => "tool",
|
||||||
|
"tool_call_id" => msg.toolCallId,
|
||||||
|
"content" => _messageContentToBlocks(msg.content)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a vector of messageContent to OpenAI content blocks.
|
||||||
|
|
||||||
|
Each textContent becomes a text block, each imageContent becomes
|
||||||
|
an image_url block.
|
||||||
|
"""
|
||||||
|
function _messageContentToBlocks(contents::Vector{messageContent})::Vector{Dict{String, Any}}
|
||||||
|
blocks = Vector{Dict{String, Any}}()
|
||||||
|
|
||||||
|
for c in contents
|
||||||
|
if c isa textContent
|
||||||
|
push!(blocks, Dict("type" => "text", "text" => c.text))
|
||||||
|
elseif c isa imageContent
|
||||||
|
push!(blocks, Dict(
|
||||||
|
"type" => "image_url",
|
||||||
|
"image_url" => Dict(
|
||||||
|
"url" => "data:$(c.mimeType);base64,$(c.data)"
|
||||||
|
)
|
||||||
|
))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return blocks
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
_toolsToOpenAI(tools::Union{OrderedDict{String, agentTool}, Nothing}) -> Vector{Dict{String, Any}}
|
||||||
|
|
||||||
|
Convert an OrderedDict of agentTool definitions into OpenAI function tool format.
|
||||||
|
|
||||||
|
Returns an empty vector when `tools` is `nothing` or empty.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
_toolsToOpenAI(nothing) # => Dict{String, Any}[]
|
||||||
|
_toolsToOpenAI(tools) # => [Dict("type" => "function", "function" => Dict("name" => "getWeather", ...))]
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function _toolsToOpenAI(tools::Union{OrderedDict{String, agentTool}, Nothing}, eventSink)::Vector{Dict{String, Any}}
|
||||||
|
tools_array = Vector{Dict{String, Any}}()
|
||||||
|
|
||||||
|
if tools !== nothing
|
||||||
|
for (_, tool) in tools
|
||||||
|
push!(tools_array, Dict(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict(
|
||||||
|
"name" => tool.name,
|
||||||
|
"description" => tool.description,
|
||||||
|
"parameters" => tool.inputSchema
|
||||||
|
)
|
||||||
|
))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tools_array
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
validateRequiredArgs(args::AbstractDict{String, Any}, inputSchema::AbstractDict{String, Any}) -> Union{Nothing,String}
|
||||||
|
|
||||||
|
Validates that all required fields listed in the tool's JSON Schema are present
|
||||||
|
in `args`. Returns `nothing` if validation passes, or a descriptive error string
|
||||||
|
listing the missing required fields.
|
||||||
|
|
||||||
|
This is the default `validateRequiredArgs` hook. Tool authors can override it
|
||||||
|
with a custom validation function that performs additional checks (e.g. type
|
||||||
|
coercion, format validation, cross-field constraints).
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `args::AbstractDict{String, Any}`: The arguments provided by the LLM
|
||||||
|
- `inputSchema::AbstractDict{String, Any}`: The tool's `inputSchema` (JSON Schema format)
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing` if all required args are present
|
||||||
|
- `String` error message listing missing fields otherwise
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
schema = Dict("required" => ["city"])
|
||||||
|
args = Dict{String,Any}()
|
||||||
|
validateRequiredArgs(args, schema) # => "Missing required arguments: city"
|
||||||
|
|
||||||
|
args2 = Dict("city" => "Tokyo")
|
||||||
|
validateRequiredArgs(args2, schema) # => nothing
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function validateRequiredArgs(args::AbstractDict{String, Any}, inputSchema::AbstractDict{String, Any})::Union{Nothing,String}
|
||||||
|
required = get(inputSchema, "required", Any[])
|
||||||
|
if isempty(required)
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
missing = String[]
|
||||||
|
for field in required
|
||||||
|
if !(field in keys(args))
|
||||||
|
push!(missing, field)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !isempty(missing)
|
||||||
|
return "Missing required arguments: $(join(missing, ", "))"
|
||||||
|
end
|
||||||
|
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
validateToolArguments(tool::agentTool, prepared::agentToolCall) -> Dict{String,Any}
|
||||||
|
|
||||||
|
Validates the prepared tool call arguments by calling the tool's
|
||||||
|
`validateRequiredArgs` hook (or the default implementation). If validation
|
||||||
|
fails, returns a modified `agentToolCall` with an empty arguments dict
|
||||||
|
so downstream code can detect the failure. If the hook exists on the tool
|
||||||
|
and returns an error string, that error is returned.
|
||||||
|
|
||||||
|
This runs **before** the `beforeToolCall` hook, allowing the agent to
|
||||||
|
reject invalid calls without invoking lifecycle callbacks or logging
|
||||||
|
false `toolExecutionStart` events.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `tool::agentTool`: The resolved tool definition
|
||||||
|
- `prepared::agentToolCall`: The prepared tool call with potentially transformed arguments
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `Dict{String,Any}`: The validated arguments if successful
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
- Throws `ArgumentError` if validation fails — this is caught by `prepareToolCall`
|
||||||
|
and converted to an `immediateOutcome`
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
# With validateRequiredArgs hook set on the tool
|
||||||
|
validateToolArguments(toolWithHook, tc) # => validated args or throws
|
||||||
|
|
||||||
|
# With default validation (nothing on tool)
|
||||||
|
validateToolArguments(toolDefault, tc) # => args or throws
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function validateToolArguments(tool::agentTool, prepared::agentToolCall)::AbstractDict{String, Any}
|
||||||
|
# Use default (2-arg: args + schema) or tool-specific hook (1-arg: args only)
|
||||||
|
if isnothing(tool.validateRequiredArgs)
|
||||||
|
result = validateRequiredArgs(prepared.arguments, tool.inputSchema)
|
||||||
|
else
|
||||||
|
result = tool.validateRequiredArgs(prepared.arguments)
|
||||||
|
end
|
||||||
|
|
||||||
|
if result !== nothing
|
||||||
|
throw(ArgumentError(result))
|
||||||
|
end
|
||||||
|
|
||||||
|
return prepared.arguments
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end # module util
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
[deps]
|
|
||||||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
|
||||||
@@ -0,0 +1,642 @@
|
|||||||
|
using Test
|
||||||
|
using YiemAgent
|
||||||
|
using YiemAgent.agentCore
|
||||||
|
using YiemAgent.type
|
||||||
|
using JSON
|
||||||
|
|
||||||
|
# Import the function from the private module scope
|
||||||
|
import YiemAgent.agentCore: _extractToolCalls
|
||||||
|
|
||||||
|
@testset "_extractToolCalls" begin
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Format 1: response["message"]["tool_calls"] (LMStudio.jl style) #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@testset "single tool call via message format" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"finish_reason" => "tool_calls",
|
||||||
|
"index" => 0,
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"content" => "",
|
||||||
|
"reasoning_content" => "Let me check the weather.",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getWeather",
|
||||||
|
"arguments" => "{\"city\":\"Bangkok, Thailand\"}",
|
||||||
|
),
|
||||||
|
"id" => "tc_001",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == "getWeather"
|
||||||
|
@test tc_list[1].id == "tc_001"
|
||||||
|
@test tc_list[1].type == "function"
|
||||||
|
@test tc_list[1].arguments["city"] == "Bangkok, Thailand"
|
||||||
|
@test assistant_msg isa assistantMessage
|
||||||
|
@test assistant_msg.role == "assistant"
|
||||||
|
@test assistant_msg.stopReason == "tool_calls"
|
||||||
|
@test length(assistant_msg.content) == 1
|
||||||
|
@test assistant_msg.content[1] isa reasoningContent
|
||||||
|
@test assistant_msg.content[1].text == "Let me check the weather."
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "multiple tool calls via message format" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"content" => "",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getWeather",
|
||||||
|
"arguments" => "{\"city\":\"Tokyo, Japan\"}",
|
||||||
|
),
|
||||||
|
"id" => "tc_001",
|
||||||
|
),
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getTime",
|
||||||
|
"arguments" => "{\"timezone\":\"Asia/Tokyo\"}",
|
||||||
|
),
|
||||||
|
"id" => "tc_002",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 2
|
||||||
|
@test tc_list[1].name == "getWeather"
|
||||||
|
@test tc_list[1].arguments["city"] == "Tokyo, Japan"
|
||||||
|
@test tc_list[2].name == "getTime"
|
||||||
|
@test tc_list[2].arguments["timezone"] == "Asia/Tokyo"
|
||||||
|
@test assistant_msg.role == "assistant"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool call with empty arguments string" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "listTools",
|
||||||
|
"arguments" => "{}",
|
||||||
|
),
|
||||||
|
"id" => "tc_empty",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == "listTools"
|
||||||
|
@test tc_list[1].arguments == Dict{String,Any}()
|
||||||
|
@test assistant_msg.stopReason == "end_turn"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool call with missing id falls back to uuid" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getTime",
|
||||||
|
"arguments" => "{\"city\":\"NYC\"}",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test !isempty(tc_list[1].id)
|
||||||
|
@test tc_list[1].name == "getTime"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool call with non-string arguments (pre-parsed dict)" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getWeather",
|
||||||
|
"arguments" => Dict{String,Any}("city" => "London", "units" => "fahrenheit"),
|
||||||
|
),
|
||||||
|
"id" => "tc_parsed",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].arguments["city"] == "London"
|
||||||
|
@test tc_list[1].arguments["units"] == "fahrenheit"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool call with api/provider/model/usage metadata" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"api" => "openai",
|
||||||
|
"provider" => "anthropic",
|
||||||
|
"model" => "claude-3-opus",
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getTime",
|
||||||
|
"arguments" => "{}",
|
||||||
|
),
|
||||||
|
"id" => "tc_meta",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test assistant_msg.api == "openai"
|
||||||
|
@test assistant_msg.provider == "anthropic"
|
||||||
|
@test assistant_msg.model == "claude-3-opus"
|
||||||
|
end
|
||||||
|
|
||||||
|
# --------------------------------------------------------------- #
|
||||||
|
# Format 2: response.content blocks (OpenAI API style) #
|
||||||
|
# --------------------------------------------------------------- #
|
||||||
|
|
||||||
|
@testset "content blocks with tool_calls" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}("type" => "text", "text" => "Let me check."),
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "tool_calls",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getWeather",
|
||||||
|
"arguments" => "{\"city\":\"Paris\"}",
|
||||||
|
),
|
||||||
|
"id" => "tc_block_1",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == "getWeather"
|
||||||
|
@test tc_list[1].arguments["city"] == "Paris"
|
||||||
|
# text block before tool_calls should be included in content
|
||||||
|
@test length(assistant_msg.content) == 1
|
||||||
|
@test assistant_msg.content[1].text == "Let me check."
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "content blocks with tool_call (single-call format)" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "tool_call",
|
||||||
|
"id" => "tc_single",
|
||||||
|
"name" => "getTime",
|
||||||
|
"arguments" => Dict{String,Any}("timezone" => "Europe/London"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == "getTime"
|
||||||
|
@test tc_list[1].id == "tc_single"
|
||||||
|
@test tc_list[1].arguments["timezone"] == "Europe/London"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "content blocks with reasoning and text" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}("type" => "reasoning", "text" => "Thinking..."),
|
||||||
|
Dict{String,Any}("type" => "text", "text" => "Here's the answer."),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == false
|
||||||
|
@test length(tc_list) == 0
|
||||||
|
@test length(assistant_msg.content) == 2
|
||||||
|
@test assistant_msg.content[1] isa reasoningContent
|
||||||
|
@test assistant_msg.content[1].text == "Thinking..."
|
||||||
|
@test assistant_msg.content[2] isa textContent
|
||||||
|
@test assistant_msg.content[2].text == "Here's the answer."
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "content blocks with text and tool_call (tool_call not in content)" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}("type" => "text", "text" => "Sure, I'll check."),
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "tool_call",
|
||||||
|
"id" => "tc_mix",
|
||||||
|
"name" => "getWeather",
|
||||||
|
"arguments" => Dict{String,Any}("city" => "London"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == "getWeather"
|
||||||
|
# text block included, tool_call block excluded from content
|
||||||
|
@test length(assistant_msg.content) == 1
|
||||||
|
@test assistant_msg.content[1].text == "Sure, I'll check."
|
||||||
|
end
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# assistantMessage construction #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@testset "assistantMessage with error_message and errorMessage fallback" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"error_message" => "rate limit",
|
||||||
|
"content" => Any[Dict{String,Any}("type" => "text", "text" => "fail")],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == false
|
||||||
|
@test assistant_msg.errorMessage == "rate limit"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "assistantMessage with usage tracking" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[Dict{String,Any}("type" => "text", "text" => "hi")],
|
||||||
|
"usage" => llmUsage(100, 50),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test assistant_msg.usage.inputTokens == 100
|
||||||
|
@test assistant_msg.usage.outputTokens == 50
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "assistantMessage with invalid usage defaults to zero" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[Dict{String,Any}("type" => "text", "text" => "hi")],
|
||||||
|
"usage" => "invalid",
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test assistant_msg.usage.inputTokens == 0
|
||||||
|
@test assistant_msg.usage.outputTokens == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "reasoning_content as textContent" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"reasoning_content" => textContent("internal thought"),
|
||||||
|
"content" => Any[Dict{String,Any}("type" => "text", "text" => "output")],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test length(assistant_msg.content) == 2
|
||||||
|
@test assistant_msg.content[1] isa reasoningContent
|
||||||
|
@test assistant_msg.content[1].text == "internal thought"
|
||||||
|
@test assistant_msg.content[2] isa textContent
|
||||||
|
@test assistant_msg.content[2].text == "output"
|
||||||
|
end
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# No tool call cases #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@testset "no tool calls found" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}("type" => "text", "text" => "Hello world."),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == false
|
||||||
|
@test length(tc_list) == 0
|
||||||
|
@test assistant_msg.stopReason == "end_turn"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "empty message" begin
|
||||||
|
response = Dict{String,Any}()
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == false
|
||||||
|
@test length(tc_list) == 0
|
||||||
|
@test assistant_msg.role == "assistant"
|
||||||
|
@test assistant_msg.stopReason == "end_turn"
|
||||||
|
@test length(assistant_msg.content) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "message with empty tool_calls array" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"tool_calls" => Any[],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == false
|
||||||
|
@test length(tc_list) == 0
|
||||||
|
@test assistant_msg.role == "assistant"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "message field is not a Dict" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => "not a dict",
|
||||||
|
"content" => Any[Dict{String,Any}("type" => "text", "text" => "fallback")],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == false
|
||||||
|
@test length(tc_list) == 0
|
||||||
|
@test length(assistant_msg.content) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Format 1 takes priority over Format 2" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getWeather",
|
||||||
|
"arguments" => "{\"city\":\"Format1\"}",
|
||||||
|
),
|
||||||
|
"id" => "tc_fmt1",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "tool_call",
|
||||||
|
"id" => "tc_fmt2",
|
||||||
|
"name" => "getTime",
|
||||||
|
"arguments" => Dict{String,Any}("city" => "Format2"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == "getWeather"
|
||||||
|
end
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Edge cases #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@testset "tool call with null arguments" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getTime",
|
||||||
|
"arguments" => nothing,
|
||||||
|
),
|
||||||
|
"id" => "tc_null",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == "getTime"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool call with missing function key" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"id" => "tc_nofunc",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == ""
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool call with missing name in function block" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "assistant",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}("arguments" => "{}"),
|
||||||
|
"id" => "tc_noname",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == ""
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "message format with JSON.Object (JSON.parse result)" begin
|
||||||
|
json_str = JSON.json(Dict(
|
||||||
|
"message" => Dict(
|
||||||
|
"role" => "assistant",
|
||||||
|
"tool_calls" => [
|
||||||
|
Dict(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict("name" => "getWeather", "arguments" => "{\"city\":\"Test\"}"),
|
||||||
|
"id" => "tc_jsonobj",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
))
|
||||||
|
parsed = JSON.parse(json_str)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(parsed)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == "getWeather"
|
||||||
|
@test tc_list[1].arguments["city"] == "Test"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool_calls block with mixed content types (text + tool_calls)" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}("type" => "text", "text" => "I'll check both."),
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "tool_calls",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getWeather",
|
||||||
|
"arguments" => "{\"city\":\"London\"}",
|
||||||
|
),
|
||||||
|
"id" => "tc_mix1",
|
||||||
|
),
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getTime",
|
||||||
|
"arguments" => "{\"timezone\":\"UTC\"}",
|
||||||
|
),
|
||||||
|
"id" => "tc_mix2",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 2
|
||||||
|
@test tc_list[1].name == "getWeather"
|
||||||
|
@test tc_list[2].name == "getTime"
|
||||||
|
@test length(assistant_msg.content) == 1
|
||||||
|
@test assistant_msg.content[1].text == "I'll check both."
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool_call block without arguments field" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "tool_call",
|
||||||
|
"id" => "tc_noargs",
|
||||||
|
"name" => "getTime",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].arguments == Dict{String,Any}()
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool_calls block with empty tool_calls array" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "tool_calls",
|
||||||
|
"tool_calls" => Any[],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == false
|
||||||
|
@test length(tc_list) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool_calls block with non-AbstractDict elements" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "tool_calls",
|
||||||
|
"tool_calls" => Any["not a dict", 42, nothing],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == false
|
||||||
|
@test length(tc_list) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "content field is not a Vector" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => "not a vector",
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == false
|
||||||
|
@test length(tc_list) == 0
|
||||||
|
@test length(assistant_msg.content) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Dict-based response with all metadata fields" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"api" => "openai",
|
||||||
|
"provider" => "anthropic",
|
||||||
|
"model" => "claude-3-sonnet",
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "tool_call",
|
||||||
|
"id" => "tc_meta",
|
||||||
|
"name" => "getTime",
|
||||||
|
"arguments" => Dict{String,Any}("city" => "Seoul"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == true
|
||||||
|
@test length(tc_list) == 1
|
||||||
|
@test tc_list[1].name == "getTime"
|
||||||
|
@test tc_list[1].arguments["city"] == "Seoul"
|
||||||
|
@test assistant_msg.api == "openai"
|
||||||
|
@test assistant_msg.provider == "anthropic"
|
||||||
|
@test assistant_msg.model == "claude-3-sonnet"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "default role is assistant" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[Dict{String,Any}("type" => "text", "text" => "no role specified")],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test assistant_msg.role == "assistant"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "tool call with custom role in message format" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"message" => Dict{String,Any}(
|
||||||
|
"role" => "custom_role",
|
||||||
|
"tool_calls" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "function",
|
||||||
|
"function" => Dict{String,Any}(
|
||||||
|
"name" => "getWeather",
|
||||||
|
"arguments" => "{}",
|
||||||
|
),
|
||||||
|
"id" => "tc_role",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test assistant_msg.role == "custom_role"
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "image content block handling" begin
|
||||||
|
response = Dict{String,Any}(
|
||||||
|
"content" => Any[
|
||||||
|
Dict{String,Any}(
|
||||||
|
"type" => "image_url",
|
||||||
|
"image_url" => Dict("url" => "data:image/png;base64,abc123"),
|
||||||
|
),
|
||||||
|
Dict{String,Any}("type" => "text", "text" => "What is this?"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
|
@test has_toolcalls == false
|
||||||
|
@test length(assistant_msg.content) == 2
|
||||||
|
@test assistant_msg.content[1] isa textContent
|
||||||
|
@test assistant_msg.content[1].text == ""
|
||||||
|
@test assistant_msg.content[2].text == "What is this?"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,296 +0,0 @@
|
|||||||
using Revise
|
|
||||||
using JSON, JSON3, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames
|
|
||||||
using YiemAgent, GeneralUtils
|
|
||||||
using Base.Threads
|
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# load config
|
|
||||||
config = JSON.parsefile("/appfolder/app/dev/YiemAgent/test/config.json")
|
|
||||||
# config = copy(JSON.parsefile("../mountvolume/config.json"))
|
|
||||||
|
|
||||||
|
|
||||||
function executeSQL(sql::T) where {T<:AbstractString}
|
|
||||||
host = config[:externalservice][:wineDB][:host]
|
|
||||||
port = config[:externalservice][:wineDB][:port]
|
|
||||||
dbname = config[:externalservice][:wineDB][:dbname]
|
|
||||||
user = config[:externalservice][:wineDB][:user]
|
|
||||||
password = config[:externalservice][:wineDB][:password]
|
|
||||||
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
|
|
||||||
result = LibPQ.execute(DBconnection, sql)
|
|
||||||
close(DBconnection)
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function executeSQLVectorDB(sql)
|
|
||||||
host = config[:externalservice][:SQLVectorDB][:host]
|
|
||||||
port = config[:externalservice][:SQLVectorDB][:port]
|
|
||||||
dbname = config[:externalservice][:SQLVectorDB][:dbname]
|
|
||||||
user = config[:externalservice][:SQLVectorDB][:user]
|
|
||||||
password = config[:externalservice][:SQLVectorDB][:password]
|
|
||||||
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
|
|
||||||
result = LibPQ.execute(DBconnection, sql)
|
|
||||||
close(DBconnection)
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function text2textInstructLLM(prompt::String; maxattempt::Integer=3, modelsize::String="medium",
|
|
||||||
llmkwargs=Dict(
|
|
||||||
:num_ctx => 32768,
|
|
||||||
:temperature => 0.1,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
|
||||||
config[:externalservice][:loadbalancer][:mqtttopic];
|
|
||||||
msgPurpose="inference",
|
|
||||||
senderName="yiemagent",
|
|
||||||
senderId=sessionId,
|
|
||||||
receiverName="text2textinstruct_$modelsize",
|
|
||||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
|
||||||
:msgMeta => msgMeta,
|
|
||||||
:payload => Dict(
|
|
||||||
:text => prompt,
|
|
||||||
:kwargs => llmkwargs
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
response = nothing
|
|
||||||
for attempts in 1:maxattempt
|
|
||||||
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=180, maxattempt=maxattempt)
|
|
||||||
payload = _response[:response]
|
|
||||||
if _response[:success] && payload[:text] !== nothing
|
|
||||||
response = _response[:response][:text]
|
|
||||||
break
|
|
||||||
else
|
|
||||||
println("\n<text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
pprintln(outgoingMsg)
|
|
||||||
println("</text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
|
||||||
sleep(3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return response
|
|
||||||
end
|
|
||||||
|
|
||||||
# get text embedding from a LLM service
|
|
||||||
function getEmbedding(text::T) where {T<:AbstractString}
|
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
|
||||||
config[:externalservice][:loadbalancer][:mqtttopic];
|
|
||||||
msgPurpose="embedding",
|
|
||||||
senderName="yiemagent",
|
|
||||||
senderId=sessionId,
|
|
||||||
receiverName="textembedding",
|
|
||||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
|
||||||
:msgMeta => msgMeta,
|
|
||||||
:payload => Dict(
|
|
||||||
:text => [text] # must be a vector of string
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120, maxattempt=3)
|
|
||||||
embedding = response[:response][:embeddings]
|
|
||||||
return embedding
|
|
||||||
end
|
|
||||||
|
|
||||||
function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
|
|
||||||
vectorDB::Function; limit::Integer=1
|
|
||||||
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
|
|
||||||
# get embedding from LLM service
|
|
||||||
embedding = getEmbedding(text)[1]
|
|
||||||
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
|
|
||||||
sql = """
|
|
||||||
SELECT *, $embeddingColumnName <-> '$embedding' as distance
|
|
||||||
FROM $tablename
|
|
||||||
ORDER BY distance LIMIT $limit;
|
|
||||||
"""
|
|
||||||
response = vectorDB(sql)
|
|
||||||
df = DataFrame(response)
|
|
||||||
return df
|
|
||||||
end
|
|
||||||
|
|
||||||
function similarSQLVectorDB(query; maxdistance::Integer=100)
|
|
||||||
tablename = "sqlllm_decision_repository"
|
|
||||||
# get embedding of the query
|
|
||||||
df = findSimilarTextFromVectorDB(query, tablename,
|
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
|
||||||
# println(df[1, [:id, :function_output]])
|
|
||||||
row, col = size(df)
|
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
|
||||||
# distance = 100 # CHANGE this is for testing only
|
|
||||||
if row != 0 && distance < maxdistance
|
|
||||||
# if there is usable SQL, return it.
|
|
||||||
output_b64 = df[1, :function_output_base64] # pick the closest match
|
|
||||||
output_str = String(base64decode(output_b64))
|
|
||||||
rowid = df[1, :id]
|
|
||||||
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
return (dict=output_str, distance=distance)
|
|
||||||
else
|
|
||||||
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
return (dict=nothing, distance=nothing)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=3) where {T1<:AbstractString, T2<:AbstractString}
|
|
||||||
tablename = "sqlllm_decision_repository"
|
|
||||||
# get embedding of the query
|
|
||||||
# query = state[:thoughtHistory][:question]
|
|
||||||
df = findSimilarTextFromVectorDB(query, tablename,
|
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
|
||||||
row, col = size(df)
|
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
|
||||||
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
|
||||||
query_embedding = getEmbedding(query)[1]
|
|
||||||
query = replace(query, "'" => "")
|
|
||||||
sql_base64 = base64encode(SQL)
|
|
||||||
sql_ = replace(SQL, "'" => "")
|
|
||||||
|
|
||||||
sql = """
|
|
||||||
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
|
|
||||||
"""
|
|
||||||
# println("\n~~~ added new decision to vectorDB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
# println(sql)
|
|
||||||
_ = executeSQLVectorDB(sql)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=3
|
|
||||||
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
|
|
||||||
tablename = "sommelier_decision_repository"
|
|
||||||
# find similar
|
|
||||||
println("\n~~~ search vectorDB for this: $recentevents ", @__FILE__, " ", @__LINE__)
|
|
||||||
df = findSimilarTextFromVectorDB(recentevents, tablename,
|
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
|
||||||
row, col = size(df)
|
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
|
||||||
if row != 0 && distance < maxdistance
|
|
||||||
# if there is usable decision, return it.
|
|
||||||
rowid = df[1, :id]
|
|
||||||
println("\n~~~ found similar decision. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
|
|
||||||
output_b64 = df[1, :function_output_base64] # pick the closest match
|
|
||||||
_output_str = String(base64decode(output_b64))
|
|
||||||
output = copy(JSON.parsefile(_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 = "12345"
|
|
||||||
|
|
||||||
externalFunction = (
|
|
||||||
getEmbedding=getEmbedding,
|
|
||||||
text2textInstructLLM=text2textInstructLLM,
|
|
||||||
executeSQL=executeSQL,
|
|
||||||
similarSQLVectorDB=similarSQLVectorDB,
|
|
||||||
insertSQLVectorDB=insertSQLVectorDB,
|
|
||||||
similarSommelierDecision=similarSommelierDecision,
|
|
||||||
insertSommelierDecision=insertSommelierDecision,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
a = YiemAgent.sommelier(
|
|
||||||
externalFunction;
|
|
||||||
name="Ton",
|
|
||||||
id=sessionId, # agent instance id
|
|
||||||
retailername="Yiem",
|
|
||||||
)
|
|
||||||
|
|
||||||
while true
|
|
||||||
print("\nyour respond: ")
|
|
||||||
user_answer = readline()
|
|
||||||
response = YiemAgent.conversation(agent;
|
|
||||||
userinput=Dict(:text=> user_answer),
|
|
||||||
maximumMsg=50)
|
|
||||||
println("\n$response")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# response = YiemAgent.conversation(a, Dict(:text=> "I want to get a French red wine under 100."))
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
hello I want to get a bottle of red wine for my boss. I have a budget around 50 dollars. Show me some options.
|
|
||||||
|
|
||||||
I have no idea about his wine taste but he likes spicy food.
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
using Revise
|
|
||||||
using YiemAgent, GeneralUtils, JSON3, DataStructures
|
|
||||||
|
|
||||||
thoughtDict = OrderedDict(
|
|
||||||
:Question=> "Hello, I would like a get a bottle of wine",
|
|
||||||
:Thought_1=> "The customer wants to buy a bottle of wine, but we need more information about their preferences.",
|
|
||||||
:Action_1=> Dict(
|
|
||||||
:name=> "chatbox",
|
|
||||||
:input=> "What occasion are you buying the wine for?",
|
|
||||||
),
|
|
||||||
:Observation_1=> "We are having a wedding pary this weekend.",
|
|
||||||
|
|
||||||
:Thought_2=> "A wedding party is a great occasion to have a good bottle of wine.",
|
|
||||||
:Action_2=> Dict(
|
|
||||||
:name=> "chatbox",
|
|
||||||
:input=> "What type of food will you be serving with the wine?",
|
|
||||||
),
|
|
||||||
:Observation_2=> "I think it is Thai dishes",
|
|
||||||
|
|
||||||
:Thought_3=> "Now that I know the occasion and food, I need to ask about the budget.",
|
|
||||||
:Action_3=> Dict(
|
|
||||||
:name=> "chatbox",
|
|
||||||
:input=> "What is your budget for this wine?",
|
|
||||||
),
|
|
||||||
:Observation_3=> "50 bucks",
|
|
||||||
|
|
||||||
:Thought_4=> "With a budget of \$50, we have a wide range of options. Now that I know it's a wedding party and Thai dishes, I need to ask about the type of wine they prefer.",
|
|
||||||
:Action_4=> Dict(
|
|
||||||
:name=> "chatbox",
|
|
||||||
:input=> "What type of wine are you looking for? (Red, White, Sparkling, Rose, Dessert, Fortified)",
|
|
||||||
),
|
|
||||||
:Observation_4=> "Sparkling please.",
|
|
||||||
|
|
||||||
:Thought_5=> "Now that I know the occasion, food, budget and preferred type of wine, it's time to check our inventory for the best matching wine.",
|
|
||||||
:Action_5=> Dict(
|
|
||||||
:name=> "winestock",
|
|
||||||
:input=> "wine with budget \$50, Thai dishes, sparkling, wedding party",
|
|
||||||
),
|
|
||||||
:Observation_5=> "I found the following wine in stock {1 : Zena Crown Vista, 2 : Schrader Cabernet Sauvignon}",
|
|
||||||
|
|
||||||
:Thought_6=> "Now that I have all the information, it's time to recommend a wine that fits their preferences.",
|
|
||||||
:Action_6=> Dict(
|
|
||||||
:name=> "recommendation",
|
|
||||||
:input=> "I recommend Zena Crown Vista for its sparkling and affordable price.",
|
|
||||||
),
|
|
||||||
:Observation_6=> "I don't like it. Do you have another option?",
|
|
||||||
)
|
|
||||||
|
|
||||||
_thoughtJsonStr = JSON.json(thoughtDict)
|
|
||||||
thoughtJsonStr = _thoughtJsonStr[1:end-1] # remove } at the end
|
|
||||||
# @show thoughtJsonStr
|
|
||||||
|
|
||||||
_, latestThoughtIndice = GeneralUtils.findHighestIndexKey(thoughtDict, "Thought")
|
|
||||||
nextThoughtIndice = latestThoughtIndice + 1
|
|
||||||
|
|
||||||
_prompt =
|
|
||||||
"""
|
|
||||||
You are a helpful sommelier working for a wine store.
|
|
||||||
Your goal is to reccommend the best wine from your inventory that match the user preferences.
|
|
||||||
|
|
||||||
You must follow the following criteria:
|
|
||||||
1) Get to know what occasion the user is buying wine for
|
|
||||||
2) Get to know what food the user will have with wine
|
|
||||||
3) Get to know how much the user willing to spend
|
|
||||||
4) Get to know type of wine the user is looking for e.g. Red, White, Sparkling, Rose, Dessert, Fortified
|
|
||||||
5) Get to know what characteristics of wine the user is looking for
|
|
||||||
e.g. tannin, sweetness, intensity, acidity
|
|
||||||
6) Check your inventory for the best wine that match the user preference
|
|
||||||
7) Recommend wine to the user
|
|
||||||
|
|
||||||
You should only respond with interleaving Thought, Action, Observation steps.
|
|
||||||
Thought can reason about the current situation, and Action can be three types:
|
|
||||||
1) winestock[query], which you can use to find wine in your inventory. The more input data the better.
|
|
||||||
2) chatbox[text], which you can use to interact with the user.
|
|
||||||
3) recommendation[answer], which returns your wine reccommendation to the user.
|
|
||||||
|
|
||||||
You should only respond in JSON format as describe below:
|
|
||||||
{
|
|
||||||
"Thought": "your reasoning",
|
|
||||||
"Action": {"name": "action to take", "input": "Action input"},
|
|
||||||
"Observation": "result of the action"
|
|
||||||
}
|
|
||||||
|
|
||||||
Here are some examples:
|
|
||||||
{
|
|
||||||
"Question": "I would like to buy a sedan with 8 seats.",
|
|
||||||
"Thought_1": "Our showroom carries various vehicle model. But I'm not sure whether we have a models that fits the user demand, I need to check our inventory.",
|
|
||||||
"Action_1": {"name": "inventory", "input": "sedan with 8 seats."},
|
|
||||||
"Observation_1": "Several model has 8 seats. Available color are black, red green"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"Thought_2": "I have to ask the user what color he likes.",
|
|
||||||
"Action_2": {"name": "chatbox", "input": "Which color do you like?"}
|
|
||||||
"Observation_2": "I'll take black."
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"Thought_3": "There is only one model that fits the user preference. It's Yiem model A",
|
|
||||||
"Action_3": {"name": "recommendation", "input": "I recommend a Yiem model A"}
|
|
||||||
}
|
|
||||||
|
|
||||||
Let's begin!
|
|
||||||
|
|
||||||
$(JSON.json(thoughtDict))
|
|
||||||
{Thought_$nextThoughtIndice
|
|
||||||
"""
|
|
||||||
|
|
||||||
prompt = YiemAgent.formatLLMtext_llama3instruct("system", _prompt)
|
|
||||||
@show prompt
|
|
||||||
msgMeta = Dict(:requestResponse => nothing,
|
|
||||||
:msgPurpose => nothing,
|
|
||||||
:receiverId => nothing,
|
|
||||||
:getPost => nothing,
|
|
||||||
:msgId => "4c7111e0-c30e-44c3-8f85-1c8b3f03a8be",
|
|
||||||
:acknowledgestatus => nothing,
|
|
||||||
:replyToMsgId => nothing,
|
|
||||||
:msgFormatVersion => nothing,
|
|
||||||
:mqttServerInfo => Dict(:port => 1883, :broker => "mqtt.yiem.cc"),
|
|
||||||
:sendTopic => "/loadbalancer/requestingservice",
|
|
||||||
:receiverName => "text2textinstruct",
|
|
||||||
:replyTopic => nothing,
|
|
||||||
:senderName => "decisionMaker",
|
|
||||||
:senderSelfnote => nothing,
|
|
||||||
:senderId => "testingSessionID",
|
|
||||||
:timeStamp => "2024-05-04T08:06:23.561"
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
|
||||||
:msgMeta=> msgMeta,
|
|
||||||
:payload=> Dict(
|
|
||||||
:text=> prompt,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
|
||||||
thoughtJsonStr = _response[:response][:text]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
using Revise # remove when this package is completed
|
|
||||||
using YiemAgent, GeneralUtils, JSON3, MQTTClient, Dates, UUIDs, DataStructures
|
|
||||||
using Base.Threads
|
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
||||||
|
|
||||||
config = copy(JSON.parsefile("config.json"))
|
|
||||||
|
|
||||||
instanceInternalTopic = config[:serviceInternalTopic][:mqtttopic] * "/1"
|
|
||||||
|
|
||||||
client, connection = MakeConnection(config[:mqttServerInfo][:broker],
|
|
||||||
config[:mqttServerInfo][:port])
|
|
||||||
|
|
||||||
receiveUserMsgChannel = Channel{Dict}(4)
|
|
||||||
receiveInternalMsgChannel = Channel{Dict}(4)
|
|
||||||
|
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
|
||||||
"N/A",
|
|
||||||
replyTopic = config[:servicetopic][:mqtttopic] # ask frontend reply to this instance_chat_topic
|
|
||||||
)
|
|
||||||
|
|
||||||
agentConfig = Dict(
|
|
||||||
:mqttServerInfo=> config[:mqttServerInfo],
|
|
||||||
:receivemsg=> Dict(
|
|
||||||
:prompt=> config[:servicetopic][:mqtttopic], # topic to receive prompt i.e. frontend send msg to this topic
|
|
||||||
:internal=> instanceInternalTopic,
|
|
||||||
),
|
|
||||||
:externalservice=> config[:externalservice],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Instantiate an agent
|
|
||||||
tools=Dict( # update input format
|
|
||||||
"askbox"=> Dict(
|
|
||||||
:description => "<askbox tool description>Useful for when you need to ask the user for more context. Do not ask the user their own question.</askbox tool description>",
|
|
||||||
:input => """<input>Input is a text in JSON format.</input><input example>{\"Q1\": \"How are you doing?\", \"Q2\": \"How may I help you?\"}</input example>""",
|
|
||||||
:output => "" ,
|
|
||||||
:func => nothing,
|
|
||||||
),
|
|
||||||
# "winestock"=> Dict(
|
|
||||||
# :description => "<winestock tool description>A handy tool for searching wine in your inventory that match the user preferences.</winestock tool description>",
|
|
||||||
# :input => """<input>Input is a JSON-formatted string that contains a detailed and precise search query.</input><input example>{\"wine type\": \"rose\", \"price\": \"max 35\", \"sweetness level\": \"sweet\", \"intensity level\": \"light bodied\", \"Tannin level\": \"low\", \"Acidity level\": \"low\"}</input example>""",
|
|
||||||
# :output => """<output>Output are wines that match the search query in JSON format.""",
|
|
||||||
# :func => ChatAgent.winestock,
|
|
||||||
# ),
|
|
||||||
"finalanswer"=> Dict(
|
|
||||||
:description => "<tool description>Useful for when you are ready to recommend wines to the user.</tool description>",
|
|
||||||
:input => """<input format>{\"finalanswer\": \"some text\"}.</input format><input example>{\"finalanswer\": \"I recommend Zena Crown Vista\"}</input example>""",
|
|
||||||
:output => "" ,
|
|
||||||
:func => nothing,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
a = YiemAgent.sommelier(
|
|
||||||
receiveUserMsgChannel,
|
|
||||||
receiveInternalMsgChannel,
|
|
||||||
agentConfig,
|
|
||||||
name= "assistant",
|
|
||||||
id= "testingSessionID", # agent instance id
|
|
||||||
tools=tools,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
input =
|
|
||||||
OrderedDict{String, Any}(:question => "Hello, I would like a get a bottle of wine", :thought_1 => "It's great that the user is looking for a bottle of wine. To give them a personalized recommendation, I need to know more about their preferences.", :action_1 => Dict{String, Any}(:name => "chatbox", :input => "What occasion are you planning to use this wine for?"), :observation_1 => "We are holding a wedding party", :thought_2 => "A wedding party is a great occasion for a special bottle of wine. I need to know what type of food will be served, and how much the user is willing to spend.", :action_2 => Dict{String, Any}(:name => "chatbox", :input => "What type of food will you be serving at the wedding?"), :observation_2 => "It will be Thai dishes.", :thought_3 => "The type of wine that pairs well with Thai dishes is usually a crisp and refreshing white wine, but I also need to consider the budget and personal preferences.", :action_3 => Dict{String, Any}(:name => "chatbox", :input => "How much are you willing to spend on this bottle of wine?"), :observation_3 => "I would spend up to 50 bucks.", :thought_4 => "I have a good idea of the occasion, food, and budget. Now I need to know what type of wine the user is looking for.", :action_4 => Dict{String, Any}(:name => "chatbox", :input => "What type of wine are you usually looking for? Red, White, Sparkling, Rose, Dessert or Fortified?"), :observation_4 => "I like full-bodied Red wine with low tannin.", :thought_5 => "Now that I have all the necessary information, I can start searching for a suitable wine in our inventory.", :action_5 => Dict{String, Any}(:name => "winestock", :input => "red wine with low tannins"), :observation_5 => "I found the following wines in our stock: \n{\n 1: El Enemigo Cabernet Franc 2019\n2: Tantara Chardonnay 2017\n\n}\n", :thought_6 => "Now that I have the information about the wine, it's time to make a recommendation.", :action_6 => Dict{String, Any}(:name => "recommendbox", :input => "El Enemigo Cabernet Franc 2019"), :observation_6 => "I don't like the one you recommend. I want dry wine.")
|
|
||||||
|
|
||||||
|
|
||||||
result = YiemAgent.jsoncorrection(a, input)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
using Revise
|
|
||||||
using YiemAgent, GeneralUtils, JSON3, DataStructures, LibPQ
|
|
||||||
using SQLLLM
|
|
||||||
|
|
||||||
|
|
||||||
# _prompt =
|
|
||||||
# """
|
|
||||||
# You are a helpful assistant.
|
|
||||||
# answer the following question:
|
|
||||||
# From the following CSV text:
|
|
||||||
# "{\"tabledescription\":[\"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.\",\"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.\",\"The wine_food table represents the association between wines and food items. It estab" ⋯ 477 bytes ⋯ "ed to retailer names, usernames, passwords, addresses, contact persons, telephone numbers, email addresses, additional_search_term, other attributes (in JSON format) and a description.\",\"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.\",\"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.\"],\"tablename\":[\"customer\",\"wine\",\"wine_food\",\"food\",\"retailer\",\"retailer_wine\",\"retailer_food\"]}"
|
|
||||||
# What is the description of table wine?
|
|
||||||
# """
|
|
||||||
|
|
||||||
# prompt = YiemAgent.formatLLMtext_llama3instruct("system", _prompt)
|
|
||||||
# @show prompt
|
|
||||||
# msgMeta = Dict(:requestResponse => nothing,
|
|
||||||
# :msgPurpose => nothing,
|
|
||||||
# :receiverId => nothing,
|
|
||||||
# :getPost => nothing,
|
|
||||||
# :msgId => "4c7111e0-c30e-44c3-8f85-1c8b3f03a8be",
|
|
||||||
# :acknowledgestatus => nothing,
|
|
||||||
# :replyToMsgId => nothing,
|
|
||||||
# :msgFormatVersion => nothing,
|
|
||||||
# :mqttServerInfo => Dict(:port => 1883, :broker => "mqtt.yiem.cc"),
|
|
||||||
# :sendTopic => "/loadbalancer/requestingservice",
|
|
||||||
# :receiverName => "text2textinstruct",
|
|
||||||
# :replyTopic => nothing,
|
|
||||||
# :senderName => "decisionMaker",
|
|
||||||
# :senderSelfnote => nothing,
|
|
||||||
# :senderId => "testingSessionID",
|
|
||||||
# :timeStamp => "2024-05-04T08:06:23.561"
|
|
||||||
# )
|
|
||||||
|
|
||||||
# outgoingMsg = Dict(
|
|
||||||
# :msgMeta=> msgMeta,
|
|
||||||
# :payload=> Dict(
|
|
||||||
# :text=> prompt,
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# _response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
|
||||||
# result = _response[:response][:text]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DBconnection = LibPQ.Connection("host=192.168.88.12 port=5432 dbname=yiem_wine_assistant user=yiem password=yiem@Postgres_0.0")
|
|
||||||
|
|
||||||
tableinfo, df1, df2, df3 = SQLLLM.tableinfo(DBconnection, "wine")
|
|
||||||
|
|
||||||
|
|
||||||
_prompt =
|
|
||||||
"""
|
|
||||||
You are a helpful assistant helping to answer user question from a database table.
|
|
||||||
|
|
||||||
$tableinfo
|
|
||||||
|
|
||||||
Are there any chardonnay?
|
|
||||||
"""
|
|
||||||
|
|
||||||
prompt = YiemAgent.formatLLMtext_llama3instruct("system", _prompt)
|
|
||||||
@show prompt
|
|
||||||
msgMeta = Dict(:requestResponse => nothing,
|
|
||||||
:msgPurpose => nothing,
|
|
||||||
:receiverId => nothing,
|
|
||||||
:getPost => nothing,
|
|
||||||
:msgId => "4c7111e0-c30e-44c3-8f85-1c8b3f03a8be",
|
|
||||||
:acknowledgestatus => nothing,
|
|
||||||
:replyToMsgId => nothing,
|
|
||||||
:msgFormatVersion => nothing,
|
|
||||||
:mqttServerInfo => Dict(:port => 1883, :broker => "mqtt.yiem.cc"),
|
|
||||||
:sendTopic => "/loadbalancer/requestingservice",
|
|
||||||
:receiverName => "text2textinstruct",
|
|
||||||
:replyTopic => nothing,
|
|
||||||
:senderName => "decisionMaker",
|
|
||||||
:senderSelfnote => nothing,
|
|
||||||
:senderId => "testingSessionID",
|
|
||||||
:timeStamp => "2024-05-04T08:06:23.561"
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
|
||||||
:msgMeta=> msgMeta,
|
|
||||||
:payload=> Dict(
|
|
||||||
:text=> prompt,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
|
||||||
result2 = _response[:response][:text]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using Test
|
||||||
|
using YiemAgent
|
||||||
|
|
||||||
|
include("toolTest.jl")
|
||||||
|
include("_extractToolCalls.jl")
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
|
|
||||||
using JSON, Dates, UUIDs, PrettyPrinting, Base64, NATS, HTTP
|
|
||||||
using GeneralUtils, msghandler
|
|
||||||
|
|
||||||
config = JSON.parsefile("./appconfig.json")
|
|
||||||
|
|
||||||
agent_conn = NATS.connect(config["nats_server_info"]["url"])
|
|
||||||
|
|
||||||
function text2text_instruct_llm(sender_id::String, openai_msg::Dict{String, Any})
|
|
||||||
payloads = [("msg", openai_msg, "dictionary")] # List of tuples
|
|
||||||
_, msg_envelope_json_str = msghandler.smartpack(
|
|
||||||
config["externalservice"]["servicesloadbalancer"]["nats"],
|
|
||||||
payloads;
|
|
||||||
sender_id=sender_id,
|
|
||||||
msg_purpose="text2text",
|
|
||||||
broker_url=config["nats_server_info"]["url"],
|
|
||||||
fileserver_url=config["externalservice"]["fileserver"]["url"])
|
|
||||||
|
|
||||||
reply = NATS.request(agent_conn,
|
|
||||||
config["externalservice"]["servicesloadbalancer"]["nats"],
|
|
||||||
msg_envelope_json_str, timeout=120)
|
|
||||||
|
|
||||||
incoming_env_json_str = String(reply.payload)
|
|
||||||
incoming_env = msghandler.smartunpack(incoming_env_json_str)
|
|
||||||
_llm_response = incoming_env["payloads"][1][2]
|
|
||||||
llm_response = _llm_response["choices"][1]["message"]["content"]
|
|
||||||
return llm_response
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 1. Read local file and encode to base64 string
|
|
||||||
image1_path = "test/large_image.png"
|
|
||||||
image1_bytes = read(image1_path)
|
|
||||||
image1_base64_string = base64encode(image1_bytes)
|
|
||||||
|
|
||||||
# 2. Match the MIME type according to your file extension (e.g., png, jpeg)
|
|
||||||
mime_type = "image/png"
|
|
||||||
data1_uri = "data:$(mime_type);base64,$(image1_base64_string)"
|
|
||||||
|
|
||||||
# 3. Construct payload with the Data URI
|
|
||||||
openai_msg = Dict(
|
|
||||||
"model" => "gemma-4-E4B-it-UD-Q4_K_XL",
|
|
||||||
"messages" => [
|
|
||||||
Dict(
|
|
||||||
"role" => "user",
|
|
||||||
"content" => [
|
|
||||||
Dict("type" => "text", "text" => "Do you know this wine? Just give me brief intro."),
|
|
||||||
Dict(
|
|
||||||
"type" => "image_url",
|
|
||||||
"image_url" => Dict("url" => data1_uri)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
],
|
|
||||||
"temperature" => 0.7
|
|
||||||
)
|
|
||||||
|
|
||||||
llm_response = text2text_instruct_llm(openai_msg)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 1. Read local file and encode to base64 string
|
|
||||||
image2_path = "test/large_image.png"
|
|
||||||
image2_bytes = read(image2_path)
|
|
||||||
image2_base64_string = base64encode(image2_bytes)
|
|
||||||
|
|
||||||
# 2. Match the MIME type according to your file extension (e.g., png, jpeg)
|
|
||||||
mime_type = "image/png"
|
|
||||||
data2_uri = "data:$(mime_type);base64,$(image2_base64_string)"
|
|
||||||
|
|
||||||
systemmsg =
|
|
||||||
"""
|
|
||||||
# Store Policy
|
|
||||||
- Generally speaking, the store inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know exactly until you check your inventory.
|
|
||||||
- If you found wines in the store's database, they are in stock.
|
|
||||||
- You can only recommend wines that are currently in our inventory
|
|
||||||
- Before searching the database for wine, ensure you have at least the following information: 1) budget, 2) wine type, and 3) occasion. Additional details are always helpful. If the user is unsure, provide relevant information and gather insights to make reasonable inferences.
|
|
||||||
- Ask the user one question at a time.
|
|
||||||
- Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar as these terms cannot be used to search the database.
|
|
||||||
- Once the user has selected their wine, if you haven't already, ask the user whether they need any further assistance. Do not offer any additional services.
|
|
||||||
- Only end the conversation when the user explicitly intends to do so. When ending, ensure a polite farewell and an invitation to return in the future.
|
|
||||||
- Spicy foods should be paired only with light red wines.
|
|
||||||
- We do not sell organic, sustainable, gluten-free, and sulfite-free wine. Inform the user imediately if they are looking for these types of wines. Do not sell our wines as such.
|
|
||||||
- Gift box, gift card, and custom messages are available. Inform the user to contact our sales team.
|
|
||||||
|
|
||||||
# Store Guidelines
|
|
||||||
- Greeting the customer warmly by ask them how could you help. Do not ask any other questions during this greeting.
|
|
||||||
- Customer may provide images for you to look up.
|
|
||||||
- Encourage the customer to explore different options and try new things.
|
|
||||||
- If you are unable to locate the desired item in the database after 2 attempts, it may not be available in your inventory. In such cases, inform the user that the item is unavailable and suggest an alternative instead.
|
|
||||||
- Your store carries only wine.
|
|
||||||
- Vintage 0 means non-vintage.
|
|
||||||
- Start searching the database as broadly as possible within the given information boundary to maximize the chances of finding. Avoid unnecessary parameters unless specified by the user. Refine the search subsequently.
|
|
||||||
|
|
||||||
# Situation
|
|
||||||
Your customer is coming into the store
|
|
||||||
|
|
||||||
# Role
|
|
||||||
Your name is Janie. You are a helpful sommelier for website-based Yiem Wine's wine store. You are working under your mentor supervision.
|
|
||||||
|
|
||||||
# Objective
|
|
||||||
1. Establish a connection with the customer by talking to them politely and showing your enthusiasm for their wine preferences.
|
|
||||||
2. Provide relevant information and guide them to select the best wines only from your store's inventory that align with their preferences.
|
|
||||||
|
|
||||||
# Responsibility Includes
|
|
||||||
1. According to the store's policy and guidelines, make an informed decision about what you need to do to achieve the objective
|
|
||||||
2. Keep the conversation with the customer going smoothly
|
|
||||||
3. Obey your mentor's suggestions.
|
|
||||||
|
|
||||||
# Responsibility Does NOT Include
|
|
||||||
|
|
||||||
1. Requesting the user to place an order, make a purchase, or confirm the order. These are the job of our sales team at the store.
|
|
||||||
2. Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
|
|
||||||
3. Answering questions or offering additional services beyond those related to your store's wine recommendations such as discounts, quantity, rewards programs, promotions, delivery options, shipping, boxes, gift wrapping, packaging, personalized messages or something similar. These are the job of our sales team at the store.
|
|
||||||
|
|
||||||
# You should then respond to the user with interleaving plan, action_name, action_input
|
|
||||||
1) plan: Based on the current situation, state a complete action plan to complete the task and rationale. 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.
|
|
||||||
|
|
||||||
# You should only respond in JSON format as described below
|
|
||||||
"plan": "...",
|
|
||||||
"action_name": "...",
|
|
||||||
"action_input": "..."
|
|
||||||
|
|
||||||
# Available Actions
|
|
||||||
- **CHAT_BOX** which you can use to talk with the user.
|
|
||||||
- **CHECK_WINE** allows you to check information about wines you want in your inventory's database. The input is text that specify supported search criteria includeing: retailer_name, wine price, winery, name, vintage, region, country, type, grape varietal, tasting notes, occasion, food pairing, intensity, tannin, sweetness, and acidity.
|
|
||||||
- Example query 1: "Dry, full-bodied red wine from 1) region: Burgundy, country: France or 2) region: Tuscany, country: Italy. Grape varietal: Merlot or Syrah. price 100 to 1000 USD."
|
|
||||||
- Example query 2: "Red or white wine, medium tannin, price under 700 USD"
|
|
||||||
- Example query 3: "white wine, region: Tuscany or Bordeaux, country: Italy or France
|
|
||||||
- **PRESENT_WINE_GUIDELINE** which you can use to check the store guidelines about how to present wines you have found to the user. The input is "nothing" keyword. The output is the guidelines that you can follow.
|
|
||||||
- **END_CONVER_GUIDELINE** which you can use to check the store guidelines about how to end the conversation with the user. The input is "nothing" keyword. The output is the guidelines that you can follow.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
openai_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" => "Do you know this wine? Just give me brief intro."),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
Dict(
|
|
||||||
"role" => "assistant",
|
|
||||||
"content" => [
|
|
||||||
Dict("type" => "text", "text" =>
|
|
||||||
"""
|
|
||||||
" <plan>I will greet the customer warmly as Janie, acknowledge their request to find a similar wine for their wedding party based on the image, identify the wine type and country (Italian Sparkling Wine), and then use the CHECK_WINE action to search the inventory for suitable options.</plan>\n <action_name>CHAT_BOX</action_name>\n <action_input>Hello! I'm Janie, and I'd be delighted to help you find the perfect wine for your wedding party. That beautiful wine in the image appears to be an Italian sparkling wine, which is wonderful for a celebration like a wedding! Since you have an unlimited budget, I can certainly look for some truly exceptional options. To start, I will check our inventory for similar Italian sparkling wines that are perfect for a wedding celebration.</action_input><action_result> User response in the next message </action_result>"
|
|
||||||
"""
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
Dict(
|
|
||||||
"role" => "user",
|
|
||||||
"content" => [
|
|
||||||
Dict("type" => "text", "text" => "ok"),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
],
|
|
||||||
"temperature" => 0.7
|
|
||||||
)
|
|
||||||
|
|
||||||
llm_response = text2text_instruct_llm(openai_msg)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-291
@@ -1,291 +0,0 @@
|
|||||||
using JSON, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames, DataStructures, HTTP, Base64,
|
|
||||||
NATS, Base.Threads
|
|
||||||
using YiemAgent, GeneralUtils, msghandler
|
|
||||||
|
|
||||||
|
|
||||||
function text2text_instruct_llm(sender_id::String, openai_msg::Dict{String, Any})
|
|
||||||
payloads = [("msg", openai_msg, "dictionary")] # List of tuples
|
|
||||||
_, msg_envelope_json_str = msghandler.smartpack(
|
|
||||||
config["externalService"]["servicesloadbalancer"]["nats"],
|
|
||||||
payloads;
|
|
||||||
sender_id=sender_id,
|
|
||||||
msg_purpose="text2text",
|
|
||||||
broker_url=config["nats_server_info"]["url"],
|
|
||||||
fileserver_url=config["externalService"]["fileserver"]["url"])
|
|
||||||
|
|
||||||
reply = NATS.request(agent_conn,
|
|
||||||
config["externalService"]["servicesloadbalancer"]["nats"],
|
|
||||||
msg_envelope_json_str, timeout=120)
|
|
||||||
|
|
||||||
incoming_env_json_str = String(reply.payload)
|
|
||||||
incoming_env = msghandler.smartunpack(incoming_env_json_str)
|
|
||||||
_llm_response = incoming_env["payloads"][1][2]
|
|
||||||
llm_response = _llm_response["choices"][1]["message"]["content"]
|
|
||||||
return llm_response
|
|
||||||
end
|
|
||||||
|
|
||||||
#TESTING get text embedding from a LLM service
|
|
||||||
function get_embedding(text::AbstractArray{String})
|
|
||||||
documents_dict = Dict("documents" => text)
|
|
||||||
payloads = [("documents", documents_dict, "dictionary")]
|
|
||||||
_, msg_envelope_json_str = msghandler.smartpack(
|
|
||||||
config["externalService"]["servicesloadbalancer"]["nats"],
|
|
||||||
payloads;
|
|
||||||
msg_purpose="embedding",
|
|
||||||
broker_url=config["nats_server_info"]["url"],
|
|
||||||
fileserver_url=config["externalService"]["fileserver"]["url"])
|
|
||||||
|
|
||||||
reply = NATS.request(agent_conn,
|
|
||||||
config["externalService"]["servicesloadbalancer"]["nats"],
|
|
||||||
msg_envelope_json_str, timeout=120)
|
|
||||||
incoming_env_json_str = String(reply.payload)
|
|
||||||
incoming_env = msghandler.smartunpack(incoming_env_json_str)
|
|
||||||
embedding_response = incoming_env["payloads"][1][2]
|
|
||||||
|
|
||||||
return embedding_response
|
|
||||||
end
|
|
||||||
|
|
||||||
#TESTING
|
|
||||||
function execute_sql_winedb(config::JSON.Object, sql::T) where {T<:AbstractString}
|
|
||||||
host_url, _port = split(config["externalservice"]["sommpanion_db"]["url"], ':')
|
|
||||||
port = parse(Int, _port)
|
|
||||||
dbname = "winedb"
|
|
||||||
user = config["externalservice"]["sommpanion_db"]["user"]
|
|
||||||
password = config["externalservice"]["sommpanion_db"]["password"]
|
|
||||||
db_connection = LibPQ.Connection("host=$host_url port=$port dbname=$dbname user=$user password=$password")
|
|
||||||
result = LibPQ.execute(db_connection, sql)
|
|
||||||
LibPQ.close(db_connection)
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
#TESTING
|
|
||||||
function similar_sql_vectordb(query; maxdistance::Integer=100)
|
|
||||||
tablename = "sqlllm_decision_repository"
|
|
||||||
# get embedding of the query
|
|
||||||
df = find_similar_text_from_vectordb(query, tablename,
|
|
||||||
"function_input_embedding", execute_sql_vectordb)
|
|
||||||
# println(df[1, [:id, :function_output]])
|
|
||||||
row, col = size(df)
|
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
|
||||||
# distance = 100 # CHANGE this is for testing only
|
|
||||||
if row != 0 && distance < maxdistance
|
|
||||||
# if there is usable SQL, return it.
|
|
||||||
output_b64 = df[1, :function_output_base64] # pick the closest match
|
|
||||||
output_str = String(base64decode(output_b64))
|
|
||||||
rowid = df[1, :id]
|
|
||||||
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
return (dict=output_str, distance=distance)
|
|
||||||
else
|
|
||||||
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
return (dict=nothing, distance=nothing)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#TESTING
|
|
||||||
function insert_sql_vectordb(query::T1, SQL::T2; maxdistance::Integer=3) where {T1<:AbstractString, T2<:AbstractString}
|
|
||||||
tablename = "sqlllm_decision_repository"
|
|
||||||
# get embedding of the query
|
|
||||||
# query = state[:thoughtHistory][:question]
|
|
||||||
df = find_similar_text_from_vectordb(query, tablename,
|
|
||||||
"function_input_embedding", execute_sql_vectordb)
|
|
||||||
row, col = size(df)
|
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
|
||||||
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
|
||||||
_query_embedding = get_embedding([query])[1]
|
|
||||||
query_embedding = _query_embedding["data"][1]["embedding"]
|
|
||||||
query = replace(query, "'" => "")
|
|
||||||
sql_base64 = base64encode(SQL)
|
|
||||||
sql_ = replace(SQL, "'" => "")
|
|
||||||
|
|
||||||
sql = """
|
|
||||||
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
|
|
||||||
"""
|
|
||||||
# println("\n~~~ added new decision to vectorDB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
# println(sql)
|
|
||||||
_ = execute_sql_vectordb(sql)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#TESTING
|
|
||||||
function execute_sql_vectordb(sql::T) where {T<:AbstractString}
|
|
||||||
host_url, _port = split(config["SQLVectorDB"]["url"], ':')
|
|
||||||
port = parse(Int, _port)
|
|
||||||
dbname = config[:externalservice][:SQLVectorDB][:dbname]
|
|
||||||
user = config[:externalservice][:SQLVectorDB][:user]
|
|
||||||
password = config[:externalservice][:SQLVectorDB][:password]
|
|
||||||
DBconnection = LibPQ.Connection("host=$host_url port=$port dbname=$dbname user=$user password=$password")
|
|
||||||
result = LibPQ.execute(DBconnection, sql)
|
|
||||||
close(DBconnection)
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function similar_sommelier_decision(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 = find_similar_text_from_vectordb(recentevents, tablename,
|
|
||||||
"function_input_embedding", execute_sql_vectordb)
|
|
||||||
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.read(_output_str))
|
|
||||||
return output
|
|
||||||
else
|
|
||||||
println("\n~~~ similar decision not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
|
|
||||||
return nothing
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#TESTING
|
|
||||||
function find_similar_text_from_vectordb(text::T1, tablename::T2, embeddingColumnName::T3,
|
|
||||||
vectorDB::Function; limit::Integer=1
|
|
||||||
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
|
|
||||||
# get embedding from LLM service
|
|
||||||
_embedding = get_embedding([text])[1]
|
|
||||||
embedding = _embedding["data"][1]["embedding"]
|
|
||||||
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
|
|
||||||
sql = """
|
|
||||||
SELECT *, $embeddingColumnName <-> '$embedding' as distance
|
|
||||||
FROM $tablename
|
|
||||||
ORDER BY distance LIMIT $limit;
|
|
||||||
"""
|
|
||||||
response = vectorDB(sql)
|
|
||||||
df = DataFrame(response)
|
|
||||||
return df
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function insert_sommelier_decision(recentevents::T1, decision::T2; maxdistance::Integer=5
|
|
||||||
) where {T1<:AbstractString, T2<:AbstractDict}
|
|
||||||
tablename = "sommelier_decision_repository"
|
|
||||||
# find similar
|
|
||||||
df = find_similar_text_from_vectordb(recentevents, tablename,
|
|
||||||
"function_input_embedding", execute_sql_vectordb)
|
|
||||||
row, col = size(df)
|
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
|
||||||
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
|
||||||
_embedding = get_embedding([recentevents])[1]
|
|
||||||
recentevents_embedding = _embedding["data"][1]["embedding"]
|
|
||||||
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)
|
|
||||||
_ = execute_sql_vectordb(sql)
|
|
||||||
else
|
|
||||||
println("~~~ similar decision previously cached, distance $distance ", @__FILE__, " ", @__LINE__)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
sessionId = "0"
|
|
||||||
backend_session_topic = "sommpanion.backend.agentbackend.v1.inbox.$sessionId"
|
|
||||||
|
|
||||||
config = JSON.parsefile("./dummy_config.json")
|
|
||||||
agent_ch = Channel(8)
|
|
||||||
agent_conn = NATS.connect(config["nats_server_info"]["url"])
|
|
||||||
sub2 = NATS.subscribe(agent_conn, backend_session_topic) do msg
|
|
||||||
put!(agent_ch, msg)
|
|
||||||
end
|
|
||||||
|
|
||||||
agent_context = YiemAgent.agentcontext(
|
|
||||||
text2text_instruct_llm,
|
|
||||||
get_embedding,
|
|
||||||
execute_sql_winedb,
|
|
||||||
similar_sql_vectordb,
|
|
||||||
insert_sql_vectordb,
|
|
||||||
similar_sommelier_decision,
|
|
||||||
insert_sommelier_decision
|
|
||||||
)
|
|
||||||
|
|
||||||
# can't instantiate
|
|
||||||
agent = YiemAgent.sommelier(
|
|
||||||
agent_context;
|
|
||||||
name="Janie",
|
|
||||||
id=sessionId, # agent instance id
|
|
||||||
retailername="Yiem",
|
|
||||||
llmFormatName=""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 1. Read local file and encode to base64 string
|
|
||||||
image1_path = "test/large_image.png"
|
|
||||||
image1_bytes = read(image1_path)
|
|
||||||
image1_base64_string = base64encode(image1_bytes)
|
|
||||||
|
|
||||||
# 2. Match the MIME type according to your file extension (e.g., png, jpeg)
|
|
||||||
mime_type = "image/png"
|
|
||||||
data1_uri = "data:$(mime_type);base64,$(image1_base64_string)"
|
|
||||||
|
|
||||||
# 3. Construct payload with the Data URI
|
|
||||||
usermsg = Dict{String, Any}(
|
|
||||||
"role" => "user",
|
|
||||||
"content" => [
|
|
||||||
Dict("type" => "text", "text" => "รู้จักไวน์ที่อยู่ในรูปมั้ย"),
|
|
||||||
Dict(
|
|
||||||
"type" => "image_url",
|
|
||||||
"image_url" => Dict("url" => data1_uri)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
result = YiemAgent.conversation(agent; userinput=usermsg)
|
|
||||||
println(result)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user