@@ -1,7 +1,7 @@
|
|||||||
name = "YiemAgent"
|
name = "YiemAgent"
|
||||||
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
||||||
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
||||||
|
|||||||
1486
src/interface.jl
1486
src/interface.jl
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
|||||||
module llmfunction
|
module llmfunction
|
||||||
|
|
||||||
export virtualWineUserChatbox, jsoncorrection, checkinventory, # recommendbox,
|
export virtualWineUserChatbox, jsoncorrection, checkwine, # recommendbox,
|
||||||
virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes_1,
|
virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes_1,
|
||||||
extractWineAttributes_2, paraphrase
|
extractWineAttributes_2, paraphrase
|
||||||
|
|
||||||
using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs, Dates
|
using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs, Dates, DataFrames
|
||||||
using GeneralUtils, SQLLLM
|
using GeneralUtils, SQLLLM
|
||||||
using ..type, ..util
|
using ..type, ..util
|
||||||
|
|
||||||
@@ -288,15 +288,25 @@ julia> result = checkinventory(agent, input)
|
|||||||
|
|
||||||
# Signature
|
# Signature
|
||||||
"""
|
"""
|
||||||
function checkinventory(a::T1, input::T2
|
function checkwine(a::T1, input::T2; maxattempt::Int=3
|
||||||
) where {T1<:agent, T2<:AbstractString}
|
) where {T1<:agent, T2<:AbstractString}
|
||||||
|
|
||||||
println("\ncheckinventory order: $input ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
println("\ncheckinventory order: $input ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
wineattributes_1 = extractWineAttributes_1(a, input)
|
wineattributes_1 = extractWineAttributes_1(a, input)
|
||||||
wineattributes_2 = extractWineAttributes_2(a, input)
|
wineattributes_2 = extractWineAttributes_2(a, input)
|
||||||
|
|
||||||
_inventoryquery = "retailer name: $(a.retailername), $wineattributes_1, $wineattributes_2"
|
# placeholder
|
||||||
inventoryquery = "Retrieves winery, wine_name, wine_id, 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}"
|
textresult = nothing
|
||||||
|
rawresponse = nothing
|
||||||
|
|
||||||
|
for i in 1:maxattempt
|
||||||
|
|
||||||
|
#CHANGE if you want to add retailer name
|
||||||
|
# _inventoryquery = "retailer name: $(a.retailername), $wineattributes_1, $wineattributes_2"
|
||||||
|
_inventoryquery = "$wineattributes_1, $wineattributes_2"
|
||||||
|
|
||||||
|
retrieve_attributes = ["winery", "wine_name", "wine_id", "vintage", "region", "country", "wine_type", "grape", "serving_temperature", "sweetness", "intensity", "tannin", "acidity", "tasting_notes", "price", "currency"]
|
||||||
|
inventoryquery = "Retrieves $retrieve_attributes of wines that match the following criteria - {$_inventoryquery}"
|
||||||
println("\ncheckinventory input: $inventoryquery ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
println("\ncheckinventory input: $inventoryquery ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
# add suppport for similarSQLVectorDB
|
# add suppport for similarSQLVectorDB
|
||||||
textresult, rawresponse = SQLLLM.query(inventoryquery, a.func[:executeSQL],
|
textresult, rawresponse = SQLLLM.query(inventoryquery, a.func[:executeSQL],
|
||||||
@@ -304,7 +314,20 @@ function checkinventory(a::T1, input::T2
|
|||||||
insertSQLVectorDB=a.func[:insertSQLVectorDB],
|
insertSQLVectorDB=a.func[:insertSQLVectorDB],
|
||||||
similarSQLVectorDB=a.func[:similarSQLVectorDB],
|
similarSQLVectorDB=a.func[:similarSQLVectorDB],
|
||||||
llmFormatName="qwen3")
|
llmFormatName="qwen3")
|
||||||
|
# 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
|
||||||
|
if typeof(rawresponse) == DataFrame &&
|
||||||
|
!occursin("The resulting table has 0 row", textresult) &&
|
||||||
|
!all(isin)
|
||||||
|
|
||||||
|
errornote = "Not all of $retrieve_attributes appear in search result"
|
||||||
|
println("\nERROR YiemAgent checkwine() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
println("\ncheckinventory result ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
println("\ncheckinventory result ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
println(textresult)
|
println(textresult)
|
||||||
|
|
||||||
@@ -339,150 +362,210 @@ 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 "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 "N/A" 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.
|
||||||
- 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:
|
||||||
Thought: state your understanding of the current situation
|
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, such as Burgundy, Bordeaux, Champagne, Napa Valley, Tuscany, California, Oregon, 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 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"
|
||||||
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 brief description of the wine's taste, such as "butter", "oak", "fruity", etc
|
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.
|
||||||
Wine_price: price range of wine.
|
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
|
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
|
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:
|
||||||
Thought: ...
|
{
|
||||||
Wine_name: ...
|
"wine_name": "...",
|
||||||
Winery: ...
|
"winery": "...",
|
||||||
Vintage: ...
|
"vintage": "...",
|
||||||
Region: ...
|
"region": "...",
|
||||||
Country: ...
|
"country": "...",
|
||||||
Wine_type:
|
"wine_type": "...",
|
||||||
Grape_varietal: ...
|
"grape_varietal": "...",
|
||||||
Tasting_notes: ...
|
"tasting_notes": "...",
|
||||||
Wine_price: ...
|
"wine_price_min": "...",
|
||||||
Occasion: ...
|
"wine_price_max": "...",
|
||||||
Food_to_be_paired_with_wine: ...
|
"occasion": "...",
|
||||||
|
"food_to_be_paired_with_wine": "..."
|
||||||
|
}
|
||||||
|
|
||||||
Here are some example:
|
Here are some example:
|
||||||
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: 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 or Napa Valley",
|
||||||
|
"country": "Italy or United States",
|
||||||
|
"wine_type": "red or white",
|
||||||
|
"grape_varietal": "Chenin Blanc or 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
|
||||||
{"reasoning": ..., "winery": "Domaine du Collier", "wine_name": "Saumur Blanc", "vintage": "2019", "region": "Saumur", "country": "France", "wine_type": "white", "grape_varietal": "Merlot", "tasting_notes": "NA", "wine_price": "NA", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
{
|
||||||
|
"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 = ["Thought:", "Wine_name:", "Winery:", "Vintage:", "Region:", "Country:", "Wine_type:", "Grape_varietal:", "Tasting_notes:", "Wine_price:", "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 = ["thought", "wine_name", "winery", "vintage", "region", "country", "wine_type", "grape_varietal", "tasting_notes", "wine_price", "occasion", "food_to_be_paired_with_wine"]
|
|
||||||
errornote = "N/A"
|
errornote = "N/A"
|
||||||
|
|
||||||
llmkwargs=Dict(
|
|
||||||
:num_ctx => 32768,
|
|
||||||
:temperature => 0.5,
|
|
||||||
)
|
|
||||||
|
|
||||||
for attempt in 1:maxattempt
|
for attempt in 1:maxattempt
|
||||||
#[PENDING] I should add generatequestion()
|
|
||||||
|
|
||||||
if attempt > 1
|
|
||||||
println("\nYiemAgent extractWineAttributes_1() attempt $attempt/$maxattempt ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
end
|
|
||||||
|
|
||||||
usermsg =
|
usermsg =
|
||||||
"""
|
"""
|
||||||
User's query: $input
|
$input
|
||||||
|
"""
|
||||||
|
context =
|
||||||
|
"""
|
||||||
|
<context>
|
||||||
P.S. $errornote
|
P.S. $errornote
|
||||||
|
</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, "granite3")
|
prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
|
||||||
response = a.func[:text2textInstructLLM](prompt;
|
# add info
|
||||||
modelsize="medium", llmkwargs=llmkwargs, senderId=a.id)
|
prompt = prompt * context
|
||||||
|
|
||||||
|
response = a.func[:text2textInstructLLM](prompt; modelsize="medium", senderId=a.id)
|
||||||
|
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
response = GeneralUtils.deFormatLLMtext(response, "granite3")
|
think, response = GeneralUtils.extractthink(response)
|
||||||
|
|
||||||
# check wheter all attributes are in the response
|
responsedict = nothing
|
||||||
checkFlag = false
|
try
|
||||||
for word in header
|
responsedict = copy(JSON3.read(response))
|
||||||
if !occursin(word, response)
|
catch
|
||||||
errornote = "In your previous attempts, the $word attribute is missing. Please try again."
|
println("\nERROR YiemAgent extractWineAttributes_1() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
println("\nYiemAgent extractWineAttributes_1() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
checkFlag = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
checkFlag == true ? continue : nothing
|
|
||||||
|
|
||||||
# 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())")
|
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
responsedict = GeneralUtils.textToDict(response, header;
|
|
||||||
dictKey=dictkey, symbolkey=true)
|
# 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())")
|
||||||
|
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]
|
||||||
|
for i in removekeys
|
||||||
|
delete!(responsedict, i)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
delete!(responsedict, :vintage)
|
||||||
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 ∉ removekeys
|
||||||
# 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
|
||||||
if j == :wine_price
|
if j == :wine_price
|
||||||
if responsedict[:wine_price] != "NA"
|
if responsedict[:wine_price] != "N/A"
|
||||||
# check whether wine_price is in ranged number
|
# check whether wine_price is in ranged number
|
||||||
if !occursin('-', responsedict[:wine_price])
|
if !occursin("to", responsedict[:wine_price])
|
||||||
errornote = "In your previous attempt, the 'wine_price' was not set to a ranged number. Please adjust it accordingly."
|
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())")
|
println("\nERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
checkFlag = true
|
checkFlag = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
# check whether max wine_price is in the input
|
# # check whether max wine_price is in the input
|
||||||
pricerange = split(responsedict[:wine_price], '-')
|
# pricerange = split(responsedict[:wine_price], '-')
|
||||||
minprice = pricerange[1]
|
# minprice = pricerange[1]
|
||||||
maxprice = pricerange[end]
|
# maxprice = pricerange[end]
|
||||||
if !occursin(maxprice, input)
|
# if !occursin(maxprice, input)
|
||||||
responsedict[:wine_price] = "NA"
|
# responsedict[:wine_price] = "N/A"
|
||||||
end
|
# end
|
||||||
# price range like 100-100 is not good
|
# # price range like 100-100 is not good
|
||||||
if minprice == maxprice
|
# if minprice == maxprice
|
||||||
errornote = "In your previous attempt, you inputted 'wine_price' with a 'minimum' value equaling the 'maximum', which is not valid."
|
# 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())")
|
# println("\nERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
checkFlag = true
|
# checkFlag = true
|
||||||
break
|
# break
|
||||||
end
|
# end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
content = responsedict[j]
|
content = responsedict[j]
|
||||||
@@ -517,7 +600,7 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
|
|||||||
result = ""
|
result = ""
|
||||||
for (k, v) in responsedict
|
for (k, v) in responsedict
|
||||||
# some time LLM generate text with "(some comment)". this line removes it
|
# some time LLM generate text with "(some comment)". this line removes it
|
||||||
if !occursin("NA", v) && v != "" && !occursin("none", v) && !occursin("None", v)
|
if !occursin("N/A", v) && v != "" && !occursin("none", v) && !occursin("None", v)
|
||||||
result *= "$k: $v, "
|
result *= "$k: $v, "
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -540,7 +623,7 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
|
|
||||||
conversiontable =
|
conversiontable =
|
||||||
"""
|
"""
|
||||||
<Conversion Table>
|
<conversion_table>
|
||||||
Intensity level:
|
Intensity level:
|
||||||
1 to 2: May correspond to "light-bodied" or a similar description.
|
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.
|
2 to 3: May correspond to "med light bodied", "medium light" or a similar description.
|
||||||
@@ -565,127 +648,151 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
3 to 4: May correspond to "medium acidity" or a similar description.
|
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 "semi high acidity" or a similar description.
|
||||||
4 to 5: May correspond to "high acidity" or a similar description.
|
4 to 5: May correspond to "high acidity" or a similar description.
|
||||||
</Conversion Table>
|
</conversion_table>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
systemmsg =
|
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.
|
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:
|
At each round of conversation, you will be given the following information:
|
||||||
Conversion Table: ...
|
conversion_table: a conversion table that maps descriptive words to their corresponding integer levels
|
||||||
User's query: ...
|
query: the words from the user's query that describe their preferences
|
||||||
|
|
||||||
The preference form requires the following information:
|
The preference form requires the following information:
|
||||||
sweetness, acidity, tannin, intensity
|
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.
|
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.
|
||||||
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.
|
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:
|
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_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
|
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_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
|
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_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
|
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_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
|
intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
|
||||||
You should only respond in format as described below:
|
You should only respond in JSON format as described below:
|
||||||
Sweetness_keyword: ...
|
{
|
||||||
Sweetness: ...
|
"sweetness_keyword": "...",
|
||||||
Acidity_keyword: ...
|
"sweetness": "...",
|
||||||
Acidity: ...
|
"acidity_keyword": "...",
|
||||||
Tannin_keyword: ...
|
"acidity": "...",
|
||||||
Tannin: ...
|
"tannin_keyword": "...",
|
||||||
Intensity_keyword: ...
|
"tannin": "...",
|
||||||
Intensity: ...
|
"intensity_keyword": "...",
|
||||||
|
"intensity": "..."
|
||||||
|
}
|
||||||
|
|
||||||
Here are some examples:
|
Here are some examples:
|
||||||
User's query: I want a wine with a medium-bodied, low acidity, medium tannin.
|
User's query: I want a wine with a medium-bodied, low acidity, medium tannin.
|
||||||
Sweetness_keyword: NA
|
{
|
||||||
Sweetness: NA
|
"sweetness_keyword": "N/A",
|
||||||
Acidity_keyword: low acidity
|
"sweetness": "N/A",
|
||||||
Acidity: 1-2
|
"acidity_keyword": "low acidity",
|
||||||
Tannin_keyword: medium tannin
|
"acidity": "1-2",
|
||||||
Tannin: 3-4
|
"tannin_keyword": "medium tannin",
|
||||||
Intensity_keyword: medium-bodied
|
"tannin": "3-4",
|
||||||
Intensity: 3-4
|
"intensity_keyword": "medium-bodied",
|
||||||
|
"intensity": "3-4"
|
||||||
|
}
|
||||||
|
|
||||||
User's query: German red wine, under 100, pairs with spicy food
|
User's query: German red wine, under 100, pairs with spicy food
|
||||||
Sweetness_keyword: NA
|
{
|
||||||
Sweetness: NA
|
"sweetness_keyword": "N/A",
|
||||||
Acidity_keyword: NA
|
"sweetness": "N/A",
|
||||||
Acidity: NA
|
"acidity_keyword": "N/A",
|
||||||
Tannin_keyword: NA
|
"acidity": "N/A",
|
||||||
Tannin: NA
|
"tannin_keyword": "N/A",
|
||||||
Intensity_keyword: NA
|
"tannin": "N/A",
|
||||||
Intensity: NA
|
"intensity_keyword": "N/A",
|
||||||
|
"intensity": "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
header = ["Sweetness_keyword:", "Sweetness:", "Acidity_keyword:", "Acidity:", "Tannin_keyword:", "Tannin:", "Intensity_keyword:", "Intensity:"]
|
requiredKeys = [: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 = ""
|
# 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"
|
||||||
|
|
||||||
for attempt in 1:10
|
for attempt in 1:10
|
||||||
usermsg =
|
context =
|
||||||
"""
|
"""
|
||||||
$conversiontable
|
$conversiontable
|
||||||
User's query: $input
|
<query>
|
||||||
|
$input
|
||||||
|
</query>
|
||||||
P.S. $errornote
|
P.S. $errornote
|
||||||
|
/no_think
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_prompt =
|
unformatPrompt =
|
||||||
[
|
[
|
||||||
Dict(:name=> "system", :text=> systemmsg),
|
Dict(:name=> "system", :text=> systemmsg),
|
||||||
Dict(:name=> "user", :text=> usermsg)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt, "granite3")
|
prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
|
||||||
|
# add info
|
||||||
|
prompt = prompt * context
|
||||||
|
|
||||||
response = a.func[:text2textInstructLLM](prompt)
|
response = a.func[:text2textInstructLLM](prompt; modelsize="medium", senderId=a.id)
|
||||||
response = GeneralUtils.deFormatLLMtext(response, "granite3")
|
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
||||||
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
|
think, response = GeneralUtils.extractthink(response)
|
||||||
|
|
||||||
# check whether response has all answer's key points
|
responsedict = nothing
|
||||||
detected_kw = GeneralUtils.detect_keyword(header, response)
|
try
|
||||||
if 0 ∈ values(detected_kw)
|
responsedict = copy(JSON3.read(response))
|
||||||
errornote = "In your previous attempt does not have all answer's key points"
|
catch
|
||||||
println("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
println("\nERROR YiemAgent extractWineAttributes_2() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
continue
|
|
||||||
elseif sum(values(detected_kw)) > length(header)
|
|
||||||
errornote = "In your previous attempt has duplicated answer's key points"
|
|
||||||
println("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
responsedict = GeneralUtils.textToDict(response, header;
|
# check whether all answer's key points are in responsedict
|
||||||
dictKey=dictkey, symbolkey=true)
|
_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())")
|
||||||
|
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
|
# check whether each describing keyword is in the input to prevent halucination
|
||||||
for i in ["sweetness", "acidity", "tannin", "intensity"]
|
for i in ["sweetness", "acidity", "tannin", "intensity"]
|
||||||
keyword = Symbol(i * "_keyword") # e.g. sweetness_keyword
|
keyword = Symbol(i * "_keyword") # e.g. sweetness_keyword
|
||||||
value = responsedict[keyword]
|
value = responsedict[keyword]
|
||||||
if value != "NA" && !occursin(value, input)
|
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"
|
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())")
|
println("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
# if value == "NA" then responsedict[i] = "NA"
|
# if value == "N/A" then responsedict[i] = "N/A"
|
||||||
# e.g. if sweetness_keyword == "NA" then sweetness = "NA"
|
# e.g. if sweetness_keyword == "N/A" then sweetness = "N/A"
|
||||||
if value == "NA"
|
if value == "N/A"
|
||||||
responsedict[Symbol(i)] = "NA"
|
responsedict[Symbol(i)] = "N/A"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# some time LLM not put integer range
|
# some time LLM not put integer range
|
||||||
for (k, v) in responsedict
|
for (k, v) in responsedict
|
||||||
if !occursin("keyword", string(k))
|
if !occursin("keyword", string(k))
|
||||||
if v !== "NA" && (!occursin('-', v) || length(v) > 5)
|
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."
|
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())")
|
println("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
continue
|
continue
|
||||||
@@ -693,18 +800,25 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# some time LLM says NA-2. Need to convert NA to 1
|
# some time LLM says N/A-2. Need to convert N/A to 1
|
||||||
for (k, v) in responsedict
|
for (k, v) in responsedict
|
||||||
if occursin("NA", v) && occursin("-", v)
|
if occursin("N/A", v) && occursin("-", v)
|
||||||
new_v = replace(v, "NA"=>"1")
|
new_v = replace(v, "N/A"=>"1")
|
||||||
responsedict[k] = new_v
|
responsedict[k] = new_v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# delete some key words from responsedict
|
||||||
|
for (k, v) in responsedict
|
||||||
|
if k ∈ [:sweetness_keyword, :acidity_keyword, :tannin_keyword, :intensity_keyword]
|
||||||
|
delete!(responsedict, k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
result = ""
|
result = ""
|
||||||
for (k, v) in responsedict
|
for (k, v) in responsedict
|
||||||
# some time LLM generate text with "(some comment)". this line removes it
|
# some time LLM generate text with "(some comment)". this line removes it
|
||||||
if !occursin("NA", v)
|
if !occursin("N/A", v)
|
||||||
result *= "$k: $v, "
|
result *= "$k: $v, "
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -752,11 +866,12 @@ 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"]
|
||||||
|
|
||||||
errornote = ""
|
errornote = "N/A"
|
||||||
response = nothing # placeholder for show when error msg show up
|
response = nothing # placeholder for show when error msg show up
|
||||||
|
|
||||||
|
|
||||||
@@ -773,11 +888,12 @@ function paraphrase(text2textInstructLLM::Function, text::String)
|
|||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt, "granite3")
|
prompt = GeneralUtils.formatLLMtext(_prompt, a.llmFormatName)
|
||||||
|
|
||||||
try
|
try
|
||||||
response = text2textInstructLLM(prompt)
|
response = text2textInstructLLM(prompt)
|
||||||
response = GeneralUtils.deFormatLLMtext(response, "granite3")
|
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
||||||
|
think, response = GeneralUtils.extractthink(response)
|
||||||
# sometime the model response like this "here's how I would respond: ..."
|
# sometime the model response like this "here's how I would respond: ..."
|
||||||
if occursin("respond:", response)
|
if occursin("respond:", response)
|
||||||
errornote = "You don't need to intro your response"
|
errornote = "You don't need to intro your response"
|
||||||
|
|||||||
97
src/type.jl
97
src/type.jl
@@ -1,6 +1,6 @@
|
|||||||
module type
|
module type
|
||||||
|
|
||||||
export agent, sommelier, companion
|
export agent, sommelier, companion, virtualcustomer
|
||||||
|
|
||||||
using Dates, UUIDs, DataStructures, JSON3
|
using Dates, UUIDs, DataStructures, JSON3
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
@@ -24,16 +24,12 @@ end
|
|||||||
function companion(
|
function companion(
|
||||||
func::NamedTuple # NamedTuple of functions
|
func::NamedTuple # NamedTuple of functions
|
||||||
;
|
;
|
||||||
systemmsg::Union{String, Nothing}= nothing,
|
|
||||||
name::String= "Assistant",
|
name::String= "Assistant",
|
||||||
id::String= GeneralUtils.uuid4snakecase(),
|
id::String= GeneralUtils.uuid4snakecase(),
|
||||||
maxHistoryMsg::Integer= 20,
|
maxHistoryMsg::Integer= 20,
|
||||||
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
|
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
|
||||||
llmFormatName::String= "granite3"
|
llmFormatName::String= "granite3",
|
||||||
)
|
systemmsg::String=
|
||||||
|
|
||||||
if systemmsg === nothing
|
|
||||||
systemmsg =
|
|
||||||
"""
|
"""
|
||||||
Your name: $name
|
Your name: $name
|
||||||
Your sex: Female
|
Your sex: Female
|
||||||
@@ -43,8 +39,8 @@ function companion(
|
|||||||
- Your like to be short and concise.
|
- Your like to be short and concise.
|
||||||
|
|
||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
""",
|
||||||
end
|
)
|
||||||
|
|
||||||
tools = Dict( # update input format
|
tools = Dict( # update input format
|
||||||
"CHATBOX"=> Dict(
|
"CHATBOX"=> Dict(
|
||||||
@@ -197,8 +193,83 @@ 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
|
:scratchpad=> "", #[PENDING] should be a dict e.g. Dict(:database_search_result=>Dict(:wines=> "", :search_query=> ""))
|
||||||
|
),
|
||||||
|
:events=> Vector{Dict{Symbol, Any}}(),
|
||||||
|
:state=> Dict{Symbol, Any}(
|
||||||
|
),
|
||||||
|
:recap=> OrderedDict{Symbol, Any}(),
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
newAgent = sommelier(
|
||||||
|
name,
|
||||||
|
id,
|
||||||
|
retailername,
|
||||||
|
tools,
|
||||||
|
maxHistoryMsg,
|
||||||
|
chathistory,
|
||||||
|
memory,
|
||||||
|
func,
|
||||||
|
llmFormatName
|
||||||
|
)
|
||||||
|
|
||||||
|
return newAgent
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
mutable struct virtualcustomer <: agent
|
||||||
|
name::String # agent name
|
||||||
|
id::String # agent id
|
||||||
|
systemmsg::String # system message
|
||||||
|
tools::Dict
|
||||||
|
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
|
||||||
|
chathistory::Vector{Dict{Symbol, Any}}
|
||||||
|
memory::Dict{Symbol, Any}
|
||||||
|
func # NamedTuple of functions
|
||||||
|
llmFormatName::String
|
||||||
|
end
|
||||||
|
|
||||||
|
function virtualcustomer(
|
||||||
|
func, # NamedTuple of functions
|
||||||
|
;
|
||||||
|
name::String= "Assistant",
|
||||||
|
id::String= string(uuid4()),
|
||||||
|
maxHistoryMsg::Integer= 20,
|
||||||
|
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
|
||||||
|
llmFormatName::String= "granite3",
|
||||||
|
systemmsg::String=
|
||||||
|
"""
|
||||||
|
Your name: $name
|
||||||
|
Your sex: Female
|
||||||
|
Your role: You are a helpful assistant.
|
||||||
|
You should follow the following guidelines:
|
||||||
|
- Focus on the latest conversation.
|
||||||
|
- Your like to be short and concise.
|
||||||
|
|
||||||
|
Let's begin!
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
tools = Dict( # update input format
|
||||||
|
"chatbox"=> Dict(
|
||||||
|
:description => "<askbox tool description>Useful for when you need to ask the user for more context. Do not ask the user their own question.</askbox tool description>",
|
||||||
|
:input => """<input>Input is a text in JSON format.</input><input example>{\"Q1\": \"How are you doing?\", \"Q2\": \"How may I help you?\"}</input example>""",
|
||||||
|
:output => "" ,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
""" Memory
|
||||||
|
Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
|
||||||
|
NO "system" message in chathistory because I want to add it at the inference time
|
||||||
|
chathistory= [
|
||||||
|
Dict(:name=>"user", :text=> "Wassup!", :timestamp=> Dates.now()),
|
||||||
|
Dict(:name=>"assistant", :text=> "Hi I'm your assistant.", :timestamp=> Dates.now()),
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
memory = Dict{Symbol, Any}(
|
||||||
|
:shortmem=> OrderedDict{Symbol, Any}(
|
||||||
),
|
),
|
||||||
:events=> Vector{Dict{Symbol, Any}}(),
|
:events=> Vector{Dict{Symbol, Any}}(),
|
||||||
:state=> Dict{Symbol, Any}(
|
:state=> Dict{Symbol, Any}(
|
||||||
@@ -206,10 +277,10 @@ function sommelier(
|
|||||||
:recap=> OrderedDict{Symbol, Any}(),
|
:recap=> OrderedDict{Symbol, Any}(),
|
||||||
)
|
)
|
||||||
|
|
||||||
newAgent = sommelier(
|
newAgent = virtualcustomer(
|
||||||
name,
|
name,
|
||||||
id,
|
id,
|
||||||
retailername,
|
systemmsg,
|
||||||
tools,
|
tools,
|
||||||
maxHistoryMsg,
|
maxHistoryMsg,
|
||||||
chathistory,
|
chathistory,
|
||||||
|
|||||||
95
src/util.jl
95
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
|
availableWineToText, createEventsLog, createChatLog
|
||||||
|
|
||||||
using UUIDs, Dates, DataStructures, HTTP, JSON3
|
using UUIDs, Dates, DataStructures, HTTP, JSON3
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
@@ -297,22 +297,25 @@ function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothin
|
|||||||
1:length(events)
|
1:length(events)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Iterate through events and format each one
|
#[WORKING] Iterate through events and format each one
|
||||||
for (i, event) in zip(ind, events)
|
for i in ind
|
||||||
|
event = events[i]
|
||||||
# If no outcome exists, format without outcome
|
# If no outcome exists, format without outcome
|
||||||
if event[:outcome] === nothing
|
# if event[:actionname] == "CHATBOX"
|
||||||
timeline *= "Event_$i $(event[:subject])> $(event[:actioninput])\n"
|
# timeline *= "Event_$i $(event[:subject])> action_name: $(event[:actionname]), action_input: $(event[:actioninput])\n"
|
||||||
|
# elseif event[:actionname] == "CHECKINVENTORY" && event[:outcome] === nothing
|
||||||
|
# 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
|
||||||
|
if event[:actionname] == "CHECKWINE"
|
||||||
|
timeline *= "Event_$i $(event[:subject])> action_name: $(event[:actionname]), action_input: $(event[:actioninput]), observation: $(event[:outcome])\n"
|
||||||
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])\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return formatted timeline string
|
# Return formatted timeline string
|
||||||
return timeline
|
return timeline
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
|
# function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
|
||||||
# ) where {T1<:AbstractVector}
|
# ) where {T1<:AbstractVector}
|
||||||
# # Initialize empty timeline string
|
# # Initialize empty timeline string
|
||||||
@@ -327,15 +330,14 @@ end
|
|||||||
# end
|
# end
|
||||||
|
|
||||||
# # Iterate through events and format each one
|
# # Iterate through events and format each one
|
||||||
# for (i, event) in zip(ind, events)
|
# for i in ind
|
||||||
|
# event = events[i]
|
||||||
# # If no outcome exists, format without outcome
|
# # If no outcome exists, format without outcome
|
||||||
# subject = titlecase(event[:subject])
|
|
||||||
# if event[:outcome] === nothing
|
# if event[:outcome] === nothing
|
||||||
|
# timeline *= "Event_$i $(event[:subject])> action_name: $(event[:actionname]), action_input: $(event[:actioninput]), observation: Not done yet.\n"
|
||||||
# timeline *= "Event_$i) Who: $subject Action_name: $(event[:actionname]) Action_input: $(event[:actioninput])\n"
|
|
||||||
# # If outcome exists, include it in formatting
|
# # If outcome exists, include it in formatting
|
||||||
# else
|
# else
|
||||||
# timeline *= "Event_$i) Who: $subject Action_name: $(event[:actionname]) Action_input: $(event[:actioninput]) Action output: $(event[:outcome])\n"
|
# timeline *= "Event_$i $(event[:subject])> action_name: $(event[:actionname]), action_input: $(event[:actioninput]), observation: $(event[:outcome])\n"
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
|
|
||||||
@@ -344,6 +346,73 @@ end
|
|||||||
# end
|
# end
|
||||||
|
|
||||||
|
|
||||||
|
function createEventsLog(events::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(events)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Iterate through events and format each one
|
||||||
|
for i in ind
|
||||||
|
event = events[i]
|
||||||
|
# If no outcome exists, format without outcome
|
||||||
|
if event[:outcome] === nothing
|
||||||
|
subject = event[:subject]
|
||||||
|
actioninput = event[:actioninput]
|
||||||
|
d = Dict{Symbol, String}(:name=>subject, :text=>actioninput)
|
||||||
|
push!(log, d)
|
||||||
|
else
|
||||||
|
subject = event[:subject]
|
||||||
|
actioninput = event[:actioninput]
|
||||||
|
outcome = event[:outcome]
|
||||||
|
str = "Action: $actioninput Outcome: $outcome"
|
||||||
|
d = Dict{Symbol, String}(:name=>subject, :text=>str)
|
||||||
|
push!(log, d)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return log
|
||||||
|
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 in ind
|
||||||
|
event = chatdict[i]
|
||||||
|
subject = event[:name]
|
||||||
|
text = event[:text]
|
||||||
|
d = Dict{Symbol, String}(:name=>subject, :text=>text)
|
||||||
|
push!(log, d)
|
||||||
|
end
|
||||||
|
|
||||||
|
return log
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user