This commit is contained in:
2026-08-05 19:09:56 +07:00
parent afc866a3a5
commit ec28e0ff54
3 changed files with 953 additions and 594 deletions
+188
View File
@@ -396,6 +396,7 @@ mutable struct yiemAgent <: agent # High-level agent wrapper
sessionId::Union{String, Nothing} # Optional session identifier
maxRetryDelayMs::Union{Int64, Nothing} # Maximum delay between retries (ms)
parallelToolExecute::Bool # Default: false
agentEventSink::Function # agent emits its status via this function
end
"""
@@ -419,6 +420,7 @@ on `inputChannel` and `followUpChannel` channels concurrently.
- `sessionId::Union{String, Nothing}`: Optional session identifier (default: `nothing`)
- `maxRetryDelayMs::Union{Int64, Nothing}`: Maximum delay between retries in milliseconds (default: `nothing`)
- `parallelToolExecute::Bool`: Run tool calls in parallel (default: `false`)
- `agentEventSink::Function`: Callback to receive agent events
# Returns
- A new `yiemAgent` instance with an active background task
@@ -444,6 +446,7 @@ function yiemAgent(
sessionId::Union{String, Nothing}=nothing,
maxRetryDelayMs::Union{Int64, Nothing}=nothing,
parallelToolExecute::Bool=false,
agentEventSink::Function,
)
# Create channels: input (user -> agent), followUp (async queue), output (agent -> user)
inputChannel = Channel(16)
@@ -467,6 +470,7 @@ function yiemAgent(
sessionId,
maxRetryDelayMs,
parallelToolExecute,
agentEventSink,
)
# Spawn the background loop and attach it
@@ -477,6 +481,190 @@ end
"""
preparedToolCall(tool, toolCall, args)
Intermediate state between tool call validation and execution.
Created after `prepareToolCall` succeeds; serves as the bridge to
the execution phase. Keeping the resolved tool, original call
metadata, and validated args together avoids repeated lookups and
allows the execution phase to access all necessary data without
carrying the full context through the call chain.
# Fields
- `tool::agentTool`: The resolved tool definition from the context
- `toolCall::agentToolCall`: The original tool call from the assistant
- `args::any`: Validated (and coerced) argument values
# Examples
```julia
# After prepareToolCall succeeds, the agent holds a preparedToolCall
prep = preparedToolCall(
tool, # agentTool found in context.tools
toolCall, # {id: "call_1", name: "search_wine", arguments: "{\"query\": \"red wine\"}"}
validatedArgs # Dict("query" => "red wine")
)
```
"""
struct preparedToolCall
tool::agentTool # The resolved tool definition from the context
toolCall::agentToolCall # The original tool call from the assistant
args::any # Validated (and coerced) argument values
end
"""
immediateOutcome(result, isError)
A tool call that was resolved without actual execution — either
because the tool was not found, validation failed, or a
`beforeToolCall` hook blocked the call. The result is produced
immediately and emitted as a tool result message.
Returning an outcome instead of throwing an exception is intentional:
it lets the agent feed the error back to the LLM as a tool result so
the model can recover — for example, by re-issuing a tool call with
corrected arguments after a validation failure.
# Fields
- `result::agentToolResult`: The pre-computed tool result
- `isError::bool`: Whether this outcome represents an error
# Examples
```julia
# Tool not found — immediate error
immediateOutcome(
createErrorToolResult("Tool search_wine not found"),
true
)
# beforeToolCall hook blocked execution
immediateOutcome(
createErrorToolResult("Tool execution was blocked"),
true
)
```
"""
struct immediateOutcome
result::agentToolResult # The pre-computed tool result
isError::bool # Whether this outcome represents an error
end
"""
executedOutcome(result, isError)
A tool call that has been executed by `tool.execute()` but has not
yet been through the `afterToolCall` hook. This intermediate state
is necessary because the hook may mutate the result (content, usage,
termination, error status). Keeping execution and finalization
separate allows the hook to inspect the raw result and decide
whether to transform it or replace it entirely.
# Fields
- `result::agentToolResult`: The tool's execution result
- `isError::bool`: Whether execution raised an error
# Examples
```julia
# Successful execution
executedOutcome(
agentToolResult([textContent("text", "Found 3 wines")], dict{any,any}(), dict{any,any}()),
false
)
# Execution error
executedOutcome(
createErrorToolResult("Connection timeout"),
true
)
```
"""
struct executedOutcome
result::agentToolResult # The tool's execution result
isError::bool # Whether execution raised an error
end
"""
finalizedOutcome(toolCall, result, isError)
The complete outcome of a tool call after both execution and the
`afterToolCall` hook. This is the final form used to construct
the `toolResultMessage` emitted to the agent loop.
The three-phase design (prepare → execute → finalize) exists so
that each phase has a single responsibility: preparation handles
validation and gating, execution performs the actual work, and
finalization applies post-processing hooks. This separation allows
the agent loop to emit `toolExecutionEnd` events with the
finalized data while keeping each phase independently testable
and swappable.
# Fields
- `toolCall::agentToolCall`: The original tool call reference
- `result::agentToolResult`: The final tool result (post-afterToolCall)
- `isError::bool`: Whether the call failed or was blocked
# Examples
```julia
# Normal successful finalization
finalizedOutcome(tc, agentToolResult(content, details, usage, false), false)
# afterToolCall mutated result and set terminate
finalizedOutcome(tc, agentToolResult(content, details, usage, true), false)
```
"""
struct finalizedOutcome
toolCall::agentToolCall # The original tool call reference
result::agentToolResult # The final tool result (post-afterToolCall)
isError::bool # Whether the call failed or was blocked
end
"""
agentToolCallBatch(messages, terminate)
A batch of tool result messages from executing one or more tool calls.
The `terminate` flag indicates whether all tools in the batch
requested termination, which causes the agent loop to stop
processing further turns.
This flag is set by the tool implementation (not the end user) to
signal that the agent should not call the LLM again. Typical use
cases:
- Task completion: a tool like `deploy` or `submit` finishes its
work and returns `terminate: true` so the agent stops instead
of asking the LLM what to do next.
- Unrecoverable error: a tool hits a fatal condition (e.g.
database connection lost, auth token expired) and returns
`terminate: true` so the agent stops with an error message
rather than retrying.
- Async handoff: a tool triggers a long-running external
operation and wants the agent to stop now; the external system
will later resume the agent via `continue()`.
If `terminate` is `false` (default), the agent loop feeds the tool
results back to the LLM for another turn.
# Fields
- `messages::Vector{toolResultMessage}`: Tool result messages for this batch
- `terminate::Bool`: Whether the batch should terminate the loop
# Examples
```julia
# Batch of 3 tool results, no termination
agentToolCallBatch(resultMessages, false)
# All tools requested termination
agentToolCallBatch(resultMessages, true)
# Empty batch — terminate is false regardless
agentToolCallBatch(toolResultMessage[], false)
```
"""
struct agentToolCallBatch
messages::vector{toolResultMessage} # Tool result messages for this batch
terminate::bool # Whether the batch should terminate the loop
end