static tool loading
This commit is contained in:
+132
-89
@@ -1,16 +1,23 @@
|
||||
module agentCore
|
||||
|
||||
export yiemAgent, _agentLoop, OpenAiToUserMessage, _extractToolCalls
|
||||
export yiemAgent, _agentLoop, OpenAiToUserMessage, _extractToolCalls,
|
||||
executePreparedToolCall, prepareToolCall, executeToolCallsSequential,
|
||||
executeToolCallsParallel, executeToolCalls
|
||||
|
||||
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||
DataFrames, Base.Threads, NATS
|
||||
using GeneralUtils
|
||||
using ..type, ..utils, ..toolRegistry
|
||||
|
||||
function register_all_tools(store::toolRegistry.toolStore)
|
||||
# Call parent module's version which has access to tool functions
|
||||
parentmodule(@__MODULE__).register_all_tools(store)
|
||||
end
|
||||
|
||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||
|
||||
"""
|
||||
docstring
|
||||
docstring
|
||||
"""
|
||||
mutable struct yiemAgent <: agent # High-level agent wrapper
|
||||
_state::agentState # Current state (prompt, model, messages, tools, etc.)
|
||||
@@ -84,7 +91,6 @@ on `inputChannel` and `followUpChannel` channels concurrently.
|
||||
- A new `yiemAgent` instance with an active background task
|
||||
"""
|
||||
function yiemAgent(
|
||||
toolsFolderPath::String,
|
||||
llmCall,
|
||||
;
|
||||
systemPrompt::String="You are helpful assistant.",
|
||||
@@ -106,9 +112,9 @@ function yiemAgent(
|
||||
followUp = Channel(32)
|
||||
outputChannel = Channel(16)
|
||||
|
||||
# load tools from toolsFolderPath
|
||||
# load tools (statically registered at module init)
|
||||
toolStore1 = toolStore(name="myagent")
|
||||
loadTools(toolStore1, toolsFolderPath)
|
||||
register_all_tools(toolStore1)
|
||||
|
||||
# Create struct with a placeholder task, then spawn and replace it
|
||||
agent = yiemAgent(
|
||||
@@ -291,6 +297,8 @@ function _agentLoop(agent::yiemAgent)
|
||||
processingTask = nothing # reset
|
||||
end
|
||||
agent.agentEventSink("_agentLoop 6")
|
||||
agent.agentEventSink(string(typeof(processingTask)))
|
||||
agent.agentEventSink("_agentLoop 7")
|
||||
end
|
||||
catch e
|
||||
# On any error, send error response and exit the loop
|
||||
@@ -329,7 +337,7 @@ julia> # Currently returns a placeholder echo response
|
||||
function _processMessage(
|
||||
inputChannel::Channel,
|
||||
agentEventSink,
|
||||
messages::Vector{agentMessage},
|
||||
agentMsgHistory::Vector{agentMessage},
|
||||
systemPrompt::String,
|
||||
tools::OrderedDict{String, agentTool},
|
||||
prepareContext::Function,
|
||||
@@ -370,12 +378,12 @@ function _processMessage(
|
||||
end
|
||||
agentEventSink("_processMessage 5")
|
||||
user_msg = OpenAiToUserMessage(raw_msg)
|
||||
push!(messages, user_msg)
|
||||
push!(agentMsgHistory, user_msg)
|
||||
agentEventSink("_processMessage 6")
|
||||
end
|
||||
agentEventSink("_processMessage 7")
|
||||
# call prepareContext()
|
||||
state = agentState(systemPrompt, nothing, tools, messages)
|
||||
state = agentState(systemPrompt, nothing, tools, agentMsgHistory)
|
||||
agentEventSink("_processMessage 8")
|
||||
preparedContext = prepareContext(state, agentEventSink)
|
||||
agentEventSink("_processMessage 8")
|
||||
@@ -396,7 +404,7 @@ function _processMessage(
|
||||
agentEventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList")
|
||||
|
||||
if hasToolCalls && length(toolCallList) > 0
|
||||
#WORKING Build context and config for executeToolCalls
|
||||
# Build context and config for executeToolCalls
|
||||
|
||||
config = agentLoopConfig(
|
||||
beforeToolCall,
|
||||
@@ -408,23 +416,21 @@ function _processMessage(
|
||||
agentEventSink("_processMessage 12")
|
||||
|
||||
# call executeToolCalls()
|
||||
|
||||
batch = executeToolCalls(preparedContext, assistant_msg, toolCallList, config, signal,
|
||||
agentEventSink)
|
||||
|
||||
|
||||
toolResultBatch = executeToolCalls(preparedContext, assistant_msg, toolCallList, config,
|
||||
signal, agentEventSink)
|
||||
agentEventSink("_processMessage 13")
|
||||
error("debug marker")
|
||||
# save toolResults to messages
|
||||
for tool_result in batch.messages
|
||||
push!(messages, tool_result)
|
||||
end
|
||||
|
||||
if batch.terminate
|
||||
# If batch requested termination, build a final response
|
||||
# save toolResults to messages
|
||||
for toolResult in toolResultBatch.messages
|
||||
push!(agentMsgHistory, toolResult)
|
||||
end
|
||||
agentEventSink("_processMessage 14")
|
||||
if toolResultBatch.terminate
|
||||
agentEventSink("_processMessage 15")
|
||||
# If toolResultBatch requested termination, build a final response
|
||||
final_content = [textContent("Tool execution completed.")]
|
||||
for tool_result in batch.messages
|
||||
for content_block in tool_result.content
|
||||
for toolResult in toolResultBatch.messages
|
||||
for content_block in toolResult.content
|
||||
if content_block isa textContent
|
||||
append!(final_content, [content_block])
|
||||
elseif content_block isa Dict
|
||||
@@ -434,6 +440,7 @@ function _processMessage(
|
||||
end
|
||||
end
|
||||
end
|
||||
agentEventSink("_processMessage 16")
|
||||
final_response = assistantMessage(
|
||||
role="assistant",
|
||||
content=final_content,
|
||||
@@ -441,7 +448,7 @@ function _processMessage(
|
||||
model=assistant_msg.model,
|
||||
usage=assistant_msg.usage,
|
||||
stopReason="tool_use_terminated",
|
||||
errorMessage=if any(x -> x.isError, batch.messages)
|
||||
errorMessage=if any(x -> x.isError, toolResultBatch.messages)
|
||||
"One or more tool calls failed"
|
||||
else
|
||||
nothing
|
||||
@@ -451,12 +458,13 @@ function _processMessage(
|
||||
break
|
||||
end
|
||||
else
|
||||
agentEventSink("_processMessage 17")
|
||||
# LLM did not use tool calls — this is the final response
|
||||
final_response = assistant_msg
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
agentEventSink("_processMessage 18")
|
||||
return final_response
|
||||
end
|
||||
|
||||
@@ -524,7 +532,7 @@ function createToolResultMessage(f::finalizedOutcome)::toolResultMessage
|
||||
return toolResultMessage(
|
||||
"toolResult", f.toolCall.id, f.toolCall.name,
|
||||
f.result.content, f.result.details, f.result.usage,
|
||||
get(f.result, :addedToolNames, string[]), f.isError, nowMillis()
|
||||
nothing, f.isError, now()
|
||||
)
|
||||
end
|
||||
|
||||
@@ -563,8 +571,10 @@ function _extractToolCalls(response)
|
||||
toolCallList = agentToolCall[]
|
||||
|
||||
# Helper: parse args (JSON string -> Dict, or pass through)
|
||||
parse_args(raw) = raw isa AbstractDict ? Dict{String,Any}(raw) :
|
||||
raw isa String ? JSON.parse(raw) : Dict{String,Any}()
|
||||
parse_args(raw) = raw isa AbstractDict && !(raw isa Dict{String,Any}) ?
|
||||
Dict{String,Any}(raw) :
|
||||
raw isa String ? JSON.parse(raw) :
|
||||
raw isa Dict{String,Any} ? raw : Dict{String,Any}()
|
||||
|
||||
# Helper: build agentToolCall (positional)
|
||||
make_tc(tc_data, default_id=string(uuid4())) = begin
|
||||
@@ -713,12 +723,27 @@ end
|
||||
"""
|
||||
shouldTerminate(finalizedCalls::Vector{finalizedOutcome}) -> Bool
|
||||
|
||||
The `terminate` flag is set by tool implementations, not by the agent
|
||||
or the LLM. It signals that the tool itself has completed the user's
|
||||
request or encountered a fatal condition, so the agent should stop
|
||||
processing further turns without calling the LLM again.
|
||||
|
||||
Common scenarios where a tool sets `terminate: true`:
|
||||
- **Task completion**: one-shot tools like `deploy`, `submit`, or
|
||||
`send_payment` finish their work and report directly to the user
|
||||
instead of asking the LLM "what next?"
|
||||
- **Unrecoverable error**: a tool hits a fatal condition (database
|
||||
connection lost, auth token expired) and stops the agent from
|
||||
retrying endlessly.
|
||||
- **Async handoff**: a tool triggers a long-running external operation
|
||||
and wants the agent to stop now; the external system will resume
|
||||
the agent later via `continue()`.
|
||||
|
||||
Returns `true` only when every finalized call in the batch has
|
||||
`result.terminate == true`. All tools must agree — if any tool
|
||||
did not request termination, the agent continues. This prevents
|
||||
a single tool that happens to set `terminate: true` (e.g. for
|
||||
metadata purposes) from accidentally stopping the agent when
|
||||
other tools in the batch did not intend to terminate.
|
||||
a single tool that happens to set `terminate: true` from accidentally
|
||||
stopping the agent when other tools in the batch did not intend to terminate.
|
||||
|
||||
# Arguments
|
||||
- `finalizedCalls`: Vector of finalized tool call outcomes
|
||||
@@ -739,7 +764,7 @@ true
|
||||
```
|
||||
"""
|
||||
function shouldTerminate(batches::Vector{finalizedOutcome})::Bool
|
||||
return !isempty(batches) && all(b -> b.result.terminate, batches)
|
||||
return !isempty(batches) && all(b -> b.result.terminate, batches)
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -849,7 +874,7 @@ function prepareToolCall(
|
||||
agentEventSink
|
||||
)::Union{preparedToolCall,immediateOutcome}
|
||||
agentEventSink("prepareToolCall 1")
|
||||
tool = get(context.tools, toolCall.name, nothing)
|
||||
tool = get(context.tools, toolCall.name, nothing) # pick a called tool from tool store
|
||||
if tool === nothing
|
||||
agentEventSink("prepareToolCall 2")
|
||||
return immediateOutcome(createErrorToolResult("Tool $toolCall.name not found"), true)
|
||||
@@ -859,8 +884,10 @@ function prepareToolCall(
|
||||
agentEventSink("prepareToolCall 3")
|
||||
# 1. prepare arguments (tool-specific transform)
|
||||
prepared = prepareToolCallArguments(tool, toolCall)
|
||||
agentEventSink(string(prepared.arguments))
|
||||
agentEventSink("prepareToolCall 4")
|
||||
validatedArgs = validateToolArguments(tool, prepared)
|
||||
agentEventSink(string(validatedArgs))
|
||||
agentEventSink("prepareToolCall 5")
|
||||
# 2. beforeToolCall hook — can block
|
||||
if config.beforeToolCall !== nothing
|
||||
@@ -886,12 +913,12 @@ function prepareToolCall(
|
||||
return preparedToolCall(tool, toolCall, validatedArgs)
|
||||
catch e
|
||||
bt = catch_backtrace()
|
||||
err_msg = sprint() do io
|
||||
errMsg = sprint() do io
|
||||
showerror(io, e, bt)
|
||||
println(io)
|
||||
end
|
||||
|
||||
agentEventSink(err_msg)
|
||||
agentEventSink(errMsg)
|
||||
|
||||
return immediateOutcome(createErrorToolResult(sprint(showerror, e)), true)
|
||||
end
|
||||
@@ -937,36 +964,40 @@ executePreparedToolCall(prep, nothing, emit)
|
||||
# => executedOutcome(createErrorToolResult("Connection timeout"), true)
|
||||
```
|
||||
"""
|
||||
|
||||
function executePreparedToolCall(
|
||||
prep::preparedToolCall,
|
||||
signal::Union{Nothing,abortSignal},
|
||||
agentEventSink,
|
||||
)::executedOutcome
|
||||
agentEventSink("executePreparedToolCall 1")
|
||||
updateEvents = promise[]
|
||||
accepting = true
|
||||
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,
|
||||
partialResult -> begin
|
||||
if accepting
|
||||
push!(updateEvents,
|
||||
agentEventSink(toolExecUpdateEvent(prep.toolCall.id, prep.toolCall.name,
|
||||
prep.toolCall.arguments, partialResult)))
|
||||
end
|
||||
end
|
||||
)
|
||||
accepting = false
|
||||
wait.(updateEvents)
|
||||
result = prep.tool.execute(prep.toolCall.id, prep.args, signal, agentEventSink)
|
||||
agentEventSink(result.content[1].text)
|
||||
agentEventSink("executePreparedToolCall 4")
|
||||
return executedOutcome(result, false)
|
||||
catch err
|
||||
accepting = false
|
||||
wait.(updateEvents)
|
||||
return executedOutcome(createErrorToolResult(sprint(showerror, err)), true)
|
||||
catch e
|
||||
bt = catch_backtrace()
|
||||
errMsg = sprint() do io
|
||||
showerror(io, e, bt)
|
||||
println(io)
|
||||
end
|
||||
agentEventSink(errMsg)
|
||||
|
||||
return executedOutcome(createErrorToolResult(sprint(showerror, e)), true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# ── per-call finalization ───────────────────────────────────────
|
||||
|
||||
"""
|
||||
@@ -1030,16 +1061,19 @@ function finalizeExecutedToolCall(
|
||||
executed::executedOutcome,
|
||||
config::agentLoopConfig,
|
||||
signal::Union{Nothing,abortSignal},
|
||||
agentEventSink
|
||||
)::finalizedOutcome
|
||||
|
||||
agentEventSink("finalizeExecutedToolCall 1")
|
||||
result = executed.result
|
||||
isError = executed.isError
|
||||
|
||||
agentEventSink("finalizeExecutedToolCall 2")
|
||||
if config.afterToolCall !== nothing
|
||||
try
|
||||
after = config.afterToolCall(
|
||||
afterToolCallContext(assistantMsg, prep.toolCall, prep.args, result, isError, context), signal
|
||||
afterToolCallContext(assistantMsg, prep.toolCall, prep.args, result, isError, context),
|
||||
signal
|
||||
)
|
||||
agentEventSink("finalizeExecutedToolCall 3")
|
||||
if after !== nothing
|
||||
result = merge(result, dict(:content=>get(after,:content,result.content),
|
||||
:details=>get(after,:details,result.details),
|
||||
@@ -1047,12 +1081,19 @@ function finalizeExecutedToolCall(
|
||||
:terminate=>get(after,:terminate,result.terminate)))
|
||||
isError = get(after, :isError, isError)
|
||||
end
|
||||
catch err
|
||||
result = createErrorToolResult(sprint(showerror, err))
|
||||
catch e
|
||||
bt = catch_backtrace()
|
||||
errMsg = sprint() do io
|
||||
showerror(io, e, bt)
|
||||
println(io)
|
||||
end
|
||||
agentEventSink(errMsg)
|
||||
|
||||
result = createErrorToolResult(sprint(showerror, e))
|
||||
isError = true
|
||||
end
|
||||
end
|
||||
|
||||
agentEventSink("finalizeExecutedToolCall 4")
|
||||
return finalizedOutcome(prep.toolCall, result, isError)
|
||||
end
|
||||
|
||||
@@ -1123,30 +1164,32 @@ function executeToolCallsSequential(
|
||||
messages = toolResultMessage[]
|
||||
|
||||
for tc in toolCalls
|
||||
agentEventSink("start tool execute $(tc.id), $(tc.name), $(tc.arguments)")
|
||||
prep = prepareToolCall(context, assistantMsg, tc, config, signal, agentEventSink)
|
||||
agentEventSink("executeToolCallsSequential 2")
|
||||
if prep isa immediateOutcome
|
||||
agentEventSink("executeToolCallsSequential 2-1")
|
||||
finalized = finalizedOutcome(tc, prep.result, prep.isError)
|
||||
agentEventSink("executeToolCallsSequential 2-2")
|
||||
else
|
||||
agentEventSink("executeToolCallsSequential 3")
|
||||
executed = executePreparedToolCall(prep, signal, agentEventSink)
|
||||
agentEventSink("executeToolCallsSequential 3-1")
|
||||
finalized = finalizeExecutedToolCall(context, assistantMsg, prep, executed, config,
|
||||
signal)
|
||||
agentEventSink("executeToolCallsSequential 3-2")
|
||||
end
|
||||
agentEventSink("executeToolCallsSequential 4")
|
||||
agentEventSink("$(finalized.toolCall.id), $(finalized.toolCall.name),
|
||||
$(finalized.result), $(finalized.isError)")
|
||||
push!(messages, createToolResultMessage(finalized))
|
||||
push!(finalizedCalls, finalized)
|
||||
agentEventSink("executeToolCallsSequential 5")
|
||||
if signal !== nothing && signal.aborted
|
||||
break
|
||||
end
|
||||
agentEventSink("start tool execute $(tc.id), $(tc.name), $(tc.arguments)")
|
||||
prep = prepareToolCall(context, assistantMsg, tc, config, signal, agentEventSink)
|
||||
agentEventSink("executeToolCallsSequential " * string(prep.args))
|
||||
|
||||
if prep isa immediateOutcome
|
||||
agentEventSink("executeToolCallsSequential 2-1")
|
||||
finalized = finalizedOutcome(tc, prep.result, prep.isError)
|
||||
agentEventSink("executeToolCallsSequential 2-2")
|
||||
else
|
||||
agentEventSink("executeToolCallsSequential 3")
|
||||
#XXX
|
||||
executed = executePreparedToolCall(prep, signal, agentEventSink)
|
||||
agentEventSink("executeToolCallsSequential 3-1")
|
||||
finalized = finalizeExecutedToolCall(context, assistantMsg, prep, executed, config,
|
||||
signal, agentEventSink)
|
||||
agentEventSink("executeToolCallsSequential 3-2")
|
||||
end
|
||||
agentEventSink("executeToolCallsSequential 4")
|
||||
agentEventSink("$(finalized.toolCall.id), $(finalized.toolCall.name),
|
||||
$(finalized.result), $(finalized.isError)")
|
||||
push!(messages, createToolResultMessage(finalized))
|
||||
push!(finalizedCalls, finalized)
|
||||
agentEventSink("executeToolCallsSequential 5")
|
||||
if signal !== nothing && signal.aborted
|
||||
break
|
||||
end
|
||||
end
|
||||
agentEventSink("executeToolCallsSequential 6")
|
||||
return agentToolCallBatch(messages, shouldTerminate(finalizedCalls))
|
||||
@@ -1218,12 +1261,12 @@ function executeToolCallsParallel(
|
||||
agentEventSink,
|
||||
)::agentToolCallBatch
|
||||
|
||||
entries = union{finalizedOutcome,task{finalizedOutcome}}[]
|
||||
entries = Union{finalizedOutcome,Task}[]
|
||||
|
||||
for tc in toolCalls
|
||||
agentEventSink(toolExecStartEvent(tc.id, tc.name, tc.arguments))
|
||||
|
||||
prep = prepareToolCall(context, assistantMsg, tc, config, signal)
|
||||
prep = prepareToolCall(context, assistantMsg, tc, config, signal, agentEventSink)
|
||||
|
||||
if prep isa immediateOutcome
|
||||
finalized = finalizedOutcome(tc, prep.result, prep.isError)
|
||||
@@ -1231,15 +1274,15 @@ function executeToolCallsParallel(
|
||||
finalized.result, finalized.isError))
|
||||
push!(entries, finalized)
|
||||
else
|
||||
task = task() do
|
||||
t = Task() do
|
||||
executed = executePreparedToolCall(prep, signal, agentEventSink)
|
||||
finalized = finalizeExecutedToolCall(context, assistantMsg, prep, executed, config, signal)
|
||||
agentEventSink(toolExecEndEvent(finalized.toolCall.id, finalized.toolCall.name,
|
||||
finalized.result, finalized.isError))
|
||||
return finalized
|
||||
end
|
||||
schedule(task)
|
||||
push!(entries, task)
|
||||
schedule(t)
|
||||
push!(entries, t)
|
||||
end
|
||||
|
||||
if signal !== nothing && signal.aborted
|
||||
@@ -1249,7 +1292,7 @@ function executeToolCallsParallel(
|
||||
|
||||
finalizedCalls = finalizedOutcome[]
|
||||
for entry in entries
|
||||
outcome = entry isa task ? fetch(entry) : entry
|
||||
outcome = entry isa Task ? fetch(entry) : entry
|
||||
push!(finalizedCalls, outcome)
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user