update
This commit is contained in:
@@ -1,11 +1,5 @@
|
||||
_processMessage 10
|
||||
JSON.Object{String, Any}("finish_reason" => "stop", "index" => 0, "message" => JSON.Object{String, Any}("role" => "assistant", "content" => "The weather in Bangkok is currently Sunny with a temperature of 22°C.", "reasoning_content" => "The user is asking for the weather in Bangkok.\nI have already retrieved the weather information in the previous turn and provided it to the user.\nThe user's current input is \"What's the weather in Bangkok?\", which is the same question as before.\nI should provide the same answer again.\nNo new tool calls are needed.\nI will simply state the weather information retrieved previously.\nWeather in Bangkok: Sunny, 22°C.\nI will output the answer directly.\n"))
|
||||
_processMessage 11
|
||||
hasToolCalls: false
|
||||
toolCallList: YiemAgent.type.agentToolCall[]
|
||||
_processMessage 17
|
||||
_processMessage 18
|
||||
|
||||
---
|
||||
here is my log. from the log, it seems like _processMessage() is finished but somehow the log didn't show _agentLoop 5 message. _processMessage() may not exit properly but why?
|
||||
|
||||
check my understand:
|
||||
1) if LLM didn't use tool calls, assistantMessage get pushed into agent._state.messages and
|
||||
it will be the latest message in agent._state.messages. then _agentLoop() can pick it as
|
||||
the output to outputChannel
|
||||
2) if LLM use tool calls but toolResultBatch.terminate is false, assistantMessageToolCall
|
||||
@@ -0,0 +1,8 @@
|
||||
check my understanding
|
||||
1) if LLM didn't use tool calls, assistantMessage get pushed into agent._state.messages and it will be the latest message in agent._state.messages. then _agentLoop() can pick it as the output to outputChannel
|
||||
2) if LLM use tool calls, assistantMessageToolCall get pushed into agent._state.messages. then toolResult get pushed into agent._state.messages. if toolResultBatch.terminate is false then _processMessage() loop continue
|
||||
3) if LLM use tool calls, assistantMessageToolCall get pushed into agent._state.messages. then toolResult get pushed into agent._state.messages. if toolResultBatch.terminate is true then final_response message get pushed into agent._state.messages. _processMessage() loop exit. then _agentLoop() can pick it as the output to outputChannel
|
||||
|
||||
Is my understanding correct?
|
||||
|
||||
|
||||
+107
-90
@@ -208,25 +208,25 @@ function _agentLoop(agent::yiemAgent)
|
||||
while true
|
||||
while newUserMsg === nothing
|
||||
if isready(agent.inputChannel)
|
||||
agent.agentEventSink("_agentLoop 1")
|
||||
agent.agentEventSink("_agentLoop 1 agent._state.messages length $(length(agent._state.messages))")
|
||||
# agent process new user msg immediately after the current tool call finished.
|
||||
newUserMsg = take!(agent.inputChannel)
|
||||
agent.agentEventSink("new user msg")
|
||||
else
|
||||
# check followUp message after _processMessage() is done
|
||||
if typeof(processingTask) == Task && istaskdone(processingTask) == true
|
||||
agent.agentEventSink("_agentLoop 2")
|
||||
agent.agentEventSink("_agentLoop 2 agent._state.messages length $(length(agent._state.messages))")
|
||||
# if agent runs is done but followUpChannel has messages,
|
||||
# put new message in inputChannel instead
|
||||
if isready(agent.followUpChannel)
|
||||
agent.agentEventSink("_agentLoop 3")
|
||||
agent.agentEventSink("_agentLoop 3 agent._state.messages length $(length(agent._state.messages))")
|
||||
while isready(agent.followUpChannel)
|
||||
followUpMsg = take!(agent.followUpChannel)
|
||||
put!(agent.inputChannel, followUpMsg)
|
||||
end
|
||||
else # _processMessage() done and no followUp message.
|
||||
agent.agentEventSink("_agentLoop 4")
|
||||
result = fetch(processingTask)
|
||||
agent.agentEventSink("_agentLoop 4 agent._state.messages length $(length(agent._state.messages))")
|
||||
result = agent._state.messages[end]
|
||||
put!(agent.outputChannel, result)
|
||||
agent.agentEventSink(result.content[1].text)
|
||||
processingTask = nothing # reset
|
||||
@@ -259,7 +259,7 @@ function _agentLoop(agent::yiemAgent)
|
||||
else
|
||||
# spawn new _processMessage() if it is not already running.
|
||||
if processingTask === nothing
|
||||
agent.agentEventSink("_agentLoop 2")
|
||||
agent.agentEventSink("_agentLoop 5 agent._state.messages length $(length(agent._state.messages))")
|
||||
# Dispatch message through the processing pipeline
|
||||
processingTask = @spawn _processMessage(
|
||||
processMessageInputCh,
|
||||
@@ -325,8 +325,8 @@ function _processMessage(
|
||||
beforeToolCall::Union{Function, Nothing},
|
||||
afterToolCall::Union{Function, Nothing},
|
||||
parallelToolExecute::Bool,
|
||||
)::assistantMessage
|
||||
agentEventSink("_processMessage 1")
|
||||
)::Nothing
|
||||
agentEventSink("_processMessage 1 _state.messages length $(length(agentMsgHistory))")
|
||||
# loop until llmCall() response didn't use tool calls
|
||||
final_response = nothing
|
||||
|
||||
@@ -346,41 +346,46 @@ function _processMessage(
|
||||
while true
|
||||
# Drain inputChannel and convert OpenAI-format messages to userMessage type
|
||||
while isready(inputChannel)
|
||||
agentEventSink("_processMessage 2")
|
||||
agentEventSink("_processMessage 2 _state.messages length $(length(agentMsgHistory))")
|
||||
newUserMsg_openai = take!(inputChannel)
|
||||
agentEventSink("_processMessage 3")
|
||||
agentEventSink("_processMessage 3 _state.messages length $(length(agentMsgHistory))")
|
||||
if newUserMsg_openai === :shutdown
|
||||
agentEventSink("_processMessage 4")
|
||||
agentEventSink("_processMessage 4 _state.messages length $(length(agentMsgHistory))")
|
||||
# Re-emit shutdown signal for the loop to handle
|
||||
put!(inputChannel, :shutdown)
|
||||
break
|
||||
end
|
||||
agentEventSink("_processMessage 5")
|
||||
agentEventSink("_processMessage 5 _state.messages length $(length(agentMsgHistory))")
|
||||
newUserMsg = OpenAiToUserMessage(newUserMsg_openai)
|
||||
push!(agentMsgHistory, newUserMsg)
|
||||
agentEventSink("_processMessage 6")
|
||||
agentEventSink("_processMessage 6 _state.messages length $(length(agentMsgHistory))")
|
||||
end
|
||||
agentEventSink("_processMessage 7")
|
||||
agentEventSink("_processMessage 7 _state.messages length $(length(agentMsgHistory))")
|
||||
# call prepareContext()
|
||||
state = agentState(systemPrompt, nothing, tools, agentMsgHistory)
|
||||
agentEventSink("_processMessage 8")
|
||||
agentEventSink("_processMessage 8 _state.messages length $(length(agentMsgHistory))")
|
||||
preparedContext = prepareContext(state, agentEventSink)
|
||||
agentEventSink("_processMessage 8")
|
||||
agentEventSink("_processMessage 9 _state.messages length $(length(agentMsgHistory))")
|
||||
# Call formatMessagesForLLM() to format for LLM
|
||||
formattedMessages = formatMessagesForLLM(preparedContext, agentEventSink)
|
||||
|
||||
agentEventSink("_processMessage 10")
|
||||
|
||||
agentEventSink("_processMessage 10 formattedMessages $formattedMessages")
|
||||
|
||||
""" 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 = llmCall(formattedMessages)
|
||||
agentEventSink(string(response))
|
||||
agentEventSink(" llmCall " * string(response))
|
||||
|
||||
agentEventSink("_processMessage 11")
|
||||
agentEventSink("_processMessage 11 _state.messages length $(length(agentMsgHistory))")
|
||||
# Extract tool calls from LLM response content blocks
|
||||
hasToolCalls, toolCallList, assistant_msg = _extractToolCalls(response)
|
||||
agentEventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList")
|
||||
agentEventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList _state.messages length $(length(agentMsgHistory))")
|
||||
agentEventSink(string(assistant_msg))
|
||||
agentEventSink("_processMessage 11-1")
|
||||
|
||||
# Add assistant message (tool calls or text) to history for next LLM turn
|
||||
push!(agentMsgHistory, assistant_msg)
|
||||
|
||||
if hasToolCalls && length(toolCallList) > 0
|
||||
# Build context and config for executeToolCalls
|
||||
@@ -392,20 +397,20 @@ function _processMessage(
|
||||
)
|
||||
|
||||
signal = abortSignal(false)
|
||||
agentEventSink("_processMessage 12")
|
||||
agentEventSink("_processMessage 12 _state.messages length $(length(agentMsgHistory))")
|
||||
|
||||
# call executeToolCalls()
|
||||
toolResultBatch = executeToolCalls(preparedContext, assistant_msg, toolCallList, config,
|
||||
signal, agentEventSink)
|
||||
agentEventSink("_processMessage 13")
|
||||
agentEventSink("_processMessage 13 _state.messages length $(length(agentMsgHistory))")
|
||||
|
||||
# save toolResults to messages
|
||||
for toolResult in toolResultBatch.messages
|
||||
push!(agentMsgHistory, toolResult)
|
||||
end
|
||||
agentEventSink("_processMessage 14")
|
||||
agentEventSink("_processMessage 14 _state.messages length $(length(agentMsgHistory))")
|
||||
if toolResultBatch.terminate
|
||||
agentEventSink("_processMessage 15")
|
||||
agentEventSink("_processMessage 15 _state.messages length $(length(agentMsgHistory))")
|
||||
# If toolResultBatch requested termination, build a final response
|
||||
final_content = [textContent("Tool execution completed.")]
|
||||
for toolResult in toolResultBatch.messages
|
||||
@@ -419,7 +424,7 @@ function _processMessage(
|
||||
end
|
||||
end
|
||||
end
|
||||
agentEventSink("_processMessage 16")
|
||||
agentEventSink("_processMessage 16 _state.messages length $(length(agentMsgHistory))")
|
||||
final_response = assistantMessage(
|
||||
role="assistant",
|
||||
content=final_content,
|
||||
@@ -434,17 +439,17 @@ function _processMessage(
|
||||
end,
|
||||
timestamp=now(),
|
||||
)
|
||||
push!(agentMsgHistory, final_response)
|
||||
break
|
||||
end
|
||||
else
|
||||
agentEventSink("_processMessage 17")
|
||||
# LLM did not use tool calls — this is the final response
|
||||
final_response = assistant_msg
|
||||
agentEventSink("_processMessage 17 _state.messages length $(length(agentMsgHistory))")
|
||||
# LLM did not use tool calls —
|
||||
break
|
||||
end
|
||||
end
|
||||
agentEventSink("_processMessage 18")
|
||||
return final_response
|
||||
agentEventSink("_processMessage 18 _state.messages length $(length(agentMsgHistory))")
|
||||
return nothing
|
||||
end
|
||||
|
||||
|
||||
@@ -516,24 +521,28 @@ function createToolResultMessage(f::finalizedOutcome)::toolResultMessage
|
||||
end
|
||||
|
||||
"""
|
||||
_extractToolCalls(response) -> Tuple{Bool, Vector{agentToolCall}, assistantMessage}
|
||||
_extractToolCalls(response) -> Tuple{Bool, Vector{agentToolCall}, Union{assistantMessageToolCall, assistantMessage}}
|
||||
|
||||
Extracts tool calls from the LLM response and constructs an `assistantMessage`.
|
||||
Extracts tool calls from the LLM response and constructs a message object.
|
||||
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`.
|
||||
`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).
|
||||
`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).
|
||||
|
||||
The `assistantMessage` is constructed from:
|
||||
- `reasoning_content` (string) → added as a `textContent` block
|
||||
- `response.content` blocks (text/ reasoning) → added to content
|
||||
When `hasToolCalls` is true, returns an `assistantMessageToolCall` with the tool calls
|
||||
and reasoning content. When `hasToolCalls` is false, returns an `assistantMessage`
|
||||
with text/content blocks from the response.
|
||||
|
||||
The message is constructed from:
|
||||
- `reasoning_content` (string) → stored in `reasoning` field (for tool calls) or `textContent` (for text)
|
||||
- `response.content` blocks (text/reasoning) → added to content for text responses
|
||||
- Top-level `api`, `provider`, `model`, `usage` → copied to the message
|
||||
- `finish_reason` → used as `stopReason`
|
||||
|
||||
@@ -541,7 +550,9 @@ The `assistantMessage` is constructed from:
|
||||
- `response`: LLM response object (Dict/JSON.Object or struct with `.content` field)
|
||||
|
||||
# Returns
|
||||
- `Tuple{Bool, Vector{agentToolCall}, assistantMessage}`: `(hasToolCalls, toolCallList, assistantMsg)`
|
||||
- `Tuple{Bool, Vector{agentToolCall}, Union{assistantMessageToolCall, assistantMessage}}`:
|
||||
`(hasToolCalls, toolCallList, message)` where message is `assistantMessageToolCall`
|
||||
when tool calls exist, `assistantMessage` otherwise
|
||||
|
||||
# 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")]))
|
||||
"""
|
||||
@@ -616,20 +627,6 @@ function _extractToolCalls(response)
|
||||
end
|
||||
|
||||
# ── Construct assistantMessage from response ──────────────────────
|
||||
# Check Format 1 nested message for reasoning_content, role, etc.
|
||||
reasoning = get(response, "reasoning_content", nothing)
|
||||
if reasoning === nothing && msg !== nothing && msg isa AbstractDict
|
||||
reasoning = get(msg, "reasoning_content", nothing)
|
||||
end
|
||||
if reasoning isa String
|
||||
reasoning_text = reasoning
|
||||
elseif reasoning isa textContent
|
||||
reasoning_text = reasoning.text
|
||||
else
|
||||
reasoning_text = ""
|
||||
end
|
||||
reasoning_block = !isempty(reasoning_text) ? [textContent(reasoning_text)] : textContent[]
|
||||
|
||||
finish_reason = get(response, "finish_reason", nothing)
|
||||
stop_reason = finish_reason isa AbstractString ? String(finish_reason) : "end_turn"
|
||||
|
||||
@@ -638,6 +635,21 @@ function _extractToolCalls(response)
|
||||
model = get(response, "model", nothing)
|
||||
usage = get(response, "usage", nothing)
|
||||
|
||||
# Collect reasoning from reasoning_content field (Format 1: Anthropic-style)
|
||||
reasoning_text = get(response, "reasoning_content", nothing)
|
||||
if reasoning_text === nothing && msg !== nothing && msg isa AbstractDict
|
||||
reasoning_text = get(msg, "reasoning_content", nothing)
|
||||
end
|
||||
if reasoning_text isa String
|
||||
reasoning_text = reasoning_text
|
||||
elseif reasoning_text isa textContent
|
||||
reasoning_text = reasoning_text.text
|
||||
else
|
||||
reasoning_text = ""
|
||||
end
|
||||
reasoning_content = !isempty(reasoning_text) ? [textContent(reasoning_text)] : textContent[]
|
||||
|
||||
# Collect content blocks from response.content array (Format 2: OpenAI-style)
|
||||
content_from_response = get(response, "content", nothing)
|
||||
content_blocks = Vector{messageContent}()
|
||||
if content_from_response isa Vector
|
||||
@@ -658,15 +670,11 @@ function _extractToolCalls(response)
|
||||
end
|
||||
end
|
||||
|
||||
# Combine content blocks and reasoning
|
||||
if !isempty(content_blocks) && !isempty(reasoning_block)
|
||||
all_content = vcat(reasoning_block, content_blocks)
|
||||
elseif !isempty(content_blocks)
|
||||
all_content = content_blocks
|
||||
elseif !isempty(reasoning_block)
|
||||
all_content = reasoning_block
|
||||
# Combine reasoning_content field + content array blocks, deduplicating reasoning
|
||||
if !isempty(reasoning_content)
|
||||
all_content = vcat(reasoning_content, content_blocks)
|
||||
else
|
||||
all_content = textContent[]
|
||||
all_content = content_blocks
|
||||
end
|
||||
|
||||
error_msg = get(response, "error_message", get(response, "errorMessage", nothing))
|
||||
@@ -684,17 +692,32 @@ function _extractToolCalls(response)
|
||||
end
|
||||
end
|
||||
|
||||
assistant_msg = assistantMessage(
|
||||
role = role,
|
||||
content = all_content,
|
||||
api = api isa AbstractString ? String(api) : "",
|
||||
provider = provider isa AbstractString ? String(provider) : "",
|
||||
model = model,
|
||||
usage = usage,
|
||||
stopReason = stop_reason,
|
||||
errorMessage = error_msg,
|
||||
timestamp = now(),
|
||||
)
|
||||
if hasToolCalls
|
||||
assistant_msg = assistantMessageToolCall(
|
||||
role = role,
|
||||
toolCalls = toolCallList,
|
||||
content = all_content,
|
||||
api = api isa AbstractString ? String(api) : "",
|
||||
provider = provider isa AbstractString ? String(provider) : "",
|
||||
model = model,
|
||||
usage = usage,
|
||||
stopReason = stop_reason,
|
||||
errorMessage = error_msg,
|
||||
timestamp = now(),
|
||||
)
|
||||
else
|
||||
assistant_msg = assistantMessage(
|
||||
role = role,
|
||||
content = all_content,
|
||||
api = api isa AbstractString ? String(api) : "",
|
||||
provider = provider isa AbstractString ? String(provider) : "",
|
||||
model = model,
|
||||
usage = usage,
|
||||
stopReason = stop_reason,
|
||||
errorMessage = error_msg,
|
||||
timestamp = now(),
|
||||
)
|
||||
end
|
||||
|
||||
return hasToolCalls, toolCallList, assistant_msg
|
||||
end
|
||||
@@ -846,7 +869,7 @@ prepareToolCall(context, msg, tc, config, abortedSignal)
|
||||
"""
|
||||
function prepareToolCall(
|
||||
context::agentContext,
|
||||
assistantMsg::assistantMessage,
|
||||
assistantMsg::assistantMessageToolCall,
|
||||
toolCall::agentToolCall,
|
||||
config::agentLoopConfig,
|
||||
signal::abortSignal,
|
||||
@@ -863,10 +886,10 @@ function prepareToolCall(
|
||||
agentEventSink("prepareToolCall 3")
|
||||
# 1. prepare arguments (tool-specific transform)
|
||||
prepared = prepareToolCallArguments(tool, toolCall)
|
||||
agentEventSink(string(prepared.arguments))
|
||||
agentEventSink("prepared " * string(prepared.arguments))
|
||||
agentEventSink("prepareToolCall 4")
|
||||
validatedArgs = validateToolArguments(tool, prepared)
|
||||
agentEventSink(string(validatedArgs))
|
||||
agentEventSink("validatedArgs " * string(validatedArgs))
|
||||
agentEventSink("prepareToolCall 5")
|
||||
# 2. beforeToolCall hook — can block
|
||||
if config.beforeToolCall !== nothing
|
||||
@@ -950,14 +973,8 @@ function executePreparedToolCall(
|
||||
agentEventSink,
|
||||
)::executedOutcome
|
||||
agentEventSink("executePreparedToolCall 1")
|
||||
agentEventSink(prep.toolCall.id)
|
||||
agentEventSink(prep.toolCall.name)
|
||||
agentEventSink("executePreparedToolCall 2")
|
||||
s = string(prep.args)
|
||||
agentEventSink(s)
|
||||
agentEventSink("executePreparedToolCall 3")
|
||||
t = string(fieldnames(typeof(prep.tool)))
|
||||
agentEventSink("executePreparedToolCall 3-1 " * t)
|
||||
|
||||
try
|
||||
result = prep.tool.execute(prep.toolCall.id, prep.args, signal, agentEventSink)
|
||||
@@ -1035,7 +1052,7 @@ finalizeExecutedToolCall(context, msg, prep, execFail, config, nothing)
|
||||
"""
|
||||
function finalizeExecutedToolCall(
|
||||
context::agentContext,
|
||||
assistantMsg::assistantMessage,
|
||||
assistantMsg::assistantMessageToolCall,
|
||||
prep::preparedToolCall,
|
||||
executed::executedOutcome,
|
||||
config::agentLoopConfig,
|
||||
@@ -1132,7 +1149,7 @@ executeToolCallsSequential(ctx, msg, [deployTc], config, nothing, emit)
|
||||
"""
|
||||
function executeToolCallsSequential(
|
||||
context::agentContext,
|
||||
assistantMsg::assistantMessage,
|
||||
assistantMsg::assistantMessageToolCall,
|
||||
toolCalls::Vector{agentToolCall},
|
||||
config::agentLoopConfig,
|
||||
signal::abortSignal,
|
||||
@@ -1161,7 +1178,7 @@ function executeToolCallsSequential(
|
||||
agentEventSink("executeToolCallsSequential 3-2")
|
||||
end
|
||||
agentEventSink("executeToolCallsSequential 4")
|
||||
agentEventSink("$(finalized.toolCall.id), $(finalized.toolCall.name),
|
||||
agentEventSink("finalized $(finalized.toolCall.id), $(finalized.toolCall.name),
|
||||
$(finalized.result), $(finalized.isError)")
|
||||
push!(messages, createToolResultMessage(finalized))
|
||||
push!(finalizedCalls, finalized)
|
||||
@@ -1233,7 +1250,7 @@ executeToolCallsParallel(ctx, msg, [tc1, tc2, tc3], config, abortedSignal, emit)
|
||||
"""
|
||||
function executeToolCallsParallel(
|
||||
context::agentContext,
|
||||
assistantMsg::assistantMessage,
|
||||
assistantMsg::assistantMessageToolCall,
|
||||
toolCalls::Vector{agentToolCall},
|
||||
config::agentLoopConfig,
|
||||
signal::abortSignal,
|
||||
@@ -1335,7 +1352,7 @@ executeToolCalls(ctx, msg, [searchTc, fetchTc], configSequential, nothing, emit)
|
||||
"""
|
||||
function executeToolCalls(
|
||||
context::agentContext,
|
||||
assistantMsg::assistantMessage,
|
||||
assistantMsg::assistantMessageToolCall,
|
||||
toolCalls::Vector{agentToolCall},
|
||||
config::agentLoopConfig,
|
||||
signal::abortSignal,
|
||||
|
||||
+52
-6
@@ -6,8 +6,8 @@
|
||||
modelCost, llmModel, llmUsage,
|
||||
# Message content types
|
||||
textContent, imageContent,
|
||||
# Message types
|
||||
userMessage, assistantMessage, toolResultMessage,
|
||||
# Message types
|
||||
userMessage, assistantMessageToolCall, assistantMessage, toolResultMessage,
|
||||
# Tool types
|
||||
agentTool, validateRequiredArgs,
|
||||
# Context types
|
||||
@@ -108,6 +108,52 @@ function userMessage(; role="user", content=Vector{messageContent}(), timestamp=
|
||||
return userMessage(role, content, timestamp)
|
||||
end
|
||||
|
||||
struct assistantMessageToolCall <: agentMessage # Assistant message containing tool calls
|
||||
role::String # Always "assistant"
|
||||
toolCalls::Vector{agentToolCall} # Tool calls to execute
|
||||
content::Vector{messageContent} # Reasoning/thinking content blocks
|
||||
api::String # API name used (e.g., "openai")
|
||||
provider::String # Provider name (e.g., "anthropic")
|
||||
model::String # Model identifier
|
||||
usage::llmUsage # Token usage for this message
|
||||
stopReason::String # Why generation stopped (e.g., "tool_calls")
|
||||
errorMessage::Union{String, Nothing} # Error if generation failed
|
||||
timestamp::Timestamp # When the message was received
|
||||
end
|
||||
|
||||
"""
|
||||
Create a new assistant message containing tool calls.
|
||||
|
||||
# Arguments
|
||||
- `role::String`: Always "assistant"
|
||||
- `toolCalls::Vector{agentToolCall}`: Tool calls to execute
|
||||
- `content::Vector{messageContent}`: Reasoning/thinking content blocks
|
||||
- `api::String`: API name used
|
||||
- `provider::String`: Provider name
|
||||
- `model::String`: Model identifier
|
||||
- `usage::llmUsage`: Token usage
|
||||
- `stopReason::String`: Why generation stopped
|
||||
- `errorMessage::Union{String, Nothing}`: Error if generation failed
|
||||
- `timestamp::Timestamp`: When the message was received
|
||||
|
||||
# Returns
|
||||
- A new `assistantMessageToolCall` instance
|
||||
|
||||
# Examples
|
||||
```julia
|
||||
julia> tc = agentToolCall("function", "call_1", "getWeather", Dict("city" => "Tokyo"))
|
||||
julia> msg = assistantMessageToolCall(toolCalls=[tc], stopReason="tool_calls")
|
||||
assistantMessageToolCall("assistant", [agentToolCall(...)], messageContent[], "", "", "", llmUsage(0, 0), "tool_calls", nothing, DateTime(...))
|
||||
```
|
||||
"""
|
||||
function assistantMessageToolCall(; role="assistant", toolCalls=agentToolCall[],
|
||||
content=Vector{messageContent}(), api="", provider="", model=nothing, usage=llmUsage(0, 0),
|
||||
stopReason="tool_calls", errorMessage=nothing, timestamp=now())
|
||||
model_str = model isa AbstractString ? String(model) : ""
|
||||
return assistantMessageToolCall(role, toolCalls, content, api, provider, model_str,
|
||||
usage, stopReason, errorMessage, timestamp)
|
||||
end
|
||||
|
||||
struct assistantMessage <: agentMessage # Message from the AI assistant
|
||||
role::String # Always "assistant"
|
||||
content::Vector{messageContent} # Text and/or image content
|
||||
@@ -434,13 +480,13 @@ end
|
||||
Context passed to the `beforeToolCall` hook.
|
||||
|
||||
# Arguments
|
||||
- `message::assistantMessage`: The assistant message containing the tool call
|
||||
- `message::assistantMessageToolCall`: The assistant message containing the tool call
|
||||
- `toolCall::agentToolCall`: The tool call being prepared
|
||||
- `args::Dict{String,Any}`: Validated tool arguments
|
||||
- `context::agentContext`: Current conversation context
|
||||
"""
|
||||
struct beforeToolCallContext
|
||||
message::assistantMessage
|
||||
message::assistantMessageToolCall
|
||||
toolCall::agentToolCall
|
||||
args::Dict{String,Any}
|
||||
context::agentContext
|
||||
@@ -455,7 +501,7 @@ end
|
||||
Context passed to the `afterToolCall` hook.
|
||||
|
||||
# Arguments
|
||||
- `message::assistantMessage`: The assistant message containing the tool call
|
||||
- `message::assistantMessageToolCall`: The assistant message containing the tool call
|
||||
- `toolCall::agentToolCall`: The tool call that was executed
|
||||
- `args::Dict{String,Any}`: Tool arguments
|
||||
- `result::agentToolResult`: The raw tool result
|
||||
@@ -463,7 +509,7 @@ Context passed to the `afterToolCall` hook.
|
||||
- `context::agentContext`: Current conversation context
|
||||
"""
|
||||
struct afterToolCallContext
|
||||
message::assistantMessage
|
||||
message::assistantMessageToolCall
|
||||
toolCall::agentToolCall
|
||||
args::Dict{String,Any}
|
||||
result::agentToolResult
|
||||
|
||||
@@ -221,6 +221,8 @@ function formatMsgForLLM(ctx::agentContext, agentEventSink)::Dict{String, Any}
|
||||
for msg in ctx.messages
|
||||
if msg isa userMessage
|
||||
push!(messages, _userMessageToOpenAI(msg))
|
||||
elseif msg isa assistantMessageToolCall
|
||||
push!(messages, _assistantMessageToolCallToOpenAI(msg))
|
||||
elseif msg isa assistantMessage
|
||||
push!(messages, _assistantMessageToOpenAI(msg))
|
||||
elseif msg isa toolResultMessage
|
||||
@@ -336,6 +338,44 @@ end
|
||||
|
||||
|
||||
"""
|
||||
Convert an assistantMessageToolCall to OpenAI message format.
|
||||
|
||||
Produces a message with role="assistant", content=null, and a tool_calls array:
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": null,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_1",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"arguments": "{\"location\": \"San Francisco, CA\"}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
function _assistantMessageToolCallToOpenAI(msg::assistantMessageToolCall)::Dict{String, Any}
|
||||
tool_calls = Dict{String, Any}[]
|
||||
for tc in msg.toolCalls
|
||||
push!(tool_calls, Dict(
|
||||
"id" => tc.id,
|
||||
"type" => tc.type,
|
||||
"function" => Dict(
|
||||
"name" => tc.name,
|
||||
"arguments" => JSON.json(tc.arguments)
|
||||
)
|
||||
))
|
||||
end
|
||||
return Dict(
|
||||
"role" => "assistant",
|
||||
"content" => nothing,
|
||||
"tool_calls" => tool_calls
|
||||
)
|
||||
end
|
||||
|
||||
"""
|
||||
Convert an assistantMessage to OpenAI message format.
|
||||
"""
|
||||
function _assistantMessageToOpenAI(msg::assistantMessage)::Dict{String, Any}
|
||||
|
||||
Reference in New Issue
Block a user