This commit is contained in:
narawat lamaiin
2024-08-27 13:22:53 +07:00
parent a80b8e0260
commit ca0f9cd53f
5 changed files with 70 additions and 141 deletions

View File

@@ -1,6 +1,6 @@
module interface module interface
export addNewMessage, conversation, decisionMaker, evaluator, reflector export addNewMessage, conversation, decisionMaker, evaluator, reflector, generatechat
# isterminal, # isterminal,
using JSON3, DataStructures, Dates, UUIDs, HTTP, Random, MQTTClient, PrettyPrinting, Serialization using JSON3, DataStructures, Dates, UUIDs, HTTP, Random, MQTTClient, PrettyPrinting, Serialization
@@ -234,7 +234,8 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age
""" """
Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for Yiem's online wine store. Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for Yiem's online wine store.
Your goal includes: Your goal includes:
1) Help the user select the best wines from your inventory that align with the user's preferences. 1) Get to know the user preferences about wine
2) Help them select the best wines from your inventory that align with their preferences
Your responsibility includes: Your responsibility includes:
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
@@ -251,18 +252,12 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age
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 which wines your store carries 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 which wines your store carries until you check your inventory.
- All wines in your inventory are always in stock. - All wines in your inventory are always in stock.
- Use the "understand-then-check" inventory strategy to understand the user, as there are many wines in the inventory. - If the user interrupts, prioritize the user
- 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 you present the wines to the user, 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.
You should follow the following guidelines as you see fit: You should follow the following guidelines:
- If the user interrupts, prioritize the user. - Identifying at least four preferences before checking inventory significantly improves search results
- If you don't already know, find out the user's budget.
- If you don't already know, find out the type of wine the user is looking for, such as red, white, sparkling, rose, dessert, fortified.
- If you don't already know, find out the occasion for which the user is buying wine.
- If you don't already know, find out the characteristics of wine the user is looking for, such as tannin, sweetness, intensity, acidity.
- If you don't already know, find out what food will be served with wine.
- If you haven't already, introduce the wines you found in the database to the user first.
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:
@@ -271,13 +266,13 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age
- State your step by step reasoning about the current situation. - State your step by step reasoning about the current situation.
2) Plan: Based on the current situation, state a complete plan to complete the task. Be specific. 2) Plan: Based on the current situation, state a complete plan to complete the task. Be specific.
3) Action_name (Must be aligned with your plan): The name of the action which can be one of the following functions: 3) Action_name (Must be aligned with your plan): The name of the action which can be one of the following functions:
- CHATBOX which you can use to generate conversation in order to communicate 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 in your inventory. The input is a search term in verbal English. - CHECKINVENTORY which you can use to check info about wine in your inventory. The input is a search term in verbal English.
Good query example: black car, a stereo, 200 mile range, electric motor. Good query example: black car, a stereo, 200 mile range, electric motor.
- PRESENTBOX which you can use to introduce / suggest / recommend wines you just found in the database to the user. It is better than the CHATBOX function for presenting wines. - PRESENTBOX which you can use to introduce / suggest / recommend wines you just found in the database to the user.
The input is instructions on how you want the presentation to be conducted. The input is instructions on how you want the presentation to be conducted.
Here are some input example, Here are some input examples,
"First, provide detailed introductions of the wines. "First, provide detailed introductions of Zena Crown, Schrader Cabernet Sauvignon.
Second, if there are multiple wines, offer a thorough comparison of each option, highlighting their differences. Second, if there are multiple wines, offer a thorough comparison of each option, highlighting their differences.
Third, explain the potential impact each option could bring to the user." Third, explain the potential impact each option could bring to the user."
- ENDCONVERSATION which you can use when you want to finish the conversation with the user. The input is "NA". - ENDCONVERSATION which you can use when you want to finish the conversation with the user. The input is "NA".
@@ -875,7 +870,7 @@ julia> response = ChatAgent.conversation(newAgent, "Hi! how are you?")
# Signature # Signature
""" """
function conversation(a::T, userinput::Dict) where {T<:agent} function conversation(a::T, userinput::Dict) where {T<:agent}
println("--> conver 1 ", @__FILE__, " ", @__LINE__)
# place holder # place holder
actionname = nothing actionname = nothing
result = nothing result = nothing
@@ -887,7 +882,7 @@ function conversation(a::T, userinput::Dict) where {T<:agent}
else else
# add usermsg to a.chathistory # add usermsg to a.chathistory
addNewMessage(a, "user", userinput[:text]) addNewMessage(a, "user", userinput[:text])
println("--> conver 2 ", @__FILE__, " ", @__LINE__)
# add user activity to events memory # add user activity to events memory
push!(a.memory[:events], push!(a.memory[:events],
eventdict(; eventdict(;
@@ -897,7 +892,7 @@ function conversation(a::T, userinput::Dict) where {T<:agent}
action_or_dialogue= userinput[:text], action_or_dialogue= userinput[:text],
) )
) )
println("--> conver 3 ", @__FILE__, " ", @__LINE__)
# use dummy memory to check generatechat() for halucination (checking inventory) # use dummy memory to check generatechat() for halucination (checking inventory)
for i in 1:3 for i in 1:3
actionname, result = think(a) actionname, result = think(a)
@@ -905,7 +900,7 @@ function conversation(a::T, userinput::Dict) where {T<:agent}
break break
end end
end end
println("--> conver 4 ", @__FILE__, " ", @__LINE__)
# thought will be added to chat model via context # thought will be added to chat model via context
chatresponse = generatechat(a.memory, a.chathistory, a.text2textInstructLLM) chatresponse = generatechat(a.memory, a.chathistory, a.text2textInstructLLM)
@@ -952,7 +947,8 @@ function think(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}}
# map action and input() to llm function # map action and input() to llm function
response = response =
if actionname == "CHATBOX" if actionname == "CHATBOX"
(result=actioninput, errormsg=nothing, success=true) input = "My plan: $(thoughtDict[:plan])"
(result=input, errormsg=nothing, success=true)
elseif actionname == "CHECKINVENTORY" elseif actionname == "CHECKINVENTORY"
checkinventory(a, actioninput) checkinventory(a, actioninput)
elseif actionname == "PRESENTBOX" elseif actionname == "PRESENTBOX"
@@ -1023,14 +1019,16 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F
systemmsg = systemmsg =
""" """
Your name is "Jenie". You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. Your name is "Jenie". You are a helpful assistant acting as a polite, website-based sommelier for an online wine store.
Your goal is: Recommend the best wines from your inventory that align with the user's preferences. You are currently talking with the user.
Your goal includes:
1) Help the user select the best wines from your inventory that align with the user's preferences.
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 does not include: Your responsibility do not include:
1) Processing sales orders or engaging in any other sales-related activities. 1) Asking or guiding the user to make a purchase
2) Ordering the wines. 2) Processing sales orders or engaging in any other sales-related activities
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: ...
@@ -1042,6 +1040,7 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F
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
You should then respond to the user with: You should then respond to the user with:
1) Mentioning_wine: Are you going to mentioning specific wine name to the user? Can be "Yes" or "No" 1) Mentioning_wine: Are you going to mentioning specific wine name to the user? Can be "Yes" or "No"
@@ -1216,16 +1215,20 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St
- 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.
You should follow the following guidelines: You should follow the following guidelines:
- Focus on the latest conversation. - Focus on the latest conversation
- 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. - If the user interrupts, prioritize the user
- All wines in your inventory are always in stock. - If you don't already know, find out the user's budget
- If you don't already know, find out the type of wine the user is looking for, such as red, white, sparkling, rose, dessert, fortified
- If you don't already know, find out the occasion for which the user is buying wine
- If you don't already know, find out the characteristics of wine the user is looking for, such as tannin, sweetness, intensity, acidity
- If you don't already know, find out what food will be served with wine
- If you haven't already, introduce the wines you found in the database to the user first
You should then respond to the user with: You should then respond to the user with:
1) Understanding: 1) Understanding:
- 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
4) Note: Additional info you want to say
You must only respond in format as described below: You must only respond in format as described below:
Understanding: ... Understanding: ...
@@ -1236,7 +1239,6 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St
Q3: ... Q3: ...
A3: ... A3: ...
... ...
Note: ...
Here are some examples: Here are some examples:
Q: The user is asking for a cappuccino. Do I have it at my cafe? Q: The user is asking for a cappuccino. Do I have it at my cafe?
@@ -1309,7 +1311,7 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St
end end
# response = string(split(response, "Please")[1]) # LLM usually add comments which is no need. # response = string(split(response, "Please")[1]) # LLM usually add comments which is no need.
responsedict = GeneralUtils.textToDict(response, responsedict = GeneralUtils.textToDict(response,
["Understanding", "Q1", "Note"], ["Understanding", "Q1"],
rightmarker=":", symbolkey=true, lowercasekey=true) rightmarker=":", symbolkey=true, lowercasekey=true)
response = "Q1: " * responsedict[:q1] response = "Q1: " * responsedict[:q1]
println("--> generatequestion ", @__FILE__, " ", @__LINE__) println("--> generatequestion ", @__FILE__, " ", @__LINE__)

