langchain-core 1.0.0a6__py3-none-any.whl → 1.0.0a8__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 (131) hide show
  1. langchain_core/_api/__init__.py +3 -3
  2. langchain_core/_api/beta_decorator.py +6 -6
  3. langchain_core/_api/deprecation.py +21 -29
  4. langchain_core/_api/path.py +3 -6
  5. langchain_core/_import_utils.py +2 -3
  6. langchain_core/agents.py +10 -11
  7. langchain_core/caches.py +7 -7
  8. langchain_core/callbacks/base.py +91 -91
  9. langchain_core/callbacks/file.py +11 -11
  10. langchain_core/callbacks/manager.py +86 -89
  11. langchain_core/callbacks/stdout.py +8 -8
  12. langchain_core/callbacks/usage.py +4 -4
  13. langchain_core/chat_history.py +5 -5
  14. langchain_core/document_loaders/base.py +2 -2
  15. langchain_core/document_loaders/langsmith.py +15 -15
  16. langchain_core/documents/base.py +16 -16
  17. langchain_core/documents/compressor.py +4 -4
  18. langchain_core/example_selectors/length_based.py +1 -1
  19. langchain_core/example_selectors/semantic_similarity.py +17 -19
  20. langchain_core/exceptions.py +3 -3
  21. langchain_core/globals.py +3 -151
  22. langchain_core/indexing/api.py +44 -43
  23. langchain_core/indexing/base.py +30 -30
  24. langchain_core/indexing/in_memory.py +3 -3
  25. langchain_core/language_models/_utils.py +5 -7
  26. langchain_core/language_models/base.py +18 -132
  27. langchain_core/language_models/chat_models.py +118 -227
  28. langchain_core/language_models/fake.py +11 -11
  29. langchain_core/language_models/fake_chat_models.py +35 -29
  30. langchain_core/language_models/llms.py +91 -201
  31. langchain_core/load/dump.py +1 -1
  32. langchain_core/load/load.py +11 -12
  33. langchain_core/load/mapping.py +2 -4
  34. langchain_core/load/serializable.py +2 -4
  35. langchain_core/messages/ai.py +17 -20
  36. langchain_core/messages/base.py +23 -25
  37. langchain_core/messages/block_translators/__init__.py +2 -5
  38. langchain_core/messages/block_translators/anthropic.py +3 -3
  39. langchain_core/messages/block_translators/bedrock_converse.py +2 -2
  40. langchain_core/messages/block_translators/langchain_v0.py +2 -2
  41. langchain_core/messages/block_translators/openai.py +6 -6
  42. langchain_core/messages/content.py +120 -124
  43. langchain_core/messages/human.py +7 -7
  44. langchain_core/messages/system.py +7 -7
  45. langchain_core/messages/tool.py +24 -24
  46. langchain_core/messages/utils.py +67 -79
  47. langchain_core/output_parsers/base.py +12 -14
  48. langchain_core/output_parsers/json.py +4 -4
  49. langchain_core/output_parsers/list.py +3 -5
  50. langchain_core/output_parsers/openai_functions.py +3 -3
  51. langchain_core/output_parsers/openai_tools.py +3 -3
  52. langchain_core/output_parsers/pydantic.py +2 -2
  53. langchain_core/output_parsers/transform.py +13 -15
  54. langchain_core/output_parsers/xml.py +7 -9
  55. langchain_core/outputs/chat_generation.py +4 -4
  56. langchain_core/outputs/chat_result.py +1 -3
  57. langchain_core/outputs/generation.py +2 -2
  58. langchain_core/outputs/llm_result.py +5 -5
  59. langchain_core/prompts/__init__.py +1 -5
  60. langchain_core/prompts/base.py +10 -15
  61. langchain_core/prompts/chat.py +31 -82
  62. langchain_core/prompts/dict.py +2 -2
  63. langchain_core/prompts/few_shot.py +5 -5
  64. langchain_core/prompts/few_shot_with_templates.py +4 -4
  65. langchain_core/prompts/loading.py +3 -5
  66. langchain_core/prompts/prompt.py +4 -16
  67. langchain_core/prompts/string.py +2 -1
  68. langchain_core/prompts/structured.py +16 -23
  69. langchain_core/rate_limiters.py +3 -4
  70. langchain_core/retrievers.py +14 -14
  71. langchain_core/runnables/base.py +928 -1042
  72. langchain_core/runnables/branch.py +36 -40
  73. langchain_core/runnables/config.py +27 -35
  74. langchain_core/runnables/configurable.py +108 -124
  75. langchain_core/runnables/fallbacks.py +76 -72
  76. langchain_core/runnables/graph.py +39 -45
  77. langchain_core/runnables/graph_ascii.py +9 -11
  78. langchain_core/runnables/graph_mermaid.py +18 -19
  79. langchain_core/runnables/graph_png.py +8 -9
  80. langchain_core/runnables/history.py +114 -127
  81. langchain_core/runnables/passthrough.py +113 -139
  82. langchain_core/runnables/retry.py +43 -48
  83. langchain_core/runnables/router.py +23 -28
  84. langchain_core/runnables/schema.py +42 -44
  85. langchain_core/runnables/utils.py +28 -31
  86. langchain_core/stores.py +9 -13
  87. langchain_core/structured_query.py +8 -8
  88. langchain_core/tools/base.py +62 -115
  89. langchain_core/tools/convert.py +31 -35
  90. langchain_core/tools/render.py +1 -1
  91. langchain_core/tools/retriever.py +4 -4
  92. langchain_core/tools/simple.py +13 -17
  93. langchain_core/tools/structured.py +12 -15
  94. langchain_core/tracers/base.py +62 -64
  95. langchain_core/tracers/context.py +17 -35
  96. langchain_core/tracers/core.py +49 -53
  97. langchain_core/tracers/evaluation.py +11 -11
  98. langchain_core/tracers/event_stream.py +58 -60
  99. langchain_core/tracers/langchain.py +13 -13
  100. langchain_core/tracers/log_stream.py +22 -24
  101. langchain_core/tracers/root_listeners.py +14 -14
  102. langchain_core/tracers/run_collector.py +2 -4
  103. langchain_core/tracers/schemas.py +8 -8
  104. langchain_core/tracers/stdout.py +2 -1
  105. langchain_core/utils/__init__.py +0 -3
  106. langchain_core/utils/_merge.py +2 -2
  107. langchain_core/utils/aiter.py +24 -28
  108. langchain_core/utils/env.py +4 -4
  109. langchain_core/utils/function_calling.py +31 -41
  110. langchain_core/utils/html.py +3 -4
  111. langchain_core/utils/input.py +3 -3
  112. langchain_core/utils/iter.py +15 -19
  113. langchain_core/utils/json.py +3 -2
  114. langchain_core/utils/json_schema.py +6 -6
  115. langchain_core/utils/mustache.py +3 -5
  116. langchain_core/utils/pydantic.py +16 -18
  117. langchain_core/utils/usage.py +1 -1
  118. langchain_core/utils/utils.py +29 -29
  119. langchain_core/vectorstores/base.py +18 -21
  120. langchain_core/vectorstores/in_memory.py +14 -87
  121. langchain_core/vectorstores/utils.py +2 -2
  122. langchain_core/version.py +1 -1
  123. {langchain_core-1.0.0a6.dist-info → langchain_core-1.0.0a8.dist-info}/METADATA +10 -21
  124. langchain_core-1.0.0a8.dist-info/RECORD +176 -0
  125. {langchain_core-1.0.0a6.dist-info → langchain_core-1.0.0a8.dist-info}/WHEEL +1 -1
  126. langchain_core/messages/block_translators/ollama.py +0 -47
  127. langchain_core/prompts/pipeline.py +0 -138
  128. langchain_core/tracers/langchain_v1.py +0 -31
  129. langchain_core/utils/loading.py +0 -35
  130. langchain_core-1.0.0a6.dist-info/RECORD +0 -181
  131. langchain_core-1.0.0a6.dist-info/entry_points.txt +0 -4
