update
This commit is contained in:
293
src/interface.jl
293
src/interface.jl
@@ -214,20 +214,24 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent}
|
||||
"""
|
||||
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.
|
||||
|
||||
At each round of conversation, the user will give you the current situation:
|
||||
Context: ...
|
||||
Your earlier conversation with the user: ...
|
||||
|
||||
You must follow the following DO guidelines:
|
||||
You SHOULD follow the following guidelines:
|
||||
- If the user interrupts, prioritize the user then get back to the guidelines.
|
||||
- Check your inventory before mentioning any specific wine.
|
||||
- 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 will be served with wine
|
||||
- Search for wines that match the user preferences
|
||||
- Recommend wine to the user
|
||||
- Ask the customer if there is anything else you could help. If not, finish the conversation.
|
||||
|
||||
You MUST follow the following guidelines:
|
||||
- Do not mentioning any wine until you've check your inventory.
|
||||
|
||||
You should then respond to the user with interleaving Thought, Plan, Action and Observation:
|
||||
- thought:
|
||||
@@ -235,7 +239,7 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent}
|
||||
- plan: Based on the current situation, state a complete plan to complete the task. Be specific.
|
||||
- action_name (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) CHECKINVENTORY[query], which you can use to find info about wine in your inventory. "query" is a search term in verbal English.
|
||||
2) CHECKINVENTORY[query], which you can use to check info about wine in your inventory. "query" is a search term in verbal English.
|
||||
Good query example: black car with a stereo, 200 mile range and an electric motor.
|
||||
Good query example: How many car brand are from Asia?
|
||||
- action_input: input to the action
|
||||
@@ -926,13 +930,16 @@ julia> response = ChatAgent.conversation(newAgent, "Hi! how are you?")
|
||||
|
||||
# TODO
|
||||
- [] update docstring
|
||||
- [x] MCTS() for planning
|
||||
- [] add recap to initialState for earlier completed question
|
||||
- [WORKING] conversation loop
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function conversation(a::T, userinput::Dict) where {T<:agent}
|
||||
|
||||
# place holder
|
||||
actionname = nothing
|
||||
result = nothing
|
||||
chatresponse = nothing
|
||||
|
||||
if userinput[:text] == "newtopic"
|
||||
clearhistory(a)
|
||||
@@ -941,10 +948,45 @@ function conversation(a::T, userinput::Dict) where {T<:agent}
|
||||
# add usermsg to a.chathistory
|
||||
addNewMessage(a, "user", userinput[:text])
|
||||
|
||||
think(a)
|
||||
actionname, result = think(a)
|
||||
|
||||
# -------- use dummy memory to check generatechat() for halucination (checking inventory) -------- #
|
||||
mem = deepcopy(a.memory)
|
||||
if actionname == "CHATBOX"
|
||||
mem[:chatbox] = result
|
||||
else
|
||||
push!(mem[:shortmem], Dict(Symbol(actionname)=> result))
|
||||
end
|
||||
|
||||
# thought will be added to chat model via context
|
||||
chatresponse = generatechat(a)
|
||||
chatresponse = generatechat(mem, a.chathistory, a.text2textInstructLLM)
|
||||
|
||||
# some time LLM said to user that it (checking inventory) but it is not.
|
||||
# if chatresponse want to check inventory but think() didn't checkinventory then do it
|
||||
if occursin("(check", chatresponse) && occursin("inventory)", chatresponse) &&
|
||||
actionname != "checkinventory"
|
||||
|
||||
actionname, result = forceInventoryCheck(a)
|
||||
if actionname == "CHATBOX"
|
||||
a.memory[:chatbox] = result
|
||||
else
|
||||
push!(a.memory[:shortmem], Dict(Symbol(actionname)=> result))
|
||||
end
|
||||
|
||||
# generate chatresponse again because we have force inventory check
|
||||
chatresponse = generatechat(a.memory, a.chathistory, a.text2textInstructLLM)
|
||||
|
||||
else
|
||||
if actionname == "CHATBOX"
|
||||
a.memory[:chatbox] = result
|
||||
else
|
||||
push!(a.memory[:shortmem], Dict(Symbol(actionname)=> result))
|
||||
end
|
||||
|
||||
# since chatresponse does not halucinate i.e. no (check inventory), it does not need
|
||||
# to regenerate again and con be use directly
|
||||
end
|
||||
|
||||
addNewMessage(a, "assistant", chatresponse)
|
||||
|
||||
return chatresponse
|
||||
@@ -952,78 +994,6 @@ function conversation(a::T, userinput::Dict) where {T<:agent}
|
||||
|
||||
|
||||
end
|
||||
# function conversation(a::T, userinput::Dict) where {T<:agent}
|
||||
# config = deepcopy(a.config)
|
||||
# pprint(config)
|
||||
# if userinput[:text] == "newtopic"
|
||||
# clearhistory(a)
|
||||
# return "Okay. What shall we talk about?"
|
||||
# else
|
||||
# # add usermsg to a.chathistory
|
||||
# addNewMessage(a, "user", userinput[:text])
|
||||
|
||||
# if isempty(a.plan[:currenttrajectory])
|
||||
|
||||
# # initial state
|
||||
# a.plan[:currenttrajectory] = Dict{Symbol, Any}(
|
||||
# # deepcopy the info to prevent modifying the info unintentionally during MCTS planning
|
||||
# :customerinfo=> deepcopy(a.keywordinfo[:customerinfo]),
|
||||
# :storeinfo=> deepcopy(a.keywordinfo[:storeinfo]),
|
||||
# :userselect=> nothing,
|
||||
# :reward=> 0,
|
||||
# :isterminal=> false,
|
||||
# :evaluation=> nothing,
|
||||
# :lesson=> nothing,
|
||||
|
||||
# :totalTrajectoryReward=> nothing,
|
||||
|
||||
# # contain question, thought_1, action_1, observation_1, thought_2, ...
|
||||
# :thoughtHistory=> OrderedDict{Symbol, Any}(
|
||||
# #[] :recap=>,
|
||||
# :question=> userinput[:text],
|
||||
# ),
|
||||
|
||||
# # store conversation for virtual customer because the virtual customer agent is just
|
||||
# # a function and stateless.
|
||||
# :virtualCustomerChatHistory=> Vector{Dict{Symbol, Any}}(
|
||||
# [Dict(:name=> "user", :text=> userinput[:text])]
|
||||
# ),
|
||||
# )
|
||||
# else
|
||||
# _, a.plan[:currenttrajectory] = makeNewState(a.plan[:currenttrajectory],
|
||||
# a.plan[:activeplan][:thoughtHistory], userinput[:text], userinput[:select],
|
||||
# userinput[:reward], userinput[:isterminal])
|
||||
# end
|
||||
# end
|
||||
|
||||
# while true
|
||||
# bestNextState, besttrajectory = LLMMCTS.runMCTS(a.plan[:currenttrajectory],
|
||||
# transition, config, decisionMaker, evaluator, reflector;
|
||||
# totalsample=2, maxDepth=3, maxiterations=3, explorationweight=1.0)
|
||||
# a.plan[:activeplan] = bestNextState
|
||||
|
||||
# latestActionKey, latestActionIndice =
|
||||
# GeneralUtils.findHighestIndexKey(bestNextState[:thoughtHistory], "action")
|
||||
# actionname = bestNextState[:thoughtHistory][latestActionKey][:name]
|
||||
# actioninput = bestNextState[:thoughtHistory][latestActionKey][:input]
|
||||
|
||||
# # transition
|
||||
# if actionname == "chatbox"
|
||||
# # add usermsg to a.chathistory
|
||||
# addNewMessage(a, "assistant", actioninput)
|
||||
# return actioninput
|
||||
# elseif actionname == "recommendbox"
|
||||
# # add usermsg to a.chathistory
|
||||
# addNewMessage(a, "assistant", actioninput)
|
||||
# return actioninput
|
||||
# else
|
||||
# _, a.plan[:currenttrajectory] = transition(a, a.plan[:currenttrajectory], a.plan[:activeplan])
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
@@ -1043,7 +1013,7 @@ julia>
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function think(a::T) where {T<:agent}
|
||||
function think(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}} where {T<:agent}
|
||||
thoughtDict = decisionMaker(a)
|
||||
actionname = thoughtDict[:action_name]
|
||||
actioninput = thoughtDict[:action_input]
|
||||
@@ -1066,11 +1036,133 @@ function think(a::T) where {T<:agent}
|
||||
errormsg::Union{AbstractString, Nothing} = haskey(response, :errormsg) ? response[:errormsg] : nothing
|
||||
success::Bool = haskey(response, :success) ? response[:success] : false
|
||||
|
||||
if actionname == "CHATBOX"
|
||||
a.memory[:chatbox] = result
|
||||
else
|
||||
push!(a.memory[:shortmem], Dict(Symbol(actionname)=> result))
|
||||
return (actionname=actionname, result=result)
|
||||
end
|
||||
|
||||
|
||||
""" Force to think and check inventory
|
||||
[WORKING]
|
||||
"""
|
||||
function forceInventoryCheck(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}} where {T<:agent}
|
||||
|
||||
thoughtDict = thinkCheckInventory(a)
|
||||
actionname = thoughtDict[:action_name]
|
||||
actioninput = thoughtDict[:action_input]
|
||||
|
||||
# map action and input() to llm function
|
||||
response =
|
||||
if actionname == "CHECKINVENTORY"
|
||||
checkinventory(a, actioninput)
|
||||
else
|
||||
error("undefined LLM function. Requesting $actionname")
|
||||
end
|
||||
|
||||
# this section allow LLM functions above to have different return values.
|
||||
result = haskey(response, :result) ? response[:result] : nothing
|
||||
select = haskey(response, :select) ? response[:select] : nothing
|
||||
reward::Integer = haskey(response, :reward) ? response[:reward] : 0
|
||||
isterminal::Bool = haskey(response, :isterminal) ? response[:isterminal] : false
|
||||
errormsg::Union{AbstractString, Nothing} = haskey(response, :errormsg) ? response[:errormsg] : nothing
|
||||
success::Bool = haskey(response, :success) ? response[:success] : false
|
||||
|
||||
return (actionname=actionname, result=result)
|
||||
end
|
||||
|
||||
"""
|
||||
[WORKING]
|
||||
"""
|
||||
function thinkCheckInventory(a::T)::Dict{Symbol, Any} where {T<:agent}
|
||||
|
||||
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.
|
||||
|
||||
Definitions:
|
||||
- observation: result of the preceding immediate action.
|
||||
|
||||
At each round of conversation, the user will give you the current situation:
|
||||
Context: ...
|
||||
Your earlier conversation with the user: ...
|
||||
|
||||
You must follow the following guidelines:
|
||||
- Check inventory immediately based on what you know about 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, state a complete plan to complete the task. Be specific.
|
||||
- action_name (Must be aligned with your plan): Can be one of the following functions:
|
||||
1) CHECKINVENTORY[query], which you can use to check info about wine in your inventory. "query" is a search term in verbal English.
|
||||
Good query example: black car with a stereo, 200 mile range and an electric motor.
|
||||
Good query example: How many car brand are from Asia?
|
||||
- action_input: input to the action
|
||||
|
||||
You should only respond in format as described below:
|
||||
thought: ...
|
||||
plan: ...
|
||||
action_name: ...
|
||||
action_input: ...
|
||||
|
||||
Let's begin!
|
||||
"""
|
||||
|
||||
usermsg =
|
||||
"""
|
||||
Context: None
|
||||
Your earlier conversation with the user: $(vectorOfDictToText(a.chathistory))
|
||||
"""
|
||||
|
||||
_prompt =
|
||||
[
|
||||
Dict(:name=> "system", :text=> systemmsg),
|
||||
Dict(:name=> "user", :text=> usermsg)
|
||||
]
|
||||
|
||||
# put in model format
|
||||
prompt = GeneralUtils.formatLLMtext(_prompt, "llama3instruct")
|
||||
prompt *=
|
||||
"""
|
||||
<|start_header_id|>assistant<|end_header_id|>
|
||||
"""
|
||||
response = nothing # store for show when error msg show up
|
||||
for attempt in 1:10
|
||||
try
|
||||
response = a.text2textInstructLLM(prompt)
|
||||
responsedict = GeneralUtils.textToDict(response,
|
||||
["thought", "plan", "action_name", "action_input"],
|
||||
rightmarker=":", symbolkey=true)
|
||||
|
||||
if responsedict[:action_name] ∉ ["CHECKINVENTORY"]
|
||||
error("decisionMaker didn't use the given functions ", @__LINE__)
|
||||
end
|
||||
|
||||
for i ∈ [:thought, :plan, :action_name]
|
||||
if length(JSON3.write(responsedict[i])) == 0
|
||||
error("$i is empty ", @__LINE__)
|
||||
end
|
||||
end
|
||||
|
||||
# check if there are more than 1 key per categories
|
||||
for i ∈ [:thought, :plan, :action_name, :action_input]
|
||||
matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
|
||||
if length(matchkeys) > 1
|
||||
error("DecisionMaker has more than one key per categories")
|
||||
end
|
||||
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("")
|
||||
println("Attempt $attempt. Error occurred: $errorMsg\n$st")
|
||||
println("")
|
||||
end
|
||||
end
|
||||
error("DecisionMaker failed to generate a thought ", response)
|
||||
end
|
||||
|
||||
|
||||
@@ -1090,14 +1182,14 @@ julia>
|
||||
|
||||
# TODO
|
||||
- [] update docs
|
||||
- [WORKING] implement the function
|
||||
- [x] implement the function
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function generatechat(a::T) where {T<:agent}
|
||||
function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::Function)
|
||||
systemmsg =
|
||||
"""
|
||||
You are a polite sommelier working for a wine store.
|
||||
You are an website-based polite sommelier working for an online wine store.
|
||||
Your task is to help the user choose the best wine that match the user preferences from your inventory.
|
||||
|
||||
At each round of conversation, the user will give you the current situation:
|
||||
@@ -1105,9 +1197,11 @@ function generatechat(a::T) where {T<:agent}
|
||||
Your thoughts: Your current thinking in your mind
|
||||
Your earlier conversation with the user: ...
|
||||
|
||||
You must follow the following DON'T guidelines:
|
||||
- Don't mention any specific wine until you've checked your inventory.
|
||||
You must follow the following guidelines:
|
||||
- Do not mentioning any wine until you've check your inventory.
|
||||
- Check your inventory before recommending any specific wine.
|
||||
- Your thoughts matter.
|
||||
- Do not offer the user to try wine as you are internet-based agent.
|
||||
|
||||
You should then respond to the user with:
|
||||
- chat: what do you want to say to the user based on the current situation
|
||||
@@ -1118,15 +1212,17 @@ function generatechat(a::T) where {T<:agent}
|
||||
Let's begin!
|
||||
"""
|
||||
|
||||
context = length(a.memory[:shortmem]) > 0 ? vectorOfDictToText(a.memory[:shortmem], withkey=false) : "None"
|
||||
context = length(memory[:shortmem]) > 0 ? vectorOfDictToText(memory[:shortmem], withkey=false) : "None"
|
||||
|
||||
usermsg =
|
||||
"""
|
||||
Context: $context
|
||||
Your earlier conversation with the user: $(vectorOfDictToText(a.chathistory))
|
||||
Your thoughts: $(a.memory[:chatbox])
|
||||
Your earlier conversation with the user: $(vectorOfDictToText(chathistory))
|
||||
Your thoughts: $(memory[:chatbox])
|
||||
"""
|
||||
|
||||
println("")
|
||||
println("--> think ", @__FILE__, " ", @__LINE__)
|
||||
println(memory[:chatbox])
|
||||
_prompt =
|
||||
[
|
||||
Dict(:name=> "system", :text=> systemmsg),
|
||||
@@ -1142,7 +1238,7 @@ function generatechat(a::T) where {T<:agent}
|
||||
|
||||
for attempt in 1:5
|
||||
try
|
||||
response = a.text2textInstructLLM(prompt)
|
||||
response = text2textInstructLLM(prompt)
|
||||
responsedict = GeneralUtils.textToDict(response,
|
||||
["chat"],
|
||||
rightmarker=":", symbolkey=true)
|
||||
@@ -1175,13 +1271,6 @@ function generatechat(a::T) where {T<:agent}
|
||||
end
|
||||
end
|
||||
error("generatechat failed to generate an evaluation")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user