update
This commit is contained in:
261
src/interface.jl
261
src/interface.jl
@@ -135,9 +135,9 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
|
||||
timeline = ""
|
||||
for (i, event) in enumerate(a.memory[:events][ind])
|
||||
if event[:outcome] === nothing
|
||||
timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue])\n"
|
||||
timeline *= "$i) $(event[:subject])> $(event[:actioninput])\n"
|
||||
else
|
||||
timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue]) $(event[:outcome])\n"
|
||||
timeline *= "$i) $(event[:subject])> $(event[:actioninput]) $(event[:outcome])\n"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -170,7 +170,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
|
||||
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.
|
||||
- All wines in your inventory are always in stock.
|
||||
- Before checking the inventory, engage in conversation to indirectly investigate the customer's intention, budget and preferences, which will significantly improve inventory search results.
|
||||
- Engage in conversation to indirectly investigate the customer's intention, budget and preferences before checking your inventory.
|
||||
- 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.
|
||||
- Medium and full-bodied red wines should not be paired with spicy foods.
|
||||
@@ -218,13 +218,16 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
|
||||
for winename in winenames
|
||||
if !occursin(winename, chathistory)
|
||||
println("\n~~~ Yiem decisionMaker() found wines from DB ", @__FILE__, " ", @__LINE__)
|
||||
return Dict(:action_name=> "PRESENTBOX",
|
||||
:action_input=> """
|
||||
1) Provide detailed introductions of the wines you just found to the customer.
|
||||
2) Explain how the wine could match the customer's intention and what its effects might mean for the customer's experience.
|
||||
3) If multiple wines are available, highlight their differences and provide a comprehensive comparison of how each option aligns with the customer's intention and what the potential effects of each option could mean for the customer's experience.
|
||||
4) Provide your personal recommendation based on your understanding of the customer's preferences.
|
||||
""")
|
||||
d = Dict(
|
||||
:understanding=> "I understand that the customer is looking for a wine that matches their intention and budget.",
|
||||
:reasoning=> "I checked the inventory and found wines that match the customer's criteria. I will present the wines to the customer.",
|
||||
:plan=> "1) Provide detailed introductions of the wines you just found to the customer.
|
||||
2) Explain how the wine could match the customer's intention and what its effects might mean for the customer's experience.
|
||||
3) If multiple wines are available, highlight their differences and provide a comprehensive comparison of how each option aligns with the customer's intention and what the potential effects of each option could mean for the customer's experience.
|
||||
4) Provide your personal recommendation based on your understanding of the customer's preferences.",
|
||||
:action_name=> "PRESENTBOX",
|
||||
:action_input=> "")
|
||||
return d
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -254,87 +257,95 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
|
||||
<|start_header_id|>assistant<|end_header_id|>
|
||||
"""
|
||||
|
||||
try
|
||||
response = a.func[:text2textInstructLLM](prompt)
|
||||
response = GeneralUtils.remove_french_accents(response)
|
||||
responsedict = GeneralUtils.textToDict(response,
|
||||
["Understanding", "Reasoning", "Plan", "Action_name", "Action_input"],
|
||||
rightmarker=":", symbolkey=true, lowercasekey=true)
|
||||
response = a.func[:text2textInstructLLM](prompt)
|
||||
response = GeneralUtils.remove_french_accents(response)
|
||||
|
||||
if responsedict[:action_name] ∉ ["CHATBOX", "PRESENTBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
||||
errornote = "You must use the given functions"
|
||||
error("You must use the given functions ", @__FILE__, " ", @__LINE__)
|
||||
# check if response contain more than one functions from ["CHATBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
||||
count = 0
|
||||
for i ∈ ["CHATBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
||||
if occursin(i, response)
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
if count > 1
|
||||
errornote = "You must use only one function"
|
||||
println("Attempt $attempt $errornote ", @__FILE__, " ", @__LINE__)
|
||||
continue
|
||||
end
|
||||
|
||||
for i ∈ [:understanding, :plan, :action_name]
|
||||
if length(responsedict[i]) == 0
|
||||
error("$i is empty ", @__FILE__, " ", @__LINE__)
|
||||
end
|
||||
responsedict = GeneralUtils.textToDict(response,
|
||||
["Understanding", "Reasoning", "Plan", "Action_name", "Action_input"],
|
||||
rightmarker=":", symbolkey=true, lowercasekey=true)
|
||||
|
||||
if responsedict[:action_name] ∉ ["CHATBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
||||
errornote = "You must use the given functions"
|
||||
println("Attempt $attempt $errornote ", @__FILE__, " ", @__LINE__)
|
||||
continue
|
||||
end
|
||||
|
||||
for i ∈ [:understanding, :plan, :action_name]
|
||||
if length(responsedict[i]) == 0
|
||||
error("$i is empty ", @__FILE__, " ", @__LINE__)
|
||||
errornote = "$i is empty"
|
||||
println("Attempt $attempt $errornote ", @__FILE__, " ", @__LINE__)
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
# check if there are more than 1 key per categories
|
||||
for i ∈ [:understanding, :plan, :action_name, :action_input]
|
||||
matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
|
||||
if length(matchkeys) > 1
|
||||
error("DecisionMaker has more than one key per categories")
|
||||
end
|
||||
# check if there are more than 1 key per categories
|
||||
for i ∈ [:understanding, :plan, :action_name, :action_input]
|
||||
matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
|
||||
if length(matchkeys) > 1
|
||||
errornote = "DecisionMaker has more than one key per categories"
|
||||
println("Attempt $attempt $errornote ", @__FILE__, " ", @__LINE__)
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
println("\n~~~ Yiem decisionMaker() ", @__FILE__, " ", @__LINE__)
|
||||
pprintln(Dict(responsedict))
|
||||
println("\n~~~ Yiem decisionMaker() ", @__FILE__, " ", @__LINE__)
|
||||
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, response)
|
||||
if mentioned_winery != "None"
|
||||
mentioned_winery = String.(strip.(split(mentioned_winery, ",")))
|
||||
# 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, response)
|
||||
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
|
||||
# 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
|
||||
|
||||
# if wine is mentioned but not in timeline or shortmem,
|
||||
# then the agent is not supposed to recommend the wine
|
||||
if responsedict[:action_name] == "CHATBOX" &&
|
||||
isWineInEvent == false
|
||||
|
||||
errornote = "Note: Before recommending a wine, ensure it's in your inventory. Check your stock first."
|
||||
error("Before recommending a wine, ensure it's in your inventory. Check your stock first.")
|
||||
end
|
||||
end
|
||||
|
||||
if occursin("--|", response)
|
||||
errornote = "Note: tables are not allowed. Do not include them your response."
|
||||
error("your response contain tables which is not allowed.")
|
||||
# if wine is mentioned but not in timeline or shortmem,
|
||||
# then the agent is not supposed to recommend the wine
|
||||
if responsedict[:action_name] == "CHATBOX" &&
|
||||
isWineInEvent == false
|
||||
|
||||
errornote = "Note: Before recommending a wine, ensure it's in your inventory. Check your stock first."
|
||||
println("Attempt $attempt $errornote ", @__FILE__, " ", @__LINE__)
|
||||
continue
|
||||
end
|
||||
|
||||
delete!(responsedict, :mentioned_winery)
|
||||
|
||||
# #CHANGE cache decision dict into vectorDB, this should be after new message is added to a.memory[:events]
|
||||
# println("\n~~~ Do you want to cache decision dict? (y/n)")
|
||||
# user_answer = readline()
|
||||
# if user_answer == "y"
|
||||
# timeline = timeline
|
||||
# decisiondict = responsedict
|
||||
# a.func[:insertSommelierDecision](timeline, decisiondict)
|
||||
# end
|
||||
|
||||
return responsedict
|
||||
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. \nError occurred: $errorMsg\n$st \nPrompt $prompt", @__FILE__, " ", @__LINE__)
|
||||
end
|
||||
|
||||
delete!(responsedict, :mentioned_winery)
|
||||
|
||||
# #CHANGE cache decision dict into vectorDB, this should be after new message is added to a.memory[:events]
|
||||
# println("\n~~~ Do you want to cache decision dict? (y/n)")
|
||||
# user_answer = readline()
|
||||
# if user_answer == "y"
|
||||
# timeline = timeline
|
||||
# decisiondict = responsedict
|
||||
# a.func[:insertSommelierDecision](timeline, decisiondict)
|
||||
# end
|
||||
|
||||
return responsedict
|
||||
end
|
||||
error("DecisionMaker failed to generate a thought ", response)
|
||||
end
|
||||
@@ -693,7 +704,7 @@ function conversation(a::sommelier, userinput::Dict)
|
||||
event_description="the user talks to the assistant.",
|
||||
timestamp=Dates.now(),
|
||||
subject="user",
|
||||
action_or_dialogue=userinput[:text],
|
||||
actioninput=userinput[:text],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -729,7 +740,7 @@ function conversation(a::companion, userinput::Dict)
|
||||
event_description="the user talks to the assistant.",
|
||||
timestamp=Dates.now(),
|
||||
subject="user",
|
||||
action_or_dialogue=userinput[:text],
|
||||
actioninput=userinput[:text],
|
||||
)
|
||||
)
|
||||
chatresponse = generatechat(a)
|
||||
@@ -741,7 +752,7 @@ function conversation(a::companion, userinput::Dict)
|
||||
event_description="the assistant talks to the user.",
|
||||
timestamp=Dates.now(),
|
||||
subject="assistant",
|
||||
action_or_dialogue=chatresponse,
|
||||
actioninput=chatresponse,
|
||||
)
|
||||
)
|
||||
return chatresponse
|
||||
@@ -775,8 +786,7 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
|
||||
# map action and input() to llm function
|
||||
response =
|
||||
if actionname == "CHATBOX"
|
||||
input = thoughtDict[:action_input]
|
||||
(result=input, errormsg=nothing, success=true)
|
||||
(result=thoughtDict[:plan], errormsg=nothing, success=true)
|
||||
elseif actionname == "CHECKINVENTORY"
|
||||
checkinventory(a, actioninput)
|
||||
elseif actionname == "PRESENTBOX"
|
||||
@@ -797,16 +807,25 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
|
||||
errormsg::Union{AbstractString,Nothing} = haskey(response, :errormsg) ? response[:errormsg] : nothing
|
||||
success::Bool = haskey(response, :success) ? response[:success] : false
|
||||
|
||||
# manage memory (pass msg to generatechat)
|
||||
#[WORKING] manage memory (pass msg to generatechat)
|
||||
if actionname ∈ ["CHATBOX", "PRESENTBOX", "ENDCONVERSATION"]
|
||||
chatresponse = generatechat(a, result)
|
||||
chatresponse = generatechat(a, thoughtDict)
|
||||
push!(a.memory[:events],
|
||||
eventdict(;
|
||||
event_description="the assistant talks to the user.",
|
||||
timestamp=Dates.now(),
|
||||
subject="assistant",
|
||||
action_or_dialogue=chatresponse,
|
||||
thought=thoughtDict,
|
||||
actionname=actionname,
|
||||
actioninput=chatresponse,
|
||||
)
|
||||
|
||||
# eventdict(;
|
||||
# event_description="the assistant talks to the user.",
|
||||
# timestamp=Dates.now(),
|
||||
# subject="assistant",
|
||||
# actioninput=chatresponse,
|
||||
# )
|
||||
)
|
||||
result = chatresponse
|
||||
if actionname == "PRESENTBOX"
|
||||
@@ -833,8 +852,10 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
|
||||
event_description= "the assistant searched the database.",
|
||||
timestamp= Dates.now(),
|
||||
subject= "assistant",
|
||||
action_or_dialogue= "I searched the database with this query: $actioninput",
|
||||
outcome= "This is what I found in the database, $result"
|
||||
thought=thoughtDict,
|
||||
actionname=actionname,
|
||||
actioninput= "I searched the database with this query: $actioninput",
|
||||
outcome= "This is what I've found in the database, $result"
|
||||
)
|
||||
)
|
||||
else
|
||||
@@ -866,7 +887,7 @@ julia>
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function generatechat(a::sommelier, thought::T) where {T<:AbstractString}
|
||||
function generatechat(a::sommelier, thoughtDict)
|
||||
systemmsg =
|
||||
"""
|
||||
Your name is $(a.name). You are a helpful English-speaking assistant, acting as a polite, website-based sommelier for an online wine store.
|
||||
@@ -922,7 +943,7 @@ function generatechat(a::sommelier, thought::T) where {T<:AbstractString}
|
||||
usermsg = """
|
||||
Your ongoing conversation with the user: $chathistory
|
||||
Contex: $context
|
||||
Your thoughts: $thought
|
||||
Your thoughts: $(thoughtDict[:understanding]) $(thoughtDict[:reasoning]) $(thoughtDict[:plan])
|
||||
$errornote
|
||||
"""
|
||||
|
||||
@@ -1016,28 +1037,29 @@ end
|
||||
|
||||
|
||||
function generatechat(a::companion)
|
||||
systemmsg =
|
||||
"""
|
||||
Your name is $(a.name). You are a helpful assistant.
|
||||
You are currently talking with the user.
|
||||
Your goal includes:
|
||||
1) Help the user as best as you can
|
||||
systemmsg =
|
||||
if a.systemmsg === nothing
|
||||
systemmsg =
|
||||
"""
|
||||
You are a helpful assistant.
|
||||
You are currently talking with the user.
|
||||
Your goal includes:
|
||||
1) Help the user as best as you can
|
||||
|
||||
Your responsibility includes:
|
||||
1) Given the situation, help the user.
|
||||
At each round of conversation, you will be given the following information:
|
||||
Your ongoing conversation with the user: ...
|
||||
|
||||
At each round of conversation, you will be given the current situation:
|
||||
Your ongoing conversation with the user: ...
|
||||
Context: ...
|
||||
You should then respond to the user with:
|
||||
1) chat: Given the information, what would you say to the user?
|
||||
|
||||
You should then respond to the user with:
|
||||
1) Chat: Given the situation, what would you say to the user?
|
||||
You should only respond in JSON format as described below:
|
||||
{"chat": ...}
|
||||
|
||||
You should only respond in format as described below:
|
||||
Chat: ...
|
||||
|
||||
Let's begin!
|
||||
"""
|
||||
Let's begin!
|
||||
"""
|
||||
else
|
||||
a.systemmsg
|
||||
end
|
||||
|
||||
chathistory = vectorOfDictToText(a.chathistory)
|
||||
response = nothing # placeholder for show when error msg show up
|
||||
@@ -1059,24 +1081,9 @@ function generatechat(a::companion)
|
||||
<|start_header_id|>assistant<|end_header_id|>
|
||||
"""
|
||||
|
||||
try
|
||||
response = a.func[:text2textInstructLLM](prompt)
|
||||
println("\n~~~ generatechat() ", @__FILE__, " ", @__LINE__)
|
||||
pprintln(response)
|
||||
response = a.text2textInstructLLM(prompt)
|
||||
|
||||
responsedict = GeneralUtils.textToDict(response, ["Chat"],
|
||||
rightmarker=":", symbolkey=true, lowercasekey=true)
|
||||
|
||||
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("\n Attempt $attempt. Error occurred: $errorMsg\n$st ", @__FILE__, " ", @__LINE__)
|
||||
end
|
||||
return response
|
||||
end
|
||||
error("generatechat failed to generate a response")
|
||||
end
|
||||
@@ -1185,9 +1192,9 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St
|
||||
timeline = ""
|
||||
for (i, event) in enumerate(a.memory[:events][ind])
|
||||
if event[:outcome] === nothing
|
||||
timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue])\n"
|
||||
timeline *= "$i) $(event[:subject])> $(event[:actioninput])\n"
|
||||
else
|
||||
timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue]) $(event[:outcome])\n"
|
||||
timeline *= "$i) $(event[:subject])> $(event[:actioninput]) $(event[:outcome])\n"
|
||||
end
|
||||
end
|
||||
errornote = ""
|
||||
@@ -1291,9 +1298,9 @@ function generateSituationReport(a, text2textInstructLLM::Function; skiprecent::
|
||||
timeline = ""
|
||||
for (i, event) in enumerate(events)
|
||||
if event[:outcome] === nothing
|
||||
timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue])\n"
|
||||
timeline *= "$i) $(event[:subject])> $(event[:actioninput])\n"
|
||||
else
|
||||
timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue]) $(event[:outcome])\n"
|
||||
timeline *= "$i) $(event[:subject])> $(event[:actioninput]) $(event[:outcome])\n"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user