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")
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 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\": \"Lost at Shanghai center\"}";
}
return "{\"error\": \"Tracking number not found\"}";
}
}
public static class MarketingTools extends AbsToolProvider {
@ToolMapping(description = "Issue compensation. Small orders: coupon; Large: 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;
} else if ("coupon".equals(strategy)) {
return "[System] $20 coupon credited.";
}
return "[Manual] Strategy mismatch, escalated to human agent.";
}
}
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("job_001");
String result = agent.prompt("My order ORD_20251229 hasn't arrived. Can you check?")
.session(session).call().getContent();
System.out.println(result);
Adding 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 query; returns pending if intercepted |
/ai/hitl/task |
GET | Get pending task details |
/ai/hitl/submit |
POST | Approve/reject |
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 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.
Official docs: article/1285 | article/1286 | article/1329
Top comments (0)