From 6e2391e0e3d157cd652dd003a72cedc009615426 Mon Sep 17 00:00:00 2001 From: narawat lamaiin Date: Thu, 18 Jul 2024 11:22:53 +0700 Subject: [PATCH] update --- src/interface.jl | 9 +- src/llmfunction.jl | 238 ++++++++++++++++++++++++--------------------- src/type.jl | 5 +- test/runtest.jl | 54 ++++++---- 4 files changed, 168 insertions(+), 138 deletions(-) diff --git a/src/interface.jl b/src/interface.jl index a033561..d5a2cdb 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -226,7 +226,7 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent} - 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 + - Get to know what food will be served with wine You must follow the following DON'T guidelines: - Don't mention any specific wine until you've checked your inventory. @@ -1024,6 +1024,8 @@ end # end + + """ # Arguments @@ -1052,8 +1054,7 @@ function think(a::T) where {T<:agent} if actionname == "CHATBOX" (result=actioninput, errormsg=nothing, success=true) elseif actionname == "WINESTOCK" - DBconnection = LibPQ.Connection("host=192.168.88.12 port=5432 dbname=yiem_wine_assistant user=yiem password=yiem@Postgres_0.0") - winestock(actioninput, DBconnection, a.text2textInstructLLM) + winestock(a, actioninput) else error("undefined LLM function. Requesting $actionname") end @@ -1097,7 +1098,7 @@ julia> function generatechat(a::T) where {T<:agent} systemmsg = """ - You are a helpful sommelier working for a wine store. + You are a polite 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. At each round of conversation, the user will give you the current situation: diff --git a/src/llmfunction.jl b/src/llmfunction.jl index 1a75d3d..43ce908 100644 --- a/src/llmfunction.jl +++ b/src/llmfunction.jl @@ -362,15 +362,14 @@ julia> result = winestock(agent, input) # Signature """ -function winestock(input::T, DBconnection, text2textInstructLLM::Function - )::Union{Tuple{String, Number, Number, Bool}, Tuple{String, Nothing, Number, Bool}} where {T<:AbstractString} +function winestock(a::T1, input::T2 + )::Union{Tuple{String, Number, Number, Bool}, Tuple{String, Nothing, Number, Bool}} where {T1<:agent, T2<:AbstractString} # SELECT * # FROM food # WHERE 'China' = ANY(food_name) # OR 'India' = ANY(food_name); - wineattributes = wineattributes_wordToNumber(a, input) systemmsg = @@ -559,149 +558,162 @@ function winestock(input::T, DBconnection, text2textInstructLLM::Function # return result, nothing, 0, false end -function wineattributes_wordToNumber(config::T1, input::T2 - )::Dict where {T1<:AbstractDict, T2<:AbstractString} + +""" + +# Arguments + - `v::Integer` + dummy variable + +# Return + +# Example +```jldoctest +julia> +``` + +# TODO + - [] update docstring + - [WORKING] implement the function + +# Signature +""" +function wineattributes_wordToNumber(a::T1, input::T2 + )::Dict where {T1<:agent, T2<:AbstractString} + + converstiontable = + """ + Conversion Table: + Intensity level: + Level 1: May correspond to "light-bodied" or a similar description. + Level 2: May correspond to "med light", "medium light" or a similar description. + Level 3: May correspond to "medium" or a similar description. + Level 4: May correspond to "med full", "medium full" or a similar description. + Level 5: May correspond to "full" or a similar description. + Sweetness level: + Level 1: May correspond to "dry", "no sweet" or a similar description. + Level 2: May correspond to "off dry", "less sweet" or a similar description. + Level 3: May correspond to "semi sweet" or a similar description. + Level 4: May correspond to "sweet" or a similar description. + Level 5: May correspond to "very sweet" or a similar description. + Tannin level: + Level 1: May correspond to "low tannin" or a similar description. + Level 2: May correspond to "semi low tannin" or a similar description. + Level 3: May correspond to "medium tannin" or a similar description. + Level 4: May correspond to "semi high tannin" or a similar description. + Level 5: May correspond to "high tannin" or a similar description. + Acidity level: + Level 1: May correspond to "low acidity" or a similar description. + Level 2: May correspond to "semi low acidity" or a similar description. + Level 3: May correspond to "medium acidity" or a similar description. + Level 4: May correspond to "semi high acidity" or a similar description. + Level 5: May correspond to "high acidity" or a similar description. + """ systemmsg = """ - As an attentive sommelier, your mission is to determine the user's preferred levels of sweetness, intensity, tannin, acidity and other criteria for a wine based on their input. - You'll achieve this by referring to the provided conversion table. - - Conversion Table: - Intensity level: - Level 1: May correspond to "light-bodied" or a similar description. - Level 2: May correspond to "med light", "medium light" or a similar description. - Level 3: May correspond to "medium" or a similar description. - Level 4: May correspond to "med full", "medium full" or a similar description. - Level 5: May correspond to "full" or a similar description. - Sweetness level: - Level 1: May correspond to "dry", "no sweet" or a similar description. - Level 2: May correspond to "off dry", "less sweet" or a similar description. - Level 3: May correspond to "semi sweet" or a similar description. - Level 4: May correspond to "sweet" or a similar description. - Level 5: May correspond to "very sweet" or a similar description. - Tannin level: - Level 1: May correspond to "low tannin" or a similar description. - Level 2: May correspond to "semi low tannin" or a similar description. - Level 3: May correspond to "medium tannin" or a similar description. - Level 4: May correspond to "semi high tannin" or a similar description. - Level 5: May correspond to "high tannin" or a similar description. - Acidity level: - Level 1: May correspond to "low acidity" or a similar description. - Level 2: May correspond to "semi low acidity" or a similar description. - Level 3: May correspond to "medium acidity" or a similar description. - Level 4: May correspond to "semi high acidity" or a similar description. - Level 5: May correspond to "high acidity" or a similar description. + As an attentive sommelier, your task is to determine the user's wine preferred levels of sweetness, intensity, tannin, acidity and other criteria based on the user query. - You should only respond in JSON format as describe below: - { - "attributes": - { - "sweetness": "sweetness level", - "acidity": "acidity level", - "tannin": "tannin level", - "intensity": "intensity level" - } - } + At each round of conversation, the user will give you the current situation: + Conversion Table: ... + Query: ... + Last round missing info: some info are missing + + You should follow the following guidelines: + - If specific information is unavailable, please use "NA" to indicate this. + - Use converstion table to convert sweetness, acidity, tannin, intensity describing word into an integer. + - Do not generate other comments. + + You should then respond to the user with: + - wine_type: Can be one of: red, white, sparkling, rose, dessert or fortified + - budget: ... + - occasion: ... + - food_pairing: food that will be served with wine + - country: wine's country of origin + - grape_variety: ... + - sweetness: S where S is an integer of sweetness level + - acidity: A where A is an integer of acidity level + - tannin: T where T is an integer of tannin level + - intensity: I where I is an integer of intensity level + + You should only respond in format as described below: + wine_type: ... + budget: ... + occasion: ... + food_pairing: ... + country: ... + grape_variety: ... + sweetness: + acidity: ... + tannin: ... + intensity: ... Here are some examples: - user: "price < 25, full-bodied white wine with sweetness level 2, low tannin level and medium acidity level, Pizza" - assistant: - { - "attributes": - { - "wine_type": "white" - "budget": less than 25", - "food_pairing": "Pizza", - "sweetness": 2, - "acidity": 3, - "tannin": 1, - "intensity": 5 - } - } + user: "price < 25, full-bodied white wine with sweetness level 2, low tannin level and medium acidity level, Pizza, France, Riesling" + assistant: + wine_type: white, budget: less than 25, food_pairing: Pizza, country: France, grape_variety: Riesling, sweetness: 2, acidity: 3, tannin: 1, intensity: 5 - user: body=full-bodied, off dry, acidity=medium, intensity=intense + user: body=full-bodied, dry, acidity=medium and low tannin assistant: - { - "attributes": - { - "sweetness": 2, - "acidity": 3, - "tannin": "not specified", - "intensity": 5 - } - } + wine_type: NA, budget: NA, food_pairing: NA, country: NA, grape_variety: NA, sweetness: 1, acidity: 3, tannin: 1, intensity: 5 Let's begin! """ + missinginfo = "None" + usermsg = """ - $input + Conversion Table: $converstiontable + Query: $input + Last round missing info: $missinginfo """ - chathistory = + _prompt = [ Dict(:name=> "system", :text=> systemmsg), Dict(:name=> "user", :text=> usermsg) ] - + # put in model format - prompt = formatLLMtext(chathistory, "llama3instruct") + prompt = GeneralUtils.formatLLMtext(_prompt, "llama3instruct") prompt *= - """ - <|start_header_id|>assistant<|end_header_id|> - { - """ + """ + <|start_header_id|>assistant<|end_header_id|> + """ - pprint(prompt) - externalService = config[:externalservice][:text2textinstruct] + attributes = ["wine_type", "budget", "occasion", "food_pairing", "country", "grape_variety", "sweetness", "acidity", "tannin", "intensity"] - # send formatted input to user using GeneralUtils.sendReceiveMqttMsg - msgMeta = GeneralUtils.generate_msgMeta( - externalService[:mqtttopic], - senderName= "wineattributes_wordToNumber", - senderId= string(uuid4()), - receiverName= "text2textinstruct", - mqttBroker= config[:mqttServerInfo][:broker], - mqttBrokerPort= config[:mqttServerInfo][:port], - ) - - outgoingMsg = Dict( - :msgMeta=> msgMeta, - :payload=> Dict( - :text=> prompt, - ) - ) - for attempt in 1:5 try - response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120) - _responseJsonStr = response[:response][:text] - expectedJsonExample = - """ - Here is an expected JSON format: - { - "attributes": - { - "...": "...", - "...": "...", - } - } - """ - responseJsonStr = jsoncorrection(config, _responseJsonStr, expectedJsonExample) - _responseDict = copy(JSON3.read(responseJsonStr)) - responseDict = _responseDict[:attributes] - - return responseDict + response = a.text2textInstructLLM(prompt) + responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true) + + for i ∈ attributes + 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 ∈ attributes + matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i) + if length(matchkeys) > 1 + error("generatechat has more than one key per categories") + end + end + + 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("") - @warn "Attempt $attempt. Error occurred: $errorMsg\n$st" + println("Attempt $attempt. Error occurred: $errorMsg\n$st") println("") end end diff --git a/src/type.jl b/src/type.jl index 4504071..8b5e805 100644 --- a/src/type.jl +++ b/src/type.jl @@ -97,10 +97,12 @@ mutable struct sommelier <: agent # communication function text2textInstructLLM::Function + executeSQL::Function end function sommelier( - text2textInstructLLM::Function + text2textInstructLLM::Function, + executeSQL::Function ; name::String= "Assistant", id::String= string(uuid4()), @@ -140,6 +142,7 @@ function sommelier( chathistory, memory, text2textInstructLLM, + executeSQL ) return newAgent diff --git a/test/runtest.jl b/test/runtest.jl index 6dc7bab..3baef2a 100644 --- a/test/runtest.jl +++ b/test/runtest.jl @@ -27,6 +27,13 @@ msgMeta = GeneralUtils.generate_msgMeta( # :externalservice=> config[:externalservice], # ) +function executeSQL(sql::T) where {T<:AbstractString} + DBconnection = LibPQ.Connection("host=192.168.88.12 port=5432 dbname=yiem_wine_assistant user=yiem password=yiem@Postgres_0.0") + result = LibPQ.execute(DBconnection, sql) + close(DBconnection) + return result +end + function text2textInstructLLM(prompt::String) msgMeta = GeneralUtils.generate_msgMeta( config[:externalservice][:text2textinstruct][:mqtttopic], @@ -58,6 +65,7 @@ end # Instantiate an agent a = YiemAgent.sommelier( text2textInstructLLM, + executeSQL; name="assistant", id="testingSessionID", # agent instance id ) @@ -71,27 +79,33 @@ end -function main() - userinput = "Hello, I would like a get a bottle of wine." - for i in 1:10 - response = YiemAgent.conversation(a, Dict(:text=> userinput)) - println("") - println("--> assistant response: \n", response) - println("") - println("--> user input:") - userinput = readline() - end -end - -main() - - - - - - - +# function main() +# userinput = "Hello, I would like a get a bottle of wine." +# for i in 1:10 +# response = YiemAgent.conversation(a, Dict(:text=> userinput)) +# println("") +# println("--> assistant response: \n", response) +# println("") +# println("--> user input:") +# userinput = readline() +# end +# end +# main() + + +input = "query=\"off dry, medium tannin French Rosé and spicy Thai food pairing under 30 dollars\"" +YiemAgent.winestock(a, input) + + + + +""" +I'm having a graduation party this evening. I'll pay at most 30 bucks. +I have no idea. The party will be formal. What type of wine people usually get for this occasion? +What about sparkling Rose? + +"""