Compare commits

...

2 Commits

Author SHA1 Message Date
Eugene Yurtsev
7177327655 qx 2025-07-18 13:43:25 -04:00
Eugene Yurtsev
f37ea13fba x 2025-07-18 11:15:51 -04:00
3 changed files with 305 additions and 2 deletions

View File

@@ -203,7 +203,10 @@ def _format_ls_structured_output(ls_structured_output_format: Optional[dict]) ->
return ls_structured_output_format_dict
class BaseChatModel(BaseLanguageModel[BaseMessage], ABC):
from langchain_core.messages.v2 import AIMessage as AIMessageV2
class BaseChatModel(BaseLanguageModel[AIMessageV2], ABC):
"""Base class for chat models.
Key imperative methods:

View File

@@ -40,6 +40,7 @@ from langchain_core.messages.human import HumanMessage, HumanMessageChunk
from langchain_core.messages.modifier import RemoveMessage
from langchain_core.messages.system import SystemMessage, SystemMessageChunk
from langchain_core.messages.tool import ToolCall, ToolMessage, ToolMessageChunk
from langchain_core.messages.v2 import MessageV2
if TYPE_CHECKING:
from langchain_text_splitters import TextSplitter
@@ -202,7 +203,7 @@ def message_chunk_to_message(chunk: BaseMessageChunk) -> BaseMessage:
MessageLikeRepresentation = Union[
BaseMessage, list[str], tuple[str, str], str, dict[str, Any]
BaseMessage, list[str], tuple[str, str], str, dict[str, Any], MessageV2
]

View File

@@ -0,0 +1,299 @@
import uuid
from dataclasses import dataclass, field
from typing import Any, List, Literal, Optional, TypedDict, Union
from langchain_core.messages.ai import UsageMetadata
def _ensure_id(id_val: Optional[str]) -> str:
"""Ensure the ID is a valid string, generating a new UUID if not provided.
Args:
id_val: Optional string ID value to validate.
Returns:
A valid string ID, either the provided value or a new UUID.
"""
return id_val or str(uuid.uuid4())
@dataclass
class ContentBlock:
"""A block of content within a message.
This represents a single unit of content that can be of various types
including text, images, code, or other structured data.
Attributes:
type: The type of content block (text, image, code, or other).
data: The actual content data, either as a string or dictionary.
"""
type: Literal["text", "image", "code", "other"]
data: Union[str, dict]
class Provider(TypedDict):
"""Information about the provider that generated the message.
Contains metadata about the AI provider and model used to generate content.
Attributes:
name: Name and version of the provider that created the content block.
model_name: Name of the model that generated the content block.
"""
name: str
"""Name and version of the provider that created the content block."""
model_name: str
"""Name of the model that generated the content block."""
@dataclass
class AIMessage:
"""A message generated by an AI assistant.
Represents a response from an AI model, including text content, tool calls,
and metadata about the generation process.
Attributes:
id: Unique identifier for the message.
type: Message type identifier, always "ai".
name: Optional human-readable name for the message.
lc_version: Encoding version for the message.
content: List of content blocks containing the message data.
tool_calls: Optional list of tool calls made by the AI.
invalid_tool_calls: Optional list of tool calls that failed validation.
usage: Optional dictionary containing usage statistics.
"""
id: str
type: Literal["ai"] = "ai"
name: Optional[str] = None
"""An optional name for the message.
This can be used to provide a human-readable name for the message.
Usage of this field is optional, and whether it's used or not is up to the
model implementation.
"""
lc_version: str = "v1"
"""Encoding version for the message."""
content: List[ContentBlock] = field(default_factory=list)
tool_calls: Optional[List[dict]] = None
invalid_tool_calls: Optional[List[dict]] = None
usage: Optional[dict] = None
def __post_init__(self) -> None:
"""Initialize computed fields after dataclass creation.
Ensures the message has a valid ID and initializes the response field.
"""
self.id = _ensure_id(self.id)
self._response = None
@property
def response(self) -> Optional[Any]:
"""Get the raw provider response when available.
The response contains the original data returned by the AI provider.
Returns:
The raw provider response if available, None otherwise.
Response is expected to be serializable.
"""
return self._response
@dataclass
class AIMessageChunk:
"""A partial chunk of an AI message during streaming.
Represents a portion of an AI response that is delivered incrementally
during streaming generation. Contains partial content and metadata.
Attributes:
id: Unique identifier for the message chunk.
type: Message type identifier, always "ai_chunk".
name: Optional human-readable name for the message.
content: List of content blocks containing partial message data.
tool_call_chunks: Optional list of partial tool call data.
usage_metadata: Optional metadata about token usage and costs.
"""
id: str
type: Literal["ai_chunk"] = "ai_chunk"
name: Optional[str] = None
"""An optional name for the message.
This can be used to provide a human-readable name for the message.
Usage of this field is optional, and whether it's used or not is up to the
model implementation.
"""
content: List[ContentBlock] = field(default_factory=list)
tool_call_chunks: Optional[List[dict]] = None
usage_metadata: Optional[UsageMetadata] = None
"""If provided, usage metadata for a message, such as token counts.
This is a standard representation of token usage that is consistent across models.
"""
def __post_init__(self) -> None:
"""Initialize computed fields after dataclass creation.
Ensures the message chunk has a valid ID.
"""
self.id = _ensure_id(self.id)
@dataclass
class HumanMessage:
"""A message from a human user.
Represents input from a human user in a conversation, containing text
or other content types like images.
Attributes:
id: Unique identifier for the message.
content: List of content blocks containing the user's input.
name: Optional human-readable name for the message.
type: Message type identifier, always "human".
"""
id: str
content: List[ContentBlock]
name: Optional[str] = None
"""An optional name for the message.
This can be used to provide a human-readable name for the message.
Usage of this field is optional, and whether it's used or not is up to the
model implementation.
"""
type: Literal["human"] = "human"
"""The type of the message. Must be a string that is unique to the message type.
The purpose of this field is to allow for easy identification of the message type
when deserializing messages.
"""
def __init__(
self, content: Union[str, List[ContentBlock]], id: Optional[str] = None
):
"""Initialize a human message.
Args:
content: Message content as string or list of content blocks.
id: Optional unique identifier for the message.
"""
self.id = _ensure_id(id)
if isinstance(content, str):
self.content = [ContentBlock(type="text", data=content)]
else:
self.content = content
def text(self) -> str:
"""Extract all text content from the message.
Returns:
Concatenated string of all text blocks in the message.
"""
return "".join(
block.data
for block in self.content
if block.type == "text" and isinstance(block.data, str)
)
@dataclass
class SystemMessage:
"""A system message containing instructions or context.
Represents system-level instructions or context that guides the AI's
behavior and understanding of the conversation.
Attributes:
id: Unique identifier for the message.
content: List of content blocks containing system instructions.
type: Message type identifier, always "system".
"""
id: str
content: List[ContentBlock]
type: Literal["system"] = "system"
def __init__(
self, content: Union[str, List[ContentBlock]], *, id: Optional[str] = None
):
"""Initialize a system message.
Args:
content: System instructions as string or list of content blocks.
id: Optional unique identifier for the message.
"""
self.id = _ensure_id(id)
if isinstance(content, str):
self.content = [ContentBlock(type="text", data=content)]
else:
self.content = content
def text(self) -> str:
"""Extract all text content from the system message.
Returns:
Concatenated string of all text blocks in the message.
"""
return "".join(
block.data
for block in self.content
if block.type == "text" and isinstance(block.data, str)
)
@dataclass
class ToolMessage:
"""A message containing the result of a tool execution.
Represents the output from executing a tool or function call,
including the result data and execution status.
Attributes:
id: Unique identifier for the message.
tool_call_id: ID of the tool call this message responds to.
content: The result content from tool execution.
artifact: Optional app-side payload not intended for the model.
status: Execution status ("success" or "error").
type: Message type identifier, always "tool".
"""
id: str
tool_call_id: str
content: List[ContentBlock]
artifact: Optional[Any] = None # App-side payload not for the model
status: Literal["success", "error"] = "success"
type: Literal["tool"] = "tool"
@property
def text(self):
return
def __post_init__(self):
"""Initialize computed fields after dataclass creation.
Ensures the tool message has a valid ID.
"""
self.id = _ensure_id(self.id)
# Alias for a message type that can be any of the defined message types
MessageV2 = Union[
AIMessage,
AIMessageChunk,
HumanMessage,
SystemMessage,
ToolMessage,
]