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 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:

View File

@@ -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

View File

@@ -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