By nghxni

Technical article

AiAgentDemoSrv v1.0.0: an LLM order assistant built from Camel routes

AiAgentDemoSrv proves a production-friendly pattern: one stable HTTP Listener entry, langchain4j-agent as the decision layer, and business APIs exposed as pure XML langchain4j-tools routes — discovered by tag, with no Java code per tool.

Natural language in, deterministic APIs out

Order-system users do not want to remember API paths or payload fields — they ask: "show all orders for customer Zhang San", "check details of order ord00xa12", "cancel this order with a reason". The service has to turn those sentences into deterministic, auditable API calls.

The demo answers with four pieces: an HTTP Listener entry, an agent orchestration layer, multiple langchain4j-tools business tools, and one structured response contract carrying both a readable answer (responseText) and machine-friendly tool output (toolData).

One listener, one agent, three XML tools

HTTP Listener entry

A single undertow endpoint restricts the entry to POST /api/ai/agent/chat on server.port=19095, enabled by HTTP.Listener=true.

Agent and memory wiring

The route sets CamelLangChain4jAgentSystemMessage from ai.system.prompt, takes CamelLangChain4jAgentMemoryId from the request's $.memoryId, and reads the user input from $.message — memory on, with ai.memory.max.turns=10.

Tag-discovered tools

Bound with tags=order-demo, the agent discovers three tools — queryOrderDetail, cancelOrder, listRecentOrders — each a langchain4j-tools route whose URI declares a description and parameter.xxx=type for argument extraction.

One response contract

After execution, DataSonnet packages success, memoryId, responseText, toolData, and timestamp into one JSON — suited to front-end rendering and downstream auditing alike.

Every tool route is self-describing in its URI — cancelOrder, for example:

<from uri="langchain4j-tools:cancelOrder?tags=order-demo&amp;description=Cancel an existing order by order ID. Requires a reason for cancellation.&amp;parameter.orderId=string&amp;parameter.reason=string"/>

Route and properties, verbatim

The whole wiring lives in ai-agent-demo-route.xml plus the two properties files — HTTP entry and agent call:

<from uri="undertow:http://0.0.0.0:{{server.port}}/api/ai/agent/chat?httpMethodRestrict=POST"/>
...
<to uri="langchain4j-agent:{{service.ai.type}}Assistant?agent=#genericAiAgent&amp;tags={{ai.agent.tags}}"/>
# common.config.properties
server.port=19095
HTTP.Listener=true

# service.config.properties
service.ai.route=true
service.ai.type=orderdemo
service.ai.mode=agent
ai.agent.tags=order-demo
ai.system.prompt=You are an order management assistant. You can help users query order status and cancel orders. Please respond in English.
ai.memory.enabled=true
ai.memory.max.turns=10

Validate with curl

The two-turn cancellation shows memory best: the first turn gives only the order ID, so the agent asks for the cancellation reason (toolData=null); the second turn reuses the same memoryId with a reason, triggers cancelOrder, and the final answer confirms the order ID and reason.

# turn 1 — agent asks for the reason (toolData=null)
curl -X POST "http://localhost:19095/api/ai/agent/chat" \
  -H "Content-Type: application/json" \
  -d '{"memoryId":"002","message":"Cancel order ord00xa12"}'

# turn 2 — same memoryId, reason supplied, cancelOrder fires
curl -X POST "http://localhost:19095/api/ai/agent/chat" \
  -H "Content-Type: application/json" \
  -d '{"memoryId":"002","message":"Cancel order ord00xa12 due to seven-day no-reason return"}'

Query requests behave the same way: "Query all order information for customer Zhang San" triggers listRecentOrders, "Query details for order ord00xa12" triggers queryOrderDetail, and toolData carries the structured order payload.

When behavior is off, check these first

Agent never calls tools: confirm ai.agent.tags matches every tool route's tags, the description is clear and action-oriented, and each parameter.xxx=type is declared. Endpoint 404 or no listener: confirm HTTP.Listener=true, server.port=19095, and that the request path is exactly /api/ai/agent/chat.

Silent tool HTTP failures: the demo calls use throwExceptionOnFailure=false, so locate them via the [AI-TOOL] httpStatus log lines and the servicelog response body. Lost multi-turn context: reuse one memoryId across turns and keep ai.memory.enabled=true. Garbled Chinese responses: keep the UTF-8 headers in tool routes and make sure requestCharsetProcessor and jsonResponseProcessor stay in the chain.

AI chat component guideAiAgentDemoSrv routes