Files
YiemAgent/src/agentCore.jl
T
2026-08-04 19:45:08 +07:00

274 lines
6.5 KiB
Julia

module agentCore
# export prompt
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
DataFrames, Serde
using GeneralUtils
using ..type, ..util, ..llmfunction
# ---------------------------------------------- 100 --------------------------------------------- #
"""
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
to `outputChannel`. Exits on `:shutdown` signal.
# Arguments
- `agent::yiemAgent`: The agent whose loop to run
# Returns
- `nothing` — the loop runs until `:shutdown` is received or an error occurs
# Notes
- This function is automatically spawned as a background task when a `yiemAgent` is created.
- On any error, logs the error with `@error` and exits the loop.
- Message priority: `inputChannel` messages are checked before `followUpChannel` messages.
# Examples
```jldoctest
julia> # Called automatically by yiemAgent constructor
```
"""
function _agent_loop(agent::yiemAgent)
try
processing_task = nothing
""" cases:
1) agent -> idle, user msg -> nothing
typeof(processing_task) == Nothing
agent._state.activeRun -> false
agent.inputChannel -> nothing
agent.followUpChannel -> nothing
2) agent -> idle, user msg -> new msg
typeof(processing_task) == Nothing
agent._state.activeRun -> false
agent.inputChannel -> new msg
agent.followUpChannel -> nothing
3) agent -> running, user msg -> nothing
typeof(processing_task) == Task, istaskdone(processing_task) -> false
agent._state.activeRun -> true
agent.inputChannel -> nothing
agent.followUpChannel -> nothing
4) agent -> running, user msg -> new msg
typeof(processing_task) == Task, istaskdone(processing_task) -> false
agent._state.activeRun -> true
agent.inputChannel -> new msg
agent.followUpChannel -> nothing
5) agent -> running, user msg -> nothing, user msg follow up -> new msg
typeof(processing_task) == Task, istaskdone(processing_task) -> false
agent._state.activeRun -> true
agent.inputChannel -> nothing
agent.followUpChannel -> new msg
6) agent -> idle, user msg -> nothing
typeof(processing_task) == Task, istaskdone(processing_task) -> true
agent._state.activeRun -> false
agent.inputChannel -> nothing
agent.followUpChannel -> nothing
"""
while true
result = nothing
msg = nothing
while msg === nothing
if isready(agent.inputChannel)
# message will be taken then process in _process_message()
msg = fetch!(agent.inputChannel)
else
yield()
end
end
# Check for shutdown signal
if msg === :shutdown
# Drain all remaining messages in the input channel
if isready(agent.inputChannel)
while isready(agent.inputChannel)
_ = take!(agent.inputChannel)
end
end
if isready(agent.followUpChannel)
while isready(agent.followUpChannel)
_ = take!(agent.followUpChannel)
end
end
#TODO make sure every running tools ended properly
break
end
# make active
if agent._state.activeRun == false
# Dispatch message through the processing pipeline
processing_task = @spawn _process_message(agent, msg)
agent._state.activeRun = true
end
# during agent runs, check followUp message after _process_message() is done
if typeof(processing_task) == Task && istaskdone(processing_task) == false
# if followUp message available, add them all to agent.inputChannel
if isready(agent.followUpChannel)
while isready(agent.followUpChannel)
followMsg = take!(agent.followUpChannel)
put!(agent.inputChannel, followMsg)
end
end
continue # continue to process user message in the next loop
elseif typeof(processing_task) == Task && istaskdone(processing_task) == true
# 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
if isready(agent.followUpChannel)
while isready(agent.followUpChannel)
_ = take!(agent.followUpChannel)
end
end
result = fetch(processing_task)
put!(agent.outputChannel, result)
agent._state.activeRun = false
processing_task = nothing
end
end
catch e
# On any error, send error response and exit the loop
@error "Agent loop failed" error=e
end
end
"""
Process a single message through the agent pipeline.
This is the core processing function where LLM calls, tool execution, and response generation
should be implemented. Currently a placeholder that echoes back the received message.
# Arguments
- `agent::yiemAgent`: The agent processing the message
- `msg`: The message to process (from `inputChannel` or `followUpChannel`)
# Returns
- An `assistantMessage` instance with the processed response
# Notes
- Implement the full processing pipeline:
1. Add `msg` to `agent._state.messages`
2. Call `agent.formatMsgForLLM(agent._state)` 1 to format for LLM
3. If `agent.preprocessMessages` is set, call it on the formatted messages
4. Call the LLM (blocking — the task waits here)
5. If agent has tools, handle tool calls in a loop
6. Build `assistantMessage` and return it
# Examples
```jldoctest
julia> # Currently returns a placeholder echo response
```
"""
function _process_message(agent::yiemAgent, msg)
# WORKING Replace with actual processing logic
# 2. Call agent.formatMsgForLLM(agent._state) to format for LLM
# 3. If preprocessMessages is set, call agent.preprocessMessages(...)
# 4. Call the LLM (blocking — the task waits here)
# 5. If agent has tools, handle tool calls in a loop
# 6. Build assistantMessage and return it
# Placeholder: echo back the message as a simple response
@warn "TODO: implement _process_message"
return assistantMessage(
role="assistant",
content=[textContent("Received: $(msg)")],
api="", model="", usage=nothing,
stopReason="end_turn",
errorMessage=nothing,
timestamp=now(),
)
end
end # end of module