486 lines
14 KiB
Julia
486 lines
14 KiB
Julia
module util
|
|
|
|
export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
|
|
availableWineToText, createEventsLog, createChatLog, checkAgentResponse_JSON,
|
|
checkAgentResponse_text
|
|
|
|
using UUIDs, Dates, DataStructures, HTTP, JSON3
|
|
using GeneralUtils
|
|
using ..type
|
|
|
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
|
|
""" Clear agent chat history.
|
|
|
|
# Arguments
|
|
- `a::agent`
|
|
an agent
|
|
|
|
# Return
|
|
- nothing
|
|
|
|
# Example
|
|
```jldoctest
|
|
julia> using YiemAgent, MQTTClient, GeneralUtils
|
|
julia> client, connection = MakeConnection("test.mosquitto.org", 1883)
|
|
julia> connect(client, connection)
|
|
julia> msgMeta = GeneralUtils.generate_msgMeta("testtopic")
|
|
julia> agentConfig = Dict(
|
|
:receiveprompt=>Dict(
|
|
:mqtttopic=> "testtopic/receive",
|
|
),
|
|
:receiveinternal=>Dict(
|
|
:mqtttopic=> "testtopic/internal",
|
|
),
|
|
:text2text=>Dict(
|
|
:mqtttopic=> "testtopic/text2text",
|
|
),
|
|
)
|
|
julia> a = YiemAgent.sommelier(
|
|
client,
|
|
msgMeta,
|
|
agentConfig,
|
|
)
|
|
julia> YiemAgent.addNewMessage(a, "user", "hello")
|
|
julia> YiemAgent.clearhistory(a)
|
|
```
|
|
|
|
# TODO
|
|
- [PENDING] clear memory
|
|
|
|
# Signature
|
|
"""
|
|
function clearhistory(a::T) where {T<:agent}
|
|
empty!(a.chathistory)
|
|
empty!(a.memory[:shortmem])
|
|
empty!(a.memory[:events])
|
|
a.memory[:chatbox] = ""
|
|
end
|
|
|
|
|
|
""" Add new message to agent.
|
|
|
|
Arguments\n
|
|
-----
|
|
a::agent
|
|
an agent
|
|
role::String
|
|
message sender role i.e. system, user or assistant
|
|
text::String
|
|
message text
|
|
|
|
Return\n
|
|
-----
|
|
nothing
|
|
|
|
Example\n
|
|
-----
|
|
```jldoctest
|
|
julia> using YiemAgent, MQTTClient, GeneralUtils
|
|
julia> client, connection = MakeConnection("test.mosquitto.org", 1883)
|
|
julia> connect(client, connection)
|
|
julia> msgMeta = GeneralUtils.generate_msgMeta("testtopic")
|
|
julia> agentConfig = Dict(
|
|
:receiveprompt=>Dict(
|
|
:mqtttopic=> "testtopic/receive",
|
|
),
|
|
:receiveinternal=>Dict(
|
|
:mqtttopic=> "testtopic/internal",
|
|
),
|
|
:text2text=>Dict(
|
|
:mqtttopic=> "testtopic/text2text",
|
|
),
|
|
)
|
|
julia> a = YiemAgent.sommelier(
|
|
client,
|
|
msgMeta,
|
|
agentConfig,
|
|
)
|
|
julia> YiemAgent.addNewMessage(a, "user", "hello")
|
|
```
|
|
|
|
Signature\n
|
|
-----
|
|
"""
|
|
function addNewMessage(a::T1, name::String, text::T2;
|
|
maximumMsg::Integer=30) where {T1<:agent, T2<:AbstractString}
|
|
if name ∉ ["system", "user", "assistant"] # guard against typo
|
|
error("name is not in agent.availableRole $(@__LINE__)")
|
|
end
|
|
|
|
#[PENDING] summarize the oldest 10 message
|
|
if length(a.chathistory) > maximumMsg
|
|
summarize(a.chathistory)
|
|
else
|
|
d = Dict(:name=> name, :text=> text, :timestamp=> Dates.now())
|
|
push!(a.chathistory, d)
|
|
end
|
|
end
|
|
|
|
|
|
""" Converts a vector of dictionaries to a formatted string.
|
|
This function takes in a vector of dictionaries and outputs a single string where each dictionary's keys are prefixed by their values.
|
|
|
|
# Arguments
|
|
- `vecd::Vector`
|
|
A vector of dictionaries containing chat messages
|
|
- `withkey::Bool`
|
|
Whether to include the name as a prefix in the output text. Default is true
|
|
- `range::Union{Nothing,UnitRange,Int}`
|
|
Optional range of messages to include. If nothing, includes all messages
|
|
|
|
# Returns
|
|
A formatted string where each line contains either:
|
|
- If withkey=true: "name> message\n"
|
|
- If withkey=false: "message\n"
|
|
|
|
# Example
|
|
|
|
julia> using Revise
|
|
julia> using GeneralUtils
|
|
julia> vecd = [Dict(:name => "John", :text => "Hello"), Dict(:name => "Jane", :text => "Goodbye")]
|
|
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
|
|
"John> Hello\nJane> Goodbye\n"
|
|
```
|
|
"""
|
|
function chatHistoryToText(vecd::Vector; withkey=true, range=nothing)::String
|
|
# Initialize an empty string to hold the final text
|
|
text = ""
|
|
|
|
# Get the elements within the specified range, or all elements if no range provided
|
|
elements = isnothing(range) ? vecd : vecd[range]
|
|
|
|
# Determine whether to include the key in the output text or not
|
|
if withkey
|
|
# Loop through each dictionary in the input vector
|
|
for d in elements
|
|
# Extract the 'name' and 'text' keys from the dictionary
|
|
name = titlecase(d[:name])
|
|
_text = d[:text]
|
|
|
|
# Append the formatted string to the text variable
|
|
text *= "$name> $_text \n"
|
|
end
|
|
else
|
|
# Loop through each dictionary in the input vector
|
|
for d in elements
|
|
# Iterate over all key-value pairs in the dictionary
|
|
for (k, v) in d
|
|
# Append the formatted string to the text variable
|
|
text *= "$v \n"
|
|
end
|
|
end
|
|
end
|
|
|
|
# Return the final text
|
|
return text
|
|
end
|
|
|
|
|
|
function availableWineToText(vecd::Vector)::String
|
|
# Initialize an empty string to hold the final text
|
|
rowtext = ""
|
|
# Loop through each dictionary in the input vector
|
|
for (i, d) in enumerate(vecd)
|
|
# Iterate over all key-value pairs in the dictionary
|
|
temp = []
|
|
for (k, v) in d
|
|
# Append the formatted string to the text variable
|
|
t = "$k:$v"
|
|
push!(temp, t)
|
|
end
|
|
_rowtext = join(temp, ',')
|
|
rowtext *= "$i) $_rowtext "
|
|
end
|
|
|
|
return rowtext
|
|
end
|
|
|
|
|
|
|
|
""" Create a dictionary representing an event with optional details.
|
|
|
|
# Arguments
|
|
- `event_description::Union{String, Nothing}`
|
|
A description of the event
|
|
- `timestamp::Union{DateTime, Nothing}`
|
|
The time when the event occurred
|
|
- `subject::Union{String, Nothing}`
|
|
The subject or entity associated with the event
|
|
- `thought::Union{AbstractDict, Nothing}`
|
|
Any associated thoughts or metadata
|
|
- `actionname::Union{String, Nothing}`
|
|
The name of the action performed (e.g., "CHAT", "CHECKINVENTORY")
|
|
- `actioninput::Union{String, Nothing}`
|
|
Input or parameters for the action
|
|
- `location::Union{String, Nothing}`
|
|
Where the event took place
|
|
- `equipment_used::Union{String, Nothing}`
|
|
Equipment involved in the event
|
|
- `material_used::Union{String, Nothing}`
|
|
Materials used during the event
|
|
- `outcome::Union{String, Nothing}`
|
|
The result or consequence of the event after action execution
|
|
- `note::Union{String, Nothing}`
|
|
Additional notes or comments
|
|
|
|
# Returns
|
|
A dictionary with event details as symbol-keyed key-value pairs
|
|
"""
|
|
function eventdict(;
|
|
event_description::Union{String, Nothing}=nothing,
|
|
timestamp::Union{DateTime, Nothing}=nothing,
|
|
subject::Union{String, Nothing}=nothing,
|
|
thought::Union{AbstractDict, Nothing}=nothing,
|
|
actionname::Union{String, Nothing}=nothing, # "CHAT", "CHECKINVENTORY", "PRESENTBOX", etc
|
|
actioninput::Union{String, Nothing}=nothing,
|
|
location::Union{String, Nothing}=nothing,
|
|
equipment_used::Union{String, Nothing}=nothing,
|
|
material_used::Union{String, Nothing}=nothing,
|
|
observation::Union{String, Nothing}=nothing,
|
|
note::Union{String, Nothing}=nothing,
|
|
)
|
|
|
|
d = Dict{Symbol, Any}(
|
|
:event_description=> event_description,
|
|
:timestamp=> timestamp,
|
|
:subject=> subject,
|
|
:thought=> thought,
|
|
:actionname=> actionname,
|
|
:actioninput=> actioninput,
|
|
:location=> location,
|
|
:equipment_used=> equipment_used,
|
|
:material_used=> material_used,
|
|
:observation=> observation,
|
|
:note=> note,
|
|
)
|
|
|
|
return d
|
|
end
|
|
|
|
|
|
""" Create a formatted timeline string from a sequence of events.
|
|
|
|
# Arguments
|
|
- `events::T1`
|
|
Vector of event dictionaries containing subject, actioninput and optional outcome fields
|
|
Each event dictionary should have the following keys:
|
|
- :subject - The subject or entity performing the action
|
|
- :actioninput - The action or input performed by the subject
|
|
- :observation - (Optional) The result or outcome of the action
|
|
|
|
# Returns
|
|
- `timeline::String`
|
|
A formatted string representing the events with their subjects, actions, and optional outcomes
|
|
Format: "{index}) {subject}> {actioninput} {outcome}\n" for each event
|
|
|
|
# Example
|
|
|
|
events = [
|
|
Dict(:subject => "User", :actioninput => "Hello", :observation => nothing),
|
|
Dict(:subject => "Assistant", :actioninput => "Hi there!", :observation => "with a smile")
|
|
]
|
|
timeline = createTimeline(events)
|
|
# 1) User> Hello
|
|
# 2) Assistant> Hi there! with a smile
|
|
|
|
"""
|
|
function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
|
|
) where {T1<:AbstractVector}
|
|
# Initialize empty timeline string
|
|
timeline = ""
|
|
|
|
# Determine which indices to use - either provided range or full length
|
|
ind =
|
|
if eventindex !== nothing
|
|
[eventindex...]
|
|
else
|
|
1:length(events)
|
|
end
|
|
|
|
# Iterate through events and format each one
|
|
for i in ind
|
|
event = events[i]
|
|
# If no outcome exists, format without outcome
|
|
# if event[:actionname] == "CHATBOX"
|
|
# timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput])\n"
|
|
# elseif event[:actionname] == "CHECKINVENTORY" && event[:observation] === nothing
|
|
# timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput]), observation: Not done yet.\n"
|
|
# If outcome exists, include it in formatting
|
|
if event[:actionname] == "CHECKWINE"
|
|
timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput]), observation: $(event[:observation])\n"
|
|
else
|
|
timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput])\n"
|
|
end
|
|
end
|
|
|
|
# Return formatted timeline string
|
|
return timeline
|
|
end
|
|
# function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
|
|
# ) where {T1<:AbstractVector}
|
|
# # Initialize empty timeline string
|
|
# timeline = ""
|
|
|
|
# # Determine which indices to use - either provided range or full length
|
|
# ind =
|
|
# if eventindex !== nothing
|
|
# [eventindex...]
|
|
# else
|
|
# 1:length(events)
|
|
# end
|
|
|
|
# # Iterate through events and format each one
|
|
# for i in ind
|
|
# event = events[i]
|
|
# # If no outcome exists, format without outcome
|
|
# if event[:observation] === nothing
|
|
# timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput]), observation: Not done yet.\n"
|
|
# # If outcome exists, include it in formatting
|
|
# else
|
|
# timeline *= "Event_$i $(event[:subject])> actionname: $(event[:actionname]), actioninput: $(event[:actioninput]), observation: $(event[:observation])\n"
|
|
# end
|
|
# end
|
|
|
|
# # Return formatted timeline string
|
|
# return timeline
|
|
# end
|
|
|
|
|
|
function createEventsLog(events::T1; index::Union{UnitRange, Nothing}=nothing
|
|
) where {T1<:AbstractVector}
|
|
# Initialize empty log array
|
|
log = Dict{Symbol, String}[]
|
|
|
|
# Determine which indices to use - either provided range or full length
|
|
ind =
|
|
if index !== nothing
|
|
[index...]
|
|
else
|
|
1:length(events)
|
|
end
|
|
|
|
# Iterate through events and format each one
|
|
for i in ind
|
|
event = events[i]
|
|
# If no outcome exists, format without outcome
|
|
if event[:observation] === nothing
|
|
subject = event[:subject]
|
|
actionname = event[:actionname]
|
|
actioninput = event[:actioninput]
|
|
str = "actionname: $actionname, actioninput: $actioninput"
|
|
d = Dict{Symbol, String}(:name=>subject, :text=>str)
|
|
push!(log, d)
|
|
else
|
|
subject = event[:subject]
|
|
actionname = event[:actionname]
|
|
actioninput = event[:actioninput]
|
|
observation = event[:observation]
|
|
str = "actionname: $actionname, actioninput: $actioninput, observation: $observation"
|
|
d = Dict{Symbol, String}(:name=>subject, :text=>str)
|
|
push!(log, d)
|
|
end
|
|
end
|
|
|
|
return log
|
|
end
|
|
|
|
|
|
function createChatLog(chatdict::T1; index::Union{UnitRange, Nothing}=nothing
|
|
) where {T1<:AbstractVector}
|
|
# Initialize empty log array
|
|
log = Dict{Symbol, String}[]
|
|
|
|
# Determine which indices to use - either provided range or full length
|
|
ind =
|
|
if index !== nothing
|
|
[index...]
|
|
else
|
|
1:length(chatdict)
|
|
end
|
|
|
|
# Iterate through events and format each one
|
|
for i in ind
|
|
event = chatdict[i]
|
|
subject = event[:name]
|
|
text = event[:text]
|
|
d = Dict{Symbol, String}(:name=>subject, :text=>text)
|
|
push!(log, d)
|
|
end
|
|
|
|
return log
|
|
end
|
|
|
|
|
|
function checkAgentResponse_text(response::String, requiredHeader::T
|
|
)::Tuple where {T<:Array{String}}
|
|
detected_kw = GeneralUtils.detectKeywordVariation(requiredHeader, response)
|
|
missingkeys = [k for (k, v) in detected_kw if v === nothing]
|
|
ispass = false
|
|
errormsg = nothing
|
|
if !isempty(missingkeys)
|
|
errormsg = "$missingkeys are missing from your previous response"
|
|
ispass = false
|
|
elseif sum([length(i) for i in values(detected_kw)]) > length(requiredHeader)
|
|
errormsg = "Your previous attempt has duplicated points according to the required response format"
|
|
ispass = false
|
|
else
|
|
ispass = true
|
|
end
|
|
return (ispass, errormsg)
|
|
end
|
|
|
|
|
|
function checkAgentResponse_JSON(responsedict::Dict, requiredKeys::T
|
|
)::Tuple where {T<:Array{Symbol}}
|
|
_responsedictKey = keys(responsedict)
|
|
responsedictKey = [i for i in _responsedictKey] # convert into a list
|
|
is_requiredKeys_in_responsedictKey = [i ∈ responsedictKey for i in requiredKeys]
|
|
ispass = false
|
|
errormsg = nothing
|
|
if length(is_requiredKeys_in_responsedictKey) > length(requiredKeys)
|
|
errormsg = "Your previous attempt has duplicated points according to the required response format"
|
|
ispass = false
|
|
elseif !all(is_requiredKeys_in_responsedictKey)
|
|
zeroind = findall(x -> x == 0, is_requiredKeys_in_responsedictKey)
|
|
missingkeys = [requiredKeys[i] for i in zeroind]
|
|
errormsg = "$missingkeys are missing from your previous response"
|
|
ispass = false
|
|
else
|
|
ispass = true
|
|
end
|
|
return (ispass, errormsg)
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
end # module util |