From a5dc2c11228dbd2b03c7bbea05df938bb4d6a24a Mon Sep 17 00:00:00 2001 From: narawat lamaiin Date: Tue, 20 Aug 2024 20:19:06 +0700 Subject: [PATCH] update --- src/interface.jl | 180 +++++++++++++++++++++++++++++++++++++++------ src/llmfunction.jl | 3 +- test/runtest.jl | 19 +++-- 3 files changed, 174 insertions(+), 28 deletions(-) diff --git a/src/interface.jl b/src/interface.jl index e5d62d4..fabe831 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -246,16 +246,16 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} - 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 intention for the talk. Be specific. + - 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 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. + - 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 details of the action + 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: ... + thought: Let's think step by step. In order to ... plan: ... action_name: ... action_input: ... @@ -339,6 +339,11 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} 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" + 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 @@ -1094,8 +1099,8 @@ function think(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}} checkinventory(a, actioninput) elseif actionname == "PRESENTBOX" x = """ - 1) Present $actioninput for the user to choose: " - 2) compare each option against the others and explain why each one is a suitable match for the user's specific needs. + 1) Introduce $actioninput in details for the user to choose." + 2) compare each option against the others in details and explain why each one is a suitable match for the user's specific needs. """ (result=x, errormsg=nothing, success=true) elseif actionname == "ENDCONVERSATION" @@ -1283,11 +1288,14 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F 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 + 1) Given the situation, convey your thoughts to the user. + + 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 thoughts: Your current thoughts in your mind + Your thoughts: Your current thoughts in your mind. Context: ... You MUST follow the following guidelines: @@ -1297,12 +1305,14 @@ 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, convey your thoughts to the user + 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 You should only respond in format as described below: chat: ... mentioning_wine: ... + note: ... Let's begin! """ @@ -1342,7 +1352,7 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F try response = text2textInstructLLM(prompt) - responsedict = GeneralUtils.textToDict(response,["chat", "mentioning_wine"], + responsedict = GeneralUtils.textToDict(response,["chat", "mentioning_wine", "note"], rightmarker=":", symbolkey=true) for i ∈ [:chat] @@ -1394,6 +1404,124 @@ 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 @@ -1470,9 +1598,10 @@ function generatequestion(a, text2textInstructLLM::Function)::String - 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 + 1) thought: State your reasoning about the current situation 2) Q: Given the situation, "ask yourself" about the situation at least two, but no more than five, questions. 3) A: Given the situation, "answer to yourself" the best you can + 4) note: Put everything you want to add here Here are some examples: Q: The user is asking for a cappuccino. Do I have it at my cafe? @@ -1481,16 +1610,21 @@ function generatequestion(a, text2textInstructLLM::Function)::String 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: I've searched the database and found ... + Q: Did I introduce the wines to the user yet? + A: Not yet. I will introduce the wines now. You must only respond in format as described below: - Reasoning: ... - Q 1: ... - A 1: ... - Q 2: ... - A 2: ... - Q 3: ... - A 3: ... + thought: ... , In order to ..., I should ask myself the following questions. + Q_1: ... + A_1: ... + Q_2: ... + A_2: ... + Q_3: ... + A_3: ... ... + note: ... Let's begin! """ @@ -1529,11 +1663,15 @@ function generatequestion(a, text2textInstructLLM::Function)::String try response = text2textInstructLLM(prompt) - q_number = count("Q ", response) + q_number = count("Q_", response) if q_number < 1 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. + # response = string(split(response, "Please")[1]) # LLM usually add comments which is no need. + responsedict = GeneralUtils.textToDict(response, + ["thought", "Q_1", "note"], + rightmarker=":", symbolkey=true) + response = "Q_1: " * responsedict[:Q_1] println("--> generatequestion ", @__FILE__, " ", @__LINE__) pprintln(response) return response @@ -1575,7 +1713,7 @@ function generateSituationReport(a, text2textInstructLLM::Function)::Dict systemmsg = """ You are the assistant being in the given events. - Your task is to writes a summary for each event. + Your task is to writes a summary for each event in an ongoing series. At each round of conversation, you will be given the situation: Total events: number of events you need to summarize. diff --git a/src/llmfunction.jl b/src/llmfunction.jl index 22671aa..0a39f7e 100644 --- a/src/llmfunction.jl +++ b/src/llmfunction.jl @@ -355,7 +355,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 + - country: a country where the wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "UnitedStates" - 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 @@ -456,6 +456,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2< end responsedict[:flavors] = replace(responsedict[:flavors], "notes"=>"") + delete!(responsedict, :region) delete!(responsedict, :reasoning) delete!(responsedict, :tasting_notes) delete!(responsedict, :flavors) diff --git a/test/runtest.jl b/test/runtest.jl index 55e83ed..ff7bb4b 100644 --- a/test/runtest.jl +++ b/test/runtest.jl @@ -1,5 +1,5 @@ using Revise # remove when this package is completed -using YiemAgent, GeneralUtils, JSON3, MQTTClient, Dates, UUIDs, LibPQ, DataStructures, SQLLLM +using YiemAgent, GeneralUtils, JSON3, MQTTClient, Dates, UUIDs, LibPQ using Base.Threads # ---------------------------------------------- 100 --------------------------------------------- # @@ -68,19 +68,26 @@ function main() response = YiemAgent.conversation(a, Dict(:text=> userinput)) println("") println("--> assistant response: \n", response) - println("") - println("--> user input:") - userinput = readline() + userinput = "" + for i in 1:3 + if userinput == "" + println("") + println("--> user input:") + userinput = readline() + else + break + end + end end end main() """ - I'm joining a graduation party this evening. I want a bottle of full bodied, dry white wine from the United States. I'm ok with all price range. + 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. 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. """