#7336·mem0

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

Author: BlueX888Created Sep 15, 2026Updated Sep 16, 2026
Labelssdk-python

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 pathamazon.*nova* models take the converse branch (line 402 / line 622) and parse content[0].text; only non-Nova amazon.* is affected.
  • Transport / response decodingresponse_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 branchesmeta, 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.