A Finished Text Block Is Not a Finished Bedrock Answer
Map one Responses text stream to Bedrock ConverseStream without treating a text boundary, truncated answer or late completion after Stop as accepted output.
When moving a text-stream UI from OpenAI Responses to Bedrock ConverseStream, preserve the distinction between a text boundary, a response ending and application acceptance. Renaming the delta event is not enough. An adapter that accepts a finished block can publish an answer that subsequently fails, runs out of tokens or completes after the user has stopped that attempt.
This comparison is deliberately small: synchronous HTTP/SSE Responses with stream: true, background: false and gpt-4.1-2025-04-14; versus Bedrock Runtime ConverseStream through the US inference profile us.anthropic.claude-haiku-4-5-20251001-v1:0, originating in us-east-1. One assistant text answer is allowed. Tools are absent and reasoning is not requested. The examples are supplied, offline, synthetic event projections, not captured provider traffic, complete wire fixtures or evidence that either model produces these answers.
The application already uses the complete-answer release option described in streaming partial-answer disclosure. It can show fixed progress, but does not expose generated answer text until acceptance. This article does not select a disclosure policy or make directly rendered streaming safe. It identifies the lifecycle observations that the selected policy needs after an API change.
1. Record the API pair, not just two model names
The GPT-4.1 model page lists the selected snapshot, Responses and streaming support. The Responses streaming guide describes typed SSE events. A Chat Completions chunk handler is not this source contract.
AWS's Haiku 4.5 model card identifies Runtime Converse streaming support and the selected US profile. The target endpoint is https://bedrock-runtime.us-east-1.amazonaws.com. The card lists US destinations beyond the source Region; that endpoint does not establish single-Region inference. Placement approval belongs to the separate Bedrock placement decision.
Pin the application policy alongside the API identifiers: one text answer, no tool blocks, no requested stop sequences, complete-answer release and a named semantic acceptance rule. Record the deployed parser/client revision separately. The documentation comparison does not prove account entitlement, deployed-client behavior or a successful invocation. None was tested here. Background responses, stream resumption, tool-argument assembly, server-side tools and provider-held conversation transfer are outside this contract.
This narrow profile makes a conservative adapter practical. It must hold unexpected output rather than silently ignoring it. If the product actually needs multiple messages, tool calls or reasoning content, revise the profile and its tests before accepting those events. Tool-schema portability owns completed tool proposals; stateful API migration owns provider-state reconstruction. Neither is replaced by this text adapter.
2. Keep native boundaries visible
For Responses, response.output_text.done closes a text part, not the whole response. The separate terminal events include response.completed, response.failed and response.incomplete; stream errors also have an error event. Text deltas carry item, output and content indexes. Retain those identities while assembling the answer. These distinctions are documented in the Responses event reference.
For ConverseStream, the conversation streaming guide distinguishes messageStart, indexed content-block deltas/stops, messageStop and metadata. Its documented contentBlockStart is for tool use. Do not require a text block to begin with a tool-use start event. A normal text projection can have a delta followed by a block stop without one.
| Observation | Source projection | Target projection | Application meaning in this profile |
|---|---|---|---|
| Text arrives | response.output_text.delta | contentBlockDelta.delta.text | Append to this attempt's private candidate only. |
| Text/block closes | response.output_text.done | contentBlockStop | Close that indexed part/block. Do not release the answer. |
| Native normal ending | response.completed | messageStop.stopReason: end_turn | Eligible for the application's content check, not automatically accepted. |
| Limited or exceptional ending | response.incomplete, response.failed, error | Non-accepted stop reason or stream exception | Preserve the reason; withhold candidate text. |
| Local Stop | Application action | Application action | Revoke this attempt's release eligibility immediately. |
| Transport ends | Reader/transport observation | Reader/transport observation | EOF alone does not establish a native normal ending. |
The target's stop-reason type includes max_tokens, stop_sequence, tool_use and filtering/intervention outcomes as well as end_turn. For this profile, only end_turn is a normal-ending candidate. Holding stop_sequence is a local restriction because this request does not use stop sequences, not a claim that the API treats it as an error.
3. Separate four observations in the attempt record
Store an application attempt identifier before reading the first event. It is not the provider response ID. Attach it to every decoded event, local Stop and delivery callback. A retry receives a new identifier and a fresh candidate buffer. Events from the old reader cannot advance the new attempt.
Keep four independent observations: candidate assembly, native outcome, local release eligibility and content acceptance. A fifth, transport/accounting completion, is useful for diagnostics but must not masquerade as content approval. A completed provider response can contain an answer the application rejects. A stopped attempt can later yield a normal provider ending without becoming releasable again.
For the target, metadata carries usage and metrics. That is not another answer-completion event. The chosen release contract should state whether it waits for clean stream drainage before committing the answer. Here it does: a stream exception observed after a normal ending but before clean drainage holds release. Missing expected metadata is recorded as an accounting anomaly for review, not filled with invented zero usage. This conservative drain requirement is application policy, not an AWS guarantee about late exceptions.
The ConverseStream output union includes model-stream, throttling, validation, service-unavailable and internal-server exceptions. Retain the native exception category for recovery decisions. Do not turn all of them into an empty successful answer. A new retry may be appropriate under the application's existing retry policy, but it must not inherit half an answer.
Text closure precedes the release gate. Native completion, local eligibility and content review supply different evidence. Stop vetoes release rather than forming a required stage in a successful answer.
4. Work a positive source and target trace
The fictional task is to answer from one supplied statement: “The public desk opens Wednesday from 09:00 to 17:00 UTC. It is closed Thursday.” The application accepts an answer only if it preserves both days, Wednesday's 09:00 to 17:00 opening hours and the UTC time zone, without inventing other opening hours. Equivalent phrasing is allowed. A normal terminal event alone cannot establish that rule.
The companion trace pack supplies decoded event projections. In source case S1, two deltas assemble the two sentences; the text-done projection agrees with the assembled text; the completed-response projection contains that same answer. A clean drain follows. In target case T1, two text deltas at block index zero assemble the same answer, then block stop, end_turn, metadata and clean drain follow. There is deliberately no fabricated text contentBlockStart.
Both cases are eligible for the semantic check. Their expected result is one accepted answer in the visible response and one matching conversation-history entry. Fixed progress is removed. The private buffer is not a second history message. Usage observations remain separate and provider-native; this exercise does not equate token counts or costs.
Require agreement between the source assembled candidate and its final response projection. Do not append the final text to the deltas and duplicate the answer. On the target, retain block indexes and deterministic assembly order. This specimen admits exactly one text block; an unexpected second block is a profile change requiring review, not proof that Converse cannot produce multiple blocks.
If the normal source handler works only because it reads the final response object, the target needs a deliberate assembly path. Conversely, an SDK's convenient accumulated text property should not hide how interruption, local Stop or block identities are handled. The actual client is untested here, so the release review must inspect its reader and callbacks rather than assuming these projections cover its wire decoding.
5. Make the negative traces change the outcome
Consider T2: the first sentence arrives, the block closes, then modelStreamErrorException is observed. A block-done success handler would publish incomplete opening-hours information. The expected result is an interrupted status with no candidate answer in UI, history, export or clipboard output. Diagnostic storage may retain the partial under its separate access and retention policy; it is not an accepted conversation turn.
T3 ends at max_tokens. Even if the partial reads fluently, this profile withholds it and records the limit outcome. T4 reaches EOF without messageStop. It is interrupted or unknown, never inferred normal. S2 likewise closes source text and then fails. These distinguish part completion from response completion on both sides of the migration.
T5 demonstrates a different failure: a normal end_turn and clean drain accompany “The desk opens Thursday.” Native completion is valid evidence about the lifecycle, but the answer fails the supplied content rule. Hold it as rejected content. Without this case, a test pack could prove event handling while teaching the application to accept the wrong answer.
T6 supplies an unexpected tool-use start. The target's delta union supports forms beyond text. This article's profile does not. Hold the attempt and surface a profile mismatch; do not stringify arbitrary blocks into text, ignore them or dispatch a tool. A proposed tool's authority is governed by the independent production agent architecture, not the stream adapter.
6. Stop release locally without inventing a remote receipt
In T7, Stop is recorded after a text delta but before a late end_turn. The local attempt becomes ineligible immediately. Late events may inform diagnostics, but cannot restore its eligibility or create a history turn. If the user retries, T8 verifies that a late delta from the old attempt cannot append to the new attempt's candidate.
OpenAI's background-mode guide distinguishes background cancellation from terminating the connection for a synchronous response. This comparison uses the synchronous path. Record the local abort request and what the client actually observes. Do not present a local button click as a measured provider shutdown time, a charge reversal or a provider cancellation receipt. No such behavior was executed here.
On the target, closing the application's reader and disabling release are local controls. This document does not establish how an unspecified deployed SDK propagates aborts or what remote work has already occurred. A UI can truthfully say that it stopped accepting this answer without claiming that all remote computation ended at that instant. Once output has already been accepted, Stop cannot retroactively make that observed release disappear. Define that UI ordering explicitly.
This is text-output cancellation only. If the real workflow dispatches writes, use cancellation and pending writes for in-flight effects, reconciliation and reversal authority. A clean text-stream cancellation test supplies no evidence about those effects.
7. Turn the worksheet into an integration test, not a self-certification
The companion contains filled decisions plus a blank record. Replace its exact client, event observations and destinations with the application's own evidence. Feed supplied projections at the decoded-event boundary in a test harness only if that is the layer under review. Such a test does not validate HTTP/SSE parsing, AWS EventStream framing, SDK decoding, credentials or provider behavior. Those require different evidence.
For each trace, capture the visible response, conversation-history record and any export or copy representation. Assert expected absence as carefully as expected text. For Stop, hold the reader at a deterministic boundary, record Stop, deliver the late terminal, then inspect all destinations. For retry isolation, alternate old and new attempt events and show that the accepted answer belongs solely to the new attempt.
Useful failure signals are a history entry created at block stop, accepted text after Stop, doubled source text, an EOF marked successful, tool output appearing as prose, or an answer accepted solely because its terminal was normal. Useful passing evidence names the exact reader revision, release rule and trace, with destination readback. Counting synthetic examples is not that evidence.
There is no adapter or checker here that merely recognizes the supplied literals and declares migration success. The pack is a review contract. Retain the existing integration if it cannot preserve these distinctions; revise the application adapter if the supported profile is sufficient; choose a different evaluated route if the product needs excluded semantics. The broader fallback decision remains with AI provider continuity.
8. Take one bounded next step
Ask the integration owner to fill the blank record for the deployed reader and replay S1, S2 and T1 through T8 through the real UI acceptance path. Add representative captured traces only under separate test and data authority. Have the domain reviewer check the deployed client's native event interpretation and retain the destination observations with the integration owner. This article's supplied projections cannot establish acceptance of that implementation.
Related services
AI Product Integration & OpenAI Development Services
Embed AI capabilities into your existing products without rebuilding them. Integration architecture, latency strategy, fallback design, cost controls, and operational tooling from day one.
AI Observability, LLM Monitoring & Governance
LLMOps consulting for AI observability, LLM monitoring, evaluation and guardrails. Review production answer quality, operating failures and cost evidence.