This commit is contained in:
narawat lamaiin
2024-07-16 15:32:26 +07:00
parent fdc50d1b90
commit 37a9a38796
4 changed files with 132 additions and 64 deletions

View File

@@ -174,23 +174,59 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent}
# {"thought" # {"thought"
# """ # """
# systemmsg =
# """
# You are a helpful sommelier working for a wine store.
# Your task is to help the user choose the best wine that match the user preferences from your inventory.
# You are also eager to improve your helpfulness.
# You must follow the following guidelines:
# - Get to know how much the user willing to spend
# - Get to know type of wine the user is looking for e.g. red, white, sparkling, rose, dessert, fortified
# - Get to know what occasion the user is buying wine for
# - Get to know what characteristics of wine the user is looking for e.g. tannin, sweetness, intensity, acidity
# - Get to know what food the user will have with wine
# At each round of conversation, the user will give you the current situation:
# Context: ...
# Your earlier conversation with the user: ...
# You should then respond to the user with interleaving Thought, Plan, Action and Observation:
# - thought:
# 1) State your reasoning about the current situation.
# - plan: Based on the current situation, what would you do to complete the task? Be specific.
# - action (Must be aligned with your plan): Can be one of the following functions:
# 1) CHATBOX[text], which you can use to talk with the user. "text" is in verbal English.
# 2) WINESTOCK[query], which you can use to find info about wine in your inventory. "query" is a search term in verbal English.
# - observation: result of the action.
# You should only respond in format as described below:
# thought: ...
# plan: ...
# action_name: ...
# action_input: ...
# observation: ...
# Let's begin!
# """
systemmsg = systemmsg =
""" """
You are a helpful sommelier working for a wine store. You are a helpful sommelier working for a wine store.
Your task is to help the user choose the best wine that match the user preferences from your inventory. Your task is to help the user choose the best wine that match the user preferences from your inventory.
You are also eager to improve your helpfulness. You are also eager to improve your helpfulness.
You must follow the following guidelines: At each round of conversation, the user will give you the current situation:
Context: ...
Your earlier conversation with the user: ...
You should follow the following guidelines:
- Get to know how much the user willing to spend - Get to know how much the user willing to spend
- Get to know type of wine the user is looking for e.g. red, white, sparkling, rose, dessert, fortified - Get to know type of wine the user is looking for e.g. red, white, sparkling, rose, dessert, fortified
- Get to know what occasion the user is buying wine for - Get to know what occasion the user is buying wine for
- Get to know what characteristics of wine the user is looking for e.g. tannin, sweetness, intensity, acidity - Get to know what characteristics of wine the user is looking for e.g. tannin, sweetness, intensity, acidity
- Get to know what food the user will have with wine - Get to know what food the user will have with wine
At each round of conversation, the user will give you the current situation:
Context: ...
Your earlier conversation with the user: ...
You should then respond to the user with interleaving Thought, Plan, Action and Observation: You should then respond to the user with interleaving Thought, Plan, Action and Observation:
- thought: - thought:
1) State your reasoning about the current situation. 1) State your reasoning about the current situation.
@@ -213,7 +249,7 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent}
usermsg = usermsg =
""" """
Context: None Context: None
Your earlier conversation with the user: $(chatHistoryToString(a)) Your earlier conversation with the user: $(vectorOfDictToText(a.chathistory))
""" """
_prompt = _prompt =
@@ -902,10 +938,11 @@ function conversation(a::T, userinput::Dict) where {T<:agent}
# add usermsg to a.chathistory # add usermsg to a.chathistory
addNewMessage(a, "user", userinput[:text]) addNewMessage(a, "user", userinput[:text])
thought = think(a) think(a)
# thought will be added to chat model via context # thought will be added to chat model via context
chatresponse = generatechat(a, thought) chatresponse = generatechat(a)
addNewMessage(a, "assistant", chatresponse)
return chatresponse return chatresponse
end end
@@ -1024,8 +1061,12 @@ function think(a::T) where {T<:agent}
isterminal::Bool = haskey(response, :isterminal) ? response[:isterminal] : false isterminal::Bool = haskey(response, :isterminal) ? response[:isterminal] : false
errormsg::Union{AbstractString, Nothing} = haskey(response, :errormsg) ? response[:errormsg] : nothing errormsg::Union{AbstractString, Nothing} = haskey(response, :errormsg) ? response[:errormsg] : nothing
success::Bool = haskey(response, :success) ? response[:success] : false success::Bool = haskey(response, :success) ? response[:success] : false
a.shortmem
return result if actionname == "CHATBOX"
a.memory[:chatbox] = result
else
push!(a.memory[:shortmem], Dict(Symbol(actionname)=> result))
end
end end
@@ -1049,27 +1090,22 @@ julia>
# Signature # Signature
""" """
function generatechat(a::T1, input::T2) where {T1<:agent, T2<:AbstractString} function generatechat(a::T) where {T<:agent}
systemmsg = systemmsg =
""" """
You are a helpful sommelier working for a wine store. You are a helpful sommelier working for a wine store.
Your task is to help the user choose the best wine that match the user preferences from your inventory. Your task is to help the user choose the best wine that match the user preferences from your inventory.
You are also eager to improve your helpfulness.
You must follow the following guidelines: At each round of conversation, the user will give you the current situation:
- Get to know how much the user willing to spend
- Get to know type of wine the user is looking for e.g. red, white, sparkling, rose, dessert, fortified
- Get to know what occasion the user is buying wine for
- Get to know what characteristics of wine the user is looking for e.g. tannin, sweetness, intensity, acidity
- Get to know what food the user will have with wine
At each round of conversation, the user will give you:
Context: ... Context: ...
Your thoughts: Your current thinking in your mind Your thoughts: Your current thinking in your mind
Your earlier conversation with the user: ... Your earlier conversation with the user: ...
You must follow the following guidelines (if the user interrupts, prioritize the user):
- Do not recommend specific wine before you checked your inventory.
You should then respond to the user with: You should then respond to the user with:
- chat: what do you want to say to the user - chat: what do you want to say to the user based on the current situation
You should only respond in format as described below: You should only respond in format as described below:
chat: ... chat: ...
@@ -1077,11 +1113,13 @@ function generatechat(a::T1, input::T2) where {T1<:agent, T2<:AbstractString}
Let's begin! Let's begin!
""" """
context = length(a.memory[:shortmem]) > 0 ? vectorOfDictToText(a.memory[:shortmem], withkey=false) : "None"
usermsg = usermsg =
""" """
Context: None Context: $context
Your thoughts: $input Your earlier conversation with the user: $(vectorOfDictToText(a.chathistory))
Your earlier conversation with the user: $(chatHistoryToString(a)) Your thoughts: $(a.memory[:chatbox])
""" """
_prompt = _prompt =
@@ -1099,31 +1137,31 @@ function generatechat(a::T1, input::T2) where {T1<:agent, T2<:AbstractString}
for attempt in 1:5 for attempt in 1:5
try try
response = text2textInstructLLM(prompt) response = a.text2textInstructLLM(prompt)
responsedict = GeneralUtils.textToDict(response, responsedict = GeneralUtils.textToDict(response,
["chat"], ["chat"],
rightmarker=":", symbolkey=true) rightmarker=":", symbolkey=true)
# check if dict has all required value for i [:chat]
evaluationtext::AbstractString = responsedict[:evaluation] if length(JSON3.write(responsedict[i])) == 0
responsedict[:score] = parse(Int, responsedict[:score]) # convert string "5" into integer 5 error("$i is empty ", @__LINE__)
score::Integer = responsedict[:score] end
accepted_as_answer::AbstractString = responsedict[:accepted_as_answer]
suggestion::AbstractString = responsedict[:suggestion]
# add to state here instead to in transition() because the latter causes julia extension crash (a bug in julia extension)
state[:evaluation] = responsedict[:evaluation]
state[:evaluationscore] = responsedict[:score]
state[:accepted_as_answer] = responsedict[:accepted_as_answer]
state[:suggestion] = responsedict[:suggestion]
# mark as terminal state when the answer is achieved
if accepted_as_answer == "Yes"
state[:isterminal] = true
state[:reward] = 1
end end
return responsedict[:score] # check if there are more than 1 key per categories
for i [:chat]
matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
if length(matchkeys) > 1
error("generatechat has more than one key per categories")
end
end
result = responsedict[:chat]
#[WORKING] delete chat
a.memory[:chatbox] = ""
return result
catch e catch e
io = IOBuffer() io = IOBuffer()
showerror(io, e) showerror(io, e)
@@ -1134,7 +1172,7 @@ function generatechat(a::T1, input::T2) where {T1<:agent, T2<:AbstractString}
println("") println("")
end end
end end
error("evaluator failed to generate an evaluation") error("generatechat failed to generate an evaluation")

