This commit is contained in:
narawat lamaiin
2024-05-19 17:55:15 +07:00
parent afc2b0ddd2
commit fcf8d855b8
5 changed files with 207 additions and 33 deletions

View File

@@ -3,7 +3,7 @@ module llmfunction
export virtualWineUserChatbox, jsoncorrection, winestock,
virtualWineUserRecommendbox, userChatbox, userRecommendbox
using HTTP, JSON3, URIs, Random
using HTTP, JSON3, URIs, Random, PrettyPrinting
using GeneralUtils
using ..type, ..util
@@ -164,28 +164,119 @@ julia>
# TODO
- [] update docs
- [x] write a prompt for virtual customer
# Signature
"""
function virtualWineUserChatbox(a::T1, input::T2
function virtualWineUserChatbox(a::T1, input::T2, virtualCustomerChatHistory
)::Union{Tuple{String, Number, Number, Bool}, Tuple{String, Nothing, Number, Bool}} 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__)")
previouswines =
"""
You have the following wines previously:
"""
systemmsg =
"""
You find yourself in a well-stocked wine store, engaged in a conversation with the store's knowledgeable sommelier.
You're on a quest to find a bottle of wine that aligns with your specific preferences and requirements.
The ideal wine you're seeking should meet the following criteria:
1. It should fit within your budget.
2. It should be suitable for the occasion you're planning.
3. It should pair well with the food you intend to serve.
4. It should be of a particular type of wine you prefer.
5. It should possess certain characteristics, including:
- The level of sweetness.
- The intensity of its flavor.
- The amount of tannin it contains.
- Its acidity level.
Here's the criteria details:
{
"budget": 50,
"occasion": "graduation ceremony",
"food pairing": "Thai food",
"type of wine": "red",
"wine sweetness level": "dry",
"wine intensity level": "full-bodied",
"wine tannin level": "low",
"wine acidity level": "medium",
}
You should only respond with "text", "select", "reward", "isterminal" steps.
"text" is your conversation.
"select" is an integer. Choose an option when presented with choices, or leave it null if none of the options satisfy you or if no choices are available.
"reward" is an integer, it can be three number:
1) 1 if you find the right wine.
2) 0 if you dont find the ideal wine.
3) -1 if youre dissatisfied with the sommeliers response.
"isterminal" can be false if you still want to talk with the sommelier, true otherwise.
You should only respond in JSON format as describe below:
{
"text": "your conversation",
"select": null,
"reward": 0,
"isterminal": false
}
Here are some examples:
{
"text": "My budget is 30 USD.",
"select": null,
"reward": 0,
"isterminal": false
}
{
"text": "I like the 2nd option.",
"select": 2,
"reward": 1,
"isterminal": true
}
Let's begin!
"""
pushfirst!(virtualCustomerChatHistory, Dict(:name=> "system", :text=> systemmsg))
# replace the :user key in chathistory to allow the virtual wine customer AI roleplay
chathistory::Vector{Dict{Symbol, Any}} = Vector{Dict{Symbol, Any}}()
for i in virtualCustomerChatHistory
newdict = Dict()
newdict[:name] =
if i[:name] == "user"
"you"
elseif i[:name] == "assistant"
"sommelier"
else
i[:name]
end
newdict[:text] = i[:text]
push!(chathistory, newdict)
end
push!(chathistory, Dict(:name=> "assistant", :text=> input))
# put in model format
prompt = formatLLMtext(chathistory, "llama3instruct")
prompt *=
"""
<|start_header_id|>you<|end_header_id|>
{"text"
"""
pprint(prompt)
externalService = a.config[:externalservice][:text2textinstruct]
# send formatted input to user using GeneralUtils.sendReceiveMqttMsg
msgMeta = GeneralUtils.generate_msgMeta(
virtualWineCustomer[:mqtttopic],
externalService[:mqtttopic],
senderName= "virtualWineUserChatbox",
senderId= a.id,
receiverName= "virtualWineCustomer",
receiverName= "text2textinstruct",
mqttBroker= a.config[:mqttServerInfo][:broker],
mqttBrokerPort= a.config[:mqttServerInfo][:port],
msgId = "dummyid" #CHANGE remove after testing finished
@@ -201,10 +292,33 @@ function virtualWineUserChatbox(a::T1, input::T2
attempt = 0
for attempt in 1:5
try
result = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120)
response = result[:response]
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120)
_responseJsonStr = response[:response][:text]
expectedJsonExample =
"""
Here is an expected JSON format:
{
"text": "...",
"select": "...",
"reward": "...",
"isterminal": "..."
}
"""
responseJsonStr = jsoncorrection(a, _responseJsonStr, expectedJsonExample)
responseDict = copy(JSON3.read(responseJsonStr))
return (response[:text], response[:select], response[:reward], response[:isterminal])
text = responseDict[:text]
select = responseDict[:select] == "null" ? nothing : responseDict[:select]
reward = responseDict[:reward]
isterminal = responseDict[:isterminal]
if text != "" && select != "" && reward != "" && isterminal != ""
# pass test
else
error("virtual customer not answer correctly")
end
return (text, select, reward, isterminal)
catch e
io = IOBuffer()
showerror(io, e)
@@ -218,6 +332,57 @@ function virtualWineUserChatbox(a::T1, input::T2
error("virtualWineUserChatbox failed to get a response")
end
# function virtualWineUserChatbox(a::T1, input::T2
# )::Union{Tuple{String, Number, Number, Bool}, Tuple{String, Nothing, Number, Bool}} 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= "virtualWineUserChatbox",
# 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,
# )
# )
# attempt = 0
# for attempt in 1:5
# try
# result = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120)
# response = result[:response]
# return (response[:text], response[:select], response[:reward], response[:isterminal])
# catch e
# io = IOBuffer()
# showerror(io, e)
# errorMsg = String(take!(io))
# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
# println("")
# @warn "Error occurred: $errorMsg\n$st"
# println("")
# end
# end
# error("virtualWineUserChatbox failed to get a response")
# end
""" Search wine in stock.
@@ -239,7 +404,7 @@ julia> result = winestock(agent, input)
# TODO
[] update docs
[PENDING] implement the function
[WORKING] implement the function
# Signature
"""
@@ -302,8 +467,8 @@ function jsoncorrection(a::T1, input::T2,
_prompt =
"""
Your goal are:
1) Use the info why the given JSON string failed to load and provide a corrected version that can be loaded by Python's json.load function.
2) The user need Corrected JSON string only. Do not provide any other info.
1) Use the expected JSON format as a guideline to check why the given JSON string failed to load and provide a corrected version that can be loaded by Python's json.load function.
2) Provide Corrected JSON string only. Do not provide any other info.
$correctJsonExample