update
This commit is contained in:
+143
-2
@@ -1,6 +1,6 @@
|
|||||||
module agentCore
|
module agentCore
|
||||||
|
|
||||||
export _agent_loop, OpenAiToUserMessage
|
export yiemAgent, _agent_loop, OpenAiToUserMessage
|
||||||
|
|
||||||
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||||
DataFrames, Base.Threads
|
DataFrames, Base.Threads
|
||||||
@@ -9,6 +9,147 @@ using ..type, ..utils
|
|||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
# ---------------------------------------------- 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.
|
Private agent loop. Runs in a background `@spawn` task.
|
||||||
|
|
||||||
@@ -526,7 +667,7 @@ function prepareToolCall(
|
|||||||
prepared = prepareToolCallArguments(tool, toolCall)
|
prepared = prepareToolCallArguments(tool, toolCall)
|
||||||
validatedArgs = validateToolArguments(tool, prepared)
|
validatedArgs = validateToolArguments(tool, prepared)
|
||||||
|
|
||||||
#WORKING 2. beforeToolCall hook — can block
|
# 2. beforeToolCall hook — can block
|
||||||
if config.beforeToolCall !== nothing
|
if config.beforeToolCall !== nothing
|
||||||
before = config.beforeToolCall(
|
before = config.beforeToolCall(
|
||||||
beforeToolCallContext(assistantMsg, toolCall, validatedArgs, context),
|
beforeToolCallContext(assistantMsg, toolCall, validatedArgs, context),
|
||||||
|
|||||||
+1
-143
@@ -5,153 +5,11 @@ export prompt
|
|||||||
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||||
DataFrames
|
DataFrames
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
using ..type, ..utils, ..toolRegistry
|
using ..type, ..utils, ..agentCore, ..toolRegistry
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
# ---------------------------------------------- 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.
|
Send a message to the agent's input channel.
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -2,7 +2,8 @@ module utils
|
|||||||
|
|
||||||
export clearhistory, availableWineToText, prepareContext, formatMsgForLLM, validateRequiredArgs,
|
export clearhistory, availableWineToText, prepareContext, formatMsgForLLM, validateRequiredArgs,
|
||||||
validateToolArguments, _userMessageToOpenAI,
|
validateToolArguments, _userMessageToOpenAI,
|
||||||
_assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks
|
_assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks,
|
||||||
|
beforeToolCall
|
||||||
|
|
||||||
using UUIDs, Dates, DataStructures, HTTP, JSON
|
using UUIDs, Dates, DataStructures, HTTP, JSON
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
@@ -219,7 +220,7 @@ function formatMsgForLLM(ctx::agentContext)::Dict{String, Any}
|
|||||||
return Dict("messages" => messages)
|
return Dict("messages" => messages)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#TODO
|
||||||
function beforeToolCall(context::beforeToolCallContext, signal::abortSignal)::beforeToolCallResult
|
function beforeToolCall(context::beforeToolCallContext, signal::abortSignal)::beforeToolCallResult
|
||||||
# final context check
|
# final context check
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user