@@ -5,7 +5,7 @@ import inspect
5
5
  import typing
6
6
  from collections.abc import AsyncIterator, Iterator, Sequence
7
7
  from functools import wraps
8
- from typing import TYPE_CHECKING, Any, Optional, Union, cast
8
+ from typing import TYPE_CHECKING, Any, cast
9
9
 
10
10
  from pydantic import BaseModel, ConfigDict
11
11
  from typing_extensions import override
@@ -51,41 +51,39 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
51
51
  more convenient to use the ``with_fallbacks`` method on a Runnable.
52
52
 
53
53
  Example:
54
+ ```python
55
+ from langchain_core.chat_models.openai import ChatOpenAI
56
+ from langchain_core.chat_models.anthropic import ChatAnthropic
54
57
 
55
- .. code-block:: python
56
-
57
- from langchain_core.chat_models.openai import ChatOpenAI
58
- from langchain_core.chat_models.anthropic import ChatAnthropic
59
-
60
- model = ChatAnthropic(model="claude-3-haiku-20240307").with_fallbacks(
61
- [ChatOpenAI(model="gpt-3.5-turbo-0125")]
62
- )
63
- # Will usually use ChatAnthropic, but fallback to ChatOpenAI
64
- # if ChatAnthropic fails.
65
- model.invoke("hello")
66
-
67
- # And you can also use fallbacks at the level of a chain.
68
- # Here if both LLM providers fail, we'll fallback to a good hardcoded
69
- # response.
58
+ model = ChatAnthropic(model="claude-3-haiku-20240307").with_fallbacks(
59
+ [ChatOpenAI(model="gpt-3.5-turbo-0125")]
60
+ )
61
+ # Will usually use ChatAnthropic, but fallback to ChatOpenAI
62
+ # if ChatAnthropic fails.
63
+ model.invoke("hello")
70
64
 
71
- from langchain_core.prompts import PromptTemplate
72
- from langchain_core.output_parser import StrOutputParser
73
- from langchain_core.runnables import RunnableLambda
65
+ # And you can also use fallbacks at the level of a chain.
66
+ # Here if both LLM providers fail, we'll fallback to a good hardcoded
67
+ # response.
74
68
 
69
+ from langchain_core.prompts import PromptTemplate
70
+ from langchain_core.output_parser import StrOutputParser
71
+ from langchain_core.runnables import RunnableLambda
75
72
 
76
- def when_all_is_lost(inputs):
77
- return (
78
- "Looks like our LLM providers are down. "
79
- "Here's a nice 🦜️ emoji for you instead."
80
- )
81
73
 
74
+ def when_all_is_lost(inputs):
75
+ return (
76
+ "Looks like our LLM providers are down. "
77
+ "Here's a nice 🦜️ emoji for you instead."
78
+ )
82
79
 
83
- chain_with_fallback = (
84
- PromptTemplate.from_template("Tell me a joke about {topic}")
85
- | model
86
- | StrOutputParser()
87
- ).with_fallbacks([RunnableLambda(when_all_is_lost)])
88
80
 
81
+ chain_with_fallback = (
82
+ PromptTemplate.from_template("Tell me a joke about {topic}")
83
+ | model
84
+ | StrOutputParser()
85
+ ).with_fallbacks([RunnableLambda(when_all_is_lost)])
86
+ ```
89
87
  """
90
88
 
91
89
  runnable: Runnable[Input, Output]
@@ -97,7 +95,7 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
97
95
 
98
96
  Any exception that is not a subclass of these exceptions will be raised immediately.
99
97
  """
