From 85240c5fb8a4a8a76be4e2ebec2b738ae79eb2a7 Mon Sep 17 00:00:00 2001 From: narawat lamaiin Date: Mon, 29 Apr 2024 17:01:43 +0700 Subject: [PATCH] update --- src/interface.jl | 206 ++++++++++++++++----------------- src/llmfunction.jl | 280 +++++++++++++++++++++++++++++++++++++++++++++ src/mcts.jl | 15 ++- test/runtest.jl | 2 +- 4 files changed, 392 insertions(+), 111 deletions(-) diff --git a/src/interface.jl b/src/interface.jl index 7cc276c..d9cce19 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -33,6 +33,17 @@ using ..type, ..util, ..llmfunction, ..mcts # ---------------------------------------------- 100 --------------------------------------------- # +macro executeStringFunction(functionStr, args...) + # Parse the function string into an expression + func_expr = Meta.parse(functionStr) + + # Create a new function with the parsed expression + function_to_call = eval(Expr(:function, Expr(:call, func_expr, args...), func_expr.args[2:end]...)) + + # Call the newly created function with the provided arguments + function_to_call(args...) +end + """ Think and choose action # Arguments @@ -97,9 +108,9 @@ function decisionMaker(a::T1, state::T2)::String where {T1<:agent, T2<:AbstractD You should only respond with interleaving step-by-step Thought, Action, Observation steps. Thought can reason about the current situation, and Action can be three types: - 1) Chatbox[text], which you can use to interact with the user. - 2) Winestock[query], which you can use to find wine in your inventory. - 3) Finish[answer], which returns your wine reccommendation to the user. + 1) winestock[query], which you can use to find wine in your inventory. + 2) chatbox[text], which you can use to interact with the user. + 3) finish[answer], which returns your wine reccommendation to the user. You should only respond in JSON format as describe below: { @@ -115,15 +126,15 @@ function decisionMaker(a::T1, state::T2)::String where {T1<:agent, T2<:AbstractD { "Question": "I would like to buy a sedan.", "Thought_1": "I have many cars in my inventory suitable for several usage scenarios.", - "Thought_2": "It would be better if I knew what the user intends to do with his car."1, + "Thought_2": "It would be better if I knew what the user intends to do with his car.", "Thought_3": "I will ask the user what is the intended usecase", - "Action_1": {"action": "Chatbox", "input": "What will you use it for?"} + "Action_1": {"action": "chatbox", "input": "What will you use it for?"} } { "Question": "I'm looking for a sedan.", "Thought_1": "I have many types of sedans in my inventory, each with diverse features.", "Thought_2": "It would be easier to make a recommendation if I knew what feature the user is looking for. I should ask the user.", - "Action_1": {"action": "Chatbox", "input": "Do you have any specific feature in mind?"} + "Action_1": {"action": "chatbox", "input": "Do you have any specific feature in mind?"} } $reflect @@ -157,25 +168,20 @@ end """ - Arguments\n - ----- - - Return\n - ----- +# Arguments + +# Return - Example\n - ----- - ```jldoctest - julia> - ``` +# Example +```jldoctest +julia> +``` - TODO\n - ----- - [] update docstring - [] implement the function +# TODO + - [] update docstring + - [] implement the function - Signature\n - ----- +# Signature """ function stateValueEstimator() @@ -184,25 +190,20 @@ end """ - Arguments\n - ----- - - Return\n - ----- +# Arguments + +# Return - Example\n - ----- - ```jldoctest - julia> - ``` +# Example +```jldoctest +julia> +``` - TODO\n - ----- - [] update docstring - [] implement the function +# TODO + - [] update docstring + - [] implement the function - Signature\n - ----- +# Signature """ function reflector() @@ -211,91 +212,80 @@ end """ - Arguments\n - ----- - - Return\n - ----- +# Arguments + +# Return - Example\n - ----- - ```jldoctest - julia> - ``` +# Example +```jldoctest +julia> +``` - TODO\n - ----- - [] update docstring - [] implement the function - [] implement RAG to pull similar experience +# TODO + - [] update docstring + - [] implement the function - Signature\n - ----- +# Signature """ function isterminal() end - - """ Chat with llm. - Arguments\n - ----- - a::agent - an agent - - Return\n - ----- - None +# Arguments + `a::agent` + an agent + +# Return + None - Example\n - ----- - ```jldoctest - julia> using JSON3, UUIDs, Dates, FileIO, MQTTClient, ChatAgent - julia> const mqttBroker = "mqtt.yiem.cc" - julia> mqttclient, connection = MakeConnection(mqttBroker, 1883) - julia> 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, - ), - ) - julia> msgMeta = Dict( - :msgPurpose=> "updateStatus", - :from=> "agent", - :to=> "llmAI", - :requestresponse=> "request", - :sendto=> "", # destination topic - :replyTo=> "agent/api/v0.1.0/txt/response", # requester ask responseer to send reply to this topic - :repondToMsgId=> "", # responseer is responseing to this msg id - :taskstatus=> "", # "complete", "fail", "waiting" or other status - :timestamp=> Dates.now(), - :msgId=> "$(uuid4())", - ) - julia> a = ChatAgent.agentReflex( - "Jene", - mqttclient, - msgMeta, - agentConfigTopic, # I need a function to send msg to config topic to get load balancer - role=:sommelier, - tools=tools - ) - julia> newAgent = ChatAgent.agentReact(agent) - julia> response = ChatAgent.conversation(newAgent, "Hi! how are you?") - ``` +# Example +```jldoctest +julia> using JSON3, UUIDs, Dates, FileIO, MQTTClient, ChatAgent +julia> const mqttBroker = "mqtt.yiem.cc" +julia> mqttclient, connection = MakeConnection(mqttBroker, 1883) +julia> 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, + ), + ) +julia> msgMeta = Dict( + :msgPurpose=> "updateStatus", + :from=> "agent", + :to=> "llmAI", + :requestresponse=> "request", + :sendto=> "", # destination topic + :replyTo=> "agent/api/v0.1.0/txt/response", # requester ask responseer to send reply to this topic + :repondToMsgId=> "", # responseer is responseing to this msg id + :taskstatus=> "", # "complete", "fail", "waiting" or other status + :timestamp=> Dates.now(), + :msgId=> "$(uuid4())", +) +julia> a = ChatAgent.agentReflex( + "Jene", + mqttclient, + msgMeta, + agentConfigTopic, # I need a function to send msg to config topic to get load balancer + role=:sommelier, + tools=tools + ) +julia> newAgent = ChatAgent.agentReact(agent) +julia> response = ChatAgent.conversation(newAgent, "Hi! how are you?") +``` - Signature\n - ----- +# TODO +- [] update docstring +- [PENDING] MCTS() for planning + +# Signature """ function conversation(a::T, userinput::Dict) where {T<:agent} - """ - [] update document - [PENDING] MCTS() for planning - """ + # "newtopic" command to delete chat history if userinput[:text] == "newtopic" clearhistory(a) diff --git a/src/llmfunction.jl b/src/llmfunction.jl index d90968f..ee30e5c 100644 --- a/src/llmfunction.jl +++ b/src/llmfunction.jl @@ -8,6 +8,286 @@ using ..type, ..util # ---------------------------------------------- 100 --------------------------------------------- # +""" + +# Arguments + +# Return + +# Example +```jldoctest +julia> +``` + +# TODO + - [] update docstring + - [] implement the function + +# Signature +""" +function chatbox(input::T) where {T<:AbstractString} + # put in model format +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 diff --git a/src/mcts.jl b/src/mcts.jl index 154bb35..b4e568f 100644 --- a/src/mcts.jl +++ b/src/mcts.jl @@ -9,7 +9,7 @@ export MCTSNode, runMCTS using Dates, UUIDs, DataStructures, JSON3, Random using GeneralUtils -using ..type +using ..type, ..llmfunction # ---------------------------------------------- 100 --------------------------------------------- # @@ -232,8 +232,19 @@ julia> """ function transition(a::T1, state::T2, action::T3, actioninput::T3) where {T1<:agent, T2<:AbstractDict, T3<:AbstractString} +error("--> transition") - error("--> transition") + # map action and input() to llm function + # result = + # if action == "chatbox" + # chatbox(input) + # elseif + + # else + + # end + + end """ diff --git a/test/runtest.jl b/test/runtest.jl index 56759dd..d425e83 100644 --- a/test/runtest.jl +++ b/test/runtest.jl @@ -6,7 +6,7 @@ using Base.Threads config = copy(JSON3.read("config.json")) -instanceInternalTopic = config[:serviceInternalTopic][:value] * "/1" +instanceInternalTopic = config[:serviceInternalTopic][:mqtttopic] * "/1" client, connection = MakeConnection(config[:mqttServerInfo][:value][:broker], config[:mqttServerInfo][:value][:port])