From c95b345cdee09ae3b42db8a336f48c7ff14596ed Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:43:00 -0700 Subject: [PATCH 01/18] [community]: Render documents to graphviz - **Description:** Adds a helper that renders documents with the GraphVectorStore metadata fields to Graphviz for visualization. This is helpful for understanding and debugging. --- .../graph_vectorstores/visualize.py | 76 +++++++++++++++++++ .../graph_vectorstores/test_visualize.py | 64 ++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 libs/community/langchain_community/graph_vectorstores/visualize.py create mode 100644 libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py new file mode 100644 index 0000000000000..acfee9640d5d7 --- /dev/null +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -0,0 +1,76 @@ +from typing import Dict, Iterable, Optional, Set +from langchain_core.documents import Document +from langchain_core.graph_vectorstores.links import get_links + +def _escape_id(id: str) -> str: + return id.replace(':', '_') + +_EDGE_DIRECTION = { + "in": "back", + "out": "forward", + "bidir": "both", +} + +def render_graphviz( + documents: Iterable[Document], + node_color: str = "white", + node_colors: Optional[Dict[str, str]] = {}, +) -> "graphviz.Digraph": + """Render a collection of GraphVectorStore documents to GraphViz format. + + Args: + documents: The documents to render. + node_color: General node color. Defaults to `white`. + node_colors: Dictionary specifying colors of specific nodes. Useful for + emphasizing nodes that were selected by MMR, or differ from other + results. + + Returns: + The "graphviz.Digraph" representing the nodes. May be printed to source, + or rendered using `dot`. + + Note: + To render the generated DOT source code, you also need to install Graphviz_ + (`download page `_, + `archived versions `_, + `installation procedure for Windows `_). + """ + if node_colors is None: + node_colors = {} + try: + import graphviz + except (ImportError, ModuleNotFoundError): + raise ImportError( + "Could not import graphviz python package. " + "Please install it with `pip install graphviz`." + ) + + try: + graphviz.version() + except graphviz.ExecutableNotFound: + raise ImportError( + "Could not execute `dot`. " + "Make sure graphviz executable is installed (see https://www.graphviz.org/download/)." + ) + + tags = set() + + graph = graphviz.Digraph() + graph.attr("node", style="filled") + for document in documents: + id = document.id + if id is None: + raise ValueError(f"Illegal graph document without ID: {document}") + escaped_id = _escape_id(id) + color = node_colors.get(id) or node_color + graph.node(escaped_id, label = f"{id}", shape="note", fillcolor=color) + + for link in get_links(document): + tag = f"{link.kind}_{link.tag}" + if tag not in tags: + graph.node(tag, label=f"{link.kind}:{link.tag}") + tags.add(tag) + + graph.edge(escaped_id, tag, dir=_EDGE_DIRECTION[link.direction]) + return graph + diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py new file mode 100644 index 0000000000000..61889ccd68fd4 --- /dev/null +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -0,0 +1,64 @@ +from langchain_core.graph_vectorstores.links import METADATA_LINKS_KEY, Link +from langchain_core.documents import Document +from langchain_community.graph_vectorstores.visualize import render_graphviz + +def test_visualize_simple_graph(): + doc1 = Document( + id = "a", + page_content = "some content", + metadata = { + METADATA_LINKS_KEY: [ + Link.incoming("href", "a"), + Link.bidir("kw", "foo"), + ] + }, + ) + doc2 = Document( + id = "b", + page_content = "some more content", + metadata = { + METADATA_LINKS_KEY: [ + Link.incoming("href", "b"), + Link.outgoing("href", "a"), + Link.bidir("kw", "foo"), + Link.bidir("kw", "bar"), + ] + }, + ) + + assert render_graphviz([doc1, doc2]).source == ( + 'digraph {\n' + '\tnode [style=filled]\n' + '\ta [label=a fillcolor=white shape=note]\n' + '\thref_a [label="href:a"]\n' + '\ta -> href_a [dir=back]\n' + '\tkw_foo [label="kw:foo"]\n' + '\ta -> kw_foo [dir=both]\n' + '\tb [label=b fillcolor=white shape=note]\n' + '\thref_b [label="href:b"]\n' + '\tb -> href_b [dir=back]\n' + '\tb -> href_a [dir=forward]\n' + '\tb -> kw_foo [dir=both]\n' + '\tkw_bar [label="kw:bar"]\n' + '\tb -> kw_bar [dir=both]\n' + '}\n' + ) + + assert render_graphviz([doc1, doc2], + node_colors = { "a": "gold"}).source == ( + 'digraph {\n' + '\tnode [style=filled]\n' + '\ta [label=a fillcolor=gold shape=note]\n' + '\thref_a [label="href:a"]\n' + '\ta -> href_a [dir=back]\n' + '\tkw_foo [label="kw:foo"]\n' + '\ta -> kw_foo [dir=both]\n' + '\tb [label=b fillcolor=white shape=note]\n' + '\thref_b [label="href:b"]\n' + '\tb -> href_b [dir=back]\n' + '\tb -> href_a [dir=forward]\n' + '\tb -> kw_foo [dir=both]\n' + '\tkw_bar [label="kw:bar"]\n' + '\tb -> kw_bar [dir=both]\n' + '}\n' + ) \ No newline at end of file From 59767f4a8cc819f24f2dd6849fe7df3c4e546db3 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:02:45 -0700 Subject: [PATCH 02/18] add page content as tooltip --- libs/community/extended_testing_deps.txt | 1 + .../graph_vectorstores/visualize.py | 26 +++++-- .../graph_vectorstores/test_visualize.py | 67 ++++++++++--------- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/libs/community/extended_testing_deps.txt b/libs/community/extended_testing_deps.txt index d9879fd6aa07c..be09a81eed33e 100644 --- a/libs/community/extended_testing_deps.txt +++ b/libs/community/extended_testing_deps.txt @@ -29,6 +29,7 @@ gliner>=0.2.7 google-cloud-documentai>=2.20.1,<3 gql>=3.4.1,<4 gradientai>=1.4.0,<2 +graphviz>=0.20.3,<0.21 hdbcli>=2.19.21,<3 hologres-vector==0.0.6 html2text>=2020.1.16 diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index acfee9640d5d7..9679288914bcf 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -1,9 +1,15 @@ -from typing import Dict, Iterable, Optional, Set +from typing import TYPE_CHECKING, Dict, Iterable, Optional + from langchain_core.documents import Document from langchain_core.graph_vectorstores.links import get_links +if TYPE_CHECKING: + import graphviz + + def _escape_id(id: str) -> str: - return id.replace(':', '_') + return id.replace(":", "_") + _EDGE_DIRECTION = { "in": "back", @@ -11,10 +17,11 @@ def _escape_id(id: str) -> str: "bidir": "both", } + def render_graphviz( - documents: Iterable[Document], - node_color: str = "white", - node_colors: Optional[Dict[str, str]] = {}, + documents: Iterable[Document], + node_color: str = "white", + node_colors: Optional[Dict[str, str]] = {}, ) -> "graphviz.Digraph": """Render a collection of GraphVectorStore documents to GraphViz format. @@ -63,7 +70,13 @@ def render_graphviz( raise ValueError(f"Illegal graph document without ID: {document}") escaped_id = _escape_id(id) color = node_colors.get(id) or node_color - graph.node(escaped_id, label = f"{id}", shape="note", fillcolor=color) + graph.node( + escaped_id, + label=f"{id}", + shape="note", + fillcolor=color, + tooltip=document.page_content, + ) for link in get_links(document): tag = f"{link.kind}_{link.tag}" @@ -73,4 +86,3 @@ def render_graphviz( graph.edge(escaped_id, tag, dir=_EDGE_DIRECTION[link.direction]) return graph - diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index 61889ccd68fd4..abef5be8f6e90 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -1,12 +1,16 @@ -from langchain_core.graph_vectorstores.links import METADATA_LINKS_KEY, Link +import pytest from langchain_core.documents import Document +from langchain_core.graph_vectorstores.links import METADATA_LINKS_KEY, Link + from langchain_community.graph_vectorstores.visualize import render_graphviz + +@pytest.mark.requires("graphviz") def test_visualize_simple_graph(): doc1 = Document( - id = "a", - page_content = "some content", - metadata = { + id="a", + page_content="some content", + metadata={ METADATA_LINKS_KEY: [ Link.incoming("href", "a"), Link.bidir("kw", "foo"), @@ -14,9 +18,9 @@ def test_visualize_simple_graph(): }, ) doc2 = Document( - id = "b", - page_content = "some more content", - metadata = { + id="b", + page_content="some more content", + metadata={ METADATA_LINKS_KEY: [ Link.incoming("href", "b"), Link.outgoing("href", "a"), @@ -27,38 +31,37 @@ def test_visualize_simple_graph(): ) assert render_graphviz([doc1, doc2]).source == ( - 'digraph {\n' - '\tnode [style=filled]\n' - '\ta [label=a fillcolor=white shape=note]\n' + "digraph {\n" + "\tnode [style=filled]\n" + "\ta [label=a fillcolor=white shape=note]\n" '\thref_a [label="href:a"]\n' - '\ta -> href_a [dir=back]\n' + "\ta -> href_a [dir=back]\n" '\tkw_foo [label="kw:foo"]\n' - '\ta -> kw_foo [dir=both]\n' - '\tb [label=b fillcolor=white shape=note]\n' + "\ta -> kw_foo [dir=both]\n" + "\tb [label=b fillcolor=white shape=note]\n" '\thref_b [label="href:b"]\n' - '\tb -> href_b [dir=back]\n' - '\tb -> href_a [dir=forward]\n' - '\tb -> kw_foo [dir=both]\n' + "\tb -> href_b [dir=back]\n" + "\tb -> href_a [dir=forward]\n" + "\tb -> kw_foo [dir=both]\n" '\tkw_bar [label="kw:bar"]\n' - '\tb -> kw_bar [dir=both]\n' - '}\n' + "\tb -> kw_bar [dir=both]\n" + "}\n" ) - assert render_graphviz([doc1, doc2], - node_colors = { "a": "gold"}).source == ( - 'digraph {\n' - '\tnode [style=filled]\n' - '\ta [label=a fillcolor=gold shape=note]\n' + assert render_graphviz([doc1, doc2], node_colors={"a": "gold"}).source == ( + "digraph {\n" + "\tnode [style=filled]\n" + "\ta [label=a fillcolor=gold shape=note]\n" '\thref_a [label="href:a"]\n' - '\ta -> href_a [dir=back]\n' + "\ta -> href_a [dir=back]\n" '\tkw_foo [label="kw:foo"]\n' - '\ta -> kw_foo [dir=both]\n' - '\tb [label=b fillcolor=white shape=note]\n' + "\ta -> kw_foo [dir=both]\n" + "\tb [label=b fillcolor=white shape=note]\n" '\thref_b [label="href:b"]\n' - '\tb -> href_b [dir=back]\n' - '\tb -> href_a [dir=forward]\n' - '\tb -> kw_foo [dir=both]\n' + "\tb -> href_b [dir=back]\n" + "\tb -> href_a [dir=forward]\n" + "\tb -> kw_foo [dir=both]\n" '\tkw_bar [label="kw:bar"]\n' - '\tb -> kw_bar [dir=both]\n' - '}\n' - ) \ No newline at end of file + "\tb -> kw_bar [dir=both]\n" + "}\n" + ) From 9598ea425bf055dc8f5aa3feb9461e3f88105b82 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:03:42 -0700 Subject: [PATCH 03/18] escape --- .../langchain_community/graph_vectorstores/visualize.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index 9679288914bcf..2f9d52e548a26 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -72,16 +72,16 @@ def render_graphviz( color = node_colors.get(id) or node_color graph.node( escaped_id, - label=f"{id}", + label=graphviz.escape(id), shape="note", fillcolor=color, - tooltip=document.page_content, + tooltip=graphviz.escape(document.page_content), ) for link in get_links(document): tag = f"{link.kind}_{link.tag}" if tag not in tags: - graph.node(tag, label=f"{link.kind}:{link.tag}") + graph.node(tag, label=graphviz.escape(f"{link.kind}:{link.tag}")) tags.add(tag) graph.edge(escaped_id, tag, dir=_EDGE_DIRECTION[link.direction]) From 2f88fa897fe64bffccc726de299c6be5a7b726d3 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:06:12 -0700 Subject: [PATCH 04/18] update tests --- .../unit_tests/graph_vectorstores/test_visualize.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index abef5be8f6e90..a6663dc36d5b4 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -19,7 +19,7 @@ def test_visualize_simple_graph(): ) doc2 = Document( id="b", - page_content="some more content", + page_content="", metadata={ METADATA_LINKS_KEY: [ Link.incoming("href", "b"), @@ -33,12 +33,12 @@ def test_visualize_simple_graph(): assert render_graphviz([doc1, doc2]).source == ( "digraph {\n" "\tnode [style=filled]\n" - "\ta [label=a fillcolor=white shape=note]\n" + '\ta [label=a fillcolor=white shape=note tooltip="some content"]\n' '\thref_a [label="href:a"]\n' "\ta -> href_a [dir=back]\n" '\tkw_foo [label="kw:foo"]\n' "\ta -> kw_foo [dir=both]\n" - "\tb [label=b fillcolor=white shape=note]\n" + '\tb [label=b fillcolor=white shape=note tooltip=""]\n' '\thref_b [label="href:b"]\n' "\tb -> href_b [dir=back]\n" "\tb -> href_a [dir=forward]\n" @@ -51,12 +51,12 @@ def test_visualize_simple_graph(): assert render_graphviz([doc1, doc2], node_colors={"a": "gold"}).source == ( "digraph {\n" "\tnode [style=filled]\n" - "\ta [label=a fillcolor=gold shape=note]\n" + '\ta [label=a fillcolor=gold shape=note tooltip="some content"]\n' '\thref_a [label="href:a"]\n' "\ta -> href_a [dir=back]\n" '\tkw_foo [label="kw:foo"]\n' "\ta -> kw_foo [dir=both]\n" - "\tb [label=b fillcolor=white shape=note]\n" + '\tb [label=b fillcolor=white shape=note tooltip=""]\n' '\thref_b [label="href:b"]\n' "\tb -> href_b [dir=back]\n" "\tb -> href_a [dir=forward]\n" From 6d88b202b1bd1d9f41dccb181f26139fc9cf5a30 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:45:46 -0700 Subject: [PATCH 05/18] include page content prefix --- .../graph_vectorstores/visualize.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index 2f9d52e548a26..db3c873cb7a88 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -1,4 +1,5 @@ from typing import TYPE_CHECKING, Dict, Iterable, Optional +import re from langchain_core.documents import Document from langchain_core.graph_vectorstores.links import get_links @@ -17,6 +18,21 @@ def _escape_id(id: str) -> str: "bidir": "both", } +_WORD_RE = re.compile('\s*\S+') + +def _split_prefix(s: str, max_chars: int = 50) -> str: + words = _WORD_RE.finditer(s) + + split = min(len(s), max_chars) + for word in words: + if word.end(0) > max_chars: + break + split = word.end(0) + + if split == len(s): + return s + else: + return f"{s[0:split]}..." def render_graphviz( documents: Iterable[Document], @@ -70,9 +86,12 @@ def render_graphviz( raise ValueError(f"Illegal graph document without ID: {document}") escaped_id = _escape_id(id) color = node_colors.get(id) or node_color + + + node_label=f"{graphviz.escape(id)}\n{graphviz.escape(_split_prefix(document.page_content))}" graph.node( escaped_id, - label=graphviz.escape(id), + label=node_label, shape="note", fillcolor=color, tooltip=graphviz.escape(document.page_content), From b87d802e885be47067b57c86c0626d6028cb6341 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Wed, 31 Jul 2024 08:04:03 -0700 Subject: [PATCH 06/18] allow none colors --- .../graph_vectorstores/visualize.py | 8 ++--- .../graph_vectorstores/test_visualize.py | 29 ++++++++++++++++--- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index db3c873cb7a88..7e11e51ecd1e8 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -36,8 +36,8 @@ def _split_prefix(s: str, max_chars: int = 50) -> str: def render_graphviz( documents: Iterable[Document], - node_color: str = "white", - node_colors: Optional[Dict[str, str]] = {}, + node_color: Optional[str] = None, + node_colors: Optional[Dict[str, Optional[str]]] = {}, ) -> "graphviz.Digraph": """Render a collection of GraphVectorStore documents to GraphViz format. @@ -79,14 +79,14 @@ def render_graphviz( tags = set() graph = graphviz.Digraph() + graph.attr(rankdir="LR") graph.attr("node", style="filled") for document in documents: id = document.id if id is None: raise ValueError(f"Illegal graph document without ID: {document}") escaped_id = _escape_id(id) - color = node_colors.get(id) or node_color - + color = node_colors[id] if id in node_colors else node_color node_label=f"{graphviz.escape(id)}\n{graphviz.escape(_split_prefix(document.page_content))}" graph.node( diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index a6663dc36d5b4..6638df8c6b4b8 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -32,13 +32,14 @@ def test_visualize_simple_graph(): assert render_graphviz([doc1, doc2]).source == ( "digraph {\n" + "\trankdir=LR\n" "\tnode [style=filled]\n" - '\ta [label=a fillcolor=white shape=note tooltip="some content"]\n' + '\ta [label="a\nsome content" shape=note tooltip="some content"]\n' '\thref_a [label="href:a"]\n' "\ta -> href_a [dir=back]\n" '\tkw_foo [label="kw:foo"]\n' "\ta -> kw_foo [dir=both]\n" - '\tb [label=b fillcolor=white shape=note tooltip=""]\n' + '\tb [label="b\n" shape=note tooltip=""]\n' '\thref_b [label="href:b"]\n' "\tb -> href_b [dir=back]\n" "\tb -> href_a [dir=forward]\n" @@ -50,13 +51,33 @@ def test_visualize_simple_graph(): assert render_graphviz([doc1, doc2], node_colors={"a": "gold"}).source == ( "digraph {\n" + "\trankdir=LR\n" "\tnode [style=filled]\n" - '\ta [label=a fillcolor=gold shape=note tooltip="some content"]\n' + '\ta [label="a\nsome content" fillcolor=gold shape=note tooltip="some content"]\n' '\thref_a [label="href:a"]\n' "\ta -> href_a [dir=back]\n" '\tkw_foo [label="kw:foo"]\n' "\ta -> kw_foo [dir=both]\n" - '\tb [label=b fillcolor=white shape=note tooltip=""]\n' + '\tb [label="b\n" shape=note tooltip=""]\n' + '\thref_b [label="href:b"]\n' + "\tb -> href_b [dir=back]\n" + "\tb -> href_a [dir=forward]\n" + "\tb -> kw_foo [dir=both]\n" + '\tkw_bar [label="kw:bar"]\n' + "\tb -> kw_bar [dir=both]\n" + "}\n" + ) + + assert render_graphviz([doc1, doc2], node_color = "gold", node_colors={"a": None}).source == ( + "digraph {\n" + "\trankdir=LR\n" + "\tnode [style=filled]\n" + '\ta [label="a\nsome content" shape=note tooltip="some content"]\n' + '\thref_a [label="href:a"]\n' + "\ta -> href_a [dir=back]\n" + '\tkw_foo [label="kw:foo"]\n' + "\ta -> kw_foo [dir=both]\n" + '\tb [label="b\n" fillcolor=gold shape=note tooltip=""]\n' '\thref_b [label="href:b"]\n' "\tb -> href_b [dir=back]\n" "\tb -> href_a [dir=forward]\n" From 3ee648f7a4c872554952233c0906ab52d54e2b93 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Wed, 31 Jul 2024 08:14:07 -0700 Subject: [PATCH 07/18] allow setting the engine --- .../langchain_community/graph_vectorstores/visualize.py | 4 +++- .../tests/unit_tests/graph_vectorstores/test_visualize.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index 7e11e51ecd1e8..ccc7621e082d5 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -36,6 +36,7 @@ def _split_prefix(s: str, max_chars: int = 50) -> str: def render_graphviz( documents: Iterable[Document], + engine: Optional[str] = None, node_color: Optional[str] = None, node_colors: Optional[Dict[str, Optional[str]]] = {}, ) -> "graphviz.Digraph": @@ -43,6 +44,7 @@ def render_graphviz( Args: documents: The documents to render. + engine: GraphViz layout engine to use. `None` uses the default. node_color: General node color. Defaults to `white`. node_colors: Dictionary specifying colors of specific nodes. Useful for emphasizing nodes that were selected by MMR, or differ from other @@ -78,7 +80,7 @@ def render_graphviz( tags = set() - graph = graphviz.Digraph() + graph = graphviz.Digraph(engine=engine) graph.attr(rankdir="LR") graph.attr("node", style="filled") for document in documents: diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index 6638df8c6b4b8..f5be9c20c0cf4 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -49,6 +49,8 @@ def test_visualize_simple_graph(): "}\n" ) + assert render_graphviz([doc1, doc2], engine="fdp").engine == "fdp" + assert render_graphviz([doc1, doc2], node_colors={"a": "gold"}).source == ( "digraph {\n" "\trankdir=LR\n" From 3c3966e16c5fb022d4b020df1a6ce11ba17a40e8 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Wed, 31 Jul 2024 08:23:51 -0700 Subject: [PATCH 08/18] fix lint --- .../graph_vectorstores/visualize.py | 13 +++++++++---- .../graph_vectorstores/test_visualize.py | 16 +++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index ccc7621e082d5..5c6df76284bf7 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -1,5 +1,5 @@ -from typing import TYPE_CHECKING, Dict, Iterable, Optional import re +from typing import TYPE_CHECKING, Dict, Iterable, Optional from langchain_core.documents import Document from langchain_core.graph_vectorstores.links import get_links @@ -18,7 +18,8 @@ def _escape_id(id: str) -> str: "bidir": "both", } -_WORD_RE = re.compile('\s*\S+') +_WORD_RE = re.compile("\s*\S+") + def _split_prefix(s: str, max_chars: int = 50) -> str: words = _WORD_RE.finditer(s) @@ -29,11 +30,12 @@ def _split_prefix(s: str, max_chars: int = 50) -> str: break split = word.end(0) - if split == len(s): + if split == len(s): return s else: return f"{s[0:split]}..." + def render_graphviz( documents: Iterable[Document], engine: Optional[str] = None, @@ -90,7 +92,10 @@ def render_graphviz( escaped_id = _escape_id(id) color = node_colors[id] if id in node_colors else node_color - node_label=f"{graphviz.escape(id)}\n{graphviz.escape(_split_prefix(document.page_content))}" + node_label = "\n".join([ + graphviz.escape(id), + graphviz.escape(_split_prefix(document.page_content)), + ]) graph.node( escaped_id, label=node_label, diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index f5be9c20c0cf4..d6fdfee94de1a 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -39,7 +39,8 @@ def test_visualize_simple_graph(): "\ta -> href_a [dir=back]\n" '\tkw_foo [label="kw:foo"]\n' "\ta -> kw_foo [dir=both]\n" - '\tb [label="b\n" shape=note tooltip=""]\n' + '\tb [label="b\n" ' + 'shape=note tooltip=""]\n' '\thref_b [label="href:b"]\n' "\tb -> href_b [dir=back]\n" "\tb -> href_a [dir=forward]\n" @@ -55,12 +56,14 @@ def test_visualize_simple_graph(): "digraph {\n" "\trankdir=LR\n" "\tnode [style=filled]\n" - '\ta [label="a\nsome content" fillcolor=gold shape=note tooltip="some content"]\n' + '\ta [label="a\nsome content" fillcolor=gold ' + 'shape=note tooltip="some content"]\n' '\thref_a [label="href:a"]\n' "\ta -> href_a [dir=back]\n" '\tkw_foo [label="kw:foo"]\n' "\ta -> kw_foo [dir=both]\n" - '\tb [label="b\n" shape=note tooltip=""]\n' + '\tb [label="b\n" ' + 'shape=note tooltip=""]\n' '\thref_b [label="href:b"]\n' "\tb -> href_b [dir=back]\n" "\tb -> href_a [dir=forward]\n" @@ -70,7 +73,9 @@ def test_visualize_simple_graph(): "}\n" ) - assert render_graphviz([doc1, doc2], node_color = "gold", node_colors={"a": None}).source == ( + assert render_graphviz( + [doc1, doc2], node_color="gold", node_colors={"a": None} + ).source == ( "digraph {\n" "\trankdir=LR\n" "\tnode [style=filled]\n" @@ -79,7 +84,8 @@ def test_visualize_simple_graph(): "\ta -> href_a [dir=back]\n" '\tkw_foo [label="kw:foo"]\n' "\ta -> kw_foo [dir=both]\n" - '\tb [label="b\n" fillcolor=gold shape=note tooltip=""]\n' + '\tb [label="b\n" fillcolor=gold ' + 'shape=note tooltip=""]\n' '\thref_b [label="href:b"]\n' "\tb -> href_b [dir=back]\n" "\tb -> href_a [dir=forward]\n" From c417e8f6ecae7b71e7085a5162f27e4374e9d676 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:08:04 -0700 Subject: [PATCH 09/18] fix issue with tag IDs; allow skipping tags --- .../graph_vectorstores/visualize.py | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index 5c6df76284bf7..fbcc81c3e0f87 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -1,5 +1,5 @@ import re -from typing import TYPE_CHECKING, Dict, Iterable, Optional +from typing import TYPE_CHECKING, Dict, Iterable, Optional, Tuple from langchain_core.documents import Document from langchain_core.graph_vectorstores.links import get_links @@ -40,17 +40,20 @@ def render_graphviz( documents: Iterable[Document], engine: Optional[str] = None, node_color: Optional[str] = None, - node_colors: Optional[Dict[str, Optional[str]]] = {}, + node_colors: Optional[Dict[str, Optional[str]]] = None, + skip_tags: Iterable[Tuple[str, str]] = (), ) -> "graphviz.Digraph": """Render a collection of GraphVectorStore documents to GraphViz format. Args: documents: The documents to render. engine: GraphViz layout engine to use. `None` uses the default. - node_color: General node color. Defaults to `white`. + node_color: Default node color. Defaults to `white`. node_colors: Dictionary specifying colors of specific nodes. Useful for emphasizing nodes that were selected by MMR, or differ from other results. + skip_tags: Set of tags to skip when rendering the graph. Specified as + tuples containing the kind and tag. Returns: The "graphviz.Digraph" representing the nodes. May be printed to source, @@ -64,6 +67,9 @@ def render_graphviz( """ if node_colors is None: node_colors = {} + if node_color is None: + node_color = "white" + try: import graphviz except (ImportError, ModuleNotFoundError): @@ -80,11 +86,13 @@ def render_graphviz( "Make sure graphviz executable is installed (see https://www.graphviz.org/download/)." ) - tags = set() - graph = graphviz.Digraph(engine=engine) graph.attr(rankdir="LR") graph.attr("node", style="filled") + + skip_tags = set(skip_tags) + tags = { } + for document in documents: id = document.id if id is None: @@ -105,10 +113,15 @@ def render_graphviz( ) for link in get_links(document): - tag = f"{link.kind}_{link.tag}" - if tag not in tags: - graph.node(tag, label=graphviz.escape(f"{link.kind}:{link.tag}")) - tags.add(tag) + tag_key = (link.kind, link.tag) + if tag_key in skip_tags: + continue + + tag_id = tags.get(tag_key) + if tag_id is None: + tag_id = f"tag_{len(tags)}" + tags[tag_key] = tag_id + graph.node(tag_id, label=graphviz.escape(f"{link.kind}:{link.tag}")) - graph.edge(escaped_id, tag, dir=_EDGE_DIRECTION[link.direction]) + graph.edge(escaped_id, tag_id, dir=_EDGE_DIRECTION[link.direction]) return graph From 21e1a9f2ca6c7f975034fb5cf8b33e01719c479d Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:14:57 -0700 Subject: [PATCH 10/18] lint --- .../graph_vectorstores/visualize.py | 15 +++++++++------ .../graph_vectorstores/test_visualize.py | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index fbcc81c3e0f87..6810f91c3b03a 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -2,7 +2,8 @@ from typing import TYPE_CHECKING, Dict, Iterable, Optional, Tuple from langchain_core.documents import Document -from langchain_core.graph_vectorstores.links import get_links + +from langchain_community.graph_vectorstores.links import get_links if TYPE_CHECKING: import graphviz @@ -91,7 +92,7 @@ def render_graphviz( graph.attr("node", style="filled") skip_tags = set(skip_tags) - tags = { } + tags: dict[Tuple[str, str], str] = {} for document in documents: id = document.id @@ -100,10 +101,12 @@ def render_graphviz( escaped_id = _escape_id(id) color = node_colors[id] if id in node_colors else node_color - node_label = "\n".join([ - graphviz.escape(id), - graphviz.escape(_split_prefix(document.page_content)), - ]) + node_label = "\n".join( + [ + graphviz.escape(id), + graphviz.escape(_split_prefix(document.page_content)), + ] + ) graph.node( escaped_id, label=node_label, diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index d6fdfee94de1a..6e446af768ae2 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -6,7 +6,7 @@ @pytest.mark.requires("graphviz") -def test_visualize_simple_graph(): +def test_visualize_simple_graph() -> None: doc1 = Document( id="a", page_content="some content", From e112ae7156eb684aca1b93ed3791a8fffe254de2 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:20:09 -0700 Subject: [PATCH 11/18] fix --- .../tests/unit_tests/graph_vectorstores/test_visualize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index 6e446af768ae2..10601956a79a2 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -1,7 +1,7 @@ import pytest from langchain_core.documents import Document -from langchain_core.graph_vectorstores.links import METADATA_LINKS_KEY, Link +from langchain_community.graph_vectorstores.links import METADATA_LINKS_KEY, Link from langchain_community.graph_vectorstores.visualize import render_graphviz From f547250556f72c4be060e8b7fe060a2322a5a9c7 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:25:40 -0700 Subject: [PATCH 12/18] fix test --- .../langchain_community/graph_vectorstores/visualize.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index 6810f91c3b03a..33bee819109de 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -79,14 +79,6 @@ def render_graphviz( "Please install it with `pip install graphviz`." ) - try: - graphviz.version() - except graphviz.ExecutableNotFound: - raise ImportError( - "Could not execute `dot`. " - "Make sure graphviz executable is installed (see https://www.graphviz.org/download/)." - ) - graph = graphviz.Digraph(engine=engine) graph.attr(rankdir="LR") graph.attr("node", style="filled") From 33dd684b7ae6611a5b0cae031d5e67d6595e8e0f Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:40:13 -0700 Subject: [PATCH 13/18] update tests --- .../graph_vectorstores/test_visualize.py | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index 10601956a79a2..23518b833561c 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -35,18 +35,18 @@ def test_visualize_simple_graph() -> None: "\trankdir=LR\n" "\tnode [style=filled]\n" '\ta [label="a\nsome content" shape=note tooltip="some content"]\n' - '\thref_a [label="href:a"]\n' - "\ta -> href_a [dir=back]\n" - '\tkw_foo [label="kw:foo"]\n' - "\ta -> kw_foo [dir=both]\n" + '\tag_0 [label="href:a"]\n' + "\ta -> tag_0 [dir=back]\n" + '\ttag_1 [label="kw:foo"]\n' + "\ta -> tag_1 [dir=both]\n" '\tb [label="b\n" ' 'shape=note tooltip=""]\n' - '\thref_b [label="href:b"]\n' - "\tb -> href_b [dir=back]\n" - "\tb -> href_a [dir=forward]\n" - "\tb -> kw_foo [dir=both]\n" - '\tkw_bar [label="kw:bar"]\n' - "\tb -> kw_bar [dir=both]\n" + '\ttag_2 [label="href:b"]\n' + "\tb -> tag_2 [dir=back]\n" + "\tb -> tag_0 [dir=forward]\n" + "\tb -> tag_1 [dir=both]\n" + '\ttag_3 [label="kw:bar"]\n' + "\tb -> tag_3 [dir=both]\n" "}\n" ) @@ -58,18 +58,18 @@ def test_visualize_simple_graph() -> None: "\tnode [style=filled]\n" '\ta [label="a\nsome content" fillcolor=gold ' 'shape=note tooltip="some content"]\n' - '\thref_a [label="href:a"]\n' - "\ta -> href_a [dir=back]\n" - '\tkw_foo [label="kw:foo"]\n' - "\ta -> kw_foo [dir=both]\n" + '\tag_0 [label="href:a"]\n' + "\ta -> tag_0 [dir=back]\n" + '\ttag_1 [label="kw:foo"]\n' + "\ta -> tag_1 [dir=both]\n" '\tb [label="b\n" ' 'shape=note tooltip=""]\n' - '\thref_b [label="href:b"]\n' - "\tb -> href_b [dir=back]\n" - "\tb -> href_a [dir=forward]\n" - "\tb -> kw_foo [dir=both]\n" - '\tkw_bar [label="kw:bar"]\n' - "\tb -> kw_bar [dir=both]\n" + '\ttag_2 [label="href:b"]\n' + "\tb -> tag_2 [dir=back]\n" + "\tb -> tag_0 [dir=forward]\n" + "\tb -> tag_1 [dir=both]\n" + '\ttag_3 [label="kw:bar"]\n' + "\tb -> tag_3 [dir=both]\n" "}\n" ) @@ -80,17 +80,17 @@ def test_visualize_simple_graph() -> None: "\trankdir=LR\n" "\tnode [style=filled]\n" '\ta [label="a\nsome content" shape=note tooltip="some content"]\n' - '\thref_a [label="href:a"]\n' - "\ta -> href_a [dir=back]\n" - '\tkw_foo [label="kw:foo"]\n' - "\ta -> kw_foo [dir=both]\n" + '\ttag_0 [label="href:a"]\n' + "\ta -> tag_0 [dir=back]\n" + '\ttag_1 [label="kw:foo"]\n' + "\ta -> tag_1 [dir=both]\n" '\tb [label="b\n" fillcolor=gold ' 'shape=note tooltip=""]\n' - '\thref_b [label="href:b"]\n' - "\tb -> href_b [dir=back]\n" - "\tb -> href_a [dir=forward]\n" - "\tb -> kw_foo [dir=both]\n" - '\tkw_bar [label="kw:bar"]\n' - "\tb -> kw_bar [dir=both]\n" + '\ttag_2 [label="href:b"]\n' + "\tb -> tag_2 [dir=back]\n" + "\tb -> tag_0 [dir=forward]\n" + "\tb -> tag_1 [dir=both]\n" + '\ttag_3 [label="kw:bar"]\n' + "\tb -> tag_3 [dir=both]\n" "}\n" ) From 5aaefcbbb339a7f207a6a1f1a70d522390f537c6 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:42:43 -0700 Subject: [PATCH 14/18] fix --- .../tests/unit_tests/graph_vectorstores/test_visualize.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index 23518b833561c..a20ac1e510f06 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -35,7 +35,7 @@ def test_visualize_simple_graph() -> None: "\trankdir=LR\n" "\tnode [style=filled]\n" '\ta [label="a\nsome content" shape=note tooltip="some content"]\n' - '\tag_0 [label="href:a"]\n' + '\ttag_0 [label="href:a"]\n' "\ta -> tag_0 [dir=back]\n" '\ttag_1 [label="kw:foo"]\n' "\ta -> tag_1 [dir=both]\n" @@ -58,7 +58,7 @@ def test_visualize_simple_graph() -> None: "\tnode [style=filled]\n" '\ta [label="a\nsome content" fillcolor=gold ' 'shape=note tooltip="some content"]\n' - '\tag_0 [label="href:a"]\n' + '\ttag_0 [label="href:a"]\n' "\ta -> tag_0 [dir=back]\n" '\ttag_1 [label="kw:foo"]\n' "\ta -> tag_1 [dir=both]\n" From c027c0e5b9300825b34aeb0dc4f7a3459e03b589 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:54:47 -0700 Subject: [PATCH 15/18] fix --- .../langchain_community/graph_vectorstores/visualize.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index 33bee819109de..e9ba1a5f0a908 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -49,7 +49,7 @@ def render_graphviz( Args: documents: The documents to render. engine: GraphViz layout engine to use. `None` uses the default. - node_color: Default node color. Defaults to `white`. + node_color: Default node color. node_colors: Dictionary specifying colors of specific nodes. Useful for emphasizing nodes that were selected by MMR, or differ from other results. @@ -68,8 +68,6 @@ def render_graphviz( """ if node_colors is None: node_colors = {} - if node_color is None: - node_color = "white" try: import graphviz From ffa3e0dc44802412be5dc340bf681402ec8e1af1 Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:01:56 -0700 Subject: [PATCH 16/18] add test for skip_tags --- .../graph_vectorstores/test_visualize.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index a20ac1e510f06..219e2cc81a83f 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -94,3 +94,22 @@ def test_visualize_simple_graph() -> None: "\tb -> tag_3 [dir=both]\n" "}\n" ) + + assert render_graphviz( + [doc1, doc2], skip_tags=[("kw", "foo")] + ).source == ( + "digraph {\n" + "\trankdir=LR\n" + "\tnode [style=filled]\n" + '\ta [label="a\nsome content" shape=note tooltip="some content"]\n' + '\ttag_0 [label="href:a"]\n' + "\ta -> tag_0 [dir=back]\n" + '\tb [label="b\n" ' + 'shape=note tooltip=""]\n' + '\ttag_1 [label="href:b"]\n' + "\tb -> tag_1 [dir=back]\n" + "\tb -> tag_0 [dir=forward]\n" + '\ttag_2 [label="kw:bar"]\n' + "\tb -> tag_2 [dir=both]\n" + "}\n" + ) \ No newline at end of file From 11341c18b3a72cb37dbc4af6700261522970b12a Mon Sep 17 00:00:00 2001 From: Ben Chambers <35960+bjchambers@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:17:55 -0700 Subject: [PATCH 17/18] lint --- .../tests/unit_tests/graph_vectorstores/test_visualize.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py index 219e2cc81a83f..89615c0bfe6b9 100644 --- a/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py +++ b/libs/community/tests/unit_tests/graph_vectorstores/test_visualize.py @@ -95,9 +95,7 @@ def test_visualize_simple_graph() -> None: "}\n" ) - assert render_graphviz( - [doc1, doc2], skip_tags=[("kw", "foo")] - ).source == ( + assert render_graphviz([doc1, doc2], skip_tags=[("kw", "foo")]).source == ( "digraph {\n" "\trankdir=LR\n" "\tnode [style=filled]\n" @@ -112,4 +110,4 @@ def test_visualize_simple_graph() -> None: '\ttag_2 [label="kw:bar"]\n' "\tb -> tag_2 [dir=both]\n" "}\n" - ) \ No newline at end of file + ) From d65c905fe798a16884ac78d9533f6aa5df9ed644 Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Fri, 13 Dec 2024 17:59:34 -0800 Subject: [PATCH 18/18] x --- .../langchain_community/graph_vectorstores/visualize.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/community/langchain_community/graph_vectorstores/visualize.py b/libs/community/langchain_community/graph_vectorstores/visualize.py index e9ba1a5f0a908..7dd0f1dcfcf20 100644 --- a/libs/community/langchain_community/graph_vectorstores/visualize.py +++ b/libs/community/langchain_community/graph_vectorstores/visualize.py @@ -1,6 +1,7 @@ import re from typing import TYPE_CHECKING, Dict, Iterable, Optional, Tuple +from langchain_core._api import beta from langchain_core.documents import Document from langchain_community.graph_vectorstores.links import get_links @@ -37,6 +38,7 @@ def _split_prefix(s: str, max_chars: int = 50) -> str: return f"{s[0:split]}..." +@beta() def render_graphviz( documents: Iterable[Document], engine: Optional[str] = None,