From 4e499e6c8ebfe319bab4a9110ad93851fa9a4459 Mon Sep 17 00:00:00 2001 From: tonaerospace Date: Wed, 22 Nov 2023 02:20:00 +0000 Subject: [PATCH] update --- src/interface.jl | 343 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 297 insertions(+), 46 deletions(-) diff --git a/src/interface.jl b/src/interface.jl index 88a35e0..881edb0 100755 --- a/src/interface.jl +++ b/src/interface.jl @@ -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 =