781 lines
26 KiB
Julia
781 lines
26 KiB
Julia
module type
|
|
export Timestamp,
|
|
# Abstract types
|
|
messageContent, agentMessage, agent,
|
|
# Model types
|
|
modelCost, llmModel, llmUsage,
|
|
# Message content types
|
|
textContent, imageContent,
|
|
# Message types
|
|
userMessage, assistantMessage, toolResultMessage,
|
|
# Tool types
|
|
agentTool, validateRequiredArgs,
|
|
# Context types
|
|
agentContext, agentState, agentToolCall, prepareNextTurnContext,
|
|
# Loop & execution types
|
|
agentLoopConfig, abortSignal, agentToolResult,beforeToolCallContext,
|
|
beforeToolCallResult, afterToolCallContext,
|
|
# Event types
|
|
toolExecStartEvent, toolExecUpdateEvent, toolExecEndEvent,
|
|
# Agent
|
|
yiemAgent,
|
|
# Tool call lifecycle types
|
|
preparedToolCall, immediateOutcome, executedOutcome, finalizedOutcome,
|
|
agentToolCallBatch,
|
|
# Functions (defined elsewhere)
|
|
runAgent, takeResponse, followUp, stopAgent
|
|
|
|
|
|
using Dates, UUIDs, DataStructures, JSON, NATS, Base.Threads
|
|
using GeneralUtils
|
|
|
|
const Timestamp = DateTime
|
|
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
# LLM model info #
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
|
|
struct modelCost # Model pricing per 1M tokens
|
|
input::Float64 # Price per 1M input tokens
|
|
output::Float64 # Price per 1M output tokens
|
|
cache_read::Float64 # Price per 1M cached read tokens
|
|
cache_write::Float64 # Price per 1M cache write tokens
|
|
end
|
|
|
|
struct llmModel # LLM model configuration
|
|
id::String # Unique model identifier
|
|
name::String # Human-readable model name
|
|
provider::String # Provider name (e.g., "anthropic", "openai")
|
|
baseUrl::String # API endpoint base URL
|
|
reasoning::Bool # Whether the model supports chain-of-thought
|
|
input::Vector{String} # Supported input modalities (e.g., "text", "image")
|
|
cost::modelCost # Pricing information
|
|
contextWindow::Int64 # Maximum context length in tokens
|
|
maxTokens::Int64 # Maximum output tokens per completion
|
|
end
|
|
|
|
struct llmUsage
|
|
inputTokens::Int64
|
|
outputTokens::Int64
|
|
end
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
# Message content types #
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
|
|
abstract type messageContent end # Base type for message content
|
|
|
|
struct textContent <: messageContent # Plain text message content
|
|
text::String # The text content
|
|
end
|
|
|
|
struct imageContent <: messageContent # Image message content
|
|
data::String # Base64-encoded image data
|
|
mimeType::String # MIME type (e.g., "image/png")
|
|
end
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
# Message types #
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
abstract type agentMessage end # Base type for all agent messages
|
|
|
|
struct userMessage <: agentMessage # Message from the user
|
|
role::String # Always "user"
|
|
content::Vector{messageContent} # Text and/or image content
|
|
timestamp::Timestamp # When the message was sent
|
|
end
|
|
|
|
"""
|
|
Create a new user message.
|
|
|
|
# Arguments
|
|
- `role::String`: Always "user"
|
|
- `content::Vector{messageContent}`: Text and/or image content
|
|
- `timestamp::Timestamp`: When the message was sent
|
|
|
|
# Returns
|
|
- A new `userMessage` instance
|
|
|
|
# Examples
|
|
```julia
|
|
julia> msg = userMessage(content=[textContent("Hello")])
|
|
userMessage("user", [textContent("Hello")], DateTime(...))
|
|
```
|
|
"""
|
|
function userMessage(; role="user", content=Vector{messageContent}(), timestamp=now())
|
|
return userMessage(role, content, timestamp)
|
|
end
|
|
|
|
struct assistantMessage <: agentMessage # Message from the AI assistant
|
|
role::String # Always "assistant"
|
|
content::Vector{messageContent} # Text and/or image content
|
|
api::String # API name used (e.g., "openai")
|
|
provider::String # Provider name (e.g., "anthropic")
|
|
model::String # Model identifier
|
|
usage::llmUsage # Token usage for this message
|
|
stopReason::String # Why generation stopped (e.g., "end_turn")
|
|
errorMessage::Union{String, Nothing} # Error if generation failed
|
|
timestamp::Timestamp # When the message was received
|
|
end
|
|
|
|
"""
|
|
Create a new assistant message.
|
|
|
|
# Arguments
|
|
- `role::String`: Always "assistant"
|
|
- `content::Vector{messageContent}`: Text and/or image content
|
|
- `api::String`: API name used (e.g., "openai")
|
|
- `provider::String`: Provider name (e.g., "anthropic")
|
|
- `model::String`: Model identifier
|
|
- `usage::llmUsage`: Token usage for this message
|
|
- `stopReason::String`: Why generation stopped (e.g., "end_turn")
|
|
- `errorMessage::Union{String, Nothing}`: Error if generation failed
|
|
- `timestamp::Timestamp`: When the message was received
|
|
|
|
# Returns
|
|
- A new `assistantMessage` instance
|
|
|
|
# Examples
|
|
```julia
|
|
julia> msg = assistantMessage(content=[textContent("Hello!")], model="gpt-4")
|
|
assistantMessage("assistant", [textContent("Hello!")], "", "", "gpt-4", ..., "end_turn", nothing, DateTime(...))
|
|
```
|
|
"""
|
|
function assistantMessage(; role="assistant", content=Vector{messageContent}(),
|
|
api="", provider="", model=nothing, usage=llmUsage(0, 0), stopReason="end_turn",
|
|
errorMessage=nothing, timestamp=now())
|
|
model_str = model isa AbstractString ? String(model) : ""
|
|
return assistantMessage(role, content, api, provider, model_str, usage, stopReason, errorMessage, timestamp)
|
|
end
|
|
|
|
struct toolResultMessage <: agentMessage # Result returned from a tool execution
|
|
role::String # Always "tool"
|
|
toolCallId::String # ID matching the tool call
|
|
toolName::String # Name of the executed tool
|
|
content::Vector{messageContent} # Tool output content
|
|
details::Any # Additional tool-specific details
|
|
usage::Union{llmUsage, Nothing} # Token usage if applicable
|
|
addedToolNames::Union{Vector{String}, Nothing} # Tools added during execution
|
|
isError::Bool # Whether the tool call resulted in an error
|
|
timestamp::Timestamp # When the result was recorded
|
|
end
|
|
|
|
"""
|
|
Create a new tool result message.
|
|
|
|
# Arguments
|
|
- `role::String`: Always "tool"
|
|
- `toolCallId::String`: ID matching the tool call
|
|
- `toolName::String`: Name of the executed tool
|
|
- `content::Vector{messageContent}`: Tool output content
|
|
- `details::Any`: Additional tool-specific details
|
|
- `usage::Union{llmUsage, Nothing}`: Token usage if applicable
|
|
- `addedToolNames::Union{Vector{String}, Nothing}`: Tools added during execution
|
|
- `isError::Bool`: Whether the tool call resulted in an error
|
|
- `timestamp::Timestamp`: When the result was recorded
|
|
|
|
# Returns
|
|
- A new `toolResultMessage` instance
|
|
|
|
# Examples
|
|
```julia
|
|
julia> msg = toolResultMessage(toolCallId="call_123", toolName="search", content=[textContent("results")])
|
|
toolResultMessage("tool", "call_123", "search", [textContent("results")], nothing, nothing, nothing, false, DateTime(...))
|
|
```
|
|
"""
|
|
function toolResultMessage(; role="tool", toolCallId="", toolName="",
|
|
content=Vector{messageContent}(), details=nothing, usage=nothing,
|
|
addedToolNames=nothing, isError=false, timestamp=now())
|
|
return toolResultMessage(role, toolCallId, toolName, content, details, usage, addedToolNames, isError, timestamp)
|
|
end
|
|
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
# Tool types #
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
|
|
"""
|
|
A tool available to the agent.
|
|
|
|
Maps MCP server tool definitions to an executable Julia tool.
|
|
|
|
# Arguments
|
|
- `name::String`: Tool identifier (from MCP `name`)
|
|
- `label::String`: Human-readable tool name (from MCP `title`)
|
|
- `description::String`: What the tool does (from MCP `description`)
|
|
- `inputSchema::Any`: Tool parameters schema (from MCP `inputSchema`, JSON Schema format)
|
|
- `execute::Function`: Tool execution function, signature:
|
|
`execute(toolCallId::String, args::Dict, signal::Union{Nothing,AbortSignal}, onPartialResult::Function)`
|
|
- `prepareArguments::Union{Function, Nothing}`: Optional argument preparation callback
|
|
- `validateRequiredArgs::Union{Function, Nothing}`: Optional validation hook, signature:
|
|
`validateRequiredArgs(args::Dict) -> Union{Nothing, String}` where `String` is an error message
|
|
- `parallelToolExecute::Bool`: Override: run tool calls sequentially or in parallel
|
|
|
|
# Returns
|
|
- A new `agentTool` instance
|
|
|
|
# MCP Tool Example
|
|
```
|
|
{
|
|
"name": "getWeather",
|
|
"title": "Weather Lookup",
|
|
"description": "Fetch current weather and forecast for a given city.",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"city": { "type": "string", "description": "City and state/country" },
|
|
"units": { "type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius" }
|
|
},
|
|
"required": ["city"]
|
|
}
|
|
}
|
|
```
|
|
|
|
# Example
|
|
```julia
|
|
tool = agentTool(
|
|
name="getWeather",
|
|
label="Weather Lookup",
|
|
description="Fetch current weather and forecast for a given city.",
|
|
inputSchema=Dict(
|
|
"type" => "object",
|
|
"properties" => Dict(
|
|
"city" => Dict("type" => "string", "description" => "City name"),
|
|
"units" => Dict("type" => "string", "enum" => ["celsius", "fahrenheit"])
|
|
),
|
|
"required" => ["city"]
|
|
),
|
|
execute=(toolCallId, args, signal, onPartialResult) -> begin
|
|
city = args["city"]
|
|
return agentToolResult(
|
|
[textContent("Sunny, 22C in Bangkok")],
|
|
Dict{Any,Any}(), nothing, false
|
|
)
|
|
end,
|
|
prepareArguments = nothing,
|
|
validateRequiredArgs = nothing,
|
|
parallelToolExecute = false
|
|
)
|
|
```
|
|
"""
|
|
struct agentTool # A tool available to the agent
|
|
name::String # Tool identifier
|
|
label::String # Human-readable tool name
|
|
description::String # What the tool does
|
|
inputSchema::Any # Tool parameters schema (JSON schema, MCP inputSchema format)
|
|
execute # Tool execution function
|
|
prepareArguments::Union{Function, Nothing} # Optional argument preparation callback
|
|
validateRequiredArgs::Union{Function, Nothing} # Optional validation hook for required args
|
|
parallelToolExecute::Bool # Override: run tool calls sequentially or in parallel
|
|
end
|
|
|
|
"""
|
|
Keyword constructor for agentTool — allows `agentTool(name=..., label=..., ...)`.
|
|
"""
|
|
function agentTool(; name::String, label::String, description::String, inputSchema::Any,
|
|
execute, prepareArguments::Union{Function, Nothing}=nothing,
|
|
validateRequiredArgs::Union{Function, Nothing}=nothing,
|
|
parallelToolExecute::Bool=false)
|
|
return agentTool(name, label, description, inputSchema, execute,
|
|
prepareArguments, validateRequiredArgs, parallelToolExecute)
|
|
end
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
# Agent context #
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
|
|
"""
|
|
Snapshot of the agent's conversation context.
|
|
|
|
# Arguments
|
|
- `systemPrompt::String`: System prompt for the agent
|
|
- `messages::Vector{agentMessage}`: Conversation messages
|
|
- `tools::Union{OrderedDict{String, agentTool}, Nothing}`: Available tools keyed by name for O(1) lookup
|
|
|
|
# Returns
|
|
- A new `agentContext` instance
|
|
"""
|
|
struct agentContext # Snapshot of the agent's conversation context
|
|
systemPrompt::String # System prompt for the agent
|
|
messages::Vector{agentMessage} # Conversation messages
|
|
tools::Union{OrderedDict{String, agentTool}, Nothing} # Available tools keyed by name
|
|
end
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
# Agent state #
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
|
|
mutable struct agentState # Mutable runtime state of an agent
|
|
systemPrompt::String # System prompt for the agent
|
|
model::Union{llmModel, Nothing} # LLM model to use
|
|
tools::OrderedDict{String, agentTool} # Available tools keyed by name, insertion-ordered
|
|
|
|
# messages history includes userMessage, assistantMessage, toolResultMessage. NO system prompt
|
|
messages::Vector{agentMessage}
|
|
|
|
pendingToolCalls::Vector{String} # Tool call IDs waiting for results
|
|
errorMessage::Union{String, Nothing} # Last error message
|
|
end
|
|
|
|
"""
|
|
Create a new mutable agent state.
|
|
|
|
Creates a deep copy of the provided tools and messages to isolate the
|
|
new state from external references.
|
|
|
|
# Arguments
|
|
- `systemPrompt::String`: System prompt text
|
|
- `model::llmModel`: LLM model to use (defaults to an unknown model)
|
|
- `tools::OrderedDict{String, agentTool}`: Available tools keyed by name (deep copied)
|
|
- `messages::Vector{agentMessage}`: Conversation messages (deep copied)
|
|
|
|
# Returns
|
|
- A new `agentState` instance with an empty pending tool calls list and no error
|
|
|
|
# Examples
|
|
```julia
|
|
julia> state = agentState(systemPrompt="You are a helpful assistant")
|
|
agentState("You are a helpful assistant", OrderedDict{String, agentTool}(), agentMessage[], String[], nothing)
|
|
"""
|
|
function agentState(
|
|
systemPrompt::String="",
|
|
model=llmModel("model_1", "unknown", "unknown", "", false, String[],
|
|
modelCost(0.0, 0.0, 0.0, 0.0), 0, 0),
|
|
tools::OrderedDict{String, agentTool}=OrderedDict{String, agentTool}(),
|
|
messages::Vector{agentMessage}=agentMessage[],
|
|
)
|
|
agentState(
|
|
systemPrompt,
|
|
model,
|
|
deepcopy(tools),
|
|
deepcopy(messages),
|
|
Vector{String}(),
|
|
nothing,
|
|
)
|
|
end
|
|
|
|
|
|
struct agentToolCall # A tool invocation from the LLM
|
|
type::String # Always "function"
|
|
id::String # Unique tool call identifier
|
|
name::String # Tool name
|
|
arguments::Dict{String, Any} # Parsed tool arguments
|
|
end
|
|
|
|
|
|
"""
|
|
Context for preparing the next conversation turn.
|
|
|
|
# Arguments
|
|
- `message::assistantMessage`: The assistant's message that just completed
|
|
- `toolResults::Vector{toolResultMessage}`: Tool results from this turn
|
|
- `context::agentContext`: Current conversation context
|
|
- `newMessages::Vector{agentMessage}`: Messages to append to the context
|
|
|
|
# Returns
|
|
- A new `prepareNextTurnContext` instance
|
|
"""
|
|
struct prepareNextTurnContext # Context for preparing the next conversation turn
|
|
message::assistantMessage # The assistant's message that just completed
|
|
toolResults::Vector{toolResultMessage} # Tool results from this turn
|
|
context::agentContext # Current conversation context
|
|
newMessages::Vector{agentMessage} # Messages to append to the context
|
|
end
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
# Agent loop configuration & tool execution types #
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
|
|
"""
|
|
Configuration for the agent tool execution loop.
|
|
|
|
# Arguments
|
|
- `beforeToolCall::Union{Function, Nothing}`: Callback before tool execution
|
|
- `afterToolCall::Union{Function, Nothing}`: Callback after tool execution
|
|
- `toolExecution::String`: Execution mode — "sequential" or "parallel"
|
|
"""
|
|
struct agentLoopConfig
|
|
beforeToolCall::Union{Function, Nothing}
|
|
afterToolCall::Union{Function, Nothing}
|
|
toolExecution::String
|
|
end
|
|
|
|
"""
|
|
Signal for aborting ongoing operations.
|
|
|
|
# Arguments
|
|
- `aborted::Bool`: Whether the operation has been aborted
|
|
"""
|
|
struct abortSignal
|
|
aborted::Bool
|
|
end
|
|
|
|
"""
|
|
Result returned by tool execution before the `afterToolCall` hook.
|
|
|
|
# Arguments
|
|
- `content::Vector{messageContent}`: Tool output content
|
|
- `details::Dict{Any,Any}`: Tool-specific details
|
|
- `usage::Union{llmUsage, Nothing}`: Token usage if applicable
|
|
- `terminate::Bool`: Whether tool requests termination of the agent loop
|
|
"""
|
|
struct agentToolResult
|
|
content::Vector{messageContent}
|
|
details::Dict{Any,Any}
|
|
usage::Union{llmUsage, Nothing}
|
|
terminate::Bool
|
|
end
|
|
|
|
"""
|
|
Context passed to the `beforeToolCall` hook.
|
|
|
|
# Arguments
|
|
- `message::assistantMessage`: The assistant message containing the tool call
|
|
- `toolCall::agentToolCall`: The tool call being prepared
|
|
- `args::Dict{String,Any}`: Validated tool arguments
|
|
- `context::agentContext`: Current conversation context
|
|
"""
|
|
struct beforeToolCallContext
|
|
message::assistantMessage
|
|
toolCall::agentToolCall
|
|
args::Dict{String,Any}
|
|
context::agentContext
|
|
end
|
|
|
|
struct beforeToolCallResult
|
|
block::Bool
|
|
reason::String
|
|
end
|
|
|
|
"""
|
|
Context passed to the `afterToolCall` hook.
|
|
|
|
# Arguments
|
|
- `message::assistantMessage`: The assistant message containing the tool call
|
|
- `toolCall::agentToolCall`: The tool call that was executed
|
|
- `args::Dict{String,Any}`: Tool arguments
|
|
- `result::agentToolResult`: The raw tool result
|
|
- `isError::Bool`: Whether execution resulted in an error
|
|
- `context::agentContext`: Current conversation context
|
|
"""
|
|
struct afterToolCallContext
|
|
message::assistantMessage
|
|
toolCall::agentToolCall
|
|
args::Dict{String,Any}
|
|
result::agentToolResult
|
|
isError::Bool
|
|
context::agentContext
|
|
end
|
|
|
|
"""
|
|
Event emitted when a tool call execution starts.
|
|
|
|
# Arguments
|
|
- `toolCallId::String`: ID of the tool call
|
|
- `toolName::String`: Name of the tool
|
|
- `arguments::Dict{String,Any}`: Tool arguments
|
|
"""
|
|
struct toolExecStartEvent
|
|
toolCallId::String
|
|
toolName::String
|
|
arguments::Dict{String,Any}
|
|
end
|
|
|
|
"""
|
|
Event emitted with partial results during tool execution.
|
|
|
|
# Arguments
|
|
- `toolCallId::String`: ID of the tool call
|
|
- `toolName::String`: Name of the tool
|
|
- `arguments::Dict{String,Any}`: Tool arguments
|
|
- `partialResult::Any`: The partial result data
|
|
"""
|
|
struct toolExecUpdateEvent
|
|
toolCallId::String
|
|
toolName::String
|
|
arguments::Dict{String,Any}
|
|
partialResult::Any
|
|
end
|
|
|
|
"""
|
|
Event emitted when a tool call execution ends.
|
|
|
|
# Arguments
|
|
- `toolCallId::String`: ID of the tool call
|
|
- `toolName::String`: Name of the tool
|
|
- `result::agentToolResult`: The final tool result
|
|
- `isError::Bool`: Whether execution resulted in an error
|
|
"""
|
|
struct toolExecEndEvent
|
|
toolCallId::String
|
|
toolName::String
|
|
result::agentToolResult
|
|
isError::Bool
|
|
end
|
|
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
# Agent struct #
|
|
# ------------------------------------------------------------------------------------------------ #
|
|
|
|
abstract type agent end
|
|
|
|
"""
|
|
preparedToolCall(tool, toolCall, args)
|
|
|
|
Intermediate state between tool call validation and execution.
|
|
Created after `prepareToolCall` succeeds; serves as the bridge to
|
|
the execution phase. Keeping the resolved tool, original call
|
|
metadata, and validated args together avoids repeated lookups and
|
|
allows the execution phase to access all necessary data without
|
|
carrying the full context through the call chain.
|
|
|
|
# Fields
|
|
- `tool::agentTool`: The resolved tool definition from the context
|
|
- `toolCall::agentToolCall`: The original tool call from the assistant
|
|
- `args::any`: Validated (and coerced) argument values
|
|
|
|
# Examples
|
|
```julia
|
|
# After prepareToolCall succeeds, the agent holds a preparedToolCall
|
|
prep = preparedToolCall(
|
|
tool, # agentTool found in context.tools
|
|
toolCall, # {id: "call_1", name: "search_wine", arguments: "{\"query\": \"red wine\"}"}
|
|
validatedArgs # Dict("query" => "red wine")
|
|
)
|
|
```
|
|
"""
|
|
struct preparedToolCall
|
|
tool::agentTool # The resolved tool definition from the context
|
|
toolCall::agentToolCall # The original tool call from the assistant
|
|
args::Any # Validated (and coerced) argument values
|
|
end
|
|
|
|
"""
|
|
immediateOutcome(result, isError)
|
|
|
|
A tool call that was resolved without actual execution — either
|
|
because the tool was not found, validation failed, or a
|
|
`beforeToolCall` hook blocked the call. The result is produced
|
|
immediately and emitted as a tool result message.
|
|
|
|
Returning an outcome instead of throwing an exception is intentional:
|
|
it lets the agent feed the error back to the LLM as a tool result so
|
|
the model can recover — for example, by re-issuing a tool call with
|
|
corrected arguments after a validation failure.
|
|
|
|
# Fields
|
|
- `result::agentToolResult`: The pre-computed tool result
|
|
- `isError::Bool`: Whether this outcome represents an error
|
|
|
|
# Examples
|
|
```julia
|
|
# Tool not found — immediate error
|
|
immediateOutcome(
|
|
createErrorToolResult("Tool search_wine not found"),
|
|
true
|
|
)
|
|
|
|
# beforeToolCall hook blocked execution
|
|
immediateOutcome(
|
|
createErrorToolResult("Tool execution was blocked"),
|
|
true
|
|
)
|
|
```
|
|
"""
|
|
struct immediateOutcome
|
|
result::agentToolResult # The pre-computed tool result
|
|
isError::Bool # Whether this outcome represents an error
|
|
end
|
|
|
|
"""
|
|
executedOutcome(result, isError)
|
|
|
|
A tool call that has been executed by `tool.execute()` but has not
|
|
yet been through the `afterToolCall` hook. This intermediate state
|
|
is necessary because the hook may mutate the result (content, usage,
|
|
termination, error status). Keeping execution and finalization
|
|
separate allows the hook to inspect the raw result and decide
|
|
whether to transform it or replace it entirely.
|
|
|
|
# Fields
|
|
- `result::agentToolResult`: The tool's execution result
|
|
- `isError::Bool`: Whether execution raised an error
|
|
|
|
# Examples
|
|
```julia
|
|
# Successful execution
|
|
executedOutcome(
|
|
agentToolResult([textContent("text", "Found 3 wines")], dict{any,any}(), dict{any,any}()),
|
|
false
|
|
)
|
|
|
|
# Execution error
|
|
executedOutcome(
|
|
createErrorToolResult("Connection timeout"),
|
|
true
|
|
)
|
|
```
|
|
"""
|
|
struct executedOutcome
|
|
result::agentToolResult # The tool's execution result
|
|
isError::Bool # Whether execution raised an error
|
|
end
|
|
|
|
"""
|
|
finalizedOutcome(toolCall, result, isError)
|
|
|
|
The complete outcome of a tool call after both execution and the
|
|
`afterToolCall` hook. This is the final form used to construct
|
|
the `toolResultMessage` emitted to the agent loop.
|
|
|
|
The three-phase design (prepare → execute → finalize) exists so
|
|
that each phase has a single responsibility: preparation handles
|
|
validation and gating, execution performs the actual work, and
|
|
finalization applies post-processing hooks. This separation allows
|
|
the agent loop to emit `toolExecutionEnd` events with the
|
|
finalized data while keeping each phase independently testable
|
|
and swappable.
|
|
|
|
# Fields
|
|
- `toolCall::agentToolCall`: The original tool call reference
|
|
- `result::agentToolResult`: The final tool result (post-afterToolCall)
|
|
- `isError::Bool`: Whether the call failed or was blocked
|
|
|
|
# Examples
|
|
```julia
|
|
# Normal successful finalization
|
|
finalizedOutcome(tc, agentToolResult(content, details, usage, false), false)
|
|
|
|
# afterToolCall mutated result and set terminate
|
|
finalizedOutcome(tc, agentToolResult(content, details, usage, true), false)
|
|
```
|
|
"""
|
|
struct finalizedOutcome
|
|
toolCall::agentToolCall # The original tool call reference
|
|
result::agentToolResult # The final tool result (post-afterToolCall)
|
|
isError::Bool # Whether the call failed or was blocked
|
|
end
|
|
|
|
"""
|
|
agentToolCallBatch(messages, terminate)
|
|
|
|
A batch of tool result messages from executing one or more tool calls.
|
|
The `terminate` flag indicates whether all tools in the batch
|
|
requested termination, which causes the agent loop to stop
|
|
processing further turns.
|
|
|
|
This flag is set by the tool implementation (not the end user) to
|
|
signal that the agent should not call the LLM again. Typical use
|
|
cases:
|
|
|
|
- Task completion: a tool like `deploy` or `submit` finishes its
|
|
work and returns `terminate: true` so the agent stops instead
|
|
of asking the LLM what to do next.
|
|
- Unrecoverable error: a tool hits a fatal condition (e.g.
|
|
database connection lost, auth token expired) and returns
|
|
`terminate: true` so the agent stops with an error message
|
|
rather than retrying.
|
|
- Async handoff: a tool triggers a long-running external
|
|
operation and wants the agent to stop now; the external system
|
|
will later resume the agent via `continue()`.
|
|
|
|
If `terminate` is `false` (default), the agent loop feeds the tool
|
|
results back to the LLM for another turn.
|
|
|
|
# Fields
|
|
- `messages::Vector{toolResultMessage}`: Tool result messages for this batch
|
|
- `terminate::Bool`: Whether the batch should terminate the loop
|
|
|
|
# Examples
|
|
```julia
|
|
# Batch of 3 tool results, no termination
|
|
agentToolCallBatch(resultMessages, false)
|
|
|
|
# All tools requested termination
|
|
agentToolCallBatch(resultMessages, true)
|
|
|
|
# Empty batch — terminate is false regardless
|
|
agentToolCallBatch(toolResultMessage[], false)
|
|
```
|
|
"""
|
|
struct agentToolCallBatch
|
|
messages::Vector{toolResultMessage} # Tool result messages for this batch
|
|
terminate::Bool # Whether the batch should terminate the loop
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
end # module type
|