[pull] main from apache:main#165
Merged
Merged
Conversation
## Bedrock integration for Burr
Adds first-class support for Amazon Bedrock's Converse API to Burr, so users can build state machines and agents on top of Bedrock models (Claude, Titan, Llama, etc.) without writing boto3 plumbing themselves.
### Architecture
The integration is structured in three layers: a pure "core" that knows how to talk to Bedrock, a thin mixin that adapts it to Burr's action interface, and two concrete action classes that plug into Burr's state machine.
Layer 1 _BedrockCore (transport + request building). This is a plain Python class with no knowledge of Burr actions. It holds the configuration (model id, region, guardrail, inference config, retry count, optional injected client) and exposes two things: get_client(), which returns the bedrock-runtime boto3 client (either the one injected at construction time, or a new one built lazily with a Config(retries=...) on first use), and build_converse_request(state), which calls the user's input_mapper(state) and assembles the kwargs dict passed to converse() / converse_stream() — modelId, messages, inferenceConfig, and the optional system and guardrailConfig blocks. Guardrail validation lives here: if guardrail_id is set without an explicit guardrail_version, the constructor raises ValueError so nobody accidentally ships against an unpublished DRAFT.
Layer 2 - _BedrockBase (Burr adapter mixin). A small mixin that holds a _BedrockCore instance and exposes the three properties Burr actions need: reads, writes, and name. It exists purely to avoid duplicating constructor wiring and property boilerplate between the sync and streaming classes.
Layer 3 - concrete actions.
BedrockAction inherits from _BedrockBase and Burr's SingleStepAction. Its run_and_update() builds the Converse request, calls converse(), runs the response through _text_from_content_blocks() (which joins every text block in the response, so multi-block replies and mixed text/tool-use messages aren't silently truncated), and then uses _model_result_for_writes() to map the model output to whichever keys the user declared in writes — "response" gets the joined text, "usage" gets the token counts, "stop_reason" gets the Bedrock stop reason, and any other custom key also gets the joined text.
BedrockStreamingAction inherits from _BedrockBase and Burr's StreamingAction. Its stream_run() is a generator that iterates the Bedrock event stream, collecting text chunks into a list[str] (joined once at the end — not concatenated in a loop), and yields one dict per contentBlockDelta event plus a final complete: True payload. Its update() runs only on the final chunk and reuses the same _model_result_for_writes() helper, so streaming and non-streaming actions write state using identical logic.
Helper functions. Two pure functions sit at module scope and are shared by both actions: _text_from_content_blocks(blocks) tolerates non-text blocks (e.g. toolUse) and returns the joined text, and _model_result_for_writes(text, usage, stop_reason, writes) centralises the "which result goes into which state key" logic so the sync and streaming paths can't drift out of sync.
#### Cross-cutting concerns.
boto3 is imported inside a try/except that calls require_plugin(...) on failure, so installing Burr without the [bedrock] extra doesn't break anything until a user actually touches the module.
The BedrockAction / BedrockStreamingAction symbols are exposed via a __getattr__ in
__init__.py
, giving callers the ergonomic from burr.integrations import BedrockAction without importing boto3 for every Burr user.
Errors from boto3 (ClientError) are logged at ERROR level and re-raised unchanged, so Burr's own retry/lifecycle hooks can handle them at the application level.
State machine shape. Both actions are drop-in nodes in a Burr graph. A typical chatbot wiring is human_input → bedrock_call → save_response → human_input, where bedrock_call is one of these two actions reading chat_history and writing response. For agents, bedrock_call is followed by a conditional transition into a tool-executor action and back again — the integration itself stays stateless across calls; all conversation and tool state lives in Burr's State.
## Changes
- `burr/integrations/bedrock.py`
- `BedrockAction` — single-step action wrapping Bedrock `converse()`. Handles lazy client creation, retries, guardrails, inference config, and maps model output into state.
- `BedrockStreamingAction` — streaming variant using `converse_stream()`, yields chunks incrementally and merges final text into state on completion.
- Shared `_BedrockBase` + `_BedrockCore` to avoid duplication across the two classes.
- Helpers `_text_from_content_blocks` (joins all text blocks, tolerates tool-use blocks) and `_model_result_for_writes` (dynamically maps user's `writes` keys to response fields).
- `burr/integrations/__init__.py` — lazy exports for `BedrockAction` / `BedrockStreamingAction` so `boto3` is only imported when the classes are actually used.
- `pyproject.toml` — adds `[bedrock]` optional extra (`boto3`).
- `docs/reference/integrations/bedrock.rst` — Sphinx reference docs with install and IAM setup notes.
- `docs/getting_started/install.rst` — adds Bedrock to the install matrix.
- `examples/integrations/bedrock/` — runnable example with `application()` (sync) and `streaming_application()` (streaming) functions, plus README and requirements.
- `.github/workflows/python-package.yml` — dedicated `test-bedrock` job so AWS deps stay out of the default test matrix.
- `tests/integrations/test_bip0042_bedrock.py` — 23 unit tests covering imports, guardrail validation, sync/streaming interfaces, multi-block responses, custom `writes` keys, and error propagation.
## Design notes
- **No `boto3` import at module load** — guarded by `require_plugin`, so installs without the `[bedrock]` extra still work.
- **Client injection** — both actions accept a pre-built `bedrock-runtime` client for tests and distributed execution; otherwise the client is created lazily on first use.
- **Guardrail safety** — setting `guardrail_id` without an explicit `guardrail_version` raises `ValueError` rather than silently defaulting to `DRAFT`.
- **Empty vs. default inference config** — `inference_config={}` is respected; `None` falls back to `{"maxTokens": 4096}`.
- **Custom `writes` keys** — both sync and streaming actions map the model's text output to whichever `writes` key the user declares (e.g. `writes=["answer"]`), with `"response"` kept for backwards compatibility.
## How I tested this
- Ran the full unit suite: 23/23 bedrock tests pass, broader Burr suite unchanged.
- Ran the example against real Bedrock (Claude 3 Haiku, `us-east-1`) — both sync and streaming return valid output.
- Exercised end-to-end scenarios: multi-turn chat with state-managed history, custom `writes` keys, multi-content-block responses, guardrail validation paths, and `ClientError` propagation.
## Notes
- Follow-ups I'd suggest as separate PRs:
- Async variants (`AsyncBedrockAction` / `AsyncBedrockStreamingAction`) on top of `AsyncStreamingAction`.
- Native `toolConfig` support in `BedrockAction` for building agents without going around the abstraction.
- `stream_result` with typed state via `pydantic` integration.
## Checklist
- [x] PR has an informative and human-readable title
- [x] Changes are limited to a single goal (Bedrock integration only — tracking/SQS work split out per earlier review)
- [x] Code passed pre-commit
- [x] New functionality is tested (23 unit tests + real-API smoke)
- [x] New functions are documented (docstrings + Sphinx reference)
- [x] Project documentation updated (`docs/reference/integrations/bedrock.rst`, `docs/getting_started/install.rst`, example README)
--- Commits
* Add Bedrock integration, tests, docs, and CI
* fix(bedrock): streaming writes parity, multi-block text, list join for stream
* style: black-format bedrock integration
* Format Bedrock integration; add runnable Bedrock example
Apply Black formatting to burr/integrations/bedrock.py for CI.
Add examples/integrations/bedrock with BedrockAction and BedrockStreamingAction, requirements and README, and list it in examples/README.
* chore: fix burr/examples newline for pre-commit
end-of-file-fixer expects a trailing newline on burr/examples so
pre-commit run --all-files passes.
* fix bedrock converse content blocks and set_user_input return
Made-with: Cursor
* fixed review comments
Made-with: Cursor
* Fix Bedrock test imports for isort compatibility
---------
Co-authored-by: vaquarkhan <vaquarkhan@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )