412 lines
11 KiB
Julia
Executable File
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 |