100
- exception_key: Optional[str] = None
98
+ exception_key: str | None = None
101
99
  """If string is specified then handled exceptions will be passed to fallbacks as
102
100
  part of the input under the specified key. If None, exceptions
103
101
  will not be passed to fallbacks. If used, the base Runnable and its fallbacks
@@ -118,14 +116,12 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
118
116
  return self.runnable.OutputType
119
117
 
120
118
  @override
121
- def get_input_schema(
122
- self, config: Optional[RunnableConfig] = None
123
- ) -> type[BaseModel]:
119
+ def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]:
124
120
  return self.runnable.get_input_schema(config)
125
121
 
126
122
  @override
127
123
  def get_output_schema(
128
- self, config: Optional[RunnableConfig] = None
124
+ self, config: RunnableConfig | None = None
129
125
  ) -> type[BaseModel]:
130
126
  return self.runnable.get_output_schema(config)
131
127
 
@@ -166,7 +162,7 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
166
162
 
167
163
  @override
168
164
  def invoke(
169
- self, input: Input, config: Optional[RunnableConfig] = None, **kwargs: Any
165
+ self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
170
166
  ) -> Output:
171
167
  if self.exception_key is not None and not isinstance(input, dict):
172
168
  msg = (
@@ -218,8 +214,8 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
218
214
  async def ainvoke(
219
215
  self,
220
216
  input: Input,
221
- config: Optional[RunnableConfig] = None,
222
- **kwargs: Optional[Any],
217
+ config: RunnableConfig | None = None,
218
+ **kwargs: Any | None,
223
219
  ) -> Output:
224
220
  if self.exception_key is not None and not isinstance(input, dict):
225
221
  msg = (
@@ -268,10 +264,10 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
268
264
  def batch(
269
265
  self,
270
266
  inputs: list[Input],
271
- config: Optional[Union[RunnableConfig, list[RunnableConfig]]] = None,
267
+ config: RunnableConfig | list[RunnableConfig] | None = None,
272
268
  *,
273
269
  return_exceptions: bool = False,
274
- **kwargs: Optional[Any],
270
+ **kwargs: Any | None,
275
271
  ) -> list[Output]:
276
272
  if self.exception_key is not None and not all(
277
273
  isinstance(input_, dict) for input_ in inputs
@@ -307,7 +303,9 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
307
303
  name=config.get("run_name") or self.get_name(),
308
304
  run_id=config.pop("run_id", None),
309
305
  )
310
- for cm, input_, config in zip(callback_managers, inputs, configs)
306
+ for cm, input_, config in zip(
307
+ callback_managers, inputs, configs, strict=False
308
+ )
311
309
  ]
312
310
 
313
311
  to_return: dict[int, Any] = {}
@@ -325,7 +323,9 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
325
323
  return_exceptions=True,
326
324
  **kwargs,
327
325
  )
328
- for (i, input_), output in zip(sorted(run_again.copy().items()), outputs):
326
+ for (i, input_), output in zip(
327
+ sorted(run_again.copy().items()), outputs, strict=False
328
+ ):
329
329
  if isinstance(output, BaseException) and not isinstance(
330
330
  output, self.exceptions_to_handle
331
331
  ):
@@ -360,10 +360,10 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
360
360
  async def abatch(
361
361
  self,
362
362
  inputs: list[Input],
363
- config: Optional[Union[RunnableConfig, list[RunnableConfig]]] = None,
363
+ config: RunnableConfig | list[RunnableConfig] | None = None,
364
364
  *,
365
365
  return_exceptions: bool = False,
366
- **kwargs: Optional[Any],
366
+ **kwargs: Any | None,
367
367
  ) -> list[Output]:
368
368
  if self.exception_key is not None and not all(
369
369
  isinstance(input_, dict) for input_ in inputs
@@ -400,11 +400,13 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
400
400
  name=config.get("run_name") or self.get_name(),
401
401
  run_id=config.pop("run_id", None),
402
402
  )
403
- for cm, input_, config in zip(callback_managers, inputs, configs)
403
+ for cm, input_, config in zip(
404
+ callback_managers, inputs, configs, strict=False
405
+ )
404
406
  )
405
407
  )
406
408
 
407
- to_return: dict[int, Union[Output, BaseException]] = {}
409
+ to_return: dict[int, Output | BaseException] = {}
408
410
  run_again = dict(enumerate(inputs))
409
411
  handled_exceptions: dict[int, BaseException] = {}
410
412
  first_to_raise = None
@@ -420,7 +422,9 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
420
422
  **kwargs,
421
423
  )
422
424
 
423
- for (i, input_), output in zip(sorted(run_again.copy().items()), outputs):
425
+ for (i, input_), output in zip(
426
+ sorted(run_again.copy().items()), outputs, strict=False
427
+ ):
424
428
  if isinstance(output, BaseException) and not isinstance(
425
429
  output, self.exceptions_to_handle
426
430
  ):
@@ -460,8 +464,8 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
460
464
  def stream(
461
465
  self,
462
466
  input: Input,
463
- config: Optional[RunnableConfig] = None,
464
- **kwargs: Optional[Any],
467
+ config: RunnableConfig | None = None,
468
+ **kwargs: Any | None,
465
469
  ) -> Iterator[Output]:
466
470
  if self.exception_key is not None and not isinstance(input, dict):
467
471
  msg = (
@@ -507,7 +511,7 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
507
511
  raise first_error
508
512
 
509
513
  yield chunk
510
- output: Optional[Output] = chunk
514
+ output: Output | None = chunk
511
515
  try:
512
516
  for chunk in stream:
513
517
  yield chunk
@@ -524,8 +528,8 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
524
528
  async def astream(
525
529
  self,
526
530
  input: Input,
527
- config: Optional[RunnableConfig] = None,
528
- **kwargs: Optional[Any],
531
+ config: RunnableConfig | None = None,
532
+ **kwargs: Any | None,
529
533
  ) -> AsyncIterator[Output]:
530
534
  if self.exception_key is not None and not isinstance(input, dict):
531
535
  msg = (
@@ -571,7 +575,7 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
571
575
  raise first_error
572
576
 
573
577
  yield chunk
574
- output: Optional[Output] = chunk
578
+ output: Output | None = chunk
575
579
  try:
576
580
  async for chunk in stream:
577
581
  yield chunk
@@ -595,27 +599,27 @@ class RunnableWithFallbacks(RunnableSerializable[Input, Output]):
595
599
  self.fallbacks is replaced with getattr(x, name).
596
600
 
597
601
  Example:
598
- .. code-block:: python
599
-
600
- from langchain_openai import ChatOpenAI
601
- from langchain_anthropic import ChatAnthropic
602
-
603
- gpt_4o = ChatOpenAI(model="gpt-4o")
604
- claude_3_sonnet = ChatAnthropic(model="claude-3-7-sonnet-20250219")
605
- llm = gpt_4o.with_fallbacks([claude_3_sonnet])
606
-
607
- llm.model_name
608
- # -> "gpt-4o"
609
-
610
- # .bind_tools() is called on both ChatOpenAI and ChatAnthropic
611
- # Equivalent to:
612
- # gpt_4o.bind_tools([...]).with_fallbacks([claude_3_sonnet.bind_tools([...])])
613
- llm.bind_tools([...])
614
- # -> RunnableWithFallbacks(
615
- runnable=RunnableBinding(bound=ChatOpenAI(...), kwargs={"tools": [...]}),
616
- fallbacks=[RunnableBinding(bound=ChatAnthropic(...), kwargs={"tools": [...]})],
617
- )
602
+ ```python
603
+ from langchain_openai import ChatOpenAI
604
+ from langchain_anthropic import ChatAnthropic
605
+
606
+ gpt_4o = ChatOpenAI(model="gpt-4o")
607
+ claude_3_sonnet = ChatAnthropic(model="claude-3-7-sonnet-20250219")
608
+ llm = gpt_4o.with_fallbacks([claude_3_sonnet])
609
+
610
+ llm.model_name
611
+ # -> "gpt-4o"
612
+
613
+ # .bind_tools() is called on both ChatOpenAI and ChatAnthropic
614
+ # Equivalent to:
615
+ # gpt_4o.bind_tools([...]).with_fallbacks([claude_3_sonnet.bind_tools([...])])
616
+ llm.bind_tools([...])
617
+ # -> RunnableWithFallbacks(
618
+ runnable=RunnableBinding(bound=ChatOpenAI(...), kwargs={"tools": [...]}),
619
+ fallbacks=[RunnableBinding(bound=ChatAnthropic(...), kwargs={"tools": [...]})],
620
+ )
618
621
 
622
+ ```
619
623
  """ # noqa: E501
