update
This commit is contained in:
+32
-32
@@ -89,7 +89,7 @@ using YiemAgent, YiemAgent.type, YiemAgent.toolRegistry
|
||||
# Create agent — tools are registered automatically via register_all_tools()
|
||||
agent = yiemAgent(
|
||||
llmCall = my_llm_call, # Function that calls the LLM API
|
||||
agentEventSink = my_event_sink, # Function for TUI/logging
|
||||
eventSink = my_event_sink, # Function for TUI/logging
|
||||
)
|
||||
```
|
||||
|
||||
@@ -98,7 +98,7 @@ agent = yiemAgent(
|
||||
| Parameter | Type | Required | Purpose |
|
||||
|-----------|------|----------|---------|
|
||||
| `llmCall` | `Function` | **Yes** | `(messages::Dict) -> assistantMessage` — invokes the LLM |
|
||||
| `agentEventSink` | `Function` | **Yes** | `(event) -> nothing` — receives tool lifecycle events |
|
||||
| `eventSink` | `Function` | **Yes** | `(event) -> nothing` — receives tool lifecycle events |
|
||||
| `systemPrompt` | `String` | No (default: "You are helpful assistant.") | System prompt text |
|
||||
| `model` | `llmModel` | No | LLM model config |
|
||||
| `messages` | `Vector{agentMessage}` | No (default: empty) | Initial conversation history |
|
||||
@@ -179,7 +179,7 @@ Each phase has a single responsibility and produces an intermediate result:
|
||||
| Phase | Function | Input | Output | Purpose |
|
||||
|-------|----------|-------|--------|---------|
|
||||
| Prepare | `prepareToolCall()` | `agentContext`, `assistantMessage`, `agentToolCall`, `agentLoopConfig`, `abortSignal` | `preparedToolCall` or `immediateOutcome` | Resolve tool, validate args, run pre-hook |
|
||||
| Execute | `executePreparedToolCall()` | `preparedToolCall`, `abortSignal`, `agentEventSink` | `executedOutcome` | Call `tool.execute()`, stream partial results |
|
||||
| Execute | `executePreparedToolCall()` | `preparedToolCall`, `abortSignal`, `eventSink` | `executedOutcome` | Call `tool.execute()`, stream partial results |
|
||||
| Finalize | `finalizeExecutedToolCall()` | `agentContext`, `assistantMessage`, `preparedToolCall`, `executedOutcome`, `agentLoopConfig`, `abortSignal` | `finalizedOutcome` | Run post-hook, emit end event |
|
||||
|
||||
The pipeline ensures that **every tool call produces a result**, even on failure. Errors are captured as `immediateOutcome`, `executedOutcome`, or `finalizedOutcome` with `isError=true`, then converted to `toolResultMessage` objects that are fed back to the LLM conversation history.
|
||||
@@ -222,7 +222,7 @@ end
|
||||
|
||||
```julia
|
||||
execute(toolCallId::String,
|
||||
args::Dict{String,Any},
|
||||
args::AbstractDict{String, Any},
|
||||
signal::Union{Nothing,abortSignal},
|
||||
onPartialResult::Function)::agentToolResult
|
||||
```
|
||||
@@ -430,12 +430,12 @@ function _processMessage(agent::yiemAgent)::assistantMessage
|
||||
|
||||
# ── Step 2: Prepare context ─────────────────────────────────
|
||||
state = agentState(systemPrompt, nothing, tools, messages)
|
||||
preparedContext = prepareContext(state, agentEventSink)
|
||||
preparedContext = prepareContext(state, eventSink)
|
||||
# Default: deep copies systemPrompt, messages, tools from agentState → agentContext
|
||||
# Override point: filter tools, inject context, modify system prompt
|
||||
|
||||
# ── Step 3: Format for LLM ──────────────────────────────────
|
||||
formattedMessages = formatMsgForLLM(preparedContext, agentEventSink)
|
||||
formattedMessages = formatMsgForLLM(preparedContext, eventSink)
|
||||
# Converts agentContext → Dict("messages" => [...]) in OpenAI format
|
||||
# Wraps systemPrompt as system role, converts each messageContent block
|
||||
|
||||
@@ -456,7 +456,7 @@ function _processMessage(agent::yiemAgent)::assistantMessage
|
||||
signal = abortSignal(false)
|
||||
|
||||
# Execute tool calls (sequential or parallel)
|
||||
batch = executeToolCalls(context, assistant_msg, toolCallList, config, signal, agentEventSink)
|
||||
batch = executeToolCalls(context, assistant_msg, toolCallList, config, signal, eventSink)
|
||||
|
||||
# Save results to conversation history
|
||||
for tool_result in batch.messages
|
||||
@@ -611,7 +611,7 @@ function prepareToolCall(
|
||||
function executePreparedToolCall(
|
||||
prep::preparedToolCall,
|
||||
signal::Union{Nothing, abortSignal},
|
||||
agentEventSink,
|
||||
eventSink,
|
||||
)::executedOutcome
|
||||
```
|
||||
|
||||
@@ -623,7 +623,7 @@ function executePreparedToolCall(
|
||||
prep.toolCall.id,
|
||||
prep.args,
|
||||
signal,
|
||||
agentEventSink # serves as onPartialResult callback
|
||||
eventSink # serves as onPartialResult callback
|
||||
)
|
||||
return executedOutcome(result, false)
|
||||
```
|
||||
@@ -698,7 +698,7 @@ function executeToolCalls(
|
||||
toolCalls::Vector{agentToolCall},
|
||||
config::agentLoopConfig,
|
||||
signal::Union{Nothing, abortSignal},
|
||||
agentEventSink,
|
||||
eventSink,
|
||||
)::agentToolCallBatch
|
||||
```
|
||||
|
||||
@@ -733,12 +733,12 @@ function executeToolCallsSequential(...)::agentToolCallBatch
|
||||
messages = toolResultMessage[]
|
||||
|
||||
for tc in toolCalls
|
||||
prep = prepareToolCall(context, assistantMsg, tc, config, signal, agentEventSink)
|
||||
prep = prepareToolCall(context, assistantMsg, tc, config, signal, eventSink)
|
||||
|
||||
if prep isa immediateOutcome
|
||||
finalized = finalizedOutcome(tc, prep.result, prep.isError)
|
||||
else
|
||||
executed = executePreparedToolCall(prep, signal, agentEventSink)
|
||||
executed = executePreparedToolCall(prep, signal, eventSink)
|
||||
finalized = finalizeExecutedToolCall(context, assistantMsg, prep, executed, config, signal)
|
||||
end
|
||||
|
||||
@@ -763,14 +763,14 @@ function executeToolCallsParallel(...)::agentToolCallBatch
|
||||
entries = union{finalizedOutcome, task{finalizedOutcome}}[]
|
||||
|
||||
for tc in toolCalls
|
||||
prep = prepareToolCall(context, assistantMsg, tc, config, signal, agentEventSink)
|
||||
prep = prepareToolCall(context, assistantMsg, tc, config, signal, eventSink)
|
||||
|
||||
if prep isa immediateOutcome
|
||||
finalized = finalizedOutcome(tc, prep.result, prep.isError)
|
||||
push!(entries, finalized) # immediate outcome — no task
|
||||
else
|
||||
task = task() do
|
||||
executed = executePreparedToolCall(prep, signal, agentEventSink)
|
||||
executed = executePreparedToolCall(prep, signal, eventSink)
|
||||
finalized = finalizeExecutedToolCall(context, assistantMsg, prep, executed, config, signal)
|
||||
return finalized
|
||||
end
|
||||
@@ -845,7 +845,7 @@ From the type documentation (`type.jl:803-815`):
|
||||
**Source:** `agentCore.jl:266-307`
|
||||
|
||||
```julia
|
||||
batch = executeToolCalls(context, response, tool_call_list, config, signal, agentEventSink)
|
||||
batch = executeToolCalls(context, response, tool_call_list, config, signal, eventSink)
|
||||
|
||||
# Save results to conversation history
|
||||
for tool_result in batch.messages
|
||||
@@ -1037,13 +1037,13 @@ end
|
||||
|
||||
### Event Sink
|
||||
|
||||
The `agentEventSink` function is passed through the entire call chain:
|
||||
The `eventSink` function is passed through the entire call chain:
|
||||
|
||||
```julia
|
||||
agentEventSink = agent.agentEventSink # set during yiemAgent construction
|
||||
eventSink = agent.eventSink # set during yiemAgent construction
|
||||
```
|
||||
|
||||
The `agentEventSink` function is a user-provided callback that receives all events. This is typically used by:
|
||||
The `eventSink` function is a user-provided callback that receives all events. This is typically used by:
|
||||
- **TUI (Terminal UI):** Display real-time progress, tool names, results
|
||||
- **Logging systems:** Record tool execution history
|
||||
- **Monitoring:** Track tool usage, execution times, error rates
|
||||
@@ -1057,12 +1057,12 @@ The `agentEventSink` function is a user-provided callback that receives all even
|
||||
|
||||
| Hook | Signature | Called | Purpose |
|
||||
|------|-----------|--------|---------|
|
||||
| `prepareContext` | `(state::agentState, agentEventSink) -> agentContext` | Before each LLM call | Filter tools, inject context, modify system prompt |
|
||||
| `formatMsgForLLM` | `(ctx::agentContext, agentEventSink) -> Dict` | After `prepareContext` | Convert to LLM-specific format |
|
||||
| `prepareContext` | `(state::agentState, eventSink) -> agentContext` | Before each LLM call | Filter tools, inject context, modify system prompt |
|
||||
| `formatMsgForLLM` | `(ctx::agentContext, eventSink) -> Dict` | After `prepareContext` | Convert to LLM-specific format |
|
||||
| `llmCall` | `(messages::Dict) -> assistantMessage` | After formatting | Actually invoke the LLM API |
|
||||
| `beforeToolCall` | `(msgCtx::beforeToolCallContext, signal) -> Union{Nothing, Dict}` | In `prepareToolCall` | Ask for user permission, block execution, abort |
|
||||
| `afterToolCall` | `(afterToolCallContext::afterToolCallContext, signal) -> Union{Nothing, Dict}` | In `finalizeExecutedToolCall` | Mutate result, mask data, flip `terminate` |
|
||||
| `agentEventSink` | `(event) -> nothing` | Throughout lifecycle | Emit events for TUI, logging, monitoring |
|
||||
| `eventSink` | `(event) -> nothing` | Throughout lifecycle | Emit events for TUI, logging, monitoring |
|
||||
|
||||
### `beforeToolCall` Hook
|
||||
|
||||
@@ -1125,7 +1125,7 @@ end
|
||||
**Source:** `utils.jl:111-125`
|
||||
|
||||
```julia
|
||||
function prepareContext(state::agentState, agentEventSink)::agentContext
|
||||
function prepareContext(state::agentState, eventSink)::agentContext
|
||||
# TODO: filter tools from state.tools based on user intent
|
||||
filteredTools = state.tools
|
||||
|
||||
@@ -1152,7 +1152,7 @@ end
|
||||
Default implementation converts `agentContext` to OpenAI-compatible format:
|
||||
|
||||
```julia
|
||||
function formatMsgForLLM(ctx::agentContext, agentEventSink)::Dict{String, Any}
|
||||
function formatMsgForLLM(ctx::agentContext, eventSink)::Dict{String, Any}
|
||||
messages = Vector{Dict{String, Any}}()
|
||||
|
||||
# System prompt as system message
|
||||
@@ -1284,7 +1284,7 @@ LOOP ITERATION 1 — LLM DECIDES TO USE A TOOL
|
||||
│ Step 6: Execute tool calls
|
||||
│ context = agentContext(systemPrompt, messages, tools)
|
||||
│ config = agentLoopConfig(beforeToolCall, afterToolCall, "sequential")
|
||||
│ batch = executeToolCalls(context, response, tool_call_list, config, signal, agentEventSink)
|
||||
│ batch = executeToolCalls(context, response, tool_call_list, config, signal, eventSink)
|
||||
|
||||
|
||||
LOOP ITERATION 1 — executeToolCallsSequential
|
||||
@@ -1299,7 +1299,7 @@ LOOP ITERATION 1 — executeToolCallsSequential
|
||||
│ → preparedToolCall(tool, tc, {"city" => "Tokyo"})
|
||||
│
|
||||
│ EXECUTE:
|
||||
│ result = tool.execute("call_1", {"city" => "Tokyo"}, nothing, agentEventSink)
|
||||
│ result = tool.execute("call_1", {"city" => "Tokyo"}, nothing, eventSink)
|
||||
│ → agentToolResult([textContent("Weather in Tokyo: Sunny, 22°C")], {}, nothing, false)
|
||||
│ → executedOutcome(result, false)
|
||||
│
|
||||
@@ -1365,20 +1365,20 @@ end
|
||||
|
||||
```julia
|
||||
# Argument preparation (before validation)
|
||||
function <name>PrepareArguments(args::Dict{String,Any})::Dict{String,Any}
|
||||
function <name>PrepareArguments(args::AbstractDict{String, Any})::AbstractDict{String, Any}
|
||||
# Return modified args, or args unchanged
|
||||
return args
|
||||
end
|
||||
|
||||
# Custom validation (before execution)
|
||||
function <name>ValidateRequiredArgs(args::Dict{String,Any})::Union{Nothing,String}
|
||||
function <name>ValidateRequiredArgs(args::AbstractDict{String, Any})::Union{Nothing,String}
|
||||
# Return nothing to pass, or error string to fail
|
||||
return nothing
|
||||
end
|
||||
|
||||
# Core execution
|
||||
function <name>Execute(toolCallId::String,
|
||||
args::Dict{String,Any},
|
||||
args::AbstractDict{String, Any},
|
||||
signal::Union{Nothing,abortSignal},
|
||||
onPartialResult::Function)::agentToolResult
|
||||
# Return agentToolResult with content, details, usage, terminate
|
||||
@@ -1400,17 +1400,17 @@ function helper_function(...)
|
||||
end
|
||||
|
||||
# Optional: prepareArguments
|
||||
function myToolPrepareArguments(args::Dict{String,Any})::Dict{String,Any}
|
||||
function myToolPrepareArguments(args::AbstractDict{String, Any})::AbstractDict{String, Any}
|
||||
return args
|
||||
end
|
||||
|
||||
# Optional: validateRequiredArgs
|
||||
function myToolValidateRequiredArgs(args::Dict{String,Any})::Union{Nothing,String}
|
||||
function myToolValidateRequiredArgs(args::AbstractDict{String, Any})::Union{Nothing,String}
|
||||
return nothing
|
||||
end
|
||||
|
||||
# Required: execute function
|
||||
function myToolExecute(toolCallId::String, args::Dict{String,Any},
|
||||
function myToolExecute(toolCallId::String, args::AbstractDict{String, Any},
|
||||
signal::Union{Nothing,abortSignal},
|
||||
onPartialResult::Function)::agentToolResult
|
||||
...
|
||||
@@ -1481,7 +1481,7 @@ To add a new tool (e.g., `searchWine.jl`):
|
||||
using .type
|
||||
# using AdditionalPkg # add if needed
|
||||
|
||||
function searchWineExecute(toolCallId::String, args::Dict{String,Any},
|
||||
function searchWineExecute(toolCallId::String, args::AbstractDict{String, Any},
|
||||
signal::Union{Nothing,abortSignal}, onPartialResult)
|
||||
query = get(args, "query", "")
|
||||
result = search_wine_db(query)
|
||||
|
||||
Reference in New Issue
Block a user