diff --git a/clients/aws-sdk-bedrock-runtime/CHANGELOG.md b/clients/aws-sdk-bedrock-runtime/CHANGELOG.md index f7cc37e..37e8b79 100644 --- a/clients/aws-sdk-bedrock-runtime/CHANGELOG.md +++ b/clients/aws-sdk-bedrock-runtime/CHANGELOG.md @@ -2,6 +2,25 @@ ## Unreleased +* None Yet. + +## v0.3.0 + +### API Changes +* Adds support for Audio Blocks and Streaming Image Output plus new Stop Reasons of malformed_model_output and malformed_tool_use. +* Adds support for Bedrock Runtime Reserved Service. + +### Breaking +* Function signature for `resolve_retry_strategy` has been changed to prevent unnecessary code duplication in operation methods. This will affect all 0.3.0 clients. + +### Dependencies +* **Updated**: `smithy_aws_core[eventstream, json]` from `~=0.2.0` to `~=0.3.0`. +* **Updated**: `smithy_core` from `~=0.2.0` to `~=0.3.0`. + +### Enhancements +* Add comprehensive integration tests for non-streaming, output streaming, and bidirectional streaming operations. + + ## v0.2.0 ### API Changes @@ -26,7 +45,7 @@ * New stop reason for Converse and ConverseStream ### Enhancements -* Improvements to the underlying AWS CRT HTTP client result in a signifigant decrease in CPU usage. Addresses [aws-sdk-python#11](https://github.com/awslabs/aws-sdk-python/issues/11). +* Improvements to the underlying AWS CRT HTTP client result in a significant decrease in CPU usage. Addresses [aws-sdk-python#11](https://github.com/awslabs/aws-sdk-python/issues/11). ### Dependencies diff --git a/clients/aws-sdk-bedrock-runtime/docs/conf.py b/clients/aws-sdk-bedrock-runtime/docs/conf.py index 8a68fa8..205b692 100644 --- a/clients/aws-sdk-bedrock-runtime/docs/conf.py +++ b/clients/aws-sdk-bedrock-runtime/docs/conf.py @@ -7,7 +7,7 @@ project = "Amazon Bedrock Runtime" author = "Amazon Web Services" -release = "0.2.0" +release = "0.3.0" extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"] diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/AudioBlock.rst b/clients/aws-sdk-bedrock-runtime/docs/models/AudioBlock.rst new file mode 100644 index 0000000..501d94f --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/AudioBlock.rst @@ -0,0 +1,8 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +AudioBlock +========== + +.. autoclass:: aws_sdk_bedrock_runtime.models.AudioBlock + :members: diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/AudioSource.rst b/clients/aws-sdk-bedrock-runtime/docs/models/AudioSource.rst new file mode 100644 index 0000000..9bef7a0 --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/AudioSource.rst @@ -0,0 +1,9 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +.. _AudioSource: + +AudioSource +=========== + +.. autodata:: aws_sdk_bedrock_runtime.models.AudioSource diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/AudioSourceBytes.rst b/clients/aws-sdk-bedrock-runtime/docs/models/AudioSourceBytes.rst new file mode 100644 index 0000000..ca6c558 --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/AudioSourceBytes.rst @@ -0,0 +1,9 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +.. _AudioSourceBytes: + +AudioSourceBytes +================ + +.. autoclass:: aws_sdk_bedrock_runtime.models.AudioSourceBytes diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/AudioSourceS3Location.rst b/clients/aws-sdk-bedrock-runtime/docs/models/AudioSourceS3Location.rst new file mode 100644 index 0000000..8af80b0 --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/AudioSourceS3Location.rst @@ -0,0 +1,9 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +.. _AudioSourceS3Location: + +AudioSourceS3Location +===================== + +.. autoclass:: aws_sdk_bedrock_runtime.models.AudioSourceS3Location diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/AudioSourceUnknown.rst b/clients/aws-sdk-bedrock-runtime/docs/models/AudioSourceUnknown.rst new file mode 100644 index 0000000..fed9644 --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/AudioSourceUnknown.rst @@ -0,0 +1,9 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +.. _AudioSourceUnknown: + +AudioSourceUnknown +================== + +.. autoclass:: aws_sdk_bedrock_runtime.models.AudioSourceUnknown diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/ContentBlockAudio.rst b/clients/aws-sdk-bedrock-runtime/docs/models/ContentBlockAudio.rst new file mode 100644 index 0000000..5b5e67f --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/ContentBlockAudio.rst @@ -0,0 +1,9 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +.. _ContentBlockAudio: + +ContentBlockAudio +================= + +.. autoclass:: aws_sdk_bedrock_runtime.models.ContentBlockAudio diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/ContentBlockDeltaImage.rst b/clients/aws-sdk-bedrock-runtime/docs/models/ContentBlockDeltaImage.rst new file mode 100644 index 0000000..bb03cac --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/ContentBlockDeltaImage.rst @@ -0,0 +1,9 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +.. _ContentBlockDeltaImage: + +ContentBlockDeltaImage +====================== + +.. autoclass:: aws_sdk_bedrock_runtime.models.ContentBlockDeltaImage diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/ContentBlockStartImage.rst b/clients/aws-sdk-bedrock-runtime/docs/models/ContentBlockStartImage.rst new file mode 100644 index 0000000..2f096f6 --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/ContentBlockStartImage.rst @@ -0,0 +1,9 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +.. _ContentBlockStartImage: + +ContentBlockStartImage +====================== + +.. autoclass:: aws_sdk_bedrock_runtime.models.ContentBlockStartImage diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/ErrorBlock.rst b/clients/aws-sdk-bedrock-runtime/docs/models/ErrorBlock.rst new file mode 100644 index 0000000..3f33edd --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/ErrorBlock.rst @@ -0,0 +1,8 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +ErrorBlock +========== + +.. autoclass:: aws_sdk_bedrock_runtime.models.ErrorBlock + :members: diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/ImageBlockDelta.rst b/clients/aws-sdk-bedrock-runtime/docs/models/ImageBlockDelta.rst new file mode 100644 index 0000000..416140e --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/ImageBlockDelta.rst @@ -0,0 +1,8 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +ImageBlockDelta +=============== + +.. autoclass:: aws_sdk_bedrock_runtime.models.ImageBlockDelta + :members: diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/ImageBlockStart.rst b/clients/aws-sdk-bedrock-runtime/docs/models/ImageBlockStart.rst new file mode 100644 index 0000000..c538b92 --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/ImageBlockStart.rst @@ -0,0 +1,8 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +ImageBlockStart +=============== + +.. autoclass:: aws_sdk_bedrock_runtime.models.ImageBlockStart + :members: diff --git a/clients/aws-sdk-bedrock-runtime/docs/models/ToolResultBlockDeltaJson.rst b/clients/aws-sdk-bedrock-runtime/docs/models/ToolResultBlockDeltaJson.rst new file mode 100644 index 0000000..f30dc03 --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/docs/models/ToolResultBlockDeltaJson.rst @@ -0,0 +1,9 @@ +.. + Code generated by smithy-python-codegen DO NOT EDIT. + +.. _ToolResultBlockDeltaJson: + +ToolResultBlockDeltaJson +======================== + +.. autoclass:: aws_sdk_bedrock_runtime.models.ToolResultBlockDeltaJson diff --git a/clients/aws-sdk-bedrock-runtime/pyproject.toml b/clients/aws-sdk-bedrock-runtime/pyproject.toml index 67f1c3d..d79eaa7 100644 --- a/clients/aws-sdk-bedrock-runtime/pyproject.toml +++ b/clients/aws-sdk-bedrock-runtime/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "aws_sdk_bedrock_runtime" -version = "0.2.0" +version = "0.3.0" description = "aws_sdk_bedrock_runtime client" readme = "README.md" requires-python = ">=3.12" @@ -24,15 +24,15 @@ classifiers = [ ] dependencies = [ - "smithy_aws_core[eventstream, json]~=0.2.0", - "smithy_core~=0.2.0", + "smithy_aws_core[eventstream, json]~=0.3.0", + "smithy_core~=0.3.0", "smithy_http[awscrt]~=0.3.0" ] [dependency-groups] test = [ - "pytest>=7.2.0,<8.0.0", - "pytest-asyncio>=0.20.3,<0.21.0" + "pytest>=9.0.1,<10.0.0", + "pytest-asyncio>=1.3.0,<1.4.0" ] docs = [ diff --git a/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/__init__.py b/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/__init__.py index c8417da..768ec96 100644 --- a/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/__init__.py +++ b/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/__init__.py @@ -1,3 +1,3 @@ # Code generated by smithy-python-codegen DO NOT EDIT. -__version__: str = "0.2.0" +__version__: str = "0.3.0" diff --git a/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/_private/schemas.py b/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/_private/schemas.py index 103a5f5..fde8457 100644 --- a/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/_private/schemas.py +++ b/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/_private/schemas.py @@ -2249,6 +2249,122 @@ }, ) +ERROR_BLOCK = Schema.collection( + id=ShapeID("com.amazonaws.bedrockruntime#ErrorBlock"), + traits=[Trait.new(id=ShapeID("smithy.api#sensitive"))], + members={"message": {"target": STRING}}, +) + +AUDIO_FORMAT = Schema.collection( + id=ShapeID("com.amazonaws.bedrockruntime#AudioFormat"), + shape_type=ShapeType.ENUM, + members={ + "MP3": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="mp3")], + }, + "OPUS": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="opus")], + }, + "WAV": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="wav")], + }, + "AAC": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="aac")], + }, + "FLAC": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="flac")], + }, + "MP4": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="mp4")], + }, + "OGG": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="ogg")], + }, + "MKV": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="mkv")], + }, + "MKA": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="mka")], + }, + "X_AAC": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="x-aac")], + }, + "M4A": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="m4a")], + }, + "MPEG": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="mpeg")], + }, + "MPGA": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="mpga")], + }, + "PCM": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="pcm")], + }, + "WEBM": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="webm")], + }, + }, +) + +S3_LOCATION = Schema.collection( + id=ShapeID("com.amazonaws.bedrockruntime#S3Location"), + members={ + "uri": { + "target": S3_URI, + "traits": [Trait.new(id=ShapeID("smithy.api#required"))], + }, + "bucketOwner": {"target": ACCOUNT_ID}, + }, +) + +AUDIO_SOURCE = Schema.collection( + id=ShapeID("com.amazonaws.bedrockruntime#AudioSource"), + shape_type=ShapeType.UNION, + traits=[Trait.new(id=ShapeID("smithy.api#sensitive"))], + members={ + "bytes": { + "target": BLOB, + "traits": [ + Trait.new( + id=ShapeID("smithy.api#length"), value=MappingProxyType({"min": 1}) + ) + ], + }, + "s3Location": {"target": S3_LOCATION}, + }, +) + +AUDIO_BLOCK = Schema.collection( + id=ShapeID("com.amazonaws.bedrockruntime#AudioBlock"), + members={ + "format": { + "target": AUDIO_FORMAT, + "traits": [Trait.new(id=ShapeID("smithy.api#required"))], + }, + "source": { + "target": AUDIO_SOURCE, + "traits": [Trait.new(id=ShapeID("smithy.api#required"))], + }, + "error": {"target": ERROR_BLOCK}, + }, +) + CACHE_POINT_TYPE = Schema.collection( id=ShapeID("com.amazonaws.bedrockruntime#CachePointType"), shape_type=ShapeType.ENUM, @@ -2520,17 +2636,6 @@ members={"member": {"target": DOCUMENT_CONTENT_BLOCK}}, ) -S3_LOCATION = Schema.collection( - id=ShapeID("com.amazonaws.bedrockruntime#S3Location"), - members={ - "uri": { - "target": S3_URI, - "traits": [Trait.new(id=ShapeID("smithy.api#required"))], - }, - "bucketOwner": {"target": ACCOUNT_ID}, - }, -) - DOCUMENT_SOURCE = Schema.collection( id=ShapeID("com.amazonaws.bedrockruntime#DocumentSource"), shape_type=ShapeType.UNION, @@ -2699,6 +2804,7 @@ IMAGE_SOURCE = Schema.collection( id=ShapeID("com.amazonaws.bedrockruntime#ImageSource"), shape_type=ShapeType.UNION, + traits=[Trait.new(id=ShapeID("smithy.api#sensitive"))], members={ "bytes": { "target": BLOB, @@ -2723,6 +2829,7 @@ "target": IMAGE_SOURCE, "traits": [Trait.new(id=ShapeID("smithy.api#required"))], }, + "error": {"target": ERROR_BLOCK}, }, ) @@ -2970,6 +3077,7 @@ "image": {"target": IMAGE_BLOCK}, "document": {"target": DOCUMENT_BLOCK}, "video": {"target": VIDEO_BLOCK}, + "audio": {"target": AUDIO_BLOCK}, "toolUse": {"target": TOOL_USE_BLOCK}, "toolResult": {"target": TOOL_RESULT_BLOCK}, "guardContent": {"target": GUARDRAIL_CONVERSE_CONTENT_BLOCK}, @@ -3134,6 +3242,10 @@ "target": UNIT, "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="flex")], }, + "RESERVED": { + "target": UNIT, + "traits": [Trait.new(id=ShapeID("smithy.api#enumValue"), value="reserved")], + }, }, ) @@ -3346,6 +3458,22 @@ Trait.new(id=ShapeID("smithy.api#enumValue"), value="content_filtered") ], }, + "MALFORMED_MODEL_OUTPUT": { + "target": UNIT, + "traits": [ + Trait.new( + id=ShapeID("smithy.api#enumValue"), value="malformed_model_output" + ) + ], + }, + "MALFORMED_TOOL_USE": { + "target": UNIT, + "traits": [ + Trait.new( + id=ShapeID("smithy.api#enumValue"), value="malformed_tool_use" + ) + ], + }, "MODEL_CONTEXT_WINDOW_EXCEEDED": { "target": UNIT, "traits": [ @@ -3660,6 +3788,11 @@ }, ) +IMAGE_BLOCK_DELTA = Schema.collection( + id=ShapeID("com.amazonaws.bedrockruntime#ImageBlockDelta"), + members={"source": {"target": IMAGE_SOURCE}, "error": {"target": ERROR_BLOCK}}, +) + REASONING_CONTENT_BLOCK_DELTA = Schema.collection( id=ShapeID("com.amazonaws.bedrockruntime#ReasoningContentBlockDelta"), shape_type=ShapeType.UNION, @@ -3674,7 +3807,7 @@ TOOL_RESULT_BLOCK_DELTA = Schema.collection( id=ShapeID("com.amazonaws.bedrockruntime#ToolResultBlockDelta"), shape_type=ShapeType.UNION, - members={"text": {"target": STRING}}, + members={"text": {"target": STRING}, "json": {"target": DOCUMENT}}, ) TOOL_RESULT_BLOCKS_DELTA = Schema.collection( @@ -3702,6 +3835,7 @@ "toolResult": {"target": TOOL_RESULT_BLOCKS_DELTA}, "reasoningContent": {"target": REASONING_CONTENT_BLOCK_DELTA}, "citation": {"target": CITATIONS_DELTA}, + "image": {"target": IMAGE_BLOCK_DELTA}, }, ) @@ -3719,6 +3853,16 @@ }, ) +IMAGE_BLOCK_START = Schema.collection( + id=ShapeID("com.amazonaws.bedrockruntime#ImageBlockStart"), + members={ + "format": { + "target": IMAGE_FORMAT, + "traits": [Trait.new(id=ShapeID("smithy.api#required"))], + } + }, +) + TOOL_RESULT_BLOCK_START = Schema.collection( id=ShapeID("com.amazonaws.bedrockruntime#ToolResultBlockStart"), members={ @@ -3752,6 +3896,7 @@ members={ "toolUse": {"target": TOOL_USE_BLOCK_START}, "toolResult": {"target": TOOL_RESULT_BLOCK_START}, + "image": {"target": IMAGE_BLOCK_START}, }, ) diff --git a/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/client.py b/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/client.py index 763178c..e93f02b 100644 --- a/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/client.py +++ b/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/client.py @@ -7,8 +7,7 @@ from smithy_core.aio.eventstream import DuplexEventStream, OutputEventStream from smithy_core.exceptions import ExpectationNotMetError from smithy_core.interceptors import InterceptorChain -from smithy_core.interfaces.retries import RetryStrategy -from smithy_core.retries import RetryStrategyOptions, RetryStrategyResolver +from smithy_core.retries import RetryStrategyResolver from smithy_core.types import TypedProperties from smithy_http.plugins import user_agent_plugin @@ -110,22 +109,9 @@ async def apply_guardrail( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -201,22 +187,9 @@ async def converse( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -300,22 +273,9 @@ async def converse_stream( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -384,22 +344,9 @@ async def count_tokens( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -438,22 +385,9 @@ async def get_async_invoke( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -510,22 +444,9 @@ async def invoke_model( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -578,22 +499,9 @@ async def invoke_model_with_bidirectional_stream( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -665,22 +573,9 @@ async def invoke_model_with_response_stream( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -721,22 +616,9 @@ async def list_async_invokes( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -784,22 +666,9 @@ async def start_async_invoke( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( diff --git a/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/models.py b/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/models.py index 653e020..3e96a2b 100644 --- a/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/models.py +++ b/clients/aws-sdk-bedrock-runtime/src/aws_sdk_bedrock_runtime/models.py @@ -23,6 +23,8 @@ ASYNC_INVOKE_OUTPUT_DATA_CONFIG as _SCHEMA_ASYNC_INVOKE_OUTPUT_DATA_CONFIG, ASYNC_INVOKE_S3_OUTPUT_DATA_CONFIG as _SCHEMA_ASYNC_INVOKE_S3_OUTPUT_DATA_CONFIG, ASYNC_INVOKE_SUMMARY as _SCHEMA_ASYNC_INVOKE_SUMMARY, + AUDIO_BLOCK as _SCHEMA_AUDIO_BLOCK, + AUDIO_SOURCE as _SCHEMA_AUDIO_SOURCE, AUTO_TOOL_CHOICE as _SCHEMA_AUTO_TOOL_CHOICE, BIDIRECTIONAL_INPUT_PAYLOAD_PART as _SCHEMA_BIDIRECTIONAL_INPUT_PAYLOAD_PART, BIDIRECTIONAL_OUTPUT_PAYLOAD_PART as _SCHEMA_BIDIRECTIONAL_OUTPUT_PAYLOAD_PART, @@ -66,6 +68,7 @@ DOCUMENT_CONTENT_BLOCK as _SCHEMA_DOCUMENT_CONTENT_BLOCK, DOCUMENT_PAGE_LOCATION as _SCHEMA_DOCUMENT_PAGE_LOCATION, DOCUMENT_SOURCE as _SCHEMA_DOCUMENT_SOURCE, + ERROR_BLOCK as _SCHEMA_ERROR_BLOCK, GET_ASYNC_INVOKE as _SCHEMA_GET_ASYNC_INVOKE, GET_ASYNC_INVOKE_INPUT as _SCHEMA_GET_ASYNC_INVOKE_INPUT, GET_ASYNC_INVOKE_OUTPUT as _SCHEMA_GET_ASYNC_INVOKE_OUTPUT, @@ -116,6 +119,8 @@ GUARDRAIL_USAGE as _SCHEMA_GUARDRAIL_USAGE, GUARDRAIL_WORD_POLICY_ASSESSMENT as _SCHEMA_GUARDRAIL_WORD_POLICY_ASSESSMENT, IMAGE_BLOCK as _SCHEMA_IMAGE_BLOCK, + IMAGE_BLOCK_DELTA as _SCHEMA_IMAGE_BLOCK_DELTA, + IMAGE_BLOCK_START as _SCHEMA_IMAGE_BLOCK_START, IMAGE_SOURCE as _SCHEMA_IMAGE_SOURCE, INFERENCE_CONFIGURATION as _SCHEMA_INFERENCE_CONFIGURATION, INTERNAL_SERVER_EXCEPTION as _SCHEMA_INTERNAL_SERVER_EXCEPTION, @@ -6101,6 +6106,283 @@ def _consumer(schema: Schema, de: ShapeDeserializer) -> None: return kwargs +@dataclass(kw_only=True) +class ErrorBlock: + """ + A block containing error information when content processing fails. + """ + + message: str | None = None + """ + A human-readable error message describing what went wrong during content + processing. + """ + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_ERROR_BLOCK, self) + + def serialize_members(self, serializer: ShapeSerializer): + if self.message is not None: + serializer.write_string( + _SCHEMA_ERROR_BLOCK.members["message"], self.message + ) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["message"] = de.read_string( + _SCHEMA_ERROR_BLOCK.members["message"] + ) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_ERROR_BLOCK, consumer=_consumer) + return kwargs + + +class AudioFormat(StrEnum): + MP3 = "mp3" + OPUS = "opus" + WAV = "wav" + AAC = "aac" + FLAC = "flac" + MP4 = "mp4" + OGG = "ogg" + MKV = "mkv" + MKA = "mka" + X_AAC = "x-aac" + M4_A = "m4a" + MPEG = "mpeg" + MPGA = "mpga" + PCM = "pcm" + WEBM = "webm" + + +@dataclass(kw_only=True) +class S3Location: + """ + A storage location in an Amazon S3 bucket. + """ + + uri: str + """ + An object URI starting with ``s3://``. + """ + + bucket_owner: str | None = None + """ + If the bucket belongs to another AWS account, specify that account's ID. + """ + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_S3_LOCATION, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_string(_SCHEMA_S3_LOCATION.members["uri"], self.uri) + if self.bucket_owner is not None: + serializer.write_string( + _SCHEMA_S3_LOCATION.members["bucketOwner"], self.bucket_owner + ) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["uri"] = de.read_string(_SCHEMA_S3_LOCATION.members["uri"]) + + case 1: + kwargs["bucket_owner"] = de.read_string( + _SCHEMA_S3_LOCATION.members["bucketOwner"] + ) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_S3_LOCATION, consumer=_consumer) + return kwargs + + +@dataclass +class AudioSourceBytes: + """ + Audio data encoded in base64. + """ + + value: bytes + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_AUDIO_SOURCE, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_blob(_SCHEMA_AUDIO_SOURCE.members["bytes"], self.value) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(value=deserializer.read_blob(_SCHEMA_AUDIO_SOURCE.members["bytes"])) + + +@dataclass +class AudioSourceS3Location: + """ + A reference to audio data stored in an Amazon S3 bucket. To see which models + support S3 uploads, see `Supported models and features for Converse `_ + . + """ + + value: S3Location + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_AUDIO_SOURCE, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_AUDIO_SOURCE.members["s3Location"], self.value) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(value=S3Location.deserialize(deserializer)) + + +@dataclass +class AudioSourceUnknown: + """Represents an unknown variant. + + If you receive this value, you will need to update your library to receive the + parsed value. + + This value may not be deliberately sent. + """ + + tag: str + + def serialize(self, serializer: ShapeSerializer): + raise SerializationError("Unknown union variants may not be serialized.") + + def serialize_members(self, serializer: ShapeSerializer): + raise SerializationError("Unknown union variants may not be serialized.") + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + raise NotImplementedError() + + +AudioSource = Union[AudioSourceBytes | AudioSourceS3Location | AudioSourceUnknown] + +""" +The source of audio data, which can be provided either as raw bytes or a +reference to an S3 location. +""" + + +class _AudioSourceDeserializer: + _result: AudioSource | None = None + + def deserialize(self, deserializer: ShapeDeserializer) -> AudioSource: + self._result = None + deserializer.read_struct(_SCHEMA_AUDIO_SOURCE, self._consumer) + + if self._result is None: + raise SerializationError( + "Unions must have exactly one value, but found none." + ) + + return self._result + + def _consumer(self, schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + self._set_result(AudioSourceBytes.deserialize(de)) + + case 1: + self._set_result(AudioSourceS3Location.deserialize(de)) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + def _set_result(self, value: AudioSource) -> None: + if self._result is not None: + raise SerializationError( + "Unions must have exactly one value, but found more than one." + ) + self._result = value + + +@dataclass(kw_only=True) +class AudioBlock: + """ + An audio content block that contains audio data in various supported formats. + """ + + format: str + """ + The format of the audio data, such as MP3, WAV, FLAC, or other supported audio + formats. + """ + + source: AudioSource = field(repr=False) + """ + The source of the audio data, which can be provided as raw bytes or an S3 + location. + """ + + error: ErrorBlock | None = field(repr=False, default=None) + """ + Error information if the audio block could not be processed or contains invalid + data. + """ + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_AUDIO_BLOCK, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_string(_SCHEMA_AUDIO_BLOCK.members["format"], self.format) + serializer.write_struct(_SCHEMA_AUDIO_BLOCK.members["source"], self.source) + if self.error is not None: + serializer.write_struct(_SCHEMA_AUDIO_BLOCK.members["error"], self.error) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["format"] = de.read_string( + _SCHEMA_AUDIO_BLOCK.members["format"] + ) + + case 1: + kwargs["source"] = _AudioSourceDeserializer().deserialize(de) + + case 2: + kwargs["error"] = ErrorBlock.deserialize(de) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_AUDIO_BLOCK, consumer=_consumer) + return kwargs + + class CachePointType(StrEnum): DEFAULT = "default" @@ -7248,57 +7530,6 @@ def _read_value(d: ShapeDeserializer): return result -@dataclass(kw_only=True) -class S3Location: - """ - A storage location in an Amazon S3 bucket. - """ - - uri: str - """ - An object URI starting with ``s3://``. - """ - - bucket_owner: str | None = None - """ - If the bucket belongs to another AWS account, specify that account's ID. - """ - - def serialize(self, serializer: ShapeSerializer): - serializer.write_struct(_SCHEMA_S3_LOCATION, self) - - def serialize_members(self, serializer: ShapeSerializer): - serializer.write_string(_SCHEMA_S3_LOCATION.members["uri"], self.uri) - if self.bucket_owner is not None: - serializer.write_string( - _SCHEMA_S3_LOCATION.members["bucketOwner"], self.bucket_owner - ) - - @classmethod - def deserialize(cls, deserializer: ShapeDeserializer) -> Self: - return cls(**cls.deserialize_kwargs(deserializer)) - - @classmethod - def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: - kwargs: dict[str, Any] = {} - - def _consumer(schema: Schema, de: ShapeDeserializer) -> None: - match schema.expect_member_index(): - case 0: - kwargs["uri"] = de.read_string(_SCHEMA_S3_LOCATION.members["uri"]) - - case 1: - kwargs["bucket_owner"] = de.read_string( - _SCHEMA_S3_LOCATION.members["bucketOwner"] - ) - - case _: - logger.debug("Unexpected member schema: %s", schema) - - deserializer.read_struct(_SCHEMA_S3_LOCATION, consumer=_consumer) - return kwargs - - @dataclass class DocumentSourceBytes: """ @@ -8055,17 +8286,25 @@ class ImageBlock: The format of the image. """ - source: ImageSource + source: ImageSource = field(repr=False) """ The source for the image. """ + error: ErrorBlock | None = field(repr=False, default=None) + """ + Error information if the image block could not be processed or contains invalid + data. + """ + def serialize(self, serializer: ShapeSerializer): serializer.write_struct(_SCHEMA_IMAGE_BLOCK, self) def serialize_members(self, serializer: ShapeSerializer): serializer.write_string(_SCHEMA_IMAGE_BLOCK.members["format"], self.format) serializer.write_struct(_SCHEMA_IMAGE_BLOCK.members["source"], self.source) + if self.error is not None: + serializer.write_struct(_SCHEMA_IMAGE_BLOCK.members["error"], self.error) @classmethod def deserialize(cls, deserializer: ShapeDeserializer) -> Self: @@ -8085,6 +8324,9 @@ def _consumer(schema: Schema, de: ShapeDeserializer) -> None: case 1: kwargs["source"] = _ImageSourceDeserializer().deserialize(de) + case 2: + kwargs["error"] = ErrorBlock.deserialize(de) + case _: logger.debug("Unexpected member schema: %s", schema) @@ -9082,6 +9324,25 @@ def deserialize(cls, deserializer: ShapeDeserializer) -> Self: return cls(value=VideoBlock.deserialize(deserializer)) +@dataclass +class ContentBlockAudio: + """ + An audio content block containing audio data in the conversation. + """ + + value: AudioBlock + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_CONTENT_BLOCK, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_CONTENT_BLOCK.members["audio"], self.value) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(value=AudioBlock.deserialize(deserializer)) + + @dataclass class ContentBlockToolUse: """ @@ -9261,6 +9522,7 @@ def deserialize(cls, deserializer: ShapeDeserializer) -> Self: | ContentBlockImage | ContentBlockDocument | ContentBlockVideo + | ContentBlockAudio | ContentBlockToolUse | ContentBlockToolResult | ContentBlockGuardContent @@ -9308,24 +9570,27 @@ def _consumer(self, schema: Schema, de: ShapeDeserializer) -> None: self._set_result(ContentBlockVideo.deserialize(de)) case 4: - self._set_result(ContentBlockToolUse.deserialize(de)) + self._set_result(ContentBlockAudio.deserialize(de)) case 5: - self._set_result(ContentBlockToolResult.deserialize(de)) + self._set_result(ContentBlockToolUse.deserialize(de)) case 6: - self._set_result(ContentBlockGuardContent.deserialize(de)) + self._set_result(ContentBlockToolResult.deserialize(de)) case 7: - self._set_result(ContentBlockCachePoint.deserialize(de)) + self._set_result(ContentBlockGuardContent.deserialize(de)) case 8: - self._set_result(ContentBlockReasoningContent.deserialize(de)) + self._set_result(ContentBlockCachePoint.deserialize(de)) case 9: - self._set_result(ContentBlockCitationsContent.deserialize(de)) + self._set_result(ContentBlockReasoningContent.deserialize(de)) case 10: + self._set_result(ContentBlockCitationsContent.deserialize(de)) + + case 11: self._set_result(ContentBlockSearchResult.deserialize(de)) case _: @@ -9647,6 +9912,7 @@ class ServiceTierType(StrEnum): PRIORITY = "priority" DEFAULT = "default" FLEX = "flex" + RESERVED = "reserved" @dataclass(kw_only=True) @@ -10873,6 +11139,8 @@ class StopReason(StrEnum): STOP_SEQUENCE = "stop_sequence" GUARDRAIL_INTERVENED = "guardrail_intervened" CONTENT_FILTERED = "content_filtered" + MALFORMED_MODEL_OUTPUT = "malformed_model_output" + MALFORMED_TOOL_USE = "malformed_tool_use" MODEL_CONTEXT_WINDOW_EXCEEDED = "model_context_window_exceeded" @@ -12124,6 +12392,60 @@ def _consumer(schema: Schema, de: ShapeDeserializer) -> None: return kwargs +@dataclass(kw_only=True) +class ImageBlockDelta: + """ + A streaming delta event that contains incremental image data during streaming + responses. + """ + + source: ImageSource | None = field(repr=False, default=None) + """ + The incremental image source data for this delta event. + """ + + error: ErrorBlock | None = field(repr=False, default=None) + """ + Error information if this image delta could not be processed. + """ + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_IMAGE_BLOCK_DELTA, self) + + def serialize_members(self, serializer: ShapeSerializer): + if self.source is not None: + serializer.write_struct( + _SCHEMA_IMAGE_BLOCK_DELTA.members["source"], self.source + ) + + if self.error is not None: + serializer.write_struct( + _SCHEMA_IMAGE_BLOCK_DELTA.members["error"], self.error + ) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["source"] = _ImageSourceDeserializer().deserialize(de) + + case 1: + kwargs["error"] = ErrorBlock.deserialize(de) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_IMAGE_BLOCK_DELTA, consumer=_consumer) + return kwargs + + @dataclass class ReasoningContentBlockDeltaText: """ @@ -12305,6 +12627,32 @@ def deserialize(cls, deserializer: ShapeDeserializer) -> Self: ) +@dataclass +class ToolResultBlockDeltaJson: + """ + The JSON schema for the tool result content block. see `JSON Schema Reference `_ + . + """ + + value: Document + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_TOOL_RESULT_BLOCK_DELTA, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_document( + _SCHEMA_TOOL_RESULT_BLOCK_DELTA.members["json"], self.value + ) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls( + value=deserializer.read_document( + _SCHEMA_TOOL_RESULT_BLOCK_DELTA.members["json"] + ) + ) + + @dataclass class ToolResultBlockDeltaUnknown: """Represents an unknown variant. @@ -12328,7 +12676,9 @@ def deserialize(cls, deserializer: ShapeDeserializer) -> Self: raise NotImplementedError() -ToolResultBlockDelta = Union[ToolResultBlockDeltaText | ToolResultBlockDeltaUnknown] +ToolResultBlockDelta = Union[ + ToolResultBlockDeltaText | ToolResultBlockDeltaJson | ToolResultBlockDeltaUnknown +] """ Contains incremental updates to tool results information during streaming @@ -12356,6 +12706,9 @@ def _consumer(self, schema: Schema, de: ShapeDeserializer) -> None: case 0: self._set_result(ToolResultBlockDeltaText.deserialize(de)) + case 1: + self._set_result(ToolResultBlockDeltaJson.deserialize(de)) + case _: logger.debug("Unexpected member schema: %s", schema) @@ -12547,6 +12900,27 @@ def deserialize(cls, deserializer: ShapeDeserializer) -> Self: return cls(value=CitationsDelta.deserialize(deserializer)) +@dataclass +class ContentBlockDeltaImage: + """ + A streaming delta event containing incremental image data. + """ + + value: ImageBlockDelta + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_CONTENT_BLOCK_DELTA, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_struct( + _SCHEMA_CONTENT_BLOCK_DELTA.members["image"], self.value + ) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(value=ImageBlockDelta.deserialize(deserializer)) + + @dataclass class ContentBlockDeltaUnknown: """Represents an unknown variant. @@ -12576,6 +12950,7 @@ def deserialize(cls, deserializer: ShapeDeserializer) -> Self: | ContentBlockDeltaToolResult | ContentBlockDeltaReasoningContent | ContentBlockDeltaCitation + | ContentBlockDeltaImage | ContentBlockDeltaUnknown ] @@ -12615,6 +12990,9 @@ def _consumer(self, schema: Schema, de: ShapeDeserializer) -> None: case 4: self._set_result(ContentBlockDeltaCitation.deserialize(de)) + case 5: + self._set_result(ContentBlockDeltaImage.deserialize(de)) + case _: logger.debug("Unexpected member schema: %s", schema) @@ -12679,6 +13057,48 @@ def _consumer(schema: Schema, de: ShapeDeserializer) -> None: return kwargs +@dataclass(kw_only=True) +class ImageBlockStart: + """ + The initial event in a streaming image block that indicates the start of image + content. + """ + + format: str + """ + The format of the image data that will be streamed in subsequent delta events. + """ + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_IMAGE_BLOCK_START, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_string( + _SCHEMA_IMAGE_BLOCK_START.members["format"], self.format + ) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["format"] = de.read_string( + _SCHEMA_IMAGE_BLOCK_START.members["format"] + ) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_IMAGE_BLOCK_START, consumer=_consumer) + return kwargs + + @dataclass(kw_only=True) class ToolResultBlockStart: """ @@ -12859,6 +13279,27 @@ def deserialize(cls, deserializer: ShapeDeserializer) -> Self: return cls(value=ToolResultBlockStart.deserialize(deserializer)) +@dataclass +class ContentBlockStartImage: + """ + The initial event indicating the start of a streaming image block. + """ + + value: ImageBlockStart + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_CONTENT_BLOCK_START, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_struct( + _SCHEMA_CONTENT_BLOCK_START.members["image"], self.value + ) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(value=ImageBlockStart.deserialize(deserializer)) + + @dataclass class ContentBlockStartUnknown: """Represents an unknown variant. @@ -12883,7 +13324,10 @@ def deserialize(cls, deserializer: ShapeDeserializer) -> Self: ContentBlockStart = Union[ - ContentBlockStartToolUse | ContentBlockStartToolResult | ContentBlockStartUnknown + ContentBlockStartToolUse + | ContentBlockStartToolResult + | ContentBlockStartImage + | ContentBlockStartUnknown ] """ @@ -12913,6 +13357,9 @@ def _consumer(self, schema: Schema, de: ShapeDeserializer) -> None: case 1: self._set_result(ContentBlockStartToolResult.deserialize(de)) + case 2: + self._set_result(ContentBlockStartImage.deserialize(de)) + case _: logger.debug("Unexpected member schema: %s", schema) diff --git a/clients/aws-sdk-bedrock-runtime/tests/integration/__init__.py b/clients/aws-sdk-bedrock-runtime/tests/integration/__init__.py new file mode 100644 index 0000000..44217f9 --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/tests/integration/__init__.py @@ -0,0 +1,26 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + + +from pathlib import Path + +from smithy_aws_core.identity import EnvironmentCredentialsResolver + +from aws_sdk_bedrock_runtime.client import BedrockRuntimeClient +from aws_sdk_bedrock_runtime.config import Config + +MODEL_ID = "global.amazon.nova-2-lite-v1:0" +BIDIRECTIONAL_MODEL_ID = "amazon.nova-2-sonic-v1:0" +MESSAGE = "Who created the Python programming language?" +AUDIO_FILE = Path(__file__).parent / "assets" / "test.pcm" + + +def create_bedrock_client(region: str) -> BedrockRuntimeClient: + """Helper to create a BedrockRuntimeClient for a given region.""" + return BedrockRuntimeClient( + config=Config( + endpoint_uri=f"https://bedrock-runtime.{region}.amazonaws.com", + region=region, + aws_credentials_identity_resolver=EnvironmentCredentialsResolver(), + ) + ) diff --git a/clients/aws-sdk-bedrock-runtime/tests/integration/assets/test.pcm b/clients/aws-sdk-bedrock-runtime/tests/integration/assets/test.pcm new file mode 100644 index 0000000..0fc61db Binary files /dev/null and b/clients/aws-sdk-bedrock-runtime/tests/integration/assets/test.pcm differ diff --git a/clients/aws-sdk-bedrock-runtime/tests/integration/test_bidirectional_streaming.py b/clients/aws-sdk-bedrock-runtime/tests/integration/test_bidirectional_streaming.py new file mode 100644 index 0000000..67f6955 --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/tests/integration/test_bidirectional_streaming.py @@ -0,0 +1,282 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Test bidirectional streaming duplex event stream handling.""" + +import asyncio +import base64 +import json +import uuid + +from smithy_core.aio.eventstream import DuplexEventStream + +from aws_sdk_bedrock_runtime.models import ( + BidirectionalInputPayloadPart, + InvokeModelWithBidirectionalStreamInputChunk, + InvokeModelWithBidirectionalStreamOperationInput, + InvokeModelWithBidirectionalStreamInput, + InvokeModelWithBidirectionalStreamOutput, + InvokeModelWithBidirectionalStreamOperationOutput, + InvokeModelWithBidirectionalStreamOutputChunk, +) + +from . import AUDIO_FILE, BIDIRECTIONAL_MODEL_ID, create_bedrock_client + +CHUNK_SIZE = 512 +SILENCE_CHUNKS = 125 +RESPONSE_WAIT_TIME = 3 + +DEFAULT_SYSTEM_PROMPT = ( + "You are a friendly assistant. Keep your responses short, " + "generally one or two sentences." +) + +START_SESSION_EVENT = """{ + "event": { + "sessionStart": { + "inferenceConfiguration": { + "maxTokens": 1024, + "topP": 0.9, + "temperature": 0.7 + } + } + } +}""" + +START_PROMPT_EVENT = """{ + "event": { + "promptStart": { + "promptName": "%s", + "textOutputConfiguration": { + "mediaType": "text/plain" + }, + "audioOutputConfiguration": { + "mediaType": "audio/lpcm", + "sampleRateHertz": 24000, + "sampleSizeBits": 16, + "channelCount": 1, + "voiceId": "matthew", + "encoding": "base64", + "audioType": "SPEECH" + } + } + } +}""" + +TEXT_CONTENT_START_EVENT = """{ + "event": { + "contentStart": { + "promptName": "%s", + "contentName": "%s", + "type": "TEXT", + "interactive": true, + "role": "%s", + "textInputConfiguration": { + "mediaType": "text/plain" + } + } + } +}""" + +TEXT_INPUT_EVENT = """{ + "event": { + "textInput": { + "promptName": "%s", + "contentName": "%s", + "content": "%s" + } + } +}""" + +CONTENT_END_EVENT = """{ + "event": { + "contentEnd": { + "promptName": "%s", + "contentName": "%s" + } + } +}""" + +AUDIO_CONTENT_START_EVENT = """{ + "event": { + "contentStart": { + "promptName": "%s", + "contentName": "%s", + "type": "AUDIO", + "interactive": true, + "role": "USER", + "audioInputConfiguration": { + "mediaType": "audio/lpcm", + "sampleRateHertz": 16000, + "sampleSizeBits": 16, + "channelCount": 1, + "audioType": "SPEECH", + "encoding": "base64" + } + } + } +}""" + +AUDIO_INPUT_EVENT = """{ + "event": { + "audioInput": { + "promptName": "%s", + "contentName": "%s", + "content": "%s" + } + } +}""" + +PROMPT_END_EVENT = """{ + "event": { + "promptEnd": { + "promptName": "%s" + } + } +}""" + +SESSION_END_EVENT = """{ + "event": { + "sessionEnd": {} + } +}""" + + +async def _send_event( + stream: DuplexEventStream[ + InvokeModelWithBidirectionalStreamInput, + InvokeModelWithBidirectionalStreamOutput, + InvokeModelWithBidirectionalStreamOperationOutput, + ], + event_json: str, +) -> None: + """Send a raw event JSON string to the Bedrock stream.""" + event = InvokeModelWithBidirectionalStreamInputChunk( + value=BidirectionalInputPayloadPart(bytes_=event_json.encode("utf-8")) + ) + await stream.input_stream.send(event) + + +async def _send_audio_chunks( + stream: DuplexEventStream[ + InvokeModelWithBidirectionalStreamInput, + InvokeModelWithBidirectionalStreamOutput, + InvokeModelWithBidirectionalStreamOperationOutput, + ], + prompt_name: str, + audio_content_name: str, +) -> None: + """Send audio chunks from file simulating real-time delay.""" + chunk_count = 0 + with AUDIO_FILE.open("rb") as f: + while chunk := f.read(CHUNK_SIZE): + chunk_count += 1 + encoded_chunk = base64.b64encode(chunk).decode("utf-8") + await _send_event( + stream, + AUDIO_INPUT_EVENT % (prompt_name, audio_content_name, encoded_chunk), + ) + # 512 bytes / (16000 Hz * 2 bytes/sample) = 0.016s per chunk + await asyncio.sleep(0.016) + + assert chunk_count > 0, f"No audio chunks were sent from {AUDIO_FILE}" + + silence_chunk = bytes(CHUNK_SIZE) + encoded_silence = base64.b64encode(silence_chunk).decode("utf-8") + for _ in range(SILENCE_CHUNKS): + await _send_event( + stream, + AUDIO_INPUT_EVENT % (prompt_name, audio_content_name, encoded_silence), + ) + await asyncio.sleep(0.016) + + await _send_event(stream, CONTENT_END_EVENT % (prompt_name, audio_content_name)) + await asyncio.sleep(RESPONSE_WAIT_TIME) + await _send_event(stream, PROMPT_END_EVENT % prompt_name) + await _send_event(stream, SESSION_END_EVENT) + + +async def _receive_stream_output( + stream: DuplexEventStream[ + InvokeModelWithBidirectionalStreamInput, + InvokeModelWithBidirectionalStreamOutput, + InvokeModelWithBidirectionalStreamOperationOutput, + ], +) -> tuple[bool, bool, list[str]]: + """Receive and collect output from the bidirectional stream. + + Returns: + Tuple of (got_text, got_audio, all_text_output) + """ + got_text = False + got_audio = False + all_text_output: list[str] = [] + + await stream.await_output() + output_stream = stream.output_stream + if output_stream is None: + return got_text, got_audio, all_text_output + + async for out in output_stream: + if not isinstance(out, InvokeModelWithBidirectionalStreamOutputChunk): + raise RuntimeError( + f"Received unexpected event type in stream: {type(out).__name__}" + ) + + payload = out.value.bytes_ + if not payload: + continue + + msg = json.loads(payload.decode("utf-8")) + event_data = msg.get("event", {}) + + if "textOutput" in event_data: + got_text = True + text_content = event_data["textOutput"].get("content", "") + all_text_output.append(text_content) + if "audioOutput" in event_data: + got_audio = True + if "completionEnd" in event_data: + break + + return got_text, got_audio, all_text_output + + +async def test_invoke_model_with_bidirectional_stream() -> None: + """Test bidirectional streaming with audio input and text/audio output.""" + bedrock_client = create_bedrock_client("us-east-1") + + stream = await bedrock_client.invoke_model_with_bidirectional_stream( + InvokeModelWithBidirectionalStreamOperationInput( + model_id=BIDIRECTIONAL_MODEL_ID + ) + ) + + prompt_name = str(uuid.uuid4()) + content_name = str(uuid.uuid4()) + audio_content_name = str(uuid.uuid4()) + + init_events = [ + START_SESSION_EVENT, + START_PROMPT_EVENT % prompt_name, + TEXT_CONTENT_START_EVENT % (prompt_name, content_name, "SYSTEM"), + TEXT_INPUT_EVENT % (prompt_name, content_name, DEFAULT_SYSTEM_PROMPT), + CONTENT_END_EVENT % (prompt_name, content_name), + ] + + for event in init_events: + await _send_event(stream, event) + + await _send_event( + stream, AUDIO_CONTENT_START_EVENT % (prompt_name, audio_content_name) + ) + + results = await asyncio.gather( + _send_audio_chunks(stream, prompt_name, audio_content_name), + _receive_stream_output(stream), + ) + got_text, got_audio, all_text_output = results[1] + + assert got_text, "Expected to receive text output" + assert got_audio, "Expected to receive audio output" + assert len(all_text_output) > 0, "Expected non-empty text output" diff --git a/clients/aws-sdk-bedrock-runtime/tests/integration/test_non_streaming.py b/clients/aws-sdk-bedrock-runtime/tests/integration/test_non_streaming.py new file mode 100644 index 0000000..aefb9de --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/tests/integration/test_non_streaming.py @@ -0,0 +1,37 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Test non-streaming output type handling.""" + +from aws_sdk_bedrock_runtime.models import ( + ContentBlockText, + ConverseInput, + ConverseOperationOutput, + ConverseOutputMessage, + Message, +) + +from . import MESSAGE, MODEL_ID, create_bedrock_client + + +async def test_converse() -> None: + bedrock_client = create_bedrock_client("us-west-2") + + input_message = Message(role="user", content=[ContentBlockText(value=MESSAGE)]) + response = await bedrock_client.converse( + ConverseInput(model_id=MODEL_ID, messages=[input_message]) + ) + + assert isinstance(response, ConverseOperationOutput) + assert isinstance(response.output, ConverseOutputMessage) + + output_message = response.output.value + assert output_message.role == "assistant" + assert len(output_message.content) > 0 + + content_block = output_message.content[0] + assert isinstance(content_block, ContentBlockText) + assert isinstance(content_block.value, str) and content_block.value + + assert response.usage.input_tokens > 0 + assert response.usage.output_tokens > 0 diff --git a/clients/aws-sdk-bedrock-runtime/tests/integration/test_output_streaming.py b/clients/aws-sdk-bedrock-runtime/tests/integration/test_output_streaming.py new file mode 100644 index 0000000..b06b53e --- /dev/null +++ b/clients/aws-sdk-bedrock-runtime/tests/integration/test_output_streaming.py @@ -0,0 +1,45 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Test output streaming event stream handling.""" + +from aws_sdk_bedrock_runtime.models import ( + ContentBlockDeltaText, + ContentBlockText, + ConverseStreamInput, + ConverseStreamOperationOutput, + ConverseStreamOutputContentBlockDelta, + ConverseStreamOutputMetadata, + Message, +) + +from . import MESSAGE, MODEL_ID, create_bedrock_client + + +async def test_converse_stream() -> None: + bedrock_client = create_bedrock_client("us-west-2") + + input_message = Message(role="user", content=[ContentBlockText(value=MESSAGE)]) + response = await bedrock_client.converse_stream( + ConverseStreamInput(model_id=MODEL_ID, messages=[input_message]) + ) + + received_text: list[str] = [] + metadata_received = False + + async with response as stream: + async for event in stream.output_stream: + if isinstance(event, ConverseStreamOutputContentBlockDelta): + delta = event.value.delta + if isinstance(delta, ContentBlockDeltaText): + received_text.append(delta.value) + elif isinstance(event, ConverseStreamOutputMetadata): + metadata_received = True + assert event.value.usage.input_tokens > 0 + assert event.value.usage.output_tokens > 0 + + full_response = "".join(received_text) + assert full_response + + assert metadata_received + assert isinstance(stream.output, ConverseStreamOperationOutput) diff --git a/clients/aws-sdk-python/pyproject.toml b/clients/aws-sdk-python/pyproject.toml index 09548ec..0be0e54 100644 --- a/clients/aws-sdk-python/pyproject.toml +++ b/clients/aws-sdk-python/pyproject.toml @@ -22,9 +22,9 @@ classifiers = [ dependencies = [] [project.optional-dependencies] -bedrock_runtime = ["aws_sdk_bedrock_runtime==0.2.0"] -sagemaker_runtime_http2 = ["aws_sdk_sagemaker_runtime_http2==0.1.0"] -transcribe_streaming = ["aws_sdk_transcribe_streaming==0.2.0"] +bedrock_runtime = ["aws_sdk_bedrock_runtime==0.3.0"] +sagemaker_runtime_http2 = ["aws_sdk_sagemaker_runtime_http2==0.3.0"] +transcribe_streaming = ["aws_sdk_transcribe_streaming==0.3.0"] all = [ "aws_sdk_python[bedrock_runtime]", "aws_sdk_python[sagemaker_runtime_http2]", diff --git a/clients/aws-sdk-python/src/aws_sdk_python/__init__.py b/clients/aws-sdk-python/src/aws_sdk_python/__init__.py index f326146..57db727 100644 --- a/clients/aws-sdk-python/src/aws_sdk_python/__init__.py +++ b/clients/aws-sdk-python/src/aws_sdk_python/__init__.py @@ -1,6 +1,6 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -__version__ = "0.2.0" +__version__ = "0.3.0" # TODO: Consider adding relative imports for services from the top level namespace? diff --git a/clients/aws-sdk-sagemaker-runtime-http2/CHANGELOG.md b/clients/aws-sdk-sagemaker-runtime-http2/CHANGELOG.md index b0ce462..8bc9e07 100644 --- a/clients/aws-sdk-sagemaker-runtime-http2/CHANGELOG.md +++ b/clients/aws-sdk-sagemaker-runtime-http2/CHANGELOG.md @@ -2,6 +2,20 @@ ## Unreleased +* None yet. + +## v0.3.0 + +This release proceeds 0.1.0. There is no 0.2.0 as the initial release was intended to be inline +with the `smithy-aws-core` version used in the client. + +### Breaking +* Function signature for `resolve_retry_strategy` has been changed to prevent unnecessary code duplication in operation methods. This will affect all 0.3.0 clients. + +### Dependencies +* **Updated**: `smithy_aws_core[eventstream, json]` from `~=0.2.0` to `~=0.3.0`. +* **Updated**: `smithy_core` from `~=0.2.0` to `~=0.3.0`. + ## v0.1.0 ### Features diff --git a/clients/aws-sdk-sagemaker-runtime-http2/docs/conf.py b/clients/aws-sdk-sagemaker-runtime-http2/docs/conf.py index 0ff545c..c25a79d 100644 --- a/clients/aws-sdk-sagemaker-runtime-http2/docs/conf.py +++ b/clients/aws-sdk-sagemaker-runtime-http2/docs/conf.py @@ -7,7 +7,7 @@ project = "Amazon SageMaker Runtime HTTP2" author = "Amazon Web Services" -release = "0.1.0" +release = "0.3.0" extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"] diff --git a/clients/aws-sdk-sagemaker-runtime-http2/pyproject.toml b/clients/aws-sdk-sagemaker-runtime-http2/pyproject.toml index 8b9a41a..cafc944 100644 --- a/clients/aws-sdk-sagemaker-runtime-http2/pyproject.toml +++ b/clients/aws-sdk-sagemaker-runtime-http2/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "aws_sdk_sagemaker_runtime_http2" -version = "0.1.0" +version = "0.3.0" description = "aws_sdk_sagemaker_runtime_http2 client" readme = "README.md" requires-python = ">=3.12" @@ -24,15 +24,15 @@ classifiers = [ ] dependencies = [ - "smithy_aws_core[eventstream, json]~=0.2.0", - "smithy_core~=0.2.0", + "smithy_aws_core[eventstream, json]~=0.3.0", + "smithy_core~=0.3.0", "smithy_http[awscrt]~=0.3.0" ] [dependency-groups] test = [ - "pytest>=7.2.0,<8.0.0", - "pytest-asyncio>=0.20.3,<0.21.0" + "pytest>=9.0.1,<10.0.0", + "pytest-asyncio>=1.3.0,<1.4.0" ] docs = [ diff --git a/clients/aws-sdk-sagemaker-runtime-http2/src/aws_sdk_sagemaker_runtime_http2/__init__.py b/clients/aws-sdk-sagemaker-runtime-http2/src/aws_sdk_sagemaker_runtime_http2/__init__.py index e1ee049..768ec96 100644 --- a/clients/aws-sdk-sagemaker-runtime-http2/src/aws_sdk_sagemaker_runtime_http2/__init__.py +++ b/clients/aws-sdk-sagemaker-runtime-http2/src/aws_sdk_sagemaker_runtime_http2/__init__.py @@ -1,3 +1,3 @@ # Code generated by smithy-python-codegen DO NOT EDIT. -__version__: str = "0.1.0" +__version__: str = "0.3.0" diff --git a/clients/aws-sdk-sagemaker-runtime-http2/src/aws_sdk_sagemaker_runtime_http2/client.py b/clients/aws-sdk-sagemaker-runtime-http2/src/aws_sdk_sagemaker_runtime_http2/client.py index 2829a88..a9b015d 100644 --- a/clients/aws-sdk-sagemaker-runtime-http2/src/aws_sdk_sagemaker_runtime_http2/client.py +++ b/clients/aws-sdk-sagemaker-runtime-http2/src/aws_sdk_sagemaker_runtime_http2/client.py @@ -7,8 +7,7 @@ from smithy_core.aio.eventstream import DuplexEventStream from smithy_core.exceptions import ExpectationNotMetError from smithy_core.interceptors import InterceptorChain -from smithy_core.interfaces.retries import RetryStrategy -from smithy_core.retries import RetryStrategyOptions, RetryStrategyResolver +from smithy_core.retries import RetryStrategyResolver from smithy_core.types import TypedProperties from smithy_http.plugins import user_agent_plugin @@ -108,22 +107,9 @@ async def invoke_endpoint_with_bidirectional_stream( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( diff --git a/clients/aws-sdk-transcribe-streaming/CHANGELOG.md b/clients/aws-sdk-transcribe-streaming/CHANGELOG.md index e2ec005..8be04cc 100644 --- a/clients/aws-sdk-transcribe-streaming/CHANGELOG.md +++ b/clients/aws-sdk-transcribe-streaming/CHANGELOG.md @@ -2,6 +2,17 @@ ## Unreleased +* None yet. + +## v0.3.0 + +### Breaking +* Function signature for `resolve_retry_strategy` has been changed to prevent unnecessary code duplication in operation methods. This will affect all 0.3.0 clients. + +### Dependencies +* **Updated**: `smithy_aws_core[eventstream, json]` from `~=0.2.0` to `~=0.3.0`. +* **Updated**: `smithy_core` from `~=0.2.0` to `~=0.3.0`. + ## v0.2.0 ### API Changes diff --git a/clients/aws-sdk-transcribe-streaming/docs/conf.py b/clients/aws-sdk-transcribe-streaming/docs/conf.py index e746b4c..c4f201a 100644 --- a/clients/aws-sdk-transcribe-streaming/docs/conf.py +++ b/clients/aws-sdk-transcribe-streaming/docs/conf.py @@ -7,7 +7,7 @@ project = "Amazon Transcribe Streaming Service" author = "Amazon Web Services" -release = "0.2.0" +release = "0.3.0" extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"] diff --git a/clients/aws-sdk-transcribe-streaming/pyproject.toml b/clients/aws-sdk-transcribe-streaming/pyproject.toml index 17e258a..d80bde3 100644 --- a/clients/aws-sdk-transcribe-streaming/pyproject.toml +++ b/clients/aws-sdk-transcribe-streaming/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "aws_sdk_transcribe_streaming" -version = "0.2.0" +version = "0.3.0" description = "aws_sdk_transcribe_streaming client" readme = "README.md" requires-python = ">=3.12" @@ -24,15 +24,15 @@ classifiers = [ ] dependencies = [ - "smithy_aws_core[eventstream, json]~=0.2.0", - "smithy_core~=0.2.0", + "smithy_aws_core[eventstream, json]~=0.3.0", + "smithy_core~=0.3.0", "smithy_http[awscrt]~=0.3.0" ] [dependency-groups] test = [ - "pytest>=7.2.0,<8.0.0", - "pytest-asyncio>=0.20.3,<0.21.0" + "pytest>=9.0.1,<10.0.0", + "pytest-asyncio>=1.3.0,<1.4.0" ] docs = [ diff --git a/clients/aws-sdk-transcribe-streaming/src/aws_sdk_transcribe_streaming/__init__.py b/clients/aws-sdk-transcribe-streaming/src/aws_sdk_transcribe_streaming/__init__.py index c8417da..768ec96 100644 --- a/clients/aws-sdk-transcribe-streaming/src/aws_sdk_transcribe_streaming/__init__.py +++ b/clients/aws-sdk-transcribe-streaming/src/aws_sdk_transcribe_streaming/__init__.py @@ -1,3 +1,3 @@ # Code generated by smithy-python-codegen DO NOT EDIT. -__version__: str = "0.2.0" +__version__: str = "0.3.0" diff --git a/clients/aws-sdk-transcribe-streaming/src/aws_sdk_transcribe_streaming/client.py b/clients/aws-sdk-transcribe-streaming/src/aws_sdk_transcribe_streaming/client.py index d8155f9..346ac89 100644 --- a/clients/aws-sdk-transcribe-streaming/src/aws_sdk_transcribe_streaming/client.py +++ b/clients/aws-sdk-transcribe-streaming/src/aws_sdk_transcribe_streaming/client.py @@ -7,8 +7,7 @@ from smithy_core.aio.eventstream import DuplexEventStream from smithy_core.exceptions import ExpectationNotMetError from smithy_core.interceptors import InterceptorChain -from smithy_core.interfaces.retries import RetryStrategy -from smithy_core.retries import RetryStrategyOptions, RetryStrategyResolver +from smithy_core.retries import RetryStrategyResolver from smithy_core.types import TypedProperties from smithy_http.plugins import user_agent_plugin @@ -105,22 +104,9 @@ async def get_medical_scribe_stream( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -179,22 +165,9 @@ async def start_call_analytics_stream_transcription( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -277,22 +250,9 @@ async def start_medical_scribe_stream( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -356,22 +316,9 @@ async def start_medical_stream_transcription( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( @@ -430,22 +377,9 @@ async def start_stream_transcription( "protocol and transport MUST be set on the config to make calls." ) - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline(protocol=config.protocol, transport=config.transport) call = ClientCall( diff --git a/clients/aws-sdk-transcribe-streaming/tests/integration/__init__.py b/clients/aws-sdk-transcribe-streaming/tests/integration/__init__.py new file mode 100644 index 0000000..d5b39d3 --- /dev/null +++ b/clients/aws-sdk-transcribe-streaming/tests/integration/__init__.py @@ -0,0 +1,22 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +from pathlib import Path + +from smithy_aws_core.identity import EnvironmentCredentialsResolver + +from aws_sdk_transcribe_streaming.client import TranscribeStreamingClient +from aws_sdk_transcribe_streaming.config import Config + +AUDIO_FILE = Path(__file__).parent / "assets" / "test.wav" + + +def create_transcribe_client(region: str) -> TranscribeStreamingClient: + """Helper to create a TranscribeStreamingClient for a given region.""" + return TranscribeStreamingClient( + config=Config( + endpoint_uri=f"https://transcribestreaming.{region}.amazonaws.com", + region=region, + aws_credentials_identity_resolver=EnvironmentCredentialsResolver(), + ) + ) diff --git a/clients/aws-sdk-transcribe-streaming/tests/integration/assets/test.wav b/clients/aws-sdk-transcribe-streaming/tests/integration/assets/test.wav new file mode 100644 index 0000000..5f6ca02 Binary files /dev/null and b/clients/aws-sdk-transcribe-streaming/tests/integration/assets/test.wav differ diff --git a/clients/aws-sdk-transcribe-streaming/tests/integration/test_bidirectional_streaming.py b/clients/aws-sdk-transcribe-streaming/tests/integration/test_bidirectional_streaming.py new file mode 100644 index 0000000..d4582e5 --- /dev/null +++ b/clients/aws-sdk-transcribe-streaming/tests/integration/test_bidirectional_streaming.py @@ -0,0 +1,112 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Test bidirectional event stream handling.""" + +import asyncio +import time + +from smithy_core.aio.eventstream import DuplexEventStream + +from aws_sdk_transcribe_streaming.models import ( + AudioEvent, + AudioStream, + AudioStreamAudioEvent, + LanguageCode, + MediaEncoding, + StartStreamTranscriptionInput, + StartStreamTranscriptionOutput, + TranscriptResultStream, + TranscriptResultStreamTranscriptEvent, +) + +from . import AUDIO_FILE, create_transcribe_client + + +SAMPLE_RATE = 16000 +BYTES_PER_SAMPLE = 2 +CHANNEL_NUMS = 1 +CHUNK_SIZE = 1024 * 8 + + +async def _send_audio_chunks( + stream: DuplexEventStream[ + AudioStream, TranscriptResultStream, StartStreamTranscriptionOutput + ], +) -> None: + """Send audio chunks from file simulating real-time delay.""" + start_time = time.time() + elapsed_audio_time = 0.0 + + with AUDIO_FILE.open("rb") as f: + while chunk := f.read(CHUNK_SIZE): + await stream.input_stream.send( + AudioStreamAudioEvent(value=AudioEvent(audio_chunk=chunk)) + ) + elapsed_audio_time += len(chunk) / ( + BYTES_PER_SAMPLE * SAMPLE_RATE * CHANNEL_NUMS + ) + wait_time = start_time + elapsed_audio_time - time.time() + await asyncio.sleep(wait_time) + + # Send an empty audio event to signal end of input + await stream.input_stream.send( + AudioStreamAudioEvent(value=AudioEvent(audio_chunk=b"")) + ) + await asyncio.sleep(0.4) + await stream.input_stream.close() + + +async def _receive_transcription_output( + stream: DuplexEventStream[ + AudioStream, TranscriptResultStream, StartStreamTranscriptionOutput + ], +) -> tuple[bool, list[str]]: + """Receive and collect transcription output from the stream. + + Returns: + Tuple of (got_transcript_events, transcripts) + """ + got_transcript_events = False + transcripts: list[str] = [] + + _, output_stream = await stream.await_output() + if output_stream is None: + return got_transcript_events, transcripts + + async for event in output_stream: + if not isinstance(event, TranscriptResultStreamTranscriptEvent): + raise RuntimeError( + f"Received unexpected event type in stream: {type(event).__name__}" + ) + + got_transcript_events = True + if event.value.transcript and event.value.transcript.results: + for result in event.value.transcript.results: + if result.alternatives: + for alt in result.alternatives: + if alt.transcript: + transcripts.append(alt.transcript) + + return got_transcript_events, transcripts + + +async def test_start_stream_transcription() -> None: + """Test bidirectional streaming with audio input and transcription output.""" + transcribe_client = create_transcribe_client("us-west-2") + + stream = await transcribe_client.start_stream_transcription( + input=StartStreamTranscriptionInput( + language_code=LanguageCode.EN_US, + media_sample_rate_hertz=SAMPLE_RATE, + media_encoding=MediaEncoding.PCM, + ) + ) + + results = await asyncio.gather( + _send_audio_chunks(stream), _receive_transcription_output(stream) + ) + got_transcript_events, transcripts = results[1] + + assert got_transcript_events, "Expected to receive transcript events" + assert len(transcripts) > 0, "Expected to receive at least one transcript" diff --git a/clients/aws-sdk-transcribe-streaming/tests/integration/test_non_streaming.py b/clients/aws-sdk-transcribe-streaming/tests/integration/test_non_streaming.py new file mode 100644 index 0000000..80137bd --- /dev/null +++ b/clients/aws-sdk-transcribe-streaming/tests/integration/test_non_streaming.py @@ -0,0 +1,123 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Test non-streaming output type handling. + +This test requires AWS resources (an IAM role and an S3 bucket). +To set them up locally, run: + + uv run scripts/setup_resources.py + +Then export the environment variables shown in the output. +""" + +import asyncio +import os +import time +import uuid + +import pytest + +from aws_sdk_transcribe_streaming.models import ( + ClinicalNoteGenerationSettings, + GetMedicalScribeStreamInput, + GetMedicalScribeStreamOutput, + LanguageCode, + MedicalScribeAudioEvent, + MedicalScribeConfigurationEvent, + MedicalScribeInputStreamAudioEvent, + MedicalScribeInputStreamConfigurationEvent, + MedicalScribeInputStreamSessionControlEvent, + MedicalScribePostStreamAnalyticsSettings, + MedicalScribeSessionControlEvent, + MedicalScribeSessionControlEventType, + MediaEncoding, + StartMedicalScribeStreamInput, +) + +from . import AUDIO_FILE, create_transcribe_client + +SAMPLE_RATE = 16000 +BYTES_PER_SAMPLE = 2 +CHANNEL_NUMS = 1 +CHUNK_SIZE = 1024 * 8 + + +async def test_get_medical_scribe_stream() -> None: + role_arn = os.environ.get("HEALTHSCRIBE_ROLE_ARN") + s3_bucket = os.environ.get("HEALTHSCRIBE_S3_BUCKET") + + if not role_arn or not s3_bucket: + pytest.fail("HEALTHSCRIBE_ROLE_ARN or HEALTHSCRIBE_S3_BUCKET not set") + + transcribe_client = create_transcribe_client("us-east-1") + session_id = str(uuid.uuid4()) + + stream = await transcribe_client.start_medical_scribe_stream( + input=StartMedicalScribeStreamInput( + language_code=LanguageCode.EN_US, + media_sample_rate_hertz=SAMPLE_RATE, + media_encoding=MediaEncoding.PCM, + session_id=session_id, + ) + ) + + await stream.input_stream.send( + MedicalScribeInputStreamConfigurationEvent( + value=MedicalScribeConfigurationEvent( + resource_access_role_arn=role_arn, + post_stream_analytics_settings=MedicalScribePostStreamAnalyticsSettings( + clinical_note_generation_settings=ClinicalNoteGenerationSettings( + output_bucket_name=s3_bucket + ) + ), + ) + ) + ) + + start_time = time.time() + elapsed_audio_time = 0.0 + + with AUDIO_FILE.open("rb") as f: + while chunk := f.read(CHUNK_SIZE): + await stream.input_stream.send( + MedicalScribeInputStreamAudioEvent( + value=MedicalScribeAudioEvent(audio_chunk=chunk) + ) + ) + elapsed_audio_time += len(chunk) / ( + BYTES_PER_SAMPLE * SAMPLE_RATE * CHANNEL_NUMS + ) + wait_time = start_time + elapsed_audio_time - time.time() + if wait_time > 0: + await asyncio.sleep(wait_time) + + await stream.input_stream.send( + MedicalScribeInputStreamSessionControlEvent( + value=MedicalScribeSessionControlEvent( + type=MedicalScribeSessionControlEventType.END_OF_SESSION + ) + ) + ) + await stream.input_stream.close() + + await stream.await_output() + + # Consume output stream events to properly close the connection + if stream.output_stream: + async for _ in stream.output_stream: + pass + + response = await transcribe_client.get_medical_scribe_stream( + input=GetMedicalScribeStreamInput(session_id=session_id) + ) + + assert isinstance(response, GetMedicalScribeStreamOutput) + assert response.medical_scribe_stream_details is not None + + details = response.medical_scribe_stream_details + assert details.session_id == session_id + assert details.stream_status == "COMPLETED" + assert details.language_code == "en-US" + assert details.media_encoding == "pcm" + assert details.media_sample_rate_hertz == SAMPLE_RATE diff --git a/clients/aws-sdk-transcribe-streaming/tests/setup_resources.py b/clients/aws-sdk-transcribe-streaming/tests/setup_resources.py new file mode 100644 index 0000000..35c05f7 --- /dev/null +++ b/clients/aws-sdk-transcribe-streaming/tests/setup_resources.py @@ -0,0 +1,97 @@ +# /// script +# requires-python = ">=3.12" +# dependencies = [ +# "boto3", +# ] +# /// +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Setup script to create AWS resources for integration tests. + +Creates an IAM role and S3 bucket needed for medical scribe integration tests. + +Note: + This script is intended for local testing only and should not be used for + production setups. + +Usage: + uv run scripts/setup_resources.py +""" + +import json +from typing import Any + +import boto3 + + +def create_iam_role(iam_client: Any, role_name: str, bucket_name: str) -> None: + trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "transcribe.streaming.amazonaws.com" + ] + }, + "Action": "sts:AssumeRole", + } + ] + } + + try: + iam_client.create_role( + RoleName=role_name, AssumeRolePolicyDocument=json.dumps(trust_policy) + ) + except iam_client.exceptions.EntityAlreadyExistsException: + pass + + permissions_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "s3:PutObject" + ], + "Resource": [ + f"arn:aws:s3:::{bucket_name}", + f"arn:aws:s3:::{bucket_name}/*", + ], + "Effect": "Allow" + } + ] + } + + iam_client.put_role_policy( + RoleName=role_name, + PolicyName="HealthScribeS3Access", + PolicyDocument=json.dumps(permissions_policy), + ) + + +def setup_healthscribe_resources() -> tuple[str, str]: + region = "us-east-1" + iam = boto3.client("iam") + s3 = boto3.client("s3", region_name=region) + sts = boto3.client("sts") + + account_id = sts.get_caller_identity()["Account"] + bucket_name = f"healthscribe-test-{account_id}-{region}" + role_name = "HealthScribeIntegrationTestRole" + + s3.create_bucket(Bucket=bucket_name) + create_iam_role(iam, role_name, bucket_name) + + role_arn = f"arn:aws:iam::{account_id}:role/{role_name}" + return role_arn, bucket_name + + +if __name__ == "__main__": + role_arn, bucket_name = setup_healthscribe_resources() + + print("Setup complete. Export these environment variables before running tests:") + print(f"export HEALTHSCRIBE_ROLE_ARN={role_arn}") + print(f"export HEALTHSCRIBE_S3_BUCKET={bucket_name}") diff --git a/codegen/aws-models/bedrock-runtime.json b/codegen/aws-models/bedrock-runtime.json index c19a306..fc1ac68 100644 --- a/codegen/aws-models/bedrock-runtime.json +++ b/codegen/aws-models/bedrock-runtime.json @@ -1068,6 +1068,153 @@ "smithy.api#documentation": "

