rfc: AIMessage.parsed

This commit is contained in:
Bagatur 2025-01-06 16:11:20 -05:00
parent 6a152ce245
commit 22863b8ac3
3 changed files with 120 additions and 1 deletions

View File

@ -2,7 +2,7 @@ import json
import operator import operator
from typing import Any, Literal, Optional, Union, cast from typing import Any, Literal, Optional, Union, cast
from pydantic import model_validator from pydantic import BaseModel, model_validator
from typing_extensions import NotRequired, Self, TypedDict from typing_extensions import NotRequired, Self, TypedDict
from langchain_core.messages.base import ( from langchain_core.messages.base import (
@ -163,6 +163,8 @@ class AIMessage(BaseMessage):
This is a standard representation of token usage that is consistent across models. This is a standard representation of token usage that is consistent across models.
""" """
parsed: Optional[Union[dict, BaseModel]] = None
"""The auto-parsed message contents."""
type: Literal["ai"] = "ai" type: Literal["ai"] = "ai"
"""The type of the message (used for deserialization). Defaults to "ai".""" """The type of the message (used for deserialization). Defaults to "ai"."""
@ -440,11 +442,20 @@ def add_ai_message_chunks(
else: else:
usage_metadata = None usage_metadata = None
# 'parsed' always represents an aggregation not an incremental value, so the last
# non-null value is kept.
parsed = None
for m in reversed([left, *others]):
if m.parsed is not None:
parsed = m.parsed
break
return left.__class__( return left.__class__(
example=left.example, example=left.example,
content=content, content=content,
additional_kwargs=additional_kwargs, additional_kwargs=additional_kwargs,
tool_call_chunks=tool_call_chunks, tool_call_chunks=tool_call_chunks,
parsed=parsed,
response_metadata=response_metadata, response_metadata=response_metadata,
usage_metadata=usage_metadata, usage_metadata=usage_metadata,
id=left.id, id=left.id,

View File

@ -77,6 +77,21 @@
'default': None, 'default': None,
'title': 'Name', 'title': 'Name',
}), }),
'parsed': dict({
'anyOf': list([
dict({
'type': 'object',
}),
dict({
'$ref': '#/$defs/BaseModel',
}),
dict({
'type': 'null',
}),
]),
'default': None,
'title': 'Parsed',
}),
'response_metadata': dict({ 'response_metadata': dict({
'title': 'Response Metadata', 'title': 'Response Metadata',
'type': 'object', 'type': 'object',
@ -181,6 +196,21 @@
'default': None, 'default': None,
'title': 'Name', 'title': 'Name',
}), }),
'parsed': dict({
'anyOf': list([
dict({
'type': 'object',
}),
dict({
'$ref': '#/$defs/BaseModel',
}),
dict({
'type': 'null',
}),
]),
'default': None,
'title': 'Parsed',
}),
'response_metadata': dict({ 'response_metadata': dict({
'title': 'Response Metadata', 'title': 'Response Metadata',
'type': 'object', 'type': 'object',
@ -227,6 +257,12 @@
'title': 'AIMessageChunk', 'title': 'AIMessageChunk',
'type': 'object', 'type': 'object',
}), }),
'BaseModel': dict({
'properties': dict({
}),
'title': 'BaseModel',
'type': 'object',
}),
'ChatMessage': dict({ 'ChatMessage': dict({
'additionalProperties': True, 'additionalProperties': True,
'description': 'Message that can be assigned an arbitrary speaker (i.e. role).', 'description': 'Message that can be assigned an arbitrary speaker (i.e. role).',
@ -1507,6 +1543,21 @@
'default': None, 'default': None,
'title': 'Name', 'title': 'Name',
}), }),
'parsed': dict({
'anyOf': list([
dict({
'type': 'object',
}),
dict({
'$ref': '#/$defs/BaseModel',
}),
dict({
'type': 'null',
}),
]),
'default': None,
'title': 'Parsed',
}),
'response_metadata': dict({ 'response_metadata': dict({
'title': 'Response Metadata', 'title': 'Response Metadata',
'type': 'object', 'type': 'object',
@ -1611,6 +1662,21 @@
'default': None, 'default': None,
'title': 'Name', 'title': 'Name',
}), }),
'parsed': dict({
'anyOf': list([
dict({
'type': 'object',
}),
dict({
'$ref': '#/$defs/BaseModel',
}),
dict({
'type': 'null',
}),
]),
'default': None,
'title': 'Parsed',
}),
'response_metadata': dict({ 'response_metadata': dict({
'title': 'Response Metadata', 'title': 'Response Metadata',
'type': 'object', 'type': 'object',
@ -1657,6 +1723,12 @@
'title': 'AIMessageChunk', 'title': 'AIMessageChunk',
'type': 'object', 'type': 'object',
}), }),
'BaseModel': dict({
'properties': dict({
}),
'title': 'BaseModel',
'type': 'object',
}),
'ChatMessage': dict({ 'ChatMessage': dict({
'additionalProperties': True, 'additionalProperties': True,
'description': 'Message that can be assigned an arbitrary speaker (i.e. role).', 'description': 'Message that can be assigned an arbitrary speaker (i.e. role).',

View File

@ -451,6 +451,21 @@
'default': None, 'default': None,
'title': 'Name', 'title': 'Name',
}), }),
'parsed': dict({
'anyOf': list([
dict({
'type': 'object',
}),
dict({
'$ref': '#/$defs/BaseModel',
}),
dict({
'type': 'null',
}),
]),
'default': None,
'title': 'Parsed',
}),
'response_metadata': dict({ 'response_metadata': dict({
'title': 'Response Metadata', 'title': 'Response Metadata',
'type': 'object', 'type': 'object',
@ -555,6 +570,21 @@
'default': None, 'default': None,
'title': 'Name', 'title': 'Name',
}), }),
'parsed': dict({
'anyOf': list([
dict({
'type': 'object',
}),
dict({
'$ref': '#/$defs/BaseModel',
}),
dict({
'type': 'null',
}),
]),
'default': None,
'title': 'Parsed',
}),
'response_metadata': dict({ 'response_metadata': dict({
'title': 'Response Metadata', 'title': 'Response Metadata',
'type': 'object', 'type': 'object',
@ -601,6 +631,12 @@
'title': 'AIMessageChunk', 'title': 'AIMessageChunk',
'type': 'object', 'type': 'object',
}), }),
'BaseModel': dict({
'properties': dict({
}),
'title': 'BaseModel',
'type': 'object',
}),
'ChatMessage': dict({ 'ChatMessage': dict({
'additionalProperties': True, 'additionalProperties': True,
'description': 'Message that can be assigned an arbitrary speaker (i.e. role).', 'description': 'Message that can be assigned an arbitrary speaker (i.e. role).',