From 92029fbe86724fa04fb2c0db98a06ea3f06d0eb3 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 4 Feb 2025 12:54:01 +0100 Subject: [PATCH 1/9] split openai-client instrumentation for implementation and test reuse --- custom/build.gradle.kts | 2 +- .../common/build.gradle.kts | 10 + ...nAiOkHttpClientBuilderInstrumentation.java | 0 .../wrappers/ChatCompletionEventsHelper.java | 0 .../otel/openai/wrappers/Constants.java | 0 .../wrappers/DelegatingInvocationHandler.java | 0 .../EventLoggingStreamedResponse.java | 0 .../otel/openai/wrappers/GenAiAttributes.java | 0 .../openai/wrappers/GenAiClientMetrics.java | 0 .../wrappers/InstrumentationSettings.java | 0 .../InstrumentedChatCompletionService.java | 0 .../wrappers/InstrumentedChatService.java | 0 .../InstrumentedEmbeddingsService.java | 0 .../wrappers/InstrumentedOpenAiClient.java | 4 +- .../otel/openai/wrappers/MapValueBuilder.java | 0 .../wrappers/StreamedMessageBuffer.java | 0 .../wrappers/TracingStreamedResponse.java | 0 .../build.gradle.kts | 9 +- .../OpenAiClientInstrumentationModule.java | 0 .../java/co/elastic/otel/openai/ChatTest.java | 23 + .../elastic/otel/openai/EmbeddingsTest.java | 23 + .../openai/LiveAPIChatIntegrationTest.java | 2 +- .../src/test/resources/mappings/chattest.yaml | 0 .../resources/mappings/embeddingstest.yaml | 0 .../testing-common/build.gradle.kts | 16 + .../co/elastic/otel/openai/ChatTestBase.java} | 16 +- .../otel/openai/EmbeddingsTestBase.java} | 2 +- .../otel/openai/OpenAIRecordingExtension.java | 3 +- ...rintEqualToJsonStubMappingTransformer.java | 0 .../otel/openai/ResponseHeaderScrubber.java | 0 .../co/elastic/otel/openai/ValAssert.java | 0 .../otel/openai/YamlFileMappingsSource.java | 0 .../InstrumentationSettingsAccessor.java | 0 .../src/main/resources/mappings/chattest.yaml | 1239 +++++++++++++++++ .../resources/mappings/embeddingstest.yaml | 100 ++ settings.gradle.kts | 4 +- 36 files changed, 1432 insertions(+), 21 deletions(-) create mode 100644 instrumentation/openai-client-instrumentation/common/build.gradle.kts rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/OpenAiOkHttpClientBuilderInstrumentation.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/Constants.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/DelegatingInvocationHandler.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/EventLoggingStreamedResponse.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/GenAiAttributes.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/GenAiClientMetrics.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/InstrumentationSettings.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatService.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedEmbeddingsService.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java (96%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/MapValueBuilder.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/StreamedMessageBuffer.java (100%) rename instrumentation/openai-client-instrumentation/{ => common}/src/main/java/co/elastic/otel/openai/wrappers/TracingStreamedResponse.java (100%) rename instrumentation/openai-client-instrumentation/{ => instrumentation-0.13.0}/build.gradle.kts (67%) rename instrumentation/openai-client-instrumentation/{ => instrumentation-0.13.0}/src/main/java/co/elastic/otel/openai/OpenAiClientInstrumentationModule.java (100%) create mode 100644 instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/ChatTest.java create mode 100644 instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/EmbeddingsTest.java rename instrumentation/openai-client-instrumentation/{ => instrumentation-0.13.0}/src/test/java/co/elastic/otel/openai/LiveAPIChatIntegrationTest.java (99%) rename instrumentation/openai-client-instrumentation/{ => instrumentation-0.13.0}/src/test/resources/mappings/chattest.yaml (100%) rename instrumentation/openai-client-instrumentation/{ => instrumentation-0.13.0}/src/test/resources/mappings/embeddingstest.yaml (100%) create mode 100644 instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts rename instrumentation/openai-client-instrumentation/{src/test/java/co/elastic/otel/openai/ChatTest.java => testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java} (99%) rename instrumentation/openai-client-instrumentation/{src/test/java/co/elastic/otel/openai/EmbeddingsTest.java => testing-common/src/main/java/co/elastic/otel/openai/EmbeddingsTestBase.java} (99%) rename instrumentation/openai-client-instrumentation/{src/test => testing-common/src/main}/java/co/elastic/otel/openai/OpenAIRecordingExtension.java (95%) rename instrumentation/openai-client-instrumentation/{src/test => testing-common/src/main}/java/co/elastic/otel/openai/PrettyPrintEqualToJsonStubMappingTransformer.java (100%) rename instrumentation/openai-client-instrumentation/{src/test => testing-common/src/main}/java/co/elastic/otel/openai/ResponseHeaderScrubber.java (100%) rename instrumentation/openai-client-instrumentation/{src/test => testing-common/src/main}/java/co/elastic/otel/openai/ValAssert.java (100%) rename instrumentation/openai-client-instrumentation/{src/test => testing-common/src/main}/java/co/elastic/otel/openai/YamlFileMappingsSource.java (100%) rename instrumentation/openai-client-instrumentation/{src/test => testing-common/src/main}/java/co/elastic/otel/openai/wrappers/InstrumentationSettingsAccessor.java (100%) create mode 100644 instrumentation/openai-client-instrumentation/testing-common/src/main/resources/mappings/chattest.yaml create mode 100644 instrumentation/openai-client-instrumentation/testing-common/src/main/resources/mappings/embeddingstest.yaml diff --git a/custom/build.gradle.kts b/custom/build.gradle.kts index e0eb65d6..43548d2a 100644 --- a/custom/build.gradle.kts +++ b/custom/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } val instrumentations = listOf( - ":instrumentation:openai-client-instrumentation" + ":instrumentation:openai-client-instrumentation:instrumentation-0.13.0" ) dependencies { diff --git a/instrumentation/openai-client-instrumentation/common/build.gradle.kts b/instrumentation/openai-client-instrumentation/common/build.gradle.kts new file mode 100644 index 00000000..0b652dd8 --- /dev/null +++ b/instrumentation/openai-client-instrumentation/common/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("elastic-otel.java-conventions") +} + +dependencies { + compileOnly(catalog.openaiClient) + compileOnly("io.opentelemetry:opentelemetry-sdk") + compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api") + compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api") +} diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/OpenAiOkHttpClientBuilderInstrumentation.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/OpenAiOkHttpClientBuilderInstrumentation.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/OpenAiOkHttpClientBuilderInstrumentation.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/OpenAiOkHttpClientBuilderInstrumentation.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/Constants.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/Constants.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/Constants.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/Constants.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/DelegatingInvocationHandler.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/DelegatingInvocationHandler.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/DelegatingInvocationHandler.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/DelegatingInvocationHandler.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/EventLoggingStreamedResponse.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/EventLoggingStreamedResponse.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/EventLoggingStreamedResponse.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/EventLoggingStreamedResponse.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/GenAiAttributes.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/GenAiAttributes.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/GenAiAttributes.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/GenAiAttributes.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/GenAiClientMetrics.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/GenAiClientMetrics.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/GenAiClientMetrics.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/GenAiClientMetrics.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentationSettings.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentationSettings.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentationSettings.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentationSettings.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatService.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatService.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatService.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatService.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedEmbeddingsService.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedEmbeddingsService.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedEmbeddingsService.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedEmbeddingsService.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java similarity index 96% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java index c111494f..bce500fd 100644 --- a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java @@ -97,8 +97,8 @@ public OpenAIClient build() { } } return new InstrumentedOpenAiClient( - delegate, - new InstrumentationSettings(emitEvents, captureMessageContent, hostname, port)) + delegate, + new InstrumentationSettings(emitEvents, captureMessageContent, hostname, port)) .createProxy(); } } diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/MapValueBuilder.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/MapValueBuilder.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/MapValueBuilder.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/MapValueBuilder.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/StreamedMessageBuffer.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/StreamedMessageBuffer.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/StreamedMessageBuffer.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/StreamedMessageBuffer.java diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/TracingStreamedResponse.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/TracingStreamedResponse.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/wrappers/TracingStreamedResponse.java rename to instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/TracingStreamedResponse.java diff --git a/instrumentation/openai-client-instrumentation/build.gradle.kts b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/build.gradle.kts similarity index 67% rename from instrumentation/openai-client-instrumentation/build.gradle.kts rename to instrumentation/openai-client-instrumentation/instrumentation-0.13.0/build.gradle.kts index 102a1f70..c9be9004 100644 --- a/instrumentation/openai-client-instrumentation/build.gradle.kts +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/build.gradle.kts @@ -5,13 +5,10 @@ plugins { } dependencies { - compileOnly(catalog.openaiClient) - testImplementation(catalog.openaiClient) + implementation(project(":instrumentation:openai-client-instrumentation:common")) - testImplementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2") - testImplementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.2") - testImplementation("org.slf4j:slf4j-simple:2.0.16") - testImplementation(catalog.wiremock) + testImplementation(catalog.openaiClient) + testImplementation(project(":instrumentation:openai-client-instrumentation:testing-common")) } muzzle { diff --git a/instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/OpenAiClientInstrumentationModule.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/OpenAiClientInstrumentationModule.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/main/java/co/elastic/otel/openai/OpenAiClientInstrumentationModule.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/OpenAiClientInstrumentationModule.java diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/ChatTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/ChatTest.java new file mode 100644 index 00000000..c496b25a --- /dev/null +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/ChatTest.java @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 co.elastic.otel.openai; + +class ChatTest extends ChatTestBase { + +} diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/EmbeddingsTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/EmbeddingsTest.java new file mode 100644 index 00000000..2901acaa --- /dev/null +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/EmbeddingsTest.java @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 co.elastic.otel.openai; + +class EmbeddingsTest extends EmbeddingsTestBase { + +} diff --git a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/LiveAPIChatIntegrationTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/LiveAPIChatIntegrationTest.java similarity index 99% rename from instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/LiveAPIChatIntegrationTest.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/LiveAPIChatIntegrationTest.java index 0367e7ce..11d521ef 100644 --- a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/LiveAPIChatIntegrationTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/LiveAPIChatIntegrationTest.java @@ -123,7 +123,7 @@ void toolCallsWithCaptureMessageContent() { "Hi there! I can help with that. Can you please provide your order ID?"), createUserMessage("i think it is order_12345"))) .model(TEST_CHAT_MODEL) - .addTool(ChatTest.buildGetDeliveryDateToolDefinition()) + .addTool(ChatTestBase.buildGetDeliveryDateToolDefinition()) .build(); long startTimeNanos = System.nanoTime(); diff --git a/instrumentation/openai-client-instrumentation/src/test/resources/mappings/chattest.yaml b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/chattest.yaml similarity index 100% rename from instrumentation/openai-client-instrumentation/src/test/resources/mappings/chattest.yaml rename to instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/chattest.yaml diff --git a/instrumentation/openai-client-instrumentation/src/test/resources/mappings/embeddingstest.yaml b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/embeddingstest.yaml similarity index 100% rename from instrumentation/openai-client-instrumentation/src/test/resources/mappings/embeddingstest.yaml rename to instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/embeddingstest.yaml diff --git a/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts b/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts new file mode 100644 index 00000000..590b7e8b --- /dev/null +++ b/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("elastic-otel.java-conventions") +} + +dependencies { + compileOnly(catalog.openaiClient) + + compileOnly(project(":instrumentation:openai-client-instrumentation:common")) + implementation("io.opentelemetry.javaagent:opentelemetry-testing-common") + implementation("io.opentelemetry:opentelemetry-sdk-testing") + + implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.2") + implementation("org.slf4j:slf4j-simple:2.0.16") + implementation(catalog.wiremock) +} diff --git a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/ChatTest.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java similarity index 99% rename from instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/ChatTest.java rename to instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java index d035b987..d481a4c6 100644 --- a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/ChatTest.java +++ b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java @@ -86,7 +86,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -class ChatTest { +class ChatTestBase { private static final String TEST_CHAT_MODEL = "gpt-4o-mini"; private static final String TEST_CHAT_RESPONSE_MODEL = "gpt-4o-mini-2024-07-18"; private static final String TEST_CHAT_INPUT = @@ -583,7 +583,7 @@ void toolCalls() { assertThat(attr) .containsEntry("event.name", "gen_ai.system.message") .containsEntry(GEN_AI_SYSTEM, "openai")); - assertThat(log.getBodyValue()).satisfies(ChatTest::assertThatValueIsEmptyMap); + assertThat(log.getBodyValue()).satisfies(ChatTestBase::assertThatValueIsEmptyMap); }) .anySatisfy( log -> { @@ -593,7 +593,7 @@ void toolCalls() { assertThat(attr) .containsEntry("event.name", "gen_ai.user.message") .containsEntry(GEN_AI_SYSTEM, "openai")); - assertThat(log.getBodyValue()).satisfies(ChatTest::assertThatValueIsEmptyMap); + assertThat(log.getBodyValue()).satisfies(ChatTestBase::assertThatValueIsEmptyMap); }) .anySatisfy( log -> { @@ -603,7 +603,7 @@ void toolCalls() { assertThat(attr) .containsEntry("event.name", "gen_ai.assistant.message") .containsEntry(GEN_AI_SYSTEM, "openai")); - assertThat(log.getBodyValue()).satisfies(ChatTest::assertThatValueIsEmptyMap); + assertThat(log.getBodyValue()).satisfies(ChatTestBase::assertThatValueIsEmptyMap); }) .anySatisfy( log -> { @@ -613,7 +613,7 @@ void toolCalls() { assertThat(attr) .containsEntry("event.name", "gen_ai.user.message") .containsEntry(GEN_AI_SYSTEM, "openai")); - assertThat(log.getBodyValue()).satisfies(ChatTest::assertThatValueIsEmptyMap); + assertThat(log.getBodyValue()).satisfies(ChatTestBase::assertThatValueIsEmptyMap); }) .anySatisfy( log -> { @@ -1196,7 +1196,7 @@ void stream() throws Exception { .containsEntry("event.name", "gen_ai.user.message") .containsEntry(GEN_AI_SYSTEM, "openai")) .hasSpanContext(spanCtx); - assertThat(log.getBodyValue()).satisfies(ChatTest::assertThatValueIsEmptyMap); + assertThat(log.getBodyValue()).satisfies(ChatTestBase::assertThatValueIsEmptyMap); }) .anySatisfy( log -> { @@ -1296,7 +1296,7 @@ void streamWithIncludeUsage() throws Exception { .containsEntry("event.name", "gen_ai.user.message") .containsEntry(GEN_AI_SYSTEM, "openai")) .hasSpanContext(spanCtx); - assertThat(log.getBodyValue()).satisfies(ChatTest::assertThatValueIsEmptyMap); + assertThat(log.getBodyValue()).satisfies(ChatTestBase::assertThatValueIsEmptyMap); }) .anySatisfy( log -> { @@ -1436,7 +1436,7 @@ void streamAllTheClientOptions() throws Exception { .containsEntry("event.name", "gen_ai.user.message") .containsEntry(GEN_AI_SYSTEM, "openai")) .hasSpanContext(spanCtx); - assertThat(log.getBodyValue()).satisfies(ChatTest::assertThatValueIsEmptyMap); + assertThat(log.getBodyValue()).satisfies(ChatTestBase::assertThatValueIsEmptyMap); }) .anySatisfy( log -> { diff --git a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/EmbeddingsTest.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/EmbeddingsTestBase.java similarity index 99% rename from instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/EmbeddingsTest.java rename to instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/EmbeddingsTestBase.java index 856f81b8..bf311190 100644 --- a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/EmbeddingsTest.java +++ b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/EmbeddingsTestBase.java @@ -40,7 +40,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -class EmbeddingsTest { +class EmbeddingsTestBase { private static final String MODEL = System.getenv().getOrDefault("OPENAI_MODEL", "text-embedding-3-small"); diff --git a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/OpenAIRecordingExtension.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/OpenAIRecordingExtension.java similarity index 95% rename from instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/OpenAIRecordingExtension.java rename to instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/OpenAIRecordingExtension.java index 73d45462..3e8c7d8b 100644 --- a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/OpenAIRecordingExtension.java +++ b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/OpenAIRecordingExtension.java @@ -50,7 +50,8 @@ final class OpenAIRecordingExtension extends WireMockExtension { PrettyPrintEqualToJsonStubMappingTransformer.class) .mappingSource( new YamlFileMappingsSource( - new SingleRootFileSource("src/test/resources").child("mappings"))))); + new SingleRootFileSource("../testing-common/src/main/resources").child( + "mappings"))))); this.testName = testName; } diff --git a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/PrettyPrintEqualToJsonStubMappingTransformer.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/PrettyPrintEqualToJsonStubMappingTransformer.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/PrettyPrintEqualToJsonStubMappingTransformer.java rename to instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/PrettyPrintEqualToJsonStubMappingTransformer.java diff --git a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/ResponseHeaderScrubber.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ResponseHeaderScrubber.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/ResponseHeaderScrubber.java rename to instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ResponseHeaderScrubber.java diff --git a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/ValAssert.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ValAssert.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/ValAssert.java rename to instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ValAssert.java diff --git a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/YamlFileMappingsSource.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/YamlFileMappingsSource.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/YamlFileMappingsSource.java rename to instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/YamlFileMappingsSource.java diff --git a/instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/wrappers/InstrumentationSettingsAccessor.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentationSettingsAccessor.java similarity index 100% rename from instrumentation/openai-client-instrumentation/src/test/java/co/elastic/otel/openai/wrappers/InstrumentationSettingsAccessor.java rename to instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentationSettingsAccessor.java diff --git a/instrumentation/openai-client-instrumentation/testing-common/src/main/resources/mappings/chattest.yaml b/instrumentation/openai-client-instrumentation/testing-common/src/main/resources/mappings/chattest.yaml new file mode 100644 index 00000000..3d8bf361 --- /dev/null +++ b/instrumentation/openai-client-instrumentation/testing-common/src/main/resources/mappings/chattest.yaml @@ -0,0 +1,1239 @@ +--- +id: 3f9cba30-8e45-408c-8f93-8a2f28fc1147 +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "frequency_penalty" : 0, + "max_tokens" : 100, + "presence_penalty" : 0, + "response_format" : { + "type" : "text" + }, + "seed" : 100, + "stop" : [ "foo" ], + "temperature" : 1, + "top_p" : 1 + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "id": "chatcmpl-ApviQlqj8fahybjC0N8MSkPiFt2ls", + "object": "chat.completion", + "created": 1736939950, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Southern Ocean.", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 22, + "completion_tokens": 3, + "total_tokens": 25, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_72ed7ab54c" + } + headers: + Date: "Wed, 15 Jan 2025 11:19:10 GMT" + Content-Type: application/json + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "168" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9999" + x-ratelimit-remaining-tokens: "199883" + x-ratelimit-reset-requests: 8.64s + x-ratelimit-reset-tokens: 34ms + x-request-id: req_6c7842b1c3f10b8637bb26050a52edb4 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 902578210b55d269-FRA +uuid: 3f9cba30-8e45-408c-8f93-8a2f28fc1147 +persistent: true +insertionIndex: 2 +--- +id: 1d03b2c6-450b-4dfc-8c66-65007ac6662b +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", + "role" : "user" + } ], + "model" : "gpt-4o-mini" + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "id": "chatcmpl-ApviRmVhVukgmmFTXvHvlvgmBf6Fr", + "object": "chat.completion", + "created": 1736939951, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "South Atlantic Ocean.", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 22, + "completion_tokens": 5, + "total_tokens": 27, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_72ed7ab54c" + } + headers: + Date: "Wed, 15 Jan 2025 11:19:11 GMT" + Content-Type: application/json + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "454" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9998" + x-ratelimit-remaining-tokens: "199967" + x-ratelimit-reset-requests: 16.283s + x-ratelimit-reset-tokens: 9ms + x-request-id: req_ea9e3f4cc3b7f2162e1451ccd969e674 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 902578271bdc30f6-FRA +uuid: 1d03b2c6-450b-4dfc-8c66-65007ac6662b +persistent: true +insertionIndex: 6 +--- +id: 4d01d762-0339-43c1-8dc8-24b44e8df5ea +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "You are a helpful assistant providing weather updates.", + "role" : "system" + }, { + "content" : "What is the weather in New York City and London?", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "tools" : [ { + "type" : "function", + "function" : { + "name" : "get_weather", + "parameters" : { + "type" : "object", + "required" : [ "location" ], + "additionalProperties" : false, + "properties" : { + "location" : { + "description" : "The location to get the current temperature for", + "type" : "string" + } + } + } + } + } ] + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "id": "chatcmpl-ApviSs94mF5FrHZHISwvYcpDclw4O", + "object": "chat.completion", + "created": 1736939952, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_praw3bINJa8YgORLR1j5YqAd", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "{\"location\": \"New York City\"}" + } + }, + { + "id": "call_wBr1xgbiWQaqY0D0enFzmkR1", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "{\"location\": \"London\"}" + } + } + ], + "refusal": null + }, + "logprobs": null, + "finish_reason": "tool_calls" + } + ], + "usage": { + "prompt_tokens": 67, + "completion_tokens": 47, + "total_tokens": 114, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_bd83329f63" + } + headers: + Date: "Wed, 15 Jan 2025 11:19:13 GMT" + Content-Type: application/json + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "965" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9997" + x-ratelimit-remaining-tokens: "199956" + x-ratelimit-reset-requests: 24.043s + x-ratelimit-reset-tokens: 13ms + x-request-id: req_461add22df2e5e30c211828be358cd63 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 9025782c9cf31da6-FRA +uuid: 4d01d762-0339-43c1-8dc8-24b44e8df5ea +persistent: true +insertionIndex: 11 +--- +id: 0f1e52b2-bf92-4f44-8223-5dd7d93bca36 +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "You are a helpful assistant providing weather updates.", + "role" : "system" + }, { + "content" : "What is the weather in New York City and London?", + "role" : "user" + }, { + "content" : "", + "role" : "assistant", + "tool_calls" : [ { + "id" : "call_praw3bINJa8YgORLR1j5YqAd", + "type" : "function", + "function" : { + "name" : "get_weather", + "arguments" : "{\"location\": \"New York City\"}" + } + }, { + "id" : "call_wBr1xgbiWQaqY0D0enFzmkR1", + "type" : "function", + "function" : { + "name" : "get_weather", + "arguments" : "{\"location\": \"London\"}" + } + } ] + }, { + "role" : "tool", + "content" : "25 degrees and sunny", + "tool_call_id" : "call_praw3bINJa8YgORLR1j5YqAd" + }, { + "role" : "tool", + "content" : "15 degrees and raining", + "tool_call_id" : "call_wBr1xgbiWQaqY0D0enFzmkR1" + } ], + "model" : "gpt-4o-mini" + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "id": "chatcmpl-ApviUidJQ9YrYP5xeNzIt8EVfYbFW", + "object": "chat.completion", + "created": 1736939954, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "The current weather in New York City is 25 degrees Celsius and sunny. In London, it is 15 degrees Celsius and raining.", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 99, + "completion_tokens": 27, + "total_tokens": 126, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_72ed7ab54c" + } + headers: + Date: "Wed, 15 Jan 2025 11:19:14 GMT" + Content-Type: application/json + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "1200" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9996" + x-ratelimit-remaining-tokens: "199943" + x-ratelimit-reset-requests: 31.232s + x-ratelimit-reset-tokens: 17ms + x-request-id: req_feb030ccccced2bf6afe7c40c72d7411 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 90257835aaeb3603-FRA +uuid: 0f1e52b2-bf92-4f44-8223-5dd7d93bca36 +persistent: true +insertionIndex: 12 +--- +id: fcb1f170-582b-4213-8905-088801bd5da1 +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", + "role" : "user" + } ], + "model" : "gpt-4o-mini" + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "id": "chatcmpl-ApvifdVpIRcgHgDW6sFspxbSUOPji", + "object": "chat.completion", + "created": 1736939965, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "South Atlantic Ocean.", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 22, + "completion_tokens": 5, + "total_tokens": 27, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_72ed7ab54c" + } + headers: + Date: "Wed, 15 Jan 2025 11:19:25 GMT" + Content-Type: application/json + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "289" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9996" + x-ratelimit-remaining-tokens: "199967" + x-ratelimit-reset-requests: 28.224s + x-ratelimit-reset-tokens: 9ms + x-request-id: req_558428698c1c501afd73150c5949a976 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 9025787e89d9903a-FRA +uuid: fcb1f170-582b-4213-8905-088801bd5da1 +persistent: true +insertionIndex: 19 +--- +id: f1c29c6d-77f6-416b-8d67-3af3bf41748c +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "stream" : true + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: |+ + data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"South"},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Atlantic"},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Ocean"},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + + data: [DONE] + + headers: + Date: "Wed, 15 Jan 2025 11:19:26 GMT" + Content-Type: text/event-stream; charset=utf-8 + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "320" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9995" + x-ratelimit-remaining-tokens: "199967" + x-ratelimit-reset-requests: 36.155s + x-ratelimit-reset-tokens: 9ms + x-request-id: req_04002595aba2c59a6bc5ec701a16c2da + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 90257882d8ff9730-FRA +uuid: f1c29c6d-77f6-416b-8d67-3af3bf41748c +persistent: true +insertionIndex: 27 +--- +id: 6c176fd9-33ef-43dc-95e1-b41302ac459b +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "frequency_penalty" : 0, + "max_tokens" : 100, + "presence_penalty" : 0, + "response_format" : { + "type" : "text" + }, + "seed" : 100, + "stop" : [ "foo" ], + "temperature" : 1, + "top_p" : 1, + "stream" : true + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: |+ + data: {"id":"chatcmpl-ApvihZr5if54NlG1FkM0FLYadGaZW","object":"chat.completion.chunk","created":1736939967,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_bd83329f63","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvihZr5if54NlG1FkM0FLYadGaZW","object":"chat.completion.chunk","created":1736939967,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_bd83329f63","choices":[{"index":0,"delta":{"content":"Southern"},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvihZr5if54NlG1FkM0FLYadGaZW","object":"chat.completion.chunk","created":1736939967,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_bd83329f63","choices":[{"index":0,"delta":{"content":" Ocean"},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvihZr5if54NlG1FkM0FLYadGaZW","object":"chat.completion.chunk","created":1736939967,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_bd83329f63","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvihZr5if54NlG1FkM0FLYadGaZW","object":"chat.completion.chunk","created":1736939967,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_bd83329f63","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + + data: [DONE] + + headers: + Date: "Wed, 15 Jan 2025 11:19:27 GMT" + Content-Type: text/event-stream; charset=utf-8 + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "232" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9994" + x-ratelimit-remaining-tokens: "199883" + x-ratelimit-reset-requests: 43.871s + x-ratelimit-reset-tokens: 34ms + x-request-id: req_143a0a089d19b5550f77633653d093c7 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 90257888caa4dc74-FRA +uuid: 6c176fd9-33ef-43dc-95e1-b41302ac459b +persistent: true +insertionIndex: 36 +--- +id: 86161b62-8793-4a43-9231-2c0c4719f212 +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "You are a helpful customer support assistant. Use the supplied tools to assist the user.", + "role" : "system" + }, { + "content" : "Hi, can you tell me the delivery date for my order?", + "role" : "user" + }, { + "content" : "Hi there! I can help with that. Can you please provide your order ID?", + "role" : "assistant" + }, { + "content" : "i think it is order_12345", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "tools" : [ { + "type" : "function", + "function" : { + "description" : "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'", + "name" : "get_delivery_date", + "parameters" : { + "type" : "object", + "required" : [ "order_id" ], + "additionalProperties" : false, + "properties" : { + "order_id" : { + "description" : "The customer's order ID.", + "type" : "string" + } + } + } + } + } ] + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "id": "chatcmpl-ApvihK7kXkNWtzppIkF4fFwxNUlfq", + "object": "chat.completion", + "created": 1736939967, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_FmRndu0ugAh8YjL9oUoJhWAh", + "type": "function", + "function": { + "name": "get_delivery_date", + "arguments": "{\"order_id\":\"order_12345\"}" + } + } + ], + "refusal": null + }, + "logprobs": null, + "finish_reason": "tool_calls" + } + ], + "usage": { + "prompt_tokens": 140, + "completion_tokens": 20, + "total_tokens": 160, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_72ed7ab54c" + } + headers: + Date: "Wed, 15 Jan 2025 11:19:28 GMT" + Content-Type: application/json + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "425" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9994" + x-ratelimit-remaining-tokens: "199921" + x-ratelimit-reset-requests: 51.776s + x-ratelimit-reset-tokens: 23ms + x-request-id: req_807eb46408ee64b75533fed721c20a31 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 9025788d4bc237d4-FRA +uuid: 86161b62-8793-4a43-9231-2c0c4719f212 +persistent: true +insertionIndex: 46 +--- +id: ca660e30-0e44-46fa-9133-7eaf446e9ad2 +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", + "role" : "user" + } ], + "model" : "gpt-4o-mini" + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "id": "chatcmpl-Apviihciys1xpuAyFIR97rqKK8gfi", + "object": "chat.completion", + "created": 1736939968, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Southern Ocean", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 22, + "completion_tokens": 3, + "total_tokens": 25, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_bd83329f63" + } + headers: + Date: "Wed, 15 Jan 2025 11:19:28 GMT" + Content-Type: application/json + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "195" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9993" + x-ratelimit-remaining-tokens: "199967" + x-ratelimit-reset-requests: 59.512s + x-ratelimit-reset-tokens: 9ms + x-request-id: req_53cec6fba0cc88410bb818e78d4c843e + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 90257892fe73dcba-FRA +uuid: ca660e30-0e44-46fa-9133-7eaf446e9ad2 +persistent: true +insertionIndex: 57 +--- +id: 358a4259-7ba6-49f9-b4bf-e2884e69e80b +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "stream_options" : { + "include_usage" : true + }, + "stream" : true + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: |+ + data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null} + + data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"South"},"logprobs":null,"finish_reason":null}],"usage":null} + + data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Atlantic"},"logprobs":null,"finish_reason":null}],"usage":null} + + data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Ocean"},"logprobs":null,"finish_reason":null}],"usage":null} + + data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}],"usage":null} + + data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null} + + data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[],"usage":{"prompt_tokens":22,"completion_tokens":4,"total_tokens":26,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}}} + + data: [DONE] + + headers: + Date: "Wed, 15 Jan 2025 11:19:29 GMT" + Content-Type: text/event-stream; charset=utf-8 + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "314" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9992" + x-ratelimit-remaining-tokens: "199967" + x-ratelimit-reset-requests: 1m7.606s + x-ratelimit-reset-tokens: 9ms + x-request-id: req_bff585d123188cdc9028a65bacdb0538 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 902578965e1bdc5a-FRA +uuid: 358a4259-7ba6-49f9-b4bf-e2884e69e80b +persistent: true +insertionIndex: 69 +--- +id: cebd91eb-be1a-4246-91be-a5a518feb0a9 +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "You are a helpful customer support assistant. Use the supplied tools to assist the user.", + "role" : "system" + }, { + "content" : "Hi, can you tell me the delivery date for my order?", + "role" : "user" + }, { + "content" : "Hi there! I can help with that. Can you please provide your order ID?", + "role" : "assistant" + }, { + "content" : "i think it is order_12345", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "tools" : [ { + "type" : "function", + "function" : { + "description" : "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'", + "name" : "get_delivery_date", + "parameters" : { + "type" : "object", + "required" : [ "order_id" ], + "additionalProperties" : false, + "properties" : { + "order_id" : { + "description" : "The customer's order ID.", + "type" : "string" + } + } + } + } + } ] + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "id": "chatcmpl-Apvile0yBKxo1xS0aexIJkmR0Qjn1", + "object": "chat.completion", + "created": 1736939971, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_q1MEs5sEuhADqC1WRvne6VRk", + "type": "function", + "function": { + "name": "get_delivery_date", + "arguments": "{\"order_id\":\"order_12345\"}" + } + } + ], + "refusal": null + }, + "logprobs": null, + "finish_reason": "tool_calls" + } + ], + "usage": { + "prompt_tokens": 140, + "completion_tokens": 20, + "total_tokens": 160, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_72ed7ab54c" + } + headers: + Date: "Wed, 15 Jan 2025 11:19:32 GMT" + Content-Type: application/json + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "515" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9991" + x-ratelimit-remaining-tokens: "199921" + x-ratelimit-reset-requests: 1m13.799s + x-ratelimit-reset-tokens: 23ms + x-request-id: req_9e1675aea9843f1e0c7d216f96f89855 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 902578a5ad1a691f-FRA +uuid: cebd91eb-be1a-4246-91be-a5a518feb0a9 +persistent: true +insertionIndex: 94 +--- +id: 33095242-9962-4d27-81cb-09b6cfa2e8da +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "stream" : true + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: |+ + data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"South"},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Atlantic"},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Ocean"},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + + data: [DONE] + + headers: + Date: "Wed, 15 Jan 2025 11:19:32 GMT" + Content-Type: text/event-stream; charset=utf-8 + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "255" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9990" + x-ratelimit-remaining-tokens: "199967" + x-ratelimit-reset-requests: 1m21.437s + x-ratelimit-reset-tokens: 9ms + x-request-id: req_947d002fda44ce948ff47b98637e1715 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 902578abdf5d4d43-FRA +uuid: 33095242-9962-4d27-81cb-09b6cfa2e8da +persistent: true +insertionIndex: 108 +--- +id: 8995a2ce-d896-4e5c-9e7a-337ca6f09eba +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "n" : 2 + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "id": "chatcmpl-ApvinZ5v2SpojKFd7QM2eCcASAnl5", + "object": "chat.completion", + "created": 1736939973, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Atlantic Ocean.", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + }, + { + "index": 1, + "message": { + "role": "assistant", + "content": "South Atlantic Ocean.", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 22, + "completion_tokens": 9, + "total_tokens": 31, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_72ed7ab54c" + } + headers: + Date: "Wed, 15 Jan 2025 11:19:33 GMT" + Content-Type: application/json + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "465" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9989" + x-ratelimit-remaining-tokens: "199952" + x-ratelimit-reset-requests: 1m29.236s + x-ratelimit-reset-tokens: 14ms + x-request-id: req_b61dcec32e2c5fb3e358673fedff3c90 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 902578b12a68366f-FRA +uuid: 8995a2ce-d896-4e5c-9e7a-337ca6f09eba +persistent: true +insertionIndex: 123 +--- +id: 59d444c7-bc5a-497a-8a5d-74864d7d11c0 +name: chat_completions +request: + url: /chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "You are a helpful customer support assistant. Use the supplied tools to assist the user.", + "role" : "system" + }, { + "content" : "Hi, can you tell me the delivery date for my order?", + "role" : "user" + }, { + "content" : "Hi there! I can help with that. Can you please provide your order ID?", + "role" : "assistant" + }, { + "content" : "i think it is order_12345", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "tools" : [ { + "type" : "function", + "function" : { + "description" : "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'", + "name" : "get_delivery_date", + "parameters" : { + "type" : "object", + "required" : [ "order_id" ], + "additionalProperties" : false, + "properties" : { + "order_id" : { + "description" : "The customer's order ID.", + "type" : "string" + } + } + } + } + } ], + "stream" : true + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: |+ + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_U1rxxS68HLPkdVOpTVYDUtQz","type":"function","function":{"name":"get_delivery_date","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"order"}}]},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_id"}}]},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"order"}}]},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_"}}]},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"123"}}]},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"45"}}]},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]} + + data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} + + data: [DONE] + + headers: + Date: "Wed, 15 Jan 2025 11:19:34 GMT" + Content-Type: text/event-stream; charset=utf-8 + access-control-expose-headers: X-Request-ID + openai-organization: test_openai_org_id + openai-processing-ms: "376" + openai-version: 2020-10-01 + x-ratelimit-limit-requests: "10000" + x-ratelimit-limit-tokens: "200000" + x-ratelimit-remaining-requests: "9988" + x-ratelimit-remaining-tokens: "199921" + x-ratelimit-reset-requests: 1m37.071s + x-ratelimit-reset-tokens: 23ms + x-request-id: req_4adf8d6eda1aba18b3f09004daae99b6 + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 902578b63b448ecb-FRA +uuid: 59d444c7-bc5a-497a-8a5d-74864d7d11c0 +persistent: true +insertionIndex: 139 +--- +id: 02ad43a7-4627-4cc7-b5e6-538c9ff83b53 +name: bad-url_chat_completions +request: + url: /bad-url/chat/completions + method: POST + bodyPatterns: + - equalToJson: |- + { + "messages" : [ { + "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", + "role" : "user" + } ], + "model" : "gpt-4o-mini", + "stream" : true + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 404 + body: "\r\n404 Not Found\r\n\r\n

404\ + \ Not Found

\r\n
nginx
\r\n\r\n\r\ + \n" + headers: + Date: "Wed, 15 Jan 2025 11:19:35 GMT" + Content-Type: text/html + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 902578bc1bf1dbd3-FRA +uuid: 02ad43a7-4627-4cc7-b5e6-538c9ff83b53 +persistent: true +insertionIndex: 156 diff --git a/instrumentation/openai-client-instrumentation/testing-common/src/main/resources/mappings/embeddingstest.yaml b/instrumentation/openai-client-instrumentation/testing-common/src/main/resources/mappings/embeddingstest.yaml new file mode 100644 index 00000000..0d6acbe7 --- /dev/null +++ b/instrumentation/openai-client-instrumentation/testing-common/src/main/resources/mappings/embeddingstest.yaml @@ -0,0 +1,100 @@ +--- +id: dce3d5d1-803c-48f5-97cb-537b7aa4ac63 +name: embeddings +request: + url: /embeddings + method: POST + bodyPatterns: + - equalToJson: |- + { + "input" : [ "South Atlantic Ocean." ], + "model" : "text-embedding-3-small", + "encoding_format" : "base64" + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 200 + body: | + { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": "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" + } + ], + "model": "text-embedding-3-small", + "usage": { + "prompt_tokens": 4, + "total_tokens": 4 + } + } + headers: + Date: "Wed, 15 Jan 2025 11:25:55 GMT" + Content-Type: application/json + access-control-allow-origin: '*' + access-control-expose-headers: X-Request-ID + openai-model: text-embedding-3-small + openai-organization: test_openai_org_id + openai-processing-ms: "72" + openai-version: 2020-10-01 + strict-transport-security: max-age=31536000; includeSubDomains; preload + via: envoy-router-6546796fb4-hp5kf + x-envoy-upstream-service-time: "56" + x-ratelimit-limit-requests: "3000" + x-ratelimit-limit-tokens: "1000000" + x-ratelimit-remaining-requests: "2999" + x-ratelimit-remaining-tokens: "999994" + x-ratelimit-reset-requests: 20ms + x-ratelimit-reset-tokens: 0s + x-request-id: req_b8c83a88703f7772c7b23dc39f18f7e8 + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 90258201890a6910-FRA +uuid: dce3d5d1-803c-48f5-97cb-537b7aa4ac63 +persistent: true +insertionIndex: 32 +--- +id: 74958f37-7bf6-4130-bc0a-dcef6236e896 +name: embeddings +request: + url: /embeddings + method: POST + bodyPatterns: + - equalToJson: |- + { + "input" : [ "South Atlantic Ocean." ], + "model" : "not-a-model", + "encoding_format" : "base64" + } + ignoreArrayOrder: false + ignoreExtraElements: false +response: + status: 404 + body: | + { + "error": { + "message": "The model `not-a-model` does not exist or you do not have access to it.", + "type": "invalid_request_error", + "param": null, + "code": "model_not_found" + } + } + headers: + Date: "Wed, 15 Jan 2025 11:25:56 GMT" + Content-Type: application/json; charset=utf-8 + vary: Origin + x-request-id: req_a973c97036d0f21f3598a32bf29f9d6d + strict-transport-security: max-age=31536000; includeSubDomains; preload + CF-Cache-Status: DYNAMIC + Set-Cookie: test_set_cookie + X-Content-Type-Options: nosniff + Server: cloudflare + CF-RAY: 90258206bf14dbbf-FRA +uuid: 74958f37-7bf6-4130-bc0a-dcef6236e896 +persistent: true +insertionIndex: 51 diff --git a/settings.gradle.kts b/settings.gradle.kts index e744603c..ad150326 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,7 +17,9 @@ include("agentextension") include("bootstrap") include("custom") include("instrumentation") -include("instrumentation:openai-client-instrumentation") +include("instrumentation:openai-client-instrumentation:common") +include("instrumentation:openai-client-instrumentation:testing-common") +include("instrumentation:openai-client-instrumentation:instrumentation-0.13.0") include("inferred-spans") include("resources") include("smoke-tests") From 3640ff5b352a3ee0d2c41d6326a300345ff4e699 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 5 Feb 2025 10:02:14 +0100 Subject: [PATCH 2/9] Add support for openai client 0.14+ --- custom/build.gradle.kts | 3 +- gradle/libs.versions.toml | 2 +- .../otel/openai/wrappers/ApiAdapter.java | 87 ++ .../wrappers/ChatCompletionEventsHelper.java | 57 +- .../InstrumentedChatCompletionService.java | 8 +- .../instrumentation-0.13.0/build.gradle.kts | 15 +- .../otel/openai/v0_13_0/ApiAdapterImpl.java | 94 ++ .../OpenAiClientInstrumentationModule.java | 52 + .../elastic/otel/openai/v0_13_0/ChatTest.java | 75 + .../EmbeddingsTest.java} | 6 +- .../src/test/resources/mappings/chattest.yaml | 1239 ----------------- .../resources/mappings/embeddingstest.yaml | 100 -- .../instrumentation-latest/build.gradle.kts | 24 + .../otel/openai/latest/ApiAdapterImpl.java | 82 ++ .../OpenAiClientInstrumentationModule.java | 9 +- .../elastic/otel/openai/latest/ChatTest.java | 75 + .../otel/openai/latest}/EmbeddingsTest.java | 4 +- .../latest}/LiveAPIChatIntegrationTest.java | 20 +- .../co/elastic/otel/openai/ChatTestBase.java | 48 +- .../otel/openai/EmbeddingsTestBase.java | 2 +- settings.gradle.kts | 1 + 21 files changed, 569 insertions(+), 1434 deletions(-) create mode 100644 instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java create mode 100644 instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java create mode 100644 instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java create mode 100644 instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java rename instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/{ChatTest.java => v0_13_0/EmbeddingsTest.java} (85%) delete mode 100644 instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/chattest.yaml delete mode 100644 instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/embeddingstest.yaml create mode 100644 instrumentation/openai-client-instrumentation/instrumentation-latest/build.gradle.kts create mode 100644 instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java rename instrumentation/openai-client-instrumentation/{instrumentation-0.13.0/src/main/java/co/elastic/otel/openai => instrumentation-latest/src/main/java/co/elastic/otel/openai/latest}/OpenAiClientInstrumentationModule.java (85%) create mode 100644 instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java rename instrumentation/openai-client-instrumentation/{instrumentation-0.13.0/src/test/java/co/elastic/otel/openai => instrumentation-latest/src/test/java/co/elastic/otel/openai/latest}/EmbeddingsTest.java (90%) rename instrumentation/openai-client-instrumentation/{instrumentation-0.13.0/src/test/java/co/elastic/otel/openai => instrumentation-latest/src/test/java/co/elastic/otel/openai/latest}/LiveAPIChatIntegrationTest.java (98%) diff --git a/custom/build.gradle.kts b/custom/build.gradle.kts index 43548d2a..85b5eac4 100644 --- a/custom/build.gradle.kts +++ b/custom/build.gradle.kts @@ -3,7 +3,8 @@ plugins { } val instrumentations = listOf( - ":instrumentation:openai-client-instrumentation:instrumentation-0.13.0" + ":instrumentation:openai-client-instrumentation:instrumentation-0.13.0", + ":instrumentation:openai-client-instrumentation:instrumentation-latest" ) dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5d151235..edf4bf19 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -77,7 +77,7 @@ ant = "org.apache.ant:ant:1.10.15" asm = "org.ow2.asm:asm:9.7" # Instrumented libraries -openaiClient = "com.openai:openai-java:0.13.0" +openaiClient = "com.openai:openai-java:0.20.0" [bundles] diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java new file mode 100644 index 00000000..76eabb80 --- /dev/null +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java @@ -0,0 +1,87 @@ +package co.elastic.otel.openai.wrappers; + +import com.openai.models.ChatCompletionAssistantMessageParam; +import com.openai.models.ChatCompletionContentPart; +import com.openai.models.ChatCompletionCreateParams; +import com.openai.models.ChatCompletionMessageParam; +import com.openai.models.ChatCompletionSystemMessageParam; +import com.openai.models.ChatCompletionToolMessageParam; +import com.openai.models.ChatCompletionUserMessageParam; + +/** + * Api Adapter to encapsulate breaking changes across openai-client versions. + * If e.g. methods are renamed we add a adapter method here, so that we can provide + * per-version implementations. These implementations have to be added to instrumentations as helpers, + * which also ensures muzzle works effectively. + */ +public abstract class ApiAdapter { + + public static final ApiAdapter INSTANCE = lookupAdapter(); + + private static ApiAdapter lookupAdapter() { + Class implClass = tryLookupClass("co.elastic.otel.openai.v0_13_0.ApiAdapterImpl"); + if (implClass == null) { + implClass = tryLookupClass("co.elastic.otel.openai.latest.ApiAdapterImpl"); + } + if (implClass == null) { + throw new IllegalStateException( + "No Adapter implementation found in instrumentation helpers!"); + } + try { + return (ApiAdapter) implClass.getConstructor().newInstance(); + } catch (Exception e) { + throw new IllegalStateException("Failed to instantiate adapter", e); + } + } + + /** + * Extracts the concrete message object e.g. ({@link ChatCompletionUserMessageParam}) + * from the given encapsulating {@link ChatCompletionMessageParam}. + * + * @param base the encapsulating param + * @return the unboxed concrete message param type + */ + public abstract Object extractConcreteCompletionMessageParam(ChatCompletionMessageParam base); + + /** + * @return the contained text, if the content is next. null otherwise. + */ + public abstract String asText(ChatCompletionToolMessageParam.Content content); + + /** + * @return the contained text, if the content is next. null otherwise. + */ + public abstract String asText(ChatCompletionAssistantMessageParam.Content content); + + /** + * @return the contained text, if the content is next. null otherwise. + */ + public abstract String asText(ChatCompletionSystemMessageParam.Content content); + + /** + * @return the contained text, if the content is next. null otherwise. + */ + public abstract String asText(ChatCompletionUserMessageParam.Content content); + + /** + * @return the text or refusal reason if either is available, otherwise null + */ + public abstract String extractTextOrRefusal( + ChatCompletionAssistantMessageParam.Content.ChatCompletionRequestAssistantMessageContentPart part); + + /** + * @return the text if available, otherwise null + */ + public abstract String extractText(ChatCompletionContentPart part); + + public abstract String extractType(ChatCompletionCreateParams.ResponseFormat val); + + private static Class tryLookupClass(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + return null; + } + } + +} diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java index 015b736b..fadb7886 100644 --- a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java @@ -22,7 +22,6 @@ import com.openai.models.ChatCompletion; import com.openai.models.ChatCompletionAssistantMessageParam; -import com.openai.models.ChatCompletionContentPart; import com.openai.models.ChatCompletionContentPartText; import com.openai.models.ChatCompletionCreateParams; import com.openai.models.ChatCompletionMessage; @@ -40,6 +39,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; public class ChatCompletionEventsHelper { @@ -54,24 +54,25 @@ public static void emitPromptLogEvents( if (!settings.emitEvents) { return; } + for (ChatCompletionMessageParam msg : request.messages()) { String eventType; MapValueBuilder bodyBuilder = new MapValueBuilder(); - if (msg.isChatCompletionSystemMessageParam()) { - ChatCompletionSystemMessageParam sysMsg = msg.asChatCompletionSystemMessageParam(); + Object concreteMessageParam = ApiAdapter.INSTANCE.extractConcreteCompletionMessageParam(msg); + if (concreteMessageParam instanceof ChatCompletionSystemMessageParam) { + ChatCompletionSystemMessageParam sysMsg = (ChatCompletionSystemMessageParam) concreteMessageParam; eventType = "gen_ai.system.message"; if (settings.captureMessageContent) { putIfNotEmpty(bodyBuilder, "content", contentToString(sysMsg.content())); } - } else if (msg.isChatCompletionUserMessageParam()) { - ChatCompletionUserMessageParam userMsg = msg.asChatCompletionUserMessageParam(); + } else if (concreteMessageParam instanceof ChatCompletionUserMessageParam) { + ChatCompletionUserMessageParam userMsg = (ChatCompletionUserMessageParam) concreteMessageParam; eventType = "gen_ai.user.message"; if (settings.captureMessageContent) { putIfNotEmpty(bodyBuilder, "content", contentToString(userMsg.content())); } - } else if (msg.isChatCompletionAssistantMessageParam()) { - ChatCompletionAssistantMessageParam assistantMsg = - msg.asChatCompletionAssistantMessageParam(); + } else if (concreteMessageParam instanceof ChatCompletionAssistantMessageParam) { + ChatCompletionAssistantMessageParam assistantMsg = (ChatCompletionAssistantMessageParam) concreteMessageParam; eventType = "gen_ai.assistant.message"; if (settings.captureMessageContent) { assistantMsg @@ -89,8 +90,8 @@ public static void emitPromptLogEvents( bodyBuilder.put("tool_calls", Value.of(toolCallsJson)); }); } - } else if (msg.isChatCompletionToolMessageParam()) { - ChatCompletionToolMessageParam toolMsg = msg.asChatCompletionToolMessageParam(); + } else if (concreteMessageParam instanceof ChatCompletionToolMessageParam) { + ChatCompletionToolMessageParam toolMsg = (ChatCompletionToolMessageParam) concreteMessageParam; eventType = "gen_ai.tool.message"; if (settings.captureMessageContent) { putIfNotEmpty(bodyBuilder, "content", contentToString(toolMsg.content())); @@ -110,8 +111,9 @@ private static void putIfNotEmpty(MapValueBuilder bodyBuilder, String key, Strin } private static String contentToString(ChatCompletionToolMessageParam.Content content) { - if (content.isTextContent()) { - return content.asTextContent(); + String text = ApiAdapter.INSTANCE.asText(content); + if (text != null) { + return text; } else if (content.isArrayOfContentParts()) { return content.asArrayOfContentParts().stream() .map(ChatCompletionContentPartText::text) @@ -122,19 +124,13 @@ private static String contentToString(ChatCompletionToolMessageParam.Content con } private static String contentToString(ChatCompletionAssistantMessageParam.Content content) { - if (content.isTextContent()) { - return content.asTextContent(); + String text = ApiAdapter.INSTANCE.asText(content); + if (text != null) { + return text; } else if (content.isArrayOfContentParts()) { return content.asArrayOfContentParts().stream() - .map( - cnt -> { - if (cnt.isChatCompletionContentPartText()) { - return cnt.asChatCompletionContentPartText().text(); - } else if (cnt.isChatCompletionContentPartRefusal()) { - return cnt.asChatCompletionContentPartRefusal().refusal(); - } - return ""; - }) + .map(ApiAdapter.INSTANCE::extractTextOrRefusal) + .filter(Objects::nonNull) .collect(Collectors.joining()); } else { throw new IllegalStateException("Unhandled content type for " + content); @@ -142,8 +138,9 @@ private static String contentToString(ChatCompletionAssistantMessageParam.Conten } private static String contentToString(ChatCompletionSystemMessageParam.Content content) { - if (content.isTextContent()) { - return content.asTextContent(); + String text = ApiAdapter.INSTANCE.asText(content); + if (text != null) { + return text; } else if (content.isArrayOfContentParts()) { return content.asArrayOfContentParts().stream() .map(ChatCompletionContentPartText::text) @@ -154,13 +151,13 @@ private static String contentToString(ChatCompletionSystemMessageParam.Content c } private static String contentToString(ChatCompletionUserMessageParam.Content content) { - if (content.isTextContent()) { - return content.asTextContent(); + String text = ApiAdapter.INSTANCE.asText(content); + if (text != null) { + return text; } else if (content.isArrayOfContentParts()) { return content.asArrayOfContentParts().stream() - .filter(ChatCompletionContentPart::isChatCompletionContentPartText) - .map(ChatCompletionContentPart::asChatCompletionContentPartText) - .map(ChatCompletionContentPartText::text) + .map(ApiAdapter.INSTANCE::extractText) + .filter(Objects::nonNull) .collect(Collectors.joining()); } else { throw new IllegalStateException("Unhandled content type for " + content); diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java index 412045d6..de5e015d 100644 --- a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java @@ -137,11 +137,9 @@ public void onStart( .responseFormat() .ifPresent( val -> { - if (val.isResponseFormatText()) { - attributes.put( - GEN_AI_OPENAI_REQUEST_RESPONSE_FORMAT, - val.asResponseFormatText()._type().toString()); - } + attributes.put( + GEN_AI_OPENAI_REQUEST_RESPONSE_FORMAT, + ApiAdapter.INSTANCE.extractType(val)); }); } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/build.gradle.kts b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/build.gradle.kts index c9be9004..63da0932 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/build.gradle.kts +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/build.gradle.kts @@ -4,20 +4,21 @@ plugins { id("elastic-otel.instrumentation-conventions") } +val openAiVersion = "0.13.0"; // DO NOT UPGRADE + dependencies { + compileOnly("com.openai:openai-java:${openAiVersion}") implementation(project(":instrumentation:openai-client-instrumentation:common")) - testImplementation(catalog.openaiClient) + testImplementation("com.openai:openai-java:${openAiVersion}") testImplementation(project(":instrumentation:openai-client-instrumentation:testing-common")) } muzzle { pass { - val openaiClientLib = catalog.openaiClient.get() - group.set(openaiClientLib.group) - module.set(openaiClientLib.name) - versions.set("(,${openaiClientLib.version}]") - // no assertInverse.set(true) here because we don't want muzzle to fail for newer releases on our main branch - // instead, renovate will bump the version and failures will be automatically detected on that bump PR + group.set("com.openai") + module.set("openai-java") + versions.set("(,${openAiVersion}]") + assertInverse.set(true) } } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java new file mode 100644 index 00000000..f46a0798 --- /dev/null +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java @@ -0,0 +1,94 @@ +package co.elastic.otel.openai.v0_13_0; + +import co.elastic.otel.openai.wrappers.ApiAdapter; +import com.openai.models.ChatCompletionAssistantMessageParam; +import com.openai.models.ChatCompletionContentPart; +import com.openai.models.ChatCompletionCreateParams; +import com.openai.models.ChatCompletionMessageParam; +import com.openai.models.ChatCompletionSystemMessageParam; +import com.openai.models.ChatCompletionToolMessageParam; +import com.openai.models.ChatCompletionUserMessageParam; + +public class ApiAdapterImpl extends ApiAdapter { + + @Override + public Object extractConcreteCompletionMessageParam(ChatCompletionMessageParam base) { + if (base.isChatCompletionSystemMessageParam()) { + return base.asChatCompletionSystemMessageParam(); + } + if (base.isChatCompletionUserMessageParam()) { + return base.asChatCompletionUserMessageParam(); + } + if (base.isChatCompletionAssistantMessageParam()) { + return base.asChatCompletionAssistantMessageParam(); + } + if (base.isChatCompletionToolMessageParam()) { + return base.asChatCompletionToolMessageParam(); + } + throw new IllegalStateException("Unhandled message param type: " + base); + } + + @Override + public String extractText(ChatCompletionContentPart part) { + if (part.isChatCompletionContentPartText()) { + return part.asChatCompletionContentPartText().text(); + } + return null; + } + + @Override + public String extractType(ChatCompletionCreateParams.ResponseFormat val) { + if (val.isResponseFormatText()) { + return val.asResponseFormatText()._type().toString(); + } else if (val.isResponseFormatJsonObject()) { + return val.asResponseFormatJsonObject()._type().toString(); + } else if (val.isResponseFormatJsonSchema()) { + return val.asResponseFormatJsonSchema()._type().toString(); + } + return ""; + } + + @Override + public String extractTextOrRefusal( + ChatCompletionAssistantMessageParam.Content.ChatCompletionRequestAssistantMessageContentPart part) { + if (part.isChatCompletionContentPartText()) { + return part.asChatCompletionContentPartText().text(); + } + if (part.isChatCompletionContentPartRefusal()) { + return part.asChatCompletionContentPartRefusal().refusal(); + } + return null; + } + + @Override + public String asText(ChatCompletionUserMessageParam.Content content) { + if (content.isTextContent()) { + return content.asTextContent(); + } + return ""; + } + + @Override + public String asText(ChatCompletionSystemMessageParam.Content content) { + if (content.isTextContent()) { + return content.asTextContent(); + } + return ""; + } + + @Override + public String asText(ChatCompletionAssistantMessageParam.Content content) { + if (content.isTextContent()) { + return content.asTextContent(); + } + return ""; + } + + @Override + public String asText(ChatCompletionToolMessageParam.Content content) { + if (content.isTextContent()) { + return content.asTextContent(); + } + return ""; + } +} diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java new file mode 100644 index 00000000..ee6e84d0 --- /dev/null +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 co.elastic.otel.openai.v0_13_0; + +import co.elastic.otel.openai.OpenAiOkHttpClientBuilderInstrumentation; +import co.elastic.otel.openai.wrappers.Constants; +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.Collections; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class OpenAiClientInstrumentationModule extends InstrumentationModule { + + public OpenAiClientInstrumentationModule() { + super(Constants.INSTRUMENTATION_NAME); + } + + @Override + public List typeInstrumentations() { + return Collections.singletonList(new OpenAiOkHttpClientBuilderInstrumentation()); + } + + @Override + public boolean isHelperClass(String className) { + return className.startsWith("co.elastic.otel.openai"); + } + + @Override + public List getAdditionalHelperClassNames() { + return Collections.singletonList("co.elastic.otel.openai.v0_13_0.ApiAdapterImpl"); + } + + +} diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java new file mode 100644 index 00000000..7021d5c0 --- /dev/null +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 co.elastic.otel.openai.v0_13_0; + +import co.elastic.otel.openai.ChatTestBase; +import com.openai.models.ChatCompletionAssistantMessageParam; +import com.openai.models.ChatCompletionMessageParam; +import com.openai.models.ChatCompletionMessageToolCall; +import com.openai.models.ChatCompletionSystemMessageParam; +import com.openai.models.ChatCompletionToolMessageParam; +import com.openai.models.ChatCompletionUserMessageParam; +import java.util.List; + +class ChatTest extends ChatTestBase { + + @Override + protected ChatCompletionMessageParam createAssistantMessage(String content) { + return ChatCompletionMessageParam.ofChatCompletionAssistantMessageParam( + ChatCompletionAssistantMessageParam.builder() + .content(ChatCompletionAssistantMessageParam.Content.ofTextContent(content)) + .build()); + } + + @Override + protected ChatCompletionMessageParam createAssistantMessage( + List toolCalls) { + return ChatCompletionMessageParam.ofChatCompletionAssistantMessageParam( + ChatCompletionAssistantMessageParam.builder() + .content(ChatCompletionAssistantMessageParam.Content.ofTextContent("")) + .toolCalls(toolCalls) + .build()); + } + + @Override + protected ChatCompletionMessageParam createUserMessage(String content) { + return ChatCompletionMessageParam.ofChatCompletionUserMessageParam( + ChatCompletionUserMessageParam.builder() + .content(ChatCompletionUserMessageParam.Content.ofTextContent(content)) + .build()); + } + + @Override + protected ChatCompletionMessageParam createSystemMessage(String content) { + return ChatCompletionMessageParam.ofChatCompletionSystemMessageParam( + ChatCompletionSystemMessageParam.builder() + .content(ChatCompletionSystemMessageParam.Content.ofTextContent(content)) + .build()); + } + + @Override + protected ChatCompletionMessageParam createToolMessage(String response, String id) { + return ChatCompletionMessageParam.ofChatCompletionToolMessageParam( + ChatCompletionToolMessageParam.builder() + .toolCallId(id) + .content(ChatCompletionToolMessageParam.Content.ofTextContent(response)) + .build()); + } + +} diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/ChatTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/EmbeddingsTest.java similarity index 85% rename from instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/ChatTest.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/EmbeddingsTest.java index c496b25a..33149517 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/ChatTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/EmbeddingsTest.java @@ -16,8 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai; +package co.elastic.otel.openai.v0_13_0; -class ChatTest extends ChatTestBase { +import co.elastic.otel.openai.EmbeddingsTestBase; + +class EmbeddingsTest extends EmbeddingsTestBase { } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/chattest.yaml b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/chattest.yaml deleted file mode 100644 index 3d8bf361..00000000 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/chattest.yaml +++ /dev/null @@ -1,1239 +0,0 @@ ---- -id: 3f9cba30-8e45-408c-8f93-8a2f28fc1147 -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "frequency_penalty" : 0, - "max_tokens" : 100, - "presence_penalty" : 0, - "response_format" : { - "type" : "text" - }, - "seed" : 100, - "stop" : [ "foo" ], - "temperature" : 1, - "top_p" : 1 - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: | - { - "id": "chatcmpl-ApviQlqj8fahybjC0N8MSkPiFt2ls", - "object": "chat.completion", - "created": 1736939950, - "model": "gpt-4o-mini-2024-07-18", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "Southern Ocean.", - "refusal": null - }, - "logprobs": null, - "finish_reason": "stop" - } - ], - "usage": { - "prompt_tokens": 22, - "completion_tokens": 3, - "total_tokens": 25, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0 - }, - "completion_tokens_details": { - "reasoning_tokens": 0, - "audio_tokens": 0, - "accepted_prediction_tokens": 0, - "rejected_prediction_tokens": 0 - } - }, - "service_tier": "default", - "system_fingerprint": "fp_72ed7ab54c" - } - headers: - Date: "Wed, 15 Jan 2025 11:19:10 GMT" - Content-Type: application/json - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "168" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9999" - x-ratelimit-remaining-tokens: "199883" - x-ratelimit-reset-requests: 8.64s - x-ratelimit-reset-tokens: 34ms - x-request-id: req_6c7842b1c3f10b8637bb26050a52edb4 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 902578210b55d269-FRA -uuid: 3f9cba30-8e45-408c-8f93-8a2f28fc1147 -persistent: true -insertionIndex: 2 ---- -id: 1d03b2c6-450b-4dfc-8c66-65007ac6662b -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", - "role" : "user" - } ], - "model" : "gpt-4o-mini" - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: | - { - "id": "chatcmpl-ApviRmVhVukgmmFTXvHvlvgmBf6Fr", - "object": "chat.completion", - "created": 1736939951, - "model": "gpt-4o-mini-2024-07-18", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "South Atlantic Ocean.", - "refusal": null - }, - "logprobs": null, - "finish_reason": "stop" - } - ], - "usage": { - "prompt_tokens": 22, - "completion_tokens": 5, - "total_tokens": 27, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0 - }, - "completion_tokens_details": { - "reasoning_tokens": 0, - "audio_tokens": 0, - "accepted_prediction_tokens": 0, - "rejected_prediction_tokens": 0 - } - }, - "service_tier": "default", - "system_fingerprint": "fp_72ed7ab54c" - } - headers: - Date: "Wed, 15 Jan 2025 11:19:11 GMT" - Content-Type: application/json - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "454" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9998" - x-ratelimit-remaining-tokens: "199967" - x-ratelimit-reset-requests: 16.283s - x-ratelimit-reset-tokens: 9ms - x-request-id: req_ea9e3f4cc3b7f2162e1451ccd969e674 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 902578271bdc30f6-FRA -uuid: 1d03b2c6-450b-4dfc-8c66-65007ac6662b -persistent: true -insertionIndex: 6 ---- -id: 4d01d762-0339-43c1-8dc8-24b44e8df5ea -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "You are a helpful assistant providing weather updates.", - "role" : "system" - }, { - "content" : "What is the weather in New York City and London?", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "tools" : [ { - "type" : "function", - "function" : { - "name" : "get_weather", - "parameters" : { - "type" : "object", - "required" : [ "location" ], - "additionalProperties" : false, - "properties" : { - "location" : { - "description" : "The location to get the current temperature for", - "type" : "string" - } - } - } - } - } ] - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: | - { - "id": "chatcmpl-ApviSs94mF5FrHZHISwvYcpDclw4O", - "object": "chat.completion", - "created": 1736939952, - "model": "gpt-4o-mini-2024-07-18", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": null, - "tool_calls": [ - { - "id": "call_praw3bINJa8YgORLR1j5YqAd", - "type": "function", - "function": { - "name": "get_weather", - "arguments": "{\"location\": \"New York City\"}" - } - }, - { - "id": "call_wBr1xgbiWQaqY0D0enFzmkR1", - "type": "function", - "function": { - "name": "get_weather", - "arguments": "{\"location\": \"London\"}" - } - } - ], - "refusal": null - }, - "logprobs": null, - "finish_reason": "tool_calls" - } - ], - "usage": { - "prompt_tokens": 67, - "completion_tokens": 47, - "total_tokens": 114, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0 - }, - "completion_tokens_details": { - "reasoning_tokens": 0, - "audio_tokens": 0, - "accepted_prediction_tokens": 0, - "rejected_prediction_tokens": 0 - } - }, - "service_tier": "default", - "system_fingerprint": "fp_bd83329f63" - } - headers: - Date: "Wed, 15 Jan 2025 11:19:13 GMT" - Content-Type: application/json - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "965" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9997" - x-ratelimit-remaining-tokens: "199956" - x-ratelimit-reset-requests: 24.043s - x-ratelimit-reset-tokens: 13ms - x-request-id: req_461add22df2e5e30c211828be358cd63 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 9025782c9cf31da6-FRA -uuid: 4d01d762-0339-43c1-8dc8-24b44e8df5ea -persistent: true -insertionIndex: 11 ---- -id: 0f1e52b2-bf92-4f44-8223-5dd7d93bca36 -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "You are a helpful assistant providing weather updates.", - "role" : "system" - }, { - "content" : "What is the weather in New York City and London?", - "role" : "user" - }, { - "content" : "", - "role" : "assistant", - "tool_calls" : [ { - "id" : "call_praw3bINJa8YgORLR1j5YqAd", - "type" : "function", - "function" : { - "name" : "get_weather", - "arguments" : "{\"location\": \"New York City\"}" - } - }, { - "id" : "call_wBr1xgbiWQaqY0D0enFzmkR1", - "type" : "function", - "function" : { - "name" : "get_weather", - "arguments" : "{\"location\": \"London\"}" - } - } ] - }, { - "role" : "tool", - "content" : "25 degrees and sunny", - "tool_call_id" : "call_praw3bINJa8YgORLR1j5YqAd" - }, { - "role" : "tool", - "content" : "15 degrees and raining", - "tool_call_id" : "call_wBr1xgbiWQaqY0D0enFzmkR1" - } ], - "model" : "gpt-4o-mini" - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: | - { - "id": "chatcmpl-ApviUidJQ9YrYP5xeNzIt8EVfYbFW", - "object": "chat.completion", - "created": 1736939954, - "model": "gpt-4o-mini-2024-07-18", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "The current weather in New York City is 25 degrees Celsius and sunny. In London, it is 15 degrees Celsius and raining.", - "refusal": null - }, - "logprobs": null, - "finish_reason": "stop" - } - ], - "usage": { - "prompt_tokens": 99, - "completion_tokens": 27, - "total_tokens": 126, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0 - }, - "completion_tokens_details": { - "reasoning_tokens": 0, - "audio_tokens": 0, - "accepted_prediction_tokens": 0, - "rejected_prediction_tokens": 0 - } - }, - "service_tier": "default", - "system_fingerprint": "fp_72ed7ab54c" - } - headers: - Date: "Wed, 15 Jan 2025 11:19:14 GMT" - Content-Type: application/json - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "1200" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9996" - x-ratelimit-remaining-tokens: "199943" - x-ratelimit-reset-requests: 31.232s - x-ratelimit-reset-tokens: 17ms - x-request-id: req_feb030ccccced2bf6afe7c40c72d7411 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 90257835aaeb3603-FRA -uuid: 0f1e52b2-bf92-4f44-8223-5dd7d93bca36 -persistent: true -insertionIndex: 12 ---- -id: fcb1f170-582b-4213-8905-088801bd5da1 -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", - "role" : "user" - } ], - "model" : "gpt-4o-mini" - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: | - { - "id": "chatcmpl-ApvifdVpIRcgHgDW6sFspxbSUOPji", - "object": "chat.completion", - "created": 1736939965, - "model": "gpt-4o-mini-2024-07-18", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "South Atlantic Ocean.", - "refusal": null - }, - "logprobs": null, - "finish_reason": "stop" - } - ], - "usage": { - "prompt_tokens": 22, - "completion_tokens": 5, - "total_tokens": 27, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0 - }, - "completion_tokens_details": { - "reasoning_tokens": 0, - "audio_tokens": 0, - "accepted_prediction_tokens": 0, - "rejected_prediction_tokens": 0 - } - }, - "service_tier": "default", - "system_fingerprint": "fp_72ed7ab54c" - } - headers: - Date: "Wed, 15 Jan 2025 11:19:25 GMT" - Content-Type: application/json - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "289" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9996" - x-ratelimit-remaining-tokens: "199967" - x-ratelimit-reset-requests: 28.224s - x-ratelimit-reset-tokens: 9ms - x-request-id: req_558428698c1c501afd73150c5949a976 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 9025787e89d9903a-FRA -uuid: fcb1f170-582b-4213-8905-088801bd5da1 -persistent: true -insertionIndex: 19 ---- -id: f1c29c6d-77f6-416b-8d67-3af3bf41748c -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "stream" : true - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: |+ - data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"South"},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Atlantic"},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Ocean"},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvigJsNSpkH4VnSxDSWzfAyc8uy4","object":"chat.completion.chunk","created":1736939966,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - - data: [DONE] - - headers: - Date: "Wed, 15 Jan 2025 11:19:26 GMT" - Content-Type: text/event-stream; charset=utf-8 - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "320" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9995" - x-ratelimit-remaining-tokens: "199967" - x-ratelimit-reset-requests: 36.155s - x-ratelimit-reset-tokens: 9ms - x-request-id: req_04002595aba2c59a6bc5ec701a16c2da - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 90257882d8ff9730-FRA -uuid: f1c29c6d-77f6-416b-8d67-3af3bf41748c -persistent: true -insertionIndex: 27 ---- -id: 6c176fd9-33ef-43dc-95e1-b41302ac459b -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "frequency_penalty" : 0, - "max_tokens" : 100, - "presence_penalty" : 0, - "response_format" : { - "type" : "text" - }, - "seed" : 100, - "stop" : [ "foo" ], - "temperature" : 1, - "top_p" : 1, - "stream" : true - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: |+ - data: {"id":"chatcmpl-ApvihZr5if54NlG1FkM0FLYadGaZW","object":"chat.completion.chunk","created":1736939967,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_bd83329f63","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvihZr5if54NlG1FkM0FLYadGaZW","object":"chat.completion.chunk","created":1736939967,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_bd83329f63","choices":[{"index":0,"delta":{"content":"Southern"},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvihZr5if54NlG1FkM0FLYadGaZW","object":"chat.completion.chunk","created":1736939967,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_bd83329f63","choices":[{"index":0,"delta":{"content":" Ocean"},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvihZr5if54NlG1FkM0FLYadGaZW","object":"chat.completion.chunk","created":1736939967,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_bd83329f63","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvihZr5if54NlG1FkM0FLYadGaZW","object":"chat.completion.chunk","created":1736939967,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_bd83329f63","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - - data: [DONE] - - headers: - Date: "Wed, 15 Jan 2025 11:19:27 GMT" - Content-Type: text/event-stream; charset=utf-8 - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "232" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9994" - x-ratelimit-remaining-tokens: "199883" - x-ratelimit-reset-requests: 43.871s - x-ratelimit-reset-tokens: 34ms - x-request-id: req_143a0a089d19b5550f77633653d093c7 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 90257888caa4dc74-FRA -uuid: 6c176fd9-33ef-43dc-95e1-b41302ac459b -persistent: true -insertionIndex: 36 ---- -id: 86161b62-8793-4a43-9231-2c0c4719f212 -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "You are a helpful customer support assistant. Use the supplied tools to assist the user.", - "role" : "system" - }, { - "content" : "Hi, can you tell me the delivery date for my order?", - "role" : "user" - }, { - "content" : "Hi there! I can help with that. Can you please provide your order ID?", - "role" : "assistant" - }, { - "content" : "i think it is order_12345", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "tools" : [ { - "type" : "function", - "function" : { - "description" : "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'", - "name" : "get_delivery_date", - "parameters" : { - "type" : "object", - "required" : [ "order_id" ], - "additionalProperties" : false, - "properties" : { - "order_id" : { - "description" : "The customer's order ID.", - "type" : "string" - } - } - } - } - } ] - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: | - { - "id": "chatcmpl-ApvihK7kXkNWtzppIkF4fFwxNUlfq", - "object": "chat.completion", - "created": 1736939967, - "model": "gpt-4o-mini-2024-07-18", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": null, - "tool_calls": [ - { - "id": "call_FmRndu0ugAh8YjL9oUoJhWAh", - "type": "function", - "function": { - "name": "get_delivery_date", - "arguments": "{\"order_id\":\"order_12345\"}" - } - } - ], - "refusal": null - }, - "logprobs": null, - "finish_reason": "tool_calls" - } - ], - "usage": { - "prompt_tokens": 140, - "completion_tokens": 20, - "total_tokens": 160, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0 - }, - "completion_tokens_details": { - "reasoning_tokens": 0, - "audio_tokens": 0, - "accepted_prediction_tokens": 0, - "rejected_prediction_tokens": 0 - } - }, - "service_tier": "default", - "system_fingerprint": "fp_72ed7ab54c" - } - headers: - Date: "Wed, 15 Jan 2025 11:19:28 GMT" - Content-Type: application/json - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "425" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9994" - x-ratelimit-remaining-tokens: "199921" - x-ratelimit-reset-requests: 51.776s - x-ratelimit-reset-tokens: 23ms - x-request-id: req_807eb46408ee64b75533fed721c20a31 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 9025788d4bc237d4-FRA -uuid: 86161b62-8793-4a43-9231-2c0c4719f212 -persistent: true -insertionIndex: 46 ---- -id: ca660e30-0e44-46fa-9133-7eaf446e9ad2 -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", - "role" : "user" - } ], - "model" : "gpt-4o-mini" - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: | - { - "id": "chatcmpl-Apviihciys1xpuAyFIR97rqKK8gfi", - "object": "chat.completion", - "created": 1736939968, - "model": "gpt-4o-mini-2024-07-18", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "Southern Ocean", - "refusal": null - }, - "logprobs": null, - "finish_reason": "stop" - } - ], - "usage": { - "prompt_tokens": 22, - "completion_tokens": 3, - "total_tokens": 25, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0 - }, - "completion_tokens_details": { - "reasoning_tokens": 0, - "audio_tokens": 0, - "accepted_prediction_tokens": 0, - "rejected_prediction_tokens": 0 - } - }, - "service_tier": "default", - "system_fingerprint": "fp_bd83329f63" - } - headers: - Date: "Wed, 15 Jan 2025 11:19:28 GMT" - Content-Type: application/json - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "195" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9993" - x-ratelimit-remaining-tokens: "199967" - x-ratelimit-reset-requests: 59.512s - x-ratelimit-reset-tokens: 9ms - x-request-id: req_53cec6fba0cc88410bb818e78d4c843e - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 90257892fe73dcba-FRA -uuid: ca660e30-0e44-46fa-9133-7eaf446e9ad2 -persistent: true -insertionIndex: 57 ---- -id: 358a4259-7ba6-49f9-b4bf-e2884e69e80b -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "stream_options" : { - "include_usage" : true - }, - "stream" : true - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: |+ - data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null} - - data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"South"},"logprobs":null,"finish_reason":null}],"usage":null} - - data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Atlantic"},"logprobs":null,"finish_reason":null}],"usage":null} - - data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Ocean"},"logprobs":null,"finish_reason":null}],"usage":null} - - data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}],"usage":null} - - data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null} - - data: {"id":"chatcmpl-ApvijNaIbvnhtW8P4xCz9SJjFD4Uq","object":"chat.completion.chunk","created":1736939969,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[],"usage":{"prompt_tokens":22,"completion_tokens":4,"total_tokens":26,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}}} - - data: [DONE] - - headers: - Date: "Wed, 15 Jan 2025 11:19:29 GMT" - Content-Type: text/event-stream; charset=utf-8 - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "314" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9992" - x-ratelimit-remaining-tokens: "199967" - x-ratelimit-reset-requests: 1m7.606s - x-ratelimit-reset-tokens: 9ms - x-request-id: req_bff585d123188cdc9028a65bacdb0538 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 902578965e1bdc5a-FRA -uuid: 358a4259-7ba6-49f9-b4bf-e2884e69e80b -persistent: true -insertionIndex: 69 ---- -id: cebd91eb-be1a-4246-91be-a5a518feb0a9 -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "You are a helpful customer support assistant. Use the supplied tools to assist the user.", - "role" : "system" - }, { - "content" : "Hi, can you tell me the delivery date for my order?", - "role" : "user" - }, { - "content" : "Hi there! I can help with that. Can you please provide your order ID?", - "role" : "assistant" - }, { - "content" : "i think it is order_12345", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "tools" : [ { - "type" : "function", - "function" : { - "description" : "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'", - "name" : "get_delivery_date", - "parameters" : { - "type" : "object", - "required" : [ "order_id" ], - "additionalProperties" : false, - "properties" : { - "order_id" : { - "description" : "The customer's order ID.", - "type" : "string" - } - } - } - } - } ] - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: | - { - "id": "chatcmpl-Apvile0yBKxo1xS0aexIJkmR0Qjn1", - "object": "chat.completion", - "created": 1736939971, - "model": "gpt-4o-mini-2024-07-18", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": null, - "tool_calls": [ - { - "id": "call_q1MEs5sEuhADqC1WRvne6VRk", - "type": "function", - "function": { - "name": "get_delivery_date", - "arguments": "{\"order_id\":\"order_12345\"}" - } - } - ], - "refusal": null - }, - "logprobs": null, - "finish_reason": "tool_calls" - } - ], - "usage": { - "prompt_tokens": 140, - "completion_tokens": 20, - "total_tokens": 160, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0 - }, - "completion_tokens_details": { - "reasoning_tokens": 0, - "audio_tokens": 0, - "accepted_prediction_tokens": 0, - "rejected_prediction_tokens": 0 - } - }, - "service_tier": "default", - "system_fingerprint": "fp_72ed7ab54c" - } - headers: - Date: "Wed, 15 Jan 2025 11:19:32 GMT" - Content-Type: application/json - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "515" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9991" - x-ratelimit-remaining-tokens: "199921" - x-ratelimit-reset-requests: 1m13.799s - x-ratelimit-reset-tokens: 23ms - x-request-id: req_9e1675aea9843f1e0c7d216f96f89855 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 902578a5ad1a691f-FRA -uuid: cebd91eb-be1a-4246-91be-a5a518feb0a9 -persistent: true -insertionIndex: 94 ---- -id: 33095242-9962-4d27-81cb-09b6cfa2e8da -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "stream" : true - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: |+ - data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"South"},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Atlantic"},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":" Ocean"},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvimUEw4ey6z52Vo4z76UTUiPTAF","object":"chat.completion.chunk","created":1736939972,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - - data: [DONE] - - headers: - Date: "Wed, 15 Jan 2025 11:19:32 GMT" - Content-Type: text/event-stream; charset=utf-8 - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "255" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9990" - x-ratelimit-remaining-tokens: "199967" - x-ratelimit-reset-requests: 1m21.437s - x-ratelimit-reset-tokens: 9ms - x-request-id: req_947d002fda44ce948ff47b98637e1715 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 902578abdf5d4d43-FRA -uuid: 33095242-9962-4d27-81cb-09b6cfa2e8da -persistent: true -insertionIndex: 108 ---- -id: 8995a2ce-d896-4e5c-9e7a-337ca6f09eba -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "n" : 2 - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: | - { - "id": "chatcmpl-ApvinZ5v2SpojKFd7QM2eCcASAnl5", - "object": "chat.completion", - "created": 1736939973, - "model": "gpt-4o-mini-2024-07-18", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "Atlantic Ocean.", - "refusal": null - }, - "logprobs": null, - "finish_reason": "stop" - }, - { - "index": 1, - "message": { - "role": "assistant", - "content": "South Atlantic Ocean.", - "refusal": null - }, - "logprobs": null, - "finish_reason": "stop" - } - ], - "usage": { - "prompt_tokens": 22, - "completion_tokens": 9, - "total_tokens": 31, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0 - }, - "completion_tokens_details": { - "reasoning_tokens": 0, - "audio_tokens": 0, - "accepted_prediction_tokens": 0, - "rejected_prediction_tokens": 0 - } - }, - "service_tier": "default", - "system_fingerprint": "fp_72ed7ab54c" - } - headers: - Date: "Wed, 15 Jan 2025 11:19:33 GMT" - Content-Type: application/json - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "465" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9989" - x-ratelimit-remaining-tokens: "199952" - x-ratelimit-reset-requests: 1m29.236s - x-ratelimit-reset-tokens: 14ms - x-request-id: req_b61dcec32e2c5fb3e358673fedff3c90 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 902578b12a68366f-FRA -uuid: 8995a2ce-d896-4e5c-9e7a-337ca6f09eba -persistent: true -insertionIndex: 123 ---- -id: 59d444c7-bc5a-497a-8a5d-74864d7d11c0 -name: chat_completions -request: - url: /chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "You are a helpful customer support assistant. Use the supplied tools to assist the user.", - "role" : "system" - }, { - "content" : "Hi, can you tell me the delivery date for my order?", - "role" : "user" - }, { - "content" : "Hi there! I can help with that. Can you please provide your order ID?", - "role" : "assistant" - }, { - "content" : "i think it is order_12345", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "tools" : [ { - "type" : "function", - "function" : { - "description" : "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'", - "name" : "get_delivery_date", - "parameters" : { - "type" : "object", - "required" : [ "order_id" ], - "additionalProperties" : false, - "properties" : { - "order_id" : { - "description" : "The customer's order ID.", - "type" : "string" - } - } - } - } - } ], - "stream" : true - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: |+ - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_U1rxxS68HLPkdVOpTVYDUtQz","type":"function","function":{"name":"get_delivery_date","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"order"}}]},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_id"}}]},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"order"}}]},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_"}}]},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"123"}}]},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"45"}}]},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]} - - data: {"id":"chatcmpl-ApvioLCkN5XjQxUO9zFlOUUG3dAHq","object":"chat.completion.chunk","created":1736939974,"model":"gpt-4o-mini-2024-07-18","service_tier":"default","system_fingerprint":"fp_72ed7ab54c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} - - data: [DONE] - - headers: - Date: "Wed, 15 Jan 2025 11:19:34 GMT" - Content-Type: text/event-stream; charset=utf-8 - access-control-expose-headers: X-Request-ID - openai-organization: test_openai_org_id - openai-processing-ms: "376" - openai-version: 2020-10-01 - x-ratelimit-limit-requests: "10000" - x-ratelimit-limit-tokens: "200000" - x-ratelimit-remaining-requests: "9988" - x-ratelimit-remaining-tokens: "199921" - x-ratelimit-reset-requests: 1m37.071s - x-ratelimit-reset-tokens: 23ms - x-request-id: req_4adf8d6eda1aba18b3f09004daae99b6 - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 902578b63b448ecb-FRA -uuid: 59d444c7-bc5a-497a-8a5d-74864d7d11c0 -persistent: true -insertionIndex: 139 ---- -id: 02ad43a7-4627-4cc7-b5e6-538c9ff83b53 -name: bad-url_chat_completions -request: - url: /bad-url/chat/completions - method: POST - bodyPatterns: - - equalToJson: |- - { - "messages" : [ { - "content" : "Answer in up to 3 words: Which ocean contains Bouvet Island?", - "role" : "user" - } ], - "model" : "gpt-4o-mini", - "stream" : true - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 404 - body: "\r\n404 Not Found\r\n\r\n

