update
This commit is contained in:
@@ -0,0 +1,758 @@
|
||||
# AgentCore.jl - AgentLoop Component Deep Dive
|
||||
|
||||
## AgentLoop Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ AgentLoop Layer │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Public API │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
agentLoop()
|
||||
├─ prompts: Vector{AgentMessage}
|
||||
├─ context: AgentContext
|
||||
├─ config: AgentLoopConfig
|
||||
├─ signal: Union{Nothing, AbortSignal}
|
||||
└─ stream_fn: StreamFn
|
||||
└─ Returns: EventStream
|
||||
|
||||
agentLoopContinue()
|
||||
├─ context: AgentContext
|
||||
├─ config: AgentLoopConfig
|
||||
├─ signal: Union{Nothing, AbortSignal}
|
||||
└─ stream_fn: StreamFn
|
||||
└─ Returns: EventStream
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Internal Flow │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 1. runAgentLoop() ── Entry point for new conversation │
|
||||
│ - Creates copy of prompts │
|
||||
│ - Appends prompts to context.messages │
|
||||
│ - Emits AgentStartEvent │
|
||||
│ - Emits TurnStartEvent │
|
||||
│ - Emits MessageStart/End for each prompt │
|
||||
│ - Calls runLoop() │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 2. runLoop() ── Main event loop │
|
||||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ while true: │ │
|
||||
│ │ 1. Get steering/follow-up messages (if any) │ │
|
||||
│ │ 2. Emit messages as UserMessage │ │
|
||||
│ │ 3. streamAssistantResponse() │ │
|
||||
│ │ 4. Execute tool calls (sequential or parallel) │ │
|
||||
│ │ 5. Emit TurnEndEvent │ │
|
||||
│ │ 6. prepare_next_turn (optional) │ │
|
||||
│ │ 7. should_stop_after_turn? (check termination) │ │
|
||||
│ │ 8. Loop continues if not terminated │ │
|
||||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 3. streamAssistantResponse() ── LLM interaction │
|
||||
│ - transform_context (optional) │
|
||||
│ - convert_to_llm (transform to Message[]) │
|
||||
│ - Call stream_fn (LLM API) │
|
||||
│ - Stream response deltas │
|
||||
│ - Emit MessageStart/Update/End events │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 4. executeToolCalls() ── Tool execution │
|
||||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ if EXECUTION_SEQUENTIAL || has_sequential_tool: │ │
|
||||
│ │ executeToolCallsSequential() │ │
|
||||
│ │ else: │ │
|
||||
│ │ executeToolCallsParallel() │ │
|
||||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 5. AgentEndEvent ── Final event with all messages │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## AgentLoopConfig
|
||||
|
||||
```julia
|
||||
struct AgentLoopConfig
|
||||
model::Model
|
||||
reasoning::Union{ThinkingLevel, Nothing}
|
||||
session_id::Union{String, Nothing}
|
||||
on_payload::Union{Function, Nothing}
|
||||
on_response::Union{Function, Nothing}
|
||||
transport::String
|
||||
thinking_budgets::Union{Dict{String, Int64}, Nothing}
|
||||
max_retry_delay_ms::Union{Int64, Nothing}
|
||||
tool_execution::ToolExecutionMode
|
||||
before_tool_call::Union{Function, Nothing}
|
||||
after_tool_call::Union{Function, Nothing}
|
||||
prepare_next_turn::Union{Function, Nothing}
|
||||
convert_to_llm::Function
|
||||
transform_context::Union{Function, Nothing}
|
||||
get_api_key::Union{Function, Nothing}
|
||||
get_steering_messages::Union{Function, Nothing}
|
||||
get_follow_up_messages::Union{Function, Nothing}
|
||||
end
|
||||
```
|
||||
|
||||
## Main Functions
|
||||
|
||||
### agentLoop()
|
||||
|
||||
```julia
|
||||
function agentLoop(
|
||||
prompts::Vector{AgentMessage},
|
||||
context::AgentContext,
|
||||
config::AgentLoopConfig,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
stream_fn::StreamFn,
|
||||
)::EventStream
|
||||
```
|
||||
|
||||
**Purpose**: Start a new conversation with initial prompts
|
||||
|
||||
**Flow**:
|
||||
1. Create event stream
|
||||
2. Spawn thread to run agent loop
|
||||
3. Return stream for event consumption
|
||||
|
||||
```julia
|
||||
stream = agentLoop(
|
||||
[UserMessage("user", [TextContent("Hello")], timestamp)],
|
||||
AgentContext(system_prompt, messages, tools),
|
||||
config,
|
||||
nothing,
|
||||
stream_fn,
|
||||
)
|
||||
|
||||
# Consume events
|
||||
for event in stream
|
||||
if event isa MessageEndEvent
|
||||
println("Received: $(event.message)")
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### runAgentLoop()
|
||||
|
||||
```julia
|
||||
function runAgentLoop(
|
||||
prompts::Vector{AgentMessage},
|
||||
context::AgentContext,
|
||||
config::AgentLoopConfig,
|
||||
emit::AgentEventSink,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
stream_fn::StreamFn,
|
||||
)::Vector{AgentMessage}
|
||||
```
|
||||
|
||||
**Purpose**: Execute agent loop with initial prompts
|
||||
|
||||
**Flow**:
|
||||
1. Copy prompts to new_messages
|
||||
2. Append prompts to context.messages
|
||||
3. Emit AgentStartEvent
|
||||
4. For each prompt: emit MessageStartEvent, MessageEndEvent
|
||||
5. Call runLoop()
|
||||
|
||||
### runLoop() - The Heart of AgentLoop
|
||||
|
||||
```julia
|
||||
function runLoop(
|
||||
initial_context::AgentContext,
|
||||
new_messages::Vector{AgentMessage},
|
||||
initial_config::AgentLoopConfig,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
emit::AgentEventSink,
|
||||
stream_function::StreamFn,
|
||||
)::Nothing
|
||||
```
|
||||
|
||||
**Main Loop**:
|
||||
```julia
|
||||
current_context = initial_context
|
||||
config = initial_config
|
||||
first_turn = true
|
||||
pending_messages = get_steering_messages()
|
||||
|
||||
while true
|
||||
# Process steering/follow-up messages
|
||||
while !isempty(pending_messages)
|
||||
if !first_turn
|
||||
emit(TurnStartEvent())
|
||||
else
|
||||
first_turn = false
|
||||
end
|
||||
|
||||
# Emit pending messages
|
||||
for message in pending_messages
|
||||
emit(MessageStartEvent(message))
|
||||
emit(MessageEndEvent(message))
|
||||
push!(current_context.messages, message)
|
||||
push!(new_messages, message)
|
||||
end
|
||||
|
||||
pending_messages = []
|
||||
end
|
||||
|
||||
# Stream assistant response
|
||||
message = streamAssistantResponse(
|
||||
current_context,
|
||||
config,
|
||||
signal,
|
||||
emit,
|
||||
stream_function,
|
||||
)
|
||||
push!(new_messages, message)
|
||||
|
||||
# Check for errors
|
||||
if message.stop_reason in ("error", "aborted")
|
||||
emit(TurnEndEvent(message, []))
|
||||
emit(AgentEndEvent(new_messages))
|
||||
return
|
||||
end
|
||||
|
||||
# Execute tool calls
|
||||
tool_calls = filter(c -> c isa ToolCall, message.content)
|
||||
tool_results = []
|
||||
has_more_tool_calls = false
|
||||
|
||||
if !isempty(tool_calls)
|
||||
executed_batch = if message.stop_reason == "length"
|
||||
failToolCallsFromTruncatedMessage(tool_calls, emit)
|
||||
else
|
||||
executeToolCalls(
|
||||
current_context,
|
||||
message,
|
||||
config,
|
||||
signal,
|
||||
emit,
|
||||
)
|
||||
end
|
||||
append!(tool_results, executed_batch.messages)
|
||||
has_more_tool_calls = !executed_batch.terminate
|
||||
|
||||
for result in tool_results
|
||||
push!(current_context.messages, result)
|
||||
push!(new_messages, result)
|
||||
end
|
||||
end
|
||||
|
||||
emit(TurnEndEvent(message, tool_results))
|
||||
|
||||
# Prepare next turn (optional)
|
||||
next_turn_context = PrepareNextTurnContext(
|
||||
message, tool_results, current_context, new_messages
|
||||
)
|
||||
next_turn_snapshot = prepare_next_turn(config, next_turn_context)
|
||||
|
||||
if !isnothing(next_turn_snapshot)
|
||||
current_context = next_turn_snapshot.context
|
||||
config = AgentLoopConfig(
|
||||
model = next_turn_snapshot.model,
|
||||
reasoning = next_turn_snapshot.thinking_level,
|
||||
# ... other config fields
|
||||
)
|
||||
end
|
||||
|
||||
# Check if should stop
|
||||
if should_stop_after_turn(config, next_turn_context)
|
||||
emit(AgentEndEvent(new_messages))
|
||||
return
|
||||
end
|
||||
|
||||
# Get next pending messages
|
||||
pending_messages = get_steering_messages()
|
||||
|
||||
# Check follow-up messages
|
||||
follow_up_messages = get_follow_up_messages()
|
||||
if !isempty(follow_up_messages)
|
||||
pending_messages = follow_up_messages
|
||||
continue
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
emit(AgentEndEvent(new_messages))
|
||||
```
|
||||
|
||||
### streamAssistantResponse()
|
||||
|
||||
```julia
|
||||
function streamAssistantResponse(
|
||||
context::AgentContext,
|
||||
config::AgentLoopConfig,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
emit::AgentEventSink,
|
||||
stream_function::StreamFn,
|
||||
)::AssistantMessage
|
||||
```
|
||||
|
||||
**Flow**:
|
||||
1. Get messages from context
|
||||
2. Apply transform_context (optional)
|
||||
3. Convert to LLM messages with convert_to_llm
|
||||
4. Create Context object
|
||||
5. Resolve API key
|
||||
6. Call stream_fn with model, context, and config
|
||||
7. Stream events:
|
||||
- "start" → MessageStartEvent
|
||||
- "text_start", "text_delta", "text_end" → MessageUpdateEvent
|
||||
- "done", "error" → MessageEndEvent
|
||||
|
||||
### executeToolCalls()
|
||||
|
||||
```julia
|
||||
function executeToolCalls(
|
||||
current_context::AgentContext,
|
||||
assistant_message::AssistantMessage,
|
||||
config::AgentLoopConfig,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
emit::AgentEventSink,
|
||||
)::ExecutedToolCallBatch
|
||||
```
|
||||
|
||||
**Logic**:
|
||||
```julia
|
||||
tool_calls = filter(c -> c isa ToolCall, assistant_message.content)
|
||||
|
||||
# Check if any tool requires sequential execution
|
||||
has_sequential = any(tc -> begin
|
||||
tool = findfirst(t -> t.name == tc.name, current_context.tools)
|
||||
!isnothing(tool) && tool.execution_mode == EXECUTION_SEQUENTIAL
|
||||
end, tool_calls)
|
||||
|
||||
# Determine execution mode
|
||||
if config.tool_execution == EXECUTION_SEQUENTIAL || has_sequential
|
||||
executeToolCallsSequential(...)
|
||||
else
|
||||
executeToolCallsParallel(...)
|
||||
end
|
||||
```
|
||||
|
||||
### executeToolCallsSequential()
|
||||
|
||||
```julia
|
||||
function executeToolCallsSequential(
|
||||
current_context::AgentContext,
|
||||
assistant_message::AssistantMessage,
|
||||
tool_calls::Vector{ToolCall},
|
||||
config::AgentLoopConfig,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
emit::AgentEventSink,
|
||||
)::ExecutedToolCallBatch
|
||||
```
|
||||
|
||||
**Flow** (for each tool call):
|
||||
1. Emit ToolExecutionStartEvent
|
||||
2. prepareToolCall() → PreparedToolCall or ImmediateToolCallOutcome
|
||||
3. If prepared: executePreparedToolCall()
|
||||
4. finalizeExecutedToolCall()
|
||||
5. Emit ToolExecutionEndEvent
|
||||
6. Emit ToolResultMessage
|
||||
7. Check if signal.aborted → break
|
||||
|
||||
### executeToolCallsParallel()
|
||||
|
||||
```julia
|
||||
function executeToolCallsParallel(
|
||||
current_context::AgentContext,
|
||||
assistant_message::AssistantMessage,
|
||||
tool_calls::Vector{ToolCall},
|
||||
config::AgentLoopConfig,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
emit::AgentEventSink,
|
||||
)::ExecutedToolCallBatch
|
||||
```
|
||||
|
||||
**Flow**:
|
||||
1. For each tool call:
|
||||
- If immediate: execute and add to finalized_calls
|
||||
- If prepared: create closure, add to finalized_calls
|
||||
2. For each entry in finalized_calls:
|
||||
- If closure: execute closure
|
||||
- If finalized: use as-is
|
||||
3. Collect all tool results
|
||||
4. Return batch
|
||||
|
||||
### prepareToolCall()
|
||||
|
||||
```julia
|
||||
function prepareToolCall(
|
||||
current_context::AgentContext,
|
||||
assistant_message::AssistantMessage,
|
||||
tool_call::ToolCall,
|
||||
config::AgentLoopConfig,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
)::Union{PreparedToolCall, ImmediateToolCallOutcome}
|
||||
```
|
||||
|
||||
**Flow**:
|
||||
1. Find tool by name
|
||||
2. If not found → ImmediateToolCallOutcome (error)
|
||||
3. before_tool_call hook (optional)
|
||||
4. prepareToolCallArguments() (optional)
|
||||
5. validateToolArguments()
|
||||
6. Return PreparedToolCall
|
||||
|
||||
### executePreparedToolCall()
|
||||
|
||||
```julia
|
||||
function executePreparedToolCall(
|
||||
prepared::PreparedToolCall,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
emit::AgentEventSink,
|
||||
)::ExecutedToolCallOutcome
|
||||
```
|
||||
|
||||
**Flow**:
|
||||
1. Call tool.execute(id, args, signal, on_update)
|
||||
2. Collect update events (if any)
|
||||
3. Wait for all update events
|
||||
4. Return ExecutedToolCallOutcome(result)
|
||||
|
||||
### finalizeExecutedToolCall()
|
||||
|
||||
```julia
|
||||
function finalizeExecutedToolCall(
|
||||
current_context::AgentContext,
|
||||
assistant_message::AssistantMessage,
|
||||
prepared::PreparedToolCall,
|
||||
executed::ExecutedToolCallOutcome,
|
||||
config::AgentLoopConfig,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
)::FinalizedToolCallOutcome
|
||||
```
|
||||
|
||||
**Flow**:
|
||||
1. after_tool_call hook (optional)
|
||||
2. Return FinalizedToolCallOutcome
|
||||
|
||||
### createToolResultMessage()
|
||||
|
||||
```julia
|
||||
function createToolResultMessage(
|
||||
finalized::FinalizedToolCallOutcome,
|
||||
)::ToolResultMessage
|
||||
```
|
||||
|
||||
**Creates**:
|
||||
```julia
|
||||
ToolResultMessage(
|
||||
"toolResult",
|
||||
finalized.tool_call.id,
|
||||
finalized.tool_call.name,
|
||||
finalized.result.content,
|
||||
finalized.result.details,
|
||||
finalized.result.usage,
|
||||
finalized.result.added_tool_names,
|
||||
finalized.is_error,
|
||||
timestamp,
|
||||
)
|
||||
```
|
||||
|
||||
## Execution Modes
|
||||
|
||||
### Sequential Execution
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Sequential Execution Flow │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────┐
|
||||
│ TC1 │ ──► prepareToolCall()
|
||||
└──────┘ │
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ execute() │ ──► Wait for completion
|
||||
└──────────────┘ │
|
||||
│ ▼
|
||||
├───────────── createToolResultMessage()
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌──────────┐
|
||||
│ TC2 │ ──► │ │ Result1 │
|
||||
└──────┘ └──────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ execute() │
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ TC3 │ ──► │
|
||||
└──────┘ │
|
||||
│ ▼
|
||||
├───── createToolResultMessage()
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌──────────┐
|
||||
│ execute() │ │ │ Result2 │
|
||||
└──────────────┘ └──────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
│ Result3 │
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
### Parallel Execution
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Parallel Execution Flow │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────┐
|
||||
│ TC1 │ ──► prepareToolCall() ──► create closure ──► ┐
|
||||
└──────┘ │
|
||||
│
|
||||
┌──────┐ │
|
||||
│ TC2 │ ──► prepareToolCall() ──► create closure ──► ├─► All closures queued
|
||||
└──────┘ │
|
||||
│
|
||||
┌──────┐ │
|
||||
│ TC3 │ ──► prepareToolCall() ──► create closure ──► ┘
|
||||
└──────┘
|
||||
|
||||
│
|
||||
▼
|
||||
┌───────────────────────┐
|
||||
│ for closure in closures│
|
||||
│ execute_closure() │
|
||||
└───────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────┐
|
||||
│ Collect all results │
|
||||
└───────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────┐
|
||||
│ createToolResult() │
|
||||
└───────────────────────┘
|
||||
```
|
||||
|
||||
## Helper Types
|
||||
|
||||
### ExecutedToolCallBatch
|
||||
|
||||
```julia
|
||||
struct ExecutedToolCallBatch
|
||||
messages::Vector{ToolResultMessage}
|
||||
terminate::Bool
|
||||
end
|
||||
```
|
||||
|
||||
- `messages`: All tool result messages
|
||||
- `terminate`: If true, stop agent after this batch
|
||||
|
||||
### PrepareNextTurnContext
|
||||
|
||||
```julia
|
||||
struct PrepareNextTurnContext
|
||||
message::AssistantMessage
|
||||
tool_results::Vector{ToolResultMessage}
|
||||
context::AgentContext
|
||||
new_messages::Vector{AgentMessage}
|
||||
end
|
||||
```
|
||||
|
||||
Used by prepare_next_turn hook to decide next steps.
|
||||
|
||||
### Before/After Tool Call Contexts
|
||||
|
||||
```julia
|
||||
struct BeforeToolCallContext
|
||||
assistant_message::AssistantMessage
|
||||
tool_call::ToolCall
|
||||
args::Any
|
||||
context::AgentContext
|
||||
end
|
||||
|
||||
struct BeforeToolCallResult
|
||||
block::Union{Bool, Nothing}
|
||||
reason::Union{String, Nothing}
|
||||
end
|
||||
|
||||
struct AfterToolCallContext
|
||||
assistant_message::AssistantMessage
|
||||
tool_call::ToolCall
|
||||
args::Any
|
||||
result::AgentToolResult
|
||||
is_error::Bool
|
||||
context::AgentContext
|
||||
end
|
||||
|
||||
struct AfterToolCallResult
|
||||
content::Union{Vector{MessageContent}, Nothing}
|
||||
details::Union{Any, Nothing}
|
||||
is_error::Union{Bool, Nothing}
|
||||
usage::Union{Usage, Nothing}
|
||||
terminate::Union{Bool, Nothing}
|
||||
end
|
||||
```
|
||||
|
||||
## Event Emission Timeline
|
||||
|
||||
```
|
||||
AgentStartEvent
|
||||
│
|
||||
├─ TurnStartEvent (turn 1)
|
||||
│ │
|
||||
│ ├─ MessageStartEvent (user prompt)
|
||||
│ ├─ MessageEndEvent (user prompt)
|
||||
│ │
|
||||
│ ├─ MessageStartEvent (assistant)
|
||||
│ ├─ MessageUpdateEvent (text delta)
|
||||
│ ├─ MessageUpdateEvent (tool call delta)
|
||||
│ ├─ MessageEndEvent (assistant)
|
||||
│ │
|
||||
│ ├─ ToolExecutionStartEvent (tc1)
|
||||
│ ├─ ToolExecutionEndEvent (tc1)
|
||||
│ │
|
||||
│ ├─ ToolExecutionStartEvent (tc2)
|
||||
│ ├─ ToolExecutionEndEvent (tc2)
|
||||
│ │
|
||||
│ └─ TurnEndEvent (assistant, tool_results)
|
||||
│
|
||||
├─ TurnStartEvent (turn 2 - if needed)
|
||||
│ │
|
||||
│ ├─ MessageStartEvent (steering/follow-up)
|
||||
│ ├─ MessageEndEvent (steering/follow-up)
|
||||
│ │
|
||||
│ ├─ MessageStartEvent (assistant)
|
||||
│ ├─ MessageUpdateEvent (text)
|
||||
│ ├─ MessageEndEvent (assistant)
|
||||
│ │
|
||||
│ └─ TurnEndEvent (assistant, [])
|
||||
│
|
||||
└─ AgentEndEvent (final messages)
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### 1. Message Transformation Pipeline
|
||||
|
||||
```
|
||||
AgentMessage[] (internal)
|
||||
│
|
||||
│ transform_context()
|
||||
▼
|
||||
AgentMessage[] (transformed)
|
||||
│
|
||||
│ convert_to_llm()
|
||||
▼
|
||||
Message[] (LLM API)
|
||||
```
|
||||
|
||||
### 2. Tool Call Lifecycle
|
||||
|
||||
```
|
||||
ToolCall (in assistant message)
|
||||
│
|
||||
├─ before_tool_call (hook)
|
||||
│
|
||||
├─ prepareToolCall()
|
||||
│ ├─ validate arguments
|
||||
│ └─ prepare arguments (optional)
|
||||
│
|
||||
├─ execute()
|
||||
│ ├─ Immediate: return result
|
||||
│ └─ Prepared: async execution
|
||||
│
|
||||
├─ after_tool_call (hook)
|
||||
│
|
||||
└─ createToolResultMessage()
|
||||
```
|
||||
|
||||
### 3. Turn Termination
|
||||
|
||||
```julia
|
||||
# Turn ends when:
|
||||
# 1. No more pending messages
|
||||
# 2. No more tool calls to execute
|
||||
# 3. should_stop_after_turn() returns true
|
||||
|
||||
# Reasons to stop:
|
||||
# - Max turns reached
|
||||
# - Tool returned terminate=true
|
||||
# - Error or abort
|
||||
# - Steering/follow-up queues empty
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use sequential execution** for tools that modify shared state
|
||||
2. **Use parallel execution** for independent tool calls (better performance)
|
||||
3. **Implement prepare_next_turn** for dynamic model/thinking level changes
|
||||
4. **Use before_tool_call** for logging or blocking sensitive operations
|
||||
5. **Use after_tool_call** for modifying results or collecting metrics
|
||||
|
||||
## Complete Example
|
||||
|
||||
```julia
|
||||
using AgentCore
|
||||
|
||||
# Create config
|
||||
config = AgentLoopConfig(
|
||||
model = my_model,
|
||||
reasoning = THINKING_MEDIUM,
|
||||
tool_execution = EXECUTION_PARALLEL,
|
||||
before_tool_call = myBeforeToolCallHook,
|
||||
after_tool_call = myAfterToolCallHook,
|
||||
prepare_next_turn = myPrepareNextTurnHook,
|
||||
convert_to_llm = myConvertToLlm,
|
||||
transform_context = myTransformContext,
|
||||
get_api_key = myGetApiKey,
|
||||
get_steering_messages = myGetSteeringMessages,
|
||||
get_follow_up_messages = myGetFollowUpMessages,
|
||||
)
|
||||
|
||||
# Start agent loop
|
||||
stream = agentLoop(
|
||||
[UserMessage("user", [TextContent("Hello")], timestamp)],
|
||||
AgentContext(system_prompt, messages, tools),
|
||||
config,
|
||||
nothing,
|
||||
stream_fn,
|
||||
)
|
||||
|
||||
# Consume events
|
||||
final_messages = []
|
||||
for event in stream
|
||||
if event isa MessageEndEvent
|
||||
push!(final_messages, event.message)
|
||||
end
|
||||
end
|
||||
|
||||
# Or use event sink
|
||||
messages = []
|
||||
emit(event) = push!(messages, event)
|
||||
|
||||
messages = runAgentLoop(
|
||||
[UserMessage(...)],
|
||||
context,
|
||||
config,
|
||||
emit,
|
||||
nothing,
|
||||
stream_fn,
|
||||
)
|
||||
```
|
||||
|
||||
This documentation provides a comprehensive understanding of the AgentLoop component, including its architecture, main functions, execution modes, and best practices for building AI agents with AgentCore.jl.
|
||||
Reference in New Issue
Block a user