update
This commit is contained in:
+201
-659
@@ -1,465 +1,187 @@
|
||||
# AgentCore.jl - Tools Deep Dive
|
||||
|
||||
## Tool Architecture with Data Flow
|
||||
## Tool Types (from types.jl)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tool Layer │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ AgentTool │
|
||||
│ - name: String (identifier) │
|
||||
│ - label: String (display name) │
|
||||
│ - description: String (what it does) │
|
||||
│ - parameters::Any (JSON schema or type) │
|
||||
│ - execute::Function (main logic) │
|
||||
│ - prepare_arguments::Union{Function, Nothing} │
|
||||
│ - execution_mode::Union{ToolExecutionMode, Nothing} │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ BashTool │ │ ReadTool │ │ WriteTool │
|
||||
│ - bash() │ │ - read() │ │ - write() │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
┌─────────────┐
|
||||
│ EditTool │
|
||||
│ - edit() │
|
||||
└─────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tool Execution Data Flow │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Input: AssistantMessage (from LLM)
|
||||
content::Vector{MessageContent}
|
||||
└─ Contains: TextContent[] and ToolCall[]
|
||||
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ extract ToolCalls │
|
||||
│ filter(c -> c isa ToolCall, assistant_message.content) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ToolCall Type │
|
||||
│ • type::String ("tool") │
|
||||
│ • id::String (unique identifier) │
|
||||
│ • name::String (tool name to execute) │
|
||||
│ • arguments::Dict{String, Any} (JSON-like arguments) │
|
||||
│ • partial_json::Union{String, Nothing} │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─► prepareToolCall()
|
||||
│ Input: tool_call::ToolCall
|
||||
│ Output: Union{PreparedToolCall, ImmediateToolCallOutcome}
|
||||
│
|
||||
│ Steps:
|
||||
│ 1. Find tool by name in context.tools
|
||||
│ 2. before_tool_call hook (optional)
|
||||
│ Input: BeforeToolCallContext
|
||||
│ Output: BeforeToolCallResult (block, reason)
|
||||
│ 3. prepareToolCallArguments() (optional)
|
||||
│ Input: tool_call.arguments::Dict{String, Any}
|
||||
│ Output: prepared_arguments::Any
|
||||
│ 4. validateToolArguments()
|
||||
│ Input: prepared_tool_call.arguments
|
||||
│ Output: validated_args::Any
|
||||
│ 5. Return: PreparedToolCall(kind, tool_call, tool, args)
|
||||
│
|
||||
├─► executePreparedToolCall() (if prepared)
|
||||
│ Input: PreparedToolCall
|
||||
│ Output: ExecutedToolCallOutcome
|
||||
│
|
||||
│ tool.execute(tool_call.id, args, signal, on_update)
|
||||
│ Input: tool_call_id::String
|
||||
│ args::Any
|
||||
│ signal::Union{Any, Nothing}
|
||||
│ on_update::Function (streaming updates)
|
||||
│ Output: AgentToolResultMutable
|
||||
│ • content::Vector{MessageContent}
|
||||
│ • details::Any
|
||||
│ • usage::Union{Usage, Nothing}
|
||||
│ • terminate::Union{Bool, Nothing}
|
||||
│
|
||||
├─► finalizeExecutedToolCall()
|
||||
│ Input: ExecutedToolCallOutcome
|
||||
│ Output: FinalizedToolCallOutcome
|
||||
│
|
||||
│ Steps:
|
||||
│ 1. after_tool_call hook (optional)
|
||||
│ Input: AfterToolCallContext
|
||||
│ Output: AfterToolCallResult (patches)
|
||||
│ 2. Apply patches to result
|
||||
│ 3. Return: FinalizedToolCallOutcome(tool_call, result, is_error)
|
||||
│
|
||||
└─► createToolResultMessage()
|
||||
Input: FinalizedToolCallOutcome
|
||||
Output: ToolResultMessage
|
||||
• role: "toolResult"
|
||||
• tool_call_id::String (matches ToolCall.id)
|
||||
• tool_name::String (matches ToolCall.name)
|
||||
• content::Vector{MessageContent}
|
||||
• details::Any
|
||||
• usage::Union{Usage, Nothing}
|
||||
• added_tool_names::Union{Vector{String}, Nothing}
|
||||
• is_error::Bool
|
||||
• timestamp::Timestamp (Int64)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ToolResultMessage[] (one per ToolCall) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─► Append to context.messages (AgentState.messages)
|
||||
└─► Next turn: LLM sees tool results as input
|
||||
```
|
||||
|
||||
## Built-in Tools
|
||||
|
||||
## Built-in Tools
|
||||
|
||||
### 1. BashTool
|
||||
### AgentTool (struct)
|
||||
|
||||
```julia
|
||||
struct BashToolOptions{TContext}
|
||||
command_prefix::Union{String, Nothing}
|
||||
prepare::Union{BashPrepare{TContext}, Nothing}
|
||||
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
|
||||
|
||||
struct BashPrepare{TContext}
|
||||
mutable struct BashPrepare{TContext}
|
||||
function::Function
|
||||
context::TContext
|
||||
signal::Union{Any, Nothing}
|
||||
end
|
||||
|
||||
struct BashToolDetails
|
||||
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
|
||||
```
|
||||
|
||||
#### createBashTool()
|
||||
**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
|
||||
function createBashTool{TContext}(options::Union{BashToolOptions{TContext}, Nothing}=nothing)
|
||||
return AgentTool(
|
||||
"bash",
|
||||
"bash",
|
||||
"Execute a bash command in the current working directory.",
|
||||
Dict{String, Any}(),
|
||||
(tool_call_id, params, signal, on_update, context) -> begin
|
||||
# Execute command
|
||||
result = executeBashCommand(params, signal, on_update)
|
||||
|
||||
# Return result
|
||||
return AgentToolResult(
|
||||
[TextContent(result.output)],
|
||||
BashToolDetails(result.truncation, result.full_path),
|
||||
nothing,
|
||||
nothing,
|
||||
result.terminate,
|
||||
)
|
||||
end,
|
||||
nothing, # prepare_arguments
|
||||
nothing, # execution_mode (default: use config)
|
||||
)
|
||||
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
|
||||
```
|
||||
|
||||
**Parameters Schema**:
|
||||
```json
|
||||
{
|
||||
"command": "string",
|
||||
"timeout": "number (optional)",
|
||||
"cwd": "string (optional)",
|
||||
"env": "object (optional)"
|
||||
}
|
||||
```
|
||||
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`
|
||||
|
||||
**Example**:
|
||||
```julia
|
||||
# Create tool
|
||||
bash_tool = createBashTool()
|
||||
|
||||
# Agent receives command
|
||||
tool_call = ToolCall("tool", "tc1", "bash", Dict(
|
||||
"command" => "ls -la",
|
||||
"timeout" => 30
|
||||
), nothing)
|
||||
|
||||
# Execute
|
||||
result = bash_tool.execute(
|
||||
"tc1",
|
||||
Dict("command" => "ls -la", "timeout" => 30),
|
||||
nothing,
|
||||
on_update, # Callback for streaming output
|
||||
nothing,
|
||||
)
|
||||
|
||||
# Result
|
||||
AgentToolResult(
|
||||
[TextContent("total 12\n-rw-r--r-- 1 user user 100 Jan 1 file1.md\n-rw-r--r-- 1 user user 200 Jan 2 file2.md\n")],
|
||||
BashToolDetails(truncation_info, nothing),
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
```
|
||||
|
||||
### 2. ReadTool
|
||||
|
||||
```julia
|
||||
struct ReadToolOptions{TContext}
|
||||
max_size::Union{Int64, Nothing}
|
||||
max_lines::Union{Int64, Nothing}
|
||||
image_processor::Union{ReadImageProcessor, Nothing}
|
||||
prepare::Union{ReadPrepare{TContext}, Nothing}
|
||||
end
|
||||
|
||||
struct ReadImageProcessor
|
||||
function::Function
|
||||
context::Any
|
||||
end
|
||||
|
||||
struct ReadImageProcessorResult
|
||||
content::Vector{MessageContent}
|
||||
usage::Union{Usage, Nothing}
|
||||
end
|
||||
```
|
||||
|
||||
#### createReadTool()
|
||||
|
||||
```julia
|
||||
function createReadTool{TContext}(options::Union{ReadToolOptions{TContext}, Nothing}=nothing)
|
||||
return AgentTool(
|
||||
"read",
|
||||
"read",
|
||||
"Read a file from the file system.",
|
||||
Dict{String, Any}(),
|
||||
(tool_call_id, params, signal, on_update, context) -> begin
|
||||
# Read file
|
||||
result = readFileSystem(params, signal, options)
|
||||
|
||||
# Process content
|
||||
content = if isImage(params.path)
|
||||
# Image processing
|
||||
image_result = options.image_processor.function(result.path, context)
|
||||
image_result.content
|
||||
else
|
||||
# Text content
|
||||
[TextContent(result.content)]
|
||||
end
|
||||
|
||||
return AgentToolResult(
|
||||
content,
|
||||
ReadToolDetails(result.size, result.truncated, result.full_path),
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
end,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
end
|
||||
```
|
||||
|
||||
**Parameters Schema**:
|
||||
```json
|
||||
{
|
||||
"path": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```julia
|
||||
# Create tool
|
||||
read_tool = createReadTool()
|
||||
|
||||
# Agent requests to read file
|
||||
tool_call = ToolCall("tool", "tc2", "read", Dict(
|
||||
"path" => "src/main.jl"
|
||||
), nothing)
|
||||
|
||||
# Execute
|
||||
result = read_tool.execute("tc2", Dict("path" => "src/main.jl"), nothing, nothing, nothing)
|
||||
|
||||
# Result
|
||||
AgentToolResult(
|
||||
[TextContent("module Main\nfunction main()\n println(\"Hello\")\nend\nend\n")],
|
||||
ReadToolDetails(1234, false, "/path/to/src/main.jl"),
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
```
|
||||
|
||||
### 3. WriteTool
|
||||
|
||||
```julia
|
||||
struct WriteToolInput
|
||||
path::String
|
||||
content::String
|
||||
end
|
||||
```
|
||||
|
||||
#### createWriteTool()
|
||||
|
||||
```julia
|
||||
function createWriteTool{TContext}(options::Union{WriteToolOptions{TContext}, Nothing}=nothing)
|
||||
return AgentTool(
|
||||
"write",
|
||||
"write",
|
||||
"Write content to a file.",
|
||||
Dict{String, Any}(),
|
||||
(tool_call_id, params, signal, on_update, context) -> begin
|
||||
# Write file
|
||||
result = writeToFile(params, signal)
|
||||
|
||||
return AgentToolResult(
|
||||
[TextContent(result.message)],
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
end,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
end
|
||||
```
|
||||
|
||||
**Parameters Schema**:
|
||||
```json
|
||||
{
|
||||
"path": "string",
|
||||
"content": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```julia
|
||||
# Create tool
|
||||
write_tool = createWriteTool()
|
||||
|
||||
# Agent wants to write file
|
||||
tool_call = ToolCall("tool", "tc3", "write", Dict(
|
||||
"path" => "output.txt",
|
||||
"content" => "Hello World"
|
||||
), nothing)
|
||||
|
||||
# Execute
|
||||
result = write_tool.execute("tc3", Dict(
|
||||
"path" => "output.txt",
|
||||
"content" => "Hello World"
|
||||
), nothing, nothing, nothing)
|
||||
|
||||
# Result
|
||||
AgentToolResult(
|
||||
[TextContent("File written: output.txt (11 bytes)")],
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
```
|
||||
|
||||
### 4. EditTool
|
||||
|
||||
```julia
|
||||
struct EditToolInput
|
||||
path::String
|
||||
find::String
|
||||
replacement::String
|
||||
end
|
||||
|
||||
struct EditToolDetails
|
||||
edits::Vector{Edit}
|
||||
before_content::String
|
||||
after_content::String
|
||||
end
|
||||
```
|
||||
|
||||
#### createEditTool()
|
||||
|
||||
```julia
|
||||
function createEditTool{TContext}(options::Union{EditToolOptions{TContext}, Nothing}=nothing)
|
||||
return AgentTool(
|
||||
"edit",
|
||||
"edit",
|
||||
"Edit a file by finding and replacing text.",
|
||||
Dict{String, Any}(),
|
||||
(tool_call_id, params, signal, on_update, context) -> begin
|
||||
# Read file
|
||||
before_content = read(params.path)
|
||||
|
||||
# Apply edit
|
||||
after_content = replace(before_content, params.find => params.replacement)
|
||||
|
||||
# Write file
|
||||
write(params.path, after_content)
|
||||
|
||||
return AgentToolResult(
|
||||
[TextContent("Edit applied successfully")],
|
||||
EditToolDetails([Edit(params.find, params.replacement)], before_content, after_content),
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
end,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
end
|
||||
```
|
||||
|
||||
**Parameters Schema**:
|
||||
```json
|
||||
{
|
||||
"path": "string",
|
||||
"find": "string",
|
||||
"replacement": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```julia
|
||||
# Create tool
|
||||
edit_tool = createEditTool()
|
||||
|
||||
# Agent wants to replace text
|
||||
tool_call = ToolCall("tool", "tc4", "edit", Dict(
|
||||
"path" => "README.md",
|
||||
"find" => "v1.0.0",
|
||||
"replacement" => "v2.0.0"
|
||||
), nothing)
|
||||
|
||||
# Execute
|
||||
result = edit_tool.execute("tc4", Dict(
|
||||
"path" => "README.md",
|
||||
"find" => "v1.0.0",
|
||||
"replacement" => "v2.0.0"
|
||||
), nothing, nothing, nothing)
|
||||
|
||||
# Result
|
||||
AgentToolResult(
|
||||
[TextContent("Edit applied: README.md")],
|
||||
EditToolDetails([Edit("v1.0.0", "v2.0.0")], "Version 1.0.0", "Version 2.0.0"),
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
```
|
||||
|
||||
## Tool Execution Hooks
|
||||
|
||||
### before_tool_call
|
||||
### BeforeToolCallContext / BeforeToolCallResult (from types.jl)
|
||||
|
||||
```julia
|
||||
struct BeforeToolCallContext
|
||||
@@ -475,32 +197,7 @@ struct BeforeToolCallResult
|
||||
end
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```julia
|
||||
function myBeforeToolCall(context, signal)
|
||||
tool_name = context.tool_call.name
|
||||
|
||||
# Block dangerous commands
|
||||
if tool_name == "bash" && contains(context.args["command"], "rm -rf /")
|
||||
return BeforeToolCallResult(
|
||||
true,
|
||||
"Blocking dangerous command: rm -rf /"
|
||||
)
|
||||
end
|
||||
|
||||
# Log tool execution
|
||||
println("Executing tool: $tool_name")
|
||||
|
||||
return nothing # Allow execution
|
||||
end
|
||||
|
||||
# Configure agent
|
||||
agent = Agent(Dict(
|
||||
:beforeToolCall => myBeforeToolCall,
|
||||
))
|
||||
```
|
||||
|
||||
### after_tool_call
|
||||
### AfterToolCallContext / AfterToolCallResult (from types.jl)
|
||||
|
||||
```julia
|
||||
struct AfterToolCallContext
|
||||
@@ -521,37 +218,7 @@ struct AfterToolCallResult
|
||||
end
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```julia
|
||||
function myAfterToolCall(context, signal)
|
||||
tool_name = context.tool_call.name
|
||||
|
||||
# Modify bash output
|
||||
if tool_name == "bash"
|
||||
# Add timestamp to output
|
||||
new_content = [
|
||||
TextContent("[Executed at $(Dates.now())]\n"),
|
||||
context.result.content[1],
|
||||
]
|
||||
return AfterToolCallResult(
|
||||
content = new_content,
|
||||
details = context.result.details,
|
||||
is_error = context.is_error,
|
||||
usage = context.result.usage,
|
||||
terminate = context.result.terminate,
|
||||
)
|
||||
end
|
||||
|
||||
return nothing # Use original result
|
||||
end
|
||||
|
||||
# Configure agent
|
||||
agent = Agent(Dict(
|
||||
:afterToolCall => myAfterToolCall,
|
||||
))
|
||||
```
|
||||
|
||||
### prepare_next_turn
|
||||
### PrepareNextTurnContext / AgentLoopTurnUpdate (from types.jl)
|
||||
|
||||
```julia
|
||||
struct PrepareNextTurnContext
|
||||
@@ -568,193 +235,73 @@ struct AgentLoopTurnUpdate
|
||||
end
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```julia
|
||||
function myPrepareNextTurn(context, signal)
|
||||
# Check if we should use a different model
|
||||
last_message = context.message
|
||||
tool_results = context.tool_results
|
||||
|
||||
# If tool execution had errors, use more capable model
|
||||
has_errors = any(r -> r.is_error, tool_results)
|
||||
if has_errors
|
||||
return AgentLoopTurnUpdate(
|
||||
context = context.context,
|
||||
model = Model("gpt-4", "GPT-4", "openai", "openai", "", ...),
|
||||
thinking_level = THINKING_HIGH,
|
||||
)
|
||||
end
|
||||
|
||||
return nothing # Keep current settings
|
||||
end
|
||||
|
||||
# Configure agent
|
||||
agent = Agent(Dict(
|
||||
:prepareNextTurn => myPrepareNextTurn,
|
||||
))
|
||||
```
|
||||
|
||||
## Tool Execution Modes
|
||||
|
||||
### Sequential Execution
|
||||
|
||||
```julia
|
||||
# Tools run one at a time, in order
|
||||
# Use case: Tools that modify shared state
|
||||
|
||||
# Configure tool
|
||||
bash_tool = AgentTool(
|
||||
"bash",
|
||||
"bash",
|
||||
"Execute bash command",
|
||||
...,
|
||||
execute,
|
||||
nothing,
|
||||
EXECUTION_SEQUENTIAL, # Force sequential
|
||||
)
|
||||
|
||||
# Or configure globally
|
||||
# Configure on Agent
|
||||
agent = Agent(Dict(
|
||||
:toolExecution => EXECUTION_SEQUENTIAL,
|
||||
))
|
||||
```
|
||||
|
||||
**Example Scenario**:
|
||||
```julia
|
||||
# Sequential execution (correct order)
|
||||
|
||||
1. Tool 1: create_directory("build/")
|
||||
└─ Creates build/ directory
|
||||
|
||||
2. Tool 2: write("build/app.js", "...")
|
||||
└─ Writes file to build/
|
||||
|
||||
(If parallel: might fail because build/ doesn't exist yet)
|
||||
```
|
||||
|
||||
### Parallel Execution
|
||||
### Parallel Execution (default)
|
||||
|
||||
```julia
|
||||
# Tools run concurrently
|
||||
# Use case: Independent operations
|
||||
|
||||
# Default behavior
|
||||
agent = Agent(Dict(
|
||||
:toolExecution => EXECUTION_PARALLEL, # Default
|
||||
:toolExecution => EXECUTION_PARALLEL,
|
||||
))
|
||||
```
|
||||
|
||||
**Example Scenario**:
|
||||
```julia
|
||||
# Parallel execution (independent operations)
|
||||
|
||||
1. Tool 1: read("README.md") ─────┐
|
||||
2. Tool 2: read("CHANGELOG.md") ─┼─► Run simultaneously
|
||||
3. Tool 3: read("LICENSE") ──────┘
|
||||
|
||||
(Parallel: All three read operations can happen at once)
|
||||
(Sequential: Would wait for each read to complete)
|
||||
```
|
||||
|
||||
## Custom Tools
|
||||
|
||||
### Example: Database Tool
|
||||
Tools can also specify their own mode:
|
||||
|
||||
```julia
|
||||
function createDatabaseTool()
|
||||
return AgentTool(
|
||||
"database",
|
||||
"database",
|
||||
"Execute SQL queries against the database.",
|
||||
Dict{String, Any}(
|
||||
"type" => "object",
|
||||
"properties" => Dict(
|
||||
"query" => Dict("type" => "string"),
|
||||
"params" => Dict("type" => "array", "items" => Dict("type" => "string")),
|
||||
),
|
||||
"required" => ["query"],
|
||||
),
|
||||
(tool_call_id, params, signal, on_update, context) -> begin
|
||||
# Execute query
|
||||
query = params["query"]
|
||||
result = executeQuery(query)
|
||||
|
||||
# Format output
|
||||
output = formatQueryResult(result)
|
||||
|
||||
return AgentToolResult(
|
||||
[TextContent(output)],
|
||||
Dict("rows_affected" => result.rows_affected),
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
end,
|
||||
nothing,
|
||||
EXECUTION_SEQUENTIAL,
|
||||
)
|
||||
end
|
||||
|
||||
# Usage
|
||||
db_tool = createDatabaseTool()
|
||||
agent = Agent(Dict(:tools => [db_tool]))
|
||||
agent_tool = AgentTool(
|
||||
"name",
|
||||
"label",
|
||||
"description",
|
||||
params_schema,
|
||||
execute_fn,
|
||||
nothing,
|
||||
EXECUTION_SEQUENTIAL, # or EXECUTION_PARALLEL
|
||||
)
|
||||
```
|
||||
|
||||
### Example: HTTP Request Tool
|
||||
## Tool Exports (from tools/index.jl)
|
||||
|
||||
```julia
|
||||
function createHTTPTool()
|
||||
return AgentTool(
|
||||
"http",
|
||||
"http",
|
||||
"Make HTTP requests.",
|
||||
Dict{String, Any}(
|
||||
"type" => "object",
|
||||
"properties" => Dict(
|
||||
"url" => Dict("type" => "string"),
|
||||
"method" => Dict("type" => "string", "enum" => ["GET", "POST", "PUT", "DELETE"]),
|
||||
"body" => Dict("type" => "string"),
|
||||
"headers" => Dict("type" => "object"),
|
||||
),
|
||||
"required" => ["url", "method"],
|
||||
),
|
||||
(tool_call_id, params, signal, on_update, context) -> begin
|
||||
# Make request
|
||||
url = params["url"]
|
||||
method = params["method"]
|
||||
body = get(params, "body", nothing)
|
||||
headers = get(params, "headers", Dict())
|
||||
|
||||
response = makeHTTPRequest(method, url, body, headers)
|
||||
|
||||
return AgentToolResult(
|
||||
[TextContent(response.body)],
|
||||
Dict(
|
||||
"status_code" => response.status_code,
|
||||
"headers" => response.headers,
|
||||
),
|
||||
nothing,
|
||||
nothing,
|
||||
nothing,
|
||||
)
|
||||
end,
|
||||
nothing,
|
||||
EXECUTION_PARALLEL,
|
||||
)
|
||||
end
|
||||
export
|
||||
createBashTool,
|
||||
createReadTool,
|
||||
createWriteTool,
|
||||
createEditTool,
|
||||
BashExecution,
|
||||
BashPrepare,
|
||||
BashToolDetails,
|
||||
BashToolInput,
|
||||
BashToolOptions,
|
||||
EditToolDetails,
|
||||
EditToolInput,
|
||||
ReadToolDetails,
|
||||
ReadToolInput,
|
||||
ReadToolOptions,
|
||||
ReadImageProcessor,
|
||||
ReadImageProcessorResult,
|
||||
WriteToolInput
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
## Example: Creating and Using Tools
|
||||
|
||||
```julia
|
||||
using AgentCore
|
||||
|
||||
# 1. Create tools
|
||||
# Create tools
|
||||
bash_tool = createBashTool()
|
||||
read_tool = createReadTool()
|
||||
write_tool = createWriteTool()
|
||||
|
||||
# 2. Configure hooks
|
||||
# Configure hooks
|
||||
before_hook = (context, signal) -> begin
|
||||
println("About to execute: $(context.tool_call.name)")
|
||||
return nothing
|
||||
@@ -769,22 +316,17 @@ after_hook = (context, signal) -> begin
|
||||
return nothing
|
||||
end
|
||||
|
||||
# 3. Create agent
|
||||
# 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,
|
||||
))
|
||||
|
||||
# 4. Run conversation
|
||||
# Run prompt
|
||||
prompt(agent, "List files in current directory and read the first one")
|
||||
|
||||
# 5. Agent will:
|
||||
# - Execute bash("ls -la") tool
|
||||
# - Parse output to find first file
|
||||
# - Execute read("path/to/file") tool
|
||||
# - Return content to user
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
Reference in New Issue
Block a user