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.

Files changed (119) hide show
  1. langchain_core/_api/beta_decorator.py +22 -44
  2. langchain_core/_api/deprecation.py +30 -17
  3. langchain_core/_api/path.py +19 -2
  4. langchain_core/_import_utils.py +7 -0
  5. langchain_core/agents.py +10 -6
  6. langchain_core/beta/runnables/context.py +1 -2
  7. langchain_core/callbacks/base.py +28 -15
  8. langchain_core/callbacks/manager.py +83 -71
  9. langchain_core/callbacks/usage.py +6 -4
  10. langchain_core/chat_history.py +29 -21
  11. langchain_core/document_loaders/base.py +34 -9
  12. langchain_core/document_loaders/langsmith.py +4 -1
  13. langchain_core/documents/base.py +35 -10
  14. langchain_core/documents/transformers.py +4 -2
  15. langchain_core/embeddings/fake.py +8 -5
  16. langchain_core/env.py +2 -3
  17. langchain_core/example_selectors/base.py +12 -0
  18. langchain_core/exceptions.py +7 -0
  19. langchain_core/globals.py +17 -28
  20. langchain_core/indexing/api.py +88 -76
  21. langchain_core/indexing/base.py +5 -8
  22. langchain_core/indexing/in_memory.py +23 -3
  23. langchain_core/language_models/__init__.py +3 -2
  24. langchain_core/language_models/base.py +31 -20
  25. langchain_core/language_models/chat_models.py +98 -27
  26. langchain_core/language_models/fake_chat_models.py +10 -9
  27. langchain_core/language_models/llms.py +52 -18
  28. langchain_core/load/dump.py +2 -3
  29. langchain_core/load/load.py +15 -1
  30. langchain_core/load/serializable.py +39 -44
  31. langchain_core/memory.py +7 -3
  32. langchain_core/messages/ai.py +53 -24
  33. langchain_core/messages/base.py +43 -22
  34. langchain_core/messages/chat.py +4 -1
  35. langchain_core/messages/content_blocks.py +23 -2
  36. langchain_core/messages/function.py +9 -5
  37. langchain_core/messages/human.py +13 -10
  38. langchain_core/messages/modifier.py +1 -0
  39. langchain_core/messages/system.py +11 -8
  40. langchain_core/messages/tool.py +60 -29
  41. langchain_core/messages/utils.py +250 -131
  42. langchain_core/output_parsers/base.py +5 -2
  43. langchain_core/output_parsers/json.py +4 -4
  44. langchain_core/output_parsers/list.py +7 -22
  45. langchain_core/output_parsers/openai_functions.py +3 -0
  46. langchain_core/output_parsers/openai_tools.py +6 -1
  47. langchain_core/output_parsers/pydantic.py +4 -0
  48. langchain_core/output_parsers/string.py +5 -1
  49. langchain_core/output_parsers/xml.py +19 -19
  50. langchain_core/outputs/chat_generation.py +25 -10
  51. langchain_core/outputs/generation.py +14 -3
  52. langchain_core/outputs/llm_result.py +8 -1
  53. langchain_core/prompt_values.py +16 -6
  54. langchain_core/prompts/base.py +4 -9
  55. langchain_core/prompts/chat.py +89 -57
  56. langchain_core/prompts/dict.py +16 -8
  57. langchain_core/prompts/few_shot.py +12 -11
  58. langchain_core/prompts/few_shot_with_templates.py +5 -1
  59. langchain_core/prompts/image.py +12 -5
  60. langchain_core/prompts/message.py +5 -6
  61. langchain_core/prompts/pipeline.py +13 -8
  62. langchain_core/prompts/prompt.py +22 -8
  63. langchain_core/prompts/string.py +18 -10
  64. langchain_core/prompts/structured.py +7 -2
  65. langchain_core/rate_limiters.py +2 -2
  66. langchain_core/retrievers.py +7 -6
  67. langchain_core/runnables/base.py +406 -186
  68. langchain_core/runnables/branch.py +14 -19
  69. langchain_core/runnables/config.py +9 -15
  70. langchain_core/runnables/configurable.py +34 -19
  71. langchain_core/runnables/fallbacks.py +20 -13
  72. langchain_core/runnables/graph.py +48 -38
  73. langchain_core/runnables/graph_ascii.py +41 -18
  74. langchain_core/runnables/graph_mermaid.py +54 -25
  75. langchain_core/runnables/graph_png.py +27 -31
  76. langchain_core/runnables/history.py +55 -58
  77. langchain_core/runnables/passthrough.py +44 -21
  78. langchain_core/runnables/retry.py +44 -23
  79. langchain_core/runnables/router.py +9 -8
  80. langchain_core/runnables/schema.py +2 -0
  81. langchain_core/runnables/utils.py +51 -89
  82. langchain_core/stores.py +19 -31
  83. langchain_core/sys_info.py +9 -8
  84. langchain_core/tools/base.py +37 -28
  85. langchain_core/tools/convert.py +26 -15
  86. langchain_core/tools/simple.py +36 -8
  87. langchain_core/tools/structured.py +25 -12
  88. langchain_core/tracers/base.py +2 -2
  89. langchain_core/tracers/context.py +5 -1
  90. langchain_core/tracers/core.py +109 -39
  91. langchain_core/tracers/evaluation.py +22 -26
  92. langchain_core/tracers/event_stream.py +45 -34
  93. langchain_core/tracers/langchain.py +12 -3
  94. langchain_core/tracers/langchain_v1.py +10 -2
  95. langchain_core/tracers/log_stream.py +56 -17
  96. langchain_core/tracers/root_listeners.py +4 -20
  97. langchain_core/tracers/run_collector.py +6 -16
  98. langchain_core/tracers/schemas.py +5 -1
  99. langchain_core/utils/aiter.py +15 -7
  100. langchain_core/utils/env.py +3 -0
  101. langchain_core/utils/function_calling.py +50 -28
  102. langchain_core/utils/interactive_env.py +6 -2
  103. langchain_core/utils/iter.py +12 -4
  104. langchain_core/utils/json.py +12 -3
  105. langchain_core/utils/json_schema.py +156 -40
  106. langchain_core/utils/loading.py +5 -1
  107. langchain_core/utils/mustache.py +24 -15
  108. langchain_core/utils/pydantic.py +38 -9
  109. langchain_core/utils/utils.py +25 -9
  110. langchain_core/vectorstores/base.py +7 -20
  111. langchain_core/vectorstores/in_memory.py +23 -17
  112. langchain_core/vectorstores/utils.py +18 -12
  113. langchain_core/version.py +1 -1
  114. langchain_core-0.3.77.dist-info/METADATA +67 -0
  115. langchain_core-0.3.77.dist-info/RECORD +174 -0
  116. langchain_core-0.3.75.dist-info/METADATA +0 -106
  117. langchain_core-0.3.75.dist-info/RECORD +0 -174
  118. {langchain_core-0.3.75.dist-info → langchain_core-0.3.77.dist-info}/WHEEL +0 -0
  119. {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
- """Inline every pure {'$ref':...}.
74
+ """Dereference JSON Schema $ref objects, handling both pure and mixed references.
47
75
 
48
- But:
49
- - if shallow_refs=True: only break cycles, do not inline nested refs
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
- Also skip recursion under any key in skip_keys.
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) Pure $ref node?
58
- if isinstance(obj, dict) and "$ref" in set(obj.keys()):
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
- # cycle?
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
- processed_refs.add(ref_path)
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
- # grab + copy the target
66
- target = deepcopy(_retrieve_ref(ref_path, full_schema))
114
+ # Mark this reference as being processed (for cycle detection)
115
+ processed_refs.add(ref_path)
67
116
 
