diff --git a/src/interface.jl b/src/interface.jl index 26a5f15..a9b3b4b 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -96,7 +96,7 @@ julia> output_thoughtDict = Dict( # Signature """ -function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} +function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:agent} # lessonDict = copy(JSON3.read("lesson.json")) @@ -173,50 +173,72 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} # {"thought" # """ + # systemmsg = # """ - # You are a helpful sommelier working for a wine store. - # Your task is to help the user choose the best wine that match the user preferences from your inventory. - # You are also eager to improve your helpfulness. - - # You must follow the following guidelines: - # - Get to know how much the user willing to spend - # - Get to know type of wine the user is looking for e.g. red, white, sparkling, rose, dessert, fortified - # - Get to know what occasion the user is buying wine for - # - Get to know what characteristics of wine the user is looking for e.g. tannin, sweetness, intensity, acidity - # - Get to know what food the user will have with wine + # You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. + # Your goal is: Help the user select the best wines from your inventory that align with the user's preferences. - # At each round of conversation, the user will give you the current situation: + # Your responsibility includes: + # 1) Make an informed decision about what you need to do to achieve the goal. + # 2) Thanks the user when they are done choosing wines and invite them to comeback next time. + + # Your responsibility does not include: + # 1) Processing sales orders or engaging in any other sales-related activities. + + # At each round of conversation, you will be given the current situation: + # Recap: ... # Context: ... - # Your earlier conversation with the user: ... - # You should then respond to the user with interleaving Thought, Plan, Action and Observation: - # - thought: - # 1) State your reasoning about the current situation. - # - plan: Based on the current situation, what would you do to complete the task? Be specific. - # - action (Must be aligned with your plan): Can be one of the following functions: - # 1) CHATBOX[text], which you can use to talk with the user. "text" is in verbal English. - # 2) CHECKINVENTORY[query], which you can use to find info about wine in your inventory. "query" is a search term in verbal English. - # - observation: result of the action. + # You MUST follow the following guidelines: + # - Generally speaking, your inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know which wines your store carries until you check your inventory. + # - All wines in your inventory are always in stock. + # - Use the "understand-then-check" inventory strategy to understand the user, as there are many wines in the inventory. + # - 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, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. + + # You should follow the following guidelines as you see fit: + # - If the user interrupts, prioritize the user. + # - If you don't already know, find out the user's budget. + # - If you don't already know, find out the type of wine the user is looking for, such as red, white, sparkling, rose, dessert, fortified. + # - If you don't already know, find out the occasion for which the user is buying wine. + # - If you don't already know, find out the characteristics of wine the user is looking for, such as tannin, sweetness, intensity, acidity. + # - If you don't already know, find out what food will be served with wine. + # - If you haven't already, introduce the wines you found in the database to the user first. + + # You should then respond to the user with interleaving Thought, Plan, Action: + # 1) thought: + # - State your reasoning about the current situation. + # 2) plan: Based on the current situation, state a complete plan to complete the task. Be specific. + # 3) action_name (Must be aligned with your plan): The name of the action which can be one of the following functions: + # - CHATBOX which you can use to generate conversation in order to communicate with the user. The input is your intentions for the dialogue. Be specific. + # - CHECKINVENTORY which you can use to check info about wine in your inventory. The input is a search term in verbal English. + # Good query example: black car, a stereo, 200 mile range, electric motor. + # - PRESENTBOX which you can use to introduce / suggest / recommend wines you just found in the database to the user. It is better than the CHATBOX function for presenting wines. The input is the names of wines to introduce. + # - ENDCONVERSATION which you can use when you want to finish the conversation with the user. The input is "NA". + # 4) action_input: input of the action + # 5) mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No" # You should only respond in format as described below: # thought: ... # plan: ... # action_name: ... # action_input: ... - # observation: ... + # mentioning_wine: ... # Let's begin! # """ + systemmsg = """ You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. - Your goal is: Help the user select the best wines from your inventory that align with the user's preferences. + Your goal includes: + 1) Help the user select the best wines from your inventory that align with the user's 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 are done choosing wines and invite them to comeback next time. + 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 does not include: 1) Processing sales orders or engaging in any other sales-related activities. @@ -225,12 +247,12 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} Recap: ... Context: ... - You MUST follow the following guidelines: + You must follow the following guidelines: - Generally speaking, your inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know which wines your store carries until you check your inventory. - All wines in your inventory are always in stock. - Use the "understand-then-check" inventory strategy to understand the user, as there are many wines in the inventory. - 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, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. + - Once you present the wines to the user, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. You should follow the following guidelines as you see fit: - If the user interrupts, prioritize the user. @@ -241,34 +263,48 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} - If you don't already know, find out what food will be served with wine. - If you haven't already, introduce the wines you found in the database to the user first. - You should then respond to the user with interleaving Thought, Plan, Action: - 1) thought: - - State your reasoning about the current situation. - 2) plan: Based on the current situation, state a complete plan to complete the task. Be specific. - 3) action_name (Must be aligned with your plan): The name of the action which can be one of the following functions: + You should then respond to the user with interleaving Understanding, Reasoning, Plan, Action: + 1) Understanding: + - State your understanding about the current situation. + 2) Reasoning: + - State your step by step reasoning about the current situation. + 2) Plan: Based on the current situation, state a complete plan to complete the task. Be specific. + 3) Action_name (Must be aligned with your plan): The name of the action which can be one of the following functions: - CHATBOX which you can use to generate conversation in order to communicate with the user. The input is your intentions for the dialogue. Be specific. - CHECKINVENTORY which you can use to check info about wine in your inventory. The input is a search term in verbal English. Good query example: black car, a stereo, 200 mile range, electric motor. - PRESENTBOX which you can use to introduce / suggest / recommend wines you just found in the database to the user. It is better than the CHATBOX function for presenting wines. The input is the names of wines to introduce. - ENDCONVERSATION which you can use when you want to finish the conversation with the user. The input is "NA". - 4) action_input: input of the action - 5) mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No" + 4) Action_input: input of the action + 5) Mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No" You should only respond in format as described below: - thought: ... - plan: ... - action_name: ... - action_input: ... - mentioning_wine: ... + Understanding: ... + Reasoning: ... + Plan: ... + Action_name: ... + Action_input: ... + Mentioning_wine: ... Let's begin! """ - context = - if length(a.memory[:shortmem]) > 0 - vectorOfDictToText(a.memory[:shortmem], withkey=false) - else - "" + totalevents = length(a.memory[:events]) + ind = + if totalevents > recent + start = totalevents-recent + start:totalevents + else + 1:totalevents + end + + timeline = "" + for (i, event) in enumerate(a.memory[:events][ind]) + if event[:outcome] === nothing + timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue])\n" + else + timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue]) $(event[:outcome])\n" + end end # get only the latest 3 conversation @@ -283,7 +319,7 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} usermsg = """ Recap: $(a.memory[:recap]) - Your recent conversation with the user: $chathistory + Your recent events: $timeline Your Q&A: $(a.memory[:QandA]) $errornote """ @@ -304,26 +340,22 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} try response = a.text2textInstructLLM(prompt) responsedict = GeneralUtils.textToDict(response, - ["thought", "plan", "action_name", "action_input", "mentioning_wine"], + ["Understanding", "Reasoning", "Plan", "Action_name", "Action_input", "Mentioning_wine"], rightmarker=":", symbolkey=true) - # if occursin('[', responsedict[:action_name]) - # action_input = GeneralUtils.getStringBetweenCharacters(responsedict[:action_name], '[', ']') - # action_name = string(split(responsedict[:action_name], '[')[1]) - # end - if responsedict[:action_name] ∉ ["CHATBOX", "PRESENTBOX", "CHECKINVENTORY", "ENDCONVERSATION"] + if responsedict[:Action_name] ∉ ["CHATBOX", "PRESENTBOX", "CHECKINVENTORY", "ENDCONVERSATION"] errornote = "You must use the given functions" error("You must use the given functions ", @__FILE__, " ", @__LINE__) end - for i ∈ [:thought, :plan, :action_name] + for i ∈ [:Understanding, :Plan, :Action_name] if length(responsedict[i]) == 0 error("$i is empty ", @__FILE__, " ", @__LINE__) end end # check if there are more than 1 key per categories - for i ∈ [:thought, :plan, :action_name, :action_input, :mentioning_wine] + for i ∈ [:Understanding, :Plan, :Action_name, :Action_input, :Mentioning_wine] matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i) if length(matchkeys) > 1 error("DecisionMaker has more than one key per categories") @@ -332,17 +364,17 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} println("") println("--> Yiem decisionMaker() ", @__FILE__, " ", @__LINE__) - pprintln(responsedict) + pprintln(Dict(responsedict)) # check if LLM recommend wine before checking inventory isMemEmpty = isempty(a.memory[:shortmem]) - if occursin("Yes", responsedict[:mentioning_wine]) && isMemEmpty && - responsedict[:action_name] != "CHECKINVENTORY" + if occursin("Yes", responsedict[:Mentioning_wine]) && isMemEmpty && + responsedict[:Action_name] != "CHECKINVENTORY" errornote = "Note: You can't recommend wines yet. You must check your inventory before recommending wine to the user." error( "You can't recommend wines yet. You must check your inventory before recommending wines") - elseif responsedict[:action_name] == "PRESENTBOX" && isMemEmpty && - responsedict[:action_name] != "CHECKINVENTORY" + elseif responsedict[:Action_name] == "PRESENTBOX" && isMemEmpty && + responsedict[:Action_name] != "CHECKINVENTORY" errornote = "Note: You can't recommend wines yet. You must check your inventory before recommending wine to the user." error( "You can't recommend wines yet. You must check your inventory before recommending wines") @@ -350,11 +382,7 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} errornote = "" end - delete!(responsedict, :mentioning_wine) - - # if length(a.memory[:shortmem]) > 0 && responsedict[:action_name] != "PRESENTBOX" - # responsedict[:action_name] = "PRESENTBOX" - # end + delete!(responsedict, :Mentioning_wine) return responsedict catch e @@ -369,157 +397,6 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} end error("DecisionMaker failed to generate a thought ", response) end -# function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} - -# # lessonDict = copy(JSON3.read("lesson.json")) - -# # lesson = -# # if isempty(lessonDict) -# # "" -# # else -# # lessons = Dict{Symbol, Any}() -# # for (k, v) in lessonDict -# # lessons[k] = lessonDict[k][:lesson] -# # end - -# # """ -# # You have attempted to help the user before and failed, either because your reasoning for the -# # recommendation was incorrect or your response did not exactly match the user expectation. -# # The following lesson(s) give a plan to avoid failing to help the user in the same way you -# # did previously. Use them to improve your strategy to help the user. - -# # Here are some lessons in JSON format: -# # $(JSON3.write(lessons)) - -# # When providing the thought and action for the current trial, that into account these failed -# # trajectories and make sure not to repeat the same mistakes and incorrect answers. -# # """ -# # end - -# _prompt = -# """ -# You are a helpful sommelier working for a wine store. -# Your goal is to recommend the best wine from your inventory that match the user preferences. -# You are also keen to improve your recommendation with lesson(s). - -# You must follow the following criteria: -# 1) Get to know how much the user willing to spend -# 2) Get to know type of wine the user is looking for e.g. red, white, sparkling, rose, dessert, fortified -# 3) Get to know what occasion the user is buying wine for -# 4) Get to know what characteristics of wine the user is looking for -# e.g. tannin, sweetness, intensity, acidity -# 5) Get to know what food the user will have with wine -# 6) Check your inventory for the best wine that match the user preference -# 7) Recommend wine to the user - -# You should only respond with interleaving Thought, Action, Observation steps. -# Thought can reason about the current situation, and Action can be three types: -# 1) winestock[query], which you can use to find wine in your inventory. The more input data the better. -# 2) CHATBOX[text], which you can use to interact with the user. -# After each observation, provide the next Thought and next Action. - -# You should only respond in JSON format as describe below: -# { -# "thought": "your reasoning", -# "action": {"name": "action to take", "input": "action input"}, -# "observation": "result of the action" -# } - -# Here are some examples: -# { -# "question": "I would like to buy a sedan with 8 seats.", -# "thought_1": "Our showroom carries various vehicle model. But I'm not sure whether we have a models that fits the user demand, I need to check our inventory.", -# "action_1": {"name": "inventory", "input": "sedan with 8 seats."}, -# "observation_1": "Several model has 8 seats. Available color are black, red green" -# } -# { -# "thought": "I have a few color for the user to choose from. I will ask him what color he likes.", -# "action": {"name": "CHATBOX", "input": "Which color do you like?"} -# "observation": "I'll take black." -# } - -# $lesson - -# Let's begin! - -# $(JSON3.write(state[:thoughtHistory])) -# {"thought" -# """ - -# # apply LLM specific instruct format -# externalService = config[:externalservice][:text2textinstruct] -# llminfo = externalService[:llminfo] -# prompt = -# if llminfo[:name] == "llama3instruct" -# formatLLMtext_llama3instruct("system", _prompt) -# else -# error("llm model name is not defied yet $(@__LINE__)") -# end - -# msgMeta = GeneralUtils.generate_msgMeta( -# externalService[:mqtttopic], -# senderName= "decisionMaker", -# senderId= string(uuid4()), -# receiverName= "text2textinstruct", -# mqttBroker= config[:mqttServerInfo][:broker], -# mqttBrokerPort= config[:mqttServerInfo][:port], -# ) - -# outgoingMsg = Dict( -# :msgMeta=> msgMeta, -# :payload=> Dict( -# :text=> prompt, -# :kwargs=> Dict( -# :max_tokens=> 512, -# :stop=> ["<|eot_id|>"], -# ) -# ) -# ) -# @show outgoingMsg - -# for attempt in 1:5 -# try -# response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg) -# _responseJsonStr = response[:response][:text] -# expectedJsonExample = -# """ -# Here is an expected JSON format: -# { -# "thought": "...", -# "action": {"name": "...", "input": "..."}, -# "observation": "..." -# } -# """ -# responseJsonStr = jsoncorrection(config, _responseJsonStr, expectedJsonExample) -# thoughtDict = copy(JSON3.read(responseJsonStr)) - -# # check if dict has all required value -# thought::AbstractString = thoughtDict[:thought] -# actionname::AbstractString = thoughtDict[:action][:name] -# actioninput::AbstractString = thoughtDict[:action][:input] -# if actionname ∈ ["winestock", "CHATBOX", "recommendbox"] -# # LLM use available function -# elseif thought == "" -# error("DecisionMaker has no thought") -# elseif length(actioninput) == 0 -# error("DecisionMaker has no actioninput") -# else -# error("DecisionMaker use wrong function") -# end - -# return thoughtDict -# catch e -# io = IOBuffer() -# showerror(io, e) -# errorMsg = String(take!(io)) -# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) -# println("") -# @warn "Attempt $attempt. Error occurred: $errorMsg\n$st" -# println("") -# end -# end -# error("DecisionMaker failed to generate a thought") -# end """ Assigns a scalar value to each new child node to be used for selec- @@ -1032,6 +909,7 @@ function conversation(a::T, userinput::Dict) where {T<:agent} occursin("inventory)", chatresponse) || occursin("inventory*", chatresponse) if llmCheckInv && actionname != "checkinventory" + error("force check inventory should not be called") actionname, result = forceInventoryCheck(a) push!(a.memory[:shortmem], Dict(Symbol(actionname)=> result)) @@ -1074,9 +952,11 @@ julia> """ function think(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}} where {T<:agent} + a.memory[:recap] = generateSituationReport(a, a.text2textInstructLLM; skiprecent=5) + a.memory[:QandA] = generatequestion(a, a.text2textInstructLLM; recent=5) + + dummy = situationtrack(a, a.text2textInstructLLM) - a.memory[:recap] = generateSituationReport(a, a.text2textInstructLLM) - a.memory[:QandA] = generatequestion(a, a.text2textInstructLLM) # after the user selected their wine. No question should be asked # a.memory[:QandA] = # if occursin("None", a.memory[:sitrep][:wine_selected]) @@ -1086,10 +966,10 @@ function think(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}} # "None" # end - thoughtDict = decisionMaker(a) + thoughtDict = decisionMaker(a; recent=5) - actionname = thoughtDict[:action_name] - actioninput = thoughtDict[:action_input] + actionname = thoughtDict[:Action_name] + actioninput = thoughtDict[:Action_input] # map action and input() to llm function response = @@ -1141,8 +1021,8 @@ end function forceInventoryCheck(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}} where {T<:agent} println("--> forceInventoryCheck()") thoughtDict = thinkCheckInventory(a) - actionname = thoughtDict[:action_name] - actioninput = thoughtDict[:action_input] + actionname = thoughtDict[:Action_name] + actioninput = thoughtDict[:Action_input] # map action and input() to llm function response = @@ -1191,7 +1071,7 @@ function thinkCheckInventory(a::T)::Dict{Symbol, Any} where {T<:agent} 1) CHECKINVENTORY[query], which you can use to check info about wine in your inventory. "query" is a search term in verbal English. Good query example: black car with a stereo, 200 mile range and an electric motor. Good query example: How many car brand are from Asia? - - action_input: input to the action + - Action_input: input to the action You should only respond in format as described below: thought: ... @@ -1306,14 +1186,12 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F - Focus on the latest conversation. You should then respond to the user with: - 1) chat: Given the situation, what would you say to convey your thoughts to the user? - 2) mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No" - 3) note: Put everything you want to add here. Otherwise, put "NA" + 1) Chat: Given the situation, what would you say to convey your thoughts to the user? + 2) Mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No" You should only respond in format as described below: - chat: ... - mentioning_wine: ... - note: ... + Chat: ... + Mentioning_wine: ... Let's begin! """ @@ -1353,17 +1231,17 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F try response = text2textInstructLLM(prompt) - responsedict = GeneralUtils.textToDict(response,["chat", "mentioning_wine", "note"], + responsedict = GeneralUtils.textToDict(response,["Chat", "Mentioning_wine"], rightmarker=":", symbolkey=true) - for i ∈ [:chat] + for i ∈ [:Chat] if length(JSON3.write(responsedict[i])) == 0 error("$i is empty ", @__LINE__) end end # check if there are more than 1 key per categories - for i ∈ [:chat] + for i ∈ [:Chat] matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i) if length(matchkeys) > 1 error("generatechat has more than one key per categories") @@ -1371,17 +1249,17 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F end # check if Context: is in chat - if occursin("Context:", responsedict[:chat]) + if occursin("Context:", responsedict[:Chat]) error("Context: is in text. This is not allowed") end println("") println("--> generatechat() ", @__FILE__, " ", @__LINE__) - pprintln(responsedict) + pprintln(Dict(responsedict)) # check if LLM recommend wine before checking inventory isMemEmpty = isempty(memory[:shortmem]) - if occursin("Yes", responsedict[:mentioning_wine]) && isMemEmpty + if occursin("Yes", responsedict[:Mentioning_wine]) && isMemEmpty errornote = "Note: You can't recommend wines yet. You must check your inventory before recommending wine to the user." error( "You must check your inventory before recommending wine") else @@ -1389,8 +1267,8 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F end memory[:CHATBOX] = "" # delete content because it no longer used. - delete!(responsedict, :mentioning_wine) - result = responsedict[:chat] + delete!(responsedict, :Mentioning_wine) + result = responsedict[:Chat] return result catch e @@ -1405,127 +1283,8 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F end error("generatechat failed to generate an evaluation") end -# function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::Function) -# systemmsg = -# """ -# You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. -# Your goal is: Recommend the best wines from your inventory that align with the user's preferences. - -# Your responsibility includes: -# 1) Given the situation, convey your thoughts to the user -# At each round of conversation, you will be given the current situation: -# Your conversation with the user: ... -# Your thoughts: Your current thoughts in your mind -# Context: ... - -# You MUST follow the following guidelines: -# - Do not offer additional services you didn't thought. - -# You should follow the following guidelines: -# - Focus on the latest conversation. - -# You should then respond to the user with: -# 1) chat: Given the situation, convey your thoughts to the user -# 2) mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No" - -# You should only respond in format as described below: -# chat: ... -# mentioning_wine: ... - -# Let's begin! -# """ - -# context = -# if length(memory[:shortmem]) > 0 #[WORKING] add with number order 1), 2) -# vectorOfDictToText(memory[:shortmem], withkey=false) -# else -# "" -# end - -# chathistory = vectorOfDictToText(chathistory) -# errornote = "" -# response = nothing # placeholder for show when error msg show up - -# for attempt in 1:10 -# usermsg = -# """ -# Your conversation with the user: $chathistory -# $context -# Your thoughts: $(memory[:CHATBOX]) -# $errornote -# """ - -# _prompt = -# [ -# Dict(:name=> "system", :text=> systemmsg), -# Dict(:name=> "user", :text=> usermsg) -# ] - -# # put in model format -# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct") -# prompt *= -# """ -# <|start_header_id|>assistant<|end_header_id|> -# """ - -# try -# response = text2textInstructLLM(prompt) -# responsedict = GeneralUtils.textToDict(response,["chat", "mentioning_wine"], -# rightmarker=":", symbolkey=true) - -# for i ∈ [:chat] -# if length(JSON3.write(responsedict[i])) == 0 -# error("$i is empty ", @__LINE__) -# end -# end - -# # check if there are more than 1 key per categories -# for i ∈ [:chat] -# matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i) -# if length(matchkeys) > 1 -# error("generatechat has more than one key per categories") -# end -# end - -# # check if Context: is in chat -# if occursin("Context:", responsedict[:chat]) -# error("Context: is in text. This is not allowed") -# end - -# println("") -# println("--> generatechat() ", @__FILE__, " ", @__LINE__) -# pprintln(responsedict) - -# # check if LLM recommend wine before checking inventory -# isMemEmpty = isempty(memory[:shortmem]) -# if occursin("Yes", responsedict[:mentioning_wine]) && isMemEmpty -# errornote = "Note: You can't recommend wines yet. You must check your inventory before recommending wine to the user." -# error( "You must check your inventory before recommending wine") -# else -# errornote = "" -# end - -# memory[:CHATBOX] = "" # delete content because it no longer used. -# delete!(responsedict, :mentioning_wine) -# result = responsedict[:chat] - -# return result -# catch e -# io = IOBuffer() -# showerror(io, e) -# errorMsg = String(take!(io)) -# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) -# println("") -# println("Attempt $attempt. Error occurred: $errorMsg\n$st") -# println("") -# end -# end -# error("generatechat failed to generate an evaluation") -# end - - -function generatequestion(a, text2textInstructLLM::Function)::String +function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::String # systemmsg = # """ @@ -1575,17 +1334,17 @@ function generatequestion(a, text2textInstructLLM::Function)::String """ You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. Your goal includes: - 1) Help the user select the best wines from your inventory that align with the user's preferences. - + 1) Help the user select the best wines from your inventory that align with the user's preferences + 2) Thanks the user when they don't need any further assistance and invite them to comeback next time + Your responsibility includes: 1) Ask yourself what to do about the current situation - 2) Thanks the user when they are done choosing wines and invite them to comeback next time Your responsibility does not include: 1) Processing sales orders or engaging in any other sales-related activities. At each round of conversation, you will be given the current situation: - Your conversation with the user: ... + Your recent events: ... Context: ... You must follow the following guidelines: @@ -1599,25 +1358,14 @@ function generatequestion(a, text2textInstructLLM::Function)::String - All wines in your inventory are always in stock. You should then respond to the user with: - 1) thought: State your reasoning about the current situation - 2) Q: Given the situation, "ask yourself" about the situation at least three, but no more than ten, questions. + 1) Understanding: + - State your understanding about the current situation + 2) Q: Given the situation, "ask yourself" at least five, but no more than ten, questions 3) A: Given the situation, "answer to yourself" the best you can - 4) note: Additional info you want to say. Otherwise, "NA" - - Here are some examples: - Q: The user is asking for a cappuccino. Do I have it at my cafe? - A: No I don't. - Q: Since I don't have a cappuccino but I have a Late, should I ask if they are okay with that? - A: Yes, I should. - Q: Are they allergic to milk? - A: Since they mentioned a cappuccino before, I think they are not allergic to milk. - Q: Do I search the database yet? - A: Not yet. - Q: Did I introduce the wines to the user yet? - A: Not yet. I will introduce the wines now. + 4) Note: Additional info you want to say You must only respond in format as described below: - thought: ... + Understanding: ... Q1: ... A1: ... Q2: ... @@ -1625,27 +1373,55 @@ function generatequestion(a, text2textInstructLLM::Function)::String Q3: ... A3: ... ... - note: ... + Note: ... + + Here are some examples: + Q: The user is asking for a cappuccino. Do I have it at my cafe? + A: No I don't. + + Q: Since I don't have a cappuccino but I have a Late, should I ask if they are okay with that? + A: Yes, I should. + + Q: Are they allergic to milk? + A: According to the situation, since they mentioned a cappuccino before, it seems they are not allergic to milk. + + Q: Have I searched the database yet? + A: According to the situation, no. I need more information. + + Q: What did I find in the database? + A: According to the situation, ... + + Q: Did I introduce the coffee blend varieties to the user yet? + A: According to the situation, no, I didn't because I have not searched the database yet. Let's begin! """ + + totalevents = length(a.memory[:events]) + ind = + if totalevents > recent + start = totalevents-recent + start:totalevents + else + 1:totalevents + end - context = - if length(a.memory[:shortmem]) > 0 - vectorOfDictToText(a.memory[:shortmem], withkey=false) - else - "" + timeline = "" + for (i, event) in enumerate(a.memory[:events][ind]) + if event[:outcome] === nothing + timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue])\n" + else + timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue]) $(event[:outcome])\n" + end end - chathistory = vectorOfDictToText(a.chathistory) errornote = "" response = nothing # store for show when error msg show up for attempt in 1:10 usermsg = """ - Your conversation with the user: $chathistory Recap: $(a.memory[:recap]) - $context + Your recent events: $timeline $errornote """ @@ -1665,12 +1441,12 @@ function generatequestion(a, text2textInstructLLM::Function)::String try response = text2textInstructLLM(prompt) q_number = count("Q", response) - if q_number < 3 + if q_number < 5 error("too few questions only $q_number questions are generated ", @__FILE__, " ", @__LINE__) end # response = string(split(response, "Please")[1]) # LLM usually add comments which is no need. responsedict = GeneralUtils.textToDict(response, - ["thought", "Q1", "note"], + ["Understanding", "Q1", "Note"], rightmarker=":", symbolkey=true) response = "Q1: " * responsedict[:Q1] println("--> generatequestion ", @__FILE__, " ", @__LINE__) @@ -1688,8 +1464,170 @@ function generatequestion(a, text2textInstructLLM::Function)::String end error("generatequestion failed to generate a thought ", response) end +# function generatequestion(a, text2textInstructLLM::Function)::String -function generateSituationReport(a, text2textInstructLLM::Function)::Dict +# # systemmsg = +# # """ +# # You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. +# # Your goal is: Help the user select the best wines from your inventory that align with the user's preferences. + +# # Your responsibility includes: +# # 1) Self questioning about the current situation +# # 2) Thanks the user when they are done choosing wines and invite them to comeback next time + +# # Your responsibility does not include: +# # 1) Processing sales orders or engaging in any other sales-related activities. + +# # At each round of conversation, you will be given the current situation: +# # Your conversation with the user: ... +# # Context: ... + +# # You must follow the following guidelines: +# # - Your question should be specific, self-contained and not require any additional context. +# # - Do not generate any question or comments at the end. +# # - Once the user has selected their wine, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. + +# # You should follow the following guidelines: +# # - Focus on the latest conversation. +# # - 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. +# # - All wines in your inventory are always in stock. + +# # You should then respond to the user with: +# # 1) Reasoning: State your detailed reasoning of the current situation +# # 2) Q: Based on the current situation, ask yourself at least two, but no more than five, questions about what you need to do to achieve the goal. +# # 3) A: Based on the current situation, answer to yourself the best you can + +# # You must only respond in format as described below: +# # Reasoning: ... +# # Q 1: ... +# # A 1: ... +# # Q 2: ... +# # A 2: ... +# # Q 3: ... +# # A 3: ... +# # ... + +# # Let's begin! +# # """ + +# systemmsg = +# """ +# You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. +# Your goal includes: +# 1) Help the user select the best wines from your inventory that align with the user's preferences. +# 2) Thanks the user when they are done choosing wines and invite them to comeback next time + +# Your responsibility includes: +# 1) Ask yourself what to do about the current situation + +# Your responsibility does not include: +# 1) Processing sales orders or engaging in any other sales-related activities. + +# At each round of conversation, you will be given the current situation: +# Your conversation with the user: ... +# Context: ... + +# You must follow the following guidelines: +# - Your question should be specific, self-contained and not require any additional context. +# - Do not generate any question or comments at the end. +# - Once the user has selected their wine, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. + +# You should follow the following guidelines: +# - Focus on the latest conversation. +# - 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. +# - All wines in your inventory are always in stock. + +# You should then respond to the user with: +# 1) thought: State your reasoning about the current situation +# 2) Q: Given the situation, "ask yourself" at least three, but no more than ten, questions. +# 3) A: Given the situation, "answer to yourself" the best you can +# 4) Note: Additional info you want to say. Otherwise, "NA" + +# You must only respond in format as described below: +# thought: ... +# Q1: ... +# A1: ... +# Q2: ... +# A2: ... +# Q3: ... +# A3: ... +# ... +# Note: ... + +# Here are some examples: +# Q: The user is asking for a cappuccino. Do I have it at my cafe? +# A: No I don't. + +# Q: Since I don't have a cappuccino but I have a Late, should I ask if they are okay with that? +# A: Yes, I should. + +# Q: Are they allergic to milk? +# A: According to the situation, since they mentioned a cappuccino before, it seems they are not allergic to milk. + +# Let's begin! +# """ + +# context = +# if length(a.memory[:shortmem]) > 0 +# vectorOfDictToText(a.memory[:shortmem], withkey=false) +# else +# "" +# end +# chathistory = vectorOfDictToText(a.chathistory) +# errornote = "" +# response = nothing # store for show when error msg show up + +# for attempt in 1:10 +# usermsg = +# """ +# Your conversation with the user: $chathistory +# Recap: $(a.memory[:recap]) +# $context +# $errornote +# """ + +# _prompt = +# [ +# Dict(:name=> "system", :text=> systemmsg), +# Dict(:name=> "user", :text=> usermsg) +# ] + +# # put in model format +# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct") +# prompt *= +# """ +# <|start_header_id|>assistant<|end_header_id|> +# """ + +# try +# response = text2textInstructLLM(prompt) +# q_number = count("Q", response) +# if q_number < 3 +# error("too few questions only $q_number questions are generated ", @__FILE__, " ", @__LINE__) +# end +# # response = string(split(response, "Please")[1]) # LLM usually add comments which is no need. +# responsedict = GeneralUtils.textToDict(response, +# ["thought", "Q1", "Note"], +# rightmarker=":", symbolkey=true) +# response = "Q1: " * responsedict[:Q1] +# println("--> generatequestion ", @__FILE__, " ", @__LINE__) +# pprintln(response) +# return response +# catch e +# io = IOBuffer() +# showerror(io, e) +# errorMsg = String(take!(io)) +# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) +# println("") +# println("Attempt $attempt. Error occurred: $errorMsg\n$st") +# println("") +# end +# end +# error("generatequestion failed to generate a thought ", response) +# end + +function generateSituationReport(a, text2textInstructLLM::Function; skiprecent::Integer=0 + )::Dict # systemmsg = # """ @@ -1725,19 +1663,23 @@ function generateSituationReport(a, text2textInstructLLM::Function)::Dict event: a detailed summary for each event without exaggerated details. You must only respond in format as described below: - event_1: ... - event_2: ... + Event_1: ... + Event_2: ... ... Here are some examples: - 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 ask me about where to buy a toy. + Event_2: I told the user to go to the store at 2nd floor. Let's begin! """ + + if length(a.memory[:events]) <= skiprecent + return Dict(:recap=> "None") + end timeline = "" - for (i, event) in enumerate(a.memory[:events]) + for (i, event) in enumerate(a.memory[:events][1:end-skiprecent]) if event[:outcome] === nothing timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue])\n" else @@ -1776,9 +1718,9 @@ function generateSituationReport(a, text2textInstructLLM::Function)::Dict # println("--> generateSituationReport ", @__FILE__, " ", @__LINE__) pprintln(response) - eventcount = count("event_", response) + eventcount = count("Event_", response) - if eventcount < length(a.memory[:events]) + if eventcount < (length(a.memory[:events]) - skiprecent) errornote = "Note: You need to summarize every events." println("the summary covers $eventcount/$(length(a.memory[:events])) ", @__FILE__, " ", @__LINE__) else @@ -1787,111 +1729,158 @@ function generateSituationReport(a, text2textInstructLLM::Function)::Dict end error("generateSituationReport failed to generate a thought ", response) end -# function generateSituationReport(a, text2textInstructLLM::Function)::Dict -# # systemmsg = -# # """ -# # You are a helpful assistant in the given timeline. -# # You task is to writes a situational report on the current situation. - -# # At each round of conversation, you will be given the current situation: -# # Timeline: ... -# # Context: ... -# # You should then respond to the user with: -# # 1) report: State your detailed situational report on the current situation. -# # 2) wine_selected: Indicates whether the user selected wine. It can be the name of the selected wine or "None" + +function situationtrack(a, text2textInstructLLM::Function)::String + + systemmsg = + """ + You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. -# # You must only respond in format as described below: -# # report: ... -# # wine_selected: ... - -# # Let's begin! -# # """ - -# systemmsg = -# """ -# You are the assistant being in the given events. -# Your task is to writes a summary of each event. - -# At each round of conversation, you will be given the situation: -# Events timeline: ... -# Context: ... - -# You should then respond to the user with: -# event: a detailed summary of each event without exaggerated details. + Sommelier's general workflow includes: + 1) Greet the user and introduce yourself. + 2) Understand the user's needs. + 3) Looking for the wines that match user's needs in the database. + 4) Introduce the wines to the user. + 5) Thanks the user when they are done choosing wines and invite them to comeback next time. -# You must only respond in format as described below: -# event 1: ... -# event 2: ... -# ... + Your goal includes: + 1) Help the user select the best wines from your inventory that align with the user's preferences. + + Your responsibility includes: + 1) Ask yourself specifically about how you are progressing towards the goal. -# Here are some examples: -# 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. + At each round of conversation, you will be given the current situation: + Your conversation with the user: ... + Context: ... -# Let's begin! -# """ + You must follow the following guidelines: + - Your question should be specific, self-contained and not require any additional context. + - Do not generate any question or comments at the end. + + You should follow the following guidelines: + - Focus on the latest event. + + You should then respond to the user with: + 1) Understanding: State your understanding about the current situation + 2) Q: Given the situation, "ask yourself" at least five, but no more than ten, questions. + 3) A: Given the situation, "answer to yourself" the best you can + 4) Note: Additional info you want to say. Otherwise, "NA" + + You must only respond in format as described below: + Understanding: ... + Q1: ... + A1: ... + Q2: ... + A2: ... + Q3: ... + A3: ... + ... + Note: ... + + Here are some examples: + Q: Where am I in the workflow? + A: According to the situation, ... + + Q: Am I greeting the user and introducing myself? + A: According to the situation, no. + + Q: Did the user tell me what they like? + A: According to the situation, no. We are just starting the conversation. + + Q: What do I know about the user? + A: According to the situation, … + + Q: Have I searched the database yet? + A: According to the situation, no. I need more information. + + Q: Did I introduce the coffee blend varieties to the user yet? + A: According to the situation, no, I didn't because I have not searched the database yet. + + Q: What did I find in the database? + A: According to the situation, I found the following … + + Let's begin! + """ + + timeline = "" + for (i, event) in enumerate(a.memory[:events]) + if event[:outcome] === nothing + timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue])\n" + else + timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue]) $(event[:outcome])\n" + end + end -# timeline = "" -# for (i, event) in enumerate(a.memory[:events]) -# if event[:outcome] === nothing -# timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue])\n" -# else -# timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue]) $(event[:outcome])\n" -# end -# end + context = + if length(a.memory[:shortmem]) > 0 + vectorOfDictToText(a.memory[:shortmem], withkey=false) + else + "" + end + chathistory = vectorOfDictToText(a.chathistory) + errornote = "" + response = nothing # store for show when error msg show up + + for attempt in 1:10 + usermsg = + """ + Events timeline: $timeline + $context + $errornote + """ + + _prompt = + [ + Dict(:name=> "system", :text=> systemmsg), + Dict(:name=> "user", :text=> usermsg) + ] + + # put in model format + prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct") + prompt *= + """ + <|start_header_id|>assistant<|end_header_id|> + """ + + try + response = text2textInstructLLM(prompt) + q_number = count("Q", response) + if q_number < 3 + error("too few questions only $q_number questions are generated ", @__FILE__, " ", @__LINE__) + end + # response = string(split(response, "Please")[1]) # LLM usually add comments which is no need. + responsedict = GeneralUtils.textToDict(response, + ["Understanding", "Q1", "Note"], + rightmarker=":", symbolkey=true) + response = "Q1: " * responsedict[:Q1] + println("--> situationtrack ", @__FILE__, " ", @__LINE__) + pprintln(Dict(responsedict)) + return response + catch e + io = IOBuffer() + showerror(io, e) + errorMsg = String(take!(io)) + st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) + println("") + println("Attempt $attempt. Error occurred: $errorMsg\n$st") + println("") + end + end + error("situationtrack failed to generate a thought ", response) +end + + + + -# errornote = "" -# response = nothing # store for show when error msg show up -# for attempt in 1:10 -# usermsg = -# """ -# Events timeline: $timeline -# $errornote -# """ -# _prompt = -# [ -# Dict(:name=> "system", :text=> systemmsg), -# Dict(:name=> "user", :text=> usermsg) -# ] -# # put in model format -# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct") -# prompt *= -# """ -# <|start_header_id|>assistant<|end_header_id|> -# """ -# try -# response = text2textInstructLLM(prompt) -# # responsedict = GeneralUtils.textToDict(response, -# # ["summary", "presented", "selected"], -# # rightmarker=":", symbolkey=true) -# # println("--> generateSituationReport ", @__FILE__, " ", @__LINE__) -# pprintln(response) -# eventcount = count("event", response) -# if eventcount < length(a.memory[:events]) -# error("the summary is missing some events ", @__FILE__, " ", @__LINE__) -# end - -# return Dict(:recap=>response) -# catch e -# io = IOBuffer() -# showerror(io, e) -# errorMsg = String(take!(io)) -# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) -# println("") -# println("Attempt $attempt. Error occurred: $errorMsg\n$st") -# println("") -# end -# end -# error("generateSituationReport failed to generate a thought ", response) -# end @@ -2071,9 +2060,6 @@ end - - - diff --git a/src/llmfunction.jl b/src/llmfunction.jl index 0a39f7e..c5613c7 100644 --- a/src/llmfunction.jl +++ b/src/llmfunction.jl @@ -307,6 +307,9 @@ function checkinventory(a::T1, input::T2 ) ) + println("checkinventory result ", @__FILE__, " ", @__LINE__) + println(result) + return (result=result, success=true, errormsg=nothing) end @@ -355,7 +358,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2< - occasion: ... - food_to_be_paired_with_wine: food that the user will be served with the wine - region: a region in a country where the wine is produced, such as Burgundy, Napa Valley, etc - - country: a country where the wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "UnitedStates" + - country: a country where the wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "United States" - grape_variety: the name of the primary grape used to make the wine - flavors: names of items that the wine tastes like, such as citrus, lime, etc - aromas: the wine's aromas, such as fruity, floral, etc diff --git a/test/runtest.jl b/test/runtest.jl index 5ed599f..1d044a7 100644 --- a/test/runtest.jl +++ b/test/runtest.jl @@ -84,7 +84,7 @@ end main() """ - I'm joining a graduation party this evening. I want a bottle of full bodied, dry white wine from the US. I'm ok with any price range. + I'm joining a graduation party this evening. I want a bottle of dry white wine from the US. I'm ok with any price range. Well, the party is small casual with close friends and no food serving. I'm open to suggestion since I have no specific idea about wine. I'm ok with any region.