update
This commit is contained in:
89
src/GeneralUtils.jl
Normal file
89
src/GeneralUtils.jl
Normal file
@@ -0,0 +1,89 @@
|
||||
module GeneralUtils
|
||||
|
||||
|
||||
export noNegative!, randomWithProb, randomChoiceWithProb, findIndex, limitvalue
|
||||
|
||||
include("util.jl")
|
||||
using .util
|
||||
|
||||
include("communication.jl")
|
||||
using .communication
|
||||
|
||||
include("llmUtil.jl")
|
||||
using .llmUtil
|
||||
|
||||
include("interface.jl")
|
||||
using .interface
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------------------------100
|
||||
|
||||
""" version 0.0.4
|
||||
Todo:
|
||||
- [*1] cartesianAssign for different matrix dimension
|
||||
|
||||
Change from version: 0.0.3
|
||||
-
|
||||
|
||||
All features
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end # generalUtils
|
||||
640
src/communication.jl
Normal file
640
src/communication.jl
Normal file
@@ -0,0 +1,640 @@
|
||||
module communication
|
||||
|
||||
export generate_msgMeta, isMqttConnectionAlive, checkMqttConnection!,
|
||||
sendMqttMsg, sendReceiveMqttMsg, mqttClientInstance
|
||||
|
||||
using JSON3, DataStructures, Distributions, Random, Dates, UUIDs, MQTTClient
|
||||
using ..util
|
||||
|
||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||
|
||||
mutable struct mqttClientInstance
|
||||
mqttBrokerAddress::String
|
||||
mqttBrokerPort::Integer
|
||||
subtopic::Vector{String}
|
||||
receiveDataChannel::Channel
|
||||
onMsgCallback::Function
|
||||
qos::MQTTClient.QOS
|
||||
client::MQTTClient.Client
|
||||
connection::MQTTClient.Connection
|
||||
keepalivetopic::String
|
||||
keepaliveChannel::Channel
|
||||
keepaliveCheckInterval::Integer # second
|
||||
lastTimeMqttConnCheck::DateTime
|
||||
end
|
||||
|
||||
function mqttClientInstance(
|
||||
mqttBrokerAddress::String,
|
||||
mqttBrokerPort::Integer,
|
||||
subtopic::Vector{String},
|
||||
receiveDataChannel::Channel,
|
||||
keepaliveChannel::Channel, # user needs to specify because it has to be accessible by user-defined onMsgCallback()
|
||||
onMsgCallback::Function
|
||||
;
|
||||
keepalivetopic::String= "/keepalive/$(uuid4())",
|
||||
keepaliveCheckInterval::Integer=30,
|
||||
qos::MQTTClient.QOS=QOS_1,
|
||||
)
|
||||
|
||||
client, connection = MQTTClient.MakeConnection(mqttBrokerAddress, mqttBrokerPort)
|
||||
MQTTClient.connect(client, connection)
|
||||
for i in subtopic
|
||||
MQTTClient.subscribe(client, i, onMsgCallback, qos=qos)
|
||||
end
|
||||
MQTTClient.subscribe(client, keepalivetopic, onMsgCallback, qos=qos)
|
||||
|
||||
instance = mqttClientInstance(
|
||||
mqttBrokerAddress,
|
||||
mqttBrokerPort,
|
||||
subtopic,
|
||||
receiveDataChannel,
|
||||
onMsgCallback,
|
||||
qos,
|
||||
client,
|
||||
connection,
|
||||
keepalivetopic,
|
||||
keepaliveChannel,
|
||||
keepaliveCheckInterval,
|
||||
Dates.now(),
|
||||
)
|
||||
|
||||
return instance
|
||||
end
|
||||
|
||||
|
||||
""" Generate msgMeta to be including in a message. So the message receiver know
|
||||
what to do with the message.
|
||||
|
||||
Arguments\n
|
||||
-----
|
||||
sendTopic::String
|
||||
topic the sender sends to e.g. "/agent/wine/api/v1/prompt"
|
||||
|
||||
Keyword Arguments\n
|
||||
-----
|
||||
msgPurpose::String
|
||||
purpose of this message e.g. "updateStatus"
|
||||
senderName::String
|
||||
sender name (String) e.g. "agent-wine-web-frontend"
|
||||
senderId::String
|
||||
sender id e.g. string(uuid4())
|
||||
receiverName::String
|
||||
msg receiver name (String) e.g. "agent-backend"
|
||||
receiverId::String
|
||||
msg receiver id, nothing means everyone in the topic e.g. string(uuid4())
|
||||
requestresponse::String
|
||||
this message is a "request" or "response"
|
||||
getpost::String
|
||||
this message is a "get" or "post"
|
||||
msgId::String
|
||||
message ID e.g. string(uuid4())
|
||||
timestamp::String
|
||||
message published timestamp e.g. string(Dates.now())
|
||||
senderselfnote::Any
|
||||
help sender manage incoming reply msg, could be "for text inference", "img generation"
|
||||
replyTopic::String
|
||||
sender ask receiver to reply to this topic e.g. "/agent/frontend"
|
||||
replyToMsgId::String
|
||||
sender is responding to this msg ID e.g. string(uuid4())
|
||||
acknowledgestatus::String
|
||||
e.g. "acknowledged"
|
||||
mqttBrokerAddress::String
|
||||
MQTT broker URI. e.g. "test.mosquitto.org"
|
||||
mqttBrokerPort::Integer
|
||||
MQTT broker port
|
||||
|
||||
Return\n
|
||||
-----
|
||||
msgMeta::Dict{Symbol, Any}
|
||||
|
||||
Example\n
|
||||
-----
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils
|
||||
julia> msgMeta = GeneralUtils.generate_msgMeta(
|
||||
"/agent/frontend/wine/chat/api/v1/txt/receive",
|
||||
msgPurpose="keepalive",
|
||||
senderName="keepaliveservice")
|
||||
```
|
||||
|
||||
Signature\n
|
||||
-----
|
||||
"""
|
||||
function generate_msgMeta(
|
||||
sendTopic::T1, # topic the sender sends to e.g. "/agent/wine/api/v1/prompt"
|
||||
;
|
||||
msgPurpose::Union{T1, Nothing}= nothing, # purpose of this message e.g. "updateStatus"
|
||||
senderName::Union{T1, Nothing}= nothing, # sender name (String) e.g. "agent-wine-web-frontend"
|
||||
senderId::Union{T1, Nothing}= nothing, # sender id e.g. string(uuid4())
|
||||
receiverName::Union{T1, Nothing}= nothing, # msg receiver name (String) e.g. "agent-backend"
|
||||
receiverId::Union{T1, Nothing}= nothing, # id of msg receiver, nothing means everyone in the topic e.g. string(uuid4())
|
||||
requestresponse::Union{T1, Nothing}= nothing, # this message is a "request" or "response"
|
||||
getpost::Union{T1, Nothing}= nothing, # this message is a "get" or "post"
|
||||
msgId::Union{T1, Nothing}= string(uuid4()),
|
||||
timestamp= string(Dates.now()), # message published timestamp
|
||||
|
||||
# help sender manage incoming reply msg, could be "for text inference", "img generation"
|
||||
senderselfnote::Any= nothing,
|
||||
|
||||
# sender ask receiver to reply to this topic
|
||||
# e.g. "/agent/frontend/wine/chat/api/v1/txt/receive"
|
||||
replyTopic::Union{T1, Nothing}= nothing,
|
||||
|
||||
replyToMsgId::Union{T1, Nothing}= nothing, # sender is responding to this msg ID
|
||||
acknowledgestatus::Union{T1, Nothing}= nothing, # "acknowledged",
|
||||
|
||||
mqttBrokerAddress::T1= "test.mosquitto.org",
|
||||
mqttBrokerPort::Integer= 1883,
|
||||
|
||||
msgFormatVersion::Union{T1, Nothing}= nothing,
|
||||
|
||||
)::Dict{Symbol, Any} where {T1<:AbstractString}
|
||||
|
||||
msgMeta::Dict=Dict(
|
||||
:sendTopic=> sendTopic, # topic the sender sends to e.g. "/agent/wine/api/v1/prompt"
|
||||
:msgPurpose=> msgPurpose, # purpose of this message e.g. "updateStatus"
|
||||
:senderName=> senderName, # sender name (String) e.g. "agent-wine-web-frontend"
|
||||
:senderId=> senderId, # sender id e.g. string(uuid4())
|
||||
:receiverName=> receiverName, # msg receiver name (String) e.g. "agent-backend"
|
||||
:receiverId=> receiverId, # msg receiver id, nothing means everyone in the topic e.g. string(uuid4())
|
||||
:requestResponse=> requestresponse, # this message is a "request" or "response"
|
||||
:getPost=> getpost, # this message is a "get" or "post"
|
||||
:msgId=> msgId,
|
||||
:timeStamp=> timestamp, # message published timestamp
|
||||
|
||||
# help sender manage incoming reply msg, could be "for text inference", "img generation"
|
||||
:senderSelfnote=>senderselfnote,
|
||||
|
||||
# sender ask receiver to reply to this topic
|
||||
# e.g. "/agent/frontend/wine/chat/api/v1/txt/receive"
|
||||
:replyTopic=> replyTopic,
|
||||
|
||||
:replyToMsgId=> replyToMsgId,
|
||||
:acknowledgestatus=> acknowledgestatus,
|
||||
|
||||
:mqttBrokerAddress=> mqttBrokerAddress,
|
||||
:mqttBrokerPort=> mqttBrokerPort,
|
||||
|
||||
:msgFormatVersion=> msgFormatVersion,
|
||||
)
|
||||
|
||||
return msgMeta
|
||||
end
|
||||
|
||||
|
||||
|
||||
""" Check mqtt server connection.
|
||||
|
||||
Arguments\n
|
||||
-----
|
||||
mqttInstanceDict::Dict{Symbol, Any}
|
||||
a dictionary contain mqtt instance. 1 per mqtt client.
|
||||
interval::Integer
|
||||
time interval to check mqtt server in seconds
|
||||
|
||||
Return\n
|
||||
-----
|
||||
isconnectionalive::Bool
|
||||
true if mqtt connection is alive
|
||||
|
||||
Example\n
|
||||
-----
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils, Dates, JSON3, MQTTClient
|
||||
julia> mqttMsgReceiveChannel = Channel(8)
|
||||
julia> keepaliveChannel = Channel(8)
|
||||
julia> function onMsgCallback(topic, payload)
|
||||
jobj = JSON3.read(String(payload))
|
||||
onMsg = copy(jobj)
|
||||
put!(mqttMsgReceiveChannel, onMsg)
|
||||
end
|
||||
julia> mqttInstanceDict = Dict{Symbol, Any}(
|
||||
:mqttServerInfo=> Dict(
|
||||
:broker=> "test.mosquitto.org",
|
||||
:port=> 1883,
|
||||
),
|
||||
:onMsgCallback=> onMsgCallback,
|
||||
:client=> nothing,
|
||||
:connection=> nothing,
|
||||
:subtopic=> ["/servicetopic", "/keepalivetopic"],
|
||||
:keepalivetopic=> "/keepalive",
|
||||
:keepaliveChannel=> keepaliveChannel,
|
||||
:lastTimeMqttConnCheck=> Dates.now(),
|
||||
)
|
||||
julia> mqttInstanceDict[:client], mqttInstanceDict[:connection] =
|
||||
MakeConnection(mqttInstanceDict[:mqttServerInfo][:broker],
|
||||
mqttInstanceDict[:mqttServerInfo][:port])
|
||||
julia> connect(mqttInstanceDict[:client], mqttInstanceDict[:connection])
|
||||
julia> mqttConnStatus = GeneralUtils.isMqttConnectionAlive(mqttInstanceDict)
|
||||
julia> println(mqttConnStatus)
|
||||
```
|
||||
|
||||
Signature\n
|
||||
-----
|
||||
"""
|
||||
function isMqttConnectionAlive(mqttInstanceDict::Dict{Symbol, Any})::Bool
|
||||
|
||||
starttime = Dates.now()
|
||||
isconnectionalive = false
|
||||
|
||||
# ditch old keepalive msg is any
|
||||
while isready(mqttInstanceDict[:keepaliveChannel])
|
||||
_ = take!(mqttInstanceDict[:keepaliveChannel])
|
||||
end
|
||||
|
||||
msgMeta = generate_msgMeta(
|
||||
mqttInstanceDict[:keepalivetopic],
|
||||
msgPurpose= "keepalive",
|
||||
)
|
||||
|
||||
keepaliveMsg = Dict(
|
||||
:msgMeta => msgMeta,
|
||||
:text=>"keepalive",
|
||||
)
|
||||
|
||||
publish(mqttInstanceDict[:client], keepaliveMsg[:msgMeta][:sendTopic],
|
||||
JSON3.write(keepaliveMsg))
|
||||
|
||||
timediff = 0
|
||||
while timediff < 5
|
||||
timediff = timedifference(starttime, Dates.now(), "seconds")
|
||||
if isready(mqttInstanceDict[:keepaliveChannel])
|
||||
incomingMsg = take!(mqttInstanceDict[:keepaliveChannel])
|
||||
if incomingMsg[:msgMeta][:msgId] == keepaliveMsg[:msgMeta][:msgId]
|
||||
# connection is alive
|
||||
isconnectionalive = true
|
||||
break
|
||||
end
|
||||
end
|
||||
sleep(1)
|
||||
end
|
||||
return isconnectionalive
|
||||
end
|
||||
function isMqttConnectionAlive(mqttInstance::mqttClientInstance)::Bool
|
||||
|
||||
starttime = Dates.now()
|
||||
isconnectionalive = false
|
||||
|
||||
# ditch old keepalive msg is any
|
||||
while isready(mqttInstance.keepaliveChannel)
|
||||
_ = take!(mqttInstance.keepaliveChannel)
|
||||
end
|
||||
|
||||
msgMeta = generate_msgMeta(
|
||||
mqttInstance.keepalivetopic,
|
||||
msgPurpose= "keepalive",
|
||||
)
|
||||
|
||||
keepaliveMsg = Dict(
|
||||
:msgMeta => msgMeta,
|
||||
:text=>"keepalive",
|
||||
)
|
||||
|
||||
publish(mqttInstance.client, keepaliveMsg[:msgMeta][:sendTopic],
|
||||
JSON3.write(keepaliveMsg))
|
||||
|
||||
timediff = 0
|
||||
while timediff < 5
|
||||
timediff = timedifference(starttime, Dates.now(), "seconds")
|
||||
if isready(mqttInstance.keepaliveChannel)
|
||||
incomingMsg = take!(mqttInstance.keepaliveChannel)
|
||||
if incomingMsg[:msgMeta][:msgId] == keepaliveMsg[:msgMeta][:msgId]
|
||||
# connection is alive
|
||||
isconnectionalive = true
|
||||
break
|
||||
end
|
||||
end
|
||||
sleep(1)
|
||||
end
|
||||
return isconnectionalive
|
||||
end
|
||||
|
||||
|
||||
""" Check mqtt server connection, reconnect if disconnected.
|
||||
|
||||
Arguments\n
|
||||
-----
|
||||
mqttInstanceDict::Dict{Symbol, Any}
|
||||
a dictionary contain mqtt instance. 1 per mqtt client.
|
||||
interval::Integer
|
||||
time interval to check mqtt server in seconds
|
||||
|
||||
Return\n
|
||||
-----
|
||||
isreconnect::Bool
|
||||
true if mqtt connection is reconnected
|
||||
|
||||
Example\n
|
||||
-----
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils, Dates, JSON3, MQTTClient
|
||||
julia> mqttMsgReceiveChannel = Channel(8)
|
||||
julia> keepaliveChannel = Channel(8)
|
||||
julia> function onMsgCallback(topic, payload)
|
||||
jobj = JSON3.read(String(payload))
|
||||
onMsg = copy(jobj)
|
||||
put!(mqttMsgReceiveChannel, onMsg)
|
||||
end
|
||||
julia> mqttInstanceDict = Dict{Symbol, Any}(
|
||||
:mqttServerInfo=> Dict(
|
||||
:broker=> "test.mosquitto.org",
|
||||
:port=> 1883,
|
||||
),
|
||||
:onMsgCallback=> onMsgCallback, # function
|
||||
:client=> nothing,
|
||||
:connection=> nothing,
|
||||
:subtopic=> ["/servicetopic", "/sometopic"], # Vector{String}
|
||||
:keepalivetopic=> "/keepalive",
|
||||
:keepaliveChannel=> keepaliveChannel, # Channel{Dict}
|
||||
:lastTimeMqttConnCheck=> Dates.now(),
|
||||
)
|
||||
julia> mqttInstanceDict[:client], mqttInstanceDict[:connection] =
|
||||
MakeConnection(mqttInstanceDict[:mqttServerInfo][:broker],
|
||||
mqttInstanceDict[:mqttServerInfo][:port])
|
||||
julia> connect(mqttInstanceDict[:client], mqttInstanceDict[:connection])
|
||||
julia> GeneralUtils.checkMqttConnection!(mqttInstanceDict, 5)
|
||||
```
|
||||
|
||||
Signature\n
|
||||
-----
|
||||
"""
|
||||
function checkMqttConnection!(mqttInstanceDict::Dict{Symbol, Any}, interval::Integer)::Bool
|
||||
isreconnect = false
|
||||
|
||||
# check if mqtt connection is still up
|
||||
intervaldiff = timedifference(mqttInstanceDict[:lastTimeMqttConnCheck], Dates.now(), "seconds")
|
||||
|
||||
if intervaldiff > interval
|
||||
while true
|
||||
mqttConnStatus = isMqttConnectionAlive(mqttInstanceDict)
|
||||
if mqttConnStatus == false
|
||||
println("Attemping to reconnect")
|
||||
# use new client to reconnect instead of the previous one because I don't want to modify MQTTClient.jl yet
|
||||
mqttInstanceDict[:client], mqttInstanceDict[:connection] =
|
||||
MakeConnection(mqttInstanceDict[:mqttServerInfo][:broker],
|
||||
mqttInstanceDict[:mqttServerInfo][:port])
|
||||
connect(mqttInstanceDict[:client], mqttInstanceDict[:connection])
|
||||
for topic in mqttInstanceDict[:subtopic]
|
||||
subscribe(mqttInstanceDict[:client], topic, mqttInstanceDict[:onMsgCallback], qos=QOS_1)
|
||||
end
|
||||
isreconnect = true
|
||||
println("reconnected")
|
||||
else
|
||||
mqttInstanceDict[:lastTimeMqttConnCheck] = Dates.now()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return isreconnect
|
||||
end
|
||||
function checkMqttConnection!(mqttInstance::mqttClientInstance;
|
||||
keepaliveCheckInterval::Union{Integer, Nothing}=nothing)::Bool
|
||||
|
||||
interval = keepaliveCheckInterval !== nothing ? keepaliveCheckInterval : mqttInstance.keepaliveCheckInterval
|
||||
isreconnect = false
|
||||
|
||||
# check if mqtt connection is still up
|
||||
intervaldiff = timedifference(mqttInstance.lastTimeMqttConnCheck, Dates.now(), "seconds")
|
||||
|
||||
if intervaldiff > interval
|
||||
while true
|
||||
mqttConnStatus = isMqttConnectionAlive(mqttInstance)
|
||||
if mqttConnStatus == false
|
||||
println("Attemping to reconnect")
|
||||
# use new client to reconnect instead of the previous one because I don't want to modify MQTTClient.jl yet
|
||||
mqttInstance.client, mqttInstance.connection =
|
||||
MakeConnection(mqttInstance.mqttBrokerAddress,
|
||||
mqttInstance.mqttBrokerPort)
|
||||
connect(mqttInstance.client, mqttInstance.connection)
|
||||
for topic in mqttInstance.subtopic
|
||||
subscribe(mqttInstance.client, topic, mqttInstance.onMsgCallback, qos=mqttInstance.qos)
|
||||
end
|
||||
MQTTClient.subscribe(mqttInstance.client, mqttInstance.keepalivetopic, mqttInstance.onMsgCallback, qos=mqttInstance.qos)
|
||||
isreconnect = true
|
||||
println("reconnected")
|
||||
else
|
||||
mqttInstance.lastTimeMqttConnCheck = Dates.now()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return isreconnect
|
||||
end
|
||||
|
||||
|
||||
""" Send a message to specified MQTT topic then wait for reply.
|
||||
|
||||
# Arguments
|
||||
- `outgoingMsg::Dict`
|
||||
an outgoing message
|
||||
|
||||
# Return
|
||||
- `result::NamedTuple`
|
||||
( isDone= true, error= nothing )
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils, Dates, JSON3, UUIDs
|
||||
julia> msgMeta = GeneralUtils.generate_msgMeta(
|
||||
"/testtopic",
|
||||
senderName= "somename",
|
||||
senderId= string(uuid4()),
|
||||
mqttBrokerAddress= "test.mosquitto.org",
|
||||
mqttBrokerPort= 1883,
|
||||
)
|
||||
julia> outgoingMsg = Dict(
|
||||
:msgMeta=> msgMeta,
|
||||
:payload=> Dict("hello"=> "World"),
|
||||
)
|
||||
julia> isDone, error = GeneralUtils.sendMqttMsg(outgoingMsg)
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function sendMqttMsg(outgoingMsg::Dict{Symbol, T})::NamedTuple where {T<:Any}
|
||||
try
|
||||
# Instantiate a client and connection.
|
||||
client, connection = MakeConnection(
|
||||
outgoingMsg[:msgMeta][:mqttBrokerAddress],
|
||||
outgoingMsg[:msgMeta][:mqttBrokerPort])
|
||||
connect(client, connection)
|
||||
|
||||
publish(client, outgoingMsg[:msgMeta][:sendTopic], JSON3.write(outgoingMsg))
|
||||
# disconnect mqtt
|
||||
disconnect(client)
|
||||
return (isDone=true, error=nothing)
|
||||
catch e
|
||||
return (isDone=false, error=e)
|
||||
end
|
||||
end
|
||||
|
||||
function sendMqttMsg(mqttInstance::mqttClientInstance, outgoingMsg::Dict{Symbol, T}
|
||||
)::NamedTuple where {T<:Any}
|
||||
try
|
||||
publish(mqttInstance.client, outgoingMsg[:msgMeta][:sendTopic], JSON3.write(outgoingMsg))
|
||||
# disconnect mqtt
|
||||
disconnect(client)
|
||||
return (isDone=true, error=nothing)
|
||||
catch e
|
||||
return (isDone=false, error=e)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
""" Send a message to specified MQTT topic then wait for reply.
|
||||
|
||||
# Arguments
|
||||
- `outgoingMsg::Dict{Symbol, T}`
|
||||
an outgoing message
|
||||
|
||||
# Keyword Arguments
|
||||
- `timeout::Integer`
|
||||
time to wait for a response before error
|
||||
|
||||
# Return
|
||||
- `result::NamedTuple`
|
||||
(
|
||||
isDone= true, # idicates whether sending MQTT message successful
|
||||
error= nothing # error message
|
||||
response= somemessage # response message
|
||||
)
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils, Dates, UUIDs
|
||||
julia> msgMeta = GeneralUtils.generate_msgMeta(
|
||||
"/testtopic",
|
||||
senderName= "somename",
|
||||
senderId= string(uuid4()),
|
||||
mqttBrokerAddress= "mqtt.yiem.cc",
|
||||
mqttBrokerPort= 1883,
|
||||
)
|
||||
julia> outgoingMsg = Dict(
|
||||
:msgMeta=> msgMeta,
|
||||
:payload=> Dict(:hello=> "World"),
|
||||
)
|
||||
julia> isDone, error, response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function sendReceiveMqttMsg(outgoingMsg::Dict{Symbol, T};
|
||||
timeout::Integer=60, maxattempt::Integer=1)::NamedTuple where {T<:Any}
|
||||
|
||||
mqttMsgReceiveTopic = "/GeneralUtils_sendReceiveMqttMsg/$(outgoingMsg[:msgMeta][:senderId])"
|
||||
mqttMsgReceiveChannel = Channel(4)
|
||||
|
||||
# ask message receiver to send a message back to specified topic
|
||||
outgoingMsg[:msgMeta][:replyTopic] = mqttMsgReceiveTopic
|
||||
|
||||
# Define the callback for receiving messages.
|
||||
function onMsgCallback(topic, payload)
|
||||
jobj = JSON3.read(String(payload))
|
||||
onMsg = copy(jobj)
|
||||
put!(mqttMsgReceiveChannel, onMsg)
|
||||
end
|
||||
|
||||
# Instantiate a client and connection.
|
||||
client, connection = MakeConnection(outgoingMsg[:msgMeta][:mqttBrokerAddress],
|
||||
outgoingMsg[:msgMeta][:mqttBrokerPort])
|
||||
connect(client, connection)
|
||||
subscribe(client, mqttMsgReceiveTopic, onMsgCallback, qos=QOS_1)
|
||||
|
||||
timepass = nothing
|
||||
attempts = 1
|
||||
while attempts <= maxattempt
|
||||
publish(client, outgoingMsg[:msgMeta][:sendTopic], JSON3.write(outgoingMsg))
|
||||
|
||||
starttime = Dates.now()
|
||||
while true
|
||||
timepass = timedifference(starttime, Dates.now(), "seconds")
|
||||
|
||||
if timepass <= timeout
|
||||
if isready(mqttMsgReceiveChannel)
|
||||
incomingMsg = take!(mqttMsgReceiveChannel)
|
||||
|
||||
if incomingMsg[:msgMeta][:replyToMsgId] == outgoingMsg[:msgMeta][:msgId]
|
||||
# disconnect mqtt
|
||||
unsubscribe(client, mqttMsgReceiveTopic)
|
||||
disconnect(client)
|
||||
return (isDone=true, error=nothing, response=incomingMsg[:payload])
|
||||
end
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
sleep(1)
|
||||
end
|
||||
attempts += 1
|
||||
println("attempts $attempts ", @__FILE__, " ", @__LINE__)
|
||||
end
|
||||
|
||||
# disconnect mqtt
|
||||
unsubscribe(client, mqttMsgReceiveTopic)
|
||||
disconnect(client)
|
||||
return (isDone=false, error="no response, timeout $timepass/$timeout, $attempts publish attempted",
|
||||
response=nothing)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end # module communication
|
||||
1186
src/interface.jl
Normal file
1186
src/interface.jl
Normal file
File diff suppressed because it is too large
Load Diff
261
src/llmUtil.jl
Normal file
261
src/llmUtil.jl
Normal file
@@ -0,0 +1,261 @@
|
||||
module llmUtil
|
||||
|
||||
export formatLLMtext, formatLLMtext_llama3instruct, jsoncorrection
|
||||
|
||||
using UUIDs, JSON3, Dates
|
||||
using GeneralUtils
|
||||
|
||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||
|
||||
|
||||
""" Convert a single chat dictionary into LLM model instruct format.
|
||||
|
||||
# Llama 3 instruct format example
|
||||
<|begin_of_text|>
|
||||
<|start_header_id|>system<|end_header_id|>
|
||||
You are a helpful assistant.
|
||||
<|eot_id|>
|
||||
<|start_header_id|>user<|end_header_id|>
|
||||
Get me an icecream.
|
||||
<|eot_id|>
|
||||
<|start_header_id|>assistant<|end_header_id|>
|
||||
Go buy it yourself at 7-11.
|
||||
<|eot_id|>
|
||||
|
||||
# Arguments
|
||||
- `name::T`
|
||||
message owner name e.f. "system", "user" or "assistant"
|
||||
- `text::T`
|
||||
|
||||
# Return
|
||||
- `formattedtext::String`
|
||||
text formatted to model format
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using YiemAgent
|
||||
julia> d = Dict(:name=> "system",:text=> "You are a helpful, respectful and honest assistant.",)
|
||||
julia> formattedtext = YiemAgent.formatLLMtext_llama3instruct(d[:name], d[:text])
|
||||
"<|begin_of_text|>\n <|start_header_id|>system<|end_header_id|>\n You are a helpful, respectful and honest assistant.\n <|eot_id|>\n"
|
||||
```
|
||||
|
||||
Signature
|
||||
"""
|
||||
function formatLLMtext_llama3instruct(name::T, text::T) where {T<:AbstractString}
|
||||
formattedtext =
|
||||
if name == "system"
|
||||
"""
|
||||
<|start_header_id|>$name<|end_header_id|>
|
||||
$text
|
||||
<|eot_id|>
|
||||
"""
|
||||
else
|
||||
"""
|
||||
<|start_header_id|>$name<|end_header_id|>
|
||||
$text
|
||||
<|eot_id|>
|
||||
"""
|
||||
end
|
||||
|
||||
return formattedtext
|
||||
end
|
||||
# function formatLLMtext_llama3instruct(name::T, text::T) where {T<:AbstractString}
|
||||
# formattedtext =
|
||||
# if name == "system"
|
||||
# """
|
||||
# <|begin_of_text|>
|
||||
# <|start_header_id|>$name<|end_header_id|>
|
||||
# $text
|
||||
# <|eot_id|>
|
||||
# """
|
||||
# else
|
||||
# """
|
||||
# <|start_header_id|>$name<|end_header_id|>
|
||||
# $text
|
||||
# <|eot_id|>
|
||||
# """
|
||||
# end
|
||||
|
||||
# return formattedtext
|
||||
# end
|
||||
|
||||
|
||||
|
||||
""" Convert a chat messages in vector of dictionary into LLM model instruct format.
|
||||
|
||||
# Arguments
|
||||
- `messages::Vector{Dict{Symbol, T}}`
|
||||
message owner name e.f. "system", "user" or "assistant"
|
||||
- `formatname::T`
|
||||
format name to be used
|
||||
|
||||
# Return
|
||||
- `formattedtext::String`
|
||||
text formatted to model format
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using YiemAgent
|
||||
julia> chatmessage = [
|
||||
Dict(:name=> "system",:text=> "You are a helpful, respectful and honest assistant.",),
|
||||
Dict(:name=> "user",:text=> "list me all planets in our solar system.",),
|
||||
Dict(:name=> "assistant",:text=> "I'm sorry. I don't know. You tell me.",),
|
||||
]
|
||||
julia> formattedtext = YiemAgent.formatLLMtext(chatmessage, "llama3instruct")
|
||||
"<|begin_of_text|>\n <|start_header_id|>system<|end_header_id|>\n You are a helpful, respectful and honest assistant.\n <|eot_id|>\n <|start_header_id|>user<|end_header_id|>\n list me all planets in our solar system.\n <|eot_id|>\n <|start_header_id|>assistant<|end_header_id|>\n I'm sorry. I don't know. You tell me.\n <|eot_id|>\n"
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function formatLLMtext(messages::Vector{Dict{Symbol, T}}; formatname::String="llama3instruct"
|
||||
)::String where {T<:Any}
|
||||
f = if formatname == "llama3instruct"
|
||||
formatLLMtext_llama3instruct
|
||||
elseif formatname == "mistral"
|
||||
# not define yet
|
||||
elseif formatname == "phi3instruct"
|
||||
formatLLMtext_phi3instruct
|
||||
else
|
||||
error("$formatname template not define yet")
|
||||
end
|
||||
|
||||
str = ""
|
||||
for t in messages
|
||||
str *= f(t[:name], t[:text])
|
||||
end
|
||||
|
||||
# add <|assistant|> so that the model don't generate it and I don't need to clean it up later
|
||||
if formatname == "phi3instruct"
|
||||
str *= "<|assistant|>\n"
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
""" Attemp to correct LLM response's incorrect JSON response.
|
||||
|
||||
# Arguments
|
||||
- `a::T1`
|
||||
one of Yiem's agent
|
||||
- `input::T2`
|
||||
text to be send to virtual wine customer
|
||||
|
||||
# Return
|
||||
- `correctjson::String`
|
||||
corrected json string
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia>
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function jsoncorrection(config::T1, input::T2, correctJsonExample::T3;
|
||||
maxattempt::Integer=3
|
||||
) where {T1<:AbstractDict, T2<:AbstractString, T3<:AbstractString}
|
||||
|
||||
incorrectjson = deepcopy(input)
|
||||
correctjson = nothing
|
||||
|
||||
for attempt in 1:maxattempt
|
||||
try
|
||||
d = copy(JSON3.read(incorrectjson))
|
||||
correctjson = incorrectjson
|
||||
return correctjson
|
||||
catch e
|
||||
@warn "Attempting to correct JSON string. Attempt $attempt"
|
||||
e = """$e"""
|
||||
if occursin("EOF", e)
|
||||
e = split(e, "EOF")[1] * "EOF"
|
||||
end
|
||||
incorrectjson = deepcopy(input)
|
||||
_prompt =
|
||||
"""
|
||||
Your goal are:
|
||||
1) Use the expected JSON format as a guideline to check why the given JSON string failed to load and provide a corrected version that can be loaded by Python's json.load function.
|
||||
2) Provide Corrected JSON string only. Do not provide any other info.
|
||||
|
||||
$correctJsonExample
|
||||
|
||||
Let's begin!
|
||||
Given JSON string: $incorrectjson
|
||||
The given JSON string failed to load previously because: $e
|
||||
Corrected JSON string:
|
||||
"""
|
||||
|
||||
# apply LLM specific instruct format
|
||||
externalService = config[:externalservice][:text2textinstruct]
|
||||
llminfo = externalService[:llminfo]
|
||||
prompt =
|
||||
if llminfo[:name] == "llama3instruct"
|
||||
formatLLMtext_llama3instruct("system", _prompt)
|
||||
else
|
||||
error("llm model name is not defied yet $(@__LINE__)")
|
||||
end
|
||||
|
||||
# send formatted input to user using GeneralUtils.sendReceiveMqttMsg
|
||||
msgMeta = GeneralUtils.generate_msgMeta(
|
||||
externalService[:mqtttopic],
|
||||
senderName= "jsoncorrection",
|
||||
senderId= string(uuid4()),
|
||||
receiverName= "text2textinstruct",
|
||||
mqttBroker= config[:mqttServerInfo][:broker],
|
||||
mqttBrokerPort= config[:mqttServerInfo][:port],
|
||||
)
|
||||
|
||||
outgoingMsg = Dict(
|
||||
:msgMeta=> msgMeta,
|
||||
:payload=> Dict(
|
||||
:text=> prompt,
|
||||
:kwargs=> Dict(
|
||||
:max_tokens=> 512,
|
||||
:stop=> ["<|eot_id|>"],
|
||||
)
|
||||
)
|
||||
)
|
||||
result = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120)
|
||||
incorrectjson = result[:response][:text]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end # module llmUtil
|
||||
387
src/util.jl
Normal file
387
src/util.jl
Normal file
@@ -0,0 +1,387 @@
|
||||
module util
|
||||
|
||||
export timedifference, showstracktrace, findHighestIndexKey, uuid4snakecase, replaceDictKeys,
|
||||
findMatchingDictKey, textToDict, randstring, randstrings
|
||||
|
||||
using JSON3, DataStructures, Distributions, Random, Dates, UUIDs, MQTTClient
|
||||
|
||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||
|
||||
""" Compute time different between start time and stop time in a given unit.
|
||||
Unit can be "milliseconds", "seconds", "minutes", "hours".
|
||||
|
||||
# Arguments
|
||||
- `starttime::DateTime`
|
||||
start time
|
||||
- `stoptime::DateTime`
|
||||
stop time
|
||||
- `unit::String`
|
||||
unit of time difference
|
||||
|
||||
# Return
|
||||
- time difference in given unit
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils, Dates
|
||||
julia> a = Dates.now()
|
||||
julia> b = a + Dates.Day(5) # add 5 days
|
||||
julia> GeneralUtils.timedifference(a, b, "hours")
|
||||
120
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function timedifference(starttime::DateTime, stoptime::DateTime, unit::String)::Integer
|
||||
diff = stoptime - starttime
|
||||
unit = lowercase(unit)
|
||||
|
||||
if unit == "milliseconds"
|
||||
return diff.value
|
||||
elseif unit == "seconds"
|
||||
return diff.value ÷ 1000
|
||||
elseif unit == "minutes"
|
||||
return diff.value ÷ (1000 * 60)
|
||||
elseif unit == "hours"
|
||||
return diff.value ÷ (1000 * 60 * 60)
|
||||
else
|
||||
error("Invalid unit specified. Please choose from: milliseconds, seconds, minutes, hours")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
""" Capture then show error and stacktrace
|
||||
|
||||
# Arguments
|
||||
- `f::Function`
|
||||
a function that might throws an error
|
||||
- `args` function f arguments
|
||||
|
||||
# Return
|
||||
- `outcome::NamedTuple`
|
||||
(success, result, errormsg, st)
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils, PrettyPrinting
|
||||
julia> testf(a, b) = a + b
|
||||
julia> success, result, errormsg, st = GeneralUtils.showstracktrace(testf, 5, "6")
|
||||
julia> pprint(st)
|
||||
16-element Vector{Base.StackTraces.StackFrame}:
|
||||
testf(a::Int64, b::String) at REPL[12]:1
|
||||
showstracktrace(::Function, ::Int64, ::Vararg{Any}) at util.jl:95
|
||||
...
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function showstracktrace(f::Function, args...)::NamedTuple
|
||||
global st = nothing # stacktrace
|
||||
global errorMsg = nothing
|
||||
global success = false
|
||||
global fResult = nothing
|
||||
|
||||
try
|
||||
success, fResult
|
||||
fResult = f(args...)
|
||||
success = true
|
||||
catch e
|
||||
io = IOBuffer()
|
||||
showerror(io, e)
|
||||
errorMsg = String(take!(io))
|
||||
|
||||
st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
|
||||
@warn "Error occurred: $errorMsg\n$st"
|
||||
end
|
||||
|
||||
return (success=success, result=fResult, errormsg=errorMsg, st=st)
|
||||
end
|
||||
|
||||
""" Find all match key of a dictionary for a given key.
|
||||
|
||||
# Arguments
|
||||
- `d<:AbstractDict`
|
||||
The dictionary to search for keys.
|
||||
- `text<:Symbol`
|
||||
The text to match against the keys.
|
||||
|
||||
# Returns
|
||||
- `result::Vector{Symbol}`
|
||||
A vector of matched key
|
||||
|
||||
|
||||
# Examples
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils
|
||||
julia> d = Dict(:key_1 => "apple", :key_12 => "banana", :key_3 => "cherry")
|
||||
julia> GeneralUtils.findMatchingDictKey(d, "key_1")
|
||||
2-element Vector{Symbol}:
|
||||
:key_1
|
||||
:key_12
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function findMatchingDictKey(d::T, text::Union{String, Symbol}
|
||||
)::Vector{Symbol} where {T<:AbstractDict}
|
||||
|
||||
_matching_keys = filter(k -> occursin(string(text), string(k)), keys(d))
|
||||
matching_keys = collect(_matching_keys) # convert from Set into Array
|
||||
|
||||
return matching_keys
|
||||
end
|
||||
|
||||
|
||||
"""
|
||||
Find the key in a dictionary `d` with the highest index value that matches a given `text`.
|
||||
|
||||
# Arguments
|
||||
- `d<:AbstractDict`
|
||||
The dictionary to search for keys.
|
||||
- `text<:Union{String, Symbol}`
|
||||
The text to match against the keys.
|
||||
|
||||
# Returns
|
||||
- `NamedTuple{(:result, :maxindice), Tuple{Union{Symbol, Nothing}, Union{Integer, Nothing}}}`
|
||||
The key in `d` with the highest index value that matches `text`, or `nothing` if no matches are found.
|
||||
|
||||
# Examples
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils
|
||||
julia> d = Dict(:key_1 => "apple", :key_2 => "banana", :key_3 => "cherry")
|
||||
julia> GeneralUtils.findHighestIndexKey(d, "key")
|
||||
(:key_3, 3)
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function findHighestIndexKey(d::T, text::Union{String, Symbol}
|
||||
)::NamedTuple{(:result, :maxindice), Tuple{Union{Symbol, Nothing}, Union{Integer, Nothing}}} where {T<:AbstractDict}
|
||||
|
||||
matching_keys = findMatchingDictKey(d, text)
|
||||
|
||||
if isempty(matching_keys)
|
||||
return (result=nothing, maxindice=nothing)
|
||||
elseif length(matching_keys) == 1 && matching_keys[1] == Symbol(text)
|
||||
return (result=Symbol(text), maxindice=nothing)
|
||||
else
|
||||
indices = parse.(Int, replace.(string.(matching_keys), r"[^\d]" => ""))
|
||||
maxIndexKey = matching_keys[argmax(indices)]
|
||||
return (result=maxIndexKey, maxindice=maximum(indices))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
""" Get uuid4 with snake case
|
||||
|
||||
# Return
|
||||
- `uuid4::String`
|
||||
uuid4 with snake case
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils
|
||||
julia> GeneralUtils.uuid4snakecase()
|
||||
"0f6e4f_568c_4df4_8c79_1d7a58072f4a"
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function uuid4snakecase()::String
|
||||
_id = string(uuid4())
|
||||
id = replace(_id, "-" => "_")
|
||||
return id
|
||||
end
|
||||
|
||||
|
||||
""" Replace a dictionary key with the new key
|
||||
|
||||
# Arguments
|
||||
- `d::Dict`
|
||||
The input dictionary that you want to modify
|
||||
- `replacementMap::Dict`
|
||||
A dictionary that maps old keys to new keys
|
||||
|
||||
# Return
|
||||
- `newDict::Dict`
|
||||
new dictionary with the replaced keys
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> using Revise
|
||||
julia> using GeneralUtils
|
||||
julia> d = Dict(:a => 1, :b => 2, :c => 3)
|
||||
julia> replacement_map = Dict(:a => :x, :b => :y)
|
||||
julia> new_dict = GeneralUtils.replaceDictKeys(d, replacement_map)
|
||||
Dict{Any, Any} with 3 entries:
|
||||
:y => 2
|
||||
:c => 3
|
||||
:x => 1
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function replaceDictKeys(d::Dict, replacementMap::Dict)::Dict
|
||||
newDict = Dict()
|
||||
for (key, value) in d
|
||||
newKey = get(replacementMap, key, key) # Get the replacement key if it exists, otherwise keep the original key
|
||||
newDict[newKey] = value
|
||||
end
|
||||
return newDict
|
||||
end
|
||||
|
||||
|
||||
""" Convert text into a dictionary with a given keywords. This function use keywords to slice
|
||||
a given text into the following format: KW1|kw1_text|KW2|kw2_text|KW3|kw3_text.
|
||||
The left most string which has no keyword will be discarded. WARNING, ordering is important
|
||||
|
||||
# Arguments
|
||||
- `text::String`
|
||||
A text to be converted.
|
||||
- `keywords::Vector{String}`
|
||||
A list of keywords to be used to slice the text.
|
||||
These keywords also be the resulting dict keys.
|
||||
# Keyword Arguments
|
||||
- `rightmarker::String`
|
||||
A maker used to make a word to be unique. Ex, A keyword "plan" with rightmarker ":",
|
||||
the function will search for "plan:" otherwise the function will search for "plan".
|
||||
The marker will not be in the resulting dict keys.
|
||||
- `symbolkey::Bool`
|
||||
If true, resulting dict's key will be Symbols, otherwise string.
|
||||
- `lowercasekey::Bool`
|
||||
set resulting dict's key to be lowercase
|
||||
|
||||
# Return
|
||||
- `d::OrderedDict`
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> text = "TODAY thought: what to do plan: wake up and going out action: 1. wake up 2. eat 3. sleep"
|
||||
julia> sample_keywords = ["thought", "plan", "action"]
|
||||
julia> resultdict = GeneralUtils.textToDict(text, sample_keywords; rightmarker=":", symbolkey=true)
|
||||
julia> println(resultdict)
|
||||
OrderedCollections.OrderedDict{Any, Any}(:thought => "what to do",
|
||||
:plan => "wake up and going out",
|
||||
:action => "1. wake up 2. eat 3. sleep")
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function textToDict(text::String, keywords::Vector{String};
|
||||
rightmarker::Union{String, Nothing}=nothing, symbolkey::Bool=false, lowercasekey::Bool=false
|
||||
)::OrderedDict
|
||||
|
||||
od1, od2 =
|
||||
if symbolkey
|
||||
OrderedDict{Symbol, Any}(), OrderedDict{Symbol, Any}()
|
||||
else
|
||||
OrderedDict{String, Any}(), OrderedDict{String, Any}()
|
||||
end
|
||||
|
||||
remainingtext = text
|
||||
|
||||
for keyword in reverse(keywords)
|
||||
mkeyword = rightmarker !== nothing ? keyword * rightmarker : keyword
|
||||
|
||||
# Find the position of the keyword in the text
|
||||
keywordidx = findlast(mkeyword, remainingtext)
|
||||
|
||||
if keywordidx !== nothing
|
||||
substr = remainingtext[keywordidx[end]+1:end]
|
||||
str = string(strip(substr)) # Removes both leading and trailing whitespace.
|
||||
_key = lowercasekey == true ? lowercase(keyword) : keyword
|
||||
key = symbolkey == true ? Symbol(_key) : _key
|
||||
od1[key] = str
|
||||
remainingtext = remainingtext[1:keywordidx[1]-1]
|
||||
else
|
||||
error("""keyword "$keyword" not found in the provided text""")
|
||||
end
|
||||
end
|
||||
|
||||
keywords = lowercasekey == true ? lowercase.(keywords) : keywords
|
||||
|
||||
# correct the order
|
||||
for keyword in keywords
|
||||
key = symbolkey == true ? Symbol(keyword) : keyword
|
||||
od2[key] = od1[key]
|
||||
end
|
||||
|
||||
return od2
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
""" Generate a random string
|
||||
|
||||
# Arguments
|
||||
- `n::Integer`
|
||||
A number of string to be generated
|
||||
|
||||
# Return
|
||||
- `s::String`
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> result = randstring(5)
|
||||
"fysmp"
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
randstring(n::Integer)::String = String(rand('a':'z', n))
|
||||
|
||||
|
||||
""" Generate a random string in group
|
||||
|
||||
# Arguments
|
||||
- `totalgroup::Integer`
|
||||
A number of group of random string to be generated
|
||||
- `stringlength::Integer`
|
||||
A number of string to be generated
|
||||
|
||||
# Return
|
||||
- `s::String`
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> result = randstrings(3, 5)
|
||||
"fysmp cmhdk iuytr"
|
||||
```
|
||||
|
||||
# Signature
|
||||
"""
|
||||
function randstrings(totalgroup::Integer, stringlength::Integer)::String
|
||||
str = ""
|
||||
for i in 1:totalgroup
|
||||
str *= randstring(stringlength) * " "
|
||||
end
|
||||
str = strip(str)
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end # module util
|
||||
Reference in New Issue
Block a user