Skip to content

[Bug]: Voice Agent aws_bedrock think provider returns no output and no error for reasoning models #759

Description

@dg-edcharbeneau

Summary

Reasoning models on the aws_bedrock think provider produce no reply, no AgentThinking, and no Error/Warning — the turn is silently dropped.

What happened?

Actual: The agent accepts the user turn and then goes silent. No AgentThinking, no ConversationText, no Warning, no Error. The socket stays open until the client times out. There is nothing in the message stream to debug against.

Expected: Either the agent speaks the model's text output, or it emits a Warning/Error naming the cause.

Silence with no diagnostic is the core of this report. Even if reasoning output stays unsupported, the failure needs to be visible to the client.

Scope

Silent on aws_bedrock:

  • openai.gpt-oss-120b-1:0
  • openai.gpt-oss-20b-1:0
  • us.deepseek.r1-v1:0

Non-reasoning models — same provider, same credentials, same region — work correctly:

  • us.amazon.nova-micro-v1:0 → replies
  • us.meta.llama3-3-70b-instruct-v1:0 → replies

The model is not at fault

Calling Bedrock directly with the same IAM credentials, converse_stream returns text normally. Reasoning models emit a reasoningContent block before the text block:

maxTokens=20   reasoning=74  ch   text=0   ch   stopReason=max_tokens
maxTokens=50   reasoning=182 ch   text=0   ch   stopReason=max_tokens
maxTokens=100  reasoning=124 ch   text=6   ch   stopReason=end_turn  -> 'Paris.'
maxTokens=400  reasoning=99  ch   text=6   ch   stopReason=end_turn  -> 'Paris.'

Nova, by contrast, produces reasoning=0 ch and full text at every budget.

The decisive control: the same model family works through Deepgram's own Groq provider.

openai/gpt-oss-20b   via groq         -> 'Paris.'   OK
openai.gpt-oss-120b  via aws_bedrock  -> silence    FAIL

Same model family, two provider paths, opposite outcomes. That isolates the defect to the aws_bedrock provider's handling of the response — not to Bedrock, not to the model, not to credentials.

Likely cause

The provider appears not to handle reasoningContent blocks in the Converse stream. Either they aren't recognized and the text block is never surfaced, or an output-token cap is spent on reasoning before any text is produced — the maxTokens=20/50 rows above show exactly that shape, stopReason=max_tokens with zero text. I can't distinguish these from outside.

There is no configuration workaround

reasoning_mode is not accepted on this provider. Sending it rejects the entire Settings message:

{"type": "Error", "code": "UNPARSABLE_CLIENT_MESSAGE"}

That matches the generated types — reasoning_mode is present on ThinkSettingsV1Provider_OpenAi and ThinkSettingsV1Provider_Groq and absent from ThinkSettingsV1Provider_AwsBedrock:

class ThinkSettingsV1Provider_AwsBedrock(UncheckedBaseModel):
    type: typing.Literal["aws_bedrock"] = "aws_bedrock"
    model: AwsBedrockThinkProviderModel
    temperature: typing.Optional[float] = None
    credentials: typing.Optional[AwsBedrockThinkProviderCredentials] = None

Since model accepts any string for BYO, nothing stops a user from selecting a reasoning model.

Requested fix, in priority order

  1. Emit a diagnostic. A Warning such as THINK_NO_TEXT_CONTENT would turn an undebuggable silence into a one-line fix. Highest-value change by far.
  2. Handle reasoningContent — drop those blocks and speak the text blocks.
  3. Support reasoning_mode on aws_bedrock, matching open_ai and groq.

Steps to reproduce

  1. pip install "websockets>=14"
  2. export DEEPGRAM_API_KEY=... and export AWS_REGION=us-east-2 AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=...
  3. python bedrock-repro.py (full script below — ~150 lines, no microphone or browser)
  4. Observe the ISSUE 1 block: the two reasoning models report SILENT - no reply, no error while the nova-micro and groq controls reply.

Requires Bedrock model access for us.amazon.nova-micro-v1:0, openai.gpt-oss-120b-1:0 and us.deepseek.r1-v1:0 in the target region. Runs in about 90 seconds.

Observed output:

ISSUE 1 - reasoning models return nothing on aws_bedrock
  gpt-oss-120b   / aws_bedrock                               FAILED SILENT - no reply, no error
  deepseek-r1    / aws_bedrock                               FAILED SILENT - no reply, no error
  nova-micro     / aws_bedrock  (control, non-reasoning)     REPLIED 'The capital of France is Paris.'
  gpt-oss-20b    / groq         (control, same model family) REPLIED 'Paris.'
  gpt-oss-120b   / aws_bedrock  + reasoning_mode=none        FAILED UNPARSABLE_CLIENT_MESSAGE

