update
This commit is contained in:
882
src/interface.jl
882
src/interface.jl
File diff suppressed because it is too large
Load Diff
@@ -339,7 +339,7 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
|
|||||||
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.
|
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:
|
At each round of conversation, the user will give you the following:
|
||||||
User's query: ...
|
- The query: the query provided by the user.
|
||||||
|
|
||||||
You must follow the following guidelines:
|
You must follow the following 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.
|
- 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.
|
||||||
@@ -347,121 +347,167 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
|
|||||||
- Do not generate other comments.
|
- Do not generate other comments.
|
||||||
|
|
||||||
You should then respond to the user with:
|
You should then respond to the user with:
|
||||||
Wine_name: name of the wine
|
wine_name: name of the wine
|
||||||
Winery: name of the winery
|
winery: name of the winery
|
||||||
Vintage: the year of the wine
|
vintage: the year of the wine
|
||||||
Region: a region (NOT a country) where the wine is produced, such as Burgundy, Napa Valley, etc
|
region: a region (NOT a country) where the wine is produced, such as Burgundy, Napa Valley, etc
|
||||||
Country: a country where wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "United States"
|
country: a country where 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"
|
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
|
grape_varietal: the name of the primary grape used to make the wine
|
||||||
Tasting_notes: a word describe the wine's flavor, such as "butter", "oak", "fruity", "raspberry", "earthy", "floral", etc
|
tasting_notes: a word describe the wine's flavor, such as "butter", "oak", "fruity", "raspberry", "earthy", "floral", etc
|
||||||
Wine_price_range: price range of wine. Example: For price 10-20, price range will be "10 to 20". For price 100, price range will be 0 to 100.
|
wine_price_min: minimum price range of wine. Example: For wine price 20, wine_price_min will be 0. For wine price 10 to 100, wine_price_min will be 10.
|
||||||
Occasion: the occasion the user is having the wine for
|
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.
|
||||||
Food_to_be_paired_with_wine: food that the user will be served with the wine such as poultry, fish, steak, etc
|
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 format as described below:
|
You should only respond in JSON format as described below:
|
||||||
Wine_name: ...
|
{
|
||||||
Winery: ...
|
"wine_name": "...",
|
||||||
Vintage: ...
|
"winery": "...",
|
||||||
Region: ...
|
"vintage": "...",
|
||||||
Country: ...
|
"region": "...",
|
||||||
Wine_type:
|
"country": "...",
|
||||||
Grape_varietal: ...
|
"wine_type": "...",
|
||||||
Tasting_notes: ...
|
"grape_varietal": "...",
|
||||||
Wine_price_range: ...
|
"tasting_notes": "...",
|
||||||
Occasion: ...
|
"wine_price_min": "...",
|
||||||
Food_to_be_paired_with_wine: ...
|
"wine_price_max": "...",
|
||||||
|
"occasion": "...",
|
||||||
|
"food_to_be_paired_with_wine": "..."
|
||||||
|
}
|
||||||
|
|
||||||
Here are some example:
|
Here are some example:
|
||||||
|
|
||||||
User's query: red, Chenin Blanc, Riesling, 20 USD from Tuscany, Italy or Napa Valley, USA
|
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. Region: Tuscany, Napa Valley. Country: Italy, United States. Wine_type: red, white. Grape_varietal: Chenin Blanc, Riesling. Tasting_notes: citrus. Wine_price_range: 0 to 20. Occasion: N/A. Food_to_be_paired_with_wine: N/A
|
{
|
||||||
|
"wine_name": "N/A",
|
||||||
|
"winery": "N/A",
|
||||||
|
"vintage": "N/A",
|
||||||
|
"region": "Tuscany, Napa Valley",
|
||||||
|
"country": "Italy, United States",
|
||||||
|
"wine_type": "red, white",
|
||||||
|
"grape_varietal": "Chenin Blanc, Riesling",
|
||||||
|
"tasting_notes": "citrus",
|
||||||
|
"wine_price_min": "0",
|
||||||
|
"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
|
User's query: Domaine du Collier Saumur Blanc 2019, France, white, Merlot
|
||||||
Winery: Domaine du Collier. Wine_name: Saumur Blanc. Vintage: 2019. Region: Saumur. Country: France. Wine_type: white. Grape_varietal: Merlot. Tasting_notes: plum. Wine_price_range: N/A. Occasion: N/A. Food_to_be_paired_with_wine: N/A.
|
{
|
||||||
|
"wine_name": "Saumur Blanc",
|
||||||
|
"winery": "Domaine du Collier",
|
||||||
|
"vintage": "2019",
|
||||||
|
"region": "Saumur",
|
||||||
|
"country": "France",
|
||||||
|
"wine_type": "white",
|
||||||
|
"grape_varietal": "Merlot",
|
||||||
|
"tasting_notes": "plum",
|
||||||
|
"wine_price_min": "N/A",
|
||||||
|
"wine_price_max": "N/A",
|
||||||
|
"occasion": "N/A",
|
||||||
|
"food_to_be_paired_with_wine": "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
header = ["Wine_name:", "Winery:", "Vintage:", "Region:", "Country:", "Wine_type:", "Grape_varietal:", "Tasting_notes:", "Wine_price_range:", "Occasion:", "Food_to_be_paired_with_wine:"]
|
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]
|
||||||
dictkey = ["wine_name", "winery", "vintage", "region", "country", "wine_type", "grape_varietal", "tasting_notes", "wine_price_range", "occasion", "food_to_be_paired_with_wine"]
|
|
||||||
errornote = "N/A"
|
errornote = "N/A"
|
||||||
|
|
||||||
llmkwargs=Dict(
|
|
||||||
:num_ctx => 32768,
|
|
||||||
:temperature => 0.2,
|
|
||||||
)
|
|
||||||
|
|
||||||
for attempt in 1:maxattempt
|
for attempt in 1:maxattempt
|
||||||
usermsg =
|
usermsg =
|
||||||
"""
|
"""
|
||||||
$input
|
$input
|
||||||
"""
|
"""
|
||||||
assistantinfo =
|
context =
|
||||||
"""
|
"""
|
||||||
<information>
|
<context>
|
||||||
P.S. $errornote
|
P.S. $errornote
|
||||||
</information>
|
</context>
|
||||||
|
/no_think
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_prompt =
|
unformatPrompt =
|
||||||
[
|
[
|
||||||
Dict(:name=> "system", :text=> systemmsg),
|
Dict(:name=> "system", :text=> systemmsg),
|
||||||
Dict(:name=> "user", :text=> usermsg)
|
Dict(:name=> "user", :text=> usermsg)
|
||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt, a.llmFormatName)
|
prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
|
||||||
# add info
|
# add info
|
||||||
prompt = prompt * assistantinfo
|
prompt = prompt * context
|
||||||
|
|
||||||
response = a.func[:text2textInstructLLM](prompt;
|
response = a.func[:text2textInstructLLM](prompt; modelsize="medium", senderId=a.id)
|
||||||
modelsize="medium", llmkwargs=llmkwargs, senderId=a.id)
|
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
|
||||||
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
||||||
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
think, response = GeneralUtils.extractthink(response)
|
think, response = GeneralUtils.extractthink(response)
|
||||||
|
|
||||||
# check whether response has all header
|
responsedict = nothing
|
||||||
detected_kw = GeneralUtils.detect_keyword(header, response)
|
try
|
||||||
kwvalue = [i for i in values(detected_kw)]
|
responsedict = copy(JSON3.read(response))
|
||||||
zeroind = findall(x -> x == 0, kwvalue)
|
catch
|
||||||
missingkeys = [header[i] for i in zeroind]
|
println("\nERROR YiemAgent extractWineAttributes_1() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
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
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
# check whether response has all answer's key points
|
# check whether all answer's key points are in responsedict
|
||||||
detected_kw = GeneralUtils.detect_keyword(header, response)
|
_responsedictKey = keys(responsedict)
|
||||||
if 0 ∈ values(detected_kw)
|
responsedictKey = [i for i in _responsedictKey] # convert into a list
|
||||||
errornote = "In your previous attempts, the response does not have all answer's key points"
|
is_requiredKeys_in_responsedictKey = [i ∈ responsedictKey for i in requiredKeys]
|
||||||
println("\nYiemAgent extractWineAttributes_1() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
|
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
|
continue
|
||||||
elseif sum(values(detected_kw)) > length(header)
|
elseif !all(is_requiredKeys_in_responsedictKey)
|
||||||
errornote = "In your previous attempts, the response has duplicated answer's key points"
|
zeroind = findall(x -> x == 0, is_requiredKeys_in_responsedictKey)
|
||||||
println("\nYiemAgent extractWineAttributes_1() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
missingkeys = [requiredKeys[i] for i in zeroind]
|
||||||
println(response)
|
errornote = "$missingkeys are missing from your previous response"
|
||||||
|
println("\nERROR YiemAgent extractWineAttributes_1() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
responsedict = GeneralUtils.textToDict(response, header;
|
|
||||||
dictKey=dictkey, symbolkey=true)
|
# # 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)
|
||||||
|
|
||||||
delete!(responsedict, :thought)
|
delete!(responsedict, :thought)
|
||||||
delete!(responsedict, :tasting_notes)
|
delete!(responsedict, :tasting_notes)
|
||||||
delete!(responsedict, :occasion)
|
delete!(responsedict, :occasion)
|
||||||
delete!(responsedict, :food_to_be_paired_with_wine)
|
delete!(responsedict, :food_to_be_paired_with_wine)
|
||||||
|
|
||||||
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
|
# check if winery, wine_name, region, country, wine_type, grape_varietal's value are in the query because sometime AI halucinates
|
||||||
checkFlag = false
|
checkFlag = false
|
||||||
for i in dictkey
|
for i in requiredKeys
|
||||||
j = Symbol(i)
|
j = Symbol(i)
|
||||||
if j ∉ [:thought, :tasting_notes, :occasion, :food_to_be_paired_with_wine]
|
if j ∉ [:thought, :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
|
# in case j is wine_price it needs to be checked differently because its value is ranged
|
||||||
@@ -759,7 +805,8 @@ function paraphrase(text2textInstructLLM::Function, text::String)
|
|||||||
|
|
||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
|
#[WORKING] use JSON3 the same as extractWineAttributes_1 is better
|
||||||
|
#[WORKING] change this function to use the same format use decisionMater
|
||||||
header = ["Paraphrase:"]
|
header = ["Paraphrase:"]
|
||||||
dictkey = ["paraphrase"]
|
dictkey = ["paraphrase"]
|
||||||
|
|
||||||
|
|||||||
@@ -193,8 +193,7 @@ function sommelier(
|
|||||||
"""
|
"""
|
||||||
memory = Dict{Symbol, Any}(
|
memory = Dict{Symbol, Any}(
|
||||||
:shortmem=> OrderedDict{Symbol, Any}(
|
:shortmem=> OrderedDict{Symbol, Any}(
|
||||||
:available_wine=> [],
|
:db_search_result=> Any[],
|
||||||
:found_wine=> [], # used by decisionMaker(). This is to prevent decisionMaker() keep presenting the same wines
|
|
||||||
),
|
),
|
||||||
:events=> Vector{Dict{Symbol, Any}}(),
|
:events=> Vector{Dict{Symbol, Any}}(),
|
||||||
:state=> Dict{Symbol, Any}(
|
:state=> Dict{Symbol, Any}(
|
||||||
|
|||||||
42
src/util.jl
42
src/util.jl
@@ -1,7 +1,7 @@
|
|||||||
module util
|
module util
|
||||||
|
|
||||||
export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
|
export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
|
||||||
availableWineToText, createEventsLog
|
availableWineToText, createEventsLog, createChatLog
|
||||||
|
|
||||||
using UUIDs, Dates, DataStructures, HTTP, JSON3
|
using UUIDs, Dates, DataStructures, HTTP, JSON3
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
@@ -301,10 +301,10 @@ function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothin
|
|||||||
for (i, event) in zip(ind, events)
|
for (i, event) in zip(ind, events)
|
||||||
# If no outcome exists, format without outcome
|
# If no outcome exists, format without outcome
|
||||||
if event[:outcome] === nothing
|
if event[:outcome] === nothing
|
||||||
timeline *= "Event_$i $(event[:subject])> $(event[:actioninput])\n"
|
timeline *= "Event_$i $(event[:subject])> action_name: $(event[:actionname]), action_input: $(event[:actioninput]), observation: Not done yet.\n"
|
||||||
# If outcome exists, include it in formatting
|
# If outcome exists, include it in formatting
|
||||||
else
|
else
|
||||||
timeline *= "Event_$i $(event[:subject])> $(event[:actioninput]) $(event[:outcome])\n"
|
timeline *= "Event_$i $(event[:subject])> action_name: $(event[:actionname]), action_input: $(event[:actioninput]), observation: $(event[:outcome])\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -313,15 +313,15 @@ function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothin
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function createEventsLog(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
|
function createEventsLog(events::T1; index::Union{UnitRange, Nothing}=nothing
|
||||||
) where {T1<:AbstractVector}
|
) where {T1<:AbstractVector}
|
||||||
# Initialize empty log array
|
# Initialize empty log array
|
||||||
log = Dict{Symbol, String}[]
|
log = Dict{Symbol, String}[]
|
||||||
|
|
||||||
# Determine which indices to use - either provided range or full length
|
# Determine which indices to use - either provided range or full length
|
||||||
ind =
|
ind =
|
||||||
if eventindex !== nothing
|
if index !== nothing
|
||||||
[eventindex...]
|
[index...]
|
||||||
else
|
else
|
||||||
1:length(events)
|
1:length(events)
|
||||||
end
|
end
|
||||||
@@ -338,7 +338,7 @@ function createEventsLog(events::T1; eventindex::Union{UnitRange, Nothing}=nothi
|
|||||||
subject = event[:subject]
|
subject = event[:subject]
|
||||||
actioninput = event[:actioninput]
|
actioninput = event[:actioninput]
|
||||||
outcome = event[:outcome]
|
outcome = event[:outcome]
|
||||||
str = "$subject: $actioninput $outcome"
|
str = "Action: $actioninput Outcome: $outcome"
|
||||||
d = Dict{Symbol, String}(:name=>subject, :text=>str)
|
d = Dict{Symbol, String}(:name=>subject, :text=>str)
|
||||||
push!(log, d)
|
push!(log, d)
|
||||||
end
|
end
|
||||||
@@ -348,6 +348,31 @@ function createEventsLog(events::T1; eventindex::Union{UnitRange, Nothing}=nothi
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function createChatLog(chatdict::T1; index::Union{UnitRange, Nothing}=nothing
|
||||||
|
) where {T1<:AbstractVector}
|
||||||
|
# Initialize empty log array
|
||||||
|
log = Dict{Symbol, String}[]
|
||||||
|
|
||||||
|
# Determine which indices to use - either provided range or full length
|
||||||
|
ind =
|
||||||
|
if index !== nothing
|
||||||
|
[index...]
|
||||||
|
else
|
||||||
|
1:length(chatdict)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Iterate through events and format each one
|
||||||
|
for (i, event) in zip(ind, chatdict)
|
||||||
|
subject = event[:name]
|
||||||
|
text = event[:text]
|
||||||
|
d = Dict{Symbol, String}(:name=>subject, :text=>text)
|
||||||
|
push!(log, d)
|
||||||
|
end
|
||||||
|
|
||||||
|
return log
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -382,9 +407,6 @@ end
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user