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 ee081b517b Fixes #9237. Add test coverage for OpenAI route-based tools
via ai-tool
ee081b517b is described below
commit ee081b517bddb6b6e4ce50bfd7894e502143b8fa
Author: James Netherton <[email protected]>
AuthorDate: Fri Sep 25 15:58:19 2026 +0100
Fixes #9237. Add test coverage for OpenAI route-based tools via ai-tool
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
integration-tests/openai/pom.xml | 17 ++++++++++
.../component/openai/it/OpenaiResource.java | 20 ++++++++++++
.../quarkus/component/openai/it/OpenaiRoutes.java | 23 ++++++++++++++
.../component/openai/it/OpenaiChatTest.java | 30 ++++++++++++++++++
...irect-d4e5f6a7-0000-0000-0000-000000000003.json | 34 ++++++++++++++++++++
...step1-d4e5f6a7-0000-0000-0000-000000000001.json | 37 ++++++++++++++++++++++
...step2-d4e5f6a7-0000-0000-0000-000000000002.json | 31 ++++++++++++++++++
7 files changed, 192 insertions(+)
diff --git a/integration-tests/openai/pom.xml b/integration-tests/openai/pom.xml
index 6bbf67069d..05c2fee8b9 100644
--- a/integration-tests/openai/pom.xml
+++ b/integration-tests/openai/pom.xml
@@ -31,6 +31,10 @@
<description>Integration tests for Camel Quarkus OpenAI
extension</description>
<dependencies>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-ai-tool</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-direct</artifactId>
@@ -120,6 +124,19 @@
</activation>
<dependencies>
<!-- The following dependencies guarantee that this module is
built after them. You can update them by running `mvn process-resources
-Pformat -N` from the source tree root directory -->
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-ai-tool-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-direct-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 f1a7409323..4cbe16b732 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
@@ -123,6 +123,26 @@ public class OpenaiResource {
return producerTemplate.requestBody("direct:chatAgentic",
chatMessageContent, String.class);
}
+ @Path("/chat/routeTools")
+ @POST
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Map<String, Object> chatRouteTools(@QueryParam("returnDirect")
boolean returnDirect, String chatMessageContent) {
+ String endpointUri = returnDirect ?
"direct:chatRouteToolsReturnDirect" : "direct:chatRouteTools";
+ Exchange exchange = producerTemplate.request(endpointUri,
+ e -> e.getMessage().setBody(chatMessageContent));
+ if (exchange.getException() != null) {
+ throw new RuntimeException(exchange.getException());
+ }
+
+ Message message = exchange.getMessage();
+ Map<String, Object> result = new HashMap<>();
+ result.put("body", message.getBody(String.class));
+ result.put("toolCalls",
message.getHeader(OpenAIConstants.MCP_TOOL_CALLS, List.class));
+ result.put("returnDirect",
message.getHeader(OpenAIConstants.MCP_RETURN_DIRECT, false, Boolean.class));
+ return result;
+ }
+
@Path("/audio/transcription")
@POST
@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 26672ddce0..dab8f1e0f4 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
@@ -96,6 +96,29 @@ public class OpenaiRoutes extends RouteBuilder {
+ "&maxToolIterations=3")
.log("Agentic response: ${body}");
+ from("direct:chatRouteTools")
+
.to("openai:chat-completion?autoToolExecution=true&tags=weather&maxToolIterations=3")
+ .log("Route tools response: ${body}");
+
+ from("direct:chatRouteToolsReturnDirect")
+
.to("openai:chat-completion?autoToolExecution=true&tags=orders&maxToolIterations=3")
+ .log("Route tools return direct response: ${body}");
+
+ from("ai-tool:get_weather?tags=weather"
+ + "&description=Get the current weather for a city"
+ + "¶meter.city=string"
+ + "¶meter.city.description=The city name"
+ + "¶meter.city.required=true")
+ .setBody(simple("Sunny, 22 degrees celsius in
${header.city}"));
+
+ from("ai-tool:get_order_status?tags=orders"
+ + "&description=Get the status of an order"
+ + "¶meter.orderId=string"
+ + "¶meter.orderId.description=The order identifier"
+ + "¶meter.orderId.required=true"
+ + "&returnDirect=true")
+ .setBody(simple("Order ${header.orderId} has been shipped"));
+
from("direct:audioTranscription")
.to("openai:audio-transcription?audioModel=whisper-1")
.log("Audio transcription response: ${body}");
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 22eb6b71a5..ab7905036c 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
@@ -43,7 +43,9 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
+import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
+import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -257,4 +259,32 @@ class OpenaiChatTest {
containsStringIgnoringCase("sum"));
}
+ @Test
+ void chatWithRouteTools() {
+ RestAssured.given()
+ .contentType(ContentType.TEXT)
+ .body("What is the weather in London? Use the get_weather
tool.")
+ .post("/openai/chat/routeTools")
+ .then()
+ .statusCode(200)
+ .body(
+ "body", containsStringIgnoringCase("sunny"),
+ "toolCalls", contains("get_weather"),
+ "returnDirect", equalTo(false));
+ }
+
+ @Test
+ void chatWithRouteToolsReturnDirect() {
+ RestAssured.given()
+ .queryParam("returnDirect", true)
+ .contentType(ContentType.TEXT)
+ .body("What is the status of order 12345? Use the
get_order_status tool.")
+ .post("/openai/chat/routeTools")
+ .then()
+ .statusCode(200)
+ .body(
+ "body", equalTo("Order 12345 has been shipped"),
+ "toolCalls", contains("get_order_status"),
+ "returnDirect", equalTo(true));
+ }
}
diff --git
a/integration-tests/openai/src/test/resources/mappings/chat_completions-route-tools-return-direct-d4e5f6a7-0000-0000-0000-000000000003.json
b/integration-tests/openai/src/test/resources/mappings/chat_completions-route-tools-return-direct-d4e5f6a7-0000-0000-0000-000000000003.json
new file mode 100644
index 0000000000..17c1fb3a57
--- /dev/null
+++
b/integration-tests/openai/src/test/resources/mappings/chat_completions-route-tools-return-direct-d4e5f6a7-0000-0000-0000-000000000003.json
@@ -0,0 +1,34 @@
+{
+ "id" : "d4e5f6a7-0000-0000-0000-000000000003",
+ "name" : "chat_completions_route_tools_return_direct",
+ "request" : {
+ "url" : "/chat/completions",
+ "method" : "POST",
+ "bodyPatterns" : [
+ {
+ "equalToJson" : "{\"messages\":[{\"content\":\"What is the status of
order 12345? Use the get_order_status
tool.\",\"role\":\"user\"}],\"model\":\"gpt-5\"}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : true
+ },
+ {
+ "matchesJsonPath" : "$.tools[?(@.function.name == 'get_order_status')]"
+ },
+ {
+ "matchesJsonPath" : {
+ "expression" : "$.tools[?(@.function.name == 'get_weather')]",
+ "absent" : true
+ }
+ }
+ ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\n \"id\": \"chatcmpl-route-tools-3\",\n \"object\":
\"chat.completion\",\n \"created\": 1770382702,\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_route-tools-3\",\n
\"type\": \"function\",\n \"function\": {\n \"name\":
\"get_order_status\",\n \"arg [...]
+ "headers" : {
+ "Content-Type" : "application/json"
+ }
+ },
+ "uuid" : "d4e5f6a7-0000-0000-0000-000000000003",
+ "persistent" : true,
+ "insertionIndex" : 302
+}
diff --git
a/integration-tests/openai/src/test/resources/mappings/chat_completions-route-tools-step1-d4e5f6a7-0000-0000-0000-000000000001.json
b/integration-tests/openai/src/test/resources/mappings/chat_completions-route-tools-step1-d4e5f6a7-0000-0000-0000-000000000001.json
new file mode 100644
index 0000000000..193697b549
--- /dev/null
+++
b/integration-tests/openai/src/test/resources/mappings/chat_completions-route-tools-step1-d4e5f6a7-0000-0000-0000-000000000001.json
@@ -0,0 +1,37 @@
+{
+ "id" : "d4e5f6a7-0000-0000-0000-000000000001",
+ "name" : "chat_completions_route_tools_step1",
+ "scenarioName" : "route-tools-weather",
+ "requiredScenarioState" : "Started",
+ "newScenarioState" : "tool-executed",
+ "request" : {
+ "url" : "/chat/completions",
+ "method" : "POST",
+ "bodyPatterns" : [
+ {
+ "equalToJson" : "{\"messages\":[{\"content\":\"What is the weather in
London? Use the get_weather tool.\",\"role\":\"user\"}],\"model\":\"gpt-5\"}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : true
+ },
+ {
+ "matchesJsonPath" : "$.tools[?(@.function.name == 'get_weather')]"
+ },
+ {
+ "matchesJsonPath" : {
+ "expression" : "$.tools[?(@.function.name == 'get_order_status')]",
+ "absent" : true
+ }
+ }
+ ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\n \"id\": \"chatcmpl-route-tools-1\",\n \"object\":
\"chat.completion\",\n \"created\": 1770382700,\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_route-tools-1\",\n
\"type\": \"function\",\n \"function\": {\n \"name\":
\"get_weather\",\n \"argument [...]
+ "headers" : {
+ "Content-Type" : "application/json"
+ }
+ },
+ "uuid" : "d4e5f6a7-0000-0000-0000-000000000001",
+ "persistent" : true,
+ "insertionIndex" : 300
+}
diff --git
a/integration-tests/openai/src/test/resources/mappings/chat_completions-route-tools-step2-d4e5f6a7-0000-0000-0000-000000000002.json
b/integration-tests/openai/src/test/resources/mappings/chat_completions-route-tools-step2-d4e5f6a7-0000-0000-0000-000000000002.json
new file mode 100644
index 0000000000..12dc416977
--- /dev/null
+++
b/integration-tests/openai/src/test/resources/mappings/chat_completions-route-tools-step2-d4e5f6a7-0000-0000-0000-000000000002.json
@@ -0,0 +1,31 @@
+{
+ "id" : "d4e5f6a7-0000-0000-0000-000000000002",
+ "name" : "chat_completions_route_tools_step2",
+ "scenarioName" : "route-tools-weather",
+ "requiredScenarioState" : "tool-executed",
+ "newScenarioState" : "Started",
+ "request" : {
+ "url" : "/chat/completions",
+ "method" : "POST",
+ "bodyPatterns" : [
+ {
+ "equalToJson" : "{\"messages\":[{\"content\":\"What is the weather in
London? Use the get_weather tool.\",\"role\":\"user\"}],\"model\":\"gpt-5\"}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : true
+ },
+ {
+ "matchesJsonPath" : "$.messages[?(@.content == 'Sunny, 22 degrees
celsius in London')]"
+ }
+ ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\n \"id\": \"chatcmpl-route-tools-2\",\n \"object\":
\"chat.completion\",\n \"created\": 1770382701,\n \"model\": \"gpt-5\",\n
\"choices\": [\n {\n \"index\": 0,\n \"message\": {\n
\"role\": \"assistant\",\n \"content\": \"The weather in London is sunny
with a temperature of 22 degrees celsius.\",\n \"refusal\": null\n
},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n
\"prompt_tokens\": 60,\n \"completion [...]
+ "headers" : {
+ "Content-Type" : "application/json"
+ }
+ },
+ "uuid" : "d4e5f6a7-0000-0000-0000-000000000002",
+ "persistent" : true,
+ "insertionIndex" : 301
+}