894 lines
20 KiB
Markdown
894 lines
20 KiB
Markdown
# AgentCore.jl - Examples and Patterns
|
|
|
|
## Quick Start Examples
|
|
|
|
### Example 1: Basic Conversation
|
|
|
|
```julia
|
|
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
|
|
wait_for_idle(agent)
|
|
|
|
# Get final state
|
|
state = get_state(agent)
|
|
println("Total messages: $(length(state.messages))")
|
|
```
|
|
|
|
### Example 2: Conversation with Memory
|
|
|
|
```julia
|
|
# Create session storage
|
|
storage = JsonlSessionStorage(
|
|
JsonlSessionMetadata(
|
|
"session_1",
|
|
"2024-01-01T00:00:00Z",
|
|
"/path/to/project",
|
|
"/path/to/session.jsonl",
|
|
nothing,
|
|
Dict("project" => "my-project"),
|
|
),
|
|
"/path/to/session.jsonl",
|
|
)
|
|
|
|
# Create session
|
|
session = Session(storage)
|
|
|
|
# Create agent with session
|
|
agent = Agent(Dict(
|
|
:systemPrompt => "You are a helpful assistant.",
|
|
:model => model,
|
|
:tools => [bash_tool],
|
|
:sessionId => session.getMetadata().id,
|
|
))
|
|
|
|
# Add messages to session
|
|
function addToSession(session, message)
|
|
appendMessage(session, message)
|
|
end
|
|
|
|
# Start conversation
|
|
prompt(agent, "Hello, my name is Alice.")
|
|
|
|
# Continue conversation (messages persist in session)
|
|
prompt(agent, "What's the weather like today?")
|
|
|
|
# Check session stats
|
|
stats = getSessionStats(session)
|
|
println("Messages: $(stats.message_count)")
|
|
println("Total tokens: $(stats.total_tokens)")
|
|
```
|
|
|
|
### Example 3: Steering and Follow-Up
|
|
|
|
```julia
|
|
# Start conversation
|
|
prompt(agent, "Create a Python project.")
|
|
|
|
# User wants to redirect
|
|
steer(agent, UserMessage("user", [TextContent("Actually, let's use Node.js instead")], timestamp))
|
|
|
|
# Wait for redirection
|
|
wait_for_idle(agent)
|
|
|
|
# Agent would normally stop, but user has more
|
|
prompt(agent, "Wait, there's one more thing...")
|
|
followUp(agent, UserMessage("user", [TextContent("Can you add tests?")], timestamp))
|
|
|
|
# Continue until completion
|
|
while hasQueuedMessages(agent)
|
|
wait_for_idle(agent)
|
|
end
|
|
```
|
|
|
|
### Example 4: Branching Conversations
|
|
|
|
```julia
|
|
# Initial conversation
|
|
prompt(agent, "I want to build a web app.")
|
|
|
|
# User decides to explore a different path
|
|
session.moveTo(msg_3_id) # Go back to message 3
|
|
|
|
# Create branch
|
|
appendBranchSummary(
|
|
session,
|
|
"User decided to explore mobile app instead",
|
|
msg_3_id,
|
|
Dict("focus" => "mobile"),
|
|
)
|
|
|
|
# Continue on new branch
|
|
prompt(agent, "Let's build a mobile app instead.")
|
|
|
|
# Check branches
|
|
branch = getBranch(session)
|
|
println("Current branch has $(length(branch)) entries")
|
|
```
|
|
|
|
## Advanced Patterns
|
|
|
|
### Pattern 1: Long-Running Agent with Compaction
|
|
|
|
```julia
|
|
# Configure compaction settings
|
|
MAX_TOKENS = 120000 # Stay under 128K limit
|
|
COMPACTION_THRESHOLD = 100000
|
|
|
|
# Agent loop with compaction
|
|
function runAgentWithCompaction(agent, session)
|
|
while true
|
|
# Get current token count
|
|
stats = getSessionStats(session)
|
|
|
|
if stats.total_tokens > COMPACTION_THRESHOLD
|
|
# Compact session
|
|
compactSession(session)
|
|
end
|
|
|
|
# Check if agent is idle
|
|
if !hasQueuedMessages(agent) && !isnothing(agent.active_run)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
function compactSession(session)
|
|
# Get current branch
|
|
branch = getBranch(session)
|
|
|
|
# Calculate tokens to compact
|
|
total_tokens = 0
|
|
for entry in branch
|
|
if entry isa MessageEntry
|
|
total_tokens += estimateTokens(entry.message)
|
|
end
|
|
end
|
|
|
|
if total_tokens < COMPACTION_THRESHOLD
|
|
return
|
|
end
|
|
|
|
# Identify messages to compact
|
|
messages_to_compact = []
|
|
tokens_to_keep = 50000 # Keep recent 50K tokens
|
|
|
|
for entry in branch
|
|
if entry isa MessageEntry
|
|
msg_tokens = estimateTokens(entry.message)
|
|
if tokens_to_keep > 0
|
|
tokens_to_keep -= msg_tokens
|
|
else
|
|
push!(messages_to_compact, entry)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Generate summary
|
|
summary = generateSummary(messages_to_compact)
|
|
|
|
# Create compaction entry
|
|
appendCompaction(
|
|
session,
|
|
summary,
|
|
messages_to_compact[end].id,
|
|
total_tokens,
|
|
)
|
|
|
|
println("Compacted $(length(messages_to_compact)) messages")
|
|
end
|
|
|
|
function estimateTokens(message::AgentMessage)::Int64
|
|
# Simple estimation: ~4 chars per token
|
|
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
|
|
|
|
function generateSummary(messages::Vector{MessageEntry})::String
|
|
# Use LLM to generate summary
|
|
summary = "Conversation summary:"
|
|
for msg in messages
|
|
summary *= "\n- $(msg.message)"
|
|
end
|
|
return summary
|
|
end
|
|
```
|
|
|
|
### Pattern 2: Custom Tool with Context
|
|
|
|
```julia
|
|
# Define context type
|
|
struct DatabaseContext
|
|
connection::Any
|
|
user::String
|
|
end
|
|
|
|
# Create tool with context
|
|
function createDatabaseTool()
|
|
return AgentTool(
|
|
"database",
|
|
"database",
|
|
"Execute SQL queries",
|
|
Dict{String, Any}(),
|
|
(tool_call_id, params, signal, on_update, context) -> begin
|
|
if !isa(context, DatabaseContext)
|
|
return AgentToolResult(
|
|
[TextContent("Error: Database context not provided")],
|
|
nothing,
|
|
nothing,
|
|
nothing,
|
|
true, # terminate
|
|
)
|
|
end
|
|
|
|
# Execute query
|
|
query = params["query"]
|
|
result = executeQuery(context.connection, query)
|
|
|
|
return AgentToolResult(
|
|
[TextContent(formatResult(result))],
|
|
Dict("user" => context.user),
|
|
nothing,
|
|
nothing,
|
|
nothing,
|
|
)
|
|
end,
|
|
nothing,
|
|
EXECUTION_SEQUENTIAL,
|
|
)
|
|
end
|
|
|
|
# Use tool with context
|
|
db_context = DatabaseContext(connection, "alice")
|
|
|
|
harness = AgentHarness(Dict(
|
|
:tools => [createDatabaseTool()],
|
|
:tool_context => AgentHarnessToolContextSource(db_context),
|
|
))
|
|
```
|
|
|
|
### Pattern 3: Dynamic Model Selection
|
|
|
|
```julia
|
|
# Hook to change model based on task
|
|
function dynamicModelSelection(context, signal)
|
|
# Check message content
|
|
last_message = context.message
|
|
|
|
# If complex task, use more capable model
|
|
if contains(join(last_message.content), "analyze")
|
|
return AgentLoopTurnUpdate(
|
|
context = context.context,
|
|
model = Model("gpt-4", "GPT-4", "openai", ...),
|
|
thinking_level = THINKING_HIGH,
|
|
)
|
|
end
|
|
|
|
# Otherwise use cheaper model
|
|
return AgentLoopTurnUpdate(
|
|
context = context.context,
|
|
model = Model("gpt-3.5", "GPT-3.5", "openai", ...),
|
|
thinking_level = THINKING_MEDIUM,
|
|
)
|
|
end
|
|
|
|
# Configure agent
|
|
agent = Agent(Dict(
|
|
:prepareNextTurn => dynamicModelSelection,
|
|
))
|
|
```
|
|
|
|
### Pattern 4: Rate Limiting
|
|
|
|
```julia
|
|
# Rate limiter
|
|
struct RateLimiter
|
|
calls_per_minute::Int
|
|
last_calls::Vector{DateTime}
|
|
end
|
|
|
|
function RateLimiter(calls_per_minute::Int)
|
|
return RateLimiter(calls_per_minute, DateTime[])
|
|
end
|
|
|
|
function rateLimit(limiter::RateLimiter)
|
|
now = Dates.now()
|
|
|
|
# Remove old calls
|
|
limiter.last_calls = filter(
|
|
c -> Dates.value(now - c) / 1000 < 60,
|
|
limiter.last_calls,
|
|
)
|
|
|
|
# Check limit
|
|
if length(limiter.last_calls) >= limiter.calls_per_minute
|
|
return false
|
|
end
|
|
|
|
# Record call
|
|
push!(limiter.last_calls, now)
|
|
return true
|
|
end
|
|
|
|
# Use in hook
|
|
limiter = RateLimiter(60) # 60 calls per minute
|
|
|
|
function rateLimitHook(event, signal)
|
|
if !rateLimit(limiter)
|
|
return BeforeProviderPayloadResult(event.payload) # Still send, but track
|
|
end
|
|
|
|
return BeforeProviderPayloadResult(event.payload)
|
|
end
|
|
|
|
# Configure
|
|
agent = Agent(Dict(
|
|
:beforeProviderPayload => rateLimitHook,
|
|
))
|
|
```
|
|
|
|
### Pattern 5: Multi-Step Tool Execution
|
|
|
|
```julia
|
|
# Tool that requires multiple steps
|
|
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
|
|
|
|
```julia
|
|
# Create read tool with image support
|
|
image_processor = ReadImageProcessor(
|
|
(path, context) -> begin
|
|
# Load image
|
|
image_data = readImage(path)
|
|
|
|
# Process with vision model
|
|
result = processImageWithVision(image_data)
|
|
|
|
return ReadImageProcessorResult(
|
|
[TextContent(result.description)],
|
|
result.usage,
|
|
)
|
|
end,
|
|
context,
|
|
)
|
|
|
|
read_tool = createReadTool(Dict(
|
|
"image_processor" => image_processor,
|
|
))
|
|
```
|
|
|
|
### Pattern 7: Session Navigation
|
|
|
|
```julia
|
|
# Navigate to specific point
|
|
session.moveTo(entry_id)
|
|
|
|
# Get branch from specific point
|
|
branch = getBranch(session, entry_id)
|
|
|
|
# Create label for easy navigation
|
|
appendLabel(session, entry_id, "important-decision")
|
|
|
|
# Find labeled entry
|
|
label = getLabel(session, "important-decision")
|
|
|
|
# Build context from branch
|
|
context = buildSessionContext(session)
|
|
|
|
# Get specific messages
|
|
messages = sessionEntryToContextMessages(entry, index, entries)
|
|
```
|
|
|
|
### Pattern 8: Batch Processing
|
|
|
|
```julia
|
|
# Process multiple prompts in batch
|
|
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)
|
|
wait_for_idle(agent)
|
|
|
|
# Get result
|
|
state = get_state(agent)
|
|
last_message = state.messages[end]
|
|
|
|
push!(results, last_message)
|
|
|
|
# Clean up
|
|
reset!(agent)
|
|
end
|
|
|
|
# Process results
|
|
for result in results
|
|
println("Result: $(result)")
|
|
end
|
|
```
|
|
|
|
### Pattern 9: Custom Event Handling
|
|
|
|
```julia
|
|
# Custom event types
|
|
struct CustomEvent <: AgentEvent
|
|
data::Any
|
|
end
|
|
|
|
# Custom event handler
|
|
function customEventHandler(event, signal)
|
|
if event isa CustomEvent
|
|
println("Custom event: $(event.data)")
|
|
end
|
|
end
|
|
|
|
# Subscribe to custom events
|
|
subscribe(agent) do event, signal
|
|
customEventHandler(event, signal)
|
|
end
|
|
|
|
# Emit custom event
|
|
emit(CustomEvent("custom data"))
|
|
```
|
|
|
|
### Pattern 10: Error Handling
|
|
|
|
```julia
|
|
# Hook for error handling
|
|
function errorHook(context, signal)
|
|
if context isa PrepareNextTurnContext
|
|
last_message = context.message
|
|
|
|
if last_message.stop_reason == "error"
|
|
println("Error in conversation: $(last_message.error_message)")
|
|
|
|
return AgentLoopTurnUpdate(
|
|
context = context.context,
|
|
model = context.context.model,
|
|
thinking_level = THINKING_HIGH, # Use more capable model
|
|
)
|
|
end
|
|
end
|
|
|
|
return nothing
|
|
end
|
|
|
|
# Use in agent
|
|
agent = Agent(Dict(
|
|
:prepareNextTurn => errorHook,
|
|
))
|
|
```
|
|
|
|
## Testing Patterns
|
|
|
|
### Unit Testing
|
|
|
|
```julia
|
|
# Test tool execution
|
|
@testset "Bash tool" begin
|
|
tool = createBashTool()
|
|
|
|
# Test successful execution
|
|
result = tool.execute("tc1", Dict("command" => "echo hello"), nothing, nothing, nothing)
|
|
@test result.content[1].text == "hello\n"
|
|
@test result.details === nothing
|
|
|
|
# Test error handling
|
|
result = tool.execute("tc2", Dict("command" => "exit 1"), nothing, nothing, nothing)
|
|
@test result.terminate === true
|
|
end
|
|
|
|
# Test agent with mock LLM
|
|
@testset "Agent with mock" begin
|
|
# Mock stream function
|
|
function mockStreamFn(model, context, options)
|
|
# Return mock response
|
|
return MockResponse([TextContent("Hello!")])
|
|
end
|
|
|
|
agent = Agent(Dict(
|
|
:stream_fn => mockStreamFn,
|
|
:systemPrompt => "You are a helpful assistant.",
|
|
:model => model,
|
|
))
|
|
|
|
# Test prompt
|
|
prompt(agent, "Hello")
|
|
wait_for_idle(agent)
|
|
|
|
# Verify result
|
|
state = get_state(agent)
|
|
@test length(state.messages) == 2 # User + Assistant
|
|
end
|
|
```
|
|
|
|
### Integration Testing
|
|
|
|
```julia
|
|
# Test full conversation flow
|
|
@testset "Full conversation" begin
|
|
# Create session storage
|
|
storage = InMemorySessionStorage(...)
|
|
session = Session(storage)
|
|
|
|
# Create agent
|
|
agent = Agent(Dict(
|
|
:systemPrompt => "You are a helpful assistant.",
|
|
:model => model,
|
|
:tools => [bash_tool],
|
|
:sessionId => session.getMetadata().id,
|
|
))
|
|
|
|
# Run conversation
|
|
prompt(agent, "What's in the directory?")
|
|
wait_for_idle(agent)
|
|
|
|
# Verify session
|
|
context = buildSessionContext(session)
|
|
@test length(context.messages) == 2
|
|
|
|
# Continue conversation
|
|
prompt(agent, "What's the weather?")
|
|
wait_for_idle(agent)
|
|
|
|
# Verify growth
|
|
context = buildSessionContext(session)
|
|
@test length(context.messages) == 4
|
|
end
|
|
```
|
|
|
|
## Performance Patterns
|
|
|
|
### Pattern 1: Caching
|
|
|
|
```julia
|
|
# Simple caching for LLM calls
|
|
struct LLMCache
|
|
cache::Dict{String, AssistantMessage}
|
|
end
|
|
|
|
function LLMCache()
|
|
return LLMCache(Dict{String, AssistantMessage}())
|
|
end
|
|
|
|
function getCached(cache::LLMCache, key::String)
|
|
return get(cache.cache, key, nothing)
|
|
end
|
|
|
|
function setCached(cache::LLMCache, key::String, value::AssistantMessage)
|
|
cache.cache[key] = value
|
|
end
|
|
|
|
# Use in stream function
|
|
function cachedStreamFn(model, context, options)
|
|
key = generateCacheKey(context)
|
|
|
|
cached = getCached(cache, key)
|
|
if !isnothing(cached)
|
|
return MockResponse(cached)
|
|
end
|
|
|
|
result = actualStreamFn(model, context, options)
|
|
setCached(cache, key, result)
|
|
return result
|
|
end
|
|
```
|
|
|
|
### Pattern 2: Batch LLM Calls
|
|
|
|
```julia
|
|
# Batch multiple LLM calls
|
|
function batchLLMCalls(calls::Vector{Dict})
|
|
results = []
|
|
|
|
for call in calls
|
|
result = streamFunction(
|
|
call[:model],
|
|
call[:context],
|
|
call[:options],
|
|
)
|
|
push!(results, result)
|
|
end
|
|
|
|
return results
|
|
end
|
|
|
|
# Use with parallel execution
|
|
tool.execute = (id, params, signal, on_update, context) -> begin
|
|
# Batch multiple LLM calls
|
|
llm_calls = [
|
|
Dict(:model => model, :context => context1, :options => options1),
|
|
Dict(:model => model, :context => context2, :options => options2),
|
|
]
|
|
|
|
results = batchLLMCalls(llm_calls)
|
|
|
|
return AgentToolResult(
|
|
[TextContent(join([r.text for r in results], "\n"))],
|
|
nothing,
|
|
nothing,
|
|
nothing,
|
|
nothing,
|
|
)
|
|
end
|
|
```
|
|
|
|
### Pattern 3: Lazy Loading
|
|
|
|
```julia
|
|
# Lazy load skills
|
|
struct LazySkills
|
|
dir::String
|
|
skills::Union{Vector{Skill}, Nothing}
|
|
end
|
|
|
|
function LazySkills(dir)
|
|
return LazySkills(dir, nothing)
|
|
end
|
|
|
|
function getSkills(lazy::LazySkills)
|
|
if isnothing(lazy.skills)
|
|
lazy.skills, _ = loadSkills(lazy.dir)
|
|
end
|
|
return lazy.skills
|
|
end
|
|
|
|
# Use in harness
|
|
harness = AgentHarness(Dict(
|
|
:resources => AgentHarnessResources(
|
|
templates,
|
|
LazySkills("/path/to/skills"),
|
|
),
|
|
))
|
|
```
|
|
|
|
## Production Patterns
|
|
|
|
### Pattern 1: Observability
|
|
|
|
```julia
|
|
# Logging hook
|
|
function loggingHook(event, signal)
|
|
if event isa BeforeProviderRequestEvent
|
|
println("[Request] $(event.model.id)")
|
|
elseif event isa AfterProviderResponseEvent
|
|
println("[Response] Status: $(event.status)")
|
|
elseif event isa ToolExecutionEndEvent
|
|
println("[Tool] $(event.tool_name): $(event.is_error ? "error" : "success")")
|
|
end
|
|
return nothing
|
|
end
|
|
|
|
# Metrics hook
|
|
function metricsHook(event, signal)
|
|
if event isa AgentStartEvent
|
|
metrics.start_time = Dates.now()
|
|
elseif event isa AgentEndEvent
|
|
duration = Dates.value(Dates.now() - metrics.start_time) / 1000
|
|
println("[Metrics] Duration: $(duration)s")
|
|
end
|
|
return nothing
|
|
end
|
|
```
|
|
|
|
### Pattern 2: Retry Logic
|
|
|
|
```julia
|
|
# Retry hook
|
|
function retryHook(event, signal)
|
|
if event isa AfterProviderResponseEvent && event.status >= 500
|
|
# Server error, retry
|
|
return BeforeProviderRequestResult(Dict(
|
|
"retry" => true,
|
|
"max_retries" => 3,
|
|
))
|
|
end
|
|
return nothing
|
|
end
|
|
|
|
# Use in stream options
|
|
harness = AgentHarness(Dict(
|
|
:stream_options => AgentHarnessStreamOptions(
|
|
max_retries = 3,
|
|
max_retry_delay_ms = 5000,
|
|
),
|
|
:retry => retryHook,
|
|
))
|
|
```
|
|
|
|
### Pattern 3: Security
|
|
|
|
```julia
|
|
# Security hook
|
|
function securityHook(event, signal)
|
|
if event isa ToolCallEvent
|
|
# Validate tool call
|
|
if event.tool_name == "bash"
|
|
command = event.input["command"]
|
|
|
|
# Block dangerous commands
|
|
dangerous_patterns = ["rm -rf /", "sudo", "curl | sh"]
|
|
for pattern in dangerous_patterns
|
|
if contains(command, pattern)
|
|
return ToolCallResult(true, "Blocked dangerous command")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return nothing
|
|
end
|
|
```
|
|
|
|
## Debugging Patterns
|
|
|
|
### Pattern 1: Conversation Trace
|
|
|
|
```julia
|
|
# Trace conversation
|
|
trace = []
|
|
|
|
subscribe(agent) do event, signal
|
|
if event isa MessageEndEvent
|
|
push!(trace, Dict(
|
|
"role" => event.message.role,
|
|
"content" => event.message.content,
|
|
))
|
|
end
|
|
end
|
|
|
|
# Run conversation
|
|
prompt(agent, "Hello")
|
|
wait_for_idle(agent)
|
|
|
|
# Print trace
|
|
for entry in trace
|
|
println("$(entry["role"]): $(entry["content"])")
|
|
end
|
|
```
|
|
|
|
### Pattern 2: Tool Call Trace
|
|
|
|
```julia
|
|
tool_trace = []
|
|
|
|
subscribe(agent) do event, signal
|
|
if event isa ToolExecutionStartEvent
|
|
push!(tool_trace, Dict(
|
|
"type" => "start",
|
|
"tool" => event.tool_name,
|
|
"args" => event.args,
|
|
))
|
|
elseif event isa ToolExecutionEndEvent
|
|
push!(tool_trace, Dict(
|
|
"type" => "end",
|
|
"tool" => event.tool_name,
|
|
"error" => event.is_error,
|
|
))
|
|
end
|
|
end
|
|
```
|
|
|
|
### Pattern 3: State Dump
|
|
|
|
```julia
|
|
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")
|
|
wait_for_idle(agent)
|
|
dumpState(agent)
|
|
```
|
|
|
|
## Best Practices Summary
|
|
|
|
1. **Start simple**, add complexity gradually
|
|
2. **Use hooks for customization**, not core logic
|
|
3. **Test with mock LLM** first
|
|
4. **Monitor token usage** for long conversations
|
|
5. **Use branches** for exploration
|
|
6. **Compact periodically** to stay within limits
|
|
7. **Handle errors gracefully**
|
|
8. **Log important events**
|
|
9. **Test edge cases**
|
|
10. **Profile performance**
|