18 KiB
18 KiB
AgentCore.jl - Examples and Patterns
Quick Start Examples
Example 1: Basic Conversation
using AgentCore
# Create model
model = Model(
"gpt-4",
"GPT-4",
"openai",
"openai",
"https://api.openai.com/v1",
true,
["text"],
ModelCost(0.00003, 0.00006, 0.0, 0.0),
128000,
4096,
)
# Create tools
bash_tool = createBashTool()
# Create agent
agent = Agent(Dict(
:systemPrompt => "You are a helpful assistant.",
:model => model,
:tools => [bash_tool],
:thinkingLevel => THINKING_MEDIUM,
:toolExecution => EXECUTION_PARALLEL,
))
# Subscribe to events
subscribe(agent) do event, signal
if event isa MessageEndEvent
println("Agent: $(event.message)")
end
end
# Start conversation
prompt(agent, "What's in the current directory?")
# Wait for completion
waitForIdle(agent)
# Get final state
state = get_state(agent)
println("Total messages: $(length(state.messages))")
Example 2: Conversation with Memory
# Create session storage
metadata = JsonlSessionMetadata(
"session_1",
"2024-01-01T00:00:00Z",
"/path/to/project",
"/path/to/session.jsonl",
nothing,
Dict("project" => "my-project"),
)
storage = JsonlSessionStorage(metadata, "/path/to/session.jsonl")
# Create session
session = Session(storage)
# Add messages to session
appendMessage(session, UserMessage("user", [TextContent("Hello, my name is Alice.")], Int64(Dates.now(Dates.UTC).datetime)))
# Check session stats
stats = getSessionStats(session)
println("Messages: $(stats.message_count)")
println("Total tokens: $(stats.total_tokens)")
# Create agent with session
agent = Agent(Dict(
:systemPrompt => "You are a helpful assistant.",
:model => model,
:tools => [bash_tool],
:sessionId => getMetadata(session).id,
))
Example 3: Steering and Follow-Up
# Start conversation
prompt(agent, "Create a Python project.")
# Queue a steering message (injected after current assistant turn)
timestamp = Int64(Dates.now(Dates.UTC).datetime)
steer(agent, UserMessage("user", [TextContent("Actually, let's use Node.js instead")], timestamp))
# Wait for redirection
waitForIdle(agent)
# Queue a follow-up message (runs only after agent would otherwise stop)
followUp(agent, UserMessage("user", [TextContent("Can you add tests?")], timestamp))
# Continue until completion
while hasQueuedMessages(agent)
waitForIdle(agent)
end
Example 4: Branching Conversations
# Initial conversation
prompt(agent, "I want to build a web app.")
# Get the branch at a specific point
entry_id = "msg_3_id"
branch = getBranch(session, entry_id)
println("Branch has $(length(branch)) entries")
# Move to a specific entry (creates a branch summary if summary is provided)
moveTo(session, entry_id, Dict("summary" => "User decided to explore mobile app instead"))
# Continue on new branch
prompt(agent, "Let's build a mobile app instead.")
# Check session branch
branch = getBranch(session)
println("Current branch has $(length(branch)) entries")
Advanced Patterns
Pattern 1: Token Usage Monitoring
# Simple token estimation from messages
function estimateTokens(message::AgentMessage)::Int64
content = if message isa UserMessage
join([c.text for c in message.content if c isa TextContent])
elseif message isa AssistantMessage
join([c.text for c in message.content if c isa TextContent])
elseif message isa ToolResultMessage
join([c.text for c in message.content if c isa TextContent])
else
""
end
return ceil(Int, length(content) / 4)
end
# Monitor session token usage
function checkTokenUsage(agent, session)
state = get_state(agent)
stats = getSessionStats(session)
println("Session tokens: $(stats.total_tokens)")
println("Messages in state: $(length(state.messages))")
total_estimated = sum(estimateTokens, state.messages)
println("Estimated total tokens: $(total_estimated)")
return stats.total_tokens
end
# Agent loop with token monitoring
function runAgentWithMonitoring(agent, session, max_tokens=120000)
while true
total = checkTokenUsage(agent, session)
if total > max_tokens
println("Approaching token limit: $(total)")
break
end
if !hasQueuedMessages(agent) && isnothing(agent.active_run)
break
end
end
end
Pattern 2: Custom Tool
# Create a custom tool
function createCustomTool()
return AgentTool(
"custom_tool",
"custom_tool",
"A custom tool description.",
Dict{String, Any}(),
(tool_call_id, params, signal, on_update, context) -> begin
# Execute tool logic
value = params["value"]
# Send progress updates
on_update("Processing $value...")
result = processValue(value)
return AgentToolResult(
[TextContent(result)],
nothing,
nothing,
nothing,
nothing, # terminate
)
end,
nothing,
EXECUTION_SEQUENTIAL,
)
end
# Use custom tool
custom_tool = createCustomTool()
agent = Agent(Dict(
:systemPrompt => "You are a helpful assistant.",
:model => model,
:tools => [bash_tool, custom_tool],
))
Pattern 3: Dynamic Model Selection via Hook
# Hook to change model based on conversation context
function dynamicModelSelection(signal)
# This hook is called between turns to potentially change the model
# Return AgentLoopTurnUpdate to change model/thinking_level, or nothing to keep current
return nothing
end
# Configure agent with the hook
agent = Agent(Dict(
:prepareNextTurn => dynamicModelSelection,
))
# The hook receives an AgentEvent and AbortSignal.
# Access conversation context via:
# context.message - the last assistant message
# context.tool_results - tool results from the last turn
# context.context - the full AgentContext
Pattern 4: Tool Call Interception
# Hook to validate or block tool calls before they execute
function toolCallValidator(event, signal)
if event isa ToolExecutionStartEvent
# Log or validate tool calls
println("Tool call: $(event.tool_name) with args: $(event.args)")
# Block dangerous commands
if event.tool_name == "bash"
args = event.args
if args isa Dict && haskey(args, :command)
cmd = args[:command]
if contains(cmd, "rm -rf /")
println("Blocked dangerous command!")
end
end
end
end
return nothing
end
# Configure with beforeToolCall hook
agent = Agent(Dict(
:beforeToolCall => toolCallValidator,
))
# After tool call hook
function toolCallLogger(event, signal)
if event isa ToolExecutionEndEvent
status = event.is_error ? "ERROR" : "OK"
println("[$status] $(event.tool_name): $(event.tool_call_id)")
end
return nothing
end
agent = Agent(Dict(
:afterToolCall => toolCallLogger,
))
Pattern 5: Multi-Step Tool Execution
# Tool that requires multiple steps with progress updates
function createMultiStepTool()
return AgentTool(
"multistep",
"multistep",
"Multi-step task",
Dict{String, Any}(),
(tool_call_id, params, signal, on_update, context) -> begin
# Step 1: Prepare
on_update("Preparing...")
prepare_result = prepareStep(params)
# Step 2: Execute
on_update("Executing...")
execute_result = executeStep(prepare_result, params)
# Step 3: Finalize
on_update("Finalizing...")
finalize_result = finalizeStep(execute_result)
return AgentToolResult(
[TextContent(finalize_result)],
Dict("steps" => 3),
nothing,
nothing,
nothing,
)
end,
nothing,
EXECUTION_SEQUENTIAL,
)
end
Pattern 6: Image Processing with Read Tool
# Create read tool with image support
read_tool = createReadTool(ReadToolOptions(
auto_resize_images=true,
image_processor=nothing,
))
# Use with agent that supports image input
agent = Agent(Dict(
:systemPrompt => "You are a helpful assistant.",
:model => model,
:tools => [read_tool],
))
# Send prompt with image content
timestamp = Int64(Dates.now(Dates.UTC).datetime)
image_msg = UserMessage(
"user",
[
TextContent("Analyze this image:"),
ImageContent(base64_data, "image/png"),
],
timestamp,
)
prompt(agent, image_msg)
Pattern 7: Session Navigation
# Navigate to specific entry
moveTo(session, entry_id)
# Get branch from specific point
branch = getBranch(session, entry_id)
# Create label for an entry (links to another entry)
appendLabel(session, entry_id, "important-decision")
# Get the label for a specific entry
label = getLabel(session, entry_id)
if !isnothing(label)
println("Label: $label")
end
# Build session context from current branch
context = buildSessionContext(session)
# Get specific messages from branch entries
entries = getBranch(session)
for (i, entry) in enumerate(entries)
messages = sessionEntryToContextMessages(entry, i, entries)
for msg in messages
println("$(msg.role): $(msg)")
end
end
Pattern 8: Batch Processing
# Process multiple prompts sequentially
prompts = [
"What is Julia?",
"What is JavaScript?",
"What is Python?",
]
results = []
for prompt_text in prompts
# Create fresh agent for each prompt
agent = Agent(Dict(
:systemPrompt => "You are a helpful assistant.",
:model => model,
:tools => [bash_tool],
))
# Run prompt
prompt(agent, prompt_text)
waitForIdle(agent)
# Get result
state = get_state(agent)
last_message = state.messages[end]
push!(results, last_message)
# Clean up
reset!(agent)
end
Pattern 9: Event Subscription
# Subscribe to various agent events
subscribe(agent) do event, signal
if event isa AgentStartEvent
println("Agent started")
elseif event isa TurnStartEvent
println("Turn started")
elseif event isa MessageStartEvent
println("Message started")
elseif event isa MessageUpdateEvent
# Partial message update during streaming
partial = event.assistant_message_event
# Access partial message content
elseif event isa MessageEndEvent
println("Message ended: $(event.message)")
elseif event isa ToolExecutionStartEvent
println("Tool exec start: $(event.tool_name)")
elseif event isa ToolExecutionUpdateEvent
# Tool progress update
println("Tool update: $(event.partial_result)")
elseif event isa ToolExecutionEndEvent
status = event.is_error ? "error" : "success"
println("Tool exec end: $(event.tool_name) [$status]")
elseif event isa TurnEndEvent
println("Turn ended")
elseif event isa AgentEndEvent
println("Agent ended with $(length(event.messages)) messages")
end
end
Pattern 10: Error Handling
# Monitor for errors in conversation
subscribe(agent) do event, signal
if event isa MessageEndEvent
msg = event.message
if msg isa AssistantMessage
if msg.stop_reason == "error"
println("Error: $(msg.error_message)")
elseif msg.stop_reason == "length"
println("Response truncated (token limit reached)")
elseif msg.stop_reason == "aborted"
println("Request aborted")
end
end
end
end
# Error handling hook
function errorHandlingHook(signal)
# This is called between turns
# Return AgentLoopTurnUpdate to modify behavior, or nothing
return nothing
end
agent = Agent(Dict(
:prepareNextTurn => errorHandlingHook,
))
Testing Patterns
Unit Testing
using Test
using AgentCore
# Test tool creation
@test createBashTool() isa AgentTool
@test createReadTool() isa AgentTool
@test createWriteTool() isa AgentTool
@test createEditTool() isa AgentTool
# Test basic agent creation
@test_throws ErrorException Agent(Dict(:model => nothing))
# Test agent state
agent = Agent(Dict(
:systemPrompt => "Test",
:model => Model("", "", "test", "test", "", false, String[], ModelCost(0,0,0,0), 0, 0),
))
state = get_state(agent)
@test state.system_prompt == "Test"
@test length(state.messages) == 0
Integration Testing with In-Memory Storage
using AgentCore
# Create in-memory session
repo = InMemorySessionRepo()
session = create(repo)
# Add messages
appendMessage(session, UserMessage("user", [TextContent("Hello")], Int64(Dates.now(Dates.UTC).datetime)))
# Verify session
stats = getSessionStats(session)
@test stats.message_count == 1
# Navigate with moveTo
entry_id = getLeafId(session)
moveTo(session, entry_id)
# Fork from entry
forked = fork(repo, getMetadata(session), Dict("entryId" => entry_id))
Performance Patterns
Pattern 1: Queue Mode Configuration
# Configure steering mode (how steering messages are queued)
agent = Agent(Dict(
:systemPrompt => "You are a helpful assistant.",
:model => model,
:steeringMode => QUEUE_ONE_AT_A_TIME, # Only one steering message processed at a time
:followUpMode => QUEUE_ALL, # All follow-ups processed in batch
))
# Clear queues as needed
clearSteeringQueue(agent)
clearFollowUpQueue(agent)
clearAllQueues(agent)
Pattern 2: Message Normalization
# Custom message normalization function
function customNormalize(messages::Vector{AgentMessage})::Vector{Message}
return filter(
(m) -> m.role == "user" || m.role == "assistant" || m.role == "toolResult",
messages,
)
end
agent = Agent(Dict(
:systemPrompt => "You are a helpful assistant.",
:model => model,
:convertToLlm => customNormalize,
))
Pattern 3: Context Transformation
# Transform context before LLM call
function transformContextFn(messages::Vector{AgentMessage}, signal)
# Filter or modify messages before sending to LLM
filtered = filter(m -> m.role != "toolResult", messages)
return filtered
end
agent = Agent(Dict(
:systemPrompt => "You are a helpful assistant.",
:model => model,
:transformContext => transformContextFn,
))
Production Patterns
Pattern 1: Observability via Events
# Log all agent events for debugging and monitoring
subscribe(agent) do event, signal
timestamp = Dates.now(Dates.UTC)
if event isa AgentStartEvent
println("[$timestamp] AgentStart")
elseif event isa AgentEndEvent
println("[$timestamp] AgentEnd ($(length(event.messages)) messages)")
elseif event isa TurnStartEvent
println("[$timestamp] TurnStart")
elseif event isa TurnEndEvent
tool_count = length(event.tool_results)
println("[$timestamp] TurnEnd ($tool_count tools)")
elseif event isa ToolExecutionStartEvent
println("[$timestamp] ToolStart: $(event.tool_name)")
elseif event isa ToolExecutionEndEvent
status = event.is_error ? "ERROR" : "OK"
println("[$timestamp] ToolEnd: $(event.tool_name) [$status]")
end
end
Pattern 2: Abort Handling
# Abort a running agent
if !isnothing(agent.active_run)
abort(agent)
end
# Check if agent is idle
if isnothing(agent.active_run)
println("Agent is idle")
end
Pattern 3: Continue from Transcript
# Continue from the last message in the transcript
continue!(agent)
# The last message must be user or tool-result role.
# If the last message is assistant, pending steering/follow-up messages
# are processed first, then an error is thrown if none exist.
Debugging Patterns
Pattern 1: Conversation Trace
# Trace all messages in the conversation
trace = []
subscribe(agent) do event, signal
if event isa MessageEndEvent
msg = event.message
push!(trace, Dict(
"role" => msg.role,
"type" => typeof(msg).name.name,
))
end
end
# Run conversation
prompt(agent, "Hello")
waitForIdle(agent)
# Print trace
for entry in trace
println("$(entry["type"]): $(entry["role"])")
end
Pattern 2: Tool Call Trace
tool_trace = []
subscribe(agent) do event, signal
if event isa ToolExecutionStartEvent
push!(tool_trace, Dict(
"type" => "start",
"tool" => event.tool_name,
"id" => event.tool_call_id,
"args" => event.args,
))
elseif event isa ToolExecutionEndEvent
push!(tool_trace, Dict(
"type" => "end",
"tool" => event.tool_name,
"id" => event.tool_call_id,
"error" => event.is_error,
))
end
end
Pattern 3: State Dump
function dumpState(agent)
state = get_state(agent)
println("=== Agent State ===")
println("System prompt: $(state.system_prompt)")
println("Model: $(state.model.name)")
println("Thinking level: $(state.thinking_level)")
println("Messages: $(length(state.messages))")
println("Tools: $(length(state.tools))")
println("==================")
end
# Use after conversation
prompt(agent, "Hello")
waitForIdle(agent)
dumpState(agent)
Best Practices Summary
- Start simple, add complexity gradually
- Use hooks for customization, not core logic
- Test with basic agent first before adding hooks
- Monitor token usage for long conversations
- Use branches for exploration
- Handle errors gracefully via event subscriptions
- Log important events
- Clear queues when not needed
- Use correct Julia naming conventions (camelCase for functions)
- Pass session as first argument for session functions