update
This commit is contained in:
+104
-40
@@ -1,6 +1,6 @@
|
|||||||
module agentCore
|
module agentCore
|
||||||
|
|
||||||
export yiemAgent, _agentLoop, OpenAiToUserMessage
|
export yiemAgent, _agentLoop, OpenAiToUserMessage, _extractToolCalls
|
||||||
|
|
||||||
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||||
DataFrames, Base.Threads, NATS
|
DataFrames, Base.Threads, NATS
|
||||||
@@ -235,7 +235,7 @@ function _agentLoop(agent::yiemAgent)
|
|||||||
break
|
break
|
||||||
else
|
else
|
||||||
agent.agentEventSink("_agentLoop push 1")
|
agent.agentEventSink("_agentLoop push 1")
|
||||||
put!(processMessageInputCh, msg) #WORKING
|
put!(processMessageInputCh, msg)
|
||||||
agent.agentEventSink("_agentLoop push 2")
|
agent.agentEventSink("_agentLoop push 2")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -357,9 +357,6 @@ function _processMessage(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
while true
|
while true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Drain inputChannel and convert OpenAI-format messages to userMessage type
|
# Drain inputChannel and convert OpenAI-format messages to userMessage type
|
||||||
while isready(inputChannel)
|
while isready(inputChannel)
|
||||||
agentEventSink("_processMessage 2")
|
agentEventSink("_processMessage 2")
|
||||||
@@ -385,47 +382,19 @@ function _processMessage(
|
|||||||
# Call formatMessagesForLLM() to format for LLM
|
# Call formatMessagesForLLM() to format for LLM
|
||||||
formattedMessages = formatMessagesForLLM(preparedContext, agentEventSink)
|
formattedMessages = formatMessagesForLLM(preparedContext, agentEventSink)
|
||||||
|
|
||||||
agentEventSink("_processMessage 10")
|
agentEventSink("_processMessage 10")
|
||||||
# Call llmCall() (blocking — the task waits here)
|
|
||||||
response = llmCall(formattedMessages)
|
|
||||||
|
|
||||||
""" response example
|
""" response example
|
||||||
response = JSON.Object{String, Any}("finish_reason" => "tool_calls", "index" => 0, "message" => JSON.Object{String, Any}("role" => "assistant", "content" => "", "reasoning_content" => "Here's a thinking process:\n\n1. **Identify User Request**: The user is asking for the weather in Bangkok.\n2. **Locate Relevant Tool**: I have a `getWeather` function available.\n3. **Check Function Parameters**:\n - `city` (required): City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'\n - `units` (optional, default \"celsius\"): Temperature scale (\"celsius\" or \"fahrenheit\")\n4. **Prepare Parameters**:\n - `city`: \"Bangkok, Thailand\" (adding country for clarity, though just \"Bangkok\" might work, following the example format is safer)\n - `units`: Not specified, so I'll use the default (\"celsius\")\n5. **Execute Tool Call**: Call `getWeather` with `city: \"Bangkok, Thailand\"`\n6. **Anticipate Response**: The function will return current weather and forecast data for Bangkok. I'll then format it nicely for the user.\n - *Self-Correction/Verification during thought*: The prompt says \"city: City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'\". I'll use \"Bangkok, Thailand\". The `units` parameter is optional, so I'll omit it to use the default.\n - Proceed with tool call.✅\n", "tool_calls" => Any[JSON.Object{String, Any}("type" => "function", "function" => JSON.Object{String, Any}("name" => "getWeather", "arguments" => "{\"city\":\"Bangkok, Thailand\"}"), "id" => "6fOilR5QPcdppbAAHluhkRyUDu3oWMAL")]))
|
response = JSON.Object{String, Any}("finish_reason" => "tool_calls", "index" => 0, "message" => JSON.Object{String, Any}("role" => "assistant", "content" => "", "reasoning_content" => "Here's a thinking process:\n\n1. **Identify User Request**: The user is asking for the weather in Bangkok.\n2. **Locate Relevant Tool**: I have a `getWeather` function available.\n3. **Check Function Parameters**:\n - `city` (required): City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'\n - `units` (optional, default \"celsius\"): Temperature scale (\"celsius\" or \"fahrenheit\")\n4. **Prepare Parameters**:\n - `city`: \"Bangkok, Thailand\" (adding country for clarity, though just \"Bangkok\" might work, following the example format is safer)\n - `units`: Not specified, so I'll use the default (\"celsius\")\n5. **Execute Tool Call**: Call `getWeather` with `city: \"Bangkok, Thailand\"`\n6. **Anticipate Response**: The function will return current weather and forecast data for Bangkok. I'll then format it nicely for the user.\n - *Self-Correction/Verification during thought*: The prompt says \"city: City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'\". I'll use \"Bangkok, Thailand\". The `units` parameter is optional, so I'll omit it to use the default.\n - Proceed with tool call.✅\n", "tool_calls" => Any[JSON.Object{String, Any}("type" => "function", "function" => JSON.Object{String, Any}("name" => "getWeather", "arguments" => "{\"city\":\"Bangkok, Thailand\"}"), "id" => "6fOilR5QPcdppbAAHluhkRyUDu3oWMAL")]))
|
||||||
"""
|
"""
|
||||||
|
response = llmCall(formattedMessages)
|
||||||
agentEventSink(string(response))
|
agentEventSink(string(response))
|
||||||
|
|
||||||
agentEventSink("_processMessage 11")
|
agentEventSink("_processMessage 11")
|
||||||
#WORKING Check if LLM used tool calls (inspect content for tool_call blocks)
|
# Extract tool calls from LLM response content blocks
|
||||||
hasToolCalls = false
|
hasToolCalls, toolCallList = _extractToolCalls(response)
|
||||||
toolCallList = agentToolCall[]
|
agentEventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList")
|
||||||
|
error("debug marker")
|
||||||
for content_block in response.content # extract response
|
|
||||||
if content_block isa Dict
|
|
||||||
if get(content_block, :type, "") == "tool_calls"
|
|
||||||
hasToolCalls = true
|
|
||||||
for tc_data in get(content_block, :tool_calls, [])
|
|
||||||
tc = agentToolCall(
|
|
||||||
type="function",
|
|
||||||
id=get(tc_data, :id, string(uuid4())),
|
|
||||||
name=get(tc_data, :function, Dict{String,Any}())[:name],
|
|
||||||
arguments=get(tc_data, :function, Dict{String,Any}())[:arguments],
|
|
||||||
)
|
|
||||||
push!(toolCallList, tc)
|
|
||||||
end
|
|
||||||
elseif get(content_block, :type, "") == "tool_call"
|
|
||||||
hasToolCalls = true
|
|
||||||
tc_data = content_block
|
|
||||||
tc = agentToolCall(
|
|
||||||
type="function",
|
|
||||||
id=get(tc_data, :id, string(uuid4())),
|
|
||||||
name=get(tc_data, :name, ""),
|
|
||||||
arguments=get(tc_data, :arguments, Dict{String,Any}()),
|
|
||||||
)
|
|
||||||
push!(toolCallList, tc)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if hasToolCalls && length(toolCallList) > 0
|
if hasToolCalls && length(toolCallList) > 0
|
||||||
# Build context and config for executeToolCalls
|
# Build context and config for executeToolCalls
|
||||||
context = agentContext(
|
context = agentContext(
|
||||||
@@ -560,6 +529,101 @@ function createToolResultMessage(f::finalizedOutcome)::toolResultMessage
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
_extractToolCalls(response) -> Tuple{Bool, Vector{agentToolCall}}
|
||||||
|
|
||||||
|
Extracts tool calls from the LLM response. Supports two response formats:
|
||||||
|
|
||||||
|
1. **Message format** (e.g. from LMStudio.jl / vLLM):
|
||||||
|
`response["message"]["tool_calls"]` — array of tool call objects with
|
||||||
|
`"type" => "function"`, `"function" => Dict("name" => ..., "arguments" => "...")`,
|
||||||
|
and `"id"`. The `"arguments"` value is a JSON string that gets parsed via `JSON.parse`.
|
||||||
|
|
||||||
|
2. **Content blocks format** (e.g. from OpenAI API):
|
||||||
|
`response.content` — array of content blocks. Blocks with `"type" => "tool_calls"`
|
||||||
|
contain a `"tool_calls"` array in format 1. Blocks with `"type" => "tool_call"`
|
||||||
|
have `"name"`, `"arguments"`, `"id"` at the top level (already-parsed args dict).
|
||||||
|
|
||||||
|
Returns `(hasToolCalls, toolCallList)` where `hasToolCalls` is `true` if any
|
||||||
|
tool calls were found, and `toolCallList` is a vector of `agentToolCall` structs.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `response`: LLM response object (Dict/JSON.Object or struct with `.content` field)
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `Tuple{Bool, Vector{agentToolCall}}`: `(hasToolCalls, toolCallList)`
|
||||||
|
"""
|
||||||
|
function _extractToolCalls(response)
|
||||||
|
hasToolCalls = false
|
||||||
|
toolCallList = agentToolCall[]
|
||||||
|
|
||||||
|
# Helper: parse args (JSON string -> Dict, or pass through)
|
||||||
|
parse_args(raw) = raw isa AbstractDict ? Dict{String,Any}(raw) :
|
||||||
|
raw isa String ? JSON.parse(raw) : Dict{String,Any}()
|
||||||
|
|
||||||
|
# Helper: build agentToolCall (positional)
|
||||||
|
make_tc(tc_data, default_id=string(uuid4())) = begin
|
||||||
|
func = get(tc_data, "function", Dict{String,Any}())
|
||||||
|
args = parse_args(get(func, "arguments", "{}"))
|
||||||
|
name = get(func, "name", "")
|
||||||
|
id_val = get(tc_data, "id", default_id)
|
||||||
|
agentToolCall("function", id_val, name, args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Format 1: response["message"]["tool_calls"] (LMStudio.jl / vLLM style)
|
||||||
|
msg = get(response, "message", nothing)
|
||||||
|
if msg !== nothing && msg isa AbstractDict
|
||||||
|
tc_array = get(msg, "tool_calls", nothing)
|
||||||
|
if tc_array !== nothing && tc_array isa Vector
|
||||||
|
for tc_data in tc_array
|
||||||
|
if tc_data isa AbstractDict
|
||||||
|
hasToolCalls = true
|
||||||
|
push!(toolCallList, make_tc(tc_data))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Format 2: response.content blocks (OpenAI API style)
|
||||||
|
if !hasToolCalls
|
||||||
|
content = nothing
|
||||||
|
if response isa AbstractDict
|
||||||
|
content = get(response, "content", nothing)
|
||||||
|
else
|
||||||
|
try
|
||||||
|
content = getfield(response, :content)
|
||||||
|
catch
|
||||||
|
content = nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if content isa Vector
|
||||||
|
for content_block in content
|
||||||
|
if content_block isa AbstractDict
|
||||||
|
if get(content_block, "type", "") == "tool_calls"
|
||||||
|
hasToolCalls = true
|
||||||
|
for tc_data in get(content_block, "tool_calls", [])
|
||||||
|
if tc_data isa AbstractDict
|
||||||
|
push!(toolCallList, make_tc(tc_data))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif get(content_block, "type", "") == "tool_call"
|
||||||
|
hasToolCalls = true
|
||||||
|
tc = agentToolCall(
|
||||||
|
"function",
|
||||||
|
get(content_block, "id", string(uuid4())),
|
||||||
|
get(content_block, "name", ""),
|
||||||
|
get(content_block, "arguments", Dict{String,Any}()),
|
||||||
|
)
|
||||||
|
push!(toolCallList, tc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return hasToolCalls, toolCallList
|
||||||
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
shouldTerminate(finalizedCalls::Vector{finalizedOutcome}) -> Bool
|
shouldTerminate(finalizedCalls::Vector{finalizedOutcome}) -> Bool
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,343 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using Test
|
||||||
|
using YiemAgent
|
||||||
|
|
||||||
|
include("toolTest.jl")
|
||||||
|
include("_extractToolCalls.jl")
|
||||||
|
|||||||
Reference in New Issue
Block a user