Compare commits

..

81 Commits

Author SHA1 Message Date
ton 6e53f57707 Merge pull request 'v0.7.4' (#39) from v0.7.4 into main
Reviewed-on: #39
2026-07-28 00:16:48 +00:00
ton 1eea6c66b6 Merge pull request 'update' (#38) from v0.7.4-add_vector_search into v0.7.4
Reviewed-on: #38
2026-07-27 10:43:41 +00:00
ton 37ff68c963 update 2026-07-27 17:43:11 +07:00
ton e5fc800c83 Merge pull request 'update' (#37) from v0.7.4-add_vector_search into v0.7.4
Reviewed-on: #37
2026-07-27 09:55:11 +00:00
ton 4397ec5fb3 update 2026-07-27 16:54:36 +07:00
ton 9e3f5a0967 Merge pull request 'v0.7.4' (#36) from v0.7.4 into main
Reviewed-on: #36
2026-07-27 02:48:28 +00:00
ton 30b79a3b34 Merge pull request 'fix tool name' (#35) from v0.7.4-add_vector_search into v0.7.4
Reviewed-on: #35
2026-07-27 02:47:43 +00:00
ton e5b8518c00 fix tool name 2026-07-27 09:47:07 +07:00
ton d115e60ddf Merge pull request 'update' (#34) from v0.7.4-add_vector_search into v0.7.4
Reviewed-on: #34
2026-07-26 16:02:35 +00:00
ton e0e6aced33 update 2026-07-26 23:01:51 +07:00
ton c803238f86 Merge pull request 'v0.7.4-add_vector_search' (#33) from v0.7.4-add_vector_search into v0.7.4
Reviewed-on: #33
2026-07-26 15:42:34 +00:00
ton 4d6cdbcf4a update 2026-07-26 22:42:09 +07:00
ton 206c0d2c62 Merge pull request 'v0.7.4-add_vector_search' (#32) from v0.7.4-add_vector_search into main
Reviewed-on: #32
2026-07-26 15:21:53 +00:00
ton 597319a147 update 2026-07-26 22:21:10 +07:00
ton abfe6f45fb update 2026-07-25 10:13:02 +07:00
ton f8b3150c17 update 2026-07-24 16:44:49 +07:00
ton caed2a82d6 update 2026-07-24 15:51:32 +07:00
ton 7ec3edfd77 update 2026-07-24 08:31:36 +07:00
ton d7adfaffa8 update 2026-07-23 21:14:45 +07:00
ton 5bde0ca1f2 Merge pull request 'update' (#31) from v0.7.4-use_yaml_format into v0.7.4
Reviewed-on: #31
2026-07-22 03:20:13 +00:00
ton a3ab288cfe update 2026-07-22 10:18:28 +07:00
ton 17b0974d82 Merge pull request 'v0.7.4-predefine_wine_search' (#30) from v0.7.4-predefine_wine_search into v0.7.4
Reviewed-on: #30
2026-07-21 13:17:25 +00:00
ton 8fed0b5e8f update 2026-07-21 20:11:12 +07:00
ton 567d6b79d7 up version 2026-07-21 18:02:02 +07:00
ton 74be3e5717 Merge pull request 'v0.7.3' (#29) from v0.7.3 into main
Reviewed-on: #29
2026-07-20 05:26:19 +00:00
ton 402b6fcadd Merge pull request 'update' (#28) from v0.7.3-fix_item_info into v0.7.3
Reviewed-on: #28
2026-07-20 05:24:33 +00:00
ton ef523aaa48 update 2026-07-20 12:23:03 +07:00
ton ddbb135b6b update 2026-07-17 12:22:56 +07:00
ton afda364484 update 2026-07-17 12:03:36 +07:00
ton af73d955eb Merge pull request 'v0.7.1' (#27) from v0.7.1 into main
Reviewed-on: #27
2026-07-17 03:28:17 +00:00
ton 39cf9a72a1 Merge pull request 'v0.7.1-fix_single_items_info_frontend' (#26) from v0.7.1-fix_single_items_info_frontend into v0.7.1
Reviewed-on: #26
2026-07-17 03:28:06 +00:00
ton 1c829ad854 update 2026-07-17 10:14:13 +07:00
ton da98baddb6 update 2026-07-17 10:06:32 +07:00
ton c5fbaabf42 Merge pull request 'v0.7.0' (#25) from v0.7.0 into main
Reviewed-on: #25
2026-07-17 00:03:01 +00:00
ton 8080905bad Merge pull request 'v0.7.0-output_openai_msg' (#24) from v0.7.0-output_openai_msg into v0.7.0
Reviewed-on: #24
2026-07-17 00:02:47 +00:00
ton b349c3a8b6 update 2026-07-17 07:01:55 +07:00
ton 0148e03d6a update 2026-07-16 23:31:36 +07:00
ton e718cc4a5c update 2026-07-16 23:13:18 +07:00
ton 87bc6a46a1 update 2026-07-16 22:31:40 +07:00
ton 44bb8baf7c Merge pull request 'update' (#23) from v0.6.0-output_text_image into main
Reviewed-on: #23
2026-07-15 11:51:43 +00:00
ton 18b2d54ba7 update 2026-07-15 18:51:21 +07:00
ton b3c3bb9b75 Merge pull request 'update' (#22) from v0.6.0-output_text_image into main
Reviewed-on: #22
2026-07-15 11:50:09 +00:00
ton d004193b19 update 2026-07-15 18:49:51 +07:00
ton 8898226825 Merge pull request 'v0.6.0-output_text_image' (#21) from v0.6.0-output_text_image into main
Reviewed-on: #21
2026-07-15 11:48:20 +00:00
ton 686b9b2e92 update 2026-07-15 18:47:18 +07:00
ton 3acf46964b update 2026-07-15 14:25:04 +07:00
ton 5c7caf0b49 Merge pull request 'update' (#20) from v0.6.0-output_text_image into main
Reviewed-on: #20
2026-07-15 07:01:48 +00:00
ton ad917ea8d0 update 2026-07-15 14:01:32 +07:00
ton edeef4ed2a Merge pull request 'v0.6.0-output_text_image' (#19) from v0.6.0-output_text_image into main
Reviewed-on: #19
2026-07-15 06:59:56 +00:00
ton 31daa805f3 update 2026-07-15 13:59:28 +07:00
ton c9937ab5d7 update 2026-07-15 13:59:01 +07:00
ton 4610137f04 Merge pull request 'update' (#18) from v0.6.0-output_text_image into main
Reviewed-on: #18
2026-07-15 05:20:58 +00:00
ton 9d7eed7cde update 2026-07-15 12:20:44 +07:00
ton aedc53bf86 Merge pull request 'update' (#17) from v0.6.0-output_text_image into main
Reviewed-on: #17
2026-07-15 05:17:56 +00:00
ton 286da3cf2c update 2026-07-15 12:16:59 +07:00
ton 7fa988313d Merge pull request 'update' (#16) from v0.6.0-output_text_image into main
Reviewed-on: #16
2026-07-15 05:15:31 +00:00
ton e5b19dd268 update 2026-07-15 12:14:32 +07:00
ton 0df4159261 Merge pull request 'update' (#15) from v0.6.0-output_text_image into main
Reviewed-on: #15
2026-07-15 05:11:05 +00:00
ton 45e8ded111 update 2026-07-15 12:10:36 +07:00
ton 9167ece0c0 Merge pull request 'v0.6.0' (#14) from v0.6.0 into main
Reviewed-on: #14
2026-07-15 04:57:47 +00:00
ton a6a9395ecc Merge pull request 'update' (#13) from v0.6.0-output_text_image into v0.6.0
Reviewed-on: #13
2026-07-15 04:57:11 +00:00
ton a503d4d759 update 2026-07-15 11:56:44 +07:00
ton f45a036971 Merge pull request 'v0.6.0' (#12) from v0.6.0 into main
Reviewed-on: #12
2026-07-15 04:38:48 +00:00
ton 24b85be58b Merge pull request 'updatet' (#11) from v0.6.0-output_text_image into v0.6.0
Reviewed-on: #11
2026-07-15 04:38:18 +00:00
ton a798cd119e updatet 2026-07-14 18:08:36 +07:00
ton fa338dd0f8 Merge pull request 'v0.5.0' (#10) from v0.5.0 into main
Reviewed-on: #10
2026-07-12 08:03:41 +00:00
ton 8d4bf5f01f Merge pull request 'v0.5.0-tool_role' (#9) from v0.5.0-tool_role into v0.5.0
Reviewed-on: #9
2026-07-12 08:03:26 +00:00
ton cd6f6ef961 update 2026-07-12 11:14:30 +07:00
ton fdec34832d update 2026-07-12 10:54:15 +07:00
ton 3c72373b85 update 2026-07-12 05:54:47 +07:00
ton 688a8c4df2 update 2026-07-11 21:45:49 +07:00
ton 8bd4986be2 Merge pull request 'v0.4.3' (#8) from v0.4.3 into main
Reviewed-on: #8
2026-07-10 10:47:34 +00:00
ton 6e5809fc9b Merge pull request 'v0.4.3-fix_markdown_response' (#7) from v0.4.3-fix_markdown_response into v0.4.3
Reviewed-on: #7
2026-07-10 10:47:18 +00:00
ton 2b7c0041e5 up version 2026-07-10 17:46:55 +07:00
ton 9ff0b48eec update 2026-07-10 17:45:58 +07:00
ton afeb4c7aef Merge pull request 'v0.4.2' (#6) from v0.4.2 into main
Reviewed-on: #6
2026-07-09 13:20:36 +00:00
ton 2942a89730 Merge pull request 'v0.4.2-limit_consecutive_same_tool_use' (#5) from v0.4.2-limit_consecutive_same_tool_use into v0.4.2
Reviewed-on: #5
2026-07-09 13:20:21 +00:00
ton 6a66f58e63 update 2026-07-09 20:19:41 +07:00
ton 1d0353d793 update 2026-07-09 20:14:04 +07:00
ton 0f6aa7c79f update 2026-07-09 19:45:04 +07:00
ton 0320fd321f Merge pull request 'v0.4.1' (#4) from v0.4.1 into main
Reviewed-on: #4
2026-07-09 01:01:53 +00:00
12 changed files with 1772 additions and 1265 deletions
+130 -24
View File
@@ -2,7 +2,7 @@
julia_version = "1.12.6" julia_version = "1.12.6"
manifest_format = "2.0" manifest_format = "2.0"
project_hash = "09bd5c43d6ad954d8be233d27fc343ea1149c0b0" project_hash = "1c1379a2cec320abc347f3acb5ee815ba9855aa6"
[[deps.Accessors]] [[deps.Accessors]]
deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"] deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
@@ -38,6 +38,12 @@ 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.ArrowTypes]] [[deps.ArrowTypes]]
deps = ["Sockets", "UUIDs"] deps = ["Sockets", "UUIDs"]
git-tree-sha1 = "404265cd8128a2515a81d5eae16de90fdef05101" git-tree-sha1 = "404265cd8128a2515a81d5eae16de90fdef05101"
@@ -91,9 +97,9 @@ uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.8" version = "0.7.8"
[[deps.CommonSolve]] [[deps.CommonSolve]]
git-tree-sha1 = "99ee296f88c12485402e37c2fd025f95ae097637" git-tree-sha1 = "eeaad7cef88554c2fa56b5a3f71cfd5cb708c662"
uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
version = "0.2.9" version = "0.2.11"
[[deps.Compat]] [[deps.Compat]]
deps = ["TOML", "UUIDs"] deps = ["TOML", "UUIDs"]
@@ -181,6 +187,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"
@@ -224,11 +244,21 @@ git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec"
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
version = "0.1.10" version = "0.1.10"
[[deps.EzXML]]
deps = ["Printf", "XML2_jll"]
git-tree-sha1 = "7ea1aa5869e2626ccae84480e4f37185bc6f41d3"
uuid = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615"
version = "1.2.3"
[[deps.FileIO]] [[deps.FileIO]]
deps = ["Pkg", "Requires", "UUIDs"] deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "91e0e5c68d02bcdaae76d3c8ceb4361e8f28d2e9" git-tree-sha1 = "6621fef488e496356c9c9625d0562c12a6070819"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.16.5" version = "1.20.0"
weakdeps = ["HTTP"]
[deps.FileIO.extensions]
HTTPExt = "HTTP"
[[deps.FilePathsBase]] [[deps.FilePathsBase]]
deps = ["Compat", "Dates"] deps = ["Compat", "Dates"]
@@ -250,6 +280,7 @@ deps = ["LinearAlgebra"]
git-tree-sha1 = "2f979084d1e13948a3352cf64a25df6bd3b4dca3" git-tree-sha1 = "2f979084d1e13948a3352cf64a25df6bd3b4dca3"
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
version = "1.16.0" version = "1.16.0"
weakdeps = ["PDMats", "SparseArrays", "StaticArrays", "Statistics"]
[deps.FillArrays.extensions] [deps.FillArrays.extensions]
FillArraysPDMatsExt = "PDMats" FillArraysPDMatsExt = "PDMats"
@@ -257,12 +288,6 @@ 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"
@@ -274,18 +299,31 @@ uuid = "a0844989-3bd2-4988-8bea-c9407ab0941b"
version = "1.1.0" version = "1.1.0"
[[deps.GeneralUtils]] [[deps.GeneralUtils]]
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "HTTP", "JSON", "NATS", "PrettyPrinting", "Random", "Revise", "SHA", "UUIDs"] deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "Graphs", "HTTP", "JSON", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SHA", "StringDistances", "UUIDs"]
git-tree-sha1 = "7c0600c166a5deb2c607018a491c04eb25969c2e" git-tree-sha1 = "93293126d24d3929ef6a5067f347bc28c6582c71"
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.5.10"
[[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 = "c2c808326222b6dc4bec295a83b55f79aeec98e0"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "2.5.4" version = "2.5.5"
[[deps.HashArrayMappedTries]] [[deps.HashArrayMappedTries]]
git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae" git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae"
@@ -310,6 +348,11 @@ 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.InlineStrings]] [[deps.InlineStrings]]
git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d" git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d"
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
@@ -463,6 +506,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"
@@ -645,15 +694,17 @@ version = "0.4.2"
[[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 = "ebf455bb866ee6737030e3d3816bb6a0683c4325"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "3.3.2" version = "3.4.0"
[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"]
@@ -734,9 +785,9 @@ version = "0.5.1+0"
[[deps.Roots]] [[deps.Roots]]
deps = ["Accessors", "CommonSolve", "Printf"] deps = ["Accessors", "CommonSolve", "Printf"]
git-tree-sha1 = "ed45bcc7cf3c8887595b973f2b1efbe91dcc50ec" git-tree-sha1 = "a7caaf7ba8cf307112ca443784d1b56b4a591455"
uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665" uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
version = "3.0.1" version = "3.0.5"
[deps.Roots.extensions] [deps.Roots.extensions]
RootsChainRulesCoreExt = "ChainRulesCore" RootsChainRulesCoreExt = "ChainRulesCore"
@@ -760,11 +811,11 @@ version = "0.7.0"
[[deps.SQLLLM]] [[deps.SQLLLM]]
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "PrettyPrinting", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"] deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "PrettyPrinting", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"]
git-tree-sha1 = "93cc1ae6202279a2eb4e1dbfff706c5bc158609d" git-tree-sha1 = "bae2fd2e2b087753fbb3415896be41df1ae0eb90"
repo-rev = "main" repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/SQLLLM" repo-url = "https://git.yiem.cc/ton/SQLLLM"
uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3" uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
version = "0.2.5" version = "0.2.8"
[[deps.SQLStrings]] [[deps.SQLStrings]]
git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c" git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c"
@@ -789,10 +840,22 @@ git-tree-sha1 = "084c47c7c5ce5cfecefa0a98dff69eb3646b5a80"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.4.10" version = "1.4.10"
[[deps.Serde]]
deps = ["CSV", "Dates", "EzXML", "JSON", "TOML", "UUIDs", "YAML"]
git-tree-sha1 = "f397fc8779cc53e4677c2708f3802c6996f28d00"
uuid = "db9b398d-9517-45f8-9a95-92af99003e0e"
version = "3.7.2"
[[deps.Serialization]] [[deps.Serialization]]
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"
@@ -826,6 +889,25 @@ 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 = "246a8bb2e6667f832eea063c3a56aef96429a3db"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.9.18"
[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 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0"
@@ -862,6 +944,18 @@ 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 = "cd83a04baf746e3b43b83c61b7de77ab0409b80a"
uuid = "88034a9c-02f8-509d-84a9-84ec65e18404"
version = "1.0.0"
[[deps.StringEncodings]]
deps = ["Libiconv_jll"]
git-tree-sha1 = "b765e46ba27ecf6b44faf70df40c57aa3a547dcb"
uuid = "69024149-9ee7-55f6-a4c4-859efe599b68"
version = "0.3.7"
[[deps.StringManipulation]] [[deps.StringManipulation]]
deps = ["PrecompileTools"] deps = ["PrecompileTools"]
git-tree-sha1 = "d05693d339e37d6ab134c5ab53c29fce5ee5d7d5" git-tree-sha1 = "d05693d339e37d6ab134c5ab53c29fce5ee5d7d5"
@@ -982,11 +1076,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.YAML]]
deps = ["Base64", "Dates", "Printf", "StringEncodings"]
git-tree-sha1 = "a1c0c7585346251353cddede21f180b96388c403"
uuid = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
version = "0.4.16"
[[deps.YiemAgent]] [[deps.YiemAgent]]
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "GeneralUtils", "HTTP", "JSON", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serialization", "URIs", "UUIDs"] deps = ["Base64", "CSV", "DataFrames", "DataStructures", "Dates", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serde", "Serialization", "URIs", "UUIDs"]
path = "." path = "."
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2" uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
version = "0.4.0" version = "0.7.4"
[[deps.Zlib_jll]] [[deps.Zlib_jll]]
deps = ["Libdl"] deps = ["Libdl"]
+7 -3
View File
@@ -1,9 +1,10 @@
name = "YiemAgent" name = "YiemAgent"
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2" uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
version = "0.4.1" version = "0.7.4"
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"
@@ -18,16 +19,19 @@ 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" SQLLLM = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
Serde = "db9b398d-9517-45f8-9a95-92af99003e0e"
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"
[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.5.10"
HTTP = "2.4.0" HTTP = "2.4.0"
JSON = "1.6.1" JSON = "1.6.1"
LLMMCTS = "0.1.5" LLMMCTS = "0.1.5"
NATS = "0.1.0" NATS = "0.1.0"
SQLLLM = "0.2.5" SQLLLM = "0.2.8"
Serde = "3.7.2"
+1 -1
View File
@@ -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 -5
View File
@@ -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",
+10 -13
View File
@@ -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__
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

+328 -572
View File
File diff suppressed because it is too large Load Diff
+955 -349
View File
File diff suppressed because it is too large Load Diff
+20 -93
View File
@@ -16,83 +16,18 @@ mutable struct agentcontext
insertSQLVectorDB::Function insertSQLVectorDB::Function
similarSommelierDecision::Function similarSommelierDecision::Function
insertSommelierDecision::Function insertSommelierDecision::Function
find_related_tables_for_user_question::Function
pg_conn_str::String
agentconfig::AbstractDict
end end
abstract type agent end abstract type agent end
mutable struct companion <: agent
name::String # agent name
id::String # agent id
systemmsg::String # system message
tools::Dict # tools
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
chathistory::Vector{Dict{String, Any}}
memory::Dict{String, Any}
context::NamedTuple # NamedTuple of functions
llmFormatName::String
end
function companion(
context::agentcontext # NamedTuple of functions
;
name::String= "Assistant",
id::String= GeneralUtils.uuid4snakecase(),
maxHistoryMsg::Integer= 20,
chathistory::Vector{Dict{String, String}} = Vector{Dict{String, String}}(),
llmFormatName::String= "granite3",
systemmsg::String=
"""
Your name: $name
Your sex: Female
Your role: You are a helpful assistant.
You should follow the following guidelines:
- Focus on the latest conversation.
- Your like to be short and concise.
Let's begin!
""",
)
tools = Dict( # update input format
"CHAT_BOX"=> Dict(
"description" => "- CHAT_BOX which you can use to talk with the user. The input is your intentions for the dialogue. Be specific.",
),
)
""" Memory
Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
NO "system" message in chathistory because I want to add it at the inference time
chathistory= [
Dict("name"=>"user", "text"=> "Wassup!", "timestamp"=> Dates.now()),
Dict("name"=>"assistant", "text"=> "Hi I'm your assistant.", "timestamp"=> Dates.now()),
]
"""
memory = Dict{String, Any}(
"events"=> Vector{Dict{String, Any}}(),
"state"=> Dict{String, Any}(), # state of the agent
"recap"=> OrderedDict{String, Any}(), # recap summary of the conversation
)
newAgent = companion(
name,
id,
systemmsg,
tools,
maxHistoryMsg,
chathistory,
memory,
context,
llmFormatName
)
return newAgent
end
mutable struct sommelier <: agent mutable struct sommelier <: agent
name::String # agent name name::String # agent name
id::String # agent id id::String # agent id
retailername::String retailername::String
retailerid::String
tools::Dict tools::Dict
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
chathistory::Vector{Dict{String, Any}} chathistory::Vector{Dict{String, Any}}
@@ -140,11 +75,12 @@ julia> agent = sommelier(context, name="WineExpert", id="123", retailername="MyW
``` ```
""" """
function sommelier( function sommelier(
context::agentcontext, # app context context::agentcontext, # agent functions, db connect and other context
; ;
name::String= "Assistant", name::String= "Assistant",
id::String= string(uuid4()), id::String= string(uuid4()),
retailername::String= "retailer_name", retailername::String= "not specified",
retailerid::String= "not specified",
maxHistoryMsg::Integer= 20, maxHistoryMsg::Integer= 20,
chathistory::Vector{Dict{String, Any}} = Vector{Dict{String, Any}}(), chathistory::Vector{Dict{String, Any}} = Vector{Dict{String, Any}}(),
llmFormatName::String= "granite3" llmFormatName::String= "granite3"
@@ -205,17 +141,14 @@ function sommelier(
memory = Dict{String, Any}( memory = Dict{String, Any}(
"shortmem"=> OrderedDict{String, Any}(), "shortmem"=> OrderedDict{String, Any}(),
"scratchpad"=> "", "scratchpad"=> "",
"events"=> Vector{Dict{String, Any}}(),
"state"=> Dict{String, Any}(
),
"recap"=> OrderedDict{String, Any}(), "recap"=> OrderedDict{String, Any}(),
) )
newAgent = sommelier( newAgent = sommelier(
name, name,
id, id,
retailername, retailername,
retailerid,
tools, tools,
maxHistoryMsg, maxHistoryMsg,
chathistory, chathistory,
@@ -231,7 +164,6 @@ function sommelier(
- You can only recommend wines that are currently in our inventory - 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. - 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. - 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. - 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. - 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. - Spicy foods should be paired only with light red wines.
@@ -246,6 +178,7 @@ function sommelier(
- Your store carries only wine. - Your store carries only wine.
- Vintage 0 means non-vintage. - 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. - 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.
- User usually ask for something similar. This means you should use the search term based on the profile they like.
# situation # situation
You are having conversation with a customer. You are having conversation with a customer.
@@ -266,26 +199,20 @@ function sommelier(
- Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store. - Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
- 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. - 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 # you should then respond to the user with interleaving think, action_name, action_input in JSON format
1) **plan**, Based on the current situation, state a complete action plan to complete the task and rationale. Be specific. 1) "think", Your step-by-step reasoning process. Explain why you are choosing this action.
2) **action_name**, (Typically corresponds to the execution of the first step in your plan) Can be one of the available_actions name 2) "action_name", Can be one of the available actions. Typically corresponds to the execution of the first step in your thought
3) **action_input**, The input to the action you are about to perform according to your plan. 3) "action_input", The input to the action you are about to perform.
After the action is executed you gets "action_result". It is the output from the action you selected. 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 # available actions
**CHAT_BOX**, which you can use to talk with the user. The input is dialogue you want to chat with the user according to your plan. "CHAT_BOX", which you can use to talk with the user. The input is dialogue you want to say 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 search information about wines you want in your inventory's database. The input is strictly supported search term including: retailer_name, wine price, winery, name, vintage, region, country, type of wine, 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 Burgundy, France. Grape varietal could be 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 from Tuscany, Italy or Bordeaux, France
**WINE_PRESENTATION_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. "WINE_PRESENTATION_GUIDELINE", store guidelines about how to present wines to the user appropriately. The input is "null" keyword.
**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. "END_CONVER_GUIDELINE", store guidelines about how to end the conversation with the user appropriately. The input is "null" keyword.
""" """
system_msg = Dict( system_msg = Dict(
@@ -299,7 +226,7 @@ function sommelier(
return newAgent return newAgent
end end
mutable struct virtualcustomer <: agent mutable struct virtualcustomer <: agent
name::String # agent name name::String # agent name
+5 -5
View File
@@ -95,15 +95,15 @@ end
""" """
function addNewMessage(a::T1, name::String, userinput::T2; function addNewMessage(a::T1, name::String, userinput::T2;
maximumMsg::Integer=30) where {T1<:agent, T2<:AbstractDict} maximumMsg::Integer=30) where {T1<:agent, T2<:AbstractDict}
if name ["system", "user", "assistant"] # guard against typo # if name ∉ ["system", "user", "assistant"] # guard against typo
error("name is not in agent.availableRole $(@__LINE__)") # error("name is not in agent.availableRole $(@__LINE__)")
end # end
#TODO summarize the oldest 10 message #TODO summarize the oldest 10 message
if length(a.chathistory) > maximumMsg if length(a.chathistory) > maximumMsg
summarize(a.chathistory) summarize(a.chathistory)
else else
userinput["timestamp"] = Dates.now() # userinput["timestamp"] = Dates.now()
push!(a.chathistory, userinput) push!(a.chathistory, userinput)
end end
end end
@@ -297,7 +297,7 @@ function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothin
# timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"])\n" # timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"])\n"
# elseif event["action_name"] == "CHECKINVENTORY" && event["observation"] === nothing # 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" # 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" if event["action_name"] == "SEARCH_WINE_DATABASE"
timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"]), observation: $(event["observation"])\\n" timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"]), observation: $(event["observation"])\\n"
else else
timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"])\\n" timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"])\\n"
+2 -2
View File
@@ -128,7 +128,7 @@ systemmsg =
# 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
@@ -157,7 +157,7 @@ openai_msg = Dict(
"content" => [ "content" => [
Dict("type" => "text", "text" => 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>" " <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 SEARCH_WINE_DATABASE 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>"
""" """
), ),
] ]
+308 -198
View File
@@ -2,200 +2,236 @@ using JSON, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames, DataStructu
NATS, Base.Threads NATS, Base.Threads
using YiemAgent, GeneralUtils, msghandler 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"])
function text2text_instruct_llm(sender_id::String, openai_msg::Dict{String, Any}) reply = NATS.request(agent_conn,
payloads = [("msg", openai_msg, "dictionary")] # List of tuples config["externalservice"]["servicesloadbalancer"]["nats"],
_, msg_envelope_json_str = msghandler.smartpack( msg_envelope_json_str, timeout=120)
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, incoming_env_json_str = String(reply.payload)
config["externalService"]["servicesloadbalancer"]["nats"], incoming_env = msghandler.smartunpack(incoming_env_json_str)
msg_envelope_json_str, timeout=120) _llm_response = incoming_env["payloads"][1][2]
llm_response = _llm_response["choices"][1]["message"]["content"]
incoming_env_json_str = String(reply.payload) return llm_response
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
end
#TESTING """ get a single text embedding from a LLM service
function insert_sql_vectordb(query::T1, SQL::T2; maxdistance::Integer=3) where {T1<:AbstractString, T2<:AbstractString} Example
tablename = "sqlllm_decision_repository" text = ["hello"]
# get embedding of the query embedding = get_embedding(text)
# query = state[:thoughtHistory][:question] """
df = find_similar_text_from_vectordb(query, tablename, function get_embedding(text::AbstractArray{String})
"function_input_embedding", execute_sql_vectordb) documents_dict = Dict("documents" => text)
row, col = size(df) payloads = [("documents", documents_dict, "dictionary")]
distance = row == 0 ? Inf : df[1, :distance] _, msg_envelope_json_str = msghandler.smartpack(
if row == 0 || distance > maxdistance # no close enough SQL stored in the database config["externalservice"]["servicesloadbalancer"]["nats"],
_query_embedding = get_embedding([query])[1] payloads;
query_embedding = _query_embedding["data"][1]["embedding"] msg_purpose="embedding",
query = replace(query, "'" => "") broker_url=config["nats_server_info"]["url"],
sql_base64 = base64encode(SQL) fileserver_url=config["externalservice"]["fileserver"]["url"])
sql_ = replace(SQL, "'" => "")
sql = """ reply = NATS.request(agent_conn,
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding'); config["externalservice"]["servicesloadbalancer"]["nats"],
""" msg_envelope_json_str, timeout=120)
# println("\n~~~ added new decision to vectorDB ", @__FILE__, ":", @__LINE__, " $(Dates.now())") incoming_env_json_str = String(reply.payload)
# println(sql) incoming_env = msghandler.smartunpack(incoming_env_json_str)
_ = execute_sql_vectordb(sql) embedding_response = incoming_env["payloads"][1][2]
return embedding_response
end end
end
#TESTING """ sql = "SELECT * FROM wine;"
function execute_sql_vectordb(sql::T) where {T<:AbstractString} result = execute_sql_winedb(sql)
host_url, _port = split(config["SQLVectorDB"]["url"], ':') """
port = parse(Int, _port) function execute_sql_winedb(sql::T) where {T<:AbstractString}
dbname = config[:externalservice][:SQLVectorDB][:dbname] host_url, _port = split(config["externalservice"]["sommpanion_db"]["url"], ':')
user = config[:externalservice][:SQLVectorDB][:user] port = parse(Int, _port)
password = config[:externalservice][:SQLVectorDB][:password] dbname = "winedb"
DBconnection = LibPQ.Connection("host=$host_url port=$port dbname=$dbname user=$user password=$password") user = config["externalservice"]["sommpanion_db"]["user"]
result = LibPQ.execute(DBconnection, sql) password = config["externalservice"]["sommpanion_db"]["password"]
close(DBconnection) db_connection = LibPQ.Connection("host=$host_url port=$port dbname=$dbname user=$user password=$password")
return result result = nothing
end try
result = LibPQ.execute(db_connection, sql)
catch e
function similar_sommelier_decision(recentevents::T1; maxdistance::Integer=3 LibPQ.close(db_connection)
)::Union{AbstractDict, Nothing} where {T1<:AbstractString} end
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 = LibPQ.close(db_connection)
""" return result
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
end
""" find similar sql from vector database
sql = "SELECT * FROM wine;"
result, distance = similar_sql_vectordb(sql)
"""
function similar_sql_vectordb(sql::T; maxdistance::Number=0.2) where {T<:AbstractString}
tablename = "sqlllm_decision_repository"
# get embedding of the query
df = find_similar_text_from_vectordb(sql, tablename,
"function_input_embedding", execute_sql_vectordb)
# println(df[1, [:id, :function_output]])
row, col = size(df)
distance = row == 0 ? Inf : df[1, :distance]
if row != 0 && distance < maxdistance
# if there is usable SQL, return it.
output_b64 = df[1, :function_output_base64] # pick the closest match
output_str = String(base64decode(output_b64))
rowid = df[1, :id]
println("\n--| similar sql found. row id $rowid, distance $distance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(output_str)
return (result=output_str, distance=distance)
else
println("\n--| similar sql not found, max distance $maxdistance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
return (result=nothing, distance=nothing)
end
end
""" insert query and sql into vector database
query = "get all wines from wine table"
sql = "SELECT * FROM wine;"
insert_sql_vectordb(query, sql)
"""
function insert_sql_vectordb(query::T1, SQL::T2; maxdistance::Number=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])
_query_embedding = GeneralUtils.dictify(_query_embedding)
# println("\n--- _query_embedding() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# println(_query_embedding)
# println("---\n")
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
""" execute sql against vectordb
sql = "SELECT * FROM wine;"
result = execute_sql_vectordb(sql)
"""
function execute_sql_vectordb(sql::T) where {T<:AbstractString}
host_url, _port = split(config["externalservice"]["sommpanion_vectordb"]["url"], ':')
port = parse(Int, _port)
dbname = config["externalservice"]["sommpanion_vectordb"]["dbname"]
user = config["externalservice"]["sommpanion_vectordb"]["user"]
password = config["externalservice"]["sommpanion_vectordb"]["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
""" search similar decision llm made from vectordb
"""
function similar_sommelier_decision(recentevents::T1; maxdistance::Integer=3
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
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
# 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
""" search similar text from vectordb
"""
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])
_embedding = _embedding["data"][1]["embedding"]
_embedding = "$_embedding"
embedding = _embedding[4:end]
# 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
""" insert decision llm made to vectordb
"""
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
config = JSON.parsefile("./appconfig.json")
sessionId = "0" sessionId = "0"
backend_session_topic = "sommpanion.backend.agentbackend.v1.inbox.$sessionId" backend_session_topic = "sommpanion.testsubject"
config = JSON.parsefile("./dummy_config.json")
agent_ch = Channel(8) agent_ch = Channel(8)
agent_conn = NATS.connect(config["nats_server_info"]["url"]) agent_conn = NATS.connect(config["nats_server_info"]["url"])
sub2 = NATS.subscribe(agent_conn, backend_session_topic) do msg sub2 = NATS.subscribe(agent_conn, backend_session_topic) do msg
put!(agent_ch, msg) put!(agent_ch, msg)
end end
@@ -210,32 +246,34 @@ agent_context = YiemAgent.agentcontext(
insert_sommelier_decision insert_sommelier_decision
) )
# can't instantiate # can't instantiate
agent = YiemAgent.sommelier( agent = YiemAgent.sommelier(
agent_context; agent_context;
name="Janie", name="Janie",
id=sessionId, # agent instance id id=sessionId, # agent instance id
retailername="Yiem", retailername="Yiem Wine Ltd.",
llmFormatName="" llmFormatName=""
) )
# 1. Read local file and encode to base64 string
image1_path = "test/large_image.png" image1_path = "test/large_image.png"
image1_bytes = read(image1_path) image1_bytes = read(image1_path)
image1_base64_string = base64encode(image1_bytes) image1_base64_string = base64encode(image1_bytes)
# 2. Match the MIME type according to your file extension (e.g., png, jpeg)
mime_type = "image/png" mime_type = "image/png"
data1_uri = "data:$(mime_type);base64,$(image1_base64_string)" data1_uri = "data:$(mime_type);base64,$(image1_base64_string)"
# 1. Read local file and encode to base64 string
image2_path = "test/small_image.png"
image2_bytes = read(image2_path)
image2_base64_string = base64encode(image2_bytes)
mime_type = "image/png"
data2_uri = "data:$(mime_type);base64,$(image2_base64_string)"
# 3. Construct payload with the Data URI # 3. Construct payload with the Data URI
usermsg = Dict{String, Any}( message = Dict(
"role" => "user", "role" => "user",
"content" => [ "content" => [
Dict("type" => "text", "text" => "รู้จักไวน์ที่อยู่ในรูปมั้ย"), Dict("type" => "text", "text" => "Do you know type of wine in the image?"),
Dict( Dict(
"type" => "image_url", "type" => "image_url",
"image_url" => Dict("url" => data1_uri) "image_url" => Dict("url" => data1_uri)
@@ -243,8 +281,80 @@ usermsg = Dict{String, Any}(
] ]
) )
result = YiemAgent.conversation(agent; userinput=usermsg) result = YiemAgent.conversation(agent; userinput=message)
println(result) println("\n$result")
# message = Dict(
# "role" => "user",
# "content" => [
# Dict("type" => "text", "text" =>
# "
# เป็นงานเลี้ยงทั่วไป
# "),
# ]
# )
# result = YiemAgent.conversation(agent; userinput=message)
# println("\n$result")
# message = Dict(
# "role" => "user",
# "content" => [
# Dict("type" => "text", "text" => "no thanks. that's all"),
# ]
# )
# result = YiemAgent.conversation(agent; userinput=message)
# println("\n$result")
# message = Dict(
# "role" => "user",
# "content" => [
# Dict("type" => "text", "text" => "What about this wine?"),
# Dict(
# "type" => "image_url",
# "image_url" => Dict("url" => data2_uri)
# )
# ]
# )
# result = YiemAgent.conversation(agent; userinput=message)
# println("\n$result")