This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new cb12bcd9a740 CAMEL-23860: Add token usage and finish reason headers to
langchain4j chat and tools
cb12bcd9a740 is described below
commit cb12bcd9a740690c933def0272a69cd450360e92
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jul 1 11:17:47 2026 +0200
CAMEL-23860: Add token usage and finish reason headers to langchain4j chat
and tools
Add token usage (input/output/total token count) and finish reason as
exchange headers to the camel-langchain4j-chat and camel-langchain4j-tools
producers, matching the pattern already established in
camel-langchain4j-embeddings. For the tools producer, token usage is
accumulated across the tool-calling loop iterations so the headers reflect
the total cost of the entire agentic interaction.
Closes #24347
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
.../catalog/components/langchain4j-agent.json | 6 +-
.../camel/catalog/components/langchain4j-chat.json | 6 +-
.../catalog/components/langchain4j-tools.json | 6 ++
.../component/langchain4j/agent/api/Agent.java | 5 +-
.../langchain4j/agent/api/AgentWithMemory.java | 3 +-
.../langchain4j/agent/api/AgentWithoutMemory.java | 3 +-
.../agent/api/AiAgentWithMemoryService.java | 13 ++--
.../agent/api/AiAgentWithoutMemoryService.java | 17 ++---
.../component/langchain4j/agent/api/Headers.java | 12 ++++
.../langchain4j/agent/langchain4j-agent.json | 6 +-
.../agent/LangChain4jAgentProducer.java | 21 +++++-
.../LangChain4jAgentAutoConversionTest.java | 3 +-
.../LangChain4jAgentStructuredOutputTest.java | 5 +-
.../langchain4j/chat/langchain4j-chat.json | 6 +-
.../langchain4j/chat/LangChain4jChatHeaders.java | 12 ++++
.../langchain4j/chat/LangChain4jChatProducer.java | 25 ++++++-
.../langchain4j/tools/langchain4j-tools.json | 6 ++
.../tools/LangChain4jToolsEndpoint.java | 3 +-
.../tools/LangChain4jToolsHeaders.java} | 19 ++++--
.../tools/LangChain4jToolsProducer.java | 56 +++++++++++++---
.../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 17 +++++
.../builder/endpoint/EndpointHeaderBuilders.java | 13 ++++
.../LangChain4jAgentEndpointBuilderFactory.java | 52 +++++++++++++++
.../dsl/LangChain4jChatEndpointBuilderFactory.java | 52 +++++++++++++++
.../LangChain4jToolsEndpointBuilderFactory.java | 76 ++++++++++++++++++++++
25 files changed, 398 insertions(+), 45 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json
index 7573c35232aa..54c2ce9b38cf 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json
@@ -42,7 +42,11 @@
"CamelLangChain4jAgentUserMessage": { "index": 2, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The user message to accompany file
content when using WrappedFile as input.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#USER_MESSAGE" },
"CamelLangChain4jAgentMediaType": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The media type (MIME type) of the file
content. Overrides auto-detection from file extension.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#MEDIA_TYPE" },
"CamelLangChain4jAgentExcludeTags": { "index": 4, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Comma-separated list of Camel tool tags
to exclude from this agent invocation.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#EXCLUDE_TAGS" },
- "CamelLangChain4jAgentExcludeMcpServers": { "index": 5, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Comma-separated list of MCP server
names (keys) to exclude from this agent invocation.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#EXCLUDE_MCP_SERVERS" }
+ "CamelLangChain4jAgentExcludeMcpServers": { "index": 5, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Comma-separated list of MCP server
names (keys) to exclude from this agent invocation.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#EXCLUDE_MCP_SERVERS"
},
+ "CamelLangChain4jAgentFinishReason": { "index": 6, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "dev.langchain4j.model.output.FinishReason", "enum": [ "STOP",
"LENGTH", "TOOL_EXECUTION", "CONTENT_FILTER", "OTHER" ], "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description": "The
Finish Reason.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#FINISH_REASON" },
+ "CamelLangChain4jAgentInputTokenCount": { "index": 7, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Input Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#INPUT_TOKEN_COUNT" },
+ "CamelLangChain4jAgentOutputTokenCount": { "index": 8, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Output Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#OUTPUT_TOKEN_COUNT" },
+ "CamelLangChain4jAgentTotalTokenCount": { "index": 9, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Total Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#TOTAL_TOKEN_COUNT" }
},
"properties": {
"agentId": { "index": 0, "kind": "path", "displayName": "Agent Id",
"group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The Agent id" },
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
index c0c6529cfcee..904654d2fc07 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
@@ -32,7 +32,11 @@
},
"headers": {
"CamelLangChain4jChatPromptTemplate": { "index": 0, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The prompt Template.", "constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#PROMPT_TEMPLATE"
},
- "CamelLangChain4jChatAugmentedData": { "index": 1, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Augmented Data for RAG",
"constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#AUGMENTED_DATA"
}
+ "CamelLangChain4jChatAugmentedData": { "index": 1, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Augmented Data for RAG",
"constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#AUGMENTED_DATA"
},
+ "CamelLangChain4jChatFinishReason": { "index": 2, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "dev.langchain4j.model.output.FinishReason", "enum": [ "STOP",
"LENGTH", "TOOL_EXECUTION", "CONTENT_FILTER", "OTHER" ], "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description": "The
Finish Reason.", "constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#FINISH_REASON"
},
+ "CamelLangChain4jChatInputTokenCount": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Input Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#INPUT_TOKEN_COUNT"
},
+ "CamelLangChain4jChatOutputTokenCount": { "index": 4, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Output Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#OUTPUT_TOKEN_COUNT"
},
+ "CamelLangChain4jChatTotalTokenCount": { "index": 5, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Total Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#TOTAL_TOKEN_COUNT"
}
},
"properties": {
"chatId": { "index": 0, "kind": "path", "displayName": "Chat Id", "group":
"producer", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The id" },
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
index 8d877c5db54f..6cb564042d3d 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
@@ -30,6 +30,12 @@
"autowiredEnabled": { "index": 3, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
"chatModel": { "index": 4, "kind": "property", "displayName": "Chat
Model", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration",
"configurationField": "configuration", "description": "Chat Model of type
dev.langchain4j.model.chat. [...]
},
+ "headers": {
+ "CamelLangChain4jToolsFinishReason": { "index": 0, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "dev.langchain4j.model.output.FinishReason", "enum": [ "STOP",
"LENGTH", "TOOL_EXECUTION", "CONTENT_FILTER", "OTHER" ], "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description": "The
Finish Reason.", "constantName":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsHeaders#FINISH_REASON"
},
+ "CamelLangChain4jToolsInputTokenCount": { "index": 1, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Input Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsHeaders#INPUT_TOKEN_COUNT"
},
+ "CamelLangChain4jToolsOutputTokenCount": { "index": 2, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Output Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsHeaders#OUTPUT_TOKEN_COUNT"
},
+ "CamelLangChain4jToolsTotalTokenCount": { "index": 3, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Total Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsHeaders#TOTAL_TOKEN_COUNT"
}
+ },
"properties": {
"toolId": { "index": 0, "kind": "path", "displayName": "Tool Id", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The tool id" },
"tags": { "index": 1, "kind": "parameter", "displayName": "Tags", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The tags for the tools" },
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java
index 7d52dfdaa4b9..6a311a5c94b3 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.langchain4j.agent.api;
+import dev.langchain4j.service.Result;
import dev.langchain4j.service.tool.ToolProvider;
import org.apache.camel.Exchange;
import org.apache.camel.InvalidPayloadRuntimeException;
@@ -129,10 +130,10 @@ public interface Agent {
* (for stateful agents)
* @param toolProvider the tool provider that enables the agent to
execute functions and interact with external
* systems; may be {@code null} if no tools are
needed
- * @return the AI agent's response as a string
+ * @return the AI agent's response with token usage
metadata
* @throws RuntimeException if the chat interaction fails due to model
errors, configuration issues, or tool
* execution failures
*/
- String chat(AiAgentBody<?> aiAgentBody, ToolProvider toolProvider);
+ Result<String> chat(AiAgentBody<?> aiAgentBody, ToolProvider toolProvider);
}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java
index 2f33a925349a..62a46b775f69 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java
@@ -18,6 +18,7 @@ package org.apache.camel.component.langchain4j.agent.api;
import dev.langchain4j.data.message.Content;
import dev.langchain4j.service.AiServices;
+import dev.langchain4j.service.Result;
import dev.langchain4j.service.tool.ToolProvider;
/**
@@ -34,7 +35,7 @@ public class AgentWithMemory extends
AbstractAgent<AiAgentWithMemoryService> {
}
@Override
- public String chat(AiAgentBody<?> aiAgentBody, ToolProvider toolProvider) {
+ public Result<String> chat(AiAgentBody<?> aiAgentBody, ToolProvider
toolProvider) {
AiAgentWithMemoryService agentService =
createAiAgentService(toolProvider);
String userMessage = aiAgentBody.getUserMessage();
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java
index 82d26fbf0fd6..c2b0279cedd5 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java
@@ -18,6 +18,7 @@ package org.apache.camel.component.langchain4j.agent.api;
import dev.langchain4j.data.message.Content;
import dev.langchain4j.service.AiServices;
+import dev.langchain4j.service.Result;
import dev.langchain4j.service.tool.ToolProvider;
/**
@@ -34,7 +35,7 @@ public class AgentWithoutMemory extends
AbstractAgent<AiAgentWithoutMemoryServic
}
@Override
- public String chat(AiAgentBody<?> aiAgentBody, ToolProvider toolProvider) {
+ public Result<String> chat(AiAgentBody<?> aiAgentBody, ToolProvider
toolProvider) {
AiAgentWithoutMemoryService agentService =
createAiAgentService(toolProvider);
String userMessage = aiAgentBody.getUserMessage();
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AiAgentWithMemoryService.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AiAgentWithMemoryService.java
index 4837a096e917..48be3234c8bd 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AiAgentWithMemoryService.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AiAgentWithMemoryService.java
@@ -18,6 +18,7 @@ package org.apache.camel.component.langchain4j.agent.api;
import dev.langchain4j.data.message.Content;
import dev.langchain4j.service.MemoryId;
+import dev.langchain4j.service.Result;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
@@ -35,7 +36,7 @@ public interface AiAgentWithMemoryService {
* @param message
* @return
*/
- String chat(@MemoryId Object memoryId, @UserMessage String message);
+ Result<String> chat(@MemoryId Object memoryId, @UserMessage String
message);
/**
* Chat with a user message containing both text and additional content
(e.g., images, audio) with memory support.
@@ -43,9 +44,9 @@ public interface AiAgentWithMemoryService {
* @param memoryId the memory identifier for conversation history
* @param message the text portion of the user message
* @param content additional content such as ImageContent, AudioContent,
etc.
- * @return the AI response
+ * @return the AI response with token usage metadata
*/
- String chat(@MemoryId Object memoryId, @UserMessage String message,
@UserMessage Content content);
+ Result<String> chat(@MemoryId Object memoryId, @UserMessage String
message, @UserMessage Content content);
/**
* Simple chat with a user message, system message and memory window
@@ -56,7 +57,7 @@ public interface AiAgentWithMemoryService {
* @return
*/
@SystemMessage("{{prompt}}")
- String chat(
+ Result<String> chat(
@MemoryId Object memoryId, @UserMessage String message,
@V("prompt") String prompt);
@@ -67,10 +68,10 @@ public interface AiAgentWithMemoryService {
* @param message the text portion of the user message
* @param content additional content such as ImageContent, AudioContent,
etc.
* @param prompt the system message template
- * @return the AI response
+ * @return the AI response with token usage metadata
*/
@SystemMessage("{{prompt}}")
- String chat(
+ Result<String> chat(
@MemoryId Object memoryId, @UserMessage String message,
@UserMessage Content content, @V("prompt") String prompt);
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AiAgentWithoutMemoryService.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AiAgentWithoutMemoryService.java
index dfa72a61e6e6..57519a53e81a 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AiAgentWithoutMemoryService.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AiAgentWithoutMemoryService.java
@@ -17,6 +17,7 @@
package org.apache.camel.component.langchain4j.agent.api;
import dev.langchain4j.data.message.Content;
+import dev.langchain4j.service.Result;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
@@ -30,28 +31,28 @@ public interface AiAgentWithoutMemoryService {
* Simple chat with a single user message
*
* @param message the user message
- * @return the AI response
+ * @return the AI response with token usage metadata
*/
- String chat(@UserMessage String message);
+ Result<String> chat(@UserMessage String message);
/**
* Chat with a user message containing both text and additional content
(e.g., images, audio).
*
* @param message the text portion of the user message
* @param content additional content such as ImageContent, AudioContent,
etc.
- * @return the AI response
+ * @return the AI response with token usage metadata
*/
- String chat(@UserMessage String message, @UserMessage Content content);
+ Result<String> chat(@UserMessage String message, @UserMessage Content
content);
/**
* Simple chat with a single user message and system message
*
* @param message the user message
* @param prompt the system message template
- * @return the AI response
+ * @return the AI response with token usage metadata
*/
@SystemMessage("{{prompt}}")
- String chat(@UserMessage String message, @V("prompt") String prompt);
+ Result<String> chat(@UserMessage String message, @V("prompt") String
prompt);
/**
* Chat with a user message containing both text and additional content,
with system message.
@@ -59,9 +60,9 @@ public interface AiAgentWithoutMemoryService {
* @param message the text portion of the user message
* @param content additional content such as ImageContent, AudioContent,
etc.
* @param prompt the system message template
- * @return the AI response
+ * @return the AI response with token usage metadata
*/
@SystemMessage("{{prompt}}")
- String chat(@UserMessage String message, @UserMessage Content content,
@V("prompt") String prompt);
+ Result<String> chat(@UserMessage String message, @UserMessage Content
content, @V("prompt") String prompt);
}
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Headers.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Headers.java
index a68606da9674..bb858e62da52 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Headers.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Headers.java
@@ -43,4 +43,16 @@ public class Headers {
@Metadata(description = "Comma-separated list of MCP server names (keys)
to exclude from this agent invocation.",
javaType = "String")
public static final String EXCLUDE_MCP_SERVERS =
"CamelLangChain4jAgentExcludeMcpServers";
+
+ @Metadata(description = "The Finish Reason.", javaType =
"dev.langchain4j.model.output.FinishReason")
+ public static final String FINISH_REASON =
"CamelLangChain4jAgentFinishReason";
+
+ @Metadata(description = "The Input Token Count.", javaType = "int")
+ public static final String INPUT_TOKEN_COUNT =
"CamelLangChain4jAgentInputTokenCount";
+
+ @Metadata(description = "The Output Token Count.", javaType = "int")
+ public static final String OUTPUT_TOKEN_COUNT =
"CamelLangChain4jAgentOutputTokenCount";
+
+ @Metadata(description = "The Total Token Count.", javaType = "int")
+ public static final String TOTAL_TOKEN_COUNT =
"CamelLangChain4jAgentTotalTokenCount";
}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json
b/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json
index 7573c35232aa..54c2ce9b38cf 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json
+++
b/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json
@@ -42,7 +42,11 @@
"CamelLangChain4jAgentUserMessage": { "index": 2, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The user message to accompany file
content when using WrappedFile as input.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#USER_MESSAGE" },
"CamelLangChain4jAgentMediaType": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The media type (MIME type) of the file
content. Overrides auto-detection from file extension.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#MEDIA_TYPE" },
"CamelLangChain4jAgentExcludeTags": { "index": 4, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Comma-separated list of Camel tool tags
to exclude from this agent invocation.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#EXCLUDE_TAGS" },
- "CamelLangChain4jAgentExcludeMcpServers": { "index": 5, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Comma-separated list of MCP server
names (keys) to exclude from this agent invocation.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#EXCLUDE_MCP_SERVERS" }
+ "CamelLangChain4jAgentExcludeMcpServers": { "index": 5, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Comma-separated list of MCP server
names (keys) to exclude from this agent invocation.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#EXCLUDE_MCP_SERVERS"
},
+ "CamelLangChain4jAgentFinishReason": { "index": 6, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "dev.langchain4j.model.output.FinishReason", "enum": [ "STOP",
"LENGTH", "TOOL_EXECUTION", "CONTENT_FILTER", "OTHER" ], "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description": "The
Finish Reason.", "constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#FINISH_REASON" },
+ "CamelLangChain4jAgentInputTokenCount": { "index": 7, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Input Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#INPUT_TOKEN_COUNT" },
+ "CamelLangChain4jAgentOutputTokenCount": { "index": 8, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Output Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#OUTPUT_TOKEN_COUNT" },
+ "CamelLangChain4jAgentTotalTokenCount": { "index": 9, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Total Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.agent.api.Headers#TOTAL_TOKEN_COUNT" }
},
"properties": {
"agentId": { "index": 0, "kind": "path", "displayName": "Agent Id",
"group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The Agent id" },
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
index 4e0eefac6418..13219372ccb6 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
@@ -32,12 +32,14 @@ import
dev.langchain4j.model.chat.request.ResponseFormatType;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
import dev.langchain4j.model.chat.request.json.JsonRawSchema;
import dev.langchain4j.model.chat.request.json.JsonSchema;
+import dev.langchain4j.service.Result;
import dev.langchain4j.service.output.JsonSchemas;
import dev.langchain4j.service.tool.ToolExecutor;
import dev.langchain4j.service.tool.ToolProvider;
import dev.langchain4j.service.tool.ToolProviderRequest;
import dev.langchain4j.service.tool.ToolProviderResult;
import org.apache.camel.Exchange;
+import org.apache.camel.Message;
import org.apache.camel.component.langchain4j.agent.api.AbstractAgent;
import org.apache.camel.component.langchain4j.agent.api.Agent;
import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration;
@@ -124,8 +126,23 @@ public class LangChain4jAgentProducer extends
DefaultProducer {
AiAgentBody<?> aiAgentBody =
exchange.getMessage().getMandatoryBody(AiAgentBody.class);
ToolProvider toolProvider = createComposedToolProvider(tags, exchange);
- String response = agent.chat(aiAgentBody, toolProvider);
- exchange.getMessage().setBody(response);
+ Result<String> result = agent.chat(aiAgentBody, toolProvider);
+ exchange.getMessage().setBody(result.content());
+ populateTokenUsageHeaders(result, exchange);
+ }
+
+ private void populateTokenUsageHeaders(Result<String> result, Exchange
exchange) {
+ Message message = exchange.getMessage();
+
+ if (result.finishReason() != null) {
+ message.setHeader(Headers.FINISH_REASON, result.finishReason());
+ }
+
+ if (result.tokenUsage() != null) {
+ message.setHeader(Headers.INPUT_TOKEN_COUNT,
result.tokenUsage().inputTokenCount());
+ message.setHeader(Headers.OUTPUT_TOKEN_COUNT,
result.tokenUsage().outputTokenCount());
+ message.setHeader(Headers.TOTAL_TOKEN_COUNT,
result.tokenUsage().totalTokenCount());
+ }
}
/**
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentAutoConversionTest.java
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentAutoConversionTest.java
index e5d7b5bc9ba9..c764df950f14 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentAutoConversionTest.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentAutoConversionTest.java
@@ -20,6 +20,7 @@ import java.io.ByteArrayInputStream;
import java.io.File;
import java.nio.file.Files;
+import dev.langchain4j.service.Result;
import org.apache.camel.Exchange;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.TypeConversionException;
@@ -39,7 +40,7 @@ public class LangChain4jAgentAutoConversionTest extends
CamelTestSupport {
@Override
protected RoutesBuilder createRouteBuilder() throws Exception {
- Agent mockAgent = (body, exchange) -> "Processed";
+ Agent mockAgent = (body, exchange) -> Result.<String>
builder().content("Processed").build();
context.getRegistry().bind("mockAgent", mockAgent);
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputTest.java
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputTest.java
index 5ede51a2051a..109f847bf9ff 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputTest.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentStructuredOutputTest.java
@@ -18,6 +18,7 @@ package
org.apache.camel.component.langchain4j.agent.integration;
import java.io.FileNotFoundException;
+import dev.langchain4j.service.Result;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
@@ -190,7 +191,7 @@ public class LangChain4jAgentStructuredOutputTest {
void testOutputClassWithUserProvidedAgentThrowsIllegalArgumentException()
throws Exception {
try (DefaultCamelContext context = new DefaultCamelContext()) {
AgentConfiguration config = new AgentConfiguration();
- Agent stubAgent = (aiAgentBody, toolProvider) -> "stub";
+ Agent stubAgent = (aiAgentBody, toolProvider) -> Result.<String>
builder().content("stub").build();
context.getRegistry().bind("myConfig", config);
context.getRegistry().bind("myAgent", stubAgent);
@@ -213,7 +214,7 @@ public class LangChain4jAgentStructuredOutputTest {
void testOutputClassWithAgentFactoryThrowsIllegalArgumentException()
throws Exception {
try (DefaultCamelContext context = new DefaultCamelContext()) {
AgentConfiguration config = new AgentConfiguration();
- Agent stubAgent = (aiAgentBody, toolProvider) -> "stub";
+ Agent stubAgent = (aiAgentBody, toolProvider) -> Result.<String>
builder().content("stub").build();
AgentFactory stubFactory = new AgentFactory() {
@Override
public Agent createAgent(Exchange exchange) {
diff --git
a/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
b/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
index c0c6529cfcee..904654d2fc07 100644
---
a/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
+++
b/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
@@ -32,7 +32,11 @@
},
"headers": {
"CamelLangChain4jChatPromptTemplate": { "index": 0, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The prompt Template.", "constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#PROMPT_TEMPLATE"
},
- "CamelLangChain4jChatAugmentedData": { "index": 1, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Augmented Data for RAG",
"constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#AUGMENTED_DATA"
}
+ "CamelLangChain4jChatAugmentedData": { "index": 1, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Augmented Data for RAG",
"constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#AUGMENTED_DATA"
},
+ "CamelLangChain4jChatFinishReason": { "index": 2, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "dev.langchain4j.model.output.FinishReason", "enum": [ "STOP",
"LENGTH", "TOOL_EXECUTION", "CONTENT_FILTER", "OTHER" ], "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description": "The
Finish Reason.", "constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#FINISH_REASON"
},
+ "CamelLangChain4jChatInputTokenCount": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Input Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#INPUT_TOKEN_COUNT"
},
+ "CamelLangChain4jChatOutputTokenCount": { "index": 4, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Output Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#OUTPUT_TOKEN_COUNT"
},
+ "CamelLangChain4jChatTotalTokenCount": { "index": 5, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Total Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.chat.LangChain4jChatHeaders#TOTAL_TOKEN_COUNT"
}
},
"properties": {
"chatId": { "index": 0, "kind": "path", "displayName": "Chat Id", "group":
"producer", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The id" },
diff --git
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatHeaders.java
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatHeaders.java
index 18082bc4cebe..cf4a556f3854 100644
---
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatHeaders.java
+++
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatHeaders.java
@@ -24,4 +24,16 @@ public class LangChain4jChatHeaders {
@Metadata(description = "Augmented Data for RAG", javaType = "String")
public static final String AUGMENTED_DATA =
"CamelLangChain4jChatAugmentedData";
+
+ @Metadata(description = "The Finish Reason.", javaType =
"dev.langchain4j.model.output.FinishReason")
+ public static final String FINISH_REASON =
"CamelLangChain4jChatFinishReason";
+
+ @Metadata(description = "The Input Token Count.", javaType = "int")
+ public static final String INPUT_TOKEN_COUNT =
"CamelLangChain4jChatInputTokenCount";
+
+ @Metadata(description = "The Output Token Count.", javaType = "int")
+ public static final String OUTPUT_TOKEN_COUNT =
"CamelLangChain4jChatOutputTokenCount";
+
+ @Metadata(description = "The Total Token Count.", javaType = "int")
+ public static final String TOTAL_TOKEN_COUNT =
"CamelLangChain4jChatTotalTokenCount";
}
diff --git
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java
index 05f786937df5..5a66fd569add 100644
---
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java
+++
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java
@@ -24,6 +24,7 @@ import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
+import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.rag.content.Content;
@@ -31,6 +32,7 @@ import dev.langchain4j.rag.content.injector.ContentInjector;
import dev.langchain4j.rag.content.injector.DefaultContentInjector;
import org.apache.camel.Exchange;
import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.Message;
import org.apache.camel.NoSuchHeaderException;
import org.apache.camel.support.DefaultProducer;
import org.apache.camel.util.ObjectHelper;
@@ -110,6 +112,20 @@ public class LangChain4jChatProducer extends
DefaultProducer {
exchange.getMessage().setBody(response);
}
+ private void populateTokenUsageHeaders(ChatResponse chatResponse, Exchange
exchange) {
+ Message message = exchange.getMessage();
+
+ if (chatResponse.finishReason() != null) {
+ message.setHeader(LangChain4jChatHeaders.FINISH_REASON,
chatResponse.finishReason());
+ }
+
+ if (chatResponse.tokenUsage() != null) {
+ message.setHeader(LangChain4jChatHeaders.INPUT_TOKEN_COUNT,
chatResponse.tokenUsage().inputTokenCount());
+ message.setHeader(LangChain4jChatHeaders.OUTPUT_TOKEN_COUNT,
chatResponse.tokenUsage().outputTokenCount());
+ message.setHeader(LangChain4jChatHeaders.TOTAL_TOKEN_COUNT,
chatResponse.tokenUsage().totalTokenCount());
+ }
+ }
+
/**
* Send a ChatMessage
*
@@ -119,8 +135,9 @@ public class LangChain4jChatProducer extends
DefaultProducer {
private String sendChatMessage(ChatMessage chatMessage, Exchange exchange)
{
var augmentedChatMessage = addAugmentedData(chatMessage, exchange);
- AiMessage response =
this.chatModel.chat(augmentedChatMessage).aiMessage();
- return extractAiResponse(response);
+ ChatResponse chatResponse = this.chatModel.chat(augmentedChatMessage);
+ populateTokenUsageHeaders(chatResponse, exchange);
+ return extractAiResponse(chatResponse.aiMessage());
}
/**
@@ -165,7 +182,9 @@ public class LangChain4jChatProducer extends
DefaultProducer {
}
- response = this.chatModel.chat(chatMessages).aiMessage();
+ ChatResponse chatResponse = this.chatModel.chat(chatMessages);
+ populateTokenUsageHeaders(chatResponse, exchange);
+ response = chatResponse.aiMessage();
return extractAiResponse(response);
}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json
b/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json
index 8d877c5db54f..6cb564042d3d 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json
+++
b/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json
@@ -30,6 +30,12 @@
"autowiredEnabled": { "index": 3, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
"chatModel": { "index": 4, "kind": "property", "displayName": "Chat
Model", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration",
"configurationField": "configuration", "description": "Chat Model of type
dev.langchain4j.model.chat. [...]
},
+ "headers": {
+ "CamelLangChain4jToolsFinishReason": { "index": 0, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "dev.langchain4j.model.output.FinishReason", "enum": [ "STOP",
"LENGTH", "TOOL_EXECUTION", "CONTENT_FILTER", "OTHER" ], "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description": "The
Finish Reason.", "constantName":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsHeaders#FINISH_REASON"
},
+ "CamelLangChain4jToolsInputTokenCount": { "index": 1, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Input Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsHeaders#INPUT_TOKEN_COUNT"
},
+ "CamelLangChain4jToolsOutputTokenCount": { "index": 2, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Output Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsHeaders#OUTPUT_TOKEN_COUNT"
},
+ "CamelLangChain4jToolsTotalTokenCount": { "index": 3, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "int", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The Total Token Count.",
"constantName":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsHeaders#TOTAL_TOKEN_COUNT"
}
+ },
"properties": {
"toolId": { "index": 0, "kind": "path", "displayName": "Tool Id", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The tool id" },
"tags": { "index": 1, "kind": "parameter", "displayName": "Tags", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The tags for the tools" },
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java
index e2e4cbd68146..70e4ba176255 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java
@@ -51,7 +51,8 @@ import static
org.apache.camel.component.langchain4j.tools.LangChain4jTools.SCHE
@UriEndpoint(firstVersion = "4.8.0", scheme = SCHEME,
title = "LangChain4j Tools",
syntax = "langchain4j-tools:toolId",
- category = { Category.AI })
+ category = { Category.AI },
+ headersClass = LangChain4jToolsHeaders.class)
public class LangChain4jToolsEndpoint extends DefaultEndpoint {
private static final Logger LOG =
LoggerFactory.getLogger(LangChain4jToolsEndpoint.class);
diff --git
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatHeaders.java
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsHeaders.java
similarity index 52%
copy from
components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatHeaders.java
copy to
components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsHeaders.java
index 18082bc4cebe..0c691ef2a4a9 100644
---
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatHeaders.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsHeaders.java
@@ -14,14 +14,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.component.langchain4j.chat;
+package org.apache.camel.component.langchain4j.tools;
import org.apache.camel.spi.Metadata;
-public class LangChain4jChatHeaders {
- @Metadata(description = "The prompt Template.", javaType = "String")
- public static final String PROMPT_TEMPLATE =
"CamelLangChain4jChatPromptTemplate";
+public class LangChain4jToolsHeaders {
- @Metadata(description = "Augmented Data for RAG", javaType = "String")
- public static final String AUGMENTED_DATA =
"CamelLangChain4jChatAugmentedData";
+ @Metadata(description = "The Finish Reason.", javaType =
"dev.langchain4j.model.output.FinishReason")
+ public static final String FINISH_REASON =
"CamelLangChain4jToolsFinishReason";
+
+ @Metadata(description = "The Input Token Count.", javaType = "int")
+ public static final String INPUT_TOKEN_COUNT =
"CamelLangChain4jToolsInputTokenCount";
+
+ @Metadata(description = "The Output Token Count.", javaType = "int")
+ public static final String OUTPUT_TOKEN_COUNT =
"CamelLangChain4jToolsOutputTokenCount";
+
+ @Metadata(description = "The Total Token Count.", javaType = "int")
+ public static final String TOTAL_TOKEN_COUNT =
"CamelLangChain4jToolsTotalTokenCount";
}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
index f49ac949559c..b921fe4c5370 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
@@ -40,8 +40,10 @@ import
dev.langchain4j.model.chat.request.json.JsonStringSchema;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.output.FinishReason;
import dev.langchain4j.model.output.Response;
+import dev.langchain4j.model.output.TokenUsage;
import org.apache.camel.Exchange;
import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.Message;
import org.apache.camel.TypeConverter;
import
org.apache.camel.component.langchain4j.tools.spec.CamelToolExecutorCache;
import
org.apache.camel.component.langchain4j.tools.spec.CamelToolSpecification;
@@ -122,12 +124,37 @@ public class LangChain4jToolsProducer extends
DefaultProducer {
final Exchange baseline = ExchangeHelper.createCopy(exchange, true);
+ int totalInputTokens = 0;
+ int totalOutputTokens = 0;
+ int totalTokens = 0;
+ FinishReason lastFinishReason = null;
+
// First talk to the model to get the tools to be called
int i = 0;
do {
LOG.debug("Starting iteration {}", i);
- final Response<AiMessage> response = chatWithLLM(chatMessages,
toolPair, exchange);
+ final ChatResponse chatResponse = chatWithLLM(chatMessages,
toolPair, exchange);
+
+ // Accumulate token usage across iterations
+ if (chatResponse.tokenUsage() != null) {
+ TokenUsage usage = chatResponse.tokenUsage();
+ if (usage.inputTokenCount() != null) {
+ totalInputTokens += usage.inputTokenCount();
+ }
+ if (usage.outputTokenCount() != null) {
+ totalOutputTokens += usage.outputTokenCount();
+ }
+ if (usage.totalTokenCount() != null) {
+ totalTokens += usage.totalTokenCount();
+ }
+ }
+ if (chatResponse.finishReason() != null) {
+ lastFinishReason = chatResponse.finishReason();
+ }
+
+ final Response<AiMessage> response =
Response.from(chatResponse.aiMessage());
if (isDoneExecuting(response)) {
+ populateTokenUsageHeaders(lastFinishReason, totalInputTokens,
totalOutputTokens, totalTokens, exchange);
return extractAiResponse(response);
}
@@ -138,6 +165,21 @@ public class LangChain4jToolsProducer extends
DefaultProducer {
} while (true);
}
+ private void populateTokenUsageHeaders(
+ FinishReason finishReason, int inputTokens, int outputTokens, int
totalTokens, Exchange exchange) {
+ Message message = exchange.getMessage();
+
+ if (finishReason != null) {
+ message.setHeader(LangChain4jToolsHeaders.FINISH_REASON,
finishReason);
+ }
+
+ if (inputTokens > 0 || outputTokens > 0 || totalTokens > 0) {
+ message.setHeader(LangChain4jToolsHeaders.INPUT_TOKEN_COUNT,
inputTokens);
+ message.setHeader(LangChain4jToolsHeaders.OUTPUT_TOKEN_COUNT,
outputTokens);
+ message.setHeader(LangChain4jToolsHeaders.TOTAL_TOKEN_COUNT,
totalTokens);
+ }
+ }
+
private boolean isDoneExecuting(Response<AiMessage> response) {
if (!response.content().hasToolExecutionRequests()) {
LOG.info("Finished executing tools because of there are no more
execution requests");
@@ -297,7 +339,7 @@ public class LangChain4jToolsProducer extends
DefaultProducer {
* @param toolPair the toolPair containing the available tools to be
called
* @return the response provided by the model
*/
- private Response<AiMessage> chatWithLLM(List<ChatMessage> chatMessages,
ToolPair toolPair, Exchange exchange) {
+ private ChatResponse chatWithLLM(List<ChatMessage> chatMessages, ToolPair
toolPair, Exchange exchange) {
ChatRequest.Builder requestBuilder = ChatRequest.builder()
.messages(chatMessages);
@@ -313,17 +355,15 @@ public class LangChain4jToolsProducer extends
DefaultProducer {
// generate response
ChatResponse chatResponse = this.chatModel.chat(chatRequest);
- // Convert ChatResponse to Response<AiMessage> for compatibility
AiMessage aiMessage = chatResponse.aiMessage();
- Response<AiMessage> response = Response.from(aiMessage);
- if (!response.content().hasToolExecutionRequests()) {
+ if (!aiMessage.hasToolExecutionRequests()) {
exchange.getMessage().setHeader(LangChain4jTools.NO_TOOLS_CALLED_HEADER,
Boolean.TRUE);
- return response;
+ return chatResponse;
}
- chatMessages.add(response.content());
- return response;
+ chatMessages.add(aiMessage);
+ return chatResponse;
}
/**
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 4704d07950c3..3564013b7878 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -13,6 +13,23 @@ See the xref:camel-upgrade-recipes-tool.adoc[documentation]
page for details.
== Upgrading Camel 4.21 to 4.22
+=== camel-langchain4j-agent
+
+The `Agent.chat()` method return type has changed from `String` to
`Result<String>` (from `dev.langchain4j.service.Result`).
+This allows the agent producer to expose token usage (input, output, total
token count) and finish reason as exchange headers,
+consistent with the chat, tools, and embeddings components.
+
+If you have a custom `Agent` implementation, update the `chat` method
signature:
+
+[source,java]
+----
+// Before
+String chat(AiAgentBody<?> aiAgentBody, ToolProvider toolProvider);
+
+// After
+Result<String> chat(AiAgentBody<?> aiAgentBody, ToolProvider toolProvider);
+----
+
=== camel-fory with JDK 25+ - Breaking change
Due to new requirements from Apache Fory, when using Apache Fory Dataformat,
the JVM parameter `--add-opens java.base/java.lang.invoke=ALL-UNNAMED` must be
provided.
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java
index 23b20b4a5056..9478d6a92b8b 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java
@@ -2573,6 +2573,19 @@ public class EndpointHeaderBuilders {
public static
LangChain4jEmbeddingStoreEndpointBuilderFactory.LangChain4jEmbeddingStoreHeaderNameBuilder
langchain4jEmbeddingstore() {
return
LangChain4jEmbeddingStoreEndpointBuilderFactory.LangChain4jEmbeddingStoreHeaderNameBuilder.INSTANCE;
}
+ /**
+ * LangChain4j Tools (camel-langchain4j-tools)
+ * LangChain4j Tools and Function Calling Features
+ *
+ * Category: ai
+ * Since: 4.8
+ * Maven coordinates: org.apache.camel:camel-langchain4j-tools
+ *
+ * @return the dsl builder for the headers' name.
+ */
+ public static
LangChain4jToolsEndpointBuilderFactory.LangChain4jToolsHeaderNameBuilder
langchain4jTools() {
+ return
LangChain4jToolsEndpointBuilderFactory.LangChain4jToolsHeaderNameBuilder.INSTANCE;
+ }
/**
* Language (camel-language)
* Execute scripts in any of the languages supported by Camel.
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java
index 19dc8bbec74c..f5bcb6644e06 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java
@@ -504,6 +504,58 @@ public interface LangChain4jAgentEndpointBuilderFactory {
public String langChain4jAgentExcludeMcpServers() {
return "CamelLangChain4jAgentExcludeMcpServers";
}
+ /**
+ * The Finish Reason.
+ *
+ * The option is a: {@code dev.langchain4j.model.output.FinishReason}
+ * type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code LangChain4jAgentFinishReason}.
+ */
+ public String langChain4jAgentFinishReason() {
+ return "CamelLangChain4jAgentFinishReason";
+ }
+ /**
+ * The Input Token Count.
+ *
+ * The option is a: {@code int} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code
+ * LangChain4jAgentInputTokenCount}.
+ */
+ public String langChain4jAgentInputTokenCount() {
+ return "CamelLangChain4jAgentInputTokenCount";
+ }
+ /**
+ * The Output Token Count.
+ *
+ * The option is a: {@code int} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code
+ * LangChain4jAgentOutputTokenCount}.
+ */
+ public String langChain4jAgentOutputTokenCount() {
+ return "CamelLangChain4jAgentOutputTokenCount";
+ }
+ /**
+ * The Total Token Count.
+ *
+ * The option is a: {@code int} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code
+ * LangChain4jAgentTotalTokenCount}.
+ */
+ public String langChain4jAgentTotalTokenCount() {
+ return "CamelLangChain4jAgentTotalTokenCount";
+ }
}
static LangChain4jAgentEndpointBuilder endpointBuilder(String
componentName, String path) {
class LangChain4jAgentEndpointBuilderImpl extends
AbstractEndpointBuilder implements LangChain4jAgentEndpointBuilder,
AdvancedLangChain4jAgentEndpointBuilder {
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jChatEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jChatEndpointBuilderFactory.java
index ac2e93dad51e..6a3aa17f05de 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jChatEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jChatEndpointBuilderFactory.java
@@ -262,6 +262,58 @@ public interface LangChain4jChatEndpointBuilderFactory {
public String langChain4jChatAugmentedData() {
return "CamelLangChain4jChatAugmentedData";
}
+ /**
+ * The Finish Reason.
+ *
+ * The option is a: {@code dev.langchain4j.model.output.FinishReason}
+ * type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code LangChain4jChatFinishReason}.
+ */
+ public String langChain4jChatFinishReason() {
+ return "CamelLangChain4jChatFinishReason";
+ }
+ /**
+ * The Input Token Count.
+ *
+ * The option is a: {@code int} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code
+ * LangChain4jChatInputTokenCount}.
+ */
+ public String langChain4jChatInputTokenCount() {
+ return "CamelLangChain4jChatInputTokenCount";
+ }
+ /**
+ * The Output Token Count.
+ *
+ * The option is a: {@code int} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code
+ * LangChain4jChatOutputTokenCount}.
+ */
+ public String langChain4jChatOutputTokenCount() {
+ return "CamelLangChain4jChatOutputTokenCount";
+ }
+ /**
+ * The Total Token Count.
+ *
+ * The option is a: {@code int} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code
+ * LangChain4jChatTotalTokenCount}.
+ */
+ public String langChain4jChatTotalTokenCount() {
+ return "CamelLangChain4jChatTotalTokenCount";
+ }
}
static LangChain4jChatEndpointBuilder endpointBuilder(String
componentName, String path) {
class LangChain4jChatEndpointBuilderImpl extends
AbstractEndpointBuilder implements LangChain4jChatEndpointBuilder,
AdvancedLangChain4jChatEndpointBuilder {
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
index 969d5c8d49bd..f53dd7e54b32 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
@@ -546,6 +546,19 @@ public interface LangChain4jToolsEndpointBuilderFactory {
}
public interface LangChain4jToolsBuilders {
+ /**
+ * LangChain4j Tools (camel-langchain4j-tools)
+ * LangChain4j Tools and Function Calling Features
+ *
+ * Category: ai
+ * Since: 4.8
+ * Maven coordinates: org.apache.camel:camel-langchain4j-tools
+ *
+ * @return the dsl builder for the headers' name.
+ */
+ default LangChain4jToolsHeaderNameBuilder langchain4jTools() {
+ return LangChain4jToolsHeaderNameBuilder.INSTANCE;
+ }
/**
* LangChain4j Tools (camel-langchain4j-tools)
* LangChain4j Tools and Function Calling Features
@@ -588,6 +601,69 @@ public interface LangChain4jToolsEndpointBuilderFactory {
}
}
+ /**
+ * The builder of headers' name for the LangChain4j Tools component.
+ */
+ public static class LangChain4jToolsHeaderNameBuilder {
+ /**
+ * The internal instance of the builder used to access to all the
+ * methods representing the name of headers.
+ */
+ public static final LangChain4jToolsHeaderNameBuilder INSTANCE = new
LangChain4jToolsHeaderNameBuilder();
+
+ /**
+ * The Finish Reason.
+ *
+ * The option is a: {@code dev.langchain4j.model.output.FinishReason}
+ * type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code LangChain4jToolsFinishReason}.
+ */
+ public String langChain4jToolsFinishReason() {
+ return "CamelLangChain4jToolsFinishReason";
+ }
+ /**
+ * The Input Token Count.
+ *
+ * The option is a: {@code int} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code
+ * LangChain4jToolsInputTokenCount}.
+ */
+ public String langChain4jToolsInputTokenCount() {
+ return "CamelLangChain4jToolsInputTokenCount";
+ }
+ /**
+ * The Output Token Count.
+ *
+ * The option is a: {@code int} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code
+ * LangChain4jToolsOutputTokenCount}.
+ */
+ public String langChain4jToolsOutputTokenCount() {
+ return "CamelLangChain4jToolsOutputTokenCount";
+ }
+ /**
+ * The Total Token Count.
+ *
+ * The option is a: {@code int} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code
+ * LangChain4jToolsTotalTokenCount}.
+ */
+ public String langChain4jToolsTotalTokenCount() {
+ return "CamelLangChain4jToolsTotalTokenCount";
+ }
+ }
static LangChain4jToolsEndpointBuilder endpointBuilder(String
componentName, String path) {
class LangChain4jToolsEndpointBuilderImpl extends
AbstractEndpointBuilder implements LangChain4jToolsEndpointBuilder,
AdvancedLangChain4jToolsEndpointBuilder {
public LangChain4jToolsEndpointBuilderImpl(String path) {