620
624
  attr = getattr(self.runnable, name)
621
625
  if _returns_runnable(attr):
@@ -4,17 +4,15 @@ from __future__ import annotations
4
4
 
5
5
  import inspect
6
6
  from collections import defaultdict
7
+ from collections.abc import Callable
7
8
  from dataclasses import dataclass, field
8
9
  from enum import Enum
9
10
  from typing import (
10
11
  TYPE_CHECKING,
11
12
  Any,
12
- Callable,
13
13
  NamedTuple,
14
- Optional,
15
14
  Protocol,
16
15
  TypedDict,
17
- Union,
18
16
  overload,
19
17
  )
20
18
  from uuid import UUID, uuid4
@@ -70,14 +68,12 @@ class Edge(NamedTuple):
70
68
  """The source node id."""
71
69
  target: str
72
70
  """The target node id."""
73
- data: Optional[Stringifiable] = None
71
+ data: Stringifiable | None = None
74
72
  """Optional data associated with the edge. Defaults to None."""
75
73
  conditional: bool = False
76
74
  """Whether the edge is conditional. Defaults to False."""
77
75
 
78
- def copy(
79
- self, *, source: Optional[str] = None, target: Optional[str] = None
80
- ) -> Edge:
76
+ def copy(self, *, source: str | None = None, target: str | None = None) -> Edge:
81
77
  """Return a copy of the edge with optional new source and target nodes.
82
78
 
83
79
  Args:
@@ -102,16 +98,16 @@ class Node(NamedTuple):
102
98
  """The unique identifier of the node."""
103
99
  name: str
104
100
  """The name of the node."""
105
- data: Union[type[BaseModel], RunnableType, None]
101
+ data: type[BaseModel] | RunnableType | None
106
102
  """The data of the node."""
107
- metadata: Optional[dict[str, Any]]
103
+ metadata: dict[str, Any] | None
108
104
  """Optional metadata for the node. Defaults to None."""
109
105
 
110
106
  def copy(
111
107
  self,
112
108
  *,
113
- id: Optional[str] = None,
114
- name: Optional[str] = None,
109
+ id: str | None = None,
110
+ name: str | None = None,
115
111
  ) -> Node:
116
112
  """Return a copy of the node with optional new id and name.
