Compare commits

...

32 Commits

Author SHA1 Message Date
ton a3ab288cfe update 2026-07-22 10:18:28 +07:00
ton 17b0974d82 Merge pull request 'v0.7.4-predefine_wine_search' (#30) from v0.7.4-predefine_wine_search into v0.7.4
Reviewed-on: #30
2026-07-21 13:17:25 +00:00
ton 8fed0b5e8f update 2026-07-21 20:11:12 +07:00
ton 567d6b79d7 up version 2026-07-21 18:02:02 +07:00
ton 74be3e5717 Merge pull request 'v0.7.3' (#29) from v0.7.3 into main
Reviewed-on: #29
2026-07-20 05:26:19 +00:00
ton 402b6fcadd Merge pull request 'update' (#28) from v0.7.3-fix_item_info into v0.7.3
Reviewed-on: #28
2026-07-20 05:24:33 +00:00
ton ef523aaa48 update 2026-07-20 12:23:03 +07:00
ton ddbb135b6b update 2026-07-17 12:22:56 +07:00
ton afda364484 update 2026-07-17 12:03:36 +07:00
ton af73d955eb Merge pull request 'v0.7.1' (#27) from v0.7.1 into main
Reviewed-on: #27
2026-07-17 03:28:17 +00:00
ton 39cf9a72a1 Merge pull request 'v0.7.1-fix_single_items_info_frontend' (#26) from v0.7.1-fix_single_items_info_frontend into v0.7.1
Reviewed-on: #26
2026-07-17 03:28:06 +00:00
ton 1c829ad854 update 2026-07-17 10:14:13 +07:00
ton da98baddb6 update 2026-07-17 10:06:32 +07:00
ton c5fbaabf42 Merge pull request 'v0.7.0' (#25) from v0.7.0 into main
Reviewed-on: #25
2026-07-17 00:03:01 +00:00
ton 8080905bad Merge pull request 'v0.7.0-output_openai_msg' (#24) from v0.7.0-output_openai_msg into v0.7.0
Reviewed-on: #24
2026-07-17 00:02:47 +00:00
ton b349c3a8b6 update 2026-07-17 07:01:55 +07:00
ton 0148e03d6a update 2026-07-16 23:31:36 +07:00
ton e718cc4a5c update 2026-07-16 23:13:18 +07:00
ton 87bc6a46a1 update 2026-07-16 22:31:40 +07:00
ton 44bb8baf7c Merge pull request 'update' (#23) from v0.6.0-output_text_image into main
Reviewed-on: #23
2026-07-15 11:51:43 +00:00
ton b3c3bb9b75 Merge pull request 'update' (#22) from v0.6.0-output_text_image into main
Reviewed-on: #22
2026-07-15 11:50:09 +00:00
ton 8898226825 Merge pull request 'v0.6.0-output_text_image' (#21) from v0.6.0-output_text_image into main
Reviewed-on: #21
2026-07-15 11:48:20 +00:00
ton 5c7caf0b49 Merge pull request 'update' (#20) from v0.6.0-output_text_image into main
Reviewed-on: #20
2026-07-15 07:01:48 +00:00
ton edeef4ed2a Merge pull request 'v0.6.0-output_text_image' (#19) from v0.6.0-output_text_image into main
Reviewed-on: #19
2026-07-15 06:59:56 +00:00
ton 4610137f04 Merge pull request 'update' (#18) from v0.6.0-output_text_image into main
Reviewed-on: #18
2026-07-15 05:20:58 +00:00
ton aedc53bf86 Merge pull request 'update' (#17) from v0.6.0-output_text_image into main
Reviewed-on: #17
2026-07-15 05:17:56 +00:00
ton 7fa988313d Merge pull request 'update' (#16) from v0.6.0-output_text_image into main
Reviewed-on: #16
2026-07-15 05:15:31 +00:00
ton 0df4159261 Merge pull request 'update' (#15) from v0.6.0-output_text_image into main
Reviewed-on: #15
2026-07-15 05:11:05 +00:00
ton 9167ece0c0 Merge pull request 'v0.6.0' (#14) from v0.6.0 into main
Reviewed-on: #14
2026-07-15 04:57:47 +00:00
ton a6a9395ecc Merge pull request 'update' (#13) from v0.6.0-output_text_image into v0.6.0
Reviewed-on: #13
2026-07-15 04:57:11 +00:00
ton f45a036971 Merge pull request 'v0.6.0' (#12) from v0.6.0 into main
Reviewed-on: #12
2026-07-15 04:38:48 +00:00
ton 24b85be58b Merge pull request 'updatet' (#11) from v0.6.0-output_text_image into v0.6.0
Reviewed-on: #11
2026-07-15 04:38:18 +00:00
7 changed files with 1058 additions and 490 deletions
+55 -13
View File
@@ -2,7 +2,7 @@
julia_version = "1.12.6"
manifest_format = "2.0"
project_hash = "a5128932115e2d3aa9658af105ac712db061d523"
project_hash = "dc7878808bbc4637a12e709dd495979a784824a5"
[[deps.Accessors]]
deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
@@ -97,9 +97,9 @@ uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.8"
[[deps.CommonSolve]]
git-tree-sha1 = "99ee296f88c12485402e37c2fd025f95ae097637"
git-tree-sha1 = "eeaad7cef88554c2fa56b5a3f71cfd5cb708c662"
uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
version = "0.2.9"
version = "0.2.11"
[[deps.Compat]]
deps = ["TOML", "UUIDs"]
@@ -244,11 +244,21 @@ git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec"
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
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 = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "91e0e5c68d02bcdaae76d3c8ceb4361e8f28d2e9"
git-tree-sha1 = "6621fef488e496356c9c9625d0562c12a6070819"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.16.5"
version = "1.20.0"
weakdeps = ["HTTP"]
[deps.FileIO.extensions]
HTTPExt = "HTTP"
[[deps.FilePathsBase]]
deps = ["Compat", "Dates"]
@@ -311,9 +321,9 @@ version = "1.14.0"
[[deps.HTTP]]
deps = ["Base64", "CodecZlib", "Dates", "EnumX", "PrecompileTools", "Random", "Reseau", "SHA", "URIs", "UUIDs", "Zlib_jll"]
git-tree-sha1 = "eda1d37cb55d90a17d0957c75841138c88b361a1"
git-tree-sha1 = "c2c808326222b6dc4bec295a83b55f79aeec98e0"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "2.5.4"
version = "2.5.5"
[[deps.HashArrayMappedTries]]
git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae"
@@ -496,6 +506,12 @@ version = "1.11.3+1"
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
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 = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
@@ -678,15 +694,17 @@ version = "0.4.2"
[[deps.PrettyTables]]
deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"]
git-tree-sha1 = "624de6279ab7d94fc9f672f0068107eb6619732c"
git-tree-sha1 = "ebf455bb866ee6737030e3d3816bb6a0683c4325"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "3.3.2"
version = "3.4.0"
[deps.PrettyTables.extensions]
PrettyTablesExcelExt = "XLSX"
PrettyTablesTypstryExt = "Typstry"
[deps.PrettyTables.weakdeps]
Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e"
XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0"
[[deps.Printf]]
deps = ["Unicode"]
@@ -767,9 +785,9 @@ version = "0.5.1+0"
[[deps.Roots]]
deps = ["Accessors", "CommonSolve", "Printf"]
git-tree-sha1 = "ed45bcc7cf3c8887595b973f2b1efbe91dcc50ec"
git-tree-sha1 = "a7caaf7ba8cf307112ca443784d1b56b4a591455"
uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
version = "3.0.1"
version = "3.0.5"
[deps.Roots.extensions]
RootsChainRulesCoreExt = "ChainRulesCore"
@@ -822,6 +840,12 @@ git-tree-sha1 = "084c47c7c5ce5cfecefa0a98dff69eb3646b5a80"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
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]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
version = "1.11.0"
@@ -926,6 +950,12 @@ git-tree-sha1 = "cd83a04baf746e3b43b83c61b7de77ab0409b80a"
uuid = "88034a9c-02f8-509d-84a9-84ec65e18404"
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 = ["PrecompileTools"]
git-tree-sha1 = "d05693d339e37d6ab134c5ab53c29fce5ee5d7d5"
@@ -1046,11 +1076,23 @@ git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7"
uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60"
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 = ["CSV", "DataFrames", "DataStructures", "Dates", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serialization", "URIs", "UUIDs"]
deps = ["Base64", "CSV", "DataFrames", "DataStructures", "Dates", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serialization", "URIs", "UUIDs"]
path = "."
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
version = "0.6.4"
version = "0.7.2"
[[deps.Zlib_jll]]
deps = ["Libdl"]
+5 -1
View File
@@ -1,9 +1,10 @@
name = "YiemAgent"
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
version = "0.6.4"
version = "0.7.4"
authors = ["narawat lamaiin <narawat@outlook.com>"]
[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
@@ -18,11 +19,13 @@ PrettyPrinting = "54e16d92-306c-5ea0-a30b-337be88ac337"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
SQLLLM = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
Serde = "db9b398d-9517-45f8-9a95-92af99003e0e"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[compat]
Base64 = "1.11.0"
CSV = "0.10.15"
DataFrames = "1.7.0"
GeneralUtils = "0.5.1"
@@ -31,3 +34,4 @@ JSON = "1.6.1"
LLMMCTS = "0.1.5"
NATS = "0.1.0"
SQLLLM = "0.2.8"
Serde = "3.7.2"
+199 -27
View File
@@ -1,28 +1,200 @@
using HTTP, JSON, URIs, Random, PrettyPrinting, UUIDs, Dates, DataFrames, DataStructures
using GeneralUtils, SQLLLM, YiemAgent
config = JSON.parsefile("./appconfig.json")
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"
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
LibPQ.close(db_connection)
return result
end
sql =
"""
CREATE TABLE "public"."wine" (
"wine_id" uuid DEFAULT gen_random_uuid() NOT NULL,
"seo_name" character varying(128) NOT NULL,
"wine_name" character varying(128) NOT NULL,
"winery" character varying(128) NOT NULL,
"vintage" integer NOT NULL,
"region" character varying(128) NOT NULL,
"country" character varying(128) NOT NULL,
"wine_type" character varying(128) NOT NULL,
"grape" character varying(128) NOT NULL,
"serving_temperature" character varying(128) NOT NULL,
"intensity" integer NULL,
"sweetness" integer NULL,
"tannin" integer NULL,
"acidity" integer NULL,
"fizziness" integer NULL,
"tasting_notes" text NULL,
"image_url" jsonb NULL,
"manufacturer_sku" text NULL,
"note" text NULL,
"other_attributes" jsonb NULL,
"created_time" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NULL,
"updated_time" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NULL,
"description" text NULL,
PRIMARY KEY (wine_id)
);
"""
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
function generate_wine_retail_sql(conditions::Dict{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,
NULL AS retailer_name,
rw.retailer_id
FROM wine AS w
JOIN retailer_wine AS rw
ON w.wine_id = rw.wine_id
"""
# 2. Dynamic WHERE Clause Builder
where_clauses = String[]
# Iterate over each table condition provided
for (table_name, table_conditions) in conditions
# Determine table alias
alias = if table_name == "wine"
"w"
elseif table_name == "retailer_wine"
"rw"
else
continue # Skip unsupported tables
end
# Process condition dictionaries
if isa(table_conditions, Dict) && !isempty(table_conditions)
for (column_name, filter_details) in table_conditions
if isa(filter_details, Dict) && haskey(filter_details, "operator")
op = filter_details["operator"]
raw_val = filter_details["value"]
# --- Value Type Handling ---
# Use tryparse instead of try/catch for cleaner, faster parsing
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
# Escape single quotes within string values
escaped_val = replace(string(final_val), "'" => "''")
clause = "$(alias).$(column_name) $(op) '$(escaped_val)'"
end
push!(where_clauses, clause)
end
end
end
end
# 3. Assemble Final Query
where_sql = isempty(where_clauses) ? "" : "WHERE " * join(where_clauses, " AND ")
return string(base_query, where_sql, ";")
end
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

+177 -76
View File
@@ -4,7 +4,7 @@ export addNewMessage, conversation, decisionMaker, reflector, generatechat,
generalconversation, detectWineryName, generateSituationReport
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
DataFrames, CSV
DataFrames, Serde
using GeneralUtils
using ..type, ..util, ..llmfunction
@@ -73,9 +73,9 @@ OrderedDict{String, Any} with 4 entries:
"action_result" => "1) winery: Terrazze dell Etna, wine_name: Rose Brut.
```
"""
function decisionMaker(a::T; recentevents::Integer=20, maxattempt=10
function decisionMaker(a::T; recentevents::Integer=20, maxattempt=3
) where {T<:agent}
@info "YiemAgent decisionMaker() start " @__LINE__
# lessonDict = copy(JSON.parsefile("lesson.json"))
# lesson =
@@ -122,45 +122,45 @@ function decisionMaker(a::T; recentevents::Integer=20, maxattempt=10
errornote = "N/A"
response = nothing # placeholder for show when error msg show up
for attempt in 1:maxattempt
if attempt > 1
println("\nYiemAgent decisionMaker() attempt $attempt/$maxattempt ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
end
msg = Dict(
msg = Dict(
"model" => "gemma-4-E4B-it-UD-Q4_K_XL",
"messages" => a.chathistory,
"temperature" => 0.7
)
for attempt in 1:maxattempt
response = a.context.text2textInstructLLM(a.id, msg)
response = GeneralUtils.clean_json_response(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)
# think, response = GeneralUtils.extractthink(response)
# dollar sign in Julia means string interpolation
while occursin('$', response)
response = replace(response, '$' => "USD")
end
responsedict = nothing
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
try
responsedict = Serde.parse_yaml(response)
catch e
println("\nERROR YiemAgent decisionMaker() Error: $e --(not qualify response)-> $response ", @__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")
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
@@ -172,10 +172,17 @@ function decisionMaker(a::T; recentevents::Integer=20, maxattempt=10
# println("\nYiem decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# pprintln(responsedict)
@info "YiemAgent decisionMaker() end " @__LINE__
return responsedict
end
error("DecisionMaker failed to generate a thought ", response)
# in case decisionMaker failed, force to use generatechat!()
responsedict = OrderedDict(
"plan"=> "N/A",
"action_name"=> "CHAT_BOX",
"action_input"=> "N/A"
)
return responsedict
end
@@ -304,7 +311,7 @@ function evaluator(a::T1, timeline, decisiondict, evaluateecontext
ispass, errormsg = checkAgentResponse_JSON(responsedict, requiredKeys)
if !ispass
errornote = errormsg
println("\nERROR YiemAgent evaluator() $errornote --(not qualify response)> $responsedict", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
println("\nERROR YiemAgent evaluator() $errornote --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
continue
end
@@ -347,7 +354,7 @@ message => Dict(
function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Object{String, Any}},
maximumMsg=50, max_think_loop::Integer=3)
@info "YiemAgent conversation() 1 " @__LINE__
@info "YiemAgent conversation() start " @__LINE__
userinput = GeneralUtils.dictify(userinput; keytype=String, sort_order=["text"])
# find text in usermsg
@@ -363,7 +370,7 @@ function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Obj
clearhistory(a)
return "Okay. What shall we talk about?"
else
@info "YiemAgent conversation() 2 " @__LINE__
# add usermsg to a.chathistory but how do I handle images?
addNewMessage(a, "user", userinput; maximumMsg=maximumMsg)
@@ -372,49 +379,119 @@ function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Obj
while true
loopcount += 1
if loopcount > max_think_loop
@info "YiemAgent conversation() 2-1 think count $loopcount " @__LINE__
r = generatechat!(a)
@info "YiemAgent conversation() 2-2 think count $loopcount " @__LINE__
return r
end
@info "YiemAgent conversation() 2-3 think count $loopcount " @__LINE__
thoughtdict, result_raw = think(a)
if thoughtdict["action_name"] ["CHAT_BOX"]
@info "YiemAgent conversation() 2-4 think count $loopcount " @__LINE__
thoughtdict, result_raw = generatechat!(a)
assistant_response = Dict{String, Any}(
"role" => "assistant",
"content" => [Dict("type" => "text", "text" => thoughtdict["action_input"]),]
)
addNewMessage(a, "assistant", assistant_response; maximumMsg=maximumMsg)
@info "YiemAgent conversation() 2-5 think count $loopcount " @__LINE__
return thoughtdict["action_input"]
# elseif thoughtdict["action_name"] ∈ ["CHAT_BOX"]
# @info "YiemAgent conversation() 2-4 think count $loopcount " @__LINE__
# assistant_response = Dict{String, Any}(
# "role" => "assistant",
# "content" => [
# Dict("type" => "text", "text" => thoughtdict["action_input"]),
# Dict( #WORKING put 1st image here
# "type" => "image_url",
# "image_url" => Dict("url" => image1_data_uri)
# ),
# Dict( #WORKING put 2nd image here
# "type" => "image_url",
# "image_url" => Dict("url" => image2_data_uri)
# ),
# ]
# )
# addNewMessage(a, "assistant", assistant_response; maximumMsg=maximumMsg)
# return thoughtdict["action_input"] #XXX change output from string to dict
else
items_info = []
send_item_ind = [] # index of the item being send to frontend
if haskey(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"])
push!(items_info, deepcopy(item))
push!(send_item_ind, i)
@info "YiemAgent conversation() shortmem: $(length(a.memory["shortmem"]["items_info"])), items_info: $(length(items_info)) " @__LINE__
end
end
# remove sent items
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
response_to_frontend = Dict{String, Any}(
"role" => "assistant",
"content" => [
Dict("type" => "text", "text" => thoughtdict["action_input"]),
Dict(
"type" => "items_info",
"items_info" => items_info
),
]
)
return response_to_frontend
end
thoughtdict, result_raw = think(a)
if thoughtdict["action_name"] ["CHAT_BOX"]
assistant_response = Dict{String, Any}(
"role" => "assistant",
"content" => [Dict("type" => "text", "text" => thoughtdict["action_input"]),]
)
addNewMessage(a, "assistant", assistant_response; maximumMsg=maximumMsg)
items_info = []
send_item_ind = [] # index of the item being send to frontend
if haskey(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"])
push!(items_info, deepcopy(item))
push!(send_item_ind, i)
@info "YiemAgent conversation() shortmem: $(length(a.memory["shortmem"]["items_info"])), items_info: $(length(items_info)) " @__LINE__
end
end
# remove sent items
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
response_to_frontend = Dict{String, Any}(
"role" => "assistant",
"content" => [
Dict("type" => "text", "text" => thoughtdict["action_input"]),
Dict(
"type" => "items_info",
"items_info" => items_info
),
]
)
""" intended message to send to frontend should have the following format.
response_to_frontend = Dict{String, Any}(
"role" => "assistant",
"content" => [
Dict("type" => "text", "text" => "assistant_text_response"),
Dict(
"type" => "items_info",
"items_info" => [
Dict(
"wine_name"=> "wine name 1",
"wine_id"=> "...",
"image"=> base64 encoded image,
...
),
Dict(
"wine_name"=> "wine name 2",
"wine_id"=> "...",
"image"=> base64 encoded image,
...
),
]
),
]
)
"""
return response_to_frontend
else # still in action
action_name = thoughtdict["action_name"]
action_input = thoughtdict["action_input"]
action_call = Dict{String, Any}(
"role" => "action_call",
"content" => [Dict("type" => "text", "text" => "{action_name: $action_name, action_input: $action_input}"),]
)
addNewMessage(a, "action_call", action_call; maximumMsg=maximumMsg)
action_result = thoughtdict["action_result"]
@@ -422,7 +499,9 @@ function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Obj
"role" => "action_result",
"content" => [Dict("type" => "text", "text" => "$action_result"),]
)
addNewMessage(a, "actionresult", actionresult; maximumMsg=maximumMsg)
@info "YiemAgent conversation() end think count $loopcount " @__LINE__
end
end
end
@@ -442,34 +521,51 @@ julia>
"""
function think(a::T)::NamedTuple{(:thoughtdict, :result_raw), Tuple{OrderedDict, Any}} where {T<:agent}
# a.memory[:recap] = generateSituationReport(a, a.context["text"2textInstructLLM]; skiprecent=0)
@info "YiemAgent think() start " @__LINE__
thoughtdict = decisionMaker(a)
@info "YiemAgent think() 1 " @__LINE__
# pprintln(thoughtdict)
@show thoughtdict
println("---\n")
result_raw = nothing
if thoughtdict["action_name"] ["CHAT_BOX"]
@info "YiemAgent think() 2 " @__LINE__
thoughtdict, result_raw = generatechat!(a)
# sometime CHAT_BOX input is too short.
# if thoughtdict["action_input] < 20 character, use generatechat!()
if length(thoughtdict["action_input"]) < 20
thoughtdict, result_raw = generatechat!(a)
else
thoughtdict["action_result"] = "Action result is the next user dialogue."
result_raw = thoughtdict["action_input"]
end
elseif thoughtdict["action_name"] == "END_CONVER_GUIDELINE"
@info "YiemAgent think() 3 " @__LINE__
thoughtdict, result_raw = end_conversation_guideline!(a, thoughtdict)
elseif thoughtdict["action_name"] ["WINE_PRESENTATION_GUIDELINE"]
@info "YiemAgent think() 4 " @__LINE__
thoughtdict, result_raw = wine_presentation_guideline!(a, thoughtdict)
elseif thoughtdict["action_name"] == "SEARCH_WINE_DATABASE"
@info "YiemAgent think() 5 " @__LINE__
thoughtdict, result_raw = search_wine_database!(a, thoughtdict; useSQLLLM=false)
#WORKING result_raw will be a df. i need to get images so i can send to frontend
if result_raw !== nothing && result_raw isa Vector
if haskey(a.memory["shortmem"], "items_info")
append!(a.memory["shortmem"]["items_info"], result_raw)
else
a.memory["shortmem"]["items_info"] = result_raw
end
end
else
@info "YiemAgent think() 6 " @__LINE__
error("condition is not defined ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
end
println("\n")
@info "YiemAgent think() end " @__LINE__
@show thoughtdict
@info "YiemAgent think() 7 " @__LINE__
println("---\n")
return (thoughtdict=thoughtdict, result_raw=result_raw)
end
@@ -546,7 +642,7 @@ end
#PENDING
function generatechat!(a::T; maxattempt::Integer=10
)::NamedTuple{(:thoughtdict, :result_raw), Tuple{OrderedDict, Any}} where {T<:agent}
@info "YiemAgent generatechat!() start " @__LINE__
# lessonDict = copy(JSON.parsefile("lesson.json"))
# lesson =
@@ -629,7 +725,7 @@ function generatechat!(a::T; maxattempt::Integer=10
"action_name": "...",
"action_input": "..."
"""
system_msg = Dict(
"role" => "system",
"content" => [
@@ -660,8 +756,7 @@ function generatechat!(a::T; maxattempt::Integer=10
response = GeneralUtils.clean_json_response(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)
responsedict = nothing
@@ -673,13 +768,17 @@ function generatechat!(a::T; maxattempt::Integer=10
println("\nERROR YiemAgent generatechat() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue
end
else
println("\nERROR YiemAgent generatechat() $errornote --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
continue
end
# 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")
println("\nERROR YiemAgent generatechat() $errornote --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
continue
end
@@ -691,9 +790,11 @@ function generatechat!(a::T; maxattempt::Integer=10
# println("\nYiem decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
# pprintln(responsedict)
responsedict["action_result"] = "Action result is the next user dialogue."
@info "YiemAgent generatechat!() end " @__LINE__
return (thoughtdict=responsedict, result_raw=responsedict["action_input"])
end
@info "YiemAgent generatechat() failed to generate a thought " @__LINE__
error("YiemAgent generatechat() failed to generate a thought ", response)
end
+614 -293
View File
File diff suppressed because it is too large Load Diff
+8 -80
View File
@@ -23,75 +23,6 @@ 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
name::String # agent name
id::String # agent id
@@ -210,11 +141,7 @@ function sommelier(
memory = Dict{String, Any}(
"shortmem"=> OrderedDict{String, Any}(),
"scratchpad"=> "",
"events"=> Vector{Dict{String, Any}}(),
"state"=> Dict{String, Any}(
),
"recap"=> OrderedDict{String, Any}(),
)
newAgent = sommelier(
@@ -252,6 +179,7 @@ function sommelier(
- Your store carries only wine.
- Vintage 0 means non-vintage.
- Start searching the database as broadly as possible within the given information boundary to maximize the chances of finding. Avoid unnecessary parameters unless specified by the user. Refine the search subsequently.
- User usually ask for something similar. This means you should use the search term based on the profile they like.
# situation
You are having conversation with a customer.
@@ -278,17 +206,17 @@ function sommelier(
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.
# you should only respond in JSON format as described below (not Markdown format)
"plan": "...",
"action_name": "...",
"action_input": "..."
# you should only respond in YAML format as described below
plan: "..."
action_name: "..."
action_input: "..."
# 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.
"SEARCH_WINE_DATABASE", allows you to search information about wines you want in your inventory's database. The input is supported search criteria including: retailer_name, wine price, winery, name, vintage, region, country, type, grape varietal, tasting notes, occasion, food pairing, intensity, tannin, sweetness, and acidity.
Example query 1: "Dry, full-bodied red wine from 1) region: Burgundy, country: France or 2) region: Tuscany, country: Italy. Grape varietal: Merlot or Syrah. price 100 to 1000 USD."
"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 2: "Red or white wine, medium tannin, price under 700 USD"
Example query 3: "white wine, region: Tuscany or Bordeaux, country: Italy or 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.
"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.
"""