diff --git a/src/OLD_interface.jl b/src/OLD_interface.jl
new file mode 100644
index 0000000..8c1ca5c
--- /dev/null
+++ b/src/OLD_interface.jl
@@ -0,0 +1,1400 @@
+module interface
+
+export addNewMessage, conversation, decisionMaker, reflector, generatechat,
+ generalconversation, detectWineryName, generateSituationReport
+
+using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
+ DataFrames, Serde
+using GeneralUtils
+using ..type, ..util, ..llmfunction
+
+# ------------------------------------------------------------------------------------------------ #
+# pythoncall setting #
+# ------------------------------------------------------------------------------------------------ #
+# Ref: https://github.com/JuliaPy/PythonCall.jl/issues/252
+# by setting the following variables, PythonCall.jl will use:
+# 1. system's python and packages installed by system (via apt install)
+# or 2. conda python and packages installed by conda
+# if these setting are not set (comment out), PythonCall will use its own python and packages that
+# installed by CondaPkg.jl (from env_preparation.jl)
+# ENV["JULIA_CONDAPKG_BACKEND"] = "Null" # set condapkg backend = none
+# systemPython = split(read(`which python`, String), "\n")[1] # system's python path
+# ENV["JULIA_PYTHONCALL_EXE"] = systemPython # find python location with $> which python ex. raw"/root/conda/bin/python"
+
+# using PythonCall
+# const py_agents = PythonCall.pynew()
+# const py_llms = PythonCall.pynew()
+# function __init__()
+# # PythonCall.pycopy!(py_cv2, pyimport("cv2"))
+
+# # equivalent to from urllib.request import urlopen in python
+# PythonCall.pycopy!(py_agents, pyimport("langchain.agents"))
+# PythonCall.pycopy!(py_llms, pyimport("langchain.llms"))
+# end
+
+# ---------------------------------------------- 100 --------------------------------------------- #
+
+
+macro executeStringFunction(functionStr, args...)
+ # Parse the function string into an expression
+ func_expr = Meta.parse(functionStr)
+
+ # Create a new function with the parsed expression
+ function_to_call = eval(Expr(:function,
+ Expr(:call, func_expr, args...), func_expr.args[2:end]...))
+
+ # Call the newly created function with the provided arguments
+ function_to_call(args...)
+end
+
+
+
+""" Think and choose action
+
+# Arguments
+ - `config::T1`
+ config
+ - `state::T2`
+ a game state
+
+# Keyword Arguments
+
+# Return
+ - `thoughtdict::Dict`
+
+# Example
+```jldoctest
+julia> result = decisionMaker(agent)
+
+OrderedDict{String, Any} with 4 entries:
+ "plan" => "The user provided an image of a sparkling white wine (Asolo Prosecco Bella Principessa from Italy) and requested a search for similar wines in the inventory. According to store guidelines, I must st…
+ "action_name" => "SEARCH_WINE_DATABASE"
+ "action_input" => "Sparkling white wine from Italy"
+ "action_result" => "1) winery: Terrazze dell Etna, wine_name: Rose Brut.
+```
+"""
+function decisionMaker(a::T; recentevents::Integer=20, maxattempt=3
+ ) where {T<:agent}
+ @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 =
+ """
+
+
+ """
+
+ # add context to text of the latest message (in the front).
+ # use for loop because in openai format, each msg may contain both text and image.
+ for d in a.chathistory[end]["content"]
+ if d["type"] == "text"
+ d["text"] = context * d["text"]
+ break
+ end
+ end
+ errornote = "N/A"
+ response = nothing # placeholder for show when error msg show up
+
+ """
+ {
+ "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 tool 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
+ response_format = Dict(
+ "type"=> "json_schema",
+ "json_schema"=> Dict(
+ "name"=> "user_profile",
+ "strict"=> true,
+ "schema"=> Dict(
+ "type"=> "object",
+ "properties"=> Dict(
+ "plan"=> Dict("type"=> "string"),
+ "action_name"=> Dict("type"=> "string"),
+ "action_input"=> Dict("type"=> "string"),
+ ),
+ "required"=> ["plan", "action_name", "action_input"],
+ "additionalProperties"=> false
+ )
+ )
+ )
+
+ msg = Dict(
+ "model"=> "gemma-4-E4B-it-UD-Q4_K_XL",
+ "messages"=> a.chathistory,
+ "temperature"=> 0.7,
+ "response_format"=> response_format,
+ )
+
+ for attempt in 1:maxattempt
+ response = a.context.text2textInstructLLM(a.id, msg)
+ response = GeneralUtils.remove_french_accents(response)
+ # think, response = GeneralUtils.extractthink(response)
+
+ # dollar sign in Julia means string interpolation
+ while occursin('$', response)
+ response = replace(response, '$' => "USD")
+ end
+
+ # responsedict = nothing
+ # 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")
+ # continue
+ # end
+
+ responsedict = JSON.parse(response)
+
+ 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())")
+ # pprintln(responsedict)
+ @info "YiemAgent decisionMaker() end " @__LINE__
+ return responsedict
+ end
+
+ # in case decisionMaker failed, force to use generatechat!()
+ responsedict = OrderedDict(
+ "plan"=> "N/A",
+ "action_name"=> "CHAT_BOX",
+ "action_input"=> "N/A"
+ )
+ return responsedict
+end
+
+
+
+""" Assigns a scalar value to each new child node to be used for selec-
+tion and backpropagation. This value effectively quantifies the agent's progress in task completion,
+serving as a heuristic to steer the search algorithm towards the most promising regions of the tree.
+
+# Arguments
+ - `state<:AbstractDict`
+ one of Yiem's agent
+ - `text2textInstructLLM::Function`
+ A function that handles communication to LLM service
+
+# Return
+ - `score::Integer`
+
+# Example
+```jldoctest
+julia>
+```
+
+# Signature
+"""
+function evaluator(a::T1, timeline, decisiondict, evaluateecontext
+ ) where {T1<:agent}
+
+ systemmsg =
+ """
+
+ - You are a master sommelier of an online wine store.
+
+
+ - Under your supervision, a trainee sommelier is engaging with a store customer. Each time the customer speaks, the trainee will assess the situation, determine the next course of action, and pause to await your guidance before proceeding.
+
+
+ - Improve a trainee sommelier decision based on the store policy and guidelines while ensuring seamless interactions between the trainee and customers.
+
+
+ - trajectory: A conversation between your trainee and the customer that have occurred up until now
+ - evaluatee_context: The context that evaluatee use to make a decision
+ - evaluatee_decision: The decision made by the evaluatee, consists of the following elements:
+ "plan" is the trainee's plan
+ "action_name" is the name of the action taken, which can be one of the available tool name.
+ "action_input" is the input to the action.
+
+
+ - Use only infomation provided by the store policy and guidelines as a bedrocks for your response.
+
+
+ - The trainee's plan, action_name, and action_input must be logically consistent
+ - The trainee's action_input should be in a proper format as specified by the tools.
+ - The trainee's action name and action input should make sense. For example, if the trainee isn't finished talking, he shouldn't use the END_CONVER_GUIDELINE tool.
+
+
+ 1) trajectory_evaluation: Analyze the trajectory of a solution to answer the user's original question.
+ - Evaluate the correctness of each section and the overall trajectory based on the given question.
+ - Provide detailed reasoning and analysis, focusing on the latest thought, action, and observation.
+ - Incomplete trajectory are acceptable if the thoughts and actions up to that point are correct, even if the final answer isn't reached.
+ - Do not generate additional thoughts or actions.
+ 2) decision_evaluation:
+ - Examine how the trainee's decisions align with the store's policies and guidelines before proceeding.
+ 3) suggestion: Based store policy and guidelines, provide a suggestion for the immediate decision step only.
+ 4) approval: Can be "yes" or "no". "no" if the suggestion contradict the trainee's decision; otherwise, it is "yes".
+
+
+
+ {
+ "trajectory_evaluation": "...",
+ "decision_evaluation": "...",
+ "suggestion": "...",
+ "approval": "...",
+ }
+
+
+ Let's begin!
+ """
+ requiredKeys = [:trajectory_evaluation, :decision_evaluation, :approval, :suggestion]
+ errornote = "N/A"
+
+ for attempt in 1:10
+ evaluateecontext = replace(evaluateecontext, "" => "")
+ evaluateecontext = replace(evaluateecontext, "" => "")
+
+ context =
+ """
+
+
+ $timeline
+
+
+ $evaluateecontext
+
+
+ {plan: $(decisiondict["plan"]), action_name: $(decisiondict["action_name"]), action_input: $(decisiondict["action_input"])}
+
+ P.S. $errornote
+
+ """
+
+ unformatPrompt =
+ [
+ Dict("name" => "system", "text" => systemmsg),
+ ]
+
+ # put in model format
+ prompt = GeneralUtils.formatLLMtext(unformatPrompt, a.llmFormatName)
+ # add info
+ prompt = prompt * context
+
+ response = a.context.text2textInstructLLM(prompt; senderId=a.id)
+ response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
+ response = GeneralUtils.remove_french_accents(response)
+ # response = replace(response, '$'=>"USD")
+ think, response = GeneralUtils.extractthink(response)
+
+ responsedict = nothing
+ try
+ responsedict = copy(JSON.parsefile(response))
+ catch
+ println("\nERROR YiemAgent generatechat() failed to parse response: $response", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ continue
+ end
+
+ # check whether all answer's key points are in responsedict
+ ispass, errormsg = checkAgentResponse_JSON(responsedict, requiredKeys)
+ if !ispass
+ errornote = errormsg
+ println("\nERROR YiemAgent evaluator() $errornote --(not qualify response)> $responsedict ", @__FILE__, ":", @__LINE__, " $(Dates.now())\n")
+ continue
+ end
+
+ # if accepted_as_answer ∉ ["yes", "no"] # [PENDING] add errornote into the prompt
+ # error("generated accepted_as_answer has wrong format")
+ # end
+
+ println("\nEvaluator() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ pprintln(Dict(responsedict))
+ return responsedict
+ end
+ error("Evaluator failed to generate an evaluation, Response: \n$response\n<|End of error|>")
+end
+
+""" Chat with llm.
+
+# Example userinput
+
+image_path = "test/large_image.png"
+image_bytes = read(image_path)
+base64_string = base64encode(image_bytes)
+
+# 2. Match the MIME type according to your file extension (e.g., png, jpeg)
+mime_type = "image/png"
+data1_uri = "data:;base64,"
+
+# 3. Construct payload with the Data URI
+message => Dict(
+ "role" => "user",
+ "content" => [
+ Dict("type" => "text", "text" => "Describe this image for me"),
+ Dict(
+ "type" => "image_url",
+ "image_url" => Dict("url" => data_uri)
+ )
+ ]
+ )
+
+"""
+function conversation(a::sommelier; userinput::Union{Dict{String, Any}, JSON.Object{String, Any}},
+ maximumMsg=50, max_think_loop::Integer=3)
+
+ @info "YiemAgent conversation() start " @__LINE__
+ userinput = GeneralUtils.dictify(userinput; keytype=String, sort_order=["text"])
+
+ # find text in usermsg
+ usertext = nothing
+ for (i, d) in enumerate(userinput["content"])
+ if d["type"] == "text"
+ d["text"] = GeneralUtils.remove_french_accents(d["text"])
+ usertext = d["text"]
+ end
+ end
+
+ if usertext == "newtopic"
+ clearhistory(a)
+ return "Okay. What shall we talk about?"
+ else
+
+ # add usermsg to a.chathistory but how do I handle images?
+ addNewMessage(a, "user", userinput; maximumMsg=maximumMsg)
+
+ # thinking loop until AI wants to communicate with the user
+ loopcount = 0
+ while true
+ loopcount += 1
+ if loopcount > max_think_loop
+
+ 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)
+
+ 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"]
+ actionresult = Dict{String, Any}(
+ "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
+end
+
+
+"""
+# Arguments
+
+# Return
+
+# Example
+```jldoctest
+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__
+ @show thoughtdict
+ println("---\n")
+
+ result_raw = nothing
+ if thoughtdict["action_name"] ∈ ["CHAT_BOX"]
+
+ # 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"
+
+ thoughtdict, result_raw = end_conversation_guideline!(a, thoughtdict)
+
+ elseif thoughtdict["action_name"] ∈ ["WINE_PRESENTATION_GUIDELINE"]
+
+ thoughtdict, result_raw = wine_presentation_guideline!(a, thoughtdict)
+
+ elseif thoughtdict["action_name"] == "SEARCH_WINE_DATABASE"
+
+ thoughtdict, result_raw = search_wine_database!(a, thoughtdict; useSQLLLM=false)
+ 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
+
+ error("condition is not defined ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ end
+
+ @info "YiemAgent think() end " @__LINE__
+ # @show thoughtdict
+ println("---\n")
+ return (thoughtdict=thoughtdict, result_raw=result_raw)
+end
+
+function chatbox!(a::T, thoughtdict::AbstractDict
+ )::NamedTuple{(:thoughtdict, :result_raw), Tuple{OrderedDict, Any}} where {T<:agent}
+ thoughtdict["action_result"] = "Action result is the next user dialogue."
+ return (thoughtdict=thoughtdict, result_raw=nothing)
+end
+
+function end_conversation_guideline!(a::T, thoughtdict::AbstractDict
+ )::NamedTuple{(:thoughtdict, :result_raw), Tuple{OrderedDict, Any}} where {T<:agent}
+
+ guideline =
+ """
+
+ - Provide customer with store contact info and business hours
+ - Invite customer to comeback
+
+ Business Hours: everyday 9.00-20.00
+ Tel. 0863055790
+
+
+ """
+ thoughtdict["action_result"] = guideline
+
+ return (thoughtdict=thoughtdict, result_raw=nothing)
+end
+
+function wine_presentation_guideline!(a::T, thoughtdict::AbstractDict
+ )::NamedTuple{(:thoughtdict, :result_raw), Tuple{OrderedDict, Any}} where {T<:agent}
+
+ guideline =
+ """
+
+ - Provide detailed introductions of the wines you've found to the user.
+ - Explain how the wine could match the user's intention and what its effects might mean for the user's experience.
+ - If multiple wines are available, highlight their differences and provide a comprehensive comparison of how each option aligns with the user's intention and what the potential effects of each option could mean for the user's experience.
+ - Provide your personal recommendation and provide a brief explanation of why you recommend it.
+ - People don't describe wine quality level in numbers so use convertion_table if neccessary
+
+ Intensity level:
+ 1 to 2: May correspond to "light-bodied" or a similar description.
+ 2 to 3: May correspond to "med light bodied", "medium light" or a similar description.
+ 3 to 4: May correspond to "medium bodied" or a similar description.
+ 4 to 5: May correspond to "med full bodied", "medium full" or a similar description.
+ 4 to 5: May correspond to "full bodied" or a similar description.
+ Sweetness level:
+ 1 to 2: May correspond to "dry", "no sweet" or a similar description.
+ 2 to 3: May correspond to "off dry", "less sweet" or a similar description.
+ 3 to 4: May correspond to "semi sweet" or a similar description.
+ 4 to 5: May correspond to "sweet" or a similar description.
+ 4 to 5: May correspond to "very sweet" or a similar description.
+ Tannin level:
+ 1 to 2: May correspond to "low tannin" or a similar description.
+ 2 to 3: May correspond to "semi low tannin" or a similar description.
+ 3 to 4: May correspond to "medium tannin" or a similar description.
+ 4 to 5: May correspond to "semi high tannin" or a similar description.
+ 4 to 5: May correspond to "high tannin" or a similar description.
+ Acidity level:
+ 1 to 2: May correspond to "low acidity" or a similar description.
+ 2 to 3: May correspond to "semi low acidity" or a similar description.
+ 3 to 4: May correspond to "medium acidity" or a similar description.
+ 4 to 5: May correspond to "semi high acidity" or a similar description.
+ 4 to 5: May correspond to "high acidity" or a similar description.
+
+
+ """
+ thoughtdict["action_result"] = guideline
+
+ return (thoughtdict=thoughtdict, result_raw=nothing)
+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 =
+ # 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)
+
+ systemmsg =
+ """
+ # store_policy
+ - Generally speaking, the store inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know exactly until you check your inventory.
+ - If you found wines in the store's database, they are in stock.
+ - 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.
+ - 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.
+ - 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.
+ - We do not sell organic, sustainable, gluten-free, and sulfite-free wine. Inform the user immediately if they are looking for these types of wines. Do not sell our wines as such.
+ - Gift box, gift card, and custom messages are available. Inform the user to contact our sales team.
+
+ # store_guidelines
+ - Greeting the customer warmly by ask them how could you help. Do not ask any other questions during this greeting.
+ - Customer may provide images for you to look up.
+ - Encourage the customer to explore different options and try new things.
+ - If you are unable to locate the desired item in the database after 2 attempts, it may not be available in your inventory. In such cases, inform the user that the item is unavailable and suggest an alternative instead.
+ - 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.
+
+ # situation
+ You are continuing the conversation with the user.
+
+ # your role
+ Your name is $(a.name). You are a helpful sommelier for website-based $(a.retailername)'s wine store. You are working under your mentor supervision.
+
+ # objective
+ - Establish a connection with the customer by talking to them politely and showing your enthusiasm for their wine preferences.
+ - Provide relevant information and guide them to select the best wines only from your store's inventory that align with their preferences.
+
+ # your responsibility includes
+ - According to the store's policy and guidelines, continuing conversation with the customer using CHAT_BOX action.
+ - Keep the conversation with the customer going smoothly
+
+ # your responsibility does NOT includes
+ - Requesting the user to place an order, make a purchase, or confirm the order. 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.
+
+ # you should then respond to the user with interleaving plan, 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.
+ 2) "action_name", action_name must be CHAT_BOX.
+ 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.
+
+ # 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.
+ """
+
+ system_msg = Dict(
+ "role" => "system",
+ "content" => [
+ Dict("type" => "text", "text" => systemmsg),
+ ]
+ )
+
+ chathistory = deepcopy(a.chathistory[2:end]) # use deep copy because I want to replace system msg
+ pushfirst!(chathistory, system_msg)
+
+ requiredKeys = ["plan", "action_name", "action_input"]
+
+ errornote = "N/A"
+ response = nothing # placeholder for show when error msg show up
+
+ for attempt in 1:maxattempt
+ if attempt > 1
+ println("\nYiemAgent generatechat() attempt $attempt/$maxattempt ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ end
+
+ response_format = Dict(
+ "type"=> "json_schema",
+ "json_schema"=> Dict(
+ "name"=> "user_profile",
+ "strict"=> true,
+ "schema"=> Dict(
+ "type"=> "object",
+ "properties"=> Dict(
+ "plan"=> Dict(
+ "type"=> "string",
+ "description" => "Based on the current situation, state a complete action plan to complete the task and rationale. Be specific.",
+ ),
+ "action_name"=> Dict(
+ "type"=> "string",
+ "description" => "action_name must be CHAT_BOX",
+ ),
+ "action_input"=> Dict(
+ "type"=> "string",
+ "description" => "Dialogue you want to chat with the user according to your plan.",
+ ),
+ ),
+ "required"=> ["plan", "action_name", "action_input"],
+ "additionalProperties"=> false
+ )
+ )
+ )
+
+ msg = Dict(
+ "model" => "gemma-4-E4B-it-UD-Q4_K_XL",
+ "messages" => chathistory,
+ "temperature" => 0.7,
+ "response_format"=> response_format,
+ )
+
+ 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 = strip(response)
+
+ 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 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")
+ 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."
+ @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
+
+
+function generatequestion(a, text2textInstructLLM::Function, timeline)::String
+ systemmsg =
+ """
+ Your role:
+ Your name is $(a.name). You are a helpful English-speaking, website-based sommelier for $(a.retailername)'s online store currently talking with the user.
+ Your goal includes:
+ 1) Help the user select the best wines from your inventory that align with the user's preferences
+ 2) Thanks the user when they don't need any further assistance and invite them to comeback next time
+
+ Your responsibility includes:
+ 1) From your point of view as a sommelier helping the user, ask yourself multiple questions based on the current situation
+
+ Your responsibility does NOT includes:
+ 1) Requesting the user to place an order, make a purchase, or confirm the order. These are the job of our sales team at the store.
+ 2) Processing sales orders or engaging in any other sales-related activities. These are the job of our sales team at the store.
+ 3) 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.
+
+ At each round of conversation, you will be given the info:
+ Additional info: ...
+ Your recent events: latest 5 events of the situation
+
+ You must follow the following guidelines:
+ - Your question should be specific, self-contained and not require any additional context.
+ - Once the user has chose their wine, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time.
+
+ You should follow the following guidelines:
+ - Focus on the latest conversation
+ - If the user interrupts, prioritize the user
+ - If you don't already know, find out the user's budget
+ - If you don't already know, find out the type of wine the user is looking for, such as red, white, sparkling, rose, dessert, fortified
+ - If you don't already know, find out the occasion for which the user is buying wine
+ - If you don't already know, find out the characteristics of wine the user is looking for, such as tannin, sweetness, intensity, acidity
+ - If you don't already know, find out what food will be served with wine
+ - If you haven't already, introduce the wines you found in the database to the user first
+ - Generally speaking, your inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know exactly until you check your inventory.
+ - All wines in your inventory are always in stock.
+ - Engage in conversation to indirectly investigate the customer's intention, budget and preferences before checking your inventory.
+ - 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, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time.
+ - Medium and full-bodied red wines are bad with spicy foods.
+ - If a customer requests information about discounts, quantity, rewards programs, promotions, delivery options, boxes, gift wrapping, packaging, or personalized messages, please inform them that they can contact our sales team at the store.
+
+ You should then respond to the user with:
+ 1) Thought: State your thought about the current situation
+ 2) Q: "Ask yourself" at least three, but no more than five, questions about the situation from your perspective.
+ 3) A: Given the situation, "answer to yourself" the best you can. Do not generate any extra text after you finish answering all questions
+
+ You must only respond in format as described below:
+ Q1: ...
+ A1: ...
+ Q2: ...
+ A2: ...
+ ...
+
+ Here are some examples:
+ Q: What the user is looking for?
+ A: The user is asking for a MPV car with 7-seat
+ Q: What do I know?
+ A: The user is looking for a car with 7-seat. Our dealer sell these kind of cars
+ Q: What brands the user prefer?
+ A: I don't know. The user didn't mentioned that. Let's find out.
+ Q: What else do I need to know before proceeding?
+ A: I don't know about the user budget, car's color, and other user's preferences yet. Let's find out more about the user's preferences.
+ Q: I'm still lacking information regarding the user's preferences for the powertrain. I've asked the user twice already, but perhaps they're not familiar with this. What should I do.
+ A: I'll proceed without asking the user about the powertrain.
+ Q: The user is buying for her husband, should I dig in to get more information?
+ A: Yes, I should. So that I have better idea about the user's preferences.
+ Q: Why the user saying this?
+ A: The user does not want an SUV because it does not have sliding doors
+ Q: The user is asking for a cappuccino. Do I have it at my cafe?
+ A: No I don't have.
+ Q: Since I don't have a cappuccino but I have a Late, should I ask if they are okay with that?
+ A: Yes, I should.
+ Q: Are they allergic to milk?
+ A: Since they mentioned a cappuccino before, it seems they are not allergic to milk.
+ Q: Have I checked the inventory yet?
+ A: No. I need more information from the user including ...
+ Q: What else do I need to know?
+ A: ...
+ Q: Should I present my item to the user?
+ A: Not yet, I will need to check my inventory first.
+ Q: Should I check our inventory now?
+ A: ...
+ Q: What the user intend to do with the car?
+ A: I don't know yet. Let's ask the user.
+ Q: What do I have in our inventory?
+ A: ...
+ Q: Which items are within the user price range? And which items are out of the user price rance?
+ A: ...
+ Q: Do I have what the user is looking for in our stock?
+ A: ...
+ Q: Am I certain about the information I'm going to share with the user, or should I verify the information first?
+ A: ...
+ Q: What should I do?
+ A: ...
+ Q: What shouldn't I do?
+ A: ...
+ Q: what kind of car suitable for off-road trip?
+ A: A four-wheel drive SUV is a good choice for off-road trips.
+ Q: What car specification would satisfy the user's needs?
+ A: The user is seeking an eco-friendly vehicle that accommodates seven passengers, including seniors and children, with prioritized accessibility and efficient refueling. While electric vehicles (EVs) offer eco-friendly benefits, their long charging times make hybrid models more practical for fast refueling. Additionally, a lower ground level is essential for ease of entry/exit for seniors and children. A hybrid multi-purpose vehicle (MPV) emerges as the optimal solution, balancing sustainability, seating capacity, accessibility, and refueling efficiency.
+
+ Let's begin!
+ """
+
+ header = ["Q1:"]
+ dictkey = ["q1"]
+
+ # context =
+ # if length(a.memory["shortmem"]["available_wine"]) != 0
+ # "Available wines you've found in your inventory so far: $(availableWineToText(a.memory["shortmem"]["available_wine"]))"
+ # else
+ # "N/A"
+ # end
+ database_search_result = a.memory["shortmem"]["db_search_result"]
+
+ # recent_ind = GeneralUtils.recentElementsIndex(length(a.memory[:events]), recent)
+ # recentevents = a.memory[:events][recent_ind]
+ # timeline = createTimeline(recentevents; eventindex=recent_ind)
+ errornote = "N/A"
+ response = nothing # store for show when error msg show up
+
+ # recap =
+ # if length(a.memory[:recap]) <= recent
+ # "N/A"
+ # else
+ # recapkeys = keys(a.memory[:recap])
+ # recapkeys_vec = [i for i in recapkeys]
+ # recapkeys_vec = recapkeys_vec[1:end-recent]
+ # tempmem = OrderedDict()
+ # for (k, v) in a.memory[:recap]
+ # if k ∈ recapkeys_vec
+ # tempmem[k] = v
+ # end
+ # end
+
+ # GeneralUtils.dictToString(tempmem)
+ # end
+
+ llmkwargs=Dict(
+ :num_ctx => 32768,
+ :temperature => 0.5,
+ )
+
+ for attempt in 1:10
+ if attempt > 1
+ println("\nYiemAgent generatequestion() attempt $attempt/10 ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ end
+
+ usermsg =
+ """
+ Additional info: $database_search_result
+ Your recent events: $timeline
+ P.S. $errornote
+ """
+
+ _prompt =
+ [
+ Dict("name" => "system", "text" => systemmsg),
+ Dict("name" => "user", "text" => usermsg)
+ ]
+
+ # put in model format
+ prompt = GeneralUtils.formatLLMtext(_prompt, a.llmFormatName)
+
+ response = text2textInstructLLM(prompt;
+ modelsize="medium", llmkwargs=llmkwargs, senderId=a.id)
+ response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
+ think, response = GeneralUtils.extractthink(response)
+
+ # make sure generatequestion() don't have wine name that is not from retailer inventory
+ # check whether an agent recommend wines before checking inventory or recommend wines
+ # outside its inventory
+ # ask LLM whether there are any winery mentioned in the response
+ mentioned_winery = detectWineryName(a, response)
+ if mentioned_winery != "None"
+ mentioned_winery = String.(strip.(split(mentioned_winery, ",")))
+
+ # check whether the wine is in event
+ isWineInEvent = false
+ for winename in mentioned_winery
+ for event in a.memory["events"]
+ if event["observation"] !== nothing && occursin(winename, event["observation"])
+ isWineInEvent = true
+ break
+ end
+ end
+ end
+
+ # if wine is mentioned but not in timeline or shortmem,
+ # then the agent is not supposed to recommend the wine
+ if isWineInEvent == false
+ errornote = "Your previous attempt mentioned wines that are not in your inventory which is not allowed."
+ println("\nERROR YiemAgent generatequestion() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ continue
+ end
+ end
+
+ q_number = count("Q", response)
+
+ # check for valid response
+ if q_number < 1
+ errornote = "Your previous attempt has too few questions."
+ println("\nERROR YiemAgent generatequestion() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ continue
+ # check whether "A1" is in the response, if not error.
+ elseif !occursin("A1:", response)
+ errornote = "Your previous attempt does not have A1:"
+ println("\nERROR YiemAgent generatequestion() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ continue
+ end
+
+ # check whether response has all header
+ detected_kw = GeneralUtils.detectKeywordVariation(header, response)
+ if 0 ∈ values(detected_kw)
+ errornote = "\nYour previous attempt did not have all points according to the required response format"
+ println("\nERROR YiemAgent generatequestion() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ continue
+ elseif sum(values(detected_kw)) > length(header)
+ errornote = "\nYour previous attempt has duplicated points according to the required response format"
+ println("\nERROR YiemAgent generatequestion() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ continue
+ end
+
+ responsedict = GeneralUtils.textToDict(response, header;
+ dictKey=dictkey, symbolkey=true)
+ response = "Q1: " * responsedict["q1"]
+ println("\nYiemAgent generatequestion() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ try pprintln(response) catch e println(response) end
+
+ return response
+ end
+ error("YiemAgent generatequestion() failed to generate a response ", response)
+end
+
+
+function generateSituationReport(a, text2textInstructLLM::Function; skiprecent::Integer=0
+ )::OrderedDict
+
+ systemmsg =
+ """
+ You are an assistant being in the given events.
+ Your task is to writes a summary for each event seperately into an ongoing, interleaving series.
+
+ At each round of conversation, you will be given the situation:
+ Total events: number of events you need to summarize.
+ Events timeline: ...
+ Context: ...
+
+ You should follow the following guidelines:
+ - Use the word "user" and "assistant" instead of their name in the report
+
+ You should then respond to the user with the following:
+ Event: a detailed summary for each event without exaggerated details.
+
+ You must only respond in format as described below:
+ Event_1: ...
+ Event_2: ...
+ ...
+
+ Here are some examples:
+ Event_1: The user ask me about where to buy a toy.
+ Event_2: I told the user to go to the store at 2nd floor.
+
+ Event_1: The user greets the assistant by saying 'hello'.
+ Event_2: The assistant respond warmly and inquire about how he can assist the user.
+
+ Let's begin!
+ """
+
+ header = ["Event_$i:" for i in eachindex(a.memory["events"])]
+ dictkey = lowercase.(["Event_$i" for i in eachindex(a.memory["events"])])
+
+ ind = GeneralUtils.nonRecentElementsIndex(length(a.memory["events"]), skiprecent)
+ events = a.memory["events"][ind]
+ timeline = createTimeline(events)
+
+ errornote = "N/A"
+ response = nothing # store for show when error msg show up
+ for attempt in 1:10
+ if attempt > 1 # use to prevent LLM generate the same respond over and over
+ println("\nYiemAgent generateSituationReport() attempt $attempt/10 ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ end
+
+ usermsg = """
+ Total events: $(length(events))
+ Events timeline: $timeline
+ P.S. $errornote
+ """
+
+ _prompt =
+ [
+ Dict("name" => "system", "text" => systemmsg),
+ Dict("name" => "user", "text" => usermsg)
+ ]
+
+ # put in model format
+ prompt = GeneralUtils.formatLLMtext(_prompt, "qwen3")
+
+ response = text2textInstructLLM(prompt; senderId=a.id)
+ response = GeneralUtils.deFormatLLMtext(response, "qwen3")
+
+ # check whether response has all header
+ detected_kw = GeneralUtils.detectKeywordVariation(header, response)
+ kwvalue = [i for i in values(detected_kw)]
+ zeroind = findall(x -> x == 0, kwvalue)
+ missingkeys = [header[i] for i in zeroind]
+ if 0 ∈ values(detected_kw)
+ errornote = "$missingkeys are missing in your previous attempt"
+ println("\nERROR YiemAgent generateSituationReport() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ continue
+ elseif sum(values(detected_kw)) > length(header)
+ errornote = "Your previous response has duplicated events"
+ println("\nERROR YiemAgent generateSituationReport() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ continue
+ end
+
+ responsedict = GeneralUtils.textToDict(response, header;
+ dictKey=dictkey, symbolkey=true)
+
+ println("\ngenerateSituationReport() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ try pprintln(response) catch e println(response) end
+
+ return responsedict
+ end
+ error("generateSituationReport failed to generate a response ", response)
+end
+
+
+function detectWineryName(a, text)
+ systemmsg =
+ """
+ You are a sommelier of a wine store.
+ Your task is to identify and list any winery names mentioned in the provided text.
+
+ At each round of conversation, you will be given the situation:
+ Text: a text describing the situation.
+
+ Tips:
+ - Winery usually contains Château, Chateau, Domaine, Côte, Cotes, St. de, or a combination of these words.
+
+ You should then respond to the user with:
+ Winery_names: A list of winery names mentioned in the text or "None" if no winery name is mentioned.
+
+ You must only respond in format as described below:
+ Winery_names: ...
+
+ Here are some examples:
+ Winery_names: Domaine Courbis, Chateau Lafite Rothschild, Matarromera Domaine Roulot, Château, Cotes
+
+ Let's begin!
+ """
+
+ header = ["Winery_names:"]
+ dictkey = ["winery_names"]
+
+ response = nothing # placeholder for show when error msg show up
+
+ for attempt in 1:10
+ usermsg = """
+ Text: $text
+ """
+ _prompt =
+ [
+ Dict("name" => "system", "text" => systemmsg),
+ Dict("name" => "user", "text" => usermsg)
+ ]
+
+ # put in model format
+ prompt = GeneralUtils.formatLLMtext(_prompt, a.llmFormatName)
+
+ response = a.context.text2textInstructLLM(prompt; senderId=a.id)
+ response = GeneralUtils.deFormatLLMtext(response, a.llmFormatName)
+ think, response = GeneralUtils.extractthink(response)
+ println("\ndetectWineryName() ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ try pprintln(response) catch e println(response) end
+
+ # check whether response has all header
+ detected_kw = GeneralUtils.detectKeywordVariation(header, response)
+ missingkeys = [k for (k, v) in detected_kw if v === nothing]
+
+ if !isempty(missingkeys)
+ errornote = "$missingkeys are missing from your previous response"
+ println("\nERROR YiemAgent rolegenerator() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ continue
+ elseif sum([length(i) for i in values(detected_kw)]) > length(header)
+ errornote = "\nYour previous attempt has duplicated points according to the required response format"
+ println("\nERROR YiemAgent rolegenerator() $errornote ", @__FILE__, ":", @__LINE__, " $(Dates.now())")
+ continue
+ end
+
+ responsedict = GeneralUtils.textToDict(response, header;
+ dictKey=dictkey, symbolkey=true)
+
+ result = responsedict["winery_names"]
+
+ return result
+ end
+ error("detectWineryName failed to generate a response")
+ end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+end # module interface
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/type_OLD.jl b/src/OLD_type.jl
similarity index 100%
rename from src/type_OLD.jl
rename to src/OLD_type.jl
diff --git a/src/interface.jl b/src/interface.jl
index 8c1ca5c..69a3b1b 100644
--- a/src/interface.jl
+++ b/src/interface.jl
@@ -1,52 +1,15 @@
module interface
-export addNewMessage, conversation, decisionMaker, reflector, generatechat,
- generalconversation, detectWineryName, generateSituationReport
+export prompt
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
DataFrames, Serde
using GeneralUtils
using ..type, ..util, ..llmfunction
-# ------------------------------------------------------------------------------------------------ #
-# pythoncall setting #
-# ------------------------------------------------------------------------------------------------ #
-# Ref: https://github.com/JuliaPy/PythonCall.jl/issues/252
-# by setting the following variables, PythonCall.jl will use:
-# 1. system's python and packages installed by system (via apt install)
-# or 2. conda python and packages installed by conda
-# if these setting are not set (comment out), PythonCall will use its own python and packages that
-# installed by CondaPkg.jl (from env_preparation.jl)
-# ENV["JULIA_CONDAPKG_BACKEND"] = "Null" # set condapkg backend = none
-# systemPython = split(read(`which python`, String), "\n")[1] # system's python path
-# ENV["JULIA_PYTHONCALL_EXE"] = systemPython # find python location with $> which python ex. raw"/root/conda/bin/python"
-
-# using PythonCall
-# const py_agents = PythonCall.pynew()
-# const py_llms = PythonCall.pynew()
-# function __init__()
-# # PythonCall.pycopy!(py_cv2, pyimport("cv2"))
-
-# # equivalent to from urllib.request import urlopen in python
-# PythonCall.pycopy!(py_agents, pyimport("langchain.agents"))
-# PythonCall.pycopy!(py_llms, pyimport("langchain.llms"))
-# end
-
# ---------------------------------------------- 100 --------------------------------------------- #
-macro executeStringFunction(functionStr, args...)
- # Parse the function string into an expression
- func_expr = Meta.parse(functionStr)
-
- # Create a new function with the parsed expression
- function_to_call = eval(Expr(:function,
- Expr(:call, func_expr, args...), func_expr.args[2:end]...))
-
- # Call the newly created function with the provided arguments
- function_to_call(args...)
-end
-
""" Think and choose action
diff --git a/src/type.jl b/src/type.jl
index 315b6ce..ef15777 100644
--- a/src/type.jl
+++ b/src/type.jl
@@ -166,7 +166,9 @@ end
# Agent struct
# ============================================================================
-mutable struct yiemAgent # High-level agent wrapper
+abstract type agent end
+
+mutable struct yiemAgent <: agent # High-level agent wrapper
_state::agentState # Current state (prompt, model, messages, tools, etc.)
input_ch::Channel # user sends prompt message to agent.