Files
ChatAgent/src/interface.jl
2023-11-03 02:22:27 +00:00

412 lines
11 KiB
Julia
Executable File

module interface
export agent, addNewMessage, clearMessage, removeLatestMsg, generatePrompt_tokenPrefix,
generatePrompt_tokenSuffix, conversation
using JSON3, DataStructures, Dates, UUIDs
using CommUtils
# ---------------------------------------------------------------------------- #
# pythoncall setting #
# ---------------------------------------------------------------------------- #
# Ref: https://github.com/JuliaPy/PythonCall.jl/issues/252
# by setting the following variables, PythonCall will use system python or conda python and
# packages installed by system or conda
# if these setting are not set (comment out), PythonCall will use its own python and package that
# installed by CondaPkg (from env_preparation.jl)
# ENV["JULIA_CONDAPKG_BACKEND"] = "Null"
# systemPython = split(read(`which python`, String), "\n")[1]
# ENV["JULIA_PYTHONCALL_EXE"] = systemPython # find python location with $> which python ex. raw"/root/conda/bin/python"
# using PythonCall
# const py_agents = PythonCall.pynew()
# const py_llms = PythonCall.pynew()
# function __init__()
# # PythonCall.pycopy!(py_cv2, pyimport("cv2"))
# # equivalent to from urllib.request import urlopen in python
# PythonCall.pycopy!(py_agents, pyimport("langchain.agents"))
# PythonCall.pycopy!(py_llms, pyimport("langchain.llms"))
# end
#------------------------------------------------------------------------------------------------100
@kwdef mutable struct agent
availableRole = ["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"),
]
"""
# Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
# messages= [Dict(:role=>"system", :content=> "", :timestamp=> Dates.now()),]
messages = []
thougt::String = "" # internal thinking area
info::String = "" # additional info
end
function agent(
agentName::String,
mqttClientSpec::NamedTuple;
systemMessage::String="You are a helpful assistant.", # system message of an agent
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 = agent()
newAgent.availableRole = availableRole
newAgent.maxUserMsg = maxUserMsg
systemMessage_ = "Your name is $agentName. " * systemMessage
push!(newAgent.messages, Dict(:role=>"system", :content=> systemMessage_, :timestamp=> Dates.now()))[,]
newAgent.mqttClient = CommUtils.mqttClient(mqttClientSpec)
newAgent.msgMeta = msgMeta
return newAgent
end
# @kwdef mutable struct agentLangchain
# availableRole=["system", "user", "assistant"]
# maxUserMsg::Int= 10
# llmAIRequestTopic_openblas = "llm/openblas/request"
# llmAIRequestTopic_gpu = "llm/api/v0.0.1/gpu/request"
# self_llmReceiveTopic = "chatbothub/llm/respond"
# """ 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"),
# ]
# """
# # Ref: https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
# #
# messages=[Dict(:role=>"system", :content=> "You are a helpful assistant.", :timestamp=> Dates.now()),]
# end
"""
add new message to agent
# Example
```jldoctest
julia> addNewMessage(agent1, "user", "Where should I go to buy snacks")
````
"""
function addNewMessage(a::agent, role::String, content::String)
if role a.availableRole # guard against typo
error("role is not in agent.availableRole")
end
# check whether user messages exceed limit
userMsg = 0
for i in a.messages
if i[:role] == "user"
userMsg += 1
end
end
messageleft = 0
if userMsg > a.maxUserMsg # delete all conversation
clearMessage(a)
messageleft = a.maxUserMsg
else
userMsg += 1
d = Dict(:role=> role, :content=> content, :timestamp=> Dates.now())
push!(a.messages, d)
messageleft = a.maxUserMsg - userMsg
end
return messageleft
end
function clearMessage(a::agent)
for i in eachindex(a.messages)
if length(a.messages) > 1 # system instruction will NOT be deleted
pop!(a.messages)
else
break
end
end
end
function removeLatestMsg(a::agent)
if length(a.messages) > 1
pop!(a.messages)
end
end
function generatePrompt_tokenSuffix(a::agent;
userToken::String="[/INST]", assistantToken="[INST]",
systemToken="[INST]<<SYS>> content <</SYS>>")
prompt = nothing
for msg in a.messages
role = msg[:role]
content = msg[:content]
if role == "system"
prompt = replace(systemToken, "content" => content) * " "
elseif role == "user"
prompt *= " " * content * " " * userToken
elseif role == "assistant"
prompt *= " " * content * " " * assistantToken
else
error("undefied condition role = $role")
end
end
return prompt
end
function generatePrompt_tokenPrefix(a::agent;
userToken::String="Q:", assistantToken="A:",
systemToken="[INST]<<SYS>> content <</SYS>>")
prompt = nothing
for msg in a.messages
role = msg[:role]
content = msg[:content]
if role == "system"
prompt = replace(systemToken, "content" => content) * " "
elseif role == "user"
prompt *= userToken * " " * content * " "
elseif role == "assistant"
prompt *= assistantToken * " " * content * " "
else
error("undefied condition role = $role")
end
end
return prompt
end
function conversation(a::agent, usermsg::String)
addNewMessage(a, "user", usermsg)
userIntent = identifyUserIntention(a, usermsg)
@show userIntent
#WORKING 1) add if-else user intention logic. 2) add recursive thinking
if userIntent == "chat"
generatePrompt_tokenPrefix(a, userToken="Q:", assistantToken="A:")
elseif userIntent == "task"
else
error("user intent $userIntent not define $(@__LINE__)")
end
end
#TESTING
function identifyUserIntention(a::agent, usermsg::String)
identify_usermsg =
"""
You are to determine intention of the question.
Your choices are:
chat: normal conversation that you don't need to do something.
task: a request for you to do something.
Here are examples of how to determine intention of the question:
Question: How are you?
Answer: {chat}, this question intention is about chat.
Question: Ummm.
Answer: {chat}, this question is about chat.
Question: Search for the stock prices of 'BJC' and 'IOU'on June 10th.
Answer: {task}, this question is about asking you to do something.
Question: Hello
Answer: {chat}, this question is about chat.
Question: 'BJC' and 'IOU'on June 10th.
Answer: {task}, this question is about asking you to do something.
Question: What time is it?
Answer: {task}, this question is about asking you to do something.
Question: What is the price of 'ABC'
Answer: {task}, this question is about asking you to do something.
Question: Do you like coconut?
Answer: {task}, this question is about asking you to do something.
Question: What is the weather tomorrow?
Answer: {task}, this question is about asking you to do something.
Question: I'm fine.
Answer: {chat}, this question is about chat.
Question: How to make a cake?
Answer: {task}, this question is about asking you to do something.
Question: Did you turn off the light?
Answer: {task}, this question is about asking you to do something.
Question: What is stock price of Tesla on June 6th?
Answer: {task}, this question is about asking you to do something.
Question: Tell me some jokes.
Answer: {chat}, this question is about chat.
Begin!
Question: {input}
"""
identify_usermsg = replace(identify_usermsg, "{input}" => usermsg)
result = sendPrompt(a, identify_usermsg, )
# msg = Dict(
# :msgMeta=> a.msgMeta,
# :txt=> identify_usermsg,
# )
# payloadChannel = Channel(1)
# # send prompt
# CommUtils.request(a.mqttClient, msg, pubtopic=a.mqttClient.pubtopic.llmAI)
# starttime = Dates.now()
# timeout = 10
# result = nothing
# while true
# timepass = (Dates.now() - starttime).value / 1000.0
# CommUtils.mqttRun(a.mqttClient, payloadChannel)
# if isready(payloadChannel)
# topic, payload = take!(payloadChannel)
# if payload[:msgMeta][:repondToMsgId] == msg[:msgMeta][:msgId]
# result = payload[:txt]
# break
# end
# elseif timepass <= timeout
# # skip, within waiting period
# elseif timepass > timeout
# result = nothing
# break
# else
# error("undefined condition $(@__LINE__)")
# end
# end
answer = result === nothing ? nothing : GeneralUtils.getStringBetweenCharacters(result, "{", "}")
return answer
end
function sendPrompt(a::agent, prompt::String; timeout::Int=10)
a.msgMeta[:msgId] = "$(uuid4())"
msg = Dict(
:msgMeta=> a.msgMeta,
:txt=> prompt,
)
payloadChannel = Channel(1)
#WORKING send prompt
CommUtils.request(a.mqttClient, msg)
starttime = Dates.now()
result = nothing
while true
timepass = (Dates.now() - starttime).value / 1000.0
CommUtils.mqttRun(a.mqttClient, payloadChannel)
if isready(payloadChannel)
topic, payload = take!(payloadChannel)
if payload[:msgMeta][:repondToMsgId] == msg[:msgMeta][:msgId]
result = payload[:txt]
break
end
elseif timepass <= timeout
# skip, within waiting period
elseif timepass > timeout
result = nothing
break
else
error("undefined condition $(@__LINE__)")
end
end
# function getStringBetweenCurlyBraces(s::AbstractString)
# m = match(r"\{(.+?)\}", s)
# m = m == "" ? "" : m.captures[1]
# return m
# end
# function getStringBetweenCharacters(text::AbstractString, startChar::String, endChar::String)
# startIndex= findlast(startChar, text)
# endIndex= findlast(endChar, text)
# if startIndex === nothing || endIndex === nothing
# return nothing
# else
# return text[startIndex.stop+1: endIndex.start-1]
# end
# end
end # module