A summary of an asynchronous invocation.

" } }, + "com.amazonaws.bedrockruntime#AudioBlock": { + "type": "structure", + "members": { + "format": { + "target": "com.amazonaws.bedrockruntime#AudioFormat", + "traits": { + "smithy.api#documentation": "

The format of the audio data, such as MP3, WAV, FLAC, or other supported audio formats.

", + "smithy.api#required": {} + } + }, + "source": { + "target": "com.amazonaws.bedrockruntime#AudioSource", + "traits": { + "smithy.api#documentation": "

The source of the audio data, which can be provided as raw bytes or an S3 location.

", + "smithy.api#required": {} + } + }, + "error": { + "target": "com.amazonaws.bedrockruntime#ErrorBlock", + "traits": { + "smithy.api#documentation": "

Error information if the audio block could not be processed or contains invalid data.

" + } + } + }, + "traits": { + "smithy.api#documentation": "

An audio content block that contains audio data in various supported formats.

" + } + }, + "com.amazonaws.bedrockruntime#AudioFormat": { + "type": "enum", + "members": { + "MP3": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "mp3" + } + }, + "OPUS": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "opus" + } + }, + "WAV": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "wav" + } + }, + "AAC": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "aac" + } + }, + "FLAC": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "flac" + } + }, + "MP4": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "mp4" + } + }, + "OGG": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "ogg" + } + }, + "MKV": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "mkv" + } + }, + "MKA": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "mka" + } + }, + "X_AAC": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "x-aac" + } + }, + "M4A": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "m4a" + } + }, + "MPEG": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "mpeg" + } + }, + "MPGA": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "mpga" + } + }, + "PCM": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "pcm" + } + }, + "WEBM": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "webm" + } + } + } + }, + "com.amazonaws.bedrockruntime#AudioSource": { + "type": "union", + "members": { + "bytes": { + "target": "smithy.api#Blob", + "traits": { + "smithy.api#documentation": "

