langchain-core 0.3.79__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.
- 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 +52 -65
- langchain_core/_api/path.py +3 -6
- langchain_core/_import_utils.py +3 -4
- langchain_core/agents.py +19 -19
- langchain_core/caches.py +53 -63
- langchain_core/callbacks/__init__.py +1 -8
- langchain_core/callbacks/base.py +323 -334
- langchain_core/callbacks/file.py +44 -44
- langchain_core/callbacks/manager.py +441 -507
- 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 +48 -63
- langchain_core/document_loaders/base.py +23 -23
- langchain_core/document_loaders/langsmith.py +37 -37
- langchain_core/documents/__init__.py +0 -1
- langchain_core/documents/base.py +62 -65
- langchain_core/documents/compressor.py +4 -4
- langchain_core/documents/transformers.py +28 -29
- langchain_core/embeddings/fake.py +50 -54
- langchain_core/example_selectors/length_based.py +1 -1
- langchain_core/example_selectors/semantic_similarity.py +21 -25
- langchain_core/exceptions.py +10 -11
- langchain_core/globals.py +3 -151
- langchain_core/indexing/api.py +61 -66
- langchain_core/indexing/base.py +58 -58
- langchain_core/indexing/in_memory.py +3 -3
- langchain_core/language_models/__init__.py +14 -27
- langchain_core/language_models/_utils.py +270 -84
- langchain_core/language_models/base.py +55 -162
- langchain_core/language_models/chat_models.py +442 -402
- langchain_core/language_models/fake.py +11 -11
- langchain_core/language_models/fake_chat_models.py +61 -39
- langchain_core/language_models/llms.py +123 -231
- langchain_core/load/dump.py +4 -5
- langchain_core/load/load.py +18 -28
- langchain_core/load/mapping.py +2 -4
- langchain_core/load/serializable.py +39 -40
- langchain_core/messages/__init__.py +61 -22
- langchain_core/messages/ai.py +368 -163
- langchain_core/messages/base.py +214 -43
- langchain_core/messages/block_translators/__init__.py +111 -0
- langchain_core/messages/block_translators/anthropic.py +470 -0
- langchain_core/messages/block_translators/bedrock.py +94 -0
- langchain_core/messages/block_translators/bedrock_converse.py +297 -0
- langchain_core/messages/block_translators/google_genai.py +530 -0
- langchain_core/messages/block_translators/google_vertexai.py +21 -0
- langchain_core/messages/block_translators/groq.py +143 -0
- langchain_core/messages/block_translators/langchain_v0.py +301 -0
- langchain_core/messages/block_translators/openai.py +1010 -0
- langchain_core/messages/chat.py +2 -6
- langchain_core/messages/content.py +1423 -0
- langchain_core/messages/function.py +6 -10
- langchain_core/messages/human.py +41 -38
- langchain_core/messages/modifier.py +2 -2
- langchain_core/messages/system.py +38 -28
- langchain_core/messages/tool.py +96 -103
- langchain_core/messages/utils.py +478 -504
- langchain_core/output_parsers/__init__.py +1 -14
- langchain_core/output_parsers/base.py +58 -61
- langchain_core/output_parsers/json.py +7 -8
- langchain_core/output_parsers/list.py +5 -7
- langchain_core/output_parsers/openai_functions.py +49 -47
- langchain_core/output_parsers/openai_tools.py +14 -19
- langchain_core/output_parsers/pydantic.py +12 -13
- langchain_core/output_parsers/string.py +2 -2
- langchain_core/output_parsers/transform.py +15 -17
- langchain_core/output_parsers/xml.py +8 -10
- 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 +8 -8
- langchain_core/outputs/llm_result.py +10 -10
- langchain_core/prompt_values.py +12 -12
- langchain_core/prompts/__init__.py +3 -27
- langchain_core/prompts/base.py +45 -55
- langchain_core/prompts/chat.py +254 -313
- 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 +6 -8
- langchain_core/prompts/message.py +3 -3
- langchain_core/prompts/prompt.py +24 -39
- langchain_core/prompts/string.py +4 -4
- langchain_core/prompts/structured.py +42 -50
- langchain_core/rate_limiters.py +51 -60
- langchain_core/retrievers.py +49 -190
- langchain_core/runnables/base.py +1484 -1709
- langchain_core/runnables/branch.py +45 -61
- langchain_core/runnables/config.py +80 -88
- langchain_core/runnables/configurable.py +117 -134
- langchain_core/runnables/fallbacks.py +83 -79
- langchain_core/runnables/graph.py +85 -95
- 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 +79 -74
- langchain_core/runnables/utils.py +62 -68
- langchain_core/stores.py +81 -115
- 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 +179 -187
- langchain_core/tools/convert.py +131 -139
- langchain_core/tools/render.py +10 -10
- langchain_core/tools/retriever.py +11 -11
- langchain_core/tools/simple.py +19 -24
- langchain_core/tools/structured.py +30 -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 +50 -60
- langchain_core/tracers/evaluation.py +11 -11
- langchain_core/tracers/event_stream.py +115 -70
- langchain_core/tracers/langchain.py +21 -21
- langchain_core/tracers/log_stream.py +43 -43
- 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 +46 -8
- langchain_core/utils/aiter.py +57 -61
- langchain_core/utils/env.py +9 -9
- langchain_core/utils/function_calling.py +89 -191
- 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 +37 -42
- langchain_core/utils/json.py +4 -3
- langchain_core/utils/json_schema.py +8 -8
- langchain_core/utils/mustache.py +9 -11
- langchain_core/utils/pydantic.py +33 -35
- langchain_core/utils/strings.py +5 -5
- langchain_core/utils/usage.py +1 -1
- langchain_core/utils/utils.py +80 -54
- langchain_core/vectorstores/base.py +129 -164
- langchain_core/vectorstores/in_memory.py +99 -174
- langchain_core/vectorstores/utils.py +5 -5
- langchain_core/version.py +1 -1
- {langchain_core-0.3.79.dist-info → langchain_core-1.0.0.dist-info}/METADATA +28 -27
- langchain_core-1.0.0.dist-info/RECORD +172 -0
- {langchain_core-0.3.79.dist-info → langchain_core-1.0.0.dist-info}/WHEEL +1 -1
- langchain_core/beta/__init__.py +0 -1
- langchain_core/beta/runnables/__init__.py +0 -1
- langchain_core/beta/runnables/context.py +0 -447
- langchain_core/memory.py +0 -120
- langchain_core/messages/content_blocks.py +0 -176
- 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-0.3.79.dist-info/RECORD +0 -174
- langchain_core-0.3.79.dist-info/entry_points.txt +0 -4
langchain_core/messages/base.py
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING, Any,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, cast, overload
|
|
6
6
|
|
|
7
7
|
from pydantic import ConfigDict, Field
|
|
8
|
+
from typing_extensions import Self
|
|
8
9
|
|
|
10
|
+
from langchain_core._api.deprecation import warn_deprecated
|
|
9
11
|
from langchain_core.load.serializable import Serializable
|
|
12
|
+
from langchain_core.messages import content as types
|
|
10
13
|
from langchain_core.utils import get_bolded_text
|
|
11
14
|
from langchain_core.utils._merge import merge_dicts, merge_lists
|
|
12
15
|
from langchain_core.utils.interactive_env import is_interactive_env
|
|
@@ -17,14 +20,83 @@ if TYPE_CHECKING:
|
|
|
17
20
|
from langchain_core.prompts.chat import ChatPromptTemplate
|
|
18
21
|
|
|
19
22
|
|
|
23
|
+
def _extract_reasoning_from_additional_kwargs(
|
|
24
|
+
message: BaseMessage,
|
|
25
|
+
) -> types.ReasoningContentBlock | None:
|
|
26
|
+
"""Extract `reasoning_content` from `additional_kwargs`.
|
|
27
|
+
|
|
28
|
+
Handles reasoning content stored in various formats:
|
|
29
|
+
- `additional_kwargs["reasoning_content"]` (string) - Ollama, DeepSeek, XAI, Groq
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
message: The message to extract reasoning from.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
A `ReasoningContentBlock` if reasoning content is found, None otherwise.
|
|
36
|
+
"""
|
|
37
|
+
additional_kwargs = getattr(message, "additional_kwargs", {})
|
|
38
|
+
|
|
39
|
+
reasoning_content = additional_kwargs.get("reasoning_content")
|
|
40
|
+
if reasoning_content is not None and isinstance(reasoning_content, str):
|
|
41
|
+
return {"type": "reasoning", "reasoning": reasoning_content}
|
|
42
|
+
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TextAccessor(str):
|
|
47
|
+
"""String-like object that supports both property and method access patterns.
|
|
48
|
+
|
|
49
|
+
Exists to maintain backward compatibility while transitioning from method-based to
|
|
50
|
+
property-based text access in message objects. In LangChain <v1.0, message text was
|
|
51
|
+
accessed via `.text()` method calls. In v1.0=<, the preferred pattern is property
|
|
52
|
+
access via `.text`.
|
|
53
|
+
|
|
54
|
+
Rather than breaking existing code immediately, `TextAccessor` allows both
|
|
55
|
+
patterns:
|
|
56
|
+
- Modern property access: `message.text` (returns string directly)
|
|
57
|
+
- Legacy method access: `message.text()` (callable, emits deprecation warning)
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
__slots__ = ()
|
|
62
|
+
|
|
63
|
+
def __new__(cls, value: str) -> Self:
|
|
64
|
+
"""Create new TextAccessor instance."""
|
|
65
|
+
return str.__new__(cls, value)
|
|
66
|
+
|
|
67
|
+
def __call__(self) -> str:
|
|
68
|
+
"""Enable method-style text access for backward compatibility.
|
|
69
|
+
|
|
70
|
+
This method exists solely to support legacy code that calls `.text()`
|
|
71
|
+
as a method. New code should use property access (`.text`) instead.
|
|
72
|
+
|
|
73
|
+
!!! deprecated
|
|
74
|
+
As of `langchain-core` 1.0.0, calling `.text()` as a method is deprecated.
|
|
75
|
+
Use `.text` as a property instead. This method will be removed in 2.0.0.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The string content, identical to property access.
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
warn_deprecated(
|
|
82
|
+
since="1.0.0",
|
|
83
|
+
message=(
|
|
84
|
+
"Calling .text() as a method is deprecated. "
|
|
85
|
+
"Use .text as a property instead (e.g., message.text)."
|
|
86
|
+
),
|
|
87
|
+
removal="2.0.0",
|
|
88
|
+
)
|
|
89
|
+
return str(self)
|
|
90
|
+
|
|
91
|
+
|
|
20
92
|
class BaseMessage(Serializable):
|
|
21
93
|
"""Base abstract message class.
|
|
22
94
|
|
|
23
|
-
Messages are the inputs and outputs of
|
|
95
|
+
Messages are the inputs and outputs of a chat model.
|
|
24
96
|
"""
|
|
25
97
|
|
|
26
|
-
content:
|
|
27
|
-
"""The
|
|
98
|
+
content: str | list[str | dict]
|
|
99
|
+
"""The contents of the message."""
|
|
28
100
|
|
|
29
101
|
additional_kwargs: dict = Field(default_factory=dict)
|
|
30
102
|
"""Reserved for additional payload data associated with the message.
|
|
@@ -45,7 +117,7 @@ class BaseMessage(Serializable):
|
|
|
45
117
|
|
|
46
118
|
"""
|
|
47
119
|
|
|
48
|
-
name:
|
|
120
|
+
name: str | None = None
|
|
49
121
|
"""An optional name for the message.
|
|
50
122
|
|
|
51
123
|
This can be used to provide a human-readable name for the message.
|
|
@@ -55,7 +127,7 @@ class BaseMessage(Serializable):
|
|
|
55
127
|
|
|
56
128
|
"""
|
|
57
129
|
|
|
58
|
-
id:
|
|
130
|
+
id: str | None = Field(default=None, coerce_numbers_to_str=True)
|
|
59
131
|
"""An optional unique identifier for the message.
|
|
60
132
|
|
|
61
133
|
This should ideally be provided by the provider/model which created the message.
|
|
@@ -66,21 +138,44 @@ class BaseMessage(Serializable):
|
|
|
66
138
|
extra="allow",
|
|
67
139
|
)
|
|
68
140
|
|
|
141
|
+
@overload
|
|
142
|
+
def __init__(
|
|
143
|
+
self,
|
|
144
|
+
content: str | list[str | dict],
|
|
145
|
+
**kwargs: Any,
|
|
146
|
+
) -> None: ...
|
|
147
|
+
|
|
148
|
+
@overload
|
|
69
149
|
def __init__(
|
|
70
150
|
self,
|
|
71
|
-
content:
|
|
151
|
+
content: str | list[str | dict] | None = None,
|
|
152
|
+
content_blocks: list[types.ContentBlock] | None = None,
|
|
153
|
+
**kwargs: Any,
|
|
154
|
+
) -> None: ...
|
|
155
|
+
|
|
156
|
+
def __init__(
|
|
157
|
+
self,
|
|
158
|
+
content: str | list[str | dict] | None = None,
|
|
159
|
+
content_blocks: list[types.ContentBlock] | None = None,
|
|
72
160
|
**kwargs: Any,
|
|
73
161
|
) -> None:
|
|
74
|
-
"""Initialize
|
|
162
|
+
"""Initialize a `BaseMessage`.
|
|
163
|
+
|
|
164
|
+
Specify `content` as positional arg or `content_blocks` for typing.
|
|
75
165
|
|
|
76
166
|
Args:
|
|
77
|
-
content: The
|
|
167
|
+
content: The contents of the message.
|
|
168
|
+
content_blocks: Typed standard content.
|
|
169
|
+
**kwargs: Additional arguments to pass to the parent class.
|
|
78
170
|
"""
|
|
79
|
-
|
|
171
|
+
if content_blocks is not None:
|
|
172
|
+
super().__init__(content=content_blocks, **kwargs)
|
|
173
|
+
else:
|
|
174
|
+
super().__init__(content=content, **kwargs)
|
|
80
175
|
|
|
81
176
|
@classmethod
|
|
82
177
|
def is_lc_serializable(cls) -> bool:
|
|
83
|
-
"""
|
|
178
|
+
"""`BaseMessage` is serializable.
|
|
84
179
|
|
|
85
180
|
Returns:
|
|
86
181
|
True
|
|
@@ -89,33 +184,105 @@ class BaseMessage(Serializable):
|
|
|
89
184
|
|
|
90
185
|
@classmethod
|
|
91
186
|
def get_lc_namespace(cls) -> list[str]:
|
|
92
|
-
"""Get the namespace of the
|
|
187
|
+
"""Get the namespace of the LangChain object.
|
|
93
188
|
|
|
94
189
|
Returns:
|
|
95
|
-
|
|
190
|
+
`["langchain", "schema", "messages"]`
|
|
96
191
|
"""
|
|
97
192
|
return ["langchain", "schema", "messages"]
|
|
98
193
|
|
|
99
|
-
|
|
100
|
-
|
|
194
|
+
@property
|
|
195
|
+
def content_blocks(self) -> list[types.ContentBlock]:
|
|
196
|
+
r"""Load content blocks from the message content.
|
|
197
|
+
|
|
198
|
+
!!! version-added "Added in version 1.0.0"
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
# Needed here to avoid circular import, as these classes import BaseMessages
|
|
202
|
+
from langchain_core.messages import content as types # noqa: PLC0415
|
|
203
|
+
from langchain_core.messages.block_translators.anthropic import ( # noqa: PLC0415
|
|
204
|
+
_convert_to_v1_from_anthropic_input,
|
|
205
|
+
)
|
|
206
|
+
from langchain_core.messages.block_translators.bedrock_converse import ( # noqa: PLC0415
|
|
207
|
+
_convert_to_v1_from_converse_input,
|
|
208
|
+
)
|
|
209
|
+
from langchain_core.messages.block_translators.google_genai import ( # noqa: PLC0415
|
|
210
|
+
_convert_to_v1_from_genai_input,
|
|
211
|
+
)
|
|
212
|
+
from langchain_core.messages.block_translators.langchain_v0 import ( # noqa: PLC0415
|
|
213
|
+
_convert_v0_multimodal_input_to_v1,
|
|
214
|
+
)
|
|
215
|
+
from langchain_core.messages.block_translators.openai import ( # noqa: PLC0415
|
|
216
|
+
_convert_to_v1_from_chat_completions_input,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
blocks: list[types.ContentBlock] = []
|
|
220
|
+
content = (
|
|
221
|
+
# Transpose string content to list, otherwise assumed to be list
|
|
222
|
+
[self.content]
|
|
223
|
+
if isinstance(self.content, str) and self.content
|
|
224
|
+
else self.content
|
|
225
|
+
)
|
|
226
|
+
for item in content:
|
|
227
|
+
if isinstance(item, str):
|
|
228
|
+
# Plain string content is treated as a text block
|
|
229
|
+
blocks.append({"type": "text", "text": item})
|
|
230
|
+
elif isinstance(item, dict):
|
|
231
|
+
item_type = item.get("type")
|
|
232
|
+
if item_type not in types.KNOWN_BLOCK_TYPES:
|
|
233
|
+
# Handle all provider-specific or None type blocks as non-standard -
|
|
234
|
+
# we'll come back to these later
|
|
235
|
+
blocks.append({"type": "non_standard", "value": item})
|
|
236
|
+
else:
|
|
237
|
+
# Guard against v0 blocks that share the same `type` keys
|
|
238
|
+
if "source_type" in item:
|
|
239
|
+
blocks.append({"type": "non_standard", "value": item})
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
# This can't be a v0 block (since they require `source_type`),
|
|
243
|
+
# so it's a known v1 block type
|
|
244
|
+
blocks.append(cast("types.ContentBlock", item))
|
|
245
|
+
|
|
246
|
+
# Subsequent passes: attempt to unpack non-standard blocks.
|
|
247
|
+
# This is the last stop - if we can't parse it here, it is left as non-standard
|
|
248
|
+
for parsing_step in [
|
|
249
|
+
_convert_v0_multimodal_input_to_v1,
|
|
250
|
+
_convert_to_v1_from_chat_completions_input,
|
|
251
|
+
_convert_to_v1_from_anthropic_input,
|
|
252
|
+
_convert_to_v1_from_genai_input,
|
|
253
|
+
_convert_to_v1_from_converse_input,
|
|
254
|
+
]:
|
|
255
|
+
blocks = parsing_step(blocks)
|
|
256
|
+
return blocks
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def text(self) -> TextAccessor:
|
|
260
|
+
"""Get the text content of the message as a string.
|
|
261
|
+
|
|
262
|
+
Can be used as both property (`message.text`) and method (`message.text()`).
|
|
263
|
+
|
|
264
|
+
!!! deprecated
|
|
265
|
+
As of `langchain-core` 1.0.0, calling `.text()` as a method is deprecated.
|
|
266
|
+
Use `.text` as a property instead. This method will be removed in 2.0.0.
|
|
101
267
|
|
|
102
268
|
Returns:
|
|
103
269
|
The text content of the message.
|
|
104
270
|
|
|
105
271
|
"""
|
|
106
272
|
if isinstance(self.content, str):
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
273
|
+
text_value = self.content
|
|
274
|
+
else:
|
|
275
|
+
# must be a list
|
|
276
|
+
blocks = [
|
|
277
|
+
block
|
|
278
|
+
for block in self.content
|
|
279
|
+
if isinstance(block, str)
|
|
280
|
+
or (block.get("type") == "text" and isinstance(block.get("text"), str))
|
|
281
|
+
]
|
|
282
|
+
text_value = "".join(
|
|
283
|
+
block if isinstance(block, str) else block["text"] for block in blocks
|
|
284
|
+
)
|
|
285
|
+
return TextAccessor(text_value)
|
|
119
286
|
|
|
120
287
|
def __add__(self, other: Any) -> ChatPromptTemplate:
|
|
121
288
|
"""Concatenate this message with another message.
|
|
@@ -139,8 +306,8 @@ class BaseMessage(Serializable):
|
|
|
139
306
|
"""Get a pretty representation of the message.
|
|
140
307
|
|
|
141
308
|
Args:
|
|
142
|
-
html: Whether to format the message as HTML. If True
|
|
143
|
-
formatted with HTML tags.
|
|
309
|
+
html: Whether to format the message as HTML. If `True`, the message will be
|
|
310
|
+
formatted with HTML tags.
|
|
144
311
|
|
|
145
312
|
Returns:
|
|
146
313
|
A pretty representation of the message.
|
|
@@ -158,20 +325,22 @@ class BaseMessage(Serializable):
|
|
|
158
325
|
|
|
159
326
|
|
|
160
327
|
def merge_content(
|
|
161
|
-
first_content:
|
|
162
|
-
*contents:
|
|
163
|
-
) ->
|
|
328
|
+
first_content: str | list[str | dict],
|
|
329
|
+
*contents: str | list[str | dict],
|
|
330
|
+
) -> str | list[str | dict]:
|
|
164
331
|
"""Merge multiple message contents.
|
|
165
332
|
|
|
166
333
|
Args:
|
|
167
|
-
first_content: The first
|
|
168
|
-
contents: The other
|
|
334
|
+
first_content: The first `content`. Can be a string or a list.
|
|
335
|
+
contents: The other `content`s. Can be a string or a list.
|
|
169
336
|
|
|
170
337
|
Returns:
|
|
171
338
|
The merged content.
|
|
172
339
|
|
|
173
340
|
"""
|
|
174
|
-
merged
|
|
341
|
+
merged: str | list[str | dict]
|
|
342
|
+
merged = "" if first_content is None else first_content
|
|
343
|
+
|
|
175
344
|
for content in contents:
|
|
176
345
|
# If current is a string
|
|
177
346
|
if isinstance(merged, str):
|
|
@@ -190,8 +359,10 @@ def merge_content(
|
|
|
190
359
|
elif merged and isinstance(merged[-1], str):
|
|
191
360
|
merged[-1] += content
|
|
192
361
|
# If second content is an empty string, treat as a no-op
|
|
193
|
-
elif content:
|
|
194
|
-
|
|
362
|
+
elif content == "":
|
|
363
|
+
pass
|
|
364
|
+
# Otherwise, add the second content as a new element of the list
|
|
365
|
+
elif merged:
|
|
195
366
|
merged.append(content)
|
|
196
367
|
return merged
|
|
197
368
|
|
|
@@ -217,9 +388,9 @@ class BaseMessageChunk(BaseMessage):
|
|
|
217
388
|
|
|
218
389
|
For example,
|
|
219
390
|
|
|
220
|
-
|
|
391
|
+
`AIMessageChunk(content="Hello") + AIMessageChunk(content=" World")`
|
|
221
392
|
|
|
222
|
-
will give
|
|
393
|
+
will give `AIMessageChunk(content="Hello World")`
|
|
223
394
|
|
|
224
395
|
"""
|
|
225
396
|
if isinstance(other, BaseMessageChunk):
|
|
@@ -268,8 +439,8 @@ def message_to_dict(message: BaseMessage) -> dict:
|
|
|
268
439
|
message: Message to convert.
|
|
269
440
|
|
|
270
441
|
Returns:
|
|
271
|
-
Message as a dict. The dict will have a
|
|
272
|
-
and a
|
|
442
|
+
Message as a dict. The dict will have a `type` key with the message type
|
|
443
|
+
and a `data` key with the message data as a dict.
|
|
273
444
|
|
|
274
445
|
"""
|
|
275
446
|
return {"type": message.type, "data": message.model_dump()}
|
|
@@ -279,7 +450,7 @@ def messages_to_dict(messages: Sequence[BaseMessage]) -> list[dict]:
|
|
|
279
450
|
"""Convert a sequence of Messages to a list of dictionaries.
|
|
280
451
|
|
|
281
452
|
Args:
|
|
282
|
-
messages: Sequence of messages (as
|
|
453
|
+
messages: Sequence of messages (as `BaseMessage`s) to convert.
|
|
283
454
|
|
|
284
455
|
Returns:
|
|
285
456
|
List of messages as dicts.
|
|
@@ -293,7 +464,7 @@ def get_msg_title_repr(title: str, *, bold: bool = False) -> str:
|
|
|
293
464
|
|
|
294
465
|
Args:
|
|
295
466
|
title: The title.
|
|
296
|
-
bold: Whether to bold the title.
|
|
467
|
+
bold: Whether to bold the title.
|
|
297
468
|
|
|
298
469
|
Returns:
|
|
299
470
|
The title representation.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Derivations of standard content blocks from provider content.
|
|
2
|
+
|
|
3
|
+
`AIMessage` will first attempt to use a provider-specific translator if
|
|
4
|
+
`model_provider` is set in `response_metadata` on the message. Consequently, each
|
|
5
|
+
provider translator must handle all possible content response types from the provider,
|
|
6
|
+
including text.
|
|
7
|
+
|
|
8
|
+
If no provider is set, or if the provider does not have a registered translator,
|
|
9
|
+
`AIMessage` will fall back to best-effort parsing of the content into blocks using
|
|
10
|
+
the implementation in `BaseMessage`.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from langchain_core.messages import AIMessage, AIMessageChunk
|
|
20
|
+
from langchain_core.messages import content as types
|
|
21
|
+
|
|
22
|
+
# Provider to translator mapping
|
|
23
|
+
PROVIDER_TRANSLATORS: dict[str, dict[str, Callable[..., list[types.ContentBlock]]]] = {}
|
|
24
|
+
"""Map model provider names to translator functions.
|
|
25
|
+
|
|
26
|
+
The dictionary maps provider names (e.g. `'openai'`, `'anthropic'`) to another
|
|
27
|
+
dictionary with two keys:
|
|
28
|
+
- `'translate_content'`: Function to translate `AIMessage` content.
|
|
29
|
+
- `'translate_content_chunk'`: Function to translate `AIMessageChunk` content.
|
|
30
|
+
|
|
31
|
+
When calling `content_blocks` on an `AIMessage` or `AIMessageChunk`, if
|
|
32
|
+
`model_provider` is set in `response_metadata`, the corresponding translator
|
|
33
|
+
functions will be used to parse the content into blocks. Otherwise, best-effort parsing
|
|
34
|
+
in `BaseMessage` will be used.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def register_translator(
|
|
39
|
+
provider: str,
|
|
40
|
+
translate_content: Callable[[AIMessage], list[types.ContentBlock]],
|
|
41
|
+
translate_content_chunk: Callable[[AIMessageChunk], list[types.ContentBlock]],
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Register content translators for a provider in `PROVIDER_TRANSLATORS`.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
provider: The model provider name (e.g. `'openai'`, `'anthropic'`).
|
|
47
|
+
translate_content: Function to translate `AIMessage` content.
|
|
48
|
+
translate_content_chunk: Function to translate `AIMessageChunk` content.
|
|
49
|
+
"""
|
|
50
|
+
PROVIDER_TRANSLATORS[provider] = {
|
|
51
|
+
"translate_content": translate_content,
|
|
52
|
+
"translate_content_chunk": translate_content_chunk,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_translator(
|
|
57
|
+
provider: str,
|
|
58
|
+
) -> dict[str, Callable[..., list[types.ContentBlock]]] | None:
|
|
59
|
+
"""Get the translator functions for a provider.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
provider: The model provider name.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dictionary with `'translate_content'` and `'translate_content_chunk'`
|
|
66
|
+
functions, or None if no translator is registered for the provider. In such
|
|
67
|
+
case, best-effort parsing in `BaseMessage` will be used.
|
|
68
|
+
"""
|
|
69
|
+
return PROVIDER_TRANSLATORS.get(provider)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _register_translators() -> None:
|
|
73
|
+
"""Register all translators in langchain-core.
|
|
74
|
+
|
|
75
|
+
A unit test ensures all modules in `block_translators` are represented here.
|
|
76
|
+
|
|
77
|
+
For translators implemented outside langchain-core, they can be registered by
|
|
78
|
+
calling `register_translator` from within the integration package.
|
|
79
|
+
"""
|
|
80
|
+
from langchain_core.messages.block_translators.anthropic import ( # noqa: PLC0415
|
|
81
|
+
_register_anthropic_translator,
|
|
82
|
+
)
|
|
83
|
+
from langchain_core.messages.block_translators.bedrock import ( # noqa: PLC0415
|
|
84
|
+
_register_bedrock_translator,
|
|
85
|
+
)
|
|
86
|
+
from langchain_core.messages.block_translators.bedrock_converse import ( # noqa: PLC0415
|
|
87
|
+
_register_bedrock_converse_translator,
|
|
88
|
+
)
|
|
89
|
+
from langchain_core.messages.block_translators.google_genai import ( # noqa: PLC0415
|
|
90
|
+
_register_google_genai_translator,
|
|
91
|
+
)
|
|
92
|
+
from langchain_core.messages.block_translators.google_vertexai import ( # noqa: PLC0415
|
|
93
|
+
_register_google_vertexai_translator,
|
|
94
|
+
)
|
|
95
|
+
from langchain_core.messages.block_translators.groq import ( # noqa: PLC0415
|
|
96
|
+
_register_groq_translator,
|
|
97
|
+
)
|
|
98
|
+
from langchain_core.messages.block_translators.openai import ( # noqa: PLC0415
|
|
99
|
+
_register_openai_translator,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
_register_bedrock_translator()
|
|
103
|
+
_register_bedrock_converse_translator()
|
|
104
|
+
_register_anthropic_translator()
|
|
105
|
+
_register_google_genai_translator()
|
|
106
|
+
_register_google_vertexai_translator()
|
|
107
|
+
_register_groq_translator()
|
|
108
|
+
_register_openai_translator()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
_register_translators()
|