update
This commit is contained in:
+15
-32
@@ -36,35 +36,6 @@ function validateRequiredArgs(args::Dict{String,Any})::Union{Nothing,String}
|
|||||||
return nothing
|
return nothing
|
||||||
end
|
end
|
||||||
|
|
||||||
"""
|
|
||||||
Execute the getTime tool.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `toolCallId::String`: Unique identifier for this tool call
|
|
||||||
- `args::Dict{String,Any}`: Parsed arguments from the LLM
|
|
||||||
- `signal::Union{Nothing,abortSignal}`: Optional abort signal
|
|
||||||
- `onPartialResult::Function`: Callback for streaming partial results
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- `agentToolResult`: Result content with current time data
|
|
||||||
"""
|
|
||||||
function executeTool(toolCallId::String, args::Dict{String,Any}, signal::Union{Nothing,abortSignal}, onPartialResult::Function)::agentToolResult
|
|
||||||
tz = get(args, "timezone", nothing)
|
|
||||||
city = get(args, "city", "")
|
|
||||||
|
|
||||||
# Simulate time lookup — replace with actual timezone API call
|
|
||||||
if tz !== nothing
|
|
||||||
result = "Current time in $(tz): $(now())"
|
|
||||||
else
|
|
||||||
result = "Current time in $(city): $(now())"
|
|
||||||
end
|
|
||||||
|
|
||||||
return agentToolResult(
|
|
||||||
[textContent(result)],
|
|
||||||
Dict{Any,Any}(), nothing, false
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Define and return the getTime agentTool.
|
Define and return the getTime agentTool.
|
||||||
"""
|
"""
|
||||||
@@ -76,12 +47,24 @@ function getTool()::agentTool
|
|||||||
inputSchema = Dict{String,Any}(
|
inputSchema = Dict{String,Any}(
|
||||||
"type" => "object",
|
"type" => "object",
|
||||||
"properties" => Dict(
|
"properties" => Dict(
|
||||||
"timezone" => Dict("type" => "string", "description", "IANA timezone, e.g. 'America/New_York'"),
|
"timezone" => Dict("type" => "string", "description" => "IANA timezone, e.g. 'America/New_York'"),
|
||||||
"city" => Dict("type" => "string", "description", "City name as fallback")
|
"city" => Dict("type" => "string", "description" => "City name as fallback")
|
||||||
),
|
),
|
||||||
"required" => []
|
"required" => []
|
||||||
),
|
),
|
||||||
execute = executeTool,
|
execute = (toolCallId, args, signal, onPartialResult) -> begin
|
||||||
|
tz = get(args, "timezone", nothing)
|
||||||
|
city = get(args, "city", "")
|
||||||
|
if tz !== nothing
|
||||||
|
result = "Current time in $(tz): $(now())"
|
||||||
|
else
|
||||||
|
result = "Current time in $(city): $(now())"
|
||||||
|
end
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent(result)],
|
||||||
|
Dict{Any,Any}(), nothing, false
|
||||||
|
)
|
||||||
|
end,
|
||||||
prepareArguments = nothing,
|
prepareArguments = nothing,
|
||||||
validateRequiredArgs = validateRequiredArgs,
|
validateRequiredArgs = validateRequiredArgs,
|
||||||
parallelToolExecute = false
|
parallelToolExecute = false
|
||||||
|
|||||||
+10
-31
@@ -1,33 +1,3 @@
|
|||||||
"""
|
|
||||||
Execute the getWeather tool.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `toolCallId::String`: Unique identifier for this tool call
|
|
||||||
- `args::Dict{String,Any}`: Parsed arguments from the LLM
|
|
||||||
- `signal::Union{Nothing,abortSignal}`: Optional abort signal
|
|
||||||
- `onPartialResult::Function`: Callback for streaming partial results
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- `agentToolResult`: Result content with weather data
|
|
||||||
"""
|
|
||||||
function executeTool(toolCallId::String, args::Dict{String,Any}, signal::Union{Nothing,abortSignal}, onPartialResult::Function)::agentToolResult
|
|
||||||
city = get(args, "city", "")
|
|
||||||
units = get(args, "units", "celsius")
|
|
||||||
|
|
||||||
# Simulate weather fetch — replace with actual API call
|
|
||||||
# You can call onPartialResult() here for streaming progress updates:
|
|
||||||
# onPartialResult(Dict("status" => "Fetching weather data..."))
|
|
||||||
# onPartialResult(Dict("status" => "Processing..."))
|
|
||||||
|
|
||||||
temp = units == "fahrenheit" ? "72" : "22"
|
|
||||||
unit_symbol = units == "celsius" ? "°C" : "°F"
|
|
||||||
|
|
||||||
return agentToolResult(
|
|
||||||
[textContent("Weather in $(city): Sunny, $(temp)$(unit_symbol)")],
|
|
||||||
Dict{Any,Any}(), nothing, false
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Define and return the getWeather agentTool.
|
Define and return the getWeather agentTool.
|
||||||
"""
|
"""
|
||||||
@@ -44,7 +14,16 @@ function getTool()::agentTool
|
|||||||
),
|
),
|
||||||
"required" => ["city"]
|
"required" => ["city"]
|
||||||
),
|
),
|
||||||
execute = executeTool, # reference the function defined above
|
execute = (toolCallId, args, signal, onPartialResult) -> begin
|
||||||
|
city = get(args, "city", "")
|
||||||
|
units = get(args, "units", "celsius")
|
||||||
|
temp = units == "fahrenheit" ? "72" : "22"
|
||||||
|
unit_symbol = units == "celsius" ? "°C" : "°F"
|
||||||
|
return agentToolResult(
|
||||||
|
[textContent("Weather in $(city): Sunny, $(temp)$(unit_symbol)")],
|
||||||
|
Dict{Any,Any}(), nothing, false
|
||||||
|
)
|
||||||
|
end,
|
||||||
prepareArguments = nothing,
|
prepareArguments = nothing,
|
||||||
validateRequiredArgs = nothing,
|
validateRequiredArgs = nothing,
|
||||||
parallelToolExecute = false
|
parallelToolExecute = false
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ module toolRegistry
|
|||||||
|
|
||||||
export loadTools, registerTool, getTools, clearTools
|
export loadTools, registerTool, getTools, clearTools
|
||||||
|
|
||||||
|
using Dates
|
||||||
|
using JSON
|
||||||
using ..type
|
using ..type
|
||||||
|
|
||||||
# Global registry — populated at runtime by loadTools() or registerTool()
|
# Global registry — populated at runtime by loadTools() or registerTool()
|
||||||
@@ -99,7 +101,7 @@ function loadTools(dir::String)::Vector{agentTool}
|
|||||||
end
|
end
|
||||||
|
|
||||||
tools = agentTool[]
|
tools = agentTool[]
|
||||||
jl_files = filter(f -> endswith(f, ".jl"), readdir(dir))
|
jl_files = filter(f -> endswith(f, ".jl") && !occursin(r"(?i)registry", f), readdir(dir))
|
||||||
sort!(jl_files)
|
sort!(jl_files)
|
||||||
|
|
||||||
for filename in jl_files
|
for filename in jl_files
|
||||||
|
|||||||
+80
-87
@@ -46,11 +46,86 @@ function validateToolName(name::String)::Union{Nothing,String}
|
|||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Execute the writeTool.
|
Indent a multi-line code string by the specified number of spaces.
|
||||||
|
|
||||||
Generates a new .jl tool file and registers it with the tool registry.
|
|
||||||
"""
|
"""
|
||||||
function executeTool(toolCallId::String, args::Dict{String,Any}, signal::Union{Nothing,abortSignal}, onPartialResult::Function)::agentToolResult
|
function indent_code(code::String, n::Int)::String
|
||||||
|
prefix = " "^n
|
||||||
|
lines = split(code, '\n')
|
||||||
|
result_lines = String[prefix * line for line in lines]
|
||||||
|
return join(result_lines, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a Julia Dict to a valid Julia Dict{String,Any}(...) literal string.
|
||||||
|
"""
|
||||||
|
function dict_to_julia_literal(d)::String
|
||||||
|
if d isa Dict
|
||||||
|
items = String[]
|
||||||
|
for (k, v) in d
|
||||||
|
key_str = json_string(k)
|
||||||
|
val_str = value_to_julia(v)
|
||||||
|
push!(items, "$key_str => $val_str")
|
||||||
|
end
|
||||||
|
return "Dict{String,Any}(" * join(items, ", ") * ")"
|
||||||
|
else
|
||||||
|
return value_to_julia(d)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function value_to_julia(v)::String
|
||||||
|
if v isa Dict
|
||||||
|
return dict_to_julia_literal(v)
|
||||||
|
elseif v isa Vector
|
||||||
|
items = [value_to_julia(x) for x in v]
|
||||||
|
return "[" * join(items, ", ") * "]"
|
||||||
|
elseif v isa String
|
||||||
|
escaped = replace(v, "\\" => "\\\\")
|
||||||
|
escaped = replace(escaped, "\"" => "\\\"")
|
||||||
|
return "\"$escaped\""
|
||||||
|
elseif v isa Number
|
||||||
|
return string(v)
|
||||||
|
elseif v isa Bool
|
||||||
|
return string(v)
|
||||||
|
elseif v === nothing
|
||||||
|
return "nothing"
|
||||||
|
else
|
||||||
|
return "\"$(v)\""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert any Julia value to a JSON string.
|
||||||
|
"""
|
||||||
|
function json_string(v)::String
|
||||||
|
return JSON.json(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Define and return the writeTool agentTool.
|
||||||
|
"""
|
||||||
|
function getTool()::agentTool
|
||||||
|
return agentTool(
|
||||||
|
name = "writeTool",
|
||||||
|
label = "Create Tool",
|
||||||
|
description = "Write a new Julia tool module file to src/tools/<name>.jl. The LLM provides the tool logic as executeCode; writeTool wraps it in Julia boilerplate and writes the file. Restart the agent to load the new tool.",
|
||||||
|
inputSchema = Dict{String,Any}(
|
||||||
|
"type" => "object",
|
||||||
|
"properties" => Dict(
|
||||||
|
"name" => Dict("type" => "string", "description" => "Unique tool name (valid Julia identifier, no spaces or special chars)"),
|
||||||
|
"label" => Dict("type" => "string", "description" => "Human-readable tool name shown in tool descriptions"),
|
||||||
|
"description" => Dict("type" => "string", "description" => "What the tool does (shown to LLM for tool selection decisions)"),
|
||||||
|
"inputSchema" => Dict(
|
||||||
|
"type" => "object",
|
||||||
|
"description" => "JSON Schema describing tool parameters in MCP format"
|
||||||
|
),
|
||||||
|
"executeCode" => Dict("type" => "string", "description" => "Julia code for the execute function body. Use args[\"key\"] to access parameters. Do NOT wrap in a function definition."),
|
||||||
|
"validateCode" => Dict("type" => "string", "optional" => true, "description" => "Optional custom validation Julia code (runs before execute). Use args[\"key\"] to access parameters. Return nothing to pass, or a string error message to fail."),
|
||||||
|
"prepareCode" => Dict("type" => "string", "optional" => true, "description" => "Optional argument preparation code (runs before validation). Return modified args dict."),
|
||||||
|
"parallel" => Dict("type" => "boolean", "default" => false, "description" => "Whether this tool can run in parallel with other tools")
|
||||||
|
),
|
||||||
|
"required" => ["name", "label", "description", "inputSchema", "executeCode"]
|
||||||
|
),
|
||||||
|
execute = (toolCallId::String, args::Dict{String,Any}, signal::Union{Nothing,abortSignal}, onPartialResult::Function) -> begin
|
||||||
tool_name = get(args, "name", "")::String
|
tool_name = get(args, "name", "")::String
|
||||||
tool_label = get(args, "label", tool_name)::String
|
tool_label = get(args, "label", tool_name)::String
|
||||||
tool_description = get(args, "description", "")::String
|
tool_description = get(args, "description", "")::String
|
||||||
@@ -188,89 +263,7 @@ function executeTool(toolCallId::String, args::Dict{String,Any}, signal::Union{N
|
|||||||
),
|
),
|
||||||
nothing, false
|
nothing, false
|
||||||
)
|
)
|
||||||
end
|
end,
|
||||||
|
|
||||||
"""
|
|
||||||
Indent a multi-line code string by the specified number of spaces.
|
|
||||||
"""
|
|
||||||
function indent_code(code::String, n::Int)::String
|
|
||||||
prefix = " "^n
|
|
||||||
lines = split(code, '\n')
|
|
||||||
result_lines = String[prefix * line for line in lines]
|
|
||||||
return join(result_lines, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
"""
|
|
||||||
Convert a Julia Dict to a valid Julia Dict{String,Any}(...) literal string.
|
|
||||||
"""
|
|
||||||
function dict_to_julia_literal(d)::String
|
|
||||||
if d isa Dict
|
|
||||||
items = String[]
|
|
||||||
for (k, v) in d
|
|
||||||
key_str = json_string(k)
|
|
||||||
val_str = value_to_julia(v)
|
|
||||||
push!(items, "$key_str => $val_str")
|
|
||||||
end
|
|
||||||
return "Dict{String,Any}(" * join(items, ", ") * ")"
|
|
||||||
else
|
|
||||||
return value_to_julia(d)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function value_to_julia(v)::String
|
|
||||||
if v isa Dict
|
|
||||||
return dict_to_julia_literal(v)
|
|
||||||
elseif v isa Vector
|
|
||||||
items = [value_to_julia(x) for x in v]
|
|
||||||
return "[" * join(items, ", ") * "]"
|
|
||||||
elseif v isa String
|
|
||||||
escaped = replace(v, "\\" => "\\\\")
|
|
||||||
escaped = replace(escaped, "\"" => "\\\"")
|
|
||||||
return "\"$escaped\""
|
|
||||||
elseif v isa Number
|
|
||||||
return string(v)
|
|
||||||
elseif v isa Bool
|
|
||||||
return string(v)
|
|
||||||
elseif v === nothing
|
|
||||||
return "nothing"
|
|
||||||
else
|
|
||||||
return "\"$(v)\""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
"""
|
|
||||||
Convert any Julia value to a JSON string.
|
|
||||||
"""
|
|
||||||
function json_string(v)::String
|
|
||||||
return JSON.json(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
"""
|
|
||||||
Define and return the writeTool agentTool.
|
|
||||||
"""
|
|
||||||
function getTool()::agentTool
|
|
||||||
return agentTool(
|
|
||||||
name = "writeTool",
|
|
||||||
label = "Create Tool",
|
|
||||||
description = "Write a new Julia tool module file to src/tools/<name>.jl. The LLM provides the tool logic as executeCode; writeTool wraps it in Julia boilerplate and writes the file. Restart the agent to load the new tool.",
|
|
||||||
inputSchema = Dict{String,Any}(
|
|
||||||
"type" => "object",
|
|
||||||
"properties" => Dict(
|
|
||||||
"name" => Dict("type" => "string", "description" => "Unique tool name (valid Julia identifier, no spaces or special chars)"),
|
|
||||||
"label" => Dict("type" => "string", "description" => "Human-readable tool name shown in tool descriptions"),
|
|
||||||
"description" => Dict("type" => "string", "description" => "What the tool does (shown to LLM for tool selection decisions)"),
|
|
||||||
"inputSchema" => Dict(
|
|
||||||
"type" => "object",
|
|
||||||
"description" => "JSON Schema describing tool parameters in MCP format"
|
|
||||||
),
|
|
||||||
"executeCode" => Dict("type" => "string", "description" => "Julia code for the execute function body. Use args[\"key\"] to access parameters. Do NOT wrap in a function definition."),
|
|
||||||
"validateCode" => Dict("type" => "string", "optional" => true, "description" => "Optional custom validation Julia code (runs before execute). Use args[\"key\"] to access parameters. Return nothing to pass, or a string error message to fail."),
|
|
||||||
"prepareCode" => Dict("type" => "string", "optional" => true, "description" => "Optional argument preparation code (runs before validation). Return modified args dict."),
|
|
||||||
"parallel" => Dict("type" => "boolean", "default" => false, "description" => "Whether this tool can run in parallel with other tools")
|
|
||||||
),
|
|
||||||
"required" => ["name", "label", "description", "inputSchema", "executeCode"]
|
|
||||||
),
|
|
||||||
execute = executeTool,
|
|
||||||
prepareArguments = nothing,
|
prepareArguments = nothing,
|
||||||
validateRequiredArgs = nothing,
|
validateRequiredArgs = nothing,
|
||||||
parallelToolExecute = false
|
parallelToolExecute = false
|
||||||
|
|||||||
+76
-119
@@ -3,9 +3,8 @@ using YiemAgent
|
|||||||
using YiemAgent.toolRegistry
|
using YiemAgent.toolRegistry
|
||||||
using YiemAgent.type
|
using YiemAgent.type
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# Path to the real tools directory
|
||||||
# loadTools() unit tests #
|
TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
|
|
||||||
@testset "loadTools" begin
|
@testset "loadTools" begin
|
||||||
|
|
||||||
@@ -16,138 +15,99 @@ using YiemAgent.type
|
|||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# 2. loadTools throws if a .jl file does not define getTool() #
|
# 2. loadTools throws if a .jl file does not define getTool() #
|
||||||
# Must run before any other loadTools call (getTool persists in #
|
# Must run BEFORE any other loadTools call (getTool binding #
|
||||||
# module scope after include()). #
|
# persists in module scope after include()). #
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
bad_dir = mktempdir()
|
bad_dir = mktempdir()
|
||||||
write(joinpath(bad_dir, "noTool.jl"), "x = 42\n")
|
write(joinpath(bad_dir, "noTool.jl"), "x = 42\n")
|
||||||
@test_throws ArgumentError loadTools(bad_dir)
|
@test_throws ArgumentError loadTools(bad_dir)
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# 3. loadTools loads tool files that define getTool() #
|
# 3. loadTools loads actual tool files from src/tools/ #
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
tmpdir = mktempdir()
|
loaded = loadTools(TOOLS_DIR)
|
||||||
|
|
||||||
# Create a valid tool file (must use bare type names — include() places file in toolRegistry scope)
|
|
||||||
valid_tool_echo = """
|
|
||||||
function getTool()::agentTool
|
|
||||||
return agentTool(
|
|
||||||
name = "testEcho",
|
|
||||||
label = "Echo Test",
|
|
||||||
description = "Echoes the input argument",
|
|
||||||
inputSchema = Dict{String,Any}(
|
|
||||||
"type" => "object",
|
|
||||||
"properties" => Dict("message" => Dict("type" => "string")),
|
|
||||||
"required" => Any["message"]
|
|
||||||
),
|
|
||||||
execute = (toolCallId, args, signal, onPartialResult) -> begin
|
|
||||||
return agentToolResult(
|
|
||||||
[textContent("echo: " * string(args["message"]))],
|
|
||||||
Dict{Any,Any}(), nothing, false
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
prepareArguments = nothing,
|
|
||||||
validateRequiredArgs = nothing,
|
|
||||||
parallelToolExecute = false
|
|
||||||
)
|
|
||||||
end
|
|
||||||
"""
|
|
||||||
write(joinpath(tmpdir, "getEcho.jl"), valid_tool_echo)
|
|
||||||
|
|
||||||
loaded = loadTools(tmpdir)
|
|
||||||
@test !isempty(loaded)
|
@test !isempty(loaded)
|
||||||
@test length(loaded) >= 1
|
@test length(loaded) == 3
|
||||||
|
|
||||||
names = [t.name for t in loaded]
|
names = [t.name for t in loaded]
|
||||||
@test "testEcho" in names
|
@test "getTime" in names
|
||||||
|
@test "getWeather" in names
|
||||||
# Check agentTool fields
|
@test "writeTool" in names
|
||||||
echo_tool = filter(t -> t.name == "testEcho", loaded)
|
|
||||||
@test !isempty(echo_tool)
|
|
||||||
@test echo_tool[1].label == "Echo Test"
|
|
||||||
@test echo_tool[1].description == "Echoes the input argument"
|
|
||||||
@test echo_tool[1].parallelToolExecute == false
|
|
||||||
@test echo_tool[1].execute !== nothing
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# 4. loadTools returns tools sorted alphabetically #
|
# 4. loadTools returns tools sorted alphabetically by filename #
|
||||||
|
# (getTime.jl < getWeather.jl < writeTool.jl) #
|
||||||
|
# because 'T' < 'W' in ASCII #
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
sorted_dir = mktempdir()
|
@test loaded[1].name == "getTime"
|
||||||
|
@test loaded[2].name == "getWeather"
|
||||||
tool_a = """
|
@test loaded[3].name == "writeTool"
|
||||||
function getTool()::agentTool
|
|
||||||
return agentTool(
|
|
||||||
name = "alphaTool",
|
|
||||||
label = "Alpha Tool",
|
|
||||||
description = "First tool",
|
|
||||||
inputSchema = Dict{String,Any}("type" => "object", "properties" => Dict{String,Any}(), "required" => Any[]),
|
|
||||||
execute = (toolCallId, args, signal, onPartialResult) ->
|
|
||||||
agentToolResult([textContent("alpha")], Dict{Any,Any}(), nothing, false),
|
|
||||||
prepareArguments = nothing,
|
|
||||||
validateRequiredArgs = nothing,
|
|
||||||
parallelToolExecute = false
|
|
||||||
)
|
|
||||||
end
|
|
||||||
"""
|
|
||||||
|
|
||||||
tool_m = """
|
|
||||||
function getTool()::agentTool
|
|
||||||
return agentTool(
|
|
||||||
name = "midTool",
|
|
||||||
label = "Mid Tool",
|
|
||||||
description = "Middle tool",
|
|
||||||
inputSchema = Dict{String,Any}("type" => "object", "properties" => Dict{String,Any}(), "required" => Any[]),
|
|
||||||
execute = (toolCallId, args, signal, onPartialResult) ->
|
|
||||||
agentToolResult([textContent("mid")], Dict{Any,Any}(), nothing, false),
|
|
||||||
prepareArguments = nothing,
|
|
||||||
validateRequiredArgs = nothing,
|
|
||||||
parallelToolExecute = false
|
|
||||||
)
|
|
||||||
end
|
|
||||||
"""
|
|
||||||
|
|
||||||
tool_z = """
|
|
||||||
function getTool()::agentTool
|
|
||||||
return agentTool(
|
|
||||||
name = "zuluTool",
|
|
||||||
label = "Zulu Tool",
|
|
||||||
description = "Last tool",
|
|
||||||
inputSchema = Dict{String,Any}("type" => "object", "properties" => Dict{String,Any}(), "required" => Any[]),
|
|
||||||
execute = (toolCallId, args, signal, onPartialResult) ->
|
|
||||||
agentToolResult([textContent("zulu")], Dict{Any,Any}(), nothing, false),
|
|
||||||
prepareArguments = nothing,
|
|
||||||
validateRequiredArgs = nothing,
|
|
||||||
parallelToolExecute = false
|
|
||||||
)
|
|
||||||
end
|
|
||||||
"""
|
|
||||||
|
|
||||||
write(joinpath(sorted_dir, "zTool.jl"), tool_z)
|
|
||||||
write(joinpath(sorted_dir, "aTool.jl"), tool_a)
|
|
||||||
write(joinpath(sorted_dir, "mTool.jl"), tool_m)
|
|
||||||
|
|
||||||
loaded_sorted = loadTools(sorted_dir)
|
|
||||||
# loadTools returns only tools loaded from the directory, in file-sorted order
|
|
||||||
@test length(loaded_sorted) == 3
|
|
||||||
@test loaded_sorted[1].name == "alphaTool"
|
|
||||||
@test loaded_sorted[2].name == "midTool"
|
|
||||||
@test loaded_sorted[3].name == "zuluTool"
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# 5. getTools returns a deep copy (mutations don't affect registry) #
|
# 5. Verify loaded tool fields are correct #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# getTime
|
||||||
|
time_tool = loaded[1]
|
||||||
|
@test time_tool.name == "getTime"
|
||||||
|
@test time_tool.label == "Time Lookup"
|
||||||
|
@test time_tool.validateRequiredArgs !== nothing
|
||||||
|
@test time_tool.parallelToolExecute == false
|
||||||
|
@test time_tool.inputSchema["required"] == Any[]
|
||||||
|
|
||||||
|
# getWeather
|
||||||
|
weather = loaded[2]
|
||||||
|
@test weather.name == "getWeather"
|
||||||
|
@test weather.label == "Weather Lookup"
|
||||||
|
@test weather.execute !== nothing
|
||||||
|
@test weather.parallelToolExecute == false
|
||||||
|
@test weather.inputSchema["required"] == ["city"]
|
||||||
|
|
||||||
|
# writeTool
|
||||||
|
wt = loaded[3]
|
||||||
|
@test wt.name == "writeTool"
|
||||||
|
@test wt.label == "Create Tool"
|
||||||
|
@test wt.execute !== nothing
|
||||||
|
@test "name" in wt.inputSchema["required"]
|
||||||
|
@test "executeCode" in wt.inputSchema["required"]
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 6. Tool execution returns valid results #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
sig = nothing
|
||||||
|
op = x -> x # no-op partial result callback
|
||||||
|
|
||||||
|
# execute getTime
|
||||||
|
result_t = time_tool.execute("call-1", Dict{String,Any}("city" => "Tokyo"), sig, op)
|
||||||
|
@test result_t isa agentToolResult
|
||||||
|
@test result_t.content[1] isa textContent
|
||||||
|
@test occursin("Tokyo", result_t.content[1].text)
|
||||||
|
|
||||||
|
# execute getTime with timezone
|
||||||
|
result_tz = time_tool.execute("call-2", Dict{String,Any}("timezone" => "America/New_York"), sig, op)
|
||||||
|
@test result_tz isa agentToolResult
|
||||||
|
@test occursin("America/New_York", result_tz.content[1].text)
|
||||||
|
|
||||||
|
# execute getWeather
|
||||||
|
result_w = weather.execute("call-3", Dict{String,Any}("city" => "Bangkok"), sig, op)
|
||||||
|
@test result_w isa agentToolResult
|
||||||
|
@test result_w.content[1] isa textContent
|
||||||
|
@test occursin("Bangkok", result_w.content[1].text)
|
||||||
|
|
||||||
|
# execute getWeather with units
|
||||||
|
result_w2 = weather.execute("call-4", Dict{String,Any}("city" => "London", "units" => "fahrenheit"), sig, op)
|
||||||
|
@test occursin("72°F", result_w2.content[1].text)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 7. getTools / registerTool / clearTools #
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
registry_tools = getTools()
|
registry_tools = getTools()
|
||||||
@test !isempty(registry_tools)
|
@test !isempty(registry_tools)
|
||||||
orig_count = length(registry_tools)
|
@test any(t -> t.name == "getTime", registry_tools)
|
||||||
|
@test any(t -> t.name == "getWeather", registry_tools)
|
||||||
|
|
||||||
# Clear and add a new tool via registerTool
|
|
||||||
clearTools()
|
clearTools()
|
||||||
registry_after_clear = getTools()
|
@test isempty(getTools())
|
||||||
@test isempty(registry_after_clear)
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
# 6. registerTool adds to global registry #
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
test_tool = agentTool(
|
test_tool = agentTool(
|
||||||
name = "manualTool",
|
name = "manualTool",
|
||||||
label = "Manual Tool",
|
label = "Manual Tool",
|
||||||
@@ -163,13 +123,10 @@ end
|
|||||||
reg = getTools()
|
reg = getTools()
|
||||||
@test any(t -> t.name == "manualTool", reg)
|
@test any(t -> t.name == "manualTool", reg)
|
||||||
@test count(t -> t.name == "manualTool", reg) == 1
|
@test count(t -> t.name == "manualTool", reg) == 1
|
||||||
|
@test reg[1].parallelToolExecute == true
|
||||||
# parallelToolExecute flag
|
|
||||||
manual_entry = filter(t -> t.name == "manualTool", reg)
|
|
||||||
@test manual_entry[1].parallelToolExecute == true
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# 7. getTools returns deep copy #
|
# 8. getTools returns deep copy (mutations don't affect registry) #
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
copy1 = getTools()
|
copy1 = getTools()
|
||||||
copy2 = getTools()
|
copy2 = getTools()
|
||||||
|
|||||||
Reference in New Issue
Block a user