From 7c14390400d00a8a651df887551456e6cf3735ed Mon Sep 17 00:00:00 2001 From: narawat Date: Tue, 11 Aug 2026 17:28:25 +0700 Subject: [PATCH] update --- src/agentCore.jl | 145 ++++++++++++++++++++++++++++++++++++++++++++++- src/api.jl | 144 +--------------------------------------------- src/utils.jl | 5 +- 3 files changed, 147 insertions(+), 147 deletions(-) diff --git a/src/agentCore.jl b/src/agentCore.jl index 41fd969..e39e7a6 100644 --- a/src/agentCore.jl +++ b/src/agentCore.jl @@ -1,6 +1,6 @@ module agentCore -export _agent_loop, OpenAiToUserMessage +export yiemAgent, _agent_loop, OpenAiToUserMessage using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization, DataFrames, Base.Threads @@ -9,6 +9,147 @@ using ..type, ..utils # ---------------------------------------------- 100 --------------------------------------------- # +""" +docstring +""" +mutable struct yiemAgent <: agent # High-level agent wrapper + _state::agentState # Current state (prompt, model, messages, tools, etc.) + + # user sends prompt message to agent. if agent is idle, it process user message right away. + # if agent is running, it process user message after the current tool call finished. + inputChannel::Channel + + # Buffers messages the user sends while the agent is busy. Processed after all inputChannel + # messages are handled and the agent is idle (not using a tool call). + followUpChannel::Channel + + # agent sends response message to user after processing all user messages in inputChannel + # and all followUp messages. + outputChannel::Channel + + _agent_loop::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. + # returns new Vector{agentMessage} + prepareContext::Union{Function, Nothing} + + # Convert prepareContext()'s new Vector{agentMessage} to LLM message format + formatMsgForLLM::Function + + # Actually invoke the LLM to get a completion response. The LLM response comes back as an + # assistantMessage whose content is an array of content blocks. + # Each block has a type — "text", "thinking", or "toolCall". + # The code filters for type === "toolCall" blocks, then passes them to executeToolCalls(). + llmCall::Function + + # Callback invoked before executing a tool call (ask for user permission/confirmation/abort, etc..) + beforeToolCall::Union{Function, Nothing} + + executeToolCalls::Function # execute tool calls () + + # Callback invoked after executing a tool call to sanitize tools output so the output is ready + # to be converted into toolResults message + afterToolCall::Union{Function, Nothing} + # prepareNextTurn::Union{Function, Nothing} # Callback to prepare the next conversation turn + # prepareNextTurnWithContext::Union{Function, Nothing} # Same but receives context + sessionId::Union{String, Nothing} # Optional session identifier + maxRetryDelayMs::Union{Int64, Nothing} # Maximum delay between retries (ms) + parallelToolExecute::Bool # Default: false + agentEventSink::Function # agent emits its status via this function + _tool_store::Any # Reference to the toolStore for runtime registration + end + +""" +Create a new yiemAgent instance with a background loop task. + +Spawns a background `@spawn` task that runs the agent loop, listening +on `inputChannel` and `followUpChannel` channels concurrently. + +# Keyword Arguments +- `systemPrompt::String`: System prompt for the agent +- `model`: LLM model to use +- `tools::OrderedDict{String, agentTool}`: Available tools keyed by name (default: empty) +- `messages::Vector{agentMessage}`: Initial conversation messages (default: empty) +- `formatMsgForLLM::Function`: Convert agent messages to LLM message format (default: `defaultformatMsgForLLM`) +- `llmCall::Function`: Function to invoke the LLM (required) +- `prepareContext::Union{Function, Nothing}`: Preprocess/transform messages before sending to LLM (default: `nothing`) +- `beforeToolCall::Union{Function, Nothing}`: Callback invoked before executing a tool call (default: `nothing`) +- `afterToolCall::Union{Function, Nothing}`: Callback invoked after executing a tool call (default: `nothing`) +- `prepareNextTurn::Union{Function, Nothing}`: Callback to prepare the next conversation turn (default: `nothing`) +- `prepareNextTurnWithContext::Union{Function, Nothing}`: Same but receives context (default: `nothing`) +- `sessionId::Union{String, Nothing}`: Optional session identifier (default: `nothing`) +- `maxRetryDelayMs::Union{Int64, Nothing}`: Maximum delay between retries in milliseconds (default: `nothing`) +- `parallelToolExecute::Bool`: Run tool calls in parallel (default: `false`) +- `agentEventSink::Function`: Callback to receive agent events +- `tool_store::Union{Any, Nothing}`: toolStore for runtime tool registration (default: `nothing`) + +# Returns +- A new `yiemAgent` instance with an active background task + +# Examples +```julia +julia> store = toolStore(name="agent1") +julia> tools = loadTools(store, "src/tools") +julia> agent = yiemAgent(systemPrompt="You are a helpful assistant", model=my_model, tools=tools, llmCall=..., tool_store=store) +yiemAgent(agentState(...), Channel(...), Channel(...), Channel(...), ..., store) +""" +function yiemAgent( + toolsFolderPath::String, + llmCall::Function, + ; + systemPrompt::String="You are helpful assistant.", + model=nothing, + messages::Vector{agentMessage}=agentMessage[], + prepareContext::Function=prepareContext, + formatMsgForLLM::Function=formatMsgForLLM, + beforeToolCall::Function=beforeToolCall, + afterToolCall::Function, + # prepareNextTurn::Union{Function, Nothing}=nothing, + # prepareNextTurnWithContext::Union{Function, Nothing}=nothing, + sessionId::Union{String, Nothing}=nothing, + maxRetryDelayMs::Union{Int64, Nothing}=nothing, + parallelToolExecute::Bool=false, + agentEventSink::Function, + tool_store::Union{Any, Nothing}=nothing, + ) + # Create channels: input (user -> agent), followUp (async queue), output (agent -> user) + inputChannel = Channel(16) + followUp = Channel(32) + outputChannel = Channel(16) + + # load tools from toolsFolderPath + toolStore = YiemAgent.toolStore(name="myagent") + loadTools(toolStore, toolsFolderPath) + + # Create struct with a placeholder task, then spawn and replace it + agent = yiemAgent( + agentState(systemPrompt, model, getTools(toolStore), messages), + inputChannel, + followUp, + outputChannel, + nothing, # placeholder — replaced below + prepareContext, + formatMsgForLLM, + llmCall, + beforeToolCall, + afterToolCall, + # prepareNextTurn, + # prepareNextTurnWithContext, + sessionId, + maxRetryDelayMs, + parallelToolExecute, + agentEventSink, + tool_store, + ) + + # Spawn the background loop and attach it + agent._agent_loop = @spawn _agent_loop(agent) + + return agent +end + + """ Private agent loop. Runs in a background `@spawn` task. @@ -526,7 +667,7 @@ function prepareToolCall( prepared = prepareToolCallArguments(tool, toolCall) validatedArgs = validateToolArguments(tool, prepared) - #WORKING 2. beforeToolCall hook — can block + # 2. beforeToolCall hook — can block if config.beforeToolCall !== nothing before = config.beforeToolCall( beforeToolCallContext(assistantMsg, toolCall, validatedArgs, context), diff --git a/src/api.jl b/src/api.jl index deddf01..e7e2dae 100644 --- a/src/api.jl +++ b/src/api.jl @@ -5,153 +5,11 @@ export prompt using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization, DataFrames using GeneralUtils -using ..type, ..utils, ..toolRegistry +using ..type, ..utils, ..agentCore, ..toolRegistry # ---------------------------------------------- 100 --------------------------------------------- # -""" -docstring -""" -mutable struct yiemAgent <: agent # High-level agent wrapper - _state::agentState # Current state (prompt, model, messages, tools, etc.) - - # user sends prompt message to agent. if agent is idle, it process user message right away. - # if agent is running, it process user message after the current tool call finished. - inputChannel::Channel - - # Buffers messages the user sends while the agent is busy. Processed after all inputChannel - # messages are handled and the agent is idle (not using a tool call). - followUpChannel::Channel - - # agent sends response message to user after processing all user messages in inputChannel - # and all followUp messages. - outputChannel::Channel - - _agent_loop::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. - # returns new Vector{agentMessage} - prepareContext::Union{Function, Nothing} - - # Convert prepareContext()'s new Vector{agentMessage} to LLM message format - formatMsgForLLM::Function - - # Actually invoke the LLM to get a completion response. The LLM response comes back as an - # assistantMessage whose content is an array of content blocks. - # Each block has a type — "text", "thinking", or "toolCall". - # The code filters for type === "toolCall" blocks, then passes them to executeToolCalls(). - llmCall::Function - - # Callback invoked before executing a tool call (ask for user permission/confirmation/abort, etc..) - beforeToolCall::Union{Function, Nothing} - - executeToolCalls::Function # execute tool calls () - - # Callback invoked after executing a tool call to sanitize tools output so the output is ready - # to be converted into toolResults message - afterToolCall::Union{Function, Nothing} - # prepareNextTurn::Union{Function, Nothing} # Callback to prepare the next conversation turn - # prepareNextTurnWithContext::Union{Function, Nothing} # Same but receives context - sessionId::Union{String, Nothing} # Optional session identifier - maxRetryDelayMs::Union{Int64, Nothing} # Maximum delay between retries (ms) - parallelToolExecute::Bool # Default: false - agentEventSink::Function # agent emits its status via this function - _tool_store::Any # Reference to the toolStore for runtime registration - end - -""" -Create a new yiemAgent instance with a background loop task. - -Spawns a background `@spawn` task that runs the agent loop, listening -on `inputChannel` and `followUpChannel` channels concurrently. - -# Keyword Arguments -- `systemPrompt::String`: System prompt for the agent -- `model`: LLM model to use -- `tools::OrderedDict{String, agentTool}`: Available tools keyed by name (default: empty) -- `messages::Vector{agentMessage}`: Initial conversation messages (default: empty) -- `formatMsgForLLM::Function`: Convert agent messages to LLM message format (default: `defaultformatMsgForLLM`) -- `llmCall::Function`: Function to invoke the LLM (required) -- `prepareContext::Union{Function, Nothing}`: Preprocess/transform messages before sending to LLM (default: `nothing`) -- `beforeToolCall::Union{Function, Nothing}`: Callback invoked before executing a tool call (default: `nothing`) -- `afterToolCall::Union{Function, Nothing}`: Callback invoked after executing a tool call (default: `nothing`) -- `prepareNextTurn::Union{Function, Nothing}`: Callback to prepare the next conversation turn (default: `nothing`) -- `prepareNextTurnWithContext::Union{Function, Nothing}`: Same but receives context (default: `nothing`) -- `sessionId::Union{String, Nothing}`: Optional session identifier (default: `nothing`) -- `maxRetryDelayMs::Union{Int64, Nothing}`: Maximum delay between retries in milliseconds (default: `nothing`) -- `parallelToolExecute::Bool`: Run tool calls in parallel (default: `false`) -- `agentEventSink::Function`: Callback to receive agent events -- `tool_store::Union{Any, Nothing}`: toolStore for runtime tool registration (default: `nothing`) - -# Returns -- A new `yiemAgent` instance with an active background task - -# Examples -```julia -julia> store = toolStore(name="agent1") -julia> tools = loadTools(store, "src/tools") -julia> agent = yiemAgent(systemPrompt="You are a helpful assistant", model=my_model, tools=tools, llmCall=..., tool_store=store) -yiemAgent(agentState(...), Channel(...), Channel(...), Channel(...), ..., store) -""" -function yiemAgent( - toolsFolderPath::String, - llmCall::Function, - ; - systemPrompt::String="You are helpful assistant.", - model=nothing, - messages::Vector{agentMessage}=agentMessage[], - prepareContext::Function=prepareContext, - formatMsgForLLM::Function=formatMsgForLLM, - beforeToolCall::Function, - afterToolCall::Function, - # prepareNextTurn::Union{Function, Nothing}=nothing, - # prepareNextTurnWithContext::Union{Function, Nothing}=nothing, - sessionId::Union{String, Nothing}=nothing, - maxRetryDelayMs::Union{Int64, Nothing}=nothing, - parallelToolExecute::Bool=false, - agentEventSink::Function, - tool_store::Union{Any, Nothing}=nothing, - ) - # Create channels: input (user -> agent), followUp (async queue), output (agent -> user) - inputChannel = Channel(16) - followUp = Channel(32) - outputChannel = Channel(16) - - # load tools from toolsFolderPath - toolStore = YiemAgent.toolStore(name="myagent") - loadTools(toolStore, toolsFolderPath) - - # Create struct with a placeholder task, then spawn and replace it - agent = yiemAgent( - agentState(systemPrompt, model, getTools(toolStore), messages), - inputChannel, - followUp, - outputChannel, - nothing, # placeholder — replaced below - prepareContext, - formatMsgForLLM, - llmCall, - beforeToolCall, - afterToolCall, - # prepareNextTurn, - # prepareNextTurnWithContext, - sessionId, - maxRetryDelayMs, - parallelToolExecute, - agentEventSink, - tool_store, - ) - - # Spawn the background loop and attach it - agent._agent_loop = @spawn _agent_loop(agent) - - return agent -end - - - """ Send a message to the agent's input channel. diff --git a/src/utils.jl b/src/utils.jl index 4300cb0..8dca2a4 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -2,7 +2,8 @@ module utils export clearhistory, availableWineToText, prepareContext, formatMsgForLLM, validateRequiredArgs, validateToolArguments, _userMessageToOpenAI, - _assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks + _assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks, + beforeToolCall using UUIDs, Dates, DataStructures, HTTP, JSON using GeneralUtils @@ -219,7 +220,7 @@ function formatMsgForLLM(ctx::agentContext)::Dict{String, Any} return Dict("messages" => messages) end - +#TODO function beforeToolCall(context::beforeToolCallContext, signal::abortSignal)::beforeToolCallResult # final context check