feat: complete phase 2 -- multi-agent routing, interrupt TTL, escalation, templates

- Intent classification with LLM structured output (single/multi/ambiguous)
- Discount agent with apply_discount and generate_coupon tools
- Interrupt manager with 30-min TTL auto-expiration and retry prompts
- Webhook escalation module with exponential backoff retry (max 3)
- Three vertical industry templates (e-commerce, SaaS, fintech)
- Template loading in AgentRegistry
- Enhanced supervisor prompt with dynamic agent descriptions
- 153 tests passing, 90.18% coverage
This commit is contained in:
Yaojia Wang
2026-03-30 21:04:39 +02:00
parent 7c3571b47d
commit 1050df780d
27 changed files with 1683 additions and 43 deletions

View File

@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from langchain_core.tools import BaseTool
from app.agents.discount import apply_discount, generate_coupon
from app.agents.fallback import fallback_respond
from app.agents.order_actions import cancel_order
from app.agents.order_lookup import get_order_status, get_tracking_info
@@ -16,6 +17,8 @@ _TOOL_MAP: dict[str, BaseTool] = {
"get_tracking_info": get_tracking_info,
"cancel_order": cancel_order,
"fallback_respond": fallback_respond,
"apply_discount": apply_discount,
"generate_coupon": generate_coupon,
}

View File

@@ -0,0 +1,79 @@
"""Discount agent tools -- apply discounts and generate coupons."""
from __future__ import annotations
import uuid
from langchain_core.tools import tool
from langgraph.types import interrupt
@tool
def apply_discount(order_id: str, discount_percent: int) -> dict:
"""Apply a discount to an order. Requires human approval before execution."""
if discount_percent < 1 or discount_percent > 100:
return {
"status": "error",
"order_id": order_id,
"message": f"Invalid discount: {discount_percent}%. Must be between 1 and 100.",
}
response = interrupt(
{
"action": "apply_discount",
"order_id": order_id,
"discount_percent": discount_percent,
"message": (
f"Please confirm: apply {discount_percent}% discount to order {order_id}?"
),
}
)
if isinstance(response, bool):
approved = response
elif isinstance(response, dict):
approved = response.get("approved", False)
else:
approved = bool(response)
if approved:
return {
"status": "applied",
"order_id": order_id,
"discount_percent": discount_percent,
"message": (
f"{discount_percent}% discount applied to order {order_id}."
),
}
return {
"status": "declined",
"order_id": order_id,
"message": f"Discount for order {order_id} was declined.",
}
@tool
def generate_coupon(discount_percent: int, expiry_days: int = 30) -> dict:
"""Generate a coupon code with the specified discount percentage."""
if discount_percent < 1 or discount_percent > 100:
return {
"status": "error",
"message": f"Invalid discount: {discount_percent}%. Must be between 1 and 100.",
}
if expiry_days < 1:
return {
"status": "error",
"message": f"Invalid expiry: {expiry_days} days. Must be at least 1.",
}
coupon_code = f"SAVE{discount_percent}-{uuid.uuid4().hex[:8].upper()}"
return {
"status": "generated",
"coupon_code": coupon_code,
"discount_percent": discount_percent,
"expiry_days": expiry_days,
"message": (
f"Coupon {coupon_code} generated: {discount_percent}% off, "
f"valid for {expiry_days} days."
),
}

View File

@@ -1,4 +1,4 @@
"""Fallback agent tools -- handles unmatched intents."""
"""Fallback agent tools -- handles unmatched intents and clarification requests."""
from __future__ import annotations
@@ -13,6 +13,7 @@ def fallback_respond(query: str) -> str:
"Here's what I can do:\n"
"- Check order status (e.g., 'What is the status of order 1042?')\n"
"- Get tracking information (e.g., 'Track order 1042')\n"
"- Cancel an order (e.g., 'Cancel order 1042')\n\n"
"- Cancel an order (e.g., 'Cancel order 1042')\n"
"- Apply discounts or generate coupons\n\n"
"Could you please rephrase your request?"
)