diff --git a/src/interface.jl b/src/interface.jl
index b404e08..429d4f8 100644
--- a/src/interface.jl
+++ b/src/interface.jl
@@ -97,7 +97,7 @@ julia> output_thoughtDict = Dict(
# Signature
"""
-function decisionMaker(a::T; recent::Integer=10, maxattempt=10
+function decisionMaker(a::T; recentevents::Integer=10, maxattempt=10
) where {T<:agent}
# lessonDict = copy(JSON3.read("lesson.json"))
@@ -125,35 +125,42 @@ function decisionMaker(a::T; recent::Integer=10, maxattempt=10
# """
# end
- recent_ind = GeneralUtils.recentElementsIndex(length(a.chathistory), recent; includelatest=true)
- recentevents = a.memory[:events][recent_ind]
- recentchat = createChatLog(a.chathistory[recent_ind]; index=recent_ind)
+ recentevents_ind = GeneralUtils.recentElementsIndex(length(a.memory[:events]), recentevents;
+ includelatest=true)
+ timeline = createTimeline(a.memory[:events]; eventindex=recentevents_ind)
+
+ recentchat_ind = GeneralUtils.recentElementsIndex(length(a.chathistory), recentevents;
+ 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
+
- timeline = createTimeline(recentevents; eventindex=recent_ind)
+ # # recap as caching
+ # # query similar result from vectorDB
+ # recapkeys = keys(a.memory[:recap])
+ # _recapkeys_vec = [i for i in recapkeys]
- # recap as caching
- # query similar result from vectorDB
- recapkeys = keys(a.memory[:recap])
- _recapkeys_vec = [i for i in recapkeys]
+ # # select recent keys
+ # _recentRecapKeys =
+ # if length(a.memory[:recap]) <= 3 # 1st message is a user's hello msg
+ # _recapkeys_vec
+ # elseif length(a.memory[:recap]) > 3
+ # l = length(a.memory[:recap])
+ # _recapkeys_vec[l-2:l]
+ # end
- # select recent keys
- _recentRecapKeys =
- if length(a.memory[:recap]) <= 3 # 1st message is a user's hello msg
- _recapkeys_vec
- elseif length(a.memory[:recap]) > 3
- l = length(a.memory[:recap])
- _recapkeys_vec[l-2:l]
- end
-
- # get recent recap
- _recentrecap = OrderedDict()
- for (k, v) in a.memory[:recap]
- if k ∈ _recentRecapKeys
- _recentrecap[k] = v
- end
- end
+ # # get recent recap
+ # _recentrecap = OrderedDict()
+ # for (k, v) in a.memory[:recap]
+ # if k ∈ _recentRecapKeys
+ # _recentrecap[k] = v
+ # end
+ # end
# recentrecap = GeneralUtils.dictToString_noKey(_recentrecap)
# similarDecision = a.func[:similarSommelierDecision](recentrecap)
@@ -415,7 +422,7 @@ function decisionMaker(a::T; recent::Integer=10, maxattempt=10
#[WORKING]
evaluationdict = evaluator(a, timeline, responsedict, context)
- if evaluationdict[:good_decision] == "no"
+ if evaluationdict[:approved] == "no"
mentor_comment = evaluationdict[:suggestion]
errornote = "Your previous attempt was not good enough. Please try again. Here is the mentor's suggestion: $mentor_comment"
println("\nERROR YiemAgent decisionMaker() - $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
@@ -851,7 +858,7 @@ function evaluator(a::T1, timeline, decisiondict, evaluateecontext
- Do not generate additional thoughts or actions.
2) decision_evaluation:
- Examine how the trainee's decisions align with the store's policies and guidelines before proceeding.
- 3) good_decision: Decide whether the decision make sense under the circumstances. Can be "yes" or "no"
+ 3) approved: Decide whether to let the trainee proceed or not. Can be "yes" or "no"
4) suggestion: Based store policy and guidelines, provide a suggestion for the immediate decision step only.
@@ -859,14 +866,14 @@ function evaluator(a::T1, timeline, decisiondict, evaluateecontext
{
"trajectory_evaluation": "..."
"decision_evaluation": "..."
- "good_decision": "..."
+ "approved": "..."
"suggestion": "..."
}
Let's begin!
"""
- requiredKeys = [:trajectory_evaluation, :decision_evaluation, :good_decision, :suggestion]
+ requiredKeys = [:trajectory_evaluation, :decision_evaluation, :approved, :suggestion]
errornote = "N/A"
for attempt in 1:10
@@ -1116,7 +1123,7 @@ 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; recent=5)
+ thoughtDict = decisionMaker(a; recentevents=5)
actionname = thoughtDict[:action_name]
actioninput = thoughtDict[:action_input]
@@ -1230,30 +1237,39 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
end
-function presentbox(a::sommelier, thoughtDict; maxtattempt::Integer=10)
+function presentbox(a::sommelier, thoughtDict; maxtattempt::Integer=10, recentevents::Integer=10)
+ recentchat_ind = GeneralUtils.recentElementsIndex(length(a.chathistory), recentevents;
+ includelatest=true)
+ recentchat = createChatLog(a.chathistory; index=recentchat_ind)
systemmsg =
"""
- Your profile:
- Your name is $(a.name). You are a helpful English-speaking assistant, acting as a polite, website-based sommelier for $(a.retailername)'s wine store.
- Situation:
- You have checked the inventory and found wines.
- Your mission:
- Present the wines to the customer in a way that keep the conversation smooth and engaging.
- At each round of conversation, you will be given the following information:
+
+ Your name is $(a.name). You are a helpful English-speaking assistant, acting as a polite, website-based sommelier for $(a.retailername)'s wine store.
+
+
+ You have checked the inventory and found wines that may match what the user wants
+
+
+ Present the wines to the user in a way that keep the conversation smooth and engaging.
+
+
+ Name of the wines that needs to be introduced: name of wines you are going to introduce to the user
Database search result: the result of a database search using SQL commands you have found so far
- Chat history: your ongoing conversation with the user
- Wine name: name of wines you are going to introduce.
- You should follow the following guidelines:
+
+
- Provide detailed introductions of the wines you've found to the user.
- Explain how the wine could match the user's intention and what its effects might mean for the user's experience.
- If multiple wines are available, highlight their differences and provide a comprehensive comparison of how each option aligns with the user's intention and what the potential effects of each option could mean for the user's experience.
- Provide your personal recommendation and provide a brief explanation of why you recommend it.
- You should then respond to the user with:
+
+
dialogue: Your presentation to the user
- You should only respond in format as described below:
+
+
{
"dialogue": "..."
}
+
Let's begin!
"""
@@ -1265,7 +1281,7 @@ function presentbox(a::sommelier, thoughtDict; maxtattempt::Integer=10)
"N/A"
end
- chathistory = chatHistoryToText(a.chathistory)
+ # chathistory = chatHistoryToText(a.chathistory)
errornote = "N/A"
response = nothing # placeholder for show when error msg show up
@@ -1279,9 +1295,8 @@ function presentbox(a::sommelier, thoughtDict; maxtattempt::Integer=10)
context =
"""
+ Name of the wines that needs to be introduced: $(thoughtDict[:action_input])
Database search result: $database_search_result
- Chat history: $chathistory
- Wine name: $(thoughtDict[:action_input])
P.S. $errornote
"""
@@ -1291,6 +1306,7 @@ function presentbox(a::sommelier, thoughtDict; maxtattempt::Integer=10)
Dict(:name => "system", :text => systemmsg),
]
+ unformatPrompt = vcat(unformatPrompt, recentchat)
# put in model format
prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
# add info
@@ -1311,7 +1327,7 @@ function presentbox(a::sommelier, thoughtDict; maxtattempt::Integer=10)
try
responsedict = copy(JSON3.read(response))
catch
- println("\nERROR YiemAgent presentbox() failed to parse response: $response", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ println("\nERROR YiemAgent presentbox() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue
end
@@ -1400,6 +1416,176 @@ function presentbox(a::sommelier, thoughtDict; maxtattempt::Integer=10)
end
error("presentbox() failed to generate a response")
end
+# function presentbox(a::sommelier, thoughtDict; maxtattempt::Integer=10)
+# systemmsg =
+# """
+# Your profile:
+# Your name is $(a.name). You are a helpful English-speaking assistant, acting as a polite, website-based sommelier for $(a.retailername)'s wine store.
+# Situation:
+# You have checked the inventory and found wines.
+# Your mission:
+# Present the wines to the customer in a way that keep the conversation smooth and engaging.
+# 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
+# Chat history: your ongoing conversation with the user
+# Name of the wines that needs to be introduced: name of wines you are going to introduce to the user.
+# You should follow the following guidelines:
+# - Provide detailed introductions of the wines you've found to the user.
+# - Explain how the wine could match the user's intention and what its effects might mean for the user's experience.
+# - If multiple wines are available, highlight their differences and provide a comprehensive comparison of how each option aligns with the user's intention and what the potential effects of each option could mean for the user's experience.
+# - Provide your personal recommendation and provide a brief explanation of why you recommend it.
+# You should then respond to the user with:
+# dialogue: Your presentation to the user
+# You should only respond in format as described below:
+# {
+# "dialogue": "..."
+# }
+
+# Let's begin!
+# """
+# requiredKeys = [:dialogue]
+# database_search_result =
+# if length(a.memory[:shortmem][:db_search_result]) != 0
+# availableWineToText(a.memory[:shortmem][:db_search_result])
+# else
+# "N/A"
+# end
+
+# chathistory = chatHistoryToText(a.chathistory)
+# errornote = "N/A"
+# response = nothing # placeholder for show when error msg show up
+
+
+
+# # yourthought = "$(thoughtDict[:thought]) $(thoughtDict[:plan])"
+# # yourthought1 = nothing
+
+# for attempt in 1:maxtattempt
+
+# context =
+# """
+#
+# Database search result: $database_search_result
+# Chat history: $chathistory
+# Name of the wines that needs to be introduced: $(thoughtDict[:action_input])
+# P.S. $errornote
+#
+# """
+
+# unformatPrompt =
+# [
+# Dict(:name => "system", :text => systemmsg),
+# ]
+
+# # put in model format
+# prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
+# # add info
+# prompt = prompt * context
+
+# response = a.func[:text2textInstructLLM](prompt; senderId=a.id)
+# response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
+# response = GeneralUtils.remove_french_accents(response)
+# # response = replace(response, '$'=>"USD")
+# think, response = GeneralUtils.extractthink(response)
+
+# response = replace(response, '*'=>"")
+# response = replace(response, '$' => "USD")
+# response = replace(response, '`' => "")
+# response = replace(response, "<|eot_id|>"=>"")
+
+# responsedict = nothing
+# try
+# responsedict = copy(JSON3.read(response))
+# catch
+# println("\nERROR YiemAgent presentbox() 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 presentbox() $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 presentbox() $errornote --> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+# continue
+# end
+
+# # # check whether response has not-allowed words
+# # notallowed = ["respond:", "user>", "user:"]
+# # detected_kw = GeneralUtils.detect_keyword(notallowed, response)
+# # # list all keys that have 1 value in detected_kw dictionary
+# # k = [key for (key, value) in detected_kw if value == 1]
+# # if length(k) > 0
+# # errornote = "In your previous attempt, you have $k in your response which is not allowed."
+# # println("\nERROR YiemAgent presentbox() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+# # continue
+# # end
+
+# # # check whether response has all header
+# # detected_kw = GeneralUtils.detect_keyword(header, response)
+# # if 0 ∈ values(detected_kw)
+# # errornote = "$missingkeys are missing from your previous response"
+# # println("\nERROR YiemAgent presentbox() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+# # continue
+# # elseif sum(values(detected_kw)) > length(header)
+# # errornote = "\nYour previous attempt has duplicated points according to the required response format"
+# # println("\nERROR YiemAgent presentbox() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+# # continue
+# # end
+
+# # responsedict = GeneralUtils.textToDict(response, header;
+# # dictKey=dictkey, symbolkey=true)
+
+# # check if Context: is in dialogue
+# if occursin("Context:", responsedict[:dialogue])
+# errornote = "Your previous response contains 'Context:' which is not allowed"
+# println("\nERROR YiemAgent presentbox() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+# continue
+# end
+
+# println("\nYiemAgent presentbox() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+# pprintln(Dict(responsedict))
+
+# # check whether an agent recommend wines before checking inventory or recommend wines
+# # outside its inventory
+# # ask LLM whether there are any winery mentioned in the response
+# mentioned_winery = detectWineryName(a, responsedict[:dialogue])
+# if mentioned_winery != "None"
+# mentioned_winery = String.(strip.(split(mentioned_winery, ",")))
+
+# # check whether the wine is in event
+# isWineInEvent = false
+# for winename in mentioned_winery
+# for event in a.memory[:events]
+# if event[:outcome] !== nothing && occursin(winename, event[:outcome])
+# isWineInEvent = true
+# break
+# end
+# end
+# end
+
+# # if wine is mentioned but not in timeline or shortmem,
+# # then the agent is not supposed to recommend the wine
+# if isWineInEvent == false
+# errornote = "Your previous response recommended wines that is not in your inventory which is not allowed"
+# println("\nERROR YiemAgent presentbox() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+# continue
+# end
+# end
+
+# result = responsedict[:dialogue]
+# return result
+# end
+# error("presentbox() failed to generate a response")
+# end
@@ -1547,7 +1733,7 @@ function generatechat(a::sommelier, thoughtDict; maxattempt::Integer=10)
response = replace(response, '$' => "USD")
response = replace(response, '`' => "")
response = replace(response, "<|eot_id|>"=>"")
- response = GeneralUtils.remove_french_accents(response)
+
@@ -1612,35 +1798,39 @@ function generatechat(a::sommelier, thoughtDict; maxattempt::Integer=10)
end
-function generatechat(a::companion; converPartnerName::Union{String, Nothing}=nothing, maxattempt=10)
+function generatechat(a::companion; recentevents::Integer=10,
+ converPartnerName::Union{String, Nothing}=nothing, maxattempt=10)
+
+ recentchat_ind = GeneralUtils.recentElementsIndex(length(a.chathistory), recentevents;
+ includelatest=true);
+ recentchat = createChatLog(a.chathistory; index=recentchat_ind)
+
response = nothing # placeholder for show when error msg show up
errornote = "N/A"
- llmkwargs=Dict(
- :num_ctx => 32768,
- :temperature => 0.5,
- )
+
for attempt in 1:maxattempt
if attempt > 1
println("\nYiemAgent generatechat() attempt $attempt/$maxattempt ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
end
- systemmsg = a.systemmsg * "\nP.S. $errornote\n"
- _prompt =
- [
- Dict(:name => "system", :text => systemmsg),
- ]
- for i in a.chathistory
- tempdict = Dict{Symbol, String}()
- for j in keys(i)
- if j ∉ [:timestamp]
- tempdict[j] = i[j]
- end
- end
- _prompt = vcat(_prompt, tempdict)
- end
+ context =
+ """
+
+ P.S. $errornote
+
+ """
+ unformatPrompt =
+ [
+ Dict(:name => "system", :text => a.systemmsg),
+ ]
+
+ unformatPrompt = vcat(unformatPrompt, recentchat)
# put in model format
- _prompt = GeneralUtils.formatLLMtext(_prompt, a.llmFormatName)
+ prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
+ # add info
+ prompt = prompt * context
+
# replace user and assistant with partner name
prompt = replace(_prompt, "|>user"=>"|>$(converPartnerName)")
@@ -1664,6 +1854,58 @@ function generatechat(a::companion; converPartnerName::Union{String, Nothing}=no
end
error("generatechat failed to generate a response")
end
+# function generatechat(a::companion; converPartnerName::Union{String, Nothing}=nothing, maxattempt=10)
+# response = nothing # placeholder for show when error msg show up
+# errornote = "N/A"
+# llmkwargs=Dict(
+# :num_ctx => 32768,
+# :temperature => 0.5,
+# )
+# for attempt in 1:maxattempt
+# if attempt > 1
+# println("\nYiemAgent generatechat() attempt $attempt/$maxattempt ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+# end
+
+# systemmsg = a.systemmsg * "\nP.S. $errornote\n"
+# _prompt =
+# [
+# Dict(:name => "system", :text => systemmsg),
+# ]
+# for i in a.chathistory
+# tempdict = Dict{Symbol, String}()
+# for j in keys(i)
+# if j ∉ [:timestamp]
+# tempdict[j] = i[j]
+# end
+# end
+# _prompt = vcat(_prompt, tempdict)
+# end
+
+# # put in model format
+# _prompt = GeneralUtils.formatLLMtext(_prompt, a.llmFormatName)
+
+# # replace user and assistant with partner name
+# prompt = replace(_prompt, "|>user"=>"|>$(converPartnerName)")
+# prompt = replace(prompt, "|>assistant"=>"|>$(a.name)")
+
+# response = a.func[:text2textInstructLLM](prompt; llmkwargs=llmkwargs, senderId=a.id)
+# response = replace(response, "<|im_start|>"=> "")
+# response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
+# think, response = GeneralUtils.extractthink(response)
+
+# # check whether LLM just repeat the previous dialogue
+# for msg in a.chathistory
+# if msg[:text] == response
+# errornote = "In your previous attempt, you repeated the previous dialogue. Please try again."
+# println("\nYiemAgent generatechat() $errornote:\n$response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+# continue
+# end
+# end
+
+# return response
+# end
+# error("generatechat failed to generate a response")
+# end
# modify it to work with customer object
diff --git a/src/llmfunction.jl b/src/llmfunction.jl
index 78a522f..0b10ad9 100644
--- a/src/llmfunction.jl
+++ b/src/llmfunction.jl
@@ -307,7 +307,7 @@ function checkinventory(a::T1, input::T2
println("\ncheckinventory result ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
println(textresult)
-
+ #[PENDING] if there is no wine id, it is not a valid result
return (result=textresult, rawresponse=rawresponse, success=true, errormsg=nothing)
end
diff --git a/src/util.jl b/src/util.jl
index 2f18772..c5ef652 100644
--- a/src/util.jl
+++ b/src/util.jl
@@ -297,20 +297,53 @@ function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothin
1:length(events)
end
- # Iterate through events and format each one
- for (i, event) in zip(ind, events)
+ #[WORKING] 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
- timeline *= "Event_$i $(event[:subject])> action_name: $(event[:actionname]), action_input: $(event[:actioninput]), observation: Not done yet.\n"
+ # if event[:actionname] == "CHATBOX"
+ # 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
- else
+ if event[:actionname] == "CHECKINVENTORY"
timeline *= "Event_$i $(event[:subject])> action_name: $(event[:actionname]), action_input: $(event[:actioninput]), observation: $(event[:outcome])\n"
+ else
+ timeline *= "Event_$i $(event[:subject])> action_name: $(event[:actionname]), action_input: $(event[:actioninput])\n"
end
end
# Return formatted timeline string
return timeline
end
+# function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
+# ) where {T1<:AbstractVector}
+# # Initialize empty timeline string
+# timeline = ""
+
+# # Determine which indices to use - either provided range or full length
+# ind =
+# if eventindex !== nothing
+# [eventindex...]
+# 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
+# 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
+# else
+# timeline *= "Event_$i $(event[:subject])> action_name: $(event[:actionname]), action_input: $(event[:actioninput]), observation: $(event[:outcome])\n"
+# end
+# end
+
+# # Return formatted timeline string
+# return timeline
+# end
function createEventsLog(events::T1; index::Union{UnitRange, Nothing}=nothing
@@ -327,7 +360,8 @@ function createEventsLog(events::T1; index::Union{UnitRange, Nothing}=nothing
end
# 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 event[:outcome] === nothing
subject = event[:subject]
@@ -362,7 +396,8 @@ function createChatLog(chatdict::T1; index::Union{UnitRange, Nothing}=nothing
end
# Iterate through events and format each one
- for (i, event) in zip(ind, chatdict)
+ for i in ind
+ event = chatdict[i]
subject = event[:name]
text = event[:text]
d = Dict{Symbol, String}(:name=>subject, :text=>text)