117
113
 
@@ -135,7 +131,7 @@ class Branch(NamedTuple):
135
131
 
136
132
  condition: Callable[..., str]
137
133
  """A callable that returns a string representation of the condition."""
138
- ends: Optional[dict[str, str]]
134
+ ends: dict[str, str] | None
139
135
  """Optional dictionary of end node ids for the branches. Defaults to None."""
140
136
 
141
137
 
@@ -182,7 +178,7 @@ class MermaidDrawMethod(Enum):
182
178
 
183
179
  def node_data_str(
184
180
  id: str,
185
- data: Union[type[BaseModel], RunnableType, None],
181
+ data: type[BaseModel] | RunnableType | None,
186
182
  ) -> str:
187
183
  """Convert the data of a node to a string.
188
184
 
@@ -201,7 +197,7 @@ def node_data_str(
201
197
 
202
198
  def node_data_json(
203
199
  node: Node, *, with_schemas: bool = False
204
- ) -> dict[str, Union[str, dict[str, Any]]]:
200
+ ) -> dict[str, str | dict[str, Any]]:
205
201
  """Convert the data of a node to a JSON-serializable format.
206
202
 
207
203
  Args:
@@ -316,10 +312,10 @@ class Graph:
316
312
 
317
313
  def add_node(
318
314
  self,
319
- data: Union[type[BaseModel], RunnableType, None],
320
- id: Optional[str] = None,
315
+ data: type[BaseModel] | RunnableType | None,
316
+ id: str | None = None,
321
317
  *,
322
- metadata: Optional[dict[str, Any]] = None,
318
+ metadata: dict[str, Any] | None = None,
323
319
  ) -> Node:
