Overuse may indicate design issues; consider polymorphism
if reasoning_content is not None and isinstance(reasoning_content, str):
1"""Base message."""23from __future__ import annotations45from typing import TYPE_CHECKING, Any, cast, overload67from pydantic import ConfigDict, Field89from langchain_core._api.deprecation import warn_deprecated10from langchain_core.load.serializable import Serializable11from langchain_core.messages import content as types12from langchain_core.utils import get_bolded_text13from langchain_core.utils._merge import merge_dicts, merge_lists14from langchain_core.utils.interactive_env import is_interactive_env1516if TYPE_CHECKING:17 from collections.abc import Sequence1819 from typing_extensions import Self2021 from langchain_core.prompts.chat import ChatPromptTemplate222324def _extract_reasoning_from_additional_kwargs(25 message: BaseMessage,26) -> types.ReasoningContentBlock | None:27 """Extract `reasoning_content` from `additional_kwargs`.2829 Handles reasoning content stored in various formats:30 - `additional_kwargs["reasoning_content"]` (string) - Ollama, DeepSeek, XAI, Groq3132 Args:33 message: The message to extract reasoning from.3435 Returns:36 A `ReasoningContentBlock` if reasoning content is found, None otherwise.37 """38 additional_kwargs = getattr(message, "additional_kwargs", {})3940 reasoning_content = additional_kwargs.get("reasoning_content")41 if reasoning_content is not None and isinstance(reasoning_content, str):42 return {"type": "reasoning", "reasoning": reasoning_content}4344 return None454647class TextAccessor(str):48 """String-like object that supports both property and method access patterns.4950 Exists to maintain backward compatibility while transitioning from method-based to51 property-based text access in message objects. In LangChain <v1.0, message text was52 accessed via `.text()` method calls. In v1.0=<, the preferred pattern is property53 access via `.text`.5455 Rather than breaking existing code immediately, `TextAccessor` allows both56 patterns:57 - Modern property access: `message.text` (returns string directly)58 - Legacy method access: `message.text()` (callable, emits deprecation warning)5960 """6162 __slots__ = ()6364 def __new__(cls, value: str) -> Self:65 """Create new TextAccessor instance."""66 return str.__new__(cls, value)6768 def __call__(self) -> str:69 """Enable method-style text access for backward compatibility.7071 This method exists solely to support legacy code that calls `.text()`72 as a method. New code should use property access (`.text`) instead.7374 !!! deprecated75 As of `langchain-core` 1.0.0, calling `.text()` as a method is deprecated.76 Use `.text` as a property instead. This method will be removed in 2.0.0.7778 Returns:79 The string content, identical to property access.8081 """82 warn_deprecated(83 since="1.0.0",84 message=(85 "Calling .text() as a method is deprecated. "86 "Use .text as a property instead (e.g., message.text)."87 ),88 removal="2.0.0",89 )90 return str(self)919293class BaseMessage(Serializable):94 """Base abstract message class.9596 Messages are the inputs and outputs of a chat model.9798 Examples include [`HumanMessage`][langchain.messages.HumanMessage],99 [`AIMessage`][langchain.messages.AIMessage], and100 [`SystemMessage`][langchain.messages.SystemMessage].101 """102103 content: str | list[str | dict]104 """The contents of the message."""105106 additional_kwargs: dict = Field(default_factory=dict)107 """Reserved for additional payload data associated with the message.108109 For example, for a message from an AI, this could include tool calls as110 encoded by the model provider.111112 """113114 response_metadata: dict = Field(default_factory=dict)115 """Examples: response headers, logprobs, token counts, model name."""116117 type: str118 """The type of the message. Must be a string that is unique to the message type.119120 The purpose of this field is to allow for easy identification of the message type121 when deserializing messages.122123 """124125 name: str | None = None126 """An optional name for the message.127128 This can be used to provide a human-readable name for the message.129130 Usage of this field is optional, and whether it's used or not is up to the131 model implementation.132133 """134135 id: str | None = Field(default=None, coerce_numbers_to_str=True)136 """An optional unique identifier for the message.137138 This should ideally be provided by the provider/model which created the message.139140 """141142 model_config = ConfigDict(143 extra="allow",144 )145146 @overload147 def __init__(148 self,149 content: str | list[str | dict],150 **kwargs: Any,151 ) -> None: ...152153 @overload154 def __init__(155 self,156 content: str | list[str | dict] | None = None,157 content_blocks: list[types.ContentBlock] | None = None,158 **kwargs: Any,159 ) -> None: ...160161 def __init__(162 self,163 content: str | list[str | dict] | None = None,164 content_blocks: list[types.ContentBlock] | None = None,165 **kwargs: Any,166 ) -> None:167 """Initialize a `BaseMessage`.168169 Specify `content` as positional arg or `content_blocks` for typing.170171 Args:172 content: The contents of the message.173 content_blocks: Typed standard content.174 **kwargs: Additional arguments to pass to the parent class.175 """176 if content_blocks is not None:177 super().__init__(content=content_blocks, **kwargs)178 else:179 super().__init__(content=content, **kwargs)180181 @classmethod182 def is_lc_serializable(cls) -> bool:183 """`BaseMessage` is serializable.184185 Returns:186 True187 """188 return True189190 @classmethod191 def get_lc_namespace(cls) -> list[str]:192 """Get the namespace of the LangChain object.193194 Returns:195 `["langchain", "schema", "messages"]`196 """197 return ["langchain", "schema", "messages"]198199 @property200 def content_blocks(self) -> list[types.ContentBlock]:201 r"""Load content blocks from the message content.202203 !!! version-added "Added in `langchain-core` 1.0.0"204205 """206 # Needed here to avoid circular import, as these classes import BaseMessages207 from langchain_core.messages.block_translators.anthropic import ( # noqa: PLC0415208 _convert_to_v1_from_anthropic_input,209 )210 from langchain_core.messages.block_translators.bedrock_converse import ( # noqa: PLC0415211 _convert_to_v1_from_converse_input,212 )213 from langchain_core.messages.block_translators.google_genai import ( # noqa: PLC0415214 _convert_to_v1_from_genai_input,215 )216 from langchain_core.messages.block_translators.langchain_v0 import ( # noqa: PLC0415217 _convert_v0_multimodal_input_to_v1,218 )219 from langchain_core.messages.block_translators.openai import ( # noqa: PLC0415220 _convert_to_v1_from_chat_completions_input,221 )222223 blocks: list[types.ContentBlock] = []224 content = (225 # Transpose string content to list, otherwise assumed to be list226 [self.content]227 if isinstance(self.content, str) and self.content228 else self.content229 )230 for item in content:231 if isinstance(item, str):232 # Plain string content is treated as a text block233 blocks.append({"type": "text", "text": item})234 elif isinstance(item, dict):235 item_type = item.get("type")236 if item_type not in types.KNOWN_BLOCK_TYPES:237 # Handle all provider-specific or None type blocks as non-standard -238 # we'll come back to these later239 blocks.append({"type": "non_standard", "value": item})240 else:241 # Guard against v0 blocks that share the same `type` keys242 if "source_type" in item:243 blocks.append({"type": "non_standard", "value": item})244 continue245246 # This can't be a v0 block (since they require `source_type`),247 # so it's a known v1 block type248 blocks.append(cast("types.ContentBlock", item))249250 # Subsequent passes: attempt to unpack non-standard blocks.251 # This is the last stop - if we can't parse it here, it is left as non-standard252 for parsing_step in [253 _convert_v0_multimodal_input_to_v1,254 _convert_to_v1_from_chat_completions_input,255 _convert_to_v1_from_anthropic_input,256 _convert_to_v1_from_genai_input,257 _convert_to_v1_from_converse_input,258 ]:259 blocks = parsing_step(blocks)260 return blocks261262 @property263 def text(self) -> TextAccessor:264 """Get the text content of the message as a string.265266 Can be used as both property (`message.text`) and method (`message.text()`).267268 Handles both string and list content types (e.g. for content blocks). Only269 extracts blocks with `type: 'text'`; other block types are ignored.270271 !!! deprecated272 As of `langchain-core` 1.0.0, calling `.text()` as a method is deprecated.273 Use `.text` as a property instead. This method will be removed in 2.0.0.274275 Returns:276 The text content of the message.277278 """279 if isinstance(self.content, str):280 text_value = self.content281 else:282 # Must be a list283 blocks = [284 block285 for block in self.content286 if isinstance(block, str)287 or (block.get("type") == "text" and isinstance(block.get("text"), str))288 ]289 text_value = "".join(290 block if isinstance(block, str) else block["text"] for block in blocks291 )292 return TextAccessor(text_value)293294 def __add__(self, other: Any) -> ChatPromptTemplate:295 """Concatenate this message with another message.296297 Args:298 other: Another message to concatenate with this one.299300 Returns:301 A ChatPromptTemplate containing both messages.302 """303 # Import locally to prevent circular imports.304 from langchain_core.prompts.chat import ChatPromptTemplate # noqa: PLC0415305306 prompt = ChatPromptTemplate(messages=[self])307 return prompt.__add__(other)308309 def pretty_repr(310 self,311 html: bool = False, # noqa: FBT001,FBT002312 ) -> str:313 """Get a pretty representation of the message.314315 Args:316 html: Whether to format the message as HTML. If `True`, the message will be317 formatted with HTML tags.318319 Returns:320 A pretty representation of the message.321322 Example:323 ```python324 from langchain_core.messages import HumanMessage325326 msg = HumanMessage(content="What is the capital of France?")327 print(msg.pretty_repr())328 ```329330 Results in:331332 ```txt333 ================================ Human Message =================================334335 What is the capital of France?336 ```337 """ # noqa: E501338 title = get_msg_title_repr(self.type.title() + " Message", bold=html)339 # TODO: handle non-string content.340 if self.name is not None:341 title += f"\nName: {self.name}"342 return f"{title}\n\n{self.content}"343344 def pretty_print(self) -> None:345 """Print a pretty representation of the message.346347 Example:348 ```python349 from langchain_core.messages import AIMessage350351 msg = AIMessage(content="The capital of France is Paris.")352 msg.pretty_print()353 ```354355 Results in:356357 ```txt358 ================================== Ai Message ==================================359360 The capital of France is Paris.361 ```362 """ # noqa: E501363 print(self.pretty_repr(html=is_interactive_env())) # noqa: T201364365366def merge_content(367 first_content: str | list[str | dict],368 *contents: str | list[str | dict],369) -> str | list[str | dict]:370 """Merge multiple message contents.371372 Args:373 first_content: The first `content`. Can be a string or a list.374 contents: The other `content`s. Can be a string or a list.375376 Returns:377 The merged content.378379 """380 merged: str | list[str | dict]381 merged = "" if first_content is None else first_content382383 for content in contents:384 # If current is a string385 if isinstance(merged, str):386 # If the next chunk is also a string, then merge them naively387 if isinstance(content, str):388 merged += content389 # If the next chunk is a list, add the current to the start of the list390 else:391 merged = [merged, *content]392 elif isinstance(content, list):393 # If both are lists394 merged = merge_lists(cast("list", merged), content) # type: ignore[assignment]395 # If the first content is a list, and the second content is a string396 # If the last element of the first content is a string397 # Add the second content to the last element398 elif merged and isinstance(merged[-1], str):399 merged[-1] += content400 # If second content is an empty string, treat as a no-op401 elif content == "":402 pass403 # Otherwise, add the second content as a new element of the list404 elif merged:405 merged.append(content)406 return merged407408409class BaseMessageChunk(BaseMessage):410 """Message chunk, which can be concatenated with other Message chunks."""411412 def __add__(self, other: Any) -> BaseMessageChunk: # type: ignore[override]413 """Message chunks support concatenation with other message chunks.414415 This functionality is useful to combine message chunks yielded from416 a streaming model into a complete message.417418 Args:419 other: Another message chunk to concatenate with this one.420421 Returns:422 A new message chunk that is the concatenation of this message chunk423 and the other message chunk.424425 Raises:426 TypeError: If the other object is not a message chunk.427428 Example:429 ```txt430 AIMessageChunk(content="Hello", ...)431 + AIMessageChunk(content=" World", ...)432 = AIMessageChunk(content="Hello World", ...)433 ```434 """435 if isinstance(other, BaseMessageChunk):436 # If both are (subclasses of) BaseMessageChunk,437 # concat into a single BaseMessageChunk438439 return self.__class__(440 id=self.id,441 type=self.type,442 content=merge_content(self.content, other.content),443 additional_kwargs=merge_dicts(444 self.additional_kwargs, other.additional_kwargs445 ),446 response_metadata=merge_dicts(447 self.response_metadata, other.response_metadata448 ),449 )450 if isinstance(other, list) and all(451 isinstance(o, BaseMessageChunk) for o in other452 ):453 content = merge_content(self.content, *(o.content for o in other))454 additional_kwargs = merge_dicts(455 self.additional_kwargs, *(o.additional_kwargs for o in other)456 )457 response_metadata = merge_dicts(458 self.response_metadata, *(o.response_metadata for o in other)459 )460 return self.__class__( # type: ignore[call-arg]461 id=self.id,462 content=content,463 additional_kwargs=additional_kwargs,464 response_metadata=response_metadata,465 )466 msg = (467 'unsupported operand type(s) for +: "'468 f"{self.__class__.__name__}"469 f'" and "{other.__class__.__name__}"'470 )471 raise TypeError(msg)472473474def message_to_dict(message: BaseMessage) -> dict:475 """Convert a Message to a dictionary.476477 Args:478 message: Message to convert.479480 Returns:481 Message as a dict. The dict will have a `type` key with the message type482 and a `data` key with the message data as a dict.483484 """485 return {"type": message.type, "data": message.model_dump()}486487488def messages_to_dict(messages: Sequence[BaseMessage]) -> list[dict]:489 """Convert a sequence of Messages to a list of dictionaries.490491 Args:492 messages: Sequence of messages (as `BaseMessage`s) to convert.493494 Returns:495 List of messages as dicts.496497 """498 return [message_to_dict(m) for m in messages]499500501def get_msg_title_repr(title: str, *, bold: bool = False) -> str:502 """Get a title representation for a message.503504 Args:505 title: The title.506 bold: Whether to bold the title.507508 Returns:509 The title representation.510511 """512 padded = " " + title + " "513 sep_len = (80 - len(padded)) // 2514 sep = "=" * sep_len515 second_sep = sep + "=" if len(padded) % 2 else sep516 if bold:517 padded = get_bolded_text(padded)518 return f"{sep}{padded}{second_sep}"
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.