jaf-py 2.5.9__py3-none-any.whl → 2.5.11__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.
Files changed (92) hide show
  1. jaf/__init__.py +154 -57
  2. jaf/a2a/__init__.py +42 -21
  3. jaf/a2a/agent.py +79 -126
  4. jaf/a2a/agent_card.py +87 -78
  5. jaf/a2a/client.py +30 -66
  6. jaf/a2a/examples/client_example.py +12 -12
  7. jaf/a2a/examples/integration_example.py +38 -47
  8. jaf/a2a/examples/server_example.py +56 -53
  9. jaf/a2a/memory/__init__.py +0 -4
  10. jaf/a2a/memory/cleanup.py +28 -21
  11. jaf/a2a/memory/factory.py +155 -133
  12. jaf/a2a/memory/providers/composite.py +21 -26
  13. jaf/a2a/memory/providers/in_memory.py +89 -83
  14. jaf/a2a/memory/providers/postgres.py +117 -115
  15. jaf/a2a/memory/providers/redis.py +128 -121
  16. jaf/a2a/memory/serialization.py +77 -87
  17. jaf/a2a/memory/tests/run_comprehensive_tests.py +112 -83
  18. jaf/a2a/memory/tests/test_cleanup.py +211 -94
  19. jaf/a2a/memory/tests/test_serialization.py +73 -68
  20. jaf/a2a/memory/tests/test_stress_concurrency.py +186 -133
  21. jaf/a2a/memory/tests/test_task_lifecycle.py +138 -120
  22. jaf/a2a/memory/types.py +91 -53
  23. jaf/a2a/protocol.py +95 -125
  24. jaf/a2a/server.py +90 -118
  25. jaf/a2a/standalone_client.py +30 -43
  26. jaf/a2a/tests/__init__.py +16 -33
  27. jaf/a2a/tests/run_tests.py +17 -53
  28. jaf/a2a/tests/test_agent.py +40 -140
  29. jaf/a2a/tests/test_client.py +54 -117
  30. jaf/a2a/tests/test_integration.py +28 -82
  31. jaf/a2a/tests/test_protocol.py +54 -139
  32. jaf/a2a/tests/test_types.py +50 -136
  33. jaf/a2a/types.py +58 -34
  34. jaf/cli.py +21 -41
  35. jaf/core/__init__.py +7 -1
  36. jaf/core/agent_tool.py +93 -72
  37. jaf/core/analytics.py +257 -207
  38. jaf/core/checkpoint.py +223 -0
  39. jaf/core/composition.py +249 -235
  40. jaf/core/engine.py +817 -519
  41. jaf/core/errors.py +55 -42
  42. jaf/core/guardrails.py +276 -202
  43. jaf/core/handoff.py +47 -31
  44. jaf/core/parallel_agents.py +69 -75
  45. jaf/core/performance.py +75 -73
  46. jaf/core/proxy.py +43 -44
  47. jaf/core/proxy_helpers.py +24 -27
  48. jaf/core/regeneration.py +220 -129
  49. jaf/core/state.py +68 -66
  50. jaf/core/streaming.py +115 -108
  51. jaf/core/tool_results.py +111 -101
  52. jaf/core/tools.py +114 -116
  53. jaf/core/tracing.py +269 -210
  54. jaf/core/types.py +371 -151
  55. jaf/core/workflows.py +209 -168
  56. jaf/exceptions.py +46 -38
  57. jaf/memory/__init__.py +1 -6
  58. jaf/memory/approval_storage.py +54 -77
  59. jaf/memory/factory.py +4 -4
  60. jaf/memory/providers/in_memory.py +216 -180
  61. jaf/memory/providers/postgres.py +216 -146
  62. jaf/memory/providers/redis.py +173 -116
  63. jaf/memory/types.py +70 -51
  64. jaf/memory/utils.py +36 -34
  65. jaf/plugins/__init__.py +12 -12
  66. jaf/plugins/base.py +105 -96
  67. jaf/policies/__init__.py +0 -1
  68. jaf/policies/handoff.py +37 -46
  69. jaf/policies/validation.py +76 -52
  70. jaf/providers/__init__.py +6 -3
  71. jaf/providers/mcp.py +97 -51
  72. jaf/providers/model.py +361 -280
  73. jaf/server/__init__.py +1 -1
  74. jaf/server/main.py +7 -11
  75. jaf/server/server.py +514 -359
  76. jaf/server/types.py +208 -52
  77. jaf/utils/__init__.py +17 -18
  78. jaf/utils/attachments.py +111 -116
  79. jaf/utils/document_processor.py +175 -174
  80. jaf/visualization/__init__.py +1 -1
  81. jaf/visualization/example.py +111 -110
  82. jaf/visualization/functional_core.py +46 -71
  83. jaf/visualization/graphviz.py +154 -189
  84. jaf/visualization/imperative_shell.py +7 -16
  85. jaf/visualization/types.py +8 -4
  86. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/METADATA +2 -2
  87. jaf_py-2.5.11.dist-info/RECORD +97 -0
  88. jaf_py-2.5.9.dist-info/RECORD +0 -96
  89. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/WHEEL +0 -0
  90. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/entry_points.txt +0 -0
  91. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/licenses/LICENSE +0 -0
  92. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/top_level.txt +0 -0
