13
13
# limitations under the License.
14
14
15
15
import json
16
- import os
17
16
import unittest
17
+ from unittest .mock import patch
18
+
19
+ from opentelemetry .instrumentation ._semconv import (
20
+ _OpenTelemetrySemanticConventionStability ,
21
+ _OpenTelemetryStabilitySignalType ,
22
+ _StabilityMode ,
23
+ )
24
+ from opentelemetry .util .genai .types import ContentCapturingMode
18
25
19
26
from .base import TestCase
20
27
@@ -111,10 +118,8 @@ def test_generated_span_counts_tokens(self):
111
118
self .assertEqual (span .attributes ["gen_ai.usage.input_tokens" ], 123 )
112
119
self .assertEqual (span .attributes ["gen_ai.usage.output_tokens" ], 456 )
113
120
121
+ @patch .dict ("os.environ" , {"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" : "true" })
114
122
def test_records_system_prompt_as_log (self ):
115
- os .environ ["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" ] = (
116
- "true"
117
- )
118
123
config = {"system_instruction" : "foo" }
119
124
self .configure_valid_response ()
120
125
self .generate_content (
@@ -125,10 +130,8 @@ def test_records_system_prompt_as_log(self):
125
130
self .assertEqual (event_record .attributes ["gen_ai.system" ], "gemini" )
126
131
self .assertEqual (event_record .body ["content" ], "foo" )
127
132
133
+ @patch .dict ("os.environ" , {"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" : "false" })
128
134
def test_does_not_record_system_prompt_as_log_if_disabled_by_env (self ):
129
- os .environ ["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" ] = (
130
- "false"
131
- )
132
135
config = {"system_instruction" : "foo" }
133
136
self .configure_valid_response ()
134
137
self .generate_content (
@@ -139,42 +142,34 @@ def test_does_not_record_system_prompt_as_log_if_disabled_by_env(self):
139
142
self .assertEqual (event_record .attributes ["gen_ai.system" ], "gemini" )
140
143
self .assertEqual (event_record .body ["content" ], "<elided>" )
141
144
145
+ @patch .dict ("os.environ" , {"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" : "true" })
142
146
def test_does_not_record_system_prompt_as_log_if_no_system_prompt_present (
143
147
self ,
144
148
):
145
- os .environ ["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" ] = (
146
- "true"
147
- )
148
149
self .configure_valid_response ()
149
150
self .generate_content (model = "gemini-2.0-flash" , contents = "Some input" )
150
151
self .otel .assert_does_not_have_event_named ("gen_ai.system.message" )
151
152
153
+ @patch .dict ("os.environ" , {"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" : "true" })
152
154
def test_records_user_prompt_as_log (self ):
153
- os .environ ["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" ] = (
154
- "true"
155
- )
156
155
self .configure_valid_response ()
157
156
self .generate_content (model = "gemini-2.0-flash" , contents = "Some input" )
158
157
self .otel .assert_has_event_named ("gen_ai.user.message" )
159
158
event_record = self .otel .get_event_named ("gen_ai.user.message" )
160
159
self .assertEqual (event_record .attributes ["gen_ai.system" ], "gemini" )
161
160
self .assertEqual (event_record .body ["content" ], "Some input" )
162
161
162
+ @patch .dict ("os.environ" , {"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" : "false" })
163
163
def test_does_not_record_user_prompt_as_log_if_disabled_by_env (self ):
164
- os .environ ["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" ] = (
165
- "false"
166
- )
167
164
self .configure_valid_response ()
168
165
self .generate_content (model = "gemini-2.0-flash" , contents = "Some input" )
169
166
self .otel .assert_has_event_named ("gen_ai.user.message" )
170
167
event_record = self .otel .get_event_named ("gen_ai.user.message" )
171
168
self .assertEqual (event_record .attributes ["gen_ai.system" ], "gemini" )
172
169
self .assertEqual (event_record .body ["content" ], "<elided>" )
173
170
171
+ @patch .dict ("os.environ" , {"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" : "true" })
174
172
def test_records_response_as_log (self ):
175
- os .environ ["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" ] = (
176
- "true"
177
- )
178
173
self .configure_valid_response (text = "Some response content" )
179
174
self .generate_content (model = "gemini-2.0-flash" , contents = "Some input" )
180
175
self .otel .assert_has_event_named ("gen_ai.choice" )
@@ -184,17 +179,46 @@ def test_records_response_as_log(self):
184
179
"Some response content" , json .dumps (event_record .body ["content" ])
185
180
)
186
181
182
+ @patch .dict ("os.environ" , {"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" : "false" })
187
183
def test_does_not_record_response_as_log_if_disabled_by_env (self ):
188
- os .environ ["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" ] = (
189
- "false"
190
- )
191
184
self .configure_valid_response (text = "Some response content" )
192
185
self .generate_content (model = "gemini-2.0-flash" , contents = "Some input" )
193
186
self .otel .assert_has_event_named ("gen_ai.choice" )
194
187
event_record = self .otel .get_event_named ("gen_ai.choice" )
195
188
self .assertEqual (event_record .attributes ["gen_ai.system" ], "gemini" )
196
189
self .assertEqual (event_record .body ["content" ], "<elided>" )
197
190
191
+ def test_new_semconv_record_response_as_log (self ):
192
+ for mode in ContentCapturingMode :
193
+ patched_environ = patch .dict (
194
+ "os.environ" ,
195
+ {
196
+ "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" : mode .name ,
197
+ "OTEL_SEMCONV_STABILITY_OPT_IN" : "gen_ai_latest_experimental" ,
198
+ },
199
+ )
200
+ patched_otel_mapping = patch .dict (
201
+ _OpenTelemetrySemanticConventionStability ._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING ,
202
+ {
203
+ _OpenTelemetryStabilitySignalType .GEN_AI : _StabilityMode .GEN_AI_LATEST_EXPERIMENTAL
204
+ },
205
+ )
206
+ with self .subTest (f'mode: { mode } ' , patched_environ = patched_environ ):
207
+ self .setUp ()
208
+ with patched_environ , patched_otel_mapping :
209
+ self .configure_valid_response (text = "Some response content" )
210
+ self .generate_content (model = "gemini-2.0-flash" , contents = "Some input" )
211
+
212
+ if mode in [
213
+ ContentCapturingMode .NO_CONTENT ,
214
+ ContentCapturingMode .SPAN_ONLY ,
215
+ ]:
216
+ self .otel .assert_does_not_have_event_named ("gen_ai.client.inference.operation.details" )
217
+ else :
218
+ self .otel .assert_has_event_named ("gen_ai.client.inference.operation.details" )
219
+
220
+ self .tearDown ()
221
+
198
222
def test_records_metrics_data (self ):
199
223
self .configure_valid_response ()
200
224
self .generate_content (model = "gemini-2.0-flash" , contents = "Some input" )
0 commit comments