opentelemetry-instrumentation-botocore 0.54b1__tar.gz → 0.55b1__tar.gz

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.
Files changed (91) hide show
  1. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/PKG-INFO +4 -5
  2. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/pyproject.toml +3 -4
  3. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/__init__.py +4 -2
  4. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/extensions/bedrock.py +38 -23
  5. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/extensions/bedrock_utils.py +54 -12
  6. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/version.py +1 -1
  7. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/bedrock_utils.py +0 -2
  8. opentelemetry_instrumentation_botocore-0.55b1/tests/cassettes/test_converse_stream_close_before_consumption.yaml +81 -0
  9. opentelemetry_instrumentation_botocore-0.55b1/tests/cassettes/test_converse_stream_tool_call_parsing_errors.yaml +487 -0
  10. opentelemetry_instrumentation_botocore-0.55b1/tests/cassettes/test_invoke_model_with_content_assistant_content_as_string.yaml +78 -0
  11. opentelemetry_instrumentation_botocore-0.55b1/tests/cassettes/test_invoke_model_with_response_stream_close_before_consumption[amazon.nova].yaml +106 -0
  12. opentelemetry_instrumentation_botocore-0.55b1/tests/cassettes/test_invoke_model_with_response_stream_close_before_consumption[amazon.titan].yaml +65 -0
  13. opentelemetry_instrumentation_botocore-0.55b1/tests/cassettes/test_invoke_model_with_response_stream_close_before_consumption[anthropic.claude].yaml +136 -0
  14. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/test_botocore_bedrock.py +280 -0
  15. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/test_botocore_instrumentation.py +5 -2
  16. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/test_botocore_lambda.py +1 -1
  17. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/.gitignore +0 -0
  18. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/LICENSE +0 -0
  19. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/README.rst +0 -0
  20. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/environment_variables.py +0 -0
  21. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/extensions/__init__.py +0 -0
  22. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/extensions/_messaging.py +0 -0
  23. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/extensions/dynamodb.py +0 -0
  24. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/extensions/lmbd.py +0 -0
  25. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/extensions/sns.py +0 -0
  26. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/extensions/sqs.py +0 -0
  27. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/extensions/types.py +0 -0
  28. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/package.py +0 -0
  29. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/src/opentelemetry/instrumentation/botocore/utils.py +0 -0
  30. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/README.md +0 -0
  31. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/__init__.py +0 -0
  32. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_no_content.yaml +0 -0
  33. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_no_content_different_events.yaml +0 -0
  34. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_stream_handles_event_stream_error.yaml +0 -0
  35. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_stream_no_content.yaml +0 -0
  36. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_stream_no_content_different_events.yaml +0 -0
  37. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_stream_no_content_tool_call.yaml +0 -0
  38. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_stream_with_content.yaml +0 -0
  39. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_stream_with_content_different_events.yaml +0 -0
  40. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_stream_with_content_tool_call.yaml +0 -0
  41. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_stream_with_invalid_model.yaml +0 -0
  42. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_tool_call_no_content.yaml +0 -0
  43. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_tool_call_with_content.yaml +0 -0
  44. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_with_content.yaml +0 -0
  45. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_with_content_different_events.yaml +0 -0
  46. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_converse_with_invalid_model.yaml +0 -0
  47. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content[amazon.nova].yaml +0 -0
  48. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content[amazon.titan].yaml +0 -0
  49. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content[anthropic.claude].yaml +0 -0
  50. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content[cohere.command-r].yaml +0 -0
  51. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content[cohere.command].yaml +0 -0
  52. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content[meta.llama].yaml +0 -0
  53. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content[mistral.mistral].yaml +0 -0
  54. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content_different_events[amazon.nova].yaml +0 -0
  55. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content_different_events[anthropic.claude].yaml +0 -0
  56. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content_tool_call[amazon.nova].yaml +0 -0
  57. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_no_content_tool_call[anthropic.claude].yaml +0 -0
  58. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content[amazon.nova].yaml +0 -0
  59. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content[amazon.titan].yaml +0 -0
  60. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content[anthropic.claude].yaml +0 -0
  61. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content[cohere.command-r].yaml +0 -0
  62. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content[cohere.command].yaml +0 -0
  63. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content[meta.llama].yaml +0 -0
  64. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content[mistral.mistral].yaml +0 -0
  65. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content_different_events[amazon.nova].yaml +0 -0
  66. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content_different_events[anthropic.claude].yaml +0 -0
  67. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content_tool_call[amazon.nova].yaml +0 -0
  68. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content_tool_call[anthropic.claude].yaml +0 -0
  69. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_content_user_content_as_string.yaml +0 -0
  70. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_invalid_model.yaml +0 -0
  71. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_handles_stream_error.yaml +0 -0
  72. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_invalid_model.yaml +0 -0
  73. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_no_content[amazon.nova].yaml +0 -0
  74. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_no_content[amazon.titan].yaml +0 -0
  75. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_no_content[anthropic.claude].yaml +0 -0
  76. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_no_content_different_events[amazon.nova].yaml +0 -0
  77. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_no_content_different_events[anthropic.claude].yaml +0 -0
  78. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_no_content_tool_call[amazon.nova].yaml +0 -0
  79. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_no_content_tool_call[anthropic.claude].yaml +0 -0
  80. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_with_content[amazon.nova].yaml +0 -0
  81. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_with_content[amazon.titan].yaml +0 -0
  82. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_with_content[anthropic.claude].yaml +0 -0
  83. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_with_content_different_events[amazon.nova].yaml +0 -0
  84. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_with_content_different_events[anthropic.claude].yaml +0 -0
  85. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_with_content_tool_call[amazon.nova].yaml +0 -0
  86. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/cassettes/test_invoke_model_with_response_stream_with_content_tool_call[anthropic.claude].yaml +0 -0
  87. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/conftest.py +0 -0
  88. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/test_botocore_dynamodb.py +0 -0
  89. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/test_botocore_messaging.py +0 -0
  90. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/test_botocore_sns.py +0 -0
  91. {opentelemetry_instrumentation_botocore-0.54b1 → opentelemetry_instrumentation_botocore-0.55b1}/tests/test_botocore_sqs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opentelemetry-instrumentation-botocore
