This commit is contained in:
narawat lamaiin
2025-03-31 21:30:14 +07:00
parent 883f581b2a
commit b8fd772a28
6 changed files with 818 additions and 426 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -291,20 +291,20 @@ julia> result = checkinventory(agent, input)
function checkinventory(a::T1, input::T2
) where {T1<:agent, T2<:AbstractString}
println("\ncheckinventory order: $input ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println("\ncheckinventory order: $input ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
wineattributes_1 = extractWineAttributes_1(a, input)
wineattributes_2 = extractWineAttributes_2(a, input)
_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}"
println("\ncheckinventory input: $inventoryquery ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println("\ncheckinventory input: $inventoryquery ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# add suppport for similarSQLVectorDB
textresult, rawresponse = SQLLLM.query(inventoryquery, a.func[:executeSQL],
a.func[:text2textInstructLLM],
insertSQLVectorDB=a.func[:insertSQLVectorDB],
similarSQLVectorDB=a.func[:similarSQLVectorDB])
println("\ncheckinventory result ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println("\ncheckinventory result ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
println(textresult)
return (result=textresult, rawresponse=rawresponse, success=true, errormsg=nothing)
@@ -345,7 +345,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
- Do not generate other comments.
You should then respond to the user with:
Comprehension: state your understanding of the current situation
Thought: state your understanding of the current situation
Wine_name: name of the wine
Winery: name of the winery
Vintage: the year of the wine
@@ -359,7 +359,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
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:
Comprehension: ...
Thought: ...
Wine_name: ...
Winery: ...
Vintage: ...
@@ -376,17 +376,19 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
User's query: red, Chenin Blanc, Riesling, 20 USD
{"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
{"reasoning": ..., "winery": "Domaine du Collier", "wine_name": "Saumur Blanc", "vintage": "2019", "region": "Saumur", "country": "France", "wine_type": "white", "grape_varietal": "Chenin Blanc", "tasting_notes": "NA", "wine_price": "NA", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
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_varietal": "Merlot", "tasting_notes": "NA", "wine_price": "NA", "occasion": "NA", "food_to_be_paired_with_wine": "NA"}
Let's begin!
"""
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"]
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 = ""
for attempt in 1:5
for attempt in 1:10
#[WORKING] I should add generatequestion()
usermsg =
"""
User's query: $input
@@ -409,7 +411,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
for word in header
if !occursin(word, response)
errornote = "$word attribute is missing in previous attempts"
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
checkFlag = true
break
end
@@ -418,7 +420,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
# check whether response has all header
detected_kw = GeneralUtils.detect_keyword(header, response)
if sum(values(detected_kw)) < length(header)
if 0 values(detected_kw)
errornote = "\nYiemAgent extractWineAttributes_1() response does not have all header"
continue
elseif sum(values(detected_kw)) > length(header)
@@ -428,7 +430,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
responsedict = GeneralUtils.textToDict(response, header;
dictKey=dictkey, symbolkey=true)
delete!(responsedict, :comprehension)
delete!(responsedict, :thought)
delete!(responsedict, :tasting_notes)
delete!(responsedict, :occasion)
delete!(responsedict, :food_to_be_paired_with_wine)
@@ -440,14 +442,14 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
checkFlag = false
for i in dictkey
j = Symbol(i)
if j [:comprehension, :tasting_notes, :occasion, :food_to_be_paired_with_wine]
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("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
checkFlag = true
break
end
@@ -462,7 +464,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
# price range like 100-100 is not good
if minprice == maxprice
errornote = "wine_price with minimum equals to maximum is not valid"
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
checkFlag = true
break
end
@@ -481,7 +483,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2<
for x in content #check whether price are mentioned in the 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."
println("Attempt $attempt $errornote ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
checkFlag == true
break
end
@@ -640,7 +642,7 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
# check whether response has all header
detected_kw = GeneralUtils.detect_keyword(header, response)
if sum(values(detected_kw)) < length(header)
if 0 values(detected_kw)
errornote = "\nYiemAgent extractWineAttributes_2() response does not have all header"
continue
elseif sum(values(detected_kw)) > length(header)
@@ -657,7 +659,7 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
value = responsedict[keyword]
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 ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue
end
@@ -673,7 +675,7 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
if !occursin("keyword", string(k))
if v !== "NA" && (!occursin('-', v) || length(v) > 5)
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 ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println("Attempt $attempt $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue
end
end
@@ -766,7 +768,7 @@ function paraphrase(text2textInstructLLM::Function, text::String)
# 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 : ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
error("\nparaphrase() response contain : ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
end
response = GeneralUtils.remove_french_accents(response)
response = replace(response, '*'=>"")
@@ -776,7 +778,7 @@ function paraphrase(text2textInstructLLM::Function, text::String)
# check whether response has all header
detected_kw = GeneralUtils.detect_keyword(header, response)
if sum(values(detected_kw)) < length(header)
if 0 values(detected_kw)
errornote = "\nYiemAgent paraphrase() response does not have all header"
continue
elseif sum(values(detected_kw)) > length(header)
@@ -789,7 +791,7 @@ function paraphrase(text2textInstructLLM::Function, text::String)
for i [:paraphrase]
if length(JSON3.write(responsedict[i])) == 0
error("$i is empty ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
error("$i is empty ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
end
end
@@ -801,7 +803,7 @@ function paraphrase(text2textInstructLLM::Function, text::String)
end
end
println("\nparaphrase() ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
println("\nparaphrase() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(Dict(responsedict))
result = responsedict[:paraphrase]
@@ -812,7 +814,7 @@ function paraphrase(text2textInstructLLM::Function, text::String)
showerror(io, e)
errorMsg = String(take!(io))
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 ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
end
end
error("paraphrase() failed to generate a response")
@@ -978,7 +980,7 @@ end
# ]
# # put in model format
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct")
# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="qwen")
# prompt *=
# """
# <|start_header_id|>assistant<|end_header_id|>
@@ -1010,7 +1012,7 @@ end
# state[:isterminal] = true
# state[:reward] = 1
# end
# println("--> 5 Evaluator ", Dates.now(), " ", @__FILE__, " ", @__LINE__)
# println("--> 5 Evaluator ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# pprintln(Dict(responsedict))
# return responsedict[:score]
# catch e

View File

@@ -122,47 +122,53 @@ This function takes in a vector of dictionaries and outputs a single string wher
# Arguments
- `vecd::Vector`
a vector of dictionaries
A vector of dictionaries containing chat messages
- `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
a string with the formatted dictionaries
# Returns
A formatted string where each line contains either:
- If withkey=true: "name> message\n"
- If withkey=false: "message\n"
# Example
```jldoctest
julia> using Revise
julia> using GeneralUtils
julia> vecd = [Dict(:name => "John", :text => "Hello"), Dict(:name => "Jane", :text => "Goodbye")]
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
"John> Hello\nJane> Goodbye\n"
```
# Signature
"""
function chatHistoryToText(vecd::Vector; withkey=true)::String
function chatHistoryToText(vecd::Vector; withkey=true, range=nothing)::String
# Initialize an empty string to hold the final 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
if withkey
# Loop through each dictionary in the input vector
for d in vecd
# Extract the 'name' and 'text' keys from the dictionary
name = d[:name]
_text = d[:text]
# Append the formatted string to the text variable
text *= "$name> $_text \n"
# Loop through each dictionary in the input vector
for d in elements
# Extract the 'name' and 'text' keys from the dictionary
name = d[:name]
_text = d[:text]
# Append the formatted string to the text variable
text *= "$name:> $_text \n"
end
else
# Loop through each dictionary in the input vector
for d in vecd
# Iterate over all key-value pairs in the dictionary
for (k, v) in d
# Append the formatted string to the text variable
text *= "$v \n"
end
end
# Loop through each dictionary in the input vector
for d in elements
# Iterate over all key-value pairs in the dictionary
for (k, v) in d
# Append the formatted string to the text variable
text *= "$v \n"
end
end
end
# Return the final text
@@ -191,6 +197,35 @@ 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(;
event_description::Union{String, Nothing}=nothing,
timestamp::Union{DateTime, Nothing}=nothing,
@@ -220,9 +255,33 @@ function eventdict(;
end
function createTimeline(memory::T1; skiprecent::Integer=0) where {T1<:AbstractVector}
events = memory[1:end-skiprecent]
""" 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: "{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)
# User> Hello
# Assistant> Hi there! with a smile
"""
function createTimeline(events::T1) where {T1<:AbstractVector}
timeline = ""
for (i, event) in enumerate(events)
if event[:outcome] === nothing
@@ -236,8 +295,6 @@ function createTimeline(memory::T1; skiprecent::Integer=0) where {T1<:AbstractVe
end
# """ Convert a single chat dictionary into LLM model instruct format.
# # Llama 3 instruct format example