This commit is contained in:
narawat lamaiin
2024-07-18 11:22:53 +07:00
parent 0ddc98a447
commit 6e2391e0e3
4 changed files with 168 additions and 138 deletions

View File

@@ -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 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 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 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: You must follow the following DON'T guidelines:
- Don't mention any specific wine until you've checked your inventory. - Don't mention any specific wine until you've checked your inventory.
@@ -1024,6 +1024,8 @@ end
# end # end
""" """
# Arguments # Arguments
@@ -1052,8 +1054,7 @@ function think(a::T) where {T<:agent}
if actionname == "CHATBOX" if actionname == "CHATBOX"
(result=actioninput, errormsg=nothing, success=true) (result=actioninput, errormsg=nothing, success=true)
elseif actionname == "WINESTOCK" 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(a, actioninput)
winestock(actioninput, DBconnection, a.text2textInstructLLM)
else else
error("undefined LLM function. Requesting $actionname") error("undefined LLM function. Requesting $actionname")
end end
@@ -1097,7 +1098,7 @@ julia>
function generatechat(a::T) where {T<:agent} function generatechat(a::T) where {T<:agent}
systemmsg = 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. 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: At each round of conversation, the user will give you the current situation:

View File

@@ -362,15 +362,14 @@ julia> result = winestock(agent, input)
# Signature # Signature
""" """
function winestock(input::T, DBconnection, text2textInstructLLM::Function function winestock(a::T1, input::T2
)::Union{Tuple{String, Number, Number, Bool}, Tuple{String, Nothing, Number, Bool}} where {T<:AbstractString} )::Union{Tuple{String, Number, Number, Bool}, Tuple{String, Nothing, Number, Bool}} where {T1<:agent, T2<:AbstractString}
# SELECT * # SELECT *
# FROM food # FROM food
# WHERE 'China' = ANY(food_name) # WHERE 'China' = ANY(food_name)
# OR 'India' = ANY(food_name); # OR 'India' = ANY(food_name);
wineattributes = wineattributes_wordToNumber(a, input) wineattributes = wineattributes_wordToNumber(a, input)
systemmsg = systemmsg =
@@ -559,149 +558,162 @@ function winestock(input::T, DBconnection, text2textInstructLLM::Function
# return result, nothing, 0, false # return result, nothing, 0, false
end 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 = 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. 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'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.
You should only respond in JSON format as describe below: At each round of conversation, the user will give you the current situation:
{ Conversion Table: ...
"attributes": Query: ...
{ Last round missing info: some info are missing
"sweetness": "sweetness level",
"acidity": "acidity level", You should follow the following guidelines:
"tannin": "tannin level", - If specific information is unavailable, please use "NA" to indicate this.
"intensity": "intensity level" - 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: Here are some examples:
user: "price < 25, full-bodied white wine with sweetness level 2, low tannin level and medium acidity level, Pizza" user: "price < 25, full-bodied white wine with sweetness level 2, low tannin level and medium acidity level, Pizza, France, Riesling"
assistant: assistant:
{ wine_type: white, budget: less than 25, food_pairing: Pizza, country: France, grape_variety: Riesling, sweetness: 2, acidity: 3, tannin: 1, intensity: 5
"attributes":
{
"wine_type": "white"
"budget": less than 25",
"food_pairing": "Pizza",
"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: assistant:
{ wine_type: NA, budget: NA, food_pairing: NA, country: NA, grape_variety: NA, sweetness: 1, acidity: 3, tannin: 1, intensity: 5
"attributes":
{
"sweetness": 2,
"acidity": 3,
"tannin": "not specified",
"intensity": 5
}
}
Let's begin! Let's begin!
""" """
missinginfo = "None"
usermsg = usermsg =
""" """
$input Conversion Table: $converstiontable
Query: $input
Last round missing info: $missinginfo
""" """
chathistory = _prompt =
[ [
Dict(:name=> "system", :text=> systemmsg), Dict(:name=> "system", :text=> systemmsg),
Dict(:name=> "user", :text=> usermsg) Dict(:name=> "user", :text=> usermsg)
] ]
# put in model format # put in model format
prompt = formatLLMtext(chathistory, "llama3instruct") prompt = GeneralUtils.formatLLMtext(_prompt, "llama3instruct")
prompt *= prompt *=
""" """
<|start_header_id|>assistant<|end_header_id|> <|start_header_id|>assistant<|end_header_id|>
{ """
"""
pprint(prompt) attributes = ["wine_type", "budget", "occasion", "food_pairing", "country", "grape_variety", "sweetness", "acidity", "tannin", "intensity"]
externalService = config[:externalservice][:text2textinstruct]
# 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 for attempt in 1:5
try try
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120) response = a.text2textInstructLLM(prompt)
_responseJsonStr = response[:response][:text] responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true)
expectedJsonExample =
""" for i attributes
Here is an expected JSON format: if length(JSON3.write(responsedict[i])) == 0
{ error("$i is empty ", @__LINE__)
"attributes": end
{ end
"...": "...",
"...": "...", # check if there are more than 1 key per categories
} for i attributes
} matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
""" if length(matchkeys) > 1
responseJsonStr = jsoncorrection(config, _responseJsonStr, expectedJsonExample) error("generatechat has more than one key per categories")
_responseDict = copy(JSON3.read(responseJsonStr)) end
responseDict = _responseDict[:attributes] end
return responseDict result = responsedict[:chat]
return result
catch e catch e
io = IOBuffer() io = IOBuffer()
showerror(io, e) showerror(io, e)
errorMsg = String(take!(io)) errorMsg = String(take!(io))
st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
println("") println("")
@warn "Attempt $attempt. Error occurred: $errorMsg\n$st" println("Attempt $attempt. Error occurred: $errorMsg\n$st")
println("") println("")
end end
end end

View File

@@ -97,10 +97,12 @@ mutable struct sommelier <: agent
# communication function # communication function
text2textInstructLLM::Function text2textInstructLLM::Function
executeSQL::Function
end end
function sommelier( function sommelier(
text2textInstructLLM::Function text2textInstructLLM::Function,
executeSQL::Function
; ;
name::String= "Assistant", name::String= "Assistant",
id::String= string(uuid4()), id::String= string(uuid4()),
@@ -140,6 +142,7 @@ function sommelier(
chathistory, chathistory,
memory, memory,
text2textInstructLLM, text2textInstructLLM,
executeSQL
) )
return newAgent return newAgent

View File

@@ -27,6 +27,13 @@ msgMeta = GeneralUtils.generate_msgMeta(
# :externalservice=> config[:externalservice], # :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) function text2textInstructLLM(prompt::String)
msgMeta = GeneralUtils.generate_msgMeta( msgMeta = GeneralUtils.generate_msgMeta(
config[:externalservice][:text2textinstruct][:mqtttopic], config[:externalservice][:text2textinstruct][:mqtttopic],
@@ -58,6 +65,7 @@ end
# Instantiate an agent # Instantiate an agent
a = YiemAgent.sommelier( a = YiemAgent.sommelier(
text2textInstructLLM, text2textInstructLLM,
executeSQL;
name="assistant", name="assistant",
id="testingSessionID", # agent instance id id="testingSessionID", # agent instance id
) )
@@ -71,27 +79,33 @@ end
function main() # function main()
userinput = "Hello, I would like a get a bottle of wine." # userinput = "Hello, I would like a get a bottle of wine."
for i in 1:10 # for i in 1:10
response = YiemAgent.conversation(a, Dict(:text=> userinput)) # response = YiemAgent.conversation(a, Dict(:text=> userinput))
println("") # println("")
println("--> assistant response: \n", response) # println("--> assistant response: \n", response)
println("") # println("")
println("--> user input:") # println("--> user input:")
userinput = readline() # userinput = readline()
end # end
end # end
main()
# 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?
"""