Compare commits
5 Commits
v0.1.2
...
v0.1.3-dev
| Author | SHA1 | Date | |
|---|---|---|---|
| bfadd53033 | |||
| 8fc3afe348 | |||
| c60037226a | |||
|
|
db6c9c5f2b | ||
|
|
6504099959 |
@@ -1,7 +1,7 @@
|
|||||||
name = "YiemAgent"
|
name = "YiemAgent"
|
||||||
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
||||||
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
||||||
|
|||||||
633
src/interface.jl
633
src/interface.jl
@@ -1,6 +1,6 @@
|
|||||||
module interface
|
module interface
|
||||||
|
|
||||||
export addNewMessage, conversation, decisionMaker, evaluator, reflector, generatechat,
|
export addNewMessage, conversation, decisionMaker, reflector, generatechat,
|
||||||
generalconversation, detectWineryName, generateSituationReport
|
generalconversation, detectWineryName, generateSituationReport
|
||||||
|
|
||||||
using JSON3, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
using JSON3, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||||
@@ -55,6 +55,8 @@ end
|
|||||||
config
|
config
|
||||||
- `state::T2`
|
- `state::T2`
|
||||||
a game state
|
a game state
|
||||||
|
|
||||||
|
# Keyword Arguments
|
||||||
|
|
||||||
# Return
|
# Return
|
||||||
- `thoughtDict::Dict`
|
- `thoughtDict::Dict`
|
||||||
@@ -90,8 +92,6 @@ julia> output_thoughtDict = Dict(
|
|||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
- [] update docstring
|
- [] update docstring
|
||||||
- [x] implement the function
|
|
||||||
- [] implement RAG to pull similar experience
|
|
||||||
- [] use customerinfo
|
- [] use customerinfo
|
||||||
- [] user storeinfo
|
- [] user storeinfo
|
||||||
|
|
||||||
@@ -294,15 +294,13 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
|
|||||||
Dict(:name => "user", :text => usermsg)
|
Dict(:name => "user", :text => usermsg)
|
||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# change qwen format put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
prompt *= """
|
|
||||||
<|start_header_id|>assistant<|end_header_id|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = a.func[:text2textInstructLLM](prompt)
|
response = a.func[:text2textInstructLLM](prompt)
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
response = replace(response, '*'=>"")
|
response = replace(response, "**"=>"")
|
||||||
|
response = replace(response, "***"=>"")
|
||||||
response = replace(response, "<|eot_id|>"=>"")
|
response = replace(response, "<|eot_id|>"=>"")
|
||||||
|
|
||||||
# check if response contain more than one functions from ["CHATBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
# check if response contain more than one functions from ["CHATBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
||||||
@@ -318,9 +316,10 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
|
|||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
responsedict = GeneralUtils.textToDict(response,
|
header = ["Understanding:", "Reasoning:", "Plan:", "Action_name:", "Action_input:"]
|
||||||
["Understanding", "Reasoning", "Plan", "Action_name", "Action_input"],
|
dictkey = ["understanding", "reasoning", "plan", "action_name", "action_input"]
|
||||||
rightmarker=":", symbolkey=true, lowercasekey=true)
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
|
dictKey=dictkey, symbolkey=true)
|
||||||
|
|
||||||
if responsedict[:action_name] ∉ ["CHATBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
if responsedict[:action_name] ∉ ["CHATBOX", "CHECKINVENTORY", "ENDCONVERSATION"]
|
||||||
errornote = "You must use the given functions"
|
errornote = "You must use the given functions"
|
||||||
@@ -394,284 +393,280 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
""" Assigns a scalar value to each new child node to be used for selec-
|
# """ Assigns a scalar value to each new child node to be used for selec-
|
||||||
tion and backpropagation. This value effectively quantifies the agent's progress in task completion,
|
# tion and backpropagation. This value effectively quantifies the agent's progress in task completion,
|
||||||
serving as a heuristic to steer the search algorithm towards the most promising regions of the tree.
|
# serving as a heuristic to steer the search algorithm towards the most promising regions of the tree.
|
||||||
|
|
||||||
# Arguments
|
# # Arguments
|
||||||
- `a::T1`
|
# - `a::T1`
|
||||||
one of Yiem's agent
|
# one of Yiem's agent
|
||||||
- `state::T2`
|
# - `state::T2`
|
||||||
a game state
|
# a game state
|
||||||
|
|
||||||
# Return
|
# # Return
|
||||||
- `evaluation::Tuple{String, Integer}`
|
# - `evaluation::Tuple{String, Integer}`
|
||||||
evaluation and score
|
# evaluation and score
|
||||||
|
|
||||||
# Example
|
# # Example
|
||||||
```jldoctest
|
# ```jldoctest
|
||||||
julia>
|
# julia>
|
||||||
```
|
# ```
|
||||||
|
|
||||||
# Signature
|
# # Signature
|
||||||
"""
|
# """
|
||||||
function evaluator(config::T1, state::T2
|
# function evaluator(config::T1, state::T2
|
||||||
)::Tuple{String,Integer} where {T1<:AbstractDict,T2<:AbstractDict}
|
# )::Tuple{String,Integer} where {T1<:AbstractDict,T2<:AbstractDict}
|
||||||
|
|
||||||
systemmsg =
|
# systemmsg =
|
||||||
"""
|
# """
|
||||||
Analyze the trajectories of a solution to a question answering task. The trajectories are
|
# Analyze the trajectories of a solution to a question answering task. The trajectories are
|
||||||
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) CHECKINVENTORY[query], which you can use to find wine in your inventory.
|
# 1) CHECKINVENTORY[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.
|
||||||
|
|
||||||
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
|
||||||
analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories
|
# analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories
|
||||||
can be correct if the thoughts and actions so far are correct, even if the answer is not found
|
# can be correct if the thoughts and actions so far are correct, even if the answer is not found
|
||||||
yet. Do not generate additional thoughts or actions. Then ending with the correctness score s
|
# yet. Do not generate additional thoughts or actions. Then ending with the correctness score s
|
||||||
where s is an integer from 0 to 10.
|
# where s is an integer from 0 to 10.
|
||||||
|
|
||||||
You should only respond in JSON format as describe below:
|
# You should only respond in JSON format as describe below:
|
||||||
{"evaluation": "your evaluation", "score": "your evaluation score"}
|
# {"evaluation": "your evaluation", "score": "your evaluation score"}
|
||||||
|
|
||||||
Here are some examples:
|
# Here are some examples:
|
||||||
user:
|
# user:
|
||||||
{
|
# {
|
||||||
"question": "I'm looking for a sedan with an automatic driving feature.",
|
# "question": "I'm looking for a sedan with an automatic driving feature.",
|
||||||
"thought_1": "I have many types of sedans in my inventory, each with diverse features.",
|
# "thought_1": "I have many types of sedans in my inventory, each with diverse features.",
|
||||||
"thought_2": "But there is only 1 model that has the feature customer wanted.",
|
# "thought_2": "But there is only 1 model that has the feature customer wanted.",
|
||||||
"thought_3": "I should check our inventory first to see if we have it.",
|
# "thought_3": "I should check our inventory first to see if we have it.",
|
||||||
"action_1": {"name": "inventory", "input": "Yiem model A"},
|
# "action_1": {"name": "inventory", "input": "Yiem model A"},
|
||||||
"observation_1": "Yiem model A is in stock."
|
# "observation_1": "Yiem model A is in stock."
|
||||||
}
|
# }
|
||||||
assistant
|
# assistant
|
||||||
{
|
# {
|
||||||
"evaluation": "This trajectory is correct as it is reasonable to check an inventory for info provided in the question.
|
# "evaluation": "This trajectory is correct as it is reasonable to check an inventory for info provided in the question.
|
||||||
It is also better to have simple searches corresponding to a single entity, making this the best action.",
|
# It is also better to have simple searches corresponding to a single entity, making this the best action.",
|
||||||
"score": 10
|
# "score": 10
|
||||||
}
|
# }
|
||||||
|
|
||||||
user:
|
# user:
|
||||||
{
|
# {
|
||||||
"question": "Do you have an all-in-one pen with 4 colors and a pencil for sale?",
|
# "question": "Do you have an all-in-one pen with 4 colors and a pencil for sale?",
|
||||||
"thought_1": "Let me check our inventory first to see if I have it.",
|
# "thought_1": "Let me check our inventory first to see if I have it.",
|
||||||
"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:
|
||||||
{
|
# {
|
||||||
"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,
|
||||||
not a pen and a pencil seperately. A better search term should have been a 4-colors pen with a pencil, all-in-one.",
|
# not a pen and a pencil seperately. A better search term should have been a 4-colors pen with a pencil, all-in-one.",
|
||||||
"score": 0
|
# "score": 0
|
||||||
}
|
# }
|
||||||
|
|
||||||
Let's begin!
|
# Let's begin!
|
||||||
"""
|
# """
|
||||||
|
|
||||||
usermsg = """
|
# usermsg = """
|
||||||
$(JSON3.write(state[:thoughtHistory]))
|
# $(JSON3.write(state[:thoughtHistory]))
|
||||||
"""
|
# """
|
||||||
|
|
||||||
chathistory =
|
# chathistory =
|
||||||
[
|
# [
|
||||||
Dict(:name => "system", :text => systemmsg),
|
# Dict(:name => "system", :text => systemmsg),
|
||||||
Dict(:name => "user", :text => usermsg)
|
# Dict(:name => "user", :text => usermsg)
|
||||||
]
|
# ]
|
||||||
|
|
||||||
# put in model format
|
# # put in model format
|
||||||
prompt = formatLLMtext(chathistory, "llama3instruct")
|
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
prompt *= """
|
|
||||||
<|start_header_id|>assistant<|end_header_id|>
|
|
||||||
{
|
|
||||||
"""
|
|
||||||
|
|
||||||
pprint(prompt)
|
# pprint(prompt)
|
||||||
externalService = config[:externalservice][:text2textinstruct]
|
# externalService = config[:externalservice][:text2textinstruct]
|
||||||
|
|
||||||
|
|
||||||
# apply LLM specific instruct format
|
# # apply LLM specific instruct format
|
||||||
externalService = config[:externalservice][:text2textinstruct]
|
# externalService = config[:externalservice][:text2textinstruct]
|
||||||
|
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
# msgMeta = GeneralUtils.generate_msgMeta(
|
||||||
externalService[:mqtttopic],
|
# externalService[:mqtttopic],
|
||||||
senderName="evaluator",
|
# senderName="evaluator",
|
||||||
senderId=string(uuid4()),
|
# senderId=string(uuid4()),
|
||||||
receiverName="text2textinstruct",
|
# receiverName="text2textinstruct",
|
||||||
mqttBroker=config[:mqttServerInfo][:broker],
|
# mqttBroker=config[:mqttServerInfo][:broker],
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
# mqttBrokerPort=config[:mqttServerInfo][:port],
|
||||||
)
|
# )
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
# outgoingMsg = Dict(
|
||||||
:msgMeta => msgMeta,
|
# :msgMeta => msgMeta,
|
||||||
:payload => Dict(
|
# :payload => Dict(
|
||||||
:text => prompt,
|
# :text => prompt,
|
||||||
:kwargs => Dict(
|
# :kwargs => Dict(
|
||||||
:max_tokens => 512,
|
# :max_tokens => 512,
|
||||||
:stop => ["<|eot_id|>"],
|
# :stop => ["<|eot_id|>"],
|
||||||
)
|
# )
|
||||||
)
|
# )
|
||||||
)
|
# )
|
||||||
|
|
||||||
for attempt in 1:5
|
# for attempt in 1:5
|
||||||
try
|
# try
|
||||||
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
# response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
||||||
_responseJsonStr = response[:response][:text]
|
# _responseJsonStr = response[:response][:text]
|
||||||
expectedJsonExample = """
|
# expectedJsonExample = """
|
||||||
Here is an expected JSON format:
|
# Here is an expected JSON format:
|
||||||
{"evaluation": "...", "score": "..."}
|
# {"evaluation": "...", "score": "..."}
|
||||||
"""
|
# """
|
||||||
responseJsonStr = jsoncorrection(config, _responseJsonStr, expectedJsonExample)
|
# responseJsonStr = jsoncorrection(config, _responseJsonStr, expectedJsonExample)
|
||||||
evaluationDict = copy(JSON3.read(responseJsonStr))
|
# evaluationDict = copy(JSON3.read(responseJsonStr))
|
||||||
|
|
||||||
# check if dict has all required value
|
# # check if dict has all required value
|
||||||
dummya::AbstractString = evaluationDict[:evaluation]
|
# dummya::AbstractString = evaluationDict[:evaluation]
|
||||||
dummyb::Integer = evaluationDict[:score]
|
# dummyb::Integer = evaluationDict[:score]
|
||||||
|
|
||||||
return (evaluationDict[:evaluation], evaluationDict[:score])
|
# return (evaluationDict[:evaluation], evaluationDict[:score])
|
||||||
catch e
|
# catch e
|
||||||
io = IOBuffer()
|
# io = IOBuffer()
|
||||||
showerror(io, e)
|
# showerror(io, e)
|
||||||
errorMsg = String(take!(io))
|
# errorMsg = String(take!(io))
|
||||||
st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
|
# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
|
||||||
println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
# println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
error("evaluator failed to generate an evaluation")
|
# error("evaluator failed to generate an evaluation")
|
||||||
end
|
# end
|
||||||
|
|
||||||
|
|
||||||
"""
|
# """
|
||||||
|
|
||||||
# Arguments
|
# # Arguments
|
||||||
|
|
||||||
# Return
|
# # Return
|
||||||
|
|
||||||
# Example
|
# # Example
|
||||||
```jldoctest
|
# ```jldoctest
|
||||||
julia>
|
# julia>
|
||||||
```
|
# ```
|
||||||
|
|
||||||
# TODO
|
# # TODO
|
||||||
- [] update docstring
|
# - [] update docstring
|
||||||
- [x] implement the function
|
# - [x] implement the function
|
||||||
- [x] add try block. check result that it is expected before returning
|
# - [x] add try block. check result that it is expected before returning
|
||||||
|
|
||||||
# Signature
|
# # Signature
|
||||||
"""
|
# """
|
||||||
function reflector(config::T1, state::T2)::String where {T1<:AbstractDict,T2<:AbstractDict}
|
# function reflector(config::T1, state::T2)::String where {T1<:AbstractDict,T2<:AbstractDict}
|
||||||
# https://github.com/andyz245/LanguageAgentTreeSearch/blob/main/hotpot/hotpot.py
|
# # https://github.com/andyz245/LanguageAgentTreeSearch/blob/main/hotpot/hotpot.py
|
||||||
|
|
||||||
_prompt =
|
# _prompt =
|
||||||
"""
|
# """
|
||||||
You are a helpful sommelier working for a wine store.
|
# You are a helpful sommelier working for a wine store.
|
||||||
Your goal is to recommend the best wine from your inventory that match the user preferences.
|
# Your goal is to recommend the best wine from your inventory that match the user preferences.
|
||||||
You will be given a question and a trajectory of the previous help you've done for a user.
|
# You will be given a question and a trajectory of the previous help you've done for a user.
|
||||||
You were unsuccessful in helping the user either because you guessed the wrong answer with Finish[answer], or you didn't know the user enough.
|
# You were unsuccessful in helping the user either because you guessed the wrong answer with Finish[answer], or you didn't know the user enough.
|
||||||
In a few sentences, Diagnose a possible reason for failure and devise a new, concise, high level plan that aims to mitigate the same failure.
|
# In a few sentences, Diagnose a possible reason for failure and devise a new, concise, high level plan that aims to mitigate the same failure.
|
||||||
Use complete sentences.
|
# Use complete sentences.
|
||||||
|
|
||||||
You should only respond in JSON format as describe below:
|
# You should only respond in JSON format as describe below:
|
||||||
{"reflection": "your relection"}
|
# {"reflection": "your relection"}
|
||||||
|
|
||||||
Here are some examples:
|
# Here are some examples:
|
||||||
Previous Trial:
|
# Previous Trial:
|
||||||
{
|
# {
|
||||||
"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.",
|
||||||
"action_4": {"name": "winestock", "input": "red wine with full body, pairs well with spicy food, budget \$50"},
|
# "action_4": {"name": "winestock", "input": "red wine with full body, pairs well with spicy food, budget \$50"},
|
||||||
"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. tannin level, sweetness, intensity, acidity)"},
|
# "action_5": {"name": "CHATBOX", "input": "What type of wine characteristics are you looking for? (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.",
|
||||||
"action_6": {"name": "recommendbox", "input": "El Enemigo Cabernet Franc 2019"},
|
# "action_6": {"name": "recommendbox", "input": "El Enemigo Cabernet Franc 2019"},
|
||||||
"observation_6": "I don't like the one you recommend. I want dry wine."
|
# "observation_6": "I don't like the one you recommend. I want dry wine."
|
||||||
}
|
# }
|
||||||
|
|
||||||
{
|
# {
|
||||||
"reflection": "I asked the user about the occasion, food type, and budget, and then searched for wine in the inventory right away. However, I should have asked the user for the specific wine type and their preferences in order to gather more information before making a recommendation."
|
# "reflection": "I asked the user about the occasion, food type, and budget, and then searched for wine in the inventory right away. However, I should have asked the user for the specific wine type and their preferences in order to gather more information before making a recommendation."
|
||||||
}
|
# }
|
||||||
|
|
||||||
Let's begin!
|
# Let's begin!
|
||||||
|
|
||||||
Previous trial:
|
# Previous trial:
|
||||||
$(JSON3.write(state[:thoughtHistory]))
|
# $(JSON3.write(state[:thoughtHistory]))
|
||||||
{"reflection"
|
# {"reflection"
|
||||||
"""
|
# """
|
||||||
|
|
||||||
# apply LLM specific instruct format
|
# # apply LLM specific instruct format
|
||||||
externalService = config[:externalservice][:text2textinstruct]
|
# externalService = config[:externalservice][:text2textinstruct]
|
||||||
llminfo = externalService[:llminfo]
|
# llminfo = externalService[:llminfo]
|
||||||
prompt =
|
# prompt =
|
||||||
if llminfo[:name] == "llama3instruct"
|
# if llminfo[:name] == "llama3instruct"
|
||||||
formatLLMtext_llama3instruct("system", _prompt)
|
# formatLLMtext_llama3instruct("system", _prompt)
|
||||||
else
|
# else
|
||||||
error("llm model name is not defied yet $(@__LINE__)")
|
# error("llm model name is not defied yet $(@__LINE__)")
|
||||||
end
|
# end
|
||||||
|
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
# msgMeta = GeneralUtils.generate_msgMeta(
|
||||||
a.config[:externalservice][:text2textinstruct][:mqtttopic],
|
# a.config[:externalservice][:text2textinstruct][:mqtttopic],
|
||||||
senderName="reflector",
|
# senderName="reflector",
|
||||||
senderId=string(uuid4()),
|
# senderId=string(uuid4()),
|
||||||
receiverName="text2textinstruct",
|
# receiverName="text2textinstruct",
|
||||||
mqttBroker=config[:mqttServerInfo][:broker],
|
# mqttBroker=config[:mqttServerInfo][:broker],
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
# mqttBrokerPort=config[:mqttServerInfo][:port],
|
||||||
)
|
# )
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
# outgoingMsg = Dict(
|
||||||
:msgMeta => msgMeta,
|
# :msgMeta => msgMeta,
|
||||||
:payload => Dict(
|
# :payload => Dict(
|
||||||
:text => prompt,
|
# :text => prompt,
|
||||||
:kwargs => Dict(
|
# :kwargs => Dict(
|
||||||
:max_tokens => 512,
|
# :max_tokens => 512,
|
||||||
:stop => ["<|eot_id|>"],
|
# :stop => ["<|eot_id|>"],
|
||||||
)
|
# )
|
||||||
)
|
# )
|
||||||
)
|
# )
|
||||||
|
|
||||||
for attempt in 1:5
|
# for attempt in 1:5
|
||||||
try
|
# try
|
||||||
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
# response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg)
|
||||||
_responseJsonStr = response[:response][:text]
|
# _responseJsonStr = response[:response][:text]
|
||||||
expectedJsonExample = """
|
# expectedJsonExample = """
|
||||||
Here is an expected JSON format:
|
# Here is an expected JSON format:
|
||||||
{"reflection": "..."}
|
# {"reflection": "..."}
|
||||||
"""
|
# """
|
||||||
responseJsonStr = jsoncorrection(config, _responseJsonStr, expectedJsonExample)
|
# responseJsonStr = jsoncorrection(config, _responseJsonStr, expectedJsonExample)
|
||||||
reflectionDict = copy(JSON3.read(responseJsonStr))
|
# reflectionDict = copy(JSON3.read(responseJsonStr))
|
||||||
|
|
||||||
# check if dict has all required value
|
# # check if dict has all required value
|
||||||
dummya::AbstractString = reflectionDict[:reflection]
|
# dummya::AbstractString = reflectionDict[:reflection]
|
||||||
|
|
||||||
return reflectionDict[:reflection]
|
# return reflectionDict[:reflection]
|
||||||
catch e
|
# catch e
|
||||||
io = IOBuffer()
|
# io = IOBuffer()
|
||||||
showerror(io, e)
|
# showerror(io, e)
|
||||||
errorMsg = String(take!(io))
|
# errorMsg = String(take!(io))
|
||||||
st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
|
# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
|
||||||
println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
# println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
error("reflector failed to generate a thought")
|
# error("reflector failed to generate a thought")
|
||||||
end
|
# end
|
||||||
|
|
||||||
|
|
||||||
""" Chat with llm.
|
""" Chat with llm.
|
||||||
@@ -863,46 +858,6 @@ function think(a::T)::NamedTuple{(:actionname, :result),Tuple{String,String}} wh
|
|||||||
)
|
)
|
||||||
result = chatresponse
|
result = chatresponse
|
||||||
|
|
||||||
# # store thoughtDict after the conversation finish
|
|
||||||
# if a.memory[:events][end][:thought][:action_name] == "ENDCONVERSATION"
|
|
||||||
# # generateSituationReport in the agent didn't include the last conversation
|
|
||||||
# # so the function will be called here
|
|
||||||
# a.memory[:recap] = generateSituationReport(a, a.func[:text2textInstructLLM]; skiprecent=0)
|
|
||||||
|
|
||||||
# for (i, event) in enumerate(a.memory[:events])
|
|
||||||
# if event[:subject] == "assistant"
|
|
||||||
# # create timeline of the last 3 conversation except the last one.
|
|
||||||
# # The former will be used as caching key and the latter will be the caching target
|
|
||||||
# # in vector database
|
|
||||||
# all_recapkeys = keys(a.memory[:recap]) # recap as caching
|
|
||||||
# all_recapkeys_vec = [r for r in all_recapkeys] # convert to a vector
|
|
||||||
|
|
||||||
# # select from 1 to 2nd-to-lase event (i.e. excluding the latest which is assistant's response)
|
|
||||||
# _recapkeys_vec = all_recapkeys_vec[1:i-1]
|
|
||||||
|
|
||||||
# # select only previous 3 recaps
|
|
||||||
# recapkeys_vec =
|
|
||||||
# if length(_recapkeys_vec) <= 3 # 1st message is a user's hello msg
|
|
||||||
# _recapkeys_vec # choose all
|
|
||||||
# else
|
|
||||||
# _recapkeys_vec[end-2:end]
|
|
||||||
# end
|
|
||||||
# #[PENDING] if there is specific data such as number, donot store in database
|
|
||||||
# tempmem = DataStructures.OrderedDict()
|
|
||||||
# for k in recapkeys_vec
|
|
||||||
# tempmem[k] = a.memory[:recap][k]
|
|
||||||
# end
|
|
||||||
|
|
||||||
# recap = GeneralUtils.dictToString_noKey(tempmem)
|
|
||||||
# thoughtDict = a.memory[:events][i][:thought] # latest assistant thoughtDict
|
|
||||||
# a.func[:insertSommelierDecision](recap, thoughtDict)
|
|
||||||
# else
|
|
||||||
# # skip
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# println("Caching conversation done")
|
|
||||||
# end
|
|
||||||
|
|
||||||
elseif actionname == "CHECKINVENTORY"
|
elseif actionname == "CHECKINVENTORY"
|
||||||
if rawresponse !== nothing
|
if rawresponse !== nothing
|
||||||
vd = GeneralUtils.dfToVectorDict(rawresponse)
|
vd = GeneralUtils.dfToVectorDict(rawresponse)
|
||||||
@@ -999,6 +954,9 @@ function generatechat(a::sommelier, thoughtDict)
|
|||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
header = ["Chat:"]
|
||||||
|
dictkey = ["chat"]
|
||||||
|
|
||||||
# a.memory[:shortmem][:available_wine] is a vector of dictionary
|
# a.memory[:shortmem][:available_wine] is a vector of dictionary
|
||||||
context =
|
context =
|
||||||
if length(a.memory[:shortmem][:available_wine]) != 0
|
if length(a.memory[:shortmem][:available_wine]) != 0
|
||||||
@@ -1036,10 +994,7 @@ function generatechat(a::sommelier, thoughtDict)
|
|||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
prompt *= """
|
|
||||||
<|start_header_id|>assistant<|end_header_id|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
try
|
try
|
||||||
response = a.func[:text2textInstructLLM](prompt)
|
response = a.func[:text2textInstructLLM](prompt)
|
||||||
@@ -1054,22 +1009,27 @@ function generatechat(a::sommelier, thoughtDict)
|
|||||||
response = replace(response, '`' => "")
|
response = replace(response, '`' => "")
|
||||||
response = replace(response, "<|eot_id|>"=>"")
|
response = replace(response, "<|eot_id|>"=>"")
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
responsedict = GeneralUtils.textToDict(response, ["Chat"],
|
|
||||||
rightmarker=":", symbolkey=true, lowercasekey=true)
|
|
||||||
|
|
||||||
for i ∈ [:chat]
|
# check whether response has all header
|
||||||
if length(JSON3.write(responsedict[i])) == 0
|
detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||||
error("$i is empty ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
if sum(values(detected_kw)) < length(header)
|
||||||
end
|
errornote = "\nSQL decisionMaker() response does not have all header"
|
||||||
|
continue
|
||||||
|
elseif sum(values(detected_kw)) > length(header)
|
||||||
|
errornote = "\nSQL decisionMaker() response has duplicated header"
|
||||||
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
# check if there are more than 1 key per categories
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
for i ∈ [:chat]
|
dictKey=dictkey, symbolkey=true)
|
||||||
matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
|
|
||||||
if length(matchkeys) > 1
|
# # check if there are more than 1 key per categories
|
||||||
error("generatechat has more than one key per categories")
|
# for i ∈ Symbol.(dictkey)
|
||||||
end
|
# matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
|
||||||
end
|
# if length(matchkeys) > 1
|
||||||
|
# error("generatechat has more than one key per categories")
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
# check if Context: is in chat
|
# check if Context: is in chat
|
||||||
if occursin("Context:", responsedict[:chat])
|
if occursin("Context:", responsedict[:chat])
|
||||||
@@ -1161,10 +1121,7 @@ function generatechat(a::companion)
|
|||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
prompt *= """
|
|
||||||
<|start_header_id|>assistant<|end_header_id|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = a.text2textInstructLLM(prompt)
|
response = a.text2textInstructLLM(prompt)
|
||||||
|
|
||||||
@@ -1272,7 +1229,7 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St
|
|||||||
|
|
||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
context =
|
context =
|
||||||
if length(a.memory[:shortmem][:available_wine]) != 0
|
if length(a.memory[:shortmem][:available_wine]) != 0
|
||||||
"Available wines you've found in your inventory so far: $(availableWineToText(a.memory[:shortmem][:available_wine]))"
|
"Available wines you've found in your inventory so far: $(availableWineToText(a.memory[:shortmem][:available_wine]))"
|
||||||
@@ -1333,13 +1290,36 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St
|
|||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
prompt *= """
|
|
||||||
<|start_header_id|>assistant<|end_header_id|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
try
|
try
|
||||||
response = text2textInstructLLM(prompt)
|
response = text2textInstructLLM(prompt)
|
||||||
|
# make sure generatequestion() don't have wine name that is not from retailer inventory
|
||||||
|
# check whether an agent recommend wines before checking inventory or recommend wines
|
||||||
|
# outside its inventory
|
||||||
|
# ask LLM whether there are any winery mentioned in the response
|
||||||
|
mentioned_winery = detectWineryName(a, response)
|
||||||
|
if mentioned_winery != "None"
|
||||||
|
mentioned_winery = String.(strip.(split(mentioned_winery, ",")))
|
||||||
|
|
||||||
|
# check whether the wine is in event
|
||||||
|
isWineInEvent = false
|
||||||
|
for winename in mentioned_winery
|
||||||
|
for event in a.memory[:events]
|
||||||
|
if event[:outcome] !== nothing && occursin(winename, event[:outcome])
|
||||||
|
isWineInEvent = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# if wine is mentioned but not in timeline or shortmem,
|
||||||
|
# then the agent is not supposed to recommend the wine
|
||||||
|
if isWineInEvent == false
|
||||||
|
errornote = "Previously, You mentioned wines that is not in your inventory which is not allowed."
|
||||||
|
error("Previously, You mentioned wines that is not in your inventory which is not allowed.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# sometime LLM generate more than 1 Understanding:
|
# sometime LLM generate more than 1 Understanding:
|
||||||
understanding_number = count("Understanding:", response)
|
understanding_number = count("Understanding:", response)
|
||||||
@@ -1359,9 +1339,10 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St
|
|||||||
error("no answer found in the response ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
error("no answer found in the response ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
||||||
end
|
end
|
||||||
|
|
||||||
responsedict = GeneralUtils.textToDict(response,
|
header = ["Understanding:", "Q1:"]
|
||||||
["Understanding", "Q1"],
|
dictkey = ["understanding", "q1"]
|
||||||
rightmarker=":", symbolkey=true, lowercasekey=true)
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
|
dictKey=dictkey, symbolkey=true)
|
||||||
response = "Q1: " * responsedict[:q1]
|
response = "Q1: " * responsedict[:q1]
|
||||||
println("\n~~~ generatequestion ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
println("\n~~~ generatequestion ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
||||||
pprintln(response)
|
pprintln(response)
|
||||||
@@ -1412,6 +1393,9 @@ function generateSituationReport(a, text2textInstructLLM::Function; skiprecent::
|
|||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
header = ["Event_$i:" for i in eachindex(a.memory[:events])]
|
||||||
|
dictkey = lowercase.(["Event_$i" for i in eachindex(a.memory[:events])])
|
||||||
|
|
||||||
if length(a.memory[:events]) <= skiprecent
|
if length(a.memory[:events]) <= skiprecent
|
||||||
return nothing
|
return nothing
|
||||||
end
|
end
|
||||||
@@ -1437,15 +1421,11 @@ function generateSituationReport(a, text2textInstructLLM::Function; skiprecent::
|
|||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
prompt *= """
|
|
||||||
<|start_header_id|>assistant<|end_header_id|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = text2textInstructLLM(prompt)
|
response = text2textInstructLLM(prompt)
|
||||||
eventheader = ["Event_$i" for i in eachindex(a.memory[:events])]
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
responsedict = GeneralUtils.textToDict(response, eventheader,
|
dictKey=dictkey, symbolkey=true)
|
||||||
rightmarker=":", symbolkey=true)
|
|
||||||
|
|
||||||
println("\n~~~ generateSituationReport() ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
println("\n~~~ generateSituationReport() ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
||||||
pprintln(response)
|
pprintln(response)
|
||||||
@@ -1494,18 +1474,17 @@ function detectWineryName(a, text)
|
|||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
prompt *= """
|
|
||||||
<|start_header_id|>assistant<|end_header_id|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
try
|
try
|
||||||
response = a.func[:text2textInstructLLM](prompt)
|
response = a.func[:text2textInstructLLM](prompt)
|
||||||
println("\n~~~ detectWineryName() ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
println("\n~~~ detectWineryName() ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
||||||
pprintln(response)
|
pprintln(response)
|
||||||
|
|
||||||
responsedict = GeneralUtils.textToDict(response, ["winery_names"],
|
header = ["Winery_names:"]
|
||||||
rightmarker=":", symbolkey=true, lowercasekey=true)
|
dictkey = ["winery_names"]
|
||||||
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
|
dictKey=dictkey, symbolkey=true)
|
||||||
|
|
||||||
result = responsedict[:winery_names]
|
result = responsedict[:winery_names]
|
||||||
|
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ julia>
|
|||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
- [] update docstring
|
- [] update docstring
|
||||||
- [x] implement the function
|
- implement the function
|
||||||
|
|
||||||
# Signature
|
# Signature
|
||||||
"""
|
"""
|
||||||
@@ -336,31 +336,41 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
"""
|
"""
|
||||||
As a helpful sommelier, your task is to extract the user information from the user's query as much as possible to fill out user's preference form.
|
As a helpful sommelier, your task is to extract the user information from the user's query as much as possible to fill out user's preference form.
|
||||||
|
|
||||||
At each round of conversation, the user will give you the current situation:
|
At each round of conversation, the user will give you the following:
|
||||||
User's query: ...
|
User's query: ...
|
||||||
|
|
||||||
You must follow the following guidelines:
|
You must follow the following guidelines:
|
||||||
1) If specific information required in the preference form is not available in the query or there isn't any, mark with "NA" to indicate this.
|
- If specific information required in the preference form is not available in the query or there isn't any, mark with "NA" to indicate this.
|
||||||
Additionally, words like 'any' or 'unlimited' mean no information is available.
|
Additionally, words like 'any' or 'unlimited' mean no information is available.
|
||||||
2) Do not generate other comments.
|
- Do not generate other comments.
|
||||||
|
|
||||||
You should then respond to the user with the following points:
|
You should then respond to the user with:
|
||||||
- reasoning: state your understanding of the current situation
|
Comprehension: state your understanding of the current situation
|
||||||
- wine_name: name of the wine
|
Wine_name: name of the wine
|
||||||
- winery: name of the winery
|
Winery: name of the winery
|
||||||
- vintage: the year of the wine
|
Vintage: the year of the wine
|
||||||
- region: a region (NOT a country) where the wine is produced, such as Burgundy, Napa Valley, etc
|
Region: a region (NOT a country) where the wine is produced, such as Burgundy, Napa Valley, etc
|
||||||
- country: a country where the wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "United States"
|
Country: a country where the wine is produced. Can be "Austria", "Australia", "France", "Germany", "Italy", "Portugal", "Spain", "United States"
|
||||||
- wine_type: can be one of: "red", "white", "sparkling", "rose", "dessert" or "fortified"
|
Wine_type: can be one of: "red", "white", "sparkling", "rose", "dessert" or "fortified"
|
||||||
- grape_varietal: the name of the primary grape used to make the wine
|
Grape_varietal: the name of the primary grape used to make the wine
|
||||||
- tasting_notes: a brief description of the wine's taste, such as "butter", "oak", "fruity", etc
|
Tasting_notes: a brief description of the wine's taste, such as "butter", "oak", "fruity", etc
|
||||||
- wine_price: price range of wine.
|
Wine_price: price range of wine.
|
||||||
- occasion: the occasion the user is having the wine for
|
Occasion: the occasion the user is having the wine for
|
||||||
- food_to_be_paired_with_wine: food that the user will be served with the wine such as poultry, fish, steak, etc
|
Food_to_be_paired_with_wine: food that the user will be served with the wine such as poultry, fish, steak, etc
|
||||||
|
|
||||||
|
|
||||||
You should only respond in the user's preference form (JSON) as described below:
|
You should only respond in format as described below:
|
||||||
{"reasoning": ..., "winery": ..., "wine_name": ..., "vintage": ..., "region": ..., "country": ..., "wine_type": ..., "grape_varietal": ..., "tasting_notes": ..., "wine_price": ..., "occasion": ..., "food_to_be_paired_with_wine": ...}
|
Comprehension: ...
|
||||||
|
Wine_name: ...
|
||||||
|
Winery: ...
|
||||||
|
Vintage: ...
|
||||||
|
Region: ...
|
||||||
|
Country: ...
|
||||||
|
Wine_type:
|
||||||
|
Grape_varietal: ...
|
||||||
|
Tasting_notes: ...
|
||||||
|
Wine_price: ...
|
||||||
|
Occasion: ...
|
||||||
|
Food_to_be_paired_with_wine: ...
|
||||||
|
|
||||||
Here are some example:
|
Here are some example:
|
||||||
User's query: red, Chenin Blanc, Riesling, 20 USD
|
User's query: red, Chenin Blanc, Riesling, 20 USD
|
||||||
@@ -372,7 +382,8 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
attributes = ["reasoning", "winery", "wine_name", "vintage", "region", "country", "wine_type", "grape_varietal", "tasting_notes", "wine_price", "occasion", "food_to_be_paired_with_wine"]
|
header = ["Comprehension:", "Wine_name:", "Winery:", "Vintage:", "Region:", "Country:", "Wine_type:", "Grape_varietal:", "Tasting_notes:", "Wine_price:", "Occasion:", "Food_to_be_paired_with_wine:"]
|
||||||
|
dictkey = ["comprehension", "wine_name", "winery", "vintage", "region", "country", "wine_type", "grape_varietal", "tasting_notes", "wine_price", "occasion", "food_to_be_paired_with_wine"]
|
||||||
errornote = ""
|
errornote = ""
|
||||||
|
|
||||||
for attempt in 1:5
|
for attempt in 1:5
|
||||||
@@ -389,18 +400,13 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
prompt *=
|
|
||||||
"""
|
|
||||||
<|start_header_id|>assistant<|end_header_id|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = a.func[:text2textInstructLLM](prompt)
|
response = a.func[:text2textInstructLLM](prompt)
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
|
|
||||||
# check wheter all attributes are in the response
|
# check wheter all attributes are in the response
|
||||||
checkFlag = false
|
checkFlag = false
|
||||||
for word in attributes
|
for word in header
|
||||||
if !occursin(word, response)
|
if !occursin(word, response)
|
||||||
errornote = "$word attribute is missing in previous attempts"
|
errornote = "$word attribute is missing in previous attempts"
|
||||||
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
|
||||||
@@ -409,12 +415,20 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
checkFlag == true ? continue : nothing
|
checkFlag == true ? continue : nothing
|
||||||
|
|
||||||
|
# check whether response has all header
|
||||||
|
detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||||
|
if sum(values(detected_kw)) < length(header)
|
||||||
|
errornote = "\nYiemAgent extractWineAttributes_1() response does not have all header"
|
||||||
|
continue
|
||||||
|
elseif sum(values(detected_kw)) > length(header)
|
||||||
|
errornote = "\nYiemAgent extractWineAttributes_1() response has duplicated header"
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
|
dictKey=dictkey, symbolkey=true)
|
||||||
|
|
||||||
responsedict = copy(JSON3.read(response))
|
delete!(responsedict, :comprehension)
|
||||||
|
|
||||||
# convert
|
|
||||||
|
|
||||||
delete!(responsedict, :reasoning)
|
|
||||||
delete!(responsedict, :tasting_notes)
|
delete!(responsedict, :tasting_notes)
|
||||||
delete!(responsedict, :occasion)
|
delete!(responsedict, :occasion)
|
||||||
delete!(responsedict, :food_to_be_paired_with_wine)
|
delete!(responsedict, :food_to_be_paired_with_wine)
|
||||||
@@ -424,9 +438,9 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
|
|
||||||
# check if winery, wine_name, region, country, wine_type, grape_varietal's value are in the query because sometime AI halucinates
|
# check if winery, wine_name, region, country, wine_type, grape_varietal's value are in the query because sometime AI halucinates
|
||||||
checkFlag = false
|
checkFlag = false
|
||||||
for i in attributes
|
for i in dictkey
|
||||||
j = Symbol(i)
|
j = Symbol(i)
|
||||||
if j ∉ [:reasoning, :tasting_notes, :occasion, :food_to_be_paired_with_wine]
|
if j ∉ [:comprehension, :tasting_notes, :occasion, :food_to_be_paired_with_wine]
|
||||||
# in case j is wine_price it needs to be checked differently because its value is ranged
|
# in case j is wine_price it needs to be checked differently because its value is ranged
|
||||||
if j == :wine_price
|
if j == :wine_price
|
||||||
if responsedict[:wine_price] != "NA"
|
if responsedict[:wine_price] != "NA"
|
||||||
@@ -509,7 +523,7 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
|
|
||||||
conversiontable =
|
conversiontable =
|
||||||
"""
|
"""
|
||||||
Conversion Table:
|
<Conversion Table>
|
||||||
Intensity level:
|
Intensity level:
|
||||||
1 to 2: May correspond to "light-bodied" or a similar description.
|
1 to 2: May correspond to "light-bodied" or a similar description.
|
||||||
2 to 3: May correspond to "med light bodied", "medium light" or a similar description.
|
2 to 3: May correspond to "med light bodied", "medium light" or a similar description.
|
||||||
@@ -534,6 +548,7 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
3 to 4: May correspond to "medium acidity" or a similar description.
|
3 to 4: May correspond to "medium acidity" or a similar description.
|
||||||
4 to 5: May correspond to "semi high acidity" or a similar description.
|
4 to 5: May correspond to "semi high acidity" or a similar description.
|
||||||
4 to 5: May correspond to "high acidity" or a similar description.
|
4 to 5: May correspond to "high acidity" or a similar description.
|
||||||
|
</Conversion Table>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
systemmsg =
|
systemmsg =
|
||||||
@@ -547,67 +562,64 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
The preference form requires the following information:
|
The preference form requires the following information:
|
||||||
sweetness, acidity, tannin, intensity
|
sweetness, acidity, tannin, intensity
|
||||||
|
|
||||||
You must follow the following guidelines:
|
<You must follow the following guidelines>
|
||||||
1) If specific information required in the preference form is not available in the query or there isn't any, mark with 'NA' to indicate this.
|
1) If specific information required in the preference form is not available in the query or there isn't any, mark with 'NA' to indicate this.
|
||||||
Additionally, words like 'any' or 'unlimited' mean no information is available.
|
Additionally, words like 'any' or 'unlimited' mean no information is available.
|
||||||
2) Use the conversion table to convert the descriptive word level of sweetness, intensity, tannin, and acidity into a corresponding integer.
|
2) Use the conversion table to convert the descriptive word level of sweetness, intensity, tannin, and acidity into a corresponding integer.
|
||||||
3) Do not generate other comments.
|
3) Do not generate other comments.
|
||||||
|
</You must follow the following guidelines>
|
||||||
|
|
||||||
You should then respond to the user with the following points:
|
<You should then respond to the user with>
|
||||||
- sweetness_keyword: The exact keywords in the user's query describing the sweetness level of the wine.
|
Sweetness_keyword: The exact keywords in the user's query describing the sweetness level of the wine.
|
||||||
- sweetness: ( S ), where ( S ) represents integers indicating the range of sweetness levels. Example: 1-2
|
Sweetness: ( S ), where ( S ) represents integers indicating the range of sweetness levels. Example: 1-2
|
||||||
- acidity_keyword: The exact keywords in the user's query describing the acidity level of the wine.
|
Acidity_keyword: The exact keywords in the user's query describing the acidity level of the wine.
|
||||||
- acidity: ( A ), where ( A ) represents integers indicating the range of acidity level. Example: 3-5
|
Acidity: ( A ), where ( A ) represents integers indicating the range of acidity level. Example: 3-5
|
||||||
- tannin_keyword: The exact keywords in the user's query describing the tannin level of the wine.
|
Tannin_keyword: The exact keywords in the user's query describing the tannin level of the wine.
|
||||||
- tannin: ( T ), where ( T ) represents integers indicating the range of tannin level. Example: 1-3
|
Tannin: ( T ), where ( T ) represents integers indicating the range of tannin level. Example: 1-3
|
||||||
- intensity_keyword: The exact keywords in the user's query describing the intensity level of the wine.
|
Intensity_keyword: The exact keywords in the user's query describing the intensity level of the wine.
|
||||||
- intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
|
Intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
|
||||||
|
</You should then respond to the user with>
|
||||||
|
|
||||||
You should only respond in the form (JSON) as described below:
|
<You should only respond in format as described below>
|
||||||
{
|
Sweetness_keyword: ...
|
||||||
"sweetness_keyword": ...,
|
Sweetness: ...
|
||||||
"sweetness": ...,
|
Acidity_keyword: ...
|
||||||
"acidity_keyword": ...,
|
Acidity: ...
|
||||||
"acidity": ...,
|
Tannin_keyword: ...
|
||||||
"tannin_keyword": ...,
|
Tannin: ...
|
||||||
"tannin": ...,
|
Intensity_keyword: ...
|
||||||
"intensity_keyword": ...,
|
Intensity: ...
|
||||||
"intensity": ...
|
</You should only respond in format as described below>
|
||||||
}
|
|
||||||
|
|
||||||
Here are some examples:
|
<Here are some examples>
|
||||||
User's query: I want a wine with a medium-bodied, low acidity, medium tannin.
|
User's query: I want a wine with a medium-bodied, low acidity, medium tannin.
|
||||||
{
|
Sweetness_keyword: NA
|
||||||
"sweetness_keyword": "NA",
|
Sweetness: NA
|
||||||
"sweetness": "NA",
|
Acidity_keyword: low acidity
|
||||||
"acidity_keyword": "low acidity",
|
Acidity: 1-2
|
||||||
"acidity": "1-2",
|
Tannin_keyword: medium tannin
|
||||||
"tannin_keyword": "medium tannin",
|
Tannin: 3-4
|
||||||
"tannin": "3-4",
|
Intensity_keyword: medium-bodied
|
||||||
"intensity_keyword": "medium-bodied",
|
Intensity: 3-4
|
||||||
"intensity": "3-4"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
User's query: German red wine, under 100, pairs with spicy food
|
|
||||||
{
|
|
||||||
"sweetness_keyword": "NA",
|
|
||||||
"sweetness": "NA",
|
|
||||||
"acidity_keyword": "NA",
|
|
||||||
"acidity": "NA",
|
|
||||||
"tannin_keyword": "NA",
|
|
||||||
"tannin": "NA",
|
|
||||||
"intensity_keyword": "NA",
|
|
||||||
"intensity": "NA"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
User's query: German red wine, under 100, pairs with spicy food
|
||||||
|
Sweetness_keyword: NA
|
||||||
|
Sweetness: NA
|
||||||
|
Acidity_keyword: NA
|
||||||
|
Acidity: NA
|
||||||
|
Tannin_keyword: NA
|
||||||
|
Tannin: NA
|
||||||
|
Intensity_keyword: NA
|
||||||
|
Intensity: NA
|
||||||
|
</Here are some examples>
|
||||||
|
|
||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
|
header = ["Sweetness_keyword:", "Sweetness:", "Acidity_keyword:", "Acidity:", "Tannin_keyword:", "Tannin:", "Intensity_keyword:", "Intensity:"]
|
||||||
|
dictkey = ["sweetness_keyword", "sweetness", "acidity_keyword", "acidity", "tannin_keyword", "tannin", "intensity_keyword", "intensity"]
|
||||||
errornote = ""
|
errornote = ""
|
||||||
|
|
||||||
for attempt in 1:5
|
for attempt in 1:10
|
||||||
usermsg =
|
usermsg =
|
||||||
"""
|
"""
|
||||||
$conversiontable
|
$conversiontable
|
||||||
@@ -622,14 +634,22 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
]
|
]
|
||||||
|
|
||||||
# put in model format
|
# put in model format
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
prompt *=
|
|
||||||
"""
|
|
||||||
<|start_header_id|>assistant<|end_header_id|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = a.func[:text2textInstructLLM](prompt)
|
response = a.func[:text2textInstructLLM](prompt)
|
||||||
responsedict = copy(JSON3.read(response))
|
|
||||||
|
# check whether response has all header
|
||||||
|
detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||||
|
if sum(values(detected_kw)) < length(header)
|
||||||
|
errornote = "\nYiemAgent extractWineAttributes_2() response does not have all header"
|
||||||
|
continue
|
||||||
|
elseif sum(values(detected_kw)) > length(header)
|
||||||
|
errornote = "\nYiemAgent extractWineAttributes_2() response has duplicated header"
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
|
dictKey=dictkey, symbolkey=true)
|
||||||
|
|
||||||
# check whether each describing keyword is in the input to prevent halucination
|
# check whether each describing keyword is in the input to prevent halucination
|
||||||
for i in ["sweetness", "acidity", "tannin", "intensity"]
|
for i in ["sweetness", "acidity", "tannin", "intensity"]
|
||||||
@@ -753,8 +773,11 @@ function paraphrase(text2textInstructLLM::Function, text::String)
|
|||||||
response = replace(response, '$' => "USD")
|
response = replace(response, '$' => "USD")
|
||||||
response = replace(response, '`' => "")
|
response = replace(response, '`' => "")
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
responsedict = GeneralUtils.textToDict(response, ["Paraphrase"],
|
|
||||||
rightmarker=":", symbolkey=true, lowercasekey=true)
|
header = ["Paraphrase:"]
|
||||||
|
dictkey = ["paraphrase"]
|
||||||
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
|
dictKey=dictkey, symbolkey=true)
|
||||||
|
|
||||||
for i ∈ [:paraphrase]
|
for i ∈ [:paraphrase]
|
||||||
if length(JSON3.write(responsedict[i])) == 0
|
if length(JSON3.write(responsedict[i])) == 0
|
||||||
|
|||||||
@@ -31,26 +31,46 @@
|
|||||||
"description": "organization name"
|
"description": "organization name"
|
||||||
},
|
},
|
||||||
"externalservice": {
|
"externalservice": {
|
||||||
"text2textinstruct": {
|
"loadbalancer": {
|
||||||
"mqtttopic": "/loadbalancer/requestingservice",
|
"mqtttopic": "/loadbalancer/requestingservice",
|
||||||
"description": "text to text service with instruct LLM",
|
"description": "text to text service with instruct LLM"
|
||||||
"llminfo": {
|
},
|
||||||
"name": "llama3instruct"
|
"text2textinstruct": {
|
||||||
}
|
"mqtttopic": "/loadbalancer/requestingservice",
|
||||||
},
|
"description": "text to text service with instruct LLM",
|
||||||
"virtualWineCustomer_1": {
|
"llminfo": {
|
||||||
"mqtttopic": "/virtualenvironment/winecustomer",
|
"name": "llama3instruct"
|
||||||
"description": "text to text service with instruct LLM that act as wine customer",
|
}
|
||||||
"llminfo": {
|
},
|
||||||
"name": "llama3instruct"
|
"virtualWineCustomer_1": {
|
||||||
}
|
"mqtttopic": "/virtualenvironment/winecustomer",
|
||||||
},
|
"description": "text to text service with instruct LLM that act as wine customer",
|
||||||
"text2textchat": {
|
"llminfo": {
|
||||||
"mqtttopic": "/loadbalancer/requestingservice",
|
"name": "llama3instruct"
|
||||||
"description": "text to text service with instruct LLM",
|
}
|
||||||
"llminfo": {
|
},
|
||||||
"name": "llama3instruct"
|
"text2textchat": {
|
||||||
}
|
"mqtttopic": "/loadbalancer/requestingservice",
|
||||||
}
|
"description": "text to text service with instruct LLM",
|
||||||
|
"llminfo": {
|
||||||
|
"name": "llama3instruct"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wineDB" : {
|
||||||
|
"description": "A wine database connection info for LibPQ client",
|
||||||
|
"host": "192.168.88.12",
|
||||||
|
"port": 10201,
|
||||||
|
"dbname": "wineDB",
|
||||||
|
"user": "yiemtechnologies",
|
||||||
|
"password": "yiemtechnologies@Postgres_0.0"
|
||||||
|
},
|
||||||
|
"SQLVectorDB" : {
|
||||||
|
"description": "A wine database connection info for LibPQ client",
|
||||||
|
"host": "192.168.88.12",
|
||||||
|
"port": 10203,
|
||||||
|
"dbname": "SQLVectorDB",
|
||||||
|
"user": "yiemtechnologies",
|
||||||
|
"password": "yiemtechnologies@Postgres_0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
0
test/runtests.jl
Normal file
0
test/runtests.jl
Normal file
@@ -1,272 +1,292 @@
|
|||||||
using Revise
|
using Revise
|
||||||
using JSON, JSON3, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames
|
using JSON, JSON3, Dates, UUIDs, PrettyPrinting, LibPQ, Base64, DataFrames
|
||||||
using YiemAgent, GeneralUtils
|
using YiemAgent, GeneralUtils
|
||||||
using Base.Threads
|
using Base.Threads
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# load config
|
# load config
|
||||||
config = JSON3.read("./test/config.json")
|
config = JSON3.read("/appfolder/app/dev/YiemAgent/test/config.json")
|
||||||
# config = copy(JSON3.read("../mountvolume/config.json"))
|
# config = copy(JSON3.read("../mountvolume/config.json"))
|
||||||
|
|
||||||
|
|
||||||
function executeSQL(sql::T) where {T<:AbstractString}
|
function executeSQL(sql::T) where {T<:AbstractString}
|
||||||
DBconnection = LibPQ.Connection("host=192.168.88.12 port=10201 dbname=wineDB user=yiemtechnologies password=yiemtechnologies@Postgres_0.0")
|
host = config[:externalservice][:wineDB][:host]
|
||||||
result = LibPQ.execute(DBconnection, sql)
|
port = config[:externalservice][:wineDB][:port]
|
||||||
close(DBconnection)
|
dbname = config[:externalservice][:wineDB][:dbname]
|
||||||
return result
|
user = config[:externalservice][:wineDB][:user]
|
||||||
end
|
password = config[:externalservice][:wineDB][:password]
|
||||||
|
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
|
||||||
function executeSQLVectorDB(sql)
|
result = LibPQ.execute(DBconnection, sql)
|
||||||
DBconnection = LibPQ.Connection("host=192.168.88.12 port=10203 dbname=SQLVectorDB user=yiemtechnologies password=yiemtechnologies@Postgres_0.0")
|
close(DBconnection)
|
||||||
result = LibPQ.execute(DBconnection, sql)
|
return result
|
||||||
close(DBconnection)
|
end
|
||||||
return result
|
|
||||||
end
|
function executeSQLVectorDB(sql)
|
||||||
|
host = config[:externalservice][:SQLVectorDB][:host]
|
||||||
function text2textInstructLLM(prompt::String)
|
port = config[:externalservice][:SQLVectorDB][:port]
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
dbname = config[:externalservice][:SQLVectorDB][:dbname]
|
||||||
config[:externalservice][:text2textinstruct][:mqtttopic];
|
user = config[:externalservice][:SQLVectorDB][:user]
|
||||||
msgPurpose="inference",
|
password = config[:externalservice][:SQLVectorDB][:password]
|
||||||
senderName="yiemagent",
|
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
|
||||||
senderId=string(uuid4()),
|
result = LibPQ.execute(DBconnection, sql)
|
||||||
receiverName="text2textinstruct",
|
close(DBconnection)
|
||||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
return result
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
end
|
||||||
)
|
|
||||||
|
function text2textInstructLLM(prompt::String; maxattempt=3)
|
||||||
outgoingMsg = Dict(
|
msgMeta = GeneralUtils.generate_msgMeta(
|
||||||
:msgMeta => msgMeta,
|
config[:externalservice][:loadbalancer][:mqtttopic];
|
||||||
:payload => Dict(
|
msgPurpose="inference",
|
||||||
:text => prompt,
|
senderName="yiemagent",
|
||||||
:kwargs => Dict(
|
senderId=sessionId,
|
||||||
:num_ctx => 16384,
|
receiverName="text2textinstruct_small",
|
||||||
:temperature => 0.2,
|
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
||||||
)
|
mqttBrokerPort=config[:mqttServerInfo][:port],
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
outgoingMsg = Dict(
|
||||||
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=6000)
|
:msgMeta => msgMeta,
|
||||||
response = _response[:response][:text]
|
:payload => Dict(
|
||||||
|
:text => prompt,
|
||||||
return response
|
:kwargs => Dict(
|
||||||
end
|
:num_ctx => 16384,
|
||||||
|
:temperature => 0.2,
|
||||||
# get text embedding from a LLM service
|
)
|
||||||
function getEmbedding(text::T) where {T<:AbstractString}
|
)
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
)
|
||||||
config[:externalservice][:text2textinstruct][:mqtttopic];
|
|
||||||
msgPurpose="embedding",
|
response = nothing
|
||||||
senderName="yiemagent",
|
for attempts in 1:maxattempt
|
||||||
senderId=string(uuid4()),
|
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=300, maxattempt=maxattempt)
|
||||||
receiverName="text2textinstruct",
|
payload = _response[:response]
|
||||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
if _response[:success] && payload[:text] !== nothing
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
response = _response[:response][:text]
|
||||||
)
|
break
|
||||||
|
else
|
||||||
outgoingMsg = Dict(
|
println("\n<text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
:msgMeta => msgMeta,
|
pprintln(outgoingMsg)
|
||||||
:payload => Dict(
|
println("</text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
||||||
:text => [text] # must be a vector of string
|
sleep(3)
|
||||||
)
|
end
|
||||||
)
|
end
|
||||||
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=6000)
|
|
||||||
embedding = response[:response][:embeddings]
|
return response
|
||||||
return embedding
|
end
|
||||||
end
|
|
||||||
|
# get text embedding from a LLM service
|
||||||
function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
|
function getEmbedding(text::T) where {T<:AbstractString}
|
||||||
vectorDB::Function; limit::Integer=1
|
msgMeta = GeneralUtils.generate_msgMeta(
|
||||||
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
|
config[:externalservice][:loadbalancer][:mqtttopic];
|
||||||
|
msgPurpose="embedding",
|
||||||
# get embedding from LLM service
|
senderName="yiemagent",
|
||||||
embedding = getEmbedding(text)[1]
|
senderId=sessionId,
|
||||||
|
receiverName="text2textinstruct_small",
|
||||||
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
|
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
||||||
sql = """
|
mqttBrokerPort=config[:mqttServerInfo][:port],
|
||||||
SELECT *, $embeddingColumnName <-> '$embedding' as distance
|
)
|
||||||
FROM $tablename
|
|
||||||
ORDER BY distance LIMIT $limit;
|
outgoingMsg = Dict(
|
||||||
"""
|
:msgMeta => msgMeta,
|
||||||
response = vectorDB(sql)
|
:payload => Dict(
|
||||||
df = DataFrame(response)
|
:text => [text] # must be a vector of string
|
||||||
return df
|
)
|
||||||
end
|
)
|
||||||
|
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120)
|
||||||
|
embedding = response[:response][:embeddings]
|
||||||
function similarSQLVectorDB(query; maxdistance::Integer=100)
|
return embedding
|
||||||
tablename = "sqlllm_decision_repository"
|
end
|
||||||
# get embedding of the query
|
|
||||||
df = findSimilarTextFromVectorDB(query, tablename,
|
function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
vectorDB::Function; limit::Integer=1
|
||||||
row, col = size(df)
|
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
# get embedding from LLM service
|
||||||
if row != 0 && distance < maxdistance
|
embedding = getEmbedding(text)[1]
|
||||||
# if there is usable SQL, return it.
|
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
|
||||||
output_b64 = df[1, :function_output_base64] # pick the closest match
|
sql = """
|
||||||
output_str = String(base64decode(output_b64))
|
SELECT *, $embeddingColumnName <-> '$embedding' as distance
|
||||||
rowid = df[1, :id]
|
FROM $tablename
|
||||||
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
|
ORDER BY distance LIMIT $limit;
|
||||||
return (dict=output_str, distance=distance)
|
"""
|
||||||
else
|
response = vectorDB(sql)
|
||||||
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
|
df = DataFrame(response)
|
||||||
return (dict=nothing, distance=nothing)
|
return df
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
function similarSQLVectorDB(query; maxdistance::Integer=100)
|
||||||
|
tablename = "sqlllm_decision_repository"
|
||||||
function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=1) where {T1<:AbstractString, T2<:AbstractString}
|
# get embedding of the query
|
||||||
tablename = "sqlllm_decision_repository"
|
df = findSimilarTextFromVectorDB(query, tablename,
|
||||||
# get embedding of the query
|
"function_input_embedding", executeSQLVectorDB)
|
||||||
# query = state[:thoughtHistory][:question]
|
# println(df[1, [:id, :function_output]])
|
||||||
df = findSimilarTextFromVectorDB(query, tablename,
|
row, col = size(df)
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
distance = row == 0 ? Inf : df[1, :distance]
|
||||||
row, col = size(df)
|
# distance = 100 # CHANGE this is for testing only
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
if row != 0 && distance < maxdistance
|
||||||
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
# if there is usable SQL, return it.
|
||||||
query_embedding = getEmbedding(query)[1]
|
output_b64 = df[1, :function_output_base64] # pick the closest match
|
||||||
query = replace(query, "'" => "")
|
output_str = String(base64decode(output_b64))
|
||||||
sql_base64 = base64encode(SQL)
|
rowid = df[1, :id]
|
||||||
sql_ = replace(SQL, "'" => "")
|
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
return (dict=output_str, distance=distance)
|
||||||
sql = """
|
else
|
||||||
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
|
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
"""
|
return (dict=nothing, distance=nothing)
|
||||||
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
|
end
|
||||||
println(sql)
|
end
|
||||||
_ = executeSQLVectorDB(sql)
|
|
||||||
end
|
function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=3) where {T1<:AbstractString, T2<:AbstractString}
|
||||||
end
|
tablename = "sqlllm_decision_repository"
|
||||||
|
# get embedding of the query
|
||||||
|
# query = state[:thoughtHistory][:question]
|
||||||
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=5
|
df = findSimilarTextFromVectorDB(query, tablename,
|
||||||
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
|
"function_input_embedding", executeSQLVectorDB)
|
||||||
tablename = "sommelier_decision_repository"
|
row, col = size(df)
|
||||||
# find similar
|
distance = row == 0 ? Inf : df[1, :distance]
|
||||||
println("\n~~~ search vectorDB for this: $recentevents ", @__FILE__, " ", @__LINE__)
|
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
||||||
df = findSimilarTextFromVectorDB(recentevents, tablename,
|
query_embedding = getEmbedding(query)[1]
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
query = replace(query, "'" => "")
|
||||||
row, col = size(df)
|
sql_base64 = base64encode(SQL)
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
sql_ = replace(SQL, "'" => "")
|
||||||
if row != 0 && distance < maxdistance
|
|
||||||
# if there is usable decision, return it.
|
sql = """
|
||||||
rowid = df[1, :id]
|
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
|
||||||
println("\n~~~ found similar decision. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
|
"""
|
||||||
output_b64 = df[1, :function_output_base64] # pick the closest match
|
# println("\n~~~ added new decision to vectorDB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
_output_str = String(base64decode(output_b64))
|
# println(sql)
|
||||||
output = copy(JSON3.read(_output_str))
|
_ = executeSQLVectorDB(sql)
|
||||||
return output
|
end
|
||||||
else
|
end
|
||||||
println("\n~~~ similar decision not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
|
|
||||||
return nothing
|
|
||||||
end
|
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=5
|
||||||
end
|
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
|
||||||
|
tablename = "sommelier_decision_repository"
|
||||||
|
# find similar
|
||||||
function insertSommelierDecision(recentevents::T1, decision::T2; maxdistance::Integer=5
|
println("\n~~~ search vectorDB for this: $recentevents ", @__FILE__, " ", @__LINE__)
|
||||||
) where {T1<:AbstractString, T2<:AbstractDict}
|
df = findSimilarTextFromVectorDB(recentevents, tablename,
|
||||||
tablename = "sommelier_decision_repository"
|
"function_input_embedding", executeSQLVectorDB)
|
||||||
# find similar
|
row, col = size(df)
|
||||||
df = findSimilarTextFromVectorDB(recentevents, tablename,
|
distance = row == 0 ? Inf : df[1, :distance]
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
if row != 0 && distance < maxdistance
|
||||||
row, col = size(df)
|
# if there is usable decision, return it.
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
rowid = df[1, :id]
|
||||||
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
println("\n~~~ found similar decision. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
|
||||||
recentevents_embedding = a.func[:getEmbedding](recentevents)[1]
|
output_b64 = df[1, :function_output_base64] # pick the closest match
|
||||||
recentevents = replace(recentevents, "'" => "")
|
_output_str = String(base64decode(output_b64))
|
||||||
decision_json = JSON3.write(decision)
|
output = copy(JSON3.read(_output_str))
|
||||||
decision_base64 = base64encode(decision_json)
|
return output
|
||||||
decision = replace(decision_json, "'" => "")
|
else
|
||||||
|
println("\n~~~ similar decision not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
|
||||||
sql = """
|
return nothing
|
||||||
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$recentevents', '$decision', '$decision_base64', '$recentevents_embedding');
|
end
|
||||||
"""
|
end
|
||||||
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
|
|
||||||
println(sql)
|
|
||||||
_ = executeSQLVectorDB(sql)
|
function insertSommelierDecision(recentevents::T1, decision::T2; maxdistance::Integer=5
|
||||||
else
|
) where {T1<:AbstractString, T2<:AbstractDict}
|
||||||
println("~~~ similar decision previously cached, distance $distance ", @__FILE__, " ", @__LINE__)
|
tablename = "sommelier_decision_repository"
|
||||||
end
|
# find similar
|
||||||
end
|
df = findSimilarTextFromVectorDB(recentevents, tablename,
|
||||||
|
"function_input_embedding", executeSQLVectorDB)
|
||||||
|
row, col = size(df)
|
||||||
sessionId = "12345"
|
distance = row == 0 ? Inf : df[1, :distance]
|
||||||
|
if row == 0 || distance > maxdistance # no close enough SQL stored in the database
|
||||||
externalFunction = (
|
recentevents_embedding = a.func[:getEmbedding](recentevents)[1]
|
||||||
getEmbedding=getEmbedding,
|
recentevents = replace(recentevents, "'" => "")
|
||||||
text2textInstructLLM=text2textInstructLLM,
|
decision_json = JSON3.write(decision)
|
||||||
executeSQL=executeSQL,
|
decision_base64 = base64encode(decision_json)
|
||||||
similarSQLVectorDB=similarSQLVectorDB,
|
decision = replace(decision_json, "'" => "")
|
||||||
insertSQLVectorDB=insertSQLVectorDB,
|
|
||||||
similarSommelierDecision=similarSommelierDecision,
|
sql = """
|
||||||
insertSommelierDecision=insertSommelierDecision,
|
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$recentevents', '$decision', '$decision_base64', '$recentevents_embedding');
|
||||||
)
|
"""
|
||||||
|
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
|
||||||
|
println(sql)
|
||||||
|
_ = executeSQLVectorDB(sql)
|
||||||
a = YiemAgent.sommelier(
|
else
|
||||||
externalFunction;
|
println("~~~ similar decision previously cached, distance $distance ", @__FILE__, " ", @__LINE__)
|
||||||
name="Ton",
|
end
|
||||||
id=sessionId, # agent instance id
|
end
|
||||||
retailername="Yiem",
|
|
||||||
)
|
|
||||||
|
sessionId = "12345"
|
||||||
while true
|
|
||||||
println("your respond: ")
|
externalFunction = (
|
||||||
user_answer = readline()
|
getEmbedding=getEmbedding,
|
||||||
response = YiemAgent.conversation(a, Dict(:text=> user_answer))
|
text2textInstructLLM=text2textInstructLLM,
|
||||||
println("\n$response")
|
executeSQL=executeSQL,
|
||||||
end
|
similarSQLVectorDB=similarSQLVectorDB,
|
||||||
|
insertSQLVectorDB=insertSQLVectorDB,
|
||||||
|
similarSommelierDecision=similarSommelierDecision,
|
||||||
# response = YiemAgent.conversation(a, Dict(:text=> "I want to get a French red wine under 100."))
|
insertSommelierDecision=insertSommelierDecision,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
a = YiemAgent.sommelier(
|
||||||
|
externalFunction;
|
||||||
|
name="Ton",
|
||||||
|
id=sessionId, # agent instance id
|
||||||
|
retailername="Yiem",
|
||||||
|
)
|
||||||
|
|
||||||
|
while true
|
||||||
|
println("your respond: ")
|
||||||
|
user_answer = readline()
|
||||||
|
response = YiemAgent.conversation(a, Dict(:text=> user_answer))
|
||||||
|
println("\n$response")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# response = YiemAgent.conversation(a, Dict(:text=> "I want to get a French red wine under 100."))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user