diff --git a/src/interface.jl b/src/interface.jl index ee435f3..d707cbb 100755 --- a/src/interface.jl +++ b/src/interface.jl @@ -292,11 +292,14 @@ function planner_mistral_openorca(a::agentReflex) Objective: the objective you intend to do Plan: first you should always think about the objective, the info you need and the info you have thoroughly then extract and devise a step by step plan (pay attention to correct numeral calculation and commonsense). p.s.1 each step of the plan should be a single action. + p.s.2 make sure you gather all the required information. <|im_end|> $conversation <|im_start|>assistant Objective: """ + # p.s.2 the last step should be about responding. + result = sendReceivePrompt(a, assistant_plan_prompt, max_tokens=1024, temperature=0.2) @show raw_plan = result x = split(result, "<|im_end|>")[1] @@ -396,7 +399,6 @@ end # return prompt # end - function actor_mistral_openorca(a::agentReflex) """ general prompt format: @@ -430,20 +432,16 @@ function actor_mistral_openorca(a::agentReflex) prompt = """ <|im_start|>system - {role} + $(a.roles[a.role]) {tools} - {thinkingFormat} + $(a.thinkingFormat[:actor]) {context} <|im_end|> {shorttermMemory} Thought $(a.step): """ - prompt = replace(prompt, "{role}" => a.roles[a.role]) - prompt = replace(prompt, "{thinkingFormat}" => a.thinkingFormat[:actor]) - prompt = replace(prompt, "{step}" => a.step) - - s = dictToString(a.memory[:shortterm], ["user:", "Plan 1:"]) + s = dictToString(a.memory[:shortterm], skiplist=["user:", "Plan 1:"]) prompt = replace(prompt, "{shorttermMemory}" => s) toolnames = "" @@ -455,9 +453,13 @@ function actor_mistral_openorca(a::agentReflex) end prompt = replace(prompt, "{toolnames}" => toolnames) prompt = replace(prompt, "{tools}" => "You have access to the following tools:\n$toollines") + + conversation = messagesToString_nomark(a.messages, addressAIas="I") context = """ + Your talk with the user: + $conversation {env state} {longterm memory} {plan} @@ -471,6 +473,80 @@ function actor_mistral_openorca(a::agentReflex) return prompt end +# function actor_mistral_openorca(a::agentReflex) +# """ +# general prompt format: + +# " +# <|im_start|>system +# {role} +# {tools} +# {thinkingFormat} +# <|im_end|> +# {context} +# <|im_start|>user +# {usermsg} +# <|im_end|> +# <|im_start|>assistant + +# " + +# Note: +# {context} = +# " +# {earlierConversation} +# {env state} +# {shortterm memory} +# {longterm memory} +# " +# """ + +# mark = "$(a.step)" + +# prompt = +# """ +# <|im_start|>system +# {role} +# {tools} +# {thinkingFormat} +# {context} +# <|im_end|> +# {shorttermMemory} +# Thought $(a.step): +# """ + +# prompt = replace(prompt, "{role}" => a.roles[a.role]) +# prompt = replace(prompt, "{thinkingFormat}" => a.thinkingFormat[:actor]) +# prompt = replace(prompt, "{step}" => a.step) + +# s = dictToString(a.memory[:shortterm], skiplist=["user:", "Plan 1:"]) +# prompt = replace(prompt, "{shorttermMemory}" => s) + +# toolnames = "" +# toollines = "" +# for (toolname, v) in a.tools +# toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" +# toollines *= toolline +# toolnames *= "$toolname, " +# end +# prompt = replace(prompt, "{toolnames}" => toolnames) +# prompt = replace(prompt, "{tools}" => "You have access to the following tools:\n$toollines") + +# context = +# """ +# {env state} +# {longterm memory} +# {plan} +# """ +# # context = replace(context, "{earlierConversation}" => "My earlier talk with the user:\n$(a.earlierConversation)") +# context = replace(context, "{env state}" => "") +# context = replace(context, "{longterm memory}" => "") +# context = replace(context, "{plan}" => "My plan:\n$(a.memory[:shortterm]["Plan $(a.attempt):"])") +# prompt = replace(prompt, "{context}" => context) + +# return prompt +# end + """ @@ -526,7 +602,7 @@ function conversation(a::agentReflex, usermsg::String; attemptlimit::Int=3) end # if LLM using chatbox, use returning msg form chatbox as conversation response - if workstate == "chatbox" ? + if workstate == "chatbox" #TODO paraphrase msg so that it is human friendlier word. else response = chat_mistral_openorca(a) @@ -916,8 +992,6 @@ function actor(a::agentReflex) a.memory[:shortterm] = removeHeaders(a.memory[:shortterm], a.step, ["Plan"]) a.memory[:log] = removeHeaders(a.memory[:log], a.step, ["Plan"]) println("repeating step $(a.step)") - - #WORKING else error("undefined condition decision = $decision $(@__LINE__)") end @@ -1238,7 +1312,7 @@ julia> report = formulateUserresponse(agent, shorttermMemory) ``` """ function formulateUserresponse(a) - stimulus = a.memory[:shortterm]["user:"] + stimulus = a.memory[:shortterm]["user:"] #BUG no "user" key work = dictToString(a.memory[:shortterm], ["user:"]) @@ -1294,34 +1368,10 @@ julia> shorttermMemory = OrderedDict{String, Any}( julia> decision = goNogo(agent) "Yes" ``` -""" +""" #WORKING add choice skip, if LLM already completed the step function goNogo(a) - stimulus = a.memory[:shortterm]["user:"] - work = dictToString(a.memory[:shortterm], ["user:"]) - - # prompt = - # """ - # <|im_start|>system - # Symbol meaning: - # Stimulus: the input user gives to you and you must response - # Plan: a plan - # Thought: your thought - # Act: the action you took - # Actinput: the input to the action - # Obs: the result of the action - - # Stimulus: - # $stimulus - - # Your work: - # $work - - # From your work, you job is to decide what to do next by choosing one of the following choices: - # If you are ready to do the next step of the plan say, "{Yes}". And what is the rationale behind the decision? - # If you need to repeat the latest step say, "{No}". And what is the rationale behind the decision? - # If you are ready to formulate a final response to user original stimulus say, {formulateUserresponse}. And what is the rationale behind the decision? - # <|im_end|> - # """ + # stimulus = a.memory[:shortterm]["user:"] + work = dictToString(a.memory[:shortterm]) prompt = """ @@ -1334,24 +1384,18 @@ function goNogo(a) Actinput: the input to the action Obs: the result of the action - Stimulus: - $stimulus - Your work: $work Your job is to check whether step $(a.step) of your work is completed according to the plan and choose only one of the following choices. - choice 1: If you are ready to do the next step of the plan say, "{Yes}". And what is the rationale behind the decision? - choice 2: If you need to repeat the latest step say, "{No}". And what is the rationale behind the decision? + choice 1: If you get what you intend to do and you are ready to do the next step of the plan say, "{Yes}". And what is the rationale behind the decision to do the next step? + choice 2: If you didn't get what you intend to do and you need to repeat the latest step say, "{No}". And what is the rationale behind the decision to repeat the latest step? <|im_end|> <|im_start|>assistant """ response = sendReceivePrompt(a, prompt) - - - decision = nothing reason = nothing if occursin("Yes", response) diff --git a/src/type.jl b/src/type.jl index a07ff05..c99fb98 100644 --- a/src/type.jl +++ b/src/type.jl @@ -124,7 +124,7 @@ function agentReflex( """, :sommelier => """ - You are a sommelier at an online wine reseller who always help users choosing their wine from your inventory. + You are a sommelier at an online wine reseller who always help users choosing their wine from your store. You don't know other people personal info previously. """, # :sommelier => @@ -163,7 +163,6 @@ function agentReflex( Stimulus: the input user gives to you and you must respond Plan: first you should always think about the stimulus, the info you need and the info you have thoroughly then extract and devise a step by step plan (pay attention to correct numeral calculation and commonsense). p.s.1 each step should be a single action. - p.s.2 don't respond to the stimulus yet. """, :actor=> """ @@ -246,137 +245,6 @@ end -@kwdef mutable struct agentReact <: 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 -end - -function agentReact( - 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 helpful 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 - - - - diff --git a/src/utils.jl b/src/utils.jl index 2e5c488..342006a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -4,7 +4,7 @@ export makeSummary, sendReceivePrompt, chunktext, extractStepFromPlan, checkTota detectCharacters, findDetectedCharacter, extract_number, toolNameBeingCalled, isUseTools, conversationSummary, checkReasonableness, replaceHeaders, addShortMem!, splittext, dictToString, removeHeaders, keepOnlyKeys, experience, - messagesToString, removeTrailingCharacters + messagesToString, messagesToString_nomark, removeTrailingCharacters using UUIDs, Dates, DataStructures using CommUtils, GeneralUtils @@ -624,6 +624,25 @@ function messagesToString(messages::AbstractVector{T}; addressAIas="assistant") return conversation end +""" Convert a vector of dict into 1-continous string. + +Arguments: + vecofdict, a vector of dict + +Return: + 1-continous string + +# Example +```jldoctest +julia> using ChatAgent +julia> agent = ChatAgent.agentReflex("Jene") +julia> agent.messages = [Dict(:role=> "user", :content=> "Hi there."), + Dict(:role=> "assistant", :content=> "Hello! How can I assist you today?"),] + +julia> messagesToString(agent.messages) +"user: Hi there.\nassistant: Hello! How can I assist you today?\n" +``` +""" function messagesToString_nomark(messages::AbstractVector{T}; addressAIas="assistant") where {T<:AbstractDict} conversation = "" if length(messages)!= 0 @@ -648,6 +667,39 @@ function messagesToString_nomark(messages::AbstractVector{T}; addressAIas="assis return conversation end + +function dictToString(shortMemory::T; + skiplist::Union{Array{String}, Array{Symbol}}=[""]) where {T<:AbstractDict} + s = "" + for (k, v) in shortMemory + if k ∉ skiplist + new_v = removeTrailingCharacters(v) + s1 = "$k $new_v\n" + s *= s1 + end + end + return s +end + + +# function dictToString(dict::T; +# skiplist::Union{Array{String}, Array{Symbol}}=[]) where {T<:AbstractDict} +# s = "" +# for (k, v) in dict +# if k ∉ skiplist +# s1 = "$k $v" +# s *= s1 + +# # ensure a newline seperate each sentences +# if s[end] != "\n" +# s *= "\n" +# end +# end +# end +# return s +# end + + """ Remove trailing characters from text. Arguments: @@ -840,65 +892,6 @@ function replaceHeaders(text::T, headers, step::Int) where {T<:AbstractString} end -""" Convert short term memory into 1 continous string. - -Arguments: - shortMemory = a short term memory of a ChatAgent's agent - skiplist = a list of keys in memory you want to skip - -Return: - a short term memory in 1 countinuous string - -# Example -```jldoctest -julia> shortMemory = OrderedDict( - "user:" => "Umm", - "Thought 1:" => "I like it.", - "Act 1:" => "chatbox", - "Actinput 1:" => "I get this one.", - ) - -julia> headers = ["user:"] -julia> dictToString(shortMemory, headers) -"Thought 1: I like it.\nAct 1: chatbox\nActinput 1: I get this one.\n" -``` -""" -function shortMemoryToString(shortMemory::OrderedDict, - skiplist::Union{Array{String}, Array{Symbol}}) - s = "" - for (k, v) in shortMemory - if k ∉ skiplist - s1 = "$k $v" - s *= s1 - - # ensure a newline seperate each sentences - if s[end] != "\n" - s *= "\n" - end - end - end - return s -end - - -function dictToString(dict::T, - skiplist::Union{Array{String}, Array{Symbol}}) where {T<:AbstractDict} - s = "" - for (k, v) in dict - if k ∉ skiplist - s1 = "$k $v" - s *= s1 - - # ensure a newline seperate each sentences - if s[end] != "\n" - s *= "\n" - end - end - end - return s -end - - """ Remove headers of specific step from memory. Arguments: