langchain-core 0.3.75__py3-none-any.whl → 0.3.77__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 +22 -44
- langchain_core/_api/deprecation.py +30 -17
- langchain_core/_api/path.py +19 -2
- langchain_core/_import_utils.py +7 -0
- langchain_core/agents.py +10 -6
- langchain_core/beta/runnables/context.py +1 -2
- langchain_core/callbacks/base.py +28 -15
- langchain_core/callbacks/manager.py +83 -71
- langchain_core/callbacks/usage.py +6 -4
- langchain_core/chat_history.py +29 -21
- langchain_core/document_loaders/base.py +34 -9
- langchain_core/document_loaders/langsmith.py +4 -1
- langchain_core/documents/base.py +35 -10
- langchain_core/documents/transformers.py +4 -2
- langchain_core/embeddings/fake.py +8 -5
- langchain_core/env.py +2 -3
- langchain_core/example_selectors/base.py +12 -0
- langchain_core/exceptions.py +7 -0
- langchain_core/globals.py +17 -28
- langchain_core/indexing/api.py +88 -76
- langchain_core/indexing/base.py +5 -8
- langchain_core/indexing/in_memory.py +23 -3
- langchain_core/language_models/__init__.py +3 -2
- langchain_core/language_models/base.py +31 -20
- langchain_core/language_models/chat_models.py +98 -27
- langchain_core/language_models/fake_chat_models.py +10 -9
- langchain_core/language_models/llms.py +52 -18
- langchain_core/load/dump.py +2 -3
- langchain_core/load/load.py +15 -1
- langchain_core/load/serializable.py +39 -44
- langchain_core/memory.py +7 -3
- langchain_core/messages/ai.py +53 -24
- langchain_core/messages/base.py +43 -22
- langchain_core/messages/chat.py +4 -1
- langchain_core/messages/content_blocks.py +23 -2
- langchain_core/messages/function.py +9 -5
- langchain_core/messages/human.py +13 -10
- langchain_core/messages/modifier.py +1 -0
- langchain_core/messages/system.py +11 -8
- langchain_core/messages/tool.py +60 -29
- langchain_core/messages/utils.py +250 -131
- langchain_core/output_parsers/base.py +5 -2
- langchain_core/output_parsers/json.py +4 -4
- langchain_core/output_parsers/list.py +7 -22
- langchain_core/output_parsers/openai_functions.py +3 -0
- langchain_core/output_parsers/openai_tools.py +6 -1
- langchain_core/output_parsers/pydantic.py +4 -0
- langchain_core/output_parsers/string.py +5 -1
- langchain_core/output_parsers/xml.py +19 -19
- langchain_core/outputs/chat_generation.py +25 -10
- langchain_core/outputs/generation.py +14 -3
- langchain_core/outputs/llm_result.py +8 -1
- langchain_core/prompt_values.py +16 -6
- langchain_core/prompts/base.py +4 -9
- langchain_core/prompts/chat.py +89 -57
- langchain_core/prompts/dict.py +16 -8
- langchain_core/prompts/few_shot.py +12 -11
- langchain_core/prompts/few_shot_with_templates.py +5 -1
- langchain_core/prompts/image.py +12 -5
- langchain_core/prompts/message.py +5 -6
- langchain_core/prompts/pipeline.py +13 -8
- langchain_core/prompts/prompt.py +22 -8
- langchain_core/prompts/string.py +18 -10
- langchain_core/prompts/structured.py +7 -2
- langchain_core/rate_limiters.py +2 -2
- langchain_core/retrievers.py +7 -6
- langchain_core/runnables/base.py +406 -186
- langchain_core/runnables/branch.py +14 -19
- langchain_core/runnables/config.py +9 -15
- langchain_core/runnables/configurable.py +34 -19
- langchain_core/runnables/fallbacks.py +20 -13
- langchain_core/runnables/graph.py +48 -38
- langchain_core/runnables/graph_ascii.py +41 -18
- langchain_core/runnables/graph_mermaid.py +54 -25
- langchain_core/runnables/graph_png.py +27 -31
- langchain_core/runnables/history.py +55 -58
- langchain_core/runnables/passthrough.py +44 -21
- langchain_core/runnables/retry.py +44 -23
- langchain_core/runnables/router.py +9 -8
- langchain_core/runnables/schema.py +2 -0
- langchain_core/runnables/utils.py +51 -89
- langchain_core/stores.py +19 -31
- langchain_core/sys_info.py +9 -8
- langchain_core/tools/base.py +37 -28
- langchain_core/tools/convert.py +26 -15
- langchain_core/tools/simple.py +36 -8
- langchain_core/tools/structured.py +25 -12
- langchain_core/tracers/base.py +2 -2
- langchain_core/tracers/context.py +5 -1
- langchain_core/tracers/core.py +109 -39
- langchain_core/tracers/evaluation.py +22 -26
- langchain_core/tracers/event_stream.py +45 -34
- langchain_core/tracers/langchain.py +12 -3
- langchain_core/tracers/langchain_v1.py +10 -2
- langchain_core/tracers/log_stream.py +56 -17
- langchain_core/tracers/root_listeners.py +4 -20
- langchain_core/tracers/run_collector.py +6 -16
- langchain_core/tracers/schemas.py +5 -1
- langchain_core/utils/aiter.py +15 -7
- langchain_core/utils/env.py +3 -0
- langchain_core/utils/function_calling.py +50 -28
- langchain_core/utils/interactive_env.py +6 -2
- langchain_core/utils/iter.py +12 -4
- langchain_core/utils/json.py +12 -3
- langchain_core/utils/json_schema.py +156 -40
- langchain_core/utils/loading.py +5 -1
- langchain_core/utils/mustache.py +24 -15
- langchain_core/utils/pydantic.py +38 -9
- langchain_core/utils/utils.py +25 -9
- langchain_core/vectorstores/base.py +7 -20
- langchain_core/vectorstores/in_memory.py +23 -17
- langchain_core/vectorstores/utils.py +18 -12
- langchain_core/version.py +1 -1
- langchain_core-0.3.77.dist-info/METADATA +67 -0
- langchain_core-0.3.77.dist-info/RECORD +174 -0
- langchain_core-0.3.75.dist-info/METADATA +0 -106
- langchain_core-0.3.75.dist-info/RECORD +0 -174
- {langchain_core-0.3.75.dist-info → langchain_core-0.3.77.dist-info}/WHEEL +0 -0
- {langchain_core-0.3.75.dist-info → langchain_core-0.3.77.dist-info}/entry_points.txt +0 -0
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from copy import deepcopy
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Optional
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from collections.abc import Sequence
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def _retrieve_ref(path: str, schema: dict) -> dict:
|
|
12
|
+
def _retrieve_ref(path: str, schema: dict) -> Union[list, dict]:
|
|
13
13
|
components = path.split("/")
|
|
14
14
|
if components[0] != "#":
|
|
15
15
|
msg = (
|
|
@@ -17,9 +17,12 @@ def _retrieve_ref(path: str, schema: dict) -> dict:
|
|
|
17
17
|
"with #."
|
|
18
18
|
)
|
|
19
19
|
raise ValueError(msg)
|
|
20
|
-
out = schema
|
|
20
|
+
out: Union[list, dict] = schema
|
|
21
21
|
for component in components[1:]:
|
|
22
22
|
if component in out:
|
|
23
|
+
if isinstance(out, list):
|
|
24
|
+
msg = f"Reference '{path}' not found."
|
|
25
|
+
raise KeyError(msg)
|
|
23
26
|
out = out[component]
|
|
24
27
|
elif component.isdigit():
|
|
25
28
|
index = int(component)
|
|
@@ -36,6 +39,31 @@ def _retrieve_ref(path: str, schema: dict) -> dict:
|
|
|
36
39
|
return deepcopy(out)
|
|
37
40
|
|
|
38
41
|
|
|
42
|
+
def _process_dict_properties(
|
|
43
|
+
properties: dict[str, Any],
|
|
44
|
+
full_schema: dict[str, Any],
|
|
45
|
+
processed_refs: set[str],
|
|
46
|
+
skip_keys: Sequence[str],
|
|
47
|
+
*,
|
|
48
|
+
shallow_refs: bool,
|
|
49
|
+
) -> dict[str, Any]:
|
|
50
|
+
"""Process dictionary properties, recursing into nested structures."""
|
|
51
|
+
result: dict[str, Any] = {}
|
|
52
|
+
for key, value in properties.items():
|
|
53
|
+
if key in skip_keys:
|
|
54
|
+
# Skip recursion for specified keys, just copy the value as-is
|
|
55
|
+
result[key] = deepcopy(value)
|
|
56
|
+
elif isinstance(value, (dict, list)):
|
|
57
|
+
# Recursively process nested objects and arrays
|
|
58
|
+
result[key] = _dereference_refs_helper(
|
|
59
|
+
value, full_schema, processed_refs, skip_keys, shallow_refs
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
# Copy primitive values directly
|
|
63
|
+
result[key] = value
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
|
|
39
67
|
def _dereference_refs_helper(
|
|
40
68
|
obj: Any,
|
|
41
69
|
full_schema: dict[str, Any],
|
|
@@ -43,51 +71,87 @@ def _dereference_refs_helper(
|
|
|
43
71
|
skip_keys: Sequence[str],
|
|
44
72
|
shallow_refs: bool, # noqa: FBT001
|
|
45
73
|
) -> Any:
|
|
46
|
-
"""
|
|
74
|
+
"""Dereference JSON Schema $ref objects, handling both pure and mixed references.
|
|
47
75
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- if shallow_refs=False: deep-inline all nested refs
|
|
76
|
+
This function processes JSON Schema objects containing $ref properties by resolving
|
|
77
|
+
the references and merging any additional properties. It handles:
|
|
51
78
|
|
|
52
|
-
|
|
79
|
+
- Pure $ref objects: {"$ref": "#/path/to/definition"}
|
|
80
|
+
- Mixed $ref objects: {"$ref": "#/path", "title": "Custom Title", ...}
|
|
81
|
+
- Circular references by breaking cycles and preserving non-ref properties
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
obj: The object to process (can be dict, list, or primitive)
|
|
85
|
+
full_schema: The complete schema containing all definitions
|
|
86
|
+
processed_refs: Set tracking currently processing refs (for cycle detection)
|
|
87
|
+
skip_keys: Keys under which to skip recursion
|
|
88
|
+
shallow_refs: If True, only break cycles; if False, deep-inline all refs
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The object with $ref properties resolved and merged with other properties.
|
|
53
92
|
"""
|
|
54
93
|
if processed_refs is None:
|
|
55
94
|
processed_refs = set()
|
|
56
95
|
|
|
57
|
-
# 1
|
|
58
|
-
if isinstance(obj, dict) and "$ref" in
|
|
96
|
+
# Case 1: Object contains a $ref property (pure or mixed with additional properties)
|
|
97
|
+
if isinstance(obj, dict) and "$ref" in obj:
|
|
59
98
|
ref_path = obj["$ref"]
|
|
60
|
-
|
|
99
|
+
additional_properties = {
|
|
100
|
+
key: value for key, value in obj.items() if key != "$ref"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Detect circular reference: if we're already processing this $ref,
|
|
104
|
+
# return only the additional properties to break the cycle
|
|
61
105
|
if ref_path in processed_refs:
|
|
62
|
-
return
|
|
63
|
-
|
|
106
|
+
return _process_dict_properties(
|
|
107
|
+
additional_properties,
|
|
108
|
+
full_schema,
|
|
109
|
+
processed_refs,
|
|
110
|
+
skip_keys,
|
|
111
|
+
shallow_refs=shallow_refs,
|
|
112
|
+
)
|
|
64
113
|
|
|
65
|
-
#
|
|
66
|
-
|
|
114
|
+
# Mark this reference as being processed (for cycle detection)
|
|
115
|
+
processed_refs.add(ref_path)
|
|
67
116
|
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
117
|
+
# Fetch and recursively resolve the referenced object
|
|
118
|
+
referenced_object = deepcopy(_retrieve_ref(ref_path, full_schema))
|
|
119
|
+
resolved_reference = _dereference_refs_helper(
|
|
120
|
+
referenced_object, full_schema, processed_refs, skip_keys, shallow_refs
|
|
71
121
|
)
|
|
72
122
|
|
|
123
|
+
# Clean up: remove from processing set before returning
|
|
73
124
|
processed_refs.remove(ref_path)
|
|
74
|
-
return result
|
|
75
125
|
|
|
76
|
-
|
|
126
|
+
# Pure $ref case: no additional properties, return resolved reference directly
|
|
127
|
+
if not additional_properties:
|
|
128
|
+
return resolved_reference
|
|
129
|
+
|
|
130
|
+
# Mixed $ref case: merge resolved reference with additional properties
|
|
131
|
+
# Additional properties take precedence over resolved properties
|
|
132
|
+
merged_result = {}
|
|
133
|
+
if isinstance(resolved_reference, dict):
|
|
134
|
+
merged_result.update(resolved_reference)
|
|
135
|
+
|
|
136
|
+
# Process additional properties and merge them (they override resolved ones)
|
|
137
|
+
processed_additional = _process_dict_properties(
|
|
138
|
+
additional_properties,
|
|
139
|
+
full_schema,
|
|
140
|
+
processed_refs,
|
|
141
|
+
skip_keys,
|
|
142
|
+
shallow_refs=shallow_refs,
|
|
143
|
+
)
|
|
144
|
+
merged_result.update(processed_additional)
|
|
145
|
+
|
|
146
|
+
return merged_result
|
|
147
|
+
|
|
148
|
+
# Case 2: Regular dictionary without $ref - process all properties
|
|
77
149
|
if isinstance(obj, dict):
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
# do not recurse under this key
|
|
82
|
-
out[k] = deepcopy(v)
|
|
83
|
-
elif isinstance(v, (dict, list)):
|
|
84
|
-
out[k] = _dereference_refs_helper(
|
|
85
|
-
v, full_schema, processed_refs, skip_keys, shallow_refs
|
|
86
|
-
)
|
|
87
|
-
else:
|
|
88
|
-
out[k] = v
|
|
89
|
-
return out
|
|
150
|
+
return _process_dict_properties(
|
|
151
|
+
obj, full_schema, processed_refs, skip_keys, shallow_refs=shallow_refs
|
|
152
|
+
)
|
|
90
153
|
|
|
154
|
+
# Case 3: List - recursively process each item
|
|
91
155
|
if isinstance(obj, list):
|
|
92
156
|
return [
|
|
93
157
|
_dereference_refs_helper(
|
|
@@ -96,6 +160,7 @@ def _dereference_refs_helper(
|
|
|
96
160
|
for item in obj
|
|
97
161
|
]
|
|
98
162
|
|
|
163
|
+
# Case 4: Primitive value (string, number, boolean, null) - return unchanged
|
|
99
164
|
return obj
|
|
100
165
|
|
|
101
166
|
|
|
@@ -105,16 +170,67 @@ def dereference_refs(
|
|
|
105
170
|
full_schema: Optional[dict] = None,
|
|
106
171
|
skip_keys: Optional[Sequence[str]] = None,
|
|
107
172
|
) -> dict:
|
|
108
|
-
"""
|
|
173
|
+
"""Resolve and inline JSON Schema $ref references in a schema object.
|
|
174
|
+
|
|
175
|
+
This function processes a JSON Schema and resolves all $ref references by replacing
|
|
176
|
+
them with the actual referenced content. It handles both simple references and
|
|
177
|
+
complex cases like circular references and mixed $ref objects that contain
|
|
178
|
+
additional properties alongside the $ref.
|
|
109
179
|
|
|
110
180
|
Args:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
181
|
+
schema_obj: The JSON Schema object or fragment to process. This can be a
|
|
182
|
+
complete schema or just a portion of one.
|
|
183
|
+
full_schema: The complete schema containing all definitions that $refs might
|
|
184
|
+
point to. If not provided, defaults to schema_obj (useful when the
|
|
185
|
+
schema is self-contained).
|
|
186
|
+
skip_keys: Controls recursion behavior and reference resolution depth:
|
|
187
|
+
- If None (default): Only recurse under '$defs' and use shallow reference
|
|
188
|
+
resolution (break cycles but don't deep-inline nested refs)
|
|
189
|
+
- If provided (even as []): Recurse under all keys and use deep reference
|
|
190
|
+
resolution (fully inline all nested references)
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
A new dictionary with all $ref references resolved and inlined. The original
|
|
194
|
+
schema_obj is not modified.
|
|
195
|
+
|
|
196
|
+
Examples:
|
|
197
|
+
Basic reference resolution:
|
|
198
|
+
>>> schema = {
|
|
199
|
+
... "type": "object",
|
|
200
|
+
... "properties": {"name": {"$ref": "#/$defs/string_type"}},
|
|
201
|
+
... "$defs": {"string_type": {"type": "string"}},
|
|
202
|
+
... }
|
|
203
|
+
>>> result = dereference_refs(schema)
|
|
204
|
+
>>> result["properties"]["name"] # {"type": "string"}
|
|
205
|
+
|
|
206
|
+
Mixed $ref with additional properties:
|
|
207
|
+
>>> schema = {
|
|
208
|
+
... "properties": {
|
|
209
|
+
... "name": {"$ref": "#/$defs/base", "description": "User name"}
|
|
210
|
+
... },
|
|
211
|
+
... "$defs": {"base": {"type": "string", "minLength": 1}},
|
|
212
|
+
... }
|
|
213
|
+
>>> result = dereference_refs(schema)
|
|
214
|
+
>>> result["properties"]["name"]
|
|
215
|
+
# {"type": "string", "minLength": 1, "description": "User name"}
|
|
216
|
+
|
|
217
|
+
Handling circular references:
|
|
218
|
+
>>> schema = {
|
|
219
|
+
... "properties": {"user": {"$ref": "#/$defs/User"}},
|
|
220
|
+
... "$defs": {
|
|
221
|
+
... "User": {
|
|
222
|
+
... "type": "object",
|
|
223
|
+
... "properties": {"friend": {"$ref": "#/$defs/User"}},
|
|
224
|
+
... }
|
|
225
|
+
... },
|
|
226
|
+
... }
|
|
227
|
+
>>> result = dereference_refs(schema) # Won't cause infinite recursion
|
|
228
|
+
|
|
229
|
+
Note:
|
|
230
|
+
- Circular references are handled gracefully by breaking cycles
|
|
231
|
+
- Mixed $ref objects (with both $ref and other properties) are supported
|
|
232
|
+
- Additional properties in mixed $refs override resolved properties
|
|
233
|
+
- The $defs section is preserved in the output by default
|
|
118
234
|
"""
|
|
119
235
|
full = full_schema or schema_obj
|
|
120
236
|
keys_to_skip = list(skip_keys) if skip_keys is not None else ["$defs"]
|
langchain_core/utils/loading.py
CHANGED
|
@@ -19,7 +19,11 @@ def try_load_from_hub(
|
|
|
19
19
|
*args: Any, # noqa: ARG001
|
|
20
20
|
**kwargs: Any, # noqa: ARG001
|
|
21
21
|
) -> Any:
|
|
22
|
-
"""[DEPRECATED] Try to load from the old Hub.
|
|
22
|
+
"""[DEPRECATED] Try to load from the old Hub.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
None always, indicating that we shouldn't load from the old hub.
|
|
26
|
+
"""
|
|
23
27
|
warnings.warn(
|
|
24
28
|
"Loading from the deprecated github-based Hub is no longer supported. "
|
|
25
29
|
"Please use the new LangChain Hub at https://smith.langchain.com/hub instead.",
|
langchain_core/utils/mustache.py
CHANGED
|
@@ -82,7 +82,7 @@ def l_sa_check(
|
|
|
82
82
|
"""
|
|
83
83
|
# If there is a newline, or the previous tag was a standalone
|
|
84
84
|
if literal.find("\n") != -1 or is_standalone:
|
|
85
|
-
padding = literal.
|
|
85
|
+
padding = literal.rsplit("\n", maxsplit=1)[-1]
|
|
86
86
|
|
|
87
87
|
# If all the characters since the last newline are spaces
|
|
88
88
|
# Then the next tag could be a standalone
|
|
@@ -214,17 +214,22 @@ def tokenize(
|
|
|
214
214
|
def_rdel: The default right delimiter
|
|
215
215
|
("}}" by default, as in spec compliant mustache)
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
217
|
+
Yields:
|
|
218
|
+
Mustache tags in the form of a tuple (tag_type, tag_key)
|
|
219
|
+
where tag_type is one of:
|
|
220
|
+
|
|
221
|
+
* literal
|
|
222
|
+
* section
|
|
223
|
+
* inverted section
|
|
224
|
+
* end
|
|
225
|
+
* partial
|
|
226
|
+
* no escape
|
|
227
|
+
|
|
228
|
+
and tag_key is either the key or in the case of a literal tag,
|
|
229
|
+
the literal itself.
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
ChevronError: If there is a syntax error in the template.
|
|
228
233
|
"""
|
|
229
234
|
global _CURRENT_LINE, _LAST_TAG_LINE
|
|
230
235
|
_CURRENT_LINE = 1
|
|
@@ -326,7 +331,7 @@ def tokenize(
|
|
|
326
331
|
|
|
327
332
|
|
|
328
333
|
def _html_escape(string: str) -> str:
|
|
329
|
-
"""HTML
|
|
334
|
+
"""Return the HTML-escaped string with these characters escaped: ``" & < >``."""
|
|
330
335
|
html_codes = {
|
|
331
336
|
'"': """,
|
|
332
337
|
"<": "<",
|
|
@@ -349,7 +354,7 @@ def _get_key(
|
|
|
349
354
|
def_ldel: str,
|
|
350
355
|
def_rdel: str,
|
|
351
356
|
) -> Any:
|
|
352
|
-
"""
|
|
357
|
+
"""Return a key from the current scope."""
|
|
353
358
|
# If the key is a dot
|
|
354
359
|
if key == ".":
|
|
355
360
|
# Then just return the current scope
|
|
@@ -407,7 +412,11 @@ def _get_key(
|
|
|
407
412
|
|
|
408
413
|
|
|
409
414
|
def _get_partial(name: str, partials_dict: Mapping[str, str]) -> str:
|
|
410
|
-
"""Load a partial.
|
|
415
|
+
"""Load a partial.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
The partial.
|
|
419
|
+
"""
|
|
411
420
|
try:
|
|
412
421
|
# Maybe the partial is in the dictionary
|
|
413
422
|
return partials_dict[name]
|
langchain_core/utils/pydantic.py
CHANGED
|
@@ -57,6 +57,9 @@ def get_pydantic_major_version() -> int:
|
|
|
57
57
|
"""DEPRECATED - Get the major version of Pydantic.
|
|
58
58
|
|
|
59
59
|
Use PYDANTIC_VERSION.major instead.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The major version of Pydantic.
|
|
60
63
|
"""
|
|
61
64
|
return PYDANTIC_VERSION.major
|
|
62
65
|
|
|
@@ -74,12 +77,20 @@ TBaseModel = TypeVar("TBaseModel", bound=PydanticBaseModel)
|
|
|
74
77
|
|
|
75
78
|
|
|
76
79
|
def is_pydantic_v1_subclass(cls: type) -> bool:
|
|
77
|
-
"""Check if the
|
|
80
|
+
"""Check if the given class is Pydantic v1-like.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
True if the given class is a subclass of Pydantic ``BaseModel`` 1.x.
|
|
84
|
+
"""
|
|
78
85
|
return issubclass(cls, BaseModelV1)
|
|
79
86
|
|
|
80
87
|
|
|
81
88
|
def is_pydantic_v2_subclass(cls: type) -> bool:
|
|
82
|
-
"""Check if the
|
|
89
|
+
"""Check if the given class is Pydantic v2-like.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True if the given class is a subclass of Pydantic BaseModel 2.x.
|
|
93
|
+
"""
|
|
83
94
|
return issubclass(cls, BaseModel)
|
|
84
95
|
|
|
85
96
|
|
|
@@ -90,6 +101,9 @@ def is_basemodel_subclass(cls: type) -> bool:
|
|
|
90
101
|
|
|
91
102
|
* pydantic.BaseModel in Pydantic 2.x
|
|
92
103
|
* pydantic.v1.BaseModel in Pydantic 2.x
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if the given class is a subclass of Pydantic ``BaseModel``.
|
|
93
107
|
"""
|
|
94
108
|
# Before we can use issubclass on the cls we need to check if it is a class
|
|
95
109
|
if not inspect.isclass(cls) or isinstance(cls, GenericAlias):
|
|
@@ -105,6 +119,9 @@ def is_basemodel_instance(obj: Any) -> bool:
|
|
|
105
119
|
|
|
106
120
|
* pydantic.BaseModel in Pydantic 2.x
|
|
107
121
|
* pydantic.v1.BaseModel in Pydantic 2.x
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
True if the given class is an instance of Pydantic ``BaseModel``.
|
|
108
125
|
"""
|
|
109
126
|
return isinstance(obj, (BaseModel, BaseModelV1))
|
|
110
127
|
|
|
@@ -125,7 +142,7 @@ def pre_init(func: Callable) -> Any:
|
|
|
125
142
|
# Ideally we would use @model_validator(mode="before") but this would change the
|
|
126
143
|
# order of the validators. See https://github.com/pydantic/pydantic/discussions/7434.
|
|
127
144
|
# So we keep root_validator for backward compatibility.
|
|
128
|
-
@root_validator(pre=True)
|
|
145
|
+
@root_validator(pre=True) # type: ignore[deprecated]
|
|
129
146
|
@wraps(func)
|
|
130
147
|
def wrapper(cls: type[BaseModel], values: dict[str, Any]) -> dict[str, Any]:
|
|
131
148
|
"""Decorator to run a function before model initialization.
|
|
@@ -262,7 +279,11 @@ def _create_subset_model(
|
|
|
262
279
|
descriptions: Optional[dict] = None,
|
|
263
280
|
fn_description: Optional[str] = None,
|
|
264
281
|
) -> type[BaseModel]:
|
|
265
|
-
"""Create subset model using the same pydantic version as the input model.
|
|
282
|
+
"""Create subset model using the same pydantic version as the input model.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
The created subset model.
|
|
286
|
+
"""
|
|
266
287
|
if issubclass(model, BaseModelV1):
|
|
267
288
|
return _create_subset_model_v1(
|
|
268
289
|
name,
|
|
@@ -299,13 +320,21 @@ def get_fields(model: BaseModelV1) -> dict[str, ModelField]: ...
|
|
|
299
320
|
def get_fields(
|
|
300
321
|
model: Union[type[Union[BaseModel, BaseModelV1]], BaseModel, BaseModelV1],
|
|
301
322
|
) -> Union[dict[str, FieldInfoV2], dict[str, ModelField]]:
|
|
302
|
-
"""
|
|
303
|
-
if hasattr(model, "model_fields"):
|
|
304
|
-
return model.model_fields
|
|
323
|
+
"""Return the field names of a Pydantic model.
|
|
305
324
|
|
|
306
|
-
|
|
325
|
+
Args:
|
|
326
|
+
model: The Pydantic model or instance.
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
TypeError: If the model is not a Pydantic model.
|
|
330
|
+
"""
|
|
331
|
+
if not isinstance(model, type):
|
|
332
|
+
model = type(model)
|
|
333
|
+
if issubclass(model, BaseModel):
|
|
334
|
+
return model.model_fields
|
|
335
|
+
if issubclass(model, BaseModelV1):
|
|
307
336
|
return model.__fields__
|
|
308
|
-
msg = f"Expected a Pydantic model. Got {
|
|
337
|
+
msg = f"Expected a Pydantic model. Got {model}"
|
|
309
338
|
raise TypeError(msg)
|
|
310
339
|
|
|
311
340
|
|
langchain_core/utils/utils.py
CHANGED
|
@@ -21,17 +21,14 @@ from langchain_core.utils.pydantic import (
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def xor_args(*arg_groups: tuple[str, ...]) -> Callable:
|
|
24
|
-
"""Validate specified keyword args are mutually exclusive.
|
|
24
|
+
"""Validate specified keyword args are mutually exclusive.
|
|
25
25
|
|
|
26
26
|
Args:
|
|
27
27
|
*arg_groups (tuple[str, ...]): Groups of mutually exclusive keyword args.
|
|
28
28
|
|
|
29
29
|
Returns:
|
|
30
30
|
Callable: Decorator that validates the specified keyword args
|
|
31
|
-
are mutually exclusive
|
|
32
|
-
|
|
33
|
-
Raises:
|
|
34
|
-
ValueError: If more than one arg in a group is defined.
|
|
31
|
+
are mutually exclusive.
|
|
35
32
|
"""
|
|
36
33
|
|
|
37
34
|
def decorator(func: Callable) -> Callable:
|
|
@@ -137,7 +134,7 @@ def guard_import(
|
|
|
137
134
|
try:
|
|
138
135
|
module = importlib.import_module(module_name, package)
|
|
139
136
|
except (ImportError, ModuleNotFoundError) as e:
|
|
140
|
-
pip_name = pip_name or module_name.split(".")[0].replace("_", "-")
|
|
137
|
+
pip_name = pip_name or module_name.split(".", maxsplit=1)[0].replace("_", "-")
|
|
141
138
|
msg = (
|
|
142
139
|
f"Could not import {module_name} python package. "
|
|
143
140
|
f"Please install it with `pip install {pip_name}`."
|
|
@@ -223,7 +220,7 @@ def _build_model_kwargs(
|
|
|
223
220
|
values: dict[str, Any],
|
|
224
221
|
all_required_field_names: set[str],
|
|
225
222
|
) -> dict[str, Any]:
|
|
226
|
-
"""Build "model_kwargs" param from
|
|
223
|
+
"""Build "model_kwargs" param from Pydantic constructor values.
|
|
227
224
|
|
|
228
225
|
Args:
|
|
229
226
|
values: All init args passed in by user.
|
|
@@ -381,10 +378,21 @@ def from_env(
|
|
|
381
378
|
error_message: the error message which will be raised if the key is not found
|
|
382
379
|
and no default value is provided.
|
|
383
380
|
This will be raised as a ValueError.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
factory method that will look up the value from the environment.
|
|
384
384
|
"""
|
|
385
385
|
|
|
386
386
|
def get_from_env_fn() -> Optional[str]:
|
|
387
|
-
"""Get a value from an environment variable.
|
|
387
|
+
"""Get a value from an environment variable.
|
|
388
|
+
|
|
389
|
+
Raises:
|
|
390
|
+
ValueError: If the environment variable is not set and no default is
|
|
391
|
+
provided.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
The value from the environment.
|
|
395
|
+
"""
|
|
388
396
|
if isinstance(key, (list, tuple)):
|
|
389
397
|
for k in key:
|
|
390
398
|
if k in os.environ:
|
|
@@ -445,7 +453,15 @@ def secret_from_env(
|
|
|
445
453
|
"""
|
|
446
454
|
|
|
447
455
|
def get_secret_from_env() -> Optional[SecretStr]:
|
|
448
|
-
"""Get a value from an environment variable.
|
|
456
|
+
"""Get a value from an environment variable.
|
|
457
|
+
|
|
458
|
+
Raises:
|
|
459
|
+
ValueError: If the environment variable is not set and no default is
|
|
460
|
+
provided.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
The secret from the environment.
|
|
464
|
+
"""
|
|
449
465
|
if isinstance(key, (list, tuple)):
|
|
450
466
|
for k in key:
|
|
451
467
|
if k in os.environ:
|
|
@@ -38,6 +38,7 @@ from typing import (
|
|
|
38
38
|
from pydantic import ConfigDict, Field, model_validator
|
|
39
39
|
from typing_extensions import Self, override
|
|
40
40
|
|
|
41
|
+
from langchain_core.documents import Document
|
|
41
42
|
from langchain_core.embeddings import Embeddings
|
|
42
43
|
from langchain_core.retrievers import BaseRetriever, LangSmithRetrieverParams
|
|
43
44
|
from langchain_core.runnables.config import run_in_executor
|
|
@@ -49,7 +50,6 @@ if TYPE_CHECKING:
|
|
|
49
50
|
AsyncCallbackManagerForRetrieverRun,
|
|
50
51
|
CallbackManagerForRetrieverRun,
|
|
51
52
|
)
|
|
52
|
-
from langchain_core.documents import Document
|
|
53
53
|
|
|
54
54
|
logger = logging.getLogger(__name__)
|
|
55
55
|
|
|
@@ -85,9 +85,6 @@ class VectorStore(ABC):
|
|
|
85
85
|
ValueError: If the number of ids does not match the number of texts.
|
|
86
86
|
"""
|
|
87
87
|
if type(self).add_documents != VectorStore.add_documents:
|
|
88
|
-
# Import document in local scope to avoid circular imports
|
|
89
|
-
from langchain_core.documents import Document
|
|
90
|
-
|
|
91
88
|
# This condition is triggered if the subclass has provided
|
|
92
89
|
# an implementation of the upsert method.
|
|
93
90
|
# The existing add_texts
|
|
@@ -234,9 +231,6 @@ class VectorStore(ABC):
|
|
|
234
231
|
# For backward compatibility
|
|
235
232
|
kwargs["ids"] = ids
|
|
236
233
|
if type(self).aadd_documents != VectorStore.aadd_documents:
|
|
237
|
-
# Import document in local scope to avoid circular imports
|
|
238
|
-
from langchain_core.documents import Document
|
|
239
|
-
|
|
240
234
|
# This condition is triggered if the subclass has provided
|
|
241
235
|
# an implementation of the upsert method.
|
|
242
236
|
# The existing add_texts
|
|
@@ -270,9 +264,6 @@ class VectorStore(ABC):
|
|
|
270
264
|
|
|
271
265
|
Returns:
|
|
272
266
|
List of IDs of the added texts.
|
|
273
|
-
|
|
274
|
-
Raises:
|
|
275
|
-
ValueError: If the number of ids does not match the number of documents.
|
|
276
267
|
"""
|
|
277
268
|
if type(self).add_texts != VectorStore.add_texts:
|
|
278
269
|
if "ids" not in kwargs:
|
|
@@ -303,9 +294,6 @@ class VectorStore(ABC):
|
|
|
303
294
|
|
|
304
295
|
Returns:
|
|
305
296
|
List of IDs of the added texts.
|
|
306
|
-
|
|
307
|
-
Raises:
|
|
308
|
-
ValueError: If the number of IDs does not match the number of documents.
|
|
309
297
|
"""
|
|
310
298
|
# If the async method has been overridden, we'll use that.
|
|
311
299
|
if type(self).aadd_texts != VectorStore.aadd_texts:
|
|
@@ -435,6 +423,7 @@ class VectorStore(ABC):
|
|
|
435
423
|
"""The 'correct' relevance function.
|
|
436
424
|
|
|
437
425
|
may differ depending on a few things, including:
|
|
426
|
+
|
|
438
427
|
- the distance / similarity metric used by the VectorStore
|
|
439
428
|
- the scale of your embeddings (OpenAI's are unit normed. Many others are not!)
|
|
440
429
|
- embedding dimensionality
|
|
@@ -969,30 +958,28 @@ class VectorStore(ABC):
|
|
|
969
958
|
# Retrieve more documents with higher diversity
|
|
970
959
|
# Useful if your dataset has many similar documents
|
|
971
960
|
docsearch.as_retriever(
|
|
972
|
-
search_type="mmr",
|
|
973
|
-
search_kwargs={'k': 6, 'lambda_mult': 0.25}
|
|
961
|
+
search_type="mmr", search_kwargs={"k": 6, "lambda_mult": 0.25}
|
|
974
962
|
)
|
|
975
963
|
|
|
976
964
|
# Fetch more documents for the MMR algorithm to consider
|
|
977
965
|
# But only return the top 5
|
|
978
966
|
docsearch.as_retriever(
|
|
979
|
-
search_type="mmr",
|
|
980
|
-
search_kwargs={'k': 5, 'fetch_k': 50}
|
|
967
|
+
search_type="mmr", search_kwargs={"k": 5, "fetch_k": 50}
|
|
981
968
|
)
|
|
982
969
|
|
|
983
970
|
# Only retrieve documents that have a relevance score
|
|
984
971
|
# Above a certain threshold
|
|
985
972
|
docsearch.as_retriever(
|
|
986
973
|
search_type="similarity_score_threshold",
|
|
987
|
-
search_kwargs={
|
|
974
|
+
search_kwargs={"score_threshold": 0.8},
|
|
988
975
|
)
|
|
989
976
|
|
|
990
977
|
# Only get the single most similar document from the dataset
|
|
991
|
-
docsearch.as_retriever(search_kwargs={
|
|
978
|
+
docsearch.as_retriever(search_kwargs={"k": 1})
|
|
992
979
|
|
|
993
980
|
# Use a filter to only retrieve documents from a specific paper
|
|
994
981
|
docsearch.as_retriever(
|
|
995
|
-
search_kwargs={
|
|
982
|
+
search_kwargs={"filter": {"paper_title": "GPT-4 Technical Report"}}
|
|
996
983
|
)
|
|
997
984
|
|
|
998
985
|
"""
|