Skip to content

Commit e584dff

Browse files
chore: align otel embeddings with final openinference spec (#1519)
**Description** This aligns the OpenInference embeddings data capture with the final specification I worked upstream to complete. https://github.com/Arize-ai/openinference/tree/main/spec **Related Issues/PRs (if applicable)** Arize-ai/openinference#2238 **Special notes for reviewers (if applicable)** Thanks very much @axiomofjoy from Arize who spent a lot of think time on this on the other side, helping to balance the desire to have things documented (for external implementations like this) with the practical realities of Eval UX I'm less familiar with. Signed-off-by: Adrian Cole <[email protected]>
1 parent 908fae3 commit e584dff

26 files changed

+16448
-16516
lines changed

internal/tracing/openinference/openai/embeddings_config_test.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ func TestEmbeddingsRecorder_WithConfig_HideInputs(t *testing.T) {
3636
req: basicEmbeddingReq,
3737
reqBody: basicEmbeddingReqBody,
3838
expectedAttrs: []attribute.KeyValue{
39-
attribute.String(openinference.LLMSystem, openinference.LLMSystemOpenAI),
4039
attribute.String(openinference.SpanKind, openinference.SpanKindEmbedding),
4140
attribute.String(openinference.InputValue, openinference.RedactedValue),
4241
attribute.String(openinference.EmbeddingInvocationParameters, `{"model":"text-embedding-3-small"}`),
@@ -50,7 +49,6 @@ func TestEmbeddingsRecorder_WithConfig_HideInputs(t *testing.T) {
5049
req: basicEmbeddingReq,
5150
reqBody: basicEmbeddingReqBody,
5251
expectedAttrs: []attribute.KeyValue{
53-
attribute.String(openinference.LLMSystem, openinference.LLMSystemOpenAI),
5452
attribute.String(openinference.SpanKind, openinference.SpanKindEmbedding),
5553
attribute.String(openinference.InputValue, string(basicEmbeddingReqBody)),
5654
attribute.String(openinference.InputMimeType, openinference.MimeTypeJSON),
@@ -65,7 +63,6 @@ func TestEmbeddingsRecorder_WithConfig_HideInputs(t *testing.T) {
6563
req: basicEmbeddingReq,
6664
reqBody: basicEmbeddingReqBody,
6765
expectedAttrs: []attribute.KeyValue{
68-
attribute.String(openinference.LLMSystem, openinference.LLMSystemOpenAI),
6966
attribute.String(openinference.SpanKind, openinference.SpanKindEmbedding),
7067
attribute.String(openinference.InputValue, string(basicEmbeddingReqBody)),
7168
attribute.String(openinference.InputMimeType, openinference.MimeTypeJSON),
@@ -145,8 +142,7 @@ func TestEmbeddingsRecorder_WithConfig_HideOutputs(t *testing.T) {
145142
var responseAttrs []attribute.KeyValue
146143
for _, attr := range actualSpan.Attributes {
147144
key := string(attr.Key)
148-
if key == openinference.LLMSystem ||
149-
key == openinference.SpanKind ||
145+
if key == openinference.SpanKind ||
150146
key == openinference.InputValue ||
151147
key == openinference.InputMimeType ||
152148
key == openinference.EmbeddingInvocationParameters ||

internal/tracing/openinference/openai/embeddings_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ func TestEmbeddingsRecorder_RecordRequest(t *testing.T) {
124124
reqBody: basicEmbeddingReqBody,
125125
config: &openinference.TraceConfig{},
126126
expectedAttrs: []attribute.KeyValue{
127-
attribute.String(openinference.LLMSystem, openinference.LLMSystemOpenAI),
128127
attribute.String(openinference.SpanKind, openinference.SpanKindEmbedding),
129128
attribute.String(openinference.InputValue, string(basicEmbeddingReqBody)),
130129
attribute.String(openinference.InputMimeType, openinference.MimeTypeJSON),
@@ -138,7 +137,6 @@ func TestEmbeddingsRecorder_RecordRequest(t *testing.T) {
138137
reqBody: multiInputEmbeddingReqBody,
139138
config: &openinference.TraceConfig{},
140139
expectedAttrs: []attribute.KeyValue{
141-
attribute.String(openinference.LLMSystem, openinference.LLMSystemOpenAI),
142140
attribute.String(openinference.SpanKind, openinference.SpanKindEmbedding),
143141
attribute.String(openinference.InputValue, string(multiInputEmbeddingReqBody)),
144142
attribute.String(openinference.InputMimeType, openinference.MimeTypeJSON),
@@ -157,7 +155,6 @@ func TestEmbeddingsRecorder_RecordRequest(t *testing.T) {
157155
reqBody: tokenInputEmbeddingReqBody,
158156
config: &openinference.TraceConfig{},
159157
expectedAttrs: []attribute.KeyValue{
160-
attribute.String(openinference.LLMSystem, openinference.LLMSystemOpenAI),
161158
attribute.String(openinference.SpanKind, openinference.SpanKindEmbedding),
162159
attribute.String(openinference.InputValue, string(tokenInputEmbeddingReqBody)),
163160
attribute.String(openinference.InputMimeType, openinference.MimeTypeJSON),
@@ -170,7 +167,6 @@ func TestEmbeddingsRecorder_RecordRequest(t *testing.T) {
170167
reqBody: basicEmbeddingReqBody,
171168
config: &openinference.TraceConfig{HideInputs: true},
172169
expectedAttrs: []attribute.KeyValue{
173-
attribute.String(openinference.LLMSystem, openinference.LLMSystemOpenAI),
174170
attribute.String(openinference.SpanKind, openinference.SpanKindEmbedding),
175171
attribute.String(openinference.InputValue, openinference.RedactedValue),
176172
attribute.String(openinference.EmbeddingInvocationParameters, `{"model":"text-embedding-3-small"}`),
@@ -182,7 +178,6 @@ func TestEmbeddingsRecorder_RecordRequest(t *testing.T) {
182178
reqBody: basicEmbeddingReqBody,
183179
config: &openinference.TraceConfig{HideLLMInvocationParameters: true},
184180
expectedAttrs: []attribute.KeyValue{
185-
attribute.String(openinference.LLMSystem, openinference.LLMSystemOpenAI),
186181
attribute.String(openinference.SpanKind, openinference.SpanKindEmbedding),
187182
attribute.String(openinference.InputValue, string(basicEmbeddingReqBody)),
188183
attribute.String(openinference.InputMimeType, openinference.MimeTypeJSON),

internal/tracing/openinference/openai/request_attrs.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,9 @@ type embeddingsInvocationParameters struct {
217217

218218
// buildEmbeddingsRequestAttributes builds OpenInference attributes from the embeddings request.
219219
func buildEmbeddingsRequestAttributes(embRequest *openai.EmbeddingRequest, body []byte, config *openinference.TraceConfig) []attribute.KeyValue {
220+
// Note: llm.system and llm.provider are not used in embedding spans per spec.
221+
// See: https://github.com/Arize-ai/openinference/blob/main/spec/embedding_spans.md#attributes-not-used-in-embedding-spans
220222
attrs := []attribute.KeyValue{
221-
attribute.String(openinference.LLMSystem, openinference.LLMSystemOpenAI),
222223
attribute.String(openinference.SpanKind, openinference.SpanKindEmbedding),
223224
}
224225

Lines changed: 63 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,124 @@
11
{
2-
"flags": 256,
3-
"name": "ChatCompletion",
4-
"kind": "SPAN_KIND_INTERNAL",
5-
"attributes": [
2+
"flags": 256,
3+
"name": "ChatCompletion",
4+
"kind": "SPAN_KIND_INTERNAL",
5+
"attributes": [
66
{
7-
"key": "llm.system",
8-
"value": {
9-
"stringValue": "openai"
7+
"key": "llm.system",
8+
"value": {
9+
"stringValue": "openai"
1010
}
1111
},
1212
{
13-
"key": "input.value",
14-
"value": {
15-
"stringValue": "{\"messages\": [{\"content\": \"Hello!\", \"role\": \"user\"}], \"model\": \"gpt-5-nano\"}"
13+
"key": "input.value",
14+
"value": {
15+
"stringValue": "{\"messages\": [{\"content\": \"Hello!\", \"role\": \"user\"}], \"model\": \"gpt-5-nano\"}"
1616
}
1717
},
1818
{
19-
"key": "input.mime_type",
20-
"value": {
21-
"stringValue": "application/json"
19+
"key": "input.mime_type",
20+
"value": {
21+
"stringValue": "application/json"
2222
}
2323
},
2424
{
25-
"key": "output.value",
26-
"value": {
27-
"stringValue": "{\"id\":\"chatcmpl-CM9n9OhFOl4lSSFwarLEIMnPrNWSP\",\"choices\":[{\"finish_reason\":\"stop\",\"index\":0,\"logprobs\":null,\"message\":{\"content\":\"Hi there! How can I help today? I can answer questions, brainstorm ideas, write or edit text, explain concepts, help with coding, plan projects, translate languages, or just chat. What would you like to do? If you’re not sure, tell me a topic you’re curious about and I’ll give you a quick rundown.\",\"refusal\":null,\"role\":\"assistant\",\"annotations\":[]},\"content_filter_results\":{}}],\"created\":1759396891,\"model\":\"gpt-5-nano-2025-08-07\",\"object\":\"chat.completion\",\"system_fingerprint\":null,\"usage\":{\"completion_tokens\":271,\"prompt_tokens\":8,\"total_tokens\":279,\"completion_tokens_details\":{\"accepted_prediction_tokens\":0,\"audio_tokens\":0,\"reasoning_tokens\":192,\"rejected_prediction_tokens\":0},\"prompt_tokens_details\":{\"audio_tokens\":0,\"cached_tokens\":0}},\"prompt_filter_results\":[{\"content_filter_results\":{},\"prompt_index\":0}]}"
25+
"key": "output.value",
26+
"value": {
27+
"stringValue": "{\"id\":\"chatcmpl-CM9n9OhFOl4lSSFwarLEIMnPrNWSP\",\"choices\":[{\"finish_reason\":\"stop\",\"index\":0,\"logprobs\":null,\"message\":{\"content\":\"Hi there! How can I help today? I can answer questions, brainstorm ideas, write or edit text, explain concepts, help with coding, plan projects, translate languages, or just chat. What would you like to do? If you’re not sure, tell me a topic you’re curious about and I’ll give you a quick rundown.\",\"refusal\":null,\"role\":\"assistant\",\"annotations\":[]},\"content_filter_results\":{}}],\"created\":1759396891,\"model\":\"gpt-5-nano-2025-08-07\",\"object\":\"chat.completion\",\"system_fingerprint\":null,\"usage\":{\"completion_tokens\":271,\"prompt_tokens\":8,\"total_tokens\":279,\"completion_tokens_details\":{\"accepted_prediction_tokens\":0,\"audio_tokens\":0,\"reasoning_tokens\":192,\"rejected_prediction_tokens\":0},\"prompt_tokens_details\":{\"audio_tokens\":0,\"cached_tokens\":0}},\"prompt_filter_results\":[{\"content_filter_results\":{},\"prompt_index\":0}]}"
2828
}
2929
},
3030
{
31-
"key": "output.mime_type",
32-
"value": {
33-
"stringValue": "application/json"
31+
"key": "output.mime_type",
32+
"value": {
33+
"stringValue": "application/json"
3434
}
3535
},
3636
{
37-
"key": "llm.invocation_parameters",
38-
"value": {
39-
"stringValue": "{\"model\": \"gpt-5-nano\"}"
37+
"key": "llm.invocation_parameters",
38+
"value": {
39+
"stringValue": "{\"model\": \"gpt-5-nano\"}"
4040
}
4141
},
4242
{
43-
"key": "llm.input_messages.0.message.role",
44-
"value": {
45-
"stringValue": "user"
43+
"key": "llm.input_messages.0.message.role",
44+
"value": {
45+
"stringValue": "user"
4646
}
4747
},
4848
{
49-
"key": "llm.input_messages.0.message.content",
50-
"value": {
51-
"stringValue": "Hello!"
49+
"key": "llm.input_messages.0.message.content",
50+
"value": {
51+
"stringValue": "Hello!"
5252
}
5353
},
5454
{
55-
"key": "llm.model_name",
56-
"value": {
57-
"stringValue": "gpt-5-nano-2025-08-07"
55+
"key": "llm.model_name",
56+
"value": {
57+
"stringValue": "gpt-5-nano-2025-08-07"
5858
}
5959
},
6060
{
61-
"key": "llm.token_count.total",
62-
"value": {
63-
"intValue": "279"
61+
"key": "llm.token_count.total",
62+
"value": {
63+
"intValue": "279"
6464
}
6565
},
6666
{
67-
"key": "llm.token_count.prompt",
68-
"value": {
69-
"intValue": "8"
67+
"key": "llm.token_count.prompt",
68+
"value": {
69+
"intValue": "8"
7070
}
7171
},
7272
{
73-
"key": "llm.token_count.completion",
74-
"value": {
75-
"intValue": "271"
73+
"key": "llm.token_count.completion",
74+
"value": {
75+
"intValue": "271"
7676
}
7777
},
7878
{
79-
"key": "llm.token_count.prompt_details.cache_read",
80-
"value": {
81-
"intValue": "0"
79+
"key": "llm.token_count.prompt_details.cache_read",
80+
"value": {
81+
"intValue": "0"
8282
}
8383
},
8484
{
85-
"key": "llm.token_count.prompt_details.audio",
86-
"value": {
87-
"intValue": "0"
85+
"key": "llm.token_count.prompt_details.audio",
86+
"value": {
87+
"intValue": "0"
8888
}
8989
},
9090
{
91-
"key": "llm.token_count.completion_details.reasoning",
92-
"value": {
93-
"intValue": "192"
91+
"key": "llm.token_count.completion_details.reasoning",
92+
"value": {
93+
"intValue": "192"
9494
}
9595
},
9696
{
97-
"key": "llm.token_count.completion_details.audio",
98-
"value": {
99-
"intValue": "0"
97+
"key": "llm.token_count.completion_details.audio",
98+
"value": {
99+
"intValue": "0"
100100
}
101101
},
102102
{
103-
"key": "llm.output_messages.0.message.role",
104-
"value": {
105-
"stringValue": "assistant"
103+
"key": "llm.output_messages.0.message.role",
104+
"value": {
105+
"stringValue": "assistant"
106106
}
107107
},
108108
{
109-
"key": "llm.output_messages.0.message.content",
110-
"value": {
111-
"stringValue": "Hi there! How can I help today? I can answer questions, brainstorm ideas, write or edit text, explain concepts, help with coding, plan projects, translate languages, or just chat. What would you like to do? If you’re not sure, tell me a topic you’re curious about and I’ll give you a quick rundown."
109+
"key": "llm.output_messages.0.message.content",
110+
"value": {
111+
"stringValue": "Hi there! How can I help today? I can answer questions, brainstorm ideas, write or edit text, explain concepts, help with coding, plan projects, translate languages, or just chat. What would you like to do? If you’re not sure, tell me a topic you’re curious about and I’ll give you a quick rundown."
112112
}
113113
},
114114
{
115-
"key": "openinference.span.kind",
116-
"value": {
117-
"stringValue": "LLM"
115+
"key": "openinference.span.kind",
116+
"value": {
117+
"stringValue": "LLM"
118118
}
119119
}
120120
],
121-
"status": {
122-
"code": "STATUS_CODE_OK"
121+
"status": {
122+
"code": "STATUS_CODE_OK"
123123
}
124124
}

tests/internal/testopeninference/spans/chat-bad-request.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
{
6060
"key": "exception.stacktrace",
6161
"value": {
62-
"stringValue": "Traceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/openinference/instrumentation/openai/_request.py\", line 391, in __call__\n response = await wrapped(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/openai/_base_client.py\", line 1594, in request\n raise self._make_status_error_from_response(err.response) from None\nopenai.BadRequestError: Error code: 400 - {'error': {'code': 'unsupported_parameter', 'message': \"Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.\", 'param': 'max_tokens', 'type': 'invalid_request_error'}}\n"
62+
"stringValue": "Traceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/openinference/instrumentation/openai/_request.py\", line 398, in __call__\n response = await wrapped(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/openai/_base_client.py\", line 1594, in request\n raise self._make_status_error_from_response(err.response) from None\nopenai.BadRequestError: Error code: 400 - {'error': {'code': 'unsupported_parameter', 'message': \"Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.\", 'param': 'max_tokens', 'type': 'invalid_request_error'}}\n"
6363
}
6464
},
6565
{

tests/internal/testopeninference/spans/chat-no-messages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
{
5454
"key": "exception.stacktrace",
5555
"value": {
56-
"stringValue": "Traceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/openinference/instrumentation/openai/_request.py\", line 391, in __call__\n response = await wrapped(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/openai/_base_client.py\", line 1594, in request\n raise self._make_status_error_from_response(err.response) from None\nopenai.BadRequestError: Error code: 400 - {'error': {'code': 'empty_array', 'message': \"Invalid 'messages': empty array. Expected an array with minimum length 1, but got an empty array instead.\", 'param': 'messages', 'type': 'invalid_request_error'}}\n"
56+
"stringValue": "Traceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/openinference/instrumentation/openai/_request.py\", line 398, in __call__\n response = await wrapped(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/openai/_base_client.py\", line 1594, in request\n raise self._make_status_error_from_response(err.response) from None\nopenai.BadRequestError: Error code: 400 - {'error': {'code': 'empty_array', 'message': \"Invalid 'messages': empty array. Expected an array with minimum length 1, but got an empty array instead.\", 'param': 'messages', 'type': 'invalid_request_error'}}\n"
5757
}
5858
},
5959
{

0 commit comments

Comments
 (0)