opentelemetry-instrumentation-botocore 0.54b1__py3-none-any.whl → 0.55b0__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.
@@ -109,6 +109,9 @@ from opentelemetry.instrumentation.utils import (
109
109
  )
110
110
  from opentelemetry.metrics import Instrument, Meter, get_meter
111
111
  from opentelemetry.propagators.aws.aws_xray_propagator import AwsXRayPropagator
112
+ from opentelemetry.semconv._incubating.attributes.cloud_attributes import (
113
+ CLOUD_REGION,
114
+ )
112
115
  from opentelemetry.semconv.trace import SpanAttributes
113
116
  from opentelemetry.trace import get_tracer
114
117
  from opentelemetry.trace.span import Span
@@ -276,8 +279,7 @@ class BotocoreInstrumentor(BaseInstrumentor):
276
279
  SpanAttributes.RPC_SYSTEM: "aws-api",
277
280
  SpanAttributes.RPC_SERVICE: call_context.service_id,
278
281
  SpanAttributes.RPC_METHOD: call_context.operation,
279
- # TODO: update when semantic conventions exist
280
- "aws.region": call_context.region,
282
+ CLOUD_REGION: call_context.region,
281
283
  **get_server_attributes(call_context.endpoint_url),
282
284
  }
283
285
 
@@ -499,18 +499,20 @@ class _BedrockRuntimeExtension(_AwsSdkExtension):
499
499
  [stop_reason],
500
500
  )
501
501
 
502
- event_logger = instrumentor_context.event_logger
503
- choice = _Choice.from_converse(result, capture_content)
504
- # this path is used by streaming apis, in that case we are already out of the span
505
- # context so need to add the span context manually
506
- span_ctx = span.get_span_context()
507
- event_logger.emit(
508
- choice.to_choice_event(
509
- trace_id=span_ctx.trace_id,
510
- span_id=span_ctx.span_id,
511
- trace_flags=span_ctx.trace_flags,
502
+ # In case of an early stream closure, the result may not contain outputs
503
+ if self._stream_has_output_content(result):
504
+ event_logger = instrumentor_context.event_logger
505
+ choice = _Choice.from_converse(result, capture_content)
506
+ # this path is used by streaming apis, in that case we are already out of the span
507
+ # context so need to add the span context manually
508
+ span_ctx = span.get_span_context()
509
+ event_logger.emit(
510
+ choice.to_choice_event(
511
+ trace_id=span_ctx.trace_id,
512
+ span_id=span_ctx.span_id,
513
+ trace_flags=span_ctx.trace_flags,
514
+ )
512
515
  )
513
- )
514
516
 
515
517
  metrics = instrumentor_context.metrics
516
518
  metrics_attributes = self._extract_metrics_attributes()
@@ -602,11 +604,14 @@ class _BedrockRuntimeExtension(_AwsSdkExtension):
602
604
  span: Span,
603
605
  exception,
604
606
  instrumentor_context: _BotocoreInstrumentorContext,
607
+ span_ended: bool,
605
608
  ):
606
609
  span.set_status(Status(StatusCode.ERROR, str(exception)))
607
610
  if span.is_recording():
608
611
  span.set_attribute(ERROR_TYPE, type(exception).__qualname__)
609
- span.end()
612
+
613
+ if not span_ended:
614
+ span.end()
610
615
 
611
616
  metrics = instrumentor_context.metrics
612
617
  metrics_attributes = {
@@ -638,15 +643,17 @@ class _BedrockRuntimeExtension(_AwsSdkExtension):
638
643
  result["stream"], EventStream
639
644
  ):
640
645
 
641
- def stream_done_callback(response):
646
+ def stream_done_callback(response, span_ended):
642
647
  self._converse_on_success(
643
648
  span, response, instrumentor_context, capture_content
644
649
  )
645
- span.end()
646
650
 
