Skip to content

[Bug] AWS Bedrock: legacy amazon (Titan) responses always parse to '' — _parse_response reads a 'completion' field Titan never returns #7336

Open

Description

@BlueX888

Component

Python SDK

Summary

AWSBedrockLLM._parse_response returns "" for every legacy Amazon (Titan) text response, so generate_response() silently yields an empty string and Memory.add() stores nothing.

For provider == "amazon" and a model id that does not contain nova, _generate_standard() sends a Titan-shaped request body ({"inputText": ..., "textGenerationConfig": {...}}, mem0/llms/aws_bedrock.py:291-296) through invoke_model, but the paired parse branch reads response_json.get("completion", ""). Titan's InvokeModel response has no top-level completion key, so the branch hits its default and returns "" for a perfectly valid response. Every sibling branch reads its own provider's field (meta: generation, mistral: outputs[0].text, cohere: generations[0].text, ai21: completions[0].data.text, anthropic: content[0].text), so only the legacy amazon branch is unmapped to its schema.

Steps to Reproduce

/tmp/repro/mem0_titan_parse.py — mock boto3 client, feed a response shaped exactly as AWS documents for Titan Text, and call generate_response():

import io, json
from unittest.mock import MagicMock, patch
from mem0.configs.llms.aws_bedrock import AWSBedrockConfig
from mem0.llms.aws_bedrock import AWSBedrockLLM

runtime, control = MagicMock(), MagicMock()
control.list_foundation_models.return_value = {"modelSummaries": []}
with patch("mem0.llms.aws_bedrock.boto3") as b3:
    b3.client.side_effect = lambda s, **k: runtime if s == "bedrock-runtime" else control
    # AWS docs: Titan Text InvokeModel response = inputTextTokenCount + results[].outputText
    titan_body = {"inputTextTokenCount": 3,
                  "results": [{"tokenCount": 5, "outputText": "Hi from Titan",
                               "completionReason": "FINISH"}]}
    runtime.invoke_model.return_value = {"body": io.BytesIO(json.dumps(titan_body).encode())}
    llm = AWSBedrockLLM(AWSBedrockConfig(model="amazon.titan-text-express-v1"))
    out = llm.generate_response([{"role": "system", "content": "s"},
                                 {"role": "user", "content": "Hello"}])
print("request body :", runtime.invoke_model.call_args[1]["body"])
print("response got :", repr(out), "(expected 'Hi from Titan')")

No AWS credentials or network call are involved; the mock returns the documented response shape.

Expected Behavior