Audio data encoded in base64.

", + "smithy.api#length": { + "min": 1 + } + } + }, + "s3Location": { + "target": "com.amazonaws.bedrockruntime#S3Location", + "traits": { + "smithy.api#documentation": "

A reference to audio data stored in an Amazon S3 bucket. To see which models support S3 uploads, see Supported models and features for Converse.

" + } + } + }, + "traits": { + "smithy.api#documentation": "

The source of audio data, which can be provided either as raw bytes or a reference to an S3 location.

", + "smithy.api#sensitive": {} + } + }, "com.amazonaws.bedrockruntime#AutoToolChoice": { "type": "structure", "members": {}, @@ -1392,6 +1539,12 @@ "smithy.api#documentation": "

Video to include in the message.

" } }, + "audio": { + "target": "com.amazonaws.bedrockruntime#AudioBlock", + "traits": { + "smithy.api#documentation": "

An audio content block containing audio data in the conversation.

" + } + }, "toolUse": { "target": "com.amazonaws.bedrockruntime#ToolUseBlock", "traits": { @@ -1471,6 +1624,12 @@ "traits": { "smithy.api#documentation": "

Incremental citation information that is streamed as part of the response generation process.

" } + }, + "image": { + "target": "com.amazonaws.bedrockruntime#ImageBlockDelta", + "traits": { + "smithy.api#documentation": "

