Bug Description
Thanks for the retry logic in LLMStream and the 4xx rule in APIStatusError. The AWS plugin doesn't use that rule yet, so Bedrock's permanent 4xx errors get retried like connection errors.
LLMStream._run in the AWS plugin wraps every exception raised before the first chunk in APIConnectionError(retryable=True) (llm.py L343-L347). A ValidationException from converse_stream() therefore goes out 4 times and takes about 7 s to reach the caller, though it fails the same way every time. The APIStatusError(retryable=False) the plugin raises for a non-200 response (L327-L335) is caught by the same except and re-wrapped as retryable too.
I hit this while testing OpenAI GPT-6 on Bedrock, where a few settings carried over from gpt-oss are rejected:
| request, us-east-1, main (b4df92e) |
HTTP requests sent |
time to error |
us.openai.gpt-6-sol + additional_request_fields={"reasoning_effort": "high"} |
4 |
7.33 s |
us.openai.gpt-6-sol + max_output_tokens=200000 |
4 |
7.06 s |
us.openai.gpt-6-luna, same two requests |
4 / 4 |
7.02 s / 7.35 s |
| plain request (control) |
1 |
1.20 s, pong |
Expected Behavior
Bedrock's own errors keep their HTTP status, as they do with the anthropic plugin for the Anthropic SDK's status errors (anthropic/llm.py L341-L347). Then APIStatusError's existing rule applies: 408/429/5xx are retried and other 4xx fail on the first attempt.
Reproduction Steps
cat > repro.py <<'PY'
import asyncio, time
from livekit.agents.llm import ChatContext
from livekit.plugins import aws
async def main():
llm = aws.LLM(model="us.openai.gpt-6-sol", max_output_tokens=200000)
sent = 0
def count(**_):
nonlocal sent
sent += 1
llm._session.register("before-send.bedrock-runtime.ConverseStream", count)
ctx = ChatContext()
ctx.add_message(role="user", content="Say pong.")
t0 = time.perf_counter()
try:
await llm.chat(chat_ctx=ctx).collect()
except Exception as e:
print(f"requests={sent} {time.perf_counter() - t0:.2f}s {type(e).__name__}")
asyncio.run(main())
PY
uv run python repro.py
requests=4 7.06s APIConnectionError
Operating System
macOS 26 (not OS-specific)
Models Used
AWS Bedrock via aws.LLM (any model; GPT-6 Sol/Luna above)
Package Versions
livekit-agents==1.8.2
livekit-plugins-aws==1.8.2 # main at b4df92e
aiobotocore==3.8.0
botocore==1.43.46
python==3.13
Session/Room/Call IDs
N/A, reproduces with aws.LLM alone.
Proposed Solution
try:
response = await client.converse_stream(**self._opts)
except ClientError as e:
meta = e.response.get("ResponseMetadata", {})
raise APIStatusError(
f"aws bedrock llm: error generating content: {e}",
status_code=meta.get("HTTPStatusCode", -1),
request_id=meta.get("RequestId"),
) from e
...
except APIStatusError:
raise
except Exception as e: # unchanged
With this patch the same requests fail after 1 request (0.65-1.08 s over four runs). I have it with a hermetic test and can open a PR.
Additional Context
What changes per error code
From the ConverseStream error shapes in botocore's bedrock-runtime model, sorted by APIStatusError's rule:
| Bedrock error |
HTTP |
retried today |
retried with the patch |
| ValidationException |
400 |
yes |
no |
| AccessDeniedException |
403 |
yes |
no |
| ResourceNotFoundException |
404 |
yes |
no |
| ModelErrorException |
424 |
yes |
no |
| ModelTimeoutException |
408 |
yes |
yes |
| ThrottlingException, ModelNotReadyException |
429 |
yes |
yes |
| InternalServerException, ServiceUnavailableException |
500, 503 |
yes |
yes |
The 404 row is a judgment call: on GPT-6's launch day, 1 of 9 test calls got a transient ResourceNotFoundException: Inference Profile ARN not found. If you would rather keep 404/424 retryable, those codes can stay on the APIConnectionError path.
Bug Description
Thanks for the retry logic in
LLMStreamand the 4xx rule inAPIStatusError. The AWS plugin doesn't use that rule yet, so Bedrock's permanent 4xx errors get retried like connection errors.LLMStream._runin the AWS plugin wraps every exception raised before the first chunk inAPIConnectionError(retryable=True)(llm.py L343-L347). AValidationExceptionfromconverse_stream()therefore goes out 4 times and takes about 7 s to reach the caller, though it fails the same way every time. TheAPIStatusError(retryable=False)the plugin raises for a non-200 response (L327-L335) is caught by the sameexceptand re-wrapped as retryable too.I hit this while testing OpenAI GPT-6 on Bedrock, where a few settings carried over from gpt-oss are rejected:
us.openai.gpt-6-sol+additional_request_fields={"reasoning_effort": "high"}us.openai.gpt-6-sol+max_output_tokens=200000us.openai.gpt-6-luna, same two requestspongExpected Behavior
Bedrock's own errors keep their HTTP status, as they do with the anthropic plugin for the Anthropic SDK's status errors (anthropic/llm.py L341-L347). Then
APIStatusError's existing rule applies: 408/429/5xx are retried and other 4xx fail on the first attempt.Reproduction Steps
Operating System
macOS 26 (not OS-specific)
Models Used
AWS Bedrock via
aws.LLM(any model; GPT-6 Sol/Luna above)Package Versions
livekit-agents==1.8.2 livekit-plugins-aws==1.8.2 # main at b4df92e aiobotocore==3.8.0 botocore==1.43.46 python==3.13Session/Room/Call IDs
N/A, reproduces with
aws.LLMalone.Proposed Solution
With this patch the same requests fail after 1 request (0.65-1.08 s over four runs). I have it with a hermetic test and can open a PR.
Additional Context
What changes per error code
From the
ConverseStreamerror shapes in botocore'sbedrock-runtimemodel, sorted byAPIStatusError's rule:The 404 row is a judgment call: on GPT-6's launch day, 1 of 9 test calls got a transient
ResourceNotFoundException: Inference Profile ARN not found. If you would rather keep 404/424 retryable, those codes can stay on theAPIConnectionErrorpath.