From bb81b973d3f5106c239861e62337c8ffe0d9d496 Mon Sep 17 00:00:00 2001 From: narawat lamaiin Date: Mon, 20 Jan 2025 18:19:38 +0700 Subject: [PATCH] update --- src/interface.jl | 70 ++++++++++++++++++++++++++-------------------- src/llmfunction.jl | 8 +++++- src/util.jl | 26 +++++++++++++++-- 3 files changed, 71 insertions(+), 33 deletions(-) diff --git a/src/interface.jl b/src/interface.jl index 9442011..09eb9ce 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -1,7 +1,7 @@ module interface export addNewMessage, conversation, decisionMaker, evaluator, reflector, generatechat, - generalconversation, detectWineryName + generalconversation, detectWineryName, generateSituationReport using JSON3, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization, DataFrames @@ -176,16 +176,17 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen Your name is $(a.name). You are a helpful English-speaking assistant, acting as a polite, website-based sommelier for $(a.retailername)'s wine store. Your goal includes: 1) Establish a connection with the customer by greeting them warmly - 2) Help them select the best wines from your inventory that align with their preferences + 2) Help them select the best wines only from your store's inventory that align with their preferences Your responsibility includes: 1) Make an informed decision about what you need to do to achieve the goal 2) Thanks the user when they don't need any further assistance and invite them to comeback next time Your responsibility excludes: - 1) Asking or guiding the user to make a purchase + 1) Asking or guiding the user to make an order or purchase 2) Processing sales orders or engaging in any other sales-related activities - 3) Answering questions and offering additional services beyond just recommendations. + 3) Answering questions beyond just recommendations. + 4) Offering additional services beyond just recommendations. At each round of conversation, you will be given the current situation: Your recent events: latest 5 events of the situation @@ -204,7 +205,8 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen - Encourage the customer to explore different options and try new things. - Sometimes, the item a user desires might not be available in your inventory. In such cases, inform the user that the item is unavailable and suggest an alternative instead. - If a customer requests information about discounts, quantity, rewards programs, promotions, delivery options, boxes, gift wrapping, packaging, or personalized messages, please inform them that they can contact our sales team at the store. - + - Only recommend + For your information: - vintage 0 means non-vintage. @@ -232,14 +234,17 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen Let's begin! """ - chathistory = vectorOfDictToText(a.chathistory) + chathistory = chatHistoryToText(a.chathistory) # check if winename in shortmem occurred in chathistory. if not, skip decision and imediately use PRESENTBOX if haskey(a.memory[:shortmem], :available_wine) # check if wine name mentioned in recentevents, only check first wine name is enough # because agent will recommend every wines it found each time. - df = a.memory[:shortmem][:available_wine] - winenames = df[:, :wine_name] + winenames = [] + for wine in a.memory[:shortmem][:available_wine] + push!(winenames, wine["wine_name"]) + end + for winename in winenames if !occursin(winename, chathistory) println("\n~~~ Yiem decisionMaker() found wines from DB ", Dates.now(), " ", @__FILE__, " ", @__LINE__) @@ -257,6 +262,19 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen end end + context = # may b add wine name instead of the hold wine data is better + if haskey(a.memory[:shortmem], :available_wine) + winenames = [] + for (i, wine) in enumerate(a.memory[:shortmem][:available_wine]) + name = "$i) $(wine["wine_name"]) " + push!(winenames, name) + end + availableWineName = join(winenames, ',') + "You found information about the following wines in your inventory: $availableWineName" + else + "" + end + errornote = "" response = nothing # placeholder for show when error msg show up @@ -265,6 +283,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen usermsg = """ + $context Your recent events: $recentevents Your Q&A: $QandA) $errornote @@ -734,14 +753,12 @@ function conversation(a::sommelier, userinput::Dict; maximumMsg=50) # thinking loop until AI wants to communicate with the user chatresponse = nothing - for i in 1:5 + while chatresponse === nothing actionname, result = think(a) if actionname ∈ ["CHATBOX", "PRESENTBOX", "ENDCONVERSATION"] chatresponse = result - break end end - addNewMessage(a, "assistant", chatresponse; maximumMsg=maximumMsg) return chatresponse @@ -856,22 +873,13 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh elseif actionname == "CHECKINVENTORY" if rawresponse !== nothing if haskey(a.memory[:shortmem], :available_wine) - df = a.memory[:shortmem][:available_wine] - #[TESTING] sometime df 2 df has different column size - dfCol = names(df) - rawresponse_dfCol = names(rawresponse) - if length(dfCol) > length(rawresponse_dfCol) - a.memory[:shortmem][:available_wine] = DataFrames.outerjoin(df, rawresponse, on=rawresponse_dfCol) - elseif length(dfCol) < length(rawresponse_dfCol) - a.memory[:shortmem][:available_wine] = DataFrames.outerjoin(df, rawresponse, on=dfCol) - else - a.memory[:shortmem][:available_wine] = vcat(df, rawresponse) - end + vd = GeneralUtils.dfToVectorDict(rawresponse) + a.memory[:shortmem][:available_wine] = vcat(vd, rawresponse) else - a.memory[:shortmem][:available_wine] = rawresponse + a.memory[:shortmem][:available_wine] = GeneralUtils.dfToVectorDict(rawresponse) end else - # no result, skip + println("checkinventory return nothing") end push!(a.memory[:events], @@ -889,7 +897,6 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh error("condition is not defined ", Dates.now(), " ", @__FILE__, " ", @__LINE__) end - return (actionname=actionname, result=result) end @@ -956,15 +963,15 @@ function generatechat(a::sommelier, thoughtDict) Let's begin! """ - # a.memory[:shortmem][:available_wine] is a dataframe. + # a.memory[:shortmem][:available_wine] is a vector of dictionary context = if haskey(a.memory[:shortmem], :available_wine) - "Available wines $(GeneralUtils.dfToString(a.memory[:shortmem][:available_wine]))" + "Wines previously found in your inventory: $(availableWineToText(a.memory[:shortmem][:available_wine]))" else - "None" + "N/A" end - chathistory = vectorOfDictToText(a.chathistory) + chathistory = chatHistoryToText(a.chathistory) errornote = "" response = nothing # placeholder for show when error msg show up @@ -1092,7 +1099,7 @@ function generatechat(a::companion) a.systemmsg end - chathistory = vectorOfDictToText(a.chathistory) + chathistory = chatHistoryToText(a.chathistory) response = nothing # placeholder for show when error msg show up for attempt in 1:10 @@ -1340,6 +1347,9 @@ function generateSituationReport(a, text2textInstructLLM::Function; skiprecent:: Event_1: The user ask me about where to buy a toy. Event_2: I told the user to go to the store at 2nd floor. + Event_1: The user greets the assistant by saying 'hello'. + Event_2: The assistant respond warmly and inquire about how he can assist the user. + Let's begin! """ diff --git a/src/llmfunction.jl b/src/llmfunction.jl index ae85089..1e53d73 100644 --- a/src/llmfunction.jl +++ b/src/llmfunction.jl @@ -307,6 +307,8 @@ function checkinventory(a::T1, input::T2 println("\n~~~ checkinventory result ", Dates.now(), " ", @__FILE__, " ", @__LINE__) println(textresult) + #[WORKING] when rawresponse is nothing, AI get errors + return (result=textresult, rawresponse=rawresponse, success=true, errormsg=nothing) end @@ -412,6 +414,8 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2< responsedict = copy(JSON3.read(response)) + # convert + delete!(responsedict, :reasoning) delete!(responsedict, :tasting_notes) delete!(responsedict, :occasion) @@ -444,7 +448,9 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2< end else content = responsedict[j] - if occursin(',', content) + 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 diff --git a/src/util.jl b/src/util.jl index 0c8aec0..5478394 100644 --- a/src/util.jl +++ b/src/util.jl @@ -1,6 +1,7 @@ module util -export clearhistory, addNewMessage, vectorOfDictToText, eventdict, noises, createTimeline +export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline, + availableWineToText using UUIDs, Dates, DataStructures, HTTP, JSON3 using GeneralUtils @@ -138,7 +139,7 @@ julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true) ``` # Signature """ -function vectorOfDictToText(vecd::Vector; withkey=true)::String +function chatHistoryToText(vecd::Vector; withkey=true)::String # Initialize an empty string to hold the final text text = "" @@ -169,6 +170,27 @@ function vectorOfDictToText(vecd::Vector; withkey=true)::String end +function availableWineToText(vecd::Vector)::String + # Initialize an empty string to hold the final text + rowtext = "" + # Loop through each dictionary in the input vector + for (i, d) in enumerate(vecd) + # Iterate over all key-value pairs in the dictionary + temp = [] + for (k, v) in d + # Append the formatted string to the text variable + t = "$k:$v" + push!(temp, t) + end + _rowtext = join(temp, ',') + rowtext *= "$i) $_rowtext " + end + + return rowtext +end + + + function eventdict(; event_description::Union{String, Nothing}=nothing, timestamp::Union{DateTime, Nothing}=nothing,