324
320
  """Add a node to the graph and return it.
325
321
 
@@ -357,7 +353,7 @@ class Graph:
357
353
  self,
358
354
  source: Node,
359
355
  target: Node,
360
- data: Optional[Stringifiable] = None,
356
+ data: Stringifiable | None = None,
361
357
  conditional: bool = False, # noqa: FBT001,FBT002
362
358
  ) -> Edge:
363
359
  """Add an edge to the graph and return it.
@@ -388,7 +384,7 @@ class Graph:
388
384
 
389
385
  def extend(
390
386
  self, graph: Graph, *, prefix: str = ""
391
- ) -> tuple[Optional[Node], Optional[Node]]:
387
+ ) -> tuple[Node | None, Node | None]:
392
388
  """Add all nodes and edges from another graph.
393
389
 
394
390
  Note this doesn't check for duplicates, nor does it connect the graphs.
@@ -459,7 +455,7 @@ class Graph:
459
455
  ],
460
456
  )
461
457
 
462
- def first_node(self) -> Optional[Node]:
458
+ def first_node(self) -> Node | None:
463
459
  """Find the single node that is not a target of any edge.
464
460
 
465
461
  If there is no such node, or there are multiple, return None.
@@ -471,7 +467,7 @@ class Graph:
471
467
  """
472
468
  return _first_node(self)
473
469
 
474
- def last_node(self) -> Optional[Node]:
470
+ def last_node(self) -> Node | None:
475
471
  """Find the single node that is not a source of any edge.
476
472
 
477
473
  If there is no such node, or there are multiple, return None.
@@ -531,24 +527,24 @@ class Graph:
531
527
  def draw_png(
532
528
  self,
533
529
  output_file_path: str,
534
- fontname: Optional[str] = None,
535
- labels: Optional[LabelsDict] = None,
530
+ fontname: str | None = None,
531
+ labels: LabelsDict | None = None,
536
532
  ) -> None: ...
537
533
 
538
534
  @overload
539
535
  def draw_png(
540
536
  self,
541
537
  output_file_path: None,
542
- fontname: Optional[str] = None,
543
- labels: Optional[LabelsDict] = None,
538
+ fontname: str | None = None,
539
+ labels: LabelsDict | None = None,
544
540
  ) -> bytes: ...
545
541
 
546
542
  def draw_png(
547
543
  self,
548
- output_file_path: Optional[str] = None,
549
- fontname: Optional[str] = None,
550
- labels: Optional[LabelsDict] = None,
551
- ) -> Union[bytes, None]:
544
+ output_file_path: str | None = None,
545
+ fontname: str | None = None,
546
+ labels: LabelsDict | None = None,
547
+ ) -> bytes | None:
552
548
  """Draw the graph as a PNG image.
553
549
 
554
550
  Args:
@@ -581,9 +577,9 @@ class Graph:
581
577
  *,
582
578
  with_styles: bool = True,
583
579
  curve_style: CurveStyle = CurveStyle.LINEAR,
584
- node_colors: Optional[NodeStyles] = None,
580
+ node_colors: NodeStyles | None = None,
585
581
  wrap_label_n_words: int = 9,
586
- frontmatter_config: Optional[dict[str, Any]] = None,
582
+ frontmatter_config: dict[str, Any] | None = None,
587
583
  ) -> str:
588
584
  """Draw the graph as a Mermaid syntax string.
