static tool loading
This commit is contained in:
+4
-96
@@ -1,6 +1,6 @@
|
||||
module toolRegistry
|
||||
|
||||
export toolStore, loadTools, registerTool, getTools, clearTools, listTool
|
||||
export toolStore, registerTool, getTools, clearTools, listTool
|
||||
|
||||
using Dates
|
||||
using JSON, DataStructures
|
||||
@@ -45,7 +45,7 @@ end
|
||||
Return an `agentTool` definition for listing registered tools.
|
||||
|
||||
Each call produces a **new** tool object that captures (closes over)
|
||||
`store`. `loadTools` auto-registers one so the LLM can discover tools
|
||||
`store`. `register_all_tools` auto-registers one so the LLM can discover tools
|
||||
at runtime.
|
||||
|
||||
# Arguments
|
||||
@@ -55,7 +55,7 @@ at runtime.
|
||||
```julia
|
||||
julia> store = toolStore(name="agent1");
|
||||
|
||||
julia> loadTools(store, "src/tools") # auto-registers listTools
|
||||
julia> register_all_tools(store) # auto-registers listTools
|
||||
[toolRegistry:agent1] Loaded tool: getWeather (Weather Lookup)
|
||||
[toolRegistry:agent1] Registered tool: listTools
|
||||
|
||||
@@ -97,99 +97,7 @@ function listTool(store::toolStore)::agentTool
|
||||
)
|
||||
end
|
||||
|
||||
"""
|
||||
Load `.jl` tool files from `dir` into `store`, then auto-register
|
||||
`listTool` so the LLM can discover available tools at runtime.
|
||||
|
||||
Each `.jl` file must define `function getTool()::agentTool ... end`.
|
||||
Files are sorted alphabetically for deterministic registration order.
|
||||
Each file is loaded into its own Julia submodule to avoid name collisions.
|
||||
|
||||
# Arguments
|
||||
- `store`: Tool store to populate
|
||||
- `dir`: Directory containing `.jl` tool files
|
||||
|
||||
# Returns
|
||||
- The same `store.tools` dict (modified in place)
|
||||
|
||||
# Errors
|
||||
- Throws `ArgumentError` if `dir` does not exist or a file lacks `getTool()`
|
||||
|
||||
# Example
|
||||
```julia
|
||||
julia> store = toolStore(name="agent1");
|
||||
|
||||
julia> loadTools(store, "src/tools")
|
||||
[toolRegistry:agent1] Loaded tool: getWeather (Weather Lookup)
|
||||
[toolRegistry:agent1] Loaded tool: getTime (Time Lookup)
|
||||
[toolRegistry:agent1] Registered tool: listTools
|
||||
OrderedDict{String, agentTool} with 3 entries:
|
||||
"getWeather" => agentTool(...)
|
||||
"getTime" => agentTool(...)
|
||||
"listTools" => agentTool(...)
|
||||
```
|
||||
"""
|
||||
function loadTools(store::toolStore, dir::String)::OrderedDict{String, agentTool}
|
||||
if !isdir(dir)
|
||||
throw(ArgumentError("Tool directory does not exist: $dir"))
|
||||
end
|
||||
|
||||
jl_files = filter(f -> endswith(f, ".jl") && !occursin(r"(?i)registry", f), readdir(dir))
|
||||
sort!(jl_files)
|
||||
|
||||
for filename in jl_files
|
||||
filepath = joinpath(dir, filename)
|
||||
|
||||
# Derive a unique module name from the filename only (not full path).
|
||||
# e.g. "getWeather.jl" -> "_tool_getWeather"
|
||||
mod_name = Symbol("_tool_", replace(rstrip(filename, '.'), ".jl" => ""))
|
||||
|
||||
# Build the complete module as a string and eval the parsed code.
|
||||
# Julia does not allow `module ... end` inside eval(quote ...),
|
||||
# and constructing the module AST by hand is fragile.
|
||||
# Instead, we generate the full module source as a string,
|
||||
# parse it, and eval the resulting expression.
|
||||
# Each tool file declares its own dependencies via `using` statements
|
||||
# at the top of the file — the registry only injects `using ..type`
|
||||
# to make core types (agentTool, textContent, etc.) available.
|
||||
file_content = read(filepath, String)
|
||||
module_code = """
|
||||
module $(mod_name)
|
||||
using ..type
|
||||
$(file_content)
|
||||
end
|
||||
"""
|
||||
mod = eval(Meta.parse(module_code))
|
||||
|
||||
# Call getTool() via Core.eval in the submodule's scope.
|
||||
# This evaluates getTool() entirely within the new module's world,
|
||||
# completely avoiding world-age issues — no invokelatest needed.
|
||||
# Note: all uses of `tool` must be inside the `try` block because
|
||||
# Julia 1.12's SSA form doesn't track `tool` as definitely assigned
|
||||
# after a `try-catch` where it's only assigned inside `try`.
|
||||
try
|
||||
tool = Core.eval(mod, :(getTool()))
|
||||
if !(tool isa agentTool)
|
||||
throw(ArgumentError(
|
||||
"getTool() in $(filepath) did not return an agentTool instance, got: $(typeof(tool))"
|
||||
))
|
||||
end
|
||||
store.tools[tool.name] = tool
|
||||
println("[$(store.name)] Loaded tool: $(tool.name) ($(tool.label))")
|
||||
catch e
|
||||
if e isa UndefVarError || occursin("getTool", sprint(showerror, e))
|
||||
throw(ArgumentError(
|
||||
"Tool file $(filepath) does not define a `getTool()` function in module $(mod_name). " *
|
||||
"Each tool file must define: function getTool()::agentTool ... end"
|
||||
))
|
||||
end
|
||||
rethrow(e)
|
||||
end
|
||||
end
|
||||
|
||||
registerTool(store, listTool(store))
|
||||
return store.tools
|
||||
end
|
||||
# Note: register_all_tools is defined in YiemAgent.jl where tool functions are in scope
|
||||
|
||||
"""
|
||||
registerTool(store::toolStore, tool::agentTool) -> OrderedDict{String, agentTool}
|
||||
|
||||
Reference in New Issue
Block a user