update
This commit is contained in:
+1
-1
@@ -13,7 +13,7 @@ module YiemAgent
|
|||||||
include("utils.jl")
|
include("utils.jl")
|
||||||
using .utils
|
using .utils
|
||||||
|
|
||||||
include("tools/registry.jl")
|
include("toolRegistry.jl")
|
||||||
using .toolRegistry
|
using .toolRegistry
|
||||||
|
|
||||||
# include("llmfunction.jl")
|
# include("llmfunction.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.
|
`registerTool(store, tool)` only affects that agent's tool set.
|
||||||
|
|
||||||
# Fields
|
# Fields
|
||||||
- `tools::Vector{agentTool}` — ordered tool list (for `listTool` iteration)
|
- `tools::OrderedDict{String, agentTool}` — keyed by name for O(1) lookup + ordered iteration
|
||||||
- `modules::Vector{Module}` — keeps tool submodules alive to prevent GC of closures
|
|
||||||
- `name::String` — identifier for debugging/logs
|
- `name::String` — identifier for debugging/logs
|
||||||
"""
|
"""
|
||||||
struct ToolStore
|
struct ToolStore
|
||||||
tools::Vector{agentTool}
|
tools::OrderedDict{String, agentTool}
|
||||||
modules::Vector{Module}
|
|
||||||
name::String
|
name::String
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -38,7 +36,7 @@ agent = yiemAgent(tools=getTools(store), llmCall=..., _tool_store=store)
|
|||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
function ToolStore(; name::String="default")::ToolStore
|
function ToolStore(; name::String="default")::ToolStore
|
||||||
ToolStore(agentTool[], Module[], name)
|
ToolStore(OrderedDict{String, agentTool}(), name)
|
||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -66,7 +64,7 @@ function listTool(store::ToolStore)::agentTool
|
|||||||
if isempty(tools)
|
if isempty(tools)
|
||||||
result_text = "No tools registered."
|
result_text = "No tools registered."
|
||||||
else
|
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")
|
result_text = "Available tools:\n" * join(lines, "\n")
|
||||||
end
|
end
|
||||||
return agentToolResult(
|
return agentToolResult(
|
||||||
@@ -117,7 +115,6 @@ function loadTools(store::ToolStore, dir::String)::OrderedDict{String, agentTool
|
|||||||
throw(ArgumentError("Tool directory does not exist: $dir"))
|
throw(ArgumentError("Tool directory does not exist: $dir"))
|
||||||
end
|
end
|
||||||
|
|
||||||
tools = OrderedDict{String, agentTool}()
|
|
||||||
jl_files = filter(f -> endswith(f, ".jl") && !occursin(r"(?i)registry", f), readdir(dir))
|
jl_files = filter(f -> endswith(f, ".jl") && !occursin(r"(?i)registry", f), readdir(dir))
|
||||||
sort!(jl_files)
|
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))"
|
"getTool() in $(filepath) did not return an agentTool instance, got: $(typeof(tool))"
|
||||||
))
|
))
|
||||||
end
|
end
|
||||||
# Keep module reference alive — closures in the agentTool (execute,
|
store.tools[tool.name] = tool
|
||||||
# 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
|
|
||||||
println("[$(store.name)] Loaded tool: $(tool.name) ($(tool.label))")
|
println("[$(store.name)] Loaded tool: $(tool.name) ($(tool.label))")
|
||||||
catch e
|
catch e
|
||||||
if e isa UndefVarError || occursin("getTool", sprint(showerror, 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
|
||||||
end
|
end
|
||||||
|
|
||||||
return tools
|
return store.tools
|
||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -187,7 +179,7 @@ Register a single agentTool into a specific ToolStore.
|
|||||||
- `tool::agentTool`: The tool to register
|
- `tool::agentTool`: The tool to register
|
||||||
|
|
||||||
# Returns
|
# Returns
|
||||||
- `Vector{agentTool}`: Updated tool list for this store
|
- `OrderedDict{String, agentTool}`: Updated tool dict for this store
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
```julia
|
```julia
|
||||||
@@ -196,21 +188,17 @@ julia> registerTool(store, my_tool)
|
|||||||
[toolRegistry:agent1] Registered tool: my_tool
|
[toolRegistry:agent1] Registered tool: my_tool
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
function registerTool(store::ToolStore, tool::agentTool)::Vector{agentTool}
|
function registerTool(store::ToolStore, tool::agentTool)::OrderedDict{String, agentTool}
|
||||||
push!(store.tools, tool)
|
store.tools[tool.name] = tool
|
||||||
println("[$(store.name)] Registered tool: $(tool.name)")
|
println("[$(store.name)] Registered tool: $(tool.name)")
|
||||||
return store.tools
|
return store.tools
|
||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Get all registered tools from a specific ToolStore as an
|
Get the registered tools from a specific ToolStore.
|
||||||
`OrderedDict{String, agentTool}` keyed by tool name.
|
|
||||||
|
|
||||||
The internal vector is for ordered iteration (used by `listTool`).
|
Returns the internal `OrderedDict` directly — O(1) lookup by name,
|
||||||
This function builds an `OrderedDict` so callers get:
|
ordered iteration preserving registration order.
|
||||||
- O(1) lookup by name
|
|
||||||
- Deterministic iteration order (registration order)
|
|
||||||
- Consistency with `agentState.tools` (also `OrderedDict{String, agentTool}`)
|
|
||||||
|
|
||||||
# Arguments
|
# Arguments
|
||||||
- `store::ToolStore`: The tool store to query
|
- `store::ToolStore`: The tool store to query
|
||||||
@@ -228,7 +216,7 @@ OrderedDict{String, agentTool} with 3 entries:
|
|||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
function getTools(store::ToolStore)::OrderedDict{String, agentTool}
|
function getTools(store::ToolStore)::OrderedDict{String, agentTool}
|
||||||
return OrderedDict{String, agentTool}(t.name => t for t in store.tools)
|
return store.tools
|
||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
+12
-11
@@ -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
|
## 3. Tool Registration — Per-Agent Tool Stores
|
||||||
|
|
||||||
**Source:** `tools/registry.jl`
|
**Source:** `toolRegistry.jl`
|
||||||
|
|
||||||
### How `ToolStore` Works
|
### How `ToolStore` Works
|
||||||
|
|
||||||
@@ -120,19 +120,20 @@ The registry uses **per-agent isolated storage** via the `ToolStore` struct. Eac
|
|||||||
|
|
||||||
```julia
|
```julia
|
||||||
struct ToolStore
|
struct ToolStore
|
||||||
tools::Vector{agentTool} # ordered tool list (for listTool iteration)
|
tools::OrderedDict{String, agentTool} # keyed by name for O(1) lookup + ordered iteration
|
||||||
modules::Vector{Module} # keeps tool submodules alive to prevent GC
|
name::String # identifier for debugging/logs
|
||||||
name::String # identifier for debugging/logs
|
|
||||||
end
|
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
|
### How `loadTools(store, dir)` Works
|
||||||
|
|
||||||
```julia
|
```julia
|
||||||
function loadTools(store::ToolStore, dir::String)::OrderedDict{String, agentTool}
|
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)
|
1. **Scans** `dir` for `.jl` files (excluding files matching `registry` in name)
|
||||||
2. **Sorts** filenames alphabetically for deterministic registration order
|
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
|
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
|
5. **Validates** the return value is an `agentTool` instance
|
||||||
6. **Stores** the module reference in `store.modules` to prevent GC of closures
|
6. **Registers** the tool in `store.tools` and returns an `OrderedDict{String, agentTool}`
|
||||||
7. **Registers** the tool in `store.tools` and returns an `OrderedDict{String, agentTool}`
|
|
||||||
|
|
||||||
### Why Submodules?
|
### Why Submodules?
|
||||||
|
|
||||||
Each tool file is loaded into its own **namespaced submodule**. This means:
|
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`
|
- `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`
|
- 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
|
### Registration API
|
||||||
|
|
||||||
@@ -178,10 +178,11 @@ all_tools = getTools(store1) # OrderedDict{String, agentTool} — O(1) lookup +
|
|||||||
clearTools(store1) # only clears store1
|
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
|
- O(1) lookup by tool name
|
||||||
- Deterministic iteration order (registration order)
|
- Deterministic iteration order (registration order)
|
||||||
- Consistency with `agentState.tools` (also `OrderedDict{String, agentTool}`)
|
- Consistency with `agentState.tools` (also `OrderedDict{String, agentTool}`)
|
||||||
|
- No copy overhead — mutations on the returned value affect the store
|
||||||
|
|
||||||
### Per-Agent Isolation
|
### Per-Agent Isolation
|
||||||
|
|
||||||
@@ -1118,7 +1119,7 @@ The framework supports tools that modify the tool system itself at runtime.
|
|||||||
|
|
||||||
### `listTool` — Discover Available Tools
|
### `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`.
|
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
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -142,13 +142,13 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
|
|||||||
@test reg["manualTool"].parallelToolExecute == true
|
@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)
|
copy1 = getTools(store3)
|
||||||
copy2 = getTools(store3)
|
copy2 = getTools(store3)
|
||||||
@test copy1 !== copy2
|
@test copy1 === copy2 # same reference, not a deep copy
|
||||||
empty!(copy1)
|
empty!(copy1)
|
||||||
@test !isempty(getTools(store3))
|
@test isempty(getTools(store3)) # mutation propagates
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# 9. Per-store isolation — two stores don't share tools #
|
# 9. Per-store isolation — two stores don't share tools #
|
||||||
Reference in New Issue
Block a user