diff --git a/src/interface.jl b/src/interface.jl index 0eb4b1a..609734d 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -1,7 +1,7 @@ module interface - + export addNewMessage, conversation, decisionMaker, evaluator, reflector, generatechat, - generalconversation + generalconversation using JSON3, DataStructures, Dates, UUIDs, HTTP, Random, MQTTClient, PrettyPrinting, Serialization using GeneralUtils, LLMMCTS @@ -37,11 +37,11 @@ using ..type, ..util, ..llmfunction macro executeStringFunction(functionStr, args...) # Parse the function string into an expression func_expr = Meta.parse(functionStr) - + # Create a new function with the parsed expression - function_to_call = eval(Expr(:function, - Expr(:call, func_expr, args...), func_expr.args[2:end]...)) - + function_to_call = eval(Expr(:function, + Expr(:call, func_expr, args...), func_expr.args[2:end]...)) + # Call the newly created function with the provided arguments function_to_call(args...) end @@ -96,7 +96,7 @@ julia> output_thoughtDict = Dict( # Signature """ -function decisionMaker(a::T; recent::Integer=5)::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")) @@ -128,7 +128,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age # 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 @@ -138,20 +138,20 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age # 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.", @@ -258,6 +258,10 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age You should follow the following guidelines: - Identifying at least four preferences before checking inventory significantly improves search results + - 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. + + For your information: + - vintage 0 means non-vintage. You should then respond to the user with interleaving Understanding, Reasoning, Plan, Action: 1) Understanding: @@ -269,7 +273,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age - CHATBOX which you can use to talk 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. + - PRESENTBOX which you can use to introduce / suggest / recommend wine label you just found in the inventory to the user. Not for general conversation nor follow up conversation. The input is instructions on how you want the presentation to be conducted. Here are some input examples, "First, provide detailed introductions of Zena Crown, Schrader Cabernet Sauvignon. @@ -277,7 +281,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age Third, explain the potential impact each option could bring to the user." - 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" + 5) Mentioning_wine: Are you mentioning specific wine label or winery to the user? Can be "Yes" or "No" You should only respond in format as described below: Understanding: ... @@ -289,16 +293,16 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age Let's begin! """ - + totalevents = length(a.memory[:events]) ind = if totalevents > recent - start = 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 @@ -316,33 +320,31 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age errornote = "" response = nothing # placeholder for show when error msg show up - for attempt in 1:10 - usermsg = - """ - Recap: $(a.memory[:recap]) - Your recent events: $timeline - Your Q&A: $(a.memory[:QandA]) - $errornote - """ + for attempt in 1:10 + usermsg = """ + Recap: $(a.memory[:recap]) + Your recent events: $timeline + Your Q&A: $(a.memory[:QandA]) + $errornote + """ - _prompt = - [ - Dict(:name=> "system", :text=> systemmsg), - Dict(:name=> "user", :text=> usermsg) - ] + _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|> - """ + prompt *= """ + <|start_header_id|>assistant<|end_header_id|> + """ try response = a.text2textInstructLLM(prompt) responsedict = GeneralUtils.textToDict(response, - ["Understanding", "Reasoning", "Plan", "Action_name", "Action_input", "Mentioning_wine"], - rightmarker=":", symbolkey=true, lowercasekey=true) + ["Understanding", "Reasoning", "Plan", "Action_name", "Action_input", "Mentioning_wine"], + rightmarker=":", symbolkey=true, lowercasekey=true) if responsedict[:action_name] ∉ ["CHATBOX", "PRESENTBOX", "CHECKINVENTORY", "ENDCONVERSATION"] errornote = "You must use the given functions" @@ -368,16 +370,16 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age # check if LLM recommend wine before checking inventory isMemEmpty = isempty(a.memory[:shortmem]) - 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" + 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") + 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" + + 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") else errornote = "" end @@ -419,80 +421,77 @@ julia> # Signature """ function evaluator(config::T1, state::T2 - )::Tuple{String, Integer} where {T1<:AbstractDict, T2<:AbstractDict} - - systemmsg = - """ - Analyze the trajectories of a solution to a question answering task. The trajectories are - labeled by environmental observations about the situation, thoughts that can reason about - the current situation and actions that can be three types: - 1) CHECKINVENTORY[query], which you can use to find wine in your inventory. - 2) CHATBOX[text], which you can use to interact with the user. +)::Tuple{String,Integer} where {T1<:AbstractDict,T2<:AbstractDict} - Given a question and a trajectory, evaluate its correctness and provide your reasoning and - analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories - can be correct if the thoughts and actions so far are correct, even if the answer is not found - yet. Do not generate additional thoughts or actions. Then ending with the correctness score s - where s is an integer from 0 to 10. + systemmsg = """ + Analyze the trajectories of a solution to a question answering task. The trajectories are + labeled by environmental observations about the situation, thoughts that can reason about + the current situation and actions that can be three types: + 1) CHECKINVENTORY[query], which you can use to find wine in your inventory. + 2) CHATBOX[text], which you can use to interact with the user. - You should only respond in JSON format as describe below: - {"evaluation": "your evaluation", "score": "your evaluation score"} + Given a question and a trajectory, evaluate its correctness and provide your reasoning and + analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories + can be correct if the thoughts and actions so far are correct, even if the answer is not found + yet. Do not generate additional thoughts or actions. Then ending with the correctness score s + where s is an integer from 0 to 10. - Here are some examples: - user: - { - "question": "I'm looking for a sedan with an automatic driving feature.", - "thought_1": "I have many types of sedans in my inventory, each with diverse features.", - "thought_2": "But there is only 1 model that has the feature customer wanted.", - "thought_3": "I should check our inventory first to see if we have it.", - "action_1": {"name": "inventory", "input": "Yiem model A"}, - "observation_1": "Yiem model A is in stock." - } - assistant - { - "evaluation": "This trajectory is correct as it is reasonable to check an inventory for info provided in the question. - It is also better to have simple searches corresponding to a single entity, making this the best action.", - "score": 10 - } + You should only respond in JSON format as describe below: + {"evaluation": "your evaluation", "score": "your evaluation score"} - user: - { - "question": "Do you have an all-in-one pen with 4 colors and a pencil for sale?", - "thought_1": "Let me check our inventory first to see if I have it.", - "action_1": {"name": "inventory", "input": "pen with 4 color and a pencil."}, - "observation_1": "I found {1: "Pilot Dr. grip 4-in-1 pen", 2: "Rotting pencil"}", - "thought_2": "Ok, I have what the user is asking. Let's tell the user.", - "action_2": {"name": "CHATBOX", "input": "Yes, we do have a Pilot Dr. grip 4-in-1 pen and a Rotting pencil"}, - "observation_1": "This is not what I wanted." - } - assistant: - { - "evaluation": "This trajectory is incorrect as my search term should be related to a 4-colors pen with a pencil in it, - not a pen and a pencil seperately. A better search term should have been a 4-colors pen with a pencil, all-in-one.", - "score": 0 - } + Here are some examples: + user: + { + "question": "I'm looking for a sedan with an automatic driving feature.", + "thought_1": "I have many types of sedans in my inventory, each with diverse features.", + "thought_2": "But there is only 1 model that has the feature customer wanted.", + "thought_3": "I should check our inventory first to see if we have it.", + "action_1": {"name": "inventory", "input": "Yiem model A"}, + "observation_1": "Yiem model A is in stock." + } + assistant + { + "evaluation": "This trajectory is correct as it is reasonable to check an inventory for info provided in the question. + It is also better to have simple searches corresponding to a single entity, making this the best action.", + "score": 10 + } - Let's begin! - """ + user: + { + "question": "Do you have an all-in-one pen with 4 colors and a pencil for sale?", + "thought_1": "Let me check our inventory first to see if I have it.", + "action_1": {"name": "inventory", "input": "pen with 4 color and a pencil."}, + "observation_1": "I found {1: "Pilot Dr. grip 4-in-1 pen", 2: "Rotting pencil"}", + "thought_2": "Ok, I have what the user is asking. Let's tell the user.", + "action_2": {"name": "CHATBOX", "input": "Yes, we do have a Pilot Dr. grip 4-in-1 pen and a Rotting pencil"}, + "observation_1": "This is not what I wanted." + } + assistant: + { + "evaluation": "This trajectory is incorrect as my search term should be related to a 4-colors pen with a pencil in it, + not a pen and a pencil seperately. A better search term should have been a 4-colors pen with a pencil, all-in-one.", + "score": 0 + } - usermsg = - """ - $(JSON3.write(state[:thoughtHistory])) - """ + Let's begin! + """ - chathistory = - [ - Dict(:name=> "system", :text=> systemmsg), - Dict(:name=> "user", :text=> usermsg) - ] + usermsg = """ + $(JSON3.write(state[:thoughtHistory])) + """ + + chathistory = + [ + Dict(:name => "system", :text => systemmsg), + Dict(:name => "user", :text => usermsg) + ] # put in model format prompt = formatLLMtext(chathistory, "llama3instruct") - prompt *= - """ - <|start_header_id|>assistant<|end_header_id|> - { - """ + prompt *= """ + <|start_header_id|>assistant<|end_header_id|> + { + """ pprint(prompt) externalService = config[:externalservice][:text2textinstruct] @@ -503,35 +502,34 @@ function evaluator(config::T1, state::T2 msgMeta = GeneralUtils.generate_msgMeta( externalService[:mqtttopic], - senderName= "evaluator", - senderId= string(uuid4()), - receiverName= "text2textinstruct", - mqttBroker= config[:mqttServerInfo][:broker], - mqttBrokerPort= config[:mqttServerInfo][:port], + senderName="evaluator", + 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|>"], - ) + :msgMeta => msgMeta, + :payload => Dict( + :text => prompt, + :kwargs => Dict( + :max_tokens => 512, + :stop => ["<|eot_id|>"], ) ) + ) for attempt in 1:5 try response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg) _responseJsonStr = response[:response][:text] - expectedJsonExample = - """ - Here is an expected JSON format: - {"evaluation": "...", "score": "..."} - """ + expectedJsonExample = """ + Here is an expected JSON format: + {"evaluation": "...", "score": "..."} + """ responseJsonStr = jsoncorrection(config, _responseJsonStr, expectedJsonExample) - evaluationDict = copy(JSON3.read(responseJsonStr)) + evaluationDict = copy(JSON3.read(responseJsonStr)) # check if dict has all required value dummya::AbstractString = evaluationDict[:evaluation] @@ -568,102 +566,100 @@ julia> # Signature """ -function reflector(config::T1, state::T2)::String where {T1<:AbstractDict, T2<:AbstractDict} +function reflector(config::T1, state::T2)::String where {T1<:AbstractDict,T2<:AbstractDict} # https://github.com/andyz245/LanguageAgentTreeSearch/blob/main/hotpot/hotpot.py - _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 will be given a question and a trajectory of the previous help you've done for a user. - You were unsuccessful in helping the user either because you guessed the wrong answer with Finish[answer], or you didn't know the user enough. - In a few sentences, Diagnose a possible reason for failure and devise a new, concise, high level plan that aims to mitigate the same failure. - Use complete sentences. + _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 will be given a question and a trajectory of the previous help you've done for a user. + You were unsuccessful in helping the user either because you guessed the wrong answer with Finish[answer], or you didn't know the user enough. + In a few sentences, Diagnose a possible reason for failure and devise a new, concise, high level plan that aims to mitigate the same failure. + Use complete sentences. - You should only respond in JSON format as describe below: - {"reflection": "your relection"} - - Here are some examples: - Previous Trial: - { - "question": "Hello, I would like a get a bottle of wine", - "thought_1": "A customer wants to buy a bottle of wine. Before making a recommendation, I need to know more about their preferences.", - "action_1": {"name": "CHATBOX", "input": "What is the occasion for which you're buying this wine?"}, - "observation_1": "We are holding a wedding party", + You should only respond in JSON format as describe below: + {"reflection": "your relection"} - "thought_2": "A wedding party, that's a great occasion! The customer might be looking for a celebratory drink. Let me ask some more questions to narrow down the options.", - "action_2": {"name": "CHATBOX", "input": "What type of food will you be serving at the wedding?"}, - "observation_2": "It will be Thai dishes.", + Here are some examples: + Previous Trial: + { + "question": "Hello, I would like a get a bottle of wine", + "thought_1": "A customer wants to buy a bottle of wine. Before making a recommendation, I need to know more about their preferences.", + "action_1": {"name": "CHATBOX", "input": "What is the occasion for which you're buying this wine?"}, + "observation_1": "We are holding a wedding party", - "thought_3": "With Thai food, I should recommend a wine that complements its spicy and savory flavors. And since it's a celebratory occasion, the customer might prefer a full-bodied wine.", - "action_3": {"name": "CHATBOX", "input": "What is your budget for this bottle of wine?"}, - "observation_3": "I would spend up to 50 bucks.", + "thought_2": "A wedding party, that's a great occasion! The customer might be looking for a celebratory drink. Let me ask some more questions to narrow down the options.", + "action_2": {"name": "CHATBOX", "input": "What type of food will you be serving at the wedding?"}, + "observation_2": "It will be Thai dishes.", - "thought_4": "Now that I have some more information, it's time to narrow down the options.", - "action_4": {"name": "winestock", "input": "red wine with full body, pairs well with spicy food, budget \$50"}, - "observation_4": "I found the following wines in our stock: \n{\n 1: El Enemigo Cabernet Franc 2019\n2: Tantara Chardonnay 2017\n\n}\n", + "thought_3": "With Thai food, I should recommend a wine that complements its spicy and savory flavors. And since it's a celebratory occasion, the customer might prefer a full-bodied wine.", + "action_3": {"name": "CHATBOX", "input": "What is your budget for this bottle of wine?"}, + "observation_3": "I would spend up to 50 bucks.", - "thought_5": "Now that I have a list of potential wines, I need to know more about the customer's taste preferences.", - "action_5": {"name": "CHATBOX", "input": "What type of wine characteristics are you looking for? (e.g. t.e.g. tannin level, sweetness, intensity, acidity)"}, - "observation_5": "I like full-bodied red wine with low tannin.", + "thought_4": "Now that I have some more information, it's time to narrow down the options.", + "action_4": {"name": "winestock", "input": "red wine with full body, pairs well with spicy food, budget \$50"}, + "observation_4": "I found the following wines in our stock: \n{\n 1: El Enemigo Cabernet Franc 2019\n2: Tantara Chardonnay 2017\n\n}\n", - "thought_6": "Now that I have more information about the customer's preferences, it's time to make a recommendation.", - "action_6": {"name": "recommendbox", "input": "El Enemigo Cabernet Franc 2019"}, - "observation_6": "I don't like the one you recommend. I want dry wine." - } + "thought_5": "Now that I have a list of potential wines, I need to know more about the customer's taste preferences.", + "action_5": {"name": "CHATBOX", "input": "What type of wine characteristics are you looking for? (e.g. t.e.g. tannin level, sweetness, intensity, acidity)"}, + "observation_5": "I like full-bodied red wine with low tannin.", - { - "reflection": "I asked the user about the occasion, food type, and budget, and then searched for wine in the inventory right away. However, I should have asked the user for the specific wine type and their preferences in order to gather more information before making a recommendation." - } + "thought_6": "Now that I have more information about the customer's preferences, it's time to make a recommendation.", + "action_6": {"name": "recommendbox", "input": "El Enemigo Cabernet Franc 2019"}, + "observation_6": "I don't like the one you recommend. I want dry wine." + } - Let's begin! + { + "reflection": "I asked the user about the occasion, food type, and budget, and then searched for wine in the inventory right away. However, I should have asked the user for the specific wine type and their preferences in order to gather more information before making a recommendation." + } - Previous trial: - $(JSON3.write(state[:thoughtHistory])) - {"reflection" - """ + Let's begin! + + Previous trial: + $(JSON3.write(state[:thoughtHistory])) + {"reflection" + """ # 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 + prompt = + if llminfo[:name] == "llama3instruct" + formatLLMtext_llama3instruct("system", _prompt) + else + error("llm model name is not defied yet $(@__LINE__)") + end msgMeta = GeneralUtils.generate_msgMeta( - a.config[:externalservice][:text2textinstruct][:mqtttopic], - senderName= "reflector", - senderId= string(uuid4()), - receiverName= "text2textinstruct", - mqttBroker= config[:mqttServerInfo][:broker], - mqttBrokerPort= config[:mqttServerInfo][:port], + a.config[:externalservice][:text2textinstruct][:mqtttopic], + senderName="reflector", + 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|>"], - ) + :msgMeta => msgMeta, + :payload => Dict( + :text => prompt, + :kwargs => Dict( + :max_tokens => 512, + :stop => ["<|eot_id|>"], ) ) + ) for attempt in 1:5 try response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg) _responseJsonStr = response[:response][:text] - expectedJsonExample = - """ - Here is an expected JSON format: - {"reflection": "..."} - """ + expectedJsonExample = """ + Here is an expected JSON format: + {"reflection": "..."} + """ responseJsonStr = jsoncorrection(config, _responseJsonStr, expectedJsonExample) - reflectionDict = copy(JSON3.read(responseJsonStr)) + reflectionDict = copy(JSON3.read(responseJsonStr)) # check if dict has all required value dummya::AbstractString = reflectionDict[:reflection] @@ -689,7 +685,7 @@ end # # Arguments # `a::agent` # an agent - + # # Return # None @@ -810,7 +806,7 @@ end function generalconversation(a::T, userinput::Dict) where {T<:agent} - + end @@ -881,12 +877,12 @@ function conversation(a::sommelier, userinput::Dict) addNewMessage(a, "user", userinput[:text]) # add user activity to events memory - push!(a.memory[:events], + push!(a.memory[:events], eventdict(; - event_description= "the user talks to the assistant.", - timestamp= Dates.now(), - subject= "user", - action_or_dialogue= userinput[:text], + event_description="the user talks to the assistant.", + timestamp=Dates.now(), + subject="user", + action_or_dialogue=userinput[:text], ) ) @@ -903,12 +899,12 @@ function conversation(a::sommelier, userinput::Dict) addNewMessage(a, "assistant", chatresponse) - push!(a.memory[:events], - eventdict(; - event_description= "the assistant talks to the user.", - timestamp= Dates.now(), - subject= "assistant", - action_or_dialogue= chatresponse, + push!(a.memory[:events], + eventdict(; + event_description="the assistant talks to the user.", + timestamp=Dates.now(), + subject="assistant", + action_or_dialogue=chatresponse, ) ) return chatresponse @@ -926,24 +922,24 @@ function conversation(a::companion, userinput::Dict) addNewMessage(a, "user", userinput[:text]) # add user activity to events memory - push!(a.memory[:events], + push!(a.memory[:events], eventdict(; - event_description= "the user talks to the assistant.", - timestamp= Dates.now(), - subject= "user", - action_or_dialogue= userinput[:text], + event_description="the user talks to the assistant.", + timestamp=Dates.now(), + subject="user", + action_or_dialogue=userinput[:text], ) ) chatresponse = generatechat(a) addNewMessage(a, "assistant", chatresponse) - push!(a.memory[:events], - eventdict(; - event_description= "the assistant talks to the user.", - timestamp= Dates.now(), - subject= "assistant", - action_or_dialogue= chatresponse, + push!(a.memory[:events], + eventdict(; + event_description="the assistant talks to the user.", + timestamp=Dates.now(), + subject="assistant", + action_or_dialogue=chatresponse, ) ) return chatresponse @@ -966,11 +962,11 @@ julia> # Signature """ -function think(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}} where {T<:agent} - +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) - + thoughtDict = decisionMaker(a; recent=5) actionname = thoughtDict[:action_name] @@ -1000,20 +996,20 @@ function think(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}} else error("undefined LLM function. Requesting $actionname") end - + # this section allow LLM functions above to have different return values. result = haskey(response, :result) ? response[:result] : nothing select = haskey(response, :select) ? response[:select] : nothing reward::Integer = haskey(response, :reward) ? response[:reward] : 0 isterminal::Bool = haskey(response, :isterminal) ? response[:isterminal] : false - errormsg::Union{AbstractString, Nothing} = haskey(response, :errormsg) ? response[:errormsg] : nothing + errormsg::Union{AbstractString,Nothing} = haskey(response, :errormsg) ? response[:errormsg] : nothing success::Bool = haskey(response, :success) ? response[:success] : false # manage memory (pass msg to generatechat) if actionname == "CHATBOX" a.memory[:CHATBOX] = result elseif actionname == "CHECKINVENTORY" - push!(a.memory[:shortmem], Dict(Symbol(actionname)=> result)) + push!(a.memory[:shortmem], Dict(Symbol(actionname) => result)) elseif actionname == "PRESENTBOX" # tell the generatechat() a.memory[:CHATBOX] = result elseif actionname == "ENDCONVERSATION" @@ -1048,80 +1044,77 @@ julia> # Signature """ function generatechat(a::sommelier) - systemmsg = - """ - Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. - You are currently talking with the user. - 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) Given the situation, convey your thoughts to the user. + systemmsg = """ + Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. + You are currently talking with the user. + Your goal includes: + 1) Help the user select the best wines from your inventory that align with the user's preferences. - Your responsibility do not include: - 1) Asking or guiding the user to make a purchase - 2) Processing sales orders or engaging in any other sales-related activities + 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 ongoing conversation with the user: ... - Your thoughts: Your current thoughts in your mind - Context: ... + Your responsibility do not include: + 1) Asking or guiding the user to make a purchase + 2) Processing sales orders or engaging in any other sales-related activities - You MUST follow the following guidelines: - - Do not offer additional services you didn't thought. + At each round of conversation, you will be given the current situation: + Your ongoing conversation with the user: ... + Your thoughts: Your current thoughts in your mind + Context: ... - You should follow the following guidelines: - - Focus on the latest conversation. - - If the user interrupts, prioritize the user + You MUST follow the following guidelines: + - Do not offer additional services you didn't thought. - You should then respond to the user with: - 1) Mentioning_wine: Are you going to mentioning specific wine name to the user? Can be "Yes" or "No" - 2) Chat: Given the situation, what would you say to the user? + You should follow the following guidelines: + - Focus on the latest conversation. + - If the user interrupts, prioritize the user - You should only respond in format as described below: - Mentioning_wine: ... - Chat: ... + You should then respond to the user with: + 1) Mentioning_wine: Are you going to mentioning specific wine label or winery to the user? Can be "Yes" or "No" + 2) Chat: Given the situation, what would you say to the user? - Let's begin! - """ + You should only respond in format as described below: + Mentioning_wine: ... + Chat: ... - context = - if length(a.memory[:shortmem]) > 0 - vectorOfDictToText(a.memory[:shortmem], withkey=false) - else - "" - end + 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 # placeholder for show when error msg show up for attempt in 1:10 - usermsg = - """ - Your ongoing conversation with the user: $chathistory - $context - Your thoughts: $(a.memory[:CHATBOX]) - $errornote - """ + usermsg = """ + Your ongoing conversation with the user: $chathistory + $context + Your thoughts: $(a.memory[:CHATBOX]) + $errornote + """ - _prompt = - [ - Dict(:name=> "system", :text=> systemmsg), - Dict(:name=> "user", :text=> usermsg) - ] + _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|> - """ + prompt *= """ + <|start_header_id|>assistant<|end_header_id|> + """ try response = a.text2textInstructLLM(prompt) - responsedict = GeneralUtils.textToDict(response,["Mentioning_wine", "Chat"], - rightmarker=":", symbolkey=true, lowercasekey=true) + responsedict = GeneralUtils.textToDict(response, ["Mentioning_wine", "Chat"], + rightmarker=":", symbolkey=true, lowercasekey=true) for i ∈ [:chat] if length(JSON3.write(responsedict[i])) == 0 @@ -1149,12 +1142,12 @@ function generatechat(a::sommelier) isMemEmpty = isempty(a.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") + error("You must check your inventory before recommending wine") elseif occursin("(check", responsedict[:chat]) || occursin("*check", responsedict[:chat]) || - occursin("inventory)", responsedict[:chat]) || occursin("inventory*", responsedict[:chat]) + occursin("inventory)", responsedict[:chat]) || occursin("inventory*", responsedict[:chat]) 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") + error("You must check your inventory before recommending wine") else errornote = "" end @@ -1163,7 +1156,7 @@ function generatechat(a::sommelier) delete!(responsedict, :mentioning_wine) result = responsedict[:chat] - return result + return result catch e io = IOBuffer() showerror(io, e) @@ -1176,60 +1169,57 @@ function generatechat(a::sommelier) end function generatechat(a::companion) - systemmsg = - """ - Your name is $(a.name). You are a helpful assistant. - You are currently talking with the user. - Your goal includes: - 1) Help the user as best as you can - - Your responsibility includes: - 1) Given the situation, help the user. + systemmsg = """ + Your name is $(a.name). You are a helpful assistant. + You are currently talking with the user. + Your goal includes: + 1) Help the user as best as you can - At each round of conversation, you will be given the current situation: - Your ongoing conversation with the user: ... - Context: ... + Your responsibility includes: + 1) Given the situation, help the user. - You should then respond to the user with: - 1) Chat: Given the situation, what would you say to the user? + At each round of conversation, you will be given the current situation: + Your ongoing conversation with the user: ... + Context: ... - You should only respond in format as described below: - Chat: ... + You should then respond to the user with: + 1) Chat: Given the situation, what would you say to the user? - Let's begin! - """ + You should only respond in format as described below: + Chat: ... + + Let's begin! + """ chathistory = vectorOfDictToText(a.chathistory) response = nothing # placeholder for show when error msg show up noise = "" for attempt in 1:10 - usermsg = - """ - Your ongoing conversation with the user: $chathistory - $noise - """ + usermsg = """ + Your ongoing conversation with the user: $chathistory + $noise + """ - _prompt = - [ - Dict(:name=> "system", :text=> systemmsg), - Dict(:name=> "user", :text=> usermsg) - ] + _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|> - """ + prompt *= """ + <|start_header_id|>assistant<|end_header_id|> + """ try response = a.text2textInstructLLM(prompt) println("\n~~~ generatechat() ", @__FILE__, " ", @__LINE__) pprintln(response) - responsedict = GeneralUtils.textToDict(response,["Chat"], - rightmarker=":", symbolkey=true, lowercasekey=true) + responsedict = GeneralUtils.textToDict(response, ["Chat"], + rightmarker=":", symbolkey=true, lowercasekey=true) result = responsedict[:chat] @@ -1253,7 +1243,7 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St # """ # 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 @@ -1293,88 +1283,99 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St # Let's begin! # """ - systemmsg = - """ - Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for $(a.retailername)'s online 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 don't need any further assistance and invite them to comeback next time + systemmsg = """ + Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for $(a.retailername)'s online 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 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 + 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. + 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 recent events: ... - Context: ... + At each round of conversation, you will be given the current situation: + Your recent events: ... + 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 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 - - 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 follow the following guidelines: + - Focus on the latest conversation + - 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: - 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 + 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 - You must only respond in format as described below: - Understanding: ... - Q1: ... - A1: ... - Q2: ... - A2: ... - Q3: ... - A3: ... - ... + You must only respond in format as described below: + Understanding: ... + Q1: ... + A1: ... + Q2: ... + A2: ... + Q3: ... + A3: ... + ... - Here are some examples: - Q: The user is asking for a cappuccino. Do I have it at my cafe? - A: No I don't. + Here are some examples: + Q: Why the user saying this? + A: According to the situation, ... - 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: 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 inventory yet? + A: According to the situation, no. I need more information. - 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: Should I check the inventory now? + A: According to the situation, ... - Q: Have I searched the database yet? - A: According to the situation, no. I need more information. + Q: What do I have in the inventory? + A: According to the situation, ... - Q: Did I found something in the database? - A: According to the situation, ... + Q: Which items are within the user price range? And which items are out of the user price rance? + A: According to the situation, ... - Q: Which items are within the user price range? And which items are out of the user price rance? - A: According to the situation, ... + Q: Do I have what the user wants in stock? + 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. + 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 inventory yet. - Let's begin! - """ + Q: Am I certain about the information I'm going to share with the user, or should I verify the information first? + A: According to the situation, ... + + Let's begin! + """ totalevents = length(a.memory[:events]) ind = if totalevents > recent - start = 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 @@ -1387,39 +1388,37 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St response = nothing # store for show when error msg show up for attempt in 1:10 - usermsg = - """ - Recap: $(a.memory[:recap]) - Your recent events: $timeline - $errornote - """ + usermsg = """ + Recap: $(a.memory[:recap]) + Your recent events: $timeline + $errornote + """ - _prompt = - [ - Dict(:name=> "system", :text=> systemmsg), - Dict(:name=> "user", :text=> usermsg) - ] + _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|> - """ + 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__) - # check whether "A1" is in the response, if not error. - elseif !occursin("A1", response) + # check whether "A1" is in the response, if not error. + elseif !occursin("A1:", response) error("no answer found in the response ", @__FILE__, " ", @__LINE__) end - # response = string(split(response, "Please")[1]) # LLM usually add comments which is no need. + responsedict = GeneralUtils.textToDict(response, - ["Understanding", "Q1"], - rightmarker=":", symbolkey=true, lowercasekey=true) + ["Understanding", "Q1"], + rightmarker=":", symbolkey=true, lowercasekey=true) response = "Q1: " * responsedict[:q1] println("\n~~~ generatequestion ", @__FILE__, " ", @__LINE__) pprintln(response) @@ -1429,7 +1428,7 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St showerror(io, e) errorMsg = String(take!(io)) st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) - println("\n Attempt $attempt. Error occurred: $errorMsg\n$st ", @__FILE__, " ", @__LINE__) + println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", @__FILE__, " ", @__LINE__) end end error("generatequestion failed to generate a thought ", response) @@ -1440,7 +1439,7 @@ end # # """ # # 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 @@ -1536,7 +1535,7 @@ end # Let's begin! # """ - + # context = # if length(a.memory[:shortmem]) > 0 # vectorOfDictToText(a.memory[:shortmem], withkey=false) @@ -1597,13 +1596,13 @@ end # end function generateSituationReport(a, text2textInstructLLM::Function; skiprecent::Integer=0 - )::Dict +)::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: ... @@ -1611,7 +1610,7 @@ function generateSituationReport(a, text2textInstructLLM::Function; skiprecent:: # 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" - + # You must only respond in format as described below: # report: ... # wine_selected: ... @@ -1619,37 +1618,36 @@ function generateSituationReport(a, text2textInstructLLM::Function; skiprecent:: # Let's begin! # """ - systemmsg = - """ - You are the assistant being in the given events. - Your task is to writes a summary for each event in an ongoing, interleaving series. - - At each round of conversation, you will be given the situation: - Total events: number of events you need to summarize. - Events timeline: ... - Context: ... + systemmsg = """ + You are the assistant being in the given events. + Your task is to writes a summary for each event in an ongoing, interleaving series. + + At each round of conversation, you will be given the situation: + Total events: number of events you need to summarize. + Events timeline: ... + Context: ... - You should then respond to the user with: - event: a detailed summary for each event without exaggerated details. - - You must only respond in format as described below: - Event_1: ... - Event_2: ... - ... + You should then respond to the user with: + event: a detailed summary for each event without exaggerated details. + + You must only respond in format as described below: + 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. + 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. - Let's begin! - """ + Let's begin! + """ if length(a.memory[:events]) <= skiprecent - return Dict(:recap=> "None") + return Dict(:recap => "None") end events = deepcopy(a.memory[:events][1:end-skiprecent]) - + timeline = "" for (i, event) in enumerate(events) if event[:outcome] === nothing @@ -1663,33 +1661,32 @@ function generateSituationReport(a, text2textInstructLLM::Function; skiprecent:: response = nothing # store for show when error msg show up for attempt in 1:10 - usermsg = - """ - Total events: $(length(events)) - Events timeline: $timeline - $errornote - """ + usermsg = """ + Total events: $(length(events)) + Events timeline: $timeline + $errornote + """ - _prompt = - [ - Dict(:name=> "system", :text=> systemmsg), - Dict(:name=> "user", :text=> usermsg) - ] + _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|> - """ + prompt *= """ + <|start_header_id|>assistant<|end_header_id|> + """ response = text2textInstructLLM(prompt) # responsedict = GeneralUtils.textToDict(response, # ["summary", "presented", "selected"], # rightmarker=":", symbolkey=true) - # println("--> generateSituationReport ", @__FILE__, " ", @__LINE__) + println("\n~~~ generateSituationReport() ", @__FILE__, " ", @__LINE__) pprintln(response) + eventcount = count("Event_", response) # if eventcount < (length(events)) @@ -1698,7 +1695,7 @@ function generateSituationReport(a, text2textInstructLLM::Function; skiprecent:: # else # return Dict(:recap=> response) # end - return Dict(:recap=> response) + return Dict(:recap => response) end error("generateSituationReport failed to generate a thought ", response) end @@ -1710,17 +1707,17 @@ end # systemmsg = # """ # You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. - + # 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. - + # 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. @@ -1785,7 +1782,7 @@ end # 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) @@ -1881,7 +1878,7 @@ end # # Signature # """ # function comparer(a::T1, state::T2)::Tuple{String, Integer} where {T1<:agent, T2<:AbstractDict} - + # _prompt = # """ # Analyze the trajectories of a solution to a question answering task. The trajectories are