This commit is contained in:
2026-07-02 18:25:56 +07:00
parent c4e255ec2a
commit 1577d7ae25
2 changed files with 114 additions and 145 deletions
+93 -109
View File
@@ -193,9 +193,9 @@ function decisionMaker(state::T1, text2textInstructLLM::Function, llmFormatName:
end end
end end
println("\nSQLLLM decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") # println("\nSQLLLM decisionMaker() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(responsedict) # pprintln(responsedict)
println("---") # println("---")
return responsedict return responsedict
end end
@@ -770,36 +770,25 @@ function transition(state::T, args::NamedTuple
# getting SQL from vectorDB # getting SQL from vectorDB
thoughtDict = decisionMakerF(state, text2textInstructLLM, llmFormatName; thoughtDict = decisionMakerF(state, text2textInstructLLM, llmFormatName;
querySQLVectorDBF) querySQLVectorDBF)
println("\n--- SQLLLM transition() ", @__FILE__, ":", @__LINE__, " $(Dates.now())") # println("\n--- SQLLLM transition() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(thoughtDict) # pprintln(thoughtDict)
println("---") # println("---")
rawresponse = nothing
# map action and input() to llm function # map action and input() to llm function
response = response = nothing
if thoughtDict["action_name"] == "RUNSQL" if thoughtDict["action_name"] == "RUNSQL"
response = SQLexecution(executeSQL, thoughtDict["action_input"]) response = SQLexecution(executeSQL, thoughtDict["action_input"])
if response[:success] else
thoughtDict["action_result"] = GeneralUtils.dfToString(response[:result]) error("undefined LLM function. Requesting $(thoughtDict["action_name"])")
rawresponse = response[:result] end
(rawresponse=response[:result], result=extracted, errormsg=nothing, success=true)
else newNodeKey, newstate = makeNewState(state, thoughtDict, response)
thoughtDict["action_result"] = response[:errormsg] progressvalue::Integer =
rawresponse = nothing if response[:success]
(result=nothing, errormsg=response[:errormsg], success=false) 8 # for faster agent response. if success just skip evaluation
end
else else
error("undefined LLM function. Requesting $(thoughtDict["action_name"])") evaluatorF(newstate, text2textInstructLLM, llmFormatName)
end end
# this section allow LLM functions above to have different return values.
success::Bool = haskey(response, :success) ? response[:success] : false
result = success ? response[:result] : response[:errormsg]
select = haskey(response, :select) ? response[:select] : nothing
reward::Integer = haskey(response, :reward) ? response[:reward] : 0
isterminal::Bool = haskey(response, :isterminal) ? response[:isterminal] : false
newNodeKey, newstate = makeNewState(state, thoughtDict, rawresponse, JSON.json(result),
select, reward, isterminal)
progressvalue::Integer = evaluatorF(newstate, text2textInstructLLM, llmFormatName)
return (newNodeKey=newNodeKey, newstate=newstate, progressvalue=progressvalue) return (newNodeKey=newNodeKey, newstate=newstate, progressvalue=progressvalue)
end end
@@ -891,19 +880,20 @@ function query(query::T, executeSQL::Function, text2textInstructLLM::Function;
insertSQLVectorDB::Union{Function, Nothing}=nothing, insertSQLVectorDB::Union{Function, Nothing}=nothing,
similarSQLVectorDB::Union{Function, Nothing}=nothing, similarSQLVectorDB::Union{Function, Nothing}=nothing,
llmFormatName="qwen3" llmFormatName="qwen3"
)::NamedTuple{(:text, :rawresponse), Tuple{Any, Any}} where {T<:AbstractString} ) where {T<:AbstractString}
# use similarSQLVectorDB to find similar SQL for the query # use similarSQLVectorDB to find similar SQL for the query
sql, distance = similarSQLVectorDB(query) sql, distance = similarSQLVectorDB(query)
# if sql is really match, immediately check database then return
if sql !== nothing && distance <= 1 if sql !== nothing && distance <= 1
# query vector db to get wine # query vector db to get wine
response = SQLexecution(executeSQL, sql) response = SQLexecution(executeSQL, sql)
if response[:success] if response[:success]
# intention = Dict(:intention=> "$(thoughtDict[:plan])") return (result_str=response[:result_str], result_raw=response[:result_raw])
extracted = extractContent_dataframe(response[:result], text2textInstructLLM, sql, else
llmFormatName) error(response[:errormsg])
return (text=extracted, rawresponse=response[:result]) end
end
end end
""" """
@@ -918,44 +908,44 @@ function query(query::T, executeSQL::Function, text2textInstructLLM::Function;
""" """
systemmsg = systemmsg =
""" """
<available_actions> <available_actions>
- RUNSQL, which you can use to execute SQL against the database. - RUNSQL, which you can use to execute SQL against the database.
action_input for this function must be a single SQL query to be executed against the database. action_input for this function must be a single SQL query to be executed against the database.
For more effective text search, it's necessary to use case-insensitivity and the ILIKE operator. For more effective text search, it's necessary to use case-insensitivity and the ILIKE operator.
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 ';'.
</available_actions> </available_actions>
<situation> <situation>
At each round of conversation, you will be given the following: At each round of conversation, you will be given the following:
- user question - user question
You are working under your mentor supervision and you are also eager to improve your helpfulness. You are working under your mentor supervision and you are also eager to improve your helpfulness.
</situation> </situation>
<objective> <objective>
Consult the database search guidelines. Then find the data from a database to satisfy the user's question. Consult the database search guidelines. Then find the data from a database to satisfy the user's question.
</objective> </objective>
<your responsibility includes> <your responsibility includes>
Fulfill the objective. Fulfill the objective.
</your responsibility includes> </your responsibility includes>
<database search guidelines> <database search guidelines>
- Keep SQL queries focused only on the provided information. - Keep SQL queries focused only on the provided information.
- Do not create any table in the database - Do not create any table in the database
- A junction table can be used to link tables together. Another use case is for filtering data. - A junction table can be used to link tables together. Another use case is for filtering data.
- If you can't find a single table that can be used to answer the user's query, try joining multiple tables to see if you can obtain the answer. - If you can't find a single table that can be used to answer the user's query, try joining multiple tables to see if you can obtain the answer.
- Text information in the database usually stored in lower case. If your search returns empty, try using lower case to search. - Text information in the database usually stored in lower case. If your search returns empty, try using lower case to search.
- If there is no search result from the database, remove the restrictive criteria until a search result is available, and proceed from there. - If there is no search result from the database, remove the restrictive criteria until a search result is available, and proceed from there.
</database search guidelines> </database search guidelines>
<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. Be specific. 1) plan: Based on the current situation, state a complete action plan to complete the task. Be specific.
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: (Typically corresponds to the execution of the first step in your plan) Can be one of the available_actions name
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 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>
<you should only respond in JSON format as described below> <you should only respond in JSON format as described below>
"plan": "...", "plan": "...",
"action_name": "...", "action_name": "...",
"action_input": "..." "action_input": "..."
</you should only respond in JSON format as described below> </you should only respond in JSON format as described below>
""" """
@@ -1129,11 +1119,11 @@ function query(query::T, executeSQL::Function, text2textInstructLLM::Function;
root, _, resultState, highValueState = root, _, resultState, highValueState =
LLMMCTS.runMCTS(initialstate, transition, transitionargs; LLMMCTS.runMCTS(initialstate, transition, transitionargs;
horizontalSampleExpansionPhase=1, horizontalSampleExpansionPhase=2,
horizontalSampleSimulationPhase=1, horizontalSampleSimulationPhase=2,
maxSimulationDepth=1, maxSimulationDepth=2,
maxiterations=1, maxiterations=2,
explorationweight=1.0, explorationweight=0.2,
earlystop=earlystop, earlystop=earlystop,
saveSimulatedNode=true, saveSimulatedNode=true,
multithread=false) multithread=false)
@@ -1144,11 +1134,6 @@ function query(query::T, executeSQL::Function, text2textInstructLLM::Function;
resultState = highValueState[selected] resultState = highValueState[selected]
end end
println("\n--- SQLLLM query() resultState ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
pprintln(resultState)
println("---")
max_ind = max_ind =
if length(resultState["action_history"]) == 0 if length(resultState["action_history"]) == 0
0 0
@@ -1157,25 +1142,20 @@ function query(query::T, executeSQL::Function, text2textInstructLLM::Function;
maximum(parse.(Int, k)) maximum(parse.(Int, k))
end end
latest_action = resultState["action_history"]["$max_ind"] latest_action = resultState["action_history"]["$max_ind"]
sql = latest_action["action_input"]
# add to vectorDB only if the answer is achieved and the state is terminal
if insertSQLVectorDB !== nothing && resultState["isterminal"] == true &&
resultState["accepted_as_answer"] == "yes"
insertSQLVectorDB(resultState["question"], sql)
end
if latest_action["action_result"] === nothing #CHANGE add to vectorDB only if the answer is achieved and the state is terminal
println("\nSQLLLM query() return nothing ", @__FILE__, ":", @__LINE__, " $(Dates.now())") # sql = latest_action["action_input"]
end # if insertSQLVectorDB !== nothing && resultState["isterminal"] == true &&
#WORKING 1 # resultState["accepted_as_answer"] == "yes"
error("SQLLLM query() end") # insertSQLVectorDB(resultState["question"], sql)
result = (text=latest_action["action_result"], rawresponse=resultState["rawresponse"]) # end
println("\n--- SQLLLM query() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
println("---") # println("\n--- SQLLLM query() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
error("SQLLLM query() end") # println(latest_action)
return result # println("---")
# error("SQLLLM query() end")
return (result_str=latest_action["action_result"], result_raw=resultState["result_raw"])
end end
@@ -1192,9 +1172,14 @@ julia>
# Signature # Signature
""" """
function makeNewState(currentstate::T1, thoughtDict::T4, rawresponse, response::T2, select::Union{T3, Nothing}, function makeNewState(currentstate::T1, thoughtDict::T2, response::NamedTuple,
reward::T3, isterminal::Bool )::NamedTuple{(:newNodeKey, :newstate), Tuple{String, Dict{String, <:Any}}} where {T1<:AbstractDict, T2<:AbstractDict}
)::NamedTuple{(:newNodeKey, :newstate), Tuple{String, Dict{String, <:Any}}} where {T1<:AbstractDict, T2<:AbstractString, T3<:Number, T4<:AbstractDict}
if response[:success]
thoughtDict["action_result"] = response[:result_str]
else
error(response[:errormsg])
end
newstate = deepcopy(currentstate) newstate = deepcopy(currentstate)
max_ind = max_ind =
@@ -1205,17 +1190,16 @@ function makeNewState(currentstate::T1, thoughtDict::T4, rawresponse, response::
maximum(parse.(Int, k)) maximum(parse.(Int, k))
end end
newstate["action_history"]["$(max_ind + 1)"] = thoughtDict newstate["action_history"]["$(max_ind + 1)"] = thoughtDict
newstate["reward"] = reward newstate["reward"] = haskey(response, :reward) ? response[:reward] : 0
newstate["select"] = select newstate["select"] = haskey(response, :select) ? response[:select] : nothing
newstate["isterminal"] = isterminal newstate["isterminal"] = haskey(response, :isterminal) ? response[:isterminal] : false
newstate["rawresponse"] = rawresponse # whatever return from action newstate["result_raw"] = response[:result_raw] # whatever return from action
newNodeKey = GeneralUtils.uuid4snakecase() newNodeKey = GeneralUtils.uuid4snakecase()
return (newNodeKey=newNodeKey, newstate=newstate) return (newNodeKey=newNodeKey, newstate=newstate)
end end
function generatequestion(state::T1, context, text2textInstructLLM::Function, function generatequestion(state::T1, context, text2textInstructLLM::Function,
llmFormatName::String; llmFormatName::String;
similarSQL::Union{T2, Nothing}=nothing, maxattempt=10, similarSQL::Union{T2, Nothing}=nothing, maxattempt=10,
+21 -36
View File
@@ -481,20 +481,9 @@ julia> response = SQLLLM.SQLexecution(executeSQL, sql)
# Signature # Signature
""" """
function SQLexecution(executeSQL::Function, sql::T function SQLexecution(executeSQL::Function, sql::T
) where {T<:AbstractString} )::NamedTuple where {T<:AbstractString}
try try
#XXX dummy SQL. use for testing
# sql = "SELECT w.wine_name FROM wine w JOIN wine_food wf ON w.wine_id = wf.wine_id JOIN food f ON wf.food_id = f.food_id WHERE f.\"food_name\" = 'lamb';"
# sql = " SELECT w.wine_name FROM wine w JOIN food f ON f.food_name = 'lamb' JOIN wine_food wf ON w.wine_id = wf.wine_id AND f.food_id = wf.food_id GROUP BY w.wine_name ORDER BY COUNT(DISTINCT w.wine_id) DESC;"
# sql = " SELECT COUNT(DISTINCT wf.wine_id) FROM wine w JOIN wine_food wf ON w.wine_id = wf.wine_id JOIN food f ON wf.food_id = f.food_id WHERE f.food_name ILIKE '%lamb%'"
#XXX use for package testing, remove when done
# ans = "1.schilfwein zweigelt 2.cabernet sauvignon reserve limited edition"
# ans = "There are 1500 wines that can be paired with lamb."
# ans = "1500"
# return (response=ans, errormsg=nothing, reward=1, isterminal=true)
# add LIMIT to the SQL to prevent loading large data # add LIMIT to the SQL to prevent loading large data
sql = strip(sql) sql = strip(sql)
@@ -508,39 +497,36 @@ function SQLexecution(executeSQL::Function, sql::T
else else
sql = sql * ";" sql = sql * ";"
end end
println("\n~~~ SQLexecution() SQL: ", @__FILE__, " ", @__LINE__)
println(sql)
result = executeSQL(sql) result = executeSQL(sql)
df = DataFrame(result) df = DataFrame(result)
tablesize = size(df) tablesize = size(df)
row, column = tablesize row, column = tablesize
if row == 0 if row == 0
error("\nThe resulting table has 0 row. Please try again.") return (result_str="The resulting table has 0 row.", result_raw=df, success=true, errormsg=nothing)
elseif column > 50 elseif column > 30
error("\nSQL execution success but there are more than 50 rows Please be more specific.") return (result_str="There are more than 30 columns. Please be more specific.", result_raw=df, success=true, errormsg=nothing)
else
df1 =
if row > 2
# ramdom row to pick
df[sample(1:nrow(df), 2, replace=false), :] # random select 2 rows from df
else
df
end
result = GeneralUtils.dfToString(df1)
println("\n~~~ SQLexecution() result: ", @__FILE__, " ", @__LINE__)
println(sql)
println(df1)
println("\n")
return (result_str=result, result_raw=df1, success=true, errormsg=nothing)
end end
df1 =
if row > 2
# ramdom row to pick
df[sample(1:nrow(df), 2, replace=false), :] # random select 2 rows from df
else
df
end
println("\n~~~ SQLexecution() result: ", @__FILE__, " ", @__LINE__)
println(df1)
return (result=df1, success=true, errormsg=nothing)
catch e catch e
io = IOBuffer() io = IOBuffer()
showerror(io, e) showerror(io, e)
errorMsg = String(take!(io)) errorMsg = String(take!(io))
st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace()))
println(errorMsg) println(errorMsg)
response = (result=nothing, success=false, errormsg=errorMsg) return (result_str=nothing, result_raw=nothing, success=false, errormsg=errorMsg)
return response
end end
end end
@@ -559,7 +545,7 @@ end
- `result::String` - `result::String`
# Signature # Signature
""" """ #WORKING
function extractContent_dataframe(df::DataFrame, text2textInstructLLM::Function, action::String, function extractContent_dataframe(df::DataFrame, text2textInstructLLM::Function, action::String,
llmFormatName::String llmFormatName::String
)::String )::String
@@ -633,7 +619,7 @@ function extractContent_dataframe(df::DataFrame, text2textInstructLLM::Function,
dictkey = ["about_resulting_table", "search_summary"] dictkey = ["about_resulting_table", "search_summary"]
for i in 1:5 for i in 1:5
response = text2textInstructLLM(prompt, modelsize="medium") response = text2textInstructLLM("ramdom_id", prompt)
response = GeneralUtils.deFormatLLMtext(response, llmFormatName) response = GeneralUtils.deFormatLLMtext(response, llmFormatName)
think, response = GeneralUtils.extractthink(response) think, response = GeneralUtils.extractthink(response)
@@ -653,7 +639,6 @@ function extractContent_dataframe(df::DataFrame, text2textInstructLLM::Function,
responsedict = GeneralUtils.textToDict(response, header; responsedict = GeneralUtils.textToDict(response, header;
dictKey=dictkey, symbolkey=false) dictKey=dictkey, symbolkey=false)
# result = dfstr
result = result =
""" """
Summary: $(responsedict["search_summary"]) Summary: $(responsedict["search_summary"])