@@ -15,118 +15,97 @@ from ..core.types import Agent, Tool
15
15
  from .types import GraphOptions, GraphResult
16
16
 
17
17
  COLOR_SCHEMES: Final[Dict[str, Dict[str, Any]]] = {
18
- 'default': {
19
- 'agent': {
20
- 'shape': 'box',
21
- 'fillcolor': '#E3F2FD',
22
- 'fontcolor': '#1976D2',
23
- 'style': 'filled,rounded'
18
+ "default": {
19
+ "agent": {
20
+ "shape": "box",
21
+ "fillcolor": "#E3F2FD",
22
+ "fontcolor": "#1976D2",
23
+ "style": "filled,rounded",
24
24
  },
25
- 'tool': {
26
- 'shape': 'ellipse',
27
- 'fillcolor': '#F3E5F5',
28
- 'fontcolor': '#7B1FA2',
29
- 'style': 'filled'
25
+ "tool": {
26
+ "shape": "ellipse",
27
+ "fillcolor": "#F3E5F5",
28
+ "fontcolor": "#7B1FA2",
29
+ "style": "filled",
30
30
  },
31
- 'sub_agent': {
32
- 'shape': 'box',
33
- 'fillcolor': '#E8F5E8',
34
- 'fontcolor': '#388E3C',
35
- 'style': 'filled,dashed'
31
+ "sub_agent": {
32
+ "shape": "box",
33
+ "fillcolor": "#E8F5E8",
34
+ "fontcolor": "#388E3C",
35
+ "style": "filled,dashed",
36
36
  },
37
- 'edge': {
38
- 'color': '#424242',
39
- 'style': 'solid',
40
- 'penwidth': '1.5'
41
- },
42
- 'tool_edge': {
43
- 'color': '#9C27B0',
44
- 'style': 'dashed',
45
- 'penwidth': '1.0'
46
- }
37
+ "edge": {"color": "#424242", "style": "solid", "penwidth": "1.5"},
38
+ "tool_edge": {"color": "#9C27B0", "style": "dashed", "penwidth": "1.0"},
47
39
  },
48
- 'modern': {
49
- 'agent': {
50
- 'shape': 'box',
51
- 'fillcolor': '#667eea',
52
- 'fontcolor': 'white',
53
- 'style': 'filled,rounded',
54
- 'fontname': 'Arial Bold'
40
+ "modern": {
41
+ "agent": {
42
+ "shape": "box",
43
+ "fillcolor": "#667eea",
44
+ "fontcolor": "white",
45
+ "style": "filled,rounded",
46
+ "fontname": "Arial Bold",
55
47
  },
56
- 'tool': {
57
- 'shape': 'ellipse',
58
- 'fillcolor': '#f093fb',
59
- 'fontcolor': 'white',
60
- 'style': 'filled',
61
- 'fontname': 'Arial'
48
+ "tool": {
49
+ "shape": "ellipse",
50
+ "fillcolor": "#f093fb",
51
+ "fontcolor": "white",
52
+ "style": "filled",
53
+ "fontname": "Arial",
62
54
  },
63
- 'sub_agent': {
64
- 'shape': 'box',
65
- 'fillcolor': '#4facfe',
66
- 'fontcolor': 'white',
67
- 'style': 'filled,dashed',
68
- 'fontname': 'Arial'
55
+ "sub_agent": {
56
+ "shape": "box",
57
+ "fillcolor": "#4facfe",
58
+ "fontcolor": "white",
59
+ "style": "filled,dashed",
60
+ "fontname": "Arial",
69
61
  },
70
- 'edge': {
71
- 'color': '#667eea',
72
- 'style': 'solid',
73
- 'penwidth': '2.0',
74
- 'arrowhead': 'vee'
62
+ "edge": {"color": "#667eea", "style": "solid", "penwidth": "2.0", "arrowhead": "vee"},
63
+ "tool_edge": {
64
+ "color": "#f093fb",
65
+ "style": "dashed",
66
+ "penwidth": "1.5",
67
+ "arrowhead": "open",
75
68
  },
76
- 'tool_edge': {
77
- 'color': '#f093fb',
78
- 'style': 'dashed',
79
- 'penwidth': '1.5',
80
- 'arrowhead': 'open'
81
- }
82
69
  },
83
- 'minimal': {
84
- 'agent': {
85
- 'shape': 'box',
86
- 'fillcolor': 'white',
87
- 'fontcolor': 'black',
88
- 'style': 'filled',
89
- 'penwidth': '2'
90
- },
91
- 'tool': {
92
- 'shape': 'ellipse',
93
- 'fillcolor': '#f5f5f5',
94
- 'fontcolor': 'black',
95
- 'style': 'filled'
70
+ "minimal": {
71
+ "agent": {
72
+ "shape": "box",
73
+ "fillcolor": "white",
74
+ "fontcolor": "black",
75
+ "style": "filled",
76
+ "penwidth": "2",
96
77
  },
97
- 'sub_agent': {
98
- 'shape': 'box',
99
- 'fillcolor': 'white',
100
- 'fontcolor': 'gray',
101
- 'style': 'filled,dashed'
78
+ "tool": {
79
+ "shape": "ellipse",
80
+ "fillcolor": "#f5f5f5",
81
+ "fontcolor": "black",
82
+ "style": "filled",
102
83
  },
103
- 'edge': {
104
- 'color': 'black',
105
- 'style': 'solid',
106
- 'penwidth': '1.0'
84
+ "sub_agent": {
85
+ "shape": "box",
86
+ "fillcolor": "white",
87
+ "fontcolor": "gray",
88
+ "style": "filled,dashed",
107
89
  },
108
- 'tool_edge': {
109
- 'color': 'gray',
110
- 'style': 'dashed',
111
- 'penwidth': '1.0'
112
- }
113
- }
90
+ "edge": {"color": "black", "style": "solid", "penwidth": "1.0"},
91
+ "tool_edge": {"color": "gray", "style": "dashed", "penwidth": "1.0"},
92
+ },
114
93
  }
115
94
 
116
95
 
117
96
  # ========== Core Graph Generation Functions ==========
118
97
 
98
+
119
99
  async def generate_agent_graph(
120
- agents: List[Agent],
121
- options: Optional[GraphOptions] = None
100
+ agents: List[Agent], options: Optional[GraphOptions] = None
122
101
  ) -> GraphResult:
123
102
  """
124
103
  Generate a visual graph of agent architecture using functional approach.
125
-
104
+
126
105
  Args:
127
106
  agents: List of Agent objects to visualize
128
107
  options: Graph generation options
129
-
108
+
130
109
  Returns:
131
110
  GraphResult with success status and output information
132
111
  """
@@ -139,10 +118,7 @@ async def generate_agent_graph(
139
118
  # Validate options (pure function)
140
119
  errors = validate_graph_options(opts)
141
120
  if errors:
142
- return GraphResult(
143
- success=False,
144
- error=f"Invalid options: {', '.join(errors)}"
145
- )
121
+ return GraphResult(success=False, error=f"Invalid options: {', '.join(errors)}")
146
122
 
147
123
  # Get color scheme (immutable data)
148
124
  styles = COLOR_SCHEMES[opts.color_scheme]
@@ -151,26 +127,22 @@ async def generate_agent_graph(
151
127
  graph_spec = create_agent_graph_spec(agents, opts, styles)
152
128
 
153
129
  # Render graph using imperative shell
154
- return render_graph_spec(graph_spec, opts, 'AgentGraph')
130
+ return render_graph_spec(graph_spec, opts, "AgentGraph")
155
131
 
156
132
  except Exception as error:
157
- return GraphResult(
158
- success=False,
159
- error=str(error)
160
- )
133
+ return GraphResult(success=False, error=str(error))
161
134
 
162
135
 
163
136
  async def generate_tool_graph(
164
- tools: List[Tool],
165
- options: Optional[GraphOptions] = None
137
+ tools: List[Tool], options: Optional[GraphOptions] = None
166
138
  ) -> GraphResult:
167
139
  """
168
140
  Generate a visual graph of tool ecosystem using functional approach.
169
-
141
+
170
142
  Args:
171
143
  tools: List of Tool objects to visualize
172
144
  options: Graph generation options
173
-
145
+
174
146
  Returns:
175
147
  GraphResult with success status and output information
176
148
  """
@@ -178,18 +150,12 @@ async def generate_tool_graph(
178
150
  from .imperative_shell import render_graph_spec
179
151
 
180
152
  try:
181
- opts = options or GraphOptions(
182
- title="JAF Tool Graph",
183
- layout='circo'
184
- )
153
+ opts = options or GraphOptions(title="JAF Tool Graph", layout="circo")
185
154
 
186
155
  # Validate options (pure function)
187
156
  errors = validate_graph_options(opts)
188
157
  if errors:
189
- return GraphResult(
190
- success=False,
191
- error=f"Invalid options: {', '.join(errors)}"
192
- )
158
+ return GraphResult(success=False, error=f"Invalid options: {', '.join(errors)}")
193
159
 
194
160
  # Get color scheme (immutable data)
195
161
  styles = COLOR_SCHEMES[opts.color_scheme]
@@ -198,70 +164,66 @@ async def generate_tool_graph(
198
164
  graph_spec = create_tool_graph_spec(tools, opts, styles)
199
165
 
200
166
  # Render graph using imperative shell
201
- return render_graph_spec(graph_spec, opts, 'ToolGraph')
167
+ return render_graph_spec(graph_spec, opts, "ToolGraph")
202
168
 
203
169
  except Exception as error:
204
- return GraphResult(
205
- success=False,
206
- error=str(error)
207
- )
170
+ return GraphResult(success=False, error=str(error))
208
171
 
209
172
 
210
173
  async def generate_runner_graph(
211
- agent_registry: Dict[str, Agent],
212
- options: Optional[GraphOptions] = None
174
+ agent_registry: Dict[str, Agent], options: Optional[GraphOptions] = None
213
175
  ) -> GraphResult:
214
176
  """
215
177
  Generate a visual graph of runner architecture.
216
-
178
+
217
179
  Args:
218
180
  agent_registry: Dictionary of agent name to Agent object
219
181
  options: Graph generation options
220
-
182
+
221
183
  Returns:
222
184
  GraphResult with success status and output information
223
185
  """
224
186
  try:
225
- opts = options or GraphOptions(
226
- title="JAF Runner Architecture",
227
- color_scheme='modern'
228
- )
187
+ opts = options or GraphOptions(title="JAF Runner Architecture", color_scheme="modern")
229
188
 
230
189
  # Validate options
231
190
  errors = validate_graph_options(opts)
232
191
  if errors:
233
- return GraphResult(
234
- success=False,
235
- error=f"Invalid options: {', '.join(errors)}"
236
- )
192
+ return GraphResult(success=False, error=f"Invalid options: {', '.join(errors)}")
237
193
 
238
194
  # Create digraph
239
- graph = Digraph('RunnerGraph', comment=opts.title)
240
- graph.attr('graph', compound='true')
195
+ graph = Digraph("RunnerGraph", comment=opts.title)
196
+ graph.attr("graph", compound="true")
241
197
 
242
198
  # Set graph attributes
243
- graph.attr('graph',
244
- rankdir=opts.rankdir,
245
- label=opts.title or '',
246
- labelloc='t',
247
- fontsize='16',
248
- fontname='Arial Bold',
249
- bgcolor='white',
250
- pad='0.5')
199
+ graph.attr(
200
+ "graph",
201
+ rankdir=opts.rankdir,
202
+ label=opts.title or "",
203
+ labelloc="t",
204
+ fontsize="16",
205
+ fontname="Arial Bold",
206
+ bgcolor="white",
207
+ pad="0.5",
208
+ )
251
209
 
252
210
  # Get color scheme
253
211
  styles = COLOR_SCHEMES[opts.color_scheme]
254
212
 
255
213
  # Create clusters for different components
256
- with graph.subgraph(name='cluster_agents') as agent_cluster:
257
- agent_cluster.attr(label='Agents')
258
- agent_cluster.attr(style='filled')
259
- agent_cluster.attr(fillcolor='#f8f9fa')
214
+ with graph.subgraph(name="cluster_agents") as agent_cluster:
215
+ agent_cluster.attr(label="Agents")
216
+ agent_cluster.attr(style="filled")
217
+ agent_cluster.attr(fillcolor="#f8f9fa")
260
218
 
261
219
  # Add agents to cluster
262
220
  for agent_name, agent in agent_registry.items():
263
221
  # Create agent label
264
- model_name = getattr(agent.model_config, 'name', 'default') if agent.model_config else 'default'
222
+ model_name = (
223
+ getattr(agent.model_config, "name", "default")
224
+ if agent.model_config
225
+ else "default"
226
+ )
265
227
  label = f"{agent.name}\\n({model_name})"
266
228
 
267
229
  if opts.show_tool_details and agent.tools:
@@ -274,68 +236,65 @@ async def generate_runner_graph(
274
236
  agent_cluster.node(
275
237
  agent.name,
276
238
  label=label,
277
- shape=styles['agent']['shape'],
278
- fillcolor=styles['agent']['fillcolor'],
279
- fontcolor=styles['agent']['fontcolor'],
280
- style=styles['agent']['style']
239
+ shape=styles["agent"]["shape"],
240
+ fillcolor=styles["agent"]["fillcolor"],
241
+ fontcolor=styles["agent"]["fontcolor"],
242
+ style=styles["agent"]["style"],
281
243
  )
282
244
 
283
- with graph.subgraph(name='cluster_session') as session_cluster:
284
- session_cluster.attr(label='Session Layer')
285
- session_cluster.attr(style='filled')
286
- session_cluster.attr(fillcolor='#fff3cd')
245
+ with graph.subgraph(name="cluster_session") as session_cluster:
246
+ session_cluster.attr(label="Session Layer")
247
+ session_cluster.attr(style="filled")
248
+ session_cluster.attr(fillcolor="#fff3cd")
287
249
 
288
250
  # Add session provider
289
251
  session_cluster.node(
290
- 'session_provider',
291
- label='Session\\nProvider',
292
- shape='box',
293
- fillcolor='#ffc107',
294
- fontcolor='black',
295
- style='filled,rounded'
252
+ "session_provider",
253
+ label="Session\\nProvider",
254
+ shape="box",
255
+ fillcolor="#ffc107",
256
+ fontcolor="black",
257
+ style="filled,rounded",
296
258
  )
297
259
 
298
260
  # Add runner node
299
261
  graph.node(
300
- 'runner',
301
- label='Runner',
302
- shape='diamond',
303
- fillcolor='#28a745',
304
- fontcolor='white',
305
- style='filled',
306
- fontsize='14',
307
- fontname='Arial Bold'
262
+ "runner",
263
+ label="Runner",
264
+ shape="diamond",
265
+ fillcolor="#28a745",
266
+ fontcolor="white",
267
+ style="filled",
268
+ fontsize="14",
269
+ fontname="Arial Bold",
308
270
  )
309
271
 
310
272
  # Add edges
311
273
  for agent_name in agent_registry:
312
- graph.edge('runner', agent_name, color=styles['edge']['color'],
313
- style=styles['edge']['style'], label='executes')
274
+ graph.edge(
275
+ "runner",
276
+ agent_name,
277
+ color=styles["edge"]["color"],
278
+ style=styles["edge"]["style"],
279
+ label="executes",
280
+ )
314
281
 
315
- graph.edge('runner', 'session_provider', color='#ffc107',
316
- style='dashed', label='manages')
282
+ graph.edge("runner", "session_provider", color="#ffc107", style="dashed", label="manages")
317
283
 
318
284
  # Generate output
319
285
  output_path = opts.output_path or f"./runner-graph.{opts.output_format}"
320
286
 
321
287
  # Render the graph
322
288
  graph.render(
323
- filename=output_path.replace(f'.{opts.output_format}', ''),
289
+ filename=output_path.replace(f".{opts.output_format}", ""),
324
290
  format=opts.output_format,
325
- cleanup=True
291
+ cleanup=True,
326
292
  )
327
293
 
328
- return GraphResult(
329
- success=True,
330
- output_path=output_path,
331
- graph_dot=graph.source
332
- )
294
+ return GraphResult(success=True, output_path=output_path, graph_dot=graph.source)
333
295
 
334
296
  except Exception as error:
335
- return GraphResult(
336
- success=False,
337
- error=str(error)
338
- )
297
+ return GraphResult(success=False, error=str(error))
339
298
 
340
299
 
341
300
  # ========== Helper Functions (Deprecated - moved to functional_core) ==========
@@ -344,47 +303,53 @@ async def generate_runner_graph(
344
303
 
345
304
  # ========== Validation Functions ==========
346
305
 
306
+
347
307
  def validate_graph_options(options: GraphOptions) -> List[str]:
348
308
  """
349
309
  Validate graph options and return list of errors.
350
-
310
+
351
311
  Args:
352
312
  options: GraphOptions to validate
353
-
313
+
354
314
  Returns:
355
315
  List of validation error messages
356
316
  """
357
317
  errors = []
358
318
 
359
- valid_layouts = ['dot', 'neato', 'fdp', 'circo', 'twopi']
319
+ valid_layouts = ["dot", "neato", "fdp", "circo", "twopi"]
360
320
  if options.layout not in valid_layouts:
361
321
  errors.append(f"Invalid layout '{options.layout}'. Must be one of: {valid_layouts}")
362
322
 
363
- valid_rankdirs = ['TB', 'LR', 'BT', 'RL']
323
+ valid_rankdirs = ["TB", "LR", "BT", "RL"]
364
324
  if options.rankdir not in valid_rankdirs:
365
325
  errors.append(f"Invalid rankdir '{options.rankdir}'. Must be one of: {valid_rankdirs}")
366
326
 
367
- valid_formats = ['png', 'svg', 'pdf']
327
+ valid_formats = ["png", "svg", "pdf"]
368
328
  if options.output_format not in valid_formats:
369
- errors.append(f"Invalid output_format '{options.output_format}'. Must be one of: {valid_formats}")
329
+ errors.append(
330
+ f"Invalid output_format '{options.output_format}'. Must be one of: {valid_formats}"
331
+ )
370
332
 
371
- valid_schemes = ['default', 'modern', 'minimal']
333
+ valid_schemes = ["default", "modern", "minimal"]
372
334
  if options.color_scheme not in valid_schemes:
373
- errors.append(f"Invalid color_scheme '{options.color_scheme}'. Must be one of: {valid_schemes}")
335
+ errors.append(
336
+ f"Invalid color_scheme '{options.color_scheme}'. Must be one of: {valid_schemes}"
337
+ )
374
338
 
375
339
  return errors
376
340
 
377
341
 
378
342
  # ========== Utility Functions ==========
379
343
 
344
+
380
345
  def get_graph_dot(agents: List[Agent], options: Optional[GraphOptions] = None) -> str:
381
346
  """
382
347
  Get the DOT language representation of an agent graph using functional approach.
383
-
348
+
384
349
  Args:
385
350
  agents: List of Agent objects
386
351
  options: Graph generation options
387
-
352
+
388
353
  Returns:
389
354
  DOT language string representation
390
355
  """
@@ -400,4 +365,4 @@ def get_graph_dot(agents: List[Agent], options: Optional[GraphOptions] = None) -
400
365
  graph_spec = create_agent_graph_spec(agents, opts, styles)
401
366
 
402
367
  # Convert to DOT using imperative shell (no file system side effects)
403
- return graph_spec_to_dot(graph_spec, 'AgentGraph')
368
+ return graph_spec_to_dot(graph_spec, "AgentGraph")
@@ -22,7 +22,7 @@ def apply_graph_spec_to_digraph(spec: GraphSpec, digraph: Digraph) -> Digraph:
22
22
  This is the imperative shell that contains all side effects.
23
23
  """
24
24
  # Apply graph attributes
25
- digraph.attr('graph', **spec.graph_attributes)
25
+ digraph.attr("graph", **spec.graph_attributes)
26
26
 
27
27
  # Add all nodes
28
28
  for node in spec.nodes:
@@ -36,9 +36,7 @@ def apply_graph_spec_to_digraph(spec: GraphSpec, digraph: Digraph) -> Digraph:
36
36
 
37
37
 
38
38
  def render_graph_spec(
39
- spec: GraphSpec,
40
- options: GraphOptions,
41
- graph_name: str = 'Graph'
39
+ spec: GraphSpec, options: GraphOptions, graph_name: str = "Graph"
42
40
  ) -> GraphResult:
43
41
  """
44
42
  Render a GraphSpec to an actual file using Graphviz.
@@ -56,25 +54,18 @@ def render_graph_spec(
56
54
 
57
55
  # Render (side effect)
58
56
  graph.render(
59
- filename=output_path.replace(f'.{options.output_format}', ''),
57
+ filename=output_path.replace(f".{options.output_format}", ""),
60
58
  format=options.output_format,
61
- cleanup=True
59
+ cleanup=True,
62
60
  )
63
61
 
64
- return GraphResult(
65
- success=True,
66
- output_path=output_path,
67
- graph_dot=graph.source
68
- )
62
+ return GraphResult(success=True, output_path=output_path, graph_dot=graph.source)
69
63
 
70
64
  except Exception as error:
71
- return GraphResult(
72
- success=False,
73
- error=str(error)
74
- )
65
+ return GraphResult(success=False, error=str(error))
75
66
 
76
67
 
77
- def graph_spec_to_dot(spec: GraphSpec, graph_name: str = 'Graph') -> str:
68
+ def graph_spec_to_dot(spec: GraphSpec, graph_name: str = "Graph") -> str:
78
69
  """
79
70
  Convert a GraphSpec to DOT language string without file system side effects.
80
71
  This is a pure operation that only creates the DOT representation.
@@ -13,6 +13,7 @@ from pydantic import BaseModel
13
13
  @dataclass(frozen=True)
14
14
  class NodeStyle:
15
15
  """Styling configuration for graph nodes."""
16
+
16
17
  shape: str
17
18
  fillcolor: str
18
19
  fontcolor: str
@@ -24,6 +25,7 @@ class NodeStyle:
24
25
  @dataclass(frozen=True)
25
26
  class EdgeStyle:
26
27
  """Styling configuration for graph edges."""
28
+
27
29
  color: str
28
30
  style: str
29
31
  penwidth: Optional[str] = None
@@ -32,19 +34,21 @@ class EdgeStyle:
32
34
 
33
35
  class GraphOptions(BaseModel):
34
36
  """Configuration options for graph generation."""
37
+
35
38
  title: Optional[str] = "JAF Graph"
36
- layout: Literal['dot', 'neato', 'fdp', 'circo', 'twopi'] = 'dot'
37
- rankdir: Literal['TB', 'LR', 'BT', 'RL'] = 'TB'
38
- output_format: Literal['png', 'svg', 'pdf'] = 'png'
39
+ layout: Literal["dot", "neato", "fdp", "circo", "twopi"] = "dot"
40
+ rankdir: Literal["TB", "LR", "BT", "RL"] = "TB"
41
+ output_format: Literal["png", "svg", "pdf"] = "png"
39
42
  output_path: Optional[str] = None
40
43
  show_tool_details: bool = True
41
44
  show_sub_agents: bool = True
42
- color_scheme: Literal['default', 'modern', 'minimal'] = 'default'
45
+ color_scheme: Literal["default", "modern", "minimal"] = "default"
43
46
 
44
47
 
45
48
  @dataclass(frozen=True)
46
49
  class GraphResult:
47
50
  """Result of graph generation operation."""
51
+
48
52
  success: bool
49
53
  output_path: Optional[str] = None
50
54
  error: Optional[str] = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaf-py
3
- Version: 2.5.9
3
+ Version: 2.5.11
4
4
  Summary: A purely functional agent framework with immutable state and composable tools - Python implementation
5
5
  Author: JAF Contributors
6
6
  Maintainer: JAF Contributors
@@ -82,7 +82,7 @@ Dynamic: license-file
82
82
 
83
83
  <!-- ![JAF Banner](docs/cover.png) -->
84
84
 
85
- [![Version](https://img.shields.io/badge/version-2.5.9-blue.svg)](https://github.com/xynehq/jaf-py)
85
+ [![Version](https://img.shields.io/badge/version-2.5.11-blue.svg)](https://github.com/xynehq/jaf-py)
86
86
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
87
87
  [![Docs](https://img.shields.io/badge/Docs-Live-brightgreen)](https://xynehq.github.io/jaf-py/)
88
88