V0.8.0 verify tool use #43
+41
-8
@@ -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
|
||||
```
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Dates
|
||||
|
||||
"""
|
||||
Validate required arguments for the getTime tool.
|
||||
|
||||
|
||||
+13
-7
@@ -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
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using JSON
|
||||
|
||||
"""
|
||||
Tool that writes new Julia tool module files to disk.
|
||||
|
||||
|
||||
+10
-5
@@ -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) #
|
||||
|
||||
Reference in New Issue
Block a user