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