V0.8.0 add tools #41
@@ -119,7 +119,8 @@ function loadTools(dir::String)::Vector{agentTool}
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Call getTool() — it runs in current scope where types are visible
|
# Call getTool() — it runs in current scope where types are visible
|
||||||
tool = getTool()
|
# Use invokelatest to handle world-age semantics after include()
|
||||||
|
tool = invokelatest(getTool)
|
||||||
if !(tool isa agentTool)
|
if !(tool isa agentTool)
|
||||||
throw(ArgumentError(
|
throw(ArgumentError(
|
||||||
"getTool() in $(filepath) did not return an agentTool instance, got: $(typeof(tool))"
|
"getTool() in $(filepath) did not return an agentTool instance, got: $(typeof(tool))"
|
||||||
|
|||||||
+11
@@ -269,6 +269,17 @@ struct agentTool # A tool available to the agent
|
|||||||
parallelToolExecute::Bool # Override: run tool calls sequentially or in parallel
|
parallelToolExecute::Bool # Override: run tool calls sequentially or in parallel
|
||||||
end
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Keyword constructor for agentTool — allows `agentTool(name=..., label=..., ...)`.
|
||||||
|
"""
|
||||||
|
function agentTool(; name::String, label::String, description::String, inputSchema::Any,
|
||||||
|
execute::Function, prepareArguments::Union{Function, Nothing}=nothing,
|
||||||
|
validateRequiredArgs::Union{Function, Nothing}=nothing,
|
||||||
|
parallelToolExecute::Bool=false)
|
||||||
|
return agentTool(name, label, description, inputSchema, execute,
|
||||||
|
prepareArguments, validateRequiredArgs, parallelToolExecute)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------------------------ #
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
# Agent context #
|
# Agent context #
|
||||||
|
|||||||
+179
-1
@@ -1 +1,179 @@
|
|||||||
using YiemAgent
|
using Test
|
||||||
|
using YiemAgent
|
||||||
|
using YiemAgent.toolRegistry
|
||||||
|
using YiemAgent.type
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# loadTools() unit tests #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@testset "loadTools" begin
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 1. loadTools throws on non-existent directory #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
@test_throws ArgumentError loadTools("/nonexistent/dir/that/does/not/exist")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 2. loadTools throws if a .jl file does not define getTool() #
|
||||||
|
# Must run before any other loadTools call (getTool persists in #
|
||||||
|
# module scope after include()). #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
bad_dir = mktempdir()
|
||||||
|
write(joinpath(bad_dir, "noTool.jl"), "x = 42\n")
|
||||||
|
@test_throws ArgumentError loadTools(bad_dir)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 3. loadTools loads tool files that define getTool() #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
tmpdir = mktempdir()
|
||||||
|
|
||||||
|
# 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 length(loaded) >= 1
|
||||||
|
|
||||||
|
names = [t.name for t in loaded]
|
||||||
|
@test "testEcho" in names
|
||||||
|
|
||||||
|
# Check agentTool fields
|
||||||
|
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 #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
sorted_dir = mktempdir()
|
||||||
|
|
||||||
|
tool_a = """
|
||||||
|
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) #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
registry_tools = getTools()
|
||||||
|
@test !isempty(registry_tools)
|
||||||
|
orig_count = length(registry_tools)
|
||||||
|
|
||||||
|
# Clear and add a new tool via registerTool
|
||||||
|
clearTools()
|
||||||
|
registry_after_clear = getTools()
|
||||||
|
@test isempty(registry_after_clear)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 6. registerTool adds to global registry #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
test_tool = agentTool(
|
||||||
|
name = "manualTool",
|
||||||
|
label = "Manual Tool",
|
||||||
|
description = "Registered manually",
|
||||||
|
inputSchema = Dict{String,Any}("type" => "object", "properties" => Dict{String,Any}(), "required" => Any[]),
|
||||||
|
execute = (toolCallId, args, signal, onPartialResult) ->
|
||||||
|
agentToolResult([textContent("manual")], Dict{Any,Any}(), nothing, false),
|
||||||
|
prepareArguments = nothing,
|
||||||
|
validateRequiredArgs = nothing,
|
||||||
|
parallelToolExecute = true
|
||||||
|
)
|
||||||
|
registerTool(test_tool)
|
||||||
|
reg = getTools()
|
||||||
|
@test any(t -> t.name == "manualTool", reg)
|
||||||
|
@test count(t -> t.name == "manualTool", reg) == 1
|
||||||
|
|
||||||
|
# parallelToolExecute flag
|
||||||
|
manual_entry = filter(t -> t.name == "manualTool", reg)
|
||||||
|
@test manual_entry[1].parallelToolExecute == true
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# 7. getTools returns deep copy #
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
copy1 = getTools()
|
||||||
|
copy2 = getTools()
|
||||||
|
@test copy1 !== copy2
|
||||||
|
empty!(copy1)
|
||||||
|
@test !isempty(getTools())
|
||||||
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user