Minimal code sample

# Silent: any Bedrock reasoning model. Swap in us.amazon.nova-micro-v1:0 to see it work.
settings = {
    "type": "Settings",
    "audio": {
        "input": {"encoding": "linear16", "sample_rate": 24000},
        "output": {"encoding": "linear16", "sample_rate": 24000},
    },
    "agent": {
        "listen": {"provider": {"type": "deepgram", "model": "flux-general-en", "version": "v2"}},
        "think": {
            "provider": {
                "type": "aws_bedrock",
                "model": "openai.gpt-oss-120b-1:0",
                "credentials": {
                    "type": "iam",
                    "region": "us-east-2",
                    "access_key_id": "...",
                    "secret_access_key": "...",
                },
            },
            "endpoint": {"url": "https://bedrock-runtime.us-east-2.amazonaws.com/"},
            "prompt": "You are a helpful assistant. Keep responses brief.",
        },
        "speak": {"provider": {"type": "deepgram", "model": "flux-alexis-en"}},
    },
}
# Send Settings, wait for SettingsApplied, then:
#   {"type": "InjectUserMessage", "content": "What is the capital of France?"}
# -> nothing is ever received back. No ConversationText, no Error, no Warning.
Full self-contained reproduction script (bedrock-repro.py)
"""Self-contained reproduction for two AWS Bedrock think-provider bugs.

Connects to the Voice Agent API, applies Settings, injects a user message, and
reports whether the agent produced a reply. No microphone or browser needed.

Requires: pip install "websockets>=14"   (the only dependency; no Deepgram SDK)
Env:      DEEPGRAM_API_KEY, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
          AWS_REGION (optional, defaults to us-east-2)
          Read from the environment, or from a .env beside this script if
          python-dotenv happens to be installed.

Usage:    python bedrock-repro.py
"""

import asyncio
import json
import os
import sys

try:
    # websockets >= 14; the legacy client takes extra_headers, not additional_headers
    from websockets.asyncio.client import connect
    from websockets.exceptions import ConnectionClosed, WebSocketException
except ImportError:
    raise SystemExit(
        'websockets>=14 is required.\n  pip install --upgrade "websockets>=14"'
    ) from None

try:  # optional convenience; the script runs fine without it
    from dotenv import load_dotenv

    # Scoped to a .env beside this script - the default search walks parent
    # directories, which would pick up an unrelated .env on someone else's machine.
    load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env"))
except ImportError:
    pass

