update
This commit is contained in:
@@ -0,0 +1,893 @@
|
||||
# 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**
|
||||
Reference in New Issue
Block a user