diff --git a/src/interface.jl b/src/interface.jl index ee8594c..59e0596 100755 --- a/src/interface.jl +++ b/src/interface.jl @@ -120,19 +120,21 @@ function chat_mistral_openorca(a::agentReflex) " """ + conversation = messagesToString(a.messages) + prompt = """ - <|im_start|>system + <|system|> $(a.roles[a.role]) Your earlier talk with the user: $(a.earlierConversation) - <|im_end|> - $(messagesToString(a.messages)) + <|/s|> + $conversation <|im_start|>assistant - """ response = sendReceivePrompt(a, prompt) + response = split(response, "\n\n")[1] response = split(response, "<|im_end|>")[1] return response @@ -181,7 +183,7 @@ function planner_mistral_openorca(a::agentReflex) assistant_plan_prompt = """ - <|im_start|>system + <|system|> $(a.roles[a.role]) The required info you need for wine recommendation: - type of food: ask the user @@ -190,41 +192,115 @@ function planner_mistral_openorca(a::agentReflex) - 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 work: + Your earlier work: $shorttermMemory - Use the following format: - Objective: the objective you intend to do - Aware: according to your work and your conversation with the user, ask yourself what info you have and what info you doesn't have? - Plan: first you should always think about the objective, the info you have, the info you doesn't have thoroughly then extract and devise a complete, step by step plan (pay attention to correct numeral calculation and commonsense). + 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. - - <|im_end|> + <|/s|> $conversation - <|im_start|>assistant - Objective: + <|assistant|> + Plan: """ - #WORKING remove () in steps as LLM sometimes use for (addtional info) but interfere with updatePlan - - result = sendReceivePrompt(a, assistant_plan_prompt, max_tokens=512, temperature=0.1) + 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, "\n\n")[1] - x = split(result, "<|im_end|>")[1] - @show x - x = split(x, "Step")[1] - x = split(x, "Plan:") - objective = x[1] - plan = x[2] - - return objective, plan + 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 0:"]) + +# 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) @@ -242,7 +318,7 @@ function updatePlan(a::agentReflex) prompt = """ - <|im_start|>system + <|system|> $(a.roles[a.role]) The required info you need for wine recommendation: - wine price range: ask the user @@ -266,13 +342,14 @@ function updatePlan(a::agentReflex) Plan: 1. Ask the user for their food type. Obs: It will be Thai dishes. Updated plan: 1. Ask the user for their food type (Thai dishes). - <|im_end|> + Updated plan: """ result = sendReceivePrompt(a, prompt, max_tokens=512, temperature=0.1) @show updatedPlan = result a.memory[:shortterm]["Plan 0:"] = result + end function actor_mistral_openorca(a::agentReflex) @@ -323,13 +400,15 @@ function actor_mistral_openorca(a::agentReflex) prompt = """ - <|im_start|>system + <|system|> $(a.roles[a.role]) You have access to the following tools: $toollines - $(a.thinkingFormat[:actor]) - <|im_end|> + Your earlier work: $shorttermMemory + $(a.thinkingFormat[:actor]) + <|/s|> + <|assistant|> Thought $(a.step): """ prompt = replace(prompt, "{toolnames}" => toolnames) @@ -342,7 +421,7 @@ function actor_mistral_openorca(a::agentReflex) chunkedtext = nothing tempcounter = 0.0 - while true # while Thought is empty, run actor again + while true # while Thought or Act is empty, run actor again tempcounter += 0.1 @show tempcounter response = sendReceivePrompt(a, prompt, temperature=tempcounter) @@ -375,8 +454,10 @@ function actor_mistral_openorca(a::agentReflex) chunkedtext = chunktext(response, headers) # assuming length more than 10 character means LLM has valid thinking - if length(chunkedtext["Thought $(a.step):"]) > 10 - break + if haskey(chunkedtext, "Thought $(a.step):") && haskey(chunkedtext, "Act $(a.step):") + if length(chunkedtext["Thought $(a.step):"]) > 10 && length(chunkedtext["Act $(a.step):"]) > 10 + break + end end end @@ -483,12 +564,14 @@ end function work(a::agentReflex) workstate = nothing response = nothing - - #BUG there is no Obs 2: + # user answering LLM -> Obs - if haskey(a.memory[:shortterm], "Act $(a.step):") - if occursin("chatbox", a.memory[:shortterm]["Act $(a.step):"]) - a.memory[:shortterm]["Obs $(a.step):"] = a.messages[end][:content] + 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] + end end end @@ -499,41 +582,15 @@ function work(a::agentReflex) if a.attempt <= a.attemptlimit toolname = nothing toolinput = nothing - - # if length(a.memory[:shortterm]) != 0 - # updatePlan(a) - # @show updatedPlan = a.memory[:shortterm]["Plan 0:"] - # else - # objective, plan = planner_mistral_openorca(a) - # a.memory[:shortterm]["Objective:"] = objective - # a.memory[:shortterm]["Plan $(a.attempt):"] = plan - # a.memory[:log]["Plan $(a.attempt):"] = plan - - # println("") - # @show objective - # @show plan - # end - objective, plan = planner_mistral_openorca(a) - a.memory[:shortterm]["Objective:"] = objective + 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 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 - - - println("") @show a.attempt @@ -636,24 +693,25 @@ function actor(a::agentReflex) while true # Actor loop # decide whether to repeat step or do the next step - decision, reason = goNogo(a) - println("") - @show decision - @show reason - # a.memory[:shortterm]["Check $(a.step):"] = reason + 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 + 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 toolname, toolinput = actor_mistral_openorca(a) @@ -933,7 +991,7 @@ function formulateUserresponse(a) prompt = """ - <|im_start|>system + <|system|> Symbol: Plan: a plan Thought: your thought @@ -948,8 +1006,9 @@ function formulateUserresponse(a) $work From your talk with the user and your work, formulate a response for the user. - <|im_end|> - response: + <|/s|> + <|assistant|> + response: """ response = sendReceivePrompt(a, prompt) return response @@ -1080,7 +1139,7 @@ function goNogo(a) prompt = """ - <|im_start|>system + <|system|> Symbol meaning: Plan: a plan Thought: your thought @@ -1088,18 +1147,23 @@ function goNogo(a) Actinput: the input to the action Obs: the result of the action - Your work: + Your earlier 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 - + 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} + + <|assistant|> """ response = sendReceivePrompt(a, prompt) + @show goNogo_response = response + decision = nothing reason = nothing if occursin("Yes", response) @@ -1132,7 +1196,7 @@ function checkStepCompletion(a::agentReflex) prompt = """ - <|im_start|>system + <|system|> Symbol meaning: Plan: a plan Thought: your thought @@ -1144,8 +1208,8 @@ function checkStepCompletion(a::agentReflex) $plan What is step $(a.step) of the plan? - <|im_end|> - <|im_start|>assistant + <|/s|> + <|assistant|> """ @@ -1153,17 +1217,7 @@ function checkStepCompletion(a::agentReflex) response = split(response, "<|im_end|>")[1] - # if occursin("N/A", response) - # response = replace(response, "(N/A)"=>"") - # elseif occursin(response, "(not specified, assume casual)") - # response = replace(response, "(not specified, assume casual)"=>"") - # else - # end - # mistral 7B already know info example: 2. Determine the occasion (wedding party). - if occursin("(", response) && occursin(")", response) - result = true - end return result end diff --git a/src/type.jl b/src/type.jl index 643fb39..d42eeb8 100644 --- a/src/type.jl +++ b/src/type.jl @@ -168,7 +168,7 @@ function agentReflex( """ 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). - Act: the action to take that match your thought, should be one of [{toolnames}] + 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 a7a2f69..429a1fc 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -4,7 +4,7 @@ export makeSummary, sendReceivePrompt, chunktext, extractStepFromPlan, checkTota detectCharacters, findDetectedCharacter, extract_number, toolNameBeingCalled, isUseTools, conversationSummary, checkReasonableness, replaceHeaders, addShortMem!, splittext, dictToString, removeHeaders, keepOnlyKeys, experience, - messagesToString, messagesToString_nomark, removeTrailingCharacters + messagesToString, messagesToString_nomark, removeTrailingCharacters, dictLatestStep using UUIDs, Dates, DataStructures using CommUtils, GeneralUtils @@ -378,35 +378,40 @@ Return: function isUseTools(a::agentReflex) toollines = "" for (toolname, v) in a.tools - if toolname ∉ ["chatbox"] + if toolname ∉ ["chatbox"] # LLM will always use chatbox toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" toollines *= toolline end end - conversation = messagesToString_nomark(a.messages) + conversation = messagesToString(a.messages) prompt = """ - <|im_start|>system - $(a.roles[a.role]) + <|system|> + You are a helpful assistant. You have access to the following tools: $toollines - Your conversation with the user: + Your task is to decide whether you need think thoroughly in order to respond to the user according to your conversation with the user and tools you have. + So for instance the following: + user: Hello!. How are you? + assistant: {No}, the user is greeting me, I could respond right away. + user: "I want a bottle of wine." + assistant: {Yes}, I need to think thoroughly about the user stimulus. + $conversation - - From the conversation, ask yourself what do you intend to do now? - <|im_end|> - + <|assistant|> """ # if LLM mentions any tools, use Plan/Thought/Act loop isusetool = false - response = sendReceivePrompt(a, prompt, temperature=0.0) - response = split(response, "<|im_end|>")[1] + 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(toolname, String(response)) + if occursin("Yes", String(response)) isusetool = true break end @@ -415,17 +420,14 @@ function isUseTools(a::agentReflex) if length(a.memory[:shortterm]) != 0 isusetool = true end - return isusetool end - - # function isUseTools(a::agentReflex) # toollines = "" # for (toolname, v) in a.tools -# if toolname ∉ ["chatbox"] +# if toolname ∉ ["chatbox"] # LLM will always use chatbox # toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" # toollines *= toolline # end @@ -443,74 +445,30 @@ end # Your conversation with the user: # $conversation -# From your conversation, ask yourself what do you need to do now? +# From the conversation, ask yourself what do you intend to do now? # <|im_end|> # """ # # if LLM mentions any tools, use Plan/Thought/Act loop # isusetool = false -# result = sendReceivePrompt(a, prompt, temperature=0.2) +# response = sendReceivePrompt(a, prompt, temperature=0.0) +# response = split(response, "<|im_end|>")[1] # for (toolname, v) in a.tools -# if occursin(toolname, result) +# if occursin(toolname, String(response)) # isusetool = true # break # end # end -# @show prompt -# whattodo = result -# @show whattodo -# return isusetool, whattodo +# if length(a.memory[:shortterm]) != 0 +# isusetool = true +# end + +# return isusetool # end -# function isUseTools(a::agentReflex, usermsg::String) -# toollines = "" -# for (toolname, v) in a.tools -# if toolname ∉ ["chatbox"] -# toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n" -# toollines *= toolline -# end -# end - -# prompt = -# """ -# <|im_start|>system -# $(a.roles[a.role]) -# You have access to the following tools: -# $toollines - -# Your earlier conversation with the user: -# None - -# User's message: -# $usermsg - -# From the user's message, ask yourself what do you need to do? -# <|im_end|> -# <|im_start|>assistant - -# """ - -# # if LLM mentions any tools, use Plan/Thought/Act loop -# isusetool = false -# result = sendReceivePrompt(a, prompt, temperature=0.2) -# for (toolname, v) in a.tools -# if occursin(toolname, result) -# isusetool = true -# break -# end -# end - -# whattodo = result -# @show whattodo - -# return isusetool, whattodo -# end - - - """ make a conversation summary. @@ -614,9 +572,9 @@ function messagesToString(messages::AbstractVector{T}; addressAIas="assistant") end if role == "user" - conversation *= "<|im_start|>$role: $(content[1:end-nouse])\n<|im_end|>" + conversation *= "<|$role|>\n $(content[1:end-nouse])\n" elseif role == "assistant" - conversation *= "<|im_start|>$addressAIas: $(content[1:end-nouse])\n<|im_end|>" + conversation *= "<|$addressAIas|>\n $(content[1:end-nouse])\n" else error("undefied condition role = $role $(@__LINE__)") end @@ -627,6 +585,36 @@ function messagesToString(messages::AbstractVector{T}; addressAIas="assistant") return conversation end +# function messagesToString(messages::AbstractVector{T}; addressAIas="assistant") where {T<:AbstractDict} +# conversation = "" +# if length(messages)!= 0 +# for msg in messages +# role = msg[:role] +# content = msg[:content] + +# nouse = 0 +# for i in reverse(content) +# if i == '\n' || i == ' ' +# nouse += 1 +# else +# break +# end +# end + +# if role == "user" +# conversation *= "<|im_start|>$role: $(content[1:end-nouse])\n<|im_end|>" +# elseif role == "assistant" +# conversation *= "<|im_start|>$addressAIas: $(content[1:end-nouse])\n<|im_end|>" +# else +# error("undefied condition role = $role $(@__LINE__)") +# end +# end +# else +# conversation = "N/A" +# end + +# return conversation +# end """ Convert a vector of dict into 1-continous string. @@ -1028,6 +1016,14 @@ function experience(dict::T) where {T<:AbstractDict} end +function dictLatestStep(dict::T) where {T<:AbstractDict} + @show dict + _latest_step = keys(dict) + _latest_step = [i for i in _latest_step] + _latest_step = _latest_step[end] + latest_step = parse(Int, _latest_step[end-2:end-1]) + return latest_step +end