Skip to content

Conversation

@ygree
Copy link
Contributor

@ygree ygree commented Nov 13, 2025

What Does This Do

Adds openai-java v3.0+ instrumentation for completions, chat completions, embeddings, and responses.

image
image

APM shared tests:

test_openai.py::TestOpenAiApm::test_chat_completion[java-test-ml-app-tcp-True] PASSED [ 12%]
test_openai.py::TestOpenAiApm::test_chat_completion[java-test-ml-app-tcp-False] PASSED [ 25%]
test_openai.py::TestOpenAiApm::test_completion[java-test-ml-app-tcp] PASSED [ 37%]
test_openai.py::TestOpenAiApm::test_embedding[java-test-ml-app-tcp] PASSED [ 50%]
test_openai.py::TestOpenAiApm::test_chat_completion_tool_call[java-test-ml-app-tcp-True] PASSED [ 62%]
test_openai.py::TestOpenAiApm::test_chat_completion_tool_call[java-test-ml-app-tcp-False] PASSED [ 75%]
test_openai.py::TestOpenAiApm::test_responses_create[java-test-ml-app-tcp-True] PASSED [ 87%]
test_openai.py::TestOpenAiApm::test_responses_create[java-test-ml-app-tcp-False] PASSED [100%]

LLMObs shared tests 14*/15:

test_openai.py::TestOpenAiLlmObs::test_chat_completion[java-test-ml-app-tcp-True] PASSED [  6%]
test_openai.py::TestOpenAiLlmObs::test_chat_completion[java-test-ml-app-tcp-False] PASSED [ 13%]
test_openai.py::TestOpenAiLlmObs::test_completion[java-test-ml-app-tcp] PASSED [ 20%]
test_openai.py::TestOpenAiLlmObs::test_embedding[java-test-ml-app-tcp] PASSED [ 26%]
test_openai.py::TestOpenAiLlmObs::test_chat_completion_tool_call[java-test-ml-app-tcp-True] PASSED [ 33%]
test_openai.py::TestOpenAiLlmObs::test_chat_completion_tool_call[java-test-ml-app-tcp-False] PASSED [ 40%]

test_openai.py::TestOpenAiLlmObs::test_chat_completion_telemetry[java-test-ml-app-tcp] FAILED* [ 46%]

test_openai.py::TestOpenAiLlmObs::test_responses_create[java-test-ml-app-tcp-True] PASSED [ 53%]
test_openai.py::TestOpenAiLlmObs::test_responses_create[java-test-ml-app-tcp-False] PASSED [ 60%]
test_openai.py::TestOpenAiLlmObs::test_responses_create_tool_call[java-test-ml-app-tcp-True] PASSED [ 66%]
test_openai.py::TestOpenAiLlmObs::test_responses_create_tool_call[java-test-ml-app-tcp-False] PASSED [ 73%]
test_openai.py::TestOpenAiLlmObs::test_responses_create_reasoning[java-test-ml-app-tcp-True] PASSED [ 80%]
test_openai.py::TestOpenAiLlmObs::test_responses_create_reasoning[java-test-ml-app-tcp-False] PASSED [ 86%]
test_openai.py::TestOpenAiLlmObs::test_responses_create_tool_input[java-test-ml-app-tcp-True] PASSED [ 93%]
test_openai.py::TestOpenAiLlmObs::test_responses_create_tool_input[java-test-ml-app-tcp-False] PASSED [100%]
    • test_chat_completion_telemetry FAILED because the shared test expects a namespace for the entire batch, but it should allow the namespace to be part of the metric data. Since Java batches telemetry data across subsystems and cannot guarantee that all the data belongs to the same namespace.

Motivation

Additional Notes

Contributor Checklist

Jira ticket: AIDM-163

ygree added 30 commits October 24, 2025 18:03
…d "streamed async request completion test with withRawResponse"
Call decorateWithResponse from the wrappers
Rename span resources to be aligned with trace-py
Add http.client resource assertion
Test case renaming
Reorder tests by synch, async
Fix Embeddings fixture for the latestDepTest when base64
@ygree ygree changed the title [WIP] openai-java v3.0+ instrumentation add openai-java v3.0+ instrumentation Dec 23, 2025
@ygree ygree changed the title add openai-java v3.0+ instrumentation feat: openai-java v3.0+ instrumentation Dec 23, 2025
@ygree ygree removed the tag: do not merge Do not merge changes label Dec 23, 2025
@ygree ygree marked this pull request as ready for review December 23, 2025 21:39
@ygree ygree requested review from a team as code owners December 23, 2025 21:39
@ygree ygree requested review from dougqh and removed request for a team December 23, 2025 21:39
@github-actions
Copy link
Contributor

Hi! 👋 Thanks for your pull request! 🎉

To help us review it, please make sure to:

  • Add at least one type, and one component or instrumentation label to the pull request

If you need help, please check our contributing guidelines.

Copy link
Contributor

@PerfectSlayer PerfectSlayer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round of comments for the core part. I will let IDM review the instrumentation part.

Comment on lines +112 to +116
* <blockquote>
* <p><b>NOTE:</b> The order matters. If the muzzle check fails with a NoClassDefFoundError
* (as seen in build/reports/muzzle-*.txt), it may be because some helper classes depend on
* each other. In this case, the order must be adjusted accordingly.
* </blockquote>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❔ question: ‏Which order are you referring here? Helper orders but there is no helper. Or instrumentation order?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The returned helper class names are in order. For example, if you return an array containing "Foo" and "Bar," and "Foo" references "Bar," then it will fail with a NoClassDefFoundError, which might not be obvious since both are declared.

assert value =~ expected: "Tag \"$name\": \"${value.toString()}\" does not match pattern \"$expected\""
} else if (expected instanceof Class) {
assert ((Class) expected).isInstance(value): "Tag \"$name\": instance check $expected failed for \"${value.toString()}\" of class \"${value.class}\""
assert ((Class) expected).isInstance(value): "Tag \"$name\": instance check $expected failed for \"${value.toString()}\" of class \"${value?.class}\""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔨 issue: ‏Is that to prevent null value? If so, how the value.toString() did not crash earlier?

@@ -0,0 +1,25 @@
apply from: "$rootDir/gradle/java.gradle"
apply plugin: 'idea'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❔ question: ‏What's the need for the idea plugin?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. Some modules have it. Is it no longer needed?


private CharSequence llmObsSpanName(CoreSpan<?> span) {
CharSequence operationName = span.getOperationName();
CharSequence resourceName = span.getResourceName();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 nitpick: ‏Resource name can only be fetched in case of OpenAI

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. I'll move this

this.contentLength = req.contentLength
this.contentType = req.contentType?.split(";")
this.body = req.inputStream.bytes
this.method = req.method
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❔ question: ‏Is this useful? It's only used by OpenAI instrumentation tests and has only POST from their records.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's useful for distinguishing request files with different HTTP methods but the same body. However, it can be removed since they are always POST.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: mlobs ML Observability (LLMObs) inst: others All other instrumentations type: feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants