hammad-python 0.0.24__py3-none-any.whl → 0.0.26__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.
- hammad/__init__.py +49 -268
- hammad/_main.py +226 -0
- hammad/cli/__init__.py +0 -2
- hammad/data/__init__.py +4 -5
- hammad/data/types/__init__.py +37 -1
- hammad/data/types/file.py +74 -1
- hammad/data/types/multimodal/__init__.py +14 -2
- hammad/data/types/multimodal/audio.py +106 -2
- hammad/data/types/multimodal/image.py +104 -2
- hammad/data/types/text.py +242 -0
- hammad/genai/__init__.py +22 -0
- hammad/genai/a2a/__init__.py +32 -0
- hammad/genai/a2a/workers.py +552 -0
- hammad/genai/agents/__init__.py +2 -0
- hammad/genai/agents/agent.py +115 -9
- hammad/genai/agents/run.py +379 -8
- hammad/genai/graphs/__init__.py +13 -1
- hammad/genai/graphs/_utils.py +190 -0
- hammad/genai/graphs/base.py +850 -125
- hammad/genai/graphs/types.py +2 -2
- hammad/genai/models/language/__init__.py +6 -1
- hammad/genai/models/language/run.py +308 -0
- hammad/genai/models/language/types/language_model_response.py +2 -0
- hammad/logging/logger.py +53 -8
- hammad/mcp/__init__.py +3 -0
- hammad/types.py +288 -0
- {hammad_python-0.0.24.dist-info → hammad_python-0.0.26.dist-info}/METADATA +2 -1
- {hammad_python-0.0.24.dist-info → hammad_python-0.0.26.dist-info}/RECORD +30 -26
- hammad/cli/_runner.py +0 -265
- {hammad_python-0.0.24.dist-info → hammad_python-0.0.26.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.24.dist-info → hammad_python-0.0.26.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,190 @@
|
|
1
|
+
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
if TYPE_CHECKING:
|
5
|
+
from .base import BaseGraph
|
6
|
+
|
7
|
+
|
8
|
+
def visualize_base_graph(graph: "BaseGraph", filename: str) -> None:
|
9
|
+
"""Generate a visualization of the graph with clean, readable flow."""
|
10
|
+
if not graph._action_nodes or not graph._start_action_name:
|
11
|
+
raise ValueError("No actions defined in graph")
|
12
|
+
|
13
|
+
# Build our own mermaid code for better control over layout
|
14
|
+
mermaid_lines = ["graph TD"] # Top-Down layout
|
15
|
+
|
16
|
+
# Track which nodes we've already added
|
17
|
+
added_nodes = set()
|
18
|
+
|
19
|
+
# Style definitions
|
20
|
+
mermaid_lines.append(" %% Styles")
|
21
|
+
mermaid_lines.append(" classDef startNode fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff")
|
22
|
+
mermaid_lines.append(" classDef endNode fill:#f44336,stroke:#333,stroke-width:2px,color:#fff")
|
23
|
+
mermaid_lines.append(" classDef defaultNode fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff")
|
24
|
+
mermaid_lines.append("")
|
25
|
+
|
26
|
+
# Helper to get clean node ID
|
27
|
+
def get_node_id(action_name: str) -> str:
|
28
|
+
return action_name.replace(" ", "_").replace("-", "_")
|
29
|
+
|
30
|
+
# Helper to add a node if not already added
|
31
|
+
def add_node(action_name: str, is_start: bool = False, is_end: bool = False) -> None:
|
32
|
+
if action_name not in added_nodes:
|
33
|
+
node_id = get_node_id(action_name)
|
34
|
+
# Use the action name as the display label
|
35
|
+
display_name = action_name
|
36
|
+
|
37
|
+
if is_start:
|
38
|
+
mermaid_lines.append(f" {node_id}[{display_name}]:::startNode")
|
39
|
+
elif is_end:
|
40
|
+
mermaid_lines.append(f" {node_id}[{display_name}]:::endNode")
|
41
|
+
else:
|
42
|
+
mermaid_lines.append(f" {node_id}[{display_name}]:::defaultNode")
|
43
|
+
added_nodes.add(action_name)
|
44
|
+
|
45
|
+
# Add all nodes and connections
|
46
|
+
mermaid_lines.append(" %% Nodes and connections")
|
47
|
+
|
48
|
+
# Start with the start node
|
49
|
+
add_node(graph._start_action_name, is_start=True)
|
50
|
+
|
51
|
+
# Process all actions to find connections
|
52
|
+
for action_name in graph._action_nodes:
|
53
|
+
action_func = getattr(graph, action_name, None)
|
54
|
+
if action_func and hasattr(action_func, '_action_settings'):
|
55
|
+
settings = action_func._action_settings
|
56
|
+
|
57
|
+
# Add the node
|
58
|
+
add_node(action_name, is_end=settings.terminates)
|
59
|
+
|
60
|
+
# Add connections based on 'next' settings
|
61
|
+
if settings.next:
|
62
|
+
source_id = get_node_id(action_name)
|
63
|
+
|
64
|
+
if isinstance(settings.next, str):
|
65
|
+
# Simple string case
|
66
|
+
target_id = get_node_id(settings.next)
|
67
|
+
add_node(settings.next)
|
68
|
+
mermaid_lines.append(f" {source_id} --> {target_id}")
|
69
|
+
|
70
|
+
elif isinstance(settings.next, list):
|
71
|
+
# List case - branches to multiple nodes
|
72
|
+
for next_action in settings.next:
|
73
|
+
if isinstance(next_action, str):
|
74
|
+
target_id = get_node_id(next_action)
|
75
|
+
add_node(next_action)
|
76
|
+
mermaid_lines.append(f" {source_id} --> {target_id}")
|
77
|
+
|
78
|
+
elif hasattr(settings.next, '__class__') and settings.next.__class__.__name__ == 'SelectionStrategy':
|
79
|
+
# SelectionStrategy case
|
80
|
+
if settings.next.actions:
|
81
|
+
# Show all possible paths with a decision diamond
|
82
|
+
decision_id = f"{source_id}_decision"
|
83
|
+
mermaid_lines.append(f" {source_id} --> {decision_id}{{LLM Selection}}")
|
84
|
+
|
85
|
+
for next_action in settings.next.actions:
|
86
|
+
target_id = get_node_id(next_action)
|
87
|
+
add_node(next_action)
|
88
|
+
mermaid_lines.append(f" {decision_id} --> {target_id}")
|
89
|
+
else:
|
90
|
+
# If no specific actions, it can go to any node
|
91
|
+
# For visualization, show connections to all non-start nodes
|
92
|
+
decision_id = f"{source_id}_decision"
|
93
|
+
mermaid_lines.append(f" {source_id} --> {decision_id}{{LLM Selection}}")
|
94
|
+
|
95
|
+
for other_action in graph._action_nodes:
|
96
|
+
if other_action != action_name and other_action != graph._start_action_name:
|
97
|
+
target_id = get_node_id(other_action)
|
98
|
+
add_node(other_action)
|
99
|
+
mermaid_lines.append(f" {decision_id} -.-> {target_id}")
|
100
|
+
|
101
|
+
# If start node has no explicit next, but there are other nodes, show possible connections
|
102
|
+
start_func = getattr(graph, graph._start_action_name, None)
|
103
|
+
if start_func and hasattr(start_func, '_action_settings'):
|
104
|
+
if not start_func._action_settings.next and len(graph._action_nodes) > 1:
|
105
|
+
source_id = get_node_id(graph._start_action_name)
|
106
|
+
# Find end nodes (terminates=True) to connect to
|
107
|
+
for action_name in graph._action_nodes:
|
108
|
+
if action_name != graph._start_action_name:
|
109
|
+
action_func = getattr(graph, action_name, None)
|
110
|
+
if action_func and hasattr(action_func, '_action_settings'):
|
111
|
+
if action_func._action_settings.terminates:
|
112
|
+
target_id = get_node_id(action_name)
|
113
|
+
add_node(action_name, is_end=True)
|
114
|
+
mermaid_lines.append(f" {source_id} --> {target_id}")
|
115
|
+
|
116
|
+
# Join all lines
|
117
|
+
mermaid_code = "\n".join(mermaid_lines)
|
118
|
+
|
119
|
+
# Render the mermaid diagram and save it
|
120
|
+
try:
|
121
|
+
import subprocess
|
122
|
+
import tempfile
|
123
|
+
import os
|
124
|
+
import shutil
|
125
|
+
|
126
|
+
# Check if mmdc (mermaid CLI) is available
|
127
|
+
if shutil.which('mmdc') is None:
|
128
|
+
raise FileNotFoundError("mermaid-cli (mmdc) not found. Install with: npm install -g @mermaid-js/mermaid-cli")
|
129
|
+
|
130
|
+
# Create a temporary mermaid file
|
131
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.mmd', delete=False) as temp_file:
|
132
|
+
temp_file.write(mermaid_code)
|
133
|
+
temp_mmd_path = temp_file.name
|
134
|
+
|
135
|
+
try:
|
136
|
+
# Determine output format from filename extension
|
137
|
+
output_format = 'png' # default
|
138
|
+
if filename.lower().endswith('.svg'):
|
139
|
+
output_format = 'svg'
|
140
|
+
elif filename.lower().endswith('.pdf'):
|
141
|
+
output_format = 'pdf'
|
142
|
+
|
143
|
+
# Use mermaid CLI to render the diagram
|
144
|
+
cmd = ['mmdc', '-i', temp_mmd_path, '-o', filename]
|
145
|
+
|
146
|
+
# Add format flag only if not PNG (PNG is default)
|
147
|
+
if output_format != 'png':
|
148
|
+
cmd.extend(['-f', output_format])
|
149
|
+
|
150
|
+
# Add theme and background color
|
151
|
+
cmd.extend(['-t', 'default', '-b', 'transparent'])
|
152
|
+
|
153
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
154
|
+
|
155
|
+
if result.returncode == 0:
|
156
|
+
print(f"Graph visualization saved to: {filename}")
|
157
|
+
else:
|
158
|
+
raise subprocess.CalledProcessError(result.returncode, result.args, result.stderr)
|
159
|
+
|
160
|
+
finally:
|
161
|
+
# Clean up temporary file
|
162
|
+
if os.path.exists(temp_mmd_path):
|
163
|
+
os.unlink(temp_mmd_path)
|
164
|
+
|
165
|
+
except FileNotFoundError as e:
|
166
|
+
# Provide helpful error message for missing mermaid CLI
|
167
|
+
print(f"Warning: {e}")
|
168
|
+
# Save as .mmd file instead
|
169
|
+
mmd_filename = filename.rsplit('.', 1)[0] + '.mmd'
|
170
|
+
with open(mmd_filename, "w") as f:
|
171
|
+
f.write(mermaid_code)
|
172
|
+
print(f"Mermaid code saved to: {mmd_filename}")
|
173
|
+
print("To render as PNG, install mermaid-cli: npm install -g @mermaid-js/mermaid-cli")
|
174
|
+
|
175
|
+
except subprocess.CalledProcessError as e:
|
176
|
+
# Handle mermaid CLI errors
|
177
|
+
print(f"Error rendering mermaid diagram: {e.stderr if e.stderr else str(e)}")
|
178
|
+
# Save as .mmd file as fallback
|
179
|
+
mmd_filename = filename.rsplit('.', 1)[0] + '.mmd'
|
180
|
+
with open(mmd_filename, "w") as f:
|
181
|
+
f.write(mermaid_code)
|
182
|
+
print(f"Mermaid code saved to: {mmd_filename} (rendering failed)")
|
183
|
+
|
184
|
+
except Exception as e:
|
185
|
+
# General fallback: save the mermaid code
|
186
|
+
print(f"Unexpected error: {e}")
|
187
|
+
mmd_filename = filename.rsplit('.', 1)[0] + '.mmd'
|
188
|
+
with open(mmd_filename, "w") as f:
|
189
|
+
f.write(mermaid_code)
|
190
|
+
print(f"Mermaid code saved to: {mmd_filename}")
|