update
This commit is contained in:
+4
-4
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
julia_version = "1.12.7"
|
julia_version = "1.12.7"
|
||||||
manifest_format = "2.0"
|
manifest_format = "2.0"
|
||||||
project_hash = "eeddbbbbefd50bd46b098a2d54b2795d85daa76e"
|
project_hash = "43252cf86e6030e836ede74c563b58ab2f0f0548"
|
||||||
|
|
||||||
[[deps.AWS]]
|
[[deps.AWS]]
|
||||||
deps = ["Base64", "Compat", "Dates", "Downloads", "GitHub", "HTTP", "IniFile", "JSON", "MD5", "Mocking", "OrderedCollections", "Random", "SHA", "ScopedValues", "Sockets", "URIs", "UUIDs", "XMLDict"]
|
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.GeneralUtils]]
|
||||||
deps = ["AWS", "AWSS3", "CSV", "DataFrames", "DataStructures", "Dates", "Distributions", "Graphs", "HTTP", "JSON", "LibPQ", "NATS", "OrderedCollections", "PrettyPrinting", "Random", "Revise", "SHA", "StringDistances", "URIs", "UUIDs"]
|
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-rev = "main"
|
||||||
repo-url = "https://git.yiem.cc/ton/GeneralUtils"
|
repo-url = "https://git.yiem.cc/ton/GeneralUtils"
|
||||||
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe"
|
||||||
version = "0.6.6"
|
version = "0.6.9"
|
||||||
|
|
||||||
[[deps.GitHub]]
|
[[deps.GitHub]]
|
||||||
deps = ["Base64", "Dates", "HTTP", "JSON", "MbedTLS", "Sockets", "SodiumSeal", "URIs"]
|
deps = ["Base64", "Dates", "HTTP", "JSON", "MbedTLS", "Sockets", "SodiumSeal", "URIs"]
|
||||||
@@ -1126,7 +1126,7 @@ uuid = "228000da-037f-5747-90a9-8195ccbf91a5"
|
|||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|
||||||
[[deps.YiemAgent]]
|
[[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 = "."
|
path = "."
|
||||||
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
uuid = "e012c34b-7f78-48e0-971c-7abb83b6f0a2"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ msghandler = "f2724d33-f338-4a57-b9f8-1be882570d10"
|
|||||||
Base64 = "1.11.0"
|
Base64 = "1.11.0"
|
||||||
CSV = "0.10.15"
|
CSV = "0.10.15"
|
||||||
DataFrames = "1.7.0"
|
DataFrames = "1.7.0"
|
||||||
GeneralUtils = "0.6.6"
|
GeneralUtils = "0.6.9"
|
||||||
HTTP = "2.4.0"
|
HTTP = "2.4.0"
|
||||||
JSON = "1.6.1"
|
JSON = "1.6.1"
|
||||||
NATS = "0.1.0"
|
NATS = "0.1.0"
|
||||||
|
|||||||
+94
-1
@@ -1 +1,94 @@
|
|||||||
read this code
|
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/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+96
-80
@@ -114,6 +114,27 @@ function yiemAgent(
|
|||||||
followUp = Channel(32)
|
followUp = Channel(32)
|
||||||
outputChannel = Channel(16)
|
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}()
|
tools = OrderedDict{String, agentTool}()
|
||||||
registerAllTools(tools, mcpServer; eventSink=eventSink, sessionId=sessionId)
|
registerAllTools(tools, mcpServer; eventSink=eventSink, sessionId=sessionId)
|
||||||
@@ -211,18 +232,18 @@ function _agentLoop(agent::yiemAgent)
|
|||||||
while true
|
while true
|
||||||
while newUserMsg === nothing
|
while newUserMsg === nothing
|
||||||
if isready(agent.inputChannel)
|
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.
|
# agent process new user msg immediately after the current tool call finished.
|
||||||
newUserMsg = take!(agent.inputChannel)
|
newUserMsg = take!(agent.inputChannel)
|
||||||
agent.eventSink("new user msg")
|
agent.eventSink("new user msg"; publish=false, log=true)
|
||||||
else
|
else
|
||||||
# check followUp message after _processMessage() is done
|
# check followUp message after _processMessage() is done
|
||||||
if typeof(processingTask) == Task && istaskdone(processingTask) == true
|
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,
|
# if agent runs is done but followUpChannel has messages,
|
||||||
# put new message in inputChannel instead
|
# put new message in inputChannel instead
|
||||||
if isready(agent.followUpChannel)
|
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)
|
while isready(agent.followUpChannel)
|
||||||
followUpMsg = take!(agent.followUpChannel)
|
followUpMsg = take!(agent.followUpChannel)
|
||||||
put!(agent.inputChannel, followUpMsg)
|
put!(agent.inputChannel, followUpMsg)
|
||||||
@@ -230,18 +251,18 @@ function _agentLoop(agent::yiemAgent)
|
|||||||
processingTask = nothing # reset
|
processingTask = nothing # reset
|
||||||
result = nothing # reset
|
result = nothing # reset
|
||||||
else # _processMessage() done and no followUp message.
|
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])
|
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 out reasoningContent in-place
|
||||||
filter!(c -> !(c isa reasoningContent), result.content)
|
filter!(c -> !(c isa reasoningContent), result.content)
|
||||||
agent.eventSink("_agentLoop 5 ")
|
agent.eventSink("_agentLoop 5 "; publish=false, log=true)
|
||||||
# format output
|
# format output
|
||||||
respondToUI = _assistantMessageToOpenAI(result)
|
respondToUI = _assistantMessageToOpenAI(result)
|
||||||
agent.eventSink("_agentLoop 6 ")
|
agent.eventSink("_agentLoop 6 "; publish=false, log=true)
|
||||||
put!(agent.outputChannel, respondToUI)
|
put!(agent.outputChannel, respondToUI)
|
||||||
if !isempty(result.content) && result.content[1] isa textContent
|
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
|
end
|
||||||
processingTask = nothing # reset
|
processingTask = nothing # reset
|
||||||
result = nothing # reset
|
result = nothing # reset
|
||||||
@@ -272,7 +293,7 @@ function _agentLoop(agent::yiemAgent)
|
|||||||
else
|
else
|
||||||
# spawn new _processMessage() if it is not already running.
|
# spawn new _processMessage() if it is not already running.
|
||||||
if processingTask === nothing
|
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
|
# discard all messages in followUpChannel
|
||||||
while isready(agent.followUpChannel)
|
while isready(agent.followUpChannel)
|
||||||
_ = take!(agent.followUpChannel)
|
_ = take!(agent.followUpChannel)
|
||||||
@@ -303,7 +324,7 @@ function _agentLoop(agent::yiemAgent)
|
|||||||
showerror(io, e, bt)
|
showerror(io, e, bt)
|
||||||
println(io)
|
println(io)
|
||||||
end
|
end
|
||||||
eventSink(errMsg)
|
eventSink(errMsg; publish=false, log=true)
|
||||||
|
|
||||||
# On any error, send error response and exit the loop
|
# On any error, send error response and exit the loop
|
||||||
@error "Agent loop failed" error=e
|
@error "Agent loop failed" error=e
|
||||||
@@ -351,7 +372,7 @@ function _processMessage(
|
|||||||
afterToolCall::Union{Function, Nothing},
|
afterToolCall::Union{Function, Nothing},
|
||||||
parallelToolExecute::Bool,
|
parallelToolExecute::Bool,
|
||||||
)::Nothing
|
)::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
|
# loop until llmCall() response didn't use tool calls
|
||||||
final_response = nothing
|
final_response = nothing
|
||||||
|
|
||||||
@@ -372,43 +393,43 @@ function _processMessage(
|
|||||||
try
|
try
|
||||||
# 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)
|
||||||
eventSink("_processMessage 2 _state.messages length $(length(agentMsgHistory))")
|
eventSink("_processMessage 2 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
|
||||||
newUserMsg_openai = take!(inputChannel)
|
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
|
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
|
# Re-emit shutdown signal for the loop to handle
|
||||||
put!(inputChannel, :shutdown)
|
put!(inputChannel, :shutdown)
|
||||||
break
|
break
|
||||||
end
|
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)
|
newUserMsg = OpenAiToUserMessage(newUserMsg_openai)
|
||||||
push!(agentMsgHistory, newUserMsg)
|
push!(agentMsgHistory, newUserMsg)
|
||||||
eventSink("_processMessage 6 _state.messages length $(length(agentMsgHistory))")
|
eventSink("_processMessage 6 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
|
||||||
end
|
end
|
||||||
eventSink("_processMessage 7 _state.messages length $(length(agentMsgHistory))")
|
eventSink("_processMessage 7 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
|
||||||
# call prepareContext()
|
# call prepareContext()
|
||||||
state = agentState(systemPrompt, nothing, tools, agentMsgHistory)
|
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)
|
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
|
# Call formatMessagesForLLM() to format for LLM
|
||||||
formattedMessages = formatMessagesForLLM(preparedContext, eventSink)
|
formattedMessages = formatMessagesForLLM(preparedContext, eventSink)
|
||||||
|
|
||||||
eventSink("_processMessage 10 formattedMessages $formattedMessages")
|
eventSink("_processMessage 10 formattedMessages $formattedMessages"; publish=false, log=true)
|
||||||
|
|
||||||
""" 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)
|
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
|
# Extract tool calls from LLM response content blocks
|
||||||
hasToolCalls, toolCallList, assistant_msg = _extractToolCalls(response)
|
hasToolCalls, toolCallList, assistant_msg = _extractToolCalls(response)
|
||||||
eventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList _state.messages length $(length(agentMsgHistory))")
|
eventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
|
||||||
eventSink("assistant_msg " * string(assistant_msg))
|
eventSink("assistant_msg " * string(assistant_msg); publish=false, log=true)
|
||||||
eventSink("_processMessage 11-1")
|
eventSink("_processMessage 11-1"; publish=false, log=true)
|
||||||
|
|
||||||
# Add assistant message (tool calls or text) to history for next LLM turn
|
# Add assistant message (tool calls or text) to history for next LLM turn
|
||||||
push!(agentMsgHistory, assistant_msg)
|
push!(agentMsgHistory, assistant_msg)
|
||||||
@@ -423,21 +444,21 @@ function _processMessage(
|
|||||||
)
|
)
|
||||||
|
|
||||||
signal = abortSignal(false)
|
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()
|
# call executeToolCalls()
|
||||||
toolResultBatch = executeToolCalls(preparedContext, assistant_msg, toolCallList, config,
|
toolResultBatch = executeToolCalls(preparedContext, assistant_msg, toolCallList, config,
|
||||||
signal, eventSink)
|
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
|
# save toolResults to messages
|
||||||
for toolResult in toolResultBatch.messages
|
for toolResult in toolResultBatch.messages
|
||||||
eventSink("toolResult " * string(toolResult))
|
eventSink("toolResult " * string(toolResult); publish=false, log=true)
|
||||||
push!(agentMsgHistory, toolResult)
|
push!(agentMsgHistory, toolResult)
|
||||||
end
|
end
|
||||||
eventSink("_processMessage 14 _state.messages length $(length(agentMsgHistory))")
|
eventSink("_processMessage 14 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
|
||||||
if toolResultBatch.terminate
|
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
|
# If toolResultBatch requested termination, build a final response
|
||||||
final_content = [textContent("Tool execution completed.")]
|
final_content = [textContent("Tool execution completed.")]
|
||||||
for toolResult in toolResultBatch.messages
|
for toolResult in toolResultBatch.messages
|
||||||
@@ -451,7 +472,7 @@ function _processMessage(
|
|||||||
end
|
end
|
||||||
end
|
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(
|
final_response = assistantMessage(
|
||||||
role="assistant",
|
role="assistant",
|
||||||
content=final_content,
|
content=final_content,
|
||||||
@@ -470,7 +491,7 @@ function _processMessage(
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
else
|
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 —
|
# LLM did not use tool calls —
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -481,12 +502,12 @@ function _processMessage(
|
|||||||
println(io)
|
println(io)
|
||||||
end
|
end
|
||||||
|
|
||||||
eventSink(err_msg)
|
eventSink(err_msg; publish=false, log=true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
eventSink("_processMessage 18 _state.messages length $(length(agentMsgHistory))")
|
eventSink("_processMessage 18 _state.messages length $(length(agentMsgHistory))"; publish=false, log=true)
|
||||||
return nothing
|
return nothing
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -926,46 +947,46 @@ function prepareToolCall(
|
|||||||
signal::abortSignal,
|
signal::abortSignal,
|
||||||
eventSink
|
eventSink
|
||||||
)::Union{preparedToolCall,immediateOutcome}
|
)::Union{preparedToolCall,immediateOutcome}
|
||||||
eventSink("prepareToolCall 1")
|
eventSink("prepareToolCall 1"; publish=false, log=true)
|
||||||
|
|
||||||
# pick a called tool from tool store
|
# pick a called tool from tool store
|
||||||
tool = get(context.tools, toolCall.name, nothing)
|
tool = get(context.tools, toolCall.name, nothing)
|
||||||
|
|
||||||
if tool === nothing
|
if tool === nothing
|
||||||
eventSink("prepareToolCall 2")
|
eventSink("prepareToolCall 2"; publish=false, log=true)
|
||||||
return immediateOutcome(createErrorToolResult("Tool $toolCall.name not found"), true)
|
return immediateOutcome(createErrorToolResult("Tool $toolCall.name not found"), true)
|
||||||
end
|
end
|
||||||
|
|
||||||
try
|
try
|
||||||
eventSink("prepareToolCall 3")
|
eventSink("prepareToolCall 3"; publish=false, log=true)
|
||||||
# 1. prepare arguments (tool-specific transform)
|
# 1. prepare arguments (tool-specific transform)
|
||||||
prepared = prepareToolCallArguments(tool, toolCall)
|
prepared = prepareToolCallArguments(tool, toolCall)
|
||||||
eventSink("prepared " * string(prepared.arguments))
|
eventSink("prepared " * string(prepared.arguments); publish=false, log=true)
|
||||||
eventSink("prepareToolCall 4")
|
eventSink("prepareToolCall 4"; publish=false, log=true)
|
||||||
validatedArgs = validateToolArguments(tool, prepared)
|
validatedArgs = validateToolArguments(tool, prepared)
|
||||||
eventSink("validatedArgs " * string(validatedArgs))
|
eventSink("validatedArgs " * string(validatedArgs); publish=false, log=true)
|
||||||
eventSink("prepareToolCall 5")
|
eventSink("prepareToolCall 5"; publish=false, log=true)
|
||||||
# 2. beforeToolCall hook — can block
|
# 2. beforeToolCall hook — can block
|
||||||
if config.beforeToolCall !== nothing
|
if config.beforeToolCall !== nothing
|
||||||
eventSink("prepareToolCall 6")
|
eventSink("prepareToolCall 6"; publish=false, log=true)
|
||||||
|
|
||||||
before = config.beforeToolCall(
|
before = config.beforeToolCall(
|
||||||
beforeToolCallContext(assistantMsg, toolCall, validatedArgs, context),
|
beforeToolCallContext(assistantMsg, toolCall, validatedArgs, context),
|
||||||
signal
|
signal
|
||||||
)
|
)
|
||||||
eventSink("prepareToolCall 7")
|
eventSink("prepareToolCall 7"; publish=false, log=true)
|
||||||
if signal.aborted
|
if signal.aborted
|
||||||
eventSink("prepareToolCall 8")
|
eventSink("prepareToolCall 8"; publish=false, log=true)
|
||||||
return immediateOutcome(createErrorToolResult("Operation aborted"), true)
|
return immediateOutcome(createErrorToolResult("Operation aborted"), true)
|
||||||
end
|
end
|
||||||
|
|
||||||
if before !== nothing && before.block
|
if before !== nothing && before.block
|
||||||
eventSink("prepareToolCall 9")
|
eventSink("prepareToolCall 9"; publish=false, log=true)
|
||||||
return immediateOutcome(
|
return immediateOutcome(
|
||||||
createErrorToolResult(get(before, :reason, "Tool execution was blocked")), true)
|
createErrorToolResult(get(before, :reason, "Tool execution was blocked")), true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
eventSink("prepareToolCall 10")
|
eventSink("prepareToolCall 10"; publish=false, log=true)
|
||||||
return preparedToolCall(tool, toolCall, validatedArgs)
|
return preparedToolCall(tool, toolCall, validatedArgs)
|
||||||
catch e
|
catch e
|
||||||
bt = catch_backtrace()
|
bt = catch_backtrace()
|
||||||
@@ -974,7 +995,7 @@ function prepareToolCall(
|
|||||||
println(io)
|
println(io)
|
||||||
end
|
end
|
||||||
|
|
||||||
eventSink(errMsg)
|
eventSink(errMsg; publish=false, log=true)
|
||||||
|
|
||||||
return immediateOutcome(createErrorToolResult(sprint(showerror, e)), true)
|
return immediateOutcome(createErrorToolResult(sprint(showerror, e)), true)
|
||||||
end
|
end
|
||||||
@@ -1026,13 +1047,13 @@ function executePreparedToolCall(
|
|||||||
signal::Union{Nothing,abortSignal},
|
signal::Union{Nothing,abortSignal},
|
||||||
eventSink,
|
eventSink,
|
||||||
)::executedOutcome
|
)::executedOutcome
|
||||||
eventSink("executePreparedToolCall 1")
|
eventSink("executePreparedToolCall 1"; publish=false, log=true)
|
||||||
|
|
||||||
try #WORKING pass sessionId from yiemAgent to tool call
|
try #WORKING pass sessionId from yiemAgent to tool call
|
||||||
result = prep.tool.execute(prep.toolCall.id, prep.args, signal, eventSink)
|
result = prep.tool.execute(prep.toolCall.id, prep.args, signal, eventSink)
|
||||||
eventSink("executePreparedToolCall 2")
|
eventSink("executePreparedToolCall 2"; publish=false, log=true)
|
||||||
eventSink("tool execute raw result: " * sprint(show, result))
|
eventSink("tool execute raw result: " * sprint(show, result); publish=false, log=true)
|
||||||
eventSink("executePreparedToolCall 3")
|
eventSink("executePreparedToolCall 3"; publish=false, log=true)
|
||||||
return executedOutcome(result, false)
|
return executedOutcome(result, false)
|
||||||
catch e
|
catch e
|
||||||
bt = catch_backtrace()
|
bt = catch_backtrace()
|
||||||
@@ -1040,7 +1061,7 @@ function executePreparedToolCall(
|
|||||||
showerror(io, e, bt)
|
showerror(io, e, bt)
|
||||||
println(io)
|
println(io)
|
||||||
end
|
end
|
||||||
eventSink(errMsg)
|
eventSink(errMsg; publish=false, log=true)
|
||||||
|
|
||||||
return executedOutcome(createErrorToolResult(sprint(showerror, e)), true)
|
return executedOutcome(createErrorToolResult(sprint(showerror, e)), true)
|
||||||
end
|
end
|
||||||
@@ -1112,17 +1133,17 @@ function finalizeExecutedToolCall(
|
|||||||
signal::Union{Nothing,abortSignal},
|
signal::Union{Nothing,abortSignal},
|
||||||
eventSink
|
eventSink
|
||||||
)::finalizedOutcome
|
)::finalizedOutcome
|
||||||
eventSink("finalizeExecutedToolCall 1")
|
eventSink("finalizeExecutedToolCall 1"; publish=false, log=true)
|
||||||
result = executed.result
|
result = executed.result
|
||||||
isError = executed.isError
|
isError = executed.isError
|
||||||
eventSink("finalizeExecutedToolCall 2")
|
eventSink("finalizeExecutedToolCall 2"; publish=false, log=true)
|
||||||
if config.afterToolCall !== nothing
|
if config.afterToolCall !== nothing
|
||||||
try
|
try
|
||||||
after = config.afterToolCall(
|
after = config.afterToolCall(
|
||||||
afterToolCallContext(assistantMsg, prep.toolCall, prep.args, result, isError, context),
|
afterToolCallContext(assistantMsg, prep.toolCall, prep.args, result, isError, context),
|
||||||
signal
|
signal
|
||||||
)
|
)
|
||||||
eventSink("finalizeExecutedToolCall 3")
|
eventSink("finalizeExecutedToolCall 3"; publish=false, log=true)
|
||||||
if after !== nothing
|
if after !== nothing
|
||||||
result = merge(result, dict(:content=>get(after,:content,result.content),
|
result = merge(result, dict(:content=>get(after,:content,result.content),
|
||||||
:details=>get(after,:details,result.details),
|
:details=>get(after,:details,result.details),
|
||||||
@@ -1136,13 +1157,13 @@ function finalizeExecutedToolCall(
|
|||||||
showerror(io, e, bt)
|
showerror(io, e, bt)
|
||||||
println(io)
|
println(io)
|
||||||
end
|
end
|
||||||
eventSink(errMsg)
|
eventSink(errMsg; publish=false, log=true)
|
||||||
|
|
||||||
result = createErrorToolResult(sprint(showerror, e))
|
result = createErrorToolResult(sprint(showerror, e))
|
||||||
isError = true
|
isError = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
eventSink("finalizeExecutedToolCall 4")
|
eventSink("finalizeExecutedToolCall 4"; publish=false, log=true)
|
||||||
return finalizedOutcome(prep.toolCall, result, isError)
|
return finalizedOutcome(prep.toolCall, result, isError)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1208,39 +1229,39 @@ function executeToolCallsSequential(
|
|||||||
signal::abortSignal,
|
signal::abortSignal,
|
||||||
eventSink,
|
eventSink,
|
||||||
)::agentToolCallBatch
|
)::agentToolCallBatch
|
||||||
eventSink("executeToolCallsSequential 1")
|
eventSink("executeToolCallsSequential 1"; publish=false, log=true)
|
||||||
finalizedCalls = finalizedOutcome[]
|
finalizedCalls = finalizedOutcome[]
|
||||||
messages = toolResultMessage[]
|
messages = toolResultMessage[]
|
||||||
|
|
||||||
for tc in toolCalls
|
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)
|
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
|
if prep isa immediateOutcome
|
||||||
eventSink("executeToolCallsSequential 2-1")
|
eventSink("executeToolCallsSequential 2-1"; publish=false, log=true)
|
||||||
finalized = finalizedOutcome(tc, prep.result, prep.isError)
|
finalized = finalizedOutcome(tc, prep.result, prep.isError)
|
||||||
eventSink("executeToolCallsSequential 2-2")
|
eventSink("executeToolCallsSequential 2-2"; publish=false, log=true)
|
||||||
else
|
else
|
||||||
eventSink("executeToolCallsSequential 3")
|
eventSink("executeToolCallsSequential 3"; publish=false, log=true)
|
||||||
executed = executePreparedToolCall(prep, signal, eventSink)
|
executed = executePreparedToolCall(prep, signal, eventSink)
|
||||||
|
|
||||||
eventSink("executeToolCallsSequential 3-1")
|
eventSink("executeToolCallsSequential 3-1"; publish=false, log=true)
|
||||||
finalized = finalizeExecutedToolCall(context, assistantMsg, prep, executed, config,
|
finalized = finalizeExecutedToolCall(context, assistantMsg, prep, executed, config,
|
||||||
signal, eventSink)
|
signal, eventSink)
|
||||||
eventSink("executeToolCallsSequential 3-2")
|
eventSink("executeToolCallsSequential 3-2"; publish=false, log=true)
|
||||||
end
|
end
|
||||||
eventSink("executeToolCallsSequential 4")
|
eventSink("executeToolCallsSequential 4"; publish=false, log=true)
|
||||||
eventSink("finalized $(finalized.toolCall.id), $(finalized.toolCall.name),
|
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!(messages, createToolResultMessage(finalized))
|
||||||
push!(finalizedCalls, finalized)
|
push!(finalizedCalls, finalized)
|
||||||
eventSink("executeToolCallsSequential 5")
|
eventSink("executeToolCallsSequential 5"; publish=false, log=true)
|
||||||
if signal !== nothing && signal.aborted
|
if signal !== nothing && signal.aborted
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
eventSink("executeToolCallsSequential 6")
|
eventSink("executeToolCallsSequential 6"; publish=false, log=true)
|
||||||
return agentToolCallBatch(messages, shouldTerminate(finalizedCalls))
|
return agentToolCallBatch(messages, shouldTerminate(finalizedCalls))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1313,7 +1334,7 @@ function executeToolCallsParallel(
|
|||||||
entries = Union{finalizedOutcome,Task}[]
|
entries = Union{finalizedOutcome,Task}[]
|
||||||
|
|
||||||
for tc in toolCalls
|
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)
|
prep = prepareToolCall(context, assistantMsg, tc, config, signal, eventSink)
|
||||||
|
|
||||||
@@ -1412,7 +1433,7 @@ function executeToolCalls(
|
|||||||
eventSink,
|
eventSink,
|
||||||
)::agentToolCallBatch
|
)::agentToolCallBatch
|
||||||
|
|
||||||
eventSink("_executeToolCalls 1")
|
eventSink("_executeToolCalls 1"; publish=false, log=true)
|
||||||
hasSequential = false
|
hasSequential = false
|
||||||
for tc in toolCalls
|
for tc in toolCalls
|
||||||
t = get(context.tools, tc.name, nothing)
|
t = get(context.tools, tc.name, nothing)
|
||||||
@@ -1421,13 +1442,13 @@ function executeToolCalls(
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
eventSink("_executeToolCalls 2")
|
eventSink("_executeToolCalls 2"; publish=false, log=true)
|
||||||
if config.toolExecution == "sequential" || hasSequential
|
if config.toolExecution == "sequential" || hasSequential
|
||||||
eventSink("_executeToolCalls 3")
|
eventSink("_executeToolCalls 3"; publish=false, log=true)
|
||||||
return executeToolCallsSequential(context, assistantMsg, toolCalls, config, signal,
|
return executeToolCallsSequential(context, assistantMsg, toolCalls, config, signal,
|
||||||
eventSink)
|
eventSink)
|
||||||
else
|
else
|
||||||
eventSink("_executeToolCalls 4")
|
eventSink("_executeToolCalls 4"; publish=false, log=true)
|
||||||
return executeToolCallsParallel(context, assistantMsg, toolCalls, config, signal,
|
return executeToolCallsParallel(context, assistantMsg, toolCalls, config, signal,
|
||||||
eventSink)
|
eventSink)
|
||||||
end
|
end
|
||||||
@@ -1563,11 +1584,6 @@ end
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -39,7 +39,7 @@ function promptAgent(agent::yiemAgent, msg::AbstractDict{String, Any})
|
|||||||
end
|
end
|
||||||
|
|
||||||
function promptAgent(agent::yiemAgent, text::String)
|
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
|
return agent
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ function followUp(agent::yiemAgent, msg::AbstractDict{String, Any})
|
|||||||
end
|
end
|
||||||
|
|
||||||
function followUp(agent::yiemAgent, text::String)
|
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
|
return agent
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+2
-104
@@ -3,7 +3,7 @@ module utils
|
|||||||
export clearhistory, availableWineToText, prepareContext, formatMsgForLLM, validateRequiredArgs,
|
export clearhistory, availableWineToText, prepareContext, formatMsgForLLM, validateRequiredArgs,
|
||||||
validateToolArguments, _userMessageToOpenAI,
|
validateToolArguments, _userMessageToOpenAI,
|
||||||
_assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks, _toolsToOpenAI,
|
_assistantMessageToOpenAI, _toolResultMessageToOpenAI, _messageContentToBlocks, _toolsToOpenAI,
|
||||||
beforeToolCall, afterToolCall, eventSink
|
beforeToolCall, afterToolCall
|
||||||
|
|
||||||
using UUIDs, Dates, DataStructures, HTTP, JSON, NATS
|
using UUIDs, Dates, DataStructures, HTTP, JSON, NATS
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
@@ -310,7 +310,7 @@ Common use cases:
|
|||||||
- `agentToolResult(...)` to return a modified result (content, details, usage,
|
- `agentToolResult(...)` to return a modified result (content, details, usage,
|
||||||
terminate flag can all be overridden).
|
terminate flag can all be overridden).
|
||||||
|
|
||||||
"""
|
""" #WORKING arrange result from searchWine tool
|
||||||
function afterToolCall(context::afterToolCallContext, signal::abortSignal
|
function afterToolCall(context::afterToolCallContext, signal::abortSignal
|
||||||
)::Union{agentToolResult, Nothing}
|
)::Union{agentToolResult, Nothing}
|
||||||
|
|
||||||
@@ -319,108 +319,6 @@ function afterToolCall(context::afterToolCallContext, signal::abortSignal
|
|||||||
return nothing
|
return nothing
|
||||||
end
|
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.
|
Convert a userMessage to OpenAI message format.
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user