From c5cb18f0f14cd2d0125870449a0c0c8fc78eb0b8 Mon Sep 17 00:00:00 2001 From: narawat Date: Mon, 10 Aug 2026 16:10:04 +0700 Subject: [PATCH] update --- src/YiemAgent.jl | 2 +- src/{tools/registry.jl => toolRegistry.jl} | 38 ++++++++-------------- src/tools/README.md | 23 ++++++------- test/{loadToolTest.jl => toolTest.jl} | 6 ++-- 4 files changed, 29 insertions(+), 40 deletions(-) rename src/{tools/registry.jl => toolRegistry.jl} (84%) rename test/{loadToolTest.jl => toolTest.jl} (97%) diff --git a/src/YiemAgent.jl b/src/YiemAgent.jl index 765807e..5659d95 100644 --- a/src/YiemAgent.jl +++ b/src/YiemAgent.jl @@ -13,7 +13,7 @@ module YiemAgent include("utils.jl") using .utils - include("tools/registry.jl") + include("toolRegistry.jl") using .toolRegistry # include("llmfunction.jl") diff --git a/src/tools/registry.jl b/src/toolRegistry.jl similarity index 84% rename from src/tools/registry.jl rename to src/toolRegistry.jl index 4315211..8cf9a45 100644 --- a/src/tools/registry.jl +++ b/src/toolRegistry.jl @@ -13,13 +13,11 @@ Each agent gets its own `ToolStore` so tool registration is independent — `registerTool(store, tool)` only affects that agent's tool set. # Fields -- `tools::Vector{agentTool}` — ordered tool list (for `listTool` iteration) -- `modules::Vector{Module}` — keeps tool submodules alive to prevent GC of closures +- `tools::OrderedDict{String, agentTool}` — keyed by name for O(1) lookup + ordered iteration - `name::String` — identifier for debugging/logs """ struct ToolStore - tools::Vector{agentTool} - modules::Vector{Module} + tools::OrderedDict{String, agentTool} name::String end @@ -38,7 +36,7 @@ agent = yiemAgent(tools=getTools(store), llmCall=..., _tool_store=store) ``` """ function ToolStore(; name::String="default")::ToolStore - ToolStore(agentTool[], Module[], name) + ToolStore(OrderedDict{String, agentTool}(), name) end """ @@ -66,7 +64,7 @@ function listTool(store::ToolStore)::agentTool if isempty(tools) result_text = "No tools registered." else - lines = String["- $(t.name): $(t.label) — $(t.description)" for t in tools] + lines = String["- $(t.name): $(t.label) — $(t.description)" for (k, t) in tools] result_text = "Available tools:\n" * join(lines, "\n") end return agentToolResult( @@ -117,7 +115,6 @@ function loadTools(store::ToolStore, dir::String)::OrderedDict{String, agentTool throw(ArgumentError("Tool directory does not exist: $dir")) end - tools = OrderedDict{String, agentTool}() jl_files = filter(f -> endswith(f, ".jl") && !occursin(r"(?i)registry", f), readdir(dir)) sort!(jl_files) @@ -158,12 +155,7 @@ function loadTools(store::ToolStore, dir::String)::OrderedDict{String, agentTool "getTool() in $(filepath) did not return an agentTool instance, got: $(typeof(tool))" )) end - # Keep module reference alive — closures in the agentTool (execute, - # validateRequiredArgs, prepareArguments) may reference module-scoped - # functions. Without this, GC could collect the module. - push!(store.modules, mod) - push!(store.tools, tool) - tools[tool.name] = tool + 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)) @@ -176,7 +168,7 @@ function loadTools(store::ToolStore, dir::String)::OrderedDict{String, agentTool end end - return tools + return store.tools end """ @@ -187,7 +179,7 @@ Register a single agentTool into a specific ToolStore. - `tool::agentTool`: The tool to register # Returns -- `Vector{agentTool}`: Updated tool list for this store +- `OrderedDict{String, agentTool}`: Updated tool dict for this store # Examples ```julia @@ -196,21 +188,17 @@ julia> registerTool(store, my_tool) [toolRegistry:agent1] Registered tool: my_tool ``` """ -function registerTool(store::ToolStore, tool::agentTool)::Vector{agentTool} - push!(store.tools, tool) +function registerTool(store::ToolStore, tool::agentTool)::OrderedDict{String, agentTool} + store.tools[tool.name] = tool println("[$(store.name)] Registered tool: $(tool.name)") return store.tools end """ -Get all registered tools from a specific ToolStore as an -`OrderedDict{String, agentTool}` keyed by tool name. +Get the registered tools from a specific ToolStore. -The internal vector is for ordered iteration (used by `listTool`). -This function builds an `OrderedDict` so callers get: -- O(1) lookup by name -- Deterministic iteration order (registration order) -- Consistency with `agentState.tools` (also `OrderedDict{String, agentTool}`) +Returns the internal `OrderedDict` directly — O(1) lookup by name, +ordered iteration preserving registration order. # Arguments - `store::ToolStore`: The tool store to query @@ -228,7 +216,7 @@ OrderedDict{String, agentTool} with 3 entries: ``` """ function getTools(store::ToolStore)::OrderedDict{String, agentTool} - return OrderedDict{String, agentTool}(t.name => t for t in store.tools) + return store.tools end """ diff --git a/src/tools/README.md b/src/tools/README.md index 0f4cc43..c798e03 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -112,7 +112,7 @@ The `terminate` flag is checked at the batch level. See [Section 9](#9-tool-call ## 3. Tool Registration — Per-Agent Tool Stores -**Source:** `tools/registry.jl` +**Source:** `toolRegistry.jl` ### How `ToolStore` Works @@ -120,19 +120,20 @@ The registry uses **per-agent isolated storage** via the `ToolStore` struct. Eac ```julia struct ToolStore - tools::Vector{agentTool} # ordered tool list (for listTool iteration) - modules::Vector{Module} # keeps tool submodules alive to prevent GC - name::String # identifier for debugging/logs + tools::OrderedDict{String, agentTool} # keyed by name for O(1) lookup + ordered iteration + name::String # identifier for debugging/logs end ``` +`store.tools` is an `OrderedDict` — it provides O(1) lookup by tool name and preserves insertion order for iteration. `getTools(store)` returns this `OrderedDict` directly (not a copy), so mutations on the returned value affect the store. + ### How `loadTools(store, dir)` Works ```julia function loadTools(store::ToolStore, dir::String)::OrderedDict{String, agentTool} ``` -**Source:** `tools/registry.jl:115-180` +**Source:** `toolRegistry.jl:113-177` 1. **Scans** `dir` for `.jl` files (excluding files matching `registry` in name) 2. **Sorts** filenames alphabetically for deterministic registration order @@ -147,15 +148,14 @@ function loadTools(store::ToolStore, dir::String)::OrderedDict{String, agentTool ``` 4. **Evaluates** `getTool()` within the submodule scope using `Core.eval(mod, :(getTool()))` — this avoids world-age issues 5. **Validates** the return value is an `agentTool` instance -6. **Stores** the module reference in `store.modules` to prevent GC of closures -7. **Registers** the tool in `store.tools` and returns an `OrderedDict{String, agentTool}` +6. **Registers** the tool in `store.tools` and returns an `OrderedDict{String, agentTool}` ### Why Submodules? Each tool file is loaded into its own **namespaced submodule**. This means: - `validateRequiredArgs`, `prepareArguments`, `executeTool`, and helper functions defined in `getTime.jl` are scoped under `_tool_getTime` - No name collisions between tools — `getTime.validateRequiredArgs` is distinct from `getWeather.validateRequiredArgs` -- The module reference is kept alive in `store.modules` so closures (in `execute`, `validateRequiredArgs`, `prepareArguments`) don't get garbage collected +- The module reference is kept alive by the functions stored in `agentTool` (closures in `execute`, `validateRequiredArgs`, `prepareArguments`) so they don't get garbage collected ### Registration API @@ -178,10 +178,11 @@ all_tools = getTools(store1) # OrderedDict{String, agentTool} — O(1) lookup + clearTools(store1) # only clears store1 ``` -**Why `OrderedDict` for `getTools()`?** The internal `store.tools` is a `Vector{agentTool}` for ordered iteration (used by `listTool`). `getTools()` builds an `OrderedDict` from `store.tools` so callers get: +`getTools(store)` returns the internal `OrderedDict` directly, giving callers: - O(1) lookup by tool name - Deterministic iteration order (registration order) - Consistency with `agentState.tools` (also `OrderedDict{String, agentTool}`) +- No copy overhead — mutations on the returned value affect the store ### Per-Agent Isolation @@ -1118,7 +1119,7 @@ The framework supports tools that modify the tool system itself at runtime. ### `listTool` — Discover Available Tools -**Source:** `tools/registry.jl:54-82` +**Source:** `toolRegistry.jl:54-82` Each `ToolStore` gets its own `listTool` instance bound to that store via `listTool(store)`, so each agent sees only its own tools. Primarily useful for **collision detection** before creating a new tool via `writeTool`. @@ -1395,7 +1396,7 @@ module _tool_myTool end ``` -All functions in the file are scoped under `_tool_myTool`, preventing name collisions with other tools. The module reference is kept alive in `store.modules` to prevent garbage collection of closures. +All functions in the file are scoped under `_tool_myTool`, preventing name collisions with other tools. The module reference is kept alive by the function objects stored in `agentTool`, preventing garbage collection of closures. --- diff --git a/test/loadToolTest.jl b/test/toolTest.jl similarity index 97% rename from test/loadToolTest.jl rename to test/toolTest.jl index 1c16faf..8057cf1 100644 --- a/test/loadToolTest.jl +++ b/test/toolTest.jl @@ -142,13 +142,13 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools") @test reg["manualTool"].parallelToolExecute == true # ------------------------------------------------------------------ # - # 8. getTools returns deep copy (mutations don't affect registry) # + # 8. getTools returns direct reference (mutations affect registry) # # ------------------------------------------------------------------ # copy1 = getTools(store3) copy2 = getTools(store3) - @test copy1 !== copy2 + @test copy1 === copy2 # same reference, not a deep copy empty!(copy1) - @test !isempty(getTools(store3)) + @test isempty(getTools(store3)) # mutation propagates # ------------------------------------------------------------------ # # 9. Per-store isolation — two stores don't share tools #