[Bug] AWS Bedrock: legacy amazon (Titan) responses always parse to '' — _parse_response reads a 'completion' field Titan never returns
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), commitc7ee362aff94a369af70f13f2b4f853f6793ff4c(also currentmain) - Python 3.12.14
- macOS, Darwin 25.6.0
- Trigger model:
amazon.titan-text-express-v1(anyamazon.*id withoutnova)
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 theconversebranch (line 402 / line 622) and parsecontent[0].text; only non-Novaamazon.*is affected. - Transport / response decoding —
response_bodyis valid JSON and parses fine; the failure is the field lookup, notjson.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,ai21andanthropicall read their documented fields and are not involved. - Tool-calling path — the repro uses the tools-less path (
_generate_standard, line 562) thatMemory.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.
Source: mem0ai/mem0