Skip to content

Commit d2cbfa3

Browse files
authored
core[patch]: add retries and better messages to draw_mermaid_png (#30881)
1 parent 75e50a3 commit d2cbfa3

File tree

2 files changed

+73
-10
lines changed

2 files changed

+73
-10
lines changed

libs/core/langchain_core/runnables/graph.py

+8
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,8 @@ def draw_mermaid_png(
630630
draw_method: MermaidDrawMethod = MermaidDrawMethod.API,
631631
background_color: str = "white",
632632
padding: int = 10,
633+
max_retries: int = 1,
634+
retry_delay: float = 1.0,
633635
frontmatter_config: Optional[dict[str, Any]] = None,
634636
) -> bytes:
635637
"""Draw the graph as a PNG image using Mermaid.
@@ -645,6 +647,10 @@ def draw_mermaid_png(
645647
Defaults to MermaidDrawMethod.API.
646648
background_color: The color of the background. Defaults to "white".
647649
padding: The padding around the graph. Defaults to 10.
650+
max_retries: The maximum number of retries (MermaidDrawMethod.API).
651+
Defaults to 1.
652+
retry_delay: The delay between retries (MermaidDrawMethod.API).
653+
Defaults to 1.0.
648654
frontmatter_config (dict[str, Any], optional): Mermaid frontmatter config.
649655
Can be used to customize theme and styles. Will be converted to YAML and
650656
added to the beginning of the mermaid graph. Defaults to None.
@@ -680,6 +686,8 @@ def draw_mermaid_png(
680686
draw_method=draw_method,
681687
background_color=background_color,
682688
padding=padding,
689+
max_retries=max_retries,
690+
retry_delay=retry_delay,
683691
)
684692

685693

libs/core/langchain_core/runnables/graph_mermaid.py

+65-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import asyncio
44
import base64
5+
import random
56
import re
7+
import time
68
from dataclasses import asdict
79
from pathlib import Path
810
from typing import Any, Literal, Optional
@@ -254,6 +256,8 @@ def draw_mermaid_png(
254256
draw_method: MermaidDrawMethod = MermaidDrawMethod.API,
255257
background_color: Optional[str] = "white",
256258
padding: int = 10,
259+
max_retries: int = 1,
260+
retry_delay: float = 1.0,
257261
) -> bytes:
258262
"""Draws a Mermaid graph as PNG using provided syntax.
259263
@@ -266,6 +270,10 @@ def draw_mermaid_png(
266270
background_color (str, optional): Background color of the image.
267271
Defaults to "white".
268272
padding (int, optional): Padding around the image. Defaults to 10.
273+
max_retries (int, optional): Maximum number of retries (MermaidDrawMethod.API).
274+
Defaults to 1.
275+
retry_delay (float, optional): Delay between retries (MermaidDrawMethod.API).
276+
Defaults to 1.0.
269277
270278
Returns:
271279
bytes: PNG image bytes.
@@ -283,7 +291,11 @@ def draw_mermaid_png(
283291
)
284292
elif draw_method == MermaidDrawMethod.API:
285293
img_bytes = _render_mermaid_using_api(
286-
mermaid_syntax, output_file_path, background_color
294+
mermaid_syntax,
295+
output_file_path=output_file_path,
296+
background_color=background_color,
297+
max_retries=max_retries,
298+
retry_delay=retry_delay,
287299
)
288300
else:
289301
supported_methods = ", ".join([m.value for m in MermaidDrawMethod])
@@ -371,9 +383,12 @@ async def _render_mermaid_using_pyppeteer(
371383

372384
def _render_mermaid_using_api(
373385
mermaid_syntax: str,
386+
*,
374387
output_file_path: Optional[str] = None,
375388
background_color: Optional[str] = "white",
376389
file_type: Optional[Literal["jpeg", "png", "webp"]] = "png",
390+
max_retries: int = 1,
391+
retry_delay: float = 1.0,
377392
) -> bytes:
378393
"""Renders Mermaid graph using the Mermaid.INK API."""
379394
try:
@@ -400,15 +415,55 @@ def _render_mermaid_using_api(
400415
f"https://mermaid.ink/img/{mermaid_syntax_encoded}"
401416
f"?type={file_type}&bgColor={background_color}"
402417
)
403-
response = requests.get(image_url, timeout=10)
404-
if response.status_code == requests.codes.ok:
405-
img_bytes = response.content
406-
if output_file_path is not None:
407-
Path(output_file_path).write_bytes(response.content)
408418

409-
return img_bytes
410-
msg = (
411-
f"Failed to render the graph using the Mermaid.INK API. "
412-
f"Status code: {response.status_code}."
419+
error_msg_suffix = (
420+
"To resolve this issue:\n"
421+
"1. Check your internet connection and try again\n"
422+
"2. Try with higher retry settings: "
423+
"`draw_mermaid_png(..., max_retries=5, retry_delay=2.0)`\n"
424+
"3. Use the Pyppeteer rendering method which will render your graph locally "
425+
"in a browser: `draw_mermaid_png(..., draw_method=MermaidDrawMethod.PYPPETEER)`"
413426
)
427+
428+
for attempt in range(max_retries + 1):
429+
try:
430+
response = requests.get(image_url, timeout=10)
431+
if response.status_code == requests.codes.ok:
432+
img_bytes = response.content
433+
if output_file_path is not None:
434+
Path(output_file_path).write_bytes(response.content)
435+
436+
return img_bytes
437+
438+
# If we get a server error (5xx), retry
439+
if 500 <= response.status_code < 600 and attempt < max_retries:
440+
# Exponential backoff with jitter
441+
sleep_time = retry_delay * (2**attempt) * (0.5 + 0.5 * random.random()) # noqa: S311 not used for crypto
442+
time.sleep(sleep_time)
443+
continue
444+
445+
# For other status codes, fail immediately
446+
msg = (
447+
"Failed to reach https://mermaid.ink/ API while trying to render "
448+
f"your graph. Status code: {response.status_code}.\n\n"
449+
) + error_msg_suffix
450+
raise ValueError(msg)
451+
452+
except (requests.RequestException, requests.Timeout) as e:
453+
if attempt < max_retries:
454+
# Exponential backoff with jitter
455+
sleep_time = retry_delay * (2**attempt) * (0.5 + 0.5 * random.random()) # noqa: S311 not used for crypto
456+
time.sleep(sleep_time)
457+
else:
458+
msg = (
459+
"Failed to reach https://mermaid.ink/ API while trying to render "
460+
f"your graph after {max_retries} retries. "
461+
) + error_msg_suffix
462+
raise ValueError(msg) from e
463+
464+
# This should not be reached, but just in case
465+
msg = (
466+
"Failed to reach https://mermaid.ink/ API while trying to render "
467+
f"your graph after {max_retries} retries. "
468+
) + error_msg_suffix
414469
raise ValueError(msg)

0 commit comments

Comments
 (0)