duragraph-python 0.1.0__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.
duragraph/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ """DuraGraph Python SDK - Reliable AI Workflow Orchestration."""
2
+
3
+ from duragraph.edges import edge
4
+ from duragraph.graph import Graph
5
+ from duragraph.nodes import (
6
+ entrypoint,
7
+ human_node,
8
+ llm_node,
9
+ node,
10
+ router_node,
11
+ tool_node,
12
+ )
13
+ from duragraph.types import AIMessage, HumanMessage, Message, State, ToolMessage
14
+
15
+ __version__ = "0.1.0"
16
+
17
+ __all__ = [
18
+ # Graph
19
+ "Graph",
20
+ # Node decorators
21
+ "node",
22
+ "llm_node",
23
+ "tool_node",
24
+ "router_node",
25
+ "human_node",
26
+ "entrypoint",
27
+ # Edge
28
+ "edge",
29
+ # Types
30
+ "State",
31
+ "Message",
32
+ "HumanMessage",
33
+ "AIMessage",
34
+ "ToolMessage",
35
+ ]
@@ -0,0 +1,5 @@
1
+ """CLI module for DuraGraph."""
2
+
3
+ from duragraph.cli.main import main
4
+
5
+ __all__ = ["main"]
duragraph/cli/main.py ADDED
@@ -0,0 +1,163 @@
1
+ """DuraGraph CLI entry point."""
2
+
3
+ import argparse
4
+ import sys
5
+ from pathlib import Path
6
+
7
+
8
+ def main() -> int:
9
+ """Main CLI entry point."""
10
+ parser = argparse.ArgumentParser(
11
+ prog="duragraph",
12
+ description="DuraGraph Python SDK CLI",
13
+ )
14
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
15
+
16
+ # init command
17
+ init_parser = subparsers.add_parser("init", help="Initialize a new DuraGraph project")
18
+ init_parser.add_argument("name", help="Project name")
19
+ init_parser.add_argument(
20
+ "--template",
21
+ choices=["minimal", "full"],
22
+ default="minimal",
23
+ help="Project template",
24
+ )
25
+
26
+ # dev command
27
+ dev_parser = subparsers.add_parser("dev", help="Run in development mode")
28
+ dev_parser.add_argument(
29
+ "--port",
30
+ type=int,
31
+ default=8000,
32
+ help="Port for local server",
33
+ )
34
+ dev_parser.add_argument(
35
+ "--reload",
36
+ action="store_true",
37
+ help="Enable auto-reload",
38
+ )
39
+
40
+ # deploy command
41
+ deploy_parser = subparsers.add_parser("deploy", help="Deploy to control plane")
42
+ deploy_parser.add_argument(
43
+ "--control-plane",
44
+ required=True,
45
+ help="Control plane URL",
46
+ )
47
+ deploy_parser.add_argument(
48
+ "--graph",
49
+ help="Specific graph to deploy (default: all)",
50
+ )
51
+
52
+ # visualize command
53
+ viz_parser = subparsers.add_parser("visualize", help="Visualize a graph")
54
+ viz_parser.add_argument("file", help="Python file containing the graph")
55
+ viz_parser.add_argument(
56
+ "--output",
57
+ "-o",
58
+ help="Output file (default: stdout)",
59
+ )
60
+ viz_parser.add_argument(
61
+ "--format",
62
+ choices=["mermaid", "dot", "json"],
63
+ default="mermaid",
64
+ help="Output format",
65
+ )
66
+
67
+ args = parser.parse_args()
68
+
69
+ if args.command is None:
70
+ parser.print_help()
71
+ return 0
72
+
73
+ if args.command == "init":
74
+ return cmd_init(args.name, args.template)
75
+ elif args.command == "dev":
76
+ return cmd_dev(args.port, args.reload)
77
+ elif args.command == "deploy":
78
+ return cmd_deploy(args.control_plane, args.graph)
79
+ elif args.command == "visualize":
80
+ return cmd_visualize(args.file, args.output, args.format)
81
+
82
+ return 0
83
+
84
+
85
+ def cmd_init(name: str, template: str) -> int:
86
+ """Initialize a new project."""
87
+ project_dir = Path(name)
88
+ if project_dir.exists():
89
+ print(f"Error: Directory '{name}' already exists")
90
+ return 1
91
+
92
+ project_dir.mkdir(parents=True)
93
+
94
+ # Create basic structure
95
+ (project_dir / "src").mkdir()
96
+ (project_dir / "tests").mkdir()
97
+
98
+ # Create main agent file
99
+ agent_content = '''"""Example DuraGraph agent."""
100
+
101
+ from duragraph import Graph, llm_node, entrypoint
102
+
103
+
104
+ @Graph(id="example_agent")
105
+ class ExampleAgent:
106
+ """A simple example agent."""
107
+
108
+ @entrypoint
109
+ @llm_node(model="gpt-4o-mini")
110
+ def process(self, state):
111
+ """Process the input."""
112
+ return state
113
+
114
+
115
+ if __name__ == "__main__":
116
+ agent = ExampleAgent()
117
+ result = agent.run({"message": "Hello, world!"})
118
+ print(result)
119
+ '''
120
+ (project_dir / "src" / "agent.py").write_text(agent_content)
121
+
122
+ # Create pyproject.toml
123
+ pyproject_content = f'''[project]
124
+ name = "{name}"
125
+ version = "0.1.0"
126
+ dependencies = ["duragraph"]
127
+
128
+ [tool.duragraph]
129
+ control_plane = "http://localhost:8081"
130
+ '''
131
+ (project_dir / "pyproject.toml").write_text(pyproject_content)
132
+
133
+ print(f"Created new DuraGraph project: {name}")
134
+ print("\nNext steps:")
135
+ print(f" cd {name}")
136
+ print(" uv sync")
137
+ print(" duragraph dev")
138
+ return 0
139
+
140
+
141
+ def cmd_dev(port: int, reload: bool) -> int:
142
+ """Run in development mode."""
143
+ print(f"Starting development server on port {port}...")
144
+ print("Development mode not yet implemented")
145
+ return 1
146
+
147
+
148
+ def cmd_deploy(control_plane: str, graph: str | None) -> int:
149
+ """Deploy to control plane."""
150
+ print(f"Deploying to {control_plane}...")
151
+ print("Deployment not yet implemented")
152
+ return 1
153
+
154
+
155
+ def cmd_visualize(file: str, output: str | None, format: str) -> int:
156
+ """Visualize a graph."""
157
+ print(f"Visualizing {file}...")
158
+ print("Visualization not yet implemented")
159
+ return 1
160
+
161
+
162
+ if __name__ == "__main__":
163
+ sys.exit(main())
duragraph/edges.py ADDED
@@ -0,0 +1,116 @@
1
+ """Edge definitions for DuraGraph workflows."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+
7
+ class Edge:
8
+ """Represents an edge between nodes in a graph."""
9
+
10
+ def __init__(
11
+ self,
12
+ source: str,
13
+ target: str | dict[str, str],
14
+ condition: Callable[[dict[str, Any]], bool] | None = None,
15
+ ):
16
+ self.source = source
17
+ self.target = target
18
+ self.condition = condition
19
+
20
+ def to_dict(self) -> dict[str, Any]:
21
+ """Convert edge to dictionary representation."""
22
+ result: dict[str, Any] = {
23
+ "source": self.source,
24
+ "target": self.target,
25
+ }
26
+ if self.condition is not None:
27
+ result["conditional"] = True
28
+ return result
29
+
30
+
31
+ class EdgeBuilder:
32
+ """Builder for creating edges with fluent API."""
33
+
34
+ def __init__(self, source: str):
35
+ self.source = source
36
+ self._edges: list[Edge] = []
37
+
38
+ def to(
39
+ self,
40
+ target: str,
41
+ *,
42
+ condition: Callable[[dict[str, Any]], bool] | None = None,
43
+ ) -> "EdgeBuilder":
44
+ """Add an edge to a target node.
45
+
46
+ Args:
47
+ target: Name of the target node.
48
+ condition: Optional condition function that takes state and returns bool.
49
+
50
+ Returns:
51
+ Self for chaining.
52
+ """
53
+ self._edges.append(Edge(self.source, target, condition))
54
+ return self
55
+
56
+ def to_conditional(
57
+ self,
58
+ mapping: dict[str, str],
59
+ ) -> "EdgeBuilder":
60
+ """Add conditional edges based on router output.
61
+
62
+ Args:
63
+ mapping: Dictionary mapping router output values to node names.
64
+
65
+ Returns:
66
+ Self for chaining.
67
+ """
68
+ self._edges.append(Edge(self.source, mapping))
69
+ return self
70
+
71
+ def build(self) -> list[Edge]:
72
+ """Build and return all edges."""
73
+ return self._edges
74
+
75
+
76
+ def edge(source: str) -> EdgeBuilder:
77
+ """Create an edge builder starting from a source node.
78
+
79
+ Args:
80
+ source: Name of the source node.
81
+
82
+ Returns:
83
+ EdgeBuilder for fluent edge construction.
84
+
85
+ Example:
86
+ # Simple edge
87
+ edge("classify").to("respond")
88
+
89
+ # Conditional edge
90
+ edge("router").to_conditional({
91
+ "billing": "billing_handler",
92
+ "support": "support_handler",
93
+ })
94
+ """
95
+ return EdgeBuilder(source)
96
+
97
+
98
+ class NodeProxy:
99
+ """Proxy object for node methods that enables >> operator for edge definition."""
100
+
101
+ def __init__(self, name: str, graph: Any):
102
+ self._name = name
103
+ self._graph = graph
104
+
105
+ def __rshift__(self, other: "NodeProxy") -> "NodeProxy":
106
+ """Define an edge using >> operator.
107
+
108
+ Example:
109
+ classify >> respond
110
+ """
111
+ if self._graph is not None:
112
+ self._graph._add_edge(self._name, other._name)
113
+ return other
114
+
115
+ def __repr__(self) -> str:
116
+ return f"NodeProxy({self._name})"