Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e53f57707 | |||
| 1eea6c66b6 | |||
| 37ff68c963 | |||
| e5fc800c83 | |||
| 4397ec5fb3 | |||
| 9e3f5a0967 | |||
| 30b79a3b34 | |||
| e5b8518c00 | |||
| d115e60ddf | |||
| e0e6aced33 | |||
| c803238f86 | |||
| 4d6cdbcf4a | |||
| 206c0d2c62 | |||
| 597319a147 | |||
| abfe6f45fb | |||
| f8b3150c17 | |||
| caed2a82d6 | |||
| 7ec3edfd77 | |||
| d7adfaffa8 | |||
| 5bde0ca1f2 | |||
| a3ab288cfe | |||
| 17b0974d82 | |||
| 8fed0b5e8f | |||
| 567d6b79d7 | |||
| 74be3e5717 | |||
| 402b6fcadd | |||
| ef523aaa48 |
+41
-5
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
julia_version = "1.12.6"
|
julia_version = "1.12.6"
|
||||||
manifest_format = "2.0"
|
manifest_format = "2.0"
|
||||||
project_hash = "1e317787f914f6d857feb7c23bb910d1185caed9"
|
project_hash = "1c1379a2cec320abc347f3acb5ee815ba9855aa6"
|
||||||
|
|
||||||
[[deps.Accessors]]
|
[[deps.Accessors]]
|
||||||
deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
|
deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
|
||||||
@@ -244,6 +244,12 @@ git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec"
|
|||||||
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
|
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
|
|
||||||
|
[[deps.EzXML]]
|
||||||
|
deps = ["Printf", "XML2_jll"]
|
||||||
|
git-tree-sha1 = "7ea1aa5869e2626ccae84480e4f37185bc6f41d3"
|
||||||
|
uuid = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615"
|
||||||
|
version = "1.2.3"
|
||||||
|
|
||||||
[[deps.FileIO]]
|
[[deps.FileIO]]
|
||||||
deps = ["Pkg", "Requires", "UUIDs"]
|
deps = ["Pkg", "Requires", "UUIDs"]
|
||||||
git-tree-sha1 = "6621fef488e496356c9c9625d0562c12a6070819"
|
git-tree-sha1 = "6621fef488e496356c9c9625d0562c12a6070819"
|
||||||
@@ -294,11 +300,11 @@ version = "1.1.0"
|
|||||||
|
|
||||||
[[deps.GeneralUtils]]
|
[[deps.GeneralUtils]]
|
||||||
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "Graphs", "HTTP", "JSON", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SHA", "StringDistances", "UUIDs"]
|
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "Graphs", "HTTP", "JSON", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SHA", "StringDistances", "UUIDs"]
|
||||||
git-tree-sha1 = "a75a088ee8e5faf10f554ca00748e0e6ca58d1ca"
|
git-tree-sha1 = "93293126d24d3929ef6a5067f347bc28c6582c71"
|
||||||
repo-rev = "main"
|
repo-rev = "main"
|
||||||
repo-url = "https://git.yiem.cc/ton/GeneralUtils"
|
repo-url = "https://git.yiem.cc/ton/GeneralUtils"
|
||||||
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
||||||
version = "0.5.1"
|
version = "0.5.10"
|
||||||
|
|
||||||
[[deps.Graphs]]
|
[[deps.Graphs]]
|
||||||
deps = ["ArnoldiMethod", "DataStructures", "Inflate", "LinearAlgebra", "Random", "SimpleTraits", "SparseArrays", "Statistics"]
|
deps = ["ArnoldiMethod", "DataStructures", "Inflate", "LinearAlgebra", "Random", "SimpleTraits", "SparseArrays", "Statistics"]
|
||||||
@@ -500,6 +506,12 @@ version = "1.11.3+1"
|
|||||||
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
|
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
||||||
|
[[deps.Libiconv_jll]]
|
||||||
|
deps = ["Artifacts", "JLLWrappers", "Libdl"]
|
||||||
|
git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809"
|
||||||
|
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
|
||||||
|
version = "1.18.0+0"
|
||||||
|
|
||||||
[[deps.LinearAlgebra]]
|
[[deps.LinearAlgebra]]
|
||||||
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
|
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
|
||||||
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
||||||
@@ -828,6 +840,12 @@ git-tree-sha1 = "084c47c7c5ce5cfecefa0a98dff69eb3646b5a80"
|
|||||||
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
|
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
|
||||||
version = "1.4.10"
|
version = "1.4.10"
|
||||||
|
|
||||||
|
[[deps.Serde]]
|
||||||
|
deps = ["CSV", "Dates", "EzXML", "JSON", "TOML", "UUIDs", "YAML"]
|
||||||
|
git-tree-sha1 = "f397fc8779cc53e4677c2708f3802c6996f28d00"
|
||||||
|
uuid = "db9b398d-9517-45f8-9a95-92af99003e0e"
|
||||||
|
version = "3.7.2"
|
||||||
|
|
||||||
[[deps.Serialization]]
|
[[deps.Serialization]]
|
||||||
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@@ -932,6 +950,12 @@ git-tree-sha1 = "cd83a04baf746e3b43b83c61b7de77ab0409b80a"
|
|||||||
uuid = "88034a9c-02f8-509d-84a9-84ec65e18404"
|
uuid = "88034a9c-02f8-509d-84a9-84ec65e18404"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|
||||||
|
[[deps.StringEncodings]]
|
||||||
|
deps = ["Libiconv_jll"]
|
||||||
|
git-tree-sha1 = "b765e46ba27ecf6b44faf70df40c57aa3a547dcb"
|
||||||
|
uuid = "69024149-9ee7-55f6-a4c4-859efe599b68"
|
||||||
|
version = "0.3.7"
|
||||||
|
|
||||||
[[deps.StringManipulation]]
|
[[deps.StringManipulation]]
|
||||||
deps = ["PrecompileTools"]
|
deps = ["PrecompileTools"]
|
||||||
git-tree-sha1 = "d05693d339e37d6ab134c5ab53c29fce5ee5d7d5"
|
git-tree-sha1 = "d05693d339e37d6ab134c5ab53c29fce5ee5d7d5"
|
||||||
@@ -1052,11 +1076,23 @@ git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7"
|
|||||||
uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60"
|
uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
|
||||||
|
[[deps.XML2_jll]]
|
||||||
|
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"]
|
||||||
|
git-tree-sha1 = "3f3315d89fc954a28f5b471bce698ed6e27481be"
|
||||||
|
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
|
||||||
|
version = "2.15.3+0"
|
||||||
|
|
||||||
|
[[deps.YAML]]
|
||||||
|
deps = ["Base64", "Dates", "Printf", "StringEncodings"]
|
||||||
|
git-tree-sha1 = "a1c0c7585346251353cddede21f180b96388c403"
|
||||||
|
uuid = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
|
||||||
|
version = "0.4.16"
|
||||||
|
|
||||||
[[deps.YiemAgent]]
|
[[deps.YiemAgent]]
|
||||||
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serialization", "URIs", "UUIDs"]
|
deps = ["Base64", "CSV", "DataFrames", "DataStructures", "Dates", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serde", "Serialization", "URIs", "UUIDs"]
|
||||||
path = "."
|
path = "."
|
||||||
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
||||||
version = "0.6.5"
|
version = "0.7.4"
|
||||||
|
|
||||||
[[deps.Zlib_jll]]
|
[[deps.Zlib_jll]]
|
||||||
deps = ["Libdl"]
|
deps = ["Libdl"]
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,6 @@
|
|||||||
name = "YiemAgent"
|
name = "YiemAgent"
|
||||||
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
||||||
version = "0.7.2"
|
version = "0.7.4"
|
||||||
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
authors = ["narawat lamaiin <narawat@outlook.com>"]
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
@@ -19,6 +19,7 @@ PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337"
|
|||||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||||
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
|
||||||
SQLLLM = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
|
SQLLLM = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
|
||||||
|
Serde = "db9b398d-9517-45f8-9a95-92af99003e0e"
|
||||||
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
||||||
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
|
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
|
||||||
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
||||||
@@ -27,9 +28,10 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
|||||||
Base64 = "1.11.0"
|
Base64 = "1.11.0"
|
||||||
CSV = "0.10.15"
|
CSV = "0.10.15"
|
||||||
DataFrames = "1.7.0"
|
DataFrames = "1.7.0"
|
||||||
GeneralUtils = "0.5.1"
|
GeneralUtils = "0.5.10"
|
||||||
HTTP = "2.4.0"
|
HTTP = "2.4.0"
|
||||||
JSON = "1.6.1"
|
JSON = "1.6.1"
|
||||||
LLMMCTS = "0.1.5"
|
LLMMCTS = "0.1.5"
|
||||||
NATS = "0.1.0"
|
NATS = "0.1.0"
|
||||||
SQLLLM = "0.2.8"
|
SQLLLM = "0.2.8"
|
||||||
|
Serde = "3.7.2"
|
||||||
|
|||||||
@@ -1,111 +1,10 @@
|
|||||||
using HTTP, JSON, URIs, Random, PrettyPrinting, UUIDs, Dates, DataFrames, DataStructures
|
# check if this column has vector embedding. if there is one, seach vector version instead
|
||||||
using GeneralUtils, SQLLLM, YiemAgent
|
column_name_embedding = column_name * "_embedding"
|
||||||
|
if occursin(column_name_embedding, tables_schema[column_name_embedding])
|
||||||
config = JSON.parsefile("./appconfig.json")
|
vector_column = Dict(
|
||||||
host_url, _port = split(config["externalservice"]["sommpanion_db"]["url"], ':')
|
"table_name"=> table_name,
|
||||||
port = parse(Int, _port)
|
"column_name"=> column_name_embedding,
|
||||||
dbname = "winedb"
|
"operator"=> "vector_similarity",
|
||||||
user = config["externalservice"]["sommpanion_db"]["user"]
|
"value"=> column_obj["value"]
|
||||||
password = config["externalservice"]["sommpanion_db"]["password"]
|
)
|
||||||
pg_conn_str = "host=$host_url port=$port dbname=$dbname user=$user password=$password"
|
|
||||||
|
|
||||||
function execute_sql_winedb(sql::T) where {T<:AbstractString}
|
|
||||||
host_url, _port = split(config["externalservice"]["sommpanion_db"]["url"], ':')
|
|
||||||
port = parse(Int, _port)
|
|
||||||
dbname = "winedb"
|
|
||||||
user = config["externalservice"]["sommpanion_db"]["user"]
|
|
||||||
password = config["externalservice"]["sommpanion_db"]["password"]
|
|
||||||
db_connection = LibPQ.Connection("host=$host_url port=$port dbname=$dbname user=$user password=$password")
|
|
||||||
result = nothing
|
|
||||||
try
|
|
||||||
result = LibPQ.execute(db_connection, sql)
|
|
||||||
catch e
|
|
||||||
LibPQ.close(db_connection)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
LibPQ.close(db_connection)
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
sql =
|
|
||||||
"""
|
|
||||||
SELECT T1.winery, T1.wine_name, T1.wine_id, T1.vintage, T1.region, T1.country, T1.wine_type, T1.grape, T1.serving_temperature, T1.sweetness, T1.intensity, T1.tannin, T1.acidity, T1.tasting_notes, T2.price, T2.currency, T1.image_url, T3.retailer_name, T3.retailer_id FROM "wine" AS T1 JOIN "retailer_wine" AS T2 ON T1.wine_id = T2.wine_id JOIN "retailer" AS T3 ON T2.retailer_id = T3.retailer_id WHERE T1.wine_name = 'Montrachet Grand Cru' AND T1.winery = 'Domaine Jacques Prieur' AND T3.retailer_name = 'Yiem Wines Ltd' AND T3.retailer_id = 'f54eab6b-7650-4448-b009-c53f3efbcc3b';
|
|
||||||
"""
|
|
||||||
|
|
||||||
textresult, sql_result_raw, _, _ = YiemAgent.SQLexecution(execute_sql_winedb, sql)
|
|
||||||
result_vec = GeneralUtils.dfToVectorDict(sql_result_raw)
|
|
||||||
|
|
||||||
for d in result_vec
|
|
||||||
wine_name = d["wine_name"]
|
|
||||||
image_url_json_str = d["image_url"]
|
|
||||||
image_url_json_obj = JSON.parse(image_url_json)
|
|
||||||
base_url = "http://192.168.88.106:8080/"
|
|
||||||
image_base64 =
|
|
||||||
if haskey(image_url_json_obj, "bottle")
|
|
||||||
url = base_url * image_url_json_obj["bottle"]
|
|
||||||
image_data = HTTP.get(url) # vector{int} data
|
|
||||||
image_base64_string = base64encode(image_data)
|
|
||||||
else
|
|
||||||
nothing
|
|
||||||
end
|
|
||||||
d["image"] = image_base64
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
using LibPQ
|
|
||||||
using Tables
|
|
||||||
|
|
||||||
"""
|
|
||||||
update_car_regions_one_by_one(conn::LibPQ.Connection, target_word::String)
|
|
||||||
|
|
||||||
Iterates through all rows in the 'car' table where the region is "German",
|
|
||||||
and updates them one-by-one to the `target_word`.
|
|
||||||
"""
|
|
||||||
function update_car_regions_one_by_one(pg_conn_str::String, replace_word::String , target_word::String)
|
|
||||||
conn = LibPQ.Connection(pg_conn_str)
|
|
||||||
# 1. Fetch the target rows. Assumes 'id' is the primary key.
|
|
||||||
# We select the ID to target rows individually during the update step.
|
|
||||||
select_query = "SELECT id FROM car WHERE region = '$replace_word';"
|
|
||||||
|
|
||||||
result = execute(conn, select_query)
|
|
||||||
rows = Tables.rows(result)
|
|
||||||
|
|
||||||
# 2. Prepare the update statement for execution reuse
|
|
||||||
# Using explicit types for parameter placeholders ($1, $2)
|
|
||||||
update_query = "UPDATE car SET region = \$1 WHERE id = \$2;"
|
|
||||||
|
|
||||||
println("Starting one-by-one update...")
|
|
||||||
updated_count = 0
|
|
||||||
|
|
||||||
# 3. Iterate through rows one-by-one
|
|
||||||
for row in rows
|
|
||||||
# LibPQ row values are accessed via properties or column names
|
|
||||||
row_id = row.id
|
|
||||||
|
|
||||||
# Execute the parameterized statement safely
|
|
||||||
execute(conn, update_query, [target_word, row_id])
|
|
||||||
updated_count += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
println("Successfully updated \$updated_count rows.")
|
|
||||||
return updated_count
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+156
-128
@@ -4,7 +4,7 @@ export addNewMessage, conversation, decisionMaker, reflector, generatechat,
|
|||||||
generalconversation, detectWineryName, generateSituationReport
|
generalconversation, detectWineryName, generateSituationReport
|
||||||
|
|
||||||
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||||
DataFrames, CSV
|
DataFrames, Serde
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
using ..type, ..util, ..llmfunction
|
using ..type, ..util, ..llmfunction
|
||||||
|
|
||||||
@@ -76,35 +76,7 @@ OrderedDict{String, Any} with 4 entries:
|
|||||||
function decisionMaker(a::T; recentevents::Integer=20, maxattempt=3
|
function decisionMaker(a::T; recentevents::Integer=20, maxattempt=3
|
||||||
) where {T<:agent}
|
) where {T<:agent}
|
||||||
@info "YiemAgent decisionMaker() start " @__LINE__
|
@info "YiemAgent decisionMaker() start " @__LINE__
|
||||||
# lessonDict = copy(JSON.parsefile("lesson.json"))
|
|
||||||
|
|
||||||
# lesson =
|
|
||||||
# if isempty(lessonDict)
|
|
||||||
# ""
|
|
||||||
# else
|
|
||||||
# lessons = Dict{String, Any}()
|
|
||||||
# for (k, v) in lessonDict
|
|
||||||
# lessons[k] = lessonDict[k][:lesson]
|
|
||||||
# end
|
|
||||||
|
|
||||||
# """
|
|
||||||
# You have attempted to help the user before and failed, either because your reasoning for the
|
|
||||||
# recommendation was incorrect or your response did not exactly match the user expectation.
|
|
||||||
# The following lesson(s) give a plan to avoid failing to help the user in the same way you
|
|
||||||
# did previously. Use them to improve your strategy to help the user.
|
|
||||||
|
|
||||||
# Here are some lessons in JSON format:
|
|
||||||
# $(JSON.json(lessons))
|
|
||||||
|
|
||||||
# When providing the thought and action for the current trial, that into account these failed
|
|
||||||
# trajectories and make sure not to repeat the same mistakes and incorrect answers.
|
|
||||||
# """
|
|
||||||
# end
|
|
||||||
|
|
||||||
# recentevents_ind = GeneralUtils.recentElementsIndex(
|
|
||||||
# length(a.memory["events"]), recentevents; includelatest=true)
|
|
||||||
|
|
||||||
requiredKeys = ["plan", "action_name", "action_input"]
|
|
||||||
context =
|
context =
|
||||||
"""
|
"""
|
||||||
<internal_context_for_assistant>
|
<internal_context_for_assistant>
|
||||||
@@ -122,75 +94,125 @@ function decisionMaker(a::T; recentevents::Integer=20, maxattempt=3
|
|||||||
errornote = "N/A"
|
errornote = "N/A"
|
||||||
response = nothing # placeholder for show when error msg show up
|
response = nothing # placeholder for show when error msg show up
|
||||||
|
|
||||||
for attempt in 1:maxattempt
|
"""
|
||||||
|
{
|
||||||
|
"model": "your-model.gguf",
|
||||||
|
"messages": [ ... ],
|
||||||
|
"response_format": {
|
||||||
|
"type": "json_schema",
|
||||||
|
"json_schema": {
|
||||||
|
"name": "agent_action",
|
||||||
|
"strict": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"think": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Your step-by-step reasoning process. Explain why you are choosing this action."
|
||||||
|
},
|
||||||
|
"action_name": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["search_web", "get_weather", "calculate_math"],
|
||||||
|
"description": "The exact name of the action to execute."
|
||||||
|
},
|
||||||
|
"action_input": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"query": { "type": ["string", "null"], "description": "For search_web" },
|
||||||
|
"location": { "type": ["string", "null"], "description": "For get_weather" },
|
||||||
|
"equation": { "type": ["string", "null"], "description": "For calculate_math" }
|
||||||
|
},
|
||||||
|
"required": ["query", "location", "equation"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["think", "action_name", "action_input"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# strict output format
|
||||||
msg = Dict(
|
response_format = Dict(
|
||||||
"model" => "gemma-4-E4B-it-UD-Q4_K_XL",
|
"type"=> "json_schema",
|
||||||
"messages" => a.chathistory,
|
"json_schema"=> Dict(
|
||||||
"temperature" => 0.7
|
"name"=> "user_profile",
|
||||||
|
"strict"=> true,
|
||||||
|
"schema"=> Dict(
|
||||||
|
"type"=> "object",
|
||||||
|
"properties"=> Dict(
|
||||||
|
"think"=> Dict(
|
||||||
|
"type"=> "string",
|
||||||
|
"description"=> "Your step-by-step reasoning process. Explain why you are choosing this action."
|
||||||
|
),
|
||||||
|
"action_name"=> Dict(
|
||||||
|
"type"=> "string",
|
||||||
|
"enum"=> ["CHAT_BOX", "SEARCH_WINE_DATABASE", "WINE_PRESENTATION_GUIDELINE", "END_CONVER_GUIDELINE"],
|
||||||
|
"description"=> "one of the available actions"
|
||||||
|
),
|
||||||
|
"action_input"=> Dict(
|
||||||
|
"type"=> "object",
|
||||||
|
"properties"=> Dict(
|
||||||
|
"dialogue"=> Dict("type"=> "string", "description"=> "for CHAT_BOX"),
|
||||||
|
"query"=> Dict("type"=> "string", "description"=> "for SEARCH_WINE_DATABASE"),
|
||||||
|
"present_guide"=> Dict("type"=> "null", "description"=> "for WINE_PRESENTATION_GUIDELINE"),
|
||||||
|
"endconv_guide"=> Dict("type"=> "null", "description"=> "for END_CONVER_GUIDELINE"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"required"=> ["think", "action_name", "action_input"],
|
||||||
|
"additionalProperties"=> false
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
response = a.context.text2textInstructLLM(a.id, msg)
|
msg = Dict(
|
||||||
|
"model"=> "gemma-4-E4B-it-UD-Q4_K_XL",
|
||||||
|
"messages"=> a.chathistory,
|
||||||
|
"temperature"=> 0.7,
|
||||||
|
"response_format"=> response_format,
|
||||||
|
)
|
||||||
|
|
||||||
response = GeneralUtils.clean_json_response(response)
|
for attempt in 1:maxattempt
|
||||||
|
response = a.context.text2textInstructLLM(a.id, msg)
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
think, response = GeneralUtils.extractthink(response)
|
|
||||||
response = String(split(response, ", observation")[1]) # in case LLM generate observation key which it isn't supposed to
|
|
||||||
response = strip(response)
|
|
||||||
|
|
||||||
# dollar sign in Julia means string interpolation
|
# dollar sign in Julia means string interpolation
|
||||||
while occursin('$', response)
|
while occursin('$', response)
|
||||||
response = replace(response, '$' => "USD")
|
response = replace(response, '$' => "USD")
|
||||||
end
|
end
|
||||||
|
|
||||||
responsedict = nothing
|
responsedict = JSON.parse(response)
|
||||||
if occursin(requiredKeys[2], response)
|
|
||||||
try
|
|
||||||
_responsedict = JSON.parse(response)
|
|
||||||
responsedict = GeneralUtils.dictify(_responsedict; keytype=String, sort_order=requiredKeys)
|
|
||||||
catch
|
|
||||||
println("\nERROR YiemAgent decisionMaker() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
else
|
|
||||||
println("\nERROR YiemAgent decisionMaker() $errornote --(not qualify response)-> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
# check whether all answer's key points are in responsedict
|
|
||||||
println("\n---")
|
|
||||||
println(responsedict)
|
|
||||||
println("---\n")
|
|
||||||
ispass, errormsg = GeneralUtils.checkAgentResponse_JSON(responsedict, requiredKeys)
|
|
||||||
|
|
||||||
if !ispass
|
|
||||||
errornote = errormsg
|
|
||||||
println("\nERROR YiemAgent decisionMaker() $errornote --(not qualify response)-> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
if responsedict["action_input"] == "CHAT_BOX" &&
|
|
||||||
occursin("similar", responsedict["action_input"])
|
|
||||||
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
# if responsedict["action_name"] ∉ ["CHAT_BOX", "SEARCH_WINE_DATABASE", "PRESENT_WINE_GUIDELINE", "END_CONVER_GUIDELINE"]
|
|
||||||
# errornote = "Your previous attempt didn't use the given functions"
|
|
||||||
# println("\nERROR YiemAgent decisionMaker() $errornote --(not qualify response)--> $(responsedict["action_name"])", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
# continue
|
|
||||||
# end
|
|
||||||
|
|
||||||
# println("\nYiem decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
# println("\nYiem decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
# pprintln(responsedict)
|
# pprintln(responsedict)
|
||||||
|
|
||||||
|
# for decision that use a single action. make it simpler
|
||||||
|
for (k, v) in responsedict["action_input"]
|
||||||
|
responsedict["action_input"] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
if occursin("CHAT_BOX", responsedict["action_input"])
|
||||||
|
println("\nERROR YiemAgent decisionMaker() --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
if responsedict["action_name"] ∉ ["WINE_PRESENTATION_GUIDELINE", "END_CONVER_GUIDELINE"] &&
|
||||||
|
length(responsedict["action_input"]) < 20
|
||||||
|
println("\nERROR YiemAgent decisionMaker() --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
println("\nYiem decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
pprintln(responsedict)
|
||||||
@info "YiemAgent decisionMaker() end " @__LINE__
|
@info "YiemAgent decisionMaker() end " @__LINE__
|
||||||
return responsedict
|
return responsedict
|
||||||
end
|
end
|
||||||
|
|
||||||
# in case decisionMaker failed, force to use generatechat!()
|
# in case decisionMaker failed, force to use generatechat!()
|
||||||
responsedict = OrderedDict(
|
responsedict = OrderedDict(
|
||||||
"plan"=> "N/A",
|
"think"=> "N/A",
|
||||||
"action_name"=> "CHAT_BOX",
|
"action_name"=> "CHAT_BOX",
|
||||||
"action_input"=> "N/A"
|
"action_input"=> "N/A"
|
||||||
)
|
)
|
||||||
@@ -393,8 +415,6 @@ function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Obj
|
|||||||
if loopcount > max_think_loop
|
if loopcount > max_think_loop
|
||||||
|
|
||||||
thoughtdict, result_raw = generatechat!(a)
|
thoughtdict, result_raw = generatechat!(a)
|
||||||
|
|
||||||
|
|
||||||
assistant_response = Dict{String, Any}(
|
assistant_response = Dict{String, Any}(
|
||||||
"role" => "assistant",
|
"role" => "assistant",
|
||||||
"content" => [Dict("type" => "text", "text" => thoughtdict["action_input"]),]
|
"content" => [Dict("type" => "text", "text" => thoughtdict["action_input"]),]
|
||||||
@@ -404,17 +424,17 @@ function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Obj
|
|||||||
items_info = []
|
items_info = []
|
||||||
send_item_ind = [] # index of the item being send to frontend
|
send_item_ind = [] # index of the item being send to frontend
|
||||||
if haskey(a.memory["shortmem"], "items_info")
|
if haskey(a.memory["shortmem"], "items_info")
|
||||||
|
|
||||||
for (i, item) in enumerate(a.memory["shortmem"]["items_info"])
|
for (i, item) in enumerate(a.memory["shortmem"]["items_info"])
|
||||||
|
@info "YiemAgent conversation() shortmem: $(length(a.memory["shortmem"]["items_info"])), items_info: $(length(items_info)), item name: $(item["wine_name"]) " @__LINE__
|
||||||
if haskey(item, "wine_name") && occursin(item["wine_name"], thoughtdict["action_input"])
|
if haskey(item, "wine_name") && occursin(item["wine_name"], thoughtdict["action_input"])
|
||||||
push!(items_info, deepcopy(item))
|
push!(items_info, deepcopy(item))
|
||||||
push!(send_item_ind, i)
|
push!(send_item_ind, i)
|
||||||
|
@info "YiemAgent conversation() shortmem: $(length(a.memory["shortmem"]["items_info"])), items_info: $(length(items_info)) " @__LINE__
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# remove sent items
|
# remove sent items
|
||||||
deleteat!(a.memory["shortmem"]["items_info"], send_item_ind)
|
deleteat!(a.memory["shortmem"]["items_info"], send_item_ind)
|
||||||
|
@info "YiemAgent conversation() shortmem: $(length(a.memory["shortmem"]["items_info"])), items_info: $(length(items_info)) " @__LINE__
|
||||||
end
|
end
|
||||||
|
|
||||||
response_to_frontend = Dict{String, Any}(
|
response_to_frontend = Dict{String, Any}(
|
||||||
@@ -435,8 +455,6 @@ function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Obj
|
|||||||
thoughtdict, result_raw = think(a)
|
thoughtdict, result_raw = think(a)
|
||||||
|
|
||||||
if thoughtdict["action_name"] ∈ ["CHAT_BOX"]
|
if thoughtdict["action_name"] ∈ ["CHAT_BOX"]
|
||||||
|
|
||||||
|
|
||||||
assistant_response = Dict{String, Any}(
|
assistant_response = Dict{String, Any}(
|
||||||
"role" => "assistant",
|
"role" => "assistant",
|
||||||
"content" => [Dict("type" => "text", "text" => thoughtdict["action_input"]),]
|
"content" => [Dict("type" => "text", "text" => thoughtdict["action_input"]),]
|
||||||
@@ -446,17 +464,17 @@ function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Obj
|
|||||||
items_info = []
|
items_info = []
|
||||||
send_item_ind = [] # index of the item being send to frontend
|
send_item_ind = [] # index of the item being send to frontend
|
||||||
if haskey(a.memory["shortmem"], "items_info")
|
if haskey(a.memory["shortmem"], "items_info")
|
||||||
|
|
||||||
for (i, item) in enumerate(a.memory["shortmem"]["items_info"])
|
for (i, item) in enumerate(a.memory["shortmem"]["items_info"])
|
||||||
|
@info "YiemAgent conversation() shortmem: $(length(a.memory["shortmem"]["items_info"])), items_info: $(length(items_info)), item name: $(item["wine_name"]) " @__LINE__
|
||||||
if haskey(item, "wine_name") && occursin(item["wine_name"], thoughtdict["action_input"])
|
if haskey(item, "wine_name") && occursin(item["wine_name"], thoughtdict["action_input"])
|
||||||
push!(items_info, deepcopy(item))
|
push!(items_info, deepcopy(item))
|
||||||
push!(send_item_ind, i)
|
push!(send_item_ind, i)
|
||||||
|
@info "YiemAgent conversation() shortmem: $(length(a.memory["shortmem"]["items_info"])), items_info: $(length(items_info)) " @__LINE__
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# remove sent items
|
# remove sent items
|
||||||
deleteat!(a.memory["shortmem"]["items_info"], send_item_ind)
|
deleteat!(a.memory["shortmem"]["items_info"], send_item_ind)
|
||||||
|
@info "YiemAgent conversation() shortmem: $(length(a.memory["shortmem"]["items_info"])), items_info: $(length(items_info)) " @__LINE__
|
||||||
end
|
end
|
||||||
|
|
||||||
response_to_frontend = Dict{String, Any}(
|
response_to_frontend = Dict{String, Any}(
|
||||||
@@ -580,7 +598,7 @@ function think(a::T)::NamedTuple{(:thoughtdict, :result_raw), Tuple{OrderedDict,
|
|||||||
end
|
end
|
||||||
|
|
||||||
@info "YiemAgent think() end " @__LINE__
|
@info "YiemAgent think() end " @__LINE__
|
||||||
@show thoughtdict
|
# @show thoughtdict
|
||||||
println("---\n")
|
println("---\n")
|
||||||
return (thoughtdict=thoughtdict, result_raw=result_raw)
|
return (thoughtdict=thoughtdict, result_raw=result_raw)
|
||||||
end
|
end
|
||||||
@@ -730,16 +748,14 @@ function generatechat!(a::T; maxattempt::Integer=10
|
|||||||
- Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
|
- Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
|
||||||
- Answering questions or offering additional services beyond those related to your store's wine recommendations such as discounts, quantity, rewards programs, promotions, delivery options, shipping, boxes, gift wrapping, packaging, personalized messages or something similar. These are the job of our sales team at the store.
|
- Answering questions or offering additional services beyond those related to your store's wine recommendations such as discounts, quantity, rewards programs, promotions, delivery options, shipping, boxes, gift wrapping, packaging, personalized messages or something similar. These are the job of our sales team at the store.
|
||||||
|
|
||||||
# you should then respond to the user with interleaving plan, action_name, action_input
|
# you should then respond to the user with interleaving think, action_name, action_input in JSON format
|
||||||
1) "plan", Based on the current situation, state a complete action plan to complete the task and rationale. Be specific.
|
1) "think", Your step-by-step reasoning process. Explain why you are choosing this action.
|
||||||
2) "action_name", Must be "CHAT_BOX
|
2) "action_name", Can be one of the available_actions name.
|
||||||
3) "action_input", Dialogue you want to chat with the user according to your plan.
|
3) "action_input", Dialogue you want to chat with the user according to your plan.
|
||||||
After the action is executed you gets "action_result". It is the output from the action you selected.
|
After the action is executed you gets "action_result". It is the output from the action you selected.
|
||||||
|
|
||||||
# you should only respond in JSON format as described below
|
# available actions
|
||||||
"plan": "...",
|
"CHAT_BOX", which you can use to talk with the user. The input is dialogue you want to chat with the user according to your plan.
|
||||||
"action_name": "...",
|
|
||||||
"action_input": "..."
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
system_msg = Dict(
|
system_msg = Dict(
|
||||||
@@ -762,50 +778,62 @@ function generatechat!(a::T; maxattempt::Integer=10
|
|||||||
println("\nYiemAgent generatechat() attempt $attempt/$maxattempt ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
println("\nYiemAgent generatechat() attempt $attempt/$maxattempt ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
response_format = Dict(
|
||||||
|
"type"=> "json_schema",
|
||||||
|
"json_schema"=> Dict(
|
||||||
|
"name"=> "user_profile",
|
||||||
|
"strict"=> true,
|
||||||
|
"schema"=> Dict(
|
||||||
|
"type"=> "object",
|
||||||
|
"properties"=> Dict(
|
||||||
|
"think"=> Dict(
|
||||||
|
"type"=> "string",
|
||||||
|
"description" => "Your step-by-step reasoning process. Explain why you are choosing this action.",
|
||||||
|
),
|
||||||
|
"action_name"=> Dict(
|
||||||
|
"type"=> "string",
|
||||||
|
"enum"=> ["CHAT_BOX"],
|
||||||
|
"description" => "one of the available actions",
|
||||||
|
),
|
||||||
|
"action_input"=> Dict(
|
||||||
|
"type"=> "string",
|
||||||
|
"description" => "Dialogue you want to chat with the user according to your plan.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"required"=> ["think", "action_name", "action_input"],
|
||||||
|
"additionalProperties"=> false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
msg = Dict(
|
msg = Dict(
|
||||||
"model" => "gemma-4-E4B-it-UD-Q4_K_XL",
|
"model" => "gemma-4-E4B-it-UD-Q4_K_XL",
|
||||||
"messages" => chathistory,
|
"messages" => chathistory,
|
||||||
"temperature" => 0.7
|
"temperature" => 0.7,
|
||||||
|
"response_format"=> response_format,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = a.context.text2textInstructLLM(a.id, msg)
|
response = a.context.text2textInstructLLM(a.id, msg)
|
||||||
response = GeneralUtils.clean_json_response(response)
|
|
||||||
response = GeneralUtils.remove_french_accents(response)
|
response = GeneralUtils.remove_french_accents(response)
|
||||||
think, response = GeneralUtils.extractthink(response)
|
|
||||||
|
|
||||||
response = strip(response)
|
# dollar sign in Julia means string interpolation
|
||||||
|
while occursin('$', response)
|
||||||
responsedict = nothing
|
response = replace(response, '$' => "USD")
|
||||||
if occursin(requiredKeys[2], response)
|
|
||||||
try
|
|
||||||
_responsedict = JSON.parse(response)
|
|
||||||
responsedict = GeneralUtils.dictify(_responsedict; keytype=String, sort_order=requiredKeys)
|
|
||||||
catch
|
|
||||||
println("\nERROR YiemAgent generatechat() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
continue
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
println("\nERROR YiemAgent generatechat() $errornote --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
responsedict = JSON.parse(response)
|
||||||
|
|
||||||
|
if occursin("CHAT_BOX", responsedict["action_input"]) ||
|
||||||
|
length(responsedict["action_input"]) < 20
|
||||||
|
println("\nERROR YiemAgent generatechat() --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
|
println("\nYiem generatechat!() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
pprintln(responsedict)
|
||||||
|
responsedict["action_result"] = "Action result is the next user dialogue."
|
||||||
|
@info "YiemAgent generatechat!() end " @__LINE__
|
||||||
|
|
||||||
# check whether all answer's key points are in responsedict
|
|
||||||
ispass, errormsg = GeneralUtils.checkAgentResponse_JSON(responsedict, requiredKeys)
|
|
||||||
if !ispass
|
|
||||||
errornote = errormsg
|
|
||||||
println("\nERROR YiemAgent generatechat() $errornote --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
# if responsedict["action_name"] ∉ ["CHAT_BOX", "SEARCH_WINE_DATABASE", "PRESENT_WINE_GUIDELINE", "END_CONVER_GUIDELINE"]
|
|
||||||
# errornote = "Your previous attempt didn't use the given functions"
|
|
||||||
# println("\nERROR YiemAgent decisionMaker() $errornote --(not qualify response)--> $(responsedict["action_name"])", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
# continue
|
|
||||||
# end
|
|
||||||
|
|
||||||
# println("\nYiem decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
# pprintln(responsedict)
|
|
||||||
responsedict["action_result"] = "Action result is the next user dialogue."
|
responsedict["action_result"] = "Action result is the next user dialogue."
|
||||||
@info "YiemAgent generatechat!() end " @__LINE__
|
@info "YiemAgent generatechat!() end " @__LINE__
|
||||||
return (thoughtdict=responsedict, result_raw=responsedict["action_input"])
|
return (thoughtdict=responsedict, result_raw=responsedict["action_input"])
|
||||||
|
|||||||
+569
-208
@@ -5,7 +5,7 @@ export virtualWineUserChatbox, jsoncorrection, search_wine_database!, # recomme
|
|||||||
extractWineAttributes_2, paraphrase, SQLexecution
|
extractWineAttributes_2, paraphrase, SQLexecution
|
||||||
|
|
||||||
using HTTP, JSON, URIs, Random, PrettyPrinting, UUIDs, Dates, DataFrames, DataStructures,
|
using HTTP, JSON, URIs, Random, PrettyPrinting, UUIDs, Dates, DataFrames, DataStructures,
|
||||||
Base64
|
Base64, Serde, LibPQ, NATS
|
||||||
using GeneralUtils, SQLLLM
|
using GeneralUtils, SQLLLM
|
||||||
using ..type, ..util
|
using ..type, ..util
|
||||||
|
|
||||||
@@ -277,24 +277,18 @@ end
|
|||||||
# Example
|
# Example
|
||||||
```jldoctest
|
```jldoctest
|
||||||
julia> using ChatAgent
|
julia> using ChatAgent
|
||||||
julia> agent = ChatAgent.agentReflex("Jene")
|
julia> agent = YiemAgent.sommelier(...)
|
||||||
julia> input = "{\"food\": \"pizza\", \"occasion\": \"anniversary\"}"
|
julia> thoughtdict =
|
||||||
julia> result = checkinventory(agent, input)
|
OrderedDict{String, Any}(
|
||||||
"{"wine 1": {\"Winery\": \"Pichon Baron\", \"wine name\": \"Pauillac (Grand Cru Classé)\", \"grape variety\": \"Cabernet Sauvignon\", \"year\": 2010, \"price\": \"125 USD\", \"stock ID\": \"ar-17\"}, }"
|
"plan" => "The user is asking a very specific question about a wine (Brunello di Montalcino from Tenuta CastelGiocondo). Although the policy suggests gathering budget, wine type, and occasion, the user has provided enough specific information (name, region, producer) to attempt a direct search in the database. I will use the SEARCH_WINE_DATABASE action to check if this specific wine is in our inventory.",
|
||||||
|
"action_name" => "SEARCH_WINE_DATABASE",
|
||||||
|
"action_input" => "Brunello di Montalcino from Tenuta CastelGiocondo")
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
function search_wine_database!(a::T, thoughtdict::AbstractDict; useSQLLLM::Bool=false
|
function search_wine_database!(a::T, thoughtdict::AbstractDict; useSQLLLM::Bool=false
|
||||||
)::NamedTuple{(:thoughtdict, :result_raw), Tuple{OrderedDict, Any}} where {T<:agent}
|
)::NamedTuple{(:thoughtdict, :result_raw), Tuple{OrderedDict, Any}} where {T<:agent}
|
||||||
|
|
||||||
println("\ncheckinventory order: $(thoughtdict["action_input"]) ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
println("\ncheckinventory order: $(thoughtdict["action_input"]) ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
wineattributes_1 = extractWineAttributes_1(a, thoughtdict["action_input"])
|
|
||||||
wineattributes_2 = extractWineAttributes_2(a, thoughtdict["action_input"])
|
|
||||||
|
|
||||||
retrieve_attributes = ["winery", "wine_name", "wine_id", "vintage", "region", "country", "wine_type", "grape", "serving_temperature", "sweetness", "intensity", "tannin", "acidity", "tasting_notes", "price", "currency", "image_url", "retailer_name", "retailer_id"]
|
|
||||||
_inventoryquery = "$(thoughtdict["action_input"]), $wineattributes_1, $wineattributes_2, retailer_name: $(a.retailername), retailerid: $(a.retailerid)"
|
|
||||||
inventoryquery = "Retrieves $retrieve_attributes of wines that match the following criteria - {$_inventoryquery}"
|
|
||||||
println("\ncheckinventory input: $inventoryquery ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
|
|
||||||
if useSQLLLM
|
if useSQLLLM
|
||||||
# add suppport for similarSQLVectorDB
|
# add suppport for similarSQLVectorDB
|
||||||
textresult, result_raw = SQLLLM.query(
|
textresult, result_raw = SQLLLM.query(
|
||||||
@@ -308,10 +302,47 @@ function search_wine_database!(a::T, thoughtdict::AbstractDict; useSQLLLM::Bool=
|
|||||||
else
|
else
|
||||||
|
|
||||||
# direct query with possible sql instead of SQLLLM.
|
# direct query with possible sql instead of SQLLLM.
|
||||||
sql = generatesql(a, inventoryquery)
|
hard_conditions, vector_search = wine_search_term_classification(a, thoughtdict["action_input"])
|
||||||
println("\nSQL: $sql ", @__FILE__, ":", @__LINE__, " $(Dates.now()) \n")
|
|
||||||
|
# do hard filter
|
||||||
|
# sql = generatesql(a, inventoryquery)
|
||||||
|
sql = predefined_wine_search_sql(hard_conditions)
|
||||||
|
@info "\nsql: $sql, \nvector_search: $vector_search"
|
||||||
textresult, sql_result_df, success, _ = SQLexecution(a.context.executeSQL, sql)
|
textresult, sql_result_df, success, _ = SQLexecution(a.context.executeSQL, sql)
|
||||||
|
|
||||||
|
# do vector search
|
||||||
|
vector_search_str = ""
|
||||||
|
for i in vector_search
|
||||||
|
vector_search_str = vector_search_str * " " * i["value"]
|
||||||
|
end
|
||||||
|
vector_search_str = String(strip(vector_search_str))
|
||||||
|
vector_search_str = GeneralUtils.removestring(vector_search_str, ["%"])
|
||||||
|
@show vector_search
|
||||||
|
@show vector_search_str
|
||||||
|
|
||||||
|
|
||||||
|
config = a.context.agentconfig
|
||||||
|
host_url, _port = split(config["externalservice"]["sommpanion_db"]["url"], ':')
|
||||||
|
port = parse(Int, _port)
|
||||||
|
dbname = "winedb"
|
||||||
|
user = config["externalservice"]["sommpanion_db"]["user"]
|
||||||
|
password = config["externalservice"]["sommpanion_db"]["password"]
|
||||||
|
pg_conn_str = "host=$host_url port=$port dbname=$dbname user=$user password=$password"
|
||||||
|
|
||||||
|
#WORKING
|
||||||
|
# df = GeneralUtils.find_text_vector_similarity(
|
||||||
|
# vector_search_str,
|
||||||
|
# "wine",
|
||||||
|
# "tasting_notes_embedding",
|
||||||
|
# GeneralUtils.execute_postgres_sql(pg_conn_str, sql), #BUG input pair (F, arg)
|
||||||
|
# a.context.getTextEmbedding([vector_search_str]) #BUG input pair (F, arg)
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# @show df
|
||||||
|
# error(888888)
|
||||||
|
|
||||||
|
|
||||||
items = nothing
|
items = nothing
|
||||||
if sql_result_df !== nothing
|
if sql_result_df !== nothing
|
||||||
result_vec = GeneralUtils.dfToVectorDict(sql_result_df)
|
result_vec = GeneralUtils.dfToVectorDict(sql_result_df)
|
||||||
@@ -366,9 +397,9 @@ function generatesql(a::T, searchterm::String,
|
|||||||
Fulfill the objective.
|
Fulfill the objective.
|
||||||
|
|
||||||
# you should then respond to the user with interleaving plan, action_name, action_input
|
# you should then respond to the user with interleaving plan, action_name, action_input
|
||||||
1) "plan, Based on the current situation, state a complete action plan to complete the task and rationale. Be specific.
|
1) "plan", Based on the current situation, state a complete action plan to complete the task and rationale. Be specific.
|
||||||
2) "action_name, Must be "RUNSQL"
|
2) "action_name", Must be "RUNSQL"
|
||||||
3) "action_input, The input to the action you are about to perform according to your plan.
|
3) "action_input", The input to the action you are about to perform according to your plan.
|
||||||
After the action is executed you gets "action_result". It is the output from the action you selected.
|
After the action is executed you gets "action_result". It is the output from the action you selected.
|
||||||
|
|
||||||
# you should only respond in JSON format as described below
|
# you should only respond in JSON format as described below
|
||||||
@@ -610,6 +641,298 @@ function generatesql(a::T, searchterm::String,
|
|||||||
error("SQLLLM DecisionMaker() failed to generate a thought \n", response)
|
error("SQLLLM DecisionMaker() failed to generate a thought \n", response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Example
|
||||||
|
```jldoctest
|
||||||
|
julia> using ChatAgent
|
||||||
|
julia> agent = YiemAgent.sommelier(...)
|
||||||
|
julia> thoughtdict =
|
||||||
|
OrderedDict{String, Any}(
|
||||||
|
"plan" => "The user is asking a very specific question about a wine (Brunello di Montalcino from Tenuta CastelGiocondo). Although the policy suggests gathering budget, wine type, and occasion, the user has provided enough specific information (name, region, producer) to attempt a direct search in the database. I will use the SEARCH_WINE_DATABASE action to check if this specific wine is in our inventory.",
|
||||||
|
"action_name" => "SEARCH_WINE_DATABASE",
|
||||||
|
"action_input" => "Brunello di Montalcino from Tenuta CastelGiocondo")
|
||||||
|
```
|
||||||
|
julia> predefined_wine_search_sql(agent, thoughtdict["action_input"])
|
||||||
|
"""
|
||||||
|
function wine_search_term_classification(a::T, searchterm::String,
|
||||||
|
; maxattempt=10
|
||||||
|
) where {T<:agent}
|
||||||
|
|
||||||
|
systemmsg =
|
||||||
|
"""
|
||||||
|
|
||||||
|
# situation
|
||||||
|
At each round of conversation, you will be given the following:
|
||||||
|
- user search term
|
||||||
|
- database tables schema
|
||||||
|
|
||||||
|
# objective
|
||||||
|
Consult the provided database schema (tables and columns), please map a user's natural-language search term to the appropriate database columns and tables—identify the relevant fields, operators, and values (e.g., for SQL filtering).
|
||||||
|
|
||||||
|
# your responsibility includes
|
||||||
|
Fulfill the objective.
|
||||||
|
|
||||||
|
# You must output your response as a JSON object containing a single key: "extracted_info".
|
||||||
|
The "extracted_info" key must contain an array of objects. Each object must contain:
|
||||||
|
1) "table_name": The name of the table.
|
||||||
|
2) "column_name": The specific column being filtered.
|
||||||
|
3) "operator": The comparison operator (e.g., "=", ">").
|
||||||
|
4) "value": The value to compare against.
|
||||||
|
|
||||||
|
If the user does not specify any filters, return an empty array for "extracted_info": {"extracted_info": []}.
|
||||||
|
|
||||||
|
# here are some example
|
||||||
|
<user>
|
||||||
|
4-wheel drive car with red color that will give me fast and furious emotion. No more than 7000 USD
|
||||||
|
</user>
|
||||||
|
<assistant>
|
||||||
|
{
|
||||||
|
"extracted_info": [
|
||||||
|
{
|
||||||
|
"table_name": "car_info",
|
||||||
|
"column_name": "drive_type",
|
||||||
|
"operator": "=",
|
||||||
|
"value": "4-wheel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table_name": "car_info",
|
||||||
|
"column_name": "color",
|
||||||
|
"operator": "=",
|
||||||
|
"value": "red"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table_name": "car_info",
|
||||||
|
"column_name": "drive_feeling",
|
||||||
|
"operator": "=",
|
||||||
|
"value": "fast and furious"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table_name": "price_list",
|
||||||
|
"column_name": "price",
|
||||||
|
"operator": "<",
|
||||||
|
"value": "7000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</assistant>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# use find_related_tables_for_user_question and inject only related table schema for a given search term
|
||||||
|
# to LLM instead of giving LLM all tables schema.
|
||||||
|
related_tables = a.context.find_related_tables_for_user_question(searchterm)
|
||||||
|
table_schema = ""
|
||||||
|
for table in related_tables
|
||||||
|
_table_schema_str = get_db_table_schema_simple_with_samples(a.context.pg_conn_str, table)
|
||||||
|
|
||||||
|
# _table_schema_str = GeneralUtils.get_db_table_schema_simple(a.context.pg_conn_str, table)
|
||||||
|
table_schema_str = sprint(show, _table_schema_str) * "\n"
|
||||||
|
table_schema = table_schema * table_schema_str
|
||||||
|
end
|
||||||
|
|
||||||
|
context =
|
||||||
|
"""
|
||||||
|
<internal_context_for_assistant>
|
||||||
|
<database_table_schema>
|
||||||
|
$table_schema
|
||||||
|
</database_table_schema>
|
||||||
|
</internal_context_for_assistant>
|
||||||
|
"""
|
||||||
|
input = context * searchterm
|
||||||
|
|
||||||
|
response_format = Dict(
|
||||||
|
"type" => "json_schema",
|
||||||
|
"json_schema" => Dict(
|
||||||
|
"name" => "extracted_conditions",
|
||||||
|
"strict" => true,
|
||||||
|
"schema" => Dict(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict(
|
||||||
|
"extracted_info" => Dict(
|
||||||
|
"type" => "array",
|
||||||
|
"items" => Dict(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict(
|
||||||
|
"table_name" => Dict(
|
||||||
|
"type" => "string",
|
||||||
|
"description" => "The name of the database table."
|
||||||
|
),
|
||||||
|
"column_name" => Dict(
|
||||||
|
"type" => "string",
|
||||||
|
"description" => "The name of the column to filter on."
|
||||||
|
),
|
||||||
|
"operator" => Dict(
|
||||||
|
"type" => "string",
|
||||||
|
"enum" => ["=", "!=", ">", "<", ">=", "<=", "LIKE", "IN", "IS NULL", "IS NOT NULL"],
|
||||||
|
"description" => "The SQL comparison operator."
|
||||||
|
),
|
||||||
|
"value" => Dict(
|
||||||
|
"type" => ["string", "null"],
|
||||||
|
"description" => "The value to compare against. Use null for IS NULL/IS NOT NULL."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"required" => ["table_name", "column_name", "operator", "value"],
|
||||||
|
"additionalProperties" => false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"required" => ["extracted_info"],
|
||||||
|
"additionalProperties" => false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = Dict(
|
||||||
|
"model" => "gemma-4-E4B-it-UD-Q4_K_XL",
|
||||||
|
"messages" => [
|
||||||
|
Dict(
|
||||||
|
"role" => "system",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => systemmsg),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Dict(
|
||||||
|
"role" => "user",
|
||||||
|
"content" => [
|
||||||
|
Dict("type" => "text", "text" => input),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"temperature" => 0.7,
|
||||||
|
"response_format"=> response_format,
|
||||||
|
)
|
||||||
|
|
||||||
|
for attempt in 1:maxattempt
|
||||||
|
response = a.context.text2textInstructLLM("random_id", msg)
|
||||||
|
responsedict = JSON.parse(response)
|
||||||
|
# responsedict = nothing
|
||||||
|
# try
|
||||||
|
# responsedict = Serde.parse_yaml(response)
|
||||||
|
# catch e
|
||||||
|
# println("\nERROR YiemAgent predefined_wine_search_sql() Error: $e --(not qualify response)-> $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
|
||||||
|
# continue
|
||||||
|
# end
|
||||||
|
|
||||||
|
# println("\n ", table_schema)
|
||||||
|
println("\n ", responsedict)
|
||||||
|
@info "before BM25 " @__LINE__
|
||||||
|
|
||||||
|
# to ensure user input is correct
|
||||||
|
for entry in responsedict["extracted_info"]
|
||||||
|
table_name = entry["table_name"]::String
|
||||||
|
column_name = entry["column_name"]::String
|
||||||
|
|
||||||
|
bucket = classify_column(a.context.pg_conn_str, table_name, column_name)
|
||||||
|
|
||||||
|
if bucket == "fuzzy_correction"
|
||||||
|
words_catalog = GeneralUtils.harvest_entity_catalog(a.context.pg_conn_str, table_name, column_name)
|
||||||
|
resolved_word = GeneralUtils.resolve_entity(entry["value"], words_catalog; threshold=0.9)
|
||||||
|
entry["value"] = resolved_word
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# filter for column that will be used for hard condition (SQL where clause)
|
||||||
|
# column with non-standard operator will be used in vector search
|
||||||
|
vector_search_words = ""
|
||||||
|
hard_operators = ["=","<>","!=",">","<",">=","<=","!<","!>","<=>"]
|
||||||
|
|
||||||
|
# Build new list of hard condition entries
|
||||||
|
hard_conditions = JSON.Object{String, Any}[]
|
||||||
|
vector_search = JSON.Object{String, Any}[]
|
||||||
|
for entry in responsedict["extracted_info"]
|
||||||
|
if entry["operator"] ∈ hard_operators
|
||||||
|
push!(hard_conditions, entry)
|
||||||
|
else
|
||||||
|
push!(vector_search, entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
println("")
|
||||||
|
@show responsedict
|
||||||
|
@info "predefined_wine_search_sql() " @__LINE__
|
||||||
|
|
||||||
|
return (hard_conditions=hard_conditions, vector_search=vector_search)
|
||||||
|
end
|
||||||
|
error("SQLLLM DecisionMaker() failed to generate a thought \n", response)
|
||||||
|
end
|
||||||
|
|
||||||
|
function predefined_wine_search_sql(conditions::Vector{JSON.Object{String, Any}})::String
|
||||||
|
# 1. Base SQL structure
|
||||||
|
base_query =
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
w.winery,
|
||||||
|
w.wine_name,
|
||||||
|
w.wine_id,
|
||||||
|
w.vintage,
|
||||||
|
w.region,
|
||||||
|
w.country,
|
||||||
|
w.wine_type,
|
||||||
|
w.grape,
|
||||||
|
w.serving_temperature,
|
||||||
|
w.sweetness,
|
||||||
|
w.intensity,
|
||||||
|
w.tannin,
|
||||||
|
w.acidity,
|
||||||
|
w.tasting_notes,
|
||||||
|
rw.price,
|
||||||
|
rw.currency,
|
||||||
|
w.image_url,
|
||||||
|
r.retailer_name,
|
||||||
|
rw.retailer_id
|
||||||
|
FROM wine AS w
|
||||||
|
JOIN retailer_wine AS rw ON w.wine_id = rw.wine_id
|
||||||
|
JOIN retailer AS r ON rw.retailer_id = r.retailer_id
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 2. Dynamic WHERE Clause Builder
|
||||||
|
where_clauses = String[]
|
||||||
|
|
||||||
|
# Iterate over each condition object in the array
|
||||||
|
for cond in conditions
|
||||||
|
table_name = String(cond["table_name"])
|
||||||
|
column_name = String(cond["column_name"])
|
||||||
|
op = String(cond["operator"])
|
||||||
|
raw_val = cond["value"]
|
||||||
|
|
||||||
|
# Determine table alias
|
||||||
|
alias = if table_name == "wine"
|
||||||
|
"w"
|
||||||
|
elseif table_name == "retailer_wine"
|
||||||
|
"rw"
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Value Type Handling ---
|
||||||
|
final_val = raw_val
|
||||||
|
|
||||||
|
if op in ("=", "<", ">", "<=", ">=")
|
||||||
|
str_val = string(raw_val)
|
||||||
|
num_val = tryparse(Float64, str_val)
|
||||||
|
|
||||||
|
if !isnothing(num_val)
|
||||||
|
final_val = isinteger(num_val) ? round(Int, num_val) : num_val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- SQL Formatting ---
|
||||||
|
if isa(final_val, Number)
|
||||||
|
clause = "$(alias).$(column_name) $(op) $(final_val)"
|
||||||
|
else
|
||||||
|
escaped_val = replace(string(final_val), "'" => "''")
|
||||||
|
clause = "$(alias).$(column_name) $(op) '$(escaped_val)'"
|
||||||
|
end
|
||||||
|
|
||||||
|
push!(where_clauses, clause)
|
||||||
|
end
|
||||||
|
|
||||||
|
# 3. Assemble Final Query
|
||||||
|
where_sql = isempty(where_clauses) ? "" : "WHERE " * join(where_clauses, " AND ")
|
||||||
|
|
||||||
|
return string(base_query, where_sql, ";")
|
||||||
|
end
|
||||||
|
|
||||||
function SQLexecution(executeSQL::Function, sql::T
|
function SQLexecution(executeSQL::Function, sql::T
|
||||||
)::NamedTuple where {T<:AbstractString}
|
)::NamedTuple where {T<:AbstractString}
|
||||||
|
|
||||||
@@ -660,6 +983,64 @@ function SQLexecution(executeSQL::Function, sql::T
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function DEPRECIATED_search_wine_database!(a::T, thoughtdict::AbstractDict; useSQLLLM::Bool=false
|
||||||
|
)::NamedTuple{(:thoughtdict, :result_raw), Tuple{OrderedDict, Any}} where {T<:agent}
|
||||||
|
|
||||||
|
# XXX
|
||||||
|
predefined_wine_search_sql(a, thoughtdict["action_input"])
|
||||||
|
|
||||||
|
println("\ncheckinventory order: $(thoughtdict["action_input"]) ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
wineattributes_1 = extractWineAttributes_1(a, thoughtdict["action_input"])
|
||||||
|
wineattributes_2 = extractWineAttributes_2(a, thoughtdict["action_input"])
|
||||||
|
|
||||||
|
retrieve_attributes = ["winery", "wine_name", "wine_id", "vintage", "region", "country", "wine_type", "grape", "serving_temperature", "sweetness", "intensity", "tannin", "acidity", "tasting_notes", "price", "currency", "image_url", "retailer_name", "retailer_id"]
|
||||||
|
_inventoryquery = "$(thoughtdict["action_input"]), $wineattributes_1, $wineattributes_2, retailer_name: $(a.retailername), retailerid: $(a.retailerid)"
|
||||||
|
inventoryquery = "Retrieves $retrieve_attributes of wines that match the following criteria - {$_inventoryquery}"
|
||||||
|
println("\ncheckinventory input: $inventoryquery ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
||||||
|
|
||||||
|
if useSQLLLM
|
||||||
|
# add suppport for similarSQLVectorDB
|
||||||
|
textresult, result_raw = SQLLLM.query(
|
||||||
|
inventoryquery,
|
||||||
|
a.context.executeSQL,
|
||||||
|
a.context.text2textInstructLLM;
|
||||||
|
insertSQLVectorDB=a.context.insertSQLVectorDB,
|
||||||
|
similarSQLVectorDB=a.context.similarSQLVectorDB,
|
||||||
|
llmFormatName="qwen3")
|
||||||
|
thoughtdict["action_result"] = textresult
|
||||||
|
else
|
||||||
|
|
||||||
|
# direct query with possible sql instead of SQLLLM.
|
||||||
|
sql = generatesql(a, inventoryquery)
|
||||||
|
println("\nSQL: $sql ", @__FILE__, ":", @__LINE__, " $(Dates.now()) \n")
|
||||||
|
textresult, sql_result_df, success, _ = SQLexecution(a.context.executeSQL, sql)
|
||||||
|
|
||||||
|
items = nothing
|
||||||
|
if sql_result_df !== nothing
|
||||||
|
result_vec = GeneralUtils.dfToVectorDict(sql_result_df)
|
||||||
|
|
||||||
|
# get image
|
||||||
|
for d in result_vec
|
||||||
|
image_url_json_str = d["image_url"]
|
||||||
|
image_url_json_obj = JSON.parse(image_url_json_str)
|
||||||
|
base_url = "http://192.168.88.106:8080/"
|
||||||
|
if haskey(image_url_json_obj, "bottle")
|
||||||
|
url = base_url * image_url_json_obj["bottle"]
|
||||||
|
image_data = HTTP.get(url) # vector{int} data
|
||||||
|
image_base64_string = base64encode(image_data.body)
|
||||||
|
d["image"] = image_base64_string
|
||||||
|
else
|
||||||
|
d["image"] = nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
items = result_vec # image is added to each item
|
||||||
|
end
|
||||||
|
|
||||||
|
thoughtdict["action_result"] = textresult
|
||||||
|
end
|
||||||
|
|
||||||
|
return (thoughtdict=thoughtdict, result_raw=items)
|
||||||
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -1037,219 +1418,199 @@ function extractWineAttributes_2(a::T1, input::T2)::String where {T1<:agent, T2<
|
|||||||
end
|
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!
|
|
||||||
"""
|
|
||||||
#[PENDING] use JSON the same as extractWineAttributes_1 is better. change this function to use the same format use decisionMaker
|
|
||||||
header = ["Paraphrase:"]
|
|
||||||
dictkey = ["paraphrase"]
|
|
||||||
|
|
||||||
errornote = "N/A"
|
|
||||||
response = nothing # placeholder for show when error msg show up
|
|
||||||
|
|
||||||
|
|
||||||
for attempt in 1:10
|
|
||||||
usermsg = """
|
|
||||||
Text: $text
|
function get_db_table_schema_simple_with_samples(pg_conn_str::String, table_name::String;
|
||||||
P.S. $errornote
|
schema_name::String="public")::String
|
||||||
|
conn = LibPQ.Connection(pg_conn_str)
|
||||||
|
return get_db_table_schema_simple_with_samples(conn, table_name; schema_name=schema_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_db_table_schema_simple_with_samples(conn, table_name::String; schema_name::String="public", sample_count::Int=3)::String
|
||||||
|
# 1. SQL query for catalog metadata
|
||||||
|
meta_sql = """
|
||||||
|
SELECT
|
||||||
|
a.attname AS column_name,
|
||||||
|
format_type(a.atttypid, a.atttypmod) AS data_type,
|
||||||
|
pg_get_expr(def.adbin, def.adrelid) AS default_value,
|
||||||
|
COALESCE(
|
||||||
|
(SELECT pg_get_constraintdef(p.oid)
|
||||||
|
FROM pg_catalog.pg_constraint p
|
||||||
|
WHERE p.conrelid = c.oid AND a.attnum = ANY(p.conkey)
|
||||||
|
LIMIT 1), ''
|
||||||
|
) AS constraint_definition
|
||||||
|
FROM pg_catalog.pg_attribute a
|
||||||
|
JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
|
||||||
|
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
|
||||||
|
LEFT JOIN pg_catalog.pg_attrdef def ON def.adrelid = c.oid AND def.adnum = a.attnum
|
||||||
|
WHERE c.relname = \$1
|
||||||
|
AND n.nspname = \$2
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped
|
||||||
|
ORDER BY a.attnum;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_prompt =
|
meta_res = DataFrame(execute(conn, meta_sql, [table_name, schema_name]))
|
||||||
[
|
|
||||||
Dict("name" => "system", "text" => systemmsg),
|
|
||||||
Dict("name" => "user", "text" => usermsg)
|
|
||||||
]
|
|
||||||
|
|
||||||
# put in model format
|
if nrow(meta_res) == 0
|
||||||
prompt = GeneralUtils.formatLLMtext(_prompt, a.llmFormatName)
|
error("Table '$schema_name.$table_name' not found.")
|
||||||
|
|
||||||
try
|
|
||||||
response = text2textInstructLLM(prompt)
|
|
||||||
response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
|
|
||||||
think, response = GeneralUtils.extractthink(response)
|
|
||||||
# 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 answer's key points
|
|
||||||
detected_kw = GeneralUtils.detect_keyword(header, response)
|
|
||||||
if 0 ∈ values(detected_kw)
|
|
||||||
errornote = "\nYiemAgent paraphrase() response does not have all answer's key points"
|
|
||||||
continue
|
|
||||||
elseif sum(values(detected_kw)) > length(header)
|
|
||||||
errornote = "\nnYiemAgent paraphrase() response has duplicated answer's key points"
|
|
||||||
continue
|
|
||||||
end
|
end
|
||||||
|
|
||||||
responsedict = GeneralUtils.textToDict(response, header;
|
# 2. Build single dynamic query to fetch non-null samples for all columns
|
||||||
dictKey=dictkey, symbolkey=true)
|
sample_selects = String[]
|
||||||
|
for row in eachrow(meta_res)
|
||||||
|
c_name = row.column_name
|
||||||
|
push!(sample_selects, """
|
||||||
|
(SELECT json_agg(s."$c_name")
|
||||||
|
FROM (
|
||||||
|
SELECT "$c_name"
|
||||||
|
FROM "$schema_name"."$table_name"
|
||||||
|
WHERE "$c_name" IS NOT NULL
|
||||||
|
LIMIT $sample_count
|
||||||
|
) s
|
||||||
|
) AS "$c_name"
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
|
||||||
for i ∈ [:paraphrase]
|
sample_sql = "SELECT " * join(sample_selects, ",\n ") * ";"
|
||||||
if length(JSON.json(responsedict[i])) == 0
|
sample_df = DataFrame(execute(conn, sample_sql))
|
||||||
error("$i is empty ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
|
# 3. Build DDL definitions with inline sample comments
|
||||||
|
ddl_lines = String[]
|
||||||
|
constraints = String[]
|
||||||
|
|
||||||
|
for row in eachrow(meta_res)
|
||||||
|
col_name = row.column_name
|
||||||
|
data_type = row.data_type
|
||||||
|
default_val = ismissing(row.default_value) ? "" : " DEFAULT " * row.default_value
|
||||||
|
|
||||||
|
col_def = " \"$col_name\" $data_type$default_val"
|
||||||
|
|
||||||
|
# Fetch sample data for this column from the single-row sample DataFrame
|
||||||
|
samples_comment = ""
|
||||||
|
if nrow(sample_df) > 0
|
||||||
|
raw_samples = sample_df[1, Symbol(col_name)]
|
||||||
|
samples_str = ismissing(raw_samples) || isnothing(raw_samples) ? "[]" : string(raw_samples)
|
||||||
|
samples_comment = " -- Samples: $samples_str"
|
||||||
|
end
|
||||||
|
|
||||||
|
push!(ddl_lines, col_def * samples_comment)
|
||||||
|
|
||||||
|
# Handle table-level constraints
|
||||||
|
con_def = ismissing(row.constraint_definition) ? "" : row.constraint_definition
|
||||||
|
if !isempty(con_def) && !(con_def in constraints)
|
||||||
|
push!(constraints, " " * con_def)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# check if there are more than 1 key per categories
|
all_definitions = vcat(ddl_lines, constraints)
|
||||||
for i ∈ [:paraphrase]
|
body = join(all_definitions, ",\n")
|
||||||
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())")
|
return "CREATE TABLE \"$schema_name\".\"$table_name\" (\n$body\n);"
|
||||||
pprintln(Dict(responsedict))
|
|
||||||
|
|
||||||
result = responsedict["paraphrase"]
|
|
||||||
|
|
||||||
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("\nAttempt $attempt. Error occurred: $errorMsg\n$st ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
error("paraphrase() failed to generate a response")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
""" Attemp to correct LLM response's incorrect JSON response.
|
function classify_column(pg_conn_str::String, table_name::String, column_name::String;
|
||||||
|
sample_size::Integer=1000)
|
||||||
# Arguments
|
conn = LibPQ.Connection(pg_conn_str)
|
||||||
- `a::T1`
|
return classify_column(conn, table_name, column_name; sample_size=sample_size)
|
||||||
one of Yiem's agent
|
|
||||||
- `input::T2`
|
|
||||||
text to be send to virtual wine customer
|
|
||||||
|
|
||||||
# Return
|
|
||||||
- `correctjson::String`
|
|
||||||
corrected json string
|
|
||||||
|
|
||||||
# Example
|
|
||||||
```jldoctest
|
|
||||||
julia>
|
|
||||||
```
|
|
||||||
|
|
||||||
# Signature
|
|
||||||
"""
|
|
||||||
function jsoncorrection(config::T1, input::T2, correctJsonExample::T3;
|
|
||||||
maxattempt::Integer=3
|
|
||||||
) where {T1<:AbstractDict, T2<:AbstractString, T3<:AbstractString}
|
|
||||||
|
|
||||||
incorrectjson = deepcopy(input)
|
|
||||||
correctjson = nothing
|
|
||||||
|
|
||||||
for attempt in 1:maxattempt
|
|
||||||
try
|
|
||||||
d = copy(JSON.parsefile(incorrectjson))
|
|
||||||
correctjson = incorrectjson
|
|
||||||
return correctjson
|
|
||||||
catch e
|
|
||||||
@warn "Attempting to correct JSON string. Attempt $attempt"
|
|
||||||
e = """$e"""
|
|
||||||
if occursin("EOF", e)
|
|
||||||
e = split(e, "EOF")[1] * "EOF"
|
|
||||||
end
|
|
||||||
incorrectjson = deepcopy(input)
|
|
||||||
_prompt =
|
|
||||||
"""
|
|
||||||
Your goal are:
|
|
||||||
1) Use the expected JSON format as a guideline to check why the given JSON string failed to load and provide a corrected version that can be loaded by Python's json.load function.
|
|
||||||
2) Provide Corrected JSON string only. Do not provide any other info.
|
|
||||||
|
|
||||||
$correctJsonExample
|
|
||||||
|
|
||||||
Let's begin!
|
|
||||||
Given JSON string: $incorrectjson
|
|
||||||
The given JSON string failed to load previously because: $e
|
|
||||||
Corrected JSON string:
|
|
||||||
"""
|
|
||||||
|
|
||||||
# apply LLM specific instruct format
|
|
||||||
externalService = config["externalservice"]["text2textinstruct"]
|
|
||||||
llminfo = externalService["llminfo"]
|
|
||||||
prompt =
|
|
||||||
if llminfo["name"] == "llama3instruct"
|
|
||||||
formatLLMtext_llama3instruct("system", _prompt)
|
|
||||||
else
|
|
||||||
error("llm model name is not defied yet $(@__LINE__)")
|
|
||||||
end
|
|
||||||
|
|
||||||
# send formatted input to user using GeneralUtils.sendReceiveMqttMsg
|
|
||||||
msgMeta = GeneralUtils.generate_msgMeta(
|
|
||||||
externalService["mqtttopic"],
|
|
||||||
senderName= "jsoncorrection",
|
|
||||||
senderId= string(uuid4()),
|
|
||||||
receiverName= "text2textinstruct",
|
|
||||||
mqttBroker= config["mqttServerInfo"]["broker"],
|
|
||||||
mqttBrokerPort= config["mqttServerInfo"]["port"],
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoingMsg = Dict(
|
|
||||||
"msgMeta"=> msgMeta,
|
|
||||||
"payload"=> Dict(
|
|
||||||
"text"=> prompt,
|
|
||||||
"kwargs"=> Dict(
|
|
||||||
"max_tokens"=> 512,
|
|
||||||
"stop"=> ["<|eot_id|>"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
result = GeneralUtils.sendReceiveMqttMsg(outgoingMsg; timeout=120)
|
|
||||||
incorrectjson = result[:response][:text]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function classify_column(conn::LibPQ.Connection, table_name::String, column_name::String; sample_size::Int=1000)
|
||||||
|
# 1. Fetch BOTH data_type and udt_name (User Defined Type name)
|
||||||
|
meta_query = """
|
||||||
|
SELECT data_type, udt_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = lower('$(table_name)')
|
||||||
|
AND column_name = lower('$(column_name)');
|
||||||
|
"""
|
||||||
|
|
||||||
|
pg_type = "unknown"
|
||||||
|
udt_name = "unknown"
|
||||||
|
|
||||||
|
try
|
||||||
|
df = DataFrame(LibPQ.execute(conn, meta_query))
|
||||||
|
if !isempty(df)
|
||||||
|
pg_type = df[1, :data_type]
|
||||||
|
udt_name = df[1, :udt_name]
|
||||||
|
end
|
||||||
|
catch e
|
||||||
|
@error "Failed to fetch metadata for $table_name.$column_name" exception=e
|
||||||
|
return "error"
|
||||||
|
end
|
||||||
|
|
||||||
|
# 2. FAST-TRACK: Check for pgvector FIRST
|
||||||
|
# pgvector registers as "USER-DEFINED" in data_type, but "vector" in udt_name
|
||||||
|
if udt_name == "vector"
|
||||||
|
return "semantic_search"
|
||||||
|
end
|
||||||
|
|
||||||
|
# 3. FAST-TRACK: Hard rules for standard non-text Postgres types
|
||||||
|
if pg_type in ["integer", "bigint", "smallint", "numeric", "real",
|
||||||
|
"double precision", "boolean", "date",
|
||||||
|
"timestamp without time zone", "timestamp with time zone", "uuid"]
|
||||||
|
return "exact_or_range"
|
||||||
|
end
|
||||||
|
|
||||||
|
# 4. SAMPLE: Get text statistics for remaining text columns
|
||||||
|
stats_query = """
|
||||||
|
SELECT
|
||||||
|
COUNT(*)::int AS total_count,
|
||||||
|
COUNT(DISTINCT $(column_name)::text)::int AS unique_count,
|
||||||
|
COALESCE(AVG(LENGTH($(column_name)::text)), 0)::float AS avg_len,
|
||||||
|
COALESCE(STDDEV(LENGTH($(column_name)::text)), 0)::float AS std_len
|
||||||
|
FROM (
|
||||||
|
SELECT $(column_name)
|
||||||
|
FROM $(table_name)
|
||||||
|
WHERE $(column_name) IS NOT NULL
|
||||||
|
LIMIT $sample_size
|
||||||
|
) AS sampled_data;
|
||||||
|
"""
|
||||||
|
|
||||||
|
try
|
||||||
|
df = DataFrame(LibPQ.execute(conn, stats_query))
|
||||||
|
if isempty(df) || df[1, :total_count] == 0
|
||||||
|
return "unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
total = df[1, :total_count]
|
||||||
|
unique = df[1, :unique_count]
|
||||||
|
avg_len = df[1, :avg_len]
|
||||||
|
std_len = df[1, :std_len]
|
||||||
|
ratio = unique / total
|
||||||
|
|
||||||
|
# 5. HEURISTICS: Route the column_name to the correct text bucket
|
||||||
|
return classify_text_column(unique, ratio, avg_len, std_len)
|
||||||
|
|
||||||
|
catch e
|
||||||
|
@warn "Failed to sample column_name $table_name.$column_name" exception=e
|
||||||
|
return "unknown"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# The Decision Tree for Text Columns (Unchanged, but kept for completeness)
|
||||||
|
function classify_text_column(unique_count::Integer, ratio::Float64, avg_len::Float64, std_len::Float64)
|
||||||
|
if avg_len > 60 && std_len > 25
|
||||||
|
return "full_text_search"
|
||||||
|
end
|
||||||
|
if ratio > 0.90 && avg_len < 40
|
||||||
|
return "exact_or_regex"
|
||||||
|
end
|
||||||
|
if unique_count <= 100
|
||||||
|
return "fuzzy_correction"
|
||||||
|
end
|
||||||
|
if ratio > 0.10 && avg_len < 35
|
||||||
|
return "fuzzy_correction"
|
||||||
|
end
|
||||||
|
if avg_len < 60
|
||||||
|
return "fuzzy_correction"
|
||||||
|
end
|
||||||
|
return "full_text_search"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+7
-86
@@ -23,75 +23,6 @@ end
|
|||||||
|
|
||||||
abstract type agent end
|
abstract type agent end
|
||||||
|
|
||||||
mutable struct companion <: agent
|
|
||||||
name::String # agent name
|
|
||||||
id::String # agent id
|
|
||||||
systemmsg::String # system message
|
|
||||||
tools::Dict # tools
|
|
||||||
maxHistoryMsg::Integer # e.g. 21th and earlier messages will get summarized
|
|
||||||
chathistory::Vector{Dict{String, Any}}
|
|
||||||
memory::Dict{String, Any}
|
|
||||||
context::NamedTuple # NamedTuple of functions
|
|
||||||
llmFormatName::String
|
|
||||||
end
|
|
||||||
|
|
||||||
function companion(
|
|
||||||
context::agentcontext # NamedTuple of functions
|
|
||||||
;
|
|
||||||
name::String= "Assistant",
|
|
||||||
id::String= GeneralUtils.uuid4snakecase(),
|
|
||||||
maxHistoryMsg::Integer= 20,
|
|
||||||
chathistory::Vector{Dict{String, String}} = Vector{Dict{String, String}}(),
|
|
||||||
llmFormatName::String= "granite3",
|
|
||||||
systemmsg::String=
|
|
||||||
"""
|
|
||||||
Your name: $name
|
|
||||||
Your sex: Female
|
|
||||||
Your role: You are a helpful assistant.
|
|
||||||
You should follow the following guidelines:
|
|
||||||
- Focus on the latest conversation.
|
|
||||||
- Your like to be short and concise.
|
|
||||||
|
|
||||||
Let's begin!
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
tools = Dict( # update input format
|
|
||||||
"CHAT_BOX"=> Dict(
|
|
||||||
"description" => "- CHAT_BOX which you can use to talk with the user. The input is your intentions for the dialogue. Be specific.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
""" Memory
|
|
||||||
Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
|
|
||||||
NO "system" message in chathistory because I want to add it at the inference time
|
|
||||||
chathistory= [
|
|
||||||
Dict("name"=>"user", "text"=> "Wassup!", "timestamp"=> Dates.now()),
|
|
||||||
Dict("name"=>"assistant", "text"=> "Hi I'm your assistant.", "timestamp"=> Dates.now()),
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
memory = Dict{String, Any}(
|
|
||||||
"events"=> Vector{Dict{String, Any}}(),
|
|
||||||
"state"=> Dict{String, Any}(), # state of the agent
|
|
||||||
"recap"=> OrderedDict{String, Any}(), # recap summary of the conversation
|
|
||||||
)
|
|
||||||
|
|
||||||
newAgent = companion(
|
|
||||||
name,
|
|
||||||
id,
|
|
||||||
systemmsg,
|
|
||||||
tools,
|
|
||||||
maxHistoryMsg,
|
|
||||||
chathistory,
|
|
||||||
memory,
|
|
||||||
context,
|
|
||||||
llmFormatName
|
|
||||||
)
|
|
||||||
|
|
||||||
return newAgent
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
mutable struct sommelier <: agent
|
mutable struct sommelier <: agent
|
||||||
name::String # agent name
|
name::String # agent name
|
||||||
id::String # agent id
|
id::String # agent id
|
||||||
@@ -210,11 +141,7 @@ function sommelier(
|
|||||||
memory = Dict{String, Any}(
|
memory = Dict{String, Any}(
|
||||||
"shortmem"=> OrderedDict{String, Any}(),
|
"shortmem"=> OrderedDict{String, Any}(),
|
||||||
"scratchpad"=> "",
|
"scratchpad"=> "",
|
||||||
"events"=> Vector{Dict{String, Any}}(),
|
|
||||||
"state"=> Dict{String, Any}(
|
|
||||||
),
|
|
||||||
"recap"=> OrderedDict{String, Any}(),
|
"recap"=> OrderedDict{String, Any}(),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
newAgent = sommelier(
|
newAgent = sommelier(
|
||||||
@@ -237,7 +164,6 @@ function sommelier(
|
|||||||
- You can only recommend wines that are currently in our inventory
|
- You can only recommend wines that are currently in our inventory
|
||||||
- Before searching the database for wine, ensure you have at least the following information: 1) budget, 2) wine type, and 3) occasion. Additional details are always helpful. If the user is unsure, provide relevant information and gather insights to make reasonable inferences.
|
- Before searching the database for wine, ensure you have at least the following information: 1) budget, 2) wine type, and 3) occasion. Additional details are always helpful. If the user is unsure, provide relevant information and gather insights to make reasonable inferences.
|
||||||
- Ask the user one question at a time.
|
- Ask the user one question at a time.
|
||||||
- Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar as these terms cannot be used to search the database.
|
|
||||||
- Once the user has selected their wine, if you haven't already, ask the user whether they need any further assistance. Do not offer any additional services.
|
- Once the user has selected their wine, if you haven't already, ask the user whether they need any further assistance. Do not offer any additional services.
|
||||||
- Only end the conversation when the user explicitly intends to do so. When ending, ensure a polite farewell and an invitation to return in the future.
|
- Only end the conversation when the user explicitly intends to do so. When ending, ensure a polite farewell and an invitation to return in the future.
|
||||||
- Spicy foods should be paired only with light red wines.
|
- Spicy foods should be paired only with light red wines.
|
||||||
@@ -273,25 +199,20 @@ function sommelier(
|
|||||||
- Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
|
- Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
|
||||||
- Answering questions or offering additional services beyond those related to your store's wine recommendations such as discounts, quantity, rewards programs, promotions, delivery options, shipping, boxes, gift wrapping, packaging, personalized messages or something similar. These are the job of our sales team at the store.
|
- Answering questions or offering additional services beyond those related to your store's wine recommendations such as discounts, quantity, rewards programs, promotions, delivery options, shipping, boxes, gift wrapping, packaging, personalized messages or something similar. These are the job of our sales team at the store.
|
||||||
|
|
||||||
# you should then respond to the user with interleaving plan, action_name, action_input
|
# you should then respond to the user with interleaving think, action_name, action_input in JSON format
|
||||||
1) "plan", Based on the current situation, state a complete action plan to complete the task and rationale. Be specific.
|
1) "think", Your step-by-step reasoning process. Explain why you are choosing this action.
|
||||||
2) "action_name", (Typically corresponds to the execution of the first step in your plan) Can be one of the available_actions name
|
2) "action_name", Can be one of the available actions. Typically corresponds to the execution of the first step in your thought
|
||||||
3) "action_input", The input to the action you are about to perform according to your plan.
|
3) "action_input", The input to the action you are about to perform.
|
||||||
After the action is executed you gets "action_result". It is the output from the action you selected.
|
After the action is executed you gets "action_result". It is the output from the action you selected.
|
||||||
|
|
||||||
# you should only respond in JSON format as described below (not Markdown format)
|
|
||||||
"plan": "...",
|
|
||||||
"action_name": "...",
|
|
||||||
"action_input": "..."
|
|
||||||
|
|
||||||
# available actions
|
# available actions
|
||||||
"CHAT_BOX", which you can use to talk with the user. The input is dialogue you want to chat with the user according to your plan.
|
"CHAT_BOX", which you can use to talk with the user. The input is dialogue you want to say with the user.
|
||||||
"SEARCH_WINE_DATABASE", allows you to search information about wines you want in your inventory's database. The input is strictly supported search term including: retailer_name, wine price, winery, name, vintage, region, country, type of wine, grape varietal, tasting notes, occasion, food pairing, intensity, tannin, sweetness, and acidity.
|
"SEARCH_WINE_DATABASE", allows you to search information about wines you want in your inventory's database. The input is strictly supported search term including: retailer_name, wine price, winery, name, vintage, region, country, type of wine, grape varietal, tasting notes, occasion, food pairing, intensity, tannin, sweetness, and acidity.
|
||||||
Example query 1: "Dry, full-bodied red wine from Burgundy, France. Grape varietal could be Merlot or Syrah. price 100 to 1000 USD."
|
Example query 1: "Dry, full-bodied red wine from Burgundy, France. Grape varietal could be Merlot or Syrah. price 100 to 1000 USD."
|
||||||
Example query 2: "Red or white wine, medium tannin, price under 700 USD"
|
Example query 2: "Red or white wine, medium tannin, price under 700 USD"
|
||||||
Example query 3: "white wine from Tuscany, Italy or Bordeaux, France
|
Example query 3: "white wine from Tuscany, Italy or Bordeaux, France
|
||||||
"WINE_PRESENTATION_GUIDELINE", which you can use to check the store guidelines about how to present wines you have found to the user. The input is "nothing" keyword. The output is the guidelines that you can follow.
|
"WINE_PRESENTATION_GUIDELINE", store guidelines about how to present wines to the user appropriately. The input is "null" keyword.
|
||||||
"END_CONVER_GUIDELINE", which you can use to check the store guidelines about how to end the conversation with the user. The input is "nothing" keyword. The output is the guidelines that you can follow.
|
"END_CONVER_GUIDELINE", store guidelines about how to end the conversation with the user appropriately. The input is "null" keyword.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
system_msg = Dict(
|
system_msg = Dict(
|
||||||
|
|||||||
Reference in New Issue
Block a user