langchain-core 0.4.0.dev0__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of langchain-core might be problematic. Click here for more details.

Files changed (172) hide show
  1. langchain_core/__init__.py +1 -1
  2. langchain_core/_api/__init__.py +3 -4
  3. langchain_core/_api/beta_decorator.py +45 -70
  4. langchain_core/_api/deprecation.py +80 -80
  5. langchain_core/_api/path.py +22 -8
  6. langchain_core/_import_utils.py +10 -4
  7. langchain_core/agents.py +25 -21
  8. langchain_core/caches.py +53 -63
  9. langchain_core/callbacks/__init__.py +1 -8
  10. langchain_core/callbacks/base.py +341 -348
  11. langchain_core/callbacks/file.py +55 -44
  12. langchain_core/callbacks/manager.py +546 -683
  13. langchain_core/callbacks/stdout.py +29 -30
  14. langchain_core/callbacks/streaming_stdout.py +35 -36
  15. langchain_core/callbacks/usage.py +65 -70
  16. langchain_core/chat_history.py +48 -55
  17. langchain_core/document_loaders/base.py +46 -21
  18. langchain_core/document_loaders/langsmith.py +39 -36
  19. langchain_core/documents/__init__.py +0 -1
  20. langchain_core/documents/base.py +96 -74
  21. langchain_core/documents/compressor.py +12 -9
  22. langchain_core/documents/transformers.py +29 -28
  23. langchain_core/embeddings/fake.py +56 -57
  24. langchain_core/env.py +2 -3
  25. langchain_core/example_selectors/base.py +12 -0
  26. langchain_core/example_selectors/length_based.py +1 -1
  27. langchain_core/example_selectors/semantic_similarity.py +21 -25
  28. langchain_core/exceptions.py +15 -9
  29. langchain_core/globals.py +4 -163
  30. langchain_core/indexing/api.py +132 -125
  31. langchain_core/indexing/base.py +64 -67
  32. langchain_core/indexing/in_memory.py +26 -6
  33. langchain_core/language_models/__init__.py +15 -27
  34. langchain_core/language_models/_utils.py +267 -117
  35. langchain_core/language_models/base.py +92 -177
  36. langchain_core/language_models/chat_models.py +547 -407
  37. langchain_core/language_models/fake.py +11 -11
  38. langchain_core/language_models/fake_chat_models.py +72 -118
  39. langchain_core/language_models/llms.py +168 -242
  40. langchain_core/load/dump.py +8 -11
  41. langchain_core/load/load.py +32 -28
  42. langchain_core/load/mapping.py +2 -4
  43. langchain_core/load/serializable.py +50 -56
  44. langchain_core/messages/__init__.py +36 -51
  45. langchain_core/messages/ai.py +377 -150
  46. langchain_core/messages/base.py +239 -47
  47. langchain_core/messages/block_translators/__init__.py +111 -0
  48. langchain_core/messages/block_translators/anthropic.py +470 -0
  49. langchain_core/messages/block_translators/bedrock.py +94 -0
  50. langchain_core/messages/block_translators/bedrock_converse.py +297 -0
  51. langchain_core/messages/block_translators/google_genai.py +530 -0
  52. langchain_core/messages/block_translators/google_vertexai.py +21 -0
  53. langchain_core/messages/block_translators/groq.py +143 -0
  54. langchain_core/messages/block_translators/langchain_v0.py +301 -0
  55. langchain_core/messages/block_translators/openai.py +1010 -0
  56. langchain_core/messages/chat.py +2 -3
  57. langchain_core/messages/content.py +1423 -0
  58. langchain_core/messages/function.py +7 -7
  59. langchain_core/messages/human.py +44 -38
  60. langchain_core/messages/modifier.py +3 -2
  61. langchain_core/messages/system.py +40 -27
  62. langchain_core/messages/tool.py +160 -58
  63. langchain_core/messages/utils.py +527 -638
  64. langchain_core/output_parsers/__init__.py +1 -14
  65. langchain_core/output_parsers/base.py +68 -104
  66. langchain_core/output_parsers/json.py +13 -17
  67. langchain_core/output_parsers/list.py +11 -33
  68. langchain_core/output_parsers/openai_functions.py +56 -74
  69. langchain_core/output_parsers/openai_tools.py +68 -109
  70. langchain_core/output_parsers/pydantic.py +15 -13
  71. langchain_core/output_parsers/string.py +6 -2
  72. langchain_core/output_parsers/transform.py +17 -60
  73. langchain_core/output_parsers/xml.py +34 -44
  74. langchain_core/outputs/__init__.py +1 -1
  75. langchain_core/outputs/chat_generation.py +26 -11
  76. langchain_core/outputs/chat_result.py +1 -3
  77. langchain_core/outputs/generation.py +17 -6
  78. langchain_core/outputs/llm_result.py +15 -8
  79. langchain_core/prompt_values.py +29 -123
  80. langchain_core/prompts/__init__.py +3 -27
  81. langchain_core/prompts/base.py +48 -63
  82. langchain_core/prompts/chat.py +259 -288
  83. langchain_core/prompts/dict.py +19 -11
  84. langchain_core/prompts/few_shot.py +84 -90
  85. langchain_core/prompts/few_shot_with_templates.py +14 -12
  86. langchain_core/prompts/image.py +19 -14
  87. langchain_core/prompts/loading.py +6 -8
  88. langchain_core/prompts/message.py +7 -8
  89. langchain_core/prompts/prompt.py +42 -43
  90. langchain_core/prompts/string.py +37 -16
  91. langchain_core/prompts/structured.py +43 -46
  92. langchain_core/rate_limiters.py +51 -60
  93. langchain_core/retrievers.py +52 -192
  94. langchain_core/runnables/base.py +1727 -1683
  95. langchain_core/runnables/branch.py +52 -73
  96. langchain_core/runnables/config.py +89 -103
  97. langchain_core/runnables/configurable.py +128 -130
  98. langchain_core/runnables/fallbacks.py +93 -82
  99. langchain_core/runnables/graph.py +127 -127
  100. langchain_core/runnables/graph_ascii.py +63 -41
  101. langchain_core/runnables/graph_mermaid.py +87 -70
  102. langchain_core/runnables/graph_png.py +31 -36
  103. langchain_core/runnables/history.py +145 -161
  104. langchain_core/runnables/passthrough.py +141 -144
  105. langchain_core/runnables/retry.py +84 -68
  106. langchain_core/runnables/router.py +33 -37
  107. langchain_core/runnables/schema.py +79 -72
  108. langchain_core/runnables/utils.py +95 -139
  109. langchain_core/stores.py +85 -131
  110. langchain_core/structured_query.py +11 -15
  111. langchain_core/sys_info.py +31 -32
  112. langchain_core/tools/__init__.py +1 -14
  113. langchain_core/tools/base.py +221 -247
  114. langchain_core/tools/convert.py +144 -161
  115. langchain_core/tools/render.py +10 -10
  116. langchain_core/tools/retriever.py +12 -19
  117. langchain_core/tools/simple.py +52 -29
  118. langchain_core/tools/structured.py +56 -60
  119. langchain_core/tracers/__init__.py +1 -9
  120. langchain_core/tracers/_streaming.py +6 -7
  121. langchain_core/tracers/base.py +103 -112
  122. langchain_core/tracers/context.py +29 -48
  123. langchain_core/tracers/core.py +142 -105
  124. langchain_core/tracers/evaluation.py +30 -34
  125. langchain_core/tracers/event_stream.py +162 -117
  126. langchain_core/tracers/langchain.py +34 -36
  127. langchain_core/tracers/log_stream.py +87 -49
  128. langchain_core/tracers/memory_stream.py +3 -3
  129. langchain_core/tracers/root_listeners.py +18 -34
  130. langchain_core/tracers/run_collector.py +8 -20
  131. langchain_core/tracers/schemas.py +0 -125
  132. langchain_core/tracers/stdout.py +3 -3
  133. langchain_core/utils/__init__.py +1 -4
  134. langchain_core/utils/_merge.py +47 -9
  135. langchain_core/utils/aiter.py +70 -66
  136. langchain_core/utils/env.py +12 -9
  137. langchain_core/utils/function_calling.py +139 -206
  138. langchain_core/utils/html.py +7 -8
  139. langchain_core/utils/input.py +6 -6
  140. langchain_core/utils/interactive_env.py +6 -2
  141. langchain_core/utils/iter.py +48 -45
  142. langchain_core/utils/json.py +14 -4
  143. langchain_core/utils/json_schema.py +159 -43
  144. langchain_core/utils/mustache.py +32 -25
  145. langchain_core/utils/pydantic.py +67 -40
  146. langchain_core/utils/strings.py +5 -5
  147. langchain_core/utils/usage.py +1 -1
  148. langchain_core/utils/utils.py +104 -62
  149. langchain_core/vectorstores/base.py +131 -179
  150. langchain_core/vectorstores/in_memory.py +113 -182
  151. langchain_core/vectorstores/utils.py +23 -17
  152. langchain_core/version.py +1 -1
  153. langchain_core-1.0.0.dist-info/METADATA +68 -0
  154. langchain_core-1.0.0.dist-info/RECORD +172 -0
  155. {langchain_core-0.4.0.dev0.dist-info → langchain_core-1.0.0.dist-info}/WHEEL +1 -1
  156. langchain_core/beta/__init__.py +0 -1
  157. langchain_core/beta/runnables/__init__.py +0 -1
  158. langchain_core/beta/runnables/context.py +0 -448
  159. langchain_core/memory.py +0 -116
  160. langchain_core/messages/content_blocks.py +0 -1435
  161. langchain_core/prompts/pipeline.py +0 -133
  162. langchain_core/pydantic_v1/__init__.py +0 -30
  163. langchain_core/pydantic_v1/dataclasses.py +0 -23
  164. langchain_core/pydantic_v1/main.py +0 -23
  165. langchain_core/tracers/langchain_v1.py +0 -23
  166. langchain_core/utils/loading.py +0 -31
  167. langchain_core/v1/__init__.py +0 -1
  168. langchain_core/v1/chat_models.py +0 -1047
  169. langchain_core/v1/messages.py +0 -755
  170. langchain_core-0.4.0.dev0.dist-info/METADATA +0 -108
  171. langchain_core-0.4.0.dev0.dist-info/RECORD +0 -177
  172. langchain_core-0.4.0.dev0.dist-info/entry_points.txt +0 -4
