Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0edf7dadf | ||
|
|
c21f943b12 | ||
|
|
b8fd772a28 | ||
|
|
883f581b2a | ||
|
|
5a890860a6 | ||
| 7d5bc14a09 | |||
|
|
37ba3a9d31 | ||
| bfadd53033 | |||
| 8fc3afe348 | |||
| c60037226a | |||
|
|
db6c9c5f2b | ||
|
|
6504099959 | ||
| 724b092bdb | |||
| c56c3d02b0 | |||
|
|
a7f3e29e9c | ||
|
|
b8fc23b41e | ||
|
|
cf4cd13b14 | ||
|
|
29adc077d5 | ||
|
|
d89d425885 | ||
|
|
bb81b973d3 | ||
|
|
4197625e57 | ||
|
|
3fdc0adf99 | ||
|
|
c7000f66b8 | ||
|
|
2206831bab | ||
|
|
a29e8049a7 | ||
|
|
944d9eaf2b | ||
|
|
616c159336 | ||
|
|
022cb5caf0 | ||
| cff0d31ae6 | |||
| 82167fe006 | |||
| 814a0ecc6a |
@@ -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.1"
|
version = "0.1.4"
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
||||||
@@ -22,6 +22,6 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
|||||||
|
|
||||||
[compat]
|
[compat]
|
||||||
DataFrames = "1.7.0"
|
DataFrames = "1.7.0"
|
||||||
GeneralUtils = "0.1.0"
|
GeneralUtils = "0.1, 0.2"
|
||||||
LLMMCTS = "0.1.2"
|
LLMMCTS = "0.1.2"
|
||||||
SQLLLM = "0.2.0"
|
SQLLLM = "0.2.0"
|
||||||
|
|||||||
1952
src/interface.jl
1952
src/interface.jl
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,8 @@
|
|||||||
module llmfunction
|
module llmfunction
|
||||||
|
|
||||||
export virtualWineUserChatbox, jsoncorrection, checkinventory, # recommendbox,
|
export virtualWineUserChatbox, jsoncorrection, checkinventory, # recommendbox,
|
||||||
virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes_1
|
virtualWineUserRecommendbox, userChatbox, userRecommendbox, extractWineAttributes_1,
|
||||||
|
extractWineAttributes_2, paraphrase
|
||||||
|
|
||||||
using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs, Dates
|
using HTTP, JSON3, URIs, Random, PrettyPrinting, UUIDs, Dates
|
||||||
using GeneralUtils, SQLLLM
|
using GeneralUtils, SQLLLM
|
||||||
@@ -290,20 +291,20 @@ julia> result = checkinventory(agent, input)
|
|||||||
function checkinventory(a::T1, input::T2
|
function checkinventory(a::T1, input::T2
|
||||||
) where {T1<:agent, T2<:AbstractString}
|
) where {T1<:agent, T2<:AbstractString}
|
||||||
|
|
||||||
println("\n~~~ checkinventory order: $input ", @__FILE__, " ", @__LINE__)
|
println("\ncheckinventory order: $input ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
wineattributes_1 = extractWineAttributes_1(a, input)
|
wineattributes_1 = extractWineAttributes_1(a, input)
|
||||||
wineattributes_2 = extractWineAttributes_2(a, input)
|
wineattributes_2 = extractWineAttributes_2(a, input)
|
||||||
|
|
||||||
_inventoryquery = "retailer name: $(a.retailername), $wineattributes_1, $wineattributes_2"
|
_inventoryquery = "retailer name: $(a.retailername), $wineattributes_1, $wineattributes_2"
|
||||||
inventoryquery = "Retrieves winery, wine_name, vintage, region, country, wine_type, grape, serving_temperature, sweetness, intensity, tannin, acidity, tasting_notes, price and currency of wines that match the following criteria - {$_inventoryquery}"
|
inventoryquery = "Retrieves winery, wine_name, vintage, region, country, wine_type, grape, serving_temperature, sweetness, intensity, tannin, acidity, tasting_notes, price and currency of wines that match the following criteria - {$_inventoryquery}"
|
||||||
println("~~~ checkinventory input: $inventoryquery ", @__FILE__, " ", @__LINE__)
|
println("\ncheckinventory input: $inventoryquery ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
# add suppport for similarSQLVectorDB
|
# add suppport for similarSQLVectorDB
|
||||||
textresult, rawresponse = SQLLLM.query(inventoryquery, a.func[:executeSQL],
|
textresult, rawresponse = SQLLLM.query(inventoryquery, a.func[:executeSQL],
|
||||||
a.func[:text2textInstructLLM],
|
a.func[:text2textInstructLLM],
|
||||||
insertSQLVectorDB=a.func[:insertSQLVectorDB],
|
insertSQLVectorDB=a.func[:insertSQLVectorDB],
|
||||||
similarSQLVectorDB=a.func[:similarSQLVectorDB])
|
similarSQLVectorDB=a.func[:similarSQLVectorDB])
|
||||||
|
|
||||||
println("\n~~~ checkinventory result ", @__FILE__, " ", @__LINE__)
|
println("\ncheckinventory result ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
println(textresult)
|
println(textresult)
|
||||||
|
|
||||||
return (result=textresult, rawresponse=rawresponse, success=true, errormsg=nothing)
|
return (result=textresult, rawresponse=rawresponse, success=true, errormsg=nothing)
|
||||||
@@ -325,233 +326,72 @@ julia>
|
|||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
- [] update docstring
|
- [] update docstring
|
||||||
- [x] implement the function
|
- implement the function
|
||||||
|
|
||||||
# Signature
|
# Signature
|
||||||
"""
|
"""
|
||||||
# function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
|
||||||
|
|
||||||
# systemmsg =
|
|
||||||
# """
|
|
||||||
# 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:
|
|
||||||
# User's query: ...
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
# Additionally, words like 'any' or 'unlimited' mean no information is available.
|
|
||||||
# 2) Do not generate other comments.
|
|
||||||
|
|
||||||
# You should then respond to the user with the following points:
|
|
||||||
# - reasoning: state your understanding of the current situation
|
|
||||||
# - wine_name: name of the wine
|
|
||||||
# - winery: name of the winery
|
|
||||||
# - vintage: the year of the wine
|
|
||||||
# - region: a region in 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"
|
|
||||||
# - wine_type: can be one of: "red", "white", "sparkling", "rose", "dessert" or "fortified"
|
|
||||||
# - grape_variety: 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
|
|
||||||
# - wine_price: price of wine. For example, up to 100, less than 100, 20 to 100, 30-79.95
|
|
||||||
# - 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
|
|
||||||
|
|
||||||
|
|
||||||
# You should only respond in the user's preference form as described below:
|
|
||||||
# reasoning: ...
|
|
||||||
# winery: ...
|
|
||||||
# wine_name: ...
|
|
||||||
# vintage: ...
|
|
||||||
# region: ...
|
|
||||||
# country: ...
|
|
||||||
# wine_type: ...
|
|
||||||
# grape_variety: ...
|
|
||||||
# tasting_notes: ...
|
|
||||||
# wine_price: ...
|
|
||||||
# occasion: ...
|
|
||||||
# food_to_be_paired_with_wine: ...
|
|
||||||
|
|
||||||
# Here are some example:
|
|
||||||
# User's query: red, Chenin Blanc, Riesling, under 20
|
|
||||||
# reasoning: ...
|
|
||||||
# winery: NA
|
|
||||||
# wine_name: NA
|
|
||||||
# vintage: NA
|
|
||||||
# region: NA
|
|
||||||
# country: NA
|
|
||||||
# wine_type: red
|
|
||||||
# grape_variety: Chenin Blanc, Riesling
|
|
||||||
# tasting_notes: NA
|
|
||||||
# wine_price: under 20
|
|
||||||
# occasion: NA
|
|
||||||
# food_to_be_paired_with_wine: NA
|
|
||||||
|
|
||||||
# User's query: Domaine du Collier Saumur Blanc 2019, France, white, Chenin Blanc
|
|
||||||
# reasoning: ...
|
|
||||||
# winery: Domaine du Collier
|
|
||||||
# wine_name: Saumur Blanc
|
|
||||||
# vintage: 2019
|
|
||||||
# region: Saumur
|
|
||||||
# country: France
|
|
||||||
# wine_type: white
|
|
||||||
# grape_variety: Chenin Blanc
|
|
||||||
# tasting_notes: NA
|
|
||||||
# wine_price: 109
|
|
||||||
# occasion: NA
|
|
||||||
# food_to_be_paired_with_wine: NA
|
|
||||||
|
|
||||||
# Let's begin!
|
|
||||||
# """
|
|
||||||
|
|
||||||
# attributes = ["reasoning", "winery", "wine_name", "vintage", "region", "country", "wine_type", "grape_variety", "tasting_notes", "wine_price", "occasion", "food_to_be_paired_with_wine"]
|
|
||||||
# errornote = ""
|
|
||||||
# maxattempt = 5
|
|
||||||
# for attempt in 1:maxattempt
|
|
||||||
|
|
||||||
# usermsg =
|
|
||||||
# """
|
|
||||||
# User's query: $input
|
|
||||||
# $errornote
|
|
||||||
# """
|
|
||||||
|
|
||||||
# _prompt =
|
|
||||||
# [
|
|
||||||
# Dict(:name=> "system", :text=> systemmsg),
|
|
||||||
# Dict(:name=> "user", :text=> usermsg)
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# # put in model format
|
|
||||||
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
|
||||||
# prompt *=
|
|
||||||
# """
|
|
||||||
# <|start_header_id|>assistant<|end_header_id|>
|
|
||||||
# """
|
|
||||||
|
|
||||||
# try
|
|
||||||
# response = a.func[:text2textInstructLLM](prompt)
|
|
||||||
# response = GeneralUtils.remove_french_accents(response)
|
|
||||||
|
|
||||||
# # check wheter all attributes are in the response
|
|
||||||
# for word in attributes
|
|
||||||
# if !occursin(word, response)
|
|
||||||
# error("$word attribute is missing")
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true)
|
|
||||||
|
|
||||||
# for i ∈ attributes
|
|
||||||
# if length(JSON3.write(responsedict[Symbol(i)])) == 0
|
|
||||||
# error("$i is empty ", @__LINE__)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# #check if the following attributes has more than 1 name
|
|
||||||
# # responsedict[:grape_variety] = split(responsedict[:grape_variety], ',')[1]
|
|
||||||
# # responsedict[:grape_variety] = split(responsedict[:grape_variety], '/')[1]
|
|
||||||
|
|
||||||
# responsedict[:country] = split(responsedict[:country], ',')[1]
|
|
||||||
# responsedict[:country] = split(responsedict[:country], '/')[1]
|
|
||||||
|
|
||||||
# responsedict[:region] = split(responsedict[:region], ',')[1]
|
|
||||||
# responsedict[:region] = split(responsedict[:region], '/')[1]
|
|
||||||
|
|
||||||
# delete!(responsedict, :reasoning)
|
|
||||||
# delete!(responsedict, :tasting_notes)
|
|
||||||
# delete!(responsedict, :occasion)
|
|
||||||
# delete!(responsedict, :food_to_be_paired_with_wine)
|
|
||||||
|
|
||||||
# # check if winery, wine_name, region, country, wine_type, grape_variety are in the query because sometime AI halucinates
|
|
||||||
# for i in [:grape_variety, :winery, :wine_name, :region]
|
|
||||||
# result = check_key_in_input(input, responsedict, attempt, maxattempt, i)
|
|
||||||
# if result === nothing
|
|
||||||
# # nothing wrong
|
|
||||||
# elseif result == "NA"
|
|
||||||
# responsedict[i] = "NA"
|
|
||||||
# else
|
|
||||||
# errornote = result
|
|
||||||
# error(errornote)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # remove (some text)
|
|
||||||
# for (k, v) in responsedict
|
|
||||||
# _v = replace(v, r"\(.*?\)" => "")
|
|
||||||
# responsedict[k] = _v
|
|
||||||
# end
|
|
||||||
|
|
||||||
# result = ""
|
|
||||||
# for (k, v) in responsedict
|
|
||||||
# # some time LLM generate text with "(some comment)". this line removes it
|
|
||||||
# if !occursin("NA", v) && v != "" && !occursin("none", v) && !occursin("None", v)
|
|
||||||
# result *= "$k: $v, "
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# #[PENDING] remove halucination. "highend dry white wine" --> "wine_type: white, occasion: special occasion, food_to_be_paired_with_wine: seafood, fish, country: France, Italy, USA, grape_variety: Chardonnay, Sauvignon Blanc, Pinot Grigio\nwine_notes: citrus, green apple, floral"
|
|
||||||
|
|
||||||
# result = result[1:end-2] # remove the ending ", "
|
|
||||||
|
|
||||||
# return result
|
|
||||||
# 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 ", @__FILE__, " ", @__LINE__)
|
|
||||||
# println("")
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# error("wineattributes_wordToNumber() failed to get a response")
|
|
||||||
# end
|
|
||||||
function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
||||||
|
|
||||||
systemmsg =
|
systemmsg =
|
||||||
"""
|
"""
|
||||||
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
|
Thought: 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_variety: 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 of wine. For example, up to 100, less than 100, 20 to 100, 30-79.95
|
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 format as described below:
|
||||||
You should only respond in the user's preference form (JSON) as described below:
|
Thought: ...
|
||||||
{"reasoning": ..., "winery": ..., "wine_name": ..., "vintage": ..., "region": ..., "country": ..., "wine_type": ..., "grape_variety": ..., "tasting_notes": ..., "wine_price": ..., "occasion": ..., "food_to_be_paired_with_wine": ...}
|
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, under 20
|
User's query: red, Chenin Blanc, Riesling, 20 USD
|
||||||
{"reasoning": ..., "winery": "NA", "wine_name": "NA", "vintage": "NA", "region": "NA", "country": "NA", "wine_type": "red", "grape_variety": "Chenin Blanc, Riesling", "tasting_notes": "NA", "wine_price": "under 20", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
{"reasoning": ..., "winery": "NA", "wine_name": "NA", "vintage": "NA", "region": "NA", "country": "NA", "wine_type": "red, white", "grape_varietal": "Chenin Blanc, Riesling", "tasting_notes": "NA", "wine_price": "0-20", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
||||||
|
|
||||||
User's query: Domaine du Collier Saumur Blanc 2019, France, white, Chenin Blanc
|
User's query: Domaine du Collier Saumur Blanc 2019, France, white, Merlot
|
||||||
{"reasoning": ..., "winery": "Domaine du Collier", "wine_name": "Saumur Blanc", "vintage": "2019", "region": "Saumur", "country": "France", "wine_type": "white", "grape_variety": "Chenin Blanc", "tasting_notes": "NA", "wine_price": "109", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
{"reasoning": ..., "winery": "Domaine du Collier", "wine_name": "Saumur Blanc", "vintage": "2019", "region": "Saumur", "country": "France", "wine_type": "white", "grape_varietal": "Merlot", "tasting_notes": "NA", "wine_price": "NA", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
|
||||||
|
|
||||||
Let's begin!
|
Let's begin!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
attributes = ["reasoning", "winery", "wine_name", "vintage", "region", "country", "wine_type", "grape_variety", "tasting_notes", "wine_price", "occasion", "food_to_be_paired_with_wine"]
|
header = ["Thought:", "Wine_name:", "Winery:", "Vintage:", "Region:", "Country:", "Wine_type:", "Grape_varietal:", "Tasting_notes:", "Wine_price:", "Occasion:", "Food_to_be_paired_with_wine:"]
|
||||||
|
dictkey = ["thought", "wine_name", "winery", "vintage", "region", "country", "wine_type", "grape_varietal", "tasting_notes", "wine_price", "occasion", "food_to_be_paired_with_wine"]
|
||||||
errornote = ""
|
errornote = ""
|
||||||
maxattempt = 5
|
|
||||||
for attempt in 1:maxattempt
|
for attempt in 1:10
|
||||||
|
#[WORKING] I should add generatequestion()
|
||||||
|
|
||||||
|
if attempt > 1
|
||||||
|
println("\nYiemAgent extractWineAttributes_1() attempt $attempt/10 ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
end
|
||||||
|
|
||||||
usermsg =
|
usermsg =
|
||||||
"""
|
"""
|
||||||
@@ -566,47 +406,96 @@ 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|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
try
|
|
||||||
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
|
||||||
for word in attributes
|
checkFlag = false
|
||||||
|
for word in header
|
||||||
if !occursin(word, response)
|
if !occursin(word, response)
|
||||||
error("$word attribute is missing")
|
errornote = "$word attribute is missing in previous attempts"
|
||||||
|
println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
checkFlag = true
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
checkFlag == true ? continue : nothing
|
||||||
|
|
||||||
responsedict = copy(JSON3.read(response))
|
# check whether response has all header
|
||||||
|
detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||||
|
if 0 ∈ values(detected_kw)
|
||||||
|
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)
|
||||||
|
|
||||||
delete!(responsedict, :reasoning)
|
delete!(responsedict, :thought)
|
||||||
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)
|
||||||
|
|
||||||
# check if winery, wine_name, region, country, wine_type, grape_variety are in the query because sometime AI halucinates
|
println(@__FILE__, " ", @__LINE__)
|
||||||
for i in [:grape_variety, :winery, :wine_name, :region]
|
pprintln(responsedict)
|
||||||
content = responsedict[i]
|
|
||||||
if occursin(",", content)
|
# check if winery, wine_name, region, country, wine_type, grape_varietal's value are in the query because sometime AI halucinates
|
||||||
|
checkFlag = false
|
||||||
|
for i in dictkey
|
||||||
|
j = Symbol(i)
|
||||||
|
if j ∉ [:thought, :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
|
||||||
|
if j == :wine_price
|
||||||
|
if responsedict[:wine_price] != "NA"
|
||||||
|
# check whether wine_price is in ranged number
|
||||||
|
if !occursin('-', responsedict[:wine_price])
|
||||||
|
errornote = "wine_price must be a range number"
|
||||||
|
println("ERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
checkFlag = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
# check whether max wine_price is in the input
|
||||||
|
pricerange = split(responsedict[:wine_price], '-')
|
||||||
|
minprice = pricerange[1]
|
||||||
|
maxprice = pricerange[end]
|
||||||
|
if !occursin(maxprice, input)
|
||||||
|
responsedict[:wine_price] = "NA"
|
||||||
|
end
|
||||||
|
# price range like 100-100 is not good
|
||||||
|
if minprice == maxprice
|
||||||
|
errornote = "wine_price with minimum equals to maximum is not valid"
|
||||||
|
println("ERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
checkFlag = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
content = responsedict[j]
|
||||||
|
if typeof(content) <: AbstractVector
|
||||||
|
content = strip.(content)
|
||||||
|
elseif occursin(',', content)
|
||||||
content = split(content, ",") # sometime AI generates multiple values e.g. "Chenin Blanc, Riesling"
|
content = split(content, ",") # sometime AI generates multiple values e.g. "Chenin Blanc, Riesling"
|
||||||
content = strip.(content)
|
content = strip.(content)
|
||||||
else
|
else
|
||||||
content = [content]
|
content = [content]
|
||||||
end
|
end
|
||||||
|
|
||||||
for x in content
|
# for x in content #check whether price are mentioned in the input
|
||||||
if !occursin("NA", responsedict[i]) && !occursin(x, input)
|
# if !occursin("NA", responsedict[j]) && !occursin(x, input)
|
||||||
errornote = "$x is not mentioned in the user query, you must only use the info from the query."
|
# errornote = "$x is not mentioned in the user query, you must only use the info from the query."
|
||||||
error(errornote)
|
# println("ERROR YiemAgent extractWineAttributes_1() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
# checkFlag == true
|
||||||
|
# break
|
||||||
|
# end
|
||||||
|
# end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
checkFlag == true ? continue : nothing # skip the rest code if true
|
||||||
|
|
||||||
# remove (some text)
|
# remove (some text)
|
||||||
for (k, v) in responsedict
|
for (k, v) in responsedict
|
||||||
@@ -622,20 +511,11 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#[PENDING] remove halucination. "highend dry white wine" --> "wine_type: white, occasion: special occasion, food_to_be_paired_with_wine: seafood, fish, country: France, Italy, USA, grape_variety: Chardonnay, Sauvignon Blanc, Pinot Grigio\nwine_notes: citrus, green apple, floral"
|
#[PENDING] remove halucination. "highend dry white wine" --> "wine_type: white, occasion: special occasion, food_to_be_paired_with_wine: seafood, fish, country: France, Italy, USA, grape_varietal: Chardonnay, Sauvignon Blanc, Pinot Grigio\nwine_notes: citrus, green apple, floral"
|
||||||
|
|
||||||
result = result[1:end-2] # remove the ending ", "
|
result = result[1:end-2] # remove the ending ", "
|
||||||
|
|
||||||
return result
|
return result
|
||||||
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 ", @__FILE__, " ", @__LINE__)
|
|
||||||
println("")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
error("wineattributes_wordToNumber() failed to get a response")
|
error("wineattributes_wordToNumber() failed to get a response")
|
||||||
end
|
end
|
||||||
@@ -643,12 +523,13 @@ end
|
|||||||
"""
|
"""
|
||||||
# TODO
|
# TODO
|
||||||
- [PENDING] "French dry white wines with medium bod" the LLM does not recognize sweetness. use LLM self questioning to solve.
|
- [PENDING] "French dry white wines with medium bod" the LLM does not recognize sweetness. use LLM self questioning to solve.
|
||||||
|
- [PENDING] French Syrah, Viognier, under 100. LLM extract intensiry of 3-5. why?
|
||||||
"""
|
"""
|
||||||
function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
||||||
|
|
||||||
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.
|
||||||
@@ -673,10 +554,9 @@ 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>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# chathistory = vectorOfDictToText(a.chathistory)
|
|
||||||
|
|
||||||
systemmsg =
|
systemmsg =
|
||||||
"""
|
"""
|
||||||
As an helpful sommelier, your task is to fill out the user's preference form based on the corresponding words from the user's query.
|
As an helpful sommelier, your task is to fill out the user's preference form based on the corresponding words from the user's query.
|
||||||
@@ -688,37 +568,69 @@ 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>
|
||||||
- reasoning: State your understanding of the current situation
|
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: ( A ), where ( A ) represents integers indicating the range of acidity level. Example: 3-5
|
Acidity_keyword: The exact keywords in the user's query describing the acidity level of the wine.
|
||||||
- tannin: ( T ), where ( T ) represents integers indicating the range of tannin level. Example: 1-3
|
Acidity: ( A ), where ( A ) represents integers indicating the range of acidity level. Example: 3-5
|
||||||
- intensity: ( I ), where ( I ) represents integers indicating the range of intensity level. Example: 2-4
|
Tannin_keyword: The exact keywords in the user's query describing the tannin level of the wine.
|
||||||
- notes: Anything you want to add
|
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: ( 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 as described below:
|
<You should only respond in format as described below>
|
||||||
reasoning: ...
|
Sweetness_keyword: ...
|
||||||
sweetness: ...
|
Sweetness: ...
|
||||||
acidity: ...
|
Acidity_keyword: ...
|
||||||
tannin: ...
|
Acidity: ...
|
||||||
intensity: ...
|
Tannin_keyword: ...
|
||||||
notes: ...
|
Tannin: ...
|
||||||
|
Intensity_keyword: ...
|
||||||
|
Intensity: ...
|
||||||
|
</You should only respond in format as described below>
|
||||||
|
|
||||||
|
<Here are some examples>
|
||||||
|
User's query: I want a wine with a medium-bodied, low acidity, medium tannin.
|
||||||
|
Sweetness_keyword: NA
|
||||||
|
Sweetness: NA
|
||||||
|
Acidity_keyword: low acidity
|
||||||
|
Acidity: 1-2
|
||||||
|
Tannin_keyword: medium tannin
|
||||||
|
Tannin: 3-4
|
||||||
|
Intensity_keyword: medium-bodied
|
||||||
|
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
|
||||||
|
</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 = ""
|
||||||
|
|
||||||
# chathistory = vectorOfDictToText(a.chathistory)
|
for attempt in 1:10
|
||||||
|
|
||||||
usermsg =
|
usermsg =
|
||||||
"""
|
"""
|
||||||
$conversiontable
|
$conversiontable
|
||||||
User's query: $input
|
User's query: $input
|
||||||
|
$errornote
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_prompt =
|
_prompt =
|
||||||
@@ -728,46 +640,48 @@ 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|>
|
|
||||||
"""
|
|
||||||
|
|
||||||
attributes = ["reasoning", "sweetness", "acidity", "tannin", "intensity", "notes"]
|
|
||||||
|
|
||||||
for attempt in 1:5
|
|
||||||
try
|
|
||||||
response = a.func[:text2textInstructLLM](prompt)
|
response = a.func[:text2textInstructLLM](prompt)
|
||||||
responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true)
|
|
||||||
|
|
||||||
for i ∈ attributes
|
# check whether response has all header
|
||||||
if length(JSON3.write(responsedict[Symbol(i)])) == 0
|
detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||||
error("$i is empty ", @__LINE__)
|
if 0 ∈ values(detected_kw)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
delete!(responsedict, :reasoning)
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
delete!(responsedict, :notes) # LLM traps. so it can add useless info here like comments.
|
dictKey=dictkey, symbolkey=true)
|
||||||
|
|
||||||
# some time LLM think the user mentioning acidity and tannin but actually didn't
|
# check whether each describing keyword is in the input to prevent halucination
|
||||||
for (k, v) in responsedict
|
for i in ["sweetness", "acidity", "tannin", "intensity"]
|
||||||
if k ∈ [:acidity, :tannin] && !occursin(string(k), input)
|
keyword = Symbol(i * "_keyword") # e.g. sweetness_keyword
|
||||||
responsedict[k] = "NA"
|
value = responsedict[keyword]
|
||||||
end
|
if value != "NA" && !occursin(value, input)
|
||||||
|
errornote = "WARNING. Keyword $keyword: $value does not appear in the input. You must use information from the input only"
|
||||||
|
println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove (some text)
|
# if value == "NA" then responsedict[i] = "NA"
|
||||||
for (k, v) in responsedict
|
# e.g. if sweetness_keyword == "NA" then sweetness = "NA"
|
||||||
_v = replace(v, r"\(.*?\)" => "")
|
if value == "NA"
|
||||||
responsedict[k] = _v
|
responsedict[Symbol(i)] = "NA"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# some time LLM not put integer range
|
# some time LLM not put integer range
|
||||||
for (k, v) in responsedict
|
for (k, v) in responsedict
|
||||||
responsedict[k] = v
|
if !occursin("keyword", string(k))
|
||||||
if length(v) > 5
|
if v !== "NA" && (!occursin('-', v) || length(v) > 5)
|
||||||
error("non-range is not allowed. $k $v")
|
errornote = "WARNING: The non-range value {$k: $v} is not allowed. It should be specified in a range format, i.e. min-max."
|
||||||
|
println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
continue
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -786,162 +700,131 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
result *= "$k: $v, "
|
result *= "$k: $v, "
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
result = result[1:end-2] # remove the ending ", "
|
result = result[1:end-2] # remove the ending ", "
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
error("wineattributes_wordToNumber() failed to get a response")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function paraphrase(text2textInstructLLM::Function, text::String)
|
||||||
|
systemmsg =
|
||||||
|
"""
|
||||||
|
Your name: N/A
|
||||||
|
Your vision:
|
||||||
|
- You are a helpful assistant who help the user to paraphrase their text.
|
||||||
|
Your mission:
|
||||||
|
- To help paraphrase the user's text
|
||||||
|
Mission's objective includes:
|
||||||
|
- To help paraphrase the user's text
|
||||||
|
Your responsibility includes:
|
||||||
|
1) To help paraphrase the user's text
|
||||||
|
Your responsibility does NOT includes:
|
||||||
|
1) N/A
|
||||||
|
Your profile:
|
||||||
|
- N/A
|
||||||
|
Additional information:
|
||||||
|
- N/A
|
||||||
|
|
||||||
|
At each round of conversation, you will be given the following information:
|
||||||
|
Text: The user's given text
|
||||||
|
|
||||||
|
You MUST follow the following guidelines:
|
||||||
|
- N/A
|
||||||
|
|
||||||
|
You should follow the following guidelines:
|
||||||
|
- N/A
|
||||||
|
|
||||||
|
You should then respond to the user with:
|
||||||
|
Paraphrase: Paraphrased text
|
||||||
|
|
||||||
|
You should only respond in format as described below:
|
||||||
|
Paraphrase: ...
|
||||||
|
|
||||||
|
Let's begin!
|
||||||
|
"""
|
||||||
|
|
||||||
|
header = ["Paraphrase:"]
|
||||||
|
dictkey = ["paraphrase"]
|
||||||
|
|
||||||
|
errornote = ""
|
||||||
|
response = nothing # placeholder for show when error msg show up
|
||||||
|
|
||||||
|
|
||||||
|
for attempt in 1:10
|
||||||
|
usermsg = """
|
||||||
|
Text: $text
|
||||||
|
$errornote
|
||||||
|
"""
|
||||||
|
|
||||||
|
_prompt =
|
||||||
|
[
|
||||||
|
Dict(:name => "system", :text => systemmsg),
|
||||||
|
Dict(:name => "user", :text => usermsg)
|
||||||
|
]
|
||||||
|
|
||||||
|
# put in model format
|
||||||
|
prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
|
|
||||||
|
try
|
||||||
|
response = text2textInstructLLM(prompt)
|
||||||
|
# sometime the model response like this "here's how I would respond: ..."
|
||||||
|
if occursin("respond:", response)
|
||||||
|
errornote = "You don't need to intro your response"
|
||||||
|
error("\nparaphrase() response contain : ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
end
|
||||||
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
|
response = replace(response, '*'=>"")
|
||||||
|
response = replace(response, '$' => "USD")
|
||||||
|
response = replace(response, '`' => "")
|
||||||
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
|
|
||||||
|
# check whether response has all header
|
||||||
|
detected_kw = GeneralUtils.detect_keyword(header, response)
|
||||||
|
if 0 ∈ values(detected_kw)
|
||||||
|
errornote = "\nYiemAgent paraphrase() response does not have all header"
|
||||||
|
continue
|
||||||
|
elseif sum(values(detected_kw)) > length(header)
|
||||||
|
errornote = "\nnYiemAgent paraphrase() response has duplicated header"
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
responsedict = GeneralUtils.textToDict(response, header;
|
||||||
|
dictKey=dictkey, symbolkey=true)
|
||||||
|
|
||||||
|
for i ∈ [:paraphrase]
|
||||||
|
if length(JSON3.write(responsedict[i])) == 0
|
||||||
|
error("$i is empty ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# check if there are more than 1 key per categories
|
||||||
|
for i ∈ [:paraphrase]
|
||||||
|
matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i)
|
||||||
|
if length(matchkeys) > 1
|
||||||
|
error("paraphrase() has more than one key per categories")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
println("\nparaphrase() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
pprintln(Dict(responsedict))
|
||||||
|
|
||||||
|
result = responsedict[:paraphrase]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
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("")
|
println("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
println("Attempt $attempt. Error occurred: $errorMsg\n$st")
|
|
||||||
println("")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
error("wineattributes_wordToNumber() failed to get a response")
|
error("paraphrase() failed to generate a response")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# function recommendbox(a::T1, input::T2)::String where {T1<:agent, T2<:AbstractString}
|
|
||||||
# error("recommendbox")
|
|
||||||
# systemmsg =
|
|
||||||
# """
|
|
||||||
# As an helpful sommelier, your task is to fill out the user's preference form based on the corresponding words from the user's query.
|
|
||||||
|
|
||||||
# At each round of conversation, the user will give you the current situation:
|
|
||||||
# User's query: ...
|
|
||||||
|
|
||||||
# The preference form requires the following information:
|
|
||||||
# wine_type, price, occasion, food_to_be_paired_with_wine, country, grape_variety, flavors, aromas.
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
# 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.
|
|
||||||
# 3) Do not generate other comments.
|
|
||||||
|
|
||||||
# You should then respond to the user with the following points:
|
|
||||||
# - reasoning: State your understanding of the current situation
|
|
||||||
# - wine_type: Can be one of: "red", "white", "sparkling", "rose", "dessert" or "fortified"
|
|
||||||
# - price: Must be an integer representing the cost of the wine.
|
|
||||||
# - occasion: ...
|
|
||||||
# - food_to_be_paired_with_wine: food that the user will be served with wine
|
|
||||||
# - 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.
|
|
||||||
# - flavors: Names of items that the wine tastes like.
|
|
||||||
# - aromas: wine's aroma
|
|
||||||
|
|
||||||
# You should only respond in the form as described below:
|
|
||||||
# reasoning: ...
|
|
||||||
# wine_type: ...
|
|
||||||
# price: ...
|
|
||||||
# occasion: ...
|
|
||||||
# food_to_be_paired_with_wine: ...
|
|
||||||
# country: ...
|
|
||||||
# region: ...
|
|
||||||
# grape_variety: ...
|
|
||||||
# flavors: ...
|
|
||||||
# aromas: ...
|
|
||||||
|
|
||||||
# Let's begin!
|
|
||||||
# """
|
|
||||||
|
|
||||||
# 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 =
|
|
||||||
# """
|
|
||||||
# User's query: $input
|
|
||||||
# $errornote
|
|
||||||
# """
|
|
||||||
|
|
||||||
# _prompt =
|
|
||||||
# [
|
|
||||||
# Dict(:name=> "system", :text=> systemmsg),
|
|
||||||
# Dict(:name=> "user", :text=> usermsg)
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# # put in model format
|
|
||||||
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
|
||||||
# prompt *=
|
|
||||||
# """
|
|
||||||
# <|start_header_id|>assistant<|end_header_id|>
|
|
||||||
# """
|
|
||||||
|
|
||||||
# try
|
|
||||||
# response = a.func[:text2textInstructLLM](prompt)
|
|
||||||
# responsedict = GeneralUtils.textToDict(response, attributes, rightmarker=":", symbolkey=true)
|
|
||||||
|
|
||||||
# for i ∈ attributes
|
|
||||||
# if length(JSON3.write(responsedict[Symbol(i)])) == 0
|
|
||||||
# error("$i is empty ", @__LINE__)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# #[PENDING] check if the following attributes has more than 1 name
|
|
||||||
# x = length(split(responsedict[:grape_variety], ",")) * length(split(responsedict[:grape_variety], "/"))
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# # check if grape_variety is mentioned in the input
|
|
||||||
# if responsedict[:grape_variety] != "NA" && !occursin(responsedict[:grape_variety], input)
|
|
||||||
# error("$(responsedict[:grape_variety]) is not mentioned in the input")
|
|
||||||
# end
|
|
||||||
|
|
||||||
# responsedict[:flavors] = replace(responsedict[:flavors], "notes"=>"")
|
|
||||||
# delete!(responsedict, :reasoning)
|
|
||||||
# delete!(responsedict, :tasting_notes)
|
|
||||||
# delete!(responsedict, :flavors)
|
|
||||||
# delete!(responsedict, :aromas)
|
|
||||||
|
|
||||||
# # remove (some text)
|
|
||||||
# for (k, v) in responsedict
|
|
||||||
# _v = replace(v, r"\(.*?\)" => "")
|
|
||||||
# responsedict[k] = _v
|
|
||||||
# end
|
|
||||||
|
|
||||||
# result = ""
|
|
||||||
# for (k, v) in responsedict
|
|
||||||
# # some time LLM generate text with "(some comment)". this line removes it
|
|
||||||
# if !occursin("NA", v) && v != "" && !occursin("none", v) && !occursin("None", v)
|
|
||||||
# result *= "$k: $v, "
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# #[PENDING] remove halucination. "highend dry white wine" --> "wine_type: white, occasion: special occasion, food_to_be_paired_with_wine: seafood, fish, country: France, Italy, USA, grape_variety: Chardonnay, Sauvignon Blanc, Pinot Grigio\nwine_notes: citrus, green apple, floral"
|
|
||||||
|
|
||||||
# result = result[1:end-2] # remove the ending ", "
|
|
||||||
|
|
||||||
# return result
|
|
||||||
# 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("wineattributes_wordToNumber() failed to get a response")
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
""" Attemp to correct LLM response's incorrect JSON response.
|
""" Attemp to correct LLM response's incorrect JSON response.
|
||||||
|
|
||||||
@@ -1101,7 +984,7 @@ end
|
|||||||
# ]
|
# ]
|
||||||
|
|
||||||
# # put in model format
|
# # put in model format
|
||||||
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
|
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
|
||||||
# prompt *=
|
# prompt *=
|
||||||
# """
|
# """
|
||||||
# <|start_header_id|>assistant<|end_header_id|>
|
# <|start_header_id|>assistant<|end_header_id|>
|
||||||
@@ -1133,7 +1016,7 @@ end
|
|||||||
# state[:isterminal] = true
|
# state[:isterminal] = true
|
||||||
# state[:reward] = 1
|
# state[:reward] = 1
|
||||||
# end
|
# end
|
||||||
# println("--> 5 Evaluator ", @__FILE__, " ", @__LINE__)
|
# println("--> 5 Evaluator ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
# pprintln(Dict(responsedict))
|
# pprintln(Dict(responsedict))
|
||||||
# return responsedict[:score]
|
# return responsedict[:score]
|
||||||
# catch e
|
# catch e
|
||||||
|
|||||||
14
src/type.jl
14
src/type.jl
@@ -11,8 +11,8 @@ abstract type agent end
|
|||||||
|
|
||||||
|
|
||||||
mutable struct companion <: agent
|
mutable struct companion <: agent
|
||||||
name::String # agent name
|
|
||||||
id::String # agent id
|
id::String # agent id
|
||||||
|
systemmsg::Union{String, Nothing}
|
||||||
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
|
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
|
||||||
|
|
||||||
""" Memory
|
""" Memory
|
||||||
@@ -34,8 +34,8 @@ end
|
|||||||
function companion(
|
function companion(
|
||||||
text2textInstructLLM::Function
|
text2textInstructLLM::Function
|
||||||
;
|
;
|
||||||
name::String= "Assistant",
|
|
||||||
id::String= string(uuid4()),
|
id::String= string(uuid4()),
|
||||||
|
systemmsg::Union{String, Nothing}= nothing,
|
||||||
maxHistoryMsg::Integer= 20,
|
maxHistoryMsg::Integer= 20,
|
||||||
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
|
chathistory::Vector{Dict{Symbol, String}} = Vector{Dict{Symbol, String}}(),
|
||||||
)
|
)
|
||||||
@@ -48,8 +48,8 @@ function companion(
|
|||||||
)
|
)
|
||||||
|
|
||||||
newAgent = companion(
|
newAgent = companion(
|
||||||
name,
|
|
||||||
id,
|
id,
|
||||||
|
systemmsg,
|
||||||
maxHistoryMsg,
|
maxHistoryMsg,
|
||||||
chathistory,
|
chathistory,
|
||||||
memory,
|
memory,
|
||||||
@@ -146,7 +146,6 @@ mutable struct sommelier <: agent
|
|||||||
"""
|
"""
|
||||||
chathistory::Vector{Dict{Symbol, Any}}
|
chathistory::Vector{Dict{Symbol, Any}}
|
||||||
memory::Dict{Symbol, Any}
|
memory::Dict{Symbol, Any}
|
||||||
|
|
||||||
func # NamedTuple of functions
|
func # NamedTuple of functions
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -181,11 +180,14 @@ function sommelier(
|
|||||||
|
|
||||||
memory = Dict{Symbol, Any}(
|
memory = Dict{Symbol, Any}(
|
||||||
:chatbox=> "",
|
:chatbox=> "",
|
||||||
:shortmem=> OrderedDict{Symbol, Any}(),
|
:shortmem=> OrderedDict{Symbol, Any}(
|
||||||
|
:available_wine=> [],
|
||||||
|
:found_wine=> [], # used by decisionMaker(). This is to prevent decisionMaker() keep presenting the same wines
|
||||||
|
),
|
||||||
:events=> Vector{Dict{Symbol, Any}}(),
|
:events=> Vector{Dict{Symbol, Any}}(),
|
||||||
:state=> Dict{Symbol, Any}(
|
:state=> Dict{Symbol, Any}(
|
||||||
:wine_presented_to_user=> "None",
|
|
||||||
),
|
),
|
||||||
|
:recap=> OrderedDict{Symbol, Any}(),
|
||||||
)
|
)
|
||||||
|
|
||||||
newAgent = sommelier(
|
newAgent = sommelier(
|
||||||
|
|||||||
144
src/util.jl
144
src/util.jl
@@ -1,6 +1,7 @@
|
|||||||
module util
|
module util
|
||||||
|
|
||||||
export clearhistory, addNewMessage, vectorOfDictToText, eventdict, noises
|
export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
|
||||||
|
availableWineToText
|
||||||
|
|
||||||
using UUIDs, Dates, DataStructures, HTTP, JSON3
|
using UUIDs, Dates, DataStructures, HTTP, JSON3
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
@@ -106,7 +107,7 @@ function addNewMessage(a::T1, name::String, text::T2;
|
|||||||
error("name is not in agent.availableRole $(@__LINE__)")
|
error("name is not in agent.availableRole $(@__LINE__)")
|
||||||
end
|
end
|
||||||
|
|
||||||
#[] summarize the oldest 10 message
|
#[PENDING] summarize the oldest 10 message
|
||||||
if length(a.chathistory) > maximumMsg
|
if length(a.chathistory) > maximumMsg
|
||||||
summarize(a.chathistory)
|
summarize(a.chathistory)
|
||||||
else
|
else
|
||||||
@@ -121,41 +122,47 @@ This function takes in a vector of dictionaries and outputs a single string wher
|
|||||||
|
|
||||||
# Arguments
|
# Arguments
|
||||||
- `vecd::Vector`
|
- `vecd::Vector`
|
||||||
a vector of dictionaries
|
A vector of dictionaries containing chat messages
|
||||||
- `withkey::Bool`
|
- `withkey::Bool`
|
||||||
whether to include the key in the output text. Default is true
|
Whether to include the name as a prefix in the output text. Default is true
|
||||||
|
- `range::Union{Nothing,UnitRange,Int}`
|
||||||
|
Optional range of messages to include. If nothing, includes all messages
|
||||||
|
|
||||||
# Return
|
# Returns
|
||||||
a string with the formatted dictionaries
|
A formatted string where each line contains either:
|
||||||
|
- If withkey=true: "name> message\n"
|
||||||
|
- If withkey=false: "message\n"
|
||||||
|
|
||||||
# Example
|
# Example
|
||||||
```jldoctest
|
|
||||||
julia> using Revise
|
julia> using Revise
|
||||||
julia> using GeneralUtils
|
julia> using GeneralUtils
|
||||||
julia> vecd = [Dict(:name => "John", :text => "Hello"), Dict(:name => "Jane", :text => "Goodbye")]
|
julia> vecd = [Dict(:name => "John", :text => "Hello"), Dict(:name => "Jane", :text => "Goodbye")]
|
||||||
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
|
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
|
||||||
"John> Hello\nJane> Goodbye\n"
|
"John> Hello\nJane> Goodbye\n"
|
||||||
```
|
```
|
||||||
# Signature
|
|
||||||
"""
|
"""
|
||||||
function vectorOfDictToText(vecd::Vector; withkey=true)::String
|
function chatHistoryToText(vecd::Vector; withkey=true, range=nothing)::String
|
||||||
# Initialize an empty string to hold the final text
|
# Initialize an empty string to hold the final text
|
||||||
text = ""
|
text = ""
|
||||||
|
|
||||||
|
# Get the elements within the specified range, or all elements if no range provided
|
||||||
|
elements = isnothing(range) ? vecd : vecd[range]
|
||||||
|
|
||||||
# Determine whether to include the key in the output text or not
|
# Determine whether to include the key in the output text or not
|
||||||
if withkey
|
if withkey
|
||||||
# Loop through each dictionary in the input vector
|
# Loop through each dictionary in the input vector
|
||||||
for d in vecd
|
for d in elements
|
||||||
# Extract the 'name' and 'text' keys from the dictionary
|
# Extract the 'name' and 'text' keys from the dictionary
|
||||||
name = d[:name]
|
name = d[:name]
|
||||||
_text = d[:text]
|
_text = d[:text]
|
||||||
|
|
||||||
# Append the formatted string to the text variable
|
# Append the formatted string to the text variable
|
||||||
text *= "$name> $_text \n"
|
text *= "$name:> $_text \n"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# Loop through each dictionary in the input vector
|
# Loop through each dictionary in the input vector
|
||||||
for d in vecd
|
for d in elements
|
||||||
# Iterate over all key-value pairs in the dictionary
|
# Iterate over all key-value pairs in the dictionary
|
||||||
for (k, v) in d
|
for (k, v) in d
|
||||||
# Append the formatted string to the text variable
|
# Append the formatted string to the text variable
|
||||||
@@ -169,11 +176,63 @@ function vectorOfDictToText(vecd::Vector; withkey=true)::String
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function availableWineToText(vecd::Vector)::String
|
||||||
|
# Initialize an empty string to hold the final text
|
||||||
|
rowtext = ""
|
||||||
|
# Loop through each dictionary in the input vector
|
||||||
|
for (i, d) in enumerate(vecd)
|
||||||
|
# Iterate over all key-value pairs in the dictionary
|
||||||
|
temp = []
|
||||||
|
for (k, v) in d
|
||||||
|
# Append the formatted string to the text variable
|
||||||
|
t = "$k:$v"
|
||||||
|
push!(temp, t)
|
||||||
|
end
|
||||||
|
_rowtext = join(temp, ',')
|
||||||
|
rowtext *= "$i) $_rowtext "
|
||||||
|
end
|
||||||
|
|
||||||
|
return rowtext
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
""" Create a dictionary representing an event with optional details.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `event_description::Union{String, Nothing}`
|
||||||
|
A description of the event
|
||||||
|
- `timestamp::Union{DateTime, Nothing}`
|
||||||
|
The time when the event occurred
|
||||||
|
- `subject::Union{String, Nothing}`
|
||||||
|
The subject or entity associated with the event
|
||||||
|
- `thought::Union{AbstractDict, Nothing}`
|
||||||
|
Any associated thoughts or metadata
|
||||||
|
- `actionname::Union{String, Nothing}`
|
||||||
|
The name of the action performed (e.g., "CHAT", "CHECKINVENTORY")
|
||||||
|
- `actioninput::Union{String, Nothing}`
|
||||||
|
Input or parameters for the action
|
||||||
|
- `location::Union{String, Nothing}`
|
||||||
|
Where the event took place
|
||||||
|
- `equipment_used::Union{String, Nothing}`
|
||||||
|
Equipment involved in the event
|
||||||
|
- `material_used::Union{String, Nothing}`
|
||||||
|
Materials used during the event
|
||||||
|
- `outcome::Union{String, Nothing}`
|
||||||
|
The result or consequence of the event after action execution
|
||||||
|
- `note::Union{String, Nothing}`
|
||||||
|
Additional notes or comments
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
A dictionary with event details as symbol-keyed key-value pairs
|
||||||
|
"""
|
||||||
function eventdict(;
|
function eventdict(;
|
||||||
event_description::Union{String, Nothing}=nothing,
|
event_description::Union{String, Nothing}=nothing,
|
||||||
timestamp::Union{DateTime, Nothing}=nothing,
|
timestamp::Union{DateTime, Nothing}=nothing,
|
||||||
subject::Union{String, Nothing}=nothing,
|
subject::Union{String, Nothing}=nothing,
|
||||||
action_or_dialogue::Union{String, Nothing}=nothing,
|
thought::Union{AbstractDict, Nothing}=nothing,
|
||||||
|
actionname::Union{String, Nothing}=nothing, # "CHAT", "CHECKINVENTORY", "PRESENTBOX", etc
|
||||||
|
actioninput::Union{String, Nothing}=nothing,
|
||||||
location::Union{String, Nothing}=nothing,
|
location::Union{String, Nothing}=nothing,
|
||||||
equipment_used::Union{String, Nothing}=nothing,
|
equipment_used::Union{String, Nothing}=nothing,
|
||||||
material_used::Union{String, Nothing}=nothing,
|
material_used::Union{String, Nothing}=nothing,
|
||||||
@@ -184,7 +243,9 @@ function eventdict(;
|
|||||||
:event_description=> event_description,
|
:event_description=> event_description,
|
||||||
:timestamp=> timestamp,
|
:timestamp=> timestamp,
|
||||||
:subject=> subject,
|
:subject=> subject,
|
||||||
:action_or_dialogue=> action_or_dialogue,
|
:thought=> thought,
|
||||||
|
:actionname=> actionname,
|
||||||
|
:actioninput=> actioninput,
|
||||||
:location=> location,
|
:location=> location,
|
||||||
:equipment_used=> equipment_used,
|
:equipment_used=> equipment_used,
|
||||||
:material_used=> material_used,
|
:material_used=> material_used,
|
||||||
@@ -194,6 +255,61 @@ function eventdict(;
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
""" Create a formatted timeline string from a sequence of events.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `events::T1`
|
||||||
|
Vector of event dictionaries containing subject, actioninput and optional outcome fields
|
||||||
|
Each event dictionary should have the following keys:
|
||||||
|
- :subject - The subject or entity performing the action
|
||||||
|
- :actioninput - The action or input performed by the subject
|
||||||
|
- :outcome - (Optional) The result or outcome of the action
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `timeline::String`
|
||||||
|
A formatted string representing the events with their subjects, actions, and optional outcomes
|
||||||
|
Format: "{index}) {subject}> {actioninput} {outcome}\n" for each event
|
||||||
|
|
||||||
|
# Example
|
||||||
|
|
||||||
|
events = [
|
||||||
|
Dict(:subject => "User", :actioninput => "Hello", :outcome => nothing),
|
||||||
|
Dict(:subject => "Assistant", :actioninput => "Hi there!", :outcome => "with a smile")
|
||||||
|
]
|
||||||
|
timeline = createTimeline(events)
|
||||||
|
# 1) User> Hello
|
||||||
|
# 2) Assistant> Hi there! with a smile
|
||||||
|
|
||||||
|
"""
|
||||||
|
function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
|
||||||
|
) where {T1<:AbstractVector}
|
||||||
|
# Initialize empty timeline string
|
||||||
|
timeline = ""
|
||||||
|
|
||||||
|
# Determine which indices to use - either provided range or full length
|
||||||
|
ind =
|
||||||
|
if eventindex !== nothing
|
||||||
|
[eventindex...]
|
||||||
|
else
|
||||||
|
1:length(events)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Iterate through events and format each one
|
||||||
|
for (i, event) in zip(ind, events)
|
||||||
|
# If no outcome exists, format without outcome
|
||||||
|
if event[:outcome] === nothing
|
||||||
|
timeline *= "Event_$i $(event[:subject])> $(event[:actioninput])\n"
|
||||||
|
# If outcome exists, include it in formatting
|
||||||
|
else
|
||||||
|
timeline *= "Event_$i $(event[:subject])> $(event[:actioninput]) $(event[:outcome])\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return formatted timeline string
|
||||||
|
return timeline
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# """ Convert a single chat dictionary into LLM model instruct format.
|
# """ Convert a single chat dictionary into LLM model instruct format.
|
||||||
|
|
||||||
|
|||||||
41
test/Manifest.toml
Normal file
41
test/Manifest.toml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# This file is machine-generated - editing it directly is not advised
|
||||||
|
|
||||||
|
julia_version = "1.11.4"
|
||||||
|
manifest_format = "2.0"
|
||||||
|
project_hash = "71d91126b5a1fb1020e1098d9d492de2a4438fd2"
|
||||||
|
|
||||||
|
[[deps.Base64]]
|
||||||
|
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
|
||||||
|
version = "1.11.0"
|
||||||
|
|
||||||
|
[[deps.InteractiveUtils]]
|
||||||
|
deps = ["Markdown"]
|
||||||
|
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
|
||||||
|
version = "1.11.0"
|
||||||
|
|
||||||
|
[[deps.Logging]]
|
||||||
|
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
|
||||||
|
version = "1.11.0"
|
||||||
|
|
||||||
|
[[deps.Markdown]]
|
||||||
|
deps = ["Base64"]
|
||||||
|
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
|
||||||
|
version = "1.11.0"
|
||||||
|
|
||||||
|
[[deps.Random]]
|
||||||
|
deps = ["SHA"]
|
||||||
|
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||||
|
version = "1.11.0"
|
||||||
|
|
||||||
|
[[deps.SHA]]
|
||||||
|
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
|
||||||
|
version = "0.7.0"
|
||||||
|
|
||||||
|
[[deps.Serialization]]
|
||||||
|
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
||||||
|
version = "1.11.0"
|
||||||
|
|
||||||
|
[[deps.Test]]
|
||||||
|
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
|
||||||
|
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||||
|
version = "1.11.0"
|
||||||
2
test/Project.toml
Normal file
2
test/Project.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[deps]
|
||||||
|
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||||
@@ -27,10 +27,14 @@
|
|||||||
"description": "agent role"
|
"description": "agent role"
|
||||||
},
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
"value": "yiem_hq",
|
"value": "yiem_branch_1",
|
||||||
"description": "organization name"
|
"description": "organization name"
|
||||||
},
|
},
|
||||||
"externalservice": {
|
"externalservice": {
|
||||||
|
"loadbalancer": {
|
||||||
|
"mqtttopic": "/loadbalancer/requestingservice",
|
||||||
|
"description": "text to text service with instruct LLM"
|
||||||
|
},
|
||||||
"text2textinstruct": {
|
"text2textinstruct": {
|
||||||
"mqtttopic": "/loadbalancer/requestingservice",
|
"mqtttopic": "/loadbalancer/requestingservice",
|
||||||
"description": "text to text service with instruct LLM",
|
"description": "text to text service with instruct LLM",
|
||||||
@@ -51,6 +55,22 @@
|
|||||||
"llminfo": {
|
"llminfo": {
|
||||||
"name": "llama3instruct"
|
"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
@@ -8,31 +8,41 @@ using Base.Threads
|
|||||||
|
|
||||||
|
|
||||||
# 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]
|
||||||
|
port = config[:externalservice][:wineDB][:port]
|
||||||
|
dbname = config[:externalservice][:wineDB][:dbname]
|
||||||
|
user = config[:externalservice][:wineDB][:user]
|
||||||
|
password = config[:externalservice][:wineDB][:password]
|
||||||
|
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
|
||||||
result = LibPQ.execute(DBconnection, sql)
|
result = LibPQ.execute(DBconnection, sql)
|
||||||
close(DBconnection)
|
close(DBconnection)
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
function executeSQLVectorDB(sql)
|
function executeSQLVectorDB(sql)
|
||||||
DBconnection = LibPQ.Connection("host=192.168.88.12 port=10203 dbname=SQLVectorDB user=yiemtechnologies password=yiemtechnologies@Postgres_0.0")
|
host = config[:externalservice][:SQLVectorDB][:host]
|
||||||
|
port = config[:externalservice][:SQLVectorDB][:port]
|
||||||
|
dbname = config[:externalservice][:SQLVectorDB][:dbname]
|
||||||
|
user = config[:externalservice][:SQLVectorDB][:user]
|
||||||
|
password = config[:externalservice][:SQLVectorDB][:password]
|
||||||
|
DBconnection = LibPQ.Connection("host=$host port=$port dbname=$dbname user=$user password=$password")
|
||||||
result = LibPQ.execute(DBconnection, sql)
|
result = LibPQ.execute(DBconnection, sql)
|
||||||
close(DBconnection)
|
close(DBconnection)
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
function text2textInstructLLM(prompt::String)
|
function text2textInstructLLM(prompt::String; maxattempt::Integer=2, modelsize::String="medium")
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
msgMeta = GeneralUtils.generate_msgMeta(
|
||||||
config[:externalservice][:text2textinstruct][:mqtttopic];
|
config[:externalservice][:loadbalancer][:mqtttopic];
|
||||||
msgPurpose="inference",
|
msgPurpose="inference",
|
||||||
senderName="yiemagent",
|
senderName="yiemagent",
|
||||||
senderId=string(uuid4()),
|
senderId=sessionId,
|
||||||
receiverName="text2textinstruct",
|
receiverName="text2textinstruct_$modelsize",
|
||||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
mqttBrokerPort=config[:mqttServerInfo][:port],
|
||||||
)
|
)
|
||||||
@@ -48,8 +58,20 @@ function text2textInstructLLM(prompt::String)
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=6000)
|
response = nothing
|
||||||
|
for attempts in 1:maxattempt
|
||||||
|
_response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=180, maxattempt=maxattempt)
|
||||||
|
payload = _response[:response]
|
||||||
|
if _response[:success] && payload[:text] !== nothing
|
||||||
response = _response[:response][:text]
|
response = _response[:response][:text]
|
||||||
|
break
|
||||||
|
else
|
||||||
|
println("\n<text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
pprintln(outgoingMsg)
|
||||||
|
println("</text2textInstructLLM()> attempt $attempts/$maxattempt failed ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
||||||
|
sleep(3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return response
|
return response
|
||||||
end
|
end
|
||||||
@@ -57,11 +79,11 @@ end
|
|||||||
# get text embedding from a LLM service
|
# get text embedding from a LLM service
|
||||||
function getEmbedding(text::T) where {T<:AbstractString}
|
function getEmbedding(text::T) where {T<:AbstractString}
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
msgMeta = GeneralUtils.generate_msgMeta(
|
||||||
config[:externalservice][:text2textinstruct][:mqtttopic];
|
config[:externalservice][:loadbalancer][:mqtttopic];
|
||||||
msgPurpose="embedding",
|
msgPurpose="embedding",
|
||||||
senderName="yiemagent",
|
senderName="yiemagent",
|
||||||
senderId=string(uuid4()),
|
senderId=sessionId,
|
||||||
receiverName="text2textinstruct",
|
receiverName="textembedding",
|
||||||
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
mqttBrokerAddress=config[:mqttServerInfo][:broker],
|
||||||
mqttBrokerPort=config[:mqttServerInfo][:port],
|
mqttBrokerPort=config[:mqttServerInfo][:port],
|
||||||
)
|
)
|
||||||
@@ -72,7 +94,8 @@ function getEmbedding(text::T) where {T<:AbstractString}
|
|||||||
:text => [text] # must be a vector of string
|
:text => [text] # must be a vector of string
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=6000)
|
|
||||||
|
response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120, maxattempt=3)
|
||||||
embedding = response[:response][:embeddings]
|
embedding = response[:response][:embeddings]
|
||||||
return embedding
|
return embedding
|
||||||
end
|
end
|
||||||
@@ -80,10 +103,8 @@ end
|
|||||||
function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
|
function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnName::T3,
|
||||||
vectorDB::Function; limit::Integer=1
|
vectorDB::Function; limit::Integer=1
|
||||||
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
|
)::DataFrame where {T1<:AbstractString, T2<:AbstractString, T3<:AbstractString}
|
||||||
|
|
||||||
# get embedding from LLM service
|
# get embedding from LLM service
|
||||||
embedding = getEmbedding(text)[1]
|
embedding = getEmbedding(text)[1]
|
||||||
|
|
||||||
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
|
# check whether there is close enough vector already store in vectorDB. if no, add, else skip
|
||||||
sql = """
|
sql = """
|
||||||
SELECT *, $embeddingColumnName <-> '$embedding' as distance
|
SELECT *, $embeddingColumnName <-> '$embedding' as distance
|
||||||
@@ -95,29 +116,29 @@ function findSimilarTextFromVectorDB(text::T1, tablename::T2, embeddingColumnNam
|
|||||||
return df
|
return df
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function similarSQLVectorDB(query; maxdistance::Integer=100)
|
function similarSQLVectorDB(query; maxdistance::Integer=100)
|
||||||
tablename = "sqlllm_decision_repository"
|
tablename = "sqlllm_decision_repository"
|
||||||
# get embedding of the query
|
# get embedding of the query
|
||||||
df = findSimilarTextFromVectorDB(query, tablename,
|
df = findSimilarTextFromVectorDB(query, tablename,
|
||||||
"function_input_embedding", executeSQLVectorDB)
|
"function_input_embedding", executeSQLVectorDB)
|
||||||
|
# println(df[1, [:id, :function_output]])
|
||||||
row, col = size(df)
|
row, col = size(df)
|
||||||
distance = row == 0 ? Inf : df[1, :distance]
|
distance = row == 0 ? Inf : df[1, :distance]
|
||||||
|
# distance = 100 # CHANGE this is for testing only
|
||||||
if row != 0 && distance < maxdistance
|
if row != 0 && distance < maxdistance
|
||||||
# if there is usable SQL, return it.
|
# if there is usable SQL, return it.
|
||||||
output_b64 = df[1, :function_output_base64] # pick the closest match
|
output_b64 = df[1, :function_output_base64] # pick the closest match
|
||||||
output_str = String(base64decode(output_b64))
|
output_str = String(base64decode(output_b64))
|
||||||
rowid = df[1, :id]
|
rowid = df[1, :id]
|
||||||
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, " ", @__LINE__)
|
println("\n~~~ found similar sql. row id $rowid, distance $distance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
return (dict=output_str, distance=distance)
|
return (dict=output_str, distance=distance)
|
||||||
else
|
else
|
||||||
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, " ", @__LINE__)
|
println("\n~~~ similar sql not found, max distance $maxdistance ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
return (dict=nothing, distance=nothing)
|
return (dict=nothing, distance=nothing)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=3) where {T1<:AbstractString, T2<:AbstractString}
|
||||||
function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=1) where {T1<:AbstractString, T2<:AbstractString}
|
|
||||||
tablename = "sqlllm_decision_repository"
|
tablename = "sqlllm_decision_repository"
|
||||||
# get embedding of the query
|
# get embedding of the query
|
||||||
# query = state[:thoughtHistory][:question]
|
# query = state[:thoughtHistory][:question]
|
||||||
@@ -134,14 +155,14 @@ function insertSQLVectorDB(query::T1, SQL::T2; maxdistance::Integer=1) where {T1
|
|||||||
sql = """
|
sql = """
|
||||||
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
|
INSERT INTO $tablename (function_input, function_output, function_output_base64, function_input_embedding) VALUES ('$query', '$sql_', '$sql_base64', '$query_embedding');
|
||||||
"""
|
"""
|
||||||
println("\n~~~ added new decision to vectorDB ", @__FILE__, " ", @__LINE__)
|
# println("\n~~~ added new decision to vectorDB ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
println(sql)
|
# println(sql)
|
||||||
_ = executeSQLVectorDB(sql)
|
_ = executeSQLVectorDB(sql)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=5
|
function similarSommelierDecision(recentevents::T1; maxdistance::Integer=3
|
||||||
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
|
)::Union{AbstractDict, Nothing} where {T1<:AbstractString}
|
||||||
tablename = "sommelier_decision_repository"
|
tablename = "sommelier_decision_repository"
|
||||||
# find similar
|
# find similar
|
||||||
@@ -214,7 +235,7 @@ a = YiemAgent.sommelier(
|
|||||||
)
|
)
|
||||||
|
|
||||||
while true
|
while true
|
||||||
println("your respond: ")
|
print("\nyour respond: ")
|
||||||
user_answer = readline()
|
user_answer = readline()
|
||||||
response = YiemAgent.conversation(a, Dict(:text=> user_answer))
|
response = YiemAgent.conversation(a, Dict(:text=> user_answer))
|
||||||
println("\n$response")
|
println("\n$response")
|
||||||
@@ -224,14 +245,13 @@ end
|
|||||||
# response = YiemAgent.conversation(a, Dict(:text=> "I want to get a French red wine under 100."))
|
# response = YiemAgent.conversation(a, Dict(:text=> "I want to get a French red wine under 100."))
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
hello I want to get a bottle of red wine for my boss. I have a budget around 50 dollars. Show me some options.
|
||||||
|
|
||||||
|
I have no idea about his wine taste but he likes spicy food.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user