REQUIRED = ("DEEPGRAM_API_KEY", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY")
missing = [name for name in REQUIRED if not os.environ.get(name)]
if missing:
    sys.exit(
        "Missing required environment variable(s): "
        + ", ".join(missing)
        + "\n\n  export DEEPGRAM_API_KEY=...\n"
        "  export AWS_REGION=us-east-2 AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=...\n"
        "  python bedrock-repro.py"
    )

KEY = os.environ["DEEPGRAM_API_KEY"]
REGION = os.environ.get("AWS_REGION", "us-east-2")
URL = "wss://agent.deepgram.com/v1/agent/converse"
GREETING = "Hello! I'm a Deepgram voice agent. What would you like to talk about?"

CREDENTIALS = {
    "type": "iam",
    "region": REGION,
    "access_key_id": os.environ["AWS_ACCESS_KEY_ID"],
    "secret_access_key": os.environ["AWS_SECRET_ACCESS_KEY"],
}


def settings(model, *, greeting, provider=None, reasoning_mode=None):
    if provider is None:
        provider = {"type": "aws_bedrock", "model": model, "credentials": CREDENTIALS}
        think = {"provider": provider, "endpoint": {"url": f"https://bedrock-runtime.{REGION}.amazonaws.com/"}}
    else:
        think = {"provider": provider}
    if reasoning_mode:
        think["provider"]["reasoning_mode"] = reasoning_mode
    think["prompt"] = "You are a helpful assistant. Keep responses brief."

    agent = {
        "listen": {"provider": {"type": "deepgram", "model": "flux-general-en", "version": "v2"}},
        "think": think,
        "speak": {"provider": {"type": "deepgram", "model": "flux-alexis-en"}},
    }
    if greeting:
        agent["greeting"] = GREETING
    return {
        "type": "Settings",
        "audio": {
            "input": {"encoding": "linear16", "sample_rate": 24000},
            "output": {"encoding": "linear16", "sample_rate": 24000},
        },
        "agent": agent,
    }


async def run_case(label, cfg):
    replies, codes = [], []
    expected_greeting = "greeting" in cfg["agent"]
    try:
        async with connect(URL, additional_headers={"Authorization": f"Token {KEY}"}) as ws:
            await ws.send(json.dumps(cfg))

            async def read():
                async for raw in ws:
                    if isinstance(raw, bytes):
                        continue
                    m = json.loads(raw)
                    if m.get("type") == "SettingsApplied":
                        await asyncio.sleep(2)
                        await ws.send(
                            json.dumps({"type": "InjectUserMessage", "content": "What is the capital of France?"})
                        )
                    elif m.get("type") == "ConversationText" and m.get("role") == "assistant":
                        if expected_greeting and m["content"] == GREETING:
                            continue
                        replies.append(m["content"])
                        return
                    elif m.get("type") in ("Error", "Warning"):
                        code = m.get("code")
                        if code != "CLIENT_MESSAGE_TIMEOUT" and code not in codes:
                            codes.append(code)

            await asyncio.wait_for(read(), timeout=25)
    except (asyncio.TimeoutError, ConnectionClosed):
        pass  # expected for the failing cases: the server goes silent or hangs up
    except (WebSocketException, OSError) as exc:
        # handshake rejected (bad key), DNS, TLS - report it and keep the run going
        codes.append(f"{type(exc).__name__}: {exc}")

    verdict = f"REPLIED {replies[0]!r}" if replies else ("FAILED " + (", ".join(codes) or "SILENT - no reply, no error"))
    print(f"  {label:<58} {verdict}")


async def main():
    print("\nISSUE 1 - reasoning models return nothing on aws_bedrock")
    await run_case("gpt-oss-120b   / aws_bedrock", settings("openai.gpt-oss-120b-1:0", greeting=False))
    await run_case("deepseek-r1    / aws_bedrock", settings("us.deepseek.r1-v1:0", greeting=False))
    await run_case("nova-micro     / aws_bedrock  (control, non-reasoning)", settings("us.amazon.nova-micro-v1:0", greeting=False))
    await run_case(
        "gpt-oss-20b    / groq         (control, same model family)",
        settings(None, greeting=False, provider={"type": "groq", "model": "openai/gpt-oss-20b"}),
    )
    await run_case(
        "gpt-oss-120b   / aws_bedrock  + reasoning_mode=none",
        settings("openai.gpt-oss-120b-1:0", greeting=False, reasoning_mode="none"),
    )

    print("\nISSUE 2 - greeting makes the first Bedrock request assistant-first")
    await run_case("nova-micro     / aws_bedrock  greeting ON", settings("us.amazon.nova-micro-v1:0", greeting=True))
    await run_case("nova-micro     / aws_bedrock  greeting OFF", settings("us.amazon.nova-micro-v1:0", greeting=False))
    print()


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        pass

Logs / traceback

No error is produced. That is the bug.

Full client-side message stream after InjectUserMessage, until the 25s client
timeout: (empty)

Environment

Field Value
Transport WebSocket
API endpoint / path /v1/agent/converse
Model(s) used openai.gpt-oss-120b-1:0, openai.gpt-oss-20b-1:0, us.deepseek.r1-v1:0 (silent); us.amazon.nova-micro-v1:0, us.meta.llama3-3-70b-instruct-v1:0 (work)
How often? Always
Is this a regression? No — not known to have ever worked
SDK version 7.6.0 (the repro itself uses no SDK — raw WebSocket)
Python version 3.13.14
Install method pip
OS macOS (Apple Silicon)

The attached repro deliberately does not use the Deepgram SDK — it talks to wss://agent.deepgram.com/v1/agent/converse directly with websockets, so the behavior can't be attributed to SDK version or client-side serialization. deepgram-sdk 7.6.0 is listed because it is the environment the bug was found in and the source of the generated types quoted above; it is current on PyPI at time of filing. AWS region us-east-2, IAM long-lived key, credentials type iam.


A note on venue: this is a server-side Voice Agent API defect rather than a Python SDK defect — the reproduction uses no SDK code. I'm filing here because this is the reachable public tracker for the Voice Agent surface. Please transfer or redirect if there's a better home for API-side bugs.

Provenance: the Bedrock rows in the output above are from a run on 2026-08-09 with working IAM credentials. The groq control and the reasoning_mode=noneUNPARSABLE_CLIENT_MESSAGE result were re-confirmed on 2026-08-11.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions