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
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 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.
"""
parsed: Optional[Union[dict, BaseModel]] = None
"""The auto-parsed message contents."""
type: Literal["ai"] = "ai"
"""The type of the message (used for deserialization). Defaults to "ai"."""
@ -440,11 +442,20 @@ def add_ai_message_chunks(
else:
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__(
example=left.example,
content=content,
additional_kwargs=additional_kwargs,
tool_call_chunks=tool_call_chunks,
parsed=parsed,
response_metadata=response_metadata,
usage_metadata=usage_metadata,
id=left.id,

View File

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

View File

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