This is an automated email from the ASF dual-hosted git repository.

Croway 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 1fb80364e9d0 camel-openai: handle null finish_reason from 
OpenAI-compatible servers
1fb80364e9d0 is described below

commit 1fb80364e9d09a50ac7c8662d22ede7016787415
Author: Croway <[email protected]>
AuthorDate: Wed Apr 29 13:54:15 2026 +0200

    camel-openai: handle null finish_reason from OpenAI-compatible servers
    
    Some OpenAI-compatible servers (e.g. mlx-lm) return null finish_reason
    when tools are defined but the model responds without calling any.
    Use the SDK's null-safe _finishReason() accessor instead of the
    throwing finishReason() to avoid NPE.
---
 .../camel/component/openai/OpenAIProducer.java     | 23 ++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git 
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
 
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
index 1ff143d51e13..b2366ffa5a0f 100644
--- 
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
+++ 
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
@@ -30,6 +30,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.openai.core.JsonField;
 import com.openai.core.JsonValue;
 import com.openai.core.http.StreamResponse;
 import com.openai.models.ResponseFormatJsonSchema;
@@ -378,7 +379,7 @@ public class OpenAIProducer extends DefaultAsyncProducer {
             exchange.setProperty(OpenAIConstants.RESPONSE, response);
         }
 
-        if 
(response.choices().get(0).finishReason().equals(ChatCompletion.Choice.FinishReason.TOOL_CALLS))
 {
+        if (isToolCallsFinishReason(response.choices().get(0))) {
             
exchange.getMessage().setBody(response.choices().get(0).message().toolCalls());
         } else {
             String content = 
response.choices().get(0).message().content().orElse("");
@@ -409,10 +410,10 @@ public class OpenAIProducer extends DefaultAsyncProducer {
             ChatCompletion response = 
getEndpoint().getClient().chat().completions().create(paramsBuilder.build());
             ChatCompletion.Choice choice = response.choices().get(0);
 
-            if 
(!choice.finishReason().equals(ChatCompletion.Choice.FinishReason.TOOL_CALLS)) {
+            if (!isToolCallsFinishReason(choice)) {
                 // Final LLM response
                 LOG.debug("Agentic loop completed after {} iterations, finish 
reason: {}", iteration,
-                        choice.finishReason());
+                        getFinishReasonString(choice));
                 String content = choice.message().content().orElse("");
                 exchange.getMessage().setBody(content);
                 setResponseHeaders(exchange.getMessage(), response);
@@ -566,13 +567,27 @@ public class OpenAIProducer extends DefaultAsyncProducer {
 
     }
 
+    private static boolean isToolCallsFinishReason(ChatCompletion.Choice 
choice) {
+        JsonField<ChatCompletion.Choice.FinishReason> field = 
choice._finishReason();
+        return field.asKnown()
+                .map(r -> 
r.equals(ChatCompletion.Choice.FinishReason.TOOL_CALLS))
+                .orElse(false);
+    }
+
+    private static String getFinishReasonString(ChatCompletion.Choice choice) {
+        JsonField<ChatCompletion.Choice.FinishReason> field = 
choice._finishReason();
+        return field.asKnown()
+                .map(ChatCompletion.Choice.FinishReason::toString)
+                .orElse("stop");
+    }
+
     private void setResponseHeaders(Message message, ChatCompletion response) {
         message.setHeader(OpenAIConstants.RESPONSE_ID, response.id());
         message.setHeader(OpenAIConstants.RESPONSE_MODEL, response.model());
 
         if (!response.choices().isEmpty()) {
             ChatCompletion.Choice choice = response.choices().get(0);
-            message.setHeader(OpenAIConstants.FINISH_REASON, 
choice.finishReason().toString());
+            message.setHeader(OpenAIConstants.FINISH_REASON, 
getFinishReasonString(choice));
         }
 
         if (response.usage().isPresent()) {

Reply via email to