update
This commit is contained in:
@@ -405,87 +405,295 @@
|
||||
└── Manages ──► PromptTemplates
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
## Data Flow with Type Transformations
|
||||
|
||||
### Complete User Input → Conversation History Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Data Flow Between Layers │
|
||||
│ Level 1: User Input │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
User Input
|
||||
• String: "Hello, what's in the directory?"
|
||||
• AgentMessage: UserMessage(...)
|
||||
• Vector{AgentMessage}: [UserMessage(...), AssistantMessage(...)]
|
||||
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Agent.prompt() / normalizePromptInput() │
|
||||
│ │
|
||||
│ Type Dispatch: │
|
||||
│ • String → UserMessage("user", [TextContent(input)], ts) │
|
||||
│ • AgentMessage → [input] (wrap in array) │
|
||||
│ • Vector{AgentMessage} → input (pass-through) │
|
||||
│ │
|
||||
│ Output: Vector{AgentMessage} │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ AgentState.messages (AgentMessage[]) │
|
||||
│ │
|
||||
│ AgentMessage Types: │
|
||||
│ • UserMessage (role: "user") │
|
||||
│ • AssistantMessage (role: "assistant") │
|
||||
│ • ToolResultMessage (role: "toolResult") │
|
||||
│ • BashExecutionMessage (custom) │
|
||||
│ • CompactionSummaryMessage (custom) │
|
||||
│ • BranchSummaryMessage (custom) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Level 2: AgentLoop Processing │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
User Input (String/Message)
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ transform_context() (optional hook) │
|
||||
│ │
|
||||
│ Input: Vector{AgentMessage} │
|
||||
│ Output: Vector{AgentMessage} (transformed) │
|
||||
│ - Can truncate, filter, or modify messages │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ Agent.prompt() │
|
||||
│ - normalizeInput() │
|
||||
└──────────────────────┘
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ convertToLlm() - Type Transformation Pipeline │
|
||||
│ │
|
||||
│ Input: Vector{AgentMessage} │
|
||||
│ Output: Vector{Message} (for LLM API) │
|
||||
│ │
|
||||
│ Single Dispatch Mapping: │
|
||||
│ • UserMessage → UserMessage (pass-through) │
|
||||
│ • AssistantMessage → AssistantMessage (pass-through) │
|
||||
│ • ToolResultMessage → ToolResultMessage (pass-through) │
|
||||
│ │
|
||||
│ Custom Message Conversions: │
|
||||
│ • BashExecutionMessage → UserMessage (via bashExecutionToText)│
|
||||
│ • CompactionSummaryMessage → UserMessage (wrapped) │
|
||||
│ • BranchSummaryMessage → UserMessage (wrapped) │
|
||||
└────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ AgentState.messages │ ──► AgentMessage[]
|
||||
└──────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Context for LLM API │
|
||||
│ - system_prompt: String │
|
||||
│ - messages: Vector{Message} │
|
||||
│ - tools: Vector{AgentTool} │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ AgentLoop │
|
||||
│ - transform_context │
|
||||
└──────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ LLM API Call (stream_fn) │
|
||||
│ │
|
||||
│ Input: model, context, config │
|
||||
│ Output: Stream{AssistantMessageEvent} │
|
||||
│ • StartEvent: partial AssistantMessage │
|
||||
│ • TextStartEvent/TextDeltaEvent/TextEndEvent │
|
||||
│ • ToolCallStartEvent/ToolCallDeltaEvent/ToolCallEndEvent │
|
||||
│ • DoneEvent: final AssistantMessage with usage │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ convertToLlm() │ ──► Transforms AgentMessage[] to Message[]
|
||||
└──────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ AssistantMessage (returned from LLM) │
|
||||
│ │
|
||||
│ • role: "assistant" │
|
||||
│ • content: Vector{MessageContent} │
|
||||
│ └─ Contains: TextContent[] and/or ToolCall[] │
|
||||
│ • api, provider, model: String │
|
||||
│ • usage: Usage (input, output, cache_read, cache_write) │
|
||||
│ • stop_reason: String ("done", "length", "error", etc.) │
|
||||
│ • error_message: Union{String, Nothing} │
|
||||
│ • timestamp: Timestamp (Int64) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─► Append to AgentState.messages (AssistantMessage)
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ LLM API (StreamFn) │
|
||||
│ - Context: Message[] │
|
||||
└──────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ executeToolCalls() - Tool Processing │
|
||||
│ │
|
||||
│ Extract: filter(c -> c isa ToolCall, assistant.content) │
|
||||
│ Output: ExecutedToolCallBatch │
|
||||
│ • messages: Vector{ToolResultMessage} │
|
||||
│ • terminate: Bool │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ Response (Streaming) │
|
||||
│ - Text deltas │
|
||||
│ - Tool call deltas │
|
||||
└──────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ ToolResultMessage (for each ToolCall) │
|
||||
│ │
|
||||
│ • role: "toolResult" │
|
||||
│ • tool_call_id: String (matches ToolCall.id) │
|
||||
│ • tool_name: String (matches ToolCall.name) │
|
||||
│ • content: Vector{MessageContent} │
|
||||
│ • details: Any (tool-specific) │
|
||||
│ • usage: Union{Usage, Nothing} │
|
||||
│ • added_tool_names: Union{Vector{String}, Nothing} │
|
||||
│ • is_error: Bool │
|
||||
│ • timestamp: Timestamp (Int64) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─► Append to AgentState.messages (ToolResultMessage)
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ AssistantMessage │
|
||||
│ - content: Message[] │
|
||||
└──────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Updated AgentState.messages (AgentMessage[]) │
|
||||
│ │
|
||||
│ Conversation History: │
|
||||
│ [UserMessage, AssistantMessage, ToolResultMessage, ...] │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Level 3: Session Storage (optional, for persistence) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
AgentState.messages (Vector{AgentMessage})
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ AgentState.messages │ ──► Appended to conversation
|
||||
└──────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Session Storage (JSONL) │
|
||||
│ │
|
||||
│ SessionTreeEntry Types: │
|
||||
│ • MessageEntry (agent_message) │
|
||||
│ • CompactionEntry (summary, tokens_before) │
|
||||
│ • BranchSummaryEntry (from_id, summary) │
|
||||
│ • ModelChangeEntry (provider, model_id) │
|
||||
│ • ThinkingLevelChangeEntry (thinking_level) │
|
||||
│ • ActiveToolsChangeEntry (active_tool_names) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ Tool Execution │
|
||||
│ - Extract ToolCalls │
|
||||
│ - Execute tools │
|
||||
└──────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ ToolResultMessage[] │
|
||||
└──────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ AgentState.messages │ ──► Tool results appended
|
||||
└──────────────────────┘
|
||||
│
|
||||
│ (Loop back to LLM or end)
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ Session Storage │
|
||||
│ - JSONL format │
|
||||
│ - Tree entries │
|
||||
└──────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Persisted Data (JSON format) │
|
||||
│ - Each entry has: id, parent_id, timestamp, type │
|
||||
│ - MessageEntry contains full AgentMessage │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Tool Call Execution Flow (Detailed)
|
||||
|
||||
```
|
||||
ToolCall (from AssistantMessage.content)
|
||||
│
|
||||
├─ type: "tool"
|
||||
├─ id: "tc_abc123"
|
||||
├─ name: "bash"
|
||||
├─ arguments: Dict("command" => "ls -la")
|
||||
└─ partial_json: nothing
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ prepareToolCall() │
|
||||
│ │
|
||||
│ Input: tool_call::ToolCall │
|
||||
│ Output: Union{PreparedToolCall, ImmediateToolCallOutcome} │
|
||||
│ │
|
||||
│ Steps: │
|
||||
│ 1. Find tool by name in current_context.tools │
|
||||
│ 2. before_tool_call hook (optional) │
|
||||
│ Input: BeforeToolCallContext │
|
||||
│ Output: BeforeToolCallResult (block, reason) or nothing │
|
||||
│ 3. prepareToolCallArguments() (optional) │
|
||||
│ Input: tool_call.arguments::Dict │
|
||||
│ Output: prepared_arguments::Any │
|
||||
│ 4. validateToolArguments() (optional) │
|
||||
│ Input: prepared_tool_call.arguments │
|
||||
│ Output: validated_args::Any │
|
||||
│ 5. Return: PreparedToolCall(kind, tool_call, tool, args) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ executePreparedToolCall() (if prepared) │
|
||||
│ │
|
||||
│ Input: PreparedToolCall │
|
||||
│ Output: ExecutedToolCallOutcome │
|
||||
│ │
|
||||
│ tool.execute(tool_call.id, args, signal, on_update) │
|
||||
│ │ │
|
||||
│ └─ Returns: AgentToolResultMutable │
|
||||
│ • content::Vector{MessageContent} │
|
||||
│ • details::Any │
|
||||
│ • usage::Union{Usage, Nothing} │
|
||||
│ • added_tool_names::Union{Vector{String}, Nothing} │
|
||||
│ • terminate::Union{Bool, Nothing} │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ finalizeExecutedToolCall() │
|
||||
│ │
|
||||
│ Input: ExecutedToolCallOutcome │
|
||||
│ Output: FinalizedToolCallOutcome │
|
||||
│ │
|
||||
│ Steps: │
|
||||
│ 1. after_tool_call hook (optional) │
|
||||
│ Input: AfterToolCallContext │
|
||||
│ Output: AfterToolCallResult (patches) │
|
||||
│ 2. Apply patches to result │
|
||||
│ 3. Return: FinalizedToolCallOutcome(tool_call, result, error)│
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ createToolResultMessage() │
|
||||
│ │
|
||||
│ Input: FinalizedToolCallOutcome │
|
||||
│ Output: ToolResultMessage │
|
||||
│ │
|
||||
│ Fields: │
|
||||
│ • role: "toolResult" │
|
||||
│ • tool_call_id: tool_call.id │
|
||||
│ • tool_name: tool_call.name │
|
||||
│ • content: result.content │
|
||||
│ • details: result.details │
|
||||
│ • usage: result.usage │
|
||||
│ • added_tool_names: result.added_tool_names │
|
||||
│ • is_error: is_error │
|
||||
│ • timestamp: Int64(Dates.now(Dates.UTC).datetime) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Emit: ToolResultMessage to conversation │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Summary of Type Transformations │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
User Input (String)
|
||||
│
|
||||
├─► normalizePromptInput()
|
||||
│ └─► UserMessage (AgentMessage subtype)
|
||||
│
|
||||
Vector{AgentMessage}
|
||||
│
|
||||
├─► transform_context() (optional)
|
||||
│ └─► Vector{AgentMessage} (transformed)
|
||||
│
|
||||
├─► convertToLlm()
|
||||
│ └─► Vector{Message} (LLM API format)
|
||||
│ ├── UserMessage (pass-through)
|
||||
│ ├── AssistantMessage (pass-through)
|
||||
│ ├── ToolResultMessage (pass-through)
|
||||
│ └── Custom messages → UserMessage
|
||||
│
|
||||
AssistantMessage (from LLM)
|
||||
│
|
||||
├─► executeToolCalls()
|
||||
│ └─► ToolResultMessage[]
|
||||
│
|
||||
ToolResultMessage[]
|
||||
│
|
||||
└─► Appended to AgentState.messages
|
||||
└─► Vector{AgentMessage} (updated conversation history)
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
## Summary
|
||||
|
||||
The AgentCore.jl architecture follows a clean separation of concerns:
|
||||
@@ -495,4 +703,48 @@ The AgentCore.jl architecture follows a clean separation of concerns:
|
||||
3. **AgentLoop** - Core LLM interaction loop
|
||||
4. **Session** - Conversation history management
|
||||
|
||||
### Data Transformation Summary
|
||||
|
||||
```
|
||||
Input Type Flow:
|
||||
User Input (String/Message)
|
||||
│
|
||||
├─ normalizePromptInput()
|
||||
│ └─► Vector{AgentMessage}
|
||||
│
|
||||
├─ transform_context() (optional)
|
||||
│ └─► Vector{AgentMessage} (transformed)
|
||||
│
|
||||
├─ convertToLlm()
|
||||
│ └─► Vector{Message} (LLM API format)
|
||||
│
|
||||
├─ LLM API (stream_fn)
|
||||
│ └─► AssistantMessage
|
||||
│
|
||||
├─ executeToolCalls()
|
||||
│ └─► ToolResultMessage[]
|
||||
│
|
||||
└─► Vector{AgentMessage} (final conversation)
|
||||
```
|
||||
|
||||
### Key Data Flow Patterns
|
||||
|
||||
1. **Message Transformation**: `AgentMessage[] → Message[]` via `convertToLlm()`
|
||||
- UserMessage → UserMessage (pass-through)
|
||||
- AssistantMessage → AssistantMessage (pass-through)
|
||||
- ToolResultMessage → ToolResultMessage (pass-through)
|
||||
- Custom messages (Bash, Compaction, Branch) → UserMessage
|
||||
|
||||
2. **Tool Execution**: `ToolCall → ToolResultMessage`
|
||||
- prepareToolCall() validates and prepares
|
||||
- execute() runs the tool
|
||||
- finalize() applies hooks and returns outcome
|
||||
- createToolResultMessage() creates result entry
|
||||
|
||||
3. **Event Streaming**: `Stream{Event}` with lifecycle events
|
||||
- AgentStartEvent, TurnStartEvent
|
||||
- MessageStartEvent, MessageUpdateEvent, MessageEndEvent
|
||||
- ToolExecutionStartEvent, ToolExecutionEndEvent
|
||||
- TurnEndEvent, AgentEndEvent
|
||||
|
||||
Each layer transforms data and passes it to the next layer, with clear interfaces and event hooks for customization.
|
||||
|
||||
@@ -243,16 +243,35 @@ followUp(agent, UserMessage(...))
|
||||
|
||||
```julia
|
||||
# Transform messages before sending to LLM
|
||||
function myConvertToLlm(messages::Vector{AgentMessage})
|
||||
return filter(
|
||||
m -> m.role in ["user", "assistant", "toolResult"],
|
||||
messages
|
||||
)
|
||||
function myConvertToLlm(messages::Vector{AgentMessage})::Vector{Message}
|
||||
result::Vector{Message} = Message[]
|
||||
for m in messages
|
||||
converted = convertToLlmMessage(m)
|
||||
if !isnothing(converted)
|
||||
push!(result, converted)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
agent = Agent(Dict(:convertToLlm => myConvertToLlm))
|
||||
```
|
||||
|
||||
**Data Flow**:
|
||||
```
|
||||
Vector{AgentMessage}
|
||||
│
|
||||
│ convertToLlmMessage() dispatches on type:
|
||||
│ • UserMessage → UserMessage (pass-through)
|
||||
│ • AssistantMessage → AssistantMessage (pass-through)
|
||||
│ • ToolResultMessage → ToolResultMessage (pass-through)
|
||||
│ • BashExecutionMessage → UserMessage (bashExecutionToText)
|
||||
│ • CompactionSummaryMessage → UserMessage (wrapped)
|
||||
│ • BranchSummaryMessage → UserMessage (wrapped)
|
||||
▼
|
||||
Vector{Message} (for LLM API)
|
||||
```
|
||||
|
||||
#### transform_context
|
||||
|
||||
```julia
|
||||
|
||||
@@ -27,11 +27,16 @@ agentLoopContinue()
|
||||
└─ Returns: EventStream
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Internal Flow │
|
||||
│ Data Flow with Type Transformations │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 1. runAgentLoop() ── Entry point for new conversation │
|
||||
│ Input: prompts::Vector{AgentMessage} │
|
||||
│ context::AgentContext (system_prompt, messages, tools) │
|
||||
│ config::AgentLoopConfig │
|
||||
│ Output: new_messages::Vector{AgentMessage} (appended prompts + turns) │
|
||||
│ │
|
||||
│ - Creates copy of prompts │
|
||||
│ - Appends prompts to context.messages │
|
||||
│ - Emits AgentStartEvent │
|
||||
@@ -43,12 +48,20 @@ agentLoopContinue()
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 2. runLoop() ── Main event loop │
|
||||
│ Input: current_context::AgentContext │
|
||||
│ new_messages::Vector{AgentMessage} │
|
||||
│ Output: N/A (writes to new_messages and context.messages) │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ while true: │ │
|
||||
│ │ 1. Get steering/follow-up messages (if any) │ │
|
||||
│ │ 2. Emit messages as UserMessage │ │
|
||||
│ │ 3. streamAssistantResponse() │ │
|
||||
│ │ - Input: context.messages::Vector{AgentMessage} │ │
|
||||
│ │ - Output: message::AssistantMessage │ │
|
||||
│ │ 4. Execute tool calls (sequential or parallel) │ │
|
||||
│ │ - Input: AssistantMessage with ToolCall[] │ │
|
||||
│ │ - Output: tool_results::Vector{ToolResultMessage} │ │
|
||||
│ │ 5. Emit TurnEndEvent │ │
|
||||
│ │ 6. prepare_next_turn (optional) │ │
|
||||
│ │ 7. should_stop_after_turn? (check termination) │ │
|
||||
@@ -59,27 +72,89 @@ agentLoopContinue()
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 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 │
|
||||
│ Input: context::AgentContext │
|
||||
│ config::AgentLoopConfig │
|
||||
│ Output: message::AssistantMessage │
|
||||
│ │
|
||||
│ Data Transformations: │
|
||||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Step 1: transform_context (optional) │ │
|
||||
│ │ Input: context.messages::Vector{AgentMessage} │ │
|
||||
│ │ Output: messages::Vector{AgentMessage} (transformed) │ │
|
||||
│ │ │ │
|
||||
│ │ Step 2: convert_to_llm │ │
|
||||
│ │ Input: messages::Vector{AgentMessage} │ │
|
||||
│ │ Output: llm_messages::Vector{Message} │ │
|
||||
│ │ - UserMessage → UserMessage │ │
|
||||
│ │ - AssistantMessage → AssistantMessage │ │
|
||||
│ │ - ToolResultMessage → ToolResultMessage │ │
|
||||
│ │ - BashExecutionMessage → UserMessage │ │
|
||||
│ │ - CompactionSummaryMessage → UserMessage │ │
|
||||
│ │ - BranchSummaryMessage → UserMessage │ │
|
||||
│ │ │ │
|
||||
│ │ Step 3: Call stream_fn │ │
|
||||
│ │ Input: model, llm_context::Context, config │ │
|
||||
│ │ Output: response::Stream (events) │ │
|
||||
│ │ │ │
|
||||
│ │ Step 4: Stream events │ │
|
||||
│ │ Events: start, text_start/delta/end, toolcall_start/delta/end │ │
|
||||
│ │ Final: AssistantMessage (with ToolCall[] in content) │ │
|
||||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 4. executeToolCalls() ── Tool execution │
|
||||
│ Input: assistant_message::AssistantMessage (contains ToolCall[]) │
|
||||
│ current_context::AgentContext │
|
||||
│ Output: ExecutedToolCallBatch (messages::ToolResultMessage[], terminate) │
|
||||
│ │
|
||||
│ For each ToolCall: │
|
||||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ if EXECUTION_SEQUENTIAL || has_sequential_tool: │ │
|
||||
│ │ executeToolCallsSequential() │ │
|
||||
│ │ else: │ │
|
||||
│ │ executeToolCallsParallel() │ │
|
||||
│ │ prepareToolCall() │ │
|
||||
│ │ Input: tool_call::ToolCall │ │
|
||||
│ │ Output: PreparedToolCall or ImmediateToolCallOutcome │ │
|
||||
│ │ - validates arguments │ │
|
||||
│ │ - runs before_tool_call hook (optional) │ │
|
||||
│ │ - runs prepare_arguments hook (optional) │ │
|
||||
│ │ │ │
|
||||
│ │ executePreparedToolCall() (if prepared) │ │
|
||||
│ │ Input: PreparedToolCall │ │
|
||||
│ │ Output: ExecutedToolCallOutcome │ │
|
||||
│ │ - calls tool.execute() │ │
|
||||
│ │ - returns AgentToolResultMutable │ │
|
||||
│ │ │ │
|
||||
│ │ finalizeExecutedToolCall() │ │
|
||||
│ │ Input: ExecutedToolCallOutcome │ │
|
||||
│ │ Output: FinalizedToolCallOutcome │ │
|
||||
│ │ - runs after_tool_call hook (optional) │ │
|
||||
│ │ - returns ToolCall + AgentToolResultMutable + is_error │ │
|
||||
│ │ │ │
|
||||
│ │ createToolResultMessage() │ │
|
||||
│ │ Input: FinalizedToolCallOutcome │ │
|
||||
│ │ Output: ToolResultMessage │ │
|
||||
│ │ - role: "toolResult" │ │
|
||||
│ │ - tool_call_id, tool_name, content, details │ │
|
||||
│ │ - usage, added_tool_names, is_error, timestamp │ │
|
||||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Sequential: executeToolCallsSequential() │ │
|
||||
│ │ - Executes tools one at a time, waits for each │ │
|
||||
│ │ - Returns batch of ToolResultMessage[] │ │
|
||||
│ │ │ │
|
||||
│ │ Parallel: executeToolCallsParallel() │ │
|
||||
│ │ - Creates closures for async execution │ │
|
||||
│ │ - Executes all closures, collects results │ │
|
||||
│ │ - Returns batch of ToolResultMessage[] │ │
|
||||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 5. AgentEndEvent ── Final event with all messages │
|
||||
│ Output: messages::Vector{AgentMessage} │
|
||||
│ Contains: [UserMessage, AssistantMessage, ToolResultMessage, ...] │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -301,17 +376,44 @@ function streamAssistantResponse(
|
||||
)::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
|
||||
**Data Flow**:
|
||||
|
||||
```
|
||||
Input: context.messages::Vector{AgentMessage}
|
||||
↓
|
||||
[transform_context] (optional hook)
|
||||
↓
|
||||
messages::Vector{AgentMessage}
|
||||
↓
|
||||
[convert_to_llm] - Type transformation pipeline
|
||||
↓
|
||||
llm_messages::Vector{Message}
|
||||
│
|
||||
│ AgentMessage → Message mapping:
|
||||
│ • UserMessage → UserMessage (pass-through)
|
||||
│ • AssistantMessage → AssistantMessage (pass-through)
|
||||
│ • ToolResultMessage → ToolResultMessage (pass-through)
|
||||
│ • BashExecutionMessage → UserMessage (text conversion)
|
||||
│ • CompactionSummaryMessage → UserMessage (text wrapped)
|
||||
│ • BranchSummaryMessage → UserMessage (text wrapped)
|
||||
│
|
||||
▼
|
||||
Context(system_prompt, llm_messages, tools)
|
||||
↓
|
||||
stream_fn(model, context, config) - LLM API call
|
||||
↓
|
||||
Stream of AssistantMessageEvent:
|
||||
• StartEvent (partial AssistantMessage)
|
||||
• TextStartEvent/TextDeltaEvent/TextEndEvent
|
||||
• ToolCallStartEvent/ToolCallDeltaEvent/ToolCallEndEvent
|
||||
• DoneEvent (final AssistantMessage with usage, stop_reason)
|
||||
↓
|
||||
Return: AssistantMessage
|
||||
- content::Vector{MessageContent}
|
||||
- usage::Usage
|
||||
- stop_reason::String
|
||||
- Contains ToolCall[] if tool calls requested
|
||||
```
|
||||
|
||||
### executeToolCalls()
|
||||
|
||||
@@ -320,27 +422,56 @@ function executeToolCalls(
|
||||
current_context::AgentContext,
|
||||
assistant_message::AssistantMessage,
|
||||
config::AgentLoopConfig,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
signal::Union{Nothing, AbortSignal>,
|
||||
emit::AgentEventSink,
|
||||
)::ExecutedToolCallBatch
|
||||
```
|
||||
|
||||
**Logic**:
|
||||
```julia
|
||||
tool_calls = filter(c -> c isa ToolCall, assistant_message.content)
|
||||
**Data Flow**:
|
||||
|
||||
# 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
|
||||
```
|
||||
Input: assistant_message::AssistantMessage
|
||||
- content::Vector{MessageContent}
|
||||
└─ Contains ToolCall[] and/or TextContent[]
|
||||
↓
|
||||
filter(c -> c isa ToolCall, assistant_message.content)
|
||||
↓
|
||||
tool_calls::Vector{ToolCall}
|
||||
- type: "tool"
|
||||
- id::String
|
||||
- name::String
|
||||
- arguments::Dict{String, Any}
|
||||
- partial_json::Union{String, Nothing}
|
||||
↓
|
||||
Check execution mode:
|
||||
• config.tool_execution (sequential/parallel)
|
||||
• Any tool.execution_mode == EXECUTION_SEQUENTIAL?
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Sequential Mode (or has_sequential_tool) │
|
||||
│ For each tool_call in tool_calls: │
|
||||
│ prepareToolCall() → PreparedToolCall │
|
||||
│ executePreparedToolCall() → ExecutedToolCallOutcome │
|
||||
│ finalizeExecutedToolCall() → FinalizedToolCallOutcome │
|
||||
│ createToolResultMessage() → ToolResultMessage │
|
||||
│ (wait for completion before next tool) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Parallel Mode │
|
||||
│ For each tool_call in tool_calls: │
|
||||
│ prepareToolCall() → (PreparedToolCall | ImmediateOutcome) │
|
||||
│ If prepared: create closure │
|
||||
│ If immediate: execute and add to finalized_calls │
|
||||
│ │
|
||||
│ For each entry in finalized_calls: │
|
||||
│ If closure: execute closure │
|
||||
│ If finalized: use as-is │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
ExecutedToolCallBatch
|
||||
- messages::Vector{ToolResultMessage}
|
||||
- terminate::Bool (true if all tools have terminate=true)
|
||||
```
|
||||
|
||||
### executeToolCallsSequential()
|
||||
@@ -400,29 +531,77 @@ function prepareToolCall(
|
||||
)::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
|
||||
**Data Flow**:
|
||||
|
||||
```
|
||||
Input: tool_call::ToolCall
|
||||
- id::String
|
||||
- name::String
|
||||
- arguments::Dict{String, Any}
|
||||
↓
|
||||
findfirst(t -> t.name == tool_call.name, current_context.tools)
|
||||
↓
|
||||
If tool is nothing:
|
||||
→ ImmediateToolCallOutcome("immediate", error_result, is_error=true)
|
||||
↓
|
||||
If tool exists:
|
||||
[before_tool_call hook] (optional)
|
||||
Input: BeforeToolCallContext(assistant_message, tool_call, args, context)
|
||||
Output: BeforeToolCallResult (block, reason) or nothing
|
||||
If block=true → ImmediateToolCallOutcome(error)
|
||||
↓
|
||||
prepareToolCallArguments(tool, tool_call)
|
||||
Input: tool_call.arguments::Dict{String, Any}
|
||||
Output: prepared_arguments::Any
|
||||
(Optional: transform arguments before validation)
|
||||
↓
|
||||
validateToolArguments(tool, prepared_tool_call)
|
||||
Input: prepared_tool_call.arguments
|
||||
Output: validated_args::Any
|
||||
(Optional: JSON schema validation)
|
||||
↓
|
||||
Return: PreparedToolCall("prepared", tool_call, tool, validated_args)
|
||||
- kind: "prepared"
|
||||
- tool_call: ToolCall (original)
|
||||
- tool: AgentTool
|
||||
- args: validated arguments
|
||||
```
|
||||
|
||||
### executePreparedToolCall()
|
||||
|
||||
```julia
|
||||
function executePreparedToolCall(
|
||||
prepared::PreparedToolCall,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
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)
|
||||
**Data Flow**:
|
||||
|
||||
```
|
||||
Input: prepared::PreparedToolCall
|
||||
- tool_call::ToolCall
|
||||
- tool::AgentTool
|
||||
- args::Any (validated)
|
||||
↓
|
||||
tool.execute(tool_call.id, args, signal, on_update)
|
||||
Input: tool_call_id::String
|
||||
args::Any
|
||||
signal::Union{Any, Nothing}
|
||||
on_update::Function (partial_result → void)
|
||||
Output: AgentToolResultMutable
|
||||
- content::Vector{MessageContent}
|
||||
- details::Any
|
||||
- usage::Union{Usage, Nothing}
|
||||
- added_tool_names::Union{Vector{String}, Nothing}
|
||||
- terminate::Union{Bool, Nothing}
|
||||
↓
|
||||
Collect update events from on_update callbacks
|
||||
↓
|
||||
Return: ExecutedToolCallOutcome(result, is_error=false)
|
||||
- result::AgentToolResultMutable
|
||||
```
|
||||
|
||||
### finalizeExecutedToolCall()
|
||||
|
||||
@@ -433,13 +612,43 @@ function finalizeExecutedToolCall(
|
||||
prepared::PreparedToolCall,
|
||||
executed::ExecutedToolCallOutcome,
|
||||
config::AgentLoopConfig,
|
||||
signal::Union{Nothing, AbortSignal},
|
||||
signal::Union{Nothing, AbortSignal>,
|
||||
)::FinalizedToolCallOutcome
|
||||
```
|
||||
|
||||
**Flow**:
|
||||
1. after_tool_call hook (optional)
|
||||
2. Return FinalizedToolCallOutcome
|
||||
**Data Flow**:
|
||||
|
||||
```
|
||||
Input: executed::ExecutedToolCallOutcome
|
||||
- result::AgentToolResultMutable
|
||||
- is_error::Bool
|
||||
↓
|
||||
[after_tool_call hook] (optional)
|
||||
Input: AfterToolCallContext(
|
||||
assistant_message,
|
||||
tool_call,
|
||||
args,
|
||||
result,
|
||||
is_error,
|
||||
context
|
||||
)
|
||||
Output: AfterToolCallResult (optional patches)
|
||||
- content::Union{Vector{MessageContent}, Nothing}
|
||||
- details::Union{Any, Nothing}
|
||||
- is_error::Union{Bool, Nothing}
|
||||
- usage::Union{Usage, Nothing}
|
||||
- terminate::Union{Bool, Nothing}
|
||||
↓
|
||||
Apply patches to result (if any)
|
||||
result.content = result.content ∪ patches.content
|
||||
result.details = result.details ∪ patches.details
|
||||
is_error = is_error ∪ patches.is_error
|
||||
↓
|
||||
Return: FinalizedToolCallOutcome
|
||||
- tool_call::ToolCall (original)
|
||||
- result::AgentToolResultMutable (final)
|
||||
- is_error::Bool
|
||||
```
|
||||
|
||||
### createToolResultMessage()
|
||||
|
||||
@@ -449,19 +658,39 @@ function createToolResultMessage(
|
||||
)::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,
|
||||
)
|
||||
**Data Flow**:
|
||||
|
||||
```
|
||||
Input: finalized::FinalizedToolCallOutcome
|
||||
- tool_call::ToolCall
|
||||
- result::AgentToolResultMutable
|
||||
- content::Vector{MessageContent}
|
||||
- details::Any
|
||||
- usage::Union{Usage, Nothing}
|
||||
- added_tool_names::Union{Vector{String}, Nothing}
|
||||
- is_error::Bool
|
||||
↓
|
||||
Build ToolResultMessage:
|
||||
• role: "toolResult"
|
||||
• tool_call_id: tool_call.id
|
||||
• tool_name: tool_call.name
|
||||
• content: result.content
|
||||
• details: result.details
|
||||
• usage: result.usage
|
||||
• added_tool_names: result.added_tool_names
|
||||
• is_error: is_error
|
||||
• timestamp: Int64(Dates.now(Dates.UTC).datetime)
|
||||
↓
|
||||
Output: ToolResultMessage
|
||||
- role::String ("toolResult")
|
||||
- tool_call_id::String
|
||||
- tool_name::String
|
||||
- content::Vector{MessageContent}
|
||||
- details::Any
|
||||
- usage::Union{Usage, Nothing}
|
||||
- added_tool_names::Union{Vector{String}, Nothing}
|
||||
- is_error::Bool
|
||||
- timestamp::Timestamp (Int64)
|
||||
```
|
||||
|
||||
## Execution Modes
|
||||
@@ -650,35 +879,70 @@ AgentStartEvent
|
||||
### 1. Message Transformation Pipeline
|
||||
|
||||
```
|
||||
AgentMessage[] (internal)
|
||||
Vector{AgentMessage} (internal conversation history)
|
||||
│
|
||||
│ transform_context()
|
||||
▼
|
||||
AgentMessage[] (transformed)
|
||||
├─ transform_context() (optional hook)
|
||||
│ Input: Vector{AgentMessage}
|
||||
│ Output: Vector{AgentMessage} (transformed)
|
||||
│
|
||||
│ convert_to_llm()
|
||||
▼
|
||||
Message[] (LLM API)
|
||||
└─ convert_to_llm()
|
||||
│
|
||||
│ Type mapping (single dispatch):
|
||||
│ • UserMessage → UserMessage (pass-through)
|
||||
│ • AssistantMessage → AssistantMessage (pass-through)
|
||||
│ • ToolResultMessage → ToolResultMessage (pass-through)
|
||||
│ • BashExecutionMessage → UserMessage (text conversion)
|
||||
│ • CompactionSummaryMessage → UserMessage (text wrapped)
|
||||
│ • BranchSummaryMessage → UserMessage (text wrapped)
|
||||
│
|
||||
▼
|
||||
Vector{Message} (for LLM API)
|
||||
```
|
||||
|
||||
### 2. Tool Call Lifecycle
|
||||
### 2. Tool Call Lifecycle (with Data Transformations)
|
||||
|
||||
```
|
||||
ToolCall (in assistant message)
|
||||
ToolCall (in AssistantMessage.content)
|
||||
│
|
||||
├─ before_tool_call (hook)
|
||||
├─ before_tool_call hook (optional)
|
||||
│ Input: BeforeToolCallContext(
|
||||
│ assistant_message::AssistantMessage,
|
||||
│ tool_call::ToolCall,
|
||||
│ args::Dict{String, Any},
|
||||
│ context::AgentContext
|
||||
│ )
|
||||
│ Output: BeforeToolCallResult (block, reason) or nothing
|
||||
│
|
||||
├─ prepareToolCall()
|
||||
│ ├─ validate arguments
|
||||
│ └─ prepare arguments (optional)
|
||||
│ Input: tool_call::ToolCall
|
||||
│ Output: Union{PreparedToolCall, ImmediateToolCallOutcome}
|
||||
│ • PreparedToolCall (kind, tool_call, tool, args)
|
||||
│ • ImmediateToolCallOutcome (immediate, result, is_error)
|
||||
│
|
||||
├─ execute()
|
||||
│ ├─ Immediate: return result
|
||||
│ └─ Prepared: async execution
|
||||
├─ executePreparedToolCall() (if prepared)
|
||||
│ Input: PreparedToolCall
|
||||
│ Output: ExecutedToolCallOutcome
|
||||
│ tool.execute() returns AgentToolResultMutable
|
||||
│ • content::Vector{MessageContent}
|
||||
│ • details::Any
|
||||
│ • usage::Union{Usage, Nothing}
|
||||
│ • terminate::Union{Bool, Nothing}
|
||||
│
|
||||
├─ after_tool_call (hook)
|
||||
├─ finalizeExecutedToolCall()
|
||||
│ Input: ExecutedToolCallOutcome
|
||||
│ Output: FinalizedToolCallOutcome
|
||||
│ • tool_call::ToolCall
|
||||
│ • result::AgentToolResultMutable
|
||||
│ • is_error::Bool
|
||||
│
|
||||
└─ createToolResultMessage()
|
||||
Input: FinalizedToolCallOutcome
|
||||
Output: ToolResultMessage
|
||||
• role: "toolResult"
|
||||
• tool_call_id, tool_name
|
||||
• content::Vector{MessageContent}
|
||||
• details, usage, added_tool_names
|
||||
• is_error, timestamp
|
||||
```
|
||||
|
||||
### 3. Turn Termination
|
||||
|
||||
+274
-15
@@ -98,6 +98,52 @@
|
||||
|
||||
## Message Types
|
||||
|
||||
### Type Hierarchy
|
||||
|
||||
```
|
||||
Message (for LLM API)
|
||||
├── UserMessage (role: "user")
|
||||
│ ├── content::Vector{MessageContent}
|
||||
│ │ ├── TextContent (text::String)
|
||||
│ │ └── ImageContent (data::String, mime_type::String)
|
||||
│ └── timestamp::Timestamp (Int64)
|
||||
├── AssistantMessage (role: "assistant")
|
||||
│ ├── content::Vector{MessageContent}
|
||||
│ │ ├── TextContent
|
||||
│ │ └── ToolCall (type, id, name, arguments::Dict{String, Any})
|
||||
│ ├── api::String
|
||||
│ ├── provider::String
|
||||
│ ├── model::String
|
||||
│ ├── usage::Usage
|
||||
│ │ ├── input, output, cache_read, cache_write, total_tokens::Int64
|
||||
│ │ └── cost::UsageCost (input, output, cache_read, cache_write, total::Float64)
|
||||
│ ├── stop_reason::String
|
||||
│ ├── error_message::Union{String, Nothing}
|
||||
│ └── timestamp::Timestamp
|
||||
└── ToolResultMessage (role: "toolResult")
|
||||
├── tool_call_id::String
|
||||
├── tool_name::String
|
||||
├── content::Vector{MessageContent}
|
||||
├── details::Any
|
||||
├── usage::Union{Usage, Nothing}
|
||||
├── added_tool_names::Union{Vector{String}, Nothing}
|
||||
├── is_error::Bool
|
||||
└── timestamp::Timestamp
|
||||
|
||||
AgentMessage (internal, extends Message)
|
||||
├── UserMessage (same as above)
|
||||
├── AssistantMessage (same as above)
|
||||
├── ToolResultMessage (same as above)
|
||||
├── BashExecutionMessage (custom, converted to UserMessage)
|
||||
│ ├── role, command, output, exit_code
|
||||
│ ├── cancelled, truncated, full_output_path
|
||||
│ └── exclude_from_context::Bool
|
||||
├── CompactionSummaryMessage (custom, converted to UserMessage)
|
||||
│ ├── summary, tokens_before, timestamp
|
||||
└── BranchSummaryMessage (custom, converted to UserMessage)
|
||||
├── summary, from_id, timestamp
|
||||
```
|
||||
|
||||
### UserMessage
|
||||
|
||||
```julia
|
||||
@@ -108,6 +154,16 @@ struct UserMessage <: Message
|
||||
end
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```julia
|
||||
# Simple text message
|
||||
UserMessage(
|
||||
"user",
|
||||
[TextContent("Hello, how are you?")],
|
||||
Int64(Dates.now(Dates.UTC).datetime)
|
||||
)
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```julia
|
||||
# Simple text message
|
||||
@@ -496,9 +552,9 @@ end
|
||||
|
||||
**Note**: AgentState is mutable and used internally by Agent
|
||||
|
||||
## Key Conversion Functions
|
||||
## Message Transformation Pipeline
|
||||
|
||||
### convertToLlm()
|
||||
### convertToLlm() - AgentMessage[] → Message[]
|
||||
|
||||
```julia
|
||||
function convertToLlm(messages::Vector{AgentMessage})::Vector{Message}
|
||||
@@ -515,26 +571,53 @@ function convertToLlm(messages::Vector{AgentMessage})::Vector{Message}
|
||||
end
|
||||
```
|
||||
|
||||
**Purpose**: Transform AgentMessage[] to Message[] for LLM API
|
||||
**Data Flow**:
|
||||
|
||||
```
|
||||
Vector{AgentMessage} (internal conversation history)
|
||||
│
|
||||
│ Type dispatch on convertToLlmMessage():
|
||||
│
|
||||
│ • UserMessage → UserMessage (pass-through)
|
||||
│ • AssistantMessage → AssistantMessage (pass-through)
|
||||
│ • ToolResultMessage → ToolResultMessage (pass-through)
|
||||
│
|
||||
│ Custom messages converted to UserMessage:
|
||||
│ • BashExecutionMessage → UserMessage
|
||||
│ (via bashExecutionToText() for display)
|
||||
│ • CompactionSummaryMessage → UserMessage
|
||||
│ (wrapped with COMPACTION_SUMMARY_PREFIX/SUFFIX)
|
||||
│ • BranchSummaryMessage → UserMessage
|
||||
│ (wrapped with BRANCH_SUMMARY_PREFIX/SUFFIX)
|
||||
│
|
||||
▼
|
||||
Vector{Message} (for LLM API)
|
||||
- Excludes: BashExecutionMessage (if exclude_from_context)
|
||||
- Includes: All standard messages + converted custom messages
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```julia
|
||||
# Input: AgentMessage[]
|
||||
# Input: Vector{AgentMessage}
|
||||
[
|
||||
UserMessage(...),
|
||||
AssistantMessage(...),
|
||||
ToolResultMessage(...),
|
||||
BashExecutionMessage(...), # Will be converted to UserMessage
|
||||
CompactionSummaryMessage(...), # Will be converted to UserMessage
|
||||
UserMessage("user", [TextContent("Hello")], 1234567890),
|
||||
AssistantMessage("assistant", [
|
||||
TextContent("Hi there!"),
|
||||
ToolCall("bash", "call_123", "bash", Dict("command" => "ls"), nothing)
|
||||
], "openai", "openai", "gpt-4", Usage(...), "done", nothing, 1234567891),
|
||||
BashExecutionMessage("custom", "ls -la", "file1.md\nfile2.md\n", 0, false, false, nothing, 1234567892, false),
|
||||
CompactionSummaryMessage("compactionSummary", "Previous conversation compacted", 1000, 1234567893),
|
||||
]
|
||||
|
||||
# Output: Message[]
|
||||
# Output: Vector{Message}
|
||||
[
|
||||
UserMessage(...),
|
||||
AssistantMessage(...),
|
||||
ToolResultMessage(...),
|
||||
UserMessage(...), # Converted from BashExecutionMessage
|
||||
UserMessage(...), # Converted from CompactionSummaryMessage
|
||||
UserMessage("user", [TextContent("Hello")], 1234567890),
|
||||
AssistantMessage("assistant", [
|
||||
TextContent("Hi there!"),
|
||||
ToolCall(...)
|
||||
], "openai", "openai", "gpt-4", Usage(...), "done", nothing, 1234567891),
|
||||
UserMessage("user", [TextContent("Ran `ls -la`\n```\nfile1.md\nfile2.md\n```\n")], 1234567892),
|
||||
UserMessage("user", [TextContent("<summary>Previous conversation compacted</summary>")], 1234567893),
|
||||
]
|
||||
```
|
||||
|
||||
@@ -571,6 +654,182 @@ function convertToLlmMessage(m::ToolResultMessage)
|
||||
end
|
||||
```
|
||||
|
||||
## Complete Data Flow Examples
|
||||
|
||||
### Example 1: User Prompt → Assistant Response
|
||||
|
||||
```
|
||||
User Input:
|
||||
"Hello, what's in the current directory?"
|
||||
|
||||
↓
|
||||
|
||||
prompt(agent, "Hello, what's in the current directory?")
|
||||
│
|
||||
└─► normalizePromptInput(String)
|
||||
Input: "Hello, what's in the current directory?"
|
||||
Output: [UserMessage("user", [TextContent("Hello, what's in the current directory?")], timestamp)]
|
||||
|
||||
↓
|
||||
|
||||
AgentLoop execution:
|
||||
│
|
||||
├─► transform_context() (optional)
|
||||
│ Input: [UserMessage(...)]
|
||||
│ Output: [UserMessage(...)]
|
||||
│
|
||||
├─► convert_to_llm()
|
||||
│ Input: [UserMessage(...)]
|
||||
│ Output: [UserMessage(...)]
|
||||
│
|
||||
├─► stream_fn() - LLM API
|
||||
│ Input: model, Context(...), config
|
||||
│ Output: AssistantMessage with ToolCall[]
|
||||
│ role: "assistant"
|
||||
│ content: [
|
||||
│ TextContent("I'll check the directory for you."),
|
||||
│ ToolCall("tool", "tc_123", "bash", Dict("command" => "ls -la"), nothing)
|
||||
│ ]
|
||||
│ usage: Usage(input=100, output=20, ...)
|
||||
│ stop_reason: "done"
|
||||
│
|
||||
├─► executeToolCalls()
|
||||
│ Input: AssistantMessage with ToolCall[]
|
||||
│ Output: ToolResultMessage[]
|
||||
│ role: "toolResult"
|
||||
│ tool_call_id: "tc_123"
|
||||
│ tool_name: "bash"
|
||||
│ content: [TextContent("file1.md\nfile2.md\n")]
|
||||
│ is_error: false
|
||||
│
|
||||
└─► Append to context.messages
|
||||
|
||||
↓
|
||||
|
||||
Final Conversation History:
|
||||
[
|
||||
UserMessage("user", [TextContent("Hello, what's in the current directory?")], ...),
|
||||
AssistantMessage("assistant", [
|
||||
TextContent("I'll check the directory for you."),
|
||||
ToolCall("tool", "tc_123", "bash", Dict("command" => "ls -la"), nothing)
|
||||
], "openai", "openai", "gpt-4", Usage(...), "done", nothing, ...),
|
||||
ToolResultMessage("toolResult", "tc_123", "bash", [TextContent("file1.md\nfile2.md\n")], ..., false, ...),
|
||||
]
|
||||
```
|
||||
|
||||
### Example 2: Tool Call Execution → Tool Result
|
||||
|
||||
```
|
||||
ToolCall from AssistantMessage
|
||||
│
|
||||
├─ type: "tool"
|
||||
├─ id: "tc_123"
|
||||
├─ name: "bash"
|
||||
├─ arguments: Dict("command" => "ls -la")
|
||||
└─ partial_json: nothing
|
||||
↓
|
||||
prepareToolCall(tool_call)
|
||||
↓
|
||||
Finds tool by name "bash"
|
||||
↓
|
||||
before_tool_call hook (optional)
|
||||
Input: BeforeToolCallContext(...)
|
||||
Output: BeforeToolCallResult(block=false) or nothing
|
||||
↓
|
||||
validateToolArguments(tool_call)
|
||||
Input: Dict("command" => "ls -la")
|
||||
Output: Dict("command" => "ls -la")
|
||||
↓
|
||||
Return: PreparedToolCall("prepared", tool_call, bash_tool, validated_args)
|
||||
↓
|
||||
executePreparedToolCall(prepared)
|
||||
↓
|
||||
tool.execute("tc_123", Dict("command" => "ls -la"), signal, on_update)
|
||||
↓
|
||||
Bash tool executes "ls -la" command
|
||||
Returns: AgentToolResultMutable(
|
||||
content: [TextContent("file1.md\nfile2.md\n")],
|
||||
details: BashToolDetails(...),
|
||||
usage: nothing,
|
||||
added_tool_names: nothing,
|
||||
terminate: nothing
|
||||
)
|
||||
↓
|
||||
finalizeExecutedToolCall(executed)
|
||||
↓
|
||||
after_tool_call hook (optional)
|
||||
Input: AfterToolCallContext(...)
|
||||
Output: AfterToolCallResult(...) or nothing
|
||||
↓
|
||||
Return: FinalizedToolCallOutcome(
|
||||
tool_call: ToolCall(...),
|
||||
result: AgentToolResultMutable(...),
|
||||
is_error: false
|
||||
)
|
||||
↓
|
||||
createToolResultMessage(finalized)
|
||||
↓
|
||||
Return: ToolResultMessage(
|
||||
role: "toolResult",
|
||||
tool_call_id: "tc_123",
|
||||
tool_name: "bash",
|
||||
content: [TextContent("file1.md\nfile2.md\n")],
|
||||
details: BashToolDetails(...),
|
||||
usage: nothing,
|
||||
added_tool_names: nothing,
|
||||
is_error: false,
|
||||
timestamp: Int64(...)
|
||||
)
|
||||
```
|
||||
|
||||
### Example 3: Custom Message Conversion
|
||||
|
||||
```
|
||||
BashExecutionMessage (custom, for logging)
|
||||
│
|
||||
role: "custom"
|
||||
command: "ls -la"
|
||||
output: "file1.md\nfile2.md\n"
|
||||
exit_code: 0
|
||||
cancelled: false
|
||||
truncated: false
|
||||
full_output_path: nothing
|
||||
timestamp: 1234567890
|
||||
exclude_from_context: false
|
||||
↓
|
||||
convertToLlmMessage(BashExecutionMessage)
|
||||
↓
|
||||
bashExecutionToText(msg)
|
||||
Output: "Ran `ls -la`\n```\nfile1.md\nfile2.md\n```\n"
|
||||
↓
|
||||
Return: UserMessage(
|
||||
"user",
|
||||
[TextContent("Ran `ls -la`\n```\nfile1.md\nfile2.md\n```\n")],
|
||||
1234567890
|
||||
)
|
||||
↓
|
||||
(Excluded if exclude_from_context = true)
|
||||
|
||||
────────────────────────────────────────────────────────────────────
|
||||
|
||||
CompactionSummaryMessage (custom, for history compression)
|
||||
│
|
||||
role: "compactionSummary"
|
||||
summary: "Previous 100 turns about Python programming"
|
||||
tokens_before: 15000
|
||||
timestamp: 1234567890
|
||||
↓
|
||||
convertToLlmMessage(CompactionSummaryMessage)
|
||||
↓
|
||||
Text = COMPACTION_SUMMARY_PREFIX + summary + COMPACTION_SUMMARY_SUFFIX
|
||||
Result: "<summary>\nPrevious 100 turns about Python programming\n</summary>"
|
||||
↓
|
||||
Return: UserMessage(
|
||||
"user",
|
||||
[TextContent("<summary>...\nPrevious 100 turns...\n</summary>")],
|
||||
1234567890
|
||||
)
|
||||
|
||||
## Summary
|
||||
|
||||
The type system in AgentCore.jl provides:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# AgentCore.jl - Session Management Deep Dive
|
||||
|
||||
## Session Architecture
|
||||
## Session Architecture with Data Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
@@ -20,6 +20,12 @@
|
||||
│ ▼ ▼ ▼ ▼ ▼ │
|
||||
│ Message Message Compaction Message BranchSummary │
|
||||
│ │
|
||||
│ Data Flow: │
|
||||
│ AgentMessage[] (AgentState.messages) │
|
||||
│ │ │
|
||||
│ └─► appendMessage() → MessageEntry │
|
||||
│ └─► storage.appendEntry() → JSONL file │
|
||||
│ │
|
||||
│ To navigate to E2 (fork point): │
|
||||
│ Session.moveTo(E2) │
|
||||
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
|
||||
@@ -33,6 +39,38 @@
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow: AgentMessage → SessionTreeEntry
|
||||
|
||||
```
|
||||
AgentState.messages::Vector{AgentMessage}
|
||||
│
|
||||
├─► For each message in messages:
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌──────────────────────────────────────────────────────────────┐
|
||||
│ │ appendMessage(session, AgentMessage) │
|
||||
│ │ Input: message::AgentMessage │
|
||||
│ │ Output: entry_id::String │
|
||||
│ │ │
|
||||
│ │ Steps: │
|
||||
│ │ 1. Create MessageEntry: │
|
||||
│ │ - type: "message" │
|
||||
│ │ - id: createEntryId(storage) │
|
||||
│ │ - parent_id: getLeafId(storage) │
|
||||
│ │ - timestamp: create_timestamp() │
|
||||
│ │ - message: copy(message) │
|
||||
│ │ 2. storage.appendEntry(entry) │
|
||||
│ │ - Write JSONL line to file │
|
||||
│ │ - Update leaf_id │
|
||||
│ │ 3. Return entry.id │
|
||||
│ └──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
└─► Entry stored in JSONL:
|
||||
{"type":"message","id":"msg_1","parent_id":null,"timestamp":"...","message":{...}}
|
||||
```
|
||||
|
||||
## Entry Types
|
||||
|
||||
## Entry Types
|
||||
|
||||
```julia
|
||||
|
||||
+84
-53
@@ -1,6 +1,6 @@
|
||||
# AgentCore.jl - Tools Deep Dive
|
||||
|
||||
## Tool Architecture
|
||||
## Tool Architecture with Data Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
@@ -12,7 +12,7 @@
|
||||
│ - name: String (identifier) │
|
||||
│ - label: String (display name) │
|
||||
│ - description: String (what it does) │
|
||||
│ - parameters: JSON schema │
|
||||
│ - parameters::Any (JSON schema or type) │
|
||||
│ - execute::Function (main logic) │
|
||||
│ - prepare_arguments::Union{Function, Nothing} │
|
||||
│ - execution_mode::Union{ToolExecutionMode, Nothing} │
|
||||
@@ -21,77 +21,108 @@
|
||||
┌───────────────┼───────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ BashTool │ │ ReadTool │ │ WriteTool │
|
||||
│ - bash() │ │ - read() │ │ - write() │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
┌─────────────┐
|
||||
│ EditTool │
|
||||
│ - edit() │
|
||||
└─────────────┘
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ BashTool │ │ ReadTool │ │ WriteTool │
|
||||
│ - bash() │ │ - read() │ │ - write() │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
┌─────────────┐
|
||||
│ EditTool │
|
||||
│ - edit() │
|
||||
└─────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tool Execution Flow │
|
||||
│ Tool Execution Data Flow │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Assistant Message
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ AssistantMessage: │
|
||||
│ content: [ │
|
||||
│ TextContent("I'll check the files..."), │
|
||||
│ ToolCall("bash", {command: "ls -la"}), │
|
||||
│ ToolCall("read", {path: "README.md"}) │
|
||||
│ ] │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
Input: AssistantMessage (from LLM)
|
||||
content::Vector{MessageContent}
|
||||
└─ Contains: TextContent[] and ToolCall[]
|
||||
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ extract ToolCalls │
|
||||
│ filter(c -> c isa ToolCall, assistant_message.content) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ AgentLoop.executeToolCalls() │
|
||||
│ - Extract ToolCalls from message content │
|
||||
│ - Determine execution mode (sequential/parallel) │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─► executeToolCallsSequential()
|
||||
│ (for tools that require order)
|
||||
│
|
||||
└─► executeToolCallsParallel()
|
||||
(for independent tools)
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ToolCall Type │
|
||||
│ • type::String ("tool") │
|
||||
│ • id::String (unique identifier) │
|
||||
│ • name::String (tool name to execute) │
|
||||
│ • arguments::Dict{String, Any} (JSON-like arguments) │
|
||||
│ • partial_json::Union{String, Nothing} │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─► prepareToolCall()
|
||||
│ - before_tool_call hook (optional)
|
||||
│ - validate arguments
|
||||
│ - prepare arguments (optional)
|
||||
│ Input: tool_call::ToolCall
|
||||
│ Output: Union{PreparedToolCall, ImmediateToolCallOutcome}
|
||||
│
|
||||
├─► execute()
|
||||
│ - Tool-specific logic
|
||||
│ - Return AgentToolResult
|
||||
│ Steps:
|
||||
│ 1. Find tool by name in context.tools
|
||||
│ 2. before_tool_call hook (optional)
|
||||
│ Input: BeforeToolCallContext
|
||||
│ Output: BeforeToolCallResult (block, reason)
|
||||
│ 3. prepareToolCallArguments() (optional)
|
||||
│ Input: tool_call.arguments::Dict{String, Any}
|
||||
│ Output: prepared_arguments::Any
|
||||
│ 4. validateToolArguments()
|
||||
│ Input: prepared_tool_call.arguments
|
||||
│ Output: validated_args::Any
|
||||
│ 5. Return: PreparedToolCall(kind, tool_call, tool, args)
|
||||
│
|
||||
├─► executePreparedToolCall() (if prepared)
|
||||
│ Input: PreparedToolCall
|
||||
│ Output: ExecutedToolCallOutcome
|
||||
│
|
||||
│ tool.execute(tool_call.id, args, signal, on_update)
|
||||
│ Input: tool_call_id::String
|
||||
│ args::Any
|
||||
│ signal::Union{Any, Nothing}
|
||||
│ on_update::Function (streaming updates)
|
||||
│ Output: AgentToolResultMutable
|
||||
│ • content::Vector{MessageContent}
|
||||
│ • details::Any
|
||||
│ • usage::Union{Usage, Nothing}
|
||||
│ • terminate::Union{Bool, Nothing}
|
||||
│
|
||||
├─► finalizeExecutedToolCall()
|
||||
│ - after_tool_call hook (optional)
|
||||
│ Input: ExecutedToolCallOutcome
|
||||
│ Output: FinalizedToolCallOutcome
|
||||
│
|
||||
│ Steps:
|
||||
│ 1. after_tool_call hook (optional)
|
||||
│ Input: AfterToolCallContext
|
||||
│ Output: AfterToolCallResult (patches)
|
||||
│ 2. Apply patches to result
|
||||
│ 3. Return: FinalizedToolCallOutcome(tool_call, result, is_error)
|
||||
│
|
||||
└─► createToolResultMessage()
|
||||
- Emit ToolResultMessage
|
||||
|
||||
Input: FinalizedToolCallOutcome
|
||||
Output: ToolResultMessage
|
||||
• role: "toolResult"
|
||||
• tool_call_id::String (matches ToolCall.id)
|
||||
• tool_name::String (matches ToolCall.name)
|
||||
• content::Vector{MessageContent}
|
||||
• details::Any
|
||||
• usage::Union{Usage, Nothing}
|
||||
• added_tool_names::Union{Vector{String}, Nothing}
|
||||
• is_error::Bool
|
||||
• timestamp::Timestamp (Int64)
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ ToolResultMessage │
|
||||
│ - tool_call_id: "ref to original ToolCall" │
|
||||
│ - tool_name: "bash" │
|
||||
│ - content: [TextContent("file1.md\nfile2.md\n")] │
|
||||
│ - is_error: false │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ToolResultMessage[] (one per ToolCall) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ AgentState.messages.append(tool_result) │
|
||||
│ - Next turn: LLM sees tool results │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
├─► Append to context.messages (AgentState.messages)
|
||||
└─► Next turn: LLM sees tool results as input
|
||||
```
|
||||
|
||||
## Built-in Tools
|
||||
|
||||
## Built-in Tools
|
||||
|
||||
### 1. BashTool
|
||||
|
||||
```julia
|
||||
|
||||
+186
-12
@@ -165,32 +165,206 @@ AgentStartEvent
|
||||
└─ AgentEndEvent
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
## Complete Data Flow with Type Transformations
|
||||
|
||||
### Message Transformation
|
||||
This documentation shows how data is transformed through the agent lifecycle.
|
||||
|
||||
### Message Type Hierarchy
|
||||
|
||||
```
|
||||
AgentMessage[] (internal)
|
||||
Message (for LLM API)
|
||||
├── UserMessage (role: "user")
|
||||
│ └── content::Vector{MessageContent}
|
||||
│ ├── TextContent (text::String)
|
||||
│ └── ImageContent (data::String, mime_type::String)
|
||||
├── AssistantMessage (role: "assistant")
|
||||
│ ├── content::Vector{MessageContent}
|
||||
│ │ ├── TextContent
|
||||
│ │ └── ToolCall (id, name, arguments::Dict{String, Any})
|
||||
│ ├── usage::Usage
|
||||
│ ├── stop_reason::String
|
||||
│ └── timestamp::Timestamp
|
||||
└── ToolResultMessage (role: "toolResult")
|
||||
├── tool_call_id::String
|
||||
├── tool_name::String
|
||||
├── content::Vector{MessageContent}
|
||||
├── details::Any
|
||||
├── usage::Union{Usage, Nothing}
|
||||
├── is_error::Bool
|
||||
└── timestamp::Timestamp
|
||||
|
||||
AgentMessage (internal, extends Message)
|
||||
├── UserMessage (same as above)
|
||||
├── AssistantMessage (same as above)
|
||||
├── ToolResultMessage (same as above)
|
||||
├── BashExecutionMessage (custom)
|
||||
│ ├── role, command, output, exit_code
|
||||
│ ├── cancelled, truncated, exclude_from_context
|
||||
│ └── timestamp
|
||||
├── CompactionSummaryMessage (custom)
|
||||
│ ├── summary, tokens_before, timestamp
|
||||
│ └── converted to UserMessage for LLM
|
||||
└── BranchSummaryMessage (custom)
|
||||
├── summary, from_id, timestamp
|
||||
└── converted to UserMessage for LLM
|
||||
```
|
||||
|
||||
### Complete Conversation Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 1: User Input (Vector{AgentMessage}) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
prompt(agent, "Hello!")
|
||||
│
|
||||
├─ transform_context() (optional)
|
||||
▼
|
||||
AgentMessage[] (transformed)
|
||||
└─► normalizePromptInput()
|
||||
Input: "Hello!"::String
|
||||
Output: [UserMessage("user", [TextContent("Hello!")], timestamp)]
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 2: AgentLoop Processing │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
runAgentLoop()
|
||||
│
|
||||
├─► transform_context() (optional hook)
|
||||
│ Input: [UserMessage(...)]::Vector{AgentMessage}
|
||||
│ Output: [UserMessage(...)]::Vector{AgentMessage}
|
||||
│
|
||||
├─► convert_to_llm()
|
||||
│ Input: [UserMessage(...)]::Vector{AgentMessage}
|
||||
│ Output: [UserMessage(...)]::Vector{Message}
|
||||
│
|
||||
├─► stream_fn() - LLM API call
|
||||
│ Input: model, Context(...), config
|
||||
│ Output: AssistantMessage with ToolCall[]
|
||||
│
|
||||
├─► executeToolCalls()
|
||||
│ Input: AssistantMessage (with ToolCall[])
|
||||
│ Output: ToolResultMessage[]
|
||||
│
|
||||
└─► Emit events and append to context.messages
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 3: Final Conversation State │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
context.messages::Vector{AgentMessage}
|
||||
├─ UserMessage("user", [TextContent("Hello!")], ...)
|
||||
├─ AssistantMessage("assistant", [
|
||||
│ TextContent("Hi there!"),
|
||||
│ ToolCall("bash", {...})
|
||||
│ ], ...)
|
||||
└─ ToolResultMessage("toolResult", "bash", [TextContent("...")], ...)
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 4: AgentEndEvent (final output) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
AgentEndEvent(messages::Vector{AgentMessage})
|
||||
└─ Contains full conversation history
|
||||
User Input (String / AgentMessage / Vector{AgentMessage})
|
||||
│
|
||||
├─► normalizePromptInput()
|
||||
│ Input: input::Union{String, AgentMessage, Vector{AgentMessage}}
|
||||
│ Output: Vector{AgentMessage}
|
||||
│ • String → UserMessage("user", [TextContent(input)], timestamp)
|
||||
│ • AgentMessage → [input]
|
||||
│ • Vector{AgentMessage} → input (pass-through)
|
||||
│
|
||||
├─► prompt(agent, messages)
|
||||
│ └─► runPromptMessages()
|
||||
│
|
||||
├─ convert_to_llm()
|
||||
▼
|
||||
Message[] (LLM API)
|
||||
AgentLoop Execution:
|
||||
│
|
||||
├─► transform_context() (optional hook)
|
||||
│ Input: context.messages::Vector{AgentMessage}
|
||||
│ Output: messages::Vector{AgentMessage} (transformed)
|
||||
│
|
||||
├─► convert_to_llm()
|
||||
│ Input: messages::Vector{AgentMessage}
|
||||
│ Output: llm_messages::Vector{Message}
|
||||
│
|
||||
│ AgentMessage → Message mapping:
|
||||
│ • UserMessage → UserMessage (pass-through)
|
||||
│ • AssistantMessage → AssistantMessage (pass-through)
|
||||
│ • ToolResultMessage → ToolResultMessage (pass-through)
|
||||
│ • BashExecutionMessage → UserMessage (text conversion)
|
||||
│ • CompactionSummaryMessage → UserMessage (text wrapped)
|
||||
│ • BranchSummaryMessage → UserMessage (text wrapped)
|
||||
│
|
||||
├─► LLM API Call (stream_fn)
|
||||
│ Input: model, Context(system_prompt, llm_messages, tools), config
|
||||
│ Output: Stream{AssistantMessageEvent}
|
||||
│
|
||||
├─► AssistantMessage (returned from LLM)
|
||||
│ content::Vector{MessageContent}
|
||||
│ └─ Contains: TextContent[] and/or ToolCall[]
|
||||
│
|
||||
├─► executeToolCalls() (if ToolCall[] in content)
|
||||
│ │
|
||||
│ ├─► prepareToolCall() for each ToolCall
|
||||
│ │ Input: tool_call::ToolCall
|
||||
│ │ Output: PreparedToolCall or ImmediateToolCallOutcome
|
||||
│ │
|
||||
│ ├─► executePreparedToolCall() (if prepared)
|
||||
│ │ Input: PreparedToolCall
|
||||
│ │ Output: ExecutedToolCallOutcome
|
||||
│ │ tool.execute() returns AgentToolResultMutable
|
||||
│ │
|
||||
│ ├─► finalizeExecutedToolCall()
|
||||
│ │ Input: ExecutedToolCallOutcome
|
||||
│ │ Output: FinalizedToolCallOutcome
|
||||
│ │
|
||||
│ └─► createToolResultMessage()
|
||||
│ Input: FinalizedToolCallOutcome
|
||||
│ Output: ToolResultMessage
|
||||
│ • role: "toolResult"
|
||||
│ • tool_call_id, tool_name
|
||||
│ • content::Vector{MessageContent}
|
||||
│ • details, usage, added_tool_names
|
||||
│ • is_error, timestamp
|
||||
│
|
||||
└─► Append to context.messages and new_messages
|
||||
│
|
||||
▼
|
||||
Vector{AgentMessage} (final conversation history)
|
||||
Contains: [UserMessage, AssistantMessage, ToolResultMessage, ...]
|
||||
```
|
||||
|
||||
### Tool Execution Flow
|
||||
|
||||
```
|
||||
ToolCall (in assistant message)
|
||||
ToolCall (in AssistantMessage.content)
|
||||
│
|
||||
├─ before_tool_call hook (optional)
|
||||
│ Input: BeforeToolCallContext
|
||||
│ Output: BeforeToolCallResult (block, reason) or nothing
|
||||
│
|
||||
├─ before_tool_call hook
|
||||
├─ prepareToolCall()
|
||||
├─ execute()
|
||||
├─ after_tool_call hook
|
||||
│ Input: tool_call::ToolCall
|
||||
│ Output: Union{PreparedToolCall, ImmediateToolCallOutcome}
|
||||
│ • Validates tool exists
|
||||
│ • Runs before_tool_call hook
|
||||
│ • Runs prepare_arguments hook (optional)
|
||||
│ • Runs validateToolArguments (optional)
|
||||
│
|
||||
├─ executePreparedToolCall() (if prepared)
|
||||
│ Input: PreparedToolCall
|
||||
│ Output: ExecutedToolCallOutcome
|
||||
│ tool.execute() returns AgentToolResultMutable
|
||||
│
|
||||
├─ finalizeExecutedToolCall()
|
||||
│ Input: ExecutedToolCallOutcome
|
||||
│ Output: FinalizedToolCallOutcome
|
||||
│ Runs after_tool_call hook (optional)
|
||||
│
|
||||
└─ createToolResultMessage()
|
||||
Input: FinalizedToolCallOutcome
|
||||
Output: ToolResultMessage
|
||||
• role: "toolResult"
|
||||
• tool_call_id, tool_name
|
||||
• content::Vector{MessageContent}
|
||||
• details, usage, added_tool_names
|
||||
• is_error, timestamp
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
Reference in New Issue
Block a user