This commit is contained in:
narawat lamaiin
2024-04-15 18:48:29 +07:00
parent de26d92437
commit 919f4ab0ee
7 changed files with 1058 additions and 2 deletions

View File

@@ -1,5 +1,48 @@
module YiemAgent
greet() = print("Hello World!")
# export agent, addNewMessage, clearMessage
end # module YiemAgent
""" Order by dependencies of each file. The 1st included file must not depend on any other
files and each file can only depend on the file included before it.
"""
include("type.jl")
using .type
include("util.jl")
using .util
include("llmfunction.jl")
using .llmfunction
include("interface.jl")
using .interface
# ---------------------------------------------- 100 --------------------------------------------- #
end # module YiemAgent_v1

95
src/interface.jl Normal file
View File

@@ -0,0 +1,95 @@
module interface
# export
using JSON3, DataStructures, Dates, UUIDs, HTTP, Random
using GeneralUtils
using ..type, ..util, ..llmfunction
# ------------------------------------------------------------------------------------------------ #
# pythoncall setting #
# ------------------------------------------------------------------------------------------------ #
# Ref: https://github.com/JuliaPy/PythonCall.jl/issues/252
# by setting the following variables, PythonCall.jl will use:
# 1. system's python and packages installed by system (via apt install)
# or 2. conda python and packages installed by conda
# if these setting are not set (comment out), PythonCall will use its own python and packages that
# installed by CondaPkg.jl (from env_preparation.jl)
# ENV["JULIA_CONDAPKG_BACKEND"] = "Null" # set condapkg backend = none
# systemPython = split(read(`which python`, String), "\n")[1] # system's python path
# 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 --------------------------------------------- #
""" 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
-----
inferenced text
Example\n
-----
```jldoctest
julia> using YiemAgent
```
Signature\n
-----
""" #WORKING
function addNewMessage(a::T1, role::String, text::T2) where {T1<:agent, T2<:AbstractString}
if role a.availableRole # guard against typo
error("role is not in agent.availableRole $(@__LINE__)")
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, :text=> text, :timestamp=> Dates.now())
push!(a.messages, d)
messageleft = a.maxUserMsg - userMsg
end
return messageleft
end
end # module interface

22
src/llmfunction.jl Normal file
View File

@@ -0,0 +1,22 @@
module llmfunction
# export wikisearch, winestock, askbox
using HTTP, JSON3, URIs, Random
using GeneralUtils
using ..type, ..util
# ---------------------------------------------- 100 --------------------------------------------- #
end # module llmfunction

251
src/type.jl Normal file
View File