@@ -3,14 +3,21 @@
3
3
  import json
4
4
  import logging
5
5
  import operator
6
- from typing import Any, Literal, Optional, Union, cast
6
+ from collections.abc import Sequence
7
+ from typing import Any, Literal, cast, overload
7
8
 
8
9
  from pydantic import model_validator
9
10
  from typing_extensions import NotRequired, Self, TypedDict, override
10
11
 
11
- from langchain_core.messages.base import BaseMessage, BaseMessageChunk, merge_content
12
+ from langchain_core.messages import content as types
13
+ from langchain_core.messages.base import (
14
+ BaseMessage,
15
+ BaseMessageChunk,
16
+ _extract_reasoning_from_additional_kwargs,
17
+ merge_content,
18
+ )
19
+ from langchain_core.messages.content import InvalidToolCall
12
20
  from langchain_core.messages.tool import (
13
- InvalidToolCall,
14
21
  ToolCall,
15
22
  ToolCallChunk,
16
23
  default_tool_chunk_parser,
@@ -22,39 +29,26 @@ from langchain_core.messages.tool import tool_call_chunk as create_tool_call_chu
22
29
  from langchain_core.utils._merge import merge_dicts, merge_lists
23
30
  from langchain_core.utils.json import parse_partial_json
24
31
  from langchain_core.utils.usage import _dict_int_op
32
+ from langchain_core.utils.utils import LC_AUTO_PREFIX, LC_ID_PREFIX
25
33
 
26
34
  logger = logging.getLogger(__name__)
27
35
 
28
36
 
29
- _LC_ID_PREFIX = "run-"
30
- """Internal tracing/callback system identifier.
31
-
32
- Used for:
33
- - Tracing. Every LangChain operation (LLM call, chain execution, tool use, etc.)
34
- gets a unique run_id (UUID)
35
- - Enables tracking parent-child relationships between operations
36
- """
37
-
38
- _LC_AUTO_PREFIX = "lc_"
39
- """LangChain auto-generated ID prefix for messages and content blocks."""
40
-
41
-
42
37
  class InputTokenDetails(TypedDict, total=False):
43
38
  """Breakdown of input token counts.
44
39
 
45
40
  Does *not* need to sum to full input token count. Does *not* need to have all keys.
46
41
 
47
42
  Example:
43
+ ```python
44
+ {
45
+ "audio": 10,
46
+ "cache_creation": 200,
47
+ "cache_read": 100,
48
+ }
49
+ ```
48
50
 
49
- .. code-block:: python
50
-
51
- {
52
- "audio": 10,
53
- "cache_creation": 200,
54
- "cache_read": 100,
55
- }
56
-
57
- .. versionadded:: 0.3.9
51
+ !!! version-added "Added in version 0.3.9"
58
52
 
59
53
  May also hold extra provider-specific keys.
60
54
 
@@ -72,6 +66,7 @@ class InputTokenDetails(TypedDict, total=False):
72
66
 
73
67
  Since there was a cache hit, the tokens were read from the cache. More precisely,
74
68
  the model state given these tokens was read from the cache.
69
+
75
70
  """
76
71
 
77
72
 
@@ -81,15 +76,14 @@ class OutputTokenDetails(TypedDict, total=False):
81
76
  Does *not* need to sum to full output token count. Does *not* need to have all keys.
82
77
 
83
78
  Example:
79
+ ```python
80
+ {
81
+ "audio": 10,
82
+ "reasoning": 200,
83
+ }
84
+ ```
84
85
 
85
- .. code-block:: python
86
-
87
- {
88
- "audio": 10,
89
- "reasoning": 200,
90
- }
91
-
92
- .. versionadded:: 0.3.9
86
+ !!! version-added "Added in version 0.3.9"
93
87
 
94
88
  """
95
89
 
@@ -100,6 +94,7 @@ class OutputTokenDetails(TypedDict, total=False):
100
94
 
101
95
  Tokens generated by the model in a chain of thought process (i.e. by OpenAI's o1
102
96
  models) that are not returned as part of model output.
97
+
103
98
  """
104
99
 
105
100
 
@@ -109,27 +104,25 @@ class UsageMetadata(TypedDict):
109
104
  This is a standard representation of token usage that is consistent across models.
110
105
 
111
106
  Example:
107
+ ```python
108
+ {
109
+ "input_tokens": 350,
110
+ "output_tokens": 240,
111
+ "total_tokens": 590,
112
+ "input_token_details": {
113
+ "audio": 10,
114
+ "cache_creation": 200,
115
+ "cache_read": 100,
116
+ },
117
+ "output_token_details": {
118
+ "audio": 10,
119
+ "reasoning": 200,
120
+ },
121
+ }
122
+ ```
112
123
 
113
- .. code-block:: python
114
-
115
- {
116
- "input_tokens": 350,
117
- "output_tokens": 240,
118
- "total_tokens": 590,
119
- "input_token_details": {
120
- "audio": 10,
121
- "cache_creation": 200,
122
- "cache_read": 100,
123
- },
124
- "output_token_details": {
125
- "audio": 10,
126
- "reasoning": 200,
127
- }
128
- }
129
-
130
- .. versionchanged:: 0.3.9
131
-
132
- Added ``input_token_details`` and ``output_token_details``.
124
+ !!! warning "Behavior changed in 0.3.9"
125
+ Added `input_token_details` and `output_token_details`.
133
126
 
134
127
  """
135
128
 
@@ -148,57 +141,150 @@ class UsageMetadata(TypedDict):
148
141
  """Breakdown of output token counts.
149
142
 
150
143
  Does *not* need to sum to full output token count. Does *not* need to have all keys.
144
+
151
145
  """
152
146
 
153
147
 
154
148
  class AIMessage(BaseMessage):
155
149
  """Message from an AI.
156
150
 
157
- AIMessage is returned from a chat model as a response to a prompt.
151
+ An `AIMessage` is returned from a chat model as a response to a prompt.
158
152
 
159
153
  This message represents the output of the model and consists of both
160
- the raw output as returned by the model together standardized fields
154
+ the raw output as returned by the model and standardized fields
161
155
  (e.g., tool calls, usage metadata) added by the LangChain framework.
162
- """
163
156
 
164
- example: bool = False
165
- """Use to denote that a message is part of an example conversation.
166
-
167
- At the moment, this is ignored by most models. Usage is discouraged.
168
157
  """
169
158
 
170
159
  tool_calls: list[ToolCall] = []
171
- """If provided, tool calls associated with the message."""
160
+ """If present, tool calls associated with the message."""
172
161
  invalid_tool_calls: list[InvalidToolCall] = []
173
- """If provided, tool calls with parsing errors associated with the message."""
174
- usage_metadata: Optional[UsageMetadata] = None
175
- """If provided, usage metadata for a message, such as token counts.
162
+ """If present, tool calls with parsing errors associated with the message."""
163
+ usage_metadata: UsageMetadata | None = None
164
+ """If present, usage metadata for a message, such as token counts.
176
165
 
177
166
  This is a standard representation of token usage that is consistent across models.
178
167
  """
179
168
 
180
169
  type: Literal["ai"] = "ai"
181
- """The type of the message (used for deserialization). Defaults to "ai"."""
170
+ """The type of the message (used for deserialization)."""
171
+
172
+ @overload
173
+ def __init__(
174
+ self,
175
+ content: str | list[str | dict],
176
+ **kwargs: Any,
177
+ ) -> None: ...
182
178
 
179
+ @overload
183
180
  def __init__(
184
- self, content: Union[str, list[Union[str, dict]]], **kwargs: Any
181
+ self,
182
+ content: str | list[str | dict] | None = None,
183
+ content_blocks: list[types.ContentBlock] | None = None,
184
+ **kwargs: Any,
185
+ ) -> None: ...
186
+
187
+ def __init__(
188
+ self,
189
+ content: str | list[str | dict] | None = None,
190
+ content_blocks: list[types.ContentBlock] | None = None,
191
+ **kwargs: Any,
185
192
  ) -> None:
186
- """Pass in content as positional arg.
193
+ """Initialize an `AIMessage`.
194
+
195
+ Specify `content` as positional arg or `content_blocks` for typing.
187
196
 
188
197
  Args:
189
198
  content: The content of the message.
190
- kwargs: Additional arguments to pass to the parent class.
199
+ content_blocks: Typed standard content.
200
+ **kwargs: Additional arguments to pass to the parent class.
191
201
  """
192
- super().__init__(content=content, **kwargs)
202
+ if content_blocks is not None:
203
+ # If there are tool calls in content_blocks, but not in tool_calls, add them
204
+ content_tool_calls = [
205
+ block for block in content_blocks if block.get("type") == "tool_call"
206
+ ]
207
+ if content_tool_calls and "tool_calls" not in kwargs:
208
+ kwargs["tool_calls"] = content_tool_calls
209
+
210
+ super().__init__(
211
+ content=cast("str | list[str | dict]", content_blocks),
212
+ **kwargs,
213
+ )
214
+ else:
215
+ super().__init__(content=content, **kwargs)
193
216
 
194
217
  @property
195
218
  def lc_attributes(self) -> dict:
196
- """Attrs to be serialized even if they are derived from other init args."""
219
+ """Attributes to be serialized.
220
+
221
+ Includes all attributes, even if they are derived from other initialization
222
+ arguments.
223
+ """
197
224
  return {
198
225
  "tool_calls": self.tool_calls,
199
226
  "invalid_tool_calls": self.invalid_tool_calls,
200
227
  }
201
228
 
229
+ @property
230
+ def content_blocks(self) -> list[types.ContentBlock]:
231
+ """Return standard, typed `ContentBlock` dicts from the message.
232
+
233
+ If the message has a known model provider, use the provider-specific translator
234
+ first before falling back to best-effort parsing. For details, see the property
235
+ on `BaseMessage`.
236
+ """
237
+ if self.response_metadata.get("output_version") == "v1":
238
+ return cast("list[types.ContentBlock]", self.content)
239
+
240
+ model_provider = self.response_metadata.get("model_provider")
241
+ if model_provider:
242
+ from langchain_core.messages.block_translators import ( # noqa: PLC0415
243
+ get_translator,
244
+ )
245
+
246
+ translator = get_translator(model_provider)
247
+ if translator:
248
+ try:
249
+ return translator["translate_content"](self)
250
+ except NotImplementedError:
251
+ pass
252
+
253
+ # Otherwise, use best-effort parsing
254
+ blocks = super().content_blocks
255
+
256
+ if self.tool_calls:
257
+ # Add from tool_calls if missing from content
258
+ content_tool_call_ids = {
259
+ block.get("id")
260
+ for block in self.content
261
+ if isinstance(block, dict) and block.get("type") == "tool_call"
262
+ }
263
+ for tool_call in self.tool_calls:
264
+ if (id_ := tool_call.get("id")) and id_ not in content_tool_call_ids:
265
+ tool_call_block: types.ToolCall = {
266
+ "type": "tool_call",
267
+ "id": id_,
268
+ "name": tool_call["name"],
269
+ "args": tool_call["args"],
270
+ }
271
+ if "index" in tool_call:
272
+ tool_call_block["index"] = tool_call["index"] # type: ignore[typeddict-item]
273
+ if "extras" in tool_call:
274
+ tool_call_block["extras"] = tool_call["extras"] # type: ignore[typeddict-item]
275
+ blocks.append(tool_call_block)
276
+
277
+ # Best-effort reasoning extraction from additional_kwargs
278
+ # Only add reasoning if not already present
279
+ # Insert before all other blocks to keep reasoning at the start
280
+ has_reasoning = any(block.get("type") == "reasoning" for block in blocks)
281
+ if not has_reasoning and (
282
+ reasoning_block := _extract_reasoning_from_additional_kwargs(self)
283
+ ):
284
+ blocks.insert(0, reasoning_block)
285
+
286
+ return blocks
287
+
202
288
  # TODO: remove this logic if possible, reducing breaking nature of changes
203
289
  @model_validator(mode="before")
204
290
  @classmethod
@@ -227,7 +313,9 @@ class AIMessage(BaseMessage):
227
313
  # Ensure "type" is properly set on all tool call-like dicts.
228
314
  if tool_calls := values.get("tool_calls"):
229
315
  values["tool_calls"] = [
230
- create_tool_call(**{k: v for k, v in tc.items() if k != "type"})
316
+ create_tool_call(
317
+ **{k: v for k, v in tc.items() if k not in ("type", "extras")}
318
+ )
231
319
  for tc in tool_calls
232
320
  ]
233
321
  if invalid_tool_calls := values.get("invalid_tool_calls"):
@@ -246,19 +334,19 @@ class AIMessage(BaseMessage):
246
334
 
247
335
  @override
248
336
  def pretty_repr(self, html: bool = False) -> str:
249
- """Return a pretty representation of the message.
337
+ """Return a pretty representation of the message for display.
250
338
 
251
339
  Args:
252
340
  html: Whether to return an HTML-formatted string.
253
- Defaults to False.
254
341
 
255
342
  Returns:
256
343
  A pretty representation of the message.
344
+
257
345
  """
258
346
  base = super().pretty_repr(html=html)
259
347
  lines = []
260
348
 
261
- def _format_tool_args(tc: Union[ToolCall, InvalidToolCall]) -> list[str]:
349
+ def _format_tool_args(tc: ToolCall | InvalidToolCall) -> list[str]:
262
350
  lines = [
263
351
  f" {tc.get('name', 'Tool')} ({tc.get('id')})",
264
352
  f" Call ID: {tc.get('id')}",
@@ -286,33 +374,90 @@ class AIMessage(BaseMessage):
286
374
 
287
375
 
288
376
  class AIMessageChunk(AIMessage, BaseMessageChunk):
289
- """Message chunk from an AI."""
377
+ """Message chunk from an AI (yielded when streaming)."""
290
378
 
291
379
  # Ignoring mypy re-assignment here since we're overriding the value
292
380
  # to make sure that the chunk variant can be discriminated from the
293
381
  # non-chunk variant.
294
382
  type: Literal["AIMessageChunk"] = "AIMessageChunk" # type: ignore[assignment]
295
- """The type of the message (used for deserialization).
296
- Defaults to "AIMessageChunk"."""
383
+ """The type of the message (used for deserialization)."""
297
384
 
298
385
  tool_call_chunks: list[ToolCallChunk] = []
299
386
  """If provided, tool call chunks associated with the message."""
300
387
 
388
+ chunk_position: Literal["last"] | None = None
389
+ """Optional span represented by an aggregated `AIMessageChunk`.
390
+
391
+ If a chunk with `chunk_position="last"` is aggregated into a stream,
392
+ `tool_call_chunks` in message content will be parsed into `tool_calls`.
393
+ """
394
+
301
395
  @property
302
396
  def lc_attributes(self) -> dict:
303
- """Attrs to be serialized even if they are derived from other init args."""
397
+ """Attributes to be serialized, even if they are derived from other initialization args.""" # noqa: E501
304
398
  return {
305
399
  "tool_calls": self.tool_calls,
306
400
  "invalid_tool_calls": self.invalid_tool_calls,
307
401
  }
308
402
 
403
+ @property
404
+ def content_blocks(self) -> list[types.ContentBlock]:
405
+ """Return standard, typed `ContentBlock` dicts from the message."""
406
+ if self.response_metadata.get("output_version") == "v1":
407
+ return cast("list[types.ContentBlock]", self.content)
408
+
409
+ model_provider = self.response_metadata.get("model_provider")
410
+ if model_provider:
411
+ from langchain_core.messages.block_translators import ( # noqa: PLC0415
412
+ get_translator,
413
+ )
414
+
415
+ translator = get_translator(model_provider)
416
+ if translator:
417
+ try:
418
+ return translator["translate_content_chunk"](self)
419
+ except NotImplementedError:
420
+ pass
421
+
422
+ # Otherwise, use best-effort parsing
423
+ blocks = super().content_blocks
424
+
425
+ if (
426
+ self.tool_call_chunks
427
+ and not self.content
428
+ and self.chunk_position != "last" # keep tool_calls if aggregated
429
+ ):
430
+ blocks = [
431
+ block
432
+ for block in blocks
433
+ if block["type"] not in ("tool_call", "invalid_tool_call")
434
+ ]
435
+ for tool_call_chunk in self.tool_call_chunks:
436
+ tc: types.ToolCallChunk = {
437
+ "type": "tool_call_chunk",
438
+ "id": tool_call_chunk.get("id"),
439
+ "name": tool_call_chunk.get("name"),
440
+ "args": tool_call_chunk.get("args"),
441
+ }
442
+ if (idx := tool_call_chunk.get("index")) is not None:
443
+ tc["index"] = idx
444
+ blocks.append(tc)
445
+
446
+ # Best-effort reasoning extraction from additional_kwargs
447
+ # Only add reasoning if not already present
448
+ # Insert before all other blocks to keep reasoning at the start
449
+ has_reasoning = any(block.get("type") == "reasoning" for block in blocks)
450
+ if not has_reasoning and (
451
+ reasoning_block := _extract_reasoning_from_additional_kwargs(self)
452
+ ):
453
+ blocks.insert(0, reasoning_block)
454
+
455
+ return blocks
456
+
309
457
  @model_validator(mode="after")
310
458
  def init_tool_calls(self) -> Self:
311
459
  """Initialize tool calls from tool call chunks.
312
460
 
313
- Args:
314
- values: The values to validate.
315
-
316
461
  Returns:
317
462
  The values with tool calls initialized.
318
463
 
@@ -358,7 +503,7 @@ class AIMessageChunk(AIMessage, BaseMessageChunk):
358
503
 
359
504
  for chunk in self.tool_call_chunks:
360
505
  try:
361
- args_ = parse_partial_json(chunk["args"]) if chunk["args"] != "" else {} # type: ignore[arg-type]
506
+ args_ = parse_partial_json(chunk["args"]) if chunk["args"] else {}
362
507
  if isinstance(args_, dict):
363
508
  tool_calls.append(
364
509
  create_tool_call(
@@ -373,10 +518,73 @@ class AIMessageChunk(AIMessage, BaseMessageChunk):
373
518
  add_chunk_to_invalid_tool_calls(chunk)
374
519
  self.tool_calls = tool_calls
375
520
  self.invalid_tool_calls = invalid_tool_calls
521
+
522
+ if (
523
+ self.chunk_position == "last"
524
+ and self.tool_call_chunks
525
+ and self.response_metadata.get("output_version") == "v1"
526
+ and isinstance(self.content, list)
527
+ ):
528
+ id_to_tc: dict[str, types.ToolCall] = {
529
+ cast("str", tc.get("id")): {
530
+ "type": "tool_call",
531
+ "name": tc["name"],
532
+ "args": tc["args"],
533
+ "id": tc.get("id"),
534
+ }
535
+ for tc in self.tool_calls
536
+ if "id" in tc
537
+ }
538
+ for idx, block in enumerate(self.content):
539
+ if (
540
+ isinstance(block, dict)
541
+ and block.get("type") == "tool_call_chunk"
542
+ and (call_id := block.get("id"))
543
+ and call_id in id_to_tc
544
+ ):
545
+ self.content[idx] = cast("dict[str, Any]", id_to_tc[call_id])
546
+ if "extras" in block:
547
+ # mypy does not account for instance check for dict above
548
+ self.content[idx]["extras"] = block["extras"] # type: ignore[index]
549
+
550
+ return self
551
+
552
+ @model_validator(mode="after")
553
+ def init_server_tool_calls(self) -> Self:
554
+ """Parse `server_tool_call_chunks`."""
555
+ if (
556
+ self.chunk_position == "last"
557
+ and self.response_metadata.get("output_version") == "v1"
558
+ and isinstance(self.content, list)
559
+ ):
560
+ for idx, block in enumerate(self.content):
561
+ if (
562
+ isinstance(block, dict)
563
+ and block.get("type")
564
+ in ("server_tool_call", "server_tool_call_chunk")
565
+ and (args_str := block.get("args"))
566
+ and isinstance(args_str, str)
567
+ ):
568
+ try:
569
+ args = json.loads(args_str)
570
+ if isinstance(args, dict):
571
+ self.content[idx]["type"] = "server_tool_call" # type: ignore[index]
572
+ self.content[idx]["args"] = args # type: ignore[index]
573
+ except json.JSONDecodeError:
574
+ pass
376
575
  return self
377
576
 
577
+ @overload # type: ignore[override] # summing BaseMessages gives ChatPromptTemplate
578
+ def __add__(self, other: "AIMessageChunk") -> "AIMessageChunk": ...
579
+
580
+ @overload
581
+ def __add__(self, other: Sequence["AIMessageChunk"]) -> "AIMessageChunk": ...
582
+
583
+ @overload
584
+ def __add__(self, other: Any) -> BaseMessageChunk: ...
585
+
378
586
  @override
379
- def __add__(self, other: Any) -> BaseMessageChunk: # type: ignore[override]
587
+ def __add__(self, other: Any) -> BaseMessageChunk:
380
588
  if isinstance(other, AIMessageChunk):
381
589
  return add_ai_message_chunks(self, other)
382
590
  if isinstance(other, (list, tuple)) and all(
@@ -389,11 +597,16 @@ class AIMessageChunk(AIMessage, BaseMessageChunk):
389
597
  def add_ai_message_chunks(
390
598
  left: AIMessageChunk, *others: AIMessageChunk
391
599
  ) -> AIMessageChunk:
392
- """Add multiple AIMessageChunks together."""
393
- if any(left.example != o.example for o in others):
394
- msg = "Cannot concatenate AIMessageChunks with different example values."
395
- raise ValueError(msg)
600
+ """Add multiple `AIMessageChunk`s together.
601
+
602
+ Args:
603
+ left: The first `AIMessageChunk`.
604
+ *others: Other `AIMessageChunk`s to add.
396
605
 
606
+ Returns:
607
+ The resulting `AIMessageChunk`.
608
+
609
+ """
397
610
  content = merge_content(left.content, *(o.content for o in others))
398
611
  additional_kwargs = merge_dicts(
399
612
  left.additional_kwargs, *(o.additional_kwargs for o in others)
@@ -420,7 +633,7 @@ def add_ai_message_chunks(
420
633
 
421
634
  # Token usage
422
635
  if left.usage_metadata or any(o.usage_metadata is not None for o in others):
423
- usage_metadata: Optional[UsageMetadata] = left.usage_metadata
636
+ usage_metadata: UsageMetadata | None = left.usage_metadata
424
637
  for other in others:
425
638
  usage_metadata = add_usage(usage_metadata, other.usage_metadata)
426
639
  else:
@@ -432,71 +645,79 @@ def add_ai_message_chunks(
432
645
  for id_ in candidates:
433
646
  if (
434
647
  id_
435
- and not id_.startswith(_LC_ID_PREFIX)
436
- and not id_.startswith(_LC_AUTO_PREFIX)
648
+ and not id_.startswith(LC_ID_PREFIX)
649
+ and not id_.startswith(LC_AUTO_PREFIX)
437
650
  ):
438
651
  chunk_id = id_
439
652
  break
440
653
  else:
441
- # second pass: prefer lc_* ids over run-* ids
654
+ # second pass: prefer lc_run-* ids over lc_* ids
442
655
  for id_ in candidates:
443
- if id_ and id_.startswith(_LC_AUTO_PREFIX):
656
+ if id_ and id_.startswith(LC_ID_PREFIX):
444
657
  chunk_id = id_
445
658
  break
446
659
  else:
447
- # third pass: take any remaining id (run-* ids)
660
+ # third pass: take any remaining id (auto-generated lc_* ids)
448
661
  for id_ in candidates:
449
662
  if id_:
450
663
  chunk_id = id_
451
664
  break
452
665
 
666
+ chunk_position: Literal["last"] | None = (
667
+ "last" if any(x.chunk_position == "last" for x in [left, *others]) else None
668
+ )
669
+
453
670
  return left.__class__(
454
- example=left.example,
455
671
  content=content,
456
672
  additional_kwargs=additional_kwargs,
457
673
  tool_call_chunks=tool_call_chunks,
458
674
  response_metadata=response_metadata,
459
675
  usage_metadata=usage_metadata,
460
676
  id=chunk_id,
677
+ chunk_position=chunk_position,
461
678
  )
462
679
 
463
680
 
464
- def add_usage(
465
- left: Optional[UsageMetadata], right: Optional[UsageMetadata]
466
- ) -> UsageMetadata:
681
+ def add_usage(left: UsageMetadata | None, right: UsageMetadata | None) -> UsageMetadata:
467
682
  """Recursively add two UsageMetadata objects.
468
683
 
469
684
  Example:
470
- .. code-block:: python
471
-
472
- from langchain_core.messages.ai import add_usage
473
-
474
- left = UsageMetadata(
475
- input_tokens=5,
476
- output_tokens=0,
477
- total_tokens=5,
478
- input_token_details=InputTokenDetails(cache_read=3)
479
- )
480
- right = UsageMetadata(
481
- input_tokens=0,
482
- output_tokens=10,
483
- total_tokens=10,
484
- output_token_details=OutputTokenDetails(reasoning=4)
485
- )
685
+ ```python
686
+ from langchain_core.messages.ai import add_usage
687
+
688
+ left = UsageMetadata(
689
+ input_tokens=5,
690
+ output_tokens=0,
691
+ total_tokens=5,
692
+ input_token_details=InputTokenDetails(cache_read=3),
693
+ )
694
+ right = UsageMetadata(
695
+ input_tokens=0,
696
+ output_tokens=10,
697
+ total_tokens=10,
698
+ output_token_details=OutputTokenDetails(reasoning=4),
699
+ )
486
700
 
487
- add_usage(left, right)
701
+ add_usage(left, right)
702
+ ```
488
703
 
489
704
  results in
490
705
 
491
- .. code-block:: python
706
+ ```python
707
+ UsageMetadata(
708
+ input_tokens=5,
709
+ output_tokens=10,
710
+ total_tokens=15,
711
+ input_token_details=InputTokenDetails(cache_read=3),
712
+ output_token_details=OutputTokenDetails(reasoning=4),
713
+ )
714
+ ```
715
+ Args:
716
+ left: The first `UsageMetadata` object.
717
+ right: The second `UsageMetadata` object.
492
718
 
493
- UsageMetadata(
494
- input_tokens=5,
495
- output_tokens=10,
496
- total_tokens=15,
497
- input_token_details=InputTokenDetails(cache_read=3),
498
- output_token_details=OutputTokenDetails(reasoning=4)
499
- )
719
+ Returns:
720
+ The sum of the two `UsageMetadata` objects.
500
721
 
501
722
  """
502
723
  if not (left or right):
@@ -517,43 +738,49 @@ def add_usage(
517
738
 
518
739
 
519
740
  def subtract_usage(
520
- left: Optional[UsageMetadata], right: Optional[UsageMetadata]
741
+ left: UsageMetadata | None, right: UsageMetadata | None
521
742
  ) -> UsageMetadata:
522
- """Recursively subtract two UsageMetadata objects.
743
+ """Recursively subtract two `UsageMetadata` objects.
523
744
 
524
- Token counts cannot be negative so the actual operation is max(left - right, 0).
745
+ Token counts cannot be negative so the actual operation is `max(left - right, 0)`.
525
746
 
526
747
  Example:
527
- .. code-block:: python
528
-
529
- from langchain_core.messages.ai import subtract_usage
530
-
531
- left = UsageMetadata(
532
- input_tokens=5,
533
- output_tokens=10,
534
- total_tokens=15,
535
- input_token_details=InputTokenDetails(cache_read=4)
536
- )
537
- right = UsageMetadata(
538
- input_tokens=3,
539
- output_tokens=8,
540
- total_tokens=11,
541
- output_token_details=OutputTokenDetails(reasoning=4)
542
- )
748
+ ```python
749
+ from langchain_core.messages.ai import subtract_usage
750
+
751
+ left = UsageMetadata(
752
+ input_tokens=5,
753
+ output_tokens=10,
754
+ total_tokens=15,
755
+ input_token_details=InputTokenDetails(cache_read=4),
756
+ )
757
+ right = UsageMetadata(
758
+ input_tokens=3,
759
+ output_tokens=8,
760
+ total_tokens=11,
761
+ output_token_details=OutputTokenDetails(reasoning=4),
762
+ )
543
763
 
544
- subtract_usage(left, right)
764
+ subtract_usage(left, right)
765
+ ```
545
766
 
546
767
  results in
547
768
 
548
- .. code-block:: python
769
+ ```python
770
+ UsageMetadata(
771
+ input_tokens=2,
772
+ output_tokens=2,
773
+ total_tokens=4,
774
+ input_token_details=InputTokenDetails(cache_read=4),
775
+ output_token_details=OutputTokenDetails(reasoning=0),
776
+ )
777
+ ```
778
+ Args:
779
+ left: The first `UsageMetadata` object.
780
+ right: The second `UsageMetadata` object.
549
781
 
550
- UsageMetadata(
551
- input_tokens=2,
552
- output_tokens=2,
553
- total_tokens=4,
554
- input_token_details=InputTokenDetails(cache_read=4),
555
- output_token_details=OutputTokenDetails(reasoning=0)
556
- )
782
+ Returns:
783
+ The resulting `UsageMetadata` after subtraction.
557
784
 
558
785
  """
559
786
  if not (left or right):