diff --git a/src/interface.jl b/src/interface.jl index 40ee022..e926f52 100755 --- a/src/interface.jl +++ b/src/interface.jl @@ -187,8 +187,8 @@ function planner_mistral_openorca(a::agentReflex) The required info you need for wine recommendation: - type of food: ask the user - occasion: ask the user - - type of wine (red, white, etc): ask the user - - user's personal taste of wine characteristic (sweetness, acidity, etc): ask the user + - type of wine (Rose, White, Red and Sparkling): ask the user + - user's personal taste of wine characteristic: ask the user - wine price range: ask the user - ambient temperature at the serving location: ask the user - wines we have in stock @@ -199,11 +199,9 @@ function planner_mistral_openorca(a::agentReflex) Your earlier work: $shorttermMemory - Your task is to do the following: - Plan: first you should always think about your conversation with the user and your earlier work thoroughly then extract and devise a complete, step by step plan to achieve your objective (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 if you don't have info. - P.S.3 mark a completed step with done keyword. + Your job is to do the following: + Plan: first you should always think about your conversation with the user and your earlier work thoroughly then extract and devise a complete, task by task plan to achieve your objective (pay attention to correct numeral calculation and commonsense). + P.S.1 each task of the plan should be a single action. <|/s|> $conversation <|assistant|> @@ -211,97 +209,12 @@ function planner_mistral_openorca(a::agentReflex) """ plan = sendReceivePrompt(a, assistant_plan_prompt, max_tokens=512, temperature=0.1) - plan = split(plan, "<|user|>")[1] - plan = split(plan, "<|assistant|>")[1] + plan = split(plan, "<|")[1] plan = split(plan, "\n\n")[1] return 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} -# " -# """ - -# conversation = messagesToString(a.messages) -# toollines = "" -# for (toolname, v) in a.tools -# if toolname ∉ [""] -# toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" -# toollines *= toolline -# end -# end - -# # skip objective and plan because LLM is going to generate new plan -# shorttermMemory = dictToString(a.memory[:shortterm], skiplist=["Objective:", "Plan 1:"]) - -# assistant_plan_prompt = -# """ -# <|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 wine based on the user's info above by describing the benefits of each wine in detail. - -# You have access to the following tools: -# $toollines - -# Your earlier work: -# $shorttermMemory - -# Use the following format: -# Objective: what do you think the user needs? -# Plan: first you should always think about your conversation with the user and your earlier work 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 if you don't have info. -# <|/s|> -# $conversation -# <|assistant|> -# Objective: -# """ - - -# result = sendReceivePrompt(a, assistant_plan_prompt, max_tokens=512, temperature=0.1) - -# x = split(result, "<|user|>")[1] -# x = split(result, "<|assistant|>")[1] - -# x = split(x, "Step")[1] -# x = split(x, "Plan:") -# objective = x[1] -# plan = x[2] - -# return objective, plan -# end - """ Update the current plan. """ function updatePlan(a::agentReflex) @@ -353,7 +266,7 @@ function updatePlan(a::agentReflex) end -function actor_mistral_openorca(a::agentReflex) +function actor_mistral_openorca(a::agentReflex, taskevaluate="") """ general prompt format: @@ -407,19 +320,21 @@ function actor_mistral_openorca(a::agentReflex) $toollines Your earlier work: $shorttermMemory + Your task evaluation: + $taskevaluate $(a.thinkingFormat[:actor]) <|/s|> <|assistant|> - Thought $(a.step): + Thought: """ prompt = replace(prompt, "{toolnames}" => toolnames) - prompt = replace(prompt, "{step}" => a.step) println("") @show prompt_actor = prompt response = nothing chunkedtext = nothing + latestTask = nothing tempcounter = 0.0 while true # while Thought or Act is empty, run actor again @@ -427,17 +342,18 @@ function actor_mistral_openorca(a::agentReflex) @show tempcounter response = sendReceivePrompt(a, prompt, temperature=tempcounter) response = splittext(response, ["Obs", "<|im_end|>"]) - + #WORKING + latestTask = shortMemLatestTask(a.memory[:shortterm]) +1 if !occursin("Thought", response) - response = "Thought $(a.step): " * response + response = "Thought $latestTask: " * 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) + # replace headers with headers with correct attempt and task number + response = replaceHeaders(response, headerToDetect, latestTask) response = split(response, "\n\n ")[1] headers = detectCharacters(response, headerToDetect) @@ -446,39 +362,40 @@ function actor_mistral_openorca(a::agentReflex) @show response_actor = response headerToDetect = ["Plan $(a.attempt):", - "Thought $(a.step):", - "Act $(a.step):", - "Actinput $(a.step):", - "Obs $(a.step):", - "Check $(a.step):",] + "Thought $latestTask:", + "Act $latestTask:", + "Actinput $latestTask:", + "Obs $latestTask:", + "Check $latestTask:",] headers = detectCharacters(response, headerToDetect) chunkedtext = chunktext(response, headers) # assuming length more than 10 character means LLM has valid thinking - @show iskey_Thought = haskey(chunkedtext, "Thought $(a.step):") - @show iskey_Act = haskey(chunkedtext, "Act $(a.step):") - @show iskey_Actinput = haskey(chunkedtext, "Actinput $(a.step):") + @show iskey_Thought = haskey(chunkedtext, "Thought $latestTask:") + @show iskey_Act = haskey(chunkedtext, "Act $latestTask:") + @show iskey_Actinput = haskey(chunkedtext, "Actinput $latestTask:") if iskey_Thought && iskey_Act && iskey_Actinput - if chunkedtext["Thought $(a.step):"] != " " && chunkedtext["Act $(a.step):"] != " " + if chunkedtext["Thought $latestTask:"] != " " && chunkedtext["Act $latestTask:"] != " " break end end end - toolname = toolNameBeingCalled(chunkedtext["Act $(a.step):"], a.tools) - toolinput = chunkedtext["Actinput $(a.step):"] + toolname = toolNameBeingCalled(chunkedtext["Act $latestTask:"], a.tools) + chunkedtext["Actinput $latestTask:"] = split(chunkedtext["Actinput $latestTask:"], '\n')[1] + toolinput = chunkedtext["Actinput $latestTask:"] # change trailing number to continue a.memory[:shortterm] headerToDetect = ["Question:", "Plan:", "Thought:", "Act:", "Actinput:", "Obs:", "...", "Answer:", "Conclusion:", "Summary:"] - response = replaceHeaders(response, headerToDetect, a.step) + response = replaceHeaders(response, headerToDetect, latestTask) headerToDetect = ["Plan $(a.attempt):", - "Thought $(a.step):", - "Act $(a.step):", - "Actinput $(a.step):", - "Obs $(a.step):", - "Check $(a.step):",] + "Thought $latestTask:", + "Act $latestTask:", + "Actinput $latestTask:", + "Obs $latestTask:", + "Check $latestTask:",] headers = detectCharacters(response, headerToDetect) chunkedtext = chunktext(response, headers) @@ -562,10 +479,10 @@ function work(a::agentReflex) # user answering LLM -> Obs if length(a.memory[:shortterm]) != 0 - latestStep = shortMemLatestStep(a.memory[:shortterm]) - if haskey(a.memory[:shortterm], "Act $latestStep:") - if occursin("chatbox", a.memory[:shortterm]["Act $latestStep:"]) - a.memory[:shortterm]["Obs $latestStep:"] = a.messages[end][:content] + latestTask = shortMemLatestTask(a.memory[:shortterm]) + if haskey(a.memory[:shortterm], "Act $latestTask:") + if occursin("chatbox", a.memory[:shortterm]["Act $latestTask:"]) + a.memory[:shortterm]["Obs $latestTask:"] = a.messages[end][:content] end end end @@ -578,7 +495,7 @@ function work(a::agentReflex) plan = planner_mistral_openorca(a) a.memory[:shortterm]["Plan $(a.attempt):"] = plan a.memory[:log]["Plan $(a.attempt):"] = plan - a.step = 1 # reset because new plan is created + a.task = 1 # reset because new plan is created println("") @show plan println("") @@ -596,8 +513,8 @@ function work(a::agentReflex) response = msgToUser workstate = actorstate break - elseif actorstate == "all steps done" - println("all steps done") + elseif actorstate == "all tasks done" + println("all tasks done") response = formulateUserresponse(a) @@ -640,7 +557,7 @@ function work(a::agentReflex) a.memory[:longterm][chunkedtext["Context:"]] = chunkedtext["Lesson:"] a.attempt += 1 - a.step = 0 + a.task = 0 a.memory[:shortterm] = OrderedDict{String, Any}() a.memory[:log] = OrderedDict{String, Any}() println("") @@ -667,11 +584,11 @@ end Arguments: a, one of ChatAgent's agent. - plan, a step by step plan to response + plan, a task by task plan to response Return: case 1) if actor complete the plan successfully. - actorState = "all steps done" inidicates that all step in plan were done. + actorState = "all tasks done" inidicates that all task in plan were done. msgToUser = nothing. case 2) if actor needs to talk to user for more context actorState = "chatbox" @@ -683,153 +600,41 @@ function actor(a::agentReflex) actorState = nothing msgToUser = nothing - totalsteps = checkTotalStepInPlan(a) + totaltasks = checkTotalTaskInPlan(a) while true # Actor loop - @show a.step - # check whether the current step is completed - isstepcomplete, reason = checkStepCompletion(a) - @show stepcompletecheck = reason - iskey_Thought = haskey(a.memory[:shortterm], "Thought $(a.step):") - chunkedtext = nothing + # check whether the current task is completed + istaskcomplete, taskevaluation = checkTaskCompletion(a) + @show taskevaluation + latestTask = shortMemLatestTask(a.memory[:shortterm]) +1 + println(">>> working") + # work + toolname, toolinput, chunkedtext = actor_mistral_openorca(a, taskevaluation) + println("") + @show toolname + @show toolinput + println("") - if isstepcomplete # step complete - println(">>> step +1") - a.step += 1 - elseif isstepcomplete == false && iskey_Thought == false # step not done, do work - println(">>> working") - # work - toolname, toolinput, chunkedtext = actor_mistral_openorca(a) - println("") - @show toolname - @show toolinput - println("") - @show a.step - addShortMem!(a.memory[:shortterm], chunkedtext) - println("") + addShortMem!(a.memory[:shortterm], chunkedtext) + println("") - if toolname == "chatbox" # chat with user - msgToUser = toolinput - msgToUser = split(msgToUser, "\n\n")[1] - actorState = toolname - break - elseif toolname == "noaction" - println(">>> already done") - 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 - elseif isstepcomplete == false && iskey_Thought == true # step done but not complete try again - a.memory[:shortterm] = removeHeaders(a.memory[:shortterm], a.step, ["Plan"]) - println("repeating step $(a.step)") - else - error("undefined condition. isstepcomplete=$isstepcomplete, Thought key=$iskey_Thought $(@__LINE__)") + if toolname == "chatbox" # chat with user + msgToUser = toolinput + actorState = toolname + break + elseif toolname == "noaction" + println(">>> already done") + else # function call + f = a.tools[toolname][:func] + toolresult = f(a, toolinput) + @show toolresult + + a.memory[:shortterm]["Obs $latestTask:"] = toolresult + a.memory[:log]["Obs $latestTask:"] = toolresult end - # if !haskey(a.memory[:shortterm], "Thought $(a.step):") # agent didn't do this step yet. - # # work - # toolname, toolinput, chunkedtext = actor_mistral_openorca(a) - # println("") - # @show toolname - # @show toolinput - # println("") - # @show a.step - # addShortMem!(a.memory[:shortterm], chunkedtext) - # println("") - - # if toolname == "chatbox" # chat with user - # msgToUser = toolinput - # msgToUser = split(msgToUser, "\n\n")[1] - # actorState = toolname - # break - # elseif toolname == "noaction" - # 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 - # elseif haskey(a.memory[:shortterm], "Thought $(a.step):") && isstepcomplete == false - # a.memory[:shortterm] = removeHeaders(a.memory[:shortterm], a.step, ["Plan"]) - # println("repeating step $(a.step)") - # elseif haskey(a.memory[:shortterm], "Thought $(a.step):") && isstepcomplete == true - # println(">>> step +1") - # a.step += 1 - # else - # iskey = haskey(a.memory[:shortterm], "Thought $(a.step):") - # error("undefined condition. Thought key=$iskey, isstepcomplete=$isstepcomplete $(@__LINE__)") - # end - - - - - - - -# if !iscomplete # not yet done -# -# # has Thought of current step means LLM already did this step -# if didthestep = true -# latestStep = shortMemLatestStep(a.memory[:shortterm]) -# a.memory[:shortterm] = removeHeaders(a.memory[:shortterm], latestStep, ["Plan"]) -# println("repeating step $(a.step)") -# end - -# # work -# toolname, toolinput, chunkedtext = actor_mistral_openorca(a) -# println("") -# @show toolname -# @show toolinput -# addShortMem!(a.memory[:shortterm], chunkedtext) - -# if toolname == "chatbox" # chat with user -# msgToUser = toolinput -# msgToUser = split(msgToUser, "\n\n")[1] -# actorState = toolname -# break -# elseif toolname == "noaction" -# 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 #WORKING already done -# a.step +=1 - - - -# #TODO may be let LLM call finalResponse() -# # if a.step > totalsteps # the last step of the plan is responding, let work() do this part -# # actorState = "all steps done" -# # msgToUser = nothing -# # break -# # end - -# end - - - - - - # 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 @@ -905,7 +710,7 @@ julia> shorttermMemory = OrderedDict{String, Any}( "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> guideline = "\nEvaluation Guideline:\n1. Check if the user's question has been understood correctly.\n2. Evaluate the tasks 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 tasks 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 ``` @@ -985,7 +790,7 @@ function analyze(a) Your work: $shorttermMemory - You job is to do each of the following steps in detail to analize your work. + You job is to do each of the following 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. @@ -1202,13 +1007,13 @@ end -""" Determine whether LLM should go to next step. +""" Determine whether LLM should go to next task. Arguments: a, one of ChatAgent's agent. Return: - "Yes" or "no" decision to go next step. + "Yes" or "no" decision to go next task. # Example ```jldoctest @@ -1224,11 +1029,12 @@ julia> shorttermMemory = OrderedDict{String, Any}( "Actinput 2:" => " amd graphics card latest\n", "Obs 2:" => "No info available for your search query.") -julia> decision = checkStepCompletion(agent) +julia> decision = checkTaskCompletion(agent) "Yes" ``` """ -function checkStepCompletion(a) +function checkTaskCompletion(a) + @show a.memory[:shortterm]["Plan 1:"] # stimulus = a.memory[:shortterm]["user:"] work = dictToString(a.memory[:shortterm]) @@ -1245,83 +1051,40 @@ function checkStepCompletion(a) Your earlier work: $work - Your task is to check whether step $(a.step) of your work is completed according to the plan. + Your job is to check whether each task of your plan has been completed. So for instance the following: - Step 2 of the plan: Ask user about their preferred taste of a pizza. + Task 2 of the plan: Ask user about their preferred taste of a pizza. Obs: I love Malvasia. - assistant: I can't find any relevant info that the user tell me their preferred taste in pizza wine according to Thought and Obs. Thus, step 2 isn't done yet. {answer: not done} + assistant: I can't find any relevant info that the user tell me their preferred taste in pizza wine according to Obs. Thus, task 2 isn't done yet. {answer: not done} - Step 5 of the plan: Ask user if they have any preferred type of car. + Task 5 of the plan: Ask user if they have any preferred type of car. Obs: I like a semi truck. - assistant: I found relevant info such as the user like a semi truck according to Obs. Thus, step 5 is done. {answer: done} + assistant: I found relevant info such as the user like a semi truck according to Obs. Thus, task 5 is done. {answer: done} Let's think step by step. - <|assistant|> + <|assistant|> I """ response = nothing _response = nothing while true _response = sendReceivePrompt(a, prompt, max_tokens=512) - response = split(_response, "}")[1] * "}" - if occursin("{", response) + _response = split(_response, "")[1] + if occursin("}", _response) break end end + response = "I " * split(_response, "}")[1] * "}" # sometime response have more than 1 {answer: done} decision = nothing - if occursin("not done", response) - decision = false - elseif occursin("done", response) + if occursin("done", response) decision = true else - error("undefied condition, decision $_response $(@__LINE__)") + decision = false end - + return decision, response end -# function checkStepCompletion(a) -# # stimulus = a.memory[:shortterm]["user:"] -# work = dictToString(a.memory[:shortterm]) -# prompt = -# """ -# <|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 earlier work: -# $work - - -# Your job is to check whether step $(a.step) of your work is completed according to the plan. -# So for instance the following: -# step 2 of the plan: Ask user about the occasion type. But you can't find any relevant info about occasion type in your work. -# assistant: Step 2 isn't done yet. Answer {Not done} -# step 5 of the plan: Ask user if they have any preference for the style of wine. And you found relevant info in your work such as the user like full-bodied wine. -# assistant: Step 5 is done. Answer {done} -# Let's think step by step. -# -# <|assistant|> -# """ - -# response = sendReceivePrompt(a, prompt) -# @show checkStepCompletion_response = response - -# decision = nothing -# if occursin("Not done", response) -# decision = false -# elseif occursin("done", response) -# decision = true -# else -# error("undefied condition, decision $decision $(@__LINE__)") -# end - -# return decision, response -# end """ Direct conversation is not an agent, messages does not pass through logic loop but goes directly to LLM. diff --git a/src/type.jl b/src/type.jl index d0c1bad..36296ef 100644 --- a/src/type.jl +++ b/src/type.jl @@ -95,7 +95,7 @@ julia> agent = ChatAgent.agentReflex( newplan::Bool = false # if true, new plan will be generated attemptlimit::Int = 5 # thinking round limit attempt::Int = 1 # attempted number - step::Int = 1 # step number + task::Int = 1 # task number env::AbstractString = "N/A" thinkingFormat::Union{Dict, Nothing} = nothing memory::Dict = Dict( @@ -166,11 +166,12 @@ function agentReflex( """, :actor=> """ - Your task is to do the following: - Thought: using step {step} of the plan as a guideline, what to do? (pay attention to correct numeral calculation and commonsense). + Your task is to use the following format: + Thought: using the plan and task evaluation as a guideline, what to do? (pay attention to correct numeral calculation and commonsense). Act: the action to take based on your thought, must be one of [{toolnames}] Actinput: your input to the action you chose (pay attention to the tool's input) Obs: observed result of the action + Let's think step by step """, :actorOriginal=> """ diff --git a/src/utils.jl b/src/utils.jl index 66ea2a5..3aef1c5 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,10 +1,10 @@ module utils -export makeSummary, sendReceivePrompt, chunktext, extractStepFromPlan, checkTotalStepInPlan, +export makeSummary, sendReceivePrompt, chunktext, extractStepFromPlan, checkTotalTaskInPlan, detectCharacters, findDetectedCharacter, extract_number, toolNameBeingCalled, isUsePlans, conversationSummary, checkReasonableness, replaceHeaders, addShortMem!, splittext, dictToString, removeHeaders, keepOnlyKeys, experience, - messagesToString, messagesToString_nomark, removeTrailingCharacters, shortMemLatestStep + messagesToString, messagesToString_nomark, removeTrailingCharacters, shortMemLatestTask using UUIDs, Dates, DataStructures using CommUtils, GeneralUtils @@ -210,7 +210,7 @@ function extractStepFromPlan(a::agent, plan::T, step::Int) where {T<:AbstractStr return response end -function checkTotalStepInPlan(a::agent) +function checkTotalTaskInPlan(a::agent) headers = [] for (k, v) in a.memory[:shortterm] @@ -1027,10 +1027,10 @@ Return: ```jldoctest julia> dict = OrderedDict( "Plan 1:" => "1. Ask about the type of food that will be served at the wedding party.") -julia>shortMemLatestStep(dict) +julia>shortMemLatestTask(dict) 1 """ -function shortMemLatestStep(dict::T) where {T<:AbstractDict} +function shortMemLatestTask(dict::T) where {T<:AbstractDict} _latest_step = keys(dict) _latest_step = [i for i in _latest_step] _latest_step = _latest_step[end]