This commit is contained in:
narawat lamaiin
2024-07-29 19:20:21 +07:00
parent 689bca3805
commit 792accc619
2 changed files with 246 additions and 131 deletions

View File

@@ -1,7 +1,7 @@
module llmfunction module llmfunction
export virtualWineUserChatbox, jsoncorrection, checkinventory, export virtualWineUserChatbox, jsoncorrection, checkinventory,
virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes virtualWineUserRecommendbox, userChatbox, userRecommendbox
using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs
using GeneralUtils, SQLLLM using GeneralUtils, SQLLLM
@@ -365,12 +365,12 @@ julia> result = checkinventory(agent, input)
function checkinventory(a::T1, input::T2 function checkinventory(a::T1, input::T2
)::Union{Tuple{String, Number, Number, Bool}, Tuple{String, Nothing, Number, Bool}} where {T1<:agent, T2<:AbstractString} )::Union{Tuple{String, Number, Number, Bool}, Tuple{String, Nothing, Number, Bool}} where {T1<:agent, T2<:AbstractString}
wineattributes = extractWineAttributes(a, input) wineattributes_1 = extractWineAttributes_1(a, input)
wineattributes_2 = extractWineAttributes_2(a, input)
# replace because SQLLLM didn't know what food_paired means inventoryquery = "$wineattributes_1, $wineattributes_2"
result = replace(wineattributes, "food_pairing" => "food_to_be_paired_with_wine")
result = SQLLLM.query(result, a.executeSQL, a.text2textInstructLLM) result = SQLLLM.query(inventoryquery, a.executeSQL, a.text2textInstructLLM)
return result return result
end end
@@ -394,145 +394,51 @@ julia>
# Signature # Signature
""" """
function extractWineAttributes(a::T1, input::T2 function extractWineAttributes_1(a::T1, input::T2
)::String where {T1<:agent, T2<:AbstractString} )::String 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 task is to determine the user's wine preferred levels of sweetness, intensity, tannin, acidity and other criteria based on the user query.
# At each round of conversation, the user will give you the current situation:
# Conversion Table: ...
# Query: ...
# You must follow the following guidelines:
# 1) If specific information is not available in the query, use "NA" to indicate this. Also words like "any" or "unlimited" means no information available.
# 2) Use converstion table to convert sweetness, acidity, tannin, intensity describing word into an integer.
# 3) Do not generate other comments.
# You should then respond to the user with the following points:
# - wine_type: Can be one of: red, white, sparkling, rose, dessert or fortified
# - price: ...
# - occasion: ...
# - food_pairing: food that will be served with wine
# - country: wine's country of origin
# - grape_variety: ...
# - tasting_notes: wine's flavors
# - sweetness: S where S is an integer indicating sweetness level
# - acidity: A where A is an integer indicating acidity level
# - tannin: T where T is an integer indicating tannin level
# - intensity: I where I is an integer indicating intensity level
# You should only respond in format as described below:
# repeat: repeat the user's query
# wine_type: ...
# price: ...
# occasion: ...
# food_pairing: ...
# country: ...
# grape_variety: ...
# tasting_notes: ...
# sweetness:
# acidity: ...
# tannin: ...
# intensity: ...
# Here are some examples:
# user: "price < 25, for wedding party, full-bodied white wine with sweetness level 2, apple and honey notes, low tannin level and medium acidity level, Pizza, France, Riesling"
# assistant: repeat: ... \n wine_type: white\n price: less than 25\n occasion: wedding party\n food_pairing: Pizza\n country: France\n grape_variety: Riesling\n tasting_notes: apple, honey\n sweetness: 2\n acidity: 3\n tannin: 1\n intensity: 5
# user: body=full-bodied, dry, acidity=medium and low tannin
# assistant: repeat: ... \n wine_type: NA\n price: NA\n occasion: NA\n food_pairing: NA\n country: NA\n grape_variety: NA\n tasting_notes: NA\n sweetness: 1\n acidity: 3\n tannin: 1\n intensity: 5
# Let's begin!
# """
systemmsg = systemmsg =
""" """
As an attentive sommelier, your task is to fillout the form based on the user query. The form is about the user's wine preference. As an helpful sommelier, your task is to fill the user's preference form based on the user query.
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:
Conversion Table: ... User query: ...
Query: ...
The preference form requires the following information:
wine_type, price, occasion, food_to_be_paired_with_wine, country, grape_variety, wine_notes.
You must follow the following guidelines: You must follow the following guidelines:
1) Use the conversion table to convert the descriptive word level of sweetness, intensity, tannin, and acidity into a corresponding integer. 1) If specific information required in the preference form is not available in the query, use 'NA' to indicate this.
2) If specific information required in the form is not available in the query, use 'NA' to indicate this. Additionally, words like 'any' or 'unlimited' mean no information is available. Additionally, words like 'any' or 'unlimited' mean no information is available.
2) Use the conversion table to convert the descriptive word level of sweetness, intensity, tannin, and acidity into a corresponding integer.
3) Do not generate other comments. 3) Do not generate other comments.
You should then respond to the user with the following points: You should then respond to the user with the following points:
1) reasoning: State your reasoning about the current situation. - wine_type: Can be one of: red, white, sparkling, rose, dessert or fortified
2) wine_type: Can be one of: red, white, sparkling, rose, dessert or fortified - price: ...
3) price: ... - occasion: ...
4) occasion: ... - food_to_be_paired_with_wine: food that will be served with wine
5) food_to_be_paired_with_wine: food that will be served with wine - country: wine's country of origin
6) country: wine's country of origin - grape_variety: ...
7) grape_variety: ... - wine_notes:
8) tasting_notes: wine's flavors Bad example (these words are not wine notes - descriptive words): dry, sour, full bodied, etc.
9) sweetness: S where S is an integer indicating sweetness level Good example (these words are wine notes - descriptive words): floral, citrus, earthy, fruity, tropical, nutty, etc.
10) acidity: A where A is an integer indicating acidity level
11) tannin: T where T is an integer indicating tannin level
12) intensity: I where I is an integer indicating intensity level
You should only respond in format as described below: You should only respond in the form as described below:
reasoning: ...
wine_type: ... wine_type: ...
price: ... price: ...
occasion: ... occasion: ...
food_to_be_paired_with_wine: ... food_to_be_paired_with_wine: ...
country: ... country: ...
grape_variety: ... grape_variety: ...
tasting_notes: ... wine_notes: ...
sweetness:
acidity: ...
tannin: ...
intensity: ...
Here are some examples:
user: price < 25, for wedding party, full-bodied white wine with sweetness level 2, apple and honey notes, low tannin level and medium acidity level, Pizza, France, Riesling
assistant: reasoning: ... \n wine_type: white\n price: less than 25\n occasion: wedding party\n food_to_be_paired_with_wine: Pizza\n country: France\n grape_variety: Riesling\n tasting_notes: apple, honey\n sweetness: 2\n acidity: 3\n tannin: 1\n intensity: 5
user: body=full-bodied, dry, acidity=medium and low tannin
assistant: reasoning: ... \n wine_type: NA\n price: NA\n occasion: NA\n food_to_be_paired_with_wine: NA\n country: NA\n grape_variety: NA\n tasting_notes: NA\n sweetness: 1\n acidity: 3\n tannin: 1\n intensity: 5
Let's begin! Let's begin!
""" """
usermsg = usermsg =
""" """
Conversion Table: $converstiontable User query: $input
Query: $input
""" """
_prompt = _prompt =
@@ -548,7 +454,7 @@ function extractWineAttributes(a::T1, input::T2
<|start_header_id|>assistant<|end_header_id|> <|start_header_id|>assistant<|end_header_id|>
""" """
attributes = ["reasoning", "wine_type", "price", "occasion", "food_to_be_paired_with_wine", "country", "grape_variety", "sweetness", "acidity", "tannin", "intensity"] attributes = ["wine_type", "price", "occasion", "food_to_be_paired_with_wine", "country", "grape_variety"]
for attempt in 1:5 for attempt in 1:5
try try
@@ -561,17 +467,9 @@ function extractWineAttributes(a::T1, input::T2
end end
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 = "" result = ""
for (k, v) in responsedict for (k, v) in responsedict
if k != :reasoning && !occursin("NA", v) if !occursin("NA", v)
result *= "$k: $v, " result *= "$k: $v, "
end end
end end
@@ -592,6 +490,215 @@ function extractWineAttributes(a::T1, input::T2
error("wineattributes_wordToNumber() failed to get a response") error("wineattributes_wordToNumber() failed to get a response")
end end
function extractWineAttributes_2(a::T1, input::T2
)::String where {T1<:agent, T2<:AbstractString}
converstiontable =
"""
Conversion Table:
Intensity level:
1: May correspond to "light-bodied" or a similar description.
2: May correspond to "med light", "medium light" or a similar description.
3: May correspond to "medium" or a similar description.
4: May correspond to "med full", "medium full" or a similar description.
5: May correspond to "full" or a similar description.
Sweetness level:
1: May correspond to "dry", "no sweet" or a similar description.
2: May correspond to "off dry", "less sweet" or a similar description.
3: May correspond to "semi sweet" or a similar description.
4: May correspond to "sweet" or a similar description.
5: May correspond to "very sweet" or a similar description.
Tannin level:
1: May correspond to "low tannin" or a similar description.
2: May correspond to "semi low tannin" or a similar description.
3: May correspond to "medium tannin" or a similar description.
4: May correspond to "semi high tannin" or a similar description.
5: May correspond to "high tannin" or a similar description.
Acidity level:
1: May correspond to "low acidity" or a similar description.
2: May correspond to "semi low acidity" or a similar description.
3: May correspond to "medium acidity" or a similar description.
4: May correspond to "semi high acidity" or a similar description.
5: May correspond to "high acidity" or a similar description.
"""
systemmsg =
"""
As an helpful sommelier, your task is to fill the user's preference form based on the user query.
At each round of conversation, the user will give you the current situation:
Conversion Table: ...
User query: ...
The preference form requires the following information:
sweetness, acidity, tannin, intensity
You must follow the following guidelines:
1) If specific information required in the preference form is not available in the query, use 'NA' to indicate this.
Additionally, words like 'any' or 'unlimited' mean no information is available.
2) Use the conversion table to convert the descriptive word level of sweetness, intensity, tannin, and acidity into a corresponding integer.
3) Do not generate other comments.
You should then respond to the user with the following points:
- sweetness: S where S is an integer indicating sweetness level
- acidity: D where D is an integer indicating acidity level
- tannin: T where T is an integer indicating tannin level
- intensity: I where I is an integer indicating intensity level
You should only respond in the form as described below:
sweetness: ...
acidity: ...
tannin: ...
intensity: ...
Let's begin!
"""
usermsg =
"""
Conversion Table: $converstiontable
User query: $input
"""
_prompt =
[
Dict(:name=> "system", :text=> systemmsg),
Dict(:name=> "user", :text=> usermsg)
]
# put in model format
prompt = GeneralUtils.formatLLMtext(_prompt, "llama3instruct")
prompt *=
"""
<|start_header_id|>assistant<|end_header_id|>
"""
attributes = ["sweetness", "acidity", "tannin", "intensity"]
for attempt in 1:5
try
response = a.text2textInstructLLM(prompt)
responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true)
for i attributes
if length(JSON3.write(responsedict[Symbol(i)])) == 0
error("$i is empty ", @__LINE__)
end
end
result = ""
for (k, v) in responsedict
if !occursin("NA", v)
result *= "$k: $v, "
end
end
result = result[1:end-2] # remove the ending ", "
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("wineattributes_wordToNumber() failed to get a response")
end
# function recheckWineAttributes(a::T1, input::T2
# )::String where {T1<:agent, T2<:AbstractString}
# systemmsg =
# """
# As an helpful sommelier, your task is to check what preferences the user mentioned in the query.
# At each round of conversation, the user will give you the current situation:
# User query: ...
# The preferences are:
# - wine type: red, white, sparkling, rose, dessert, fortified
# - price
# - occasion
# - food to be paired with wine
# - country
# - grape_variety
# - tasting_notes
# - sweetness
# - acidity
# - tannin
# - intensity: wine body e.g. full bodied, light bodied
# You should then respond to the user with the following points:
# 1) mentioned_preferences: list all the preferences in the user's query.
# Good example: mentioned_preferences: "price", "country", "wine type"
# You should only respond in the form as described below:
# mentioned_preferences: ...
# Let's begin!
# """
# usermsg =
# """
# User query: $input
# """
# _prompt =
# [
# Dict(:name=> "system", :text=> systemmsg),
# Dict(:name=> "user", :text=> usermsg)
# ]
# # put in model format
# prompt = GeneralUtils.formatLLMtext(_prompt, "llama3instruct")
# prompt *=
# """
# <|start_header_id|>assistant<|end_header_id|>
# """
# attributes = ["mentioned_preferences"]
# for attempt in 1:5
# try
# response = a.text2textInstructLLM(prompt)
# responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true)
# for i ∈ attributes
# if length(JSON3.write(responsedict[Symbol(i)])) == 0
# error("$i is empty ", @__LINE__)
# end
# end
# result = ""
# for (k, v) in responsedict
# if !occursin("NA", v)
# result *= "$k: $v, "
# end
# end
# result = result[1:end-2] # remove the ending ", "
# 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("wineattributes_wordToNumber() failed to get a response")
# end
""" Attemp to correct LLM response's incorrect JSON response. """ Attemp to correct LLM response's incorrect JSON response.
# Arguments # Arguments

View File

@@ -120,6 +120,14 @@ end
input = "query=\"medium-bodied dry white wine\"" input = "query=\"medium-bodied dry white wine\""
# input = "the customer is looking for a medium-bodied, dry white wine."
result = YiemAgent.checkinventory(a, input)
result = YiemAgent.extractWineAttributes(a, input)