Files
YiemAgent/src/llmfunction.jl
narawat lamaiin 8907156522 update
2024-05-04 21:17:02 +07:00

485 lines
14 KiB
Julia

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|>
# # <About yourself>
# # Your are a helpful assistant.
# # </About yourself>
# # <You have the following conversion table>
# # 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
# # </You have the following conversion table>
# # <Your job>
# # 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
# # </Your job>
# # <Example 1>
# # 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;
# # </Example 1>
# # <Example 2>
# # 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;
# # </Example 2>
# # </s>
# # <|query|>
# # $query
# # </s>
# # <|assistant|>
# # """
# prompt =
# """
# <s>
# <|system|>
# <About yourself>
# Your are a helpful assistant.
# </About yourself>
# <You have the following conversion table>
# 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
# </You have the following conversion table>
# <Your job>
# 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
# </Your job>
# </|system|>
# <Example 1>
# <query>
# {\"wine type\": \"white\", \"wine characteristics\": \"full-bodied | off-dry | low acidity | low to medium tannin\", \"price\": {\"max\": \"50\"}}
# </query>
# <|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;
# </|assistant|>
# </Example 1>
# <Example 2>
# <query>
# {\"wine characteristics\":\"low-bodied | a little sweet | low-medium tannin\",\"price\":\"22 USD\",\"occasion\":\"anniversary\",\"wine type\":\"Rose\",\"food\":\"American dishes\"}
# </query>
# <|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;
# </|assistant|>
# </Example 2>
# </s>
# <query>
# $query
# </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|>", "</s>", "<|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