diff --git a/Manifest.toml b/Manifest.toml
index 6acb603..798c085 100644
--- a/Manifest.toml
+++ b/Manifest.toml
@@ -2,7 +2,31 @@
julia_version = "1.12.6"
manifest_format = "2.0"
-project_hash = "a7a5dbec939b5bcaa54ea4aedd3c8ff223c86502"
+project_hash = "f897ee3fe396a565f9279db1f39a2f86d2c466c2"
+
+[[deps.Accessors]]
+deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
+git-tree-sha1 = "7063ad1083578215c7c4bf410368150abe8d5524"
+uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
+version = "0.1.45"
+
+ [deps.Accessors.extensions]
+ AxisKeysExt = "AxisKeys"
+ IntervalSetsExt = "IntervalSets"
+ LinearAlgebraExt = "LinearAlgebra"
+ StaticArraysExt = "StaticArrays"
+ StructArraysExt = "StructArrays"
+ TestExt = "Test"
+ UnitfulExt = "Unitful"
+
+ [deps.Accessors.weakdeps]
+ AxisKeys = "94b1ba4f-4ee9-5380-92f1-94cde586c3c5"
+ IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
+ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
+ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
+ StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
+ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
[[deps.AliasTables]]
deps = ["PtrArrays", "Random"]
@@ -90,6 +114,11 @@ git-tree-sha1 = "da54a6cd93c54950c15adf1d336cfd7d71f51a56"
uuid = "6b39b394-51ab-5f42-8807-6242bab2b4c2"
version = "0.8.7"
+[[deps.CommonSolve]]
+git-tree-sha1 = "99ee296f88c12485402e37c2fd025f95ae097637"
+uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
+version = "0.2.9"
+
[[deps.Compat]]
deps = ["TOML", "UUIDs"]
git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad"
@@ -110,12 +139,36 @@ deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "1.3.0+1"
+[[deps.CompositionsBase]]
+git-tree-sha1 = "802bb88cd69dfd1509f6670416bd4434015693ad"
+uuid = "a33af91c-f02d-484b-be07-31d278c5ca2b"
+version = "0.1.2"
+weakdeps = ["InverseFunctions"]
+
+ [deps.CompositionsBase.extensions]
+ CompositionsBaseInverseFunctionsExt = "InverseFunctions"
+
[[deps.ConcurrentUtilities]]
deps = ["Serialization", "Sockets"]
git-tree-sha1 = "21d088c496ea22914fe80906eb5bce65755e5ec8"
uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
version = "2.5.1"
+[[deps.ConstructionBase]]
+git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb"
+uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
+version = "1.6.0"
+
+ [deps.ConstructionBase.extensions]
+ ConstructionBaseIntervalSetsExt = "IntervalSets"
+ ConstructionBaseLinearAlgebraExt = "LinearAlgebra"
+ ConstructionBaseStaticArraysExt = "StaticArrays"
+
+ [deps.ConstructionBase.weakdeps]
+ IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
+ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
+ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
+
[[deps.Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
@@ -164,10 +217,10 @@ uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
version = "1.11.0"
[[deps.Distributions]]
-deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
-git-tree-sha1 = "3c8a0a9a6d4a10bdfb6b751bd2b6051ed3e25fd4"
+deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "Roots", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
+git-tree-sha1 = "cd3c5ac74cd3923c8945c6a81518c46abd0e73a3"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
-version = "0.25.127"
+version = "0.25.129"
[deps.Distributions.extensions]
DistributionsChainRulesCoreExt = "ChainRulesCore"
@@ -247,17 +300,17 @@ version = "1.11.0"
[[deps.GeneralUtils]]
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "JSON", "NATS", "PrettyPrinting", "Random", "Revise", "SHA", "UUIDs"]
-git-tree-sha1 = "f1bad7621e6ac2d235adc8c593b0aff87bd1d93e"
+git-tree-sha1 = "aafde7b6e54a06840127f8379abe19ac17db6fea"
repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/GeneralUtils"
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
-version = "0.4.2"
+version = "0.4.3"
[[deps.HTTP]]
deps = ["Base64", "CodecZlib", "Dates", "EnumX", "PrecompileTools", "Random", "Reseau", "SHA", "URIs", "UUIDs", "Zlib_jll"]
-git-tree-sha1 = "a95f80749437ffb42948848d2d2ec81a5050ef4b"
+git-tree-sha1 = "e718a35dd7386ccd6bed64a1d84d661972404b99"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
-version = "2.4.0"
+version = "2.5.1"
[[deps.HashArrayMappedTries]]
git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae"
@@ -303,6 +356,16 @@ git-tree-sha1 = "d6fe00b123e32ddd17231b35d69a6394e696fd5a"
uuid = "d8418881-c3e1-53bb-8760-2df7ec849ed5"
version = "1.11.0"
+[[deps.InverseFunctions]]
+git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb"
+uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
+version = "0.1.17"
+weakdeps = ["Dates", "Test"]
+
+ [deps.InverseFunctions.extensions]
+ InverseFunctionsDatesExt = "Dates"
+ InverseFunctionsTestExt = "Test"
+
[[deps.InvertedIndices]]
git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
@@ -450,9 +513,9 @@ version = "1.11.0"
[[deps.LoweredCodeUtils]]
deps = ["CodeTracking", "Compiler", "JuliaInterpreter"]
-git-tree-sha1 = "0aad96d7b987a5600e260eec50147b254d5ff7e6"
+git-tree-sha1 = "3733419e9a71156b389f3e331672d2e95436783f"
uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
-version = "3.6.0"
+version = "3.6.2"
[[deps.Lz4_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -460,6 +523,11 @@ git-tree-sha1 = "191686b1ac1ea9c89fc52e996ad15d1d241d1e33"
uuid = "5ced341a-0733-55b8-9ab6-a4889d929147"
version = "1.10.1+0"
+[[deps.MacroTools]]
+git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522"
+uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
+version = "0.5.16"
+
[[deps.Markdown]]
deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
@@ -558,9 +626,9 @@ version = "1.8.2"
[[deps.PDMats]]
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
-git-tree-sha1 = "e4cff168707d441cd6bf3ff7e4832bdf34278e4a"
+git-tree-sha1 = "26766d4b5f1a410c218a19b85a672c6edb693c65"
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
-version = "0.11.37"
+version = "0.11.40"
weakdeps = ["StatsBase"]
[deps.PDMats.extensions]
@@ -693,12 +761,34 @@ git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8"
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
version = "0.5.1+0"
+[[deps.Roots]]
+deps = ["Accessors", "CommonSolve", "Printf"]
+git-tree-sha1 = "91cfb1cb4f6e27557cc2df798a31eff6089a41eb"
+uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
+version = "3.0.0"
+
+ [deps.Roots.extensions]
+ RootsChainRulesCoreExt = "ChainRulesCore"
+ RootsForwardDiffExt = "ForwardDiff"
+ RootsIntervalRootFindingExt = "IntervalRootFinding"
+ RootsSymPyExt = "SymPy"
+ RootsSymPyPythonCallExt = "SymPyPythonCall"
+ RootsUnitfulExt = "Unitful"
+
+ [deps.Roots.weakdeps]
+ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
+ IntervalRootFinding = "d2bf35a9-74e0-55ec-b149-d360ff49b807"
+ SymPy = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6"
+ SymPyPythonCall = "bc8888f7-b21e-4b7c-a06a-5d9c9496438c"
+ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
+
[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"
[[deps.SQLLLM]]
-deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON3", "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"]
path = "../SQLLLM"
uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
version = "0.2.4"
@@ -864,9 +954,9 @@ version = "1.0.1"
[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
-git-tree-sha1 = "f2c1efbc8f3a609aadf318094f8fc5204bdaf344"
+git-tree-sha1 = "0f38a06c83f0007bbab3cf911262841c9a0f07e0"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
-version = "1.12.1"
+version = "1.13.0"
[[deps.Tar]]
deps = ["ArgTools", "SHA"]
@@ -925,7 +1015,7 @@ uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60"
version = "1.6.1"
[[deps.YiemAgent]]
-deps = ["CSV", "DataFrames", "DataStructures", "Dates", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serialization", "URIs", "UUIDs", "msghandler"]
+deps = ["CSV", "DataFrames", "DataStructures", "Dates", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serialization", "URIs", "UUIDs", "msghandler"]
path = "."
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
version = "0.4.0"
@@ -954,11 +1044,11 @@ version = "1.0.21+0"
[[deps.msghandler]]
deps = ["Arrow", "Base64", "DataFrames", "Dates", "GeneralUtils", "HTTP", "JSON", "NATS", "PrettyPrinting", "Revise", "UUIDs"]
-git-tree-sha1 = "02825c9cb2c15d3bc30fdf28656c1f594a04cb5f"
+git-tree-sha1 = "db16f76f72bd4fa2a87e34ef47bb787204e1f888"
repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/msghandler"
uuid = "f2724d33-f338-4a57-b9f8-1be882570d10"
-version = "0.5.6"
+version = "0.5.7"
[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
diff --git a/Project.toml b/Project.toml
index 074ed42..57d45b5 100644
--- a/Project.toml
+++ b/Project.toml
@@ -26,7 +26,7 @@ msghandler = "f2724d33-f338-4a57-b9f8-1be882570d10"
[compat]
CSV = "0.10.15"
DataFrames = "1.7.0"
-GeneralUtils = "0.4.2"
+GeneralUtils = "0.4.3"
HTTP = "2.4.0"
JSON = "1.6.1"
NATS = "0.1.0"
diff --git a/src/etc.jl b/src/etc.jl
new file mode 100644
index 0000000..6017dca
--- /dev/null
+++ b/src/etc.jl
@@ -0,0 +1,88 @@
+
+""" Recursively convert dictionary-like variable (e.g. JSON.Object) into a dictionary.
+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 a plain `Dict` 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 a `Dict`;
+ if it is an `AbstractArray` its elements will be processed recursively.
+
+# Keyword Arguments
+- `keytype::Type=Any`
+ The key type for the output Dict. Use `String` for `Dict{String,Any}`, `Symbol` for `Dict{Symbol,Any}`, or `Any` to preserve original key types.
+- `stringkey::Bool=false`
+ If `true`, every dictionary key is converted to `String` via `string(k)`. This parameter is ignored when `keytype` is explicitly set.
+
+# Return
+- A newly allocated nested structure composed of `Dict{keytype,Any}` and
+ `Vector{Any}` that mirrors the input shape but uses plain 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
+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)
+Dict{String,Any} with 3 entries:
+ "a" => 4
+ "b" => 6
+ "c" => Dict("d"=>7, "e"=>Dict("f"=>"hey", "g"=>Dict("world"=>[1, "2", 3, 4.7])))
+
+julia> A3 = dictify(A1; keytype=Symbol)
+Dict{Symbol,Any} with 3 entries:
+ :a => 4
+ :b => 6
+ :c => Dict(:d=>7, :e=>Dict("f"=>"hey", "g"=>Dict("world"=>[1, "2", 3, 4.7])))
+
+julia> B1 = dictify(d; keytype=String)
+Dict{String, Any} with 3 entries:
+"""
+function dictify(x; keytype::Type=Any)
+ # Dict-like objects
+ if x isa AbstractDict
+ # choose output key type container
+ out = Dict{keytype,Any}()
+ for (k,v) in x
+ if keytype === String
+ newk = string(k)
+ elseif keytype === Symbol
+ newk = Symbol(string(k))
+ else
+ newk = k
+ end
+ out[newk] = dictify(v; keytype=keytype)
+ end
+ return out
+ # Arrays / vectors: map elements recursively and return a Vector{Any}
+ elseif x isa AbstractArray
+ return [dictify(element; keytype=keytype) for element in x]
+ # everything else: return as-is (primitives, numbers, strings, etc.)
+ else
+ return x
+ end
+end
\ No newline at end of file
diff --git a/src/interface.jl b/src/interface.jl
index 0abc7b2..f1bee48 100644
--- a/src/interface.jl
+++ b/src/interface.jl
@@ -130,7 +130,9 @@ function decisionMaker(a::T; recentevents::Integer=20, maxattempt=10
context =
"""
- $(GeneralUtils.dict_to_string_html(a.memory["shortmem"]))
+
+ $(GeneralUtils.dict_to_string_html(a.memory["shortmem"]))
+
"""
@@ -150,14 +152,15 @@ function decisionMaker(a::T; recentevents::Integer=20, maxattempt=10
println("\nYiemAgent decisionMaker() attempt $attempt/$maxattempt ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
end
- openai_msg = Dict(
+ msg = Dict(
"model" => "gemma-4-E4B-it-UD-Q4_K_XL",
"messages" => a.chathistory,
"temperature" => 0.7
)
- response = a.context.text2textInstructLLM(a.id, openai_msg)
- # response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
+ response = a.context.text2textInstructLLM(a.id, msg)
+ response = GeneralUtils.clean_json_response(response)
+
response = GeneralUtils.remove_french_accents(response)
think, response = GeneralUtils.extractthink(response)
response = String(split(response, ", observation")[1]) # in case LLM generate observation key which it isn't supposed to
@@ -188,89 +191,6 @@ function decisionMaker(a::T; recentevents::Integer=20, maxattempt=10
println("\nYiem decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(Dict(responsedict))
- # # check whether an agent recommend wines before checking inventory or recommend wines
- # # outside its inventory
- # # ask LLM whether there are any winery mentioned in the response
- # mentioned_winery = detectWineryName(a, response)
- # if mentioned_winery != "None"
- # mentioned_winery = String.(strip.(split(mentioned_winery, ",")))
-
- # # check whether the wine is in event
- # isWineInEvent = false
- # for winename in mentioned_winery
- # for event in a.memory["events"]
- # if event["observation"] !== nothing && occursin(winename, event["observation"])
- # isWineInEvent = true
- # break
- # end
- # end
- # end
-
- # # then the agent is not supposed to recommend the wine
- # if isWineInEvent == false
- # errornote = "You recommended wines that are not in your inventory before. Please only recommend wines that you have previously found in your inventory."
- # println("\nERROR YiemAgent decisionMaker() $errornote $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # continue
- # end
- # end
-
- # delete!(responsedict, :mentioned_winery)
-
- # check whether responsedict["action_input"] is the same as previous dialogue
- # if !isempty(a.chathistory) && responsedict["action_input"] == a.chathistory[end]["text"]
- # errornote = "In your previous attempt, you repeated the previous dialogue. Please try again."
- # println("\nERROR YiemAgent decisionMaker() $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # continue
- # end
-
- # evaluationdict = evaluator(a, timeline, responsedict, context)
- # if evaluationdict[:approval] == "no"
- # mentor_comment = evaluationdict[:suggestion]
- # errornote = "Your previous attempt was not good enough. Please try again. Here is the mentor's suggestion: $mentor_comment"
- # println("\nERROR YiemAgent decisionMaker() $errornote --(not qualify response)--> \n$response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # continue
- # end
-
- # # store for later training
- # responsedict["system"] = systemmsg
- # responsedict["unformatPrompt"] = unformatPrompt
- # responsedict["prompt"] = prompt
- # responsedict["context"] = context
- # responsedict["think"] = think
- # responsedict["response"] = response
- # # responsedict["QandA"] = QandA
-
- # # check whether there is a file path exists before writing to it
- # if !haskey(a.memory["shortmem"], "decisionlog")
- # a.memory["shortmem"]["decisionlog"] = [responsedict]
- # else
- # push!(a.memory["shortmem"]["decisionlog"], responsedict)
- # end
-
- # # save to filename ./log/decisionlog.txt
- # println("\nsaving YiemAgent decisionMaker() to disk ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # filename = "agent_decision_log_$(a.id).json"
- # filepath = "/appfolder/app/log/$filename"
- # # check whether there is a file path exists before writing to it
- # if !isfile(filepath)
- # decisionlist = [responsedict]
- # println("Creating file $filepath")
- # open(filepath, "w") do io
- # JSON.pretty(io, decisionlist)
- # end
- # else
- # # read the file and append new data
- # decisionlist = copy(JSON.parsefile(filepath))
- # push!(decisionlist, responsedict)
- # println("Appending new data to file $filepath")
- # open(filepath, "w") do io
- # JSON.pretty(io, decisionlist)
- # end
- # end
- # println("\nYiemAgent decisionMaker() saved to disk is done. agent $(a.id)")
-
-
- # responsedict["prompt"] = prompt
return responsedict
end
error("DecisionMaker failed to generate a thought ", response)
diff --git a/src/llmfunction.jl b/src/llmfunction.jl
index 2564501..0008303 100644
--- a/src/llmfunction.jl
+++ b/src/llmfunction.jl
@@ -281,13 +281,7 @@ julia> input = "{\"food\": \"pizza\", \"occasion\": \"anniversary\"}"
julia> result = checkinventory(agent, input)
"{"wine 1": {\"Winery\": \"Pichon Baron\", \"wine name\": \"Pauillac (Grand Cru Classé)\", \"grape variety\": \"Cabernet Sauvignon\", \"year\": 2010, \"price\": \"125 USD\", \"stock ID\": \"ar-17\"}, }"
```
-
-# TODO
- - [] update docs
- - [x] implement the function
-
-# Signature
-"""
+"""
function checkwine(a::T1, input::T2; maxattempt::Int=3
) where {T1<:agent, T2<:AbstractString}
@@ -309,6 +303,7 @@ function checkwine(a::T1, input::T2; maxattempt::Int=3
inventoryquery = "Retrieves $retrieve_attributes of wines that match the following criteria - {$_inventoryquery}"
println("\ncheckinventory input: $inventoryquery ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# add suppport for similarSQLVectorDB
+ #WORKING
textresult, rawresponse = SQLLLM.query(
inventoryquery,
a.context.executeSQL,
@@ -316,6 +311,7 @@ function checkwine(a::T1, input::T2; maxattempt::Int=3
insertSQLVectorDB=a.context.insertSQLVectorDB,
similarSQLVectorDB=a.context.similarSQLVectorDB,
llmFormatName="qwen3")
+ error(5555)
# check if all of retrieve_attributes appears in textresult
isin = [occursin(x, textresult) for x in retrieve_attributes]
# check if rawresponse type is DataFrame so that I can check for column
@@ -349,29 +345,28 @@ end
```jldoctest
julia>
```
-
-# TODO
- - [] update docstring
- - implement the function
-
-# Signature
"""
function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
)::String where {T1<:agent, T2<:AbstractString}
systemmsg =
"""
- As a helpful sommelier, your task is to extract the user information from the user's query as much as possible to fill out user's preference form.
-
- At each round of conversation, the user will give you the following:
- - The query: the query provided by the user.
-
- You must follow the following guidelines:
+
+ At each round of conversation, the user provides the following:
+ - The query: the query provided by the user.
+
+
+ Extract information from the user's query as much as possible according to wine attributes extraction guidelines to fill out user's preference form.
+
+
+ Fulfill the objective.
+
+
- If specific information required in the preference form is not available in the query or there isn't any, mark with "N/A" to indicate this.
Additionally, words like 'any' or 'unlimited' mean no information is available.
- Do not generate other comments.
-
- You should then respond to the user with:
+
+
wine_name: name of the wine
winery: name of the winery
vintage: the year of the wine
@@ -384,27 +379,23 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
wine_price_max: maximum price range of wine. Example: For wine price 20, wine_price_max will be 20. For wine price 10 to 100, wine_price_max will be 100.
occasion: the occasion the user is having the wine for
food_to_be_paired_with_wine: food that the user will be served with the wine such as poultry, fish, steak, etc
-
- You should only respond in JSON format as described below:
- {
- "wine_name": "...",
- "winery": "...",
- "vintage": "...",
- "region": "...",
- "country": "...",
- "wine_type": "...",
- "grape_varietal": "...",
- "tasting_notes": "...",
- "wine_price_min": "...",
- "wine_price_max": "...",
- "occasion": "...",
- "food_to_be_paired_with_wine": "..."
- }
-
- Here are some example:
-
+
+
+ "wine_name": "...",
+ "winery": "...",
+ "vintage": "...",
+ "region": "...",
+ "country": "...",
+ "wine_type": "...",
+ "grape_varietal": "...",
+ "tasting_notes": "...",
+ "wine_price_min": "...",
+ "wine_price_max": "...",
+ "occasion": "...",
+ "food_to_be_paired_with_wine": "..."
+
+
User's query: red, Chenin Blanc, Riesling, 20 USD from Tuscany, Italy or Napa Valley, USA
- {
"wine_name": "N/A",
"winery": "N/A",
"vintage": "N/A",
@@ -417,10 +408,8 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
"wine_price_max": "20",
"occasion": "N/A",
"food_to_be_paired_with_wine": "N/A"
- }
User's query: Domaine du Collier Saumur Blanc 2019, France, white, Merlot
- {
"wine_name": "Saumur Blanc",
"winery": "Domaine du Collier",
"vintage": "2019",
@@ -433,171 +422,71 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
"wine_price_max": "N/A",
"occasion": "N/A",
"food_to_be_paired_with_wine": "N/A"
- }
-
- Let's begin!
+
+ """
+ requiredKeys = ["wine_name", "winery", "vintage", "region", "country", "wine_type", "grape_varietal", "tasting_notes", "wine_price_min", "wine_price_max", "occasion", "food_to_be_paired_with_wine"]
+ errornote = ""
+ context =
+ """
+
+ $errornote
+
"""
- requiredKeys = [:wine_name, :winery, :vintage, :region, :country, :wine_type, :grape_varietal, :tasting_notes, :wine_price_min, :wine_price_max, :occasion, :food_to_be_paired_with_wine]
- errornote = "N/A"
+ input = context * input
+
+ 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" => input),
+ ]
+ ),
+ ],
+ "temperature" => 0.7
+ )
for attempt in 1:maxattempt
- usermsg =
- """
- $input
- """
- context =
- """
-
- P.S. $errornote
-
- /no_think
- """
+ response = a.context.text2textInstructLLM(a.id, msg)
+ response = GeneralUtils.clean_json_response(response)
+ println("\n--- extractWineAttributes_1-1()")
+ println(response)
+ println("--- \n")
- unformatPrompt =
- [
- Dict("name"=> "system", "text"=> systemmsg),
- Dict("name"=> "user", "text"=> usermsg)
- ]
-
- # put in model format
- prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
- # add info
- prompt = prompt * context
-
- response = a.context.text2textInstructLLM(prompt; modelsize="medium", senderId=a.id)
- response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
response = GeneralUtils.remove_french_accents(response)
think, response = GeneralUtils.extractthink(response)
-
responsedict = nothing
try
- responsedict = copy(JSON.parsefile(response))
+ _responsedict = JSON.parse(response)
+ responsedict = GeneralUtils.dictify(_responsedict, keytype=String)
catch
- println("\nERROR YiemAgent extractWineAttributes_1() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ println("\nERROR YiemAgent extractWineAttributes_1() failed to parse response: $response", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue
end
-
# check whether all answer's key points are in responsedict
- _responsedictKey = keys(responsedict)
- responsedictKey = [i for i in _responsedictKey] # convert into a list
- is_requiredKeys_in_responsedictKey = [i ∈ responsedictKey for i in requiredKeys]
-
- if length(is_requiredKeys_in_responsedictKey) > length(requiredKeys)
- errornote = "Your previous attempt has more key points than answer's required key points."
- println("\nERROR YiemAgent extractWineAttributes_1() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- continue
- elseif !all(is_requiredKeys_in_responsedictKey)
- zeroind = findall(x -> x == 0, is_requiredKeys_in_responsedictKey)
- missingkeys = [requiredKeys[i] for i in zeroind]
- errornote = "$missingkeys are missing from your previous response"
- println("\nERROR YiemAgent extractWineAttributes_1() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ ispass, errormsg = checkAgentResponse_JSON(responsedict, requiredKeys)
+ if !ispass
+ errornote = errormsg
+ println("\nERROR YiemAgent extractWineAttributes_1() $errornote --(not qualify response)> $responsedict", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
continue
end
-
- # # check whether response has all header
- # detected_kw = GeneralUtils.detect_keyword(header, response)
- # kwvalue = [i for i in values(detected_kw)]
- # zeroind = findall(x -> x == 0, kwvalue)
- # missingkeys = [header[i] for i in zeroind]
- # if 0 ∈ values(detected_kw)
- # errornote = "$missingkeys are missing from your previous response"
- # println("\nERROR YiemAgent decisionMaker() $errornote:\n$response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # continue
- # elseif sum(values(detected_kw)) > length(header)
- # errornote = "Your previous attempt has duplicated points"
- # println("\nERROR YiemAgent decisionMaker() $errornote:\n$response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # continue
- # end
-
- # # check whether response has all answer's key points
- # detected_kw = GeneralUtils.detect_keyword(header, response)
- # if 0 ∈ values(detected_kw)
- # errornote = "In your previous attempts, the response does not have all answer's key points"
- # println("\nYiemAgent extractWineAttributes_1() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # continue
- # elseif sum(values(detected_kw)) > length(header)
- # errornote = "In your previous attempts, the response has duplicated answer's key points"
- # println("\nYiemAgent extractWineAttributes_1() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # println(response)
- # continue
- # end
- # responsedict = GeneralUtils.textToDict(response, header;
- # dictKey=dictkey, symbolkey=true)
-
- removekeys = [:thought, :tasting_notes, :occasion, :food_to_be_paired_with_wine, :vintage]
+ removekeys = ["thought", "tasting_notes", "occasion", "food_to_be_paired_with_wine", "vintage"]
for i in removekeys
delete!(responsedict, i)
end
-
-
-
- delete!(responsedict, :thought)
- delete!(responsedict, :tasting_notes)
- delete!(responsedict, :occasion)
- delete!(responsedict, :food_to_be_paired_with_wine)
- delete!(responsedict, :vintage)
-
- # check if winery, wine_name, region, country, wine_type, grape_varietal's value are in the query because sometime AI halucinates
- checkFlag = false
- for i in requiredKeys
- j = Symbol(i)
- if j ∉ removekeys
- # in case j is wine_price it needs to be checked differently because its value is ranged
- if j == :wine_price
-if responsedict["wine_price"] != "N/A"
- if !occursin("to", responsedict["wine_price"])
- errornote = "In your previous attempt, the 'wine_price' was set to $(responsedict["wine_price"]) which is not a correct format. Please adjust it accordingly."
- println("\nERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- checkFlag = true
- break
- end
-
- # # check whether max wine_price is in the input
- # pricerange = split(responsedict["wine_price"], '-')
- # minprice = pricerange[1]
- # maxprice = pricerange[end]
- # if !occursin(maxprice, input)
- # responsedict["wine_price"] = "N/A"
- # end
- # # price range like 100-100 is not good
- # if minprice == maxprice
- # errornote = "In your previous attempt, you inputted 'wine_price' with a 'minimum' value equaling the 'maximum', which is not valid."
- # println("\nERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # checkFlag = true
- # break
- # end
- end
- else
- content = responsedict[j]
- if typeof(content) <: AbstractVector
- content = strip.(content)
- elseif occursin(',', content)
- content = split(content, ",") # sometime AI generates multiple values e.g. "Chenin Blanc, Riesling"
- content = strip.(content)
- else
- content = [content]
- end
-
- # for x in content #check whether price are mentioned in the input
- # if !occursin("NA", responsedict[j]) && !occursin(x, input)
- # errornote = "$x is not mentioned in the user query, you must only use the info from the query."
- # println("ERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # checkFlag == true
- # break
- # end
- # end
- end
- end
- end
- checkFlag == true ? continue : nothing # skip the rest code if true
-
# remove (some text)
for (k, v) in responsedict
_v = replace(v, r"\(.*?\)" => "")
responsedict[k] = _v
end
-
result = ""
for (k, v) in responsedict
# some time LLM generate text with "(some comment)". this line removes it
@@ -605,20 +494,18 @@ if responsedict["wine_price"] != "N/A"
result *= "$k: $v, "
end
end
-
- #[PENDING] remove halucination. "highend dry white wine" --> "wine_type: white, occasion: special occasion, food_to_be_paired_with_wine: seafood, fish, country: France, Italy, USA, grape_varietal: Chardonnay, Sauvignon Blanc, Pinot Grigio\nwine_notes: citrus, green apple, floral"
-
result = result[1:end-2] # remove the ending ", "
-
+ println("\n--- extractWineAttributes_1-2()")
+ println(result)
+ println("--- \n")
return result
end
- error("wineattributes_wordToNumber() failed to get a response")
+ error("extractWineAttributes_1() failed to get a response")
end
"""
-# TODO
- - [PENDING] "French dry white wines with medium bod" the LLM does not recognize sweetness. use LLM self questioning to solve.
- - [PENDING] French Syrah, Viognier, under 100. LLM extract intensiry of 3-5. why?
+ - TODO "French dry white wines with medium bod" the LLM does not recognize sweetness. use LLM self questioning to solve.
+ - TODO French Syrah, Viognier, under 100. LLM extract intensiry of 3-5. why?
"""
function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
@@ -654,21 +541,25 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
systemmsg =
"""
- As an helpful sommelier, your task is to fill out the user's preference form based on the corresponding words from the user's query.
-
- At each round of conversation, you will be given the following information:
- conversion_table: a conversion table that maps descriptive words to their corresponding integer levels
- query: the words from the user's query that describe their preferences
-
- The preference form requires the following information:
- sweetness, acidity, tannin, intensity
-
- You must follow the following guidelines:
- 1) If specific information required in the preference form is not available in the query or there isn't any, mark with 'N/A' to indicate this.
+
+ At each round of conversation, you will be given the following information:
+ conversion_table: a conversion table that maps descriptive words to their corresponding integer levels
+ query: the words from the user's query that describe their preferences
+
+
+ Fill out the user's preference form based on the corresponding words from the user's query according to the guidelines.
+
+
+ Fulfill the objective
+
+
+ - The preference form requires sweetness, acidity, tannin, intensity infomation
+ - If specific information required in the preference form is not available in the query or there isn't any, mark with 'N/A' to indicate this.
Additionally, words like 'any' or 'unlimited' mean no information is available.
- 2) Use the conversion table to convert the descriptive word level of sweetness, intensity, tannin, and acidity into a corresponding integer.
- 3) Do not generate other comments.
- You should then respond to the user with:
+ - Use the conversion table to convert the descriptive word level of sweetness, intensity, tannin, and acidity into a corresponding integer.
+ - Do not generate other comments.
+
+
sweetness_keyword: The exact keywords in the user's query describing the sweetness level of the wine.
sweetness: ( S ), where ( S ) represents integers indicating the range of sweetness levels. Example: 1-2
acidity_keyword: The exact keywords in the user's query describing the acidity level of the wine.
@@ -677,157 +568,133 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
tannin: ( T ), where ( T ) represents integers indicating the range of tannin level. Example: 1-3
intensity_keyword: The exact keywords in the user's query describing the intensity level of the wine.
intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
- You should only respond in JSON format as described below:
- {
+
+
"sweetness_keyword": "...",
- "sweetness": "...",
+ "sweetness_min": "...",
+ "sweetness_max": "...",
"acidity_keyword": "...",
- "acidity": "...",
+ "acidity_min": "...",
+ "acidity_max": "...",
"tannin_keyword": "...",
- "tannin": "...",
+ "tannin_min": "...",
+ "tannin_max": "...",
"intensity_keyword": "...",
- "intensity": "..."
- }
-
- Here are some examples:
+ "intensity_min": "...",
+ "intensity_max": "..."
+
+
User's query: I want a wine with a medium-bodied, low acidity, medium tannin.
- {
- "sweetness_keyword": "N/A",
- "sweetness": "N/A",
- "acidity_keyword": "low acidity",
- "acidity": "1-2",
- "tannin_keyword": "medium tannin",
- "tannin": "3-4",
- "intensity_keyword": "medium-bodied",
- "intensity": "3-4"
- }
+ "sweetness_keyword": "N/A",
+ "sweetness_min": "N/A",
+ "sweetness_max": "N/A",
+ "acidity_keyword": "low acidity",
+ "acidity_min": 1,
+ "acidity_max": 2,
+ "tannin_keyword": "medium tannin",
+ "tannin_min": 3,
+ "tannin_max": 4,
+ "intensity_keyword": "medium-bodied",
+ "intensity_min": 3,
+ "intensity_max": 4
- User's query: German red wine, under 100, pairs with spicy food
- {
- "sweetness_keyword": "N/A",
- "sweetness": "N/A",
- "acidity_keyword": "N/A",
- "acidity": "N/A",
- "tannin_keyword": "N/A",
- "tannin": "N/A",
- "intensity_keyword": "N/A",
- "intensity": "N/A"
- }
-
- Let's begin!
+ User's query: German red wine, under 100, pairs with spicy food.
+ "sweetness_keyword": "N/A",
+ "sweetness_min": "N/A",
+ "sweetness_max": "N/A",
+ "acidity_keyword": "N/A",
+ "acidity_min": "N/A",
+ "acidity_max": "N/A",
+ "tannin_keyword": "N/A",
+ "tannin_min": "N/A",
+ "tannin_max": "N/A",
+ "intensity_keyword": "N/A",
+ "intensity_min": "N/A",
+ "intensity_max": "N/A"
+
+ """
+ requiredKeys = ["sweetness_keyword", "sweetness_min", "sweetness_max",
+ "acidity_keyword", "acidity_min", "acidity_max",
+ "tannin_keyword", "tannin_min", "tannin_max",
+ "intensity_keyword", "intensity_min", "intensity_max"]
+ errornote = ""
+ context =
+ """
+
+ $conversiontable
+ $errornote
+
"""
- requiredKeys = [:sweetness_keyword, :sweetness, :acidity_keyword, :acidity, :tannin_keyword, :tannin, :intensity_keyword, :intensity]
- # header = ["Sweetness_keyword:", "Sweetness:", "Acidity_keyword:", "Acidity:", "Tannin_keyword:", "Tannin:", "Intensity_keyword:", "Intensity:"]
- # dictkey = ["sweetness_keyword", "sweetness", "acidity_keyword", "acidity", "tannin_keyword", "tannin", "intensity_keyword", "intensity"]
- errornote = "N/A"
+ input = context * input
+
+ 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" => input),
+ ]
+ ),
+ ],
+ "temperature" => 0.7
+ )
for attempt in 1:10
- context =
- """
- $conversiontable
-
- $input
-
- P.S. $errornote
- /no_think
- """
+ response = a.context.text2textInstructLLM(a.id, msg)
+ response = GeneralUtils.clean_json_response(response)
+ println("\n--- extractWineAttributes_2-1()")
+ println(response)
+ println("--- \n")
- unformatPrompt =
- [
- Dict("name"=> "system", "text"=> systemmsg),
- ]
-
- # put in model format
- prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
- # add info
- prompt = prompt * context
-
- response = a.context.text2textInstructLLM(prompt; modelsize="medium", senderId=a.id)
- response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
response = GeneralUtils.remove_french_accents(response)
think, response = GeneralUtils.extractthink(response)
-
responsedict = nothing
try
- responsedict = copy(JSON.parsefile(response))
+ _responsedict = JSON.parse(response)
+ responsedict = GeneralUtils.dictify(_responsedict, keytype=String)
catch
- println("\nERROR YiemAgent extractWineAttributes_2() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ println("\nERROR YiemAgent extractWineAttributes_2() failed to parse response: $response", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue
end
-
# check whether all answer's key points are in responsedict
- _responsedictKey = keys(responsedict)
- responsedictKey = [i for i in _responsedictKey] # convert into a list
- is_requiredKeys_in_responsedictKey = [i ∈ responsedictKey for i in requiredKeys]
-
- if length(is_requiredKeys_in_responsedictKey) > length(requiredKeys)
- errornote = "Your previous attempt has more key points than answer's required key points."
- println("\nERROR YiemAgent extractWineAttributes_2() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ ispass, errormsg = checkAgentResponse_JSON(responsedict, requiredKeys)
+ if !ispass
+ errornote = errormsg
+ println("\nERROR YiemAgent extractWineAttributes_2() $errornote --(not qualify response)> $responsedict", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
continue
- elseif !all(is_requiredKeys_in_responsedictKey)
- zeroind = findall(x -> x == 0, is_requiredKeys_in_responsedictKey)
- missingkeys = [requiredKeys[i] for i in zeroind]
- errornote = "$missingkeys are missing from your previous response"
- println("\nERROR YiemAgent extractWineAttributes_2() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- continue
- end
-
- # check whether each describing keyword is in the input to prevent halucination
- for i in ["sweetness", "acidity", "tannin", "intensity"]
- keyword = Symbol(i * "_keyword") # e.g. sweetness_keyword
- value = responsedict[keyword]
- if value != "N/A" && !occursin(value, input)
- errornote = "In your previous attempt, keyword $keyword: $value does not appear in the input. You must use information from the input only"
- println("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- continue
- end
-
- # if value == "N/A" then responsedict[i] = "N/A"
- # e.g. if sweetness_keyword == "N/A" then sweetness = "N/A"
- if value == "N/A"
- responsedict[Symbol(i)] = "N/A"
- end
- end
-
- # some time LLM not put integer range
- for (k, v) in responsedict
- if !occursin("keyword", string(k))
- if v !== "N/A" && (!occursin('-', v) || length(v) > 5)
- errornote = "WARNING: The non-range value {$k: $v} is not allowed. It should be specified in a range format, i.e. min-max."
- println("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- continue
- end
- end
- end
-
- # some time LLM says N/A-2. Need to convert N/A to 1
- for (k, v) in responsedict
- if occursin("N/A", v) && occursin("-", v)
- new_v = replace(v, "N/A"=>"1")
- responsedict[k] = new_v
- end
end
# delete some key words from responsedict
for (k, v) in responsedict
- if k ∈ [:sweetness_keyword, :acidity_keyword, :tannin_keyword, :intensity_keyword]
+ if k ∈ ["sweetness_keyword", "acidity_keyword", "tannin_keyword", "intensity_keyword"]
delete!(responsedict, k)
end
end
+ # get result in String. Reject "N/A" value
result = ""
for (k, v) in responsedict
- # some time LLM generate text with "(some comment)". this line removes it
- if !occursin("N/A", v)
+ if typeof(v) <: Number
+ result *= "$k: $v, "
+ elseif typeof(v) == String && !occursin("N/A", v)
result *= "$k: $v, "
end
end
result = result[1:end-2] # remove the ending ", "
-
+ println("\n--- extractWineAttributes_2-2()")
+ println(result)
+ println("--- \n")
return result
end
- error("wineattributes_wordToNumber() failed to get a response")
+ error("extractWineAttributes_2() failed to get a response")
end
diff --git a/src/type.jl b/src/type.jl
index 4fa4687..333bb75 100644
--- a/src/type.jl
+++ b/src/type.jl
@@ -167,34 +167,40 @@ function sommelier(
Chat history use openai format as follow:
- image1_path = "test/large_image.png" ---
- image1_bytes = read(image1_path) | this part must be done
- image1_base64_string = base64encode(image1_bytes) | in frontend
- mime_type = "image/png" | not in agent code
- data1_uri = "data:;base64," ---
+ image1_path = "test/large_image.png" ---
+ image1_bytes = read(image1_path) | this part must be done
+ image1_base64_string = base64encode(image1_bytes) | in frontend
+ mime_type = "image/png" | not in agent code
+ data1_uri = "data:;base64," ---
- chathistory= [
- Dict(
- "role" => "system",
- "content" => [
- Dict("type" => "text", "text" => "You are a helpful assistant"),
- ]
- ),
- Dict(
- "role" => "user",
- "content" => [
- Dict("type" => "text", "text" => "
- LLM context here...
-
- Do you know this wine? Just give me brief intro."
- ),
- Dict(
- "type" => "image_url",
- "image_url" => Dict("url" => data1_uri)
- ),
- ]
- ),
- ]
+ chathistory= [
+ Dict(
+ "role" => "system",
+ "content" => [
+ Dict("type" => "text", "text" => "You are a helpful assistant"),
+ ]
+ ),
+ Dict(
+ "role" => "user",
+ "content" => [
+ Dict("type" => "text", "text" => "
+ LLM context here...
+
+ Do you know this wine? Just give me brief intro."
+ ),
+ Dict(
+ "type" => "image_url",
+ "image_url" => Dict("url" => data1_uri)
+ ),
+ ]
+ ),
+ ]
+
+ shortmem = Dict(
+ "1"=> Dict("plan"=> "...", "action_name"=> "...", "action_input"=> "...", "action_result"=> "..."),
+ "2"=> Dict("plan"=> "...", "action_name"=> "...", "action_input"=> "...", "action_result"=> "..."),
+ ...
+ )
"""
memory = Dict{String, Any}(
"shortmem"=> OrderedDict{String, Any}(),
@@ -220,7 +226,7 @@ function sommelier(
systemmsg =
"""
-
+
- 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
@@ -232,8 +238,8 @@ function sommelier(
- 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.
-
-
+
+
- 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.
@@ -241,8 +247,8 @@ function sommelier(
- 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.
- Search the database as broad as possible under the informantion you have will increase the chance to find wine. Avoid uneccessary parameter such as region, country, tasting notes unless the user specify
-
+ Search the database as broad as possible under the informantion you have will increase the chance to find wine. Avoid uneccessary parameter such as region, country, tasting notes unless the user specify
+
- CHATBOX which you can use to talk with the user. Be specific.
- CHECKWINE allows you to check information about wines you want in your inventory's database. The input must be supported search criteria includeing: 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."
@@ -250,19 +256,19 @@ function sommelier(
Example query 3: "white wine, region: Tuscany or Bordeaux, country: Italy or France
- PRESENTBOX which you can use to to generate proper dialogue to present wines you have found in your inventory to the user. The input are wine names that you want to present. The output is presentation of the wines.
- ENDCONVERGUIDELINE 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.
-
+
+
+ Your customer is coming into the store
+
Your name is $(newAgent.name). You are a helpful sommelier for website-based $(newAgent.retailername)'s wine store. You are working under your mentor supervision.
-
- Your customer is coming into the store
-
-
+
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.
-
+
- 1) According to the store's policy and guidelines, make an informed decision about what you need to do to achieve the goal
+ 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
2) Obey your mentor's suggestions.
@@ -271,21 +277,17 @@ function sommelier(
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.
-
+
1) plan: Based on the current situation, state a complete action plan to complete the task. Be specific.
- 2) action_name: (Typically corresponds to the execution of the first step in your plan) Can be one of the available tool name
+ 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 tool is executed you gets "action_result". It is the output from the tool you selected.
-
+ After the action is executed you gets "action_result". It is the output from the action you selected.
+
- {
"plan": "...",
"action_name": "...",
"action_input": "..."
- }
-
-
- Let's begin!
+
"""
system_msg = Dict(
diff --git a/src/util.jl b/src/util.jl
index 85fbb39..72d0290 100644
--- a/src/util.jl
+++ b/src/util.jl
@@ -307,35 +307,6 @@ function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothin
# Return formatted timeline string
return timeline
end
-# function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
-# ) where {T1<:AbstractVector}
-# # Initialize empty timeline string
-# timeline = ""
-
-# # Determine which indices to use - either provided range or full length
-# ind =
-# if eventindex !== nothing
-# [eventindex...]
-# else
-# 1:length(events)
-# end
-
-# # Iterate through events and format each one
-# for i in ind
-# event = events[i]
-# # If no outcome exists, format without outcome
-# if event["observation"] === nothing
- # timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"]), observation: Not done yet.\n"
- # # If outcome exists, include it in formatting
- # else
- # timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"]), observation: $(event["observation"])\\n"
-# end
-# end
-
-# # Return formatted timeline string
-# return timeline
-# end
-
function createEventsLog(events::T1; index::Union{UnitRange, Nothing}=nothing
) where {T1<:AbstractVector}
@@ -462,11 +433,6 @@ end
-
-
-
-
-