View File

@@ -406,6 +406,16 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
try try
response = a.text2textInstructLLM(prompt) response = a.text2textInstructLLM(prompt)
# check wheter all attributes are in the response
for word in attributes
if !occursin(word, response)
noise = String(rand('a':'z', 5))
errornote = "Background noise $noise"
error("$word attribute is missing")
end
end
responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true) responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true)
for i attributes for i attributes

View File

@@ -50,6 +50,9 @@ julia> YiemAgent.clearhistory(a)
""" """
function clearhistory(a::T) where {T<:agent} function clearhistory(a::T) where {T<:agent}
empty!(a.chathistory) empty!(a.chathistory)
empty!(a.memory[:shortmem])
empty!(a.memory[:events])
a.memory[:chatbox] = ""
end end

View File

@@ -195,7 +195,7 @@ end
main() main()
""" """
I'm joining a graduation party this evening. I want a bottle of dry white wine from the US. I'm ok with any price range. I'm joining a graduation party this evening. I want to get a bottle of white wine from the US. I'm ok with any price range.
Well, the party is small casual with close friends and no food serving. Well, the party is small casual with close friends and no food serving.
I'm open to suggestion since I have no specific idea. I'm open to suggestion since I have no specific idea.
I'm ok with any region. I'm ok with any region.
@@ -207,6 +207,12 @@ main()
""" """
wines =
"""
Summary: This table contains two wine records, both from the United States, with white wine types, moderate sweetness (2), and high intensity (5).
More details: 1) wine_id: add9824f-81b0-47da-a08a-ee20498bc6c8, wine_name: Belle Cote Chardonnay, brand: Peter Michael, manufacturer: Peter Michael, region: Californian, country: United States, wine_type: white, grape_variety: Chardonnay, serving_temperature: 11 to 13 Celsius, intensity: 5, sweetness: 2, tannin: missing, acidity: 3, fizziness: missing, tasting_notes: oak, butter, vanilla, cream, oil, lemon curd, pear, peach, apple
2) wine_id: ff9a494c-e916-44c4-9385-1c18b23aa825, wine_name: Ma Belle-Fille Chardonnay, brand: Peter Michael, manufacturer: Peter Michael, region: Californian, country: United States, wine_type: white, grape_variety: Chardonnay, serving_temperature: 11 to 13 Celsius, intensity: 5, sweetness: 2, tannin: missing, acidity: 3, fizziness: missing, tasting_notes: oak, butter, vanilla, cream, banana, cheese, apricot, peach, apple
"""
@@ -275,4 +281,8 @@ result = run_with_timeout(example_function, 3, 4; timeout=5)
println("Result: ", result) println("Result: ", result)
a = `$"hello\nworld"`
a = $"hello\nworld"
a = """$("hello\nworld")"""

