update
This commit is contained in:
+49
-69
@@ -3,7 +3,6 @@ using YiemAgent
|
||||
using YiemAgent.toolRegistry
|
||||
using YiemAgent.type
|
||||
|
||||
# Path to the real tools directory
|
||||
TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
|
||||
|
||||
@testset "loadTools" begin
|
||||
@@ -15,95 +14,77 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 2. loadTools throws if a .jl file does not define getTool() #
|
||||
# Must run BEFORE any other loadTools call (getTool binding #
|
||||
# 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 actual tool files from src/tools/ #
|
||||
# 3. Load all tools from src/tools/ #
|
||||
# ------------------------------------------------------------------ #
|
||||
clearTools()
|
||||
loaded = loadTools(TOOLS_DIR)
|
||||
@test !isempty(loaded)
|
||||
@test length(loaded) == 3
|
||||
|
||||
names = [t.name for t in loaded]
|
||||
@test "getTime" in names
|
||||
@test "getWeather" in names
|
||||
@test "writeTool" in names
|
||||
@test length(loaded) == 3 # getTime.jl, getWeather.jl, writeTool.jl
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 4. loadTools returns tools sorted alphabetically by filename #
|
||||
# (getTime.jl < getWeather.jl < writeTool.jl) #
|
||||
# because 'T' < 'W' in ASCII #
|
||||
# 4. Each loaded tool has an isolated _tool_module #
|
||||
# ------------------------------------------------------------------ #
|
||||
@test loaded[1].name == "getTime"
|
||||
@test loaded[2].name == "getWeather"
|
||||
@test loaded[3].name == "writeTool"
|
||||
for tool in loaded
|
||||
@test tool._tool_module isa Module
|
||||
end
|
||||
|
||||
# Verify modules are unique (not shared)
|
||||
modules = [t._tool_module for t in loaded]
|
||||
@test length(unique(modules)) == length(modules)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 5. Verify loaded tool fields are correct #
|
||||
# 5. Each tool's executeTool is callable #
|
||||
# ------------------------------------------------------------------ #
|
||||
# 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[]
|
||||
function run_all_execution_tests(loaded)
|
||||
sig = nothing
|
||||
op = x -> x # no-op partial result callback
|
||||
|
||||
# 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"]
|
||||
for tool in loaded
|
||||
@test tool.execute !== nothing
|
||||
@test tool._tool_module isa Module
|
||||
|
||||
# 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"]
|
||||
# Verify the module actually defines executeTool
|
||||
@test isdefined(tool._tool_module, :executeTool)
|
||||
|
||||
# Execute the tool — must return agentToolResult without throwing
|
||||
result = try
|
||||
tool.execute("call-$(tool.name)", Dict{String,Any}("city" => "Tokyo"), sig, op)
|
||||
catch err
|
||||
tool.execute("call-$(tool.name)", Dict{String,Any}(), sig, op)
|
||||
end
|
||||
@test result isa agentToolResult
|
||||
@test result.content[1] isa textContent
|
||||
end
|
||||
end
|
||||
run_all_execution_tests(loaded)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 6. Tool execution returns valid results #
|
||||
# 6. Specific tool assertions based on known tool names #
|
||||
# ------------------------------------------------------------------ #
|
||||
sig = nothing
|
||||
op = x -> x # no-op partial result callback
|
||||
time_tool = filter(t -> t.name == "getTime", loaded)
|
||||
@test !isempty(time_tool)
|
||||
@test time_tool[1].validateRequiredArgs !== nothing
|
||||
|
||||
# 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)
|
||||
getWeather_tool = filter(t -> t.name == "getWeather", loaded)
|
||||
@test !isempty(getWeather_tool)
|
||||
@test getWeather_tool[1].inputSchema["required"] == ["city"]
|
||||
|
||||
# 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)
|
||||
write_tool = filter(t -> t.name == "writeTool", loaded)
|
||||
@test !isempty(write_tool)
|
||||
@test "name" in write_tool[1].inputSchema["required"]
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 7. getTools / registerTool / clearTools #
|
||||
# ------------------------------------------------------------------ #
|
||||
registry_tools = getTools()
|
||||
@test !isempty(registry_tools)
|
||||
@test any(t -> t.name == "getTime", registry_tools)
|
||||
@test any(t -> t.name == "getWeather", registry_tools)
|
||||
@test length(registry_tools) == 3 # only the loaded tools
|
||||
|
||||
clearTools()
|
||||
@test isempty(getTools())
|
||||
@@ -117,20 +98,19 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
|
||||
agentToolResult([textContent("manual")], Dict{Any,Any}(), nothing, false),
|
||||
prepareArguments = nothing,
|
||||
validateRequiredArgs = nothing,
|
||||
parallelToolExecute = true
|
||||
parallelToolExecute = true,
|
||||
_tool_module = nothing
|
||||
)
|
||||
registerTool(test_tool)
|
||||
reg = getTools()
|
||||
@test any(t -> t.name == "manualTool", reg)
|
||||
@test count(t -> t.name == "manualTool", reg) == 1
|
||||
@test reg[1].parallelToolExecute == true
|
||||
@test reg[1]._tool_module === nothing
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 8. getTools returns deep copy (mutations don't affect registry) #
|
||||
# 8. getTools returns new vector (mutations don't affect registry) #
|
||||
# ------------------------------------------------------------------ #
|
||||
copy1 = getTools()
|
||||
copy2 = getTools()
|
||||
@test copy1 !== copy2
|
||||
empty!(copy1)
|
||||
@test !isempty(getTools())
|
||||
clearTools()
|
||||
@test isempty(getTools())
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user