This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch feature/CAMEL-23860-token-usage-headers in repository https://gitbox.apache.org/repos/asf/camel.git
commit 5f59c3785b16cb3846d8d82904d5974ddae46402 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Jul 1 09:56:57 2026 +0200 CAMEL-23860: Add token usage and finish reason headers to langchain4j chat and tools Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../camel/catalog/components/langchain4j-chat.json | 6 +- .../catalog/components/langchain4j-tools.json | 6 ++ .../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 +++++++++++++--- .../builder/endpoint/EndpointHeaderBuilders.java | 13 ++++ .../dsl/LangChain4jChatEndpointBuilderFactory.java | 52 +++++++++++++++ .../LangChain4jToolsEndpointBuilderFactory.java | 76 ++++++++++++++++++++++ 12 files changed, 260 insertions(+), 20 deletions(-) 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-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/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/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) {
