{"record":{"id":"29d2b6016f692dea","repo":"langchain-ai/langchain","slug":"expected-str-basemessage-list-basemessage-or-t-29d2b6","errorCode":null,"errorMessage":"Expected str, BaseMessage, list[BaseMessage], or tuple[BaseMessage]. Got {output_val}.","messagePattern":"Expected str, BaseMessage, list\\[BaseMessage\\], or tuple\\[BaseMessage\\]\\. Got (.+?)\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/runnables/history.py","lineNumber":519,"sourceCode":"            # If you are wrapping a chat model directly\n            # The output is actually this weird generations object\n            if key not in output_val and \"generations\" in output_val:\n                output_val = output_val[\"generations\"][0][0][\"message\"]\n            else:\n                output_val = output_val[key]\n\n        if isinstance(output_val, str):\n            return [AIMessage(content=output_val)]\n        # If value is a single message, convert to a list\n        if isinstance(output_val, BaseMessage):\n            return [output_val]\n        if isinstance(output_val, (list, tuple)):\n            return list(output_val)\n        msg = (\n            f\"Expected str, BaseMessage, list[BaseMessage], or tuple[BaseMessage]. \"\n            f\"Got {output_val}.\"\n        )\n        raise ValueError(msg)\n\n    def _enter_history(self, value: Any, config: RunnableConfig) -> list[BaseMessage]:\n        hist: BaseChatMessageHistory = config[\"configurable\"][\"message_history\"]\n        messages = hist.messages.copy()\n\n        if not self.history_messages_key:\n            # return all messages\n            input_val = (\n                value if not self.input_messages_key else value[self.input_messages_key]\n            )\n            messages += self._get_input_messages(input_val)\n        return messages\n\n    async def _aenter_history(\n        self, value: dict[str, Any], config: RunnableConfig\n    ) -> list[BaseMessage]:\n        hist: BaseChatMessageHistory = config[\"configurable\"][\"message_history\"]\n        messages = (await hist.aget_messages()).copy()","sourceCodeStart":501,"sourceCodeEnd":537,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/runnables/history.py#L501-L537","documentation":"The mirror of the input check for outputs: _get_output_messages must turn the wrapped chain's output into messages. Dicts are handled by plucking output_messages_key (or the single key, or \"output\"), but after that the value must be str, BaseMessage, or list/tuple. Anything else raises this ValueError.","triggerScenarios":"A wrapped chain whose output dict value at the chosen key is a non-message type (e.g. an int or nested dict), or an output that is not str/BaseMessage/list at all and not a dict; no output_messages_key configured.","commonSituations":"Structured-output chains returning nested JSON under a key; multi-key output dicts where the message lives under a key other than the single key or \"output\"; retriever-style outputs of custom types.","solutions":["Pass output_messages_key pointing at the key that holds the str/BaseMessage/list[BaseMessage], e.g. output_messages_key=\"answer\"","Add a final step to the wrapped chain that maps its output to a message (e.g. itemgetter(\"answer\") or a small RunnableLambda)","Ensure the value at that key is a string or message objects, not arbitrary JSON"],"exampleFix":"# before\nwrapped = RunnableWithMessageHistory(chain, get_history)\n# chain outputs {\"answer\": \"...\", \"citations\": [...]} -> ValueError or wrong key\n# after\nwrapped = RunnableWithMessageHistory(\n    chain, get_history, output_messages_key=\"answer\"\n)","handlingStrategy":"type-guard","validationCode":"# dry-run the inner chain once and inspect output shape\nsample = chain.invoke(sample_input)\nif isinstance(sample, dict):\n    assert len(sample) == 1 or \"output\" in sample or output_messages_key, sample.keys()","typeGuard":"from langchain_core.messages import BaseMessage\n\ndef is_message_output(v: object) -> bool:\n    return isinstance(v, (str, BaseMessage, list, tuple)) or isinstance(v, dict)","tryCatchPattern":"try:\n    wrapped.invoke(payload, cfg)\nexcept ValueError as e:\n    if \"Expected str, BaseMessage\" in str(e) and \"output_val\" in str(e):\n        wrapped2 = RunnableWithMessageHistory(\n            chain, get_history, output_messages_key=\"answer\"\n        )","preventionTips":["Set output_messages_key explicitly for structured-output chains","End the wrapped chain with a step that emits str or list[BaseMessage]"],"tags":["runnable","message-history","output-validation","valueerror"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}