diff --git a/src/interface.jl b/src/interface.jl index f40f571..9bd66c7 100755 --- a/src/interface.jl +++ b/src/interface.jl @@ -60,7 +60,8 @@ abstract type agent end context::String = "nothing" # internal thinking area tools::Union{Dict, Nothing} = nothing thought::String = "nothing" # contain unfinished thoughts for ReAct agent only - thoughtround::Int = 0 + thoughtround::Int = 0 # no. of thinking round + thoughtlimit::Int = 5 # thinking round limit thinkingMode::Union{Dict, Nothing} = nothing end @@ -98,10 +99,9 @@ function agentReact( Act: the tool that match your thought, should be one of {toolnames} ActInput: the input to the action (pay attention to the tool's input) Obs: the result of the action - ... (this Plan/Thought/Act/ActInput/Obs loop can repeat N times until you know the answer.) - Answer: Answer of the original question. - - When you get "No info available." 3 times in a row, just answer the question. + ... (this Plan/Thought/Act/ActInput/Obs can repeat N times until you know the answer.) + Thought: I think I know the answer + Answer: Answer of the original question Begin!""", ), @@ -109,7 +109,7 @@ function agentReact( :wikisearch=>Dict( :name => "wikisearch", :description => "Useful for when you need to search the Internet", - :input => "Input should be a keyword not a question.", + :input => "Input should be keywords not a question.", :output => "", :func => wikisearch, # put function here ), @@ -350,21 +350,27 @@ end """ Continuously run llm functions except when llm is getting Answer: or chatbox. """ -function work(a::T, prompt::String) where {T<:agent} +function work(a::T, prompt::String, maxround::Int=3) where {T<:agent} respond = nothing while true a.thoughtround += 1 - @show prompt + @show a.thoughtround toolname = nothing toolinput = nothing + #WORKING force answer if thoughtround exceed limit + if a.thoughtround > a.thoughtlimit + a.thought *= "Thought $(a.thoughtround): I think I know the answer." + prompt = a.thought + end + @show prompt respond = sendReceivePrompt(a, prompt) - @show respond headerToDetect = nothing if a.thoughtround == 1 try respond = split(respond, "Obs:")[1] + @show respond headerToDetect = ["Question:", "Plan:", "Thought:", "Act:", "ActInput:", "Obs:", "...", "Answer:", "Conclusion:", "Summary:"] catch @@ -372,6 +378,7 @@ function work(a::T, prompt::String) where {T<:agent} else try respond = split(respond, "Obs $(a.thoughtround):")[1] + @show respond headerToDetect = ["Question $(a.thoughtround):", "Plan $(a.thoughtround):", "Thought $(a.thoughtround):", "Act $(a.thoughtround):", "ActInput $(a.thoughtround):", "Obs $(a.thoughtround):", @@ -382,23 +389,9 @@ function work(a::T, prompt::String) where {T<:agent} end headers = detectCharacters(respond, headerToDetect) - @show headers chunkedtext = chunktext(respond, headers) @show chunkedtext - if a.thought == "nothing" - thought = "" - for i in chunkedtext - header = i[:header] - header = replace(header, ":"=>" $(a.thoughtround):") # add number so that llm not confused - body = i[:body] - thought *= "$header $body" - end - a.thought = thought - else - a.thought *= respond - end - Answer = findDetectedCharacter(headers, "Answer:") AnswerInd = length(Answer) != 0 ? Answer[1] : nothing Act = findDetectedCharacter(headers, "Act $(a.thoughtround):") @@ -409,18 +402,49 @@ function work(a::T, prompt::String) where {T<:agent} _ = addNewMessage(a, "assistant", respond) break else + # check for tool being called ActHeader = a.thoughtround == 1 ? "Act:" : "Act $(a.thoughtround):" - ActInd = findDetectedCharacter(headers, ActHeader)[1] - toolname = toolNameBeingCalled(chunkedtext[ActInd][:body], a.tools) - toolinput = chunkedtext[ActInd+1][:body] + if length(findDetectedCharacter(headers, ActHeader)) != 0 # check whether there is Act: in a respond + ActInd = findDetectedCharacter(headers, ActHeader)[1] + toolname = toolNameBeingCalled(chunkedtext[ActInd][:body], a.tools) + end + ActInputHeader = a.thoughtround == 1 ? "ActInput:" : "ActInput $(a.thoughtround):" + if length(findDetectedCharacter(headers, ActInputHeader)) != 0 # check whether there is ActInput: in a respond + ActInputInd = findDetectedCharacter(headers, ActInputHeader)[1] + toolinput = chunkedtext[ActInputInd][:body] + end + + # clean up if occursin(" \"", toolinput) toolinput = GeneralUtils.getStringBetweenCharacters(toolinput, " \"", "\"\n") else toolinput = GeneralUtils.getStringBetweenCharacters(toolinput, " ", "\n") end - @show toolname + @show toolname #BUG llm not specify tools @show toolinput + if toolname === nothing || toolinput === nothing + println("retry think") + a.thoughtround -= 1 + continue + end + + + + + if a.thought == "nothing" + thought = "" + for i in chunkedtext + header = i[:header] + header = replace(header, ":"=>" $(a.thoughtround):") # add number so that llm not confused + body = i[:body] + thought *= "$header $body" + end + a.thought = thought #BUG should be prompt + thought + else + a.thought *= respond + end + if toolname == "chatbox" # chat with user a.thought *= toolinput @@ -435,7 +459,6 @@ function work(a::T, prompt::String) where {T<:agent} _result = makeSummary(a, _result) end result = "Obs $(a.thoughtround): $_result\n" - @show result a.thought *= result prompt = a.thought end @@ -445,6 +468,107 @@ function work(a::T, prompt::String) where {T<:agent} return respond end +# function work(a::T, prompt::String, maxround::Int=3) where {T<:agent} +# respond = nothing +# while true +# a.thoughtround += 1 +# toolname = nothing +# toolinput = nothing + +# #WORKING force answer if thoughtround exceed limit +# if a.thoughtround > a.thoughtlimit +# a.thought *= "Thought $(a.thoughtround): I think I know the answer." +# prompt = a.thought +# end +# @show prompt +# respond = sendReceivePrompt(a, prompt) + +# headerToDetect = nothing +# if a.thoughtround == 1 +# try +# respond = split(respond, "Obs:")[1] +# @show respond +# headerToDetect = ["Question:", "Plan:", "Thought:", "Act:", "ActInput:", "Obs:", "...", "Answer:", +# "Conclusion:", "Summary:"] +# catch +# end +# else +# try +# respond = split(respond, "Obs $(a.thoughtround):")[1] +# @show respond +# headerToDetect = ["Question $(a.thoughtround):", "Plan $(a.thoughtround):", +# "Thought $(a.thoughtround):", "Act $(a.thoughtround):", +# "ActInput $(a.thoughtround):", "Obs $(a.thoughtround):", +# "...", "Answer:", +# "Conclusion:", "Summary:"] +# catch +# end +# end + +# headers = detectCharacters(respond, headerToDetect) +# chunkedtext = chunktext(respond, headers) +# @show chunkedtext + +# if a.thought == "nothing" +# thought = "" +# for i in chunkedtext +# header = i[:header] +# header = replace(header, ":"=>" $(a.thoughtround):") # add number so that llm not confused +# body = i[:body] +# thought *= "$header $body" +# end +# a.thought = thought +# else +# a.thought *= respond +# end + +# Answer = findDetectedCharacter(headers, "Answer:") +# AnswerInd = length(Answer) != 0 ? Answer[1] : nothing +# Act = findDetectedCharacter(headers, "Act $(a.thoughtround):") +# if length(Answer) == 1 && length(Act) == 0 +# a.thought = "nothing" # question finished, no more thought +# a.thoughtround = 0 +# respond = chunkedtext[AnswerInd][:body] +# _ = addNewMessage(a, "assistant", respond) +# break +# else +# # check for tool being called +# ActHeader = a.thoughtround == 1 ? "Act:" : "Act $(a.thoughtround):" +# ActInd = findDetectedCharacter(headers, ActHeader)[1] +# toolname = toolNameBeingCalled(chunkedtext[ActInd][:body], a.tools) +# toolinput = chunkedtext[ActInd+1][:body] +# if occursin(" \"", toolinput) +# toolinput = GeneralUtils.getStringBetweenCharacters(toolinput, " \"", "\"\n") +# else +# toolinput = GeneralUtils.getStringBetweenCharacters(toolinput, " ", "\n") +# end +# @show toolname #BUG llm not specify tools +# @show toolinput + + +# if toolname == "chatbox" # chat with user +# a.thought *= toolinput +# respond = toolinput +# _ = addNewMessage(a, "assistant", respond) +# break +# else # function call +# println("//////////// $(a.thoughtround)") +# f = a.tools[Symbol(toolname)][:func] +# _result = f(toolinput) +# if _result != "No info available." #TODO for use with wikisearch(). Not good for other tools +# _result = makeSummary(a, _result) +# end +# result = "Obs $(a.thoughtround): $_result\n" +# a.thought *= result +# prompt = a.thought +# end +# end +# end +# @show respond +# return respond +# end + + """ make a conversation summary. ```jldoctest @@ -715,7 +839,22 @@ function toolNameBeingCalled(text::T, tools::Dict) where {T<:AbstractString} end +function answerNow(a::T) where {T<:agent} + prompt = + """ + <|im_start|>system + {systemMsg} + Your need to determine now whether you will use tools or actions to answer the question. + You have the following choices: + If you don't need tools or actions to answer the question say, "{no}". + If you need tools or actions to answer the question say, "{yes}". + <|im_end|> + """ + prompt = replace(prompt, "{systemMsg}" => a.thought) + + error("answerNow done") +end #TODO @@ -761,23 +900,6 @@ function checkReasonableness(userMsg::String, context::String, tools) end - -function react_plan(text::String, firstlast="first") - "Plan" * GeneralUtils.getStringBetweenCharacters(text, "Plan", "Thought", firstlast=firstlast) -end - -function react_thought(text::String, firstlast="first") - "Thought" * GeneralUtils.getStringBetweenCharacters(text, "Thought", "Act", firstlast=firstlast) -end - -function react_act(text::String, firstlast="first") - "Act" * GeneralUtils.getStringBetweenCharacters(text, "Act", "ActInput", firstlast=firstlast) -end - -function react_actinput(text::String, firstlast="first") - "ActInput" * GeneralUtils.getStringBetweenCharacters(text, "ActInput", "Obs", firstlast=firstlast) -end - """ Detect given characters. Output is a list of named tuple of detected char.