Support Agent
A customer support pipeline that classifies intent, looks up orders, routes through a multi-level decision tree, generates responses with retry/fallback, and applies confidence-based routing -- all traced with waxell-observe decorators.
This example requires OPENAI_API_KEY, WAXELL_API_KEY, and WAXELL_API_URL. Use --dry-run to skip real API calls.
Architecture
Key Code
Intent Classification with Reasoning
The handler agent classifies intent via an LLM call (auto-instrumented), records the classification as a @step, and documents its reasoning chain.
@waxell.step_dec(name="classify_intent")
async def classify_intent_step(classification: str) -> dict:
"""Record intent classification step."""
return {"intent": "order_status", "raw_classification": classification[:100]}
@waxell.reasoning_dec(step="analyze_intent")
async def analyze_intent(query: str) -> dict:
"""Analyze the customer intent via reasoning chain."""
return {
"thought": "Customer mentions 'order ORD-12345' and asks 'where is my order'...",
"evidence": ["keyword:order", "keyword:where", "phrase:haven't received updates"],
"conclusion": "Intent classified as order_status with high confidence",
}
Tool Calls and Decision Tree
Order lookup and inventory check are @tool-decorated, creating database-type spans. Three @decision decorators record the routing tree.
@waxell.tool(tool_type="database")
def order_lookup(order_id: str) -> dict:
"""Look up an order by ID."""
return ORDER_DATA
@waxell.tool(tool_type="database")
def inventory_check(product_id: str) -> dict:
"""Check inventory for a product."""
return INVENTORY_DATA
@waxell.decision(name="order_found", options=["process_order", "order_not_found"])
async def decide_order_found(order_data: dict) -> dict:
return {"chosen": "process_order", "reasoning": f"Order {order_data['order_id']} found"}
@waxell.decision(name="refund_requested", options=["process_refund", "provide_status", "escalate"])
async def decide_refund_or_status(classification: str) -> dict:
return {"chosen": "provide_status", "reasoning": "Customer asking about shipping status"}
@waxell.decision(name="response_type", options=["automated_response", "human_handoff"])
async def decide_response_type() -> dict:
return {"chosen": "automated_response", "reasoning": "Clear tracking data available"}
Retry with Fallback and Confidence Routing
The evaluator agent uses @retry to demonstrate automatic retry recording on rate-limit errors, then applies confidence-based routing.
@waxell.retry_dec(max_attempts=3, strategy="fallback")
async def generate_response_with_retry(client, prompt: str):
"""Generate response with automatic retry recording on failure."""
return await client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are a friendly customer support agent..."},
{"role": "user", "content": prompt},
],
)
@waxell.decision(name="confidence_routing",
options=["send_response", "add_disclaimer", "escalate_to_human"])
async def decide_confidence_routing() -> dict:
return {"chosen": "send_response", "reasoning": "All decision points resolved with confidence >= 0.85"}
# Scores
waxell.score("classification_confidence", 0.88)
waxell.score("response_quality", "good", data_type="categorical")
waxell.score("sla_met", True, data_type="boolean")
What this demonstrates
- Multi-agent support pipeline -- orchestrator dispatches to
support-handler(classification + lookup) andsupport-evaluator(response + routing) with automatic parent-child traces. @reasoningdecorator -- documents the intent analysis chain-of-thought with thought/evidence/conclusion.@toolwithtool_type="database"-- order lookup and inventory check are recorded as database tool spans.- Four
@decisiondecorators -- a multi-level routing tree (order_found, refund_requested, response_type, confidence_routing) captured as structured decisions with options and reasoning. @retrywith fallback -- demonstrates automatic retry recording when the primary model returns 429, falling back to gpt-4o-mini.- Mixed score types -- numeric (
classification_confidence), categorical (response_quality), and boolean (sla_met) scores. - Auto-instrumented LLM calls -- both the intent classification and response generation OpenAI calls are captured automatically.
Run it
# Dry-run mode (no API key needed)
cd dev/waxell-dev
python -m app.demos.support_agent --dry-run
# Live mode
export OPENAI_API_KEY="sk-..."
export WAXELL_API_KEY="your-waxell-api-key"
export WAXELL_API_URL="https://api.waxell.ai"
python -m app.demos.support_agent