@@ -0,0 +1,251 @@
module type
export agent, sommelier, initAgentMemory
using Dates, UUIDs, DataStructures, JSON3
using GeneralUtils
# ---------------------------------------------- 100 --------------------------------------------- #
abstract type agent end
""" A sommelier agent.
Example:
```jldoctest
julia> using ChatAgent
julia> mqttClientSpec = (
clientName= "someclient", # name of this client
clientID= "$(uuid4())",
broker= "mqtt.yiem.ai",
pubtopic= (imgAI="img/api/v0.0.1/gpu/request",
txtAI="txt/api/v0.1.0/gpu/request"), # this is where LLM server located
subtopic= (imgAI="agent/api/v0.1.0/img/respond",
txtAI="agent/api/v0.1.0/txt/respond"), # this is where this agent located
keepalive= 30,
)
julia> msgMeta = Dict(
:msgPurpose=> "updateStatus",
:from=> "agent",
:to=> "llmAI",
:requestrespond=> "request",
:sendto=> "", # destination topic
:replyTo=> "agent/api/v0.1.0/txt/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())",
)
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,
),
:wikisearch=>Dict(
:name => "wikisearch",
:description => "Useful for when you need to search an encyclopedia.",
:input => "Input is keywords and not a question.",
:output => "",
:func => ChatAgent.wikisearch, # put function here
),
:winestock=>Dict(
:name => "wineStock",
:description => "useful for when you need to search your wine stock by wine description, price, name or ID.",
:input => "Input is a search query.",
:output => "Output are Wine name, description, price and ID" ,
:func => ChatAgent.winestock,
),
)
julia> agent = ChatAgent.agentReflex(
"Jene",
role=:assistant,
mqttClientSpec=mqttClientSpec,
msgMeta=msgMeta,
tools=tools
)
```
"""
@kwdef mutable struct sommelier <: agent
name::String
id::String
config::Dict
tools::Union{Dict, Nothing} = nothing
attemptlimit::Int # thinking round limit
attemptcount::Int # used to count attempted round of a task
# memory
# Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
# messages= [Dict(:role=>"system", :content=> "", :timestamp=> Dates.now()),]
chathistory = Vector{Dict{Symbol, Any}}() # store messages history in the format :name=>"message"
maxUserMsg::Int = 10 # 31th and earlier messages will get summarized
keywordinfo = Dict{Symbol, Any}(
:userinfo => Dict{Symbol, Any}(),
:retailerinfo => Dict{Symbol, Any}(),
)
mctstree = Dict{Symbol, Any}()
# 1-history point compose of:
# state_t, statevalue_t, thought_t, action_t, observation_t, state_tplus1, statevalue_tplus1
plan = Dict{Symbol, Any}(
# store 3 to 5 best plan AI frequently used to avoid having to search MCTS all the time
:existingplan => Dict{Symbol, Any}(),
:activeplan => Dict{Symbol, Any}(), # current using plan
:currenttrajectory=> Dict{Symbol, Any}(), # store
)
# communication
mqttClient::Any=nothing # store mqtt client for use in various internal functions
msgMeta::Dict # a template for msgMeta
# put incoming message here. waiting for further processing
userMsg::Channel{Dict} = Channel{Dict}(32) # for user communication
internalMsg::Channel{Dict} = Channel{Dict}(32) # for internal communication
end
function sommelier(
mqttClient,
msgMeta::Dict= GeneralUtils.generate_msgMeta("N/A"),
config::Dict = Dict(
:frontend=>Dict(
:mqtttopic=> nothing
),
:internal=>Dict(
:mqtttopic=> nothing
),
:text2text=>Dict(
:mqtttopic=> "txt2text/api/v1/prompt/gpu",
),
),
;
name::String="Assistant",
id::String=string(uuid4()),
tools::Dict=Dict(
:chatbox=>Dict(
:name => "chatbox",
:description => "Useful for when you need to communicate with the user.",
:input => "Input should be a conversation to the user.",
:output => "" ,
:func => nothing,
),
# :wikisearch=>Dict(
# :name => "wikisearch",
# :description => "Useful for when you need to search an encyclopedia",
# :input => "Input is keywords and not a question.",
# :output => "",
# :func => wikisearch, # put function here
# ),
# :wineStock=>Dict(
# :name => "wineStock",
# :description => "useful for when you need to search for wine by your description, price, name or ID.",
# :input => "Input should be a search query with as much details as possible.",
# :output => "" ,
# :func => nothing,
# ),
# :NTHING=>Dict(
# :name => "NTHING",
# :description => "useful for when you don't need to use tools or actions",
# :input => "No input is needed",
# :output => "" ,
# :func => nothing,
# ),
),
maxUserMsg::Int=20,
)
#NEXTVERSION publish to a.config[:configtopic] to get a config.
#NEXTVERSION get a config message in a.mqttMsg_internal
#NEXTVERSION set agent according to config
newAgent = sommelier(
name = name,
id = id,
config = config,
mqttClient = mqttClient,
msgMeta = msgMeta,
maxUserMsg = maxUserMsg,
tools = tools,
attemptlimit = 5,
attemptcount = 0,
)
return newAgent
end
function initAgentMemory(a::T) where {T<:agent}
a.chathistory = Dict{String,Any}()
a.mctstree = Dict{Symbol, Any}()
a.plan[:activeplan] = Dict{Symbol, Any}()
a.plan[:currenttrajectory] = Dict{Symbol, Any}()
end
end # module type

18
src/util.jl Normal file
View File

@@ -0,0 +1,18 @@
module util
# export sendReceivePrompt
using UUIDs, Dates, DataStructures, HTTP, MQTTClient, JSON3
using GeneralUtils
using ..type
# ---------------------------------------------- 100 --------------------------------------------- #
end # module util