diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionRequestHandler.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionRequestHandler.java index c42480f3aab8b..2a13278136953 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionRequestHandler.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionRequestHandler.java @@ -17,6 +17,7 @@ package org.apache.camel.test.infra.openai.mock; import java.io.IOException; +import java.io.InputStream; import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; @@ -43,7 +44,9 @@ public AudioTranscriptionRequestHandler(List expe public String handleRequest(HttpExchange exchange) throws IOException { try { // consume the request body - exchange.getRequestBody().readAllBytes(); + try (InputStream requestBody = exchange.getRequestBody()) { + requestBody.readAllBytes(); + } LOG.debug("Processing audio transcription request (call #{})", callIndex); if (expectations.isEmpty()) { diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/CloseableHttpClient.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/CloseableHttpClient.java new file mode 100644 index 0000000000000..53d3914c356ba --- /dev/null +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/CloseableHttpClient.java @@ -0,0 +1,34 @@ +/* + * 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.test.infra.openai.mock; + +import java.net.http.HttpClient; + +/** + * Wrapper that makes {@link HttpClient} usable in try-with-resources. On Java 21+ HttpClient implements AutoCloseable + * natively; the instanceof check future-proofs us for when the minimum JDK is raised. + */ +final class CloseableHttpClient implements AutoCloseable { + final HttpClient httpClient = HttpClient.newHttpClient(); + + @Override + public void close() throws Exception { + if (httpClient instanceof AutoCloseable closeable) { + closeable.close(); + } + } +} diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/EmbeddingRequestHandler.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/EmbeddingRequestHandler.java index 250a502a2681a..ae16bfd997c7b 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/EmbeddingRequestHandler.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/EmbeddingRequestHandler.java @@ -17,6 +17,7 @@ package org.apache.camel.test.infra.openai.mock; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -45,7 +46,10 @@ public EmbeddingRequestHandler(List expectations, ObjectMa public String handleRequest(HttpExchange exchange) throws IOException { try { - String requestBody = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); + String requestBody; + try (InputStream is = exchange.getRequestBody()) { + requestBody = new String(is.readAllBytes(), StandardCharsets.UTF_8); + } LOG.debug("Processing embedding request: {}", requestBody); JsonNode rootNode = objectMapper.readTree(requestBody); diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockConversationHistoryTest.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockConversationHistoryTest.java index 4bc3a9fab912d..9b6036d20c756 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockConversationHistoryTest.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockConversationHistoryTest.java @@ -17,7 +17,6 @@ package org.apache.camel.test.infra.openai.mock; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.concurrent.atomic.AtomicBoolean; @@ -48,35 +47,35 @@ public class OpenAIMockConversationHistoryTest { @Test public void testConversationHistoryAssertion() throws Exception { - HttpClient client = HttpClient.newHttpClient(); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + // Send request with conversation history + String requestBody = "{\"messages\": [" + + "{\"role\": \"user\", \"content\": \"Hi! Can you look up user 123 and tell me about our rental policies?\"}," + + + "{\"role\": \"assistant\", \"content\": \"Previous response\"}," + + "{\"role\": \"user\", \"content\": \"What's his preferred vehicle type?\"}" + + "]}"; - // Send request with conversation history - String requestBody = "{\"messages\": [" + - "{\"role\": \"user\", \"content\": \"Hi! Can you look up user 123 and tell me about our rental policies?\"}," - + - "{\"role\": \"assistant\", \"content\": \"Previous response\"}," + - "{\"role\": \"user\", \"content\": \"What's his preferred vehicle type?\"}" + - "]}"; + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(requestBody)) - .build(); + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - String responseBody = response.body(); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson = objectMapper.readTree(responseBody); - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson = objectMapper.readTree(responseBody); + JsonNode choice = responseJson.path("choices").get(0); + JsonNode message = choice.path("message"); - JsonNode choice = responseJson.path("choices").get(0); - JsonNode message = choice.path("message"); + assertEquals("assistant", message.path("role").asText()); + assertEquals("SUV", message.path("content").asText()); - assertEquals("assistant", message.path("role").asText()); - assertEquals("SUV", message.path("content").asText()); - - // Verify assertion was executed - assertTrue(secondAssertionExecuted.get(), "Second assertion should have been executed"); + // Verify assertion was executed + assertTrue(secondAssertionExecuted.get(), "Second assertion should have been executed"); + } } } diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockEmbeddingTest.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockEmbeddingTest.java index c72fb457f9ff0..e1eb17665d365 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockEmbeddingTest.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockEmbeddingTest.java @@ -17,7 +17,6 @@ package org.apache.camel.test.infra.openai.mock; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -52,171 +51,177 @@ public class OpenAIMockEmbeddingTest { @Test public void testExplicitEmbeddingVector() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"input\": \"What is Apache Camel?\", \"model\": \"text-embedding-ada-002\"}")) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - String responseBody = response.body(); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson = objectMapper.readTree(responseBody); - - assertEquals("list", responseJson.path("object").asText()); - assertEquals("camel-embedding", responseJson.path("model").asText()); - - JsonNode dataArray = responseJson.path("data"); - assertEquals(1, dataArray.size()); - - JsonNode embeddingData = dataArray.get(0); - assertEquals("embedding", embeddingData.path("object").asText()); - assertEquals(0, embeddingData.path("index").asInt()); - - JsonNode embedding = embeddingData.path("embedding"); - assertEquals(4, embedding.size()); - assertEquals(0.1, embedding.get(0).asDouble(), 0.001); - assertEquals(0.2, embedding.get(1).asDouble(), 0.001); - assertEquals(0.3, embedding.get(2).asDouble(), 0.001); - assertEquals(0.4, embedding.get(3).asDouble(), 0.001); - - // Check usage - JsonNode usage = responseJson.path("usage"); - assertTrue(usage.has("prompt_tokens")); - assertTrue(usage.has("total_tokens")); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"input\": \"What is Apache Camel?\", \"model\": \"text-embedding-ada-002\"}")) + .build(); + + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson = objectMapper.readTree(responseBody); + + assertEquals("list", responseJson.path("object").asText()); + assertEquals("camel-embedding", responseJson.path("model").asText()); + + JsonNode dataArray = responseJson.path("data"); + assertEquals(1, dataArray.size()); + + JsonNode embeddingData = dataArray.get(0); + assertEquals("embedding", embeddingData.path("object").asText()); + assertEquals(0, embeddingData.path("index").asInt()); + + JsonNode embedding = embeddingData.path("embedding"); + assertEquals(4, embedding.size()); + assertEquals(0.1, embedding.get(0).asDouble(), 0.001); + assertEquals(0.2, embedding.get(1).asDouble(), 0.001); + assertEquals(0.3, embedding.get(2).asDouble(), 0.001); + assertEquals(0.4, embedding.get(3).asDouble(), 0.001); + + // Check usage + JsonNode usage = responseJson.path("usage"); + assertTrue(usage.has("prompt_tokens")); + assertTrue(usage.has("total_tokens")); + } } @Test public void testAutoGeneratedEmbedding() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"input\": \"Hello world\", \"model\": \"text-embedding-ada-002\"}")) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - String responseBody = response.body(); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson = objectMapper.readTree(responseBody); - - assertEquals("list", responseJson.path("object").asText()); - - JsonNode embedding = responseJson.path("data").get(0).path("embedding"); - assertEquals(1536, embedding.size()); - - // All values should be between -1 and 1 - for (int i = 0; i < embedding.size(); i++) { - double value = embedding.get(i).asDouble(); - assertTrue(value >= -1.0 && value <= 1.0, - "Embedding value at index " + i + " should be between -1 and 1, but was " + value); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"input\": \"Hello world\", \"model\": \"text-embedding-ada-002\"}")) + .build(); + + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson = objectMapper.readTree(responseBody); + + assertEquals("list", responseJson.path("object").asText()); + + JsonNode embedding = responseJson.path("data").get(0).path("embedding"); + assertEquals(1536, embedding.size()); + + // All values should be between -1 and 1 + for (int i = 0; i < embedding.size(); i++) { + double value = embedding.get(i).asDouble(); + assertTrue(value >= -1.0 && value <= 1.0, + "Embedding value at index " + i + " should be between -1 and 1, but was " + value); + } } } @Test public void testArrayInputFormat() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - // OpenAI API also accepts array format for input - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"input\": [\"What is Apache Camel?\"], \"model\": \"text-embedding-ada-002\"}")) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - String responseBody = response.body(); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson = objectMapper.readTree(responseBody); - - assertEquals("list", responseJson.path("object").asText()); - - JsonNode embedding = responseJson.path("data").get(0).path("embedding"); - assertEquals(4, embedding.size()); - assertEquals(0.1, embedding.get(0).asDouble(), 0.001); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + // OpenAI API also accepts array format for input + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString( + "{\"input\": [\"What is Apache Camel?\"], \"model\": \"text-embedding-ada-002\"}")) + .build(); + + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson = objectMapper.readTree(responseBody); + + assertEquals("list", responseJson.path("object").asText()); + + JsonNode embedding = responseJson.path("data").get(0).path("embedding"); + assertEquals(4, embedding.size()); + assertEquals(0.1, embedding.get(0).asDouble(), 0.001); + } } @Test public void testDeterministicOutput() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - - // First request - HttpRequest request1 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"input\": \"Test input\", \"model\": \"text-embedding-ada-002\"}")) - .build(); - - HttpResponse response1 = client.send(request1, HttpResponse.BodyHandlers.ofString()); - - // Second request with same input - HttpRequest request2 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"input\": \"Test input\", \"model\": \"text-embedding-ada-002\"}")) - .build(); - - HttpResponse response2 = client.send(request2, HttpResponse.BodyHandlers.ofString()); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode json1 = objectMapper.readTree(response1.body()); - JsonNode json2 = objectMapper.readTree(response2.body()); - - JsonNode embedding1 = json1.path("data").get(0).path("embedding"); - JsonNode embedding2 = json2.path("data").get(0).path("embedding"); - - // Same input should produce same embedding - assertEquals(embedding1.size(), embedding2.size()); - for (int i = 0; i < embedding1.size(); i++) { - assertEquals(embedding1.get(i).asDouble(), embedding2.get(i).asDouble(), 0.0001, - "Embedding values should be identical for the same input"); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + // First request + HttpRequest request1 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"input\": \"Test input\", \"model\": \"text-embedding-ada-002\"}")) + .build(); + + HttpResponse response1 = hc.httpClient.send(request1, HttpResponse.BodyHandlers.ofString()); + + // Second request with same input + HttpRequest request2 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"input\": \"Test input\", \"model\": \"text-embedding-ada-002\"}")) + .build(); + + HttpResponse response2 = hc.httpClient.send(request2, HttpResponse.BodyHandlers.ofString()); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode json1 = objectMapper.readTree(response1.body()); + JsonNode json2 = objectMapper.readTree(response2.body()); + + JsonNode embedding1 = json1.path("data").get(0).path("embedding"); + JsonNode embedding2 = json2.path("data").get(0).path("embedding"); + + // Same input should produce same embedding + assertEquals(embedding1.size(), embedding2.size()); + for (int i = 0; i < embedding1.size(); i++) { + assertEquals(embedding1.get(i).asDouble(), embedding2.get(i).asDouble(), 0.0001, + "Embedding values should be identical for the same input"); + } } } @Test public void testBatchEmbeddings() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - // Send multiple texts in a single request - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"input\": [\"First text\", \"Second text\"], \"model\": \"text-embedding-ada-002\"}")) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - String responseBody = response.body(); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson = objectMapper.readTree(responseBody); - - assertEquals("list", responseJson.path("object").asText()); - - JsonNode dataArray = responseJson.path("data"); - assertEquals(2, dataArray.size(), "Should have 2 embeddings in the response"); - - // First embedding - JsonNode embedding1 = dataArray.get(0); - assertEquals("embedding", embedding1.path("object").asText()); - assertEquals(0, embedding1.path("index").asInt()); - assertEquals(2, embedding1.path("embedding").size()); - assertEquals(0.1, embedding1.path("embedding").get(0).asDouble(), 0.001); - assertEquals(0.2, embedding1.path("embedding").get(1).asDouble(), 0.001); - - // Second embedding - JsonNode embedding2 = dataArray.get(1); - assertEquals("embedding", embedding2.path("object").asText()); - assertEquals(1, embedding2.path("index").asInt()); - assertEquals(2, embedding2.path("embedding").size()); - assertEquals(0.3, embedding2.path("embedding").get(0).asDouble(), 0.001); - assertEquals(0.4, embedding2.path("embedding").get(1).asDouble(), 0.001); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + // Send multiple texts in a single request + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/embeddings")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString( + "{\"input\": [\"First text\", \"Second text\"], \"model\": \"text-embedding-ada-002\"}")) + .build(); + + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson = objectMapper.readTree(responseBody); + + assertEquals("list", responseJson.path("object").asText()); + + JsonNode dataArray = responseJson.path("data"); + assertEquals(2, dataArray.size(), "Should have 2 embeddings in the response"); + + // First embedding + JsonNode embedding1 = dataArray.get(0); + assertEquals("embedding", embedding1.path("object").asText()); + assertEquals(0, embedding1.path("index").asInt()); + assertEquals(2, embedding1.path("embedding").size()); + assertEquals(0.1, embedding1.path("embedding").get(0).asDouble(), 0.001); + assertEquals(0.2, embedding1.path("embedding").get(1).asDouble(), 0.001); + + // Second embedding + JsonNode embedding2 = dataArray.get(1); + assertEquals("embedding", embedding2.path("object").asText()); + assertEquals(1, embedding2.path("index").asInt()); + assertEquals(2, embedding2.path("embedding").size()); + assertEquals(0.3, embedding2.path("embedding").get(0).asDouble(), 0.001); + assertEquals(0.4, embedding2.path("embedding").get(1).asDouble(), 0.001); + } } } diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockFailuresTest.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockFailuresTest.java index 74aa778a50439..23c5216fd5a5e 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockFailuresTest.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockFailuresTest.java @@ -17,7 +17,6 @@ package org.apache.camel.test.infra.openai.mock; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -34,30 +33,32 @@ public class OpenAIMockFailuresTest { @Test public void testBadRequest() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"assistant\", \"content\": \"any sentence\"}]}")) - .build(); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"messages\": [{\"role\": \"assistant\", \"content\": \"any sentence\"}]}")) + .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - Assertions.assertEquals(500, response.statusCode()); + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + Assertions.assertEquals(500, response.statusCode()); + } } @Test public void testNotFound() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"not found sentence\"}]}")) - .build(); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"not found sentence\"}]}")) + .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - Assertions.assertEquals(500, response.statusCode()); + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + Assertions.assertEquals(500, response.statusCode()); + } } @Test diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockMultipleToolsTest.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockMultipleToolsTest.java index 436a2fac58d3d..aab3b6e191348 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockMultipleToolsTest.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockMultipleToolsTest.java @@ -17,7 +17,6 @@ package org.apache.camel.test.infra.openai.mock; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -41,60 +40,63 @@ public class OpenAIMockMultipleToolsTest { @Test void testInvokeToolAndThenInvokeTool() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - ObjectMapper objectMapper = new ObjectMapper(); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + ObjectMapper objectMapper = new ObjectMapper(); - // First request: User asks for weather in London - HttpRequest request1 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"What is the weather in london?\"}]}")) - .build(); + // First request: User asks for weather in London + HttpRequest request1 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString( + "{\"messages\": [{\"role\": \"user\", \"content\": \"What is the weather in london?\"}]}")) + .build(); - HttpResponse response1 = client.send(request1, HttpResponse.BodyHandlers.ofString()); - String responseBody1 = response1.body(); - JsonNode responseJson1 = objectMapper.readTree(responseBody1); + HttpResponse response1 = hc.httpClient.send(request1, HttpResponse.BodyHandlers.ofString()); + String responseBody1 = response1.body(); + JsonNode responseJson1 = objectMapper.readTree(responseBody1); - JsonNode choice1 = responseJson1.path("choices").get(0); - JsonNode message1 = choice1.path("message"); + JsonNode choice1 = responseJson1.path("choices").get(0); + JsonNode message1 = choice1.path("message"); - // Assert first tool call - Assertions.assertEquals("assistant", message1.path("role").asText()); - JsonNode toolCalls1 = message1.path("tool_calls"); - Assertions.assertEquals(1, toolCalls1.size()); - JsonNode toolCall1 = toolCalls1.get(0); - Assertions.assertEquals("FindsTheLatitudeAndLongitudeOfAGivenCity", toolCall1.path("function").path("name").asText()); - Assertions.assertEquals("{\"name\":\"London\"}", toolCall1.path("function").path("arguments").asText()); - String toolCallId1 = toolCall1.path("id").asText(); - Assertions.assertEquals("tool_calls", choice1.path("finish_reason").asText()); + // Assert first tool call + Assertions.assertEquals("assistant", message1.path("role").asText()); + JsonNode toolCalls1 = message1.path("tool_calls"); + Assertions.assertEquals(1, toolCalls1.size()); + JsonNode toolCall1 = toolCalls1.get(0); + Assertions.assertEquals("FindsTheLatitudeAndLongitudeOfAGivenCity", + toolCall1.path("function").path("name").asText()); + Assertions.assertEquals("{\"name\":\"London\"}", toolCall1.path("function").path("arguments").asText()); + String toolCallId1 = toolCall1.path("id").asText(); + Assertions.assertEquals("tool_calls", choice1.path("finish_reason").asText()); - // Second request: LLM provides tool output for the first tool call - String secondRequestBody = String.format( - "{\"messages\": [{\"role\": \"user\", \"content\": \"What is the weather in london?\"}, {\"role\":\"assistant\", \"tool_calls\": [{\"id\":\"%s\", \"type\":\"function\", \"function\":{\"name\":\"FindsTheLatitudeAndLongitudeOfAGivenCity\", \"arguments\":\"{\\\"name\\\":\\\"London\\\"}\"}}]}, {\"role\":\"tool\", \"tool_call_id\":\"%s\", \"content\":\"{\\\"latitude\\\": \\\"51.50758961965397\\\", \\\"longitude\\\": \\\"-0.13388057363742217\\\"}\"}]}", - toolCallId1, toolCallId1); - HttpRequest request2 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(secondRequestBody)) - .build(); + // Second request: LLM provides tool output for the first tool call + String secondRequestBody = String.format( + "{\"messages\": [{\"role\": \"user\", \"content\": \"What is the weather in london?\"}, {\"role\":\"assistant\", \"tool_calls\": [{\"id\":\"%s\", \"type\":\"function\", \"function\":{\"name\":\"FindsTheLatitudeAndLongitudeOfAGivenCity\", \"arguments\":\"{\\\"name\\\":\\\"London\\\"}\"}}]}, {\"role\":\"tool\", \"tool_call_id\":\"%s\", \"content\":\"{\\\"latitude\\\": \\\"51.50758961965397\\\", \\\"longitude\\\": \\\"-0.13388057363742217\\\"}\"}]}", + toolCallId1, toolCallId1); + HttpRequest request2 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(secondRequestBody)) + .build(); - HttpResponse response2 = client.send(request2, HttpResponse.BodyHandlers.ofString()); - String responseBody2 = response2.body(); - JsonNode responseJson2 = objectMapper.readTree(responseBody2); + HttpResponse response2 = hc.httpClient.send(request2, HttpResponse.BodyHandlers.ofString()); + String responseBody2 = response2.body(); + JsonNode responseJson2 = objectMapper.readTree(responseBody2); - JsonNode choice2 = responseJson2.path("choices").get(0); - JsonNode message2 = choice2.path("message"); + JsonNode choice2 = responseJson2.path("choices").get(0); + JsonNode message2 = choice2.path("message"); - // Assert second tool call - Assertions.assertEquals("assistant", message2.path("role").asText()); - JsonNode toolCalls2 = message2.path("tool_calls"); - Assertions.assertEquals(1, toolCalls2.size()); - JsonNode toolCall2 = toolCalls2.get(0); - Assertions.assertEquals("ForecastsTheWeatherForTheGivenLatitudeAndLongitude", - toolCall2.path("function").path("name").asText()); - Assertions.assertEquals("{\"latitude\":\"51.50758961965397\",\"longitude\":\"-0.13388057363742217\"}", - toolCall2.path("function").path("arguments").asText()); - Assertions.assertEquals("tool_calls", choice2.path("finish_reason").asText()); + // Assert second tool call + Assertions.assertEquals("assistant", message2.path("role").asText()); + JsonNode toolCalls2 = message2.path("tool_calls"); + Assertions.assertEquals(1, toolCalls2.size()); + JsonNode toolCall2 = toolCalls2.get(0); + Assertions.assertEquals("ForecastsTheWeatherForTheGivenLatitudeAndLongitude", + toolCall2.path("function").path("name").asText()); + Assertions.assertEquals("{\"latitude\":\"51.50758961965397\",\"longitude\":\"-0.13388057363742217\"}", + toolCall2.path("function").path("arguments").asText()); + Assertions.assertEquals("tool_calls", choice2.path("finish_reason").asText()); + } } } diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockReplyWithAfterToolTest.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockReplyWithAfterToolTest.java index 0ce27dacf19be..3b35f400388af 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockReplyWithAfterToolTest.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockReplyWithAfterToolTest.java @@ -17,7 +17,6 @@ package org.apache.camel.test.infra.openai.mock; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -41,56 +40,57 @@ public class OpenAIMockReplyWithAfterToolTest { @Test public void testReplyWithAfterTool() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - - // First request - should trigger tool call - HttpRequest request1 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"What is the weather in london?\"}]}")) - .build(); - - HttpResponse response1 = client.send(request1, HttpResponse.BodyHandlers.ofString()); - String responseBody1 = response1.body(); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson1 = objectMapper.readTree(responseBody1); - - JsonNode choice1 = responseJson1.path("choices").get(0); - JsonNode message1 = choice1.path("message"); - - assertEquals("assistant", message1.path("role").asText()); - assertEquals("tool_calls", choice1.path("finish_reason").asText()); - - JsonNode toolCalls = message1.path("tool_calls"); - assertEquals(1, toolCalls.size()); - - JsonNode toolCall = toolCalls.get(0); - String toolCallId = toolCall.path("id").asText(); - assertEquals("function", toolCall.path("type").asText()); - assertEquals("FindsTheLatitudeAndLongitudeOfAGivenCity", toolCall.path("function").path("name").asText()); - assertEquals("{\"name\":\"London\"}", toolCall.path("function").path("arguments").asText()); - - // Second request with tool result - should return custom reply - String secondRequestBody = String.format( - "{\"messages\": [{\"role\": \"user\", \"content\": \"What is the weather in london?\"}, {\"role\":\"tool\", \"tool_call_id\":\"%s\", \"content\":\"{\\\"latitude\\\": \\\"51.5074\\\", \\\"longitude\\\": \\\"-0.1278\\\"}\"}]}", - toolCallId); - HttpRequest request2 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(secondRequestBody)) - .build(); - - HttpResponse response2 = client.send(request2, HttpResponse.BodyHandlers.ofString()); - String responseBody2 = response2.body(); - JsonNode responseJson2 = objectMapper.readTree(responseBody2); - - JsonNode choice2 = responseJson2.path("choices").get(0); - JsonNode message2 = choice2.path("message"); - - assertEquals("assistant", message2.path("role").asText()); - assertEquals("stop", choice2.path("finish_reason").asText()); - assertEquals("the latitude of london is 1", message2.path("content").asText()); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + // First request - should trigger tool call + HttpRequest request1 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString( + "{\"messages\": [{\"role\": \"user\", \"content\": \"What is the weather in london?\"}]}")) + .build(); + + HttpResponse response1 = hc.httpClient.send(request1, HttpResponse.BodyHandlers.ofString()); + String responseBody1 = response1.body(); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson1 = objectMapper.readTree(responseBody1); + + JsonNode choice1 = responseJson1.path("choices").get(0); + JsonNode message1 = choice1.path("message"); + + assertEquals("assistant", message1.path("role").asText()); + assertEquals("tool_calls", choice1.path("finish_reason").asText()); + + JsonNode toolCalls = message1.path("tool_calls"); + assertEquals(1, toolCalls.size()); + + JsonNode toolCall = toolCalls.get(0); + String toolCallId = toolCall.path("id").asText(); + assertEquals("function", toolCall.path("type").asText()); + assertEquals("FindsTheLatitudeAndLongitudeOfAGivenCity", toolCall.path("function").path("name").asText()); + assertEquals("{\"name\":\"London\"}", toolCall.path("function").path("arguments").asText()); + + // Second request with tool result - should return custom reply + String secondRequestBody = String.format( + "{\"messages\": [{\"role\": \"user\", \"content\": \"What is the weather in london?\"}, {\"role\":\"tool\", \"tool_call_id\":\"%s\", \"content\":\"{\\\"latitude\\\": \\\"51.5074\\\", \\\"longitude\\\": \\\"-0.1278\\\"}\"}]}", + toolCallId); + HttpRequest request2 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(secondRequestBody)) + .build(); + + HttpResponse response2 = hc.httpClient.send(request2, HttpResponse.BodyHandlers.ofString()); + String responseBody2 = response2.body(); + JsonNode responseJson2 = objectMapper.readTree(responseBody2); + + JsonNode choice2 = responseJson2.path("choices").get(0); + JsonNode message2 = choice2.path("message"); + + assertEquals("assistant", message2.path("role").asText()); + assertEquals("stop", choice2.path("finish_reason").asText()); + assertEquals("the latitude of london is 1", message2.path("content").asText()); + } } } diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockReplyWithToolContentTest.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockReplyWithToolContentTest.java index 095a532a903a1..36776730cf942 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockReplyWithToolContentTest.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockReplyWithToolContentTest.java @@ -17,7 +17,6 @@ package org.apache.camel.test.infra.openai.mock; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -42,61 +41,63 @@ public class OpenAIMockReplyWithToolContentTest { @Test public void testReplyWithToolContent() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - - // First request - should trigger tool call - HttpRequest request1 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"Get location coordinates\"}]}")) - .build(); - - HttpResponse response1 = client.send(request1, HttpResponse.BodyHandlers.ofString()); - String responseBody1 = response1.body(); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson1 = objectMapper.readTree(responseBody1); - - JsonNode choice1 = responseJson1.path("choices").get(0); - JsonNode message1 = choice1.path("message"); - - assertEquals("assistant", message1.path("role").asText()); - assertEquals("tool_calls", choice1.path("finish_reason").asText()); - - JsonNode toolCalls = message1.path("tool_calls"); - assertEquals(1, toolCalls.size()); - - JsonNode toolCall = toolCalls.get(0); - String toolCallId = toolCall.path("id").asText(); - assertEquals("function", toolCall.path("type").asText()); - assertEquals("GetCoordinates", toolCall.path("function").path("name").asText()); - assertEquals("{\"location\":\"Paris\"}", toolCall.path("function").path("arguments").asText()); - - // Second request with tool result - should return tool content + custom message - String secondRequestBody = String.format( - "{\"messages\": [{\"role\": \"user\", \"content\": \"Get location coordinates\"}, {\"role\":\"tool\", \"tool_call_id\":\"%s\", \"content\":\"{\\\"latitude\\\": \\\"48.8566\\\", \\\"longitude\\\": \\\"2.3522\\\"}\"}]}", - toolCallId); - HttpRequest request2 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(secondRequestBody)) - .build(); - - HttpResponse response2 = client.send(request2, HttpResponse.BodyHandlers.ofString()); - String responseBody2 = response2.body(); - JsonNode responseJson2 = objectMapper.readTree(responseBody2); - - JsonNode choice2 = responseJson2.path("choices").get(0); - JsonNode message2 = choice2.path("message"); - - assertEquals("assistant", message2.path("role").asText()); - assertEquals("stop", choice2.path("finish_reason").asText()); - - String content = message2.path("content").asText(); - // Should contain both the tool content and the custom message - assertTrue(content.contains("{\"latitude\": \"48.8566\", \"longitude\": \"2.3522\"}")); - assertTrue(content.contains("- This is the location data I found.")); - assertEquals("{\"latitude\": \"48.8566\", \"longitude\": \"2.3522\"} - This is the location data I found.", content); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + // First request - should trigger tool call + HttpRequest request1 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString( + "{\"messages\": [{\"role\": \"user\", \"content\": \"Get location coordinates\"}]}")) + .build(); + + HttpResponse response1 = hc.httpClient.send(request1, HttpResponse.BodyHandlers.ofString()); + String responseBody1 = response1.body(); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson1 = objectMapper.readTree(responseBody1); + + JsonNode choice1 = responseJson1.path("choices").get(0); + JsonNode message1 = choice1.path("message"); + + assertEquals("assistant", message1.path("role").asText()); + assertEquals("tool_calls", choice1.path("finish_reason").asText()); + + JsonNode toolCalls = message1.path("tool_calls"); + assertEquals(1, toolCalls.size()); + + JsonNode toolCall = toolCalls.get(0); + String toolCallId = toolCall.path("id").asText(); + assertEquals("function", toolCall.path("type").asText()); + assertEquals("GetCoordinates", toolCall.path("function").path("name").asText()); + assertEquals("{\"location\":\"Paris\"}", toolCall.path("function").path("arguments").asText()); + + // Second request with tool result - should return tool content + custom message + String secondRequestBody = String.format( + "{\"messages\": [{\"role\": \"user\", \"content\": \"Get location coordinates\"}, {\"role\":\"tool\", \"tool_call_id\":\"%s\", \"content\":\"{\\\"latitude\\\": \\\"48.8566\\\", \\\"longitude\\\": \\\"2.3522\\\"}\"}]}", + toolCallId); + HttpRequest request2 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(secondRequestBody)) + .build(); + + HttpResponse response2 = hc.httpClient.send(request2, HttpResponse.BodyHandlers.ofString()); + String responseBody2 = response2.body(); + JsonNode responseJson2 = objectMapper.readTree(responseBody2); + + JsonNode choice2 = responseJson2.path("choices").get(0); + JsonNode message2 = choice2.path("message"); + + assertEquals("assistant", message2.path("role").asText()); + assertEquals("stop", choice2.path("finish_reason").asText()); + + String content = message2.path("content").asText(); + // Should contain both the tool content and the custom message + assertTrue(content.contains("{\"latitude\": \"48.8566\", \"longitude\": \"2.3522\"}")); + assertTrue(content.contains("- This is the location data I found.")); + assertEquals("{\"latitude\": \"48.8566\", \"longitude\": \"2.3522\"} - This is the location data I found.", + content); + } } } diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockSimpleAssertionTest.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockSimpleAssertionTest.java index 6d0254e7c5bac..6332c9af85335 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockSimpleAssertionTest.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockSimpleAssertionTest.java @@ -17,7 +17,6 @@ package org.apache.camel.test.infra.openai.mock; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.concurrent.atomic.AtomicBoolean; @@ -55,52 +54,52 @@ public class OpenAIMockSimpleAssertionTest { @Test public void testBothAssertionsExecuted() throws Exception { - HttpClient client = HttpClient.newHttpClient(); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + // First request + HttpRequest request1 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"first message\"}]}")) + .build(); - // First request - HttpRequest request1 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"first message\"}]}")) - .build(); + HttpResponse response1 = hc.httpClient.send(request1, HttpResponse.BodyHandlers.ofString()); + String responseBody1 = response1.body(); - HttpResponse response1 = client.send(request1, HttpResponse.BodyHandlers.ofString()); - String responseBody1 = response1.body(); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson1 = objectMapper.readTree(responseBody1); - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson1 = objectMapper.readTree(responseBody1); + JsonNode choice1 = responseJson1.path("choices").get(0); + JsonNode message1 = choice1.path("message"); - JsonNode choice1 = responseJson1.path("choices").get(0); - JsonNode message1 = choice1.path("message"); + assertEquals("assistant", message1.path("role").asText()); + assertEquals("first response", message1.path("content").asText()); - assertEquals("assistant", message1.path("role").asText()); - assertEquals("first response", message1.path("content").asText()); + // Verify first assertion was executed + assertTrue(firstAssertionExecuted.get(), "First assertion should have been executed"); - // Verify first assertion was executed - assertTrue(firstAssertionExecuted.get(), "First assertion should have been executed"); + // Second request + HttpRequest request2 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"second message\"}]}")) + .build(); - // Second request - HttpRequest request2 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"second message\"}]}")) - .build(); + HttpResponse response2 = hc.httpClient.send(request2, HttpResponse.BodyHandlers.ofString()); + String responseBody2 = response2.body(); - HttpResponse response2 = client.send(request2, HttpResponse.BodyHandlers.ofString()); - String responseBody2 = response2.body(); + ObjectMapper objectMapper2 = new ObjectMapper(); + JsonNode responseJson2 = objectMapper2.readTree(responseBody2); - ObjectMapper objectMapper2 = new ObjectMapper(); - JsonNode responseJson2 = objectMapper2.readTree(responseBody2); + JsonNode choice2 = responseJson2.path("choices").get(0); + JsonNode message2 = choice2.path("message"); - JsonNode choice2 = responseJson2.path("choices").get(0); - JsonNode message2 = choice2.path("message"); + assertEquals("assistant", message2.path("role").asText()); + assertEquals("second response", message2.path("content").asText()); - assertEquals("assistant", message2.path("role").asText()); - assertEquals("second response", message2.path("content").asText()); - - // Verify second assertion was executed - assertTrue(secondAssertionExecuted.get(), "Second assertion should have been executed"); + // Verify second assertion was executed + assertTrue(secondAssertionExecuted.get(), "Second assertion should have been executed"); + } } } diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockTest.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockTest.java index 5e00046b5db70..9be78728c6ec1 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockTest.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/OpenAIMockTest.java @@ -17,7 +17,6 @@ package org.apache.camel.test.infra.openai.mock; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -60,160 +59,165 @@ public class OpenAIMockTest { @Test public void testToolResponse() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"any sentence\"}]}")) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - String responseBody = response.body(); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson = objectMapper.readTree(responseBody); - - JsonNode choice = responseJson.path("choices").get(0); - JsonNode message = choice.path("message"); - - assertEquals("assistant", message.path("role").asText()); - assertEquals(true, message.path("content").isNull()); - assertEquals(true, message.path("refusal").isNull()); - - JsonNode toolCalls = message.path("tool_calls"); - assertEquals(1, toolCalls.size()); - - JsonNode toolCall = toolCalls.get(0); - assertEquals("function", toolCall.path("type").asText()); - assertEquals("toolName", toolCall.path("function").path("name").asText()); - assertEquals("{\"param1\":\"value1\"}", toolCall.path("function").path("arguments").asText()); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"any sentence\"}]}")) + .build(); + + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson = objectMapper.readTree(responseBody); + + JsonNode choice = responseJson.path("choices").get(0); + JsonNode message = choice.path("message"); + + assertEquals("assistant", message.path("role").asText()); + assertEquals(true, message.path("content").isNull()); + assertEquals(true, message.path("refusal").isNull()); + + JsonNode toolCalls = message.path("tool_calls"); + assertEquals(1, toolCalls.size()); + + JsonNode toolCall = toolCalls.get(0); + assertEquals("function", toolCall.path("type").asText()); + assertEquals("toolName", toolCall.path("function").path("name").asText()); + assertEquals("{\"param1\":\"value1\"}", toolCall.path("function").path("arguments").asText()); + } } @Test public void testChatResponse() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"another sentence\"}]}")) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - String responseBody = response.body(); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson = objectMapper.readTree(responseBody); - - JsonNode choice = responseJson.path("choices").get(0); - JsonNode message = choice.path("message"); - - assertEquals("assistant", message.path("role").asText()); - assertEquals("hello World", message.path("content").asText()); - assertEquals(true, message.path("refusal").isNull()); - assertEquals(true, message.path("tool_calls").isMissingNode()); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"another sentence\"}]}")) + .build(); + + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson = objectMapper.readTree(responseBody); + + JsonNode choice = responseJson.path("choices").get(0); + JsonNode message = choice.path("message"); + + assertEquals("assistant", message.path("role").asText()); + assertEquals("hello World", message.path("content").asText()); + assertEquals(true, message.path("refusal").isNull()); + assertEquals(true, message.path("tool_calls").isMissingNode()); + } } @Test public void testMultipleToolCallsResponse() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"multiple tools\"}]}")) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - String responseBody = response.body(); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson = objectMapper.readTree(responseBody); - - JsonNode choice = responseJson.path("choices").get(0); - JsonNode message = choice.path("message"); - - assertEquals("assistant", message.path("role").asText()); - assertEquals(true, message.path("content").isNull()); - assertEquals(true, message.path("refusal").isNull()); - - JsonNode toolCalls = message.path("tool_calls"); - assertEquals(2, toolCalls.size()); - - JsonNode toolCall1 = toolCalls.get(0); - assertEquals("function", toolCall1.path("type").asText()); - assertEquals("tool1", toolCall1.path("function").path("name").asText()); - assertEquals("{\"p1\":\"v1\"}", toolCall1.path("function").path("arguments").asText()); - - JsonNode toolCall2 = toolCalls.get(1); - assertEquals("function", toolCall2.path("type").asText()); - assertEquals("tool2", toolCall2.path("function").path("name").asText()); - assertEquals("{\"p2\":\"v2\",\"p3\":\"v3\"}", toolCall2.path("function").path("arguments").asText()); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"multiple tools\"}]}")) + .build(); + + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson = objectMapper.readTree(responseBody); + + JsonNode choice = responseJson.path("choices").get(0); + JsonNode message = choice.path("message"); + + assertEquals("assistant", message.path("role").asText()); + assertEquals(true, message.path("content").isNull()); + assertEquals(true, message.path("refusal").isNull()); + + JsonNode toolCalls = message.path("tool_calls"); + assertEquals(2, toolCalls.size()); + + JsonNode toolCall1 = toolCalls.get(0); + assertEquals("function", toolCall1.path("type").asText()); + assertEquals("tool1", toolCall1.path("function").path("name").asText()); + assertEquals("{\"p1\":\"v1\"}", toolCall1.path("function").path("arguments").asText()); + + JsonNode toolCall2 = toolCalls.get(1); + assertEquals("function", toolCall2.path("type").asText()); + assertEquals("tool2", toolCall2.path("function").path("name").asText()); + assertEquals("{\"p2\":\"v2\",\"p3\":\"v3\"}", toolCall2.path("function").path("arguments").asText()); + } } @Test public void testCustomResponse() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"custom response\"}]}")) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - String responseBody = response.body(); - - assertEquals("Custom response for: custom response", responseBody); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"custom response\"}]}")) + .build(); + + HttpResponse response = hc.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); + + assertEquals("Custom response for: custom response", responseBody); + } } @Test public void testToolResponseAndStop() throws Exception { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request1 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers - .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"any sentence\"}]}")) - .build(); - - HttpResponse response1 = client.send(request1, HttpResponse.BodyHandlers.ofString()); - String responseBody1 = response1.body(); - - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode responseJson1 = objectMapper.readTree(responseBody1); - - JsonNode choice1 = responseJson1.path("choices").get(0); - JsonNode message1 = choice1.path("message"); - - assertEquals("assistant", message1.path("role").asText()); - assertEquals(true, message1.path("content").isNull()); - assertEquals(true, message1.path("refusal").isNull()); - - JsonNode toolCalls = message1.path("tool_calls"); - assertEquals(1, toolCalls.size()); - - JsonNode toolCall = toolCalls.get(0); - String toolCallId = toolCall.path("id").asText(); - assertEquals("function", toolCall.path("type").asText()); - assertEquals("toolName", toolCall.path("function").path("name").asText()); - assertEquals("{\"param1\":\"value1\"}", toolCall.path("function").path("arguments").asText()); - - // Second request with tool result - String secondRequestBody = String.format( - "{\"messages\": [{\"role\": \"user\", \"content\": \"any sentence\"}, {\"role\":\"tool\", \"tool_call_id\":\"%s\", \"content\":\"{\\\"name\\\": \\\"pippo\\\"}\"}]}", - toolCallId); - HttpRequest request2 = HttpRequest.newBuilder() - .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(secondRequestBody)) - .build(); - HttpResponse response2 = client.send(request2, HttpResponse.BodyHandlers.ofString()); - String responseBody2 = response2.body(); - JsonNode responseJson2 = objectMapper.readTree(responseBody2); - - JsonNode choice2 = responseJson2.path("choices").get(0); - assertEquals("stop", choice2.path("finish_reason").asText()); + try (CloseableHttpClient hc = new CloseableHttpClient()) { + HttpRequest request1 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers + .ofString("{\"messages\": [{\"role\": \"user\", \"content\": \"any sentence\"}]}")) + .build(); + + HttpResponse response1 = hc.httpClient.send(request1, HttpResponse.BodyHandlers.ofString()); + String responseBody1 = response1.body(); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJson1 = objectMapper.readTree(responseBody1); + + JsonNode choice1 = responseJson1.path("choices").get(0); + JsonNode message1 = choice1.path("message"); + + assertEquals("assistant", message1.path("role").asText()); + assertEquals(true, message1.path("content").isNull()); + assertEquals(true, message1.path("refusal").isNull()); + + JsonNode toolCalls = message1.path("tool_calls"); + assertEquals(1, toolCalls.size()); + + JsonNode toolCall = toolCalls.get(0); + String toolCallId = toolCall.path("id").asText(); + assertEquals("function", toolCall.path("type").asText()); + assertEquals("toolName", toolCall.path("function").path("name").asText()); + assertEquals("{\"param1\":\"value1\"}", toolCall.path("function").path("arguments").asText()); + + // Second request with tool result + String secondRequestBody = String.format( + "{\"messages\": [{\"role\": \"user\", \"content\": \"any sentence\"}, {\"role\":\"tool\", \"tool_call_id\":\"%s\", \"content\":\"{\\\"name\\\": \\\"pippo\\\"}\"}]}", + toolCallId); + HttpRequest request2 = HttpRequest.newBuilder() + .uri(URI.create(openAIMock.getBaseUrl() + "/v1/chat/completions")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(secondRequestBody)) + .build(); + HttpResponse response2 = hc.httpClient.send(request2, HttpResponse.BodyHandlers.ofString()); + String responseBody2 = response2.body(); + JsonNode responseJson2 = objectMapper.readTree(responseBody2); + + JsonNode choice2 = responseJson2.path("choices").get(0); + assertEquals("stop", choice2.path("finish_reason").asText()); + } } } diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/RequestHandler.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/RequestHandler.java index 8c8aed7afb90a..b6f350f280b63 100644 --- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/RequestHandler.java +++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/RequestHandler.java @@ -17,6 +17,7 @@ package org.apache.camel.test.infra.openai.mock; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.List; @@ -44,7 +45,10 @@ public RequestHandler(List expectations, ObjectMapper objectMap public String handleRequest(HttpExchange exchange) throws IOException { try { - String requestBody = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); + String requestBody; + try (InputStream is = exchange.getRequestBody()) { + requestBody = new String(is.readAllBytes(), StandardCharsets.UTF_8); + } LOG.debug("Processing request: {}", requestBody); JsonNode rootNode = objectMapper.readTree(requestBody);