update
This commit is contained in:
427
src/interface.jl
427
src/interface.jl
@@ -4,7 +4,7 @@ export addNewMessage, conversation, decisionMaker, reflector, generatechat,
|
||||
generalconversation, detectWineryName, generateSituationReport
|
||||
|
||||
using JSON3, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||
DataFrames
|
||||
DataFrames, CSV
|
||||
using GeneralUtils
|
||||
using ..type, ..util, ..llmfunction
|
||||
|
||||
@@ -97,7 +97,8 @@ julia> output_thoughtDict = Dict(
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function decisionMaker(a::T; recent::Integer=10)::Dict{Symbol,Any} where {T<:agent}
|
||||
function decisionMaker(a::T; recent::Integer=10
|
||||
) where {T<:agent}
|
||||
|
||||
# lessonDict = copy(JSON3.read("lesson.json"))
|
||||
|
||||
@@ -126,9 +127,9 @@ function decisionMaker(a::T; recent::Integer=10)::Dict{Symbol,Any} where {T<:age
|
||||
|
||||
recent_ind = GeneralUtils.recentElementsIndex(length(a.memory[:events]), recent)
|
||||
recentevents = a.memory[:events][recent_ind]
|
||||
timeline = createTimeline(recentevents)
|
||||
timeline = createTimeline(recentevents; eventindex=recent_ind)
|
||||
|
||||
#[TESTING] recap as caching
|
||||
# recap as caching
|
||||
# query similar result from vectorDB
|
||||
recapkeys = keys(a.memory[:recap])
|
||||
_recapkeys_vec = [i for i in recapkeys]
|
||||
@@ -160,59 +161,60 @@ function decisionMaker(a::T; recent::Integer=10)::Dict{Symbol,Any} where {T<:age
|
||||
else
|
||||
systemmsg =
|
||||
"""
|
||||
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.
|
||||
Your goal includes:
|
||||
1) Establish a connection with the customer by greeting them warmly
|
||||
2) Guide them to select the best wines only from your store's inventory that align with their preferences
|
||||
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.
|
||||
Your goal includes:
|
||||
1) Establish a connection with the customer by greeting them warmly
|
||||
2) Guide them to select the best wines only from your store's inventory that align with their preferences
|
||||
|
||||
Your responsibility includes:
|
||||
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
|
||||
Your responsibility includes:
|
||||
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
|
||||
|
||||
Your responsibility does NOT includes:
|
||||
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. 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.
|
||||
Your responsibility does NOT includes:
|
||||
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. 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:
|
||||
Your recent events: latest 5 events of the situation
|
||||
Your Q&A: the question and answer you have asked yourself
|
||||
At each round of conversation, you will be given the following information:
|
||||
Your recent events: latest 5 events of the situation
|
||||
Your Q&A: the question and answer you have asked yourself
|
||||
|
||||
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.
|
||||
- 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.
|
||||
- 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.
|
||||
- Spicy foods should not be paired with medium and full-bodied red wines.
|
||||
You must follow the following guidelines:
|
||||
- Focus on the latest event
|
||||
- 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
|
||||
- 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.
|
||||
- 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.
|
||||
- Spicy foods should not be paired with medium and full-bodied red wines.
|
||||
|
||||
You should follow the following guidelines:
|
||||
- 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.
|
||||
- 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.
|
||||
You should follow the following guidelines:
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
For your information:
|
||||
- Your store carries only wine.
|
||||
- Vintage 0 means non-vintage.
|
||||
For your information:
|
||||
- Your store carries only wine.
|
||||
- Vintage 0 means non-vintage.
|
||||
|
||||
You should then respond to the user with interleaving Thought, Plan, Action_name, Action_input:
|
||||
1) Thought: Articulate your current understanding and consider the present situation.
|
||||
2) Plan: Based on the current situation, state a complete action plan to complete the task. Be specific.
|
||||
3) Action_name: (Typically corresponds to the execution of the first step in your plan) Can be one of the following function names:
|
||||
- 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 is verbal english and it should includes - winery, wine name, vintage, region, country, wine type, grape varietal, tasting notes, wine price, occasion, food to be paired with wine, intensity, tannin, sweetness, acidity.
|
||||
Bad query example: red wine that pair well with spicy food.
|
||||
You should then respond to the user with interleaving Thought, Plan, Action_name, Action_input:
|
||||
1) Thought: Articulate your current understanding and consider the current situation.
|
||||
2) Plan: Based on the current situation, state a complete action plan to complete the task. Be specific.
|
||||
3) Action_name: (Typically corresponds to the execution of the first step in your plan) Can be one of the following function names:
|
||||
- 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 is verbal english and it should includes - winery, wine name, vintage, region, country, wine type, grape varietal, tasting notes, wine price, occasion, food to be paired with wine, intensity, tannin, sweetness, acidity.
|
||||
Invalid query example: red wine that pair well with spicy food.
|
||||
- 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 when the user has finished their conversation with you, so that you can properly end the conversation. Input is "NA".
|
||||
4) Action_input: input of the action
|
||||
|
||||
- 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".
|
||||
4) Action_input: input of the action
|
||||
You should only respond in format as described below:
|
||||
Thought: ...
|
||||
Plan: ...
|
||||
Action_name: ...
|
||||
Action_input: ...
|
||||
|
||||
You should only respond in format as described below:
|
||||
Thought: ...
|
||||
Plan: ...
|
||||
Action_name: ...
|
||||
Action_input: ...
|
||||
|
||||
Let's begin!
|
||||
Let's begin!
|
||||
"""
|
||||
|
||||
header = ["Thought:", "Plan:", "Action_name:", "Action_input:"]
|
||||
@@ -220,32 +222,6 @@ function decisionMaker(a::T; recent::Integer=10)::Dict{Symbol,Any} where {T<:age
|
||||
|
||||
chathistory = chatHistoryToText(a.chathistory)
|
||||
|
||||
# check if winename in shortmem occurred in chathistory. if not, skip decision and imediately use PRESENTBOX
|
||||
if length(a.memory[:shortmem][:found_wine]) != 0
|
||||
# check if wine name mentioned in recentevents, only check first wine name is enough
|
||||
# because agent will recommend every wines it found each time.
|
||||
winenames = []
|
||||
for wine in a.memory[:shortmem][:found_wine]
|
||||
push!(winenames, wine["wine_name"])
|
||||
end
|
||||
|
||||
for winename in winenames
|
||||
if !occursin(winename, chathistory)
|
||||
println("\nYiem decisionMaker() found wines from DB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
d = Dict(
|
||||
:thought=> "I understand that the customer is looking for a wine that matches their intention and budget. 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=> "")
|
||||
a.memory[:shortmem][:found_wine] = [] # clear because PRESENTBOX command is issued. This is to prevent decisionMaker() keep presenting the same wines
|
||||
return d
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context = # may b add wine name instead of the hold wine data is better
|
||||
if length(a.memory[:shortmem][:available_wine]) != 0
|
||||
winenames = []
|
||||
@@ -277,14 +253,41 @@ function decisionMaker(a::T; recent::Integer=10)::Dict{Symbol,Any} where {T<:age
|
||||
$errornote
|
||||
"""
|
||||
|
||||
_prompt =
|
||||
unformatPrompt =
|
||||
[
|
||||
Dict(:name => "system", :text => systemmsg),
|
||||
Dict(:name => "user", :text => usermsg)
|
||||
]
|
||||
|
||||
#BUG found wine is "count 0" invalid return from CHECKINVENTORY()
|
||||
# check if winename in shortmem occurred in chathistory. if not, skip decision and imediately use PRESENTBOX
|
||||
# if length(a.memory[:shortmem][:found_wine]) != 0
|
||||
# # check if wine name mentioned in recentevents, only check first wine name is enough
|
||||
# # because agent will recommend every wines it found each time.
|
||||
# winenames = []
|
||||
# for wine in a.memory[:shortmem][:found_wine]
|
||||
# push!(winenames, wine["wine_name"])
|
||||
# end
|
||||
|
||||
# for winename in winenames
|
||||
# if !occursin(winename, chathistory)
|
||||
# println("\nYiem decisionMaker() found wines from DB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
# d = Dict(
|
||||
# :thought=> "The user is looking for a wine that matches their intention and budget. I've checked the inventory and found wines that match the customer's criteria. I will present the wines to the customer.",
|
||||
# :plan=> "1) I'll provide detailed introductions of the wines I just found to the user. 2) I'll explain how the wine could match the user's intention and what its effects might mean for the user's experience. 3) If multiple wines are available, I'll 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. 4) I'll provide my personal recommendation.",
|
||||
# :action_name=> "PRESENTBOX",
|
||||
# :action_input=> "I need to present to the user the following wines: $winenames")
|
||||
# a.memory[:shortmem][:found_wine] = [] # clear because PRESENTBOX command is issued. This is to prevent decisionMaker() keep presenting the same wines
|
||||
# result = (systemmsg=systemmsg, usermsg=usermsg, unformatPrompt=unformatPrompt, result=d)
|
||||
# println("\nYiem decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
# pprintln(Dict(d))
|
||||
# return result
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# change qwen format put in model format
|
||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||
prompt = GeneralUtils.formatLLMtext(unformatPrompt; formatname="qwen")
|
||||
|
||||
response = a.func[:text2textInstructLLM](prompt)
|
||||
response = GeneralUtils.remove_french_accents(response)
|
||||
@@ -294,7 +297,7 @@ function decisionMaker(a::T; recent::Integer=10)::Dict{Symbol,Any} where {T<:age
|
||||
|
||||
# check if response contain more than one functions from ["CHATBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
||||
count = 0
|
||||
for i ∈ ["CHATBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
||||
for i ∈ ["CHATBOX", "CHECKINVENTORY", "PRESENTBOX", "ENDCONVERSATION"]
|
||||
if occursin(i, response)
|
||||
count += 1
|
||||
end
|
||||
@@ -323,8 +326,8 @@ function decisionMaker(a::T; recent::Integer=10)::Dict{Symbol,Any} where {T<:age
|
||||
responsedict = GeneralUtils.textToDict(response, header;
|
||||
dictKey=dictkey, symbolkey=true)
|
||||
|
||||
if responsedict[:action_name] ∉ ["CHATBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
||||
errornote = "You must use the given functions"
|
||||
if responsedict[:action_name] ∉ ["CHATBOX", "CHECKINVENTORY", "PRESENTBOX", "ENDCONVERSATION"]
|
||||
errornote = "Your previous attempt didn't use the given functions"
|
||||
println("\nYiemAgent decisionMaker() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
end
|
||||
@@ -345,7 +348,7 @@ function decisionMaker(a::T; recent::Integer=10)::Dict{Symbol,Any} where {T<:age
|
||||
for i ∈ Symbol.(dictkey)
|
||||
matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
|
||||
if length(matchkeys) > 1
|
||||
errornote = "DecisionMaker has more than one key per categories"
|
||||
errornote = "Your previous attempt has more than one key per categories"
|
||||
println("\nYiemAgent decisionMaker() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
checkFlag = true
|
||||
break
|
||||
@@ -353,6 +356,15 @@ function decisionMaker(a::T; recent::Integer=10)::Dict{Symbol,Any} where {T<:age
|
||||
end
|
||||
checkFlag == true ? continue : nothing
|
||||
|
||||
# check if action_name = CHECKINVENTORY and action_input has the words "pairs well" or
|
||||
# "pair well" in it because it is not a valid query.
|
||||
detected_kw = GeneralUtils.detect_keyword(["pair", "pairs", "pairing", "well"], responsedict[:action_input])
|
||||
if responsedict[:action_name] == "CHECKINVENTORY" && sum(values(detected_kw)) != 0
|
||||
errornote = "Your previous attempt has invalid query"
|
||||
println("\nYiemAgent decisionMaker() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
end
|
||||
|
||||
println("\nYiem decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
pprintln(Dict(responsedict))
|
||||
|
||||
@@ -386,6 +398,20 @@ function decisionMaker(a::T; recent::Integer=10)::Dict{Symbol,Any} where {T<:age
|
||||
end
|
||||
|
||||
delete!(responsedict, :mentioned_winery)
|
||||
responsedict[:systemmsg] = systemmsg
|
||||
responsedict[:usermsg] = usermsg
|
||||
responsedict[:unformatPrompt] = unformatPrompt
|
||||
responsedict[:QandA] = QandA
|
||||
|
||||
# store responsedict in decisionlog.csv. if it is the first time, create the file
|
||||
if !isfile("/appfolder/app/decisionlog.csv")
|
||||
CSV.write(decisionlog, responsedict)
|
||||
else
|
||||
CSV.write(decisionlog, responsedict, append=true)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
return responsedict
|
||||
end
|
||||
@@ -813,9 +839,8 @@ julia>
|
||||
# Signature
|
||||
"""
|
||||
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=3)
|
||||
thoughtDict = decisionMaker(a; recent=5)
|
||||
|
||||
actionname = thoughtDict[:action_name]
|
||||
actioninput = thoughtDict[:action_input]
|
||||
@@ -844,22 +869,9 @@ 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)
|
||||
if actionname ∈ ["CHATBOX", "PRESENTBOX", "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=actioninput,
|
||||
)
|
||||
)
|
||||
result = chatresponse
|
||||
# if actionname ∈ ["CHATBOX", "ENDCONVERSATION"]
|
||||
# # chatresponse = generatechat(a, thoughtDict)
|
||||
# # manage memory (pass msg to generatechat)
|
||||
# if actionname ∈ ["CHATBOX", "PRESENTBOX", "ENDCONVERSATION"]
|
||||
# chatresponse = generatechat(a, thoughtDict)
|
||||
# push!(a.memory[:events],
|
||||
# eventdict(;
|
||||
# event_description="the assistant talks to the user.",
|
||||
@@ -870,20 +882,33 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
|
||||
# actioninput=actioninput,
|
||||
# )
|
||||
# )
|
||||
# result = actioninput
|
||||
# elseif actionname ∈ ["PRESENTBOX"]
|
||||
# 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
|
||||
if actionname ∈ ["CHATBOX", "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=actioninput,
|
||||
)
|
||||
)
|
||||
result = actioninput
|
||||
elseif actionname ∈ ["PRESENTBOX"]
|
||||
chatresponse = presentbox(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 == "CHECKINVENTORY"
|
||||
if rawresponse !== nothing
|
||||
@@ -926,41 +951,34 @@ function presentbox(a::sommelier, thoughtDict)
|
||||
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.
|
||||
</Your profile>
|
||||
<Situation>
|
||||
You have checked the inventory and found wines that match the customer's criteria.
|
||||
You have checked the inventory and found wines.
|
||||
</Situation>
|
||||
<Your mission>
|
||||
Present the wines to the customer in a way that keep the conversation smooth and engaging.
|
||||
</Your mission>
|
||||
<At each round of conversation, you will be given the following>
|
||||
Your ongoing conversation with the user: ...
|
||||
Inventory check result: ...
|
||||
Your thoughts: Your current thoughts in your mind
|
||||
<At each round of conversation, you will be given the following information>
|
||||
Additional info: additional information
|
||||
Chat history: your ongoing conversation with the user
|
||||
Wine name: name if wines you found.
|
||||
</At each round of conversation, you will be given the following>
|
||||
<You must follow the following guidelines>
|
||||
- Do not offer additional services you didn't think.
|
||||
- Focus on plan.
|
||||
</You must follow the following guidelines>
|
||||
<You should follow the following guidelines>
|
||||
- Focus on the latest conversation.
|
||||
- If the user interrupts, prioritize the user
|
||||
- Be honest
|
||||
- Medium and full-bodied red wines should not be paired with spicy foods.
|
||||
- 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 follow the following guidelines>
|
||||
<You should then respond to the user with>
|
||||
Chat: ...
|
||||
Dialogue: your wine presentation to the user
|
||||
</You should then respond to the user with>
|
||||
<Here are some examples>
|
||||
Your ongoing conversation with the user: "user> hello, I need a new car\n"
|
||||
Additional info: "Car previously found in your inventory: 1) Toyota Camry 2020 2) Honda Civic 2021 3) Ford Mustang 2022"
|
||||
Your thoughts: "I should recommend the car we have to the user."
|
||||
Chat: "We have a variety of cars available, including the Toyota Camry 2020, the Honda Civic 2021, and the Ford Mustang 2022. Which one would you like to see?"
|
||||
</Here are some examples>
|
||||
<You should only respond in format as described below>
|
||||
Dialogue: ...
|
||||
</You should only respond in format as described below>
|
||||
|
||||
Let's begin!
|
||||
"""
|
||||
|
||||
header = ["Chat:"]
|
||||
dictkey = ["chat"]
|
||||
header = ["Dialogue:"]
|
||||
dictkey = ["dialogue"]
|
||||
|
||||
# a.memory[:shortmem][:available_wine] is a vector of dictionary
|
||||
context =
|
||||
@@ -974,29 +992,26 @@ function presentbox(a::sommelier, thoughtDict)
|
||||
errornote = ""
|
||||
response = nothing # placeholder for show when error msg show up
|
||||
|
||||
yourthought = "$(thoughtDict[:thought]) $(thoughtDict[:plan])"
|
||||
yourthought1 = nothing
|
||||
# yourthought = "$(thoughtDict[:thought]) $(thoughtDict[:plan])"
|
||||
# yourthought1 = nothing
|
||||
|
||||
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)
|
||||
println("\nYiemAgent presentbox() attempt $attempt/10 ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
# yourthought1 = paraphrase(a.func[:text2textInstructLLM], yourthought)
|
||||
# llmkwargs[:temperature] = 0.1 * attempt
|
||||
else
|
||||
yourthought1 = yourthought
|
||||
# yourthought1 = yourthought
|
||||
end
|
||||
|
||||
usermsg = """
|
||||
<Your ongoing conversation with the user>
|
||||
$chathistory
|
||||
</Your ongoing conversation with the user>
|
||||
<Additional info>
|
||||
$context
|
||||
</Additional info>
|
||||
<Your thoughts>
|
||||
$yourthought1
|
||||
</Your thoughts>
|
||||
$errornote
|
||||
"""
|
||||
usermsg =
|
||||
"""
|
||||
$errornote
|
||||
Additional info: $context
|
||||
Chat history: $chathistory
|
||||
Wine name: $(thoughtDict[:action_input])
|
||||
"""
|
||||
|
||||
_prompt =
|
||||
[
|
||||
@@ -1010,8 +1025,9 @@ function presentbox(a::sommelier, thoughtDict)
|
||||
response = a.func[:text2textInstructLLM](prompt)
|
||||
# sometime the model response like this "here's how I would respond: ..."
|
||||
if occursin("respond:", response)
|
||||
errornote = "You don't need to intro your response"
|
||||
error("generatechat() response contain : ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
errornote = "Your previous response contains 'response:' which is not allowed"
|
||||
println("\nERROR YiemAgent presentbox() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
end
|
||||
response = GeneralUtils.remove_french_accents(response)
|
||||
response = replace(response, '*'=>"")
|
||||
@@ -1023,28 +1039,32 @@ function presentbox(a::sommelier, thoughtDict)
|
||||
# check whether response has all header
|
||||
detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||
if 0 ∈ values(detected_kw)
|
||||
errornote = "\nYiemAgent generatechat() response does not have all header"
|
||||
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 = "\nnYiemAgent generatechat() response has duplicated 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 chat
|
||||
if occursin("Context:", responsedict[:chat])
|
||||
error("Context: is in text. This is not allowed")
|
||||
# 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("\ngeneratechat() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
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[:chat])
|
||||
mentioned_winery = detectWineryName(a, responsedict[:dialogue])
|
||||
if mentioned_winery != "None"
|
||||
mentioned_winery = String.(strip.(split(mentioned_winery, ",")))
|
||||
|
||||
@@ -1062,13 +1082,13 @@ function presentbox(a::sommelier, thoughtDict)
|
||||
# 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 wines that is not in your inventory which is not allowed."
|
||||
error("Previously, You recommend wines that is not in your inventory which is not allowed.")
|
||||
errornote = "Your previous response recommends 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[:chat]
|
||||
result = responsedict[:dialogue]
|
||||
|
||||
return result
|
||||
end
|
||||
@@ -1111,8 +1131,8 @@ function generatechat(a::sommelier, thoughtDict)
|
||||
- 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 following:
|
||||
Additional info: ...
|
||||
Your thoughts: Your current thoughts in your mind
|
||||
Your ongoing conversation with the user: ...
|
||||
Your thoughts: Your current thoughts in your mind
|
||||
You must follow the following guidelines:
|
||||
- Do not offer additional services you didn't think
|
||||
You should follow the following guidelines:
|
||||
@@ -1150,11 +1170,16 @@ function generatechat(a::sommelier, thoughtDict)
|
||||
yourthought = "$(thoughtDict[:thought]) $(thoughtDict[:plan])"
|
||||
yourthought1 = nothing
|
||||
|
||||
for attempt in 1:10
|
||||
llmkwargs=Dict(
|
||||
:num_ctx => 32768,
|
||||
:temperature => 0.1,
|
||||
)
|
||||
|
||||
for attempt in 1:10
|
||||
if attempt > 1 # use to prevent LLM generate the same respond over and over
|
||||
println("\nYiemAgent generatchat() attempt $attempt/10 ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
yourthought1 = paraphrase(a.func[:text2textInstructLLM], yourthought)
|
||||
llmkwargs[:temperature] = 0.1 * attempt
|
||||
else
|
||||
yourthought1 = yourthought
|
||||
end
|
||||
@@ -1163,8 +1188,8 @@ function generatechat(a::sommelier, thoughtDict)
|
||||
"""
|
||||
$errornote
|
||||
Additional info: $context
|
||||
Your thoughts: $yourthought1
|
||||
Your ongoing conversation with the user: $chathistory
|
||||
Your thoughts: $yourthought1
|
||||
"""
|
||||
|
||||
_prompt =
|
||||
@@ -1175,15 +1200,15 @@ function generatechat(a::sommelier, thoughtDict)
|
||||
|
||||
# put in model format
|
||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||
response = a.func[:text2textInstructLLM](prompt)
|
||||
response = a.func[:text2textInstructLLM](prompt; llmkwargs=llmkwargs)
|
||||
|
||||
# sometime the model response like this "here's how I would respond: ..."
|
||||
if occursin("respond:", response)
|
||||
errornote = "You don't need to put 'response:' in your response"
|
||||
println("ERROR YiemAgent generatechat() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
errornote = "Your previous response contains 'response:' which is not allowed"
|
||||
println("\nERROR YiemAgent generatechat() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
elseif occursin("Your thoughts:", response) || occursin("your thoughts:", response)
|
||||
errornote = "You don't need to put 'Your thoughts:' in your response"
|
||||
println("ERROR YiemAgent generatechat() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
println("\nERROR YiemAgent generatechat() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
end
|
||||
response = GeneralUtils.remove_french_accents(response)
|
||||
response = replace(response, '*'=>"")
|
||||
@@ -1199,20 +1224,21 @@ function generatechat(a::sommelier, thoughtDict)
|
||||
missingkeys = [header[i] for i in zeroind]
|
||||
if 0 ∈ values(detected_kw)
|
||||
errornote = "$missingkeys are missing from your previous response"
|
||||
println("\nYiemAgent generatechat() $errornote:\n $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
println("\nERROR YiemAgent generatechat() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
elseif sum(values(detected_kw)) > length(header)
|
||||
errornote = "Your response has duplicated points"
|
||||
println("\n$errornote: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
errornote = "\nYour previous attempt has duplicated points according to the required response format"
|
||||
println("\nERROR YiemAgent generatechat() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
end
|
||||
|
||||
responsedict = GeneralUtils.textToDict(response, header;
|
||||
dictKey=dictkey, symbolkey=true)
|
||||
|
||||
# check if Context: is in chat
|
||||
# check if Context: is in dialogue
|
||||
if occursin("Context:", responsedict[:dialogue])
|
||||
println("\nYiemAgent generatechat() context is in response. This is not allowed", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
errornote = "Your previous response contains 'Context:' which is not allowed"
|
||||
println("\nERROR YiemAgent generatechat() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
end
|
||||
|
||||
@@ -1473,17 +1499,14 @@ function generatequestion(a, text2textInstructLLM::Function;
|
||||
2) Thanks the user when they don't need any further assistance and invite them to comeback next time
|
||||
|
||||
Your responsibility includes:
|
||||
1) Ask yourself:
|
||||
- what do you know
|
||||
- what you do not know
|
||||
- what could you do
|
||||
1) From your point of view as a sommelier helping the user, ask yourself multiple questions based on the current situation
|
||||
|
||||
Your responsibility does NOT includes:
|
||||
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. 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 info:
|
||||
Recap: recap of what has happened so far
|
||||
Additional info: ...
|
||||
Your recent events: latest 5 events of the situation
|
||||
@@ -1523,10 +1546,14 @@ function generatequestion(a, text2textInstructLLM::Function;
|
||||
...
|
||||
|
||||
Here are some examples:
|
||||
Q: The user is buying for her husband, should I dig in to get more information?
|
||||
A: Yes, I should. So that I have better idea about the user's preferences.
|
||||
Q: What the user is looking for?
|
||||
A: The user is asking for a MPV car with 7-seat
|
||||
Q: What do I know?
|
||||
A: The user is looking for a car with 7-seat. Our dealer sell these kind of cars
|
||||
Q: What I do not know?
|
||||
A: I don't know about the user budget, car's color, powertrain and other user's preferences.
|
||||
Q: The user is buying for her husband, should I dig in to get more information?
|
||||
A: Yes, I should. So that I have better idea about the user's preferences.
|
||||
Q: Why the user saying this?
|
||||
A: The user does not want an SUV because it does not have sliding doors
|
||||
Q: The user is asking for a cappuccino. Do I have it at my cafe?
|
||||
@@ -1539,18 +1566,18 @@ function generatequestion(a, text2textInstructLLM::Function;
|
||||
A: No. I need more information from the user including ...
|
||||
Q: What else do I need to know?
|
||||
A: ...
|
||||
Q: Should I check the inventory now?
|
||||
Q: Should I check our inventory now?
|
||||
A: ...
|
||||
Q: What the user intend to do with the car?
|
||||
A: I don't know yet. I will need to ask the user.
|
||||
Q: What do I have in the inventory?
|
||||
A: I don't know yet. Let's ask the user.
|
||||
Q: What do I have in our inventory?
|
||||
A: ...
|
||||
Q: Which items are within the user price range? And which items are out of the user price rance?
|
||||
A: ...
|
||||
Q: Do I have them in stock?
|
||||
Q: Do I have what the user is looking for in our stock?
|
||||
A: ...
|
||||
Q: Did I introduce them to the user already?
|
||||
A: Not yet.
|
||||
Q: Did I introduce what I found in our inventory to the user already?
|
||||
A: According to my conversation with the user, not yet.
|
||||
Q: Am I certain about the information I'm going to share with the user, or should I verify the information first?
|
||||
A: ...
|
||||
Q: What should I do?
|
||||
@@ -1575,7 +1602,7 @@ function generatequestion(a, text2textInstructLLM::Function;
|
||||
|
||||
recent_ind = GeneralUtils.recentElementsIndex(length(a.memory[:events]), recent)
|
||||
recentevents = a.memory[:events][recent_ind]
|
||||
timeline = createTimeline(recentevents)
|
||||
timeline = createTimeline(recentevents; eventindex=recent_ind)
|
||||
errornote = ""
|
||||
response = nothing # store for show when error msg show up
|
||||
|
||||
@@ -1596,9 +1623,15 @@ function generatequestion(a, text2textInstructLLM::Function;
|
||||
GeneralUtils.dictToString(tempmem)
|
||||
end
|
||||
|
||||
llmkwargs=Dict(
|
||||
:num_ctx => 32768,
|
||||
:temperature => 0.2,
|
||||
)
|
||||
|
||||
for attempt in 1:10
|
||||
if attempt > 1
|
||||
println("\nYiemAgent generatequestion() attempt $attempt/10 ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
llmkwargs[:temperature] = 0.1 * attempt
|
||||
end
|
||||
|
||||
usermsg =
|
||||
@@ -1618,7 +1651,7 @@ function generatequestion(a, text2textInstructLLM::Function;
|
||||
# put in model format
|
||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||
|
||||
response = text2textInstructLLM(prompt, modelsize="medium")
|
||||
response = text2textInstructLLM(prompt, modelsize="medium", llmkwargs=llmkwargs)
|
||||
# make sure generatequestion() don't have wine name that is not from retailer inventory
|
||||
# check whether an agent recommend wines before checking inventory or recommend wines
|
||||
# outside its inventory
|
||||
@@ -1649,24 +1682,26 @@ function generatequestion(a, text2textInstructLLM::Function;
|
||||
q_number = count("Q", response)
|
||||
|
||||
# check for valid response
|
||||
if q_number < 2
|
||||
errornote = "too few questions only $q_number questions are generated previously."
|
||||
println("too few questions only $q_number questions are generated ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
if q_number < 1
|
||||
errornote = "Your previous response has too few questions."
|
||||
println("\nERROR YiemAgent generatequestion() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
# check whether "A1" is in the response, if not error.
|
||||
elseif !occursin("A1:", response)
|
||||
errornote = "previous response does not have A1"
|
||||
println("\nprevious response does not have A1 ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
errornote = "Your previous response does not have A1:"
|
||||
println("\nERROR YiemAgent generatequestion() $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 = "\nresponse does not have all header"
|
||||
errornote = "\nYour previous attempt did not have all points according to the required response format"
|
||||
println("\nERROR YiemAgent generatequestion() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
elseif sum(values(detected_kw)) > length(header)
|
||||
errornote = "\nYiemAgent generatequestion() response has duplicated header"
|
||||
errornote = "\nYour previous attempt has duplicated points according to the required response format"
|
||||
println("\nERROR YiemAgent generatequestion() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||
continue
|
||||
end
|
||||
|
||||
|
||||
20
src/util.jl
20
src/util.jl
@@ -281,18 +281,27 @@ timeline = createTimeline(events)
|
||||
# 2) Assistant> Hi there! with a smile
|
||||
|
||||
"""
|
||||
function createTimeline(events::T1) where {T1<:AbstractVector}
|
||||
function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
|
||||
) where {T1<:AbstractVector}
|
||||
# Initialize empty timeline string
|
||||
timeline = ""
|
||||
|
||||
# Iterate through events with index
|
||||
for (i, event) in enumerate(events)
|
||||
# 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, event) in zip(ind, events)
|
||||
# If no outcome exists, format without outcome
|
||||
if event[:outcome] === nothing
|
||||
timeline *= "Event_$i) $(event[:subject])> $(event[:actioninput])\n"
|
||||
timeline *= "Event_$i $(event[:subject])> $(event[:actioninput])\n"
|
||||
# If outcome exists, include it in formatting
|
||||
else
|
||||
timeline *= "Event_$i) $(event[:subject])> $(event[:actioninput]) $(event[:outcome])\n"
|
||||
timeline *= "Event_$i $(event[:subject])> $(event[:actioninput]) $(event[:outcome])\n"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -302,7 +311,6 @@ end
|
||||
|
||||
|
||||
|
||||
|
||||
# """ Convert a single chat dictionary into LLM model instruct format.
|
||||
|
||||
# # Llama 3 instruct format example
|
||||
|
||||
@@ -83,7 +83,7 @@ function getEmbedding(text::T) where {T<:AbstractString}
|
||||
msgPurpose="embedding",
|
||||
senderName="yiemagent",
|
||||
senderId=sessionId,
|
||||
receiverName="text2textinstruct_small",
|
||||
receiverName="textembedding",
|
||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user