Files
ChatAgent/src/interface.jl
youremail@yourdomain.com 15d00ae0a7 update
2023-12-20 03:44:51 +00:00

1297 lines
36 KiB
Julia
Executable File

module interface
export agentReact, agentReflex,
addNewMessage, clearMessage, removeLatestMsg, conversation, directconversation,
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 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}
"
"""
conversation = messagesToString(a.messages)
prompt =
"""
<|system|>
$(a.roles[a.role])
Your earlier talk with the user:
$(a.earlierConversation)
<|/s|>
$conversation
<|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 [""]
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 have access to the following tools:
$toollines
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.
<|/s|>
$conversation
<|assistant|>
Plan:
"""
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]
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)
# conversation = messagesToString_nomark(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
work = dictToString(a.memory[:shortterm])
prompt =
"""
<|system|>
$(a.roles[a.role])
The required info you need for wine recommendation:
- wine price range: ask the user
- user's personal taste of wine: ask the user
- type of food: ask the user
- occasion: 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
Your work:
$work
Your job is to update the plan using available info from your work.
P.S. do not update if no info available.
For example:
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).
</s|>
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)
"""
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}
"
"""
toolnames = ""
toollines = ""
for (toolname, v) in a.tools
toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n"
toollines *= toolline
toolnames *= "$toolname, "
end
shorttermMemory = dictToString(a.memory[:shortterm], skiplist=["user:"])
# conversation = messagesToString_nomark(a.messages, addressAIas="I")
# context =
# """
# Your talk with the user:
# $conversation
# """
prompt =
"""
<|system|>
$(a.roles[a.role])
You have access to the following tools:
$toollines
Your earlier work:
$shorttermMemory
$(a.thinkingFormat[:actor])
<|/s|>
<|assistant|>
Thought $(a.step):
"""
prompt = replace(prompt, "{toolnames}" => toolnames)
prompt = replace(prompt, "{step}" => a.step)
println("")
@show prompt_actor = prompt
response = nothing
chunkedtext = nothing
tempcounter = 0.0
while true # while Thought or Act is empty, run actor again
tempcounter += 0.1
@show tempcounter
response = sendReceivePrompt(a, prompt, temperature=tempcounter)
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)
response = split(response, "\n\n ")[1]
headers = detectCharacters(response, headerToDetect)
println("")
@show response_actor = response
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)
# assuming length more than 10 character means LLM has valid thinking
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
toolname = toolNameBeingCalled(chunkedtext["Act $(a.step):"], a.tools)
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])
headerToDetect = ["Question:", "Plan:", "Thought:",
"Act:", "Actinput:", "Obs:", "...",
"Answer:", "Conclusion:", "Summary:"]
nextstep = latest_step+1 # next step in short term memory
response = replaceHeaders(response, headerToDetect, nextstep)
headerToDetect = ["Plan $(a.attempt):",
"Thought $nextstep:",
"Act $nextstep:",
"Actinput $nextstep:",
"Obs $nextstep:",
"Check $nextstep:",]
headers = detectCharacters(response, headerToDetect)
chunkedtext = chunktext(response, headers)
# add to memory
addShortMem!(a.memory[:shortterm], chunkedtext)
a.memory[:log] = addShortMem!(a.memory[:log], chunkedtext)
return toolname, toolinput
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" || workstate == "formulatedUserResponse"
#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
""" 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.
"""
function work(a::agentReflex)
workstate = nothing
response = nothing
# 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]
end
end
end
while true # Work loop
objective = nothing
# plan
if a.attempt <= a.attemptlimit
toolname = nothing
toolinput = nothing
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 plan
println("")
@show a.attempt
# enter actor loop
actorstate, msgToUser = actor(a)
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)
println("")
@show guideline
score = grading(a, guideline, response)
@show score
if score > 5 # good enough answer
println("")
formulatedresponse_final = response
@show formulatedresponse_final
workstate = "formulatedUserResponse"
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
headerToDetect = ["Lesson:", "Context:", ]
headers = detectCharacters(lessonwithcontext, headerToDetect)
chunkedtext = chunktext(lessonwithcontext, headers)
a.memory[:longterm][chunkedtext["Context:"]] = chunkedtext["Lesson:"]
a.attempt += 1
a.step = 0
a.memory[:shortterm] = OrderedDict{String, Any}()
a.memory[:log] = OrderedDict{String, Any}()
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
"""
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
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
toolname, toolinput = actor_mistral_openorca(a)
@show toolname
@show toolinput
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
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)
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:
$(a.memory[:shortterm]["Objective:"])
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 =
"""
<|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.
<|/s|>
<|assistant|>
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 =
"""
<|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 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}
</s|>
<|assistant|>
"""
response = sendReceivePrompt(a, prompt)
@show goNogo_response = response
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 =
"""
<|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?
<|/s|>
<|assistant|>
"""
response = sendReceivePrompt(a, prompt)
response = split(response, "<|im_end|>")[1]
return result
end
end # module