update
This commit is contained in:
+189
-322
@@ -281,13 +281,7 @@ julia> input = "{\"food\": \"pizza\", \"occasion\": \"anniversary\"}"
|
||||
julia> result = checkinventory(agent, input)
|
||||
"{"wine 1": {\"Winery\": \"Pichon Baron\", \"wine name\": \"Pauillac (Grand Cru Classé)\", \"grape variety\": \"Cabernet Sauvignon\", \"year\": 2010, \"price\": \"125 USD\", \"stock ID\": \"ar-17\"}, }"
|
||||
```
|
||||
|
||||
# TODO
|
||||
- [] update docs
|
||||
- [x] implement the function
|
||||
|
||||
# Signature
|
||||
"""
|
||||
"""
|
||||
function checkwine(a::T1, input::T2; maxattempt::Int=3
|
||||
) where {T1<:agent, T2<:AbstractString}
|
||||
|
||||
@@ -309,6 +303,7 @@ function checkwine(a::T1, input::T2; maxattempt::Int=3
|
||||
inventoryquery = "Retrieves $retrieve_attributes of wines that match the following criteria - {$_inventoryquery}"
|
||||
println("\ncheckinventory input: $inventoryquery ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
# add suppport for similarSQLVectorDB
|
||||
#WORKING
|
||||
textresult, rawresponse = SQLLLM.query(
|
||||
inventoryquery,
|
||||
a.context.executeSQL,
|
||||
@@ -316,6 +311,7 @@ function checkwine(a::T1, input::T2; maxattempt::Int=3
|
||||
insertSQLVectorDB=a.context.insertSQLVectorDB,
|
||||
similarSQLVectorDB=a.context.similarSQLVectorDB,
|
||||
llmFormatName="qwen3")
|
||||
error(5555)
|
||||
# check if all of retrieve_attributes appears in textresult
|
||||
isin = [occursin(x, textresult) for x in retrieve_attributes]
|
||||
# check if rawresponse type is DataFrame so that I can check for column
|
||||
@@ -349,29 +345,28 @@ end
|
||||
```jldoctest
|
||||
julia>
|
||||
```
|
||||
|
||||
# TODO
|
||||
- [] update docstring
|
||||
- implement the function
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
|
||||
)::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 following:
|
||||
- The query: the query provided by the user.
|
||||
|
||||
You must follow the following guidelines:
|
||||
<situation>
|
||||
At each round of conversation, the user provides the following:
|
||||
- The query: the query provided by the user.
|
||||
</situation>
|
||||
<objective>
|
||||
Extract information from the user's query as much as possible according to wine attributes extraction guidelines to fill out user's preference form.
|
||||
</objective>
|
||||
<your responsibility includes>
|
||||
Fulfill the objective.
|
||||
</your responsibility includes>
|
||||
<wine attributes extraction guidelines>
|
||||
- If specific information required in the preference form is not available in the query or there isn't any, mark with "N/A" to indicate this.
|
||||
Additionally, words like 'any' or 'unlimited' mean no information is available.
|
||||
- Do not generate other comments.
|
||||
|
||||
You should then respond to the user with:
|
||||
</wine attributes extraction guidelines>
|
||||
<you should then respond to the user with>
|
||||
wine_name: name of the wine
|
||||
winery: name of the winery
|
||||
vintage: the year of the wine
|
||||
@@ -384,27 +379,23 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
|
||||
wine_price_max: maximum price range of wine. Example: For wine price 20, wine_price_max will be 20. For wine price 10 to 100, wine_price_max will be 100.
|
||||
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 JSON format as described below:
|
||||
{
|
||||
"wine_name": "...",
|
||||
"winery": "...",
|
||||
"vintage": "...",
|
||||
"region": "...",
|
||||
"country": "...",
|
||||
"wine_type": "...",
|
||||
"grape_varietal": "...",
|
||||
"tasting_notes": "...",
|
||||
"wine_price_min": "...",
|
||||
"wine_price_max": "...",
|
||||
"occasion": "...",
|
||||
"food_to_be_paired_with_wine": "..."
|
||||
}
|
||||
|
||||
Here are some example:
|
||||
|
||||
</you should then respond to the user with>
|
||||
<you should only respond in JSON format as described below>
|
||||
"wine_name": "...",
|
||||
"winery": "...",
|
||||
"vintage": "...",
|
||||
"region": "...",
|
||||
"country": "...",
|
||||
"wine_type": "...",
|
||||
"grape_varietal": "...",
|
||||
"tasting_notes": "...",
|
||||
"wine_price_min": "...",
|
||||
"wine_price_max": "...",
|
||||
"occasion": "...",
|
||||
"food_to_be_paired_with_wine": "..."
|
||||
</you should only respond in JSON format as described below>
|
||||
<here are some examples>
|
||||
User's query: red, Chenin Blanc, Riesling, 20 USD from Tuscany, Italy or Napa Valley, USA
|
||||
{
|
||||
"wine_name": "N/A",
|
||||
"winery": "N/A",
|
||||
"vintage": "N/A",
|
||||
@@ -417,10 +408,8 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
|
||||
"wine_price_max": "20",
|
||||
"occasion": "N/A",
|
||||
"food_to_be_paired_with_wine": "N/A"
|
||||
}
|
||||
|
||||
User's query: Domaine du Collier Saumur Blanc 2019, France, white, Merlot
|
||||
{
|
||||
"wine_name": "Saumur Blanc",
|
||||
"winery": "Domaine du Collier",
|
||||
"vintage": "2019",
|
||||
@@ -433,171 +422,71 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
|
||||
"wine_price_max": "N/A",
|
||||
"occasion": "N/A",
|
||||
"food_to_be_paired_with_wine": "N/A"
|
||||
}
|
||||
|
||||
Let's begin!
|
||||
</here are some examples>
|
||||
"""
|
||||
requiredKeys = ["wine_name", "winery", "vintage", "region", "country", "wine_type", "grape_varietal", "tasting_notes", "wine_price_min", "wine_price_max", "occasion", "food_to_be_paired_with_wine"]
|
||||
errornote = ""
|
||||
context =
|
||||
"""
|
||||
<internal_context_for_assistant>
|
||||
$errornote
|
||||
</internal_context_for_assistant>
|
||||
"""
|
||||
|
||||
requiredKeys = [:wine_name, :winery, :vintage, :region, :country, :wine_type, :grape_varietal, :tasting_notes, :wine_price_min, :wine_price_max, :occasion, :food_to_be_paired_with_wine]
|
||||
errornote = "N/A"
|
||||
input = context * input
|
||||
|
||||
msg = Dict(
|
||||
"model" => "gemma-4-E4B-it-UD-Q4_K_XL",
|
||||
"messages" => [
|
||||
Dict(
|
||||
"role" => "system",
|
||||
"content" => [
|
||||
Dict("type" => "text", "text" => systemmsg),
|
||||
]
|
||||
),
|
||||
Dict(
|
||||
"role" => "user",
|
||||
"content" => [
|
||||
Dict("type" => "text", "text" => input),
|
||||
]
|
||||
),
|
||||
],
|
||||
"temperature" => 0.7
|
||||
)
|
||||
|
||||
for attempt in 1:maxattempt
|
||||
usermsg =
|
||||
"""
|
||||
$input
|
||||
"""
|
||||
context =
|
||||
"""
|
||||
<context>
|
||||
P.S. $errornote
|
||||
</context>
|
||||
/no_think
|
||||
"""
|
||||
response = a.context.text2textInstructLLM(a.id, msg)
|
||||
response = GeneralUtils.clean_json_response(response)
|
||||
println("\n--- extractWineAttributes_1-1()")
|
||||
println(response)
|
||||
println("--- \n")
|
||||
|
||||
unformatPrompt =
|
||||
[
|
||||
Dict("name"=> "system", "text"=> systemmsg),
|
||||
Dict("name"=> "user", "text"=> usermsg)
|
||||
]
|
||||
|
||||
# put in model format
|
||||
prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
|
||||
# add info
|
||||
prompt = prompt * context
|
||||
|
||||
response = a.context.text2textInstructLLM(prompt; modelsize="medium", senderId=a.id)
|
||||
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
||||
response = GeneralUtils.remove_french_accents(response)
|
||||
think, response = GeneralUtils.extractthink(response)
|
||||
|
||||
responsedict = nothing
|
||||
try
|
||||
responsedict = copy(JSON.parsefile(response))
|
||||
_responsedict = JSON.parse(response)
|
||||
responsedict = GeneralUtils.dictify(_responsedict, keytype=String)
|
||||
catch
|
||||
println("\nERROR YiemAgent extractWineAttributes_1() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
println("\nERROR YiemAgent extractWineAttributes_1() failed to parse response: $response", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
end
|
||||
|
||||
# check whether all answer's key points are in responsedict
|
||||
_responsedictKey = keys(responsedict)
|
||||
responsedictKey = [i for i in _responsedictKey] # convert into a list
|
||||
is_requiredKeys_in_responsedictKey = [i ∈ responsedictKey for i in requiredKeys]
|
||||
|
||||
if length(is_requiredKeys_in_responsedictKey) > length(requiredKeys)
|
||||
errornote = "Your previous attempt has more key points than answer's required key points."
|
||||
println("\nERROR YiemAgent extractWineAttributes_1() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
elseif !all(is_requiredKeys_in_responsedictKey)
|
||||
zeroind = findall(x -> x == 0, is_requiredKeys_in_responsedictKey)
|
||||
missingkeys = [requiredKeys[i] for i in zeroind]
|
||||
errornote = "$missingkeys are missing from your previous response"
|
||||
println("\nERROR YiemAgent extractWineAttributes_1() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
ispass, errormsg = checkAgentResponse_JSON(responsedict, requiredKeys)
|
||||
if !ispass
|
||||
errornote = errormsg
|
||||
println("\nERROR YiemAgent extractWineAttributes_1() $errornote --(not qualify response)> $responsedict", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
||||
continue
|
||||
end
|
||||
|
||||
# # check whether response has all header
|
||||
# detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||
# kwvalue = [i for i in values(detected_kw)]
|
||||
# zeroind = findall(x -> x == 0, kwvalue)
|
||||
# missingkeys = [header[i] for i in zeroind]
|
||||
# if 0 ∈ values(detected_kw)
|
||||
# errornote = "$missingkeys are missing from your previous response"
|
||||
# println("\nERROR YiemAgent decisionMaker() $errornote:\n$response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
# continue
|
||||
# elseif sum(values(detected_kw)) > length(header)
|
||||
# errornote = "Your previous attempt has duplicated points"
|
||||
# println("\nERROR YiemAgent decisionMaker() $errornote:\n$response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
# continue
|
||||
# end
|
||||
|
||||
# # check whether response has all answer's key points
|
||||
# detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||
# if 0 ∈ values(detected_kw)
|
||||
# errornote = "In your previous attempts, the response does not have all answer's key points"
|
||||
# println("\nYiemAgent extractWineAttributes_1() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
# continue
|
||||
# elseif sum(values(detected_kw)) > length(header)
|
||||
# errornote = "In your previous attempts, the response has duplicated answer's key points"
|
||||
# println("\nYiemAgent extractWineAttributes_1() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
# println(response)
|
||||
# continue
|
||||
# end
|
||||
# responsedict = GeneralUtils.textToDict(response, header;
|
||||
# dictKey=dictkey, symbolkey=true)
|
||||
|
||||
removekeys = [:thought, :tasting_notes, :occasion, :food_to_be_paired_with_wine, :vintage]
|
||||
removekeys = ["thought", "tasting_notes", "occasion", "food_to_be_paired_with_wine", "vintage"]
|
||||
for i in removekeys
|
||||
delete!(responsedict, i)
|
||||
end
|
||||
|
||||
|
||||
|
||||
delete!(responsedict, :thought)
|
||||
delete!(responsedict, :tasting_notes)
|
||||
delete!(responsedict, :occasion)
|
||||
delete!(responsedict, :food_to_be_paired_with_wine)
|
||||
delete!(responsedict, :vintage)
|
||||
|
||||
# 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 requiredKeys
|
||||
j = Symbol(i)
|
||||
if j ∉ removekeys
|
||||
# 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"] != "N/A"
|
||||
if !occursin("to", responsedict["wine_price"])
|
||||
errornote = "In your previous attempt, the 'wine_price' was set to $(responsedict["wine_price"]) which is not a correct format. Please adjust it accordingly."
|
||||
println("\nERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
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"] = "N/A"
|
||||
# end
|
||||
# # price range like 100-100 is not good
|
||||
# if minprice == maxprice
|
||||
# errornote = "In your previous attempt, you inputted 'wine_price' with a 'minimum' value equaling the 'maximum', which is not valid."
|
||||
# println("\nERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
# 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 #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."
|
||||
# println("ERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
# 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
|
||||
_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
|
||||
@@ -605,20 +494,18 @@ if responsedict["wine_price"] != "N/A"
|
||||
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_varietal: Chardonnay, Sauvignon Blanc, Pinot Grigio\nwine_notes: citrus, green apple, floral"
|
||||
|
||||
result = result[1:end-2] # remove the ending ", "
|
||||
|
||||
println("\n--- extractWineAttributes_1-2()")
|
||||
println(result)
|
||||
println("--- \n")
|
||||
return result
|
||||
end
|
||||
error("wineattributes_wordToNumber() failed to get a response")
|
||||
error("extractWineAttributes_1() failed to get a response")
|
||||
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?
|
||||
- TODO "French dry white wines with medium bod" the LLM does not recognize sweetness. use LLM self questioning to solve.
|
||||
- TODO 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}
|
||||
|
||||
@@ -654,21 +541,25 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
||||
|
||||
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, you will be given the following information:
|
||||
conversion_table: a conversion table that maps descriptive words to their corresponding integer levels
|
||||
query: the words from the user's query that describe their preferences
|
||||
|
||||
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 or there isn't any, mark with 'N/A' to indicate this.
|
||||
<situation>
|
||||
At each round of conversation, you will be given the following information:
|
||||
conversion_table: a conversion table that maps descriptive words to their corresponding integer levels
|
||||
query: the words from the user's query that describe their preferences
|
||||
</situation>
|
||||
<objective>
|
||||
Fill out the user's preference form based on the corresponding words from the user's query according to the guidelines.
|
||||
</objective>
|
||||
<your responsibility includes>
|
||||
Fulfill the objective
|
||||
</your responsibility includes>
|
||||
<guidelines>
|
||||
- The preference form requires sweetness, acidity, tannin, intensity infomation
|
||||
- If specific information required in the preference form is not available in the query or there isn't any, mark with 'N/A' 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:
|
||||
- Use the conversion table to convert the descriptive word level of sweetness, intensity, tannin, and acidity into a corresponding integer.
|
||||
- Do not generate other comments.
|
||||
</guidelines>
|
||||
<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.
|
||||
@@ -677,157 +568,133 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
||||
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 only respond in JSON format as described below:
|
||||
{
|
||||
</you should then respond to the user with>
|
||||
<you should only respond in JSON format as described below>
|
||||
"sweetness_keyword": "...",
|
||||
"sweetness": "...",
|
||||
"sweetness_min": "...",
|
||||
"sweetness_max": "...",
|
||||
"acidity_keyword": "...",
|
||||
"acidity": "...",
|
||||
"acidity_min": "...",
|
||||
"acidity_max": "...",
|
||||
"tannin_keyword": "...",
|
||||
"tannin": "...",
|
||||
"tannin_min": "...",
|
||||
"tannin_max": "...",
|
||||
"intensity_keyword": "...",
|
||||
"intensity": "..."
|
||||
}
|
||||
|
||||
Here are some examples:
|
||||
"intensity_min": "...",
|
||||
"intensity_max": "..."
|
||||
</you should only respond in JSON 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": "N/A",
|
||||
"sweetness": "N/A",
|
||||
"acidity_keyword": "low acidity",
|
||||
"acidity": "1-2",
|
||||
"tannin_keyword": "medium tannin",
|
||||
"tannin": "3-4",
|
||||
"intensity_keyword": "medium-bodied",
|
||||
"intensity": "3-4"
|
||||
}
|
||||
"sweetness_keyword": "N/A",
|
||||
"sweetness_min": "N/A",
|
||||
"sweetness_max": "N/A",
|
||||
"acidity_keyword": "low acidity",
|
||||
"acidity_min": 1,
|
||||
"acidity_max": 2,
|
||||
"tannin_keyword": "medium tannin",
|
||||
"tannin_min": 3,
|
||||
"tannin_max": 4,
|
||||
"intensity_keyword": "medium-bodied",
|
||||
"intensity_min": 3,
|
||||
"intensity_max": 4
|
||||
|
||||
User's query: German red wine, under 100, pairs with spicy food
|
||||
{
|
||||
"sweetness_keyword": "N/A",
|
||||
"sweetness": "N/A",
|
||||
"acidity_keyword": "N/A",
|
||||
"acidity": "N/A",
|
||||
"tannin_keyword": "N/A",
|
||||
"tannin": "N/A",
|
||||
"intensity_keyword": "N/A",
|
||||
"intensity": "N/A"
|
||||
}
|
||||
|
||||
Let's begin!
|
||||
User's query: German red wine, under 100, pairs with spicy food.
|
||||
"sweetness_keyword": "N/A",
|
||||
"sweetness_min": "N/A",
|
||||
"sweetness_max": "N/A",
|
||||
"acidity_keyword": "N/A",
|
||||
"acidity_min": "N/A",
|
||||
"acidity_max": "N/A",
|
||||
"tannin_keyword": "N/A",
|
||||
"tannin_min": "N/A",
|
||||
"tannin_max": "N/A",
|
||||
"intensity_keyword": "N/A",
|
||||
"intensity_min": "N/A",
|
||||
"intensity_max": "N/A"
|
||||
<here are some examples>
|
||||
"""
|
||||
requiredKeys = ["sweetness_keyword", "sweetness_min", "sweetness_max",
|
||||
"acidity_keyword", "acidity_min", "acidity_max",
|
||||
"tannin_keyword", "tannin_min", "tannin_max",
|
||||
"intensity_keyword", "intensity_min", "intensity_max"]
|
||||
errornote = ""
|
||||
context =
|
||||
"""
|
||||
<internal_context_for_assistant>
|
||||
$conversiontable
|
||||
$errornote
|
||||
</internal_context_for_assistant>
|
||||
"""
|
||||
requiredKeys = [:sweetness_keyword, :sweetness, :acidity_keyword, :acidity, :tannin_keyword, :tannin, :intensity_keyword, :intensity]
|
||||
|
||||
# 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 = "N/A"
|
||||
input = context * input
|
||||
|
||||
msg = Dict(
|
||||
"model" => "gemma-4-E4B-it-UD-Q4_K_XL",
|
||||
"messages" => [
|
||||
Dict(
|
||||
"role" => "system",
|
||||
"content" => [
|
||||
Dict("type" => "text", "text" => systemmsg),
|
||||
]
|
||||
),
|
||||
Dict(
|
||||
"role" => "user",
|
||||
"content" => [
|
||||
Dict("type" => "text", "text" => input),
|
||||
]
|
||||
),
|
||||
],
|
||||
"temperature" => 0.7
|
||||
)
|
||||
|
||||
for attempt in 1:10
|
||||
context =
|
||||
"""
|
||||
$conversiontable
|
||||
<query>
|
||||
$input
|
||||
</query>
|
||||
P.S. $errornote
|
||||
/no_think
|
||||
"""
|
||||
response = a.context.text2textInstructLLM(a.id, msg)
|
||||
response = GeneralUtils.clean_json_response(response)
|
||||
println("\n--- extractWineAttributes_2-1()")
|
||||
println(response)
|
||||
println("--- \n")
|
||||
|
||||
unformatPrompt =
|
||||
[
|
||||
Dict("name"=> "system", "text"=> systemmsg),
|
||||
]
|
||||
|
||||
# put in model format
|
||||
prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
|
||||
# add info
|
||||
prompt = prompt * context
|
||||
|
||||
response = a.context.text2textInstructLLM(prompt; modelsize="medium", senderId=a.id)
|
||||
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
||||
response = GeneralUtils.remove_french_accents(response)
|
||||
think, response = GeneralUtils.extractthink(response)
|
||||
|
||||
responsedict = nothing
|
||||
try
|
||||
responsedict = copy(JSON.parsefile(response))
|
||||
_responsedict = JSON.parse(response)
|
||||
responsedict = GeneralUtils.dictify(_responsedict, keytype=String)
|
||||
catch
|
||||
println("\nERROR YiemAgent extractWineAttributes_2() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
println("\nERROR YiemAgent extractWineAttributes_2() failed to parse response: $response", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
end
|
||||
|
||||
# check whether all answer's key points are in responsedict
|
||||
_responsedictKey = keys(responsedict)
|
||||
responsedictKey = [i for i in _responsedictKey] # convert into a list
|
||||
is_requiredKeys_in_responsedictKey = [i ∈ responsedictKey for i in requiredKeys]
|
||||
|
||||
if length(is_requiredKeys_in_responsedictKey) > length(requiredKeys)
|
||||
errornote = "Your previous attempt has more key points than answer's required key points."
|
||||
println("\nERROR YiemAgent extractWineAttributes_2() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
ispass, errormsg = checkAgentResponse_JSON(responsedict, requiredKeys)
|
||||
if !ispass
|
||||
errornote = errormsg
|
||||
println("\nERROR YiemAgent extractWineAttributes_2() $errornote --(not qualify response)> $responsedict", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
||||
continue
|
||||
elseif !all(is_requiredKeys_in_responsedictKey)
|
||||
zeroind = findall(x -> x == 0, is_requiredKeys_in_responsedictKey)
|
||||
missingkeys = [requiredKeys[i] for i in zeroind]
|
||||
errornote = "$missingkeys are missing from your previous response"
|
||||
println("\nERROR YiemAgent extractWineAttributes_2() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
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 != "N/A" && !occursin(value, input)
|
||||
errornote = "In your previous attempt, keyword $keyword: $value does not appear in the input. You must use information from the input only"
|
||||
println("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
end
|
||||
|
||||
# if value == "N/A" then responsedict[i] = "N/A"
|
||||
# e.g. if sweetness_keyword == "N/A" then sweetness = "N/A"
|
||||
if value == "N/A"
|
||||
responsedict[Symbol(i)] = "N/A"
|
||||
end
|
||||
end
|
||||
|
||||
# some time LLM not put integer range
|
||||
for (k, v) in responsedict
|
||||
if !occursin("keyword", string(k))
|
||||
if v !== "N/A" && (!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("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# some time LLM says N/A-2. Need to convert N/A to 1
|
||||
for (k, v) in responsedict
|
||||
if occursin("N/A", v) && occursin("-", v)
|
||||
new_v = replace(v, "N/A"=>"1")
|
||||
responsedict[k] = new_v
|
||||
end
|
||||
end
|
||||
|
||||
# delete some key words from responsedict
|
||||
for (k, v) in responsedict
|
||||
if k ∈ [:sweetness_keyword, :acidity_keyword, :tannin_keyword, :intensity_keyword]
|
||||
if k ∈ ["sweetness_keyword", "acidity_keyword", "tannin_keyword", "intensity_keyword"]
|
||||
delete!(responsedict, k)
|
||||
end
|
||||
end
|
||||
|
||||
# get result in String. Reject "N/A" value
|
||||
result = ""
|
||||
for (k, v) in responsedict
|
||||
# some time LLM generate text with "(some comment)". this line removes it
|
||||
if !occursin("N/A", v)
|
||||
if typeof(v) <: Number
|
||||
result *= "$k: $v, "
|
||||
elseif typeof(v) == String && !occursin("N/A", v)
|
||||
result *= "$k: $v, "
|
||||
end
|
||||
end
|
||||
result = result[1:end-2] # remove the ending ", "
|
||||
|
||||
println("\n--- extractWineAttributes_2-2()")
|
||||
println(result)
|
||||
println("--- \n")
|
||||
return result
|
||||
end
|
||||
error("wineattributes_wordToNumber() failed to get a response")
|
||||
error("extractWineAttributes_2() failed to get a response")
|
||||
end
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user