diff --git a/src/interface.jl b/src/interface.jl
index 429d4f8..5c51ecb 100644
--- a/src/interface.jl
+++ b/src/interface.jl
@@ -97,7 +97,7 @@ julia> output_thoughtDict = Dict(
# Signature
"""
-function decisionMaker(a::T; recentevents::Integer=10, maxattempt=10
+function decisionMaker(a::T; recentevents::Integer=20, maxattempt=10
) where {T<:agent}
# lessonDict = copy(JSON3.read("lesson.json"))
@@ -133,13 +133,8 @@ function decisionMaker(a::T; recentevents::Integer=10, maxattempt=10
includelatest=true)
recentchat = createChatLog(a.chathistory; index=recentchat_ind)
# recentEventsDict = createEventsLog(recentevents; index=recent_ind)
-
- #BUG timeline only cover event 1-9 out of 10 events while recentchat cover 1-9 because
- # recent_ind is based on chathistory. i should ind based on events. The reason is events always
- # have more than chathistory due to CHECKINVENTORY() function which store in events BUT NOT in
- # chathistory
-
+
# # recap as caching
# # query similar result from vectorDB
# recapkeys = keys(a.memory[:recap])
@@ -215,7 +210,7 @@ function decisionMaker(a::T; recentevents::Integer=10, maxattempt=10
- Vintage 0 means non-vintage.
At each round of conversation, you will be given the following information:
- Database search result: the result of a database search using SQL commands you have found so far
+ context: additional information about the current situation
You should then respond to the user with interleaving Plan, Action_name, Action_input:
1) plan: Based on the current situation, state a complete action plan to complete the task. Be specific.
@@ -253,14 +248,14 @@ function decisionMaker(a::T; recentevents::Integer=10, maxattempt=10
"""
- - CHATBOX which you can use to talk with the user. The input is your intentions for the dialogue. Be specific.
+ - CHATBOX which you can use to talk with the user. Be specific.
- CHECKINVENTORY allows you to check information about wines you want in your inventory's database. The input must be supported search criteria includeing: wine price, winery, name, vintage, region, country, type, grape varietal, tasting notes, occasion, food pairing, intensity, tannin, sweetness, and acidity.
Example query: "Dry, full-bodied red wine from Burgundy, France or Tuscany, Italy. Merlot varietal. price 100 to 1000 USD."
- PRESENTBOX which you can use to present wines you have found in your inventory to the user. The input are wine names that you want to present.
- - ENDCONVERSATION which you can use to properly end the conversation with the user. Input is "NA".
+ - ENDCONVERSATION which you can use to properly end the conversation with the user. Input is a dialogue where you wrap up the conversation, thank the user, and invite them to return next time.
+ $(a.memory[:shortmem][:scratchpad])
Remark: $errornote
- Database search result: $database_search_result
"""
@@ -1123,22 +1118,22 @@ julia>
"""
function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} where {T<:agent}
# a.memory[:recap] = generateSituationReport(a, a.func[:text2textInstructLLM]; skiprecent=0)
- thoughtDict = decisionMaker(a; recentevents=5)
+ thoughtDict = decisionMaker(a)
actionname = thoughtDict[:action_name]
actioninput = thoughtDict[:action_input]
# map action and input() to llm function
response =
- if actionname == "CHATBOX"
+ if actionname == "CHATBOX" || actionname == "ENDCONVERSATION"
(result=thoughtDict[:plan], errormsg=nothing, success=true)
elseif actionname == "CHECKINVENTORY"
checkinventory(a, actioninput)
elseif actionname == "PRESENTBOX"
(result=actioninput, errormsg=nothing, success=true)
- elseif actionname == "ENDCONVERSATION"
- x = "Conclude the conversation, thanks the user then goodbye and inviting them to return next time."
- (result=actioninput, errormsg=nothing, success=true)
+ # elseif actionname == "ENDCONVERSATION"
+ # x = "Conclude the conversation, thanks the user then goodbye and inviting them to return next time."
+ # (result=actioninput, errormsg=nothing, success=true)
else
error("undefined LLM function. Requesting $actionname")
end
@@ -1166,7 +1161,7 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
# )
# )
# result = chatresponse
- if actionname ∈ ["CHATBOX"]
+ if actionname ∈ ["CHATBOX", "ENDCONVERSATION"]
push!(a.memory[:events],
eventdict(;
event_description="the assistant talks to the user.",
@@ -1178,19 +1173,19 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
)
)
result = actioninput
- elseif actionname ∈ ["ENDCONVERSATION"]
- chatresponse = generatechat(a, thoughtDict)
- push!(a.memory[:events],
- eventdict(;
- event_description="the assistant talks to the user.",
- timestamp=Dates.now(),
- subject="assistant",
- thought=thoughtDict,
- actionname=actionname,
- actioninput=chatresponse,
- )
- )
- result = chatresponse
+ # elseif actionname ∈ ["ENDCONVERSATION"]
+ # chatresponse = generatechat(a, thoughtDict)
+ # push!(a.memory[:events],
+ # eventdict(;
+ # event_description="the assistant talks to the user.",
+ # timestamp=Dates.now(),
+ # subject="assistant",
+ # thought=thoughtDict,
+ # actionname=actionname,
+ # actioninput=chatresponse,
+ # )
+ # )
+ # result = chatresponse
elseif actionname ∈ ["PRESENTBOX"]
chatresponse = presentbox(a, thoughtDict)
push!(a.memory[:events],
@@ -1215,6 +1210,21 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
else
a.memory[:shortmem][:db_search_result] = vd
end
+
+ # add to scratchpad
+ a.memory[:shortmem][:scratchpad] *=
+ """
+
+ I searched the database with this search term: $actioninput This is what I found: $result
+
+ """
+ else
+ a.memory[:shortmem][:scratchpad] *=
+ """
+
+ I searched the database with this search term: $actioninput This is what I found: $result
+
+ """
end
push!(a.memory[:events],
@@ -1224,7 +1234,7 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
subject= "assistant",
thought=thoughtDict,
actionname=actionname,
- actioninput= "I found something in the database using this SQL: $actioninput",
+ actioninput= "I search the database with this search term: $actioninput",
outcome= "This is what I found:, $result"
)
)
@@ -1296,7 +1306,7 @@ function presentbox(a::sommelier, thoughtDict; maxtattempt::Integer=10, recentev
"""
Name of the wines that needs to be introduced: $(thoughtDict[:action_input])
- Database search result: $database_search_result
+ $(a.memory[:shortmem][:scratchpad])
P.S. $errornote
"""
diff --git a/src/llmfunction.jl b/src/llmfunction.jl
index 0b10ad9..c9f2552 100644
--- a/src/llmfunction.jl
+++ b/src/llmfunction.jl
@@ -500,16 +500,24 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
# 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, :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 ∉ [: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
if j == :wine_price
if responsedict[:wine_price] != "N/A"
@@ -592,7 +600,7 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
conversiontable =
"""
-
+
Intensity level:
1 to 2: May correspond to "light-bodied" or a similar description.
2 to 3: May correspond to "med light bodied", "medium light" or a similar description.
@@ -617,16 +625,16 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
3 to 4: May correspond to "medium acidity" or a similar description.
4 to 5: May correspond to "semi high acidity" or a similar description.
4 to 5: May correspond to "high acidity" or a similar description.
-
+
"""
systemmsg =
"""
As an helpful sommelier, your task is to fill out the user's preference form based on the corresponding words from the user's query.
- At each round of conversation, the user will give you the current situation:
- Conversion Table: ...
- 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
@@ -637,86 +645,109 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
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:
- Sweetness_keyword: The exact keywords in the user's query describing the sweetness level of the wine.
- Sweetness: ( S ), where ( S ) represents integers indicating the range of sweetness levels. Example: 1-2
- Acidity_keyword: The exact keywords in the user's query describing the acidity level of the wine.
- Acidity: ( A ), where ( A ) represents integers indicating the range of acidity level. Example: 3-5
- Tannin_keyword: The exact keywords in the user's query describing the tannin level of the wine.
- Tannin: ( T ), where ( T ) represents integers indicating the range of tannin level. Example: 1-3
- Intensity_keyword: The exact keywords in the user's query describing the intensity level of the wine.
- Intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
- You should only respond in format as described below:
- Sweetness_keyword: ...
- Sweetness: ...
- Acidity_keyword: ...
- Acidity: ...
- Tannin_keyword: ...
- Tannin: ...
- Intensity_keyword: ...
- Intensity: ...
+ sweetness_keyword: The exact keywords in the user's query describing the sweetness level of the wine.
+ sweetness: ( S ), where ( S ) represents integers indicating the range of sweetness levels. Example: 1-2
+ acidity_keyword: The exact keywords in the user's query describing the acidity level of the wine.
+ acidity: ( A ), where ( A ) represents integers indicating the range of acidity level. Example: 3-5
+ tannin_keyword: The exact keywords in the user's query describing the tannin level of the wine.
+ tannin: ( T ), where ( T ) represents integers indicating the range of tannin level. Example: 1-3
+ intensity_keyword: The exact keywords in the user's query describing the intensity level of the wine.
+ intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
+ You should only respond in JSON format as described below:
+ {
+ "sweetness_keyword": "...",
+ "sweetness": "...",
+ "acidity_keyword": "...",
+ "acidity": "...",
+ "tannin_keyword": "...",
+ "tannin": "...",
+ "intensity_keyword": "...",
+ "intensity": "..."
+ }
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": "N/A",
+ "acidity_keyword": "low acidity",
+ "acidity": 1-2,
+ "tannin_keyword": "medium tannin",
+ "tannin": 3-4,
+ "intensity_keyword": "medium-bodied",
+ "intensity": 3-4
+ }
User's query: German red wine, under 100, pairs with spicy food
- Sweetness_keyword: 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
+ {
+ "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!
"""
- 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"]
+ 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"
for attempt in 1:10
- usermsg =
+ context =
"""
$conversiontable
- User's query: $input
+
+ $input
+
P.S. $errornote
+ /no_think
"""
- _prompt =
+ unformatPrompt =
[
Dict(:name=> "system", :text=> systemmsg),
- Dict(:name=> "user", :text=> usermsg)
]
# put in model format
- prompt = GeneralUtils.formatLLMtext(_prompt, a.llmFormatName)
+ 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, a.llmFormatName)
+ response = GeneralUtils.remove_french_accents(response)
think, response = GeneralUtils.extractthink(response)
-
- # 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 attempt does not have all answer's key points"
- println("\nERROR YiemAgent extractWineAttributes_2() Attempt $attempt $errornote ", @__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())")
+
+ responsedict = nothing
+ try
+ responsedict = copy(JSON3.read(response))
+ catch
+ println("\nERROR YiemAgent extractWineAttributes_2() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue
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_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
for i in ["sweetness", "acidity", "tannin", "intensity"]
diff --git a/src/type.jl b/src/type.jl
index 530238d..b21d69c 100644
--- a/src/type.jl
+++ b/src/type.jl
@@ -194,6 +194,7 @@ function sommelier(
memory = Dict{Symbol, Any}(
:shortmem=> OrderedDict{Symbol, Any}(
:db_search_result=> Any[],
+ :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}(