diff --git a/src/agentCore.jl b/src/agentCore.jl
index 18defc9..311d00e 100644
--- a/src/agentCore.jl
+++ b/src/agentCore.jl
@@ -184,8 +184,10 @@ function _process_message(agent::yiemAgent)::assistantMessage
# Call llmCall() (blocking — the task waits here)
response = agent.llmCall(formatted_messages)
+
+ error(5555555)
- # Check if LLM used tool calls (inspect content for tool_call blocks)
+ #WORKING Check if LLM used tool calls (inspect content for tool_call blocks)
has_tool_calls = false
tool_call_list = agentToolCall[]
diff --git a/src/type.jl b/src/type.jl
index c7cb570..264cdd7 100644
--- a/src/type.jl
+++ b/src/type.jl
@@ -245,7 +245,7 @@ mutable struct agentState # Mutable runtime state of an agen
model::llmModel # LLM model to use
tools::Vector{agentTool} # Available tools
- # messages history includes userMessage, assistantMessage, toolResultMessage
+ # messages history includes userMessage, assistantMessage, toolResultMessage. NO system prompt
messages::Vector{agentMessage}
pendingToolCalls::Vector{String} # Tool call IDs waiting for results
@@ -568,7 +568,7 @@ yiemAgent(agentState(...), Channel(...), Channel(...), Channel(...), ..., ...)
```
"""
function yiemAgent(
- ; systemPrompt::String="",
+ ; systemPrompt::String="You are helpful assistant.",
model=nothing,
tools::Vector{agentTool}=agentTool[],
messages::Vector{agentMessage}=agentMessage[],
diff --git a/src/utils.jl b/src/utils.jl
index 2efd78b..0612a78 100644
--- a/src/utils.jl
+++ b/src/utils.jl
@@ -103,13 +103,21 @@ prepareContext(state) == deepcopy(state.messages)
# return msgs
# end
```
-"""
-function prepareContext(state::agentState)::Vector{agentMessage}
- messages = deepcopy(state.messages) # messages that will be send to LLM
+""" #WORKING
+function prepareContext(state::agentState)::agentContext
- #TODO adjust/modify and inject additional context into messages
+ #TODO filter tools from state.tools based on user intend in user message and tool description
+ filteredTools = state.tools
- return messages
+ #TODO add tools to current system prompt
+ preparedSystemPrompt = state.systemPrompt
+
+ #TODO add system prompt, adjust/modify and inject additional context into messages
+ preparedMessages = deepcopy(state.messages) # messages that will be send to LLM
+
+ agentCtx = agentContext(preparedSystemPrompt, preparedMessages, filteredTools)
+
+ return agentCtx
end
diff --git a/test/chatting_with_agent.jl b/test/chatting_with_agent.jl
deleted file mode 100644
index 5e66a0f..0000000
--- a/test/chatting_with_agent.jl
+++ /dev/null
@@ -1,296 +0,0 @@
-using Revise
-using JSON, JSON3, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames
-using YiemAgent, GeneralUtils
-using Base.Threads
-
-# ---------------------------------------------- 100 --------------------------------------------- #
-
-
-
-# load config
-config = JSON.parsefile("/appfolder/app/dev/YiemAgent/test/config.json")
-# config = copy(JSON.parsefile("../mountvolume/config.json"))
-
-
-function executeSQL(sql::T) where {T<:AbstractString}
- host = config[:externalservice][:wineDB][:host]
- port = config[:externalservice][:wineDB][:port]
- dbname = config[:externalservice][:wineDB][:dbname]
- user = config[:externalservice][:wineDB][:user]
- password = config[:externalservice][:wineDB][:password]
- DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
- result = LibPQ.execute(DBconnection, sql)
- close(DBconnection)
- return result
-end
-
-function executeSQLVectorDB(sql)
- host = config[:externalservice][:SQLVectorDB][:host]
- port = config[:externalservice][:SQLVectorDB][:port]
- dbname = config[:externalservice][:SQLVectorDB][:dbname]
- user = config[:externalservice][:SQLVectorDB][:user]
- password = config[:externalservice][:SQLVectorDB][:password]
- DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
- result = LibPQ.execute(DBconnection, sql)
- close(DBconnection)
- return result
-end
-
-function text2textInstructLLM(prompt::String; maxattempt::Integer=3, modelsize::String="medium",
- llmkwargs=Dict(
- :num_ctx => 32768,
- :temperature => 0.1,
- )
- )
- msgMeta = GeneralUtils.generate_msgMeta(
- config[:externalservice][:loadbalancer][:mqtttopic];
- msgPurpose="inference",
- senderName="yiemagent",
- senderId=sessionId,
- receiverName="text2textinstruct_$modelsize",
- mqttBrokerAddress=config[:mqttServerInfo][:broker],
- mqttBrokerPort=config[:mqttServerInfo][:port],
- )
-
- outgoingMsg = Dict(
- :msgMeta => msgMeta,
- :payload => Dict(
- :text => prompt,
- :kwargs => llmkwargs
- )
- )
-
- response = nothing
- for attempts in 1:maxattempt
- _response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=180, maxattempt=maxattempt)
- payload = _response[:response]
- if _response[:success] && payload[:text] !== nothing
- response = _response[:response][:text]
- break
- else
- println("\n attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- pprintln(outgoingMsg)
- println(" attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
- sleep(3)
- end
- end
-
- return response
-end
-
-# get text embedding from a LLM service
-function getEmbedding(text::T) where {T<:AbstractString}
- msgMeta = GeneralUtils.generate_msgMeta(
- config[:externalservice][:loadbalancer][:mqtttopic];
- msgPurpose="embedding",
- senderName="yiemagent",
- senderId=sessionId,
- receiverName="textembedding",
- mqttBrokerAddress=config[:mqttServerInfo][:broker],
- mqttBrokerPort=config[:mqttServerInfo][:port],
- )
-
- outgoingMsg = Dict(
- :msgMeta => msgMeta,
- :payload => Dict(
- :text => [text] # must be a vector of string
- )
- )
-
- response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120, maxattempt=3)
- embedding = response[:response][:embeddings]
- return embedding
-end
-
-function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
- vectorDB::Function; limit::Integer=1
- )::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
- # get embedding from LLM service
- embedding = getEmbedding(text)[1]
- # check whether there is close enough vector already store in vectorDB. if no, add, else skip
- sql = """
- SELECT *, $embeddingColumnName <-> '$embedding' as distance
- FROM $tablename
- ORDER BY distance LIMIT $limit;
- """
- response = vectorDB(sql)
- df = DataFrame(response)
- return df
-end
-
-function similarSQLVectorDB(query; maxdistance::Integer=100)
- tablename = "sqlllm_decision_repository"
- # get embedding of the query
- df = findSimilarTextFromVectorDB(query, tablename,
- "function_input_embedding", executeSQLVectorDB)
- # println(df[1, [:id, :function_output]])
- row, col = size(df)
- distance = row == 0 ? Inf : df[1, :distance]
- # distance = 100 # CHANGE this is for testing only
- if row != 0 && distance < maxdistance
- # if there is usable SQL, return it.
- output_b64 = df[1, :function_output_base64] # pick the closest match
- output_str = String(base64decode(output_b64))
- rowid = df[1, :id]
- println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- return (dict=output_str, distance=distance)
- else
- println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- return (dict=nothing, distance=nothing)
- end
-end
-
-function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=3) where {T1<:AbstractString, T2<:AbstractString}
- tablename = "sqlllm_decision_repository"
- # get embedding of the query
- # query = state[:thoughtHistory][:question]
- df = findSimilarTextFromVectorDB(query, tablename,
- "function_input_embedding", executeSQLVectorDB)
- row, col = size(df)
- distance = row == 0 ? Inf : df[1, :distance]
- if row == 0 || distance > maxdistance # no close enough SQL stored in the database
- query_embedding = getEmbedding(query)[1]
- query = replace(query, "'" => "")
- sql_base64 = base64encode(SQL)
- sql_ = replace(SQL, "'" => "")
-
- sql = """
- INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
- """
- # println("\n~~~ added new decision to vectorDB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
- # println(sql)
- _ = executeSQLVectorDB(sql)
- end
-end
-
-
-function similarSommelierDecision(recentevents::T1; maxdistance::Integer=3
- )::Union{AbstractDict, Nothing} where {T1<:AbstractString}
- tablename = "sommelier_decision_repository"
- # find similar
- println("\n~~~ search vectorDB for this: $recentevents ", @__FILE__, " ", @__LINE__)
- df = findSimilarTextFromVectorDB(recentevents, tablename,
- "function_input_embedding", executeSQLVectorDB)
- row, col = size(df)
- distance = row == 0 ? Inf : df[1, :distance]
- if row != 0 && distance < maxdistance
- # if there is usable decision, return it.
- rowid = df[1, :id]
- println("\n~~~ found similar decision. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
- output_b64 = df[1, :function_output_base64] # pick the closest match
- _output_str = String(base64decode(output_b64))
- output = copy(JSON.parsefile(_output_str))
- return output
- else
- println("\n~~~ similar decision not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
- return nothing
- end
-end
-
-
-function insertSommelierDecision(recentevents::T1, decision::T2; maxdistance::Integer=5
- ) where {T1<:AbstractString, T2<:AbstractDict}
- tablename = "sommelier_decision_repository"
- # find similar
- df = findSimilarTextFromVectorDB(recentevents, tablename,
- "function_input_embedding", executeSQLVectorDB)
- row, col = size(df)
- distance = row == 0 ? Inf : df[1, :distance]
- if row == 0 || distance > maxdistance # no close enough SQL stored in the database
- recentevents_embedding = getEmbedding(recentevents)[1]
- recentevents = replace(recentevents, "'" => "")
- decision_json = JSON.json(decision)
- decision_base64 = base64encode(decision_json)
- decision = replace(decision_json, "'" => "")
-
- sql = """
- INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$recentevents', '$decision', '$decision_base64', '$recentevents_embedding');
- """
- println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
- println(sql)
- _ = executeSQLVectorDB(sql)
- else
- println("~~~ similar decision previously cached, distance $distance ", @__FILE__, " ", @__LINE__)
- end
-end
-
-
-sessionId = "12345"
-
-externalFunction = (
- getEmbedding=getEmbedding,
- text2textInstructLLM=text2textInstructLLM,
- executeSQL=executeSQL,
- similarSQLVectorDB=similarSQLVectorDB,
- insertSQLVectorDB=insertSQLVectorDB,
- similarSommelierDecision=similarSommelierDecision,
- insertSommelierDecision=insertSommelierDecision,
- )
-
-
-
-a = YiemAgent.sommelier(
- externalFunction;
- name="Ton",
- id=sessionId, # agent instance id
- retailername="Yiem",
-)
-
-while true
- print("\nyour respond: ")
- user_answer = readline()
- response = YiemAgent.conversation(agent;
- userinput=Dict(:text=> user_answer),
- maximumMsg=50)
- println("\n$response")
-end
-
-
-# response = YiemAgent.conversation(a, Dict(:text=> "I want to get a French red wine under 100."))
-
-
-"""
-hello I want to get a bottle of red wine for my boss. I have a budget around 50 dollars. Show me some options.
-
-I have no idea about his wine taste but he likes spicy food.
-
-
-"""
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/prompttest.jl b/test/prompttest.jl
deleted file mode 100644
index b75dd4c..0000000
--- a/test/prompttest.jl
+++ /dev/null
@@ -1,159 +0,0 @@
-using Revise
-using YiemAgent, GeneralUtils, JSON3, DataStructures
-
-thoughtDict = OrderedDict(
- :Question=> "Hello, I would like a get a bottle of wine",
- :Thought_1=> "The customer wants to buy a bottle of wine, but we need more information about their preferences.",
- :Action_1=> Dict(
- :name=> "chatbox",
- :input=> "What occasion are you buying the wine for?",
- ),
- :Observation_1=> "We are having a wedding pary this weekend.",
-
- :Thought_2=> "A wedding party is a great occasion to have a good bottle of wine.",
- :Action_2=> Dict(
- :name=> "chatbox",
- :input=> "What type of food will you be serving with the wine?",
- ),
- :Observation_2=> "I think it is Thai dishes",
-
- :Thought_3=> "Now that I know the occasion and food, I need to ask about the budget.",
- :Action_3=> Dict(
- :name=> "chatbox",
- :input=> "What is your budget for this wine?",
- ),
- :Observation_3=> "50 bucks",
-
- :Thought_4=> "With a budget of \$50, we have a wide range of options. Now that I know it's a wedding party and Thai dishes, I need to ask about the type of wine they prefer.",
- :Action_4=> Dict(
- :name=> "chatbox",
- :input=> "What type of wine are you looking for? (Red, White, Sparkling, Rose, Dessert, Fortified)",
- ),
- :Observation_4=> "Sparkling please.",
-
- :Thought_5=> "Now that I know the occasion, food, budget and preferred type of wine, it's time to check our inventory for the best matching wine.",
- :Action_5=> Dict(
- :name=> "winestock",
- :input=> "wine with budget \$50, Thai dishes, sparkling, wedding party",
- ),
- :Observation_5=> "I found the following wine in stock {1 : Zena Crown Vista, 2 : Schrader Cabernet Sauvignon}",
-
- :Thought_6=> "Now that I have all the information, it's time to recommend a wine that fits their preferences.",
- :Action_6=> Dict(
- :name=> "recommendation",
- :input=> "I recommend Zena Crown Vista for its sparkling and affordable price.",
- ),
- :Observation_6=> "I don't like it. Do you have another option?",
- )
-
-_thoughtJsonStr = JSON.json(thoughtDict)
-thoughtJsonStr = _thoughtJsonStr[1:end-1] # remove } at the end
-# @show thoughtJsonStr
-
-_, latestThoughtIndice = GeneralUtils.findHighestIndexKey(thoughtDict, "Thought")
-nextThoughtIndice = latestThoughtIndice + 1
-
-_prompt =
-"""
-You are a helpful sommelier working for a wine store.
-Your goal is to reccommend the best wine from your inventory that match the user preferences.
-
-You must follow the following criteria:
-1) Get to know what occasion the user is buying wine for
-2) Get to know what food the user will have with wine
-3) Get to know how much the user willing to spend
-4) Get to know type of wine the user is looking for e.g. Red, White, Sparkling, Rose, Dessert, Fortified
-5) Get to know what characteristics of wine the user is looking for
- e.g. tannin, sweetness, intensity, acidity
-6) Check your inventory for the best wine that match the user preference
-7) Recommend wine to the user
-
-You should only respond with interleaving Thought, Action, Observation steps.
-Thought can reason about the current situation, and Action can be three types:
-1) winestock[query], which you can use to find wine in your inventory. The more input data the better.
-2) chatbox[text], which you can use to interact with the user.
-3) recommendation[answer], which returns your wine reccommendation to the user.
-
-You should only respond in JSON format as describe below:
-{
- "Thought": "your reasoning",
- "Action": {"name": "action to take", "input": "Action input"},
- "Observation": "result of the action"
-}
-
-Here are some examples:
-{
-"Question": "I would like to buy a sedan with 8 seats.",
-"Thought_1": "Our showroom carries various vehicle model. But I'm not sure whether we have a models that fits the user demand, I need to check our inventory.",
-"Action_1": {"name": "inventory", "input": "sedan with 8 seats."},
-"Observation_1": "Several model has 8 seats. Available color are black, red green"
-}
-{
- "Thought_2": "I have to ask the user what color he likes.",
- "Action_2": {"name": "chatbox", "input": "Which color do you like?"}
- "Observation_2": "I'll take black."
-}
-{
- "Thought_3": "There is only one model that fits the user preference. It's Yiem model A",
- "Action_3": {"name": "recommendation", "input": "I recommend a Yiem model A"}
-}
-
-Let's begin!
-
-$(JSON.json(thoughtDict))
-{Thought_$nextThoughtIndice
-"""
-
-prompt = YiemAgent.formatLLMtext_llama3instruct("system", _prompt)
-@show prompt
-msgMeta = Dict(:requestResponse => nothing,
- :msgPurpose => nothing,
- :receiverId => nothing,
- :getPost => nothing,
- :msgId => "4c7111e0-c30e-44c3-8f85-1c8b3f03a8be",
- :acknowledgestatus => nothing,
- :replyToMsgId => nothing,
- :msgFormatVersion => nothing,
- :mqttServerInfo => Dict(:port => 1883, :broker => "mqtt.yiem.cc"),
- :sendTopic => "/loadbalancer/requestingservice",
- :receiverName => "text2textinstruct",
- :replyTopic => nothing,
- :senderName => "decisionMaker",
- :senderSelfnote => nothing,
- :senderId => "testingSessionID",
- :timeStamp => "2024-05-04T08:06:23.561"
- )
-
-outgoingMsg = Dict(
- :msgMeta=> msgMeta,
- :payload=> Dict(
- :text=> prompt,
- )
-)
-
-
-
-_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
-thoughtJsonStr = _response[:response][:text]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/prompttest_1.jl b/test/prompttest_1.jl
deleted file mode 100644
index 229fbc6..0000000
--- a/test/prompttest_1.jl
+++ /dev/null
@@ -1,87 +0,0 @@
-using Revise # remove when this package is completed
-using YiemAgent, GeneralUtils, JSON3, MQTTClient, Dates, UUIDs, DataStructures
-using Base.Threads
-
-# ---------------------------------------------- 100 --------------------------------------------- #
-
-config = copy(JSON.parsefile("config.json"))
-
-instanceInternalTopic = config[:serviceInternalTopic][:mqtttopic] * "/1"
-
-client, connection = MakeConnection(config[:mqttServerInfo][:broker],
- config[:mqttServerInfo][:port])
-
-receiveUserMsgChannel = Channel{Dict}(4)
-receiveInternalMsgChannel = Channel{Dict}(4)
-
-msgMeta = GeneralUtils.generate_msgMeta(
- "N/A",
- replyTopic = config[:servicetopic][:mqtttopic] # ask frontend reply to this instance_chat_topic
- )
-
-agentConfig = Dict(
- :mqttServerInfo=> config[:mqttServerInfo],
- :receivemsg=> Dict(
- :prompt=> config[:servicetopic][:mqtttopic], # topic to receive prompt i.e. frontend send msg to this topic
- :internal=> instanceInternalTopic,
- ),
- :externalservice=> config[:externalservice],
-)
-
-# Instantiate an agent
-tools=Dict( # update input format
- "askbox"=> 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 => "" ,
- :func => nothing,
- ),
- # "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 => """