generate_response() should return "Hi from Titan" (Titan's results[0].outputText), as the sent body is Titan's request schema.

Concrete basis: AWS Bedrock "Amazon Titan Text models" doc (docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html), Titan Text InvokeModel response:

{ "inputTextTokenCount": int,
  "results": [{ "tokenCount": int, "outputText": "...", "completionReason": "string" }] }

outputText is documented as "The text in the response"; there is no top-level completion field anywhere in the Titan request or response, and the doc's own sample reads result['outputText']. The module's request branch at mem0/llms/aws_bedrock.py:291-296 emits exactly Titan's request schema, so its paired response schema is Titan's. The repo's own generic fallback parser (line 419+) also reads text/generation-style fields, and every sibling provider branch reads its own field — results[0].outputText is the field this branch should read.

Actual Behavior

Verifier 1:

extract_provider -> amazon
sent body      -> {"inputText": "\n\nHuman: \n\nSystem: sys\n\nUser: Hello\n\nAssistant:", "textGenerationConfig": {"maxTokenCount": 2000, "temperature": 0.1}}
provider       -> amazon
parsed         -> ''
EXPECTED       -> 'Hi from Titan'
_parse_response-> ''
completion-key -> 'x'

Verifier 2, across Titan ids:

titan-express  provider=amazon   -> ''
titan-lite     provider=amazon   -> ''
titan-premier  provider=amazon   -> ''
(after local fix: -> 'Hi from Titan' / 'Lite out' / 'Premier out')

Re-run on c7ee362aff94a369af70f13f2b4f853f6793ff4c (this report) — same result:

request body : {"inputText": "\n\nHuman: \n\nSystem: s\n\nUser: Hello\n\nAssistant:", "textGenerationConfig": {"maxTokenCount": 2000, "temperature": 0.1}}
response got : '' (expected 'Hi from Titan')

Downstream effect: Memory.add() calls llm.generate_response(...) (mem0/memory/main.py:956); an empty string makes extract_json yield nothing and no memories are stored, with no error raised.

Environment

  • mem0 version: 2.0.20 (pyproject.toml), commit c7ee362aff94a369af70f13f2b4f853f6793ff4c (also current main)
  • Python 3.12.14
  • macOS, Darwin 25.6.0
  • Trigger model: amazon.titan-text-express-v1 (any amazon.* id without nova)

Root Cause

mem0/llms/aws_bedrock.py:409-410 — the legacy Amazon branch of _parse_response reads a field Titan does not return:

else:
    # Legacy Amazon models
    return response_json.get("completion", "")

The request built for the same models at mem0/llms/aws_bedrock.py:291-296 is Titan's schema (inputText / textGenerationConfig), whose response carries the text under results[0].outputText. The lookup therefore always misses and the default "" is returned. The "nova" in model branch above (line 402) is unaffected — it parses content[0].text.

How You Verified This

What I Ran

/tmp/repro/mem0_titan_parse.py (above) against a checkout of c7ee362aff94a369af70f13f2b4f853f6793ff4c, Python 3.12.14, with boto3 patched so the Bedrock runtime client returns the response body AWS documents for Titan Text. Separately reproduced by a second verifier across amazon.titan-text-express-v1, amazon.titan-text-lite-v1 and amazon.titan-text-premier-v1.

What I Saw

The verbatim outputs in Actual Behavior: _parse_response returns '' while the request body sent is Titan's schema. After reading results[0].outputText in the same branch locally, the same runs returned 'Hi from Titan' / 'Lite out' / 'Premier out'.

Why This Is a Bug

The parser must read the response shape its own request shape elicits. AWS documents Titan Text's InvokeModel response as {"inputTextTokenCount": int, "results": [{"tokenCount": int, "outputText": "...", "completionReason": "..."}]} with no top-level completion key, and the module's request branch at line 291-296 emits exactly that model family's request schema. Every other provider branch in _parse_response reads its own response field; the legacy amazon branch is the only one reading a field its provider never returns, so it can never return a non-empty string.

What I Ruled Out

  • Nova path — amazon.*nova* models take the converse branch (line 402 / line 622) and parse content[0].text; only non-Nova amazon.* is affected.
  • Transport / response decoding — response_body is valid JSON and parses fine; the failure is the field lookup, not json.loads.
  • Credentials / region / API error — reproduced with a mocked client and no network; the request body is a valid Titan body.
  • Other provider branches — meta, mistral, cohere, ai21 and anthropic all read their documented fields and are not involved.
  • Tool-calling path — the repro uses the tools-less path (_generate_standard, line 562) that Memory.add() takes; this is not related to the tool-call issues filed separately.

Related

Searched existing issues/PRs for titan, bedrock, outputText, _parse_response, titan-text: no existing report for this parse failure. Closest open Bedrock LLM issues (different code paths, not duplicates): #6548 (parse-failure sentinel string), #6556 (tool responses omit content), #6563 (malformed Converse tool requests for amazon/cohere), #6368 (Anthropic reasoningContent ordering), #5913 (closed; inference-profile ARN provider extraction).

Happy to open a PR reading results[0].outputText in the legacy amazon branch, keeping the completion lookup as a last-resort fallback for any other legacy Amazon shape — say the word if you'd like it.

AI Assistance

AI helped find and write this up; the reproduction above was run and confirmed by me (and independently by a second verifier) before filing.

Activity

  1. debojyoti-dev commented on Sep 15, 2026

    @debojyoti-dev

    I’d like to take this issue up.

    I’ll investigate the AWSBedrockLLM request/response handling for legacy Amazon Titan models, specifically the mismatch between the Titan InvokeModel response schema and the amazon branch in _parse_response.

    The reported root cause appears to be isolated to the legacy Amazon path: _generate_standard() constructs the Titan request using inputText and textGenerationConfig, while _parse_response() currently expects a top-level completion field. Titan instead returns generated text under results[0].outputText, causing valid responses to be converted into an empty string.

    I’ll reproduce the issue with mocked Bedrock responses for the affected Titan model variants, then implement the smallest compatible fix in _parse_response. I’ll also add regression coverage to verify that generate_response() correctly extracts results[0].outputText, while ensuring the existing Nova/Converse path and other Bedrock provider parsers remain unaffected.

    I’ll run the relevant AWS Bedrock tests and the new regression case before opening a PR.

    @BlueX888

  2. O96a commented on Sep 15, 2026

    @O96a

    Confirmed — the amazon branch reads completion, which is the Anthropic-style field and Titan never returns it. Titan text models put the text in results[0].outputText in the InvokeModel body, so the non-nova path should parse that:

    if provider == "amazon" and not is_nova:
        return response["results"][0]["outputText"]

    And the silent '' fallback is what let this hide — an unexpected body shape should raise, not parse to empty, or every Titan call stores nothing and nobody notices.

  3. Ritiky23 commented on Sep 15, 2026

    @Ritiky23

    Hi maintainers! I've opened PR #7341 addressing this issue with full unit test coverage for Titan text response parsing and fallbacks.

    Could you please review and label this issue accepted so the PR is queued and reopened? Thanks!

  4. aniruddhaadak80 commented on Sep 16, 2026

    @aniruddhaadak80
    Contributor

    Thanks for the detailed repro. I have a minimal fix ready on branch fix/bedrock-titan-outputtext-7336 (reads Titan results[0].outputText with completion fallback, plus regression tests). Could a maintainer please add the bug and accepted labels per CONTRIBUTING PR Gate so I can open the PR with Closes #7336? No live AWS used, all mocked.

  5. aniruddhaadak80 commented on Sep 18, 2026

    @aniruddhaadak80
    Contributor

    Update: the competing #7341 just closed unmerged, so the path is clear. My fix branch for the Bedrock Titan outputText issue is ready - requesting the accepted label so it can go through the PR gate. CLA already signed.

  6. Ritiky23 commented on Sep 20, 2026

    @Ritiky23

    Just to clarify - PR #7341 was automatically closed by the pr-gate bot solely because this issue is awaiting the accepted triage label (per Mem0's CONTRIBUTING policy), not due to review rejection.

    PR #7341 contains the complete fix for reading results[0].outputText with legacy fallback and comprehensive unit tests, with CLA already signed.

    Could maintainers (@kartik-mem0 @whysosaket @Himanshu-Sangshetti ) please add the accepted label to this issue so #7341 automatically reopens for review? Thank you!

  7. blazingretinol commented on Sep 21, 2026

    @blazingretinol
    Contributor

    Confirmed. The legacy Amazon Titan path reads a 'completion' field that Titan responses never include, so generate_response always returns an empty string. Affects anyone using Titan models through the Bedrock provider.

    P1, fix PR under review.

  8. Madin-H23 commented on Oct 20, 2026

    @Madin-H23

    @blazingretinol — you confirmed this on 2026-09-21 as "P1, fix PR under review". One correction first: there is no pull request under review. All three earlier attempts — #7341, #7343 and #7389 — were closed by the pr-gate bot within seconds of being opened, because this issue never received the accepted label. None of them got a review comment, so the issue has been sitting without a fix for 35 days.

    A pull request is now open: #7691. It applies the fix @O96a described here (read results[0].outputText, keep completion as a fallback) and adds the tests:

    • the Titan response is returned through _parse_response and through generate_response() with a mocked invoke_model — the path the issue reports;
    • the first result wins, a response that still carries completion keeps returning it, a response with neither field still yields "", an empty results list is handled, and the Nova branch still reads content[0].text.

    I ran those tests against unmodified main first: the three Titan cases fail there (the response parses as ""), and with the fix the whole tests/llms/test_aws_bedrock.py file passes (63 tests) with ruff check clean on the locked 0.16.0.

    @O96a — if the shape is still what you had in mind, a thumbs-up from you would help; the implementation follows the snippet you posted.

    @blazingretinol, could you add the accepted label to this issue so #7691 reopens? The pull request reopens by itself once the label is there, and the reviewer then has a concrete P1 fix in front of them. I can also close mine in favour of #7341 or #7343 if you would rather revive one of those — the fix is the same in all of them.

    One administrative note: I still need to sign this repository's contributor CLA before the pull request can merge. I will take care of that on my side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1-highBlocks significant use case, high demandbugSomething isn't workingsdk-pythonPython SDK specific

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions