This commit is contained in:
2023-11-22 02:20:00 +00:00
parent 1281c5b3df
commit 4e499e6c8e

View File

@@ -60,8 +60,8 @@ abstract type agent end
context::String = "nothing" # internal thinking area
tools::Union{Dict, Nothing} = nothing
thought::String = "nothing" # contain unfinished thoughts for ReAct agent only
thoughtround::Int = 0 # no. of thinking round
thoughtlimit::Int = 5 # thinking round limit
thinkinground::Int = 0 # no. of thinking round
thinkingroundlimit::Int = 5 # thinking round limit
thinkingMode::Union{Dict, Nothing} = nothing
end
@@ -163,6 +163,138 @@ function agentReact(
return newAgent
end
@kwdef mutable struct agentReflex <: agent
availableRole::AbstractVector = ["system", "user", "assistant"]
agentName::String = "assistant"
maxUserMsg::Int = 10
earlierConversation::String = "" # summary of earlier conversation
mqttClient::Union{mqttClient, Nothing} = nothing
msgMeta::Union{Dict, Nothing} = nothing
""" Dict(Role=> Content) ; Role can be system, user, assistant
Example:
messages=[
Dict(:role=>"system", :content=> "You are a helpful assistant."),
Dict(:role=>"assistant", :content=> "How may I help you"),
Dict(:role=>"user", :content=> "Hello, how are you"),
]
"""
role::Symbol = :assistant
roles::Dict = Dict(:assistant => "You are a helpful assistant.",)
# Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
# messages= [Dict(:role=>"system", :content=> "", :timestamp=> Dates.now()),]
messages = Vector{Dict{Symbol, Any}}()
context::String = "nothing" # internal thinking area
tools::Union{Dict, Nothing} = nothing
thought::String = "nothing" # contain unfinished thoughts for ReAct agent only
thinkinground::Int = 0 # no. of thinking round
thinkingroundlimit::Int = 5 # thinking round limit
thinkingMode::Union{Dict, Nothing} = nothing
memory::Dict = Dict(
:shortterm=> "",
:longterm=>""
)
end
function agentReflex(
agentName::String,
mqttClientSpec::NamedTuple;
role::Symbol=:assistant,
roles::Dict=Dict(
:assistant =>
"""
You are a helpful assistant who answer the user's questions as best you can.
""",
:sommelier =>
"""
You are a sommelier at an online wine reseller who always ask user for wine relevant info before you could help them choosing wine.
You provide a personalized recommendation of up to two wines based on the user's preference, and you describe the benefits of each wine in detail.
You don't know other people personal info previously.
Info used to select wine:
- type of food
- occasion
- user's personal taste of wine
- wine price range
- temperature at the serving location
- wine we have in stock
""",
),
thinkingMode::Dict=Dict(
:no_thinking=> "",
:thinking=>
"""Use the following format:
Question: the input question your user is asking and you must answer
Plan: first you should always think about the question and the info you have thoroughly then extract and devise a complete plan to find the answer (pay attention to variables and their corresponding numerals).
Thought: ask yourself do you have all the info you need? And what to do according to the plan (pay attention to correct numeral calculation and commonsense).
Act: the tool that match your thought, should be one of {toolnames}
ActInput: the input to the action (pay attention to the tool's input)
Obs: the result of the action
... (this Plan/Thought/Act/ActInput/Obs can repeat N times until you know the answer.)
Thought: I think I know the answer
Answer: Answer of the original question
Begin!""",
),
tools::Dict=Dict(
:wikisearch=>Dict(
:name => "wikisearch",
:description => "Useful for when you need to search an encyclopedia",
:input => "Input is keywords and not a question.",
:output => "",
:func => wikisearch, # put function here
),
:chatbox=>Dict(
:name => "chatbox",
:description => "Useful for when you need to ask a customer for more context.",
:input => "Input should be a conversation to customer.",
:output => "" ,
:func => nothing,
),
# :wineStock=>Dict(
# :name => "wineStock",
# :description => "useful for when you need to search for wine by your description, price, name or ID.",
# :input => "Input should be a search query with as much details as possible.",
# :output => "" ,
# :func => nothing,
# ),
# :NTHING=>Dict(
# :name => "NTHING",
# :description => "useful for when you don't need to use tools or actions",
# :input => "No input is needed",
# :output => "" ,
# :func => nothing,
# ),
),
msgMeta::Dict=Dict(
:msgPurpose=> "updateStatus",
:from=> "chatbothub",
:to=> "llmAI",
:requestrespond=> "request",
:sendto=> "", # destination topic
:replyTo=> "chatbothub/llm/respond", # requester ask responder to send reply to this topic
:repondToMsgId=> "", # responder is responding to this msg id
:taskstatus=> "", # "complete", "fail", "waiting" or other status
:timestamp=> Dates.now(),
:msgId=> "$(uuid4())",
),
availableRole::AbstractArray=["system", "user", "assistant"],
maxUserMsg::Int=10,)
newAgent = agentReact()
newAgent.availableRole = availableRole
newAgent.maxUserMsg = maxUserMsg
newAgent.mqttClient = CommUtils.mqttClient(mqttClientSpec)
newAgent.msgMeta = msgMeta
newAgent.tools = tools
newAgent.role = role
newAgent.roles = roles
newAgent.thinkingMode = thinkingMode
return newAgent
end
"""
add new message to agent
@@ -356,7 +488,7 @@ function conversation(a::T, usermsg::String) where {T<:agent}
if a.thought != "nothing" # continue thought
_ = addNewMessage(a, "user", usermsg)
a.thought *= "Obs $(a.thoughtround): $usermsg\n"
a.thought *= "Obs $(a.thinkinground): $usermsg\n"
prompt = a.thought
respond = work(a, prompt)
else # new thought
@@ -392,20 +524,21 @@ end
function work(a::T, prompt::String, maxround::Int=3) where {T<:agent}
respond = nothing
while true
a.thoughtround += 1
@show a.thoughtround
a.thinkinground += 1
@show a.thinkinground
toolname = nothing
toolinput = nothing
if a.thoughtround > a.thoughtlimit
a.thought *= "Thought $(a.thoughtround): I think I know the answer."
if a.thinkinground > a.thinkingroundlimit
a.thought *= "Thought $(a.thinkinground): I think I know the answer."
prompt = a.thought
end
@show prompt
respond = sendReceivePrompt(a, prompt)
headerToDetect = nothing
if a.thoughtround == 1
if a.thinkinground == 1
try
respond = split(respond, "Obs:")[1]
headerToDetect = ["Question:", "Plan:", "Thought:", "Act:", "ActInput:", "Obs:", "...", "Answer:",
@@ -414,10 +547,10 @@ function work(a::T, prompt::String, maxround::Int=3) where {T<:agent}
end
else
try
respond = split(respond, "Obs $(a.thoughtround):")[1]
headerToDetect = ["Question $(a.thoughtround):", "Plan $(a.thoughtround):",
"Thought $(a.thoughtround):", "Act $(a.thoughtround):",
"ActInput $(a.thoughtround):", "Obs $(a.thoughtround):",
respond = split(respond, "Obs $(a.thinkinground):")[1]
headerToDetect = ["Question $(a.thinkinground):", "Plan $(a.thinkinground):",
"Thought $(a.thinkinground):", "Act $(a.thinkinground):",
"ActInput $(a.thinkinground):", "Obs $(a.thinkinground):",
"...", "Answer:",
"Conclusion:", "Summary:"]
catch
@@ -429,11 +562,11 @@ function work(a::T, prompt::String, maxround::Int=3) where {T<:agent}
Answer = findDetectedCharacter(headers, "Answer:")
AnswerInd = length(Answer) != 0 ? Answer[1] : nothing
Act = findDetectedCharacter(headers, "Act $(a.thoughtround):")
Act = findDetectedCharacter(headers, "Act $(a.thinkinground):")
if length(Answer) == 1 && length(Act) == 0
a.thought = "nothing" # question finished, no more thought
a.context = "nothing"
a.thoughtround = 0
a.thinkinground = 0
respond = chunkedtext[AnswerInd][:body]
respond = replace(respond, "<|im_end|>"=>"")
_ = addNewMessage(a, "assistant", respond)
@@ -441,12 +574,12 @@ function work(a::T, prompt::String, maxround::Int=3) where {T<:agent}
else
# check for tool being called
ActHeader = a.thoughtround == 1 ? "Act:" : "Act $(a.thoughtround):"
ActHeader = a.thinkinground == 1 ? "Act:" : "Act $(a.thinkinground):"
if length(findDetectedCharacter(headers, ActHeader)) != 0 # check whether there is Act: in a respond
ActInd = findDetectedCharacter(headers, ActHeader)[1]
toolname = toolNameBeingCalled(chunkedtext[ActInd][:body], a.tools)
end
ActInputHeader = a.thoughtround == 1 ? "ActInput:" : "ActInput $(a.thoughtround):"
ActInputHeader = a.thinkinground == 1 ? "ActInput:" : "ActInput $(a.thinkinground):"
if length(findDetectedCharacter(headers, ActInputHeader)) != 0 # check whether there is ActInput: in a respond
ActInputInd = findDetectedCharacter(headers, ActInputHeader)[1]
toolinput = chunkedtext[ActInputInd][:body]
@@ -462,7 +595,7 @@ function work(a::T, prompt::String, maxround::Int=3) where {T<:agent}
@show toolinput
if toolname === nothing || toolinput === nothing
println("toolname $toolname toolinput $toolinput retry thinking")
a.thoughtround -= 1
a.thinkinground -= 1
continue
end
@@ -470,7 +603,7 @@ function work(a::T, prompt::String, maxround::Int=3) where {T<:agent}
thought = ""
for i in chunkedtext
header = i[:header]
header = replace(header, ":"=>" $(a.thoughtround):") # add number so that llm not confused
header = replace(header, ":"=>" $(a.thinkinground):") # add number so that llm not confused
body = i[:body]
thought *= "$header $body"
end
@@ -491,7 +624,7 @@ function work(a::T, prompt::String, maxround::Int=3) where {T<:agent}
if _result != "No info available." #TODO for use with wikisearch(). Not good for other tools
_result = makeSummary(a, _result)
end
result = "Obs $(a.thoughtround): $_result\n"
result = "Obs $(a.thinkinground): $_result\n"
a.thought *= result
prompt = a.thought
end
@@ -501,6 +634,78 @@ function work(a::T, prompt::String, maxround::Int=3) where {T<:agent}
return respond
end
function conversation(a::agentReflex, usermsg::String; thinkingroundlimit::Int=3)
a.thinkingroundlimit = thinkingroundlimit
respond = nothing
# determine thinking mode
a.thinkingMode = chooseThinkingMode(a, usermsg)
if a.thinkingMode == :no_thinking
a.earlierConversation = conversationSummary(a) #TODO should be long conversation before use summary because it leaves out details
_ = addNewMessage(a, "user", usermsg)
prompt = generatePrompt_mistral_openorca(a, usermsg, thinkingmode)
@show prompt
respond = sendReceivePrompt(a, prompt)
respond = split(respond, "<|im_end|>")[1]
respond = replace(respond, "\n" => "")
_ = addNewMessage(a, "assistant", respond)
@show respond
else
respond = work(a, usermsg)
end
respond = work(a, usermsg)
return respond
end
#WORKING
function work(a::agentReflex, usermsg::String)
if a.thinkingMode == :new_thinking
a.earlierConversation = conversationSummary(a)
_ = addNewMessage(a, "user", usermsg)
elseif a.thinkingMode == :continue_thinking
_ = addNewMessage(a, "user", usermsg)
a.thought *= "Obs $(a.thinkinground): $usermsg\n"
else
error("undefined condition thinkingmode = $thinkingmode")
end
while true
# plan
a.thinkinground += 1
@show a.thinkinground
toolname = nothing
toolinput = nothing
plan = planning(a, prompt)
@show plan
# for
# # execute
# end
# evaluate
#
end
end
#WORKING
function planning()
prompt =
"""
<|im_start|>system
You are a helpful assistant.
Your job is to make a concise summary of user's text.
<|im_end|>
<|im_start|>user
{input}
<|im_end|>
<|im_start|>assistant
"""
end
"""
make a conversation summary.
```jldoctest
@@ -615,42 +820,88 @@ function makeSummary(a::T1, input::T2) where {T1<:agent, T2<:AbstractString}
end
function chooseThinkingMode(a::T, usermsg::String) where {T<:agent}
prompt =
"""
<|im_start|>system
{systemMsg}
You have access to the following tools:
{tools}
Your need to determine now whether you will use tools or actions to answer the question.
thinkingMode = nothing
if a.thought != "nothing"
thinkingMode = :continue_thinking
else
prompt =
"""
<|im_start|>system
{systemMsg}
You have access to the following tools:
{tools}
Your need to determine now whether you will use tools or actions to answer the question.
You have the following choices:
If you already know the answer or don't need tools or actions say, "{no}".
If you need tools or actions to answer the question say, "{yes}".
<|im_end|>
You have the following choices:
If you already know the answer or don't need tools or actions say, "{no}".
If you need tools or actions to answer the question say, "{yes}".
<|im_end|>
<|im_start|>user
{input}
<|im_end|>
<|im_start|>assistant
<|im_start|>user
{input}
<|im_end|>
<|im_start|>assistant
"""
toollines = ""
for (toolname, v) in a.tools
if toolname ["chatbox", "nothing"]
toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n"
toollines *= toolline
"""
toollines = ""
for (toolname, v) in a.tools
if toolname ["chatbox", "nothing"]
toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n"
toollines *= toolline
end
end
prompt = replace(prompt, "{systemMsg}" => a.roles[a.role])
prompt = replace(prompt, "{tools}" => toollines)
prompt = replace(prompt, "{input}" => usermsg)
result = sendReceivePrompt(a, prompt)
willusetools = GeneralUtils.getStringBetweenCharacters(result, "{", "}")
thinkingMode = willusetools == "yes" ? :thinking : :no_thinking
end
prompt = replace(prompt, "{systemMsg}" => a.roles[a.role])
prompt = replace(prompt, "{tools}" => toollines)
prompt = replace(prompt, "{input}" => usermsg)
result = sendReceivePrompt(a, prompt)
willusetools = GeneralUtils.getStringBetweenCharacters(result, "{", "}")
thinkingMode = willusetools == "yes" ? :thinking : :no_thinking
return thinkingMode
end
function chooseThinkingMode(a::agentReflex, usermsg::String)
thinkingMode = nothing
if a.thought != "nothing"
thinkingMode = :continue_thinking
else
prompt =
"""
<|im_start|>system
{systemMsg}
You have access to the following tools:
{tools}
Your need to determine now whether you will use tools or actions to answer the question.
You have the following choices:
If you already know the answer or don't need tools or actions say, "{no}".
If you need tools or actions to answer the question say, "{yes}".
<|im_end|>
<|im_start|>user
{input}
<|im_end|>
<|im_start|>assistant
"""
toollines = ""
for (toolname, v) in a.tools
if toolname ["chatbox", "nothing"]
toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n"
toollines *= toolline
end
end
prompt = replace(prompt, "{systemMsg}" => a.roles[a.role])
prompt = replace(prompt, "{tools}" => toollines)
prompt = replace(prompt, "{input}" => usermsg)
result = sendReceivePrompt(a, prompt)
willusetools = GeneralUtils.getStringBetweenCharacters(result, "{", "}")
thinkingMode = willusetools == "yes" ? :new_thinking : :no_thinking
end
return thinkingMode
end
function identifyUserIntention(a::T, usermsg::String) where {T<:agent}
prompt =