647
- def stream_error_callback(exception):
651
+ if not span_ended:
652
+ span.end()
653
+
654
+ def stream_error_callback(exception, span_ended):
648
655
  self._on_stream_error_callback(
649
- span, exception, instrumentor_context
656
+ span, exception, instrumentor_context, span_ended
650
657
  )
651
658
 
652
659
  result["stream"] = ConverseStreamWrapper(
@@ -677,16 +684,17 @@ class _BedrockRuntimeExtension(_AwsSdkExtension):
677
684
  elif self._call_context.operation == "InvokeModelWithResponseStream":
678
685
  if "body" in result and isinstance(result["body"], EventStream):
679
686
 
680
- def invoke_model_stream_done_callback(response):
687
+ def invoke_model_stream_done_callback(response, span_ended):
681
688
  # the callback gets data formatted as the simpler converse API
682
689
  self._converse_on_success(
683
690
  span, response, instrumentor_context, capture_content
684
691
  )
685
- span.end()
692
+ if not span_ended:
693
+ span.end()
686
694
 
687
- def invoke_model_stream_error_callback(exception):
695
+ def invoke_model_stream_error_callback(exception, span_ended):
688
696
  self._on_stream_error_callback(
689
- span, exception, instrumentor_context
697
+ span, exception, instrumentor_context, span_ended
690
698
  )
691
699
 
692
700
  result["body"] = InvokeModelWithResponseStreamWrapper(
@@ -781,9 +789,11 @@ class _BedrockRuntimeExtension(_AwsSdkExtension):
781
789
  GEN_AI_RESPONSE_FINISH_REASONS, [response_body["stopReason"]]
782
790
  )
783
791
 
784
- event_logger = instrumentor_context.event_logger
785
- choice = _Choice.from_converse(response_body, capture_content)
786
- event_logger.emit(choice.to_choice_event())
792
+ # In case of an early stream closure, the result may not contain outputs
793
+ if self._stream_has_output_content(response_body):
794
+ event_logger = instrumentor_context.event_logger
795
+ choice = _Choice.from_converse(response_body, capture_content)
796
+ event_logger.emit(choice.to_choice_event())
787
797
 
788
798
  metrics = instrumentor_context.metrics
789
799
  metrics_attributes = self._extract_metrics_attributes()
@@ -1004,3 +1014,8 @@ class _BedrockRuntimeExtension(_AwsSdkExtension):
1004
1014
  duration,
1005
1015
  attributes=metrics_attributes,
1006
1016
  )
1017
+
1018
+ def _stream_has_output_content(self, response_body: dict[str, Any]):
1019
+ return (
1020
+ "output" in response_body and "message" in response_body["output"]
1021
+ )
@@ -64,7 +64,9 @@ class ConverseStreamWrapper(ObjectProxy):
64
64
  self._response = {}
65
65
  self._message = None
66
66
  self._content_block = {}
67
+ self._tool_json_input_buf = ""
67
68
  self._record_message = False
69
+ self._ended = False
68
70
 
69
71
  def __iter__(self):
70
72
  try:
@@ -72,7 +74,7 @@ class ConverseStreamWrapper(ObjectProxy):
72
74
  self._process_event(event)
73
75
  yield event
74
76
  except EventStreamError as exc:
75
- self._stream_error_callback(exc)
77
+ self._handle_stream_error(exc)
76
78
  raise
77
79
 
78
80
  def _process_event(self, event):
@@ -88,28 +90,40 @@ class ConverseStreamWrapper(ObjectProxy):
88
90
  # {'contentBlockStart': {'start': {'toolUse': {'toolUseId': 'id', 'name': 'func_name'}}, 'contentBlockIndex': 1}}
89
91
  start = event["contentBlockStart"].get("start", {})
90
92
  if "toolUse" in start:
91
- tool_use = _decode_tool_use(start["toolUse"])
92
- self._content_block = {"toolUse": tool_use}
93
+ self._content_block = {"toolUse": start["toolUse"]}
93
94
  return
94
95
 
95
96
  if "contentBlockDelta" in event:
