diff --git a/src/interface.jl b/src/interface.jl index 3addc40..8a66e04 100755 --- a/src/interface.jl +++ b/src/interface.jl @@ -3,7 +3,7 @@ module interface export agentReact, agentReflex, addNewMessage, clearMessage, removeLatestMsg, conversation, directconversation, - writeEvaluationGuideline, grading, analyze, selfReflext, actor_mistral_openorca2, + writeEvaluationGuideline, grading, analyze, selfReflext, formulateUserresponse, extractinfo, updateEnvState, chat_mistral_openorca using JSON3, DataStructures, Dates, UUIDs, HTTP @@ -178,7 +178,7 @@ function planner_mistral_openorca(a::agentReflex) end # skip objective and plan because LLM is going to generate new plan - shorttermMemory = dictToString(a.memory[:shortterm], skiplist=["Objective:", "Plan 0:"]) + shorttermMemory = dictToString(a.memory[:shortterm], skiplist=["Objective:", "Plan 1:"]) assistant_plan_prompt = """ @@ -187,7 +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 - - user's personal taste of wine: ask the user + - type of wine (red, white, rose, sparkling, etc): ask the user + - user's personal taste of wine characteristic (sweetness, acidity, etc): ask the user - wine price range: ask the user - ambient temperature at the serving location: ask the user - wines we have in stock @@ -255,7 +256,7 @@ end # end # # skip objective and plan because LLM is going to generate new plan -# shorttermMemory = dictToString(a.memory[:shortterm], skiplist=["Objective:", "Plan 0:"]) +# shorttermMemory = dictToString(a.memory[:shortterm], skiplist=["Objective:", "Plan 1:"]) # assistant_plan_prompt = # """ @@ -348,7 +349,7 @@ function updatePlan(a::agentReflex) result = sendReceivePrompt(a, prompt, max_tokens=512, temperature=0.1) @show updatedPlan = result - a.memory[:shortterm]["Plan 0:"] = result + a.memory[:shortterm]["Plan 1:"] = result end @@ -428,9 +429,9 @@ function actor_mistral_openorca(a::agentReflex) response = splittext(response, ["Obs", "<|im_end|>"]) if !occursin("Thought", response) - response = "Thought: " * response + response = "Thought $(a.step): " * response end - + @show response_actor1 = response headerToDetect = ["Question:", "Plan:", "Thought:", "Act:", "Actinput:", "Obs:", "...", "Answer:", "Conclusion:", "Summary:"] @@ -442,7 +443,7 @@ function actor_mistral_openorca(a::agentReflex) headers = detectCharacters(response, headerToDetect) println("") - @show response_actor = response + @show response_actor2 = response headerToDetect = ["Plan $(a.attempt):", "Thought $(a.step):", @@ -465,14 +466,14 @@ function actor_mistral_openorca(a::agentReflex) toolinput = chunkedtext["Actinput $(a.step):"] # change trailing number to continue a.memory[:shortterm] - _latest_step = keys(a.memory[:shortterm]) - _latest_step = [i for i in _latest_step] - _latest_step = _latest_step[end] - latest_step = parse(Int, _latest_step[end-2:end-1]) + _latestStep = keys(a.memory[:shortterm]) + _latestStep = [i for i in _latestStep] + _latestStep = _latestStep[end] + latestStep = parse(Int, _latestStep[end-2:end-1]) headerToDetect = ["Question:", "Plan:", "Thought:", "Act:", "Actinput:", "Obs:", "...", "Answer:", "Conclusion:", "Summary:"] - nextstep = latest_step+1 # next step in short term memory + nextstep = latestStep+1 # next step in short term memory response = replaceHeaders(response, headerToDetect, nextstep) headerToDetect = ["Plan $(a.attempt):", "Thought $nextstep:", @@ -535,12 +536,12 @@ function conversation(a::agentReflex, usermsg::String; attemptlimit::Int=3) # a.earlierConversation = conversationSummary(a) _ = addNewMessage(a, "user", usermsg) - isusetools = isUseTools(a) + isuseplan = isUsePlans(a) # newinfo = extractinfo(a, usermsg) # a.env = newinfo !== nothing ? updateEnvState(a, newinfo) : a.env - @show isusetools + @show isuseplan - if isusetools # use tools before responseing + if isuseplan # use plan before responding workstate, response = work(a) end @@ -557,21 +558,6 @@ function conversation(a::agentReflex, usermsg::String; attemptlimit::Int=3) return response end -""" Direct conversation is not an agent, messages does not pass through logic loop - but goes directly to LLM. - -""" -function directconversation(a::agentReflex, usermsg::String) - response = nothing - - _ = addNewMessage(a, "user", usermsg) - - response = chat_mistral_openorca(a) - 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. @@ -582,10 +568,10 @@ function work(a::agentReflex) # user answering LLM -> Obs if length(a.memory[:shortterm]) != 0 - latest_step = dictLatestStep(a.memory[:shortterm]) - if haskey(a.memory[:shortterm], "Act $latest_step:") - if occursin("chatbox", a.memory[:shortterm]["Act $latest_step:"]) - a.memory[:shortterm]["Obs $latest_step:"] = a.messages[end][:content] + 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] end end end @@ -601,7 +587,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 = 0 # reset because new plan is created + a.step = 1 # reset because new plan is created println("") @show plan @@ -706,30 +692,15 @@ function actor(a::agentReflex) totalsteps = checkTotalStepInPlan(a) while true # Actor loop - - # decide whether to repeat step or do the next step - decision = "Yes" # yes because a.step start at 0 - if a.step != 0 - decision, reason = goNogo(a) - end - - 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 @show a.step - #WORKING checkStepCompletion - iscomplete = checkStepCompletion(a::agentReflex) - # if iscomplete - - if a.step < totalsteps # the last step of the plan is responding, let work() do this part + # check whether the current step is completed + iscomplete, reason = checkStepCompletion(a) + @show stepcompletecheck = iscomplete + if !iscomplete # not yet done + # work toolname, toolinput = actor_mistral_openorca(a) + println("") @show toolname @show toolinput @@ -747,11 +718,35 @@ function actor(a::agentReflex) a.memory[:shortterm]["Obs $(a.step):"] = toolresult a.memory[:log]["Obs $(a.step):"] = toolresult end - else - actorState = "all steps done" - msgToUser = nothing - break + else # already done + a.step +=1 + # if not add to memory yet. Add + + # 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 return actorState, msgToUser @@ -1144,14 +1139,14 @@ julia> shorttermMemory = OrderedDict{String, Any}( "Actinput 2:" => " amd graphics card latest\n", "Obs 2:" => "No info available for your search query.") -julia> decision = goNogo(agent) +julia> decision = checkStepCompletion(agent) "Yes" ``` """ -function goNogo(a) +function checkStepCompletion(a) # stimulus = a.memory[:shortterm]["user:"] work = dictToString(a.memory[:shortterm]) - + #WORKING prompt = """ <|system|> @@ -1165,76 +1160,91 @@ function goNogo(a) 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 of occasion type in your work. - assistant: Step 2 isn't done yet. {No} - 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. {Yes} + Step 2 of the plan: Ask user about their preferred taste in white wine. + Your interpretation of step 2 of the plan: My understanding of this step is that I need info about their preferred taste in white wine e.g. dryness, sweetness and etc. But I can't find any relevant info that the user tell me their preferred taste in white wine according to Obs. + assistant: Thus, step 2 isn't done yet. Answer: {Not done} + + Step 5 of the plan: Ask user if they have any preferred type of wine. + Your interpretation of step 5 of the plan: My understanding of this step is that I need info about user preferred wine type. And I found relevant info such as the user like full-bodied wine according to Obs. + assistant: Step 5 is done. Answer: {done} + Let's think step by step. <|assistant|> """ - response = sendReceivePrompt(a, prompt) - @show goNogo_response = response + response = sendReceivePrompt(a, prompt, max_tokens=512) + @show checkStepCompletion_response = response decision = nothing - reason = nothing - if occursin("Yes", response) - decision = "Yes" - elseif occursin("No", response) - decision = "No" + if occursin("Not done", response) + decision = false + elseif occursin("done", response) + decision = true 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 + 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 - -function checkStepCompletion(a::agentReflex) - result = false - #WORKING I need current step of the plan - plan = "Plan $(a.attempt):" - plan = a.memory[:shortterm][plan] - - 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 plan: - $plan +# Your earlier work: +# $work - What is step $(a.step) of the plan? - <|/s|> - <|assistant|> - """ +# 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) - - response = split(response, "<|im_end|>")[1] +# response = sendReceivePrompt(a, prompt) +# @show checkStepCompletion_response = response - - - return result +# 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. +""" +function directconversation(a::agentReflex, usermsg::String) + response = nothing + + _ = addNewMessage(a, "user", usermsg) + + response = chat_mistral_openorca(a) + response = removeTrailingCharacters(response) + _ = addNewMessage(a, "assistant", response) + return response end @@ -1285,10 +1295,6 @@ end - - - - diff --git a/src/type.jl b/src/type.jl index d42eeb8..92ad69c 100644 --- a/src/type.jl +++ b/src/type.jl @@ -94,8 +94,8 @@ julia> agent = ChatAgent.agentReflex( tools::Union{Dict, Nothing} = nothing newplan::Bool = false # if true, new plan will be generated attemptlimit::Int = 5 # thinking round limit - attempt::Int = 0 # attempted number - step::Int = 0 # step number + attempt::Int = 1 # attempted number + step::Int = 1 # step number env::AbstractString = "N/A" thinkingFormat::Union{Dict, Nothing} = nothing memory::Dict = Dict( @@ -167,7 +167,7 @@ function agentReflex( :actor=> """ Use the following format: - Thought: you should always think about what to do according to step {step} of the plan (pay attention to correct numeral calculation and commonsense). + Thought: you should always think about what to do to achieve step {step} of the plan (pay attention to correct numeral calculation and commonsense). Act: the action to take that align with your thought, should be one of [{toolnames}] Actinput: your input to the action you chose (pay attention to the tool's input) Obs: the result of the action diff --git a/src/utils.jl b/src/utils.jl index 429a1fc..ea4547b 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -2,9 +2,9 @@ module utils export makeSummary, sendReceivePrompt, chunktext, extractStepFromPlan, checkTotalStepInPlan, detectCharacters, findDetectedCharacter, extract_number, toolNameBeingCalled, - isUseTools, conversationSummary, checkReasonableness, replaceHeaders, + isUsePlans, conversationSummary, checkReasonableness, replaceHeaders, addShortMem!, splittext, dictToString, removeHeaders, keepOnlyKeys, experience, - messagesToString, messagesToString_nomark, removeTrailingCharacters, dictLatestStep + messagesToString, messagesToString_nomark, removeTrailingCharacters, shortMemLatestStep using UUIDs, Dates, DataStructures using CommUtils, GeneralUtils @@ -375,7 +375,7 @@ Return: 1. true/false # is LLM going to use tools 2. objective # what LLM going to do """ -function isUseTools(a::agentReflex) +function isUsePlans(a::agentReflex) toollines = "" for (toolname, v) in a.tools if toolname ∉ ["chatbox"] # LLM will always use chatbox @@ -405,26 +405,26 @@ function isUseTools(a::agentReflex) """ # if LLM mentions any tools, use Plan/Thought/Act loop - isusetool = false + isuseplan = false response = sendReceivePrompt(a, prompt, temperature=0.2, max_tokens=64) response = split(response, "<|assistant|>")[1] response = split(response, "<|user|>")[1] - @show response + for (toolname, v) in a.tools if occursin("Yes", String(response)) - isusetool = true + isuseplan = true break end end if length(a.memory[:shortterm]) != 0 - isusetool = true + isuseplan = true end - return isusetool + return isuseplan end -# function isUseTools(a::agentReflex) +# function isUsePlans(a::agentReflex) # toollines = "" # for (toolname, v) in a.tools # if toolname ∉ ["chatbox"] # LLM will always use chatbox @@ -451,21 +451,21 @@ end # """ # # if LLM mentions any tools, use Plan/Thought/Act loop -# isusetool = false +# isuseplan = false # response = sendReceivePrompt(a, prompt, temperature=0.0) # response = split(response, "<|im_end|>")[1] # for (toolname, v) in a.tools # if occursin(toolname, String(response)) -# isusetool = true +# isuseplan = true # break # end # end # if length(a.memory[:shortterm]) != 0 -# isusetool = true +# isuseplan = true # end -# return isusetool +# return isuseplan # end @@ -1016,8 +1016,7 @@ function experience(dict::T) where {T<:AbstractDict} end -function dictLatestStep(dict::T) where {T<:AbstractDict} - @show dict +function shortMemLatestStep(dict::T) where {T<:AbstractDict} _latest_step = keys(dict) _latest_step = [i for i in _latest_step] _latest_step = _latest_step[end]