From 189bc2efcfe98165d8abed1c22093bae536539a2 Mon Sep 17 00:00:00 2001 From: narawat Date: Mon, 10 Aug 2026 09:43:35 +0700 Subject: [PATCH] update --- src/agentCore.jl | 12 ++++-------- src/tools/registry.jl | 8 ++++---- src/type.jl | 30 +++++++++++++++--------------- test/loadToolTest.jl | 16 ++++++++-------- 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/agentCore.jl b/src/agentCore.jl index 852d145..b854d18 100644 --- a/src/agentCore.jl +++ b/src/agentCore.jl @@ -516,7 +516,7 @@ function prepareToolCall( signal::Union{Nothing, abortSignal}, )::Union{preparedToolCall,immediateOutcome} - tool = find(t -> t.name == toolCall.name, context.tools) + tool = get(context.tools, toolCall.name, nothing) if tool === nothing return immediateOutcome(createErrorToolResult("Tool $toolCall.name not found"), true) end @@ -996,13 +996,9 @@ function executeToolCalls( hasSequential = false for tc in toolCalls - for t in context.tools - if t.name == tc.name && !t.parallelToolExecute - hasSequential = true - break - end - end - if hasSequential + t = get(context.tools, tc.name, nothing) + if t !== nothing && !t.parallelToolExecute + hasSequential = true break end end diff --git a/src/tools/registry.jl b/src/tools/registry.jl index 4cc0a01..3954e8b 100644 --- a/src/tools/registry.jl +++ b/src/tools/registry.jl @@ -3,7 +3,7 @@ module toolRegistry export loadTools, registerTool, getTools, clearTools using Dates -using JSON +using JSON, DataStructures using ..type # Global registry — populated at runtime by loadTools() or registerTool() @@ -92,12 +92,12 @@ end # Errors - Throws `ArgumentError` if a tool file does not define a `getTool` function """ -function loadTools(dir::String)::Vector{agentTool} +function loadTools(dir::String)::OrderedDict{String, agentTool} if !isdir(dir) throw(ArgumentError("Tool directory does not exist: $dir")) end - tools = agentTool[] + tools = OrderedDict{String, agentTool}() jl_files = filter(f -> endswith(f, ".jl") && !occursin(r"(?i)registry", f), readdir(dir)) sort!(jl_files) @@ -143,7 +143,7 @@ function loadTools(dir::String)::Vector{agentTool} # functions. Without this, GC could collect the module. push!(_tool_modules, mod) push!(_registry, tool) - push!(tools, tool) + tools[tool.name] = tool println("[toolRegistry] Loaded tool: $(tool.name) — $(tool.label)") catch e if e isa UndefVarError || occursin("getTool", sprint(showerror, e)) diff --git a/src/type.jl b/src/type.jl index 7078168..ee373cf 100644 --- a/src/type.jl +++ b/src/type.jl @@ -291,7 +291,7 @@ Snapshot of the agent's conversation context. # Arguments - `systemPrompt::String`: System prompt for the agent - `messages::Vector{agentMessage}`: Conversation messages -- `tools::Union{Vector{agentTool}, Nothing}`: Available tools +- `tools::Union{Dict{String, agentTool}, Nothing}`: Available tools keyed by name for O(1) lookup # Returns - A new `agentContext` instance @@ -299,7 +299,7 @@ Snapshot of the agent's conversation context. struct agentContext # Snapshot of the agent's conversation context systemPrompt::String # System prompt for the agent messages::Vector{agentMessage} # Conversation messages - tools::Union{Vector{agentTool}, Nothing} # Available tools + tools::Union{Dict{String, agentTool}, Nothing} # Available tools keyed by name end @@ -308,9 +308,9 @@ end # ------------------------------------------------------------------------------------------------ # mutable struct agentState # Mutable runtime state of an agent - systemPrompt::String # System prompt text + systemPrompt::String # System prompt for the agent model::llmModel # LLM model to use - tools::Vector{agentTool} # Available tools + tools::OrderedDict{String, agentTool} # Available tools keyed by name, insertion-ordered # messages history includes userMessage, assistantMessage, toolResultMessage. NO system prompt messages::Vector{agentMessage} @@ -329,7 +329,7 @@ new state from external references. # Arguments - `systemPrompt::String`: System prompt text - `model::llmModel`: LLM model to use (defaults to an unknown model) -- `tools::Vector{agentTool}`: Available tools (deep copied) +- `tools::OrderedDict{String, agentTool}`: Available tools keyed by name (deep copied) - `messages::Vector{agentMessage}`: Conversation messages (deep copied) # Returns @@ -338,13 +338,13 @@ new state from external references. # Examples ```julia julia> state = agentState(systemPrompt="You are a helpful assistant") -agentState("You are a helpful assistant", ..., agentTool[], agentMessage[], String[], nothing) -``` +agentState("You are a helpful assistant", OrderedDict{String, agentTool}(), agentMessage[], String[], nothing) """ function agentState( systemPrompt::String="", - model::llmModel=llmModel{String}("", "", "unknown", "unknown", "", false, String[], modelCost(0.0, 0.0, 0.0, 0.0), 0, 0), - tools::Vector{agentTool}=agentTool[], + model::llmModel=llmModel{String}("", "", "unknown", "unknown", "", false, String[], + modelCost(0.0, 0.0, 0.0, 0.0), 0, 0), + tools::OrderedDict{String, agentTool}=OrderedDict{String, agentTool}(), messages::Vector{agentMessage}=agentMessage[], ) agentState( @@ -395,13 +395,13 @@ end Configuration for the agent tool execution loop. # Arguments -- `tools::Vector{agentTool}`: Available tools +- `tools::OrderedDict{String, agentTool}`: Available tools keyed by name - `beforeToolCall::Union{Function, Nothing}`: Callback before tool execution - `afterToolCall::Union{Function, Nothing}`: Callback after tool execution - `toolExecution::String`: Execution mode — "sequential" or "parallel" """ struct agentLoopConfig - tools::Vector{agentTool} + tools::OrderedDict{String, agentTool} beforeToolCall::Union{Function, Nothing} afterToolCall::Union{Function, Nothing} toolExecution::String @@ -580,7 +580,7 @@ on `inputChannel` and `followUpChannel` channels concurrently. # Keyword Arguments - `systemPrompt::String`: System prompt for the agent - `model`: LLM model to use -- `tools::Vector{agentTool}`: Available tools (default: empty) +- `tools::OrderedDict{String, agentTool}`: Available tools keyed by name (default: empty) - `messages::Vector{agentMessage}`: Initial conversation messages (default: empty) - `formatMsgForLLM::Function`: Convert agent messages to LLM message format (default: `defaultformatMsgForLLM`) - `llmCall::Function`: Function to invoke the LLM (required) @@ -599,14 +599,14 @@ on `inputChannel` and `followUpChannel` channels concurrently. # Examples ```julia -julia> agent = yiemAgent(systemPrompt="You are a helpful assistant", model=my_model) +julia> tools = loadTools("src/tools") +julia> agent = yiemAgent(systemPrompt="You are a helpful assistant", model=my_model, tools=tools, llmCall=...) yiemAgent(agentState(...), Channel(...), Channel(...), Channel(...), ..., ...) -``` """ function yiemAgent( ; systemPrompt::String="You are helpful assistant.", model=nothing, - tools::Vector{agentTool}=agentTool[], + tools::OrderedDict{String, agentTool}=OrderedDict{String, agentTool}(), messages::Vector{agentMessage}=agentMessage[], prepareContext::Union{Function, Nothing}=nothing, formatMsgForLLM::Function=defaultformatMsgForLLM, diff --git a/test/loadToolTest.jl b/test/loadToolTest.jl index a6a974e..ad6613a 100644 --- a/test/loadToolTest.jl +++ b/test/loadToolTest.jl @@ -29,7 +29,7 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools") @test !isempty(loaded) @test length(loaded) == 3 - names = [t.name for t in loaded] + names = [k for k in keys(loaded)] @test "getTime" in names @test "getWeather" in names @test "writeTool" in names @@ -39,15 +39,15 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools") # (getTime.jl < getWeather.jl < writeTool.jl) # # because 'T' < 'W' in ASCII # # ------------------------------------------------------------------ # - @test loaded[1].name == "getTime" - @test loaded[2].name == "getWeather" - @test loaded[3].name == "writeTool" + @test collect(keys(loaded))[1] == "getTime" + @test collect(keys(loaded))[2] == "getWeather" + @test collect(keys(loaded))[3] == "writeTool" # ------------------------------------------------------------------ # # 5. Verify loaded tool fields are correct # # ------------------------------------------------------------------ # # getTime - time_tool = loaded[1] + time_tool = loaded["getTime"] @test time_tool.name == "getTime" @test time_tool.label == "Time Lookup" @test time_tool.validateRequiredArgs !== nothing @@ -55,7 +55,7 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools") @test time_tool.inputSchema["required"] == Any[] # getWeather - weather = loaded[2] + weather = loaded["getWeather"] @test weather.name == "getWeather" @test weather.label == "Weather Lookup" @test weather.execute !== nothing @@ -63,7 +63,7 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools") @test weather.inputSchema["required"] == ["city"] # writeTool - wt = loaded[3] + wt = loaded["writeTool"] @test wt.name == "writeTool" @test wt.label == "Create Tool" @test wt.execute !== nothing @@ -123,7 +123,7 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools") reg = getTools() @test any(t -> t.name == "manualTool", reg) @test count(t -> t.name == "manualTool", reg) == 1 - @test reg[1].parallelToolExecute == true + @test reg[1].parallelToolExecute == true # ------------------------------------------------------------------ # # 8. getTools returns deep copy (mutations don't affect registry) #