341 lines
8.3 KiB
Markdown
341 lines
8.3 KiB
Markdown
# AgentCore.jl - Tools Deep Dive
|
|
|
|
## Tool Types (from types.jl)
|
|
|
|
### AgentTool (struct)
|
|
|
|
```julia
|
|
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)
|
|
|
|
```julia
|
|
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)
|
|
|
|
```julia
|
|
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)
|
|
|
|
```julia
|
|
@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`)
|
|
|
|
```julia
|
|
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`)
|
|
|
|
```julia
|
|
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`)
|
|
|
|
```julia
|
|
function createWriteTool{TContext}() where TContext
|
|
```
|
|
|
|
**Execute signature**: `(tool_call_id, params, signal, on_update, context) -> AgentToolResult`
|
|
|
|
### 4. EditTool (`tools/edit.jl`)
|
|
|
|
```julia
|
|
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:
|
|
|
|
```julia
|
|
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)
|
|
|
|
```julia
|
|
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)
|
|
|
|
```julia
|
|
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)
|
|
|
|
```julia
|
|
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
|
|
|
|
```julia
|
|
# Configure on Agent
|
|
agent = Agent(Dict(
|
|
:toolExecution => EXECUTION_SEQUENTIAL,
|
|
))
|
|
```
|
|
|
|
### Parallel Execution (default)
|
|
|
|
```julia
|
|
agent = Agent(Dict(
|
|
:toolExecution => EXECUTION_PARALLEL,
|
|
))
|
|
```
|
|
|
|
Tools can also specify their own mode:
|
|
|
|
```julia
|
|
agent_tool = AgentTool(
|
|
"name",
|
|
"label",
|
|
"description",
|
|
params_schema,
|
|
execute_fn,
|
|
nothing,
|
|
EXECUTION_SEQUENTIAL, # or EXECUTION_PARALLEL
|
|
)
|
|
```
|
|
|
|
## Tool Exports (from tools/index.jl)
|
|
|
|
```julia
|
|
export
|
|
createBashTool,
|
|
createReadTool,
|
|
createWriteTool,
|
|
createEditTool,
|
|
BashExecution,
|
|
BashPrepare,
|
|
BashToolDetails,
|
|
BashToolInput,
|
|
BashToolOptions,
|
|
EditToolDetails,
|
|
EditToolInput,
|
|
ReadToolDetails,
|
|
ReadToolInput,
|
|
ReadToolOptions,
|
|
ReadImageProcessor,
|
|
ReadImageProcessorResult,
|
|
WriteToolInput
|
|
```
|
|
|
|
## Example: Creating and Using Tools
|
|
|
|
```julia
|
|
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
|