68
- # deep inlining: recurse into everything
69
- result = _dereference_refs_helper(
70
- target, full_schema, processed_refs, skip_keys, shallow_refs
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
- # 2) Not a pure-$ref: recurse, skipping any keys in skip_keys
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
- out: dict[str, Any] = {}
79
- for k, v in obj.items():
80
- if k in skip_keys:
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
- """Try to substitute $refs in JSON Schema.
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
- schema_obj: The fragment to dereference.
112
- full_schema: The complete schema (defaults to schema_obj).
113
- skip_keys:
114
- - If None (the default), we skip recursion under '$defs' *and* only
115
- shallow-inline refs.
116
- - If provided (even as an empty list), we will recurse under every key and
117
- deep-inline all refs.
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"]
@@ -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.",
@@ -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.split("\n")[-1]
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
- Returns:
218
- A generator of mustache tags in the form of a tuple (tag_type, tag_key)
219
- Where tag_type is one of:
220
- * literal
221
- * section
222
- * inverted section
223
- * end
224
- * partial
225
- * no escape
226
- And tag_key is either the key or in the case of a literal tag,
227
- the literal itself.
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 escape all of these " & < >."""
334
+ """Return the HTML-escaped string with these characters escaped: ``" & < >``."""
330
335
  html_codes = {
331
336
  '"': "&quot;",
332
337
  "<": "&lt;",
@@ -349,7 +354,7 @@ def _get_key(
349
354
  def_ldel: str,
350
355
  def_rdel: str,
351
356
  ) -> Any:
352
- """Get a key from the current scope."""
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]
@@ -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 installed Pydantic version is 1.x-like."""
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 installed Pydantic version is 1.x-like."""
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
- """Get the field names of a Pydantic model."""
303
- if hasattr(model, "model_fields"):
304
- return model.model_fields
323
+ """Return the field names of a Pydantic model.
305
324
 
306
- if hasattr(model, "__fields__"):
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 {type(model)}"
337
+ msg = f"Expected a Pydantic model. Got {model}"
309
338
  raise TypeError(msg)
310
339
 
311
340
 
@@ -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 Pydanitc constructor values.
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={'score_threshold': 0.8}
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={'k': 1})
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={'filter': {'paper_title':'GPT-4 Technical Report'}}
982
+ search_kwargs={"filter": {"paper_title": "GPT-4 Technical Report"}}
996
983
  )
997
984
 
998
985
  """