module llmfunction export virtualWineCustomerChatbox, jsoncorrection using HTTP, JSON3, URIs, Random using GeneralUtils using ..type, ..util # ---------------------------------------------- 100 --------------------------------------------- # """ # Arguments # Return # Example ```jldoctest julia> ``` # TODO - [] update docstring - [PENDING] implement the function # Signature """ function chatbox(a::T1, input::T2) where {T1<:agent, T2<:AbstractString} error("--> chatbox") # put in model format virtualWineCustomer = a.config[:externalservice][:virtualWineCustomer_1] llminfo = virtualWineCustomer[:llminfo] formattedinput = if llminfo[:name] == "llama3instruct" formatLLMtext_llama3instruct("assistant", input) else error("llm model name is not defied yet $(@__LINE__)") end # send formatted input to user using GeneralUtils.sendReceiveMqttMsg # return response end """ Chatbox for chatting with virtual wine customer. # Arguments - `a::T1` one of Yiem's agent - `input::T2` text to be send to virtual wine customer # Return - `response::String` response of virtual wine customer # Example ```jldoctest julia> ``` # Signature """ function virtualWineCustomerChatbox(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString} # put in model format virtualWineCustomer = a.config[:externalservice][:virtualWineCustomer_1] llminfo = virtualWineCustomer[:llminfo] prompt = if llminfo[:name] == "llama3instruct" formatLLMtext_llama3instruct("assistant", input) else error("llm model name is not defied yet $(@__LINE__)") end # send formatted input to user using GeneralUtils.sendReceiveMqttMsg msgMeta = GeneralUtils.generate_msgMeta( virtualWineCustomer[:mqtttopic], senderName= "virtualWineCustomerChatbox", senderId= a.id, receiverName= "virtualWineCustomer", mqttBroker= a.config[:mqttServerInfo][:broker], mqttBrokerPort= a.config[:mqttServerInfo][:port], msgId = "dummyid" #CHANGE remove after testing finished ) outgoingMsg = Dict( :msgMeta=> msgMeta, :payload=> Dict( :text=> prompt, ) ) @show outgoingMsg result = GeneralUtils.sendReceiveMqttMsg(outgoingMsg) response = result[:response][:text] return response end """ Search wine in stock. Arguments\n a : one of ChatAgent's agent. Return\n A JSON string of available wine Example\n ```jldoctest julia> using ChatAgent julia> agent = ChatAgent.agentReflex("Jene") julia> input = "{\"food\": \"pizza\", \"occasion\": \"anniversary\"}" julia> result = winestock(agent, input) "{"wine 1": {\"Winery\": \"Pichon Baron\", \"wine name\": \"Pauillac (Grand Cru Classé)\", \"grape variety\": \"Cabernet Sauvignon\", \"year\": 2010, \"price\": \"125 USD\", \"stock ID\": \"ar-17\"}, }" ``` """ # function winestock(a::agentReflex, input::NamedTuple) # println("") # @show input # wineSearchCriteria = GeneralUtils.JSON3read_stringKey(input[:toolinput]) # newDict = Dict{String,Any}() # for (k,v) in wineSearchCriteria # println("k $k v $v") # newDict[string(k)] = v # end # #TODO temporary delete key "food pairing from a dict # newDict = deepcopy(a.memory[:keyword]) # delete!(newDict, "food pairing") # query = JSON3.write(newDict) # println("") # @show query # # prompt = # # """ # # <|system|> # # # # Your are a helpful assistant. # # # # # # Database table name by wine type: # # Red = table for wine type "red" # # White = table for wine type "white" # # Sparkling = table for wine type "sparkling" # # Rose = table for wine type "rose" # # Dessert = table for wine type "dessert" # # Fortified = table for wine type "fortified" # # Intensity level: # # intensity = 1, light bodied # # intensity = 2, semi-light bodied # # intensity = 3, medium bodied # # intensity = 4, semi-full bodied # # intensity = 5, full bodied # # Sweetness level: # # sweetness = 1, dry # # sweetness = 2, off-dry # # sweetness = 3, semi-sweet # # sweetness = 4, sweet # # sweetness = 5, very sweet # # Tannin level: # # tannin = 1, low tannin # # tannin = 2, semi-low tannin # # tannin = 3, medium tannin # # tannin = 4, semi-high tannin # # tannin = 5, high tannin # # Acidity level: # # acidity = 1, low acidity # # acidity = 2, semi-low acidity # # acidity = 3, medium acidity # # acidity = 4, semi-high acidity # # acidity = 5, high acidity # # # # # # Consult the conversion table then write a specific SQL command using only available info from a JSON-format query. # # List of keywords not allowed in SQL: ["BETWEEN", "--", "WHEN", "IN"] # # Use the following format: # # Info map: based on conversion table, map the info in query to appropriate variables # # SQL: write a specific SQL command # # # # # # query: {\"wine type\": \"white\", \"wine characteristics\": \"full-bodied | off-dry | low acidity | low to medium tannin\", \"price\": {\"max\": \"50\"}} # # Think: 1) low to medium tannin is not explicitly stated, but assuming it falls within the range of low-medium tannin. # # Info map: {\"wine type\": \"white\", \"intensity\": 5, \"sweetness\": 2, \"tannin\": 2, \"acidity\": 1, \"price\": 50} # # SQL: SELECT * FROM White WHERE intensity = 5 AND sweetness = 2 AND acidity = 1 AND tannin = 2 AND price <= 50; # # # # # # query: {\"wine characteristics\":\"low-bodied | a little sweet | low-medium tannin\",\"price\":\"22 USD\",\"occasion\":\"anniversary\",\"wine type\":\"Rose\",\"food\":\"American dishes\"} # # Think: 1) medium sweet is not explicitly stated, but assuming it falls within the range of dry and off-dry. # # Info map: {\"wine type\": \"Rose\", \"intensity\": 1, \"sweetness\": 3, \"tannin\": 2, \"acidity\": 3, \"price\": 22, \"food\":\"American dishes\"} # # SQL: SELECT * FROM Rose WHERE intensity = 1 AND tannin = 2 AND (sweetness = 1 OR sweetness = 2) AND price <= 22 AND food = American; # # # # # # <|query|> # # $query # # # # <|assistant|> # # """ # prompt = # """ # # <|system|> # # Your are a helpful assistant. # # # Database table name by wine type: # Red = table for wine type "red" # White = table for wine type "white" # Sparkling = table for wine type "sparkling" # Rose = table for wine type "rose" # Dessert = table for wine type "dessert" # Fortified = table for wine type "fortified" # Intensity level: # light bodied = 1 # semi-light bodied = 2 # medium bodied = 3 # semi-full bodied = 4 # full bodied = 5 # Sweetness level: # dry or low = 1 # off-dry or semi-low = 2 # semi-sweet = 3 # sweet = 4 # very sweet = 5 # Tannin level: # low tannin = 1 # semi-low tannin = 2 # medium tannin = 3 # semi-high tannin = 4 # high tannin = 4 # Acidity level: # low acidity = 1 # semi-low acidity = 2 # medium acidity = 3 # semi-high acidity = 4 # high acidity = 5 # # # Write a specific SQL command from a query using a conversion table # List of keywords not allowed the command: ["BETWEEN", "--", "WHEN", "IN"] # Use the following format: # Info map: based on conversion table, map the info in query to appropriate variables # SQL: write a specific SQL command # # # # # {\"wine type\": \"white\", \"wine characteristics\": \"full-bodied | off-dry | low acidity | low to medium tannin\", \"price\": {\"max\": \"50\"}} # # <|assistant|> # Think: 1) low to medium tannin is not explicitly stated, but assuming it falls within the range of low-medium tannin. # Info map: {\"wine type\": \"white\", \"intensity\": 5, \"sweetness\": 2, \"tannin\": 2, \"acidity\": 1, \"price\": 50} # SQL: SELECT * FROM wines WHERE wine_type = "red" AND intensity = 5 AND sweetness = 2 AND acidity = 1 AND tannin = 2 AND price <= 50; # # # # # {\"wine characteristics\":\"low-bodied | a little sweet | low-medium tannin\",\"price\":\"22 USD\",\"occasion\":\"anniversary\",\"wine type\":\"Rose\",\"food\":\"American dishes\"} # # <|assistant|> # Think: 1) medium sweet is not explicitly stated, but assuming it falls within the range of dry and off-dry. # Info map: {\"wine type\": \"Rose\", \"intensity\": 1, \"sweetness\": 3, \"tannin\": 2, \"acidity\": 3, \"price\": 22, \"food\":\"American dishes\"} # SQL: SELECT * FROM wines WHERE wine_type = "white" AND intensity = 1 AND tannin = 2 AND (sweetness = 1 OR sweetness = 2) AND price <= 22 AND food = American; # # # # # $query # # <|assistant|> # """ # println("") # @show db_prompt = prompt # _sql = nothing # while true # _sql = sendReceivePrompt(a, prompt, a.config[:text2text][:mqtttopic], # max_tokens=1024, temperature=0.4, timeout=180, # stopword=["Thought:", "Obs:", "<|system|>", "", "<|end|>", "<|user|>"]) # _sql = split(_sql, ";")[1] * ";" # @show _sql # # check for valid SQL command # check_1 = occursin("BETWEEN", _sql) # check_2 = occursin("--", _sql) # check_3 = occursin("IN", _sql) # if check_1 == false && check_2 == false && check_3 == false # break # end # println("invalid SQL command") # end # _sql = split(_sql, "SQL:")[end] # println("") # @show db_sql = replace(_sql, '\n'=>"") # # remove any blank character in front of a string # newsql = nothing # for i in eachindex(db_sql) # if db_sql[i] != ' ' # newsql = db_sql[i:end] # break # end # end # body = newsql # uri = URI(scheme="http", host="192.168.88.12", port="9010", path="/sql", userinfo="root:root") # r = HTTP.request("POST", uri, ["Accept" => "application/json", "NS"=>"yiem", "DB"=>"wines"], body) # # a.memory[:r] = r # result = copy(JSON3.read(r.body)) # wines = shuffle(result[1][:result]) # shuffle in case there are more than 1 result # # choose only 2 wines # if length(wines) > 2 # println("$(length(wines)) wines found") # wines = wines[1:2] # end # println("") # @show wines # result = nothing # if length(wines) == 0 # result = # """ # No wine match my search query. # """ # else # # write wines dictionary in to string # wines_str = "" # for (i, wine) in enumerate(wines) # winename = wine[:wine_name] # wines_str *= "$i: $(JSON3.write(wines[i]))," # end # result = # """ # I found the following wines in our stock: # { # $wines_str # } # """ # end # @show result # return result # end """ Attemp to correct LLM response's incorrect JSON response. # Arguments - `a::T1` one of Yiem's agent - `input::T2` text to be send to virtual wine customer # Return - `response::String` response of virtual wine customer # Example ```jldoctest julia> ``` # TODO - [] update docstring - [TESTING] implement the function # Signature """ function jsoncorrection(a::T1, input::T2, correctJsonExample::T3) where {T1<:agent, T2<:AbstractString, T3<:AbstractString} attemptround = 0 incorrectjson = input correctjson = nothing while true attemptround += 1 if attemptround <= 5 try JSON3.read(incorrectjson) correctjson = incorrectjson break catch println("Attempting correct JSON string. $attemptround") _prompt = """ Your goal is to correct a given incorrect JSON string. $correctJsonExample Incorrect JSON: $incorrectjson Corrention: """ # apply LLM specific instruct format externalService = a.config[:externalservice][:text2textinstruct] llminfo = externalService[:llminfo] prompt = if llminfo[:name] == "llama3instruct" formatLLMtext_llama3instruct("system", _prompt) else error("llm model name is not defied yet $(@__LINE__)") end # send formatted input to user using GeneralUtils.sendReceiveMqttMsg msgMeta = GeneralUtils.generate_msgMeta( externalService[:mqtttopic], senderName= "jsoncorrection", senderId= a.id, receiverName= "text2textinstruct", mqttBroker= a.config[:mqttServerInfo][:broker], mqttBrokerPort= a.config[:mqttServerInfo][:port], ) outgoingMsg = Dict( :msgMeta=> msgMeta, :payload=> Dict( :text=> prompt, :kwargs=> Dict( :max_tokens=> 512, :stop=> ["<|eot_id|>"], ) ) ) result = GeneralUtils.sendReceiveMqttMsg(outgoingMsg) incorrectjson = result[:response][:text] end else error("Can't fix JSON string") break end end @show correctjson return correctjson end end # module llmfunction