langchain-core 0.4.0.dev0__py3-none-any.whl → 1.0.0a1__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/_api/beta_decorator.py +2 -2
- langchain_core/_api/deprecation.py +1 -1
- langchain_core/beta/runnables/context.py +1 -1
- langchain_core/callbacks/base.py +14 -23
- langchain_core/callbacks/file.py +13 -2
- langchain_core/callbacks/manager.py +74 -157
- langchain_core/callbacks/streaming_stdout.py +3 -4
- langchain_core/callbacks/usage.py +2 -12
- langchain_core/chat_history.py +6 -6
- langchain_core/documents/base.py +1 -1
- langchain_core/documents/compressor.py +9 -6
- langchain_core/indexing/base.py +2 -2
- langchain_core/language_models/_utils.py +230 -101
- langchain_core/language_models/base.py +35 -23
- langchain_core/language_models/chat_models.py +245 -53
- langchain_core/language_models/fake_chat_models.py +28 -81
- langchain_core/load/dump.py +3 -4
- langchain_core/messages/__init__.py +38 -22
- langchain_core/messages/ai.py +188 -30
- langchain_core/messages/base.py +164 -25
- langchain_core/messages/block_translators/__init__.py +89 -0
- langchain_core/messages/block_translators/anthropic.py +451 -0
- langchain_core/messages/block_translators/bedrock.py +45 -0
- langchain_core/messages/block_translators/bedrock_converse.py +47 -0
- langchain_core/messages/block_translators/google_genai.py +45 -0
- langchain_core/messages/block_translators/google_vertexai.py +47 -0
- langchain_core/messages/block_translators/groq.py +45 -0
- langchain_core/messages/block_translators/langchain_v0.py +297 -0
- langchain_core/messages/block_translators/ollama.py +45 -0
- langchain_core/messages/block_translators/openai.py +586 -0
- langchain_core/messages/{content_blocks.py → content.py} +346 -213
- langchain_core/messages/human.py +29 -9
- langchain_core/messages/system.py +29 -9
- langchain_core/messages/tool.py +94 -13
- langchain_core/messages/utils.py +32 -234
- langchain_core/output_parsers/base.py +14 -50
- langchain_core/output_parsers/json.py +2 -5
- langchain_core/output_parsers/list.py +2 -7
- langchain_core/output_parsers/openai_functions.py +5 -28
- langchain_core/output_parsers/openai_tools.py +49 -90
- langchain_core/output_parsers/pydantic.py +2 -3
- langchain_core/output_parsers/transform.py +12 -53
- langchain_core/output_parsers/xml.py +9 -17
- langchain_core/prompt_values.py +8 -112
- langchain_core/prompts/chat.py +1 -3
- langchain_core/runnables/base.py +500 -451
- langchain_core/runnables/branch.py +1 -1
- langchain_core/runnables/fallbacks.py +4 -4
- langchain_core/runnables/history.py +1 -1
- langchain_core/runnables/passthrough.py +3 -3
- langchain_core/runnables/retry.py +1 -1
- langchain_core/runnables/router.py +1 -1
- langchain_core/structured_query.py +3 -7
- langchain_core/tools/base.py +14 -41
- langchain_core/tools/convert.py +2 -22
- langchain_core/tools/retriever.py +1 -8
- langchain_core/tools/structured.py +2 -10
- langchain_core/tracers/_streaming.py +6 -7
- langchain_core/tracers/base.py +7 -14
- langchain_core/tracers/core.py +4 -27
- langchain_core/tracers/event_stream.py +4 -15
- langchain_core/tracers/langchain.py +3 -14
- langchain_core/tracers/log_stream.py +2 -3
- langchain_core/utils/_merge.py +45 -7
- langchain_core/utils/function_calling.py +22 -9
- langchain_core/utils/utils.py +29 -0
- langchain_core/version.py +1 -1
- {langchain_core-0.4.0.dev0.dist-info → langchain_core-1.0.0a1.dist-info}/METADATA +7 -9
- {langchain_core-0.4.0.dev0.dist-info → langchain_core-1.0.0a1.dist-info}/RECORD +71 -64
- langchain_core/v1/__init__.py +0 -1
- langchain_core/v1/chat_models.py +0 -1047
- langchain_core/v1/messages.py +0 -755
- {langchain_core-0.4.0.dev0.dist-info → langchain_core-1.0.0a1.dist-info}/WHEEL +0 -0
- {langchain_core-0.4.0.dev0.dist-info → langchain_core-1.0.0a1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Derivations of standard content blocks from LangChain v0 multimodal content."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Union, cast
|
|
4
|
+
|
|
5
|
+
from langchain_core.language_models._utils import _parse_data_uri
|
|
6
|
+
from langchain_core.messages import content as types
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _convert_v0_multimodal_input_to_v1(
|
|
10
|
+
blocks: list[types.ContentBlock],
|
|
11
|
+
) -> list[types.ContentBlock]:
|
|
12
|
+
"""Convert v0 multimodal blocks to v1 format.
|
|
13
|
+
|
|
14
|
+
Processes non_standard blocks that might be v0 format and converts them
|
|
15
|
+
to proper v1 ContentBlocks.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
blocks: List of content blocks to process.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Updated list with v0 blocks converted to v1 format.
|
|
22
|
+
"""
|
|
23
|
+
converted_blocks = []
|
|
24
|
+
unpacked_blocks: list[dict[str, Any]] = [
|
|
25
|
+
cast("dict[str, Any]", block)
|
|
26
|
+
if block.get("type") != "non_standard"
|
|
27
|
+
else block["value"] # type: ignore[typeddict-item] # this is only non-standard blocks
|
|
28
|
+
for block in blocks
|
|
29
|
+
]
|
|
30
|
+
for block in unpacked_blocks:
|
|
31
|
+
if block.get("type") in {"image", "audio", "file"} and "source_type" in block:
|
|
32
|
+
converted_block = _convert_legacy_v0_content_block_to_v1(block)
|
|
33
|
+
converted_blocks.append(cast("types.ContentBlock", converted_block))
|
|
34
|
+
elif block.get("type") in types.KNOWN_BLOCK_TYPES:
|
|
35
|
+
converted_blocks.append(cast("types.ContentBlock", block))
|
|
36
|
+
else:
|
|
37
|
+
converted_blocks.append({"type": "non_standard", "value": block})
|
|
38
|
+
|
|
39
|
+
return converted_blocks
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _convert_legacy_v0_content_block_to_v1(
|
|
43
|
+
block: dict,
|
|
44
|
+
) -> Union[types.ContentBlock, dict]:
|
|
45
|
+
"""Convert a LangChain v0 content block to v1 format.
|
|
46
|
+
|
|
47
|
+
Preserves unknown keys as extras to avoid data loss.
|
|
48
|
+
|
|
49
|
+
Returns the original block unchanged if it's not in v0 format.
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def _extract_v0_extras(block_dict: dict, known_keys: set[str]) -> dict[str, Any]:
|
|
54
|
+
"""Extract unknown keys from v0 block to preserve as extras."""
|
|
55
|
+
return {k: v for k, v in block_dict.items() if k not in known_keys}
|
|
56
|
+
|
|
57
|
+
# Check if this is actually a v0 format block
|
|
58
|
+
block_type = block.get("type")
|
|
59
|
+
if block_type not in {"image", "audio", "file"} or "source_type" not in block:
|
|
60
|
+
# Not a v0 format block, return unchanged
|
|
61
|
+
return block
|
|
62
|
+
|
|
63
|
+
if block.get("type") == "image":
|
|
64
|
+
source_type = block.get("source_type")
|
|
65
|
+
if source_type == "url":
|
|
66
|
+
known_keys = {"type", "source_type", "url", "mime_type"}
|
|
67
|
+
extras = _extract_v0_extras(block, known_keys)
|
|
68
|
+
if "id" in block:
|
|
69
|
+
return types.create_image_block(
|
|
70
|
+
url=block["url"],
|
|
71
|
+
mime_type=block.get("mime_type"),
|
|
72
|
+
id=block["id"],
|
|
73
|
+
**extras,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Don't construct with an ID if not present in original block
|
|
77
|
+
v1_block = types.ImageContentBlock(type="image", url=block["url"])
|
|
78
|
+
if block.get("mime_type"):
|
|
79
|
+
v1_block["mime_type"] = block["mime_type"]
|
|
80
|
+
|
|
81
|
+
for key, value in extras.items():
|
|
82
|
+
if value is not None:
|
|
83
|
+
v1_block["extras"] = {}
|
|
84
|
+
v1_block["extras"][key] = value
|
|
85
|
+
return v1_block
|
|
86
|
+
if source_type == "base64":
|
|
87
|
+
known_keys = {"type", "source_type", "data", "mime_type"}
|
|
88
|
+
extras = _extract_v0_extras(block, known_keys)
|
|
89
|
+
if "id" in block:
|
|
90
|
+
return types.create_image_block(
|
|
91
|
+
base64=block["data"],
|
|
92
|
+
mime_type=block.get("mime_type"),
|
|
93
|
+
id=block["id"],
|
|
94
|
+
**extras,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
v1_block = types.ImageContentBlock(type="image", base64=block["data"])
|
|
98
|
+
if block.get("mime_type"):
|
|
99
|
+
v1_block["mime_type"] = block["mime_type"]
|
|
100
|
+
|
|
101
|
+
for key, value in extras.items():
|
|
102
|
+
if value is not None:
|
|
103
|
+
v1_block["extras"] = {}
|
|
104
|
+
v1_block["extras"][key] = value
|
|
105
|
+
return v1_block
|
|
106
|
+
if source_type == "id":
|
|
107
|
+
known_keys = {"type", "source_type", "id"}
|
|
108
|
+
extras = _extract_v0_extras(block, known_keys)
|
|
109
|
+
# For id `source_type`, `id` is the file reference, not block ID
|
|
110
|
+
v1_block = types.ImageContentBlock(type="image", file_id=block["id"])
|
|
111
|
+
|
|
112
|
+
for key, value in extras.items():
|
|
113
|
+
if value is not None:
|
|
114
|
+
v1_block["extras"] = {}
|
|
115
|
+
v1_block["extras"][key] = value
|
|
116
|
+
|
|
117
|
+
return v1_block
|
|
118
|
+
elif block.get("type") == "audio":
|
|
119
|
+
source_type = block.get("source_type")
|
|
120
|
+
if source_type == "url":
|
|
121
|
+
known_keys = {"type", "source_type", "url", "mime_type"}
|
|
122
|
+
extras = _extract_v0_extras(block, known_keys)
|
|
123
|
+
return types.create_audio_block(
|
|
124
|
+
url=block["url"], mime_type=block.get("mime_type"), **extras
|
|
125
|
+
)
|
|
126
|
+
if source_type == "base64":
|
|
127
|
+
known_keys = {"type", "source_type", "data", "mime_type"}
|
|
128
|
+
extras = _extract_v0_extras(block, known_keys)
|
|
129
|
+
return types.create_audio_block(
|
|
130
|
+
base64=block["data"], mime_type=block.get("mime_type"), **extras
|
|
131
|
+
)
|
|
132
|
+
if source_type == "id":
|
|
133
|
+
known_keys = {"type", "source_type", "id"}
|
|
134
|
+
extras = _extract_v0_extras(block, known_keys)
|
|
135
|
+
return types.create_audio_block(file_id=block["id"], **extras)
|
|
136
|
+
elif block.get("type") == "file":
|
|
137
|
+
source_type = block.get("source_type")
|
|
138
|
+
if source_type == "url":
|
|
139
|
+
known_keys = {"type", "source_type", "url", "mime_type"}
|
|
140
|
+
extras = _extract_v0_extras(block, known_keys)
|
|
141
|
+
return types.create_file_block(
|
|
142
|
+
url=block["url"], mime_type=block.get("mime_type"), **extras
|
|
143
|
+
)
|
|
144
|
+
if source_type == "base64":
|
|
145
|
+
known_keys = {"type", "source_type", "data", "mime_type"}
|
|
146
|
+
extras = _extract_v0_extras(block, known_keys)
|
|
147
|
+
return types.create_file_block(
|
|
148
|
+
base64=block["data"], mime_type=block.get("mime_type"), **extras
|
|
149
|
+
)
|
|
150
|
+
if source_type == "id":
|
|
151
|
+
known_keys = {"type", "source_type", "id"}
|
|
152
|
+
extras = _extract_v0_extras(block, known_keys)
|
|
153
|
+
return types.create_file_block(file_id=block["id"], **extras)
|
|
154
|
+
if source_type == "text":
|
|
155
|
+
known_keys = {"type", "source_type", "url", "mime_type"}
|
|
156
|
+
extras = _extract_v0_extras(block, known_keys)
|
|
157
|
+
return types.create_plaintext_block(
|
|
158
|
+
# In v0, URL points to the text file content
|
|
159
|
+
text=block["url"],
|
|
160
|
+
**extras,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# If we can't convert, return the block unchanged
|
|
164
|
+
return block
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _convert_openai_format_to_data_block(
|
|
168
|
+
block: dict,
|
|
169
|
+
) -> Union[types.ContentBlock, dict[Any, Any]]:
|
|
170
|
+
"""Convert OpenAI image/audio/file content block to respective v1 multimodal block.
|
|
171
|
+
|
|
172
|
+
We expect that the incoming block is verified to be in OpenAI Chat Completions
|
|
173
|
+
format.
|
|
174
|
+
|
|
175
|
+
If parsing fails, passes block through unchanged.
|
|
176
|
+
|
|
177
|
+
Mappings (Chat Completions to LangChain v1):
|
|
178
|
+
- Image -> `ImageContentBlock`
|
|
179
|
+
- Audio -> `AudioContentBlock`
|
|
180
|
+
- File -> `FileContentBlock`
|
|
181
|
+
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
# Extract extra keys to put them in `extras`
|
|
185
|
+
def _extract_extras(block_dict: dict, known_keys: set[str]) -> dict[str, Any]:
|
|
186
|
+
"""Extract unknown keys from block to preserve as extras."""
|
|
187
|
+
return {k: v for k, v in block_dict.items() if k not in known_keys}
|
|
188
|
+
|
|
189
|
+
# base64-style image block
|
|
190
|
+
if (block["type"] == "image_url") and (
|
|
191
|
+
parsed := _parse_data_uri(block["image_url"]["url"])
|
|
192
|
+
):
|
|
193
|
+
known_keys = {"type", "image_url"}
|
|
194
|
+
extras = _extract_extras(block, known_keys)
|
|
195
|
+
|
|
196
|
+
# Also extract extras from nested image_url dict
|
|
197
|
+
image_url_known_keys = {"url"}
|
|
198
|
+
image_url_extras = _extract_extras(block["image_url"], image_url_known_keys)
|
|
199
|
+
|
|
200
|
+
# Merge extras
|
|
201
|
+
all_extras = {**extras}
|
|
202
|
+
for key, value in image_url_extras.items():
|
|
203
|
+
if key == "detail": # Don't rename
|
|
204
|
+
all_extras["detail"] = value
|
|
205
|
+
else:
|
|
206
|
+
all_extras[f"image_url_{key}"] = value
|
|
207
|
+
|
|
208
|
+
return types.create_image_block(
|
|
209
|
+
# Even though this is labeled as `url`, it can be base64-encoded
|
|
210
|
+
base64=parsed["data"],
|
|
211
|
+
mime_type=parsed["mime_type"],
|
|
212
|
+
**all_extras,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# url-style image block
|
|
216
|
+
if (block["type"] == "image_url") and isinstance(
|
|
217
|
+
block["image_url"].get("url"), str
|
|
218
|
+
):
|
|
219
|
+
known_keys = {"type", "image_url"}
|
|
220
|
+
extras = _extract_extras(block, known_keys)
|
|
221
|
+
|
|
222
|
+
image_url_known_keys = {"url"}
|
|
223
|
+
image_url_extras = _extract_extras(block["image_url"], image_url_known_keys)
|
|
224
|
+
|
|
225
|
+
all_extras = {**extras}
|
|
226
|
+
for key, value in image_url_extras.items():
|
|
227
|
+
if key == "detail": # Don't rename
|
|
228
|
+
all_extras["detail"] = value
|
|
229
|
+
else:
|
|
230
|
+
all_extras[f"image_url_{key}"] = value
|
|
231
|
+
|
|
232
|
+
return types.create_image_block(
|
|
233
|
+
url=block["image_url"]["url"],
|
|
234
|
+
**all_extras,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# base64-style audio block
|
|
238
|
+
# audio is only represented via raw data, no url or ID option
|
|
239
|
+
if block["type"] == "input_audio":
|
|
240
|
+
known_keys = {"type", "input_audio"}
|
|
241
|
+
extras = _extract_extras(block, known_keys)
|
|
242
|
+
|
|
243
|
+
# Also extract extras from nested audio dict
|
|
244
|
+
audio_known_keys = {"data", "format"}
|
|
245
|
+
audio_extras = _extract_extras(block["input_audio"], audio_known_keys)
|
|
246
|
+
|
|
247
|
+
all_extras = {**extras}
|
|
248
|
+
for key, value in audio_extras.items():
|
|
249
|
+
all_extras[f"audio_{key}"] = value
|
|
250
|
+
|
|
251
|
+
return types.create_audio_block(
|
|
252
|
+
base64=block["input_audio"]["data"],
|
|
253
|
+
mime_type=f"audio/{block['input_audio']['format']}",
|
|
254
|
+
**all_extras,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# id-style file block
|
|
258
|
+
if block.get("type") == "file" and "file_id" in block.get("file", {}):
|
|
259
|
+
known_keys = {"type", "file"}
|
|
260
|
+
extras = _extract_extras(block, known_keys)
|
|
261
|
+
|
|
262
|
+
file_known_keys = {"file_id"}
|
|
263
|
+
file_extras = _extract_extras(block["file"], file_known_keys)
|
|
264
|
+
|
|
265
|
+
all_extras = {**extras}
|
|
266
|
+
for key, value in file_extras.items():
|
|
267
|
+
all_extras[f"file_{key}"] = value
|
|
268
|
+
|
|
269
|
+
return types.create_file_block(
|
|
270
|
+
file_id=block["file"]["file_id"],
|
|
271
|
+
**all_extras,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# base64-style file block
|
|
275
|
+
if (block["type"] == "file") and (
|
|
276
|
+
parsed := _parse_data_uri(block["file"]["file_data"])
|
|
277
|
+
):
|
|
278
|
+
known_keys = {"type", "file"}
|
|
279
|
+
extras = _extract_extras(block, known_keys)
|
|
280
|
+
|
|
281
|
+
file_known_keys = {"file_data", "filename"}
|
|
282
|
+
file_extras = _extract_extras(block["file"], file_known_keys)
|
|
283
|
+
|
|
284
|
+
all_extras = {**extras}
|
|
285
|
+
for key, value in file_extras.items():
|
|
286
|
+
all_extras[f"file_{key}"] = value
|
|
287
|
+
|
|
288
|
+
filename = block["file"].get("filename")
|
|
289
|
+
return types.create_file_block(
|
|
290
|
+
base64=parsed["data"],
|
|
291
|
+
mime_type="application/pdf",
|
|
292
|
+
filename=filename,
|
|
293
|
+
**all_extras,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Escape hatch
|
|
297
|
+
return block
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Derivations of standard content blocks from Ollama content."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
from langchain_core.messages import AIMessage, AIMessageChunk
|
|
6
|
+
from langchain_core.messages import content as types
|
|
7
|
+
|
|
8
|
+
WARNED = False
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def translate_content(message: AIMessage) -> list[types.ContentBlock]: # noqa: ARG001
|
|
12
|
+
"""Derive standard content blocks from a message with Ollama content."""
|
|
13
|
+
global WARNED # noqa: PLW0603
|
|
14
|
+
if not WARNED:
|
|
15
|
+
warning_message = (
|
|
16
|
+
"Content block standardization is not yet fully supported for Ollama."
|
|
17
|
+
)
|
|
18
|
+
warnings.warn(warning_message, stacklevel=2)
|
|
19
|
+
WARNED = True
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]: # noqa: ARG001
|
|
24
|
+
"""Derive standard content blocks from a message chunk with Ollama content."""
|
|
25
|
+
global WARNED # noqa: PLW0603
|
|
26
|
+
if not WARNED:
|
|
27
|
+
warning_message = (
|
|
28
|
+
"Content block standardization is not yet fully supported for Ollama."
|
|
29
|
+
)
|
|
30
|
+
warnings.warn(warning_message, stacklevel=2)
|
|
31
|
+
WARNED = True
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _register_ollama_translator() -> None:
|
|
36
|
+
"""Register the Ollama translator with the central registry.
|
|
37
|
+
|
|
38
|
+
Run automatically when the module is imported.
|
|
39
|
+
"""
|
|
40
|
+
from langchain_core.messages.block_translators import register_translator
|
|
41
|
+
|
|
42
|
+
register_translator("ollama", translate_content, translate_content_chunk)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_register_ollama_translator()
|