update
This commit is contained in:
101
previousVersion/0.0.4/src/ChatAgent.jl
Executable file
101
previousVersion/0.0.4/src/ChatAgent.jl
Executable file
@@ -0,0 +1,101 @@
|
||||
module ChatAgent
|
||||
|
||||
# export agent, addNewMessage, clearMessage
|
||||
|
||||
|
||||
""" Order by dependencies of each file. The 1st included file must not depend on any other
|
||||
files and each file can only depend on the file included before it.
|
||||
"""
|
||||
|
||||
include("type.jl")
|
||||
using .type
|
||||
|
||||
include("utils.jl")
|
||||
using .utils
|
||||
|
||||
include("llmfunction.jl")
|
||||
using .llmfunction
|
||||
|
||||
include("interface.jl")
|
||||
using .interface
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------------------------100
|
||||
|
||||
""" version 0.0.1
|
||||
Todo:
|
||||
[] add chat mechanism
|
||||
|
||||
Change from version: 0.0.0
|
||||
-
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end # module ChatAgent
|
||||
1229
previousVersion/0.0.4/src/interface.jl
Executable file
1229
previousVersion/0.0.4/src/interface.jl
Executable file
File diff suppressed because it is too large
Load Diff
146
previousVersion/0.0.4/src/llmfunction.jl
Normal file
146
previousVersion/0.0.4/src/llmfunction.jl
Normal file
@@ -0,0 +1,146 @@
|
||||
module llmfunction
|
||||
|
||||
export wikisearch, winestock
|
||||
|
||||
using HTTP, JSON3
|
||||
using GeneralUtils
|
||||
using ..type, ..utils
|
||||
#------------------------------------------------------------------------------------------------100
|
||||
|
||||
"""
|
||||
Search wikipedia.
|
||||
|
||||
Args:
|
||||
query (string): The query to search for
|
||||
|
||||
Returns:
|
||||
string: The search result text from wikipedia
|
||||
```jldoctest
|
||||
julia> using HTTP, JSON3
|
||||
julia> result = wikisearch("AMD")
|
||||
"Advanced Micro Devices, Inc., commonly abbreviated as AMD, is an ..."
|
||||
```
|
||||
"""
|
||||
function wikisearch(a::agentReflex, phrase::T) where {T<:AbstractString}
|
||||
phrase = phrase[1] == " " ? phrase[2:end] : phrase
|
||||
# prepare input phrase
|
||||
if occursin("\"", phrase)
|
||||
phrase = GeneralUtils.getStringBetweenCharacters(phrase, "\"", "\"")
|
||||
end
|
||||
phrase = replace(phrase, "\n"=>"")
|
||||
|
||||
url = "https://en.wikipedia.org/w/api.php?action=query&format=json&prop=extracts&titles=$(replace(phrase, " " => "%20"))&exintro=1&explaintext=1"
|
||||
@show url
|
||||
response = HTTP.get(url)
|
||||
json_data = JSON3.read(String(response.body))
|
||||
page_id = first(keys(json_data["query"]["pages"]))
|
||||
if page_id == "-1"
|
||||
return "Sorry, I couldn't find any Wikipedia page for the given phrase."
|
||||
end
|
||||
|
||||
result = nothing
|
||||
try
|
||||
result = json_data["query"]["pages"][page_id]["extract"]
|
||||
wiki = result
|
||||
@show wiki
|
||||
catch
|
||||
result = "No info available for your search query."
|
||||
end
|
||||
|
||||
# if result == ""
|
||||
# result = "No info available for your search query."
|
||||
# else
|
||||
# result = makeSummary(a, result)
|
||||
# end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
function winestock(a::agentReflex, phrase::T) where {T<:AbstractString}
|
||||
# result = [
|
||||
# Dict(
|
||||
# "name" => "Louis Latou - Corton-Charlamagne - Chardonnay",
|
||||
# "description" => "Corton-Charlemagne 2018 is a powerful, complex wine. Its nose is intense, with notes of white stone fruits such as white peach, fresh hazelnut, vanilla, and almond paste. The wine is full-bodied for the palate, and the vanilla is complemented by aromas of fresh almond and lime blossom. The experience ends with a very fine aromatic aftertaste that has subtle saline notes.",
|
||||
# "price" => "49",
|
||||
# "ID" => "ws-114"
|
||||
# ),
|
||||
# Dict(
|
||||
# "name" => "Louis Latou - Corton-Charlamagne - Chardonnay",
|
||||
# "description" => "Corton-Charlemagne 2018 is a powerful, complex wine. Its nose is intense, with notes of white stone fruits such as white peach, fresh hazelnut, vanilla, and almond paste. The wine is full-bodied for the palate, and the vanilla is complemented by aromas of fresh almond and lime blossom. The experience ends with a very fine aromatic aftertaste that has subtle saline notes.",
|
||||
# "price" => "49",
|
||||
# "ID" => "ws-114"
|
||||
# )
|
||||
# ]
|
||||
|
||||
result =
|
||||
"""
|
||||
1. Name: Louis Latou - Corton-Charlamagne - Chardonnay,
|
||||
Description: Corton-Charlemagne 2018 is a powerful, complex wine. Its nose is intense, with notes of white stone fruits such as white peach, fresh hazelnut, vanilla, and almond paste. The wine is full-bodied for the palate, and the vanilla is complemented by aromas of fresh almond and lime blossom. The experience ends with a very fine aromatic aftertaste that has subtle saline notes.,
|
||||
Price: 49 dollars,
|
||||
ID: ws-114
|
||||
2. Name: Chateau de Beaucastel Hommage Jacques Perrin Chateauneuf-du-Pape,
|
||||
Year: 2019,
|
||||
Description: The quintessence of Château de Beaucastel, Hommage à Jacques Perrin delights us every year, and the 2019 vintage is no exception. To the eye it offers a splendid deep red color, verging on black. Full of power and supremely elegant, the nose is of magnificent aromatic complexity with notes of black fruit and spices that offer all the characteristic expression of Mourvèdre. Perfectly balanced by an incredible freshness, the mouth is eminently elegant with intense and complex aromas of great subtlety, a full, refined texture, subtle tannins of great finesse, and infinite length. A great classic Hommage à Jacques Perrin.,
|
||||
Price: 42,
|
||||
ID: ed-23
|
||||
3. Name: M. Chapoutier Ermitage l'Ermite Blanc,
|
||||
Year: 2017
|
||||
Description: Brilliant pale yellow. Complex aromas of vanilla, almonds, dried fruits and linden-tree. The mineraliaty is marked (typical of soil). Very round and rich wine. An elegant balance, of very ripe white fruit aromas (peach and apricot) and light notes of minerality. Beautiful length and complexity.,
|
||||
Price: 13,
|
||||
ID: wwr-259
|
||||
"""
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end # end module
|
||||
376
previousVersion/0.0.4/src/type.jl
Normal file
376
previousVersion/0.0.4/src/type.jl
Normal file
@@ -0,0 +1,376 @@
|
||||
module type
|
||||
|
||||
export agent, agentReflex
|
||||
|
||||
using Dates, UUIDs, DataStructures
|
||||
using CommUtils
|
||||
|
||||
#------------------------------------------------------------------------------------------------100
|
||||
|
||||
|
||||
abstract type agent end
|
||||
|
||||
|
||||
@kwdef mutable struct agentReflex <: agent
|
||||
availableRole::AbstractVector = ["system", "user", "assistant"]
|
||||
agentName::String = "Jene" # ex. Jene
|
||||
maxUserMsg::Int = 10
|
||||
earlierConversation::String = "" # summary of earlier conversation
|
||||
mqttClient::Union{mqttClient, Nothing} = nothing
|
||||
msgMeta::Union{Dict, Nothing} = nothing
|
||||
|
||||
""" Dict(Role=> Content) ; Role can be system, user, assistant
|
||||
Example:
|
||||
messages=[
|
||||
Dict(:role=>"system", :content=> "You are a helpful assistant."),
|
||||
Dict(:role=>"assistant", :content=> "How may I help you"),
|
||||
Dict(:role=>"user", :content=> "Hello, how are you"),
|
||||
]
|
||||
"""
|
||||
role::Symbol = :assistant
|
||||
roles::Dict = Dict(:assistant => "You are a helpful assistant.",)
|
||||
|
||||
# Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
|
||||
# messages= [Dict(:role=>"system", :content=> "", :timestamp=> Dates.now()),]
|
||||
messages = Vector{Dict{Symbol, Any}}()
|
||||
tools::Union{Dict, Nothing} = nothing
|
||||
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
|
||||
thinkingmode::Symbol = :no_thinking
|
||||
thinkingFormat::Union{Dict, Nothing} = nothing
|
||||
memory::Dict = Dict(
|
||||
:shortterm=> OrderedDict{String, Any}(),
|
||||
:longterm=> OrderedDict{String, Any}(),
|
||||
:log=> OrderedDict{String, Any}(), # span from user stimulus -> multiples attempts -> final respond
|
||||
)
|
||||
end
|
||||
|
||||
function agentReflex(
|
||||
agentName::String;
|
||||
mqttClientSpec::NamedTuple=(
|
||||
clientName= "someclient", # name of this client
|
||||
clientID= "$(uuid4())",
|
||||
broker= "mqtt.yiem.ai",
|
||||
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/respond",
|
||||
txtAI="agent/api/v0.1.0/txt/respond"),
|
||||
keepalive= 30,),
|
||||
role::Symbol=:assistant,
|
||||
roles::Dict=Dict(
|
||||
:assistant =>
|
||||
"""
|
||||
You are a helpful assistant who respond to a stimulus as best you can.
|
||||
""",
|
||||
:sommelier =>
|
||||
"""
|
||||
You are a sommelier at an online wine reseller who always help users choosing their wine from your inventory.
|
||||
You don't know other people personal info previously.
|
||||
""",
|
||||
# :sommelier =>
|
||||
# """
|
||||
# You are a sommelier at an online wine reseller who always ask user for wine relevant info before you could help them choosing wine.
|
||||
# You provide a personalized recommendation of up to two wines based on the user's preference, and you describe the benefits of each wine in detail.
|
||||
# You don't know other people personal info previously.
|
||||
|
||||
# Info used to select wine:
|
||||
# - type of food
|
||||
# - occasion
|
||||
# - user's personal taste of wine
|
||||
# - wine price range
|
||||
# - temperature at the serving location
|
||||
# - wine we have in stock
|
||||
# """,
|
||||
),
|
||||
thinkingFormat::Dict=Dict(
|
||||
:react=>
|
||||
"""
|
||||
Use the following format:
|
||||
Question: the input question your user is asking and you must answer
|
||||
Plan: first you should always think about the question and the info you have thoroughly then extract and devise a complete plan to find the answer (pay attention to variables and their corresponding numerals).
|
||||
Thought: ask yourself do you have all the info you need? And what to do according to the plan (pay attention to correct numeral calculation and commonsense).
|
||||
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 can repeat N times until you know the answer.)
|
||||
Thought: I think I know the answer
|
||||
Answer: Answer of the original question
|
||||
|
||||
Begin!""",
|
||||
:planner=>
|
||||
"""
|
||||
Use the following format:
|
||||
Stimulus: the input user gives to you and you must respond
|
||||
Plan: first you should always think about the stimulus, the info you need and the info you have thoroughly then extract and devise a step by step plan (pay attention to correct numeral calculation and commonsense).
|
||||
p.s.1 each step should be a single question/action.
|
||||
p.s.2 Do not respond yet.
|
||||
""",
|
||||
:actor=>
|
||||
"""
|
||||
Use the following format:
|
||||
Thought: ask yourself do you have what you need and 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}]
|
||||
ActInput: the input to the action (pay attention to the tool's input)
|
||||
Obs: the result of the action
|
||||
Check: check whether you are ready for the next step of the plan
|
||||
""",
|
||||
),
|
||||
tools::Dict=Dict(
|
||||
:chatbox=>Dict(
|
||||
:name => "chatbox",
|
||||
:description => "Useful for when you need to communicate with the user.",
|
||||
:input => "Input should be a conversation to the user.",
|
||||
:output => "" ,
|
||||
:func => nothing,
|
||||
),
|
||||
# :wikisearch=>Dict(
|
||||
# :name => "wikisearch",
|
||||
# :description => "Useful for when you need to search an encyclopedia",
|
||||
# :input => "Input is keywords and not a question.",
|
||||
# :output => "",
|
||||
# :func => wikisearch, # put function here
|
||||
# ),
|
||||
# :wineStock=>Dict(
|
||||
# :name => "wineStock",
|
||||
# :description => "useful for when you need to search for wine by your description, price, name or ID.",
|
||||
# :input => "Input should be a search query with as much details as possible.",
|
||||
# :output => "" ,
|
||||
# :func => nothing,
|
||||
# ),
|
||||
# :NTHING=>Dict(
|
||||
# :name => "NTHING",
|
||||
# :description => "useful for when you don't need to use tools or actions",
|
||||
# :input => "No input is needed",
|
||||
# :output => "" ,
|
||||
# :func => nothing,
|
||||
# ),
|
||||
),
|
||||
msgMeta::Dict=Dict(
|
||||
:msgPurpose=> "updateStatus",
|
||||
:from=> "chatbothub",
|
||||
:to=> "llmAI",
|
||||
:requestrespond=> "request",
|
||||
:sendto=> "", # destination topic
|
||||
:replyTo=> "agent/api/v0.1.0/txt/respond", # requester ask responder to send reply to this topic
|
||||
:repondToMsgId=> "", # responder is responding to this msg id
|
||||
:taskstatus=> "", # "complete", "fail", "waiting" or other status
|
||||
:timestamp=> Dates.now(),
|
||||
:msgId=> "$(uuid4())",
|
||||
),
|
||||
availableRole::AbstractArray=["system", "user", "assistant"],
|
||||
maxUserMsg::Int=10,)
|
||||
|
||||
newAgent = agentReflex()
|
||||
newAgent.availableRole = availableRole
|
||||
newAgent.maxUserMsg = maxUserMsg
|
||||
newAgent.mqttClient = CommUtils.mqttClient(mqttClientSpec)
|
||||
newAgent.msgMeta = msgMeta
|
||||
newAgent.tools = tools
|
||||
newAgent.role = role
|
||||
newAgent.roles = roles
|
||||
newAgent.thinkingFormat = thinkingFormat
|
||||
newAgent.agentName = agentName
|
||||
|
||||
return newAgent
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@kwdef mutable struct agentReact <: agent
|
||||
availableRole::AbstractVector = ["system", "user", "assistant"]
|
||||
agentName::String = "assistant"
|
||||
maxUserMsg::Int = 10
|
||||
earlierConversation::String = "" # summary of earlier conversation
|
||||
mqttClient::Union{mqttClient, Nothing} = nothing
|
||||
msgMeta::Union{Dict, Nothing} = nothing
|
||||
|
||||
""" Dict(Role=> Content) ; Role can be system, user, assistant
|
||||
Example:
|
||||
messages=[
|
||||
Dict(:role=>"system", :content=> "You are a helpful assistant."),
|
||||
Dict(:role=>"assistant", :content=> "How may I help you"),
|
||||
Dict(:role=>"user", :content=> "Hello, how are you"),
|
||||
]
|
||||
"""
|
||||
role::Symbol = :assistant
|
||||
roles::Dict = Dict(:assistant => "You are a helpful assistant.",)
|
||||
|
||||
# Ref: Chat prompt format https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/discussions/3
|
||||
# messages= [Dict(:role=>"system", :content=> "", :timestamp=> Dates.now()),]
|
||||
messages = Vector{Dict{Symbol, Any}}()
|
||||
context::String = "nothing" # internal thinking area
|
||||
tools::Union{Dict, Nothing} = nothing
|
||||
thought::String = "nothing" # contain unfinished thoughts for ReAct agent only
|
||||
thinkinground::Int = 0 # no. of thinking round
|
||||
thinkingroundlimit::Int = 5 # thinking round limit
|
||||
thinkingMode::Union{Dict, Nothing} = nothing
|
||||
end
|
||||
|
||||
function agentReact(
|
||||
agentName::String,
|
||||
mqttClientSpec::NamedTuple;
|
||||
role::Symbol=:assistant,
|
||||
roles::Dict=Dict(
|
||||
:assistant =>
|
||||
"""
|
||||
You are a helpful assistant who answer the user's questions as best you can.
|
||||
""",
|
||||
:sommelier =>
|
||||
"""
|
||||
You are a helpful sommelier at an online wine reseller who always ask user for wine relevant info before you could help them choosing wine.
|
||||
You provide a personalized recommendation of up to two wines based on the user's preference, and you describe the benefits of each wine in detail.
|
||||
You don't know other people personal info previously.
|
||||
|
||||
Info used to select wine:
|
||||
- type of food
|
||||
- occasion
|
||||
- user's personal taste of wine
|
||||
- wine price range
|
||||
- temperature at the serving location
|
||||
- wine we have in stock
|
||||
""",
|
||||
),
|
||||
thinkingMode::Dict=Dict(
|
||||
:no_thinking=> "",
|
||||
:thinking=>
|
||||
"""Use the following format:
|
||||
Question: the input question your user is asking and you must answer
|
||||
Plan: first you should always think about the question and the info you have thoroughly then extract and devise a complete plan to find the answer (pay attention to variables and their corresponding numerals).
|
||||
Thought: ask yourself do you have all the info you need? And what to do according to the plan (pay attention to correct numeral calculation and commonsense).
|
||||
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 can repeat N times until you know the answer.)
|
||||
Thought: I think I know the answer
|
||||
Answer: Answer of the original question
|
||||
|
||||
Begin!""",
|
||||
),
|
||||
tools::Dict=Dict(
|
||||
:wikisearch=>Dict(
|
||||
:name => "wikisearch",
|
||||
:description => "Useful for when you need to search an encyclopedia",
|
||||
:input => "Input is keywords and not a question.",
|
||||
:output => "",
|
||||
:func => wikisearch, # put function here
|
||||
),
|
||||
:chatbox=>Dict(
|
||||
:name => "chatbox",
|
||||
:description => "Useful for when you need to ask a customer for more context.",
|
||||
:input => "Input should be a conversation to customer.",
|
||||
:output => "" ,
|
||||
:func => nothing,
|
||||
),
|
||||
# :wineStock=>Dict(
|
||||
# :name => "wineStock",
|
||||
# :description => "useful for when you need to search for wine by your description, price, name or ID.",
|
||||
# :input => "Input should be a search query with as much details as possible.",
|
||||
# :output => "" ,
|
||||
# :func => nothing,
|
||||
# ),
|
||||
# :NTHING=>Dict(
|
||||
# :name => "NTHING",
|
||||
# :description => "useful for when you don't need to use tools or actions",
|
||||
# :input => "No input is needed",
|
||||
# :output => "" ,
|
||||
# :func => nothing,
|
||||
# ),
|
||||
),
|
||||
msgMeta::Dict=Dict(
|
||||
:msgPurpose=> "updateStatus",
|
||||
:from=> "chatbothub",
|
||||
:to=> "llmAI",
|
||||
:requestrespond=> "request",
|
||||
:sendto=> "", # destination topic
|
||||
:replyTo=> "chatbothub/llm/respond", # requester ask responder to send reply to this topic
|
||||
:repondToMsgId=> "", # responder is responding to this msg id
|
||||
:taskstatus=> "", # "complete", "fail", "waiting" or other status
|
||||
:timestamp=> Dates.now(),
|
||||
:msgId=> "$(uuid4())",
|
||||
),
|
||||
availableRole::AbstractArray=["system", "user", "assistant"],
|
||||
maxUserMsg::Int=10,)
|
||||
|
||||
newAgent = agentReact()
|
||||
newAgent.availableRole = availableRole
|
||||
newAgent.maxUserMsg = maxUserMsg
|
||||
newAgent.mqttClient = CommUtils.mqttClient(mqttClientSpec)
|
||||
newAgent.msgMeta = msgMeta
|
||||
newAgent.tools = tools
|
||||
newAgent.role = role
|
||||
newAgent.roles = roles
|
||||
newAgent.thinkingMode = thinkingMode
|
||||
|
||||
return newAgent
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end # end module
|
||||
|
||||
|
||||
|
||||
666
previousVersion/0.0.4/src/utils.jl
Normal file
666
previousVersion/0.0.4/src/utils.jl
Normal file
@@ -0,0 +1,666 @@
|
||||
module utils
|
||||
|
||||
export makeSummary, sendReceivePrompt, chunktext, extractStepFromPlan, checkTotalStepInPlan,
|
||||
detectCharacters, findDetectedCharacter, extract_number, toolNameBeingCalled,
|
||||
chooseThinkingMode, conversationSummary, checkReasonableness, replaceHeaders,
|
||||
addShortMem!, splittext
|
||||
|
||||
using UUIDs, Dates, DataStructures
|
||||
using CommUtils, GeneralUtils
|
||||
using ..type
|
||||
|
||||
#------------------------------------------------------------------------------------------------100
|
||||
|
||||
function makeSummary(a::T1, input::T2) where {T1<:agent, T2<:AbstractString}
|
||||
summary = "Nothing."
|
||||
prompt =
|
||||
"""
|
||||
<|im_start|>system
|
||||
Input text:
|
||||
$input
|
||||
|
||||
Your job is to determine now whether you can make a summary of the input text by choosing one of following choices:
|
||||
If you cannot make a summary say, "{No}".
|
||||
If you can make a summary say, "{Yes}".
|
||||
<|im_end|>
|
||||
"""
|
||||
prompt = replace(prompt, "{input}" => input)
|
||||
result = sendReceivePrompt(a, prompt)
|
||||
result = GeneralUtils.getStringBetweenCharacters(result, "{", "}")
|
||||
if result == "Yes" # seperate summary part
|
||||
prompt =
|
||||
"""
|
||||
<|im_start|>system
|
||||
Input text:
|
||||
$input
|
||||
Your job is to make a concise summary of the input text.
|
||||
<|im_end|>
|
||||
"""
|
||||
result = sendReceivePrompt(a, prompt)
|
||||
if result[1:1] == "\n"
|
||||
summary = result[2:end]
|
||||
else
|
||||
summary = result
|
||||
end
|
||||
end
|
||||
input_summary = input
|
||||
@show input_summary
|
||||
@show summary
|
||||
|
||||
return summary
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
Send a msg to registered mqtt topic within mqttClient.
|
||||
|
||||
```jldoctest
|
||||
julia> using JSON3, UUIDs, Dates, FileIO, CommUtils, ChatAgent
|
||||
julia> mqttClientSpec = (
|
||||
clientName= "someclient", # name of this client
|
||||
clientID= "$(uuid4())",
|
||||
broker= "mqtt.yiem.ai",
|
||||
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/respond",
|
||||
txtAI="agent/api/v0.1.0/txt/respond"),
|
||||
keepalive= 30,
|
||||
)
|
||||
julia> msgMeta = Dict(
|
||||
:msgPurpose=> "updateStatus",
|
||||
:from=> "agent",
|
||||
:to=> "llmAI",
|
||||
:requestrespond=> "request",
|
||||
:sendto=> "", # destination topic
|
||||
:replyTo=> "agent/api/v0.1.0/txt/respond", # requester ask responder to send reply to this topic
|
||||
:repondToMsgId=> "", # responder is responding 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
|
||||
)
|
||||
```
|
||||
"""
|
||||
function sendReceivePrompt(a::T, prompt::String; max_tokens=256, timeout::Int=120) where {T<:agent}
|
||||
a.msgMeta[:msgId] = "$(uuid4())" # new msg id for each msg
|
||||
msg = Dict(
|
||||
:msgMeta=> a.msgMeta,
|
||||
:txt=> prompt,
|
||||
:max_tokens=>max_tokens
|
||||
)
|
||||
payloadChannel = Channel(1)
|
||||
|
||||
# send prompt
|
||||
CommUtils.request(a.mqttClient, msg)
|
||||
starttime = Dates.now()
|
||||
result = nothing
|
||||
|
||||
while true
|
||||
timepass = (Dates.now() - starttime).value / 1000.0
|
||||
CommUtils.mqttRun(a.mqttClient, payloadChannel)
|
||||
if isready(payloadChannel)
|
||||
topic, payload = take!(payloadChannel)
|
||||
if payload[:msgMeta][:repondToMsgId] == msg[:msgMeta][:msgId]
|
||||
result = haskey(payload, :txt) ? payload[:txt] : nothing
|
||||
break
|
||||
end
|
||||
elseif timepass <= timeout
|
||||
# skip, within waiting period
|
||||
elseif timepass > timeout
|
||||
println("sendReceivePrompt timeout $timepass/$timeout")
|
||||
result = nothing
|
||||
break
|
||||
else
|
||||
error("undefined condition. timepass=$timepass timeout=$timeout $(@__LINE__)")
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
"""
|
||||
Detect given characters. Output is a list of named tuple of detected char.
|
||||
|
||||
```jldoctest
|
||||
julia> text = "I like to eat apples and use utensils."
|
||||
julia> characters = ["eat", "use", "i"]
|
||||
julia> result = detectCharacters(text, characters)
|
||||
4-element Vector{Any}:
|
||||
(char = "i", start = 4, stop = 4)
|
||||
(char = "eat", start = 11, stop = 13)
|
||||
(char = "use", start = 26, stop = 28)
|
||||
(char = "i", start = 35, stop = 35)
|
||||
```
|
||||
"""
|
||||
function detectCharacters(text::T1, characters::Vector{T2}) where {T1<:AbstractString, T2<:AbstractString}
|
||||
result = []
|
||||
for i in eachindex(text)
|
||||
for char in characters
|
||||
l = length(char)
|
||||
char_startInd = i
|
||||
char_endInd = i+l-1 # -1 because Julia use inclusive index
|
||||
|
||||
if char_endInd > length(text)
|
||||
# skip
|
||||
else
|
||||
try # some time StringIndexError: invalid index [535], valid nearby indices [534]=>'é', [536]=>' '
|
||||
if text[char_startInd: char_endInd] == char
|
||||
push!(result, (char=char, start=char_startInd, stop=char_endInd))
|
||||
end
|
||||
catch
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
"""
|
||||
Chunk a text into smaller pieces by header.
|
||||
```jldoctest
|
||||
julia> using ChatAgent
|
||||
julia> text = "Plan: First, we need to find out what kind of wine the user wants."
|
||||
julia> headers = ChatAgent.detectCharacters(text, ["Nope", "sick", "First", "user", "Then", ])
|
||||
3-element Vector{Any}:
|
||||
(char = "First", start = 7, stop = 11)
|
||||
(char = "user", start = 56, stop = 59)
|
||||
(char = "Then", start = 102, stop = 105)
|
||||
julia> chunkedtext = ChatAgent.chunktext(text, headers)
|
||||
OrderedDict{String, String} with 3 entries:
|
||||
"Act 1:" => " wikisearch"
|
||||
"ActInput 1:" => " latest AMD GPU"
|
||||
"Thought 1:" => " I should always think about..."
|
||||
```
|
||||
"""
|
||||
function chunktext(text::T1, headers::T2) where {T1<:AbstractString, T2<:AbstractVector}
|
||||
result = OrderedDict{String, Any}()
|
||||
|
||||
for (i, v) in enumerate(headers)
|
||||
if i < length(headers)
|
||||
nextheader = headers[i+1]
|
||||
body = text[v[:stop]+1: nextheader[:start]-1]
|
||||
# push!(result, (header=v[:char], body=body))
|
||||
result[v[:char]] = body
|
||||
else
|
||||
body = text[v[:stop]+1: end]
|
||||
# push!(result, (header=v[:char], body=body))
|
||||
result[v[:char]] = body
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
function extractStepFromPlan(a::agent, plan::T, step::Int) where {T<:AbstractString}
|
||||
prompt =
|
||||
"""
|
||||
<|im_start|>system
|
||||
You are a helpful assistant.
|
||||
Your job is to extract step $step in the user plan.
|
||||
|
||||
Use the following format only:
|
||||
{copy the step and put it here}
|
||||
|
||||
<|im_end|>
|
||||
|
||||
<|im_start|>user
|
||||
$plan
|
||||
<|im_end|>
|
||||
<|im_start|>assistant
|
||||
|
||||
"""
|
||||
|
||||
respond = sendReceivePrompt(a, prompt)
|
||||
|
||||
return respond
|
||||
end
|
||||
|
||||
function checkTotalStepInPlan(a::agent)
|
||||
headers = []
|
||||
|
||||
for (k, v) in a.memory[:shortterm]
|
||||
push!(headers, k)
|
||||
end
|
||||
|
||||
# Plan will have number e.g. Plan 3: so I need a way to detect latest Plan
|
||||
header = nothing
|
||||
for i in reverse(headers)
|
||||
if occursin("Plan", i)
|
||||
header = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
p = a.memory[:shortterm][header]
|
||||
plan = "Plan: $p"
|
||||
prompt =
|
||||
"""
|
||||
<|im_start|>system
|
||||
You are a helpful assistant.
|
||||
Your job is to determine how many steps in a user plan.
|
||||
|
||||
Use the following format to answer:
|
||||
Total step number is {}
|
||||
<|im_end|>
|
||||
|
||||
<|im_start|>user
|
||||
$plan
|
||||
<|im_end|>
|
||||
<|im_start|>assistant
|
||||
|
||||
"""
|
||||
respond = sendReceivePrompt(a, prompt)
|
||||
result = extract_number(respond)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
"""
|
||||
Find a given character from a vector of named tuple.
|
||||
Output is character location index inside detectedCharacters
|
||||
|
||||
```jldoctest
|
||||
julia a = [ (char = "i", start = 4, stop = 4)
|
||||
(char = "eat", start = 11, stop = 13)
|
||||
(char = "use", start = 26, stop = 28)
|
||||
(char = "i", start = 35, stop = 35) ]
|
||||
julia> findDetectedCharacter(a, "i")
|
||||
[1, 4]
|
||||
```
|
||||
"""
|
||||
function findDetectedCharacter(detectedCharacters, character)
|
||||
allchar = [i[1] for i in detectedCharacters]
|
||||
return findall(isequal.(allchar, character))
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function extract_number(text::T) where {T<:AbstractString}
|
||||
regex = r"\d+" # regular expression to match one or more digits
|
||||
match = Base.match(regex, text) # find the first match in the text
|
||||
if match !== nothing
|
||||
number = parse(Int, match.match)
|
||||
return number
|
||||
else
|
||||
error("No number found in the text $(@__LINE__)")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
"""
|
||||
Extract toolname from text.
|
||||
```jldoctest
|
||||
julia> text = " internetsearch\n"
|
||||
julia> tools = Dict(
|
||||
:internetsearch=>Dict(
|
||||
:name => "internetsearch",
|
||||
:description => "Useful for when you need to search the Internet",
|
||||
:input => "Input should be a search query.",
|
||||
:output => "",
|
||||
# :func => internetsearch # function
|
||||
),
|
||||
:chatbox=>Dict(
|
||||
:name => "chatbox",
|
||||
:description => "Useful for when you need to ask a customer what you need to know or to talk with them.",
|
||||
:input => "Input should be a conversation to customer.",
|
||||
:output => "" ,
|
||||
),
|
||||
)
|
||||
julia> toolname = toolNameBeingCalled(text, tools)
|
||||
```
|
||||
"""
|
||||
function toolNameBeingCalled(text::T, tools::Dict) where {T<:AbstractString}
|
||||
toolNameBeingCalled = nothing
|
||||
for (k, v) in tools
|
||||
toolname = String(k)
|
||||
if contains(text, toolname)
|
||||
toolNameBeingCalled = toolname
|
||||
break
|
||||
end
|
||||
end
|
||||
return toolNameBeingCalled
|
||||
end
|
||||
|
||||
|
||||
|
||||
function chooseThinkingMode(a::agentReflex, usermsg::String)
|
||||
thinkingmode = nothing
|
||||
if length(a.memory[:log]) != 0
|
||||
thinkingmode = :continue_thinking
|
||||
else
|
||||
prompt =
|
||||
"""
|
||||
<|im_start|>system
|
||||
{systemMsg}
|
||||
You always use tools if there is a chance to impove your respond.
|
||||
You have access to the following tools:
|
||||
{tools}
|
||||
Your job is to determine whether you will use tools or actions to respond.
|
||||
|
||||
Choose one of the following choices:
|
||||
If you don't need to use tools or actions to respond to the stimulus say, "{no}".
|
||||
If you need tools or actions to respond to the stimulus say, "{yes}".
|
||||
If the user want to get wine say, "{yes}".
|
||||
<|im_end|>
|
||||
|
||||
<|im_start|>user
|
||||
{input}
|
||||
<|im_end|>
|
||||
<|im_start|>assistant
|
||||
|
||||
"""
|
||||
toollines = ""
|
||||
for (toolname, v) in a.tools
|
||||
if toolname ∉ ["chatbox", "nothing"]
|
||||
toolline = "$toolname: $(v[:description]) $(v[:input]) $(v[:output])\n"
|
||||
toollines *= toolline
|
||||
end
|
||||
end
|
||||
prompt = replace(prompt, "{systemMsg}" => a.roles[a.role])
|
||||
prompt = replace(prompt, "{tools}" => toollines)
|
||||
prompt = replace(prompt, "{input}" => usermsg)
|
||||
result = sendReceivePrompt(a, prompt)
|
||||
willusetools = GeneralUtils.getStringBetweenCharacters(result, "{", "}")
|
||||
thinkingmode = willusetools == "yes" ? :new_thinking : :no_thinking
|
||||
end
|
||||
|
||||
return thinkingmode
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
make a conversation summary.
|
||||
```jldoctest
|
||||
julia> conversation = [
|
||||
Dict(:role=> "user", :content=> "I would like to get a bottle of wine", :timestamp=> Dates.now()),
|
||||
Dict(:role=> "assistant", :content=> "What kind of Thai dishes are you having?", :timestamp=> Dates.now()),
|
||||
Dict(:role=> "user", :content=> "It a pad thai.", :timestamp=> Dates.now()),
|
||||
Dict(:role=> "assistant", :content=> "Is there any special occasion for this event?", :timestamp=> Dates.now()),
|
||||
Dict(:role=> "user", :content=> "We'll hold a wedding party at the beach.", :timestamp=> Dates.now()),
|
||||
Dict(:role=> "assistant", :content=> "What is your preferred type of wine?", :timestamp=> Dates.now()),
|
||||
Dict(:role=> "user", :content=> "I like dry white wine with medium tanins.", :timestamp=> Dates.now()),
|
||||
Dict(:role=> "assistant", :content=> "What is your preferred price range for this bottle of wine?", :timestamp=> Dates.now()),
|
||||
Dict(:role=> "user", :content=> "lower than 50 dollars.", :timestamp=> Dates.now()),
|
||||
Dict(:role=> "assistant", :content=> "Based on your preferences and our stock, I recommend the following two wines for you:
|
||||
1. Pierre Girardin \"Murgers des Dents de Chien\" - Saint-Aubin 1er Cru (17 USD)
|
||||
2. Etienne Sauzet'Les Perrieres' - Puligny Montrachet Premier Cru (22 USD)
|
||||
The first wine, Pierre Girardin \"Murgers des Dents de Chien\" - Saint-Aubin 1er Cru, is a great choice for its affordable price and refreshing taste.
|
||||
It pairs well with Thai dishes and will be perfect for your beach wedding party.
|
||||
The second wine, Etienne Sauzet'Les Perrieres' - Puligny Montrachet Premier Cru, offers a more complex flavor profile and slightly higher price point, but still remains within your budget.
|
||||
Both wines are suitable for serving at 22 C temperature.", :timestamp=> Dates.now()),
|
||||
]
|
||||
|
||||
julia> summary = conversationSummary(conversation)
|
||||
```
|
||||
"""
|
||||
function conversationSummary(a::T) where {T<:agent}
|
||||
prompt =
|
||||
"""
|
||||
<|im_start|>system
|
||||
You talked with a user earlier.
|
||||
Now you make a detailed bullet summary of the conversation from your perspective.
|
||||
You must refers to yourself by "I" in the summary.
|
||||
|
||||
Here are the conversation:
|
||||
{conversation}
|
||||
<|im_end|>
|
||||
|
||||
"""
|
||||
conversation = ""
|
||||
summary = "nothing"
|
||||
if length(a.messages)!= 0
|
||||
for msg in a.messages
|
||||
role = msg[:role]
|
||||
content = msg[:content]
|
||||
|
||||
if role == "user"
|
||||
conversation *= "$role: $content\n"
|
||||
elseif role == "assistant"
|
||||
conversation *= "I: $content\n"
|
||||
else
|
||||
error("undefied condition role = $role $(@__LINE__)")
|
||||
end
|
||||
end
|
||||
|
||||
prompt = replace(prompt, "{conversation}" => conversation)
|
||||
result = sendReceivePrompt(a, prompt)
|
||||
summary = result === nothing ? "nothing" : result
|
||||
summary = split(summary, "<|im_end|>")[1]
|
||||
if summary[1:1] == "\n"
|
||||
summary = summary[2:end]
|
||||
end
|
||||
end
|
||||
@show summary
|
||||
return summary
|
||||
end
|
||||
|
||||
|
||||
|
||||
#TODO
|
||||
function checkReasonableness(userMsg::String, context::String, tools)
|
||||
# Ref: https://www.youtube.com/watch?v=XV4IBaZqbps
|
||||
|
||||
prompt =
|
||||
"""
|
||||
<|im_start|>system
|
||||
You are a helpful assistant. Your job is to check the reasonableness of user assignments.
|
||||
If the user assignment can be answered given the tools available say, "This is a reasonable assignment".
|
||||
If the user assignment cannot be answered then provide some feedback to the user that may improve
|
||||
their assignment.
|
||||
|
||||
Here is the context for the assignment:
|
||||
{context}
|
||||
|
||||
<|im_end|>
|
||||
|
||||
<|im_start|>user
|
||||
{assignment}
|
||||
<|im_end|>
|
||||
<|im_start|>assistant
|
||||
|
||||
"""
|
||||
|
||||
context = "You have access to the following tools:
|
||||
WineStock: useful for when you need to find info about wine by matching your description, price, name or ID. Input should be a search query with as much details as possible."
|
||||
prompt = replace(prompt, "{assignment}" => userMsg)
|
||||
prompt = replace(prompt, "{context}" => context)
|
||||
|
||||
output_py = llm(
|
||||
prompt,
|
||||
max_tokens=512,
|
||||
temperature=0.1,
|
||||
# top_p=top_p,
|
||||
echo=false,
|
||||
stop=["</response>", "<<END>>", ],
|
||||
)
|
||||
_output_jl = pyconvert(Dict, output_py);
|
||||
output = pyconvert(Dict, _output_jl["choices"][1]);
|
||||
output["text"]
|
||||
|
||||
end
|
||||
|
||||
|
||||
""" Add chunked text to a short term memory of a chat agent
|
||||
|
||||
Args:
|
||||
shortMem = short memory of a chat agent,
|
||||
chunkedtext = a dict contains text
|
||||
|
||||
Return: no return
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> chunkedtext = OrderedDict{String, String}(
|
||||
"Thought 1:" => " I should always think about...",
|
||||
"Act 1:" => " wikisearch",
|
||||
"ActInput 1:" => " latest AMD GPU",)
|
||||
julia> shortMem = OrderedDict{String, Any}()
|
||||
julia> addShortMem!(shortMem, chunkedtext)
|
||||
OrderedDict{String, Any} with 3 entries:
|
||||
"Thought 1:" => " I should always think about..."
|
||||
"Act 1:" => " wikisearch"
|
||||
"ActInput 1:" => " latest AMD GPU"
|
||||
```
|
||||
"""
|
||||
function addShortMem!(shortMem::OrderedDict{String, Any}, chunkedtext::T) where {T<:AbstractDict}
|
||||
for (k, v) in chunkedtext
|
||||
shortMem[k] = v
|
||||
end
|
||||
return shortMem
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
""" Split text using all keywords in a list. Start spliting from rightmost of the text.
|
||||
|
||||
Args:
|
||||
text = a text you want to split
|
||||
list = a list of keywords you want to split
|
||||
|
||||
Return:
|
||||
a leftmost text after split
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> text = "Consider the type of food, occasion and temperature at the serving location."
|
||||
julia> list = ["at", "and"]
|
||||
"Consider the type of food, occasion "
|
||||
```
|
||||
"""
|
||||
function splittext(text, list)
|
||||
newtext = text
|
||||
for i in list
|
||||
newtext = split(newtext, i)[1]
|
||||
end
|
||||
return newtext
|
||||
end
|
||||
|
||||
|
||||
|
||||
"""
|
||||
Add step number to header in a text
|
||||
"""
|
||||
function addStepNumber(text::T, headers, step::Int) where {T<:AbstractString}
|
||||
newtext = text
|
||||
for i in headers
|
||||
if occursin(i[:char], newtext)
|
||||
new = replace(i[:char], ":"=> " $step:")
|
||||
newtext = replace(newtext, i[:char]=>new )
|
||||
end
|
||||
end
|
||||
return newtext
|
||||
end
|
||||
function addStepNumber(text::T, headers, step::Int, substep::Int) where {T<:AbstractString}
|
||||
newtext = text
|
||||
for i in headers
|
||||
if occursin(i[:char], newtext)
|
||||
new = replace(i[:char], ":"=> " $step-$substep:")
|
||||
newtext = replace(newtext, i[:char]=>new )
|
||||
end
|
||||
end
|
||||
return newtext
|
||||
end
|
||||
|
||||
|
||||
""" Add step number to header in a text
|
||||
|
||||
Args:
|
||||
text = a text you want to split
|
||||
headers = a list of keywords you want to add step and substep to
|
||||
|
||||
Return:
|
||||
a leftmost text after split
|
||||
|
||||
# Example
|
||||
```jldoctest
|
||||
julia> text = "Consider the type of food, occasion and temperature at the serving location."
|
||||
julia> headers = ["Thought", "Act"]
|
||||
|
||||
```
|
||||
"""
|
||||
function replaceHeaders(text::T, headers, step::Int) where {T<:AbstractString}
|
||||
newtext = text
|
||||
for i in headers
|
||||
header = i[1:end-1] # not include ":"
|
||||
if occursin(header, newtext)
|
||||
startind = findfirst(header, newtext)[1]
|
||||
stopind = findnext(":", newtext, startind+1)[end]
|
||||
word = newtext[startind: stopind]
|
||||
newword = "$header $step:"
|
||||
newtext = replace(newtext, word=> newword)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return newtext
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end # end module
|
||||
Reference in New Issue
Block a user