A streaming delta event containing incremental image data.

" + } } }, "traits": { @@ -1513,6 +1672,12 @@ "traits": { "smithy.api#documentation": "

The

" } + }, + "image": { + "target": "com.amazonaws.bedrockruntime#ImageBlockStart", + "traits": { + "smithy.api#documentation": "

The initial event indicating the start of a streaming image block.

" + } } }, "traits": { @@ -2518,6 +2683,21 @@ "smithy.api#documentation": "

Contains the content of a document.

" } }, + "com.amazonaws.bedrockruntime#ErrorBlock": { + "type": "structure", + "members": { + "message": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "

A human-readable error message describing what went wrong during content processing.

" + } + } + }, + "traits": { + "smithy.api#documentation": "

A block containing error information when content processing fails.

", + "smithy.api#sensitive": {} + } + }, "com.amazonaws.bedrockruntime#FoundationModelVersionIdentifier": { "type": "string", "traits": { @@ -4763,12 +4943,53 @@ "smithy.api#documentation": "

The source for the image.

", "smithy.api#required": {} } + }, + "error": { + "target": "com.amazonaws.bedrockruntime#ErrorBlock", + "traits": { + "smithy.api#documentation": "

Error information if the image block could not be processed or contains invalid data.

" + } } }, "traits": { "smithy.api#documentation": "

