This commit is contained in:
narawat lamaiin
2024-03-20 09:16:41 +07:00
parent d2364ee51d
commit 66d2fafcf8
3 changed files with 158 additions and 207 deletions

View File

@@ -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
"""

View File

@@ -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(
</Required wine info>
"""
),
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=>
"""
<Your job>
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.
</Your job>
""",
: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

View File

@@ -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
"""