Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
68f5fe2
start pydantic-ai-graph
samuelcolvin Dec 22, 2024
615c5e2
lower case state machine
samuelcolvin Dec 22, 2024
9ffe8f0
starting tests
samuelcolvin Dec 22, 2024
dd60d15
add history and logfire
samuelcolvin Dec 22, 2024
73db282
add example, alter types
samuelcolvin Dec 22, 2024
03fcaf2
fix dependencies
samuelcolvin Dec 23, 2024
1bae9bf
fix ci deps
samuelcolvin Dec 23, 2024
61f3d2b
fix tests for other versions
samuelcolvin Dec 23, 2024
dc769c9
change node test times
samuelcolvin Dec 23, 2024
b03e7bc
pydantic-ai-graph - simplify public generics (#539)
dmontagu Jan 2, 2025
d0bdb87
Typo in Graph Documentation (#596)
izzyacademy Jan 3, 2025
16ccd03
fix linting
samuelcolvin Jan 7, 2025
bda6dfb
separate mermaid logic
samuelcolvin Jan 7, 2025
cce71e1
fix graph type checking
samuelcolvin Jan 7, 2025
2d9f9f3
bump
samuelcolvin Jan 7, 2025
7e98bf7
adding node highlighting to mermaid, testing locally
samuelcolvin Jan 7, 2025
743fa5a
bump
samuelcolvin Jan 7, 2025
f6aa929
fix type checking imports
samuelcolvin Jan 7, 2025
246755d
fix for python 3.9
samuelcolvin Jan 7, 2025
d985db4
simplify mermaid config
samuelcolvin Jan 8, 2025
c325789
remove GraphRunner
samuelcolvin Jan 8, 2025
c41d59a
add Interrupt
samuelcolvin Jan 9, 2025
0b19632
remove interrupt, replace with "next()"
samuelcolvin Jan 9, 2025
c1b8035
address comments
samuelcolvin Jan 9, 2025
7e24d9d
switch name to pydantic-graph
samuelcolvin Jan 10, 2025
b74d0e4
allow labeling edges and notes for docstrings
samuelcolvin Jan 10, 2025
7f34a0d
allow notes to be disabled
samuelcolvin Jan 10, 2025
15573e9
adding graph tests
samuelcolvin Jan 10, 2025
de6b9e7
more mermaid tests, fix 3.9
samuelcolvin Jan 10, 2025
08e87aa
rename node to start_node in graph.run()
samuelcolvin Jan 10, 2025
25d79aa
more tests for graphs
samuelcolvin Jan 10, 2025
6e62906
coverage in tests
samuelcolvin Jan 10, 2025
c9ebc49
cleanup graph properties
samuelcolvin Jan 10, 2025
997ba99
infer graph name
samuelcolvin Jan 11, 2025
3b22850
fix for 3.9
samuelcolvin Jan 11, 2025
452a62f
adding API docs
samuelcolvin Jan 11, 2025
707129f
fix state, more docs
samuelcolvin Jan 11, 2025
80d4713
fix graph api examples
samuelcolvin Jan 11, 2025
be1563d
starting graph documentation
samuelcolvin Jan 11, 2025
6fdd1e9
fix examples
samuelcolvin Jan 11, 2025
a882a6c
more graph documentation
samuelcolvin Jan 11, 2025
f2cd72a
add GenAI example
samuelcolvin Jan 11, 2025
111f2d0
more graph docs
samuelcolvin Jan 12, 2025
5d0a834
extending graph docs
samuelcolvin Jan 13, 2025
9a7ab8e
fix history serialization
samuelcolvin Jan 14, 2025
4cd9142
add history (de)serialization tests
samuelcolvin Jan 14, 2025
598fdd6
add mermaid diagram section to graph docs
samuelcolvin Jan 14, 2025
bf8b824
fix tests
samuelcolvin Jan 14, 2025
aec8fab
add exceptions docs
samuelcolvin Jan 14, 2025
7d4f31d
docs tweaks
samuelcolvin Jan 14, 2025
79cb2b3
copy edits from @dmontagu
samuelcolvin Jan 15, 2025
38a787e
fix pydantic-graph readme
samuelcolvin Jan 15, 2025
6db58d6
snapshot state after node execution, not before
samuelcolvin Jan 15, 2025
326e5f5
improve history (de)serialization
samuelcolvin Jan 15, 2025
5b8433b
fix for older python
samuelcolvin Jan 15, 2025
b4e44bf
Graph deps (#693)
samuelcolvin Jan 15, 2025
a2144d6
docs comments, GraphContext -> GraphRunContext
samuelcolvin Jan 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
adding API docs
  • Loading branch information
samuelcolvin committed Jan 15, 2025
commit 452a62f3ff760afc7ce788e607d6aa6aa9012d9a
3 changes: 3 additions & 0 deletions docs/api/pydantic_graph/graph.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_graph`

::: pydantic_graph.graph
3 changes: 3 additions & 0 deletions docs/api/pydantic_graph/mermaid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_graph.mermaid`

::: pydantic_graph.mermaid
11 changes: 11 additions & 0 deletions docs/api/pydantic_graph/nodes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `pydantic_graph.nodes`

::: pydantic_graph.nodes
options:
members:
- GraphContext
- BaseNode
- End
- Edge
- RunEndT
- NodeRunEndT
3 changes: 3 additions & 0 deletions docs/api/pydantic_graph/state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `pydantic_graph.state`

::: pydantic_graph.state
5 changes: 5 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ nav:
- api/models/ollama.md
- api/models/test.md
- api/models/function.md
- api/pydantic_graph/graph.md
- api/pydantic_graph/nodes.md
- api/pydantic_graph/state.md
- api/pydantic_graph/mermaid.md

extra:
# hide the "Made with Material for MkDocs" message
Expand Down Expand Up @@ -150,6 +154,7 @@ markdown_extensions:

watch:
- pydantic_ai_slim
- pydantic_graph
- examples

plugins:
Expand Down
6 changes: 3 additions & 3 deletions pydantic_graph/pydantic_graph/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .exceptions import GraphRuntimeError, GraphSetupError
from .graph import Graph
from .nodes import BaseNode, Edge, End, GraphContext
from .state import AbstractState, EndEvent, HistoryStep, NodeEvent
from .state import AbstractState, EndStep, HistoryStep, NodeStep

__all__ = (
'Graph',
Expand All @@ -10,9 +10,9 @@
'GraphContext',
'Edge',
'AbstractState',
'EndEvent',
'EndStep',
'HistoryStep',
'NodeEvent',
'NodeStep',
'GraphSetupError',
'GraphRuntimeError',
)
143 changes: 108 additions & 35 deletions pydantic_graph/pydantic_graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from . import _utils, exceptions, mermaid
from ._utils import get_parent_namespace
from .nodes import BaseNode, End, GraphContext, NodeDef
from .state import EndEvent, HistoryStep, NodeEvent, StateT
from .state import EndStep, HistoryStep, NodeStep, StateT

__all__ = ('Graph',)

Expand All @@ -36,9 +36,16 @@ def __init__(
self,
*,
nodes: Sequence[type[BaseNode[StateT, RunEndT]]],
state_type: type[StateT] | None = None,
name: str | None = None,
):
"""Create a graph from a sequence of nodes.

Args:
nodes: The nodes which make up the graph, nodes need to be unique and all be generic in the same
state type.
name: Optional name for the graph, if not provided the name will be inferred from the calling frame
on the first call to a graph method.
"""
self.name = name

parent_namespace = get_parent_namespace(inspect.currentframe())
Expand All @@ -48,34 +55,6 @@ def __init__(

self._validate_edges()

def _register_node(self, node: type[BaseNode[StateT, RunEndT]], parent_namespace: dict[str, Any] | None) -> None:
node_id = node.get_id()
if existing_node := self.node_defs.get(node_id):
raise exceptions.GraphSetupError(
f'Node ID `{node_id}` is not unique — found on {existing_node.node} and {node}'
)
else:
self.node_defs[node_id] = node.get_node_def(parent_namespace)

def _validate_edges(self):
known_node_ids = self.node_defs.keys()
bad_edges: dict[str, list[str]] = {}

for node_id, node_def in self.node_defs.items():
for edge in node_def.next_node_edges.keys():
if edge not in known_node_ids:
bad_edges.setdefault(edge, []).append(f'`{node_id}`')

if bad_edges:
bad_edges_list = [f'`{k}` is referenced by {_utils.comma_and(v)}' for k, v in bad_edges.items()]
if len(bad_edges_list) == 1:
raise exceptions.GraphSetupError(f'{bad_edges_list[0]} but not included in the graph.')
else:
b = '\n'.join(f' {be}' for be in bad_edges_list)
raise exceptions.GraphSetupError(
f'Nodes are referenced in the graph but not included in the graph:\n{b}'
)

async def next(
self,
state: StateT,
Expand All @@ -84,13 +63,24 @@ async def next(
*,
infer_name: bool = True,
) -> BaseNode[StateT, Any] | End[RunEndT]:
"""Run a node in the graph and return the next node to run.

Args:
state: The current state of the graph.
node: The node to run.
history: The history of the graph run so far. NOTE: this will be mutated to add the new step.
infer_name: Whether to infer the graph name from the calling frame.

Returns:
The next node to run or [`End`][pydantic_graph.nodes.End] if the graph has finished.
"""
if infer_name and self.name is None:
self._infer_name(inspect.currentframe())
node_id = node.get_id()
if node_id not in self.node_defs:
raise exceptions.GraphRuntimeError(f'Node `{node}` is not in the graph.')

history_step: NodeEvent[StateT, RunEndT] | None = NodeEvent(state, node)
history_step: NodeStep[StateT, RunEndT] | None = NodeStep(state, node)
history.append(history_step)

ctx = GraphContext(state)
Expand All @@ -107,6 +97,15 @@ async def run(
*,
infer_name: bool = True,
) -> tuple[RunEndT, list[HistoryStep[StateT, RunEndT]]]:
"""Run the graph from a starting node until it ends.

Args:
state: The initial state of the graph.
start_node: the first node to run.
infer_name: Whether to infer the graph name from the calling frame.

Returns: The result type from ending the run and the history of the run.
"""
history: list[HistoryStep[StateT, RunEndT]] = []
if infer_name and self.name is None:
self._infer_name(inspect.currentframe())
Expand All @@ -119,7 +118,7 @@ async def run(
while True:
next_node = await self.next(state, start_node, history, infer_name=False)
if isinstance(next_node, End):
history.append(EndEvent(state, next_node))
history.append(EndStep(state, next_node))
run_span.set_attribute('history', history)
return next_node.data, history
elif isinstance(next_node, BaseNode):
Expand All @@ -136,13 +135,29 @@ def mermaid_code(
self,
*,
start_node: Sequence[mermaid.NodeIdent] | mermaid.NodeIdent | None = None,
highlighted_nodes: Sequence[mermaid.NodeIdent] | mermaid.NodeIdent | None = None,
highlight_css: str = mermaid.DEFAULT_HIGHLIGHT_CSS,
edge_labels: bool = True,
title: str | None | Literal[False] = None,
edge_labels: bool = True,
notes: bool = True,
highlighted_nodes: Sequence[mermaid.NodeIdent] | mermaid.NodeIdent | None = None,
highlight_css: str = mermaid.DEFAULT_HIGHLIGHT_CSS,
infer_name: bool = True,
) -> str:
"""Generate a diagram representing the graph as [mermaid](https://mermaid.js.org/) chart.

This method calls [`pydantic_graph.mermaid.generate_code`][pydantic_graph.mermaid.generate_code].

Args:
start_node: The node or nodes to start the graph from.
title: The title of the diagram, use `False` to not include a title.
edge_labels: Whether to include edge labels.
notes: Whether to include notes on each node.
highlighted_nodes: Optional node or nodes to highlight.
highlight_css: The CSS to use for highlighting nodes.
infer_name: Whether to infer the graph name from the calling frame.

Returns:
The mermaid code for the graph, which can then be rendered as a diagram.
"""
if infer_name and self.name is None:
self._infer_name(inspect.currentframe())
if title is None and self.name:
Expand All @@ -158,6 +173,22 @@ def mermaid_code(
)

def mermaid_image(self, infer_name: bool = True, **kwargs: Unpack[mermaid.MermaidConfig]) -> bytes:
"""Generate a diagram representing the graph as an image.

The format and diagram can be customized using `kwargs`,
see [`pydantic_graph.mermaid.MermaidConfig`][pydantic_graph.mermaid.MermaidConfig].

!!! note "Uses external service"
This method makes a request to [mermaid.ink](https://mermaid.ink) to render the image, `mermaid.ink`
is a free service not affiliated with Pydantic.

Args:
infer_name: Whether to infer the graph name from the calling frame.
**kwargs: Additional arguments to pass to `mermaid.request_image`.

Returns:
The image bytes.
"""
if infer_name and self.name is None:
self._infer_name(inspect.currentframe())
if 'title' not in kwargs and self.name:
Expand All @@ -167,12 +198,54 @@ def mermaid_image(self, infer_name: bool = True, **kwargs: Unpack[mermaid.Mermai
def mermaid_save(
self, path: Path | str, /, *, infer_name: bool = True, **kwargs: Unpack[mermaid.MermaidConfig]
) -> None:
"""Generate a diagram representing the graph and save it as an image.

The format and diagram can be customized using `kwargs`,
see [`pydantic_graph.mermaid.MermaidConfig`][pydantic_graph.mermaid.MermaidConfig].

!!! note "Uses external service"
This method makes a request to [mermaid.ink](https://mermaid.ink) to render the image, `mermaid.ink`
is a free service not affiliated with Pydantic.

Args:
path: The path to save the image to.
infer_name: Whether to infer the graph name from the calling frame.
**kwargs: Additional arguments to pass to `mermaid.save_image`.
"""
if infer_name and self.name is None:
self._infer_name(inspect.currentframe())
if 'title' not in kwargs and self.name:
kwargs['title'] = self.name
mermaid.save_image(path, self, **kwargs)

def _register_node(self, node: type[BaseNode[StateT, RunEndT]], parent_namespace: dict[str, Any] | None) -> None:
node_id = node.get_id()
if existing_node := self.node_defs.get(node_id):
raise exceptions.GraphSetupError(
f'Node ID `{node_id}` is not unique — found on {existing_node.node} and {node}'
)
else:
self.node_defs[node_id] = node.get_node_def(parent_namespace)

def _validate_edges(self):
known_node_ids = self.node_defs.keys()
bad_edges: dict[str, list[str]] = {}

for node_id, node_def in self.node_defs.items():
for edge in node_def.next_node_edges.keys():
if edge not in known_node_ids:
bad_edges.setdefault(edge, []).append(f'`{node_id}`')

if bad_edges:
bad_edges_list = [f'`{k}` is referenced by {_utils.comma_and(v)}' for k, v in bad_edges.items()]
if len(bad_edges_list) == 1:
raise exceptions.GraphSetupError(f'{bad_edges_list[0]} but not included in the graph.')
else:
b = '\n'.join(f' {be}' for be in bad_edges_list)
raise exceptions.GraphSetupError(
f'Nodes are referenced in the graph but not included in the graph:\n{b}'
)

def _infer_name(self, function_frame: FrameType | None) -> None:
"""Infer the agent name from the call frame.

Expand Down
Loading