This commit is contained in:
2026-08-10 13:33:45 +07:00
parent 3891099eaa
commit 1b69f69c7d
5 changed files with 68 additions and 20 deletions
+41 -8
View File
@@ -152,19 +152,24 @@ The `_listTool()` is auto-registered in `__init__()` (line 16-18), so `listTools
### Registration API
```julia
# Auto-load from directory
# Auto-load from directory (returns OrderedDict keyed by tool name)
tools = loadTools("src/tools") # OrderedDict{String, agentTool}
# Manual registration
registerTool(my_tool) # Adds to global _registry
# Manual registration (adds to global _registry)
registerTool(my_tool)
# Query
all_tools = getTools() # Vector{agentTool} (deep copy)
# Query (returns OrderedDict keyed by tool name, in registration order)
all_tools = getTools() # OrderedDict{String, agentTool} — O(1) lookup + deterministic order
# Clear
clearTools() # Empties _registry
```
**Why `OrderedDict` for `getTools()`?** The internal `_registry` is a `Vector{agentTool}` for ordered iteration (used by `listTools`). `getTools()` builds an `OrderedDict` from `_registry` so callers get:
- O(1) lookup by tool name
- Deterministic iteration order (registration order: `listTools` auto-registered first, then tools loaded alphabetically by filename)
- Consistency with `agentState.tools` (also `OrderedDict{String, agentTool}`)
---
## 4. The Agent Loop — High-Level Flow
@@ -1272,6 +1277,8 @@ end
```julia
# src/tools/myTool.jl
using Dates # ← tool declares its own dependencies (registry injects only `using ..type`)
# Optional: helper functions
function helper_function(...)
...
@@ -1313,18 +1320,44 @@ function getTool()::agentTool
end
```
### Dependencies
Each tool file **declares its own dependencies** via `using` statements at the top of the file. The registry does **not** inject any standard library packages — if a tool needs `Dates`, `JSON`, `HTTP`, `CSV`, or any other package, it must include its own `using` statements.
```julia
# src/tools/getTime.jl
using Dates
function executeTool(...)
now() # Dates.now requires `using Dates`
end
```
```julia
# src/tools/myApiTool.jl
using HTTP, JSON
function executeTool(...)
response = HTTP.get("https://api.example.com")
data = JSON.parse(String(response.body))
...
end
```
### Module Isolation
When `loadTools()` loads a file, it wraps it in a dynamically created submodule:
When `loadTools()` loads a file, it wraps it in a dynamically created submodule. The registry injects **only** `using ..type` to make core types (`agentTool`, `textContent`, `agentToolResult`, `abortSignal`, etc.) available:
```julia
# User writes in src/tools/myTool.jl:
using Dates, HTTP, JSON # ← tool's own dependencies
function getTool()::agentTool ... end
# loadTools() creates:
module _tool_myTool
using ..type
using Dates, UUIDs, DataStructures, JSON
using ..type # ← injected by registry (core types only)
using Dates, HTTP, JSON # ← from tool file
# (user's code here)
end
```
+2
View File
@@ -1,3 +1,5 @@
using Dates
"""
Validate required arguments for the getTime tool.
+13 -7
View File
@@ -113,13 +113,13 @@ function loadTools(dir::String)::OrderedDict{String, agentTool}
# 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.
# Also import Dates, UUIDs, DataStructures, JSON — common dependencies
# that tool files use (and that the ..type module transitively uses).
# 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
using Dates, UUIDs, DataStructures, JSON
$(file_content)
end
"""
@@ -175,13 +175,19 @@ function registerTool(tool::agentTool)::Vector{agentTool}
end
"""
Get all registered tools.
Get all registered tools as an `OrderedDict{String, agentTool}` keyed by tool name.
The internal `_registry` is a `Vector` for ordered iteration (used by `listTools`).
This function builds an `OrderedDict` from `_registry` so callers get:
- O(1) lookup by name
- Deterministic iteration order (registration order: alphabetical by filename)
- Consistency with `agentState.tools` (also `OrderedDict{String, agentTool}`)
# Returns
- `Vector{agentTool}`: Copy of the registry
- `OrderedDict{String, agentTool}`: Copy of the registry keyed by tool name, in registration order
"""
function getTools()::Vector{agentTool}
return deepcopy(_registry)
function getTools()::OrderedDict{String, agentTool}
return OrderedDict{String, agentTool}(t.name => t for t in _registry)
end
"""
+2
View File
@@ -1,3 +1,5 @@
using JSON
"""
Tool that writes new Julia tool module files to disk.
+10 -5
View File
@@ -102,8 +102,13 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
# ------------------------------------------------------------------ #
registry_tools = getTools()
@test !isempty(registry_tools)
@test any(t -> t.name == "getTime", registry_tools)
@test any(t -> t.name == "getWeather", registry_tools)
@test "getTime" in keys(registry_tools)
@test "getWeather" in keys(registry_tools)
# listTools is auto-registered via __init__() → first key, then tools loaded alphabetically
@test collect(keys(registry_tools))[1] == "listTools"
@test collect(keys(registry_tools))[2] == "getTime"
@test collect(keys(registry_tools))[3] == "getWeather"
@test collect(keys(registry_tools))[4] == "writeTool"
clearTools()
@test isempty(getTools())
@@ -121,9 +126,9 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
)
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 haskey(reg, "manualTool")
@test length(reg) == 1
@test reg["manualTool"].parallelToolExecute == true
# ------------------------------------------------------------------ #
# 8. getTools returns deep copy (mutations don't affect registry) #