update
This commit is contained in:
+92
-55
@@ -1,6 +1,6 @@
|
||||
module agentCore
|
||||
|
||||
export yiemAgent, _agent_loop, OpenAiToUserMessage
|
||||
export yiemAgent, _agentLoop, OpenAiToUserMessage
|
||||
|
||||
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||
DataFrames, Base.Threads
|
||||
@@ -27,10 +27,10 @@ mutable struct yiemAgent <: agent # High-level agent wrapper
|
||||
# and all followUp messages.
|
||||
outputChannel::Channel
|
||||
|
||||
_agent_loop::Union{Task, Nothing} # agent loop running in the background
|
||||
_agentLoop::Union{Task, Nothing} # agent loop running in the background
|
||||
|
||||
# Preprocess/transform messages and context (modify, filter, prune, inject context from memory,
|
||||
# reorder, ...) for a single LLM call in _process_message()'s loop.
|
||||
# reorder, ...) for a single LLM call in _processMessage()'s loop.
|
||||
# returns new Vector{agentMessage}
|
||||
prepareContext::Union{Function, Nothing}
|
||||
|
||||
@@ -131,7 +131,7 @@ function yiemAgent(
|
||||
)
|
||||
|
||||
# Spawn the background loop and attach it
|
||||
agent._agent_loop = @spawn _agent_loop(agent)
|
||||
agent._agentLoop = @spawn _agentLoop(agent)
|
||||
|
||||
return agent
|
||||
end
|
||||
@@ -141,7 +141,7 @@ end
|
||||
Private agent loop. Runs in a background `@spawn` task.
|
||||
|
||||
Waits on `inputChannel` and `followUpChannel`, processing whichever has a message first.
|
||||
On each iteration, dispatches the message through `_process_message` and sends the result
|
||||
On each iteration, dispatches the message through `_processMessage` and sends the result
|
||||
to `outputChannel`. Exits on `:shutdown` signal.
|
||||
|
||||
# Arguments
|
||||
@@ -160,7 +160,8 @@ to `outputChannel`. Exits on `:shutdown` signal.
|
||||
julia> # Called automatically by yiemAgent constructor
|
||||
```
|
||||
"""
|
||||
function _agent_loop(agent::yiemAgent)
|
||||
function _agentLoop(agent::yiemAgent)
|
||||
processMessageInputCh = Channel(32)
|
||||
try
|
||||
processingTask = nothing
|
||||
|
||||
@@ -208,8 +209,8 @@ function _agent_loop(agent::yiemAgent)
|
||||
while msg === nothing
|
||||
if isready(agent.inputChannel)
|
||||
|
||||
# message will be taken in _process_message()
|
||||
msg = fetch(agent.inputChannel)
|
||||
# message will be taken in _processMessage()
|
||||
msg = take!(agent.inputChannel)
|
||||
agent.agentEventSink("new user msg")
|
||||
else
|
||||
yield()
|
||||
@@ -232,29 +233,50 @@ function _agent_loop(agent::yiemAgent)
|
||||
|
||||
#TODO make sure every running tools ended properly
|
||||
break
|
||||
else
|
||||
agent.agentEventSink("_agentLoop push 1")
|
||||
put!(processMessageInputCh, msg) #WORKING
|
||||
agent.agentEventSink("_agentLoop push 2")
|
||||
end
|
||||
|
||||
# start _process_message loop
|
||||
# start _processMessage loop
|
||||
if agent._state.activeRun == false
|
||||
agent.agentEventSink("_agent_loop 2")
|
||||
agent.agentEventSink("_agentLoop 2")
|
||||
# Dispatch message through the processing pipeline
|
||||
processingTask = Threads.@spawn _process_message(agent)
|
||||
processingTask = @spawn _processMessage(
|
||||
processMessageInputCh,
|
||||
agent.agentEventSink,
|
||||
agent._state.messages,
|
||||
agent._state.systemPrompt,
|
||||
agent._state.tools,
|
||||
agent.prepareContext,
|
||||
agent.formatMsgForLLM,
|
||||
agent.llmCall,
|
||||
agent.beforeToolCall,
|
||||
agent.afterToolCall,
|
||||
agent.parallelToolExecute,
|
||||
)
|
||||
agent._state.activeRun = true
|
||||
agent.agentEventSink("_agent_loop 3")
|
||||
agent.agentEventSink("_agentLoop 3")
|
||||
end
|
||||
|
||||
# during agent runs, check followUp message after _process_message() is done
|
||||
|
||||
# during agent runs, check followUp message after _processMessage() is done
|
||||
if typeof(processingTask) == Task && istaskdone(processingTask) == false
|
||||
agent.agentEventSink("_agentLoop 4")
|
||||
# if followUp message available, add them all to agent.inputChannel
|
||||
if isready(agent.followUpChannel)
|
||||
agent.agentEventSink("_agentLoop 4-1")
|
||||
while isready(agent.followUpChannel)
|
||||
agent.agentEventSink("_agentLoop 4-2")
|
||||
followMsg = take!(agent.followUpChannel)
|
||||
put!(agent.inputChannel, followMsg)
|
||||
end
|
||||
end
|
||||
agent.agentEventSink("_agentLoop 4-3")
|
||||
continue # continue to process user message in the next loop
|
||||
|
||||
elseif typeof(processingTask) == Task && istaskdone(processingTask) == true
|
||||
agent.agentEventSink("_agentLoop 5")
|
||||
# if agent runs is done but followUpChannel has messages, discard all message in it.
|
||||
# when agent work is done it should not accept follow up msg.
|
||||
# user should put new message in inputChannel instead
|
||||
@@ -268,6 +290,7 @@ function _agent_loop(agent::yiemAgent)
|
||||
agent._state.activeRun = false # reset
|
||||
processingTask = nothing # reset
|
||||
end
|
||||
agent.agentEventSink("_agentLoop 6")
|
||||
end
|
||||
catch e
|
||||
# On any error, send error response and exit the loop
|
||||
@@ -303,13 +326,25 @@ should be implemented. Currently a placeholder that echoes back the received mes
|
||||
julia> # Currently returns a placeholder echo response
|
||||
```
|
||||
"""
|
||||
function _process_message(agent::yiemAgent)::assistantMessage
|
||||
agent.agentEventSink("_process_message 1")
|
||||
function _processMessage(
|
||||
inputChannel::Channel,
|
||||
agentEventSink,
|
||||
messages::Vector{agentMessage},
|
||||
systemPrompt::String,
|
||||
tools::OrderedDict{String, agentTool},
|
||||
prepareContext::Function,
|
||||
formatMessagesForLLM::Function,
|
||||
llmCall,
|
||||
beforeToolCall::Union{Function, Nothing},
|
||||
afterToolCall::Union{Function, Nothing},
|
||||
parallelToolExecute::Bool,
|
||||
)::assistantMessage
|
||||
agentEventSink("_processMessage 1")
|
||||
# loop until llmCall() response didn't use tool calls
|
||||
final_response = nothing
|
||||
while true
|
||||
|
||||
""" example message in agent.inputChannel
|
||||
""" example message in inputChannel
|
||||
Dict(
|
||||
"role" => "user",
|
||||
"content" => [
|
||||
@@ -323,42 +358,44 @@ function _process_message(agent::yiemAgent)::assistantMessage
|
||||
"""
|
||||
|
||||
# Drain inputChannel and convert OpenAI-format messages to userMessage type
|
||||
while isready(agent.inputChannel)
|
||||
agent.agentEventSink("_process_message 2")
|
||||
raw_msg = take!(agent.inputChannel)
|
||||
agent.agentEventSink("_process_message 3")
|
||||
while isready(inputChannel)
|
||||
agentEventSink("_processMessage 2")
|
||||
raw_msg = take!(inputChannel)
|
||||
agentEventSink("_processMessage 3")
|
||||
if raw_msg === :shutdown
|
||||
agent.agentEventSink("_process_message 4")
|
||||
agentEventSink("_processMessage 4")
|
||||
# Re-emit shutdown signal for the loop to handle
|
||||
put!(agent.inputChannel, :shutdown)
|
||||
put!(inputChannel, :shutdown)
|
||||
break
|
||||
end
|
||||
agent.agentEventSink("_process_message 5")
|
||||
agentEventSink("_processMessage 5")
|
||||
user_msg = OpenAiToUserMessage(raw_msg)
|
||||
push!(agent._state.messages, user_msg)
|
||||
agent.agentEventSink("_process_message 6")
|
||||
push!(messages, user_msg)
|
||||
agentEventSink("_processMessage 6")
|
||||
end
|
||||
agentEventSink("_processMessage 7")
|
||||
# call prepareContext()
|
||||
state = agentState(systemPrompt, nothing, tools, messages)
|
||||
agentEventSink("_processMessage 8")
|
||||
preparedContext = prepareContext(state)
|
||||
agentEventSink("_processMessage 8")
|
||||
# Call formatMessagesForLLM() to format for LLM
|
||||
formattedMessages = formatMessagesForLLM(preparedContext)
|
||||
|
||||
# call agent.prepareContext()
|
||||
preparedContext = agent.prepareContext(agent._state)
|
||||
|
||||
# Call agent.formatMsgForLLM(agent._state) to format for LLM
|
||||
formatted_messages = agent.formatMsgForLLM(preparedContext)
|
||||
|
||||
agent.agentEventSink("_process_message 7")
|
||||
agentEventSink("_processMessage 10")
|
||||
# Call llmCall() (blocking — the task waits here)
|
||||
response = llmCall(formattedMessages)
|
||||
agentEventSink(response)
|
||||
agentEventSink("_processMessage 11")
|
||||
error("debug marker")
|
||||
response = agent.llmCall(formatted_messages)
|
||||
agent.agentEventSink("_process_message 8")
|
||||
|
||||
#WORKING Check if LLM used tool calls (inspect content for tool_call blocks)
|
||||
has_tool_calls = false
|
||||
tool_call_list = agentToolCall[]
|
||||
# Check if LLM used tool calls (inspect content for tool_call blocks)
|
||||
hasToolCalls = false
|
||||
toolCallList = agentToolCall[]
|
||||
|
||||
for content_block in response.content
|
||||
if content_block isa Dict
|
||||
if get(content_block, :type, "") == "tool_calls"
|
||||
has_tool_calls = true
|
||||
hasToolCalls = true
|
||||
for tc_data in get(content_block, :tool_calls, [])
|
||||
tc = agentToolCall(
|
||||
type="function",
|
||||
@@ -366,10 +403,10 @@ function _process_message(agent::yiemAgent)::assistantMessage
|
||||
name=get(tc_data, :function, Dict{String,Any}())[:name],
|
||||
arguments=get(tc_data, :function, Dict{String,Any}())[:arguments],
|
||||
)
|
||||
push!(tool_call_list, tc)
|
||||
push!(toolCallList, tc)
|
||||
end
|
||||
elseif get(content_block, :type, "") == "tool_call"
|
||||
has_tool_calls = true
|
||||
hasToolCalls = true
|
||||
tc_data = content_block
|
||||
tc = agentToolCall(
|
||||
type="function",
|
||||
@@ -377,35 +414,35 @@ function _process_message(agent::yiemAgent)::assistantMessage
|
||||
name=get(tc_data, :name, ""),
|
||||
arguments=get(tc_data, :arguments, Dict{String,Any}()),
|
||||
)
|
||||
push!(tool_call_list, tc)
|
||||
push!(toolCallList, tc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if has_tool_calls && length(tool_call_list) > 0
|
||||
if hasToolCalls && length(toolCallList) > 0
|
||||
# Build context and config for executeToolCalls
|
||||
context = agentContext(
|
||||
agent._state.systemPrompt,
|
||||
agent._state.messages,
|
||||
agent._state.tools,
|
||||
systemPrompt,
|
||||
messages,
|
||||
tools,
|
||||
)
|
||||
|
||||
config = agentLoopConfig(
|
||||
agent._state.tools,
|
||||
agent.beforeToolCall,
|
||||
agent.afterToolCall,
|
||||
agent.parallelToolExecute ? "parallel" : "sequential",
|
||||
tools,
|
||||
beforeToolCall,
|
||||
afterToolCall,
|
||||
parallelToolExecute ? "parallel" : "sequential",
|
||||
)
|
||||
|
||||
signal = nothing
|
||||
emit = agent.agentEventSink
|
||||
emit = agentEventSink
|
||||
|
||||
# call executeToolCalls()
|
||||
batch = executeToolCalls(context, response, tool_call_list, config, signal, emit)
|
||||
batch = executeToolCalls(context, response, toolCallList, config, signal, emit)
|
||||
|
||||
# save toolResults to agent._state.messages
|
||||
# save toolResults to messages
|
||||
for tool_result in batch.messages
|
||||
push!(agent._state.messages, tool_result)
|
||||
push!(messages, tool_result)
|
||||
end
|
||||
|
||||
if batch.terminate
|
||||
|
||||
Reference in New Issue
Block a user