opentelemetry-instrumentation-openai 0.34.1__py3-none-any.whl → 0.49.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentelemetry-instrumentation-openai might be problematic. Click here for more details.
- opentelemetry/instrumentation/openai/__init__.py +11 -6
- opentelemetry/instrumentation/openai/shared/__init__.py +167 -68
- opentelemetry/instrumentation/openai/shared/chat_wrappers.py +544 -231
- opentelemetry/instrumentation/openai/shared/completion_wrappers.py +143 -81
- opentelemetry/instrumentation/openai/shared/config.py +8 -3
- opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +91 -30
- opentelemetry/instrumentation/openai/shared/event_emitter.py +108 -0
- opentelemetry/instrumentation/openai/shared/event_models.py +41 -0
- opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +1 -1
- opentelemetry/instrumentation/openai/shared/span_utils.py +0 -0
- opentelemetry/instrumentation/openai/utils.py +42 -9
- opentelemetry/instrumentation/openai/v0/__init__.py +32 -11
- opentelemetry/instrumentation/openai/v1/__init__.py +177 -69
- opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +208 -109
- opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +41 -19
- opentelemetry/instrumentation/openai/v1/responses_wrappers.py +1073 -0
- opentelemetry/instrumentation/openai/version.py +1 -1
- {opentelemetry_instrumentation_openai-0.34.1.dist-info → opentelemetry_instrumentation_openai-0.49.3.dist-info}/METADATA +7 -8
- opentelemetry_instrumentation_openai-0.49.3.dist-info/RECORD +21 -0
- {opentelemetry_instrumentation_openai-0.34.1.dist-info → opentelemetry_instrumentation_openai-0.49.3.dist-info}/WHEEL +1 -1
- opentelemetry_instrumentation_openai-0.34.1.dist-info/RECORD +0 -17
- {opentelemetry_instrumentation_openai-0.34.1.dist-info → opentelemetry_instrumentation_openai-0.49.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
from typing import Callable, Collection, Optional
|
|
2
|
-
from typing_extensions import Coroutine
|
|
3
2
|
|
|
4
3
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
5
|
-
|
|
6
4
|
from opentelemetry.instrumentation.openai.shared.config import Config
|
|
7
5
|
from opentelemetry.instrumentation.openai.utils import is_openai_v1
|
|
8
|
-
from
|
|
9
|
-
from opentelemetry.instrumentation.openai.v1 import OpenAIV1Instrumentor
|
|
6
|
+
from typing_extensions import Coroutine
|
|
10
7
|
|
|
11
8
|
_instruments = ("openai >= 0.27.0",)
|
|
12
9
|
|
|
@@ -17,33 +14,41 @@ class OpenAIInstrumentor(BaseInstrumentor):
|
|
|
17
14
|
def __init__(
|
|
18
15
|
self,
|
|
19
16
|
enrich_assistant: bool = False,
|
|
20
|
-
enrich_token_usage: bool = False,
|
|
21
17
|
exception_logger=None,
|
|
22
18
|
get_common_metrics_attributes: Callable[[], dict] = lambda: {},
|
|
23
19
|
upload_base64_image: Optional[
|
|
24
20
|
Callable[[str, str, str, str], Coroutine[None, None, str]]
|
|
25
21
|
] = lambda *args: "",
|
|
26
22
|
enable_trace_context_propagation: bool = True,
|
|
23
|
+
use_legacy_attributes: bool = True,
|
|
27
24
|
):
|
|
28
25
|
super().__init__()
|
|
29
26
|
Config.enrich_assistant = enrich_assistant
|
|
30
|
-
Config.enrich_token_usage = enrich_token_usage
|
|
31
27
|
Config.exception_logger = exception_logger
|
|
32
28
|
Config.get_common_metrics_attributes = get_common_metrics_attributes
|
|
33
29
|
Config.upload_base64_image = upload_base64_image
|
|
34
30
|
Config.enable_trace_context_propagation = enable_trace_context_propagation
|
|
31
|
+
Config.use_legacy_attributes = use_legacy_attributes
|
|
35
32
|
|
|
36
33
|
def instrumentation_dependencies(self) -> Collection[str]:
|
|
37
34
|
return _instruments
|
|
38
35
|
|
|
39
36
|
def _instrument(self, **kwargs):
|
|
40
37
|
if is_openai_v1():
|
|
38
|
+
from opentelemetry.instrumentation.openai.v1 import OpenAIV1Instrumentor
|
|
39
|
+
|
|
41
40
|
OpenAIV1Instrumentor().instrument(**kwargs)
|
|
42
41
|
else:
|
|
42
|
+
from opentelemetry.instrumentation.openai.v0 import OpenAIV0Instrumentor
|
|
43
|
+
|
|
43
44
|
OpenAIV0Instrumentor().instrument(**kwargs)
|
|
44
45
|
|
|
45
46
|
def _uninstrument(self, **kwargs):
|
|
46
47
|
if is_openai_v1():
|
|
48
|
+
from opentelemetry.instrumentation.openai.v1 import OpenAIV1Instrumentor
|
|
49
|
+
|
|
47
50
|
OpenAIV1Instrumentor().uninstrument(**kwargs)
|
|
48
51
|
else:
|
|
52
|
+
from opentelemetry.instrumentation.openai.v0 import OpenAIV0Instrumentor
|
|
53
|
+
|
|
49
54
|
OpenAIV0Instrumentor().uninstrument(**kwargs)
|
|
@@ -1,42 +1,41 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import openai
|
|
3
1
|
import json
|
|
4
|
-
import types
|
|
5
2
|
import logging
|
|
6
|
-
|
|
3
|
+
import types
|
|
4
|
+
import openai
|
|
5
|
+
import pydantic
|
|
7
6
|
from importlib.metadata import version
|
|
8
7
|
|
|
9
|
-
from opentelemetry import context as context_api
|
|
10
|
-
from opentelemetry.trace.propagation import set_span_in_context
|
|
11
|
-
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
12
|
-
|
|
13
8
|
from opentelemetry.instrumentation.openai.shared.config import Config
|
|
14
|
-
from opentelemetry.semconv_ai import SpanAttributes
|
|
15
9
|
from opentelemetry.instrumentation.openai.utils import (
|
|
16
10
|
dont_throw,
|
|
17
11
|
is_openai_v1,
|
|
18
|
-
should_record_stream_token_usage,
|
|
19
12
|
)
|
|
13
|
+
from opentelemetry.semconv._incubating.attributes import (
|
|
14
|
+
gen_ai_attributes as GenAIAttributes,
|
|
15
|
+
openai_attributes as OpenAIAttributes,
|
|
16
|
+
)
|
|
17
|
+
from opentelemetry.semconv_ai import SpanAttributes
|
|
18
|
+
from opentelemetry.trace.propagation import set_span_in_context
|
|
19
|
+
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
20
20
|
|
|
21
21
|
OPENAI_LLM_USAGE_TOKEN_TYPES = ["prompt_tokens", "completion_tokens"]
|
|
22
22
|
PROMPT_FILTER_KEY = "prompt_filter_results"
|
|
23
23
|
PROMPT_ERROR = "prompt_error"
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
_PYDANTIC_VERSION = version("pydantic")
|
|
26
|
+
|
|
27
27
|
|
|
28
28
|
logger = logging.getLogger(__name__)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
).lower() == "true" or context_api.get_value("override_enable_content_tracing")
|
|
31
|
+
def _set_span_attribute(span, name, value):
|
|
32
|
+
if value is None or value == "":
|
|
33
|
+
return
|
|
35
34
|
|
|
35
|
+
if hasattr(openai, "NOT_GIVEN") and value == openai.NOT_GIVEN:
|
|
36
|
+
return
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
if value is not None and value != "" and value != openai.NOT_GIVEN:
|
|
39
|
-
span.set_attribute(name, value)
|
|
38
|
+
span.set_attribute(name, value)
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
def _set_client_attributes(span, instance):
|
|
@@ -103,20 +102,30 @@ def set_tools_attributes(span, tools):
|
|
|
103
102
|
)
|
|
104
103
|
|
|
105
104
|
|
|
106
|
-
def _set_request_attributes(span, kwargs):
|
|
105
|
+
def _set_request_attributes(span, kwargs, instance=None):
|
|
107
106
|
if not span.is_recording():
|
|
108
107
|
return
|
|
109
108
|
|
|
110
109
|
_set_api_attributes(span)
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
|
|
111
|
+
base_url = _get_openai_base_url(instance) if instance else ""
|
|
112
|
+
vendor = _get_vendor_from_url(base_url)
|
|
113
|
+
_set_span_attribute(span, GenAIAttributes.GEN_AI_SYSTEM, vendor)
|
|
114
|
+
|
|
115
|
+
model = kwargs.get("model")
|
|
116
|
+
if vendor == "AWS" and model and "." in model:
|
|
117
|
+
model = _cross_region_check(model)
|
|
118
|
+
elif vendor == "OpenRouter":
|
|
119
|
+
model = _extract_model_name_from_provider_format(model)
|
|
120
|
+
|
|
121
|
+
_set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_MODEL, model)
|
|
113
122
|
_set_span_attribute(
|
|
114
|
-
span,
|
|
123
|
+
span, GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS, kwargs.get("max_tokens")
|
|
115
124
|
)
|
|
116
125
|
_set_span_attribute(
|
|
117
|
-
span,
|
|
126
|
+
span, GenAIAttributes.GEN_AI_REQUEST_TEMPERATURE, kwargs.get("temperature")
|
|
118
127
|
)
|
|
119
|
-
_set_span_attribute(span,
|
|
128
|
+
_set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_TOP_P, kwargs.get("top_p"))
|
|
120
129
|
_set_span_attribute(
|
|
121
130
|
span, SpanAttributes.LLM_FREQUENCY_PENALTY, kwargs.get("frequency_penalty")
|
|
122
131
|
)
|
|
@@ -133,6 +142,52 @@ def _set_request_attributes(span, kwargs):
|
|
|
133
142
|
_set_span_attribute(
|
|
134
143
|
span, SpanAttributes.LLM_IS_STREAMING, kwargs.get("stream") or False
|
|
135
144
|
)
|
|
145
|
+
_set_span_attribute(
|
|
146
|
+
span, OpenAIAttributes.OPENAI_REQUEST_SERVICE_TIER, kwargs.get("service_tier")
|
|
147
|
+
)
|
|
148
|
+
if response_format := kwargs.get("response_format"):
|
|
149
|
+
# backward-compatible check for
|
|
150
|
+
# openai.types.shared_params.response_format_json_schema.ResponseFormatJSONSchema
|
|
151
|
+
if (
|
|
152
|
+
isinstance(response_format, dict)
|
|
153
|
+
and response_format.get("type") == "json_schema"
|
|
154
|
+
and response_format.get("json_schema")
|
|
155
|
+
):
|
|
156
|
+
schema = dict(response_format.get("json_schema")).get("schema")
|
|
157
|
+
if schema:
|
|
158
|
+
_set_span_attribute(
|
|
159
|
+
span,
|
|
160
|
+
SpanAttributes.LLM_REQUEST_STRUCTURED_OUTPUT_SCHEMA,
|
|
161
|
+
json.dumps(schema),
|
|
162
|
+
)
|
|
163
|
+
elif (
|
|
164
|
+
isinstance(response_format, pydantic.BaseModel)
|
|
165
|
+
or (
|
|
166
|
+
hasattr(response_format, "model_json_schema")
|
|
167
|
+
and callable(response_format.model_json_schema)
|
|
168
|
+
)
|
|
169
|
+
):
|
|
170
|
+
_set_span_attribute(
|
|
171
|
+
span,
|
|
172
|
+
SpanAttributes.LLM_REQUEST_STRUCTURED_OUTPUT_SCHEMA,
|
|
173
|
+
json.dumps(response_format.model_json_schema()),
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
schema = None
|
|
177
|
+
try:
|
|
178
|
+
schema = json.dumps(pydantic.TypeAdapter(response_format).json_schema())
|
|
179
|
+
except Exception:
|
|
180
|
+
try:
|
|
181
|
+
schema = json.dumps(response_format)
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
if schema:
|
|
186
|
+
_set_span_attribute(
|
|
187
|
+
span,
|
|
188
|
+
SpanAttributes.LLM_REQUEST_STRUCTURED_OUTPUT_SCHEMA,
|
|
189
|
+
schema,
|
|
190
|
+
)
|
|
136
191
|
|
|
137
192
|
|
|
138
193
|
@dont_throw
|
|
@@ -143,20 +198,28 @@ def _set_response_attributes(span, response):
|
|
|
143
198
|
if "error" in response:
|
|
144
199
|
_set_span_attribute(
|
|
145
200
|
span,
|
|
146
|
-
f"{
|
|
201
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{PROMPT_ERROR}",
|
|
147
202
|
json.dumps(response.get("error")),
|
|
148
203
|
)
|
|
149
204
|
return
|
|
150
205
|
|
|
151
|
-
|
|
206
|
+
response_model = response.get("model")
|
|
207
|
+
if response_model:
|
|
208
|
+
response_model = _extract_model_name_from_provider_format(response_model)
|
|
209
|
+
_set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_MODEL, response_model)
|
|
210
|
+
_set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_ID, response.get("id"))
|
|
152
211
|
|
|
153
212
|
_set_span_attribute(
|
|
154
213
|
span,
|
|
155
214
|
SpanAttributes.LLM_OPENAI_RESPONSE_SYSTEM_FINGERPRINT,
|
|
156
215
|
response.get("system_fingerprint"),
|
|
157
216
|
)
|
|
217
|
+
_set_span_attribute(
|
|
218
|
+
span,
|
|
219
|
+
OpenAIAttributes.OPENAI_RESPONSE_SERVICE_TIER,
|
|
220
|
+
response.get("service_tier"),
|
|
221
|
+
)
|
|
158
222
|
_log_prompt_filter(span, response)
|
|
159
|
-
|
|
160
223
|
usage = response.get("usage")
|
|
161
224
|
if not usage:
|
|
162
225
|
return
|
|
@@ -169,11 +232,17 @@ def _set_response_attributes(span, response):
|
|
|
169
232
|
)
|
|
170
233
|
_set_span_attribute(
|
|
171
234
|
span,
|
|
172
|
-
|
|
235
|
+
GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS,
|
|
173
236
|
usage.get("completion_tokens"),
|
|
174
237
|
)
|
|
175
238
|
_set_span_attribute(
|
|
176
|
-
span,
|
|
239
|
+
span, GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS, usage.get("prompt_tokens")
|
|
240
|
+
)
|
|
241
|
+
prompt_tokens_details = dict(usage.get("prompt_tokens_details", {}))
|
|
242
|
+
_set_span_attribute(
|
|
243
|
+
span,
|
|
244
|
+
SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS,
|
|
245
|
+
prompt_tokens_details.get("cached_tokens", 0),
|
|
177
246
|
)
|
|
178
247
|
return
|
|
179
248
|
|
|
@@ -182,7 +251,7 @@ def _log_prompt_filter(span, response_dict):
|
|
|
182
251
|
if response_dict.get("prompt_filter_results"):
|
|
183
252
|
_set_span_attribute(
|
|
184
253
|
span,
|
|
185
|
-
f"{
|
|
254
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{PROMPT_FILTER_KEY}",
|
|
186
255
|
json.dumps(response_dict.get("prompt_filter_results")),
|
|
187
256
|
)
|
|
188
257
|
|
|
@@ -192,17 +261,17 @@ def _set_span_stream_usage(span, prompt_tokens, completion_tokens):
|
|
|
192
261
|
if not span.is_recording():
|
|
193
262
|
return
|
|
194
263
|
|
|
195
|
-
if
|
|
264
|
+
if isinstance(completion_tokens, int) and completion_tokens >= 0:
|
|
196
265
|
_set_span_attribute(
|
|
197
|
-
span,
|
|
266
|
+
span, GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS, completion_tokens
|
|
198
267
|
)
|
|
199
268
|
|
|
200
|
-
if
|
|
201
|
-
_set_span_attribute(span,
|
|
269
|
+
if isinstance(prompt_tokens, int) and prompt_tokens >= 0:
|
|
270
|
+
_set_span_attribute(span, GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS, prompt_tokens)
|
|
202
271
|
|
|
203
272
|
if (
|
|
204
|
-
|
|
205
|
-
and
|
|
273
|
+
isinstance(prompt_tokens, int)
|
|
274
|
+
and isinstance(completion_tokens, int)
|
|
206
275
|
and completion_tokens + prompt_tokens >= 0
|
|
207
276
|
):
|
|
208
277
|
_set_span_attribute(
|
|
@@ -221,6 +290,53 @@ def _get_openai_base_url(instance):
|
|
|
221
290
|
return ""
|
|
222
291
|
|
|
223
292
|
|
|
293
|
+
def _get_vendor_from_url(base_url):
|
|
294
|
+
if not base_url:
|
|
295
|
+
return "openai"
|
|
296
|
+
|
|
297
|
+
if "openai.azure.com" in base_url:
|
|
298
|
+
return "Azure"
|
|
299
|
+
elif "amazonaws.com" in base_url or "bedrock" in base_url:
|
|
300
|
+
return "AWS"
|
|
301
|
+
elif "googleapis.com" in base_url or "vertex" in base_url:
|
|
302
|
+
return "Google"
|
|
303
|
+
elif "openrouter.ai" in base_url:
|
|
304
|
+
return "OpenRouter"
|
|
305
|
+
|
|
306
|
+
return "openai"
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _cross_region_check(value):
|
|
310
|
+
if not value or "." not in value:
|
|
311
|
+
return value
|
|
312
|
+
|
|
313
|
+
prefixes = ["us", "us-gov", "eu", "apac"]
|
|
314
|
+
if any(value.startswith(prefix + ".") for prefix in prefixes):
|
|
315
|
+
parts = value.split(".")
|
|
316
|
+
if len(parts) > 2:
|
|
317
|
+
return parts[2]
|
|
318
|
+
else:
|
|
319
|
+
return value
|
|
320
|
+
else:
|
|
321
|
+
vendor, model = value.split(".", 1)
|
|
322
|
+
return model
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _extract_model_name_from_provider_format(model_name):
|
|
326
|
+
"""
|
|
327
|
+
Extract model name from provider/model format.
|
|
328
|
+
E.g., 'openai/gpt-4o' -> 'gpt-4o', 'anthropic/claude-3-sonnet' -> 'claude-3-sonnet'
|
|
329
|
+
"""
|
|
330
|
+
if not model_name:
|
|
331
|
+
return model_name
|
|
332
|
+
|
|
333
|
+
if "/" in model_name:
|
|
334
|
+
parts = model_name.split("/")
|
|
335
|
+
return parts[-1] # Return the last part (actual model name)
|
|
336
|
+
|
|
337
|
+
return model_name
|
|
338
|
+
|
|
339
|
+
|
|
224
340
|
def is_streaming_response(response):
|
|
225
341
|
if is_openai_v1():
|
|
226
342
|
return isinstance(response, openai.Stream) or isinstance(
|
|
@@ -235,7 +351,7 @@ def is_streaming_response(response):
|
|
|
235
351
|
def model_as_dict(model):
|
|
236
352
|
if isinstance(model, dict):
|
|
237
353
|
return model
|
|
238
|
-
if
|
|
354
|
+
if _PYDANTIC_VERSION < "2.0.0":
|
|
239
355
|
return model.dict()
|
|
240
356
|
if hasattr(model, "model_dump"):
|
|
241
357
|
return model.model_dump()
|
|
@@ -245,30 +361,6 @@ def model_as_dict(model):
|
|
|
245
361
|
return model
|
|
246
362
|
|
|
247
363
|
|
|
248
|
-
def get_token_count_from_string(string: str, model_name: str):
|
|
249
|
-
if not should_record_stream_token_usage():
|
|
250
|
-
return None
|
|
251
|
-
|
|
252
|
-
import tiktoken
|
|
253
|
-
|
|
254
|
-
if tiktoken_encodings.get(model_name) is None:
|
|
255
|
-
try:
|
|
256
|
-
encoding = tiktoken.encoding_for_model(model_name)
|
|
257
|
-
except KeyError as ex:
|
|
258
|
-
# no such model_name in tiktoken
|
|
259
|
-
logger.warning(
|
|
260
|
-
f"Failed to get tiktoken encoding for model_name {model_name}, error: {str(ex)}"
|
|
261
|
-
)
|
|
262
|
-
return None
|
|
263
|
-
|
|
264
|
-
tiktoken_encodings[model_name] = encoding
|
|
265
|
-
else:
|
|
266
|
-
encoding = tiktoken_encodings.get(model_name)
|
|
267
|
-
|
|
268
|
-
token_count = len(encoding.encode(string))
|
|
269
|
-
return token_count
|
|
270
|
-
|
|
271
|
-
|
|
272
364
|
def _token_type(token_type: str):
|
|
273
365
|
if token_type == "prompt_tokens":
|
|
274
366
|
return "input"
|
|
@@ -282,11 +374,12 @@ def metric_shared_attributes(
|
|
|
282
374
|
response_model: str, operation: str, server_address: str, is_streaming: bool = False
|
|
283
375
|
):
|
|
284
376
|
attributes = Config.get_common_metrics_attributes()
|
|
377
|
+
vendor = _get_vendor_from_url(server_address)
|
|
285
378
|
|
|
286
379
|
return {
|
|
287
380
|
**attributes,
|
|
288
|
-
|
|
289
|
-
|
|
381
|
+
GenAIAttributes.GEN_AI_SYSTEM: vendor,
|
|
382
|
+
GenAIAttributes.GEN_AI_RESPONSE_MODEL: response_model,
|
|
290
383
|
"gen_ai.operation.name": operation,
|
|
291
384
|
"server.address": server_address,
|
|
292
385
|
"stream": is_streaming,
|
|
@@ -294,7 +387,13 @@ def metric_shared_attributes(
|
|
|
294
387
|
|
|
295
388
|
|
|
296
389
|
def propagate_trace_context(span, kwargs):
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
390
|
+
if is_openai_v1():
|
|
391
|
+
extra_headers = kwargs.get("extra_headers", {})
|
|
392
|
+
ctx = set_span_in_context(span)
|
|
393
|
+
TraceContextTextMapPropagator().inject(extra_headers, context=ctx)
|
|
394
|
+
kwargs["extra_headers"] = extra_headers
|
|
395
|
+
else:
|
|
396
|
+
headers = kwargs.get("headers", {})
|
|
397
|
+
ctx = set_span_in_context(span)
|
|
398
|
+
TraceContextTextMapPropagator().inject(headers, context=ctx)
|
|
399
|
+
kwargs["headers"] = headers
|