404\ - \ Not Found

\r\n
nginx
\r\n\r\n\r\ - \n" - headers: - Date: "Wed, 15 Jan 2025 11:19:35 GMT" - Content-Type: text/html - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 902578bc1bf1dbd3-FRA -uuid: 02ad43a7-4627-4cc7-b5e6-538c9ff83b53 -persistent: true -insertionIndex: 156 diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/embeddingstest.yaml b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/embeddingstest.yaml deleted file mode 100644 index 0d6acbe7..00000000 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/resources/mappings/embeddingstest.yaml +++ /dev/null @@ -1,100 +0,0 @@ ---- -id: dce3d5d1-803c-48f5-97cb-537b7aa4ac63 -name: embeddings -request: - url: /embeddings - method: POST - bodyPatterns: - - equalToJson: |- - { - "input" : [ "South Atlantic Ocean." ], - "model" : "text-embedding-3-small", - "encoding_format" : "base64" - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 200 - body: | - { - "object": "list", - "data": [ - { - "object": "embedding", - "index": 0, - "embedding": "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" - } - ], - "model": "text-embedding-3-small", - "usage": { - "prompt_tokens": 4, - "total_tokens": 4 - } - } - headers: - Date: "Wed, 15 Jan 2025 11:25:55 GMT" - Content-Type: application/json - access-control-allow-origin: '*' - access-control-expose-headers: X-Request-ID - openai-model: text-embedding-3-small - openai-organization: test_openai_org_id - openai-processing-ms: "72" - openai-version: 2020-10-01 - strict-transport-security: max-age=31536000; includeSubDomains; preload - via: envoy-router-6546796fb4-hp5kf - x-envoy-upstream-service-time: "56" - x-ratelimit-limit-requests: "3000" - x-ratelimit-limit-tokens: "1000000" - x-ratelimit-remaining-requests: "2999" - x-ratelimit-remaining-tokens: "999994" - x-ratelimit-reset-requests: 20ms - x-ratelimit-reset-tokens: 0s - x-request-id: req_b8c83a88703f7772c7b23dc39f18f7e8 - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 90258201890a6910-FRA -uuid: dce3d5d1-803c-48f5-97cb-537b7aa4ac63 -persistent: true -insertionIndex: 32 ---- -id: 74958f37-7bf6-4130-bc0a-dcef6236e896 -name: embeddings -request: - url: /embeddings - method: POST - bodyPatterns: - - equalToJson: |- - { - "input" : [ "South Atlantic Ocean." ], - "model" : "not-a-model", - "encoding_format" : "base64" - } - ignoreArrayOrder: false - ignoreExtraElements: false -response: - status: 404 - body: | - { - "error": { - "message": "The model `not-a-model` does not exist or you do not have access to it.", - "type": "invalid_request_error", - "param": null, - "code": "model_not_found" - } - } - headers: - Date: "Wed, 15 Jan 2025 11:25:56 GMT" - Content-Type: application/json; charset=utf-8 - vary: Origin - x-request-id: req_a973c97036d0f21f3598a32bf29f9d6d - strict-transport-security: max-age=31536000; includeSubDomains; preload - CF-Cache-Status: DYNAMIC - Set-Cookie: test_set_cookie - X-Content-Type-Options: nosniff - Server: cloudflare - CF-RAY: 90258206bf14dbbf-FRA -uuid: 74958f37-7bf6-4130-bc0a-dcef6236e896 -persistent: true -insertionIndex: 51 diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/build.gradle.kts b/instrumentation/openai-client-instrumentation/instrumentation-latest/build.gradle.kts new file mode 100644 index 00000000..698bab67 --- /dev/null +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + alias(catalog.plugins.muzzleGeneration) + alias(catalog.plugins.muzzleCheck) + id("elastic-otel.instrumentation-conventions") +} + +dependencies { + compileOnly(catalog.openaiClient) + implementation(project(":instrumentation:openai-client-instrumentation:common")) + + testImplementation(catalog.openaiClient) + testImplementation(project(":instrumentation:openai-client-instrumentation:testing-common")) +} + +muzzle { + pass { + val openaiClientLib = catalog.openaiClient.get() + group.set(openaiClientLib.group) + module.set(openaiClientLib.name) + versions.set("(0.13.0,${openaiClientLib.version}]") + // no assertInverse.set(true) here because we don't want muzzle to fail for newer releases on our main branch + // instead, renovate will bump the version and failures will be automatically detected on that bump PR + } +} diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java new file mode 100644 index 00000000..3ef4f42b --- /dev/null +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java @@ -0,0 +1,82 @@ +package co.elastic.otel.openai.latest; + +import co.elastic.otel.openai.wrappers.ApiAdapter; +import com.openai.models.ChatCompletionAssistantMessageParam; +import com.openai.models.ChatCompletionContentPart; +import com.openai.models.ChatCompletionCreateParams; +import com.openai.models.ChatCompletionMessageParam; +import com.openai.models.ChatCompletionSystemMessageParam; +import com.openai.models.ChatCompletionToolMessageParam; +import com.openai.models.ChatCompletionUserMessageParam; + +public class ApiAdapterImpl extends ApiAdapter { + + @Override + public Object extractConcreteCompletionMessageParam(ChatCompletionMessageParam base) { + if (base.isSystem()) { + return base.asSystem(); + } + if (base.isUser()) { + return base.asUser(); + } + if (base.isAssistant()) { + return base.asAssistant(); + } + if (base.isTool()) { + return base.asTool(); + } + throw new IllegalStateException("Unhandled message param type: " + base); + } + + @Override + public String asText(ChatCompletionToolMessageParam.Content content) { + return content.isText() ? content.asText() : null; + } + + @Override + public String asText(ChatCompletionAssistantMessageParam.Content content) { + return content.isText() ? content.asText() : null; + } + + @Override + public String asText(ChatCompletionSystemMessageParam.Content content) { + return content.isText() ? content.asText() : null; + } + + @Override + public String asText(ChatCompletionUserMessageParam.Content content) { + return content.isText() ? content.asText() : null; + } + + @Override + public String extractTextOrRefusal( + ChatCompletionAssistantMessageParam.Content.ChatCompletionRequestAssistantMessageContentPart part) { + if (part.isText()) { + return part.asText().text(); + } + if (part.isRefusal()) { + return part.asRefusal().refusal(); + } + return null; + } + + @Override + public String extractText(ChatCompletionContentPart part) { + return part.isText() ? part.asText().text() : null; + } + + @Override + public String extractType(ChatCompletionCreateParams.ResponseFormat val) { + if (val.isText()) { + return val.asText()._type().toString(); + } + if (val.isJsonObject()) { + return val.asJsonObject()._type().toString(); + } + if (val.isJsonSchema()) { + return val.asJsonSchema()._type().toString(); + } + return ""; + } + +} diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/OpenAiClientInstrumentationModule.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java similarity index 85% rename from instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/OpenAiClientInstrumentationModule.java rename to instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java index 5e87fe7e..75d9eda1 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/OpenAiClientInstrumentationModule.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java @@ -16,8 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai; +package co.elastic.otel.openai.latest; +import co.elastic.otel.openai.OpenAiOkHttpClientBuilderInstrumentation; import co.elastic.otel.openai.wrappers.Constants; import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; @@ -41,4 +42,10 @@ public List typeInstrumentations() { public boolean isHelperClass(String className) { return className.startsWith("co.elastic.otel.openai"); } + + @Override + public List getAdditionalHelperClassNames() { + return Collections.singletonList("co.elastic.otel.openai.latest.ApiAdapterImpl"); + } + } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java new file mode 100644 index 00000000..113bf18f --- /dev/null +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 co.elastic.otel.openai.latest; + +import co.elastic.otel.openai.ChatTestBase; +import com.openai.models.ChatCompletionAssistantMessageParam; +import com.openai.models.ChatCompletionMessageParam; +import com.openai.models.ChatCompletionMessageToolCall; +import com.openai.models.ChatCompletionSystemMessageParam; +import com.openai.models.ChatCompletionToolMessageParam; +import com.openai.models.ChatCompletionUserMessageParam; +import java.util.List; + +class ChatTest extends ChatTestBase { + + @Override + protected ChatCompletionMessageParam createAssistantMessage(String content) { + return ChatCompletionMessageParam.ofAssistant( + ChatCompletionAssistantMessageParam.builder() + .content(ChatCompletionAssistantMessageParam.Content.ofText(content)) + .build()); + } + + @Override + protected ChatCompletionMessageParam createAssistantMessage( + List toolCalls) { + return ChatCompletionMessageParam.ofAssistant( + ChatCompletionAssistantMessageParam.builder() + .content(ChatCompletionAssistantMessageParam.Content.ofText("")) + .toolCalls(toolCalls) + .build()); + } + + @Override + protected ChatCompletionMessageParam createUserMessage(String content) { + return ChatCompletionMessageParam.ofUser( + ChatCompletionUserMessageParam.builder() + .content(ChatCompletionUserMessageParam.Content.ofText(content)) + .build()); + } + + @Override + protected ChatCompletionMessageParam createSystemMessage(String content) { + return ChatCompletionMessageParam.ofSystem( + ChatCompletionSystemMessageParam.builder() + .content(ChatCompletionSystemMessageParam.Content.ofText(content)) + .build()); + } + + @Override + protected ChatCompletionMessageParam createToolMessage(String response, String id) { + return ChatCompletionMessageParam.ofTool( + ChatCompletionToolMessageParam.builder() + .toolCallId(id) + .content(ChatCompletionToolMessageParam.Content.ofText(response)) + .build()); + } + +} diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/EmbeddingsTest.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/EmbeddingsTest.java similarity index 90% rename from instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/EmbeddingsTest.java rename to instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/EmbeddingsTest.java index 2901acaa..2f60231f 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/EmbeddingsTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/EmbeddingsTest.java @@ -16,7 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai; +package co.elastic.otel.openai.latest; + +import co.elastic.otel.openai.EmbeddingsTestBase; class EmbeddingsTest extends EmbeddingsTestBase { diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/LiveAPIChatIntegrationTest.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/LiveAPIChatIntegrationTest.java similarity index 98% rename from instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/LiveAPIChatIntegrationTest.java rename to instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/LiveAPIChatIntegrationTest.java index 11d521ef..1679bbea 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/LiveAPIChatIntegrationTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/LiveAPIChatIntegrationTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai; +package co.elastic.otel.openai.latest; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; @@ -32,6 +32,8 @@ import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_USAGE_INPUT_TOKENS; import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_USAGE_OUTPUT_TOKENS; +import co.elastic.otel.openai.ChatTestBase; +import co.elastic.otel.openai.ValAssert; import co.elastic.otel.openai.wrappers.InstrumentationSettingsAccessor; import com.openai.client.OpenAIClient; import com.openai.client.okhttp.OpenAIOkHttpClient; @@ -576,31 +578,31 @@ void streamWithCaptureMessageContent() throws Exception { } private static ChatCompletionMessageParam createAssistantMessage(String content) { - return ChatCompletionMessageParam.ofChatCompletionAssistantMessageParam( + return ChatCompletionMessageParam.ofAssistant( ChatCompletionAssistantMessageParam.builder() - .content(ChatCompletionAssistantMessageParam.Content.ofTextContent(content)) + .content(ChatCompletionAssistantMessageParam.Content.ofText(content)) .build()); } private static ChatCompletionMessageParam createUserMessage(String content) { - return ChatCompletionMessageParam.ofChatCompletionUserMessageParam( + return ChatCompletionMessageParam.ofUser( ChatCompletionUserMessageParam.builder() - .content(ChatCompletionUserMessageParam.Content.ofTextContent(content)) + .content(ChatCompletionUserMessageParam.Content.ofText(content)) .build()); } private static ChatCompletionMessageParam createSystemMessage(String content) { - return ChatCompletionMessageParam.ofChatCompletionSystemMessageParam( + return ChatCompletionMessageParam.ofSystem( ChatCompletionSystemMessageParam.builder() - .content(ChatCompletionSystemMessageParam.Content.ofTextContent(content)) + .content(ChatCompletionSystemMessageParam.Content.ofText(content)) .build()); } private static ChatCompletionMessageParam createToolMessage(String response, String id) { - return ChatCompletionMessageParam.ofChatCompletionToolMessageParam( + return ChatCompletionMessageParam.ofTool( ChatCompletionToolMessageParam.builder() .toolCallId(id) - .content(ChatCompletionToolMessageParam.Content.ofTextContent(response)) + .content(ChatCompletionToolMessageParam.Content.ofText(response)) .build()); } diff --git a/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java index d481a4c6..3f8eac49 100644 --- a/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java +++ b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java @@ -53,16 +53,12 @@ import com.openai.errors.NotFoundException; import com.openai.errors.OpenAIIoException; import com.openai.models.ChatCompletion; -import com.openai.models.ChatCompletionAssistantMessageParam; import com.openai.models.ChatCompletionChunk; import com.openai.models.ChatCompletionCreateParams; import com.openai.models.ChatCompletionMessageParam; import com.openai.models.ChatCompletionMessageToolCall; import com.openai.models.ChatCompletionStreamOptions; -import com.openai.models.ChatCompletionSystemMessageParam; import com.openai.models.ChatCompletionTool; -import com.openai.models.ChatCompletionToolMessageParam; -import com.openai.models.ChatCompletionUserMessageParam; import com.openai.models.FunctionDefinition; import com.openai.models.FunctionParameters; import com.openai.models.ResponseFormatText; @@ -86,7 +82,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -class ChatTestBase { +public abstract class ChatTestBase { private static final String TEST_CHAT_MODEL = "gpt-4o-mini"; private static final String TEST_CHAT_RESPONSE_MODEL = "gpt-4o-mini-2024-07-18"; private static final String TEST_CHAT_INPUT = @@ -2045,12 +2041,7 @@ void toolsWithFollowupAndCaptureContent() { equalTo(SERVER_PORT, (long) openai.getPort()))))); testing.clearData(); - ChatCompletionMessageParam assistantMessage = - ChatCompletionMessageParam.ofChatCompletionAssistantMessageParam( - ChatCompletionAssistantMessageParam.builder() - .content(ChatCompletionAssistantMessageParam.Content.ofTextContent("")) - .toolCalls(toolCalls) - .build()); + ChatCompletionMessageParam assistantMessage = createAssistantMessage(toolCalls); chatMessages.add(assistantMessage); chatMessages.add(createToolMessage("25 degrees and sunny", newYorkCallId)); @@ -2247,6 +2238,7 @@ void toolsWithFollowupAndCaptureContent() { equalTo(SERVER_PORT, (long) openai.getPort()))))); } + @Test void disableEvents() { // Override the enablement from setup() @@ -2315,7 +2307,7 @@ private static ChatCompletionTool buildGetWeatherToolDefinition() { .build(); } - static ChatCompletionTool buildGetDeliveryDateToolDefinition() { + public static ChatCompletionTool buildGetDeliveryDateToolDefinition() { Map orderId = new HashMap<>(); orderId.put("type", JsonValue.from("string")); orderId.put("description", JsonValue.from("The customer's order ID.")); @@ -2343,32 +2335,14 @@ static ChatCompletionTool buildGetDeliveryDateToolDefinition() { .build(); } - private static ChatCompletionMessageParam createAssistantMessage(String content) { - return ChatCompletionMessageParam.ofChatCompletionAssistantMessageParam( - ChatCompletionAssistantMessageParam.builder() - .content(ChatCompletionAssistantMessageParam.Content.ofTextContent(content)) - .build()); - } + protected abstract ChatCompletionMessageParam createAssistantMessage(String content); - private static ChatCompletionMessageParam createUserMessage(String content) { - return ChatCompletionMessageParam.ofChatCompletionUserMessageParam( - ChatCompletionUserMessageParam.builder() - .content(ChatCompletionUserMessageParam.Content.ofTextContent(content)) - .build()); - } + protected abstract ChatCompletionMessageParam createAssistantMessage( + List toolCalls); - private static ChatCompletionMessageParam createSystemMessage(String content) { - return ChatCompletionMessageParam.ofChatCompletionSystemMessageParam( - ChatCompletionSystemMessageParam.builder() - .content(ChatCompletionSystemMessageParam.Content.ofTextContent(content)) - .build()); - } + protected abstract ChatCompletionMessageParam createUserMessage(String content); - private static ChatCompletionMessageParam createToolMessage(String response, String id) { - return ChatCompletionMessageParam.ofChatCompletionToolMessageParam( - ChatCompletionToolMessageParam.builder() - .toolCallId(id) - .content(ChatCompletionToolMessageParam.Content.ofTextContent(response)) - .build()); - } + protected abstract ChatCompletionMessageParam createSystemMessage(String content); + + protected abstract ChatCompletionMessageParam createToolMessage(String response, String id); } diff --git a/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/EmbeddingsTestBase.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/EmbeddingsTestBase.java index bf311190..dcf2ac67 100644 --- a/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/EmbeddingsTestBase.java +++ b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/EmbeddingsTestBase.java @@ -40,7 +40,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -class EmbeddingsTestBase { +public class EmbeddingsTestBase { private static final String MODEL = System.getenv().getOrDefault("OPENAI_MODEL", "text-embedding-3-small"); diff --git a/settings.gradle.kts b/settings.gradle.kts index ad150326..c796ed5d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,7 @@ include("instrumentation") include("instrumentation:openai-client-instrumentation:common") include("instrumentation:openai-client-instrumentation:testing-common") include("instrumentation:openai-client-instrumentation:instrumentation-0.13.0") +include("instrumentation:openai-client-instrumentation:instrumentation-latest") include("inferred-spans") include("resources") include("smoke-tests") From ff54d5f21c2ffcb12b4ef93327e865d2b30bd5f7 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 5 Feb 2025 11:25:25 +0100 Subject: [PATCH 3/9] Fix muzzle reference parsing and add classloader matchers --- gradle/libs.versions.toml | 2 +- .../otel/openai/wrappers/ApiAdapter.java | 61 ++++++++++++------- .../wrappers/ChatCompletionEventsHelper.java | 26 ++++---- .../InstrumentedChatCompletionService.java | 2 +- .../wrappers/InstrumentedOpenAiClient.java | 4 +- .../otel/openai/v0_13_0/ApiAdapterImpl.java | 25 +++++++- .../OpenAiClientInstrumentationModule.java | 20 +++--- ...nAiOkHttpClientBuilderInstrumentation.java | 58 ++++++++++++++++++ .../elastic/otel/openai/v0_13_0/ChatTest.java | 1 - .../otel/openai/v0_13_0/EmbeddingsTest.java | 4 +- .../otel/openai/latest/ApiAdapterImpl.java | 26 +++++++- .../OpenAiClientInstrumentationModule.java | 18 +++--- ...nAiOkHttpClientBuilderInstrumentation.java | 7 ++- .../elastic/otel/openai/latest/ChatTest.java | 1 - .../otel/openai/latest/EmbeddingsTest.java | 4 +- .../co/elastic/otel/openai/ChatTestBase.java | 1 - .../otel/openai/OpenAIRecordingExtension.java | 4 +- 17 files changed, 196 insertions(+), 68 deletions(-) create mode 100644 instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiOkHttpClientBuilderInstrumentation.java rename instrumentation/openai-client-instrumentation/{common/src/main/java/co/elastic/otel/openai => instrumentation-latest/src/main/java/co/elastic/otel/openai/latest}/OpenAiOkHttpClientBuilderInstrumentation.java (89%) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index edf4bf19..58072603 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -77,7 +77,7 @@ ant = "org.apache.ant:ant:1.10.15" asm = "org.ow2.asm:asm:9.7" # Instrumented libraries -openaiClient = "com.openai:openai-java:0.20.0" +openaiClient = "com.openai:openai-java:0.21.0" [bundles] diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java index 76eabb80..8fde3680 100644 --- a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 co.elastic.otel.openai.wrappers; import com.openai.models.ChatCompletionAssistantMessageParam; @@ -7,36 +25,35 @@ import com.openai.models.ChatCompletionSystemMessageParam; import com.openai.models.ChatCompletionToolMessageParam; import com.openai.models.ChatCompletionUserMessageParam; +import java.util.function.Supplier; /** - * Api Adapter to encapsulate breaking changes across openai-client versions. - * If e.g. methods are renamed we add a adapter method here, so that we can provide - * per-version implementations. These implementations have to be added to instrumentations as helpers, - * which also ensures muzzle works effectively. + * Api Adapter to encapsulate breaking changes across openai-client versions. If e.g. methods are + * renamed we add a adapter method here, so that we can provide per-version implementations. These + * implementations have to be added to instrumentations as helpers, which also ensures muzzle works + * effectively. */ public abstract class ApiAdapter { - public static final ApiAdapter INSTANCE = lookupAdapter(); + private static volatile ApiAdapter instance; - private static ApiAdapter lookupAdapter() { - Class implClass = tryLookupClass("co.elastic.otel.openai.v0_13_0.ApiAdapterImpl"); - if (implClass == null) { - implClass = tryLookupClass("co.elastic.otel.openai.latest.ApiAdapterImpl"); - } - if (implClass == null) { - throw new IllegalStateException( - "No Adapter implementation found in instrumentation helpers!"); - } - try { - return (ApiAdapter) implClass.getConstructor().newInstance(); - } catch (Exception e) { - throw new IllegalStateException("Failed to instantiate adapter", e); + public static ApiAdapter get() { + return instance; + } + + protected static void init(Supplier implementation) { + if (instance == null) { + synchronized (ApiAdapter.class) { + if (instance == null) { + instance = implementation.get(); + } + } } } /** - * Extracts the concrete message object e.g. ({@link ChatCompletionUserMessageParam}) - * from the given encapsulating {@link ChatCompletionMessageParam}. + * Extracts the concrete message object e.g. ({@link ChatCompletionUserMessageParam}) from the + * given encapsulating {@link ChatCompletionMessageParam}. * * @param base the encapsulating param * @return the unboxed concrete message param type @@ -67,7 +84,8 @@ private static ApiAdapter lookupAdapter() { * @return the text or refusal reason if either is available, otherwise null */ public abstract String extractTextOrRefusal( - ChatCompletionAssistantMessageParam.Content.ChatCompletionRequestAssistantMessageContentPart part); + ChatCompletionAssistantMessageParam.Content.ChatCompletionRequestAssistantMessageContentPart + part); /** * @return the text if available, otherwise null @@ -83,5 +101,4 @@ private static Class tryLookupClass(String className) { return null; } } - } diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java index fadb7886..683390b6 100644 --- a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ChatCompletionEventsHelper.java @@ -58,21 +58,24 @@ public static void emitPromptLogEvents( for (ChatCompletionMessageParam msg : request.messages()) { String eventType; MapValueBuilder bodyBuilder = new MapValueBuilder(); - Object concreteMessageParam = ApiAdapter.INSTANCE.extractConcreteCompletionMessageParam(msg); + Object concreteMessageParam = ApiAdapter.get().extractConcreteCompletionMessageParam(msg); if (concreteMessageParam instanceof ChatCompletionSystemMessageParam) { - ChatCompletionSystemMessageParam sysMsg = (ChatCompletionSystemMessageParam) concreteMessageParam; + ChatCompletionSystemMessageParam sysMsg = + (ChatCompletionSystemMessageParam) concreteMessageParam; eventType = "gen_ai.system.message"; if (settings.captureMessageContent) { putIfNotEmpty(bodyBuilder, "content", contentToString(sysMsg.content())); } } else if (concreteMessageParam instanceof ChatCompletionUserMessageParam) { - ChatCompletionUserMessageParam userMsg = (ChatCompletionUserMessageParam) concreteMessageParam; + ChatCompletionUserMessageParam userMsg = + (ChatCompletionUserMessageParam) concreteMessageParam; eventType = "gen_ai.user.message"; if (settings.captureMessageContent) { putIfNotEmpty(bodyBuilder, "content", contentToString(userMsg.content())); } } else if (concreteMessageParam instanceof ChatCompletionAssistantMessageParam) { - ChatCompletionAssistantMessageParam assistantMsg = (ChatCompletionAssistantMessageParam) concreteMessageParam; + ChatCompletionAssistantMessageParam assistantMsg = + (ChatCompletionAssistantMessageParam) concreteMessageParam; eventType = "gen_ai.assistant.message"; if (settings.captureMessageContent) { assistantMsg @@ -91,7 +94,8 @@ public static void emitPromptLogEvents( }); } } else if (concreteMessageParam instanceof ChatCompletionToolMessageParam) { - ChatCompletionToolMessageParam toolMsg = (ChatCompletionToolMessageParam) concreteMessageParam; + ChatCompletionToolMessageParam toolMsg = + (ChatCompletionToolMessageParam) concreteMessageParam; eventType = "gen_ai.tool.message"; if (settings.captureMessageContent) { putIfNotEmpty(bodyBuilder, "content", contentToString(toolMsg.content())); @@ -111,7 +115,7 @@ private static void putIfNotEmpty(MapValueBuilder bodyBuilder, String key, Strin } private static String contentToString(ChatCompletionToolMessageParam.Content content) { - String text = ApiAdapter.INSTANCE.asText(content); + String text = ApiAdapter.get().asText(content); if (text != null) { return text; } else if (content.isArrayOfContentParts()) { @@ -124,12 +128,12 @@ private static String contentToString(ChatCompletionToolMessageParam.Content con } private static String contentToString(ChatCompletionAssistantMessageParam.Content content) { - String text = ApiAdapter.INSTANCE.asText(content); + String text = ApiAdapter.get().asText(content); if (text != null) { return text; } else if (content.isArrayOfContentParts()) { return content.asArrayOfContentParts().stream() - .map(ApiAdapter.INSTANCE::extractTextOrRefusal) + .map(ApiAdapter.get()::extractTextOrRefusal) .filter(Objects::nonNull) .collect(Collectors.joining()); } else { @@ -138,7 +142,7 @@ private static String contentToString(ChatCompletionAssistantMessageParam.Conten } private static String contentToString(ChatCompletionSystemMessageParam.Content content) { - String text = ApiAdapter.INSTANCE.asText(content); + String text = ApiAdapter.get().asText(content); if (text != null) { return text; } else if (content.isArrayOfContentParts()) { @@ -151,12 +155,12 @@ private static String contentToString(ChatCompletionSystemMessageParam.Content c } private static String contentToString(ChatCompletionUserMessageParam.Content content) { - String text = ApiAdapter.INSTANCE.asText(content); + String text = ApiAdapter.get().asText(content); if (text != null) { return text; } else if (content.isArrayOfContentParts()) { return content.asArrayOfContentParts().stream() - .map(ApiAdapter.INSTANCE::extractText) + .map(ApiAdapter.get()::extractText) .filter(Objects::nonNull) .collect(Collectors.joining()); } else { diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java index de5e015d..369691ba 100644 --- a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java @@ -139,7 +139,7 @@ public void onStart( val -> { attributes.put( GEN_AI_OPENAI_REQUEST_RESPONSE_FORMAT, - ApiAdapter.INSTANCE.extractType(val)); + ApiAdapter.get().extractType(val)); }); } diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java index bce500fd..c111494f 100644 --- a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedOpenAiClient.java @@ -97,8 +97,8 @@ public OpenAIClient build() { } } return new InstrumentedOpenAiClient( - delegate, - new InstrumentationSettings(emitEvents, captureMessageContent, hostname, port)) + delegate, + new InstrumentationSettings(emitEvents, captureMessageContent, hostname, port)) .createProxy(); } } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java index f46a0798..7c386fca 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 co.elastic.otel.openai.v0_13_0; import co.elastic.otel.openai.wrappers.ApiAdapter; @@ -11,6 +29,10 @@ public class ApiAdapterImpl extends ApiAdapter { + public static void init() { + ApiAdapter.init(ApiAdapterImpl::new); + } + @Override public Object extractConcreteCompletionMessageParam(ChatCompletionMessageParam base) { if (base.isChatCompletionSystemMessageParam()) { @@ -50,7 +72,8 @@ public String extractType(ChatCompletionCreateParams.ResponseFormat val) { @Override public String extractTextOrRefusal( - ChatCompletionAssistantMessageParam.Content.ChatCompletionRequestAssistantMessageContentPart part) { + ChatCompletionAssistantMessageParam.Content.ChatCompletionRequestAssistantMessageContentPart + part) { if (part.isChatCompletionContentPartText()) { return part.asChatCompletionContentPartText().text(); } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java index ee6e84d0..29866b93 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java @@ -18,13 +18,16 @@ */ package co.elastic.otel.openai.v0_13_0; -import co.elastic.otel.openai.OpenAiOkHttpClientBuilderInstrumentation; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.not; + import co.elastic.otel.openai.wrappers.Constants; import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import java.util.Collections; import java.util.List; +import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class OpenAiClientInstrumentationModule extends InstrumentationModule { @@ -33,6 +36,14 @@ public OpenAiClientInstrumentationModule() { super(Constants.INSTRUMENTATION_NAME); } + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // HandlerReferencingAsyncStreamResponse was added in 0.14.1, which is the next release after + // 0.13.0 + // 0.14.0 was a broken release which doesn't exist on maven central + return not(hasClassesNamed("com.openai.core.http.HandlerReferencingAsyncStreamResponse")); + } + @Override public List typeInstrumentations() { return Collections.singletonList(new OpenAiOkHttpClientBuilderInstrumentation()); @@ -42,11 +53,4 @@ public List typeInstrumentations() { public boolean isHelperClass(String className) { return className.startsWith("co.elastic.otel.openai"); } - - @Override - public List getAdditionalHelperClassNames() { - return Collections.singletonList("co.elastic.otel.openai.v0_13_0.ApiAdapterImpl"); - } - - } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiOkHttpClientBuilderInstrumentation.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiOkHttpClientBuilderInstrumentation.java new file mode 100644 index 00000000..974e0078 --- /dev/null +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiOkHttpClientBuilderInstrumentation.java @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 co.elastic.otel.openai.v0_13_0; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; + +import co.elastic.otel.openai.wrappers.InstrumentedOpenAiClient; +import com.openai.client.OpenAIClient; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class OpenAiOkHttpClientBuilderInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("com.openai.client.okhttp.OpenAIOkHttpClient$Builder"); + } + + @Override + public void transform(TypeTransformer typeTransformer) { + typeTransformer.applyAdviceToMethod( + named("build").and(returns(named("com.openai.client.OpenAIClient"))), + getClass().getName() + "$AdviceClass"); + } + + public static class AdviceClass { + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) + @Advice.AssignReturned.ToReturned + public static OpenAIClient onExit( + @Advice.Return OpenAIClient result, @Advice.FieldValue("baseUrl") String baseUrl) { + // This initialization has two purposes: + // Initialize the correct adapter AND ensure that it is picked up by muzzle + ApiAdapterImpl.init(); + return InstrumentedOpenAiClient.wrap(result).baseUrl(baseUrl).build(); + } + } +} diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java index 7021d5c0..75f3a8c5 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java @@ -71,5 +71,4 @@ protected ChatCompletionMessageParam createToolMessage(String response, String i .content(ChatCompletionToolMessageParam.Content.ofTextContent(response)) .build()); } - } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/EmbeddingsTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/EmbeddingsTest.java index 33149517..1b7c1216 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/EmbeddingsTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/EmbeddingsTest.java @@ -20,6 +20,4 @@ import co.elastic.otel.openai.EmbeddingsTestBase; -class EmbeddingsTest extends EmbeddingsTestBase { - -} +class EmbeddingsTest extends EmbeddingsTestBase {} diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java index 3ef4f42b..ca10a007 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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 co.elastic.otel.openai.latest; import co.elastic.otel.openai.wrappers.ApiAdapter; @@ -11,6 +29,10 @@ public class ApiAdapterImpl extends ApiAdapter { + public static void init() { + ApiAdapter.init(ApiAdapterImpl::new); + } + @Override public Object extractConcreteCompletionMessageParam(ChatCompletionMessageParam base) { if (base.isSystem()) { @@ -50,7 +72,8 @@ public String asText(ChatCompletionUserMessageParam.Content content) { @Override public String extractTextOrRefusal( - ChatCompletionAssistantMessageParam.Content.ChatCompletionRequestAssistantMessageContentPart part) { + ChatCompletionAssistantMessageParam.Content.ChatCompletionRequestAssistantMessageContentPart + part) { if (part.isText()) { return part.asText().text(); } @@ -78,5 +101,4 @@ public String extractType(ChatCompletionCreateParams.ResponseFormat val) { } return ""; } - } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java index 75d9eda1..430fb685 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java @@ -18,13 +18,15 @@ */ package co.elastic.otel.openai.latest; -import co.elastic.otel.openai.OpenAiOkHttpClientBuilderInstrumentation; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; + import co.elastic.otel.openai.wrappers.Constants; import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import java.util.Collections; import java.util.List; +import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class OpenAiClientInstrumentationModule extends InstrumentationModule { @@ -33,6 +35,14 @@ public OpenAiClientInstrumentationModule() { super(Constants.INSTRUMENTATION_NAME); } + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // HandlerReferencingAsyncStreamResponse was added in 0.14.1, which is the next release after + // 0.13.0 + // 0.14.0 was a broken release which doesn't exist on maven central + return hasClassesNamed("com.openai.core.http.HandlerReferencingAsyncStreamResponse"); + } + @Override public List typeInstrumentations() { return Collections.singletonList(new OpenAiOkHttpClientBuilderInstrumentation()); @@ -42,10 +52,4 @@ public List typeInstrumentations() { public boolean isHelperClass(String className) { return className.startsWith("co.elastic.otel.openai"); } - - @Override - public List getAdditionalHelperClassNames() { - return Collections.singletonList("co.elastic.otel.openai.latest.ApiAdapterImpl"); - } - } diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/OpenAiOkHttpClientBuilderInstrumentation.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiOkHttpClientBuilderInstrumentation.java similarity index 89% rename from instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/OpenAiOkHttpClientBuilderInstrumentation.java rename to instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiOkHttpClientBuilderInstrumentation.java index 17504559..7ac2973e 100644 --- a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/OpenAiOkHttpClientBuilderInstrumentation.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiOkHttpClientBuilderInstrumentation.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai; +package co.elastic.otel.openai.latest; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; @@ -40,7 +40,7 @@ public ElementMatcher typeMatcher() { public void transform(TypeTransformer typeTransformer) { typeTransformer.applyAdviceToMethod( named("build").and(returns(named("com.openai.client.OpenAIClient"))), - "co.elastic.otel.openai.OpenAiOkHttpClientBuilderInstrumentation$AdviceClass"); + getClass().getName() + "$AdviceClass"); } public static class AdviceClass { @@ -49,6 +49,9 @@ public static class AdviceClass { @Advice.AssignReturned.ToReturned public static OpenAIClient onExit( @Advice.Return OpenAIClient result, @Advice.FieldValue("baseUrl") String baseUrl) { + // This initialization has two purposes: + // Initialize the correct adapter AND ensure that it is picked up by muzzle + ApiAdapterImpl.init(); return InstrumentedOpenAiClient.wrap(result).baseUrl(baseUrl).build(); } } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java index 113bf18f..fe0e87a9 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java @@ -71,5 +71,4 @@ protected ChatCompletionMessageParam createToolMessage(String response, String i .content(ChatCompletionToolMessageParam.Content.ofText(response)) .build()); } - } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/EmbeddingsTest.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/EmbeddingsTest.java index 2f60231f..ee890625 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/EmbeddingsTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/EmbeddingsTest.java @@ -20,6 +20,4 @@ import co.elastic.otel.openai.EmbeddingsTestBase; -class EmbeddingsTest extends EmbeddingsTestBase { - -} +class EmbeddingsTest extends EmbeddingsTestBase {} diff --git a/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java index 3f8eac49..662a38b6 100644 --- a/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java +++ b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/ChatTestBase.java @@ -2238,7 +2238,6 @@ void toolsWithFollowupAndCaptureContent() { equalTo(SERVER_PORT, (long) openai.getPort()))))); } - @Test void disableEvents() { // Override the enablement from setup() diff --git a/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/OpenAIRecordingExtension.java b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/OpenAIRecordingExtension.java index 3e8c7d8b..b2c32a18 100644 --- a/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/OpenAIRecordingExtension.java +++ b/instrumentation/openai-client-instrumentation/testing-common/src/main/java/co/elastic/otel/openai/OpenAIRecordingExtension.java @@ -50,8 +50,8 @@ final class OpenAIRecordingExtension extends WireMockExtension { PrettyPrintEqualToJsonStubMappingTransformer.class) .mappingSource( new YamlFileMappingsSource( - new SingleRootFileSource("../testing-common/src/main/resources").child( - "mappings"))))); + new SingleRootFileSource("../testing-common/src/main/resources") + .child("mappings"))))); this.testName = testName; } From c9bf0011fe6756d06561a51ee0622d78ac865c2b Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 5 Feb 2025 11:34:58 +0100 Subject: [PATCH 4/9] Add changelog --- CHANGELOG.next-release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.next-release.md b/CHANGELOG.next-release.md index 8b137891..34076562 100644 --- a/CHANGELOG.next-release.md +++ b/CHANGELOG.next-release.md @@ -1 +1 @@ - +* Add support for OpenAI client 0.14+ - #531 From 9a45fa6ae28d70e5f979f06a14c3e102031b71ab Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 5 Feb 2025 11:40:23 +0100 Subject: [PATCH 5/9] Remove unused method, fix line breaks --- .../java/co/elastic/otel/openai/wrappers/ApiAdapter.java | 8 -------- .../openai/v0_13_0/OpenAiClientInstrumentationModule.java | 4 ++-- .../openai/latest/OpenAiClientInstrumentationModule.java | 4 ++-- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java index 8fde3680..8e1c0b3e 100644 --- a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java @@ -93,12 +93,4 @@ public abstract String extractTextOrRefusal( public abstract String extractText(ChatCompletionContentPart part); public abstract String extractType(ChatCompletionCreateParams.ResponseFormat val); - - private static Class tryLookupClass(String className) { - try { - return Class.forName(className); - } catch (ClassNotFoundException e) { - return null; - } - } } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java index 29866b93..0b42d805 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java @@ -38,8 +38,8 @@ public OpenAiClientInstrumentationModule() { @Override public ElementMatcher.Junction classLoaderMatcher() { - // HandlerReferencingAsyncStreamResponse was added in 0.14.1, which is the next release after - // 0.13.0 + // HandlerReferencingAsyncStreamResponse was added in 0.14.1, + // which is the next release after 0.13.0 // 0.14.0 was a broken release which doesn't exist on maven central return not(hasClassesNamed("com.openai.core.http.HandlerReferencingAsyncStreamResponse")); } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java index 430fb685..166084dd 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java @@ -37,8 +37,8 @@ public OpenAiClientInstrumentationModule() { @Override public ElementMatcher.Junction classLoaderMatcher() { - // HandlerReferencingAsyncStreamResponse was added in 0.14.1, which is the next release after - // 0.13.0 + // HandlerReferencingAsyncStreamResponse was added in 0.14.1, + // which is the next release after 0.13.0 // 0.14.0 was a broken release which doesn't exist on maven central return hasClassesNamed("com.openai.core.http.HandlerReferencingAsyncStreamResponse"); } From 95da2ca117f39f049f5ea3dd7d91a9d736a8c51b Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 5 Feb 2025 11:46:08 +0100 Subject: [PATCH 6/9] Remove unnecessary line break --- .../testing-common/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts b/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts index 590b7e8b..f8668bba 100644 --- a/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts +++ b/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts @@ -4,7 +4,6 @@ plugins { dependencies { compileOnly(catalog.openaiClient) - compileOnly(project(":instrumentation:openai-client-instrumentation:common")) implementation("io.opentelemetry.javaagent:opentelemetry-testing-common") implementation("io.opentelemetry:opentelemetry-sdk-testing") From 16d9ec0d7640b5bf46f32a0337c1d595a0126be3 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 5 Feb 2025 12:40:18 +0100 Subject: [PATCH 7/9] line break --- .../testing-common/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts b/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts index f8668bba..6fdff9f0 100644 --- a/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts +++ b/instrumentation/openai-client-instrumentation/testing-common/build.gradle.kts @@ -5,9 +5,9 @@ plugins { dependencies { compileOnly(catalog.openaiClient) compileOnly(project(":instrumentation:openai-client-instrumentation:common")) + implementation("io.opentelemetry.javaagent:opentelemetry-testing-common") implementation("io.opentelemetry:opentelemetry-sdk-testing") - implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.2") implementation("org.slf4j:slf4j-simple:2.0.16") From b818c1f1685a1b97f99a6bc381df0cb0deaa45ee Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 5 Feb 2025 14:17:37 +0100 Subject: [PATCH 8/9] Renamed modules and package from maximum supported version to least supported version --- custom/build.gradle.kts | 4 ++-- .../build.gradle.kts | 0 .../java/co/elastic/otel/openai/v0_14}/ApiAdapterImpl.java | 2 +- .../otel/openai/v0_14}/OpenAiClientInstrumentationModule.java | 2 +- .../v0_14}/OpenAiOkHttpClientBuilderInstrumentation.java | 2 +- .../src/test/java/co/elastic/otel/openai/v0_14}/ChatTest.java | 2 +- .../java/co/elastic/otel/openai/v0_14}/EmbeddingsTest.java | 2 +- .../otel/openai/v0_14}/LiveAPIChatIntegrationTest.java | 2 +- .../build.gradle.kts | 0 .../java/co/elastic/otel/openai/v0_2}/ApiAdapterImpl.java | 2 +- .../otel/openai/v0_2}/OpenAiClientInstrumentationModule.java | 2 +- .../v0_2}/OpenAiOkHttpClientBuilderInstrumentation.java | 2 +- .../src/test/java/co/elastic/otel/openai/v0_2}/ChatTest.java | 2 +- .../java/co/elastic/otel/openai/v0_2}/EmbeddingsTest.java | 2 +- settings.gradle.kts | 4 ++-- 15 files changed, 15 insertions(+), 15 deletions(-) rename instrumentation/openai-client-instrumentation/{instrumentation-latest => instrumentation-0.14}/build.gradle.kts (100%) rename instrumentation/openai-client-instrumentation/{instrumentation-latest/src/main/java/co/elastic/otel/openai/latest => instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14}/ApiAdapterImpl.java (98%) rename instrumentation/openai-client-instrumentation/{instrumentation-latest/src/main/java/co/elastic/otel/openai/latest => instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14}/OpenAiClientInstrumentationModule.java (98%) rename instrumentation/openai-client-instrumentation/{instrumentation-latest/src/main/java/co/elastic/otel/openai/latest => instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14}/OpenAiOkHttpClientBuilderInstrumentation.java (98%) rename instrumentation/openai-client-instrumentation/{instrumentation-latest/src/test/java/co/elastic/otel/openai/latest => instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14}/ChatTest.java (98%) rename instrumentation/openai-client-instrumentation/{instrumentation-latest/src/test/java/co/elastic/otel/openai/latest => instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14}/EmbeddingsTest.java (95%) rename instrumentation/openai-client-instrumentation/{instrumentation-latest/src/test/java/co/elastic/otel/openai/latest => instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14}/LiveAPIChatIntegrationTest.java (99%) rename instrumentation/openai-client-instrumentation/{instrumentation-0.13.0 => instrumentation-0.2}/build.gradle.kts (100%) rename instrumentation/openai-client-instrumentation/{instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0 => instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2}/ApiAdapterImpl.java (98%) rename instrumentation/openai-client-instrumentation/{instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0 => instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2}/OpenAiClientInstrumentationModule.java (98%) rename instrumentation/openai-client-instrumentation/{instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0 => instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2}/OpenAiOkHttpClientBuilderInstrumentation.java (98%) rename instrumentation/openai-client-instrumentation/{instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0 => instrumentation-0.2/src/test/java/co/elastic/otel/openai/v0_2}/ChatTest.java (98%) rename instrumentation/openai-client-instrumentation/{instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0 => instrumentation-0.2/src/test/java/co/elastic/otel/openai/v0_2}/EmbeddingsTest.java (95%) diff --git a/custom/build.gradle.kts b/custom/build.gradle.kts index 85b5eac4..688e8233 100644 --- a/custom/build.gradle.kts +++ b/custom/build.gradle.kts @@ -3,8 +3,8 @@ plugins { } val instrumentations = listOf( - ":instrumentation:openai-client-instrumentation:instrumentation-0.13.0", - ":instrumentation:openai-client-instrumentation:instrumentation-latest" + ":instrumentation:openai-client-instrumentation:instrumentation-0.2", + ":instrumentation:openai-client-instrumentation:instrumentation-0.14" ) dependencies { diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/build.gradle.kts b/instrumentation/openai-client-instrumentation/instrumentation-0.14/build.gradle.kts similarity index 100% rename from instrumentation/openai-client-instrumentation/instrumentation-latest/build.gradle.kts rename to instrumentation/openai-client-instrumentation/instrumentation-0.14/build.gradle.kts diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/ApiAdapterImpl.java similarity index 98% rename from instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/ApiAdapterImpl.java index ca10a007..67ca27fc 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/ApiAdapterImpl.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/ApiAdapterImpl.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.latest; +package co.elastic.otel.openai.v0_14; import co.elastic.otel.openai.wrappers.ApiAdapter; import com.openai.models.ChatCompletionAssistantMessageParam; diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/OpenAiClientInstrumentationModule.java similarity index 98% rename from instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/OpenAiClientInstrumentationModule.java index 166084dd..dd347256 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiClientInstrumentationModule.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/OpenAiClientInstrumentationModule.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.latest; +package co.elastic.otel.openai.v0_14; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiOkHttpClientBuilderInstrumentation.java b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/OpenAiOkHttpClientBuilderInstrumentation.java similarity index 98% rename from instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiOkHttpClientBuilderInstrumentation.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/OpenAiOkHttpClientBuilderInstrumentation.java index 7ac2973e..26ee8728 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/main/java/co/elastic/otel/openai/latest/OpenAiOkHttpClientBuilderInstrumentation.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/OpenAiOkHttpClientBuilderInstrumentation.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.latest; +package co.elastic.otel.openai.v0_14; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14/ChatTest.java similarity index 98% rename from instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14/ChatTest.java index fe0e87a9..622412a6 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/ChatTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14/ChatTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.latest; +package co.elastic.otel.openai.v0_14; import co.elastic.otel.openai.ChatTestBase; import com.openai.models.ChatCompletionAssistantMessageParam; diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/EmbeddingsTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14/EmbeddingsTest.java similarity index 95% rename from instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/EmbeddingsTest.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14/EmbeddingsTest.java index ee890625..bc8ed40e 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/EmbeddingsTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14/EmbeddingsTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.latest; +package co.elastic.otel.openai.v0_14; import co.elastic.otel.openai.EmbeddingsTestBase; diff --git a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/LiveAPIChatIntegrationTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14/LiveAPIChatIntegrationTest.java similarity index 99% rename from instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/LiveAPIChatIntegrationTest.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14/LiveAPIChatIntegrationTest.java index 1679bbea..1431153e 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-latest/src/test/java/co/elastic/otel/openai/latest/LiveAPIChatIntegrationTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/test/java/co/elastic/otel/openai/v0_14/LiveAPIChatIntegrationTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.latest; +package co.elastic.otel.openai.v0_14; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/build.gradle.kts b/instrumentation/openai-client-instrumentation/instrumentation-0.2/build.gradle.kts similarity index 100% rename from instrumentation/openai-client-instrumentation/instrumentation-0.13.0/build.gradle.kts rename to instrumentation/openai-client-instrumentation/instrumentation-0.2/build.gradle.kts diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/ApiAdapterImpl.java similarity index 98% rename from instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/ApiAdapterImpl.java index 7c386fca..6c4ca07a 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/ApiAdapterImpl.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/ApiAdapterImpl.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.v0_13_0; +package co.elastic.otel.openai.v0_2; import co.elastic.otel.openai.wrappers.ApiAdapter; import com.openai.models.ChatCompletionAssistantMessageParam; diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/OpenAiClientInstrumentationModule.java similarity index 98% rename from instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/OpenAiClientInstrumentationModule.java index 0b42d805..ae170e8e 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiClientInstrumentationModule.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/OpenAiClientInstrumentationModule.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.v0_13_0; +package co.elastic.otel.openai.v0_2; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static net.bytebuddy.matcher.ElementMatchers.not; diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiOkHttpClientBuilderInstrumentation.java b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/OpenAiOkHttpClientBuilderInstrumentation.java similarity index 98% rename from instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiOkHttpClientBuilderInstrumentation.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/OpenAiOkHttpClientBuilderInstrumentation.java index 974e0078..d0ec84c5 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/main/java/co/elastic/otel/openai/v0_13_0/OpenAiOkHttpClientBuilderInstrumentation.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/OpenAiOkHttpClientBuilderInstrumentation.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.v0_13_0; +package co.elastic.otel.openai.v0_2; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/test/java/co/elastic/otel/openai/v0_2/ChatTest.java similarity index 98% rename from instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.2/src/test/java/co/elastic/otel/openai/v0_2/ChatTest.java index 75f3a8c5..b386f5af 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/ChatTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/test/java/co/elastic/otel/openai/v0_2/ChatTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.v0_13_0; +package co.elastic.otel.openai.v0_2; import co.elastic.otel.openai.ChatTestBase; import com.openai.models.ChatCompletionAssistantMessageParam; diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/EmbeddingsTest.java b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/test/java/co/elastic/otel/openai/v0_2/EmbeddingsTest.java similarity index 95% rename from instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/EmbeddingsTest.java rename to instrumentation/openai-client-instrumentation/instrumentation-0.2/src/test/java/co/elastic/otel/openai/v0_2/EmbeddingsTest.java index 1b7c1216..814bc53d 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.13.0/src/test/java/co/elastic/otel/openai/v0_13_0/EmbeddingsTest.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/test/java/co/elastic/otel/openai/v0_2/EmbeddingsTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.openai.v0_13_0; +package co.elastic.otel.openai.v0_2; import co.elastic.otel.openai.EmbeddingsTestBase; diff --git a/settings.gradle.kts b/settings.gradle.kts index c796ed5d..8edd172a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,8 +19,8 @@ include("custom") include("instrumentation") include("instrumentation:openai-client-instrumentation:common") include("instrumentation:openai-client-instrumentation:testing-common") -include("instrumentation:openai-client-instrumentation:instrumentation-0.13.0") -include("instrumentation:openai-client-instrumentation:instrumentation-latest") +include("instrumentation:openai-client-instrumentation:instrumentation-0.2") +include("instrumentation:openai-client-instrumentation:instrumentation-0.14") include("inferred-spans") include("resources") include("smoke-tests") From 106a90c8e32b287f761aa168da49ba6077a73823 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 5 Feb 2025 14:33:03 +0100 Subject: [PATCH 9/9] Fix inconsistencies in adapters --- .../otel/openai/wrappers/ApiAdapter.java | 11 ++++++---- .../InstrumentedChatCompletionService.java | 7 +++--- .../otel/openai/v0_14/ApiAdapterImpl.java | 2 +- .../otel/openai/v0_2/ApiAdapterImpl.java | 22 +++++-------------- 4 files changed, 17 insertions(+), 25 deletions(-) diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java index 8e1c0b3e..7ae10668 100644 --- a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/ApiAdapter.java @@ -61,22 +61,22 @@ protected static void init(Supplier implementation) { public abstract Object extractConcreteCompletionMessageParam(ChatCompletionMessageParam base); /** - * @return the contained text, if the content is next. null otherwise. + * @return the contained text, if the content is text. null otherwise. */ public abstract String asText(ChatCompletionToolMessageParam.Content content); /** - * @return the contained text, if the content is next. null otherwise. + * @return the contained text, if the content is text. null otherwise. */ public abstract String asText(ChatCompletionAssistantMessageParam.Content content); /** - * @return the contained text, if the content is next. null otherwise. + * @return the contained text, if the content is text. null otherwise. */ public abstract String asText(ChatCompletionSystemMessageParam.Content content); /** - * @return the contained text, if the content is next. null otherwise. + * @return the contained text, if the content is text. null otherwise. */ public abstract String asText(ChatCompletionUserMessageParam.Content content); @@ -92,5 +92,8 @@ public abstract String extractTextOrRefusal( */ public abstract String extractText(ChatCompletionContentPart part); + /** + * @return the type if available, otherwise null + */ public abstract String extractType(ChatCompletionCreateParams.ResponseFormat val); } diff --git a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java index 369691ba..90c8d684 100644 --- a/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java +++ b/instrumentation/openai-client-instrumentation/common/src/main/java/co/elastic/otel/openai/wrappers/InstrumentedChatCompletionService.java @@ -137,9 +137,10 @@ public void onStart( .responseFormat() .ifPresent( val -> { - attributes.put( - GEN_AI_OPENAI_REQUEST_RESPONSE_FORMAT, - ApiAdapter.get().extractType(val)); + String typeString = ApiAdapter.get().extractType(val); + if (typeString != null) { + attributes.put(GEN_AI_OPENAI_REQUEST_RESPONSE_FORMAT, typeString); + } }); } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/ApiAdapterImpl.java b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/ApiAdapterImpl.java index 67ca27fc..9de26cc5 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/ApiAdapterImpl.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.14/src/main/java/co/elastic/otel/openai/v0_14/ApiAdapterImpl.java @@ -99,6 +99,6 @@ public String extractType(ChatCompletionCreateParams.ResponseFormat val) { if (val.isJsonSchema()) { return val.asJsonSchema()._type().toString(); } - return ""; + return null; } } diff --git a/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/ApiAdapterImpl.java b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/ApiAdapterImpl.java index 6c4ca07a..f00ac3cc 100644 --- a/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/ApiAdapterImpl.java +++ b/instrumentation/openai-client-instrumentation/instrumentation-0.2/src/main/java/co/elastic/otel/openai/v0_2/ApiAdapterImpl.java @@ -67,7 +67,7 @@ public String extractType(ChatCompletionCreateParams.ResponseFormat val) { } else if (val.isResponseFormatJsonSchema()) { return val.asResponseFormatJsonSchema()._type().toString(); } - return ""; + return null; } @Override @@ -85,33 +85,21 @@ public String extractTextOrRefusal( @Override public String asText(ChatCompletionUserMessageParam.Content content) { - if (content.isTextContent()) { - return content.asTextContent(); - } - return ""; + return content.isTextContent() ? content.asTextContent() : null; } @Override public String asText(ChatCompletionSystemMessageParam.Content content) { - if (content.isTextContent()) { - return content.asTextContent(); - } - return ""; + return content.isTextContent() ? content.asTextContent() : null; } @Override public String asText(ChatCompletionAssistantMessageParam.Content content) { - if (content.isTextContent()) { - return content.asTextContent(); - } - return ""; + return content.isTextContent() ? content.asTextContent() : null; } @Override public String asText(ChatCompletionToolMessageParam.Content content) { - if (content.isTextContent()) { - return content.asTextContent(); - } - return ""; + return content.isTextContent() ? content.asTextContent() : null; } }