View File

@@ -1,116 +1,20 @@
using Revise # remove when this package is completed import requests
using YiemAgent, GeneralUtils, JSON3, MQTTClient, Dates, UUIDs, DataStructures
using Base.Threads
# ---------------------------------------------- 100 --------------------------------------------- # # URL of the API endpoint
url = 'https://api.yiem.cc/wine/agent/sommelier/prompt/apiv1'
config = copy(JSON3.read("config.json")) # Data to be sent in the JSON request
data = {
instanceInternalTopic = config[:serviceInternalTopic][:mqtttopic] * "/1" 'sid': 'dummySID',
'txt': 'hello'
client, connection = MakeConnection(config[:mqttServerInfo][:broker], }
config[:mqttServerInfo][:port])
receiveUserMsgChannel = Channel{Dict}(4)
receiveInternalMsgChannel = Channel{Dict}(4)
msgMeta = GeneralUtils.generate_msgMeta(
"N/A",
replyTopic = config[:servicetopic][:mqtttopic] # ask frontend reply to this instance_chat_topic
)
agentConfig = Dict(
:mqttServerInfo=> config[:mqttServerInfo],
:receivemsg=> Dict(
:prompt=> config[:servicetopic][:mqtttopic], # topic to receive prompt i.e. frontend send msg to this topic
:internal=> instanceInternalTopic,
),
:externalservice=> config[:externalservice],
)
# Instantiate an agent
tools=Dict( # update input format
"askbox"=> Dict(
:description => "<askbox tool description>Useful for when you need to ask the user for more context. Do not ask the user their own question.</askbox tool description>",
:input => """<input>Input is a text in JSON format.</input><input example>{\"Q1\": \"How are you doing?\", \"Q2\": \"How may I help you?\"}</input example>""",
:output => "" ,
:func => nothing,
),
# "winestock"=> Dict(
# :description => "<winestock tool description>A handy tool for searching wine in your inventory that match the user preferences.</winestock tool description>",
# :input => """<input>Input is a JSON-formatted string that contains a detailed and precise search query.</input><input example>{\"wine type\": \"rose\", \"price\": \"max 35\", \"sweetness level\": \"sweet\", \"intensity level\": \"light bodied\", \"Tannin level\": \"low\", \"Acidity level\": \"low\"}</input example>""",
# :output => """<output>Output are wines that match the search query in JSON format.""",
# :func => ChatAgent.winestock,
# ),
"finalanswer"=> Dict(
:description => "<tool description>Useful for when you are ready to recommend wines to the user.</tool description>",
:input => """<input format>{\"finalanswer\": \"some text\"}.</input format><input example>{\"finalanswer\": \"I recommend Zena Crown Vista\"}</input example>""",
:output => "" ,
:func => nothing,
),
)
a = YiemAgent.sommelier(
receiveUserMsgChannel,
receiveInternalMsgChannel,
agentConfig,
name= "assistant",
id= "testingSessionID", # agent instance id
tools=tools,
)
input =
OrderedDict{Symbol, Any}(
:question => "Hello, I would like a get a bottle of wine",
:thought_1 => "A customer wants to buy a bottle of wine. Before making a recommendation, I need to know more about their preferences.",
:action_1 => Dict{Symbol, Any}(
:name => "chatbox",
:input => "What is the occasion for which you're buying this wine?"
),
:observation_1 => "We are holding a wedding party",
:thought_2 => "A wedding party, that's a great occasion! The customer might be looking for a celebratory drink. Let me ask some more questions to narrow down the options.",
:action_2 => Dict{Symbol, Any}(
:name => "chatbox",
:input => "What type of food will you be serving at the wedding?"
),
:observation_2 => "It will be Thai dishes.",
:thought_3 => "With Thai food, I should recommend a wine that complements its spicy and savory flavors. And since it's a celebratory occasion, the customer might prefer a full-bodied wine.",
:action_3 => Dict{Symbol, Any}(
:name => "chatbox",
:input => "What is your budget for this bottle of wine?"
),
:observation_3 => "I would spend up to 50 bucks.",
:thought_4 => "Now that I have some more information, it's time to narrow down the options.",
:action_4 => Dict{Symbol, Any}(
:name => "winestock",
:input => "red wine with full body, pairs well with spicy food, budget \$50"),
:observation_4 => "I found the following wines in our stock: \n{\n 1: El Enemigo Cabernet Franc 2019\n2: Tantara Chardonnay 2017\n\n}\n",
:thought_5 => "Now that I have a list of potential wines, I need to know more about the customer's taste preferences.",
:action_5 => Dict{Symbol, Any}(
:name => "chatbox",
:input => "What type of wine characteristics are you looking for? (e.g. t.e.g. tannin level, sweetness, intensity, acidity)"
),
:observation_5 => "I like full-bodied Red wine with low tannin.",
:thought_6 => "Now that I have more information about the customer's preferences, it's time to make a recommendation.",
:action_6 => Dict{Symbol, Any}(
:name => "recommendbox",
:input => "El Enemigo Cabernet Franc 2019"
),
:observation_6 => "I don't like the one you recommend. I want dry wine."
)
result = YiemAgent.reflector(a, input)
# Sending the POST request
response = requests.post(url, json=data)
# Displaying the response
print('Status Code:', response.status_code)
print('Response JSON:', response.json())