8.3 KiB
8.3 KiB
AgentCore.jl - Tools Deep Dive
Tool Types (from types.jl)
AgentTool (struct)
struct AgentTool{TParameters, TDetails}
name::String # tool identifier
label::String # display name
description::String # what it does
parameters::TParameters # JSON schema or type
execute::Function # (tool_call_id, params, signal, on_update, context) -> AgentToolResult
prepare_arguments::Union{Function, Nothing}
execution_mode::Union{ToolExecutionMode, Nothing}
end
AgentToolResult (struct)
struct AgentToolResult{T}
content::Vector{MessageContent}
details::T
usage::Union{Usage, Nothing}
added_tool_names::Union{Vector{String}, Nothing}
terminate::Union{Bool, Nothing}
end
ToolCall (struct)
struct ToolCall
type::String # always "tool"
id::String # unique identifier
name::String # tool name to execute
arguments::Dict{String, Any} # JSON-like arguments
partial_json::Union{String, Nothing}
end
ToolExecutionMode (enum)
@enum ToolExecutionMode begin
EXECUTION_SEQUENTIAL = "sequential"
EXECUTION_PARALLEL = "parallel"
end
Tool Execution Flow
AssistantMessage (from LLM)
content::Vector{MessageContent}
└─ Contains: TextContent[] and ToolCall[]
▼
Agent.execute() (in agent.jl)
└─ before_tool_call hook (Agent.before_tool_call, optional)
Input: BeforeToolCallContext
Output: BeforeToolCallResult (block, reason)
▼
For each ToolCall:
tool = find_tool(name)
tool.execute(tool_call_id, args, signal, on_update, context)
▼
AgentToolResult{T}(content, details, usage, added_tool_names, terminate)
▼
└─ after_tool_call hook (Agent.after_tool_call, optional)
Input: AfterToolCallContext
Output: AfterToolCallResult (patches: content, details, is_error, usage, terminate)
▼
ToolResultMessage (one per ToolCall)
role: "toolResult"
tool_call_id::String
tool_name::String
content::Vector{MessageContent}
details::Any
usage::Union{Usage, Nothing}
added_tool_names::Union{Vector{String}, Nothing}
is_error::Bool
timestamp::Timestamp
▼
Append to AgentState.messages
└─ Next turn: LLM sees tool results as input
Built-in Tools
1. BashTool (tools/bash.jl)
struct BashExecution
command::String
cwd::String
env::Dict{String, String}
inherit_env::Bool
end
mutable struct BashPrepare{TContext}
function::Function
context::TContext
signal::Union{Any, Nothing}
end
mutable struct BashToolOptions{TContext}
command_prefix::Union{String, Nothing}
prepare::Union{BashPrepare{TContext}, Nothing}
end
mutable struct BashToolDetails
truncation::Union{Any, Nothing}
full_output_path::Union{String, Nothing}
end
function createBashTool{TContext}(options::Union{BashToolOptions{TContext}, Nothing}=nothing) where TContext
Execute signature: (tool_call_id, params, signal, on_update, context) -> AgentToolResult
Note: The actual bash execution is a TODO stub in the current source.
2. ReadTool (tools/read.jl)
mutable struct ReadToolDetails
truncation::Union{Any, Nothing}
end
mutable struct ReadToolOptions
auto_resize_images::Bool
image_processor::Union{Any, Nothing}
end
function createReadTool{TContext}(options::Union{ReadToolOptions, Nothing}=nothing) where TContext
Execute signature: (tool_call_id, params, signal, on_update, context) -> AgentToolResult
3. WriteTool (tools/write.jl)
function createWriteTool{TContext}() where TContext
Execute signature: (tool_call_id, params, signal, on_update, context) -> AgentToolResult
4. EditTool (tools/edit.jl)
mutable struct EditToolDetails
diff::String
patch::String
first_changed_line::Union{Int64, Nothing}
end
function createEditTool{TContext}() where TContext
Execute signature: (tool_call_id, params, signal, on_update, context) -> AgentToolResult
Tool Hooks (on Agent struct)
The Agent struct in agent.jl has these hook fields:
mutable struct Agent
...
before_tool_call::Union{Function, Nothing}
after_tool_call::Union{Function, Nothing}
prepare_next_turn::Union{Function, Nothing}
prepare_next_turn_with_context::Union{Function, Nothing}
...
end
Configured via Agent(Dict(...)) options:
:beforeToolCall→Agent.before_tool_call:afterToolCall→Agent.after_tool_call:prepareNextTurn→Agent.prepare_next_turn:prepareNextTurnWithContext→Agent.prepare_next_turn_with_context
BeforeToolCallContext / BeforeToolCallResult (from types.jl)
struct BeforeToolCallContext
assistant_message::AssistantMessage
tool_call::ToolCall
args::Any
context::AgentContext
end
struct BeforeToolCallResult
block::Union{Bool, Nothing}
reason::Union{String, Nothing}
end
AfterToolCallContext / AfterToolCallResult (from types.jl)
struct AfterToolCallContext
assistant_message::AssistantMessage
tool_call::ToolCall
args::Any
result::AgentToolResult
is_error::Bool
context::AgentContext
end
struct AfterToolCallResult
content::Union{Vector{MessageContent}, Nothing}
details::Union{Any, Nothing}
is_error::Union{Bool, Nothing}
usage::Union{Usage, Nothing}
terminate::Union{Bool, Nothing}
end
PrepareNextTurnContext / AgentLoopTurnUpdate (from types.jl)
struct PrepareNextTurnContext
message::AssistantMessage
tool_results::Vector{ToolResultMessage}
context::AgentContext
new_messages::Vector{AgentMessage}
end
struct AgentLoopTurnUpdate
context::Union{AgentContext, Nothing}
model::Union{Model, Nothing}
thinking_level::Union{ThinkingLevel, Nothing}
end
Tool Execution Modes
Sequential Execution
# Configure on Agent
agent = Agent(Dict(
:toolExecution => EXECUTION_SEQUENTIAL,
))
Parallel Execution (default)
agent = Agent(Dict(
:toolExecution => EXECUTION_PARALLEL,
))
Tools can also specify their own mode:
agent_tool = AgentTool(
"name",
"label",
"description",
params_schema,
execute_fn,
nothing,
EXECUTION_SEQUENTIAL, # or EXECUTION_PARALLEL
)
Tool Exports (from tools/index.jl)
export
createBashTool,
createReadTool,
createWriteTool,
createEditTool,
BashExecution,
BashPrepare,
BashToolDetails,
BashToolInput,
BashToolOptions,
EditToolDetails,
EditToolInput,
ReadToolDetails,
ReadToolInput,
ReadToolOptions,
ReadImageProcessor,
ReadImageProcessorResult,
WriteToolInput
Example: Creating and Using Tools
using AgentCore
# Create tools
bash_tool = createBashTool()
read_tool = createReadTool()
write_tool = createWriteTool()
# Configure hooks
before_hook = (context, signal) -> begin
println("About to execute: $(context.tool_call.name)")
return nothing
end
after_hook = (context, signal) -> begin
if context.is_error
println("Tool failed: $(context.tool_call.name)")
else
println("Tool completed: $(context.tool_call.name)")
end
return nothing
end
# Create agent with tools and hooks
agent = Agent(Dict(
:systemPrompt => "You are a helpful assistant with file system access.",
:tools => [bash_tool, read_tool, write_tool],
:beforeToolCall => before_hook,
:afterToolCall => after_hook,
:toolExecution => EXECUTION_PARALLEL,
))
# Run prompt
prompt(agent, "List files in current directory and read the first one")
Best Practices
- Use sequential execution for tools that depend on shared state
- Use parallel execution for independent operations
- Implement before_tool_call hook for logging and validation
- Implement after_tool_call hook for result modification
- Use prepare_next_turn hook for dynamic model/thinking level changes
- Return terminate=true from tool when agent should stop
- Include usage statistics in tool results when possible