View File

@@ -93,7 +93,7 @@ mutable struct sommelier <: agent
""" """
chathistory::Vector{Dict{Symbol, Any}} chathistory::Vector{Dict{Symbol, Any}}
shortmem::Dict{Symbol, Any} memory::Dict{Symbol, Any}
# communication function # communication function
text2textInstructLLM::Function text2textInstructLLM::Function
@@ -105,7 +105,7 @@ function sommelier(
name::String= "Assistant", name::String= "Assistant",
id::String= string(uuid4()), id::String= string(uuid4()),
maxHistoryMsg::Integer= 20, maxHistoryMsg::Integer= 20,
chathistory::Vector{Dict{Symbol, Any}} = Vector{Dict{Symbol, Any}}(), chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
) )
tools = Dict( # update input format tools = Dict( # update input format
@@ -127,13 +127,18 @@ function sommelier(
# ), # ),
) )
memory = Dict{Symbol, Any}(
:chatbox=> "",
:shortmem=> Vector{Dict{Symbol, String}}()
)
newAgent = sommelier( newAgent = sommelier(
name, name,
id, id,
tools, tools,
maxHistoryMsg, maxHistoryMsg,
chathistory, chathistory,
Dict{Symbol, Any}(), memory,
text2textInstructLLM, text2textInstructLLM,
) )

