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.
- langchain_core/_api/__init__.py +3 -3
- langchain_core/_api/beta_decorator.py +6 -6
- langchain_core/_api/deprecation.py +21 -29
- langchain_core/_api/path.py +3 -6
- langchain_core/_import_utils.py +2 -3
- langchain_core/agents.py +10 -11
- langchain_core/caches.py +7 -7
- langchain_core/callbacks/base.py +91 -91
- langchain_core/callbacks/file.py +11 -11
- langchain_core/callbacks/manager.py +86 -89
- langchain_core/callbacks/stdout.py +8 -8
- langchain_core/callbacks/usage.py +4 -4
- langchain_core/chat_history.py +5 -5
- langchain_core/document_loaders/base.py +2 -2
- langchain_core/document_loaders/langsmith.py +15 -15
- langchain_core/documents/base.py +16 -16
- langchain_core/documents/compressor.py +4 -4
- langchain_core/example_selectors/length_based.py +1 -1
- langchain_core/example_selectors/semantic_similarity.py +17 -19
- langchain_core/exceptions.py +3 -3
- langchain_core/globals.py +3 -151
- langchain_core/indexing/api.py +44 -43
- langchain_core/indexing/base.py +30 -30
- langchain_core/indexing/in_memory.py +3 -3
- langchain_core/language_models/_utils.py +5 -7
- langchain_core/language_models/base.py +18 -132
- langchain_core/language_models/chat_models.py +118 -227
- langchain_core/language_models/fake.py +11 -11
- langchain_core/language_models/fake_chat_models.py +35 -29
- langchain_core/language_models/llms.py +91 -201
- langchain_core/load/dump.py +1 -1
- langchain_core/load/load.py +11 -12
- langchain_core/load/mapping.py +2 -4
- langchain_core/load/serializable.py +2 -4
- langchain_core/messages/ai.py +17 -20
- langchain_core/messages/base.py +23 -25
- langchain_core/messages/block_translators/__init__.py +2 -5
- langchain_core/messages/block_translators/anthropic.py +3 -3
- langchain_core/messages/block_translators/bedrock_converse.py +2 -2
- langchain_core/messages/block_translators/langchain_v0.py +2 -2
- langchain_core/messages/block_translators/openai.py +6 -6
- langchain_core/messages/content.py +120 -124
- langchain_core/messages/human.py +7 -7
- langchain_core/messages/system.py +7 -7
- langchain_core/messages/tool.py +24 -24
- langchain_core/messages/utils.py +67 -79
- langchain_core/output_parsers/base.py +12 -14
- langchain_core/output_parsers/json.py +4 -4
- langchain_core/output_parsers/list.py +3 -5
- langchain_core/output_parsers/openai_functions.py +3 -3
- langchain_core/output_parsers/openai_tools.py +3 -3
- langchain_core/output_parsers/pydantic.py +2 -2
- langchain_core/output_parsers/transform.py +13 -15
- langchain_core/output_parsers/xml.py +7 -9
- langchain_core/outputs/chat_generation.py +4 -4
- langchain_core/outputs/chat_result.py +1 -3
- langchain_core/outputs/generation.py +2 -2
- langchain_core/outputs/llm_result.py +5 -5
- langchain_core/prompts/__init__.py +1 -5
- langchain_core/prompts/base.py +10 -15
- langchain_core/prompts/chat.py +31 -82
- langchain_core/prompts/dict.py +2 -2
- langchain_core/prompts/few_shot.py +5 -5
- langchain_core/prompts/few_shot_with_templates.py +4 -4
- langchain_core/prompts/loading.py +3 -5
- langchain_core/prompts/prompt.py +4 -16
- langchain_core/prompts/string.py +2 -1
- langchain_core/prompts/structured.py +16 -23
- langchain_core/rate_limiters.py +3 -4
- langchain_core/retrievers.py +14 -14
- langchain_core/runnables/base.py +928 -1042
- langchain_core/runnables/branch.py +36 -40
- langchain_core/runnables/config.py +27 -35
- langchain_core/runnables/configurable.py +108 -124
- langchain_core/runnables/fallbacks.py +76 -72
- langchain_core/runnables/graph.py +39 -45
- langchain_core/runnables/graph_ascii.py +9 -11
- langchain_core/runnables/graph_mermaid.py +18 -19
- langchain_core/runnables/graph_png.py +8 -9
- langchain_core/runnables/history.py +114 -127
- langchain_core/runnables/passthrough.py +113 -139
- langchain_core/runnables/retry.py +43 -48
- langchain_core/runnables/router.py +23 -28
- langchain_core/runnables/schema.py +42 -44
- langchain_core/runnables/utils.py +28 -31
- langchain_core/stores.py +9 -13
- langchain_core/structured_query.py +8 -8
- langchain_core/tools/base.py +62 -115
- langchain_core/tools/convert.py +31 -35
- langchain_core/tools/render.py +1 -1
- langchain_core/tools/retriever.py +4 -4
- langchain_core/tools/simple.py +13 -17
- langchain_core/tools/structured.py +12 -15
- langchain_core/tracers/base.py +62 -64
- langchain_core/tracers/context.py +17 -35
- langchain_core/tracers/core.py +49 -53
- langchain_core/tracers/evaluation.py +11 -11
- langchain_core/tracers/event_stream.py +58 -60
- langchain_core/tracers/langchain.py +13 -13
- langchain_core/tracers/log_stream.py +22 -24
- langchain_core/tracers/root_listeners.py +14 -14
- langchain_core/tracers/run_collector.py +2 -4
- langchain_core/tracers/schemas.py +8 -8
- langchain_core/tracers/stdout.py +2 -1
- langchain_core/utils/__init__.py +0 -3
- langchain_core/utils/_merge.py +2 -2
- langchain_core/utils/aiter.py +24 -28
- langchain_core/utils/env.py +4 -4
- langchain_core/utils/function_calling.py +31 -41
- langchain_core/utils/html.py +3 -4
- langchain_core/utils/input.py +3 -3
- langchain_core/utils/iter.py +15 -19
- langchain_core/utils/json.py +3 -2
- langchain_core/utils/json_schema.py +6 -6
- langchain_core/utils/mustache.py +3 -5
- langchain_core/utils/pydantic.py +16 -18
- langchain_core/utils/usage.py +1 -1
- langchain_core/utils/utils.py +29 -29
- langchain_core/vectorstores/base.py +18 -21
- langchain_core/vectorstores/in_memory.py +14 -87
- langchain_core/vectorstores/utils.py +2 -2
- langchain_core/version.py +1 -1
- {langchain_core-1.0.0a6.dist-info → langchain_core-1.0.0a8.dist-info}/METADATA +10 -21
- langchain_core-1.0.0a8.dist-info/RECORD +176 -0
- {langchain_core-1.0.0a6.dist-info → langchain_core-1.0.0a8.dist-info}/WHEEL +1 -1
- langchain_core/messages/block_translators/ollama.py +0 -47
- langchain_core/prompts/pipeline.py +0 -138
- langchain_core/tracers/langchain_v1.py +0 -31
- langchain_core/utils/loading.py +0 -35
- langchain_core-1.0.0a6.dist-info/RECORD +0 -181
- 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,
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
222
|
-
**kwargs:
|
|
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:
|
|
267
|
+
config: RunnableConfig | list[RunnableConfig] | None = None,
|
|
272
268
|
*,
|
|
273
269
|
return_exceptions: bool = False,
|
|
274
|
-
**kwargs:
|
|
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(
|
|
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(
|
|
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:
|
|
363
|
+
config: RunnableConfig | list[RunnableConfig] | None = None,
|
|
364
364
|
*,
|
|
365
365
|
return_exceptions: bool = False,
|
|
366
|
-
**kwargs:
|
|
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(
|
|
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,
|
|
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(
|
|
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:
|
|
464
|
-
**kwargs:
|
|
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:
|
|
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:
|
|
528
|
-
**kwargs:
|
|
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:
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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:
|
|
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:
|
|
101
|
+
data: type[BaseModel] | RunnableType | None
|
|
106
102
|
"""The data of the node."""
|
|
107
|
-
metadata:
|
|
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:
|
|
114
|
-
name:
|
|
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:
|
|
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:
|
|
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,
|
|
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:
|
|
320
|
-
id:
|
|
315
|
+
data: type[BaseModel] | RunnableType | None,
|
|
316
|
+
id: str | None = None,
|
|
321
317
|
*,
|
|
322
|
-
metadata:
|
|
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:
|
|
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[
|
|
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) ->
|
|
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) ->
|
|
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:
|
|
535
|
-
labels:
|
|
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:
|
|
543
|
-
labels:
|
|
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:
|
|
549
|
-
fontname:
|
|
550
|
-
labels:
|
|
551
|
-
) ->
|
|
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:
|
|
580
|
+
node_colors: NodeStyles | None = None,
|
|
585
581
|
wrap_label_n_words: int = 9,
|
|
586
|
-
frontmatter_config:
|
|
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
|
-
|
|
605
|
-
|
|
600
|
+
```python
|
|
606
601
|
{
|
|
607
602
|
"config": {
|
|
608
603
|
"theme": "neutral",
|
|
609
604
|
"look": "handDrawn",
|
|
610
|
-
"themeVariables": {
|
|
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:
|
|
635
|
+
node_colors: NodeStyles | None = None,
|
|
642
636
|
wrap_label_n_words: int = 9,
|
|
643
|
-
output_file_path:
|
|
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:
|
|
650
|
-
base_url:
|
|
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
|
-
|
|
678
|
-
|
|
671
|
+
```python
|
|
679
672
|
{
|
|
680
673
|
"config": {
|
|
681
674
|
"theme": "neutral",
|
|
682
675
|
"look": "handDrawn",
|
|
683
|
-
"themeVariables": {
|
|
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] = ()) ->
|
|
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] = ()) ->
|
|
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
|
-
|
|
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
|
|
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:
|
|
49
|
-
last_node:
|
|
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:
|
|
52
|
+
node_styles: NodeStyles | None = None,
|
|
53
53
|
wrap_label_n_words: int = 9,
|
|
54
|
-
frontmatter_config:
|
|
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
|
-
|
|
81
|
-
|
|
80
|
+
```python
|
|
82
81
|
{
|
|
83
82
|
"config": {
|
|
84
83
|
"theme": "neutral",
|
|
85
84
|
"look": "handDrawn",
|
|
86
|
-
"themeVariables": {
|
|
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:
|
|
282
|
+
output_file_path: str | None = None,
|
|
284
283
|
draw_method: MermaidDrawMethod = MermaidDrawMethod.API,
|
|
285
|
-
background_color:
|
|
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:
|
|
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:
|
|
344
|
-
background_color:
|
|
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:
|
|
416
|
-
background_color:
|
|
417
|
-
file_type:
|
|
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:
|
|
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.
|