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

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git


The following commit(s) were added to refs/heads/main by this push:
     new 1bd64ee87f Fixes #8858. Add test coverage for openai tool calls, 
agentic loop, and audio transcription
1bd64ee87f is described below

commit 1bd64ee87f567f60e960a806e7a6a6c21f7af70e
Author: Jomin Mathew <[email protected]>
AuthorDate: Wed Aug 19 07:06:33 2026 +0100

    Fixes #8858. Add test coverage for openai tool calls, agentic loop, and 
audio transcription
    
    Add integration tests for recent camel-openai enhancements:
      - Tool call detection: verify component surfaces tool call objects
        when finish_reason is tool_calls
      - Agentic loop with MCP: full round-trip using quarkus-mcp-server
        to host an add_numbers tool, WireMock scenarios for the two-step
        model interaction
      - Audio transcription: send a WAV file to audio-transcription endpoint
        and verify the transcribed text
    
      All tests include native mode IT classes.
    
    Co-authored-by: jomin mathew <>
---
 integration-tests/openai/pom.xml                   |  17 +++++
 .../component/openai/it/OpenaiResource.java        |  24 +++++++
 .../quarkus/component/openai/it/OpenaiRoutes.java  |  15 ++++
 .../quarkus/component/openai/it/OpenaiTools.java   |  30 ++++++++
 .../quarkus/component/openai/it/OpenaiAudioIT.java |  24 +++++++
 .../component/openai/it/OpenaiAudioTest.java       |  79 +++++++++++++++++++++
 .../component/openai/it/OpenaiChatTest.java        |  26 +++++++
 .../openai/src/test/resources/audio/test-audio.wav | Bin 0 -> 44 bytes
 ...tions-b2c3d4e5-0000-0000-0000-000000000001.json |  18 +++++
 ...step1-c3d4e5f6-0000-0000-0000-000000000001.json |  26 +++++++
 ...step2-c3d4e5f6-0000-0000-0000-000000000002.json |  28 ++++++++
 ...calls-a1b2c3d4-0000-0000-0000-000000000001.json |  23 ++++++
 pom.xml                                            |   1 +
 13 files changed, 311 insertions(+)

diff --git a/integration-tests/openai/pom.xml b/integration-tests/openai/pom.xml
index 2f809d5c00..1f6543125c 100644
--- a/integration-tests/openai/pom.xml
+++ b/integration-tests/openai/pom.xml
@@ -39,6 +39,10 @@
             <groupId>org.apache.camel.quarkus</groupId>
             <artifactId>camel-quarkus-file</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-mcp-server</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
             <artifactId>camel-quarkus-openai</artifactId>
@@ -142,6 +146,19 @@
                         </exclusion>
                     </exclusions>
                 </dependency>
+                <dependency>
+                    <groupId>org.apache.camel.quarkus</groupId>
+                    
<artifactId>camel-quarkus-mcp-server-deployment</artifactId>
+                    <version>${project.version}</version>
+                    <type>pom</type>
+                    <scope>test</scope>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>*</groupId>
+                            <artifactId>*</artifactId>
+                        </exclusion>
+                    </exclusions>
+                </dependency>
                 <dependency>
                     <groupId>org.apache.camel.quarkus</groupId>
                     <artifactId>camel-quarkus-openai-deployment</artifactId>
diff --git 
a/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiResource.java
 
b/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiResource.java
index adcb368036..f1a7409323 100644
--- 
a/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiResource.java
+++ 
b/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiResource.java
@@ -107,6 +107,30 @@ public class OpenaiResource {
         return 
producerTemplate.requestBody("direct:chatStructuredOutputWithClass", 
chatMessageContent, String.class);
     }
 
+    @Path("/chat/toolCalls")
+    @POST
+    @Consumes(MediaType.TEXT_PLAIN)
+    @Produces(MediaType.TEXT_PLAIN)
+    public String chatToolCalls(String chatMessageContent) {
+        return producerTemplate.requestBody("direct:chatToolCalls", 
chatMessageContent, String.class);
+    }
+
+    @Path("/chat/agentic")
+    @POST
+    @Consumes(MediaType.TEXT_PLAIN)
+    @Produces(MediaType.TEXT_PLAIN)
+    public String chatAgentic(String chatMessageContent) {
+        return producerTemplate.requestBody("direct:chatAgentic", 
chatMessageContent, String.class);
+    }
+
+    @Path("/audio/transcription")
+    @POST
+    @Consumes(MediaType.TEXT_PLAIN)
+    @Produces(MediaType.TEXT_PLAIN)
+    public String audioTranscription(String audioFilePath) {
+        return producerTemplate.requestBody("direct:audioTranscription", new 
File(audioFilePath), String.class);
+    }
+
     @Path("/chat/results")
     @GET
     @Consumes(MediaType.TEXT_PLAIN)
