Files
YiemAgent/learning/06-TOOLS.md
T
2026-07-31 11:41:53 +07:00

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:

  • :beforeToolCallAgent.before_tool_call
  • :afterToolCallAgent.after_tool_call
  • :prepareNextTurnAgent.prepare_next_turn
  • :prepareNextTurnWithContextAgent.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

  1. Use sequential execution for tools that depend on shared state
  2. Use parallel execution for independent operations
  3. Implement before_tool_call hook for logging and validation
  4. Implement after_tool_call hook for result modification
  5. Use prepare_next_turn hook for dynamic model/thinking level changes
  6. Return terminate=true from tool when agent should stop
  7. Include usage statistics in tool results when possible