This commit is contained in:
narawat lamaiin
2024-08-07 20:41:20 +07:00
parent 99f19c2796
commit 5228e2cfe1
7 changed files with 1831 additions and 112 deletions

View File

@@ -2,7 +2,7 @@
julia_version = "1.10.4" julia_version = "1.10.4"
manifest_format = "2.0" manifest_format = "2.0"
project_hash = "03625e2270b5f9b2a2b6b43af674dcefbd8f4f9d" project_hash = "bf6c32becbc917fa1c33558e7aa59c1aac5237e3"
[[deps.AliasTables]] [[deps.AliasTables]]
deps = ["PtrArrays", "Random"] deps = ["PtrArrays", "Random"]
@@ -647,7 +647,7 @@ uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0" version = "0.7.0"
[[deps.SQLLLM]] [[deps.SQLLLM]]
deps = ["CSV", "CondaPkg", "DataFrames", "DataStructures", "Dates", "DispatchDoctor", "FileIO", "FormatCorrector", "GeneralUtils", "HTTP", "JSON3", "LLMMCTS", "LibPQ", "MQTTClient", "PrettyPrinting", "PythonCall", "Random", "Revise", "Tables", "URIs", "UUIDs"] deps = ["CSV", "CondaPkg", "DataFrames", "DataStructures", "Dates", "DispatchDoctor", "FileIO", "FormatCorrector", "GeneralUtils", "HTTP", "JSON3", "LLMMCTS", "LibPQ", "MQTTClient", "PrettyPrinting", "PythonCall", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"]
path = "/appfolder/app/privatejuliapkg/SQLLLM" path = "/appfolder/app/privatejuliapkg/SQLLLM"
uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3" uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
version = "0.1.0" version = "0.1.0"

View File

@@ -18,5 +18,6 @@ PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
SQLLLM = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3" SQLLLM = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

1595
src/interface BACKUP 2.jl Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ module interface
export addNewMessage, conversation, decisionMaker, evaluator, reflector export addNewMessage, conversation, decisionMaker, evaluator, reflector
# isterminal, # isterminal,
using JSON3, DataStructures, Dates, UUIDs, HTTP, Random, MQTTClient, PrettyPrinting using JSON3, DataStructures, Dates, UUIDs, HTTP, Random, MQTTClient, PrettyPrinting, Serialization
using GeneralUtils, LLMMCTS using GeneralUtils, LLMMCTS
using ..type, ..util, ..llmfunction using ..type, ..util, ..llmfunction
@@ -80,7 +80,7 @@ julia> config = Dict(
julia> output_thoughtDict = Dict( julia> output_thoughtDict = Dict(
:thought_1 => "The customer wants to buy a bottle of wine. This is a good start!", :thought_1 => "The customer wants to buy a bottle of wine. This is a good start!",
:action_1 => Dict{Symbol, Any}( :action_1 => Dict{Symbol, Any}(
:action=>"Chatbox", :action=>"CHATBOX",
:input=>"What occasion are you buying the wine for?" :input=>"What occasion are you buying the wine for?"
), ),
:observation_1 => "" :observation_1 => ""
@@ -93,7 +93,6 @@ julia> output_thoughtDict = Dict(
- [] implement RAG to pull similar experience - [] implement RAG to pull similar experience
- [] use customerinfo - [] use customerinfo
- [] user storeinfo - [] user storeinfo
- BUG LLM recommend wine before check inventory
# Signature # Signature
""" """
@@ -143,7 +142,7 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent}
# You should only respond with interleaving Thought, Action, Observation steps. # You should only respond with interleaving Thought, Action, Observation steps.
# Thought can reason about the current situation, and Action can be three types: # Thought can reason about the current situation, and Action can be three types:
# 1) winestock[query], which you can use to find wine in your inventory. The more input data the better. # 1) winestock[query], which you can use to find wine in your inventory. The more input data the better.
# 2) chatbox[text], which you can use to interact with the user. # 2) CHATBOX[text], which you can use to interact with the user.
# After each observation, provide the next Thought and next Action. # After each observation, provide the next Thought and next Action.
# You should only respond in JSON format as describe below: # You should only respond in JSON format as describe below:
@@ -162,7 +161,7 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent}
# } # }
# { # {
# "thought": "I have a few color for the user to choose from. I will ask him what color he likes.", # "thought": "I have a few color for the user to choose from. I will ask him what color he likes.",
# "action": {"name": "chatbox", "input": "Which color do you like?"} # "action": {"name": "CHATBOX", "input": "Which color do you like?"}
# "observation": "I'll take black." # "observation": "I'll take black."
# } # }
@@ -210,42 +209,43 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent}
# Let's begin! # Let's begin!
# """ # """
# QandA = generatequestion(a, text2textInstructLLM)
systemmsg = systemmsg =
""" """
You are a website-based, attentive, and polite sommelier working for an online wine store. You are currently talking with the user. You are a internet-based, polite sommelier working for an online wine store.
Your task is to help the user choose the best wine from your inventory that matches the user preferences. You are currently talking with the user.
Your goal is to recommend the best wines from your inventory that match the user's preferences.
Your current task is to decide what action to take so that you can achieve your goal.
Definitions: At each round of conversation, you will be given the current situation:
"observation" is result of the preceding immediate action. Your ongoing conversation with the user: ...
I found the best matched wines from inventory: ...
At each round of conversation, the user will give you the current situation:
Context: ...
Your conversation with the user: ...
You MUST follow the following guidelines: You MUST follow the following guidelines:
- Generally speaking, your inventory has some wines from France, the United States, Australia, Spain, and Italy but you won't know which wines are in stock until you check your inventory. - Generally speaking, your inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know which wines your store carries until you check your inventory.
- Use the "get to know the user's preferences, then check inventory" strategy to help the user, as there are many wines in the inventory. - Use the "understand-then-check" inventory strategy to understand the user, as there are many wines in the inventory.
- After recommending wines to the user, ask if there is anything else you can help with, but do not offer any extra services. If the user doesn't need anything else, say thank you and goodbye.
- Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar. - Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar.
- After the user chose the wine, end the conversation politely.
You should follow the following guidelines as you see fit: You should follow the following guidelines as you see fit:
- If the user interrupts, prioritize the user. - If the user interrupts, prioritize the user.
- Get to know how much the user willing to spend. - If you don't already know, find out the user's budget.
- Get to know type of wine the user is looking for e.g. red, white, sparkling, rose, dessert, fortified. - If you don't already know, find out the type of wine the user is looking for, such as red, white, sparkling, rose, dessert, fortified.
- Get to know what occasion the user is buying wine for. - If you don't already know, find out the occasion for which the user is buying wine.
- Get to know what characteristics of wine the user is looking for e.g. tannin, sweetness, intensity, acidity. - If you don't already know, find out the characteristics of wine the user is looking for, such as tannin, sweetness, intensity, acidity.
- Get to know what food will be served with wine. - If you don't already know, find out what food will be served with wine.
You should then respond to the user with interleaving Thought, Plan, Action and Observation: You should then respond to the user with interleaving Thought, Plan, Action:
- thought: - thought:
1) State your reasoning about the current situation. 1) State your reasoning about the current situation.
- plan: Based on the current situation, state a complete plan to complete the task. Be specific. - plan: Based on the current situation, state a complete plan to complete the task. Be specific.
- action_name (Must be aligned with your plan): Can be one of the following functions: - action_name (Must be aligned with your plan): The name of the action which can be one of the following functions:
1) CHATBOX[text], which you can use to talk with the user. "text" is in verbal English. 1) CHATBOX [input], which you can use to generate conversation in order to communicate with the user. The input is your intention for the talk. Be specific.
2) CHECKINVENTORY[query], which you can use to check info about wine in your inventory. "query" is a search term in verbal English. 2) CHECKINVENTORY [input], 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. Good query example: black car, a stereo, 200 mile range, electric motor.
Good query example: How many car brand are from Asia? Good query example: How many car brand are from Asia?
- action_input: input to the action - action_input: input details of the action
- mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No" - mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No"
You should only respond in format as described below: You should only respond in format as described below:
@@ -258,24 +258,25 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent}
Let's begin! Let's begin!
""" """
# context = length(a.memory[:shortmem]) > 0 ? vectorOfDictToText(a.memory[:shortmem], withkey=false) : "DO not recommending wine because inventory has not been searched yet"
context = context =
if length(a.memory[:shortmem]) > 0 if length(a.memory[:shortmem]) > 0
vectorOfDictToText(a.memory[:shortmem], withkey=false) x = vectorOfDictToText(a.memory[:shortmem], withkey=false)
x = split(x, "More details:")
y = x[2]
"I have searched the inventory and this is what I found: $y"
else else
"None" ""
end end
chathistory = vectorOfDictToText(a.chathistory) chathistory = vectorOfDictToText(a.chathistory)
checkinventory_flag = "" errornote = ""
response = nothing # placeholder for show when error msg show up response = nothing # placeholder for show when error msg show up
for attempt in 1:10 for attempt in 1:10
usermsg = usermsg =
""" """
Context: $context
Your conversation with the user: $chathistory) Your conversation with the user: $chathistory)
$checkinventory_flag $context
$errornote
""" """
_prompt = _prompt =
@@ -296,14 +297,19 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent}
responsedict = GeneralUtils.textToDict(response, responsedict = GeneralUtils.textToDict(response,
["thought", "plan", "action_name", "action_input", "mentioning_wine"], ["thought", "plan", "action_name", "action_input", "mentioning_wine"],
rightmarker=":", symbolkey=true) rightmarker=":", symbolkey=true)
# if occursin('[', responsedict[:action_name])
# action_input = GeneralUtils.getStringBetweenCharacters(responsedict[:action_name], '[', ']')
# action_name = string(split(responsedict[:action_name], '[')[1])
# end
if responsedict[:action_name] ["CHATBOX", "CHECKINVENTORY"] if responsedict[:action_name] ["RECOMMEMDBOX", "CHATBOX", "CHECKINVENTORY"]
error("decisionMaker didn't use the given functions ", @__LINE__) errornote = "You must use the given functions"
error("You must use the given functions ", @__FILE__, " ", @__LINE__)
end end
for i [:thought, :plan, :action_name] for i [:thought, :plan, :action_name]
if length(responsedict[i]) == 0 if length(responsedict[i]) == 0
error("$i is empty ", @__LINE__) error("$i is empty ", @__FILE__, " ", @__LINE__)
end end
end end
@@ -323,14 +329,18 @@ function decisionMaker(a::T)::Dict{Symbol, Any} where {T<:agent}
isMemEmpty = isempty(a.memory[:shortmem]) isMemEmpty = isempty(a.memory[:shortmem])
if occursin("Yes", responsedict[:mentioning_wine]) && isMemEmpty && if occursin("Yes", responsedict[:mentioning_wine]) && isMemEmpty &&
responsedict[:action_name] != "CHECKINVENTORY" responsedict[:action_name] != "CHECKINVENTORY"
checkinventory_flag = "Note: You must check your inventory before recommending wine to the user." errornote = "Note: You must check your inventory before recommending wine to the user."
error( "You must check your inventory before recommending wine") error( "You must check your inventory before recommending wine")
else else
checkinventory_flag = "" errornote = ""
end end
delete!(responsedict, :mentioning_wine) delete!(responsedict, :mentioning_wine)
# if length(a.memory[:shortmem]) > 0 && responsedict[:action_name] != "RECOMMEMDBOX"
# errornote = "Note: You have found the best matched wines for the user. Use R them."
# error("found wines but not recommending")
# end
return responsedict return responsedict
catch e catch e
io = IOBuffer() io = IOBuffer()
@@ -390,7 +400,7 @@ end
# You should only respond with interleaving Thought, Action, Observation steps. # You should only respond with interleaving Thought, Action, Observation steps.
# Thought can reason about the current situation, and Action can be three types: # Thought can reason about the current situation, and Action can be three types:
# 1) winestock[query], which you can use to find wine in your inventory. The more input data the better. # 1) winestock[query], which you can use to find wine in your inventory. The more input data the better.
# 2) chatbox[text], which you can use to interact with the user. # 2) CHATBOX[text], which you can use to interact with the user.
# After each observation, provide the next Thought and next Action. # After each observation, provide the next Thought and next Action.
# You should only respond in JSON format as describe below: # You should only respond in JSON format as describe below:
@@ -409,7 +419,7 @@ end
# } # }
# { # {
# "thought": "I have a few color for the user to choose from. I will ask him what color he likes.", # "thought": "I have a few color for the user to choose from. I will ask him what color he likes.",
# "action": {"name": "chatbox", "input": "Which color do you like?"} # "action": {"name": "CHATBOX", "input": "Which color do you like?"}
# "observation": "I'll take black." # "observation": "I'll take black."
# } # }
@@ -472,7 +482,7 @@ end
# thought::AbstractString = thoughtDict[:thought] # thought::AbstractString = thoughtDict[:thought]
# actionname::AbstractString = thoughtDict[:action][:name] # actionname::AbstractString = thoughtDict[:action][:name]
# actioninput::AbstractString = thoughtDict[:action][:input] # actioninput::AbstractString = thoughtDict[:action][:input]
# if actionname ∈ ["winestock", "chatbox", "recommendbox"] # if actionname ∈ ["winestock", "CHATBOX", "recommendbox"]
# # LLM use available function # # LLM use available function
# elseif thought == "" # elseif thought == ""
# error("DecisionMaker has no thought") # error("DecisionMaker has no thought")
@@ -562,7 +572,7 @@ function evaluator(config::T1, state::T2
"action_1": {"name": "inventory", "input": "pen with 4 color and a pencil."}, "action_1": {"name": "inventory", "input": "pen with 4 color and a pencil."},
"observation_1": "I found {1: "Pilot Dr. grip 4-in-1 pen", 2: "Rotting pencil"}", "observation_1": "I found {1: "Pilot Dr. grip 4-in-1 pen", 2: "Rotting pencil"}",
"thought_2": "Ok, I have what the user is asking. Let's tell the user.", "thought_2": "Ok, I have what the user is asking. Let's tell the user.",
"action_2": {"name": "chatbox", "input": "Yes, we do have a Pilot Dr. grip 4-in-1 pen and a Rotting pencil"}, "action_2": {"name": "CHATBOX", "input": "Yes, we do have a Pilot Dr. grip 4-in-1 pen and a Rotting pencil"},
"observation_1": "This is not what I wanted." "observation_1": "This is not what I wanted."
} }
assistant: assistant:
@@ -690,15 +700,15 @@ function reflector(config::T1, state::T2)::String where {T1<:AbstractDict, T2<:A
{ {
"question": "Hello, I would like a get a bottle of wine", "question": "Hello, I would like a get a bottle of wine",
"thought_1": "A customer wants to buy a bottle of wine. Before making a recommendation, I need to know more about their preferences.", "thought_1": "A customer wants to buy a bottle of wine. Before making a recommendation, I need to know more about their preferences.",
"action_1": {"name": "chatbox", "input": "What is the occasion for which you're buying this wine?"}, "action_1": {"name": "CHATBOX", "input": "What is the occasion for which you're buying this wine?"},
"observation_1": "We are holding a wedding party", "observation_1": "We are holding a wedding party",
"thought_2": "A wedding party, that's a great occasion! The customer might be looking for a celebratory drink. Let me ask some more questions to narrow down the options.", "thought_2": "A wedding party, that's a great occasion! The customer might be looking for a celebratory drink. Let me ask some more questions to narrow down the options.",
"action_2": {"name": "chatbox", "input": "What type of food will you be serving at the wedding?"}, "action_2": {"name": "CHATBOX", "input": "What type of food will you be serving at the wedding?"},
"observation_2": "It will be Thai dishes.", "observation_2": "It will be Thai dishes.",
"thought_3": "With Thai food, I should recommend a wine that complements its spicy and savory flavors. And since it's a celebratory occasion, the customer might prefer a full-bodied wine.", "thought_3": "With Thai food, I should recommend a wine that complements its spicy and savory flavors. And since it's a celebratory occasion, the customer might prefer a full-bodied wine.",
"action_3": {"name": "chatbox", "input": "What is your budget for this bottle of wine?"}, "action_3": {"name": "CHATBOX", "input": "What is your budget for this bottle of wine?"},
"observation_3": "I would spend up to 50 bucks.", "observation_3": "I would spend up to 50 bucks.",
"thought_4": "Now that I have some more information, it's time to narrow down the options.", "thought_4": "Now that I have some more information, it's time to narrow down the options.",
@@ -706,7 +716,7 @@ function reflector(config::T1, state::T2)::String where {T1<:AbstractDict, T2<:A
"observation_4": "I found the following wines in our stock: \n{\n 1: El Enemigo Cabernet Franc 2019\n2: Tantara Chardonnay 2017\n\n}\n", "observation_4": "I found the following wines in our stock: \n{\n 1: El Enemigo Cabernet Franc 2019\n2: Tantara Chardonnay 2017\n\n}\n",
"thought_5": "Now that I have a list of potential wines, I need to know more about the customer's taste preferences.", "thought_5": "Now that I have a list of potential wines, I need to know more about the customer's taste preferences.",
"action_5": {"name": "chatbox", "input": "What type of wine characteristics are you looking for? (e.g. t.e.g. tannin level, sweetness, intensity, acidity)"}, "action_5": {"name": "CHATBOX", "input": "What type of wine characteristics are you looking for? (e.g. t.e.g. tannin level, sweetness, intensity, acidity)"},
"observation_5": "I like full-bodied red wine with low tannin.", "observation_5": "I like full-bodied red wine with low tannin.",
"thought_6": "Now that I have more information about the customer's preferences, it's time to make a recommendation.", "thought_6": "Now that I have more information about the customer's preferences, it's time to make a recommendation.",
@@ -898,7 +908,7 @@ end
# actioninput = bestNextState[:thoughtHistory][latestActionKey][:input] # actioninput = bestNextState[:thoughtHistory][latestActionKey][:input]
# # transition # # transition
# if actionname == "chatbox" # if actionname == "CHATBOX"
# # add usermsg to a.chathistory # # add usermsg to a.chathistory
# addNewMessage(a, "assistant", actioninput) # addNewMessage(a, "assistant", actioninput)
# return actioninput # return actioninput
@@ -980,18 +990,16 @@ function conversation(a::T, userinput::Dict) where {T<:agent}
# add usermsg to a.chathistory # add usermsg to a.chathistory
addNewMessage(a, "user", userinput[:text]) addNewMessage(a, "user", userinput[:text])
actionname, result = think(a) #[WORKING] need to wrapped in a while loop to let it think UNTIL it use chatbox. Right now it has unsync bewteen think() and generatechat() # use dummy memory to check generatechat() for halucination (checking inventory)
for i in 1:3
# -------- use dummy memory to check generatechat() for halucination (checking inventory) -------- # actionname, result = think(a)
mem = deepcopy(a.memory)
if actionname == "CHATBOX" if actionname == "CHATBOX"
mem[:chatbox] = result break
else end
push!(mem[:shortmem], Dict(Symbol(actionname)=> result))
end end
# thought will be added to chat model via context # thought will be added to chat model via context
chatresponse = generatechat(mem, a.chathistory, a.text2textInstructLLM) chatresponse = generatechat(a.memory, a.chathistory, a.text2textInstructLLM)
# some time LLM said to user that it (checking inventory) but it is not. # some time LLM said to user that it (checking inventory) but it is not.
# if chatresponse want to check inventory but think() didn't checkinventory then do it # if chatresponse want to check inventory but think() didn't checkinventory then do it
@@ -1004,14 +1012,7 @@ function conversation(a::T, userinput::Dict) where {T<:agent}
# generate chatresponse again because we have force inventory check # generate chatresponse again because we have force inventory check
chatresponse = generatechat(a.memory, a.chathistory, a.text2textInstructLLM) chatresponse = generatechat(a.memory, a.chathistory, a.text2textInstructLLM)
else else
if actionname == "CHATBOX"
# skip
else
push!(a.memory[:shortmem], Dict(Symbol(actionname)=> result))
end
# since chatresponse does not halucinate i.e. no (check inventory), it does not need # since chatresponse does not halucinate i.e. no (check inventory), it does not need
# to regenerate again and con be use directly # to regenerate again and con be use directly
end end
@@ -1064,6 +1065,12 @@ function think(a::T)::NamedTuple{(:actionname, :result), Tuple{String, String}}
errormsg::Union{AbstractString, Nothing} = haskey(response, :errormsg) ? response[:errormsg] : nothing errormsg::Union{AbstractString, Nothing} = haskey(response, :errormsg) ? response[:errormsg] : nothing
success::Bool = haskey(response, :success) ? response[:success] : false success::Bool = haskey(response, :success) ? response[:success] : false
if actionname == "CHATBOX"
a.memory[:CHATBOX] = result
else
push!(a.memory[:shortmem], Dict(Symbol(actionname)=> result))
end
return (actionname=actionname, result=result) return (actionname=actionname, result=result)
end end
@@ -1217,19 +1224,17 @@ julia>
function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::Function) function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::Function)
systemmsg = systemmsg =
""" """
You are a website-based, attentive, and polite sommelier working for an online wine store. You are currently talking with the user. You are a website-based, polite sommelier working for an online wine store.
Your task is to help the user choose the best wine from your inventory that matches the user preferences. You are currently talking with the user.
Your task is to understand their preferences and then recommend the best wines from your inventory that match those preferences.
At each round of conversation, the user will give you the current situation: At each round of conversation, you will be given the current situation:
Context: ... Context: ...
Your earlier conversation with the user: ... Your ongoing conversation with the user: ...
Your current thoughts in your mind: ... Your current thoughts in your mind: ...
You must follow the following guidelines:
- Your thoughts matter.
You should then respond to the user with: You should then respond to the user with:
- chat: what do you want to say to the user based on the current situation - chat: Your conversation with the user according to your thoughts.
- mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No" - mentioning_wine: Are you mentioning specific wine name to the user? Can be "Yes" or "No"
You should only respond in format as described below: You should only respond in format as described below:
@@ -1239,18 +1244,27 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F
Let's begin! Let's begin!
""" """
context_1 = length(memory[:shortmem]) > 0 ? vectorOfDictToText(memory[:shortmem], withkey=false) : "None" context =
if length(memory[:shortmem]) > 0
x = vectorOfDictToText(memory[:shortmem], withkey=false)
x = split(x, "More details:")
y = x[2]
"I have searched the inventory and this is what I found: $y"
else
""
end
chathistory = vectorOfDictToText(chathistory) chathistory = vectorOfDictToText(chathistory)
checkinventory_flag = "" errornote = ""
response = nothing # placeholder for show when error msg show up response = nothing # placeholder for show when error msg show up
for attempt in 1:5 for attempt in 1:5
usermsg = usermsg =
""" """
Context: $context_1 Your conversation with the user: $chathistory)
Your earlier conversation with the user: $chathistory) $context
Your thoughts: $(memory[:chatbox]) Your thoughts: $(memory[:CHATBOX])
$checkinventory_flag $errornote
""" """
_prompt = _prompt =
@@ -1292,14 +1306,14 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F
# check if LLM recommend wine before checking inventory # check if LLM recommend wine before checking inventory
isMemEmpty = isempty(memory[:shortmem]) isMemEmpty = isempty(memory[:shortmem])
if occursin("Yes", responsedict[:mentioning_wine]) && isMemEmpty if occursin("Yes", responsedict[:mentioning_wine]) && isMemEmpty
checkinventory_flag = "Note: You must check your inventory before recommending wine to the user." errornote = "Note: You must check your inventory before recommending wine to the user."
error( "You must check your inventory before recommending wine") error( "You must check your inventory before recommending wine")
else else
checkinventory_flag = "" errornote = ""
end end
memory[:CHATBOX] = "" # delete content because it no longer used.
delete!(responsedict, :mentioning_wine) delete!(responsedict, :mentioning_wine)
result = responsedict[:chat] result = responsedict[:chat]
return result return result
@@ -1317,6 +1331,92 @@ function generatechat(memory::Dict, chathistory::Vector, text2textInstructLLM::F
end end
# function generatequestion(a, text2textInstructLLM::Function)::String
# systemmsg =
# """
# You are a helpful sommelier that generate multiple questions about the current situation.
# At each round of conversation, you will be given the current situation:
# User query: What's the user preferences about wine?
# Your work progress: ...
# You must follow the following guidelines:
# 1) Ask at least three questions but no more than five.
# 2) Your question should be specific, self-contained and not require any additional context.
# 3) Do not generate any question or comments at the end.
# You should then respond to the user with:
# - Reasoning: State your detailed reasoning of the current situation
# - Q: Your question
# - A: Your answer to the question.
# You must only respond in format as described below:
# Reasoning: ...
# Q 1: ...
# A 1: ...
# Q 2: ...
# A 2: ...
# Q 3: ...
# A 3: ...
# ...
# Let's begin!
# """
# workprogress = ""
# for (k, v) in state[:thoughtHistory]
# if k ∉ [:query]
# workprogress *= "$k: $v\n"
# end
# end
# usermsg =
# """
# $(context[:tablelist])
# User query: $(state[:thoughtHistory][:question])
# Your work progress: $workprogress
# """
# _prompt =
# [
# Dict(:name=> "system", :text=> systemmsg),
# Dict(:name=> "user", :text=> usermsg)
# ]
# # put in model format
# prompt = GeneralUtils.formatLLMtext(_prompt, "llama3instruct")
# prompt *=
# """
# <|start_header_id|>assistant<|end_header_id|>
# """
# response = nothing # store for show when error msg show up
# for attempt in 1:10
# try
# response = text2textInstructLLM(prompt)
# q_number = count("Q ", response)
# if q_number < 3
# error("too few questions only $q_number questions are generated ", @__FILE__, " ", @__LINE__)
# end
# println("--> generatequestion ", @__FILE__, " ", @__LINE__)
# pprintln(response)
# return response
# catch e
# io = IOBuffer()
# showerror(io, e)
# errorMsg = String(take!(io))
# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
# println("")
# println("Attempt $attempt. Error occurred: $errorMsg\n$st")
# println("")
# end
# end
# error("generatequestion failed to generate a thought ", response)
# end
# """ # """
@@ -1349,7 +1449,7 @@ end
# labeled by environmental observations about the situation, thoughts that can reason about # labeled by environmental observations about the situation, thoughts that can reason about
# the current situation and actions that can be three types: # the current situation and actions that can be three types:
# 1) winestock[query], which you can use to find wine in your inventory. # 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. # 2) CHATBOX[text], which you can use to interact with the user.
# 3) recommendbox[answer], which returns your wine recommendation to the user. # 3) recommendbox[answer], which returns your wine recommendation to the user.
# Given a question and a trajectory, evaluate its correctness and provide your reasoning and # Given a question and a trajectory, evaluate its correctness and provide your reasoning and
@@ -1381,7 +1481,7 @@ end
# "action_1": {"name": "inventory", "input": "pen with 4 color and a pencil."}, # "action_1": {"name": "inventory", "input": "pen with 4 color and a pencil."},
# "observation_1": "I found {1: "Pilot Dr. grip 4-in-1 pen", 2: "Rotting pencil"}", # "observation_1": "I found {1: "Pilot Dr. grip 4-in-1 pen", 2: "Rotting pencil"}",
# "thought_2": "Ok, I have what the user is asking. Let's tell the user.", # "thought_2": "Ok, I have what the user is asking. Let's tell the user.",
# "action_2": {"name": "chatbox", "input": "Yes, we do have a Pilot Dr. grip 4-in-1 pen and a Rotting pencil"}, # "action_2": {"name": "CHATBOX", "input": "Yes, we do have a Pilot Dr. grip 4-in-1 pen and a Rotting pencil"},
# "observation_1": "This is not what I wanted." # "observation_1": "This is not what I wanted."
# } # }
# {"evaluation": "This trajectory is incorrect as my search term should be related to a 4-colors pen with a pencil in it, # {"evaluation": "This trajectory is incorrect as my search term should be related to a 4-colors pen with a pencil in it,

View File

@@ -345,6 +345,7 @@ function extractWineAttributes_1(a::T1, input::T2
- occasion: ... - occasion: ...
- food_to_be_paired_with_wine: food that the user will be served with wine - food_to_be_paired_with_wine: food that the user will be served with wine
- country: wine's country of origin - country: wine's country of origin
- region: wine's region of origin such as Burgundy, Napa Valley
- grape variety: a single name of grape used to make wine. - grape variety: a single name of grape used to make wine.
- flavors: Names of items that the wine tastes like. - flavors: Names of items that the wine tastes like.
- aromas: wine's aroma - aromas: wine's aroma
@@ -356,6 +357,7 @@ function extractWineAttributes_1(a::T1, input::T2
occasion: ... occasion: ...
food_to_be_paired_with_wine: ... food_to_be_paired_with_wine: ...
country: ... country: ...
region: ...
grape_variety: ... grape_variety: ...
flavors: ... flavors: ...
aromas: ... aromas: ...
@@ -363,11 +365,14 @@ function extractWineAttributes_1(a::T1, input::T2
Let's begin! Let's begin!
""" """
# chathistory = vectorOfDictToText(a.chathistory) attributes = ["reasoning", "wine_type", "price", "occasion", "food_to_be_paired_with_wine", "country", "region", "grape_variety", "flavors", "aromas"]
errornote = ""
for attempt in 1:5
usermsg = usermsg =
""" """
User's query: $input User's query: $input
$errornote
""" """
_prompt = _prompt =
@@ -383,9 +388,6 @@ function extractWineAttributes_1(a::T1, input::T2
<|start_header_id|>assistant<|end_header_id|> <|start_header_id|>assistant<|end_header_id|>
""" """
attributes = ["reasoning", "wine_type", "price", "occasion", "food_to_be_paired_with_wine", "country", "grape_variety", "flavors", "aromas"]
errornote = ""
for attempt in 1:5
try try
response = a.text2textInstructLLM(prompt) response = a.text2textInstructLLM(prompt)
responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true) responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true)
@@ -397,8 +399,20 @@ function extractWineAttributes_1(a::T1, input::T2
end end
#[PENDING] check if grape_variety has more than 1 name #[PENDING] check if grape_variety has more than 1 name
if length(split(responsedict[:grape_variety], ",")) > 1 x = length(split(responsedict[:grape_variety], ",")) * length(split(responsedict[:grape_variety], "/"))
error("multiple name in grape_variety is not allowed") if x > 1
errornote = "only a single name in grape_variety is allowed"
error("only a single grape_variety name is allowed")
end
x = length(split(responsedict[:country], ",")) * length(split(responsedict[:country], "/"))
if x > 1
errornote = "only a single name in country is allowed"
error("only a single country name is allowed")
end
x = length(split(responsedict[:region], ",")) * length(split(responsedict[:region], "/"))
if x > 1
errornote = "only a single name in region is allowed"
error("only a single region name is allowed")
end end
responsedict[:flavors] = replace(responsedict[:flavors], "notes"=>"") responsedict[:flavors] = replace(responsedict[:flavors], "notes"=>"")

9
test/etc.jl Normal file
View File

@@ -0,0 +1,9 @@
using GeneralUtils
response = "trajectory_evaluation:\nThe trajectory is correct so far. The thought accurately reflects the user's question, and the action taken is a valid attempt to retrieve data from the database that matches the specified criteria.\n\nanswer_evaluation:\nThe observation provides information about two red wines from Bordeaux rive droite in France, which partially answers the question. However, it does not provide a complete answer as it only lists the wine names and characteristics, but does not explicitly state whether there are any other wines that match the criteria.\n\naccepted_as_answer: No\n\nscore: 6\nThe trajectory is mostly correct, but the observation does not fully address the question.\n\nsuggestion: Consider adding more filters or parameters to the database query to retrieve a complete list of wines that match the specified criteria."
responsedict = GeneralUtils.textToDict(response,
["trajectory_evaluation", "answer_evaluation", "accepted_as_answer", "score", "suggestion"],
rightmarker=":", symbolkey=true)

View File

@@ -88,9 +88,9 @@ end
main() main()
""" """
I'm having a graduation party this evening. I have unlimited budget. I want a bottle of dry red wine. I'm joining a graduation party this evening. I have unlimited budget. I want a bottle of dry red wine.
It will be a casual party with no food serving. Well, it is a small casual party with close friend and no food serving.
I'm open to suggestion since I have no specific idea about wine other than I like full bodied wine from France. I'm open to suggestion since I have no specific idea about wine but I like full bodied wine from France.
The latter one seems nice. The latter one seems nice.
""" """