diff --git a/src/interface.jl b/src/interface.jl index 30fc610..3a842dc 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -232,7 +232,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age systemmsg = """ - You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. + Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for Yiem's online wine store. Your goal includes: 1) Help the user select the best wines from your inventory that align with the user's preferences. @@ -273,7 +273,12 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol, Any} where {T<:age - CHATBOX which you can use to generate conversation in order to communicate with the user. The input is your intentions for the dialogue. Be specific. - CHECKINVENTORY which you can use to check info about wine in your inventory. The input is a search term in verbal English. Good query example: black car, a stereo, 200 mile range, electric motor. - - PRESENTBOX which you can use to introduce / suggest / recommend wines you just found in the database to the user. It is better than the CHATBOX function for presenting wines. The input is the names of wines to introduce. + - PRESENTBOX which you can use to introduce / suggest / recommend wines you just found in the database to the user. It is better than the CHATBOX function for presenting wines. + The input is instructions on how you want the presentation to be conducted. + Here are some input example, + "First, provide detailed introductions of the wines to help the user make an informed choice. + Second, if there are multiple wines, offer a thorough comparison of each option, highlighting their differences. + Third, explain the potential impact each option could bring to the user." - ENDCONVERSATION which you can use when you want to finish the conversation with the user. The input is "NA". 4) Action_input: input of the action 5) Mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No" @@ -951,7 +956,7 @@ function think(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}} checkinventory(a, actioninput) elseif actionname == "PRESENTBOX" x = """ - 1) Provide detailed introductions of $actioninput to help the user make an informed choice. + 1) Provide detailed introductions of the wines to help the user make an informed choice. 2) If there are multiple wines, offer a thorough comparison of each option, highlighting their differences. 3) Explain the potential impact each option could bring to the user. """ @@ -959,7 +964,7 @@ function think(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}} # 1) Introduce $actioninput in details for the user to choose." # 2) Compare each option against the others in details and explain why each one is a suitable match for the user's specific needs. # """ - (result=x, errormsg=nothing, success=true) + (result=actioninput, errormsg=nothing, success=true) elseif actionname == "ENDCONVERSATION" x = "Conclude the conversation, thanks the user then goodbye and inviting them to return next time." (result=x, errormsg=nothing, success=true) @@ -1016,7 +1021,7 @@ julia> function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::Function) systemmsg = """ - You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. + Your name is "Jenie". You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. Your goal is: Recommend the best wines from your inventory that align with the user's preferences. Your responsibility includes: @@ -1189,7 +1194,7 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St systemmsg = """ - You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. + Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for Yiem's online wine store. Your goal includes: 1) Help the user select the best wines from your inventory that align with the user's preferences 2) Thanks the user when they don't need any further assistance and invite them to comeback next time diff --git a/src/llmfunction.jl b/src/llmfunction.jl index c5613c7..c3913dd 100644 --- a/src/llmfunction.jl +++ b/src/llmfunction.jl @@ -295,7 +295,9 @@ function checkinventory(a::T1, input::T2 inventoryquery = "$wineattributes_1, $wineattributes_2" println("--> checkinventory input: $inventoryquery ", @__FILE__, " ", @__LINE__) - result = SQLLLM.query(inventoryquery, a.executeSQL, a.text2textInstructLLM) + result = SQLLLM.query(inventoryquery, a.executeSQL, a.text2textInstructLLM, + addSQLVectorDB=a.addSQLVectorDB, + querySQLVectorDB=a.querySQLVectorDB) push!(a.memory[:events], eventdict(; diff --git a/src/type.jl b/src/type.jl index ff324aa..d32741d 100644 --- a/src/type.jl +++ b/src/type.jl @@ -98,11 +98,15 @@ mutable struct sommelier <: agent # communication function text2textInstructLLM::Function executeSQL::Function + querySQLVectorDB::Function + addSQLVectorDB::Function end function sommelier( text2textInstructLLM::Function, - executeSQL::Function + executeSQL::Function, + querySQLVectorDB::Function, + addSQLVectorDB::Function ; name::String= "Assistant", id::String= string(uuid4()), @@ -143,7 +147,9 @@ function sommelier( chathistory, memory, text2textInstructLLM, - executeSQL + executeSQL, + querySQLVectorDB, + addSQLVectorDB ) return newAgent diff --git a/test/runtest.jl b/test/runtest.jl index 5cd69bf..6e994e2 100644 --- a/test/runtest.jl +++ b/test/runtest.jl @@ -1,5 +1,5 @@ using Revise # remove when this package is completed -using YiemAgent, GeneralUtils, JSON3, MQTTClient, Dates, UUIDs, LibPQ +using YiemAgent, GeneralUtils, JSON3, MQTTClient, Dates, UUIDs, LibPQ, Base64, DataFrames using Base.Threads # ---------------------------------------------- 100 --------------------------------------------- # @@ -55,20 +55,127 @@ function text2textInstructLLM(prompt::String) return response end - # Instantiate an agent - a = YiemAgent.sommelier( - text2textInstructLLM, - executeSQL; - name="assistant", - id="testingSessionID", # agent instance id +function executeSQLVectorDB(sql) + DBconnection = LibPQ.Connection("host=192.168.88.12 port=5433 dbname=SQLVectorDB user=yiemtechnologies@gmail.com password=yiem@Postgres_0.0") + result = LibPQ.execute(DBconnection, sql) + close(DBconnection) + return result +end + +function addSQLVectorDB(state) + # get embedding of the query + query = [state[:thoughtHistory][:question]] + msgMeta = GeneralUtils.generate_msgMeta( + config[:externalservice][:text2textinstruct][:mqtttopic]; + msgPurpose= "embedding", + senderName= "yiemagent", + senderId= string(uuid4()), + receiverName= "text2textinstruct", + mqttBrokerAddress= config[:mqttServerInfo][:broker], + mqttBrokerPort= config[:mqttServerInfo][:port], ) + outgoingMsg = Dict( + :msgMeta=> msgMeta, + :payload=> Dict( + :text=> query + ) + ) + response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg) + embedding = response[:response][:embeddings][1] + + # check whether there is close enough vector already store in vectorDB. if no, add, else skip + sql = + """ + SELECT *, embedding <-> '$embedding' as distance + FROM sql_statement_repository + ORDER BY distance LIMIT 1; + """ + response = executeSQLVectorDB(sql) + df = DataFrame(response) + row, col = size(df) + distance = row == 0 ? Inf : df[1, :distance] + if row == 0 || distance > 10 # no close enough SQL stored in the database + latestKey, _ = GeneralUtils.findHighestIndexKey(state[:thoughtHistory], :action_input) + _sqlStatement = state[:thoughtHistory][latestKey] + if occursin("SELECT", _sqlStatement) # make sure it is an SQL statement before adding into DB + sqlStatementBase64 = base64encode(_sqlStatement) + sqlStatement = replace(_sqlStatement, "'"=>"") + sql = + """ + INSERT INTO sql_statement_repository (question, sql_statement, sql_statement_base64, embedding) VALUES ('$query', '$sqlStatement', '$sqlStatementBase64', '$embedding'); + """ + _ = executeSQLVectorDB(sql) + println("--> added new SQL statement to vectorDB ", @__FILE__, " ", @__LINE__) + println(sqlStatement) + end + end +end + +function querySQLVectorDB(state) + + # provide similarSQL at the first time thinking only + latestKey, _ = GeneralUtils.findHighestIndexKey(state[:thoughtHistory], :action_input) + if latestKey === nothing + # get embedding of the query + query = [state[:thoughtHistory][:question]] + msgMeta = GeneralUtils.generate_msgMeta( + config[:externalservice][:text2textinstruct][:mqtttopic]; + msgPurpose= "embedding", + senderName= "yiemagent", + senderId= string(uuid4()), + receiverName= "text2textinstruct", + mqttBrokerAddress= config[:mqttServerInfo][:broker], + mqttBrokerPort= config[:mqttServerInfo][:port], + ) + + outgoingMsg = Dict( + :msgMeta=> msgMeta, + :payload=> Dict( + :text=> query + ) + ) + response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg) + embedding = response[:response][:embeddings][1] + + # check whether there is close enough vector already store in vectorDB. if no, add, else skip + sql = + """ + SELECT *, embedding <-> '$embedding' as distance + FROM sql_statement_repository + ORDER BY distance LIMIT 1; + """ + response = executeSQLVectorDB(sql) + df = DataFrame(response) + row, col = size(df) + distance = row == 0 ? Inf : df[1, :distance] + if row != 0 && distance < 100 + # if there is usable SQL, return it. + sqlStatementBase64 = df[1, :sql_statement_base64] + sqlStatement = String(base64decode(sqlStatementBase64)) + println("--> getting SQL statement from vectorDB ", @__FILE__, " ", @__LINE__) + println(sqlStatement) + return sqlStatement + else + return nothing + end + end + return nothing +end + + +# Instantiate an agent +a = YiemAgent.sommelier( + text2textInstructLLM, + executeSQL, + querySQLVectorDB, + addSQLVectorDB; + name= "Janie", + id= sessionId, # agent instance id +) + function main() - userinput = "Hello, I would like a get a bottle of wine." for i in 1:10 - response = YiemAgent.conversation(a, Dict(:text=> userinput)) - println("") - println("--> assistant response: \n", response) userinput = "" for i in 1:3 if userinput == "" @@ -79,6 +186,9 @@ function main() break end end + response = YiemAgent.conversation(a, Dict(:text=> userinput)) + println("") + println("--> assistant response: \n", response) end end @@ -87,8 +197,13 @@ main() """ I'm joining a graduation party this evening. I want a bottle of dry white wine from the US. I'm ok with any price range. Well, the party is small casual with close friends and no food serving. - I'm open to suggestion since I have no specific idea about wine. + I'm open to suggestion since I have no specific idea. I'm ok with any region. + + + + + The input is instructions on how you want the presentation to be conducted. """