Files
YiemAgent/learning/08-EXAMPLES.md
T
2026-07-31 11:41:53 +07:00

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

  1. Start simple, add complexity gradually
  2. Use hooks for customization, not core logic
  3. Test with basic agent first before adding hooks
  4. Monitor token usage for long conversations
  5. Use branches for exploration
  6. Handle errors gracefully via event subscriptions
  7. Log important events
  8. Clear queues when not needed
  9. Use correct Julia naming conventions (camelCase for functions)
  10. Pass session as first argument for session functions