View File

@@ -1,6 +1,6 @@
module util module util
export clearhistory, addNewMessage, chatHistoryToString export clearhistory, addNewMessage, vectorOfDictToText
using UUIDs, Dates, DataStructures, HTTP, MQTTClient, JSON3 using UUIDs, Dates, DataStructures, HTTP, MQTTClient, JSON3
using GeneralUtils using GeneralUtils
@@ -113,15 +113,43 @@ function addNewMessage(a::T1, name::String, text::T2;
end end
function chatHistoryToString(a) """
chatHistoryStr = ""
for i in a.chathistory # Arguments
name = i[:name] - `v::Integer`
text = i[:text] dummy variable
chatHistoryStr *= "$name> $text"
# Return
# Example
```jldoctest
julia>
```
# TODO
- [] update docstring
- [x] implement the function
# Signature
"""
function vectorOfDictToText(vecd::Vector; withkey=true)
text = ""
if withkey
for d in vecd
name = d[:name]
_text = d[:text]
text *= "$name> $_text \n"
end
else
for d in vecd
for (k, v) in d
text *= "$v \n"
end
end
end end
return chatHistoryStr return text
end end
@@ -131,9 +159,6 @@ end
# """ Convert a single chat dictionary into LLM model instruct format. # """ Convert a single chat dictionary into LLM model instruct format.
# # Llama 3 instruct format example # # Llama 3 instruct format example

View File

@@ -67,11 +67,11 @@ end
response = YiemAgent.conversation(a, Dict(:text=> "Hello, I would like a get a bottle of wine.")) response = YiemAgent.conversation(a, Dict(:text=> "Hello, I would like a get a bottle of wine."))
println("---> YiemAgent: ", response) println("---> YiemAgent: ", response)
response = YiemAgent.conversation(a, Dict(:text=> "I'm having a graduation party this evening. I'll pay at most 30 bucks.", response = YiemAgent.conversation(a, Dict(:text=> "I'm having a graduation party this evening. I'll pay at most 30 bucks."))
:select=> nothing, response = YiemAgent.conversation(a, Dict(:text=> "What type of wine do you recommend for a celebration?"))
:reward=> 0,
:isterminal=> false,
) )
println("---> YiemAgent: ", response) println("---> YiemAgent: ", response)