589
585
 
@@ -601,17 +597,15 @@ class Graph:
601
597
 
602
598
  Example config:
603
599
 
604
- .. code-block:: python
605
-
600
+ ```python
606
601
  {
607
602
  "config": {
608
603
  "theme": "neutral",
609
604
  "look": "handDrawn",
610
- "themeVariables": { "primaryColor": "#e2e2e2"},
605
+ "themeVariables": {"primaryColor": "#e2e2e2"},
611
606
  }
612
607
  }
613
-
614
-
608
+ ```
615
609
  Returns:
616
610
  The Mermaid syntax string.
617
611
  """
@@ -638,16 +632,16 @@ class Graph:
638
632
  self,
639
633
  *,
640
634
  curve_style: CurveStyle = CurveStyle.LINEAR,
641
- node_colors: Optional[NodeStyles] = None,
635
+ node_colors: NodeStyles | None = None,
642
636
  wrap_label_n_words: int = 9,
643
- output_file_path: Optional[str] = None,
637
+ output_file_path: str | None = None,
644
638
  draw_method: MermaidDrawMethod = MermaidDrawMethod.API,
645
639
  background_color: str = "white",
646
640
  padding: int = 10,
647
641
  max_retries: int = 1,
648
642
  retry_delay: float = 1.0,
649
- frontmatter_config: Optional[dict[str, Any]] = None,
650
- base_url: Optional[str] = None,
643
+ frontmatter_config: dict[str, Any] | None = None,
644
+ base_url: str | None = None,
651
645
  ) -> bytes:
652
646
  """Draw the graph as a PNG image using Mermaid.
653
647
 
@@ -674,15 +668,15 @@ class Graph:
674
668
 
675
669
  Example config:
676
670
 
677
- .. code-block:: python
678
-
671
+ ```python
679
672
  {
680
673
  "config": {
681
674
  "theme": "neutral",
682
675
  "look": "handDrawn",
683
- "themeVariables": { "primaryColor": "#e2e2e2"},
676
+ "themeVariables": {"primaryColor": "#e2e2e2"},
684
677
  }
685
678
  }
679
+ ```
686
680
  base_url: The base URL of the Mermaid server for rendering via API.
687
681
  Defaults to None.
688
682
 
@@ -713,7 +707,7 @@ class Graph:
713
707
  )
714
708
 
715
709
 
716
- def _first_node(graph: Graph, exclude: Sequence[str] = ()) -> Optional[Node]:
710
+ def _first_node(graph: Graph, exclude: Sequence[str] = ()) -> Node | None:
717
711
  """Find the single node that is not a target of any edge.
718
712
 
719
713
  Exclude nodes/sources with ids in the exclude list.
@@ -729,7 +723,7 @@ def _first_node(graph: Graph, exclude: Sequence[str] = ()) -> Optional[Node]:
729
723
  return found[0] if len(found) == 1 else None
730
724
 
731
725
 
732
- def _last_node(graph: Graph, exclude: Sequence[str] = ()) -> Optional[Node]:
726
+ def _last_node(graph: Graph, exclude: Sequence[str] = ()) -> Node | None:
733
727
  """Find the single node that is not a source of any edge.
734
728
 
735
729
  Exclude nodes/targets with ids in the exclude list.
@@ -255,20 +255,18 @@ def draw_ascii(vertices: Mapping[str, str], edges: Sequence[LangEdge]) -> str:
255
255
  ASCII representation
256
256
 
257
257
  Example:
258
+ ```python
259
+ from langchain_core.runnables.graph_ascii import draw_ascii
258
260
 
259
- .. code-block:: python
261
+ vertices = {1: "1", 2: "2", 3: "3", 4: "4"}
262
+ edges = [
263
+ (source, target, None, None)
264
+ for source, target in [(1, 2), (2, 3), (2, 4), (1, 4)]
265
+ ]
260
266
 
261
- from langchain_core.runnables.graph_ascii import draw_ascii
262
-
263
- vertices = {1: "1", 2: "2", 3: "3", 4: "4"}
264
- edges = [
265
- (source, target, None, None)
266
- for source, target in [(1, 2), (2, 3), (2, 4), (1, 4)]
267
- ]
268
-
269
-
270
- print(draw_ascii(vertices, edges))
271
267
 
268
+ print(draw_ascii(vertices, edges))
269
+ ```
272
270
  .. code-block::