96
97
  # {'contentBlockDelta': {'delta': {'text': "Hello"}, 'contentBlockIndex': 0}}
97
98
  # {'contentBlockDelta': {'delta': {'toolUse': {'input': '{"location":"Seattle"}'}}, 'contentBlockIndex': 1}}
99
+ # {'contentBlockDelta': {'delta': {'toolUse': {'input': 'a", "Yok'}}, 'contentBlockIndex': 1}}
98
100
  if self._record_message:
99
101
  delta = event["contentBlockDelta"].get("delta", {})
100
102
  if "text" in delta:
101
103
  self._content_block.setdefault("text", "")
102
104
  self._content_block["text"] += delta["text"]
103
105
  elif "toolUse" in delta:
104
- tool_use = _decode_tool_use(delta["toolUse"])
105
- self._content_block["toolUse"].update(tool_use)
106
+ if (
107
+ input_buf := delta["toolUse"].get("input")
108
+ ) is not None:
109
+ self._tool_json_input_buf += input_buf
106
110
  return
107
111
 
108
112
  if "contentBlockStop" in event:
109
113
  # {'contentBlockStop': {'contentBlockIndex': 0}}
110
114
  if self._record_message:
115
+ if self._tool_json_input_buf:
116
+ try:
117
+ self._content_block["toolUse"]["input"] = json.loads(
118
+ self._tool_json_input_buf
119
+ )
120
+ except json.DecodeError:
121
+ self._content_block["toolUse"]["input"] = (
122
+ self._tool_json_input_buf
123
+ )
111
124
  self._message["content"].append(self._content_block)
112
125
  self._content_block = {}
126
+ self._tool_json_input_buf = ""
113
127
  return
114
128
 
115
129
  if "messageStop" in event:
@@ -133,11 +147,23 @@ class ConverseStreamWrapper(ObjectProxy):
133
147
 
134
148
  if output_tokens := usage.get("outputTokens"):
135
149
  self._response["usage"]["outputTokens"] = output_tokens
136
-
137
- self._stream_done_callback(self._response)
150
+ self._complete_stream(self._response)
138
151
 
139
152
  return
140
153
 
154
+ def close(self):
155
+ self.__wrapped__.close()
156
+ # Treat the stream as done to ensure the span end.
157
+ self._complete_stream(self._response)
158
+
159
+ def _complete_stream(self, response):
160
+ self._stream_done_callback(response, self._ended)
161
+ self._ended = True
162
+
163
+ def _handle_stream_error(self, exc):
164
+ self._stream_error_callback(exc, self._ended)
165
+ self._ended = True
166
+
141
167
 
142
168
  # pylint: disable=abstract-method
143
169
  class InvokeModelWithResponseStreamWrapper(ObjectProxy):
@@ -163,6 +189,20 @@ class InvokeModelWithResponseStreamWrapper(ObjectProxy):
163
189
  self._content_block = {}
164
190
  self._tool_json_input_buf = ""
165
191
  self._record_message = False
192
+ self._ended = False
193
+
194
+ def close(self):
195
+ self.__wrapped__.close()
196
+ # Treat the stream as done to ensure the span end.
197
+ self._stream_done_callback(self._response, self._ended)
198
+
199
+ def _complete_stream(self, response):
200
+ self._stream_done_callback(response, self._ended)
201
+ self._ended = True
202
+
203
+ def _handle_stream_error(self, exc):
204
+ self._stream_error_callback(exc, self._ended)
205
+ self._ended = True
166
206
 
167
207
  def __iter__(self):
168
208
  try:
@@ -170,7 +210,7 @@ class InvokeModelWithResponseStreamWrapper(ObjectProxy):
170
210
  self._process_event(event)
171
211
  yield event
172
212
  except EventStreamError as exc:
173
- self._stream_error_callback(exc)
213
+ self._handle_stream_error(exc)
174
214
  raise
175
215
 
176
216
  def _process_event(self, event):
