3 Commits

Author SHA1 Message Date
724b092bdb update 2025-01-30 21:28:49 +07:00
c56c3d02b0 update 2025-01-29 12:16:01 +07:00
ton
a7f3e29e9c Merge pull request 'WIP v0.1.2-dev' (#1) from v0.1.2-dev into main
Reviewed-on: #1
2025-01-25 07:30:18 +00:00
3 changed files with 176 additions and 183 deletions

View File

@@ -182,11 +182,10 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
1) Make an informed decision about what you need to do to achieve the goal 1) Make an informed decision about what you need to do to achieve the goal
2) Thanks the user when they don't need any further assistance and invite them to comeback next time 2) Thanks the user when they don't need any further assistance and invite them to comeback next time
Your responsibility excludes: Your responsibility does NOT includes:
1) Asking or guiding the user to make an order or purchase 1) Requesting the user to place an order, make a purchase, or confirm the order. These are the job of our sales team at the store.
2) Processing sales orders or engaging in any other sales-related activities 2) Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
3) Answering questions beyond just recommendations. 3) Answering questions or offering additional services beyond those related to your store's wine recommendations such as discounts, quantity, rewards programs, promotions, delivery options, shipping, boxes, gift wrapping, packaging, personalized messages or something similar. These are the job of our sales team at the store.
4) Offering additional services beyond just recommendations.
At each round of conversation, you will be given the current situation: At each round of conversation, you will be given the current situation:
Your recent events: latest 5 events of the situation Your recent events: latest 5 events of the situation
@@ -195,7 +194,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
You must follow the following guidelines: You must follow the following guidelines:
- Generally speaking, your inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know exactly until you check your inventory. - Generally speaking, your inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know exactly until you check your inventory.
- All wines in your inventory are always in stock. - All wines in your inventory are always in stock.
- Engage in conversation to indirectly investigate the customer's intention, budget and preferences before checking your inventory. - Approach each customer with open-ended questions to understand their preferences, budget, and occasion. This will help you guide the conversation naturally while gathering essential insights. Once you have this information, you can efficiently check your inventory for the best match.
- Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar as these terms cannot be used to search the database. - Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar as these terms cannot be used to search the database.
- Once the user has selected their wine, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. - Once the user has selected their wine, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time.
- Medium and full-bodied red wines should not be paired with spicy foods. - Medium and full-bodied red wines should not be paired with spicy foods.
@@ -204,11 +203,10 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
- When searching an inventory, search as broadly as possible based on the information you have gathered so far. - When searching an inventory, search as broadly as possible based on the information you have gathered so far.
- Encourage the customer to explore different options and try new things. - Encourage the customer to explore different options and try new things.
- Sometimes, the item a user desires might not be available in your inventory. In such cases, inform the user that the item is unavailable and suggest an alternative instead. - Sometimes, the item a user desires might not be available in your inventory. In such cases, inform the user that the item is unavailable and suggest an alternative instead.
- If a customer requests information about discounts, quantity, rewards programs, promotions, delivery options, boxes, gift wrapping, packaging, or personalized messages, please inform them that they can contact our sales team at the store.
- Do not discuss other stores with the user except for your own.
For your information: For your information:
- vintage 0 means non-vintage. - Your store carries only wine.
- Vintage 0 means non-vintage.
You should then respond to the user with interleaving Understanding, Reasoning, Plan, Action: You should then respond to the user with interleaving Understanding, Reasoning, Plan, Action:
1) Understanding: 1) Understanding:
@@ -221,7 +219,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
- 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. The input is your intentions for the dialogue. Be specific.
- CHECKINVENTORY which you can use to check info about wine you want in your inventory. The input is a search term in verbal English. - CHECKINVENTORY which you can use to check info about wine you want in your inventory. The input is a search term in verbal English.
Good query example: white wine, full-bodied, France, less than 2000 USD. Good query example: white wine, full-bodied, France, less than 2000 USD.
- ENDCONVERSATION which you can use when you believe the user has concluded their interaction, to properly end the conversation with them. Input is "NA". - ENDCONVERSATION which you can use when the user has finished their conversation with you, so that you can properly end the conversation. Input is "NA".
5) Action_input: input of the action 5) Action_input: input of the action
You should only respond in format as described below: You should only respond in format as described below:
@@ -271,7 +269,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
push!(winenames, name) push!(winenames, name)
end end
availableWineName = join(winenames, ',') availableWineName = join(winenames, ',')
"You found information about the following wines in your inventory: $availableWineName" "Available wines you've found in your inventory so far: $availableWineName"
else else
"" ""
end end
@@ -862,16 +860,49 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
actionname=actionname, actionname=actionname,
actioninput=chatresponse, actioninput=chatresponse,
) )
# eventdict(;
# event_description="the assistant talks to the user.",
# timestamp=Dates.now(),
# subject="assistant",
# actioninput=chatresponse,
# )
) )
result = chatresponse result = chatresponse
# # store thoughtDict after the conversation finish
# if a.memory[:events][end][:thought][:action_name] == "ENDCONVERSATION"
# # generateSituationReport in the agent didn't include the last conversation
# # so the function will be called here
# a.memory[:recap] = generateSituationReport(a, a.func[:text2textInstructLLM]; skiprecent=0)
# for (i, event) in enumerate(a.memory[:events])
# if event[:subject] == "assistant"
# # create timeline of the last 3 conversation except the last one.
# # The former will be used as caching key and the latter will be the caching target
# # in vector database
# all_recapkeys = keys(a.memory[:recap]) # recap as caching
# all_recapkeys_vec = [r for r in all_recapkeys] # convert to a vector
# # select from 1 to 2nd-to-lase event (i.e. excluding the latest which is assistant's response)
# _recapkeys_vec = all_recapkeys_vec[1:i-1]
# # select only previous 3 recaps
# recapkeys_vec =
# if length(_recapkeys_vec) <= 3 # 1st message is a user's hello msg
# _recapkeys_vec # choose all
# else
# _recapkeys_vec[end-2:end]
# end
# #[PENDING] if there is specific data such as number, donot store in database
# tempmem = DataStructures.OrderedDict()
# for k in recapkeys_vec
# tempmem[k] = a.memory[:recap][k]
# end
# recap = GeneralUtils.dictToString_noKey(tempmem)
# thoughtDict = a.memory[:events][i][:thought] # latest assistant thoughtDict
# a.func[:insertSommelierDecision](recap, thoughtDict)
# else
# # skip
# end
# end
# println("Caching conversation done")
# end
elseif actionname == "CHECKINVENTORY" elseif actionname == "CHECKINVENTORY"
if rawresponse !== nothing if rawresponse !== nothing
vd = GeneralUtils.dfToVectorDict(rawresponse) vd = GeneralUtils.dfToVectorDict(rawresponse)
@@ -936,11 +967,10 @@ function generatechat(a::sommelier, thoughtDict)
Your responsibility includes: Your responsibility includes:
1) Given the situation, convey your thoughts to the user. 1) Given the situation, convey your thoughts to the user.
Your responsibility excludes: Your responsibility does NOT includes:
1) Asking or guiding the user to make an order or purchase 1) Requesting the user to place an order, make a purchase, or confirm the order. These are the job of our sales team at the store.
2) Processing sales orders or engaging in any other sales-related activities 2) Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
3) Answering questions beyond just recommendations. 3) Answering questions or offering additional services beyond those related to your store's wine recommendations such as discounts, quantity, rewards programs, promotions, delivery options, shipping, boxes, gift wrapping, packaging, personalized messages or something similar. These are the job of our sales team at the store.
4) Offering additional services beyond just recommendations.
At each round of conversation, you will be given the current situation: At each round of conversation, you will be given the current situation:
Your ongoing conversation with the user: ... Your ongoing conversation with the user: ...
@@ -949,13 +979,13 @@ function generatechat(a::sommelier, thoughtDict)
You MUST follow the following guidelines: You MUST follow the following guidelines:
- Do not offer additional services you didn't thought. - Do not offer additional services you didn't thought.
- Focus on plan.
You should follow the following guidelines: You should follow the following guidelines:
- Focus on the latest conversation. - Focus on the latest conversation.
- If the user interrupts, prioritize the user - If the user interrupts, prioritize the user
- Be honest - Be honest
- Medium and full-bodied red wines should not be paired with spicy foods. - Medium and full-bodied red wines should not be paired with spicy foods.
- Do not discuss other stores with the user except for your own.
You should then respond to the user with: You should then respond to the user with:
1) Chat: Given the situation, How would you respond to the user to express your thoughts honestly and keep the conversation going smoothly? 1) Chat: Given the situation, How would you respond to the user to express your thoughts honestly and keep the conversation going smoothly?
@@ -981,11 +1011,21 @@ function generatechat(a::sommelier, thoughtDict)
errornote = "" errornote = ""
response = nothing # placeholder for show when error msg show up response = nothing # placeholder for show when error msg show up
yourthought = "$(thoughtDict[:understanding]) $(thoughtDict[:reasoning]) $(thoughtDict[:plan])"
yourthought1 = nothing
for attempt in 1:10 for attempt in 1:10
if attempt > 1 # use to prevent LLM generate the same respond over and over
yourthought1 = paraphrase(a.func[:text2textInstructLLM], yourthought)
else
yourthought1 = yourthought
end
usermsg = """ usermsg = """
Your ongoing conversation with the user: $chathistory Your ongoing conversation with the user: $chathistory
Contex: $context Contex: $context
Your thoughts: $(thoughtDict[:understanding]) $(thoughtDict[:reasoning]) $(thoughtDict[:plan]) Your thoughts: $yourthought1
$errornote $errornote
""" """
@@ -1146,9 +1186,10 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St
Your responsibility includes: Your responsibility includes:
1) Ask yourself what to do about the current situation 1) Ask yourself what to do about the current situation
Your responsibility does not include: Your responsibility does NOT includes:
1) Processing sales orders or engaging in any other sales-related activities. 1) Requesting the user to place an order, make a purchase, or confirm the order. These are the job of our sales team at the store.
2) Answering questions and offering additional services beyond just recommendations. 2) Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
3) Answering questions or offering additional services beyond those related to your store's wine recommendations such as discounts, quantity, rewards programs, promotions, delivery options, shipping, boxes, gift wrapping, packaging, personalized messages or something similar. These are the job of our sales team at the store.
At each round of conversation, you will be given the current situation: At each round of conversation, you will be given the current situation:
Recap: recap of what has happened so far Recap: recap of what has happened so far
@@ -1180,7 +1221,7 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St
- State your understanding about the current situation - State your understanding about the current situation
2) Q: Given the situation, "ask yourself" at least five, but no more than ten, questions 2) Q: Given the situation, "ask yourself" at least five, but no more than ten, questions
3) A: Given the situation, "answer to yourself" the best you can 3) A: Given the situation, "answer to yourself" the best you can
- Do not generate any text after the last answer. - Do not generate any extra text after you finish answering all questions
You must only respond in format as described below: You must only respond in format as described below:
Understanding: ... Understanding: ...
@@ -1234,7 +1275,7 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St
context = context =
if length(a.memory[:shortmem][:available_wine]) != 0 if length(a.memory[:shortmem][:available_wine]) != 0
"Wines previously found in your inventory: $(availableWineToText(a.memory[:shortmem][:available_wine]))" "Available wines you've found in your inventory so far: $(availableWineToText(a.memory[:shortmem][:available_wine]))"
else else
"N/A" "N/A"
end end
@@ -1350,6 +1391,9 @@ function generateSituationReport(a, text2textInstructLLM::Function; skiprecent::
Events timeline: ... Events timeline: ...
Context: ... Context: ...
You should follow the following guidelines:
- Use the word "user" and "assistant" instead of their name in the report
You should then respond to the user with: You should then respond to the user with:
event: a detailed summary for each event without exaggerated details. event: a detailed summary for each event without exaggerated details.

View File

@@ -2,7 +2,7 @@ module llmfunction
export virtualWineUserChatbox, jsoncorrection, checkinventory, # recommendbox, export virtualWineUserChatbox, jsoncorrection, checkinventory, # recommendbox,
virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes_1, virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes_1,
extractWineAttributes_2 extractWineAttributes_2, paraphrase
using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs, Dates using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs, Dates
using GeneralUtils, SQLLLM using GeneralUtils, SQLLLM
@@ -307,8 +307,6 @@ function checkinventory(a::T1, input::T2
println("\n~~~ checkinventory result ", Dates.now(), " ", @__FILE__, " ", @__LINE__) println("\n~~~ checkinventory result ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println(textresult) println(textresult)
#[WORKING] when rawresponse is nothing, AI get errors
return (result=textresult, rawresponse=rawresponse, success=true, errormsg=nothing) return (result=textresult, rawresponse=rawresponse, success=true, errormsg=nothing)
end end
@@ -441,10 +439,19 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
end end
# check whether max wine_price is in the input # check whether max wine_price is in the input
maxprice = split(responsedict[:wine_price], '-')[end] pricerange = split(responsedict[:wine_price], '-')
minprice = pricerange[1]
maxprice = pricerange[end]
if !occursin(maxprice, input) if !occursin(maxprice, input)
responsedict[:wine_price] = "NA" responsedict[:wine_price] = "NA"
end end
# price range like 100-100 is not good
if minprice == maxprice
errornote = "wine_price with minimum equals to maximum is not valid"
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
checkFlag = true
break
end
end end
else else
content = responsedict[j] content = responsedict[j]
@@ -457,7 +464,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
content = [content] content = [content]
end end
for x in content #BUG why x is "0-1500" for x in content #check whether price are mentioned in the input
if !occursin("NA", responsedict[j]) && !occursin(x, input) if !occursin("NA", responsedict[j]) && !occursin(x, input)
errornote = "$x is not mentioned in the user query, you must only use the info from the query." errornote = "$x is not mentioned in the user query, you must only use the info from the query."
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__) println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
@@ -468,7 +475,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
end end
end end
end end
checkFlag == true ? continue : nothing checkFlag == true ? continue : nothing # skip the rest code if true
# remove (some text) # remove (some text)
for (k, v) in responsedict for (k, v) in responsedict
@@ -675,168 +682,110 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
end end
# function concept(a::sommelier, thoughtDict) function paraphrase(text2textInstructLLM::Function, text::String)
# systemmsg = systemmsg =
# """ """
# Your name: N/A Your name: N/A
# Situation: Your vision:
# - You are a helpful assistant - You are a helpful assistant who help the user to paraphrase their text.
# Your vision: Your mission:
# - This is a good opportunity to help the user - To help paraphrase the user's text
# Your mission: Mission's objective includes:
# - To describe the concept of a conversation - To help paraphrase the user's text
# Mission's objective includes: Your responsibility includes:
# - To 1) To help paraphrase the user's text
# Your responsibility includes: Your responsibility does NOT includes:
# 1) Given the situation, convey your thoughts to the user. 1) N/A
# Your responsibility excludes: Your profile:
# 1) Asking or guiding the user to make a purchase - N/A
# 2) Processing sales orders or engaging in any other sales-related activities Additional information:
# 3) Answering questions and offering additional services beyond just recommendations, such as delivery, box, gift wrapping, personalized messages. Customers can reach out to our sales at the store. - N/A
# Your profile:
# - You are a young professional in a big company.
# - You are avid party goer
# - You like beer.
# - You know nothing about wine.
# - You have a budget of 1500usd.
# Additional information:
# - your boss like spicy food.
# - your boss is a middle-aged man.
# At each round of conversation, you will be given the following information: At each round of conversation, you will be given the following information:
# Your ongoing conversation with the user: ... Text: The user's given text
# Context: ...
# Your thoughts: Your current thoughts in your mind
# You MUST follow the following guidelines: You MUST follow the following guidelines:
# - Do not offer additional services you didn't thought. - N/A
# You should follow the following guidelines: You should follow the following guidelines:
# - Focus on the latest conversation. - N/A
# - If the user interrupts, prioritize the user
# - Medium and full-bodied red wines should not be paired with spicy foods.
# You should then respond to the user with: You should then respond to the user with:
# 1) Chat: Given the situation, How would you respond to the user to express your thoughts honestly and keep the conversation going smoothly? 1) Paraphrase: Paraphrased text
# You should only respond in format as described below: You should only respond in format as described below:
# Chat: ... Paraphrase: ...
# Here are some examples of response format: Let's begin!
# Chat: "I see. Let me think about it. I'll get back to you with my recommendation." """
# Let's begin! errornote = ""
# """ response = nothing # placeholder for show when error msg show up
# # a.memory[:shortmem][:available_wine] is a dataframe.
# context =
# if haskey(a.memory[:shortmem], :available_wine)
# "Available wines $(GeneralUtils.dfToString(a.memory[:shortmem][:available_wine]))"
# else
# "None"
# end
# chathistory = vectorOfDictToText(a.chathistory) for attempt in 1:10
# errornote = "" usermsg = """
# response = nothing # placeholder for show when error msg show up Text: $text
$errornote
"""
# for attempt in 1:10 _prompt =
# usermsg = """ [
# Your ongoing conversation with the user: $chathistory Dict(:name => "system", :text => systemmsg),
# Contex: $context Dict(:name => "user", :text => usermsg)
# Your thoughts: $(thoughtDict[:understanding]) $(thoughtDict[:reasoning]) $(thoughtDict[:plan]) ]
# $errornote
# """
# _prompt = # put in model format
# [ prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
# Dict(:name => "system", :text => systemmsg), prompt *= """
# Dict(:name => "user", :text => usermsg) <|start_header_id|>assistant<|end_header_id|>
# ] """
# # put in model format try
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct") response = text2textInstructLLM(prompt)
# prompt *= """ # sometime the model response like this "here's how I would respond: ..."
# <|start_header_id|>assistant<|end_header_id|> if occursin("respond:", response)
# """ errornote = "You don't need to intro your response"
error("\n~~~ paraphrase() response contain : ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
end
response = GeneralUtils.remove_french_accents(response)
response = replace(response, '*'=>"")
response = replace(response, '$' => "USD")
response = replace(response, '`' => "")
response = GeneralUtils.remove_french_accents(response)
responsedict = GeneralUtils.textToDict(response, ["Paraphrase"],
rightmarker=":", symbolkey=true, lowercasekey=true)
# try for i [:paraphrase]
# response = a.func[:text2textInstructLLM](prompt) if length(JSON3.write(responsedict[i])) == 0
# # sometime the model response like this "here's how I would respond: ..." error("$i is empty ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
# if occursin("respond:", response) end
# errornote = "You don't need to intro your response" end
# error("generatechat() response contain : ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
# end
# response = GeneralUtils.remove_french_accents(response)
# response = replace(response, '*'=>"")
# response = replace(response, '$' => "USD")
# response = replace(response, '`' => "")
# response = GeneralUtils.remove_french_accents(response)
# responsedict = GeneralUtils.textToDict(response, ["Chat"],
# rightmarker=":", symbolkey=true, lowercasekey=true)
# for i ∈ [:chat] # check if there are more than 1 key per categories
# if length(JSON3.write(responsedict[i])) == 0 for i [:paraphrase]
# error("$i is empty ", Dates.now(), " ", @__FILE__, " ", @__LINE__) matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
# end if length(matchkeys) > 1
# end error("paraphrase() has more than one key per categories")
end
end
# # check if there are more than 1 key per categories println("\n~~~ paraphrase() ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
# for i ∈ [:chat] pprintln(Dict(responsedict))
# matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
# if length(matchkeys) > 1
# error("generatechat has more than one key per categories")
# end
# end
# # check if Context: is in chat result = responsedict[:paraphrase]
# if occursin("Context:", responsedict[:chat])
# error("Context: is in text. This is not allowed")
# end
# println("\n~~~ generatechat() ", Dates.now(), " ", @__FILE__, " ", @__LINE__) return result
# pprintln(Dict(responsedict)) catch e
io = IOBuffer()
# # check whether an agent recommend wines before checking inventory or recommend wines showerror(io, e)
# # outside its inventory errorMsg = String(take!(io))
# # ask LLM whether there are any winery mentioned in the response st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
# mentioned_winery = detectWineryName(a, responsedict[:chat]) println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
# if mentioned_winery != "None" end
# mentioned_winery = String.(strip.(split(mentioned_winery, ","))) end
error("generatechat failed to generate a response")
# # check whether the wine is in event end
# 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 = "Previously: You recommend a wine that is not in your inventory which is not allowed."
# error("Previously: You recommend a wine that is not in your inventory which is not allowed.")
# end
# end
# result = responsedict[:chat]
# return result
# catch e
# io = IOBuffer()
# showerror(io, e)
# errorMsg = String(take!(io))
# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
# println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
# end
# end
# error("generatechat failed to generate a response")
# end

View File

@@ -107,7 +107,7 @@ function addNewMessage(a::T1, name::String, text::T2;
error("name is not in agent.availableRole $(@__LINE__)") error("name is not in agent.availableRole $(@__LINE__)")
end end
#[WORKING] summarize the oldest 10 message #[PENDING] summarize the oldest 10 message
if length(a.chathistory) > maximumMsg if length(a.chathistory) > maximumMsg
summarize(a.chathistory) summarize(a.chathistory)
else else