diff --git 
a/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiRoutes.java
 
b/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiRoutes.java
index e510fec295..26672ddce0 100644
--- 
a/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiRoutes.java
+++ 
b/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiRoutes.java
@@ -85,6 +85,21 @@ public class OpenaiRoutes extends RouteBuilder {
                 .to("openai:chat-completion?conversationMemory=true")
                 .log("Chat response 2: ${body}");
 
+        from("direct:chatToolCalls")
+                .to("openai:chat-completion")
+                .log("Tool calls response: ${body}");
+
+        from("direct:chatAgentic")
+                .to("openai:chat-completion?autoToolExecution=true"
+                        + "&mcpServer.tools.transportType=streamableHttp"
+                        + 
"&mcpServer.tools.url=http://localhost:{{quarkus.http.test-port:8081}}";
+                        + "&maxToolIterations=3")
+                .log("Agentic response: ${body}");
+
+        from("direct:audioTranscription")
+                .to("openai:audio-transcription?audioModel=whisper-1")
+                .log("Audio transcription response: ${body}");
+
         from("direct:embed")
                 .to("openai-embeddings:embeddings");
 
diff --git 
a/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiTools.java
 
b/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiTools.java
new file mode 100644
index 0000000000..dc699ba8d2
--- /dev/null
+++ 
b/integration-tests/openai/src/main/java/org/apache/camel/quarkus/component/openai/it/OpenaiTools.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.openai.it;
+
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+
+public class OpenaiTools {
+
+    @Tool(name = "add_numbers", description = "Add two numbers")
+    String add(
+            @ToolArg(description = "First addend") long a,
+            @ToolArg(description = "Second addend") long b) {
+        return String.valueOf(a + b);
+    }
+}
diff --git 
a/integration-tests/openai/src/test/java/org/apache/camel/quarkus/component/openai/it/OpenaiAudioIT.java
 
b/integration-tests/openai/src/test/java/org/apache/camel/quarkus/component/openai/it/OpenaiAudioIT.java
new file mode 100644
index 0000000000..3c9547a0be
--- /dev/null
+++ 
b/integration-tests/openai/src/test/java/org/apache/camel/quarkus/component/openai/it/OpenaiAudioIT.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.openai.it;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class OpenaiAudioIT extends OpenaiAudioTest {
+
+}
diff --git 
a/integration-tests/openai/src/test/java/org/apache/camel/quarkus/component/openai/it/OpenaiAudioTest.java
 
b/integration-tests/openai/src/test/java/org/apache/camel/quarkus/component/openai/it/OpenaiAudioTest.java
new file mode 100644
index 0000000000..e5b22e71c6
--- /dev/null
+++ 
b/integration-tests/openai/src/test/java/org/apache/camel/quarkus/component/openai/it/OpenaiAudioTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.openai.it;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import org.apache.camel.util.FileUtil;
+import org.jboss.logging.Logger;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+
+import static org.hamcrest.Matchers.containsStringIgnoringCase;
+
+@QuarkusTestResource(OpenaiTestResource.class)
+@QuarkusTest
+class OpenaiAudioTest {
+
+    private static final Logger LOG = Logger.getLogger(OpenaiAudioTest.class);
+
+    @BeforeEach
+    void logTestName(TestInfo testInfo) {
+        LOG.info(String.format("Running OpenaiAudioTest test %s", 
testInfo.getDisplayName()));
+    }
+
+    @Test
+    void audioTranscription() throws IOException {
+        Path audioFile = Paths.get("target/test-audio.wav");
+
+        try (InputStream stream = 
OpenaiAudioTest.class.getResourceAsStream("/audio/test-audio.wav")) {
+            if (stream == null) {
+                throw new IllegalStateException("Failed loading 
test-audio.wav");
+            }
+
+            try (OutputStream out = new FileOutputStream(audioFile.toFile())) {
+                stream.transferTo(out);
+            }
+
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(audioFile.toAbsolutePath().toString())
+                    .post("/openai/audio/transcription")
+                    .then()
+                    .statusCode(200)
+                    .body(containsStringIgnoringCase("hello"));
+        } finally {
+            if (FileUtil.isWindows()) {
+                audioFile.toFile().deleteOnExit();
+            } else {
+                Files.deleteIfExists(audioFile);
+            }
+        }
+    }
+
+}
diff --git 
a/integration-tests/openai/src/test/java/org/apache/camel/quarkus/component/openai/it/OpenaiChatTest.java
 
b/integration-tests/openai/src/test/java/org/apache/camel/quarkus/component/openai/it/OpenaiChatTest.java
index 06eda3262d..22eb6b71a5 100644
--- 
a/integration-tests/openai/src/test/java/org/apache/camel/quarkus/component/openai/it/OpenaiChatTest.java
+++ 
b/integration-tests/openai/src/test/java/org/apache/camel/quarkus/component/openai/it/OpenaiChatTest.java
@@ -231,4 +231,30 @@ class OpenaiChatTest {
                         "price", greaterThan(0.0F));
     }
 
+    @Test
+    void chatToolCalls() {
+        RestAssured.given()
+                .contentType(ContentType.TEXT)
+                .body("What is 2 + 3?")
+                .post("/openai/chat/toolCalls")
+                .then()
+                .statusCode(200)
+                .body(
+                        containsStringIgnoringCase("add"),
+                        containsStringIgnoringCase("call_abc123"));
+    }
+
+    @Test
+    void chatAgentic() {
+        RestAssured.given()
+                .contentType(ContentType.TEXT)
+                .body("What is 17 + 25?")
+                .post("/openai/chat/agentic")
+                .then()
+                .statusCode(200)
+                .body(
+                        containsStringIgnoringCase("42"),
+                        containsStringIgnoringCase("sum"));
+    }
+
 }
