module interface export agentReact, agentReflex, addNewMessage, clearMessage, removeLatestMsg, conversation, writeEvaluationGuideline, grading, analyze, selfReflext, actor_mistral_openorca2, formulateUserresponse, extractinfo, updateEnvState, chat_mistral_openorca using JSON3, DataStructures, Dates, UUIDs, HTTP using CommUtils, GeneralUtils using ..type, ..utils # ---------------------------------------------------------------------------- # # pythoncall setting # # ---------------------------------------------------------------------------- # # Ref: https://github.com/JuliaPy/PythonCall.jl/issues/252 # by setting the following variables, PythonCall will use system python or conda python and # packages installed by system or conda # if these setting are not set (comment out), PythonCall will use its own python and package that # installed by CondaPkg (from env_preparation.jl) # ENV["JULIA_CONDAPKG_BACKEND"] = "Null" # systemPython = split(read(`which python`, String), "\n")[1] # 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 """ Add new message to agent. Arguments: Return: ```jldoctest julia> addNewMessage(agent1, "user", "Where should I go to buy snacks") ``` """ function addNewMessage(a::T1, role::String, content::T2) where {T1<:agent, T2<:AbstractString} if role ∉ a.availableRole # guard against typo error("role is not in agent.availableRole $(@__LINE__)") end # check whether user messages exceed limit userMsg = 0 for i in a.messages if i[:role] == "user" userMsg += 1 end end messageleft = 0 if userMsg > a.maxUserMsg # delete all conversation clearMessage(a) messageleft = a.maxUserMsg else userMsg += 1 d = Dict(:role=> role, :content=> content, :timestamp=> Dates.now()) push!(a.messages, d) messageleft = a.maxUserMsg - userMsg end return messageleft end function clearMessage(a::T) where {T<:agent} for i in eachindex(a.messages) if length(a.messages) > 0 pop!(a.messages) else break end end a.memory[:shortterm] = OrderedDict{String, Any}() a.memory[:log] = OrderedDict{String, Any}() @show a.messages end function removeLatestMsg(a::T) where {T<:agent} if length(a.messages) > 1 pop!(a.messages) end end # function generatePrompt_mistral_openorca(a::T, usermsg::String, role::Symbol) where {T<:agent} # prompt = # """ # <|im_start|>system # {systemMsg} # <|im_end|> # Here are the context for the question: # {context} # """ # prompt = replace(prompt, "{systemMsg}" => a.roles[role]) # toolnames = "" # toollines = "" # for (toolname, v) in a.tools # toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" # toollines *= toolline # toolnames *= "$toolname," # end # prompt = replace(prompt, "{toolnames}" => toolnames) # prompt = replace(prompt, "{tools}" => toollines) # prompt = replace(prompt, "{context}" => a.context) # prompt *= "<|im_start|>user\n" * usermsg * "\n<|im_end|>\n" # prompt *= "<|im_start|>assistant\n" # return prompt # end # function generatePrompt_mistral_openorca(a::T, usermsg::String, # thinkingMode::Symbol=:nothinking) where {T<:agent} # prompt = # """ # <|im_start|>system # {systemMsg} # You have access to the following tools: # {tools} # {thinkingMode} # <|im_end|> # Here are the context for the question: # {context} # """ # prompt = replace(prompt, "{systemMsg}" => a.roles[a.role]) # prompt = replace(prompt, "{thinkingMode}" => a.thinkingMode[thinkingMode]) # toolnames = "" # toollines = "" # for (toolname, v) in a.tools # toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" # toollines *= toolline # toolnames *= "$toolname," # end # prompt = replace(prompt, "{toolnames}" => toolnames) # prompt = replace(prompt, "{tools}" => toollines) # prompt = replace(prompt, "{context}" => a.context) # prompt *= "<|im_start|>user\nQuestion: " * usermsg * "\n<|im_end|>\n" # prompt *= "<|im_start|>assistant\n" # return prompt # end function generatePrompt_mistral_openorca(a::T, usermsg::String, thinkingMode::Symbol=:nothinking) where {T<:agent} prompt = """ <|im_start|>system {systemMsg} {tools} {thinkingMode} <|im_end|> Here are the context for the stimulus: {context} """ prompt = replace(prompt, "{systemMsg}" => a.roles[a.role]) prompt = replace(prompt, "{thinkingMode}" => a.thinkingMode[thinkingMode]) toolnames = "" toollines = "" for (toolname, v) in a.tools toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" toollines *= toolline toolnames *= "$toolname," end prompt = replace(prompt, "{toolnames}" => toolnames) prompt = replace(prompt, "{context}" => a.context) prompt *= "<|im_start|>user\nStimulus: " * usermsg * "\n<|im_end|>\n" prompt *= "<|im_start|>assistant\n" return prompt end function chat_mistral_openorca(a::agentReflex) """ general prompt format: " <|im_start|>system {role} {tools} {thinkingFormat} {context} <|im_end|> <|im_start|>user {usermsg} <|im_end|> <|im_start|>assistant " Note: {context} = " {earlierConversation} {env state} {shortterm memory} {longterm memory} " """ prompt = """ <|im_start|>system $(a.roles[a.role]) Your earlier talk with the user: $(a.earlierConversation) <|im_end|> $(messagesToString(a.messages)) <|im_start|>assistant """ response = sendReceivePrompt(a, prompt) response = split(response, "<|im_end|>")[1] return response end function planner_mistral_openorca(a::agentReflex) """ general prompt format: " <|im_start|>system {role} {tools} {thinkingFormat} <|im_end|> {context} <|im_start|>user {usermsg} <|im_end|> <|im_start|>assistant " Note: {context} = " {earlierConversation} {env state} {shortterm memory} {longterm memory} " """ conversation = messagesToString(a.messages) toollines = "" for (toolname, v) in a.tools if toolname ∉ ["chatbox"] toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" toollines *= toolline end end assistant_plan_prompt = """ <|im_start|>system $(a.roles[a.role]) The required info you need for wine recommendation: - type of food: ask the user - occasion: ask the user - user's personal taste of wine: ask the user - wine price range: ask the user - ambient temperature at the serving location: ask the user - wines we have in stock You provide a personalized recommendation of up to two wines based on the user's info above, and you describe the benefits of each wine in detail. You have access to the following tools: $toollines Use the following format: Objective: the objective you intend to do Aware: ask yourself what info you don't have? Plan: first you should always think about the objective, the info you have, the info you don't have and thoroughly then extract and devise a complete, step by step plan (pay attention to correct numeral calculation and commonsense). p.s.1 each step of the plan should be a single action. p.s.2 ask the user all you need to know and then search your inventory. <|im_end|> $conversation <|im_start|>assistant Objective: """ # p.s.1 each step of the plan should be a single action. # p.s.2 the last step should be about responding. result = sendReceivePrompt(a, assistant_plan_prompt, max_tokens=1024, temperature=0.1) @show raw_plan = result x = split(result, "<|im_end|>")[1] x = split(x, "Step")[1] x = split(x, "Plan:") objective = x[1] plan = x[2] return objective, plan end # function planner_mistral_openorca(a::agentReflex) # """ # general prompt format: # " # <|im_start|>system # {role} # {tools} # {thinkingFormat} # <|im_end|> # {context} # <|im_start|>user # {usermsg} # <|im_end|> # <|im_start|>assistant # " # Note: # {context} = # " # {earlierConversation} # {env state} # {shortterm memory} # {longterm memory} # " # """ # prompt = # """ # <|im_start|>system # {role} # {roleSpecificKnowledge} # {tools} # {thinkingFormat} # {context} # <|im_end|> # <|im_start|>user # {usermsg} # <|im_end|> # <|im_start|>assistant # Plan: # """ # prompt = replace(prompt, "{role}" => a.roles[a.role]) # prompt = replace(prompt, "{thinkingFormat}" => a.thinkingFormat[:planner]) # roleSpecificKnowledge = # """ # Info you need from the user to be able to help them selecting their best wine: # - type of food # - occasion # - user's personal taste of wine # - wine price range # - ambient temperature at the serving location # - wines we have in stock # You job is to provide a personalized recommendation of up to two wines based on the user's info above, and you describe the benefits of each wine in detail. # """ # prompt = replace(prompt, "{roleSpecificKnowledge}" => roleSpecificKnowledge) # toolnames = "" # toollines = "" # for (toolname, v) in a.tools # toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" # toollines *= toolline # toolnames *= "$toolname," # end # prompt = replace(prompt, "{toolnames}" => toolnames) # prompt = replace(prompt, "{tools}" => "You have access to the following tools:\n$toollines") # # prepare contex # context = # """ # My earlier talk with the user: # $(a.earlierConversation) # My earlier experience # $(experience(a.memory[:longterm])) # """ # prompt = replace(prompt, "{context}" => context) # # initialize short term memory # prompt = replace(prompt, "{usermsg}" => "Stimulus: $(a.memory[:shortterm]["user:"])") # return prompt # end function actor_mistral_openorca(a::agentReflex) """ general prompt format: " <|im_start|>system {role} {tools} {thinkingFormat} <|im_end|> {context} <|im_start|>user {usermsg} <|im_end|> <|im_start|>assistant " Note: {context} = " {earlierConversation} {env state} {shortterm memory} {longterm memory} " """ prompt = """ <|im_start|>system $(a.roles[a.role]) {tools} $(a.thinkingFormat[:actor]) {context} <|im_end|> {shorttermMemory} Thought $(a.step): """ s = dictToString(a.memory[:shortterm], skiplist=["user:", "Plan 1:"]) prompt = replace(prompt, "{shorttermMemory}" => s) toolnames = "" toollines = "" for (toolname, v) in a.tools toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" toollines *= toolline toolnames *= "$toolname, " end prompt = replace(prompt, "{toolnames}" => toolnames) prompt = replace(prompt, "{tools}" => "You have access to the following tools:\n$toollines") conversation = messagesToString_nomark(a.messages, addressAIas="I") context = """ Your talk with the user: $conversation {env state} {longterm memory} {plan} """ # context = replace(context, "{earlierConversation}" => "My earlier talk with the user:\n$(a.earlierConversation)") context = replace(context, "{env state}" => "") context = replace(context, "{longterm memory}" => "") context = replace(context, "{plan}" => "My plan:\n$(a.memory[:shortterm]["Plan $(a.attempt):"])") prompt = replace(prompt, "{context}" => context) return prompt end # function actor_mistral_openorca(a::agentReflex) # """ # general prompt format: # " # <|im_start|>system # {role} # {tools} # {thinkingFormat} # <|im_end|> # {context} # <|im_start|>user # {usermsg} # <|im_end|> # <|im_start|>assistant # " # Note: # {context} = # " # {earlierConversation} # {env state} # {shortterm memory} # {longterm memory} # " # """ # mark = "$(a.step)" # prompt = # """ # <|im_start|>system # {role} # {tools} # {thinkingFormat} # {context} # <|im_end|> # {shorttermMemory} # Thought $(a.step): # """ # prompt = replace(prompt, "{role}" => a.roles[a.role]) # prompt = replace(prompt, "{thinkingFormat}" => a.thinkingFormat[:actor]) # prompt = replace(prompt, "{step}" => a.step) # s = dictToString(a.memory[:shortterm], skiplist=["user:", "Plan 1:"]) # prompt = replace(prompt, "{shorttermMemory}" => s) # toolnames = "" # toollines = "" # for (toolname, v) in a.tools # toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" # toollines *= toolline # toolnames *= "$toolname, " # end # prompt = replace(prompt, "{toolnames}" => toolnames) # prompt = replace(prompt, "{tools}" => "You have access to the following tools:\n$toollines") # context = # """ # {env state} # {longterm memory} # {plan} # """ # # context = replace(context, "{earlierConversation}" => "My earlier talk with the user:\n$(a.earlierConversation)") # context = replace(context, "{env state}" => "") # context = replace(context, "{longterm memory}" => "") # context = replace(context, "{plan}" => "My plan:\n$(a.memory[:shortterm]["Plan $(a.attempt):"])") # prompt = replace(prompt, "{context}" => context) # return prompt # end """ Chat with llm. ```jldoctest julia> using JSON3, UUIDs, Dates, FileIO, CommUtils, ChatAgent julia> mqttClientSpec = ( clientName= "someclient", # name of this client clientID= "$(uuid4())", broker= "mqtt.yiem.cc", pubtopic= (imgAI="img/api/v0.0.1/gpu/request", txtAI="txt/api/v0.1.0/gpu/request"), subtopic= (imgAI="agent/api/v0.1.0/img/response", txtAI="agent/api/v0.1.0/txt/response"), keepalive= 30, ) julia> msgMeta = Dict( :msgPurpose=> "updateStatus", :from=> "agent", :to=> "llmAI", :requestresponse=> "request", :sendto=> "", # destination topic :replyTo=> "agent/api/v0.1.0/txt/response", # requester ask responseer to send reply to this topic :repondToMsgId=> "", # responseer is responseing to this msg id :taskstatus=> "", # "complete", "fail", "waiting" or other status :timestamp=> Dates.now(), :msgId=> "$(uuid4())", ) julia> newAgent = ChatAgent.agentReact( "Jene", mqttClientSpec, role=:assistant_react, msgMeta=msgMeta ) julia> response = ChatAgent.conversation(newAgent, "Hi! how are you?") ``` """ function conversation(a::agentReflex, usermsg::String; attemptlimit::Int=3) a.attemptlimit = attemptlimit workstate = nothing response = nothing # a.earlierConversation = conversationSummary(a) _ = addNewMessage(a, "user", usermsg) isusetools = isUseTools(a) # newinfo = extractinfo(a, usermsg) # a.env = newinfo !== nothing ? updateEnvState(a, newinfo) : a.env @show isusetools if isusetools # use tools before responseing workstate, response = work(a) end # if LLM using chatbox, use returning msg form chatbox as conversation response if workstate == "chatbox" #TODO paraphrase msg so that it is human friendlier word. else response = chat_mistral_openorca(a) end response = removeTrailingCharacters(response) _ = addNewMessage(a, "assistant", response) return response end """ Continuously run llm functions except when llm is getting Answer: or chatbox. There are many work() depend on thinking mode. """ function work(a::agentReflex) workstate = nothing response = nothing while true # Work loop a.memory[:shortterm] = OrderedDict{String, Any}() a.memory[:log] = OrderedDict{String, Any}() # plan if a.attempt <= a.attemptlimit toolname = nothing toolinput = nothing a.step = 0 objective, plan = planner_mistral_openorca(a) println("") @show objective @show plan # sometimes LLM add not-need word I don't want # plan = splittext(response, ["Step 1", "<|im_end|>", "Response", "Execution", # "Result", "Recommendation", "My response"]) # plan = replace(plan, "Plan:"=>"") # println("") # @show plan a.memory[:shortterm]["Plan $(a.attempt):"] = plan a.memory[:log]["Plan $(a.attempt):"] = plan println("") @show a.attempt # enter actor loop actorstate, msgToUser = actor(a) #WORKING if actorstate == "chatbox" response = msgToUser workstate = actorstate break elseif actorstate == "all steps done" println("all steps done") response = formulateUserresponse(a) println("") formulatedresponse = response @show formulatedresponse a.memory[:shortterm]["response $(a.attempt):"] = response a.memory[:log]["response $(a.attempt):"] = response # evaluate. if score > 6/10 good enough. guideline = writeEvaluationGuideline(a, objective) println("") @show guideline score = grading(a, guideline, response) @show score if score > 5 # good enough answer a.memory[:shortterm] = OrderedDict{String, Any}() a.memory[:log] = OrderedDict{String, Any}() break else # self evaluate and reflect then try again analysis = analyze(a) println("") @show analysis lessonwithcontext = selfReflext(a, analysis) println("") @show lessonwithcontext newdict = OrderedDict() a.memory[:shortterm] = keepOnlyKeys(a.memory[:shortterm], ["user:"]) headerToDetect = ["Lesson:", "Context:", ] headers = detectCharacters(lessonwithcontext, headerToDetect) chunkedtext = chunktext(lessonwithcontext, headers) a.memory[:longterm][chunkedtext["Context:"]] = chunkedtext["Lesson:"] a.attempt += 1 println("") println("RETRY $(a.attempt +1)") println("") end else error("undefied condition, actorstate $actorstate $(@__LINE__)") break end else error("attempt limit reach") break end end # good enough answer return workstate, response end # function work(a::agentReflex, usermsg::String) # response = nothing # a.memory[:shortterm] = OrderedDict{String, Any}() # a.memory[:log] = OrderedDict{String, Any}() # a.memory[:shortterm]["user:"] = usermsg # a.memory[:log]["user:"] = usermsg # a.newplan = true # while true # Work loop # # plan # if a.attempt <= a.attemptlimit # toolname = nothing # toolinput = nothing # if a.newplan == true # a.attempt += 1 # a.step = 0 # prompt_plan = planner_mistral_openorca(a) # println("") # @show prompt_plan # response = sendReceivePrompt(a, prompt_plan, max_tokens=1024) # # sometimes LLM add not-need word I don't want # plan = splittext(response, ["Step 1", "<|im_end|>", "Response", "Execution", # "Result", "Recommendation", "My response"]) # # plan = replace(plan, "Plan:"=>"") # println("") # @show plan # a.newplan = false # a.memory[:shortterm]["Plan $(a.attempt):"] = plan # a.memory[:log]["Plan $(a.attempt):"] = plan # end # println("") # @show a.attempt # # enter actor loop # actorstate, msgToUser = actor(a) # if actorstate == "chatbox" # response = msgToUser # break # elseif actorstate == "all steps done" || actorstate == "formulateUserresponse" # println("all steps done") # response = formulateUserresponse(a) # println("") # formulatedresponse = response # @show formulatedresponse # a.memory[:shortterm]["response $(a.attempt):"] = response # a.memory[:log]["response $(a.attempt):"] = response # # evaluate. if score < 8/10 try again. # guideline = writeEvaluationGuideline(a, a.memory[:shortterm]["user:"]) # println("") # @show guideline # score = grading(a, guideline, response) # @show score # if score >= 6 # good enough answer # break # else # self evaluate and reflect then try again # analysis = analyze(a) # println("") # @show analysis # lessonwithcontext = selfReflext(a, analysis) # println("") # @show lessonwithcontext # newdict = OrderedDict() # a.memory[:shortterm] = keepOnlyKeys(a.memory[:shortterm], ["user:"]) # headerToDetect = ["Lesson:", "Context:", ] # headers = detectCharacters(lessonwithcontext, headerToDetect) # chunkedtext = chunktext(lessonwithcontext, headers) # a.memory[:longterm][chunkedtext["Context:"]] = chunkedtext["Lesson:"] # a.newplan = true # println("") # println("RETRY $(a.attempt +1)") # println("") # end # else # error("undefied condition, actorstate $actorstate $(@__LINE__)") # break # end # else # error("attempt limit reach") # break # end # end # # good enough answer # # communicates with user # _ = addNewMessage(a, "assistant", response) # return response # end # function work(a::agentReflex, usermsg::String) # response = nothing # if a.thinkingmode == :new_thinking # _ = addNewMessage(a, "user", usermsg) # a.memory[:shortterm] = OrderedDict{String, Any}() # a.memory[:log] = OrderedDict{String, Any}() # a.memory[:shortterm]["user:"] = usermsg # a.memory[:log]["user:"] = usermsg # a.newplan = true # elseif a.thinkingmode == :continue_thinking # println("continue_thinking!!") # _ = addNewMessage(a, "user", usermsg) # a.memory[:shortterm]["Obs $(a.step):"] = usermsg # a.memory[:log]["Obs $(a.step):"] = usermsg # else # error("undefined condition thinkingmode = $thinkingmode $(@__LINE__)") # end # while true # Work loop # # plan # if a.attempt <= a.attemptlimit # toolname = nothing # toolinput = nothing # if a.newplan == true # a.attempt += 1 # a.step = 0 # prompt_plan = planner_mistral_openorca(a) # println("") # @show prompt_plan # response = sendReceivePrompt(a, prompt_plan, max_tokens=1024) # # sometimes LLM add not-need word I don't want # plan = splittext(response, ["Step 1", "<|im_end|>", "Response", "Execution", # "Result", "Recommendation", "My response"]) # # plan = replace(plan, "Plan:"=>"") # println("") # @show plan # a.newplan = false # a.memory[:shortterm]["Plan $(a.attempt):"] = plan # a.memory[:log]["Plan $(a.attempt):"] = plan # end # println("") # @show a.attempt # # enter actor loop # actorstate, msgToUser = actor(a) # if actorstate == "chatbox" # response = msgToUser # break # elseif actorstate == "all steps done" || actorstate == "formulateUserresponse" # println("all steps done") # response = formulateUserresponse(a) # println("") # formulatedresponse = response # @show formulatedresponse # a.memory[:shortterm]["response $(a.attempt):"] = response # a.memory[:log]["response $(a.attempt):"] = response # # evaluate. if score < 8/10 try again. # guideline = writeEvaluationGuideline(a, a.memory[:shortterm]["user:"]) # println("") # @show guideline # score = grading(a, guideline, response) # @show score # if score >= 6 # good enough answer # break # else # self evaluate and reflect then try again # analysis = analyze(a) # println("") # @show analysis # lessonwithcontext = selfReflext(a, analysis) # println("") # @show lessonwithcontext # newdict = OrderedDict() # a.memory[:shortterm] = keepOnlyKeys(a.memory[:shortterm], ["user:"]) # headerToDetect = ["Lesson:", "Context:", ] # headers = detectCharacters(lessonwithcontext, headerToDetect) # chunkedtext = chunktext(lessonwithcontext, headers) # a.memory[:longterm][chunkedtext["Context:"]] = chunkedtext["Lesson:"] # a.newplan = true # println("") # println("RETRY $(a.attempt +1)") # println("") # end # else # error("undefied condition, actorstate $actorstate $(@__LINE__)") # break # end # else # error("attempt limit reach") # break # end # end # # good enough answer # # communicates with user # _ = addNewMessage(a, "assistant", response) # return response # end # function evaluate() # end """ Actor function. Arguments: a, one of ChatAgent's agent. plan, a step by step plan to response Return: case 1) if actor complete the plan successfully. actorState = "all steps done" inidicates that all step in plan were done. msgToUser = nothing. case 2) if actor needs to talk to user for more context actorState = "chatbox" msgToUser = "message from assistant to user" """ function actor(a::agentReflex) actorState = nothing msgToUser = nothing totalsteps = checkTotalStepInPlan(a) while true # Actor loop # decide whether to repeat step or do the next step if a.step == 0 a.step = 1 else decision, reason = goNogo(a) println("") @show decision @show reason # a.memory[:shortterm]["Check $(a.step):"] = reason if decision == "Yes" # in case there is a cancel, go straight to evaluation a.step += 1 elseif decision == "No" # repeat the latest step a.memory[:shortterm] = removeHeaders(a.memory[:shortterm], a.step, ["Plan"]) a.memory[:log] = removeHeaders(a.memory[:log], a.step, ["Plan"]) println("repeating step $(a.step)") else error("undefined condition decision = $decision $(@__LINE__)") end end @show a.step if a.step < totalsteps -1 # the last step of the plan is responding, let work() do this part #WORKING check whether LLM already complete the current step iscomplete = checkStepCompletion(a) if iscomplete == false prompt_actor = actor_mistral_openorca(a) println("") @show prompt_actor response = sendReceivePrompt(a, prompt_actor) response = splittext(response, ["Obs", "<|im_end|>"]) if !occursin("Thought", response) response = "Thought: " * response end headerToDetect = ["Question:", "Plan:", "Thought:", "Act:", "Actinput:", "Obs:", "...", "Answer:", "Conclusion:", "Summary:"] # replace headers with headers with correct attempt and step number response = replaceHeaders(response, headerToDetect, a.step) headers = detectCharacters(response, headerToDetect) println("") response_actor = response @show response_actor headerToDetect = ["Plan $(a.attempt):", "Thought $(a.step):", "Act $(a.step):", "Actinput $(a.step):", "Obs $(a.step):", "Check $(a.step):",] headers = detectCharacters(response, headerToDetect) chunkedtext = chunktext(response, headers) # add to memory a.memory[:shortterm] = addShortMem!(a.memory[:shortterm], chunkedtext) a.memory[:log] = addShortMem!(a.memory[:log], chunkedtext) toolname = toolNameBeingCalled(chunkedtext["Act $(a.step):"], a.tools) toolinput = chunkedtext["Actinput $(a.step):"] @show toolname @show toolinput if toolname == "chatbox" # chat with user msgToUser = toolinput actorState = toolname break elseif toolname == "skipstep" a.step += 1 else # function call f = a.tools[toolname][:func] toolresult = f(a, toolinput) @show toolresult a.memory[:shortterm]["Obs $(a.step):"] = toolresult a.memory[:log]["Obs $(a.step):"] = toolresult end else # already complete this step, go to the next step end else actorState = "all steps done" msgToUser = nothing break end end return actorState, msgToUser end """ Write evaluation guideline. Arguments: a, one of ChatAgent's agent. usermsg, stimulus e.g. question, task and etc. Return: An evaluation guideline used to guage AI's work. # Example ```jldoctest julia> using ChatAgent, CommUtils julia> agent = ChatAgent.agentReflex("Jene") julia> usermsg = "What's AMD latest product?" " julia> evaluationGuideLine = writeEvaluationGuideline(agent, usermsg) ``` """ function writeEvaluationGuideline(a::agentReflex, usermsg::T) where {T<:AbstractString} prompt = """ <|im_start|>system You have access to the following tools: chatbox: Useful for when you need to ask a customer for more context. Input should be a conversation to customer. wikisearch: Useful for when you need to search an encyclopedia Input is keywords and not a question. Your work: $usermsg Your job are: 1. Write an evaluation guideline for your work in order to be able to evaluate your response. 2. An example of what the response should be. <|im_end|> """ response = sendReceivePrompt(a, prompt) return response end """ Determine a score out of 10 according to evaluation guideline. Arguments: a, one of ChatAgent's agent. guidelines, an evaluation guideline. shorttermMemory, a short term memory that logs what happened. Return: A score out of 10 based on guideline. # Example ```jldoctest julia> using ChatAgent, CommUtils julia> agent = ChatAgent.agentReflex("Jene") julia> shorttermMemory = OrderedDict{String, Any}( "user" => "What's the latest AMD GPU?", "Plan 1:" => " To answer this question, I will need to search for the latest AMD GPU using the wikisearch tool.\n", "Act 1:" => " wikisearch\n", "Actinput 1:" => " amd gpu latest\n", "Obs 1:" => "No info available for your search query.", "Act 2:" => " wikisearch\n", "Actinput 2:" => " amd graphics card latest\n", "Obs 2:" => "No info available for your search query.") julia> guideline = "\nEvaluation Guideline:\n1. Check if the user's question has been understood correctly.\n2. Evaluate the steps taken to provide the information requested by the user.\n3. Assess whether the correct tools were used for the task.\n4. Determine if the user's request was successfully fulfilled.\n5. Identify any potential improvements or alternative approaches that could be used in the future.\n\nThe response should include:\n1. A clear understanding of the user's question.\n2. The steps taken to provide the information requested by the user.\n3. An evaluation of whether the correct tools were used for the task.\n4. A confirmation or explanation if the user's request was successfully fulfilled.\n5. Any potential improvements or alternative approaches that could be used in the future." julia> score = grading(agent, guideline, shorttermMemory) 2 ``` """ function grading(a, guideline::T, text::T) where {T<:AbstractString} prompt = """ <|im_start|>system You have access to the following tools: chatbox: Useful for when you need to ask a customer for more context. Input should be a conversation to customer. wikisearch: Useful for when you need to search an encyclopedia Input is keywords and not a question. $guideline Your response: $text You job are: 1. Evaluate your response using the evaluation guideline and an example response. 2. Give yourself a score out of 10 for your response. Use the following format to answer: {Evaluation} Score {}/10. <|im_end|> """ println("") prompt_grading = prompt @show prompt_grading response = sendReceivePrompt(a, prompt) println("") response_grading = response @show response_grading _score = split(response[end-5:end], "/")[1] _score = split(_score, " ")[end] score = parse(Int, _score) return score end """ Analize work. Arguments: a, one of ChatAgent's agent. Return: A report of analized work. # Example ```jldoctest julia> using ChatAgent, CommUtils julia> agent = ChatAgent.agentReflex("Jene") julia> shorttermMemory = OrderedDict{String, Any}( "user:" => "What's the latest AMD GPU?", "Plan 1:" => " To answer this question, I will need to search for the latest AMD GPU using the wikisearch tool.\n", "Act 1:" => " wikisearch\n", "Actinput 1:" => " amd gpu latest\n", "Obs 1:" => "No info available for your search query.", "Act 2:" => " wikisearch\n", "Actinput 2:" => " amd graphics card latest\n", "Obs 2:" => "No info available for your search query.") julia> report = analyze(agent, shorttermMemory) ``` """ function analyze(a) shorttermMemory = dictToString(a.memory[:shortterm]) prompt = """ <|im_start|>system You have access to the following tools: chatbox: Useful for when you need to ask a customer for more context. Input should be a conversation to customer. wikisearch: Useful for when you need to search an encyclopedia Input is keywords and not a question. Your work: $shorttermMemory You job is to do each of the following steps in detail to analize your work. 1. What happened? 2. List all relationships, each with cause and effect. 3. Look at each relationship, figure out why it behaved that way. 4. What could you do to improve the response? <|im_end|> <|im_start|>assistant """ response = sendReceivePrompt(a, prompt, max_tokens=1024, timeout=180) return response end """ Write a lesson drawn from evaluation. Arguments: a, one of ChatAgent's agent. report, a report resulted from analyzing shorttermMemory Return: A lesson. # Example ```jldoctest julia> using ChatAgent, CommUtils julia> agent = ChatAgent.agentReflex("Jene") julia> report = "What happened: I tried to search for AMD's latest product using the wikisearch tool, but no information was available in the search results. Cause and effect relationships: 1. Searching \"AMD latest product\" -> No info available. 2. Searching \"most recent product release\" -> No info available. 3. Searching \"latest product\" -> No info available. Analysis of each relationship: 1. The search for \"AMD latest product\" did not provide any information because the wikisearch tool could not find relevant results for that query. 2. The search for \"most recent product release\" also did not yield any results, indicating that there might be no recent product releases available or that the information is not accessible through the wikisearch tool. 3. The search for \"latest product\" similarly resulted in no information being found, suggesting that either the latest product is not listed on the encyclopedia or it is not easily identifiable using the wikisearch tool. Improvements: To improve the response, I could try searching for AMD's products on a different source or search engine to find the most recent product release. Additionally, I could ask the user for more context or clarify their question to better understand what they are looking for." julia> lesson = selfReflext(agent, report) ``` """ function selfReflext(a, analysis::T) where {T<:AbstractString} prompt = """ <|im_start|>system You have access to the following tools: chatbox: Useful for when you need to ask a customer for more context. Input should be a conversation to customer. wikisearch: Useful for when you need to search an encyclopedia Input is keywords and not a question. Your report: $analysis Your job are: 1. Lesson: what lesson could you learn from your report?. 2. Context: what is the context this lesson could apply to? <|im_end|> """ response = sendReceivePrompt(a, prompt, max_tokens=2048) return response end """ Formulate a response from work for user's stimulus. Arguments: a, one of ChatAgent's agent. Return: A response for user's stimulus. # Example ```jldoctest julia> using ChatAgent, CommUtils julia> agent = ChatAgent.agentReflex("Jene") julia> shorttermMemory = OrderedDict{String, Any}( "user:" => "What's the latest AMD GPU?", "Plan 1:" => " To answer this question, I will need to search for the latest AMD GPU using the wikisearch tool.\n", "Act 1:" => " wikisearch\n", "Actinput 1:" => " amd gpu latest\n", "Obs 1:" => "No info available for your search query.", "Act 2:" => " wikisearch\n", "Actinput 2:" => " amd graphics card latest\n", "Obs 2:" => "No info available for your search query.") julia> report = formulateUserresponse(agent, shorttermMemory) ``` """ function formulateUserresponse(a) conversation = messagesToString_nomark(a.messages, addressAIas="I") work = dictToString(a.memory[:shortterm]) prompt = """ <|im_start|>system Symbol: Plan: a plan Thought: your thought Act: the action you took Actinput: the input to the action Obs: the result of the action Your talk with the user: $conversation Your work: $work From your talk with the user and your work, formulate a response for the user. <|im_end|> response: """ response = sendReceivePrompt(a, prompt) return response end """ Extract important info from text into key-value pair text. Arguments: a, one of ChatAgent's agent. text, a text you want to extract info Return: key-value pair text. # Example ```jldoctest julia> using ChatAgent julia> agent = ChatAgent.agentReflex("Jene") julia> text = "We are holding a wedding party at the beach." julia> extract(agent, text) "location=beach, event=wedding party" ``` """ function extractinfo(a, text::T) where {T<:AbstractString} # determine whether there are any important info in an input text prompt = """ <|im_start|>system User's message: $text Your job is determine whether there are important info in the user's message. Answer: {Yes/No/Not sure} <|im_end|> Answer: """ response = sendReceivePrompt(a, prompt, temperature=0.0) if occursin("Yes", response) prompt = """ <|im_start|>system User's message: $text Your job is to extract important info from the user's message into keys and values using this format: key=value,. p.s.1 you can extract many key-value pairs. <|im_end|> """ response = sendReceivePrompt(a, prompt, temperature=0.0) return response else return nothing end end """ Update important info from key-value pair text into another key-value pair text. Arguments: a, one of ChatAgent's agent text, a key-value pair text Return: updated key-value pair text # Example ```jldoctest julia> using ChatAgent julia> agent = ChatAgent.agentReflex("Jene") julia> currentinfo = "location=beach, event=wedding party" julia> newinfo = "wine_type=full body, dry and medium tannin\nprice_range=50 dollars" julia> updateEnvState(agent, currentinfo, newinfo) " location=beach, event=wedding party, wine_type=full body, dry and medium tannin, price_range=50 dollars" ``` """ function updateEnvState(a, newinfo) prompt = """ <|im_start|>system Current state: $(a.env) New info: $newinfo Your job is to update or add information from new info into the current state which use key-value format. <|im_end|> Updated Current State:\n """ response = sendReceivePrompt(a, prompt, temperature=0.0) return response end """ Determine whether LLM should go to next step. Arguments: a, one of ChatAgent's agent. Return: "Yes" or "no" decision to go next step. # Example ```jldoctest julia> using ChatAgent, CommUtils julia> agent = ChatAgent.agentReflex("Jene") julia> shorttermMemory = OrderedDict{String, Any}( "user:" => "What's the latest AMD GPU?", "Plan 1:" => " To answer this question, I will need to search for the latest AMD GPU using the wikisearch tool.\n", "Act 1:" => " wikisearch\n", "Actinput 1:" => " amd gpu latest\n", "Obs 1:" => "No info available for your search query.", "Act 2:" => " wikisearch\n", "Actinput 2:" => " amd graphics card latest\n", "Obs 2:" => "No info available for your search query.") julia> decision = goNogo(agent) "Yes" ``` """ function goNogo(a) # stimulus = a.memory[:shortterm]["user:"] work = dictToString(a.memory[:shortterm]) prompt = """ <|im_start|>system Symbol meaning: Plan: a plan Thought: your thought Act: the action you took Actinput: the input to the action Obs: the result of the action Your work: $work Your job is to check whether step $(a.step) of your work is completed according to the plan and choose only one of the following choices. choice 1: If you get what you intend to do and you are ready to do the next step of the plan say, "{Yes}". And what is the rationale behind the decision to do the next step? choice 2: If you didn't get what you intend to do and you need to repeat the latest step say, "{No}". And what is the rationale behind the decision to repeat the latest step? <|im_end|> <|im_start|>assistant """ response = sendReceivePrompt(a, prompt) decision = nothing reason = nothing if occursin("Yes", response) decision = "Yes" elseif occursin("No", response) decision = "No" else error("undefied condition, decision $decision $(@__LINE__)") end startInd = findfirst(decision, response)[end] +2 if occursin(":", response[startInd:end]) # check for ":" after decision cha startInd2 = findnext(":", response, startInd)[end]+1 reason = response[startInd2:end] else reason = response[startInd:end] end return decision, reason end function checkStepCompletion(a::agentReflex) result = false #WORKING I need current step of the plan plan = "Plan $(a.attempt):" plan = a.memory[:shortterm][plan] prompt = """ <|im_start|>system Symbol meaning: Plan: a plan Thought: your thought Act: the action you took Actinput: the input to the action Obs: the result of the action Your plan: $plan What is step $(a.step) of the plan? <|im_end|> <|im_start|>assistant """ response = sendReceivePrompt(a, prompt) response = split(response, "<|im_end|>")[1] # mistral 7B already know info example: 2. Determine the occasion (wedding party). if occursin("(", response) && occursin(")", response) result = true end return result end end # module