Compare commits

..

1 Commits

Author SHA1 Message Date
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
6 changed files with 182 additions and 181 deletions
+7 -11
View File
@@ -2,7 +2,7 @@
julia_version = "1.12.6" julia_version = "1.12.6"
manifest_format = "2.0" manifest_format = "2.0"
project_hash = "a5128932115e2d3aa9658af105ac712db061d523" project_hash = "db8baf2dd943e4138b5952183a64465f457da356"
[[deps.Accessors]] [[deps.Accessors]]
deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"] deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
@@ -290,11 +290,9 @@ 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" path = "../GeneralUtils"
repo-rev = "main"
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.4.10"
[[deps.Graphs]] [[deps.Graphs]]
deps = ["ArnoldiMethod", "DataStructures", "Inflate", "LinearAlgebra", "Random", "SimpleTraits", "SparseArrays", "Statistics"] deps = ["ArnoldiMethod", "DataStructures", "Inflate", "LinearAlgebra", "Random", "SimpleTraits", "SparseArrays", "Statistics"]
@@ -793,11 +791,9 @@ version = "0.7.0"
[[deps.SQLLLM]] [[deps.SQLLLM]]
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "PrettyPrinting", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"] deps = ["CSV", "DataFrames", "DataStructures", "Dates", "FileIO", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "PrettyPrinting", "Random", "Revise", "StatsBase", "Tables", "URIs", "UUIDs"]
git-tree-sha1 = "bae2fd2e2b087753fbb3415896be41df1ae0eb90" path = "../SQLLLM"
repo-rev = "main"
repo-url = "https://git.yiem.cc/ton/SQLLLM"
uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3" uuid = "2ebc79c7-cc10-4a3a-9665-d2e1d61e63d3"
version = "0.2.8" version = "0.2.7"
[[deps.SQLStrings]] [[deps.SQLStrings]]
git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c" git-tree-sha1 = "55de0530689832b1d3d43491ee6b67bd54d3323c"
@@ -1047,10 +1043,10 @@ uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60"
version = "1.6.1" version = "1.6.1"
[[deps.YiemAgent]] [[deps.YiemAgent]]
deps = ["CSV", "DataFrames", "DataStructures", "Dates", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serialization", "URIs", "UUIDs"] deps = ["CSV", "DataFrames", "DataStructures", "Dates", "GeneralUtils", "HTTP", "JSON", "LLMMCTS", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "SQLLLM", "Serialization", "URIs", "UUIDs"]
path = "." path = "."
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2" uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
version = "0.6.4" version = "0.5.0"
[[deps.Zlib_jll]] [[deps.Zlib_jll]]
deps = ["Libdl"] deps = ["Libdl"]
+1 -3
View File
@@ -1,6 +1,6 @@
name = "YiemAgent" name = "YiemAgent"
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2" uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
version = "0.6.4" version = "0.5.0"
authors = ["narawat lamaiin <narawat@outlook.com>"] authors = ["narawat lamaiin <narawat@outlook.com>"]
[deps] [deps]
@@ -25,9 +25,7 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[compat] [compat]
CSV = "0.10.15" CSV = "0.10.15"
DataFrames = "1.7.0" DataFrames = "1.7.0"
GeneralUtils = "0.5.1"
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"
+13 -28
View File
@@ -1,28 +1,13 @@
"""
CREATE TABLE "public"."wine" (
"wine_id" uuid DEFAULT gen_random_uuid() NOT NULL,
"seo_name" character varying(128) NOT NULL, d = Dict(
"wine_name" character varying(128) NOT NULL, "hello"=> 555,
"winery" character varying(128) NOT NULL, "world"=> Dict(
"vintage" integer NOT NULL, "name"=> "ton"
"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, x = 55
"serving_temperature" character varying(128) NOT NULL,
"intensity" integer NULL, @info "YiemAgent think() 1 " d x @__LINE__
"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)
);
"""
+28 -3
View File
@@ -154,6 +154,19 @@ function decisionMaker(a::T; recentevents::Integer=20, maxattempt=10
println("\nERROR YiemAgent decisionMaker() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())") println("\nERROR YiemAgent decisionMaker() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue continue
end end
# fall back to normal text because LLM default to natural chat when it didn't use action_call
else
try
responsedict = OrderedDict(
"plan"=> "I will talk to the user",
"action_name"=> "CHAT_BOX",
"action_input"=> response[2:end-1] # remove { } at the front and back that added by clean_json_response
)
catch e
println("\nERROR YiemAgent decisionMaker(). $e --(not qualify response)-> $response", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
continue
end
end end
# check whether all answer's key points are in responsedict # check whether all answer's key points are in responsedict
@@ -387,7 +400,6 @@ function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Obj
"content" => [Dict("type" => "text", "text" => thoughtdict["action_input"]),] "content" => [Dict("type" => "text", "text" => thoughtdict["action_input"]),]
) )
addNewMessage(a, "assistant", assistant_response; maximumMsg=maximumMsg) addNewMessage(a, "assistant", assistant_response; maximumMsg=maximumMsg)
@info "YiemAgent conversation() 2-5 think count $loopcount " @__LINE__
return thoughtdict["action_input"] return thoughtdict["action_input"]
# elseif thoughtdict["action_name"] ∈ ["CHAT_BOX"] # elseif thoughtdict["action_name"] ∈ ["CHAT_BOX"]
# @info "YiemAgent conversation() 2-4 think count $loopcount " @__LINE__ # @info "YiemAgent conversation() 2-4 think count $loopcount " @__LINE__
@@ -407,7 +419,7 @@ function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Obj
# ) # )
# addNewMessage(a, "assistant", assistant_response; maximumMsg=maximumMsg) # addNewMessage(a, "assistant", assistant_response; maximumMsg=maximumMsg)
# return thoughtdict["action_input"] #XXX change output from string to dict return thoughtdict["action_input"] #XXX change output from string to dict
else else
action_name = thoughtdict["action_name"] action_name = thoughtdict["action_name"]
action_input = thoughtdict["action_input"] action_input = thoughtdict["action_input"]
@@ -461,7 +473,7 @@ function think(a::T)::NamedTuple{(:thoughtdict, :result_raw), Tuple{OrderedDict,
elseif thoughtdict["action_name"] == "SEARCH_WINE_DATABASE" elseif thoughtdict["action_name"] == "SEARCH_WINE_DATABASE"
@info "YiemAgent think() 5 " @__LINE__ @info "YiemAgent think() 5 " @__LINE__
thoughtdict, result_raw = search_wine_database!(a, thoughtdict; useSQLLLM=false) thoughtdict, result_raw = search_wine_database!(a, thoughtdict; useSQLLLM=true)
#WORKING result_raw will be a df. i need to get images so i can send to frontend #WORKING result_raw will be a df. i need to get images so i can send to frontend
else else
@info "YiemAgent think() 6 " @__LINE__ @info "YiemAgent think() 6 " @__LINE__
@@ -673,6 +685,19 @@ function generatechat!(a::T; maxattempt::Integer=10
println("\nERROR YiemAgent generatechat() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())") println("\nERROR YiemAgent generatechat() failed to parse response: $response ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
continue continue
end end
# fall back to normal text because LLM default to natural chat when it didn't use action_call
else
try
responsedict = OrderedDict(
"plan"=> "I will talk to the user",
"action_name"=> "CHAT_BOX",
"action_input"=> response[2:end-1] # remove { } at the front and back that added by clean_json_response
)
catch e
println("\nERROR YiemAgent decisionMaker(). $e --(not qualify response)-> $response", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
continue
end
end end
# check whether all answer's key points are in responsedict # check whether all answer's key points are in responsedict
+130 -131
View File
@@ -396,143 +396,133 @@ function generatesql(a::T, searchterm::String,
Do not wrap the SQL as it will be executed against the database directly and SQL must be ended with ';'. Do not wrap the SQL as it will be executed against the database directly and SQL must be ended with ';'.
""" """
# table_schema = table_schema =
# """ """
# create table customer ( create table customer (
# customer_id uuid primary key default gen_random_uuid (), customer_id uuid primary key default gen_random_uuid (),
# customer_firstname varchar(128), customer_firstname varchar(128),
# customer_lastname varchar(128), customer_lastname varchar(128),
# customer_displayname varchar(128) not null, customer_displayname varchar(128) not null,
# customer_username varchar(128), customer_username varchar(128),
# customer_password varchar(128), customer_password varchar(128),
# customer_gender varchar(128), customer_gender varchar(128),
# country varchar(128), country varchar(128),
# telephone varchar(128), telephone varchar(128),
# email varchar(128) not null, email varchar(128) not null,
# customer_birthdate varchar(128), customer_birthdate varchar(128),
# note text, note text,
# other_attributes jsonb, other_attributes jsonb,
# created_time timestamptz default current_timestamp, created_time timestamptz default current_timestamp,
# updated_time timestamptz default current_timestamp, updated_time timestamptz default current_timestamp,
# description text description text
# ); );
# create table retailer ( create table retailer (
# retailer_id uuid primary key default gen_random_uuid (), retailer_id uuid primary key default gen_random_uuid (),
# retailer_name varchar(128) not null, retailer_name varchar(128) not null,
# retailer_username varchar(128) not null, retailer_username varchar(128) not null,
# retailer_password varchar(128) not null, retailer_password varchar(128) not null,
# retailer_address text not null, retailer_address text not null,
# country varchar(128) not null, country varchar(128) not null,
# contact_person varchar(128) not null, contact_person varchar(128) not null,
# telephone varchar(128) not null, telephone varchar(128) not null,
# email varchar(128) not null, email varchar(128) not null,
# note text, note text,
# other_attributes jsonb, other_attributes jsonb,
# created_time timestamptz default current_timestamp, created_time timestamptz default current_timestamp,
# updated_time timestamptz default current_timestamp, updated_time timestamptz default current_timestamp,
# description text description text
# ); );
# create table food ( create table food (
# food_id uuid primary key default gen_random_uuid (), food_id uuid primary key default gen_random_uuid (),
# food_name varchar(128) not null, food_name varchar(128) not null,
# country varchar(128), country varchar(128),
# spiciness integer, spiciness integer,
# sweetness integer, sweetness integer,
# sourness integer, sourness integer,
# savoriness integer, savoriness integer,
# bitterness integer, bitterness integer,
# serving_temperature integer, serving_temperature integer,
# image_url jsonb, image_url jsonb,
# note text, note text,
# other_attributes jsonb, other_attributes jsonb,
# created_time timestamptz default current_timestamp, created_time timestamptz default current_timestamp,
# updated_time timestamptz default current_timestamp, updated_time timestamptz default current_timestamp,
# description text description text
# ); );
# create table wine ( create table wine (
# wine_id uuid primary key default gen_random_uuid (), wine_id uuid primary key default gen_random_uuid (),
# seo_name varchar(128) not null, seo_name varchar(128) not null,
# wine_name varchar(128) not null, wine_name varchar(128) not null,
# winery varchar(128) not null, winery varchar(128) not null,
# vintage integer not null, vintage integer not null,
# region varchar(128) not null, region varchar(128) not null,
# country varchar(128) not null, country varchar(128) not null,
# wine_type varchar(128) not null, wine_type varchar(128) not null,
# grape varchar(128) not null, grape varchar(128) not null,
# serving_temperature varchar(128) not null, serving_temperature varchar(128) not null,
# intensity integer, intensity integer,
# sweetness integer, sweetness integer,
# tannin integer, tannin integer,
# acidity integer, acidity integer,
# fizziness integer, fizziness integer,
# tasting_notes text, tasting_notes text,
# image_url jsonb, image_url jsonb,
# manufacturer_sku text, manufacturer_sku text,
# note text, note text,
# other_attributes jsonb, other_attributes jsonb,
# created_time timestamptz default current_timestamp, created_time timestamptz default current_timestamp,
# updated_time timestamptz default current_timestamp, updated_time timestamptz default current_timestamp,
# description text description text
# ); );
# create table wine_food ( create table wine_food (
# wine_id uuid references wine(wine_id), wine_id uuid references wine(wine_id),
# food_id uuid references food(food_id), food_id uuid references food(food_id),
# constraint wine_food_id primary key (wine_id, food_id), constraint wine_food_id primary key (wine_id, food_id),
# created_time timestamptz default current_timestamp, created_time timestamptz default current_timestamp,
# updated_time timestamptz default current_timestamp updated_time timestamptz default current_timestamp
# ); );
# CREATE TABLE retailer_wine ( CREATE TABLE retailer_wine (
# retailer_id uuid references retailer(retailer_id), retailer_id uuid references retailer(retailer_id),
# wine_id uuid references wine(wine_id), wine_id uuid references wine(wine_id),
# constraint retailer_wine_id primary key (retailer_id, wine_id), constraint retailer_wine_id primary key (retailer_id, wine_id),
# price NUMERIC(10, 2), price NUMERIC(10, 2),
# currency varchar(3) not null, currency varchar(3) not null,
# created_time timestamptz default current_timestamp, created_time timestamptz default current_timestamp,
# updated_time timestamptz default current_timestamp updated_time timestamptz default current_timestamp
# ); );
# CREATE TABLE retailer_food ( CREATE TABLE retailer_food (
# retailer_id uuid references retailer(retailer_id), retailer_id uuid references retailer(retailer_id),
# food_id uuid references food(food_id), food_id uuid references food(food_id),
# constraint retailer_food_id primary key (retailer_id, food_id), constraint retailer_food_id primary key (retailer_id, food_id),
# price NUMERIC(10, 2), price NUMERIC(10, 2),
# currency varchar(3) not null, currency varchar(3) not null,
# created_time timestamptz default current_timestamp, created_time timestamptz default current_timestamp,
# updated_time timestamptz default current_timestamp updated_time timestamptz default current_timestamp
# ); );
# """ """
requiredKeys = ["plan", "action_name", "action_input"] requiredKeys = ["plan", "action_name", "action_input"]
errornote = "" errornote = ""
# provide similar sql only for the first attempt # provide similar sql only for the first attempt
# sql, distance = a.context.similarSQLVectorDB(searchterm) sql, distance = a.context.similarSQLVectorDB(searchterm)
# similarSQL_ = sql !== nothing ? sql : "None" similarSQL_ = sql !== nothing ? sql : "None"
# # if sql is really close, just use it # if sql is really close, just use it
# if similarSQL_ != "None" && distance <= 0.1 if similarSQL_ != "None" && distance <= 0.1
# return similarSQL_ return similarSQL_
# end
#CHANGE use find_related_tables_for_user_question and inject only related table schema instead
# of hard code table schema. CPU embedding is too slow. use embedding service on GPU.
related_tables = a.context.find_related_tables_for_user_question(searchterm)
table_schema = ""
for table in related_tables
_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 end
context = context =
@@ -541,6 +531,12 @@ function generatesql(a::T, searchterm::String,
<database_table_schema> <database_table_schema>
$table_schema $table_schema
</database_table_schema> </database_table_schema>
<possible SQL for user's search term>
$similarSQL_
</possible SQL for user's search term>
<error_note>
$errornote
<error_note>
</internal_context_for_assistant> </internal_context_for_assistant>
""" """
input = context * searchterm input = context * searchterm
@@ -755,11 +751,11 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
"wine_name": "Saumur Blanc", "wine_name": "Saumur Blanc",
"winery": "Domaine du Collier", "winery": "Domaine du Collier",
"vintage": "2019", "vintage": "2019",
"region": "N/A", "region": "Saumur",
"country": "France", "country": "France",
"wine_type": "white", "wine_type": "white",
"grape_varietal": "Merlot", "grape_varietal": "Merlot",
"tasting_notes": "N/A", "tasting_notes": "plum",
"wine_price_min": "N/A", "wine_price_min": "N/A",
"wine_price_max": "N/A", "wine_price_max": "N/A",
"occasion": "N/A", "occasion": "N/A",
@@ -827,18 +823,16 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
responsedict[k] = _v responsedict[k] = _v
end end
# println("\n--- extractWineAttributes_1-1()") println("\n--- extractWineAttributes_1-1()")
# @show responsedict @show responsedict
# @info "---\n" @__LINE__ @info "---\n" @__LINE__
# check each attributes against each column in a database table with BM25 #WORKING check each attributes against database with BM25
for (k, v) in responsedict for (k, v) in responsedict
if k ["wine_price_min", "wine_price_max"]
words_catalog = GeneralUtils.harvest_entity_catalog(a.context.pg_conn_str, "wine", k) words_catalog = GeneralUtils.harvest_entity_catalog(a.context.pg_conn_str, "wine", k)
resolved_word = GeneralUtils.resolve_entity(v, words_catalog;threshold=0.9) resolved_word = GeneralUtils.resolve_entity(v, words_catalog;threshold=0.9)
responsedict[k] = resolved_word responsedict[k] = resolved_word
end end
end
result = "" result = ""
for (k, v) in responsedict for (k, v) in responsedict
@@ -849,10 +843,15 @@ function extractWineAttributes_1(a::T1, input::T2; maxattempt=10
end end
result = result[1:end-2] # remove the ending ", " result = result[1:end-2] # remove the ending ", "
# println("\n--- extractWineAttributes_1-2()") println("\n--- extractWineAttributes_1-2()")
# @show responsedict @show responsedict
# @show result @show result
# @info "---\n" @__LINE__ @info "---\n" @__LINE__
return result return result
end end
error("extractWineAttributes_1() failed to get a response") error("extractWineAttributes_1() failed to get a response")
-2
View File
@@ -16,9 +16,7 @@ mutable struct agentcontext
insertSQLVectorDB::Function insertSQLVectorDB::Function
similarSommelierDecision::Function similarSommelierDecision::Function
insertSommelierDecision::Function insertSommelierDecision::Function
find_related_tables_for_user_question::Function
pg_conn_str::String pg_conn_str::String
agentconfig::AbstractDict
end end
abstract type agent end abstract type agent end