@@ -213,7 +253,7 @@ class InvokeModelWithResponseStreamWrapper(ObjectProxy):
213
253
  self._response["output"] = {
214
254
  "message": {"content": [{"text": chunk["outputText"]}]}
215
255
  }
216
- self._stream_done_callback(self._response)
256
+ self._complete_stream(self._response)
217
257
 
218
258
  def _process_amazon_nova_chunk(self, chunk):
219
259
  # pylint: disable=too-many-branches
@@ -283,7 +323,7 @@ class InvokeModelWithResponseStreamWrapper(ObjectProxy):
283
323
  if output_tokens := usage.get("outputTokens"):
284
324
  self._response["usage"]["outputTokens"] = output_tokens
285
325
 
286
- self._stream_done_callback(self._response)
326
+ self._complete_stream(self._response)
287
327
  return
288
328
 
289
329
  def _process_anthropic_claude_chunk(self, chunk):
@@ -355,7 +395,7 @@ class InvokeModelWithResponseStreamWrapper(ObjectProxy):
355
395
  self._record_message = False
356
396
  self._message = None
357
397
 
358
- self._stream_done_callback(self._response)
398
+ self._complete_stream(self._response)
359
399
  return
360
400
 
361
401
 
@@ -382,7 +422,9 @@ def extract_tool_calls(
382
422
  tool_uses = [item["toolUse"] for item in content if "toolUse" in item]
383
423
  if not tool_uses:
384
424
  tool_uses = [
385
- item for item in content if item.get("type") == "tool_use"
425
+ item
426
+ for item in content
427
+ if isinstance(item, dict) and item.get("type") == "tool_use"
386
428
  ]
387
429
  tool_id_key = "id"
388
430
  else:
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "0.54b1"
15
+ __version__ = "0.55b0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opentelemetry-instrumentation-botocore
3
- Version: 0.54b1
3
+ Version: 0.55b0
4
4
  Summary: OpenTelemetry Botocore instrumentation
5
5
  Project-URL: Homepage, https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-botocore
6
6
  Project-URL: Repository, https://github.com/open-telemetry/opentelemetry-python-contrib
@@ -12,17 +12,16 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: Apache Software License
13
13
  Classifier: Programming Language :: Python
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.8
16
15
  Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Programming Language :: Python :: 3.13
21
- Requires-Python: >=3.8
20
+ Requires-Python: >=3.9
22
21
  Requires-Dist: opentelemetry-api~=1.30
23
- Requires-Dist: opentelemetry-instrumentation==0.54b1
22
+ Requires-Dist: opentelemetry-instrumentation==0.55b0
24
23
  Requires-Dist: opentelemetry-propagator-aws-xray~=1.0
25
- Requires-Dist: opentelemetry-semantic-conventions==0.54b1
24
+ Requires-Dist: opentelemetry-semantic-conventions==0.55b0
26
25
  Provides-Extra: instruments
27
26
  Requires-Dist: botocore~=1.0; extra == 'instruments'
28
27
  Description-Content-Type: text/x-rst
@@ -1,19 +1,19 @@
1
- opentelemetry/instrumentation/botocore/__init__.py,sha256=O2RNAc7SGEWSaBnniTfedrIsNDER01h4VRPJgxxIG_8,14708
1
+ opentelemetry/instrumentation/botocore/__init__.py,sha256=6GGOoQveHmR8n748grJWEwHJALt9GG0H6mY5J-QXTMo,14745
2
2
  opentelemetry/instrumentation/botocore/environment_variables.py,sha256=c1lrIEj5wwxZwLd5ppJsfGADBfQLnb_HuxXDLv7ul6s,114
3
3
  opentelemetry/instrumentation/botocore/package.py,sha256=6xvfRpU_C3wlSO6pto7MhGtkPoCHDEiRO_Fh4DiC_50,622
4
4
  opentelemetry/instrumentation/botocore/utils.py,sha256=dM5LW5PjIdErBFESyxh6b8oBvbmQ7dPyYOn1YLOUFaQ,1191
5
- opentelemetry/instrumentation/botocore/version.py,sha256=LAnaEWDOviU2kJ-Xmrhq1biqtDukCylZDisfX_ER5Ng,608
5
+ opentelemetry/instrumentation/botocore/version.py,sha256=XLYywkDPmlM8y9jKdb0LLOUGn_o2miwtu9yAFKJXNeI,608
6
6
  opentelemetry/instrumentation/botocore/extensions/__init__.py,sha256=IqMWsCI0HL8gvL8-0Svn9Rp7dz2HKMcIuhRRM0twmCU,1857
7
7
  opentelemetry/instrumentation/botocore/extensions/_messaging.py,sha256=ca2Uwyb1vxWu5qUkKTlfn9KJFN6k8HOTrrBYvwX4WzA,1636
8
- opentelemetry/instrumentation/botocore/extensions/bedrock.py,sha256=Z0emYAoeP9RzuddnqWr8I-xzI3LAKp0eGc7-WSMKZmo,37169
9
- opentelemetry/instrumentation/botocore/extensions/bedrock_utils.py,sha256=KyiL_RpU4c-gyXojJcMgXe2IK5DYVY17cyx3iZrI8UI,22178
8
+ opentelemetry/instrumentation/botocore/extensions/bedrock.py,sha256=6voYLFeecQwyCyBJIC6n78a6jKVevs28YK-2EjNG9-c,37899
9
+ opentelemetry/instrumentation/botocore/extensions/bedrock_utils.py,sha256=Hn7i2aUJ9WhypYdn5sD8uwbrNObyMDS-6rvtYrJBYwQ,23717
10
10
  opentelemetry/instrumentation/botocore/extensions/dynamodb.py,sha256=cmTzHnLCO731v8L5wN-rPRyVgQHmRvH3tuG5wrFhqyA,13745
11
11
  opentelemetry/instrumentation/botocore/extensions/lmbd.py,sha256=mqPbgwDFy3XYg-pLo6A6eNu0iKSGa2-tPLwroJDuavY,4253
12
12
  opentelemetry/instrumentation/botocore/extensions/sns.py,sha256=MfppfL91tAbAjp6CIiqvYIuXQRq-PeIYbB1ZKWO5kW4,5334
13
13
  opentelemetry/instrumentation/botocore/extensions/sqs.py,sha256=9_LjlzQ0Sg92hgaL8P31cbpq_C71qCTucjj0SX1Ct5o,2916
14
14
  opentelemetry/instrumentation/botocore/extensions/types.py,sha256=jCIJXt0Zvdn2qVwX6bLUaCLgBPy6XyJ-nYEwurxAZo8,6681
15
- opentelemetry_instrumentation_botocore-0.54b1.dist-info/METADATA,sha256=S5XHFnHRY2tMF3FfFkyY86c_krOw0ewBnDQwQi4rnxw,3101
16
- opentelemetry_instrumentation_botocore-0.54b1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- opentelemetry_instrumentation_botocore-0.54b1.dist-info/entry_points.txt,sha256=v5hzQbZMJ61JuhBNq5jHYAapvv3C_486h9CTqxlkUTM,100
18
- opentelemetry_instrumentation_botocore-0.54b1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
19
- opentelemetry_instrumentation_botocore-0.54b1.dist-info/RECORD,,
15
+ opentelemetry_instrumentation_botocore-0.55b0.dist-info/METADATA,sha256=cWi8X_1-prP9PYigVn0nAaDuObTmv0vqSImGmb4iqnw,3051
16
+ opentelemetry_instrumentation_botocore-0.55b0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ opentelemetry_instrumentation_botocore-0.55b0.dist-info/entry_points.txt,sha256=v5hzQbZMJ61JuhBNq5jHYAapvv3C_486h9CTqxlkUTM,100
18
+ opentelemetry_instrumentation_botocore-0.55b0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
19
+ opentelemetry_instrumentation_botocore-0.55b0.dist-info/RECORD,,