This commit is contained in:
2026-08-07 23:13:13 +07:00
parent 8e356f06bc
commit e60cbc67c4
5 changed files with 347 additions and 7 deletions
+3
View File
@@ -13,6 +13,9 @@ module YiemAgent
include("utils.jl")
using .utils
include("tools/registry.jl")
using .toolRegistry
# include("llmfunction.jl")
# using .llmfunction
+59
View File
@@ -0,0 +1,59 @@
"""
Execute the get_weather tool.
# Arguments
- `toolCallId::String`: Unique identifier for this tool call
- `args::Dict{String,Any}`: Parsed arguments from the LLM
- `signal::Union{Nothing,abortSignal}`: Optional abort signal
- `onPartialResult::Function`: Callback for streaming partial results
# Returns
- `agentToolResult`: Result content with weather data
"""
function execute_tool(toolCallId::String, args::Dict{String,Any}, signal::Union{Nothing,abortSignal}, onPartialResult::Function) :: agentToolResult
city = get(args, "city", "")
units = get(args, "units", "celsius")
# Validate required arguments
if isempty(city)
return agentToolResult(
[textContent("Error: 'city' argument is required.")],
Dict{Any,Any}(), nothing, false
)
end
# Simulate weather fetch — replace with actual API call
# You can call onPartialResult() here for streaming progress updates:
# onPartialResult(Dict("status" => "Fetching weather data..."))
# onPartialResult(Dict("status" => "Processing..."))
temp = units == "fahrenheit" ? "72" : "22"
unit_symbol = units == "celsius" ? "°C" : "°F"
return agentToolResult(
[textContent("Weather in $(city): Sunny, $(temp)$(unit_symbol)")],
Dict{Any,Any}(), nothing, false
)
end
"""
Define and return the get_weather agentTool.
"""
function get_tool() :: agentTool
return agentTool(
name = "get_weather",
label = "Weather Lookup",
description = "Fetch current weather and forecast for a given city.",
inputSchema = Dict{String,Any}(
"type" => "object",
"properties" => Dict(
"city" => Dict("type" => "string", "description" => "City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'"),
"units" => Dict("type" => "string", "enum" => ["celsius", "fahrenheit"], "default" => "celsius", "description" => "Temperature scale")
),
"required" => ["city"]
),
execute = execute_tool, # reference the function defined above
prepareArguments = nothing,
parallelToolExecute = false
)
end
+142
View File
@@ -0,0 +1,142 @@
module toolRegistry
export load_tools, register_tool, get_tools, list_tools, clear_tools
using ..type
# Global registry — populated at runtime by load_tools() or register_tool()
const _registry = Vector{agentTool}()
"""
Load all tool modules from a directory.
Scans `dir` for `.jl` files. Each file must define a function named
`get_tool() :: agentTool`. Files are sorted alphabetically so tool
registration order is deterministic.
# Tool file format
Each `.jl` file defines one function `get_tool()` that returns an `agentTool`:
```julia
# src/tools/get_weather.jl
function get_tool() :: agentTool
return agentTool(
name = "get_weather",
label = "Weather Lookup",
description = "Fetch current weather and forecast for a given city.",
inputSchema = Dict{String,Any}(
"type" => "object",
"properties" => Dict(
"city" => Dict("type" => "string", "description" => "City and country"),
"units" => Dict("type" => "string", "enum" => ["celsius", "fahrenheit"], "default" => "celsius")
),
"required" => ["city"]
),
execute = (toolCallId, args, signal, onPartialResult) -> begin
city = args["city"]
return agentToolResult(
[textContent("Sunny, 22C in $(city)")],
Dict{Any,Any}(), nothing, false
)
end,
prepareArguments = nothing,
parallelToolExecute = false
)
end
```
# Arguments
- `dir::String`: Directory path to scan for `.jl` tool files
# Returns
- `Vector{agentTool}`: All loaded tools
# Errors
- Throws `ArgumentError` if a tool file does not define a `get_tool` function
"""
function load_tools(dir::String)::Vector{agentTool}
if !isdir(dir)
throw(ArgumentError("Tool directory does not exist: $dir"))
end
tools = agentTool[]
jl_files = filter(f -> endswith(f, ".jl"), readdir(dir))
sort!(jl_files)
for filename in jl_files
filepath = joinpath(dir, filename)
println("[toolRegistry] Loading tool from: $filepath")
# Include the file in the current module scope so all types resolve
# (agentTool, textContent, agentToolResult, etc. are all available)
include(filepath)
# Validate that get_tool was defined (include() places it in current module scope)
if !isdefined(:get_tool)
throw(ArgumentError(
"Tool file $(filepath) does not define a `get_tool()` function. " *
"Each tool file must define: function get_tool() :: agentTool ... end"
))
end
# Call get_tool() — it runs in current scope where types are visible
tool = get_tool()
if !(tool isa agentTool)
throw(ArgumentError(
"get_tool() in $(filepath) did not return an agentTool instance, got: $(typeof(tool))"
))
end
push!(_registry, tool)
push!(tools, tool)
println("[toolRegistry] Loaded tool: $(tool.name)$(tool.label)")
end
return tools
end
"""
Register a single agentTool into the global registry.
# Arguments
- `tool::agentTool`: The tool to register
# Returns
- `Vector{agentTool}`: Updated registry
"""
function register_tool(tool::agentTool)::Vector{agentTool}
push!(_registry, tool)
println("[toolRegistry] Registered tool: $(tool.name)")
return _registry
end
"""
Get all registered tools.
# Returns
- `Vector{agentTool}`: Copy of the registry
"""
function get_tools()::Vector{agentTool}
return deepcopy(_registry)
end
"""
List all registered tool names and labels.
# Returns
- `Vector{Tuple{String,String}}`: Pairs of (name, label)
"""
function list_tools()::Vector{Tuple{String,String}}
return [(t.name, t.label) for t in _registry]
end
"""
Clear all registered tools from the global registry.
"""
function clear_tools()::Nothing
empty!(_registry)
println("[toolRegistry] Registry cleared")
return nothing
end
end # module
+51 -7
View File
@@ -197,23 +197,67 @@ end
"""
A tool available to the agent.
Maps MCP server tool definitions to an executable Julia tool.
# Arguments
- `name::String`: Tool identifier
- `label::String`: Human-readable tool name
- `description::String`: What the tool does
- `parameters::TParameters`: Tool parameters schema (JSON schema)
- `execute::Function`: Tool execution function
- `name::String`: Tool identifier (from MCP `name`)
- `label::String`: Human-readable tool name (from MCP `title`)
- `description::String`: What the tool does (from MCP `description`)
- `inputSchema::Any`: Tool parameters schema (from MCP `inputSchema`, JSON Schema format)
- `execute::Function`: Tool execution function, signature:
`execute(toolCallId::String, args::Dict, signal::Union{Nothing,AbortSignal}, onPartialResult::Function)`
- `prepareArguments::Union{Function, Nothing}`: Optional argument preparation callback
- `parallelToolExecute::Bool`: Override: run tool calls sequentially or in parallel
# Returns
- A new `agentTool` instance
# MCP Tool Example
```
{
"name": "get_weather",
"title": "Weather Lookup",
"description": "Fetch current weather and forecast for a given city.",
"inputSchema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City and state/country" },
"units": { "type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius" }
},
"required": ["city"]
}
}
```
# Example
```julia
tool = agentTool(
name="get_weather",
label="Weather Lookup",
description="Fetch current weather and forecast for a given city.",
inputSchema=Dict(
"type" => "object",
"properties" => Dict(
"city" => Dict("type" => "string", "description" => "City name"),
"units" => Dict("type" => "string", "enum" => ["celsius", "fahrenheit"])
),
"required" => ["city"]
),
execute=(toolCallId, args, signal, onPartialResult) -> begin
city = args["city"]
return agentToolResult(
[textContent("Sunny, 22C in $(city)")],
Dict{Any,Any}(), nothing, false
)
end
)
```
"""
struct agentTool{TParameters, TDetails} # A tool available to the agent
struct agentTool # A tool available to the agent
name::String # Tool identifier
label::String # Human-readable tool name
description::String # What the tool does
parameters::TParameters # Tool parameters schema (JSON schema)
inputSchema::Any # Tool parameters schema (JSON schema, MCP inputSchema format)
execute::Function # Tool execution function
prepareArguments::Union{Function, Nothing} # Optional argument preparation callback
parallelToolExecute::Bool # Override: run tool calls sequentially or in parallel