273
271
 
274
272
  +---+
@@ -10,7 +10,7 @@ import string
10
10
  import time
11
11
  from dataclasses import asdict
12
12
  from pathlib import Path
13
- from typing import TYPE_CHECKING, Any, Literal, Optional
13
+ from typing import TYPE_CHECKING, Any, Literal
14
14
 
15
15
  import yaml
16
16
 
@@ -45,13 +45,13 @@ def draw_mermaid(
45
45
  nodes: dict[str, Node],
46
46
  edges: list[Edge],
47
47
  *,
48
- first_node: Optional[str] = None,
49
- last_node: Optional[str] = None,
48
+ first_node: str | None = None,
49
+ last_node: str | None = None,
50
50
  with_styles: bool = True,
51
51
  curve_style: CurveStyle = CurveStyle.LINEAR,
52
- node_styles: Optional[NodeStyles] = None,
52
+ node_styles: NodeStyles | None = None,
53
53
  wrap_label_n_words: int = 9,
54
- frontmatter_config: Optional[dict[str, Any]] = None,
54
+ frontmatter_config: dict[str, Any] | None = None,
55
55
  ) -> str:
56
56
  """Draws a Mermaid graph using the provided graph data.
57
57
 
@@ -77,16 +77,15 @@ def draw_mermaid(
77
77
 
78
78
  Example config:
79
79
 
80
- .. code-block:: python
81
-
80
+ ```python
82
81
  {
83
82
  "config": {
84
83
  "theme": "neutral",
85
84
  "look": "handDrawn",
86
- "themeVariables": { "primaryColor": "#e2e2e2"},
85
+ "themeVariables": {"primaryColor": "#e2e2e2"},
87
86
  }
88
87
  }
89
-
88
+ ```
90
89
  Returns:
91
90
  str: Mermaid graph syntax.
92
91
 
@@ -164,7 +163,7 @@ def draw_mermaid(
164
163
  src_parts = edge.source.split(":")
165
164
  tgt_parts = edge.target.split(":")
166
165
  common_prefix = ":".join(
167
- src for src, tgt in zip(src_parts, tgt_parts) if src == tgt
166
+ src for src, tgt in zip(src_parts, tgt_parts, strict=False) if src == tgt
168
167
  )
169
168
  edge_groups.setdefault(common_prefix, []).append(edge)
170
169
 
@@ -280,13 +279,13 @@ def _generate_mermaid_graph_styles(node_colors: NodeStyles) -> str:
280
279
 
281
280
  def draw_mermaid_png(
282
281
  mermaid_syntax: str,
283
- output_file_path: Optional[str] = None,
282
+ output_file_path: str | None = None,
284
283
  draw_method: MermaidDrawMethod = MermaidDrawMethod.API,
285
- background_color: Optional[str] = "white",
284
+ background_color: str | None = "white",
286
285
  padding: int = 10,
287
286
  max_retries: int = 1,
288
287
  retry_delay: float = 1.0,
289
- base_url: Optional[str] = None,
288
+ base_url: str | None = None,
290
289
  ) -> bytes:
291
290
  """Draws a Mermaid graph as PNG using provided syntax.
292
291
 
@@ -340,8 +339,8 @@ def draw_mermaid_png(
340
339
 
341
340
  async def _render_mermaid_using_pyppeteer(
342
341
  mermaid_syntax: str,
343
- output_file_path: Optional[str] = None,
344
- background_color: Optional[str] = "white",
342
+ output_file_path: str | None = None,
343
+ background_color: str | None = "white",
345
344
  padding: int = 10,
346
345
  device_scale_factor: int = 3,
347
346
  ) -> bytes:
@@ -412,12 +411,12 @@ async def _render_mermaid_using_pyppeteer(
412
411
  def _render_mermaid_using_api(
413
412
  mermaid_syntax: str,
414
413
  *,
415
- output_file_path: Optional[str] = None,
416
- background_color: Optional[str] = "white",
417
- file_type: Optional[Literal["jpeg", "png", "webp"]] = "png",
414
+ output_file_path: str | None = None,
415
+ background_color: str | None = "white",
416
+ file_type: Literal["jpeg", "png", "webp"] | None = "png",
418
417
  max_retries: int = 1,
419
418
  retry_delay: float = 1.0,
420
- base_url: Optional[str] = None,
419
+ base_url: str | None = None,
421
420
  ) -> bytes:
422
421
  """Renders Mermaid graph using the Mermaid.INK API."""
423
422
  # Defaults to using the public mermaid.ink server.