diff --git a/src/interface.jl b/src/interface.jl index 57b0453..6843738 100755 --- a/src/interface.jl +++ b/src/interface.jl @@ -85,8 +85,8 @@ function clearMessage(a::T) where {T<:agent} a.memory = newAgentMemory() - @show a.messages - @show a.memory + # @show a.messages + # @show a.memory end function removeLatestMsg(a::T) where {T<:agent} @@ -1165,77 +1165,6 @@ function actor_mistral_openorca(a::agentReflex, selfaware=nothing) end -""" - Chat with llm. - - ```jldoctest - julia> using JSON3, UUIDs, Dates, FileIO, ChatAgent - julia> mqttClientSpec = ( - clientName= "someclient", # name of this client - clientID= "$(uuid4())", - broker= "mqtt.yiem.cc", - pubtopic= (imgAI="img/api/v0.0.1/gpu/request", - txtAI="txt/api/v0.1.0/gpu/request"), - subtopic= (imgAI="agent/api/v0.1.0/img/response", - txtAI="agent/api/v0.1.0/txt/response"), - keepalive= 30, - ) - julia> msgMeta = Dict( - :msgPurpose=> "updateStatus", - :from=> "agent", - :to=> "llmAI", - :requestresponse=> "request", - :sendto=> "", # destination topic - :replyTo=> "agent/api/v0.1.0/txt/response", # requester ask responseer to send reply to this topic - :repondToMsgId=> "", # responseer is responseing to this msg id - :taskstatus=> "", # "complete", "fail", "waiting" or other status - :timestamp=> Dates.now(), - :msgId=> "$(uuid4())", - ) - julia> newAgent = ChatAgent.agentReact( - "Jene", - mqttClientSpec, - role=:assistant_react, - msgMeta=msgMeta - ) - julia> response = ChatAgent.conversation(newAgent, "Hi! how are you?") - ``` -""" -function conversation(a::agentReflex, usermsg::String; attemptlimit::Int=3) - a.attemptlimit = attemptlimit - workstate = nothing - response = nothing - - _ = addNewMessage(a, "user", usermsg) - isuseplan = isUsePlans(a) - # newinfo = extractinfo(a, usermsg) - # a.env = newinfo !== nothing ? updateEnvState(a, newinfo) : a.env - @show isuseplan - - if isuseplan # use plan before responding - if haskey(a.memory[:shortterm], "User:") == false #TODO should change role if user want to buy wine. - a.memory[:shortterm]["User:"] = usermsg - end - workstate, response = work(a) - end - - # if LLM using askbox, use returning msg form askbox as conversation response - if workstate == "askbox" || workstate == "formulatedUserResponse" - #TODO paraphrase msg so that it is human friendlier word. - else - response = chat_mistral_openorca(a) - response = split(response, "\n\n")[1] - response = split(response, "\n\n")[1] - end - - response = removeTrailingCharacters(response) - _ = addNewMessage(a, "assistant", response) - - return response -end - - - """ Chat with llm. Arguments\n @@ -1288,6 +1217,42 @@ end Signature\n ----- """ +function conversation(a::agentReflex, usermsg::String; attemptlimit::Int=3) + a.attemptlimit = attemptlimit + workstate = nothing + response = nothing + + _ = addNewMessage(a, "user", usermsg) + isuseplan = isUsePlans(a) + # newinfo = extractinfo(a, usermsg) + # a.env = newinfo !== nothing ? updateEnvState(a, newinfo) : a.env + @show isuseplan + + if isuseplan # use plan before responding + if haskey(a.memory[:shortterm], "User:") == false #TODO should change role if user want to buy wine. + a.memory[:shortterm]["User:"] = usermsg + end + workstate, response = work(a) + end + + # if LLM using askbox, use returning msg form askbox as conversation response + if workstate == "askbox" || workstate == "formulatedUserResponse" + #TODO paraphrase msg so that it is human friendlier word. + else + response = chat_mistral_openorca(a) + response = split(response, "\n\n")[1] + response = split(response, "\n\n")[1] + end + + response = removeTrailingCharacters(response) + _ = addNewMessage(a, "assistant", response) + + return response +end + + + + """ diff --git a/src/type.jl b/src/type.jl index 31d134d..aa6bba6 100644 --- a/src/type.jl +++ b/src/type.jl @@ -4,7 +4,7 @@ export agent, agentReflex, newAgentMemory using Dates, UUIDs, DataStructures -#------------------------------------------------------------------------------------------------100 +# ---------------------------------------------- 100 --------------------------------------------- # function newAgentMemory() memory::Dict{Any, Any} = Dict( @@ -79,9 +79,10 @@ julia> agent = ChatAgent.agentReflex( ``` """ @kwdef mutable struct agentReflex <: agent - agentName::String = "Jene" # ex. Jene + name::String + id::String + availableRole::AbstractVector - availableRole::AbstractVector = ["system", "user", "assistant"] """ Dict(Role=> Content) ; Role can be system, user, assistant Example: messages=[ @@ -90,13 +91,12 @@ julia> agent = ChatAgent.agentReflex( Dict(:role=>"user", :content=> "Hello, how are you"), ] """ - role::Symbol = :assistant - roles::Dict = Dict(:assistant => "You are a helpful assistant.",) - roleSpecificInstruction::Union{Dict, Nothing} = nothing - thinkingFormat::Union{Dict, Nothing} = nothing + role::Symbol + roles::Dict{Symbol, String} + roleSpecificInstruction::Dict{Symbol, String} + config::Dict tools::Union{Dict, Nothing} = nothing - newplan::Bool = false # if true, new plan will be generated attemptlimit::Int = 5 # thinking round limit attempt::Int = 1 # attempted number task::Int = 1 # task number @@ -110,22 +110,31 @@ julia> agent = ChatAgent.agentReflex( earlierConversation::String = "N/A" # summary of earlier conversation # communication - configTopic::String="" # store mqtt topic where an agent can get configuration mqttClient::Any=nothing # store mqtt client for use in various internal functions msgMeta::Union{Dict, Nothing} = nothing # a template for msgMeta - mqttMsg_chat::Vector{Dict} = Vector{Dict}() # store incoming mqtt chat msg - mqttMsg_internal::Dict = Dict() # store incoming mqtt internal use msg + mqttMsg_internal::Channel{Dict} = Channel{Dict}(32) # LLM function related winestockResult = "" end function agentReflex( - agentName::String, mqttClient, msgMeta::Dict, - configTopic::String, + config::Dict = Dict( + :frontend=>Dict( + :mqtttopic=> nothing + ), + :internal=>Dict( + :mqtttopic=> nothing + ), + :text2text=>Dict( + :mqtttopic=> "txt2text/api/v1/prompt/gpu", + ), + ), ; + name::String="Assistant", + id::String=string(uuid4()), role::Symbol=:assistant, roles::Dict=Dict( :assistant => @@ -168,51 +177,6 @@ function agentReflex( """ ), - - 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!""", - :planner=> - """ - Use the following format: - 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. - """, - :actor=> - """ - - Use the following format: - Thought: based on the plan and the recap of the plan, what to do? (pay attention to correct numeral calculation and commonsense). - Act: an action to take based on your thought, must be one of [{toolnames}] - Actinput: your input to the action based on your thought (pay attention to the tool's input) - Obs: observed result of the action - - P.S.1 ask the user one by one question. - P.S.2 ask the user what you want to know if you didn't ask yet. - - """, - :actorOriginal=> - """ - Use the following format: - Thought: you should always think about do you have all the required info and what to do according to step {step} of the plan and the info you have (pay attention to correct numeral calculation and commonsense). - Act: the action to take 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 - """, - ), tools::Dict=Dict( :chatbox=>Dict( :name => "chatbox", @@ -247,19 +211,23 @@ function agentReflex( maxUserMsg::Int=10, ) - newAgent = agentReflex() - newAgent.agentName = agentName - newAgent.mqttClient = mqttClient - newAgent.msgMeta = msgMeta - newAgent.configTopic = configTopic - newAgent.availableRole = availableRole - newAgent.maxUserMsg = maxUserMsg - newAgent.msgMeta = msgMeta - newAgent.tools = tools - newAgent.role = role - newAgent.roles = roles - newAgent.thinkingFormat = thinkingFormat - newAgent.roleSpecificInstruction = roleSpecificInstruction + #NEXTVERSION publish to agentConfigTopic to get a config. + #NEXTVERSION get a config message in a.mqttMsgList + #NEXTVERSION set agent according to config + + newAgent = agentReflex( + name = name, + id = id, + config = config, + mqttClient = mqttClient, + msgMeta = msgMeta, + availableRole = availableRole, + maxUserMsg = maxUserMsg, + tools = tools, + role = role, + roles = roles, + roleSpecificInstruction = roleSpecificInstruction, + ) return newAgent diff --git a/src/utils.jl b/src/utils.jl index 8a8f897..a7cfa44 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -11,17 +11,92 @@ using UUIDs, Dates, DataStructures, HTTP, MQTTClient using GeneralUtils using ..type -#------------------------------------------------------------------------------------------------100 +# ---------------------------------------------- 100 --------------------------------------------- # -#WORKING -function sendReceivePrompt(message::AbstractDict, mqttclient, pubtopic::String) +""" Send message to receiver then wait for reply. + + Arguments\n + ----- + a::agent + an agent + prompt::String + prompt to be sent + sendtopic::String + topic the sender sends to e.g. "/agent/wine/api/v1/prompt" + + Keyword Arguments\n + ----- + max_tokens::Integer + maximum number of tokens to be generated + timeout::Integer + timeout in seconds + temperature::AbstractFloat + range 0.0-1.0 + stopword::Vector{<:AbstractString} + list of stopword to stop text generation + + Return\n + ----- + inferenced text + + Example\n + ----- + ```jldoctest + julia> using GeneralUtils + julia> msgMeta = generate_msgMeta("/agent/frontend/wine/chat/api/v1/txt/receive") + Dict{Symbol, Union{Nothing, String}} with 13 entries: + :msgPurpose => nothing + :requestresponse => nothing + :timestamp => "2024-03-15T08:10:23.909" + :replyToMsgId => nothing + :receiverId => nothing + :getpost => nothing + :msgId => "e3467028-1dc1-4678-a6f1-a074696ca07c" + :acknowledgestatus => nothing + :sendTopic => "/agent/frontend/wine/chat/api/v1/txt/receive" + :receiverName => nothing + :replyTopic => nothing + :senderName => nothing + :senderId => nothing + ``` + + Signature\n + ----- +""" +function sendReceivePrompt(a::T1, prompt::String, sendtopic::String; + max_tokens::Integer=256, timeout::Integer=120, temperature::AbstractFloat=0.2, + stopword::T2=["nostopwordyet"], + seed=nothing) where {T1<:agent, T2<:Vector{<:AbstractString}} + + sendto = a.config[:text2text][:mqtttopic] + msgMeta = deepcopy(a.msgMeta) + msgMeta[:sendTopic] = #WORKING assign value + + sendto, + senderName = "agent-wine-backend", + receiverName = "text2text-llm", + replyTopic = a.config[:frontend][:mqtttopic], + msgId = string(uuid4()) + + + a.msgMeta[:msgId] = "$(uuid4())" # new msg id for each msg + msg = Dict( + :msgMeta=> a.msgMeta, + :txt=> prompt, + :max_tokens=> max_tokens, + :temperature=> temperature, + :stopword=> stopword, + :seed=> seed, + ) + + # send prompt + CommUtils.request(a.mqttClient, msg) starttime = Dates.now() result = nothing while true timepass = (Dates.now() - starttime).value / 1000.0 - #WORKING get payload form mqtt or rest - if isready(payloadChannel) + if isready(mqttMsg_internal) topic, payload = take!(payloadChannel) if payload[:msgMeta][:repondToMsgId] == msg[:msgMeta][:msgId] result = haskey(payload, :txt) ? payload[:txt] : nothing @@ -37,68 +112,11 @@ function sendReceivePrompt(message::AbstractDict, mqttclient, pubtopic::String) error("undefined condition. timepass=$timepass timeout=$timeout $(@__LINE__)") end end + sleep(0.1) # allow other threads to run return result end -function sendReceivePrompt(message::AbstractDict, endpoint::String) - -end - -""" - Send a msg to registered mqtt topic within mqttClient. - - ```jldoctest - julia> using JSON3, UUIDs, Dates, FileIO, ChatAgent - julia> newAgent = ChatAgent.agentReact( - "Jene", - mqttClientSpec, - role=:assistant_react, - msgMeta=msgMeta - ) - ``` -""" -# function sendReceivePrompt(a::T, prompt::String; max_tokens=256, timeout::Int=120, -# temperature::AbstractFloat=0.2, stopword=[], seed=nothing) where {T<:agent} -# a.msgMeta[:msgId] = "$(uuid4())" # new msg id for each msg -# msg = Dict( -# :msgMeta=> a.msgMeta, -# :txt=> prompt, -# :max_tokens=> max_tokens, -# :temperature=> temperature, -# :stopword=> stopword, -# :seed=> seed, -# ) -# payloadChannel = Channel(1) - -# #WORKING send prompt using mqtt or Rest - -# starttime = Dates.now() -# result = nothing - -# while true -# timepass = (Dates.now() - starttime).value / 1000.0 -# #WORKING get payload form mqtt or rest -# if isready(payloadChannel) -# topic, payload = take!(payloadChannel) -# if payload[:msgMeta][:repondToMsgId] == msg[:msgMeta][:msgId] -# result = haskey(payload, :txt) ? payload[:txt] : nothing -# break -# end -# elseif timepass <= timeout -# # skip, within waiting period -# elseif timepass > timeout -# println("sendReceivePrompt timeout $timepass/$timeout") -# result = nothing -# break -# else -# error("undefined condition. timepass=$timepass timeout=$timeout $(@__LINE__)") -# end -# end - -# return result -# end - """