diff --git a/src/agentCore.jl b/src/agentCore.jl index 5c2cc74..199e785 100644 --- a/src/agentCore.jl +++ b/src/agentCore.jl @@ -269,22 +269,26 @@ function _agentLoop(agent::yiemAgent) else # check followUp message after _processMessage() is done if typeof(processingTask) == Task && istaskdone(processingTask) == true + # agent.eventSink("_processMessage() done") # if agent runs is done but followUpChannel has messages, # put new message in inputChannel instead if isready(agent.followUpChannel) while isready(agent.followUpChannel) + # agent.eventSink("put followUp message to input channel") followUpMsg = take!(agent.followUpChannel) put!(agent.inputChannel, followUpMsg) end processingTask = nothing # reset result = nothing # reset else # _processMessage() done and no followUp message. + # agent.eventSink("no followUp message") result = deepcopy(agent._state.messages[end]) # filter out reasoningContent in-place filter!(c -> !(c isa reasoningContent), result.content) # format output respondToUI = _assistantMessageToOpenAI(result) put!(agent.outputChannel, respondToUI) + # agent.eventSink("put result to output channel") processingTask = nothing # reset result = nothing # reset end @@ -344,7 +348,7 @@ function _agentLoop(agent::yiemAgent) showerror(io, e, bt) println(io) end - eventSink(errMsg; publish=true) + # agent.eventSink(errMsg; publish=true) # On any error, send error response and exit the loop @error "Agent loop failed" error=e @@ -395,6 +399,8 @@ function _processMessage( # loop until llmCall() response didn't use tool calls final_response = nothing + # eventSink("▶ _processMessage: entered") + """ example message in inputChannel Dict( "role" => "user", @@ -420,6 +426,7 @@ function _processMessage( end newUserMsg = OpenAiToUserMessage(newUserMsg_openai) push!(agentMsgHistory, newUserMsg) + # eventSink("▶ _processMessage: user msg pushed") end # call prepareContext() state = agentState(systemPrompt, nothing, tools, agentMsgHistory) @@ -427,19 +434,22 @@ function _processMessage( # Call formatMessagesForLLM() to format for LLM formattedMessages = formatMessagesForLLM(preparedContext, eventSink) - eventSink("Thinking...") + # eventSink("▶ _processMessage: context") """ 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, eventSink=eventSink) # Extract tool calls from LLM response content blocks - hasToolCalls, toolCallList, assistant_msg = _extractToolCalls(response) + hasToolCalls, toolCallList, assistant_msg = _extractToolCalls(response, eventSink) # Add assistant message (tool calls or text) to history for next LLM turn push!(agentMsgHistory, assistant_msg) + # eventSink("▶ _processMessage: LLM response") if hasToolCalls && length(toolCallList) > 0 + # eventSink("▶ _processMessage: tool calls") + # Build context and config for executeToolCalls config = agentLoopConfig( @@ -452,11 +462,13 @@ function _processMessage( # call executeToolCalls() toolResultBatch = executeToolCalls(preparedContext, assistant_msg, toolCallList, config, signal, eventSink) + # eventSink("▶ _processMessage: tool batch") # save toolResults to messages for toolResult in toolResultBatch.messages push!(agentMsgHistory, toolResult) end if toolResultBatch.terminate + # eventSink("▶ _processMessage: TERMINATE branch") # If toolResultBatch requested termination, build a final response final_content = [textContent("Tool execution completed.")] for toolResult in toolResultBatch.messages @@ -488,6 +500,7 @@ function _processMessage( break end else + # eventSink("▶ _processMessage: FINAL response") # LLM did not use tool calls — break end @@ -498,10 +511,12 @@ function _processMessage( println(io) end - eventSink(err_msg; publish=true) + # eventSink(err_msg; publish=true) + # eventSink("▶ _processMessage: CAUGHT ERROR") end end + # eventSink("▶ _processMessage: exited") return nothing end @@ -611,9 +626,12 @@ The message is constructed from: 1) llm_useTool = 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")])) 2) llm_notUseTool = JSON.Object{String, Any}("finish_reason" => "stop", "index" => 0, "message" => JSON.Object{String, Any}("role" => "assistant", "content" => "The weather in London, UK is currently Sunny with a temperature of 22°C.", "reasoning_content" => "The user asked for the weather in London.\nI called the `getWeather` tool for London, UK.\nThe response indicates it's Sunny and 22°C.\nI will convey this information to the user.\n")) """ -function _extractToolCalls(response) +function _extractToolCalls(response, eventSink=nothing) hasToolCalls = false toolCallList = agentToolCall[] + # if eventSink !== nothing + # eventSink("▶ _extractToolCalls: parsing response") + # end # Helper: parse args (JSON string -> Dict, or pass through) parse_args(raw) = raw isa AbstractDict && !(raw isa Dict{String,Any}) ? @@ -943,9 +961,11 @@ function prepareToolCall( )::Union{preparedToolCall,immediateOutcome} # pick a called tool from tool store + # eventSink("▶ prepareToolCall: resolving") tool = get(context.tools, toolCall.name, nothing) if tool === nothing + # eventSink("▶ prepareToolCall: NOT FOUND") return immediateOutcome(createErrorToolResult("Tool $toolCall.name not found"), true) end @@ -953,6 +973,7 @@ function prepareToolCall( # 1. prepare arguments (tool-specific transform) prepared = prepareToolCallArguments(tool, toolCall) validatedArgs = validateToolArguments(tool, prepared) + eventSink("▶ prepareToolCall: validated") # 2. beforeToolCall hook — can block if config.beforeToolCall !== nothing @@ -1032,8 +1053,10 @@ function executePreparedToolCall( eventSink, )::executedOutcome + # eventSink("▶ execute: START") try result = prep.tool.execute(prep.toolCall.id, prep.args, signal, eventSink) + # eventSink("▶ execute: END") return executedOutcome(result, false) catch e bt = catch_backtrace() @@ -1041,7 +1064,8 @@ function executePreparedToolCall( showerror(io, e, bt) println(io) end - eventSink(errMsg; publish=true) + # eventSink(errMsg; publish=true) + # eventSink("▶ execute: ERROR") return executedOutcome(createErrorToolResult(sprint(showerror, e)), true) end @@ -1115,6 +1139,7 @@ function finalizeExecutedToolCall( eventSink )::finalizedOutcome + # eventSink("▶ finalize: START") result = executed.result isError = executed.isError if config.afterToolCall !== nothing @@ -1140,6 +1165,7 @@ function finalizeExecutedToolCall( isError = true end end + # eventSink("▶ finalize: END") return finalizedOutcome(prep.toolCall, result, isError) end @@ -1207,6 +1233,7 @@ function executeToolCallsSequential( )::agentToolCallBatch finalizedCalls = finalizedOutcome[] messages = toolResultMessage[] + # eventSink("▶ executeToolCallsSequential: batch start") for tc in toolCalls prep = prepareToolCall(context, assistantMsg, tc, config, signal, eventSink) @@ -1224,6 +1251,7 @@ function executeToolCallsSequential( break end end + # eventSink("▶ executeToolCallsSequential: batch end") return agentToolCallBatch(messages, shouldTerminate(finalizedCalls)) end @@ -1294,6 +1322,7 @@ function executeToolCallsParallel( )::agentToolCallBatch entries = Union{finalizedOutcome,Task}[] + # eventSink("▶ executeToolCallsParallel: batch start") for tc in toolCalls toolExecStartEvent(tc.id, tc.name, tc.arguments) @@ -1333,6 +1362,7 @@ function executeToolCallsParallel( push!(messages, createToolResultMessage(f)) end + # eventSink("▶ executeToolCallsParallel: batch end") return agentToolCallBatch(messages, shouldTerminate(finalizedCalls)) end @@ -1403,6 +1433,7 @@ function executeToolCalls( break end end + # eventSink("▶ executeToolCalls: dispatch") if config.toolExecution == "sequential" || hasSequential return executeToolCallsSequential(context, assistantMsg, toolCalls, config, signal, eventSink) @@ -1447,8 +1478,11 @@ OpenAiToUserMessage(msg) # => userMessage("user", [textContent("Hello")], DateTime(...)) ``` """ -function OpenAiToUserMessage(msg::AbstractDict)::userMessage +function OpenAiToUserMessage(msg::AbstractDict, eventSink=nothing)::userMessage content_blocks = Vector{messageContent}() + # if eventSink !== nothing + # eventSink("▶ OpenAiToUserMessage: parsing") + # end raw_content = get(msg, "content", Any[]) if raw_content isa Vector @@ -1472,6 +1506,9 @@ function OpenAiToUserMessage(msg::AbstractDict)::userMessage end end + # if eventSink !== nothing + # eventSink("▶ OpenAiToUserMessage: done") + # end return userMessage(content=content_blocks) end