langchain-core 1.0.0a5__py3-none-any.whl → 1.0.3__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.
- langchain_core/__init__.py +1 -1
- langchain_core/_api/__init__.py +3 -4
- langchain_core/_api/beta_decorator.py +23 -26
- langchain_core/_api/deprecation.py +51 -64
- langchain_core/_api/path.py +3 -6
- langchain_core/_import_utils.py +3 -4
- langchain_core/agents.py +20 -22
- langchain_core/caches.py +65 -66
- langchain_core/callbacks/__init__.py +1 -8
- langchain_core/callbacks/base.py +321 -336
- langchain_core/callbacks/file.py +44 -44
- langchain_core/callbacks/manager.py +436 -513
- langchain_core/callbacks/stdout.py +29 -30
- langchain_core/callbacks/streaming_stdout.py +32 -32
- langchain_core/callbacks/usage.py +60 -57
- langchain_core/chat_history.py +53 -68
- langchain_core/document_loaders/base.py +27 -25
- langchain_core/document_loaders/blob_loaders.py +1 -1
- langchain_core/document_loaders/langsmith.py +44 -48
- langchain_core/documents/__init__.py +23 -3
- langchain_core/documents/base.py +98 -90
- langchain_core/documents/compressor.py +10 -10
- langchain_core/documents/transformers.py +34 -35
- langchain_core/embeddings/fake.py +50 -54
- langchain_core/example_selectors/length_based.py +1 -1
- langchain_core/example_selectors/semantic_similarity.py +28 -32
- langchain_core/exceptions.py +21 -20
- langchain_core/globals.py +3 -151
- langchain_core/indexing/__init__.py +1 -1
- langchain_core/indexing/api.py +121 -126
- langchain_core/indexing/base.py +73 -75
- langchain_core/indexing/in_memory.py +4 -6
- langchain_core/language_models/__init__.py +14 -29
- langchain_core/language_models/_utils.py +58 -61
- langchain_core/language_models/base.py +53 -162
- langchain_core/language_models/chat_models.py +298 -387
- langchain_core/language_models/fake.py +11 -11
- langchain_core/language_models/fake_chat_models.py +42 -36
- langchain_core/language_models/llms.py +125 -235
- langchain_core/load/dump.py +9 -12
- langchain_core/load/load.py +18 -28
- langchain_core/load/mapping.py +2 -4
- langchain_core/load/serializable.py +42 -40
- langchain_core/messages/__init__.py +10 -16
- langchain_core/messages/ai.py +148 -148
- langchain_core/messages/base.py +58 -52
- langchain_core/messages/block_translators/__init__.py +27 -17
- langchain_core/messages/block_translators/anthropic.py +6 -6
- langchain_core/messages/block_translators/bedrock_converse.py +5 -5
- langchain_core/messages/block_translators/google_genai.py +505 -20
- langchain_core/messages/block_translators/google_vertexai.py +4 -32
- langchain_core/messages/block_translators/groq.py +117 -21
- langchain_core/messages/block_translators/langchain_v0.py +5 -5
- langchain_core/messages/block_translators/openai.py +11 -11
- langchain_core/messages/chat.py +2 -6
- langchain_core/messages/content.py +337 -328
- langchain_core/messages/function.py +6 -10
- langchain_core/messages/human.py +24 -31
- langchain_core/messages/modifier.py +2 -2
- langchain_core/messages/system.py +19 -29
- langchain_core/messages/tool.py +74 -90
- langchain_core/messages/utils.py +474 -504
- langchain_core/output_parsers/__init__.py +13 -10
- langchain_core/output_parsers/base.py +61 -61
- langchain_core/output_parsers/format_instructions.py +9 -4
- langchain_core/output_parsers/json.py +12 -10
- langchain_core/output_parsers/list.py +21 -23
- langchain_core/output_parsers/openai_functions.py +49 -47
- langchain_core/output_parsers/openai_tools.py +16 -21
- langchain_core/output_parsers/pydantic.py +13 -14
- langchain_core/output_parsers/string.py +5 -5
- langchain_core/output_parsers/transform.py +15 -17
- langchain_core/output_parsers/xml.py +35 -34
- langchain_core/outputs/__init__.py +1 -1
- langchain_core/outputs/chat_generation.py +18 -18
- langchain_core/outputs/chat_result.py +1 -3
- langchain_core/outputs/generation.py +10 -11
- langchain_core/outputs/llm_result.py +10 -10
- langchain_core/prompt_values.py +11 -17
- langchain_core/prompts/__init__.py +3 -27
- langchain_core/prompts/base.py +48 -56
- langchain_core/prompts/chat.py +275 -325
- langchain_core/prompts/dict.py +5 -5
- langchain_core/prompts/few_shot.py +81 -88
- langchain_core/prompts/few_shot_with_templates.py +11 -13
- langchain_core/prompts/image.py +12 -14
- langchain_core/prompts/loading.py +4 -6
- langchain_core/prompts/message.py +3 -3
- langchain_core/prompts/prompt.py +24 -39
- langchain_core/prompts/string.py +26 -10
- langchain_core/prompts/structured.py +49 -53
- langchain_core/rate_limiters.py +51 -60
- langchain_core/retrievers.py +61 -198
- langchain_core/runnables/base.py +1478 -1630
- langchain_core/runnables/branch.py +53 -57
- langchain_core/runnables/config.py +72 -89
- langchain_core/runnables/configurable.py +120 -137
- langchain_core/runnables/fallbacks.py +83 -79
- langchain_core/runnables/graph.py +91 -97
- langchain_core/runnables/graph_ascii.py +27 -28
- langchain_core/runnables/graph_mermaid.py +38 -50
- langchain_core/runnables/graph_png.py +15 -16
- langchain_core/runnables/history.py +135 -148
- langchain_core/runnables/passthrough.py +124 -150
- langchain_core/runnables/retry.py +46 -51
- langchain_core/runnables/router.py +25 -30
- langchain_core/runnables/schema.py +75 -80
- langchain_core/runnables/utils.py +60 -67
- langchain_core/stores.py +85 -121
- langchain_core/structured_query.py +8 -8
- langchain_core/sys_info.py +27 -29
- langchain_core/tools/__init__.py +1 -14
- langchain_core/tools/base.py +285 -229
- langchain_core/tools/convert.py +160 -155
- langchain_core/tools/render.py +10 -10
- langchain_core/tools/retriever.py +12 -11
- langchain_core/tools/simple.py +19 -24
- langchain_core/tools/structured.py +32 -39
- langchain_core/tracers/__init__.py +1 -9
- langchain_core/tracers/base.py +97 -99
- langchain_core/tracers/context.py +29 -52
- langchain_core/tracers/core.py +49 -53
- langchain_core/tracers/evaluation.py +11 -11
- langchain_core/tracers/event_stream.py +65 -64
- langchain_core/tracers/langchain.py +21 -21
- langchain_core/tracers/log_stream.py +45 -45
- langchain_core/tracers/memory_stream.py +3 -3
- langchain_core/tracers/root_listeners.py +16 -16
- langchain_core/tracers/run_collector.py +2 -4
- langchain_core/tracers/schemas.py +0 -129
- langchain_core/tracers/stdout.py +3 -3
- langchain_core/utils/__init__.py +1 -4
- langchain_core/utils/_merge.py +2 -2
- langchain_core/utils/aiter.py +57 -61
- langchain_core/utils/env.py +9 -9
- langchain_core/utils/function_calling.py +89 -186
- langchain_core/utils/html.py +7 -8
- langchain_core/utils/input.py +6 -6
- langchain_core/utils/interactive_env.py +1 -1
- langchain_core/utils/iter.py +36 -40
- langchain_core/utils/json.py +4 -3
- langchain_core/utils/json_schema.py +9 -9
- langchain_core/utils/mustache.py +8 -10
- langchain_core/utils/pydantic.py +33 -35
- langchain_core/utils/strings.py +6 -9
- langchain_core/utils/usage.py +1 -1
- langchain_core/utils/utils.py +66 -62
- langchain_core/vectorstores/base.py +182 -216
- langchain_core/vectorstores/in_memory.py +101 -176
- langchain_core/vectorstores/utils.py +5 -5
- langchain_core/version.py +1 -1
- langchain_core-1.0.3.dist-info/METADATA +69 -0
- langchain_core-1.0.3.dist-info/RECORD +172 -0
- {langchain_core-1.0.0a5.dist-info → langchain_core-1.0.3.dist-info}/WHEEL +1 -1
- langchain_core/memory.py +0 -120
- langchain_core/messages/block_translators/ollama.py +0 -47
- langchain_core/prompts/pipeline.py +0 -138
- langchain_core/pydantic_v1/__init__.py +0 -30
- langchain_core/pydantic_v1/dataclasses.py +0 -23
- langchain_core/pydantic_v1/main.py +0 -23
- langchain_core/tracers/langchain_v1.py +0 -31
- langchain_core/utils/loading.py +0 -35
- langchain_core-1.0.0a5.dist-info/METADATA +0 -77
- langchain_core-1.0.0a5.dist-info/RECORD +0 -181
- langchain_core-1.0.0a5.dist-info/entry_points.txt +0 -4
langchain_core/messages/ai.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
import operator
|
|
6
6
|
from collections.abc import Sequence
|
|
7
|
-
from typing import Any, Literal,
|
|
7
|
+
from typing import Any, Literal, cast, overload
|
|
8
8
|
|
|
9
9
|
from pydantic import model_validator
|
|
10
10
|
from typing_extensions import NotRequired, Self, TypedDict, override
|
|
@@ -40,18 +40,18 @@ class InputTokenDetails(TypedDict, total=False):
|
|
|
40
40
|
Does *not* need to sum to full input token count. Does *not* need to have all keys.
|
|
41
41
|
|
|
42
42
|
Example:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.. versionadded:: 0.3.9
|
|
43
|
+
```python
|
|
44
|
+
{
|
|
45
|
+
"audio": 10,
|
|
46
|
+
"cache_creation": 200,
|
|
47
|
+
"cache_read": 100,
|
|
48
|
+
}
|
|
49
|
+
```
|
|
52
50
|
|
|
53
51
|
May also hold extra provider-specific keys.
|
|
54
52
|
|
|
53
|
+
!!! version-added "Added in version 0.3.9"
|
|
54
|
+
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
57
|
audio: int
|
|
@@ -76,14 +76,16 @@ class OutputTokenDetails(TypedDict, total=False):
|
|
|
76
76
|
Does *not* need to sum to full output token count. Does *not* need to have all keys.
|
|
77
77
|
|
|
78
78
|
Example:
|
|
79
|
-
|
|
79
|
+
```python
|
|
80
|
+
{
|
|
81
|
+
"audio": 10,
|
|
82
|
+
"reasoning": 200,
|
|
83
|
+
}
|
|
84
|
+
```
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
"audio": 10,
|
|
83
|
-
"reasoning": 200,
|
|
84
|
-
}
|
|
86
|
+
May also hold extra provider-specific keys.
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
!!! version-added "Added in version 0.3.9"
|
|
87
89
|
|
|
88
90
|
"""
|
|
89
91
|
|
|
@@ -104,27 +106,30 @@ class UsageMetadata(TypedDict):
|
|
|
104
106
|
This is a standard representation of token usage that is consistent across models.
|
|
105
107
|
|
|
106
108
|
Example:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.. versionchanged:: 0.3.9
|
|
109
|
+
```python
|
|
110
|
+
{
|
|
111
|
+
"input_tokens": 350,
|
|
112
|
+
"output_tokens": 240,
|
|
113
|
+
"total_tokens": 590,
|
|
114
|
+
"input_token_details": {
|
|
115
|
+
"audio": 10,
|
|
116
|
+
"cache_creation": 200,
|
|
117
|
+
"cache_read": 100,
|
|
118
|
+
},
|
|
119
|
+
"output_token_details": {
|
|
120
|
+
"audio": 10,
|
|
121
|
+
"reasoning": 200,
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
!!! warning "Behavior changed in 0.3.9"
|
|
127
|
+
Added `input_token_details` and `output_token_details`.
|
|
127
128
|
|
|
129
|
+
!!! note "LangSmith SDK"
|
|
130
|
+
The LangSmith SDK also has a `UsageMetadata` class. While the two share fields,
|
|
131
|
+
LangSmith's `UsageMetadata` has additional fields to capture cost information
|
|
132
|
+
used by the LangSmith platform.
|
|
128
133
|
"""
|
|
129
134
|
|
|
130
135
|
input_tokens: int
|
|
@@ -132,7 +137,7 @@ class UsageMetadata(TypedDict):
|
|
|
132
137
|
output_tokens: int
|
|
133
138
|
"""Count of output (or completion) tokens. Sum of all output token types."""
|
|
134
139
|
total_tokens: int
|
|
135
|
-
"""Total token count. Sum of input_tokens + output_tokens
|
|
140
|
+
"""Total token count. Sum of `input_tokens` + `output_tokens`."""
|
|
136
141
|
input_token_details: NotRequired[InputTokenDetails]
|
|
137
142
|
"""Breakdown of input token counts.
|
|
138
143
|
|
|
@@ -142,64 +147,61 @@ class UsageMetadata(TypedDict):
|
|
|
142
147
|
"""Breakdown of output token counts.
|
|
143
148
|
|
|
144
149
|
Does *not* need to sum to full output token count. Does *not* need to have all keys.
|
|
145
|
-
|
|
146
150
|
"""
|
|
147
151
|
|
|
148
152
|
|
|
149
153
|
class AIMessage(BaseMessage):
|
|
150
154
|
"""Message from an AI.
|
|
151
155
|
|
|
152
|
-
AIMessage is returned from a chat model as a response to a prompt.
|
|
156
|
+
An `AIMessage` is returned from a chat model as a response to a prompt.
|
|
153
157
|
|
|
154
158
|
This message represents the output of the model and consists of both
|
|
155
|
-
the raw output as returned by the model
|
|
159
|
+
the raw output as returned by the model and standardized fields
|
|
156
160
|
(e.g., tool calls, usage metadata) added by the LangChain framework.
|
|
157
|
-
|
|
158
161
|
"""
|
|
159
162
|
|
|
160
163
|
tool_calls: list[ToolCall] = []
|
|
161
|
-
"""If
|
|
164
|
+
"""If present, tool calls associated with the message."""
|
|
162
165
|
invalid_tool_calls: list[InvalidToolCall] = []
|
|
163
|
-
"""If
|
|
164
|
-
usage_metadata:
|
|
165
|
-
"""If
|
|
166
|
+
"""If present, tool calls with parsing errors associated with the message."""
|
|
167
|
+
usage_metadata: UsageMetadata | None = None
|
|
168
|
+
"""If present, usage metadata for a message, such as token counts.
|
|
166
169
|
|
|
167
170
|
This is a standard representation of token usage that is consistent across models.
|
|
168
|
-
|
|
169
171
|
"""
|
|
170
172
|
|
|
171
173
|
type: Literal["ai"] = "ai"
|
|
172
|
-
"""The type of the message (used for deserialization).
|
|
174
|
+
"""The type of the message (used for deserialization)."""
|
|
173
175
|
|
|
174
176
|
@overload
|
|
175
177
|
def __init__(
|
|
176
178
|
self,
|
|
177
|
-
content:
|
|
179
|
+
content: str | list[str | dict],
|
|
178
180
|
**kwargs: Any,
|
|
179
181
|
) -> None: ...
|
|
180
182
|
|
|
181
183
|
@overload
|
|
182
184
|
def __init__(
|
|
183
185
|
self,
|
|
184
|
-
content:
|
|
185
|
-
content_blocks:
|
|
186
|
+
content: str | list[str | dict] | None = None,
|
|
187
|
+
content_blocks: list[types.ContentBlock] | None = None,
|
|
186
188
|
**kwargs: Any,
|
|
187
189
|
) -> None: ...
|
|
188
190
|
|
|
189
191
|
def __init__(
|
|
190
192
|
self,
|
|
191
|
-
content:
|
|
192
|
-
content_blocks:
|
|
193
|
+
content: str | list[str | dict] | None = None,
|
|
194
|
+
content_blocks: list[types.ContentBlock] | None = None,
|
|
193
195
|
**kwargs: Any,
|
|
194
196
|
) -> None:
|
|
195
|
-
"""Initialize
|
|
197
|
+
"""Initialize an `AIMessage`.
|
|
196
198
|
|
|
197
|
-
Specify
|
|
199
|
+
Specify `content` as positional arg or `content_blocks` for typing.
|
|
198
200
|
|
|
199
201
|
Args:
|
|
200
202
|
content: The content of the message.
|
|
201
203
|
content_blocks: Typed standard content.
|
|
202
|
-
kwargs: Additional arguments to pass to the parent class.
|
|
204
|
+
**kwargs: Additional arguments to pass to the parent class.
|
|
203
205
|
"""
|
|
204
206
|
if content_blocks is not None:
|
|
205
207
|
# If there are tool calls in content_blocks, but not in tool_calls, add them
|
|
@@ -210,7 +212,7 @@ class AIMessage(BaseMessage):
|
|
|
210
212
|
kwargs["tool_calls"] = content_tool_calls
|
|
211
213
|
|
|
212
214
|
super().__init__(
|
|
213
|
-
content=cast("
|
|
215
|
+
content=cast("str | list[str | dict]", content_blocks),
|
|
214
216
|
**kwargs,
|
|
215
217
|
)
|
|
216
218
|
else:
|
|
@@ -218,7 +220,11 @@ class AIMessage(BaseMessage):
|
|
|
218
220
|
|
|
219
221
|
@property
|
|
220
222
|
def lc_attributes(self) -> dict:
|
|
221
|
-
"""
|
|
223
|
+
"""Attributes to be serialized.
|
|
224
|
+
|
|
225
|
+
Includes all attributes, even if they are derived from other initialization
|
|
226
|
+
arguments.
|
|
227
|
+
"""
|
|
222
228
|
return {
|
|
223
229
|
"tool_calls": self.tool_calls,
|
|
224
230
|
"invalid_tool_calls": self.invalid_tool_calls,
|
|
@@ -226,11 +232,11 @@ class AIMessage(BaseMessage):
|
|
|
226
232
|
|
|
227
233
|
@property
|
|
228
234
|
def content_blocks(self) -> list[types.ContentBlock]:
|
|
229
|
-
"""Return
|
|
235
|
+
"""Return standard, typed `ContentBlock` dicts from the message.
|
|
230
236
|
|
|
231
237
|
If the message has a known model provider, use the provider-specific translator
|
|
232
238
|
first before falling back to best-effort parsing. For details, see the property
|
|
233
|
-
on
|
|
239
|
+
on `BaseMessage`.
|
|
234
240
|
"""
|
|
235
241
|
if self.response_metadata.get("output_version") == "v1":
|
|
236
242
|
return cast("list[types.ContentBlock]", self.content)
|
|
@@ -332,11 +338,10 @@ class AIMessage(BaseMessage):
|
|
|
332
338
|
|
|
333
339
|
@override
|
|
334
340
|
def pretty_repr(self, html: bool = False) -> str:
|
|
335
|
-
"""Return a pretty representation of the message.
|
|
341
|
+
"""Return a pretty representation of the message for display.
|
|
336
342
|
|
|
337
343
|
Args:
|
|
338
344
|
html: Whether to return an HTML-formatted string.
|
|
339
|
-
Defaults to False.
|
|
340
345
|
|
|
341
346
|
Returns:
|
|
342
347
|
A pretty representation of the message.
|
|
@@ -345,7 +350,7 @@ class AIMessage(BaseMessage):
|
|
|
345
350
|
base = super().pretty_repr(html=html)
|
|
346
351
|
lines = []
|
|
347
352
|
|
|
348
|
-
def _format_tool_args(tc:
|
|
353
|
+
def _format_tool_args(tc: ToolCall | InvalidToolCall) -> list[str]:
|
|
349
354
|
lines = [
|
|
350
355
|
f" {tc.get('name', 'Tool')} ({tc.get('id')})",
|
|
351
356
|
f" Call ID: {tc.get('id')}",
|
|
@@ -373,31 +378,27 @@ class AIMessage(BaseMessage):
|
|
|
373
378
|
|
|
374
379
|
|
|
375
380
|
class AIMessageChunk(AIMessage, BaseMessageChunk):
|
|
376
|
-
"""Message chunk from an AI."""
|
|
381
|
+
"""Message chunk from an AI (yielded when streaming)."""
|
|
377
382
|
|
|
378
383
|
# Ignoring mypy re-assignment here since we're overriding the value
|
|
379
384
|
# to make sure that the chunk variant can be discriminated from the
|
|
380
385
|
# non-chunk variant.
|
|
381
386
|
type: Literal["AIMessageChunk"] = "AIMessageChunk" # type: ignore[assignment]
|
|
382
|
-
"""The type of the message (used for deserialization).
|
|
383
|
-
|
|
384
|
-
Defaults to ``AIMessageChunk``.
|
|
385
|
-
|
|
386
|
-
"""
|
|
387
|
+
"""The type of the message (used for deserialization)."""
|
|
387
388
|
|
|
388
389
|
tool_call_chunks: list[ToolCallChunk] = []
|
|
389
390
|
"""If provided, tool call chunks associated with the message."""
|
|
390
391
|
|
|
391
|
-
chunk_position:
|
|
392
|
-
"""Optional span represented by an aggregated AIMessageChunk
|
|
392
|
+
chunk_position: Literal["last"] | None = None
|
|
393
|
+
"""Optional span represented by an aggregated `AIMessageChunk`.
|
|
393
394
|
|
|
394
|
-
If a chunk with
|
|
395
|
-
|
|
395
|
+
If a chunk with `chunk_position="last"` is aggregated into a stream,
|
|
396
|
+
`tool_call_chunks` in message content will be parsed into `tool_calls`.
|
|
396
397
|
"""
|
|
397
398
|
|
|
398
399
|
@property
|
|
399
400
|
def lc_attributes(self) -> dict:
|
|
400
|
-
"""
|
|
401
|
+
"""Attributes to be serialized, even if they are derived from other initialization args.""" # noqa: E501
|
|
401
402
|
return {
|
|
402
403
|
"tool_calls": self.tool_calls,
|
|
403
404
|
"invalid_tool_calls": self.invalid_tool_calls,
|
|
@@ -405,7 +406,7 @@ class AIMessageChunk(AIMessage, BaseMessageChunk):
|
|
|
405
406
|
|
|
406
407
|
@property
|
|
407
408
|
def content_blocks(self) -> list[types.ContentBlock]:
|
|
408
|
-
"""Return
|
|
409
|
+
"""Return standard, typed `ContentBlock` dicts from the message."""
|
|
409
410
|
if self.response_metadata.get("output_version") == "v1":
|
|
410
411
|
return cast("list[types.ContentBlock]", self.content)
|
|
411
412
|
|
|
@@ -546,12 +547,15 @@ class AIMessageChunk(AIMessage, BaseMessageChunk):
|
|
|
546
547
|
and call_id in id_to_tc
|
|
547
548
|
):
|
|
548
549
|
self.content[idx] = cast("dict[str, Any]", id_to_tc[call_id])
|
|
550
|
+
if "extras" in block:
|
|
551
|
+
# mypy does not account for instance check for dict above
|
|
552
|
+
self.content[idx]["extras"] = block["extras"] # type: ignore[index]
|
|
549
553
|
|
|
550
554
|
return self
|
|
551
555
|
|
|
552
556
|
@model_validator(mode="after")
|
|
553
557
|
def init_server_tool_calls(self) -> Self:
|
|
554
|
-
"""Parse server_tool_call_chunks
|
|
558
|
+
"""Parse `server_tool_call_chunks`."""
|
|
555
559
|
if (
|
|
556
560
|
self.chunk_position == "last"
|
|
557
561
|
and self.response_metadata.get("output_version") == "v1"
|
|
@@ -597,14 +601,14 @@ class AIMessageChunk(AIMessage, BaseMessageChunk):
|
|
|
597
601
|
def add_ai_message_chunks(
|
|
598
602
|
left: AIMessageChunk, *others: AIMessageChunk
|
|
599
603
|
) -> AIMessageChunk:
|
|
600
|
-
"""Add multiple
|
|
604
|
+
"""Add multiple `AIMessageChunk`s together.
|
|
601
605
|
|
|
602
606
|
Args:
|
|
603
|
-
left: The first
|
|
604
|
-
*others: Other
|
|
607
|
+
left: The first `AIMessageChunk`.
|
|
608
|
+
*others: Other `AIMessageChunk`s to add.
|
|
605
609
|
|
|
606
610
|
Returns:
|
|
607
|
-
The resulting
|
|
611
|
+
The resulting `AIMessageChunk`.
|
|
608
612
|
|
|
609
613
|
"""
|
|
610
614
|
content = merge_content(left.content, *(o.content for o in others))
|
|
@@ -633,7 +637,7 @@ def add_ai_message_chunks(
|
|
|
633
637
|
|
|
634
638
|
# Token usage
|
|
635
639
|
if left.usage_metadata or any(o.usage_metadata is not None for o in others):
|
|
636
|
-
usage_metadata:
|
|
640
|
+
usage_metadata: UsageMetadata | None = left.usage_metadata
|
|
637
641
|
for other in others:
|
|
638
642
|
usage_metadata = add_usage(usage_metadata, other.usage_metadata)
|
|
639
643
|
else:
|
|
@@ -651,19 +655,19 @@ def add_ai_message_chunks(
|
|
|
651
655
|
chunk_id = id_
|
|
652
656
|
break
|
|
653
657
|
else:
|
|
654
|
-
# second pass: prefer lc_run-*
|
|
658
|
+
# second pass: prefer lc_run-* IDs over lc_* IDs
|
|
655
659
|
for id_ in candidates:
|
|
656
660
|
if id_ and id_.startswith(LC_ID_PREFIX):
|
|
657
661
|
chunk_id = id_
|
|
658
662
|
break
|
|
659
663
|
else:
|
|
660
|
-
# third pass: take any remaining
|
|
664
|
+
# third pass: take any remaining ID (auto-generated lc_* IDs)
|
|
661
665
|
for id_ in candidates:
|
|
662
666
|
if id_:
|
|
663
667
|
chunk_id = id_
|
|
664
668
|
break
|
|
665
669
|
|
|
666
|
-
chunk_position:
|
|
670
|
+
chunk_position: Literal["last"] | None = (
|
|
667
671
|
"last" if any(x.chunk_position == "last" for x in [left, *others]) else None
|
|
668
672
|
)
|
|
669
673
|
|
|
@@ -678,49 +682,46 @@ def add_ai_message_chunks(
|
|
|
678
682
|
)
|
|
679
683
|
|
|
680
684
|
|
|
681
|
-
def add_usage(
|
|
682
|
-
left: Optional[UsageMetadata], right: Optional[UsageMetadata]
|
|
683
|
-
) -> UsageMetadata:
|
|
685
|
+
def add_usage(left: UsageMetadata | None, right: UsageMetadata | None) -> UsageMetadata:
|
|
684
686
|
"""Recursively add two UsageMetadata objects.
|
|
685
687
|
|
|
686
688
|
Example:
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
)
|
|
689
|
+
```python
|
|
690
|
+
from langchain_core.messages.ai import add_usage
|
|
691
|
+
|
|
692
|
+
left = UsageMetadata(
|
|
693
|
+
input_tokens=5,
|
|
694
|
+
output_tokens=0,
|
|
695
|
+
total_tokens=5,
|
|
696
|
+
input_token_details=InputTokenDetails(cache_read=3),
|
|
697
|
+
)
|
|
698
|
+
right = UsageMetadata(
|
|
699
|
+
input_tokens=0,
|
|
700
|
+
output_tokens=10,
|
|
701
|
+
total_tokens=10,
|
|
702
|
+
output_token_details=OutputTokenDetails(reasoning=4),
|
|
703
|
+
)
|
|
703
704
|
|
|
704
|
-
|
|
705
|
+
add_usage(left, right)
|
|
706
|
+
```
|
|
705
707
|
|
|
706
708
|
results in
|
|
707
709
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
710
|
+
```python
|
|
711
|
+
UsageMetadata(
|
|
712
|
+
input_tokens=5,
|
|
713
|
+
output_tokens=10,
|
|
714
|
+
total_tokens=15,
|
|
715
|
+
input_token_details=InputTokenDetails(cache_read=3),
|
|
716
|
+
output_token_details=OutputTokenDetails(reasoning=4),
|
|
717
|
+
)
|
|
718
|
+
```
|
|
718
719
|
Args:
|
|
719
|
-
left: The first
|
|
720
|
-
right: The second
|
|
720
|
+
left: The first `UsageMetadata` object.
|
|
721
|
+
right: The second `UsageMetadata` object.
|
|
721
722
|
|
|
722
723
|
Returns:
|
|
723
|
-
The sum of the two
|
|
724
|
+
The sum of the two `UsageMetadata` objects.
|
|
724
725
|
|
|
725
726
|
"""
|
|
726
727
|
if not (left or right):
|
|
@@ -741,50 +742,49 @@ def add_usage(
|
|
|
741
742
|
|
|
742
743
|
|
|
743
744
|
def subtract_usage(
|
|
744
|
-
left:
|
|
745
|
+
left: UsageMetadata | None, right: UsageMetadata | None
|
|
745
746
|
) -> UsageMetadata:
|
|
746
|
-
"""Recursively subtract two
|
|
747
|
+
"""Recursively subtract two `UsageMetadata` objects.
|
|
747
748
|
|
|
748
|
-
Token counts cannot be negative so the actual operation is
|
|
749
|
+
Token counts cannot be negative so the actual operation is `max(left - right, 0)`.
|
|
749
750
|
|
|
750
751
|
Example:
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
)
|
|
752
|
+
```python
|
|
753
|
+
from langchain_core.messages.ai import subtract_usage
|
|
754
|
+
|
|
755
|
+
left = UsageMetadata(
|
|
756
|
+
input_tokens=5,
|
|
757
|
+
output_tokens=10,
|
|
758
|
+
total_tokens=15,
|
|
759
|
+
input_token_details=InputTokenDetails(cache_read=4),
|
|
760
|
+
)
|
|
761
|
+
right = UsageMetadata(
|
|
762
|
+
input_tokens=3,
|
|
763
|
+
output_tokens=8,
|
|
764
|
+
total_tokens=11,
|
|
765
|
+
output_token_details=OutputTokenDetails(reasoning=4),
|
|
766
|
+
)
|
|
767
767
|
|
|
768
|
-
|
|
768
|
+
subtract_usage(left, right)
|
|
769
|
+
```
|
|
769
770
|
|
|
770
771
|
results in
|
|
771
772
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
773
|
+
```python
|
|
774
|
+
UsageMetadata(
|
|
775
|
+
input_tokens=2,
|
|
776
|
+
output_tokens=2,
|
|
777
|
+
total_tokens=4,
|
|
778
|
+
input_token_details=InputTokenDetails(cache_read=4),
|
|
779
|
+
output_token_details=OutputTokenDetails(reasoning=0),
|
|
780
|
+
)
|
|
781
|
+
```
|
|
782
782
|
Args:
|
|
783
|
-
left: The first
|
|
784
|
-
right: The second
|
|
783
|
+
left: The first `UsageMetadata` object.
|
|
784
|
+
right: The second `UsageMetadata` object.
|
|
785
785
|
|
|
786
786
|
Returns:
|
|
787
|
-
The resulting
|
|
787
|
+
The resulting `UsageMetadata` after subtraction.
|
|
788
788
|
|
|
789
789
|
"""
|
|
790
790
|
if not (left or right):
|