Image content for a message.

" } }, + "com.amazonaws.bedrockruntime#ImageBlockDelta": { + "type": "structure", + "members": { + "source": { + "target": "com.amazonaws.bedrockruntime#ImageSource", + "traits": { + "smithy.api#documentation": "

The incremental image source data for this delta event.

" + } + }, + "error": { + "target": "com.amazonaws.bedrockruntime#ErrorBlock", + "traits": { + "smithy.api#documentation": "

Error information if this image delta could not be processed.

" + } + } + }, + "traits": { + "smithy.api#documentation": "

A streaming delta event that contains incremental image data during streaming responses.

" + } + }, + "com.amazonaws.bedrockruntime#ImageBlockStart": { + "type": "structure", + "members": { + "format": { + "target": "com.amazonaws.bedrockruntime#ImageFormat", + "traits": { + "smithy.api#documentation": "

The format of the image data that will be streamed in subsequent delta events.

", + "smithy.api#required": {} + } + } + }, + "traits": { + "smithy.api#documentation": "

The initial event in a streaming image block that indicates the start of image content.

" + } + }, "com.amazonaws.bedrockruntime#ImageFormat": { "type": "enum", "members": { @@ -4818,7 +5039,8 @@ } }, "traits": { - "smithy.api#documentation": "

The source for an image.

" + "smithy.api#documentation": "

The source for an image.

", + "smithy.api#sensitive": {} } }, "com.amazonaws.bedrockruntime#ImagesGuarded": { @@ -6188,6 +6410,12 @@ "traits": { "smithy.api#enumValue": "flex" } + }, + "RESERVED": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "reserved" + } } } }, @@ -6396,6 +6624,18 @@ "smithy.api#enumValue": "content_filtered" } }, + "MALFORMED_MODEL_OUTPUT": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "malformed_model_output" + } + }, + "MALFORMED_TOOL_USE": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#enumValue": "malformed_tool_use" + } + }, "MODEL_CONTEXT_WINDOW_EXCEEDED": { "target": "smithy.api#Unit", "traits": { @@ -6736,6 +6976,12 @@ "traits": { "smithy.api#documentation": "

The reasoning the model used to return the output.

" } + }, + "json": { + "target": "smithy.api#Document", + "traits": { + "smithy.api#documentation": "

The JSON schema for the tool result content block. see JSON Schema Reference.

" + } } }, "traits": {