|
|
|
|
@@ -1,7 +1,8 @@
|
|
|
|
|
module llmfunction
|
|
|
|
|
|
|
|
|
|
export virtualWineUserChatbox, jsoncorrection, checkinventory, # recommendbox,
|
|
|
|
|
virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes_1
|
|
|
|
|
virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes_1,
|
|
|
|
|
extractWineAttributes_2, paraphrase
|
|
|
|
|
|
|
|
|
|
using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs, Dates
|
|
|
|
|
using GeneralUtils, SQLLLM
|
|
|
|
|
@@ -290,20 +291,20 @@ julia> result = checkinventory(agent, input)
|
|
|
|
|
function checkinventory(a::T1, input::T2
|
|
|
|
|
) where {T1<:agent, T2<:AbstractString}
|
|
|
|
|
|
|
|
|
|
println("\n~~~ checkinventory order: $input ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
println("\n~~~ checkinventory order: $input ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
wineattributes_1 = extractWineAttributes_1(a, input)
|
|
|
|
|
wineattributes_2 = extractWineAttributes_2(a, input)
|
|
|
|
|
|
|
|
|
|
_inventoryquery = "retailer name: $(a.retailername), $wineattributes_1, $wineattributes_2"
|
|
|
|
|
inventoryquery = "Retrieves winery, wine_name, vintage, region, country, wine_type, grape, serving_temperature, sweetness, intensity, tannin, acidity, tasting_notes, price and currency of wines that match the following criteria - {$_inventoryquery}"
|
|
|
|
|
println("~~~ checkinventory input: $inventoryquery ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
println("~~~ checkinventory input: $inventoryquery ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
# add suppport for similarSQLVectorDB
|
|
|
|
|
textresult, rawresponse = SQLLLM.query(inventoryquery, a.func[:executeSQL],
|
|
|
|
|
a.func[:text2textInstructLLM],
|
|
|
|
|
insertSQLVectorDB=a.func[:insertSQLVectorDB],
|
|
|
|
|
similarSQLVectorDB=a.func[:similarSQLVectorDB])
|
|
|
|
|
|
|
|
|
|
println("\n~~~ checkinventory result ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
println("\n~~~ checkinventory result ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
println(textresult)
|
|
|
|
|
|
|
|
|
|
return (result=textresult, rawresponse=rawresponse, success=true, errormsg=nothing)
|
|
|
|
|
@@ -325,234 +326,67 @@ julia>
|
|
|
|
|
|
|
|
|
|
# TODO
|
|
|
|
|
- [] update docstring
|
|
|
|
|
- [x] implement the function
|
|
|
|
|
- implement the function
|
|
|
|
|
|
|
|
|
|
# Signature
|
|
|
|
|
"""
|
|
|
|
|
# function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
|
|
|
|
|
|
|
|
|
# systemmsg =
|
|
|
|
|
# """
|
|
|
|
|
# As a helpful sommelier, your task is to extract the user information from the user's query as much as possible to fill out user's preference form.
|
|
|
|
|
|
|
|
|
|
# At each round of conversation, the user will give you the current situation:
|
|
|
|
|
# User's query: ...
|
|
|
|
|
|
|
|
|
|
# You must follow the following guidelines:
|
|
|
|
|
# 1) If specific information required in the preference form is not available in the query or there isn't any, mark with "NA" to indicate this.
|
|
|
|
|
# Additionally, words like 'any' or 'unlimited' mean no information is available.
|
|
|
|
|
# 2) Do not generate other comments.
|
|
|
|
|
|
|
|
|
|
# You should then respond to the user with the following points:
|
|
|
|
|
# - reasoning: state your understanding of the current situation
|
|
|
|
|
# - wine_name: name of the wine
|
|
|
|
|
# - winery: name of the winery
|
|
|
|
|
# - vintage: the year of the wine
|
|
|
|
|
# - region: a region in a country where the wine is produced, such as Burgundy, Napa Valley, etc
|
|
|
|
|
# - country: a country where the wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "United States"
|
|
|
|
|
# - wine_type: can be one of: "red", "white", "sparkling", "rose", "dessert" or "fortified"
|
|
|
|
|
# - grape_variety: the name of the primary grape used to make the wine
|
|
|
|
|
# - tasting_notes: a brief description of the wine's taste, such as "butter", "oak", "fruity", etc
|
|
|
|
|
# - wine_price: price of wine. For example, up to 100, less than 100, 20 to 100, 30-79.95
|
|
|
|
|
# - occasion: the occasion the user is having the wine for
|
|
|
|
|
# - food_to_be_paired_with_wine: food that the user will be served with the wine such as poultry, fish, steak, etc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# You should only respond in the user's preference form as described below:
|
|
|
|
|
# reasoning: ...
|
|
|
|
|
# winery: ...
|
|
|
|
|
# wine_name: ...
|
|
|
|
|
# vintage: ...
|
|
|
|
|
# region: ...
|
|
|
|
|
# country: ...
|
|
|
|
|
# wine_type: ...
|
|
|
|
|
# grape_variety: ...
|
|
|
|
|
# tasting_notes: ...
|
|
|
|
|
# wine_price: ...
|
|
|
|
|
# occasion: ...
|
|
|
|
|
# food_to_be_paired_with_wine: ...
|
|
|
|
|
|
|
|
|
|
# Here are some example:
|
|
|
|
|
# User's query: red, Chenin Blanc, Riesling, under 20
|
|
|
|
|
# reasoning: ...
|
|
|
|
|
# winery: NA
|
|
|
|
|
# wine_name: NA
|
|
|
|
|
# vintage: NA
|
|
|
|
|
# region: NA
|
|
|
|
|
# country: NA
|
|
|
|
|
# wine_type: red
|
|
|
|
|
# grape_variety: Chenin Blanc, Riesling
|
|
|
|
|
# tasting_notes: NA
|
|
|
|
|
# wine_price: under 20
|
|
|
|
|
# occasion: NA
|
|
|
|
|
# food_to_be_paired_with_wine: NA
|
|
|
|
|
|
|
|
|
|
# User's query: Domaine du Collier Saumur Blanc 2019, France, white, Chenin Blanc
|
|
|
|
|
# reasoning: ...
|
|
|
|
|
# winery: Domaine du Collier
|
|
|
|
|
# wine_name: Saumur Blanc
|
|
|
|
|
# vintage: 2019
|
|
|
|
|
# region: Saumur
|
|
|
|
|
# country: France
|
|
|
|
|
# wine_type: white
|
|
|
|
|
# grape_variety: Chenin Blanc
|
|
|
|
|
# tasting_notes: NA
|
|
|
|
|
# wine_price: 109
|
|
|
|
|
# occasion: NA
|
|
|
|
|
# food_to_be_paired_with_wine: NA
|
|
|
|
|
|
|
|
|
|
# Let's begin!
|
|
|
|
|
# """
|
|
|
|
|
|
|
|
|
|
# attributes = ["reasoning", "winery", "wine_name", "vintage", "region", "country", "wine_type", "grape_variety", "tasting_notes", "wine_price", "occasion", "food_to_be_paired_with_wine"]
|
|
|
|
|
# errornote = ""
|
|
|
|
|
# maxattempt = 5
|
|
|
|
|
# for attempt in 1:maxattempt
|
|
|
|
|
|
|
|
|
|
# usermsg =
|
|
|
|
|
# """
|
|
|
|
|
# User's query: $input
|
|
|
|
|
# $errornote
|
|
|
|
|
# """
|
|
|
|
|
|
|
|
|
|
# _prompt =
|
|
|
|
|
# [
|
|
|
|
|
# Dict(:name=> "system", :text=> systemmsg),
|
|
|
|
|
# Dict(:name=> "user", :text=> usermsg)
|
|
|
|
|
# ]
|
|
|
|
|
|
|
|
|
|
# # put in model format
|
|
|
|
|
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
|
|
|
|
# prompt *=
|
|
|
|
|
# """
|
|
|
|
|
# <|start_header_id|>assistant<|end_header_id|>
|
|
|
|
|
# """
|
|
|
|
|
|
|
|
|
|
# try
|
|
|
|
|
# response = a.func[:text2textInstructLLM](prompt)
|
|
|
|
|
# response = GeneralUtils.remove_french_accents(response)
|
|
|
|
|
|
|
|
|
|
# # check wheter all attributes are in the response
|
|
|
|
|
# for word in attributes
|
|
|
|
|
# if !occursin(word, response)
|
|
|
|
|
# error("$word attribute is missing")
|
|
|
|
|
# end
|
|
|
|
|
# end
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
# #check if the following attributes has more than 1 name
|
|
|
|
|
# # responsedict[:grape_variety] = split(responsedict[:grape_variety], ',')[1]
|
|
|
|
|
# # responsedict[:grape_variety] = split(responsedict[:grape_variety], '/')[1]
|
|
|
|
|
|
|
|
|
|
# responsedict[:country] = split(responsedict[:country], ',')[1]
|
|
|
|
|
# responsedict[:country] = split(responsedict[:country], '/')[1]
|
|
|
|
|
|
|
|
|
|
# responsedict[:region] = split(responsedict[:region], ',')[1]
|
|
|
|
|
# responsedict[:region] = split(responsedict[:region], '/')[1]
|
|
|
|
|
|
|
|
|
|
# delete!(responsedict, :reasoning)
|
|
|
|
|
# delete!(responsedict, :tasting_notes)
|
|
|
|
|
# delete!(responsedict, :occasion)
|
|
|
|
|
# delete!(responsedict, :food_to_be_paired_with_wine)
|
|
|
|
|
|
|
|
|
|
# # check if winery, wine_name, region, country, wine_type, grape_variety are in the query because sometime AI halucinates
|
|
|
|
|
# for i in [:grape_variety, :winery, :wine_name, :region]
|
|
|
|
|
# result = check_key_in_input(input, responsedict, attempt, maxattempt, i)
|
|
|
|
|
# if result === nothing
|
|
|
|
|
# # nothing wrong
|
|
|
|
|
# elseif result == "NA"
|
|
|
|
|
# responsedict[i] = "NA"
|
|
|
|
|
# else
|
|
|
|
|
# errornote = result
|
|
|
|
|
# error(errornote)
|
|
|
|
|
# end
|
|
|
|
|
# end
|
|
|
|
|
|
|
|
|
|
# # remove (some text)
|
|
|
|
|
# for (k, v) in responsedict
|
|
|
|
|
# _v = replace(v, r"\(.*?\)" => "")
|
|
|
|
|
# responsedict[k] = _v
|
|
|
|
|
# end
|
|
|
|
|
|
|
|
|
|
# result = ""
|
|
|
|
|
# for (k, v) in responsedict
|
|
|
|
|
# # some time LLM generate text with "(some comment)". this line removes it
|
|
|
|
|
# if !occursin("NA", v) && v != "" && !occursin("none", v) && !occursin("None", v)
|
|
|
|
|
# result *= "$k: $v, "
|
|
|
|
|
# end
|
|
|
|
|
# end
|
|
|
|
|
|
|
|
|
|
# #[PENDING] remove halucination. "highend dry white wine" --> "wine_type: white, occasion: special occasion, food_to_be_paired_with_wine: seafood, fish, country: France, Italy, USA, grape_variety: Chardonnay, Sauvignon Blanc, Pinot Grigio\nwine_notes: citrus, green apple, floral"
|
|
|
|
|
|
|
|
|
|
# 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 ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
# println("")
|
|
|
|
|
# end
|
|
|
|
|
# end
|
|
|
|
|
# error("wineattributes_wordToNumber() failed to get a response")
|
|
|
|
|
# end
|
|
|
|
|
function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
|
|
|
|
|
|
|
|
|
systemmsg =
|
|
|
|
|
"""
|
|
|
|
|
As a helpful sommelier, your task is to extract the user information from the user's query as much as possible to fill out user's preference form.
|
|
|
|
|
|
|
|
|
|
At each round of conversation, the user will give you the current situation:
|
|
|
|
|
At each round of conversation, the user will give you the following:
|
|
|
|
|
User's query: ...
|
|
|
|
|
|
|
|
|
|
You must follow the following guidelines:
|
|
|
|
|
1) If specific information required in the preference form is not available in the query or there isn't any, mark with "NA" to indicate this.
|
|
|
|
|
- If specific information required in the preference form is not available in the query or there isn't any, mark with "NA" to indicate this.
|
|
|
|
|
Additionally, words like 'any' or 'unlimited' mean no information is available.
|
|
|
|
|
2) Do not generate other comments.
|
|
|
|
|
- Do not generate other comments.
|
|
|
|
|
|
|
|
|
|
You should then respond to the user with the following points:
|
|
|
|
|
- reasoning: state your understanding of the current situation
|
|
|
|
|
- wine_name: name of the wine
|
|
|
|
|
- winery: name of the winery
|
|
|
|
|
- vintage: the year of the wine
|
|
|
|
|
- region: a region (NOT a country) where the wine is produced, such as Burgundy, Napa Valley, etc
|
|
|
|
|
- country: a country where the wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "United States"
|
|
|
|
|
- wine_type: can be one of: "red", "white", "sparkling", "rose", "dessert" or "fortified"
|
|
|
|
|
- grape_variety: the name of the primary grape used to make the wine
|
|
|
|
|
- tasting_notes: a brief description of the wine's taste, such as "butter", "oak", "fruity", etc
|
|
|
|
|
- wine_price: price of wine. For example, up to 100, less than 100, 20 to 100, 30-79.95
|
|
|
|
|
- occasion: the occasion the user is having the wine for
|
|
|
|
|
- food_to_be_paired_with_wine: food that the user will be served with the wine such as poultry, fish, steak, etc
|
|
|
|
|
You should then respond to the user with:
|
|
|
|
|
Comprehension: state your understanding of the current situation
|
|
|
|
|
Wine_name: name of the wine
|
|
|
|
|
Winery: name of the winery
|
|
|
|
|
Vintage: the year of the wine
|
|
|
|
|
Region: a region (NOT a country) where the wine is produced, such as Burgundy, Napa Valley, etc
|
|
|
|
|
Country: a country where the wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "United States"
|
|
|
|
|
Wine_type: can be one of: "red", "white", "sparkling", "rose", "dessert" or "fortified"
|
|
|
|
|
Grape_varietal: the name of the primary grape used to make the wine
|
|
|
|
|
Tasting_notes: a brief description of the wine's taste, such as "butter", "oak", "fruity", etc
|
|
|
|
|
Wine_price: price range of wine.
|
|
|
|
|
Occasion: the occasion the user is having the wine for
|
|
|
|
|
Food_to_be_paired_with_wine: food that the user will be served with the wine such as poultry, fish, steak, etc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You should only respond in the user's preference form (JSON) as described below:
|
|
|
|
|
{"reasoning": ..., "winery": ..., "wine_name": ..., "vintage": ..., "region": ..., "country": ..., "wine_type": ..., "grape_variety": ..., "tasting_notes": ..., "wine_price": ..., "occasion": ..., "food_to_be_paired_with_wine": ...}
|
|
|
|
|
You should only respond in format as described below:
|
|
|
|
|
Comprehension: ...
|
|
|
|
|
Wine_name: ...
|
|
|
|
|
Winery: ...
|
|
|
|
|
Vintage: ...
|
|
|
|
|
Region: ...
|
|
|
|
|
Country: ...
|
|
|
|
|
Wine_type:
|
|
|
|
|
Grape_varietal: ...
|
|
|
|
|
Tasting_notes: ...
|
|
|
|
|
Wine_price: ...
|
|
|
|
|
Occasion: ...
|
|
|
|
|
Food_to_be_paired_with_wine: ...
|
|
|
|
|
|
|
|
|
|
Here are some example:
|
|
|
|
|
User's query: red, Chenin Blanc, Riesling, under 20
|
|
|
|
|
{"reasoning": ..., "winery": "NA", "wine_name": "NA", "vintage": "NA", "region": "NA", "country": "NA", "wine_type": "red", "grape_variety": "Chenin Blanc, Riesling", "tasting_notes": "NA", "wine_price": "under 20", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
|
|
|
|
User's query: red, Chenin Blanc, Riesling, 20 USD
|
|
|
|
|
{"reasoning": ..., "winery": "NA", "wine_name": "NA", "vintage": "NA", "region": "NA", "country": "NA", "wine_type": "red, white", "grape_varietal": "Chenin Blanc, Riesling", "tasting_notes": "NA", "wine_price": "0-20", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
|
|
|
|
|
|
|
|
|
User's query: Domaine du Collier Saumur Blanc 2019, France, white, Chenin Blanc
|
|
|
|
|
{"reasoning": ..., "winery": "Domaine du Collier", "wine_name": "Saumur Blanc", "vintage": "2019", "region": "Saumur", "country": "France", "wine_type": "white", "grape_variety": "Chenin Blanc", "tasting_notes": "NA", "wine_price": "109", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
|
|
|
|
{"reasoning": ..., "winery": "Domaine du Collier", "wine_name": "Saumur Blanc", "vintage": "2019", "region": "Saumur", "country": "France", "wine_type": "white", "grape_varietal": "Chenin Blanc", "tasting_notes": "NA", "wine_price": "NA", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
|
|
|
|
|
|
|
|
|
Let's begin!
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
attributes = ["reasoning", "winery", "wine_name", "vintage", "region", "country", "wine_type", "grape_variety", "tasting_notes", "wine_price", "occasion", "food_to_be_paired_with_wine"]
|
|
|
|
|
header = ["Comprehension:", "Wine_name:", "Winery:", "Vintage:", "Region:", "Country:", "Wine_type:", "Grape_varietal:", "Tasting_notes:", "Wine_price:", "Occasion:", "Food_to_be_paired_with_wine:"]
|
|
|
|
|
dictkey = ["comprehension", "wine_name", "winery", "vintage", "region", "country", "wine_type", "grape_varietal", "tasting_notes", "wine_price", "occasion", "food_to_be_paired_with_wine"]
|
|
|
|
|
errornote = ""
|
|
|
|
|
maxattempt = 5
|
|
|
|
|
for attempt in 1:maxattempt
|
|
|
|
|
|
|
|
|
|
for attempt in 1:5
|
|
|
|
|
usermsg =
|
|
|
|
|
"""
|
|
|
|
|
User's query: $input
|
|
|
|
|
@@ -566,47 +400,96 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# put in model format
|
|
|
|
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
|
|
|
|
prompt *=
|
|
|
|
|
"""
|
|
|
|
|
<|start_header_id|>assistant<|end_header_id|>
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
|
|
|
|
response = a.func[:text2textInstructLLM](prompt)
|
|
|
|
|
response = GeneralUtils.remove_french_accents(response)
|
|
|
|
|
|
|
|
|
|
# check wheter all attributes are in the response
|
|
|
|
|
for word in attributes
|
|
|
|
|
checkFlag = false
|
|
|
|
|
for word in header
|
|
|
|
|
if !occursin(word, response)
|
|
|
|
|
error("$word attribute is missing")
|
|
|
|
|
errornote = "$word attribute is missing in previous attempts"
|
|
|
|
|
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
checkFlag = true
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
checkFlag == true ? continue : nothing
|
|
|
|
|
|
|
|
|
|
responsedict = copy(JSON3.read(response))
|
|
|
|
|
# check whether response has all header
|
|
|
|
|
detected_kw = GeneralUtils.detect_keyword(header, response)
|
|
|
|
|
if sum(values(detected_kw)) < length(header)
|
|
|
|
|
errornote = "\nYiemAgent extractWineAttributes_1() response does not have all header"
|
|
|
|
|
continue
|
|
|
|
|
elseif sum(values(detected_kw)) > length(header)
|
|
|
|
|
errornote = "\nYiemAgent extractWineAttributes_1() response has duplicated header"
|
|
|
|
|
continue
|
|
|
|
|
end
|
|
|
|
|
responsedict = GeneralUtils.textToDict(response, header;
|
|
|
|
|
dictKey=dictkey, symbolkey=true)
|
|
|
|
|
|
|
|
|
|
delete!(responsedict, :reasoning)
|
|
|
|
|
delete!(responsedict, :comprehension)
|
|
|
|
|
delete!(responsedict, :tasting_notes)
|
|
|
|
|
delete!(responsedict, :occasion)
|
|
|
|
|
delete!(responsedict, :food_to_be_paired_with_wine)
|
|
|
|
|
|
|
|
|
|
# check if winery, wine_name, region, country, wine_type, grape_variety are in the query because sometime AI halucinates
|
|
|
|
|
for i in [:grape_variety, :winery, :wine_name, :region]
|
|
|
|
|
content = responsedict[i]
|
|
|
|
|
if occursin(",", content)
|
|
|
|
|
println(@__FILE__, " ", @__LINE__)
|
|
|
|
|
pprintln(responsedict)
|
|
|
|
|
|
|
|
|
|
# check if winery, wine_name, region, country, wine_type, grape_varietal's value are in the query because sometime AI halucinates
|
|
|
|
|
checkFlag = false
|
|
|
|
|
for i in dictkey
|
|
|
|
|
j = Symbol(i)
|
|
|
|
|
if j ∉ [:comprehension, :tasting_notes, :occasion, :food_to_be_paired_with_wine]
|
|
|
|
|
# in case j is wine_price it needs to be checked differently because its value is ranged
|
|
|
|
|
if j == :wine_price
|
|
|
|
|
if responsedict[:wine_price] != "NA"
|
|
|
|
|
# check whether wine_price is in ranged number
|
|
|
|
|
if !occursin('-', responsedict[:wine_price])
|
|
|
|
|
errornote = "wine_price must be a range number"
|
|
|
|
|
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
checkFlag = true
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# check whether max wine_price is in the input
|
|
|
|
|
pricerange = split(responsedict[:wine_price], '-')
|
|
|
|
|
minprice = pricerange[1]
|
|
|
|
|
maxprice = pricerange[end]
|
|
|
|
|
if !occursin(maxprice, input)
|
|
|
|
|
responsedict[:wine_price] = "NA"
|
|
|
|
|
end
|
|
|
|
|
# price range like 100-100 is not good
|
|
|
|
|
if minprice == maxprice
|
|
|
|
|
errornote = "wine_price with minimum equals to maximum is not valid"
|
|
|
|
|
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
checkFlag = true
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
content = responsedict[j]
|
|
|
|
|
if typeof(content) <: AbstractVector
|
|
|
|
|
content = strip.(content)
|
|
|
|
|
elseif occursin(',', content)
|
|
|
|
|
content = split(content, ",") # sometime AI generates multiple values e.g. "Chenin Blanc, Riesling"
|
|
|
|
|
content = strip.(content)
|
|
|
|
|
else
|
|
|
|
|
content = [content]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
for x in content
|
|
|
|
|
if !occursin("NA", responsedict[i]) && !occursin(x, input)
|
|
|
|
|
for x in content #check whether price are mentioned in the input
|
|
|
|
|
if !occursin("NA", responsedict[j]) && !occursin(x, input)
|
|
|
|
|
errornote = "$x is not mentioned in the user query, you must only use the info from the query."
|
|
|
|
|
error(errornote)
|
|
|
|
|
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
checkFlag == true
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
checkFlag == true ? continue : nothing # skip the rest code if true
|
|
|
|
|
|
|
|
|
|
# remove (some text)
|
|
|
|
|
for (k, v) in responsedict
|
|
|
|
|
@@ -622,20 +505,11 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
#[PENDING] remove halucination. "highend dry white wine" --> "wine_type: white, occasion: special occasion, food_to_be_paired_with_wine: seafood, fish, country: France, Italy, USA, grape_variety: Chardonnay, Sauvignon Blanc, Pinot Grigio\nwine_notes: citrus, green apple, floral"
|
|
|
|
|
#[PENDING] remove halucination. "highend dry white wine" --> "wine_type: white, occasion: special occasion, food_to_be_paired_with_wine: seafood, fish, country: France, Italy, USA, grape_varietal: Chardonnay, Sauvignon Blanc, Pinot Grigio\nwine_notes: citrus, green apple, floral"
|
|
|
|
|
|
|
|
|
|
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 ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
println("")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
error("wineattributes_wordToNumber() failed to get a response")
|
|
|
|
|
end
|
|
|
|
|
@@ -643,12 +517,13 @@ end
|
|
|
|
|
"""
|
|
|
|
|
# TODO
|
|
|
|
|
- [PENDING] "French dry white wines with medium bod" the LLM does not recognize sweetness. use LLM self questioning to solve.
|
|
|
|
|
- [PENDING] French Syrah, Viognier, under 100. LLM extract intensiry of 3-5. why?
|
|
|
|
|
"""
|
|
|
|
|
function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
|
|
|
|
|
|
|
|
|
conversiontable =
|
|
|
|
|
"""
|
|
|
|
|
Conversion Table:
|
|
|
|
|
<Conversion Table>
|
|
|
|
|
Intensity level:
|
|
|
|
|
1 to 2: May correspond to "light-bodied" or a similar description.
|
|
|
|
|
2 to 3: May correspond to "med light bodied", "medium light" or a similar description.
|
|
|
|
|
@@ -673,10 +548,9 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|
|
|
|
3 to 4: May correspond to "medium acidity" or a similar description.
|
|
|
|
|
4 to 5: May correspond to "semi high acidity" or a similar description.
|
|
|
|
|
4 to 5: May correspond to "high acidity" or a similar description.
|
|
|
|
|
</Conversion Table>
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# chathistory = vectorOfDictToText(a.chathistory)
|
|
|
|
|
|
|
|
|
|
systemmsg =
|
|
|
|
|
"""
|
|
|
|
|
As an helpful sommelier, your task is to fill out the user's preference form based on the corresponding words from the user's query.
|
|
|
|
|
@@ -688,37 +562,69 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|
|
|
|
The preference form requires the following information:
|
|
|
|
|
sweetness, acidity, tannin, intensity
|
|
|
|
|
|
|
|
|
|
You must follow the following guidelines:
|
|
|
|
|
<You must follow the following guidelines>
|
|
|
|
|
1) If specific information required in the preference form is not available in the query or there isn't any, mark with '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 must follow the following guidelines>
|
|
|
|
|
|
|
|
|
|
You should then respond to the user with the following points:
|
|
|
|
|
- reasoning: State your understanding of the current situation
|
|
|
|
|
- sweetness: ( S ), where ( S ) represents integers indicating the range of sweetness levels. Example: 1-2
|
|
|
|
|
- acidity: ( A ), where ( A ) represents integers indicating the range of acidity level. Example: 3-5
|
|
|
|
|
- tannin: ( T ), where ( T ) represents integers indicating the range of tannin level. Example: 1-3
|
|
|
|
|
- intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
|
|
|
|
|
- notes: Anything you want to add
|
|
|
|
|
<You should then respond to the user with>
|
|
|
|
|
Sweetness_keyword: The exact keywords in the user's query describing the sweetness level of the wine.
|
|
|
|
|
Sweetness: ( S ), where ( S ) represents integers indicating the range of sweetness levels. Example: 1-2
|
|
|
|
|
Acidity_keyword: The exact keywords in the user's query describing the acidity level of the wine.
|
|
|
|
|
Acidity: ( A ), where ( A ) represents integers indicating the range of acidity level. Example: 3-5
|
|
|
|
|
Tannin_keyword: The exact keywords in the user's query describing the tannin level of the wine.
|
|
|
|
|
Tannin: ( T ), where ( T ) represents integers indicating the range of tannin level. Example: 1-3
|
|
|
|
|
Intensity_keyword: The exact keywords in the user's query describing the intensity level of the wine.
|
|
|
|
|
Intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
|
|
|
|
|
</You should then respond to the user with>
|
|
|
|
|
|
|
|
|
|
You should only respond in the form as described below:
|
|
|
|
|
reasoning: ...
|
|
|
|
|
sweetness: ...
|
|
|
|
|
acidity: ...
|
|
|
|
|
tannin: ...
|
|
|
|
|
intensity: ...
|
|
|
|
|
notes: ...
|
|
|
|
|
<You should only respond in format as described below>
|
|
|
|
|
Sweetness_keyword: ...
|
|
|
|
|
Sweetness: ...
|
|
|
|
|
Acidity_keyword: ...
|
|
|
|
|
Acidity: ...
|
|
|
|
|
Tannin_keyword: ...
|
|
|
|
|
Tannin: ...
|
|
|
|
|
Intensity_keyword: ...
|
|
|
|
|
Intensity: ...
|
|
|
|
|
</You should only respond in format as described below>
|
|
|
|
|
|
|
|
|
|
<Here are some examples>
|
|
|
|
|
User's query: I want a wine with a medium-bodied, low acidity, medium tannin.
|
|
|
|
|
Sweetness_keyword: NA
|
|
|
|
|
Sweetness: NA
|
|
|
|
|
Acidity_keyword: low acidity
|
|
|
|
|
Acidity: 1-2
|
|
|
|
|
Tannin_keyword: medium tannin
|
|
|
|
|
Tannin: 3-4
|
|
|
|
|
Intensity_keyword: medium-bodied
|
|
|
|
|
Intensity: 3-4
|
|
|
|
|
|
|
|
|
|
User's query: German red wine, under 100, pairs with spicy food
|
|
|
|
|
Sweetness_keyword: NA
|
|
|
|
|
Sweetness: NA
|
|
|
|
|
Acidity_keyword: NA
|
|
|
|
|
Acidity: NA
|
|
|
|
|
Tannin_keyword: NA
|
|
|
|
|
Tannin: NA
|
|
|
|
|
Intensity_keyword: NA
|
|
|
|
|
Intensity: NA
|
|
|
|
|
</Here are some examples>
|
|
|
|
|
|
|
|
|
|
Let's begin!
|
|
|
|
|
"""
|
|
|
|
|
header = ["Sweetness_keyword:", "Sweetness:", "Acidity_keyword:", "Acidity:", "Tannin_keyword:", "Tannin:", "Intensity_keyword:", "Intensity:"]
|
|
|
|
|
dictkey = ["sweetness_keyword", "sweetness", "acidity_keyword", "acidity", "tannin_keyword", "tannin", "intensity_keyword", "intensity"]
|
|
|
|
|
errornote = ""
|
|
|
|
|
|
|
|
|
|
# chathistory = vectorOfDictToText(a.chathistory)
|
|
|
|
|
|
|
|
|
|
for attempt in 1:10
|
|
|
|
|
usermsg =
|
|
|
|
|
"""
|
|
|
|
|
$conversiontable
|
|
|
|
|
User's query: $input
|
|
|
|
|
$errornote
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
_prompt =
|
|
|
|
|
@@ -728,46 +634,48 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# put in model format
|
|
|
|
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
|
|
|
|
prompt *=
|
|
|
|
|
"""
|
|
|
|
|
<|start_header_id|>assistant<|end_header_id|>
|
|
|
|
|
"""
|
|
|
|
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
|
|
|
|
|
|
|
|
|
attributes = ["reasoning", "sweetness", "acidity", "tannin", "intensity", "notes"]
|
|
|
|
|
|
|
|
|
|
for attempt in 1:5
|
|
|
|
|
try
|
|
|
|
|
response = a.func[: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
|
|
|
|
|
# check whether response has all header
|
|
|
|
|
detected_kw = GeneralUtils.detect_keyword(header, response)
|
|
|
|
|
if sum(values(detected_kw)) < length(header)
|
|
|
|
|
errornote = "\nYiemAgent extractWineAttributes_2() response does not have all header"
|
|
|
|
|
continue
|
|
|
|
|
elseif sum(values(detected_kw)) > length(header)
|
|
|
|
|
errornote = "\nYiemAgent extractWineAttributes_2() response has duplicated header"
|
|
|
|
|
continue
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
delete!(responsedict, :reasoning)
|
|
|
|
|
delete!(responsedict, :notes) # LLM traps. so it can add useless info here like comments.
|
|
|
|
|
responsedict = GeneralUtils.textToDict(response, header;
|
|
|
|
|
dictKey=dictkey, symbolkey=true)
|
|
|
|
|
|
|
|
|
|
# some time LLM think the user mentioning acidity and tannin but actually didn't
|
|
|
|
|
for (k, v) in responsedict
|
|
|
|
|
if k ∈ [:acidity, :tannin] && !occursin(string(k), input)
|
|
|
|
|
responsedict[k] = "NA"
|
|
|
|
|
end
|
|
|
|
|
# check whether each describing keyword is in the input to prevent halucination
|
|
|
|
|
for i in ["sweetness", "acidity", "tannin", "intensity"]
|
|
|
|
|
keyword = Symbol(i * "_keyword") # e.g. sweetness_keyword
|
|
|
|
|
value = responsedict[keyword]
|
|
|
|
|
if value != "NA" && !occursin(value, input)
|
|
|
|
|
errornote = "WARNING. Keyword $keyword: $value does not appear in the input. You must use information from the input only"
|
|
|
|
|
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
continue
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# remove (some text)
|
|
|
|
|
for (k, v) in responsedict
|
|
|
|
|
_v = replace(v, r"\(.*?\)" => "")
|
|
|
|
|
responsedict[k] = _v
|
|
|
|
|
# if value == "NA" then responsedict[i] = "NA"
|
|
|
|
|
# e.g. if sweetness_keyword == "NA" then sweetness = "NA"
|
|
|
|
|
if value == "NA"
|
|
|
|
|
responsedict[Symbol(i)] = "NA"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# some time LLM not put integer range
|
|
|
|
|
for (k, v) in responsedict
|
|
|
|
|
responsedict[k] = v
|
|
|
|
|
if length(v) > 5
|
|
|
|
|
error("non-range is not allowed. $k $v")
|
|
|
|
|
if !occursin("keyword", string(k))
|
|
|
|
|
if v !== "NA" && (!occursin('-', v) || length(v) > 5)
|
|
|
|
|
errornote = "WARNING: The non-range value {$k: $v} is not allowed. It should be specified in a range format, i.e. min-max."
|
|
|
|
|
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
continue
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
@@ -786,162 +694,123 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|
|
|
|
result *= "$k: $v, "
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
result = result[1:end-2] # remove the ending ", "
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
end
|
|
|
|
|
error("wineattributes_wordToNumber() failed to get a response")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function paraphrase(text2textInstructLLM::Function, text::String)
|
|
|
|
|
systemmsg =
|
|
|
|
|
"""
|
|
|
|
|
Your name: N/A
|
|
|
|
|
Your vision:
|
|
|
|
|
- You are a helpful assistant who help the user to paraphrase their text.
|
|
|
|
|
Your mission:
|
|
|
|
|
- To help paraphrase the user's text
|
|
|
|
|
Mission's objective includes:
|
|
|
|
|
- To help paraphrase the user's text
|
|
|
|
|
Your responsibility includes:
|
|
|
|
|
1) To help paraphrase the user's text
|
|
|
|
|
Your responsibility does NOT includes:
|
|
|
|
|
1) N/A
|
|
|
|
|
Your profile:
|
|
|
|
|
- N/A
|
|
|
|
|
Additional information:
|
|
|
|
|
- N/A
|
|
|
|
|
|
|
|
|
|
At each round of conversation, you will be given the following information:
|
|
|
|
|
Text: The user's given text
|
|
|
|
|
|
|
|
|
|
You MUST follow the following guidelines:
|
|
|
|
|
- N/A
|
|
|
|
|
|
|
|
|
|
You should follow the following guidelines:
|
|
|
|
|
- N/A
|
|
|
|
|
|
|
|
|
|
You should then respond to the user with:
|
|
|
|
|
1) Paraphrase: Paraphrased text
|
|
|
|
|
|
|
|
|
|
You should only respond in format as described below:
|
|
|
|
|
Paraphrase: ...
|
|
|
|
|
|
|
|
|
|
Let's begin!
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
errornote = ""
|
|
|
|
|
response = nothing # placeholder for show when error msg show up
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for attempt in 1:10
|
|
|
|
|
usermsg = """
|
|
|
|
|
Text: $text
|
|
|
|
|
$errornote
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
_prompt =
|
|
|
|
|
[
|
|
|
|
|
Dict(:name => "system", :text => systemmsg),
|
|
|
|
|
Dict(:name => "user", :text => usermsg)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# put in model format
|
|
|
|
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
|
|
|
|
prompt *= """
|
|
|
|
|
<|start_header_id|>assistant<|end_header_id|>
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
response = text2textInstructLLM(prompt)
|
|
|
|
|
# sometime the model response like this "here's how I would respond: ..."
|
|
|
|
|
if occursin("respond:", response)
|
|
|
|
|
errornote = "You don't need to intro your response"
|
|
|
|
|
error("\n~~~ paraphrase() response contain : ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
end
|
|
|
|
|
response = GeneralUtils.remove_french_accents(response)
|
|
|
|
|
response = replace(response, '*'=>"")
|
|
|
|
|
response = replace(response, '$' => "USD")
|
|
|
|
|
response = replace(response, '`' => "")
|
|
|
|
|
response = GeneralUtils.remove_french_accents(response)
|
|
|
|
|
|
|
|
|
|
header = ["Paraphrase:"]
|
|
|
|
|
dictkey = ["paraphrase"]
|
|
|
|
|
responsedict = GeneralUtils.textToDict(response, header;
|
|
|
|
|
dictKey=dictkey, symbolkey=true)
|
|
|
|
|
|
|
|
|
|
for i ∈ [:paraphrase]
|
|
|
|
|
if length(JSON3.write(responsedict[i])) == 0
|
|
|
|
|
error("$i is empty ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# check if there are more than 1 key per categories
|
|
|
|
|
for i ∈ [:paraphrase]
|
|
|
|
|
matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
|
|
|
|
|
if length(matchkeys) > 1
|
|
|
|
|
error("paraphrase() has more than one key per categories")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
println("\n~~~ paraphrase() ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
pprintln(Dict(responsedict))
|
|
|
|
|
|
|
|
|
|
result = responsedict[:paraphrase]
|
|
|
|
|
|
|
|
|
|
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("")
|
|
|
|
|
println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
error("wineattributes_wordToNumber() failed to get a response")
|
|
|
|
|
error("generatechat failed to generate a response")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# function recommendbox(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
|
|
|
|
# error("recommendbox")
|
|
|
|
|
# systemmsg =
|
|
|
|
|
# """
|
|
|
|
|
# As an helpful sommelier, your task is to fill out the user's preference form based on the corresponding words from the user's query.
|
|
|
|
|
|
|
|
|
|
# At each round of conversation, the user will give you the current situation:
|
|
|
|
|
# User's query: ...
|
|
|
|
|
|
|
|
|
|
# The preference form requires the following information:
|
|
|
|
|
# wine_type, price, occasion, food_to_be_paired_with_wine, country, grape_variety, flavors, aromas.
|
|
|
|
|
|
|
|
|
|
# You must follow the following guidelines:
|
|
|
|
|
# 1) If specific information required in the preference form is not available in the query or there isn't any, mark with '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:
|
|
|
|
|
# - reasoning: State your understanding of the current situation
|
|
|
|
|
# - wine_type: Can be one of: "red", "white", "sparkling", "rose", "dessert" or "fortified"
|
|
|
|
|
# - price: Must be an integer representing the cost of the wine.
|
|
|
|
|
# - occasion: ...
|
|
|
|
|
# - food_to_be_paired_with_wine: food that the user will be served with wine
|
|
|
|
|
# - country: wine's country of origin
|
|
|
|
|
# - region: wine's region of origin such as Burgundy, Napa Valley
|
|
|
|
|
# - grape variety: a single name of grape used to make wine.
|
|
|
|
|
# - flavors: Names of items that the wine tastes like.
|
|
|
|
|
# - aromas: wine's aroma
|
|
|
|
|
|
|
|
|
|
# You should only respond in the form as described below:
|
|
|
|
|
# reasoning: ...
|
|
|
|
|
# wine_type: ...
|
|
|
|
|
# price: ...
|
|
|
|
|
# occasion: ...
|
|
|
|
|
# food_to_be_paired_with_wine: ...
|
|
|
|
|
# country: ...
|
|
|
|
|
# region: ...
|
|
|
|
|
# grape_variety: ...
|
|
|
|
|
# flavors: ...
|
|
|
|
|
# aromas: ...
|
|
|
|
|
|
|
|
|
|
# Let's begin!
|
|
|
|
|
# """
|
|
|
|
|
|
|
|
|
|
# attributes = ["reasoning", "wine_type", "price", "occasion", "food_to_be_paired_with_wine", "country", "region", "grape_variety", "flavors", "aromas"]
|
|
|
|
|
# errornote = ""
|
|
|
|
|
# for attempt in 1:5
|
|
|
|
|
|
|
|
|
|
# usermsg =
|
|
|
|
|
# """
|
|
|
|
|
# User's query: $input
|
|
|
|
|
# $errornote
|
|
|
|
|
# """
|
|
|
|
|
|
|
|
|
|
# _prompt =
|
|
|
|
|
# [
|
|
|
|
|
# Dict(:name=> "system", :text=> systemmsg),
|
|
|
|
|
# Dict(:name=> "user", :text=> usermsg)
|
|
|
|
|
# ]
|
|
|
|
|
|
|
|
|
|
# # put in model format
|
|
|
|
|
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
|
|
|
|
# prompt *=
|
|
|
|
|
# """
|
|
|
|
|
# <|start_header_id|>assistant<|end_header_id|>
|
|
|
|
|
# """
|
|
|
|
|
|
|
|
|
|
# try
|
|
|
|
|
# response = a.func[: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
|
|
|
|
|
|
|
|
|
|
# #[PENDING] check if the following attributes has more than 1 name
|
|
|
|
|
# x = length(split(responsedict[:grape_variety], ",")) * length(split(responsedict[:grape_variety], "/"))
|
|
|
|
|
# if x > 1
|
|
|
|
|
# errornote = "only a single name in grape_variety is allowed"
|
|
|
|
|
# error("only a single grape_variety name is allowed")
|
|
|
|
|
# end
|
|
|
|
|
# x = length(split(responsedict[:country], ",")) * length(split(responsedict[:country], "/"))
|
|
|
|
|
# if x > 1
|
|
|
|
|
# errornote = "only a single name in country is allowed"
|
|
|
|
|
# error("only a single country name is allowed")
|
|
|
|
|
# end
|
|
|
|
|
# x = length(split(responsedict[:region], ",")) * length(split(responsedict[:region], "/"))
|
|
|
|
|
# if x > 1
|
|
|
|
|
# errornote = "only a single name in region is allowed"
|
|
|
|
|
# error("only a single region name is allowed")
|
|
|
|
|
# end
|
|
|
|
|
|
|
|
|
|
# # check if grape_variety is mentioned in the input
|
|
|
|
|
# if responsedict[:grape_variety] != "NA" && !occursin(responsedict[:grape_variety], input)
|
|
|
|
|
# error("$(responsedict[:grape_variety]) is not mentioned in the input")
|
|
|
|
|
# end
|
|
|
|
|
|
|
|
|
|
# responsedict[:flavors] = replace(responsedict[:flavors], "notes"=>"")
|
|
|
|
|
# delete!(responsedict, :reasoning)
|
|
|
|
|
# delete!(responsedict, :tasting_notes)
|
|
|
|
|
# delete!(responsedict, :flavors)
|
|
|
|
|
# delete!(responsedict, :aromas)
|
|
|
|
|
|
|
|
|
|
# # remove (some text)
|
|
|
|
|
# for (k, v) in responsedict
|
|
|
|
|
# _v = replace(v, r"\(.*?\)" => "")
|
|
|
|
|
# responsedict[k] = _v
|
|
|
|
|
# end
|
|
|
|
|
|
|
|
|
|
# result = ""
|
|
|
|
|
# for (k, v) in responsedict
|
|
|
|
|
# # some time LLM generate text with "(some comment)". this line removes it
|
|
|
|
|
# if !occursin("NA", v) && v != "" && !occursin("none", v) && !occursin("None", v)
|
|
|
|
|
# result *= "$k: $v, "
|
|
|
|
|
# end
|
|
|
|
|
# end
|
|
|
|
|
|
|
|
|
|
# #[PENDING] remove halucination. "highend dry white wine" --> "wine_type: white, occasion: special occasion, food_to_be_paired_with_wine: seafood, fish, country: France, Italy, USA, grape_variety: Chardonnay, Sauvignon Blanc, Pinot Grigio\nwine_notes: citrus, green apple, floral"
|
|
|
|
|
|
|
|
|
|
# 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.
|
|
|
|
|
|
|
|
|
|
@@ -1133,7 +1002,7 @@ end
|
|
|
|
|
# state[:isterminal] = true
|
|
|
|
|
# state[:reward] = 1
|
|
|
|
|
# end
|
|
|
|
|
# println("--> 5 Evaluator ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
# println("--> 5 Evaluator ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
|
|
|
|
# pprintln(Dict(responsedict))
|
|
|
|
|
# return responsedict[:score]
|
|
|
|
|
# catch e
|
|
|
|
|
|