344 lines
12 KiB
Julia
344 lines
12 KiB
Julia
using Test
|
|
using YiemAgent
|
|
using YiemAgent.agentCore
|
|
using YiemAgent.type
|
|
using JSON
|
|
|
|
# Import the function from the private module scope
|
|
import YiemAgent.agentCore: _extractToolCalls
|
|
|
|
@testset "_extractToolCalls" begin
|
|
|
|
# -------------------------------------------------------------- #
|
|
# Format 1: response["message"]["tool_calls"] (LMStudio.jl style) #
|
|
# -------------------------------------------------------------- #
|
|
|
|
@testset "single tool call via message format" begin
|
|
response = Dict{String,Any}(
|
|
"finish_reason" => "tool_calls",
|
|
"index" => 0,
|
|
"message" => Dict{String,Any}(
|
|
"role" => "assistant",
|
|
"content" => "",
|
|
"reasoning_content" => "Let me check the weather.",
|
|
"tool_calls" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"function" => Dict{String,Any}(
|
|
"name" => "getWeather",
|
|
"arguments" => "{\"city\":\"Bangkok, Thailand\"}",
|
|
),
|
|
"id" => "tc_001",
|
|
)
|
|
],
|
|
),
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test tc_list[1].name == "getWeather"
|
|
@test tc_list[1].id == "tc_001"
|
|
@test tc_list[1].arguments["city"] == "Bangkok, Thailand"
|
|
end
|
|
|
|
@testset "multiple tool calls via message format" begin
|
|
response = Dict{String,Any}(
|
|
"message" => Dict{String,Any}(
|
|
"role" => "assistant",
|
|
"content" => "",
|
|
"tool_calls" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"function" => Dict{String,Any}(
|
|
"name" => "getWeather",
|
|
"arguments" => "{\"city\":\"Tokyo, Japan\"}",
|
|
),
|
|
"id" => "tc_001",
|
|
),
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"function" => Dict{String,Any}(
|
|
"name" => "getTime",
|
|
"arguments" => "{\"timezone\":\"Asia/Tokyo\"}",
|
|
),
|
|
"id" => "tc_002",
|
|
),
|
|
],
|
|
),
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 2
|
|
@test tc_list[1].name == "getWeather"
|
|
@test tc_list[1].arguments["city"] == "Tokyo, Japan"
|
|
@test tc_list[2].name == "getTime"
|
|
@test tc_list[2].arguments["timezone"] == "Asia/Tokyo"
|
|
end
|
|
|
|
@testset "tool call with empty arguments string" begin
|
|
response = Dict{String,Any}(
|
|
"message" => Dict{String,Any}(
|
|
"role" => "assistant",
|
|
"tool_calls" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"function" => Dict{String,Any}(
|
|
"name" => "listTools",
|
|
"arguments" => "{}",
|
|
),
|
|
"id" => "tc_empty",
|
|
),
|
|
],
|
|
),
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test tc_list[1].name == "listTools"
|
|
@test tc_list[1].arguments == Dict{String,Any}()
|
|
end
|
|
|
|
@testset "tool call with missing id falls back to uuid" begin
|
|
response = Dict{String,Any}(
|
|
"message" => Dict{String,Any}(
|
|
"role" => "assistant",
|
|
"tool_calls" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"function" => Dict{String,Any}(
|
|
"name" => "getTime",
|
|
"arguments" => "{\"city\":\"NYC\"}",
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test !isempty(tc_list[1].id)
|
|
@test tc_list[1].name == "getTime"
|
|
end
|
|
|
|
@testset "tool call with non-string arguments (pre-parsed)" begin
|
|
response = Dict{String,Any}(
|
|
"message" => Dict{String,Any}(
|
|
"role" => "assistant",
|
|
"tool_calls" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"function" => Dict{String,Any}(
|
|
"name" => "getWeather",
|
|
"arguments" => Dict{String,Any}("city" => "London", "units" => "fahrenheit"),
|
|
),
|
|
"id" => "tc_parsed",
|
|
),
|
|
],
|
|
),
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test tc_list[1].arguments["city"] == "London"
|
|
@test tc_list[1].arguments["units"] == "fahrenheit"
|
|
end
|
|
|
|
# ----------------------------------------------------------- #
|
|
# Format 2: response.content blocks (OpenAI API style) #
|
|
# ----------------------------------------------------------- #
|
|
|
|
@testset "content blocks with tool_calls" begin
|
|
response = Dict{String,Any}(
|
|
"content" => Any[
|
|
Dict{String,Any}("type" => "text", "text" => "Let me check."),
|
|
Dict{String,Any}(
|
|
"type" => "tool_calls",
|
|
"tool_calls" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"function" => Dict{String,Any}(
|
|
"name" => "getWeather",
|
|
"arguments" => "{\"city\":\"Paris\"}",
|
|
),
|
|
"id" => "tc_block_1",
|
|
),
|
|
],
|
|
),
|
|
],
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test tc_list[1].name == "getWeather"
|
|
@test tc_list[1].arguments["city"] == "Paris"
|
|
end
|
|
|
|
@testset "content blocks with tool_call (single-call format)" begin
|
|
response = Dict{String,Any}(
|
|
"content" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "tool_call",
|
|
"id" => "tc_single",
|
|
"name" => "getTime",
|
|
"arguments" => Dict{String,Any}("timezone" => "Europe/London"),
|
|
),
|
|
],
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test tc_list[1].name == "getTime"
|
|
@test tc_list[1].id == "tc_single"
|
|
@test tc_list[1].arguments["timezone"] == "Europe/London"
|
|
end
|
|
|
|
# ----------------------------------------------------------- #
|
|
# Format 2 via struct-like object (no .content field) #
|
|
# ----------------------------------------------------------- #
|
|
|
|
@testset "no tool calls found" begin
|
|
response = Dict{String,Any}(
|
|
"content" => Any[
|
|
Dict{String,Any}("type" => "text", "text" => "Hello world."),
|
|
],
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == false
|
|
@test length(tc_list) == 0
|
|
end
|
|
|
|
@testset "empty message" begin
|
|
response = Dict{String,Any}()
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == false
|
|
@test length(tc_list) == 0
|
|
end
|
|
|
|
@testset "message with empty tool_calls array" begin
|
|
response = Dict{String,Any}(
|
|
"message" => Dict{String,Any}(
|
|
"role" => "assistant",
|
|
"tool_calls" => Any[],
|
|
),
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == false
|
|
@test length(tc_list) == 0
|
|
end
|
|
|
|
@testset "Format 1 takes priority over Format 2" begin
|
|
response = Dict{String,Any}(
|
|
"message" => Dict{String,Any}(
|
|
"role" => "assistant",
|
|
"tool_calls" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"function" => Dict{String,Any}(
|
|
"name" => "getWeather",
|
|
"arguments" => "{\"city\":\"Format1\"}",
|
|
),
|
|
"id" => "tc_fmt1",
|
|
),
|
|
],
|
|
),
|
|
"content" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "tool_call",
|
|
"id" => "tc_fmt2",
|
|
"name" => "getTime",
|
|
"arguments" => Dict{String,Any}("city" => "Format2"),
|
|
),
|
|
],
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test tc_list[1].name == "getWeather"
|
|
end
|
|
|
|
# ----------------------------------------------------------- #
|
|
# edge cases #
|
|
# ----------------------------------------------------------- #
|
|
|
|
@testset "tool call with null arguments" begin
|
|
response = Dict{String,Any}(
|
|
"message" => Dict{String,Any}(
|
|
"role" => "assistant",
|
|
"tool_calls" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"function" => Dict{String,Any}(
|
|
"name" => "getTime",
|
|
"arguments" => nothing,
|
|
),
|
|
"id" => "tc_null",
|
|
),
|
|
],
|
|
),
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test tc_list[1].name == "getTime"
|
|
end
|
|
|
|
@testset "tool call with missing function key" begin
|
|
response = Dict{String,Any}(
|
|
"message" => Dict{String,Any}(
|
|
"role" => "assistant",
|
|
"tool_calls" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"id" => "tc_nofunc",
|
|
),
|
|
],
|
|
),
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test tc_list[1].name == ""
|
|
end
|
|
|
|
@testset "tool call with missing name in function block" begin
|
|
response = Dict{String,Any}(
|
|
"message" => Dict{String,Any}(
|
|
"role" => "assistant",
|
|
"tool_calls" => Any[
|
|
Dict{String,Any}(
|
|
"type" => "function",
|
|
"function" => Dict{String,Any}("arguments" => "{}"),
|
|
"id" => "tc_noname",
|
|
),
|
|
],
|
|
),
|
|
)
|
|
has_toolcalls, tc_list = _extractToolCalls(response)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test tc_list[1].name == ""
|
|
end
|
|
|
|
@testset "message format with JSON.Object (JSON.parse result)" begin
|
|
json_str = JSON.json(Dict(
|
|
"message" => Dict(
|
|
"role" => "assistant",
|
|
"tool_calls" => [
|
|
Dict(
|
|
"type" => "function",
|
|
"function" => Dict("name" => "getWeather", "arguments" => "{\"city\":\"Test\"}"),
|
|
"id" => "tc_jsonobj",
|
|
),
|
|
],
|
|
),
|
|
))
|
|
parsed = JSON.parse(json_str)
|
|
has_toolcalls, tc_list = _extractToolCalls(parsed)
|
|
@test has_toolcalls == true
|
|
@test length(tc_list) == 1
|
|
@test tc_list[1].name == "getWeather"
|
|
@test tc_list[1].arguments["city"] == "Test"
|
|
end
|
|
|
|
end
|