diff --git a/integration-tests/openai/src/test/resources/audio/test-audio.wav 
b/integration-tests/openai/src/test/resources/audio/test-audio.wav
new file mode 100644
index 0000000000..d26b87bdbb
Binary files /dev/null and 
b/integration-tests/openai/src/test/resources/audio/test-audio.wav differ
diff --git 
a/integration-tests/openai/src/test/resources/mappings/audio_transcriptions-b2c3d4e5-0000-0000-0000-000000000001.json
 
b/integration-tests/openai/src/test/resources/mappings/audio_transcriptions-b2c3d4e5-0000-0000-0000-000000000001.json
new file mode 100644
index 0000000000..41b034cf9b
--- /dev/null
+++ 
b/integration-tests/openai/src/test/resources/mappings/audio_transcriptions-b2c3d4e5-0000-0000-0000-000000000001.json
@@ -0,0 +1,18 @@
+{
+  "id" : "b2c3d4e5-0000-0000-0000-000000000001",
+  "name" : "audio_transcriptions",
+  "request" : {
+    "url" : "/audio/transcriptions",
+    "method" : "POST"
+  },
+  "response" : {
+    "status" : 200,
+    "body" : "{\n  \"text\": \"Hello, this is a test audio 
transcription.\"\n}\n",
+    "headers" : {
+      "Content-Type" : "application/json"
+    }
+  },
+  "uuid" : "b2c3d4e5-0000-0000-0000-000000000001",
+  "persistent" : true,
+  "insertionIndex" : 200
+}
diff --git 
a/integration-tests/openai/src/test/resources/mappings/chat_completions-agentic-step1-c3d4e5f6-0000-0000-0000-000000000001.json
 
