All code based on official Solon documentation (article/1285, article/1286, article/1329).
The Problem
Last month, our after-sales support team was drowning in tickets.
Hundreds of "order not received" complaints came in daily. Our CS reps had to switch between the order system, logistics system, and compensation system for every single case. Average handling time: 15 minutes per ticket. During promotions, the backlog hit 3,000+ tickets.
I thought: the workflow is always the same—look up the order, check logistics, determine responsibility, execute compensation. Why not let AI handle this flow?
So I built an AI-driven after-sales decision engine using Solon's ReActAgent.
What Is Solon's AI Agent?
Solon introduced AI Agent capabilities starting from v3.8. The core component is ReActAgent—think of it as an LLM that can call tools.
Instead of just chatting, ReActAgent follows a "Think → Act → Observe → Re-think" loop:
- Receives a customer complaint
- Think: "I need to look up this order"
-
Act: Calls
get_order(orderId)→ gets order details - Observe: The order has tracking number track_123
- Re-think: "Got the tracking number, let me check logistics status"
-
Act: Calls
get_logistic_status(trackNo) - Observe: Status is "lost"
- Re-think: "Order amount is $158, > $100, should issue a refund"
-
Act: Calls
apply_compensation("refund", 158)
The entire chain runs without human intervention.
Defining Business Tools
In Solon, tools extend AbsToolProvider with @ToolMapping annotations:
public static class OrderTools extends AbsToolProvider {
@ToolMapping(description = "Query order details by order ID: product name, amount, tracking number")
public String get_order(@Param(description = "Order ID") String orderId) {
if ("ORD_20251229".equals(orderId)) {
return "{\"orderId\":\"ORD_20251229\", \"amount\": 158.0, " +
"\"trackNo\": \"track_123\", \"sku\": \"Smart Headphones\"}";
}
return "{\"error\": \"Order not found\"}";
}
}
public static class LogisticTools extends AbsToolProvider {
@ToolMapping(description = "Query real-time shipping status by tracking number")
public String get_logistic_status(@Param(description = "Tracking number") String trackNo) {
if ("track_123".equals(trackNo)) {
return "{\"status\": \"lost\", \"info\": \"Package lost at Shanghai distribution center\"}";
}
return "{\"error\": \"Tracking number not found\"}";
}
}
public static class MarketingTools extends AbsToolProvider {
@ToolMapping(description = "Issue compensation based on policy. " +
"Small orders (<=100): coupon; Large orders (>100): full refund")
public String apply_compensation(
@Param(description = "Strategy: coupon or refund") String strategy,
@Param(description = "Order amount") double amount) {
if ("refund".equals(strategy) && amount > 100) {
return "[System] Refund submitted for $" + amount
+ ". Expected to process within 24 hours.";
} else if ("coupon".equals(strategy)) {
return "[System] $20 coupon credited to user account.";
}
return "[Manual] Strategy mismatch, escalated to human agent.";
}
}
The key is @ToolMapping—it tells the LLM what each tool does and what parameters it needs.
Assembling and Running the Agent
ChatModel chatModel = LlmUtil.getChatModel();
ReActAgent agent = ReActAgent.of(chatModel)
.defaultToolAdd(new OrderTools())
.defaultToolAdd(new LogisticTools())
.defaultToolAdd(new MarketingTools())
.modelOptions(o -> o.temperature(0.0))
.maxTurns(10)
.autoRethink(true)
.build();
AgentSession session = InMemoryAgentSession.of("demo_customer_job_001");
String result = agent.prompt("My order ORD_20251229 hasn't arrived. Can you check?").session(session).call().getContent();
System.out.println(result);
HITL (Human-In-The-Loop) Approval
Refunds involve money. Solon provides HITLInterceptor for human oversight:
HITLInterceptor hitl = new HITLInterceptor()
.onSensitiveTool("apply_compensation")
.onTool("apply_compensation", (trace, args) -> {
double amount = Double.parseDouble(args.get("amount").toString());
return amount > 100 ? "Large refund requires approval" : null;
});
ReActAgent agent = ReActAgent.of(chatModel)
.defaultToolAdd(new OrderTools())
.defaultToolAdd(new LogisticTools())
.defaultToolAdd(new MarketingTools())
.defaultInterceptorAdd(hitl)
.build();
Approval Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/ai/hitl/call |
POST | Submit user query; returns pending approval task if intercepted |
/ai/hitl/task |
GET | Get current pending task details |
/ai/hitl/submit |
POST | Approve/reject/modify parameters |
HITLTask task = HITL.getPendingTask(session);
if ("approve".equals(action)) {
HITL.submit(session, task.getToolName(),
HITLDecision.approve().comment("Verified by admin"));
} else {
HITL.submit(session, task.getToolName(),
HITLDecision.reject("Risk rejected by admin"));
}
// Resume AI execution
ReActResponse resp = agent.prompt().session(session).call();
Real-World Impact
| Metric | Before | After |
|---|---|---|
| Time per ticket | ~15 min | <10 sec (auto) / ~2 min (HITL) |
| CS team workload | Manual every ticket | Only ~30% need human review |
| Response to customer | Hours | Seconds |
Final Thoughts
Solon's ReActAgent bridges the gap between people and systems. Clean tool definitions, flexible injection, HITL approval out-of-the-box. Docs: article/1285, article/1286, article/1329.
Top comments (0)