From 1b69f69c7d626fdba9ed2c985dc7146042c12174 Mon Sep 17 00:00:00 2001 From: narawat Date: Mon, 10 Aug 2026 13:33:45 +0700 Subject: [PATCH] update --- src/tools/README.md | 49 +++++++++++++++++++++++++++++++++++------- src/tools/getTime.jl | 2 ++ src/tools/registry.jl | 20 +++++++++++------ src/tools/writeTool.jl | 2 ++ test/loadToolTest.jl | 15 ++++++++----- 5 files changed, 68 insertions(+), 20 deletions(-) diff --git a/src/tools/README.md b/src/tools/README.md index bfe4569..a64b026 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -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 ``` diff --git a/src/tools/getTime.jl b/src/tools/getTime.jl index b58a752..94b7752 100644 --- a/src/tools/getTime.jl +++ b/src/tools/getTime.jl @@ -1,3 +1,5 @@ +using Dates + """ Validate required arguments for the getTime tool. diff --git a/src/tools/registry.jl b/src/tools/registry.jl index 3954e8b..0884853 100644 --- a/src/tools/registry.jl +++ b/src/tools/registry.jl @@ -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 """ diff --git a/src/tools/writeTool.jl b/src/tools/writeTool.jl index 40c4d8c..73dc6da 100644 --- a/src/tools/writeTool.jl +++ b/src/tools/writeTool.jl @@ -1,3 +1,5 @@ +using JSON + """ Tool that writes new Julia tool module files to disk. diff --git a/test/loadToolTest.jl b/test/loadToolTest.jl index ad6613a..f0f10df 100644 --- a/test/loadToolTest.jl +++ b/test/loadToolTest.jl @@ -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) #