module type export agent, sommelier, companion using Dates, UUIDs, DataStructures, JSON3 using GeneralUtils # ---------------------------------------------- 100 --------------------------------------------- # abstract type agent end mutable struct companion <: agent id::String # agent id systemmsg::Union{String, Nothing} maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized """ Memory Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3 NO "system" message in chathistory because I want to add it at the inference time chathistory= [ Dict(:name=>"user", :text=> "Wassup!", :timestamp=> Dates.now()), Dict(:name=>"assistant", :text=> "Hi I'm your assistant.", :timestamp=> Dates.now()), ] """ chathistory::Vector{Dict{Symbol, Any}} memory::Dict{Symbol, Any} # communication function text2textInstructLLM::Function end function companion( text2textInstructLLM::Function ; id::String= string(uuid4()), systemmsg::Union{String, Nothing}= nothing, maxHistoryMsg::Integer= 20, chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(), ) memory = Dict{Symbol, Any}( :chatbox=> "", :shortmem=> OrderedDict{Symbol, Any}(), :events=> Vector{Dict{Symbol, Any}}(), :state=> Dict{Symbol, Any}(), ) newAgent = companion( id, systemmsg, maxHistoryMsg, chathistory, memory, text2textInstructLLM ) return newAgent end """ A sommelier agent. # Arguments - `mqttClient::Client` MQTTClient's client - `msgMeta::Dict{Symbol, Any}` A dict contain info about a message. - `config::Dict{Symbol, Any}` Config info for an agent. Contain mqtt topic for internal use and other info. # Keyword Arguments - `name::String` Agent's name - `id::String` Agent's ID - `tools::Dict{Symbol, Any}` Agent's tools - `maxHistoryMsg::Integer` max history message # Return - `nothing` # Example ```jldoctest julia> using YiemAgent, MQTTClient, GeneralUtils julia> msgMeta = GeneralUtils.generate_msgMeta( "N/A", replyTopic = "/testtopic/prompt" ) julia> tools= Dict( :chatbox=>Dict( :name => "chatbox", :description => "Useful only for when you need to ask the user for more info or context. Do not ask the user their own question.", :input => "Input should be a text.", :output => "" , :func => nothing, ), ) julia> agentConfig = Dict( :receiveprompt=>Dict( :mqtttopic=> "/testtopic/prompt", # topic to receive prompt i.e. frontend send msg to this topic ), :receiveinternal=>Dict( :mqtttopic=> "/testtopic/internal", # receive topic for model's internal ), :text2text=>Dict( :mqtttopic=> "/text2text/receive", ), ) julia> client, connection = MakeConnection("test.mosquitto.org", 1883) julia> agent = YiemAgent.bsommelier( client, msgMeta, agentConfig, name= "assistant", id= "555", # agent instance id tools=tools, ) ``` # TODO - [] update docstring - [x] implement the function # Signature """ mutable struct sommelier <: agent name::String # agent name id::String # agent id retailername::String tools::Dict maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized """ Memory Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3 NO "system" message in chathistory because I want to add it at the inference time chathistory= [ Dict(:name=>"user", :text=> "Wassup!", :timestamp=> Dates.now()), Dict(:name=>"assistant", :text=> "Hi I'm your assistant.", :timestamp=> Dates.now()), ] """ chathistory::Vector{Dict{Symbol, Any}} memory::Dict{Symbol, Any} func # NamedTuple of functions end function sommelier( func, # NamedTuple of functions ; name::String= "Assistant", id::String= string(uuid4()), retailername::String= "retailer_name", maxHistoryMsg::Integer= 20, chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(), ) tools = Dict( # update input format "chatbox"=> Dict( :description => "Useful for when you need to ask the user for more context. Do not ask the user their own question.", :input => """Input is a text in JSON format.{\"Q1\": \"How are you doing?\", \"Q2\": \"How may I help you?\"}""", :output => "" , ), "winestock"=> Dict( :description => "A handy tool for searching wine in your inventory that match the user preferences.", :input => """Input is a JSON-formatted string that contains a detailed and precise search query.{\"wine type\": \"rose\", \"price\": \"max 35\", \"sweetness level\": \"sweet\", \"intensity level\": \"light bodied\", \"Tannin level\": \"low\", \"Acidity level\": \"low\"}""", :output => """Output are wines that match the search query in JSON format.""", ), # "finalanswer"=> Dict( # :description => "Useful for when you are ready to recommend wines to the user.", # :input => """{\"finalanswer\": \"some text\"}.{\"finalanswer\": \"I recommend Zena Crown Vista\"}""", # :output => "" , # :func => nothing, # ), ) memory = Dict{Symbol, Any}( :chatbox=> "", :shortmem=> OrderedDict{Symbol, Any}( :available_wine=> [], :found_wine=> [], # used by decisionMaker(). This is to prevent decisionMaker() keep presenting the same wines ), :events=> Vector{Dict{Symbol, Any}}(), :state=> Dict{Symbol, Any}( ), :recap=> OrderedDict{Symbol, Any}(), ) newAgent = sommelier( name, id, retailername, tools, maxHistoryMsg, chathistory, memory, func ) return newAgent end end # module type