From 6db50b6a52ba450355c8e0d721bf9f9268a38482 Mon Sep 17 00:00:00 2001 From: narawat Date: Wed, 2 Sep 2026 11:17:41 +0700 Subject: [PATCH] update --- Manifest.toml | 8 +-- Project.toml | 2 +- my_AI_prompt.md | 95 ++++++++++++++++++++++++- src/agentCore.jl | 176 ++++++++++++++++++++++++++--------------------- src/api.jl | 4 +- src/utils.jl | 106 +--------------------------- 6 files changed, 199 insertions(+), 192 deletions(-) diff --git a/Manifest.toml b/Manifest.toml index 5ca0ac4..c58287b 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.12.7" manifest_format = "2.0" -project_hash = "eeddbbbbefd50bd46b098a2d54b2795d85daa76e" +project_hash = "43252cf86e6030e836ede74c563b58ab2f0f0548" [[deps.AWS]] deps = ["Base64", "Compat", "Dates", "Downloads", "GitHub", "HTTP", "IniFile", "JSON", "MD5", "Mocking", "OrderedCollections", "Random", "SHA", "ScopedValues", "Sockets", "URIs", "UUIDs", "XMLDict"] @@ -334,11 +334,11 @@ version = "1.2.0" [[deps.GeneralUtils]] deps = ["AWS", "AWSS3", "CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "Graphs", "HTTP", "JSON", "LibPQ", "NATS", "OrderedCollections", "PrettyPrinting", "Random", "Revise", "SHA", "StringDistances", "URIs", "UUIDs"] -git-tree-sha1 = "deb2a19735d937b6265d18f55ce0788ac574e9c7" +git-tree-sha1 = "4d7ff3b02a9cbd6d4f65fea43551b0bca6a83a22" repo-rev = "main" repo-url = "https://git.yiem.cc/ton/GeneralUtils" uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe" -version = "0.6.6" +version = "0.6.9" [[deps.GitHub]] deps = ["Base64", "Dates", "HTTP", "JSON", "MbedTLS", "Sockets", "SodiumSeal", "URIs"] @@ -1126,7 +1126,7 @@ uuid = "228000da-037f-5747-90a9-8195ccbf91a5" version = "0.4.2" [[deps.YiemAgent]] -deps = ["Base64", "CSV", "DataFrames", "DataStructures", "Dates", "GeneralUtils", "HTTP", "JSON", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "Serialization", "URIs", "UUIDs"] +deps = ["Base64", "CSV", "DataFrames", "DataStructures", "Dates", "HTTP", "JSON", "LibPQ", "NATS", "PrettyPrinting", "Random", "Revise", "Serialization", "URIs", "UUIDs", "msghandler"] path = "." uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2" version = "0.8.0" diff --git a/Project.toml b/Project.toml index 62dde12..3f8466f 100644 --- a/Project.toml +++ b/Project.toml @@ -26,7 +26,7 @@ msghandler = "f2724d33-f338-4a57-b9f8-1be882570d10" Base64 = "1.11.0" CSV = "0.10.15" DataFrames = "1.7.0" -GeneralUtils = "0.6.6" +GeneralUtils = "0.6.9" HTTP = "2.4.0" JSON = "1.6.1" NATS = "0.1.0" diff --git a/my_AI_prompt.md b/my_AI_prompt.md index b9d0f8b..2ad6765 100644 --- a/my_AI_prompt.md +++ b/my_AI_prompt.md @@ -1 +1,94 @@ -read this code \ No newline at end of file +can you make sure yiemAgent use correct format when contacting MCP server. you can check MCP server doc at /home/ton/docker-apps/sommpanion/mcp/docs/specification.md? + +smartpack() and smartunpack() doc is at /home/ton/docker-apps/sommpanion/msghandler/README.md + +user code that use yiemAgent is here /home/ton/docker-apps/sommpanion/agent-backend/test/runtest.jl + +this is log folder that logs when I ran yiemAgent. you can check /home/ton/docker-apps/sommpanion/agent-backend/log/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/agentCore.jl b/src/agentCore.jl index b587e67..d49bb32 100644 --- a/src/agentCore.jl +++ b/src/agentCore.jl @@ -114,6 +114,27 @@ function yiemAgent( followUp = Channel(32) outputChannel = Channel(16) + if mcpServer === nothing + """ please implement the following MCP server interface + struct mcpServer + natsConn::NATS.Connection + topic::String + senderID::String + end + + # MCP protocol: tools/list — returns tool definitions (JSON-RPC 2.0) + # check input and output format from MCP server doc + function (mcp::mcpServer)(method::String, sessionId::String)::AbstractDict{String, <:Any} + error("please implement MCP server tools/list interface") + end + + # MCP protocol: tools/call — executes a tool (JSON-RPC 2.0) + function (mcp::mcpServer)(method::String, toolName::String, arguments::Dict{String, Any}, + sessionId::String)::AbstractDict{String, <:Any} + error("please implement MCP server interface") + end + """ + end tools = OrderedDict{String, agentTool}() registerAllTools(tools, mcpServer; eventSink=eventSink, sessionId=sessionId) @@ -211,18 +232,18 @@ function _agentLoop(agent::yiemAgent) while true while newUserMsg === nothing if isready(agent.inputChannel) - agent.eventSink("_agentLoop 1 agent._state.messages length $(length(agent._state.messages))") + agent.eventSink("_agentLoop 1 agent._state.messages length $(length(agent._state.messages))"; publish=false, log=true) # agent process new user msg immediately after the current tool call finished. newUserMsg = take!(agent.inputChannel) - agent.eventSink("new user msg") + agent.eventSink("new user msg"; publish=false, log=true) else # check followUp message after _processMessage() is done if typeof(processingTask) == Task && istaskdone(processingTask) == true - agent.eventSink("_agentLoop 2 agent._state.messages length $(length(agent._state.messages))") + agent.eventSink("_agentLoop 2 agent._state.messages length $(length(agent._state.messages))"; publish=false, log=true) # if agent runs is done but followUpChannel has messages, # put new message in inputChannel instead if isready(agent.followUpChannel) - agent.eventSink("_agentLoop 3 agent._state.messages length $(length(agent._state.messages))") + agent.eventSink("_agentLoop 3 agent._state.messages length $(length(agent._state.messages))"; publish=false, log=true) while isready(agent.followUpChannel) followUpMsg = take!(agent.followUpChannel) put!(agent.inputChannel, followUpMsg) @@ -230,18 +251,18 @@ function _agentLoop(agent::yiemAgent) processingTask = nothing # reset result = nothing # reset else # _processMessage() done and no followUp message. - agent.eventSink("_agentLoop 4 agent._state.messages length $(length(agent._state.messages))") + agent.eventSink("_agentLoop 4 agent._state.messages length $(length(agent._state.messages))"; publish=false, log=true) result = deepcopy(agent._state.messages[end]) - agent.eventSink("_agentLoop 4-1 ") + agent.eventSink("_agentLoop 4-1 "; publish=false, log=true) # filter out reasoningContent in-place filter!(c -> !(c isa reasoningContent), result.content) - agent.eventSink("_agentLoop 5 ") + agent.eventSink("_agentLoop 5 "; publish=false, log=true) # format output respondToUI = _assistantMessageToOpenAI(result) - agent.eventSink("_agentLoop 6 ") + agent.eventSink("_agentLoop 6 "; publish=false, log=true) put!(agent.outputChannel, respondToUI) if !isempty(result.content) && result.content[1] isa textContent - agent.eventSink(result.content[1].text) + agent.eventSink(result.content[1].text; publish=false, log=true) end processingTask = nothing # reset result = nothing # reset @@ -272,7 +293,7 @@ function _agentLoop(agent::yiemAgent) else # spawn new _processMessage() if it is not already running. if processingTask === nothing - agent.eventSink("_agentLoop 5 agent._state.messages length $(length(agent._state.messages))") + agent.eventSink("_agentLoop 5 agent._state.messages length $(length(agent._state.messages))"; publish=false, log=true) # discard all messages in followUpChannel while isready(agent.followUpChannel) _ = take!(agent.followUpChannel) @@ -303,7 +324,7 @@ function _agentLoop(agent::yiemAgent) showerror(io, e, bt) println(io) end - eventSink(errMsg) + eventSink(errMsg; publish=false, log=true) # On any error, send error response and exit the loop @error "Agent loop failed" error=e @@ -351,7 +372,7 @@ function _processMessage( afterToolCall::Union{Function, Nothing}, parallelToolExecute::Bool, )::Nothing - eventSink("_processMessage 1 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 1 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) # loop until llmCall() response didn't use tool calls final_response = nothing @@ -372,43 +393,43 @@ function _processMessage( try # Drain inputChannel and convert OpenAI-format messages to userMessage type while isready(inputChannel) - eventSink("_processMessage 2 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 2 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) newUserMsg_openai = take!(inputChannel) - eventSink("_processMessage 3 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 3 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) if newUserMsg_openai === :shutdown - eventSink("_processMessage 4 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 4 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) # Re-emit shutdown signal for the loop to handle put!(inputChannel, :shutdown) break end - eventSink("_processMessage 5 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 5 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) newUserMsg = OpenAiToUserMessage(newUserMsg_openai) push!(agentMsgHistory, newUserMsg) - eventSink("_processMessage 6 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 6 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) end - eventSink("_processMessage 7 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 7 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) # call prepareContext() state = agentState(systemPrompt, nothing, tools, agentMsgHistory) - eventSink("_processMessage 8 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 8 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) preparedContext = prepareContext(state, eventSink) - eventSink("_processMessage 9 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 9 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) # Call formatMessagesForLLM() to format for LLM formattedMessages = formatMessagesForLLM(preparedContext, eventSink) - eventSink("_processMessage 10 formattedMessages $formattedMessages") + eventSink("_processMessage 10 formattedMessages $formattedMessages"; publish=false, log=true) """ 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("LLM response " * string(response)) + eventSink("LLM response " * string(response); publish=false, log=true) - eventSink("_processMessage 11 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 11 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) # Extract tool calls from LLM response content blocks hasToolCalls, toolCallList, assistant_msg = _extractToolCalls(response) - eventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList _state.messages length $(length(agentMsgHistory))") - eventSink("assistant_msg " * string(assistant_msg)) - eventSink("_processMessage 11-1") + eventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) + eventSink("assistant_msg " * string(assistant_msg); publish=false, log=true) + eventSink("_processMessage 11-1"; publish=false, log=true) # Add assistant message (tool calls or text) to history for next LLM turn push!(agentMsgHistory, assistant_msg) @@ -423,21 +444,21 @@ function _processMessage( ) signal = abortSignal(false) - eventSink("_processMessage 12 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 12 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) # call executeToolCalls() toolResultBatch = executeToolCalls(preparedContext, assistant_msg, toolCallList, config, signal, eventSink) - eventSink("_processMessage 13 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 13 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) # save toolResults to messages for toolResult in toolResultBatch.messages - eventSink("toolResult " * string(toolResult)) + eventSink("toolResult " * string(toolResult); publish=false, log=true) push!(agentMsgHistory, toolResult) end - eventSink("_processMessage 14 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 14 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) if toolResultBatch.terminate - eventSink("_processMessage 15 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 15 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) # If toolResultBatch requested termination, build a final response final_content = [textContent("Tool execution completed.")] for toolResult in toolResultBatch.messages @@ -451,7 +472,7 @@ function _processMessage( end end end - eventSink("_processMessage 16 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 16 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) final_response = assistantMessage( role="assistant", content=final_content, @@ -470,7 +491,7 @@ function _processMessage( break end else - eventSink("_processMessage 17 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 17 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) # LLM did not use tool calls — break end @@ -481,12 +502,12 @@ function _processMessage( println(io) end - eventSink(err_msg) + eventSink(err_msg; publish=false, log=true) end end - eventSink("_processMessage 18 _state.messages length $(length(agentMsgHistory))") + eventSink("_processMessage 18 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true) return nothing end @@ -926,46 +947,46 @@ function prepareToolCall( signal::abortSignal, eventSink )::Union{preparedToolCall,immediateOutcome} - eventSink("prepareToolCall 1") + eventSink("prepareToolCall 1"; publish=false, log=true) # pick a called tool from tool store tool = get(context.tools, toolCall.name, nothing) if tool === nothing - eventSink("prepareToolCall 2") + eventSink("prepareToolCall 2"; publish=false, log=true) return immediateOutcome(createErrorToolResult("Tool $toolCall.name not found"), true) end try - eventSink("prepareToolCall 3") + eventSink("prepareToolCall 3"; publish=false, log=true) # 1. prepare arguments (tool-specific transform) prepared = prepareToolCallArguments(tool, toolCall) - eventSink("prepared " * string(prepared.arguments)) - eventSink("prepareToolCall 4") + eventSink("prepared " * string(prepared.arguments); publish=false, log=true) + eventSink("prepareToolCall 4"; publish=false, log=true) validatedArgs = validateToolArguments(tool, prepared) - eventSink("validatedArgs " * string(validatedArgs)) - eventSink("prepareToolCall 5") + eventSink("validatedArgs " * string(validatedArgs); publish=false, log=true) + eventSink("prepareToolCall 5"; publish=false, log=true) # 2. beforeToolCall hook — can block if config.beforeToolCall !== nothing - eventSink("prepareToolCall 6") + eventSink("prepareToolCall 6"; publish=false, log=true) before = config.beforeToolCall( beforeToolCallContext(assistantMsg, toolCall, validatedArgs, context), signal ) - eventSink("prepareToolCall 7") + eventSink("prepareToolCall 7"; publish=false, log=true) if signal.aborted - eventSink("prepareToolCall 8") + eventSink("prepareToolCall 8"; publish=false, log=true) return immediateOutcome(createErrorToolResult("Operation aborted"), true) end if before !== nothing && before.block - eventSink("prepareToolCall 9") + eventSink("prepareToolCall 9"; publish=false, log=true) return immediateOutcome( createErrorToolResult(get(before, :reason, "Tool execution was blocked")), true) end end - eventSink("prepareToolCall 10") + eventSink("prepareToolCall 10"; publish=false, log=true) return preparedToolCall(tool, toolCall, validatedArgs) catch e bt = catch_backtrace() @@ -974,7 +995,7 @@ function prepareToolCall( println(io) end - eventSink(errMsg) + eventSink(errMsg; publish=false, log=true) return immediateOutcome(createErrorToolResult(sprint(showerror, e)), true) end @@ -1026,13 +1047,13 @@ function executePreparedToolCall( signal::Union{Nothing,abortSignal}, eventSink, )::executedOutcome - eventSink("executePreparedToolCall 1") + eventSink("executePreparedToolCall 1"; publish=false, log=true) try #WORKING pass sessionId from yiemAgent to tool call result = prep.tool.execute(prep.toolCall.id, prep.args, signal, eventSink) - eventSink("executePreparedToolCall 2") - eventSink("tool execute raw result: " * sprint(show, result)) - eventSink("executePreparedToolCall 3") + eventSink("executePreparedToolCall 2"; publish=false, log=true) + eventSink("tool execute raw result: " * sprint(show, result); publish=false, log=true) + eventSink("executePreparedToolCall 3"; publish=false, log=true) return executedOutcome(result, false) catch e bt = catch_backtrace() @@ -1040,7 +1061,7 @@ function executePreparedToolCall( showerror(io, e, bt) println(io) end - eventSink(errMsg) + eventSink(errMsg; publish=false, log=true) return executedOutcome(createErrorToolResult(sprint(showerror, e)), true) end @@ -1112,17 +1133,17 @@ function finalizeExecutedToolCall( signal::Union{Nothing,abortSignal}, eventSink )::finalizedOutcome - eventSink("finalizeExecutedToolCall 1") + eventSink("finalizeExecutedToolCall 1"; publish=false, log=true) result = executed.result isError = executed.isError - eventSink("finalizeExecutedToolCall 2") + eventSink("finalizeExecutedToolCall 2"; publish=false, log=true) if config.afterToolCall !== nothing try after = config.afterToolCall( afterToolCallContext(assistantMsg, prep.toolCall, prep.args, result, isError, context), signal ) - eventSink("finalizeExecutedToolCall 3") + eventSink("finalizeExecutedToolCall 3"; publish=false, log=true) if after !== nothing result = merge(result, dict(:content=>get(after,:content,result.content), :details=>get(after,:details,result.details), @@ -1136,13 +1157,13 @@ function finalizeExecutedToolCall( showerror(io, e, bt) println(io) end - eventSink(errMsg) + eventSink(errMsg; publish=false, log=true) result = createErrorToolResult(sprint(showerror, e)) isError = true end end - eventSink("finalizeExecutedToolCall 4") + eventSink("finalizeExecutedToolCall 4"; publish=false, log=true) return finalizedOutcome(prep.toolCall, result, isError) end @@ -1208,39 +1229,39 @@ function executeToolCallsSequential( signal::abortSignal, eventSink, )::agentToolCallBatch - eventSink("executeToolCallsSequential 1") + eventSink("executeToolCallsSequential 1"; publish=false, log=true) finalizedCalls = finalizedOutcome[] messages = toolResultMessage[] for tc in toolCalls - eventSink("start tool execute $(tc.id), $(tc.name), $(tc.arguments)") + eventSink("start tool execute $(tc.id), $(tc.name), $(tc.arguments)"; publish=false, log=true) prep = prepareToolCall(context, assistantMsg, tc, config, signal, eventSink) - eventSink("executeToolCallsSequential " * string(prep.args)) + eventSink("executeToolCallsSequential " * string(prep.args); publish=false, log=true) if prep isa immediateOutcome - eventSink("executeToolCallsSequential 2-1") + eventSink("executeToolCallsSequential 2-1"; publish=false, log=true) finalized = finalizedOutcome(tc, prep.result, prep.isError) - eventSink("executeToolCallsSequential 2-2") + eventSink("executeToolCallsSequential 2-2"; publish=false, log=true) else - eventSink("executeToolCallsSequential 3") + eventSink("executeToolCallsSequential 3"; publish=false, log=true) executed = executePreparedToolCall(prep, signal, eventSink) - eventSink("executeToolCallsSequential 3-1") + eventSink("executeToolCallsSequential 3-1"; publish=false, log=true) finalized = finalizeExecutedToolCall(context, assistantMsg, prep, executed, config, signal, eventSink) - eventSink("executeToolCallsSequential 3-2") + eventSink("executeToolCallsSequential 3-2"; publish=false, log=true) end - eventSink("executeToolCallsSequential 4") + eventSink("executeToolCallsSequential 4"; publish=false, log=true) eventSink("finalized $(finalized.toolCall.id), $(finalized.toolCall.name), - $(finalized.result), $(finalized.isError)") + $(finalized.result), $(finalized.isError)"; publish=false, log=true) push!(messages, createToolResultMessage(finalized)) push!(finalizedCalls, finalized) - eventSink("executeToolCallsSequential 5") + eventSink("executeToolCallsSequential 5"; publish=false, log=true) if signal !== nothing && signal.aborted break end end - eventSink("executeToolCallsSequential 6") + eventSink("executeToolCallsSequential 6"; publish=false, log=true) return agentToolCallBatch(messages, shouldTerminate(finalizedCalls)) end @@ -1313,7 +1334,7 @@ function executeToolCallsParallel( entries = Union{finalizedOutcome,Task}[] for tc in toolCalls - eventSink(toolExecStartEvent(tc.id, tc.name, tc.arguments)) + eventSink(toolExecStartEvent(tc.id, tc.name, tc.arguments); publish=false, log=true) prep = prepareToolCall(context, assistantMsg, tc, config, signal, eventSink) @@ -1412,7 +1433,7 @@ function executeToolCalls( eventSink, )::agentToolCallBatch - eventSink("_executeToolCalls 1") + eventSink("_executeToolCalls 1"; publish=false, log=true) hasSequential = false for tc in toolCalls t = get(context.tools, tc.name, nothing) @@ -1421,13 +1442,13 @@ function executeToolCalls( break end end - eventSink("_executeToolCalls 2") + eventSink("_executeToolCalls 2"; publish=false, log=true) if config.toolExecution == "sequential" || hasSequential - eventSink("_executeToolCalls 3") + eventSink("_executeToolCalls 3"; publish=false, log=true) return executeToolCallsSequential(context, assistantMsg, toolCalls, config, signal, eventSink) else - eventSink("_executeToolCalls 4") + eventSink("_executeToolCalls 4"; publish=false, log=true) return executeToolCallsParallel(context, assistantMsg, toolCalls, config, signal, eventSink) end @@ -1563,11 +1584,6 @@ end - - - - - diff --git a/src/api.jl b/src/api.jl index 2d06848..13d6f4e 100644 --- a/src/api.jl +++ b/src/api.jl @@ -39,7 +39,7 @@ function promptAgent(agent::yiemAgent, msg::AbstractDict{String, Any}) end function promptAgent(agent::yiemAgent, text::String) - put!(agent.inputChannel, Dict("role" => "user", "content" => text)) + put!(agent.inputChannel, Dict("role" => "user", "content" => [Dict("type" => "text", "text" => text)])) return agent end @@ -97,7 +97,7 @@ function followUp(agent::yiemAgent, msg::AbstractDict{String, Any}) end function followUp(agent::yiemAgent, text::String) - put!(agent.followUpChannel, Dict("role" => "user", "content" => text)) + put!(agent.followUpChannel, Dict("role" => "user", "content" => [Dict("type" => "text", "text" => text)])) return agent end diff --git a/src/utils.jl b/src/utils.jl index 9c4c52c..73349e0 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -3,7 +3,7 @@ module utils export clearhistory, availableWineToText, prepareContext, formatMsgForLLM, validateRequiredArgs, validateToolArguments, _userMessageToOpenAI, _assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks, _toolsToOpenAI, - beforeToolCall, afterToolCall, eventSink + beforeToolCall, afterToolCall using UUIDs, Dates, DataStructures, HTTP, JSON, NATS using GeneralUtils @@ -310,7 +310,7 @@ Common use cases: - `agentToolResult(...)` to return a modified result (content, details, usage, terminate flag can all be overridden). -""" +""" #WORKING arrange result from searchWine tool function afterToolCall(context::afterToolCallContext, signal::abortSignal )::Union{agentToolResult, Nothing} @@ -319,108 +319,6 @@ function afterToolCall(context::afterToolCallContext, signal::abortSignal return nothing end - -""" -Create an event sink for logging messages and/or publishing to NATS. - -# Fields -- `natsConn`: NATS connection, or `nothing` for file-only sink -- `topic`: NATS topic to publish to, or `nothing` for file-only sink -- `service_name`: Optional prefix added to log entries -- `log_file`: Path to the log file -- `max_log_entries`: Maximum number of entries to keep in the log file - -# Callable interface -```julia - (es::eventSink)(msg::String; log=true, publish=true) -> Nothing -``` -Write a timestamped log entry to file, publish to NATS, or both. - -# Examples -```julia -using NATS - -conn = NATS.connect() - -# With NATS — logs to file and publishes to "agent.events" -es = eventSink(natsConn=conn, topic="agent.events", service_name="agent1") -es("agent started") # both file + NATS (default) -es("task done"; log=false) # NATS only -es("checkpoint"; publish=false) # file only - -# File only — no NATS connection -es2 = eventSink(log_file="./agent.log", service_name="worker") -es2("ready") # file only - -NATS.drain(conn) -``` -""" -struct eventSink - natsConn::Union{NATS.Connection, Nothing} - topic::Union{String, Nothing} - service_name::String - log_file::String - max_log_entries::Int -end - -eventSink(; natsConn=nothing, topic=nothing, service_name="", log_file="./log/eventSink.md", max_log_entries=100) = - eventSink(natsConn, topic, service_name, log_file, max_log_entries) - -""" - rotate!(es::eventSink) -> Nothing - -Rewrite the log file keeping only the last `max_log_entries` entries. -Call after writing when the entry count reaches the limit. -""" -function rotate!(es::eventSink) - content = read(es.log_file, String) - entries = split(content, r"- \[", keepempty=false) - open(es.log_file, "w") do f - write(f, "- [") - for e in entries[2:end] - write(f, "- [", e) - end - end -end - -""" - (es::eventSink)(msg::String; log=true, publish=true) -> Nothing - -Write a timestamped log entry to file, publish to NATS, or both. - -# Arguments -- `msg`: The message to log or publish. -- `log`: If `true`, append the entry to `es.log_file` (default `true`). -- `publish`: If `true`, publish to the NATS topic (default `true`; no-op if no NATS connection was provided). -""" -function (es::eventSink)(msg::String; log::Bool=true, publish::Bool=true) - timestamp = Dates.format(now(), "yyyy-mm-ddTHH:MM:SS.sss") - svc_prefix = isempty(es.service_name) ? "" : "[$(es.service_name)] " - entry = "- [$timestamp] $svc_prefix$msg\n" - if log - log_dir = dirname(es.log_file) - mkpath(log_dir) - open(es.log_file, "a") do f - write(f, entry) - end - # Rotate after write — keep only last max_log_entries entries - content = read(es.log_file, String) - entries = split(content, r"- \[", keepempty=false) - if length(entries) > es.max_log_entries - rotate!(es) - end - end - if publish && es.topic !== nothing && es.natsConn !== nothing - try - NATS.publish(es.natsConn, es.topic, entry) - catch e - @warn "eventSink NATS publish failed" exception=e - end - end -end - - - """ Convert a userMessage to OpenAI message format. """