3
- Version: 0.54b1
3
+ Version: 0.55b1
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.55b1
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.55b1
26
25
  Provides-Extra: instruments
27
26
  Requires-Dist: botocore~=1.0; extra == 'instruments'
28
27
  Description-Content-Type: text/x-rst
@@ -8,7 +8,7 @@ dynamic = ["version"]
8
8
  description = "OpenTelemetry Botocore instrumentation"
9
9
  readme = "README.rst"
10
10
  license = "Apache-2.0"
11
- requires-python = ">=3.8"
11
+ requires-python = ">=3.9"
12
12
  authors = [
13
13
  { name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" },
14
14
  ]
@@ -18,7 +18,6 @@ classifiers = [
18
18
  "License :: OSI Approved :: Apache Software License",
19
19
  "Programming Language :: Python",
20
20
  "Programming Language :: Python :: 3",
21
- "Programming Language :: Python :: 3.8",
22
21
  "Programming Language :: Python :: 3.9",
23
22
  "Programming Language :: Python :: 3.10",
24
23
  "Programming Language :: Python :: 3.11",
@@ -27,8 +26,8 @@ classifiers = [
27
26
  ]
28
27
  dependencies = [
29
28
  "opentelemetry-api ~= 1.30",
30
- "opentelemetry-instrumentation == 0.54b1",
31
- "opentelemetry-semantic-conventions == 0.54b1",
29
+ "opentelemetry-instrumentation == 0.55b1",
30
+ "opentelemetry-semantic-conventions == 0.55b1",
32
31
  "opentelemetry-propagator-aws-xray ~= 1.0",
33
32
  ]
34
33
 
@@ -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.55b1"
@@ -275,8 +275,6 @@ def remove_none_values(body):
275
275
  continue
276
276
  if isinstance(value, dict):
277
277
  result[key] = remove_none_values(value)
278
- elif isinstance(value, list):
279
- result[key] = [remove_none_values(i) for i in value]
280
278
  else:
281
279
  result[key] = value
282
280
  return result
@@ -0,0 +1,81 @@
1
+ interactions:
2
+ - request:
3
+ body: |-
4
+ {
5
+ "messages": [
6
+ {
7
+ "role": "user",
8
+ "content": [
9
+ {
10
+ "text": "Say this is a test"
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "inferenceConfig": {
16
+ "maxTokens": 10,
17
+ "temperature": 0.8,
18
+ "topP": 1,
19
+ "stopSequences": [
20
+ "|"
21
+ ]
22
+ }
23
+ }
24
+ headers:
25
+ Content-Length:
26
+ - '170'
27
+ Content-Type:
28
+ - application/json
29
+ User-Agent:
30
+ - Boto3/1.35.56 md/Botocore#1.35.56 ua/2.0 os/macos#24.4.0 md/arch#arm64 lang/python#3.12.0
31
+ md/pyimpl#CPython cfg/retry-mode#legacy Botocore/1.35.56
32
+ X-Amz-Date:
33
+ - 20250509T104610Z
34
+ X-Amz-Security-Token:
35
+ - test_aws_security_token
36
+ X-Amzn-Trace-Id:
37
+ - Root=1-98fd1391-6f6d1be3cc209d9ba2fb3034;Parent=6dc095915efed44e;Sampled=1
38
+ amz-sdk-invocation-id:
39
+ - 3a37ec88-d989-4397-bfbe-d463b66a3a08
40
+ amz-sdk-request:
41
+ - attempt=1
42
+ authorization:
43
+ - Bearer test_aws_authorization
44
+ method: POST
45
+ uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/amazon.titan-text-lite-v1/converse-stream
46
+ response:
47
+ body:
48
+ string: !!binary |
49
+ AAAApAAAAFJl4NbnCzpldmVudC10eXBlBwAMbWVzc2FnZVN0YXJ0DTpjb250ZW50LXR5cGUHABBh
50
+ cHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsicCI6ImFiY2RlZmdoaWprbG1u
51
+ b3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTSIsInJvbGUiOiJhc3Npc3RhbnQifSKBqDQAAADZAAAA
52
+ VxTIBhYLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBw
53
+ bGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4Ijow
54
+ LCJkZWx0YSI6eyJ0ZXh0IjoiSGksIGRpZCB5b3UgaGF2ZSBhIHF1ZXN0aW9uIn0sInAiOiJhYmNk
55
+ ZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLIn22h6U3AAAAqgAAAFbdvayfCzpldmVu
56
+ dC10eXBlBwAQY29udGVudEJsb2NrU3RvcA06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNv
57
+ bg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJwIjoiYWJjZGVm
58
+ Z2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0wifQSjvQkAAAChAAAAUTQJCC0LOmV2ZW50
59
+ LXR5cGUHAAttZXNzYWdlU3RvcA06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVz
60
+ c2FnZS10eXBlBwAFZXZlbnR7InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0QiLCJz
61
+ dG9wUmVhc29uIjoibWF4X3Rva2VucyJ9jxPapgAAAMsAAABOaoNqNAs6ZXZlbnQtdHlwZQcACG1l
62
+ dGFkYXRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVl
63
+ dmVudHsibWV0cmljcyI6eyJsYXRlbmN5TXMiOjYyNX0sInAiOiJhYmNkZWZnaGlqa2wiLCJ1c2Fn
64
+ ZSI6eyJpbnB1dFRva2VucyI6OCwib3V0cHV0VG9rZW5zIjoxMCwidG90YWxUb2tlbnMiOjE4fX0B
65
+ /Sts
66
+ headers:
67
+ Connection:
68
+ - keep-alive
69
+ Content-Type:
70
+ - application/vnd.amazon.eventstream
71
+ Date:
72
+ - Fri, 09 May 2025 10:46:10 GMT
73
+ Set-Cookie: test_set_cookie
74
+ Transfer-Encoding:
75
+ - chunked
76
+ x-amzn-RequestId:
77
+ - 5aaaa521-d2c6-4980-ba0a-7bba19633a40
78
+ status:
79
+ code: 200
80
+ message: OK
81
+ version: 1