diff --git a/src/ChatAgent.jl b/src/ChatAgent.jl index bcc89f3..2411420 100755 --- a/src/ChatAgent.jl +++ b/src/ChatAgent.jl @@ -7,11 +7,14 @@ module ChatAgent files and each file can only depend on the file included before it. """ + include("type.jl") + using .type + include("interface.jl") using .interface - #------------------------------------------------------------------------------------------------100 +#------------------------------------------------------------------------------------------------100 """ version 0.0.1 Todo: diff --git a/src/interface.jl b/src/interface.jl index d1aee7c..14edef2 100755 --- a/src/interface.jl +++ b/src/interface.jl @@ -8,6 +8,7 @@ export agentReact, agentReflex, using JSON3, DataStructures, Dates, UUIDs, HTTP using CommUtils, GeneralUtils +using ..type # ---------------------------------------------------------------------------- # # pythoncall setting # @@ -34,281 +35,6 @@ using CommUtils, GeneralUtils #------------------------------------------------------------------------------------------------100 -abstract type agent 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 - - -@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 - """, - ), - thinkingFormat::Dict=Dict( - :react=> - """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!""", - :plan=> - """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). - - Begin!""", - :qta=> - """Use the following format: - Question: the input question your user is asking and you must answer - 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) - - 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 - - return newAgent -end - """ add new message to agent @@ -779,9 +505,9 @@ function conversation(a::agentReflex, usermsg::String; thinkingroundlimit::Int=3 respond = nothing # determine thinking mode - a.thinkingMode = chooseThinkingMode(a, usermsg) + a.thinkingmode = chooseThinkingMode(a, usermsg) - if a.thinkingMode == :no_thinking + 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 = genPrompt_mistral_openorca(a, usermsg) #TODO rewrite this function @@ -800,10 +526,11 @@ end #WORKING function work(a::agentReflex, usermsg::String) - if a.thinkingMode == :new_thinking + error("work done") + if a.thinkingmode == :new_thinking a.earlierConversation = conversationSummary(a) _ = addNewMessage(a, "user", usermsg) - elseif a.thinkingMode == :continue_thinking + elseif a.thinkingmode == :continue_thinking _ = addNewMessage(a, "user", usermsg) a.thought *= "Obs $(a.thinkinground): $usermsg\n" else @@ -823,6 +550,8 @@ function work(a::agentReflex, usermsg::String) # end # evaluate # + + end end @@ -982,9 +711,9 @@ function chooseThinkingMode(a::T, usermsg::String) where {T<:agent} end function chooseThinkingMode(a::agentReflex, usermsg::String) - thinkingMode = nothing + thinkingmode = nothing if a.thought != "nothing" - thinkingMode = :continue_thinking + thinkingmode = :continue_thinking else prompt = """ @@ -1017,10 +746,10 @@ function chooseThinkingMode(a::agentReflex, usermsg::String) prompt = replace(prompt, "{input}" => usermsg) result = sendReceivePrompt(a, prompt) willusetools = GeneralUtils.getStringBetweenCharacters(result, "{", "}") - thinkingMode = willusetools == "yes" ? :new_thinking : :no_thinking + thinkingmode = willusetools == "yes" ? :new_thinking : :no_thinking end - return thinkingMode + return thinkingmode end function identifyUserIntention(a::T, usermsg::String) where {T<:agent} diff --git a/src/type.jl b/src/type.jl new file mode 100644 index 0000000..4ab0974 --- /dev/null +++ b/src/type.jl @@ -0,0 +1,358 @@ +module type + +export agent, agentReflex + +using Dates, UUIDs +using CommUtils + +#------------------------------------------------------------------------------------------------100 + + +abstract type agent end + + +@kwdef mutable struct agentReflex <: agent + availableRole::AbstractVector = ["system", "user", "assistant"] + agentName::String = "Jene" # ex. Jene + 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" # logs unfinished thoughts + thinkinground::Int = 0 # no. of thinking round + thinkingroundlimit::Int = 5 # thinking round limit + thinkingmode::Symbol = :no_thinking + thinkingFormat::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 + """, + ), + thinkingFormat::Dict=Dict( + :react=> + """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!""", + :plan=> + """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). + + Begin!""", + :qta=> + """Use the following format: + Question: the input question your user is asking and you must answer + 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) + + 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 = agentReflex() + newAgent.availableRole = availableRole + newAgent.maxUserMsg = maxUserMsg + newAgent.mqttClient = CommUtils.mqttClient(mqttClientSpec) + newAgent.msgMeta = msgMeta + newAgent.tools = tools + newAgent.role = role + newAgent.roles = roles + newAgent.thinkingFormat = thinkingFormat + newAgent.name = agentName + + return newAgent +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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +end # end module + + +