b/integration-tests/openai/src/test/resources/mappings/chat_completions-agentic-step1-c3d4e5f6-0000-0000-0000-000000000001.json
new file mode 100644
index 0000000000..f568cbbf62
--- /dev/null
+++ 
b/integration-tests/openai/src/test/resources/mappings/chat_completions-agentic-step1-c3d4e5f6-0000-0000-0000-000000000001.json
@@ -0,0 +1,26 @@
+{
+  "id" : "c3d4e5f6-0000-0000-0000-000000000001",
+  "name" : "chat_completions_agentic_step1",
+  "scenarioName" : "agentic-add-numbers",
+  "requiredScenarioState" : "Started",
+  "newScenarioState" : "tool-executed",
+  "request" : {
+    "url" : "/chat/completions",
+    "method" : "POST",
+    "bodyPatterns" : [ {
+      "equalToJson" : "{\"messages\":[{\"content\":\"What is 17 + 
25?\",\"role\":\"user\"}],\"model\":\"gpt-5\"}",
+      "ignoreArrayOrder" : true,
+      "ignoreExtraElements" : true
+    } ]
+  },
+  "response" : {
+    "status" : 200,
+    "body" : "{\n  \"id\": \"chatcmpl-agentic-1\",\n  \"object\": 
\"chat.completion\",\n  \"created\": 1770382622,\n  \"model\": \"gpt-5\",\n  
\"choices\": [\n    {\n      \"index\": 0,\n      \"message\": {\n        
\"role\": \"assistant\",\n        \"content\": null,\n        \"tool_calls\": 
[\n          {\n            \"id\": \"call_agentic_1\",\n            \"type\": 
\"function\",\n            \"function\": {\n              \"name\": 
\"add_numbers\",\n              \"arguments\": \"{ [...]
+    "headers" : {
+      "Content-Type" : "application/json"
+    }
+  },
+  "uuid" : "c3d4e5f6-0000-0000-0000-000000000001",
+  "persistent" : true,
+  "insertionIndex" : 200
+}
diff --git 
a/integration-tests/openai/src/test/resources/mappings/chat_completions-agentic-step2-c3d4e5f6-0000-0000-0000-000000000002.json
 
b/integration-tests/openai/src/test/resources/mappings/chat_completions-agentic-step2-c3d4e5f6-0000-0000-0000-000000000002.json
new file mode 100644
index 0000000000..af64a62927
--- /dev/null
+++ 
b/integration-tests/openai/src/test/resources/mappings/chat_completions-agentic-step2-c3d4e5f6-0000-0000-0000-000000000002.json
@@ -0,0 +1,28 @@
+{
+  "id" : "c3d4e5f6-0000-0000-0000-000000000002",
+  "name" : "chat_completions_agentic_step2",
+  "scenarioName" : "agentic-add-numbers",
+  "requiredScenarioState" : "tool-executed",
+  "newScenarioState" : "Started",
+  "request" : {
+    "url" : "/chat/completions",
+    "method" : "POST",
+    "bodyPatterns" : [ {
+      "equalToJson" : "{\"messages\":[{\"content\":\"What is 17 + 
25?\",\"role\":\"user\"}],\"model\":\"gpt-5\"}",
+      "ignoreArrayOrder" : true,
+      "ignoreExtraElements" : true
+    }, {
+      "matchesJsonPath" : "$.messages[?(@.content == '42')]"
+    } ]
+  },
+  "response" : {
+    "status" : 200,
+    "body" : "{\n  \"id\": \"chatcmpl-agentic-2\",\n  \"object\": 
\"chat.completion\",\n  \"created\": 1770382623,\n  \"model\": \"gpt-5\",\n  
\"choices\": [\n    {\n      \"index\": 0,\n      \"message\": {\n        
\"role\": \"assistant\",\n        \"content\": \"The sum of 17 and 25 is 
42.\",\n        \"refusal\": null\n      },\n      \"finish_reason\": 
\"stop\"\n    }\n  ],\n  \"usage\": {\n    \"prompt_tokens\": 80,\n    
\"completion_tokens\": 15,\n    \"total_tokens\": 95\n  }\n}\n",
+    "headers" : {
+      "Content-Type" : "application/json"
+    }
+  },
+  "uuid" : "c3d4e5f6-0000-0000-0000-000000000002",
+  "persistent" : true,
+  "insertionIndex" : 201
+}
diff --git 
a/integration-tests/openai/src/test/resources/mappings/chat_completions-tool-calls-a1b2c3d4-0000-0000-0000-000000000001.json
 
b/integration-tests/openai/src/test/resources/mappings/chat_completions-tool-calls-a1b2c3d4-0000-0000-0000-000000000001.json
new file mode 100644
index 0000000000..235b0237f4
--- /dev/null
+++ 
b/integration-tests/openai/src/test/resources/mappings/chat_completions-tool-calls-a1b2c3d4-0000-0000-0000-000000000001.json
@@ -0,0 +1,23 @@
+{
+  "id" : "a1b2c3d4-0000-0000-0000-000000000001",
+  "name" : "chat_completions_tool_calls",
+  "request" : {
+    "url" : "/chat/completions",
+    "method" : "POST",
+    "bodyPatterns" : [ {
+      "equalToJson" : "{\"messages\":[{\"content\":\"What is 2 + 
3?\",\"role\":\"user\"}],\"model\":\"gpt-5\"}",
+      "ignoreArrayOrder" : true,
+      "ignoreExtraElements" : true
+    } ]
+  },
+  "response" : {
+    "status" : 200,
+    "body" : "{\n  \"id\": \"chatcmpl-tool-calls\",\n  \"object\": 
\"chat.completion\",\n  \"created\": 1770382622,\n  \"model\": 
\"gpt-4o-2025-08-07\",\n  \"choices\": [\n    {\n      \"index\": 0,\n      
\"message\": {\n        \"role\": \"assistant\",\n        \"content\": null,\n  
      \"tool_calls\": [\n          {\n            \"id\": \"call_abc123\",\n    
        \"type\": \"function\",\n            \"function\": {\n              
\"name\": \"add\",\n              \"arguments\": \ [...]
+    "headers" : {
+      "Content-Type" : "application/json"
+    }
+  },
+  "uuid" : "a1b2c3d4-0000-0000-0000-000000000001",
+  "persistent" : true,
+  "insertionIndex" : 100
+}
diff --git a/pom.xml b/pom.xml
index d1e8959087..c534006d40 100644
--- a/pom.xml
+++ b/pom.xml
@@ -599,6 +599,7 @@
                                     <exclude>**/*.proto</exclude>
                                     <exclude>**/*.spec</exclude>
                                     <exclude>**/*.txt</exclude>
+                                    <exclude>**/*.wav</exclude>
                                     <exclude>**/*.xquery</exclude>
                                     <exclude>**/.factorypath</exclude>
                                     <exclude>**/.pnp.js</exclude>

Reply via email to