jdesiree commited on
Commit
d4d436a
·
verified ·
1 Parent(s): 5e4b8e6

Redesigned for LangGraph Agentic workflow

Browse files
Files changed (1) hide show
  1. graph_tool.py +78 -117
graph_tool.py CHANGED
@@ -1,146 +1,107 @@
1
  import base64
2
- from io import BytesIO
3
- import matplotlib.pyplot as plt
4
- import numpy as np
5
  import json
 
 
 
 
6
 
7
- def generate_plot(input: str, return_html: bool = True):
 
 
 
 
 
 
 
 
 
8
  """
9
- Generates a plot (bar, line, or pie) and returns it as an HTML-formatted Base64-encoded image string,
10
- or just the Base64 string if return_html=False.
11
-
12
  Args:
13
- input (str): JSON string containing all plot configuration:
14
- {
15
- "data": {"key1": value1, "key2": value2, ...},
16
- "labels": ["label1", "label2", ...],
17
- "plot_type": "bar|line|pie",
18
- "title": "Plot Title",
19
- "x_label": "X Axis Label" (optional),
20
- "y_label": "Y Axis Label" (optional)
21
- }
22
- return_html (bool): Whether to return full HTML <img> tag (default True).
23
-
24
  Returns:
25
- str: Either an HTML <img> tag or a raw Base64-encoded image string.
 
 
26
  """
27
- try:
28
- # Parse the main JSON configuration
29
- config = json.loads(input)
30
- except json.JSONDecodeError as e:
31
- return f'<p style="color:red;">Error parsing JSON configuration: {e}</p>'
32
 
33
- # Extract parameters with defaults
34
  try:
35
- data = config.get("data", {})
36
- labels = config.get("labels", [])
37
- plot_type = config.get("plot_type", "bar")
38
- title = config.get("title", "Untitled Plot")
39
- x_label = config.get("x_label", "")
40
- y_label = config.get("y_label", "")
41
- except Exception as e:
42
- return f'<p style="color:red;">Error extracting configuration parameters: {e}</p>'
43
-
44
- # Validate inputs
45
- if not isinstance(data, dict):
46
- return '<p style="color:red;">Data must be a dictionary with keys as labels and values as numbers.</p>'
47
-
48
- if not data:
49
- return '<p style="color:red;">Data dictionary is empty.</p>'
50
-
51
- if not isinstance(labels, list):
52
- return '<p style="color:red;">Labels must be a list.</p>'
53
 
 
54
  try:
55
  fig, ax = plt.subplots(figsize=(10, 6))
56
-
57
- # Extract keys and values from the data dictionary
58
- x_data = list(data.keys())
59
- y_data = list(data.values())
60
-
61
- # Ensure y_data contains numeric values
62
- try:
63
- y_data = [float(val) for val in y_data]
64
- except (ValueError, TypeError):
65
- return '<p style="color:red;">All data values must be numeric.</p>'
66
-
67
- # Replace x-axis labels if provided and length matches
68
- if labels and len(labels) == len(x_data):
69
- x_data = labels
70
-
71
  if plot_type == 'bar':
72
- bars = ax.bar(x_data, y_data)
 
 
73
  ax.set_xlabel(x_label)
74
  ax.set_ylabel(y_label)
75
  ax.set_ylim(bottom=0)
76
-
77
- # Add value labels on top of bars
78
  for bar, value in zip(bars, y_data):
79
  height = bar.get_height()
80
- ax.text(bar.get_x() + bar.get_width()/2., height,
81
- f'{value}', ha='center', va='bottom')
82
-
83
  elif plot_type == 'line':
84
- ax.plot(x_data, y_data, marker='o', linewidth=2, markersize=6)
 
85
  ax.set_xlabel(x_label)
86
  ax.set_ylabel(y_label)
87
  ax.set_ylim(bottom=0)
88
  ax.grid(True, alpha=0.3)
89
-
90
  elif plot_type == 'pie':
91
- # For pie charts, use labels parameter if provided, otherwise use data keys
92
- pie_labels = labels if len(labels) == len(y_data) else list(data.keys())
93
- wedges, texts, autotexts = ax.pie(
94
- y_data,
95
- labels=pie_labels,
96
- autopct='%1.1f%%',
97
- startangle=90,
98
- textprops={'fontsize': 10}
99
- )
100
- ax.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle
101
- # Don't set x/y labels for pie charts
102
-
103
  else:
104
- return f'<p style="color:red;">Invalid plot_type: {plot_type}. Choose "bar", "line", or "pie".</p>'
105
-
106
- ax.set_title(title, fontsize=14, fontweight='bold', pad=20)
107
-
108
- # Improve layout
109
  plt.tight_layout()
 
 
 
 
 
 
110
 
111
- # Save plot to a BytesIO buffer in memory
112
- buf = BytesIO()
113
- plt.savefig(
114
- buf,
115
- format='png',
116
- bbox_inches='tight',
117
- dpi=150,
118
- facecolor='white',
119
- edgecolor='none'
120
- )
121
- plt.close(fig) # Close the plot to free up memory
122
-
123
- # Encode the image data to a Base64 string
124
  img_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
125
-
126
- if return_html:
127
- # More descriptive alt text for accessibility
128
- alt_text = title
129
- if plot_type == "pie":
130
- alt_text += " - " + ", ".join(f"{lbl}: {val}" for lbl, val in zip(pie_labels, y_data))
131
- else:
132
- alt_text += " - " + ", ".join(f"{lbl}: {val}" for lbl, val in zip(x_data, y_data))
133
-
134
- return f'''
135
- <div style="text-align: center; margin: 20px 0;">
136
- <img src="data:image/png;base64,{img_base64}"
137
- style="max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);"
138
- alt="{alt_text}" />
139
- </div>
140
- '''
141
- else:
142
- return img_base64
143
-
144
  except Exception as e:
145
- plt.close('all') # Clean up any open figures
146
- return f'<p style="color:red;">Error generating plot: {str(e)}</p>'
 
 
 
1
  import base64
2
+ import io
 
 
3
  import json
4
+ from typing import Dict, List, Literal, Tuple
5
+
6
+ import matplotlib.pyplot as plt
7
+ from langchain_core.tools import tool
8
 
9
+ # Use the @tool decorator and specify the "content_and_artifact" response format.
10
+ @tool(response_format="content_and_artifact")
11
+ def generate_plot(
12
+ data: Dict[str, float],
13
+ plot_type: Literal["bar", "line", "pie"],
14
+ title: str = "Generated Plot",
15
+ labels: List[str] = None,
16
+ x_label: str = "",
17
+ y_label: str = ""
18
+ ) -> Tuple:
19
  """
20
+ Generates a plot (bar, line, or pie) from a dictionary of data and returns it
21
+ as a base64 encoded PNG image artifact.
22
+
23
  Args:
24
+ data (Dict[str, float]): A dictionary where keys are labels and values are the numeric data to plot.
25
+ plot_type (Literal["bar", "line", "pie"]): The type of plot to generate.
26
+ title (str): The title for the plot.
27
+ labels (List[str]): Optional list of labels to use for the x-axis or pie slices. If not provided, data keys are used.
28
+ x_label (str): The label for the x-axis (for bar and line charts).
29
+ y_label (str): The label for the y-axis (for bar and line charts).
30
+
 
 
 
 
31
  Returns:
32
+ A tuple containing:
33
+ - A string message confirming the plot was generated.
34
+ - A dictionary artifact with the base64 encoded image string and its format.
35
  """
36
+ # --- Input Validation ---
37
+ if not isinstance(data, dict) or not data:
38
+ content = "Error: Data must be a non-empty dictionary."
39
+ artifact = {"error": content}
40
+ return content, artifact
41
 
 
42
  try:
43
+ y_data = [float(val) for val in data.values()]
44
+ except (ValueError, TypeError):
45
+ content = "Error: All data values must be numeric."
46
+ artifact = {"error": content}
47
+ return content, artifact
48
+
49
+ x_data = list(data.keys())
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ # --- Plot Generation ---
52
  try:
53
  fig, ax = plt.subplots(figsize=(10, 6))
54
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  if plot_type == 'bar':
56
+ # Use provided labels if they match the data length, otherwise use data keys
57
+ bar_labels = labels if labels and len(labels) == len(x_data) else x_data
58
+ bars = ax.bar(bar_labels, y_data)
59
  ax.set_xlabel(x_label)
60
  ax.set_ylabel(y_label)
61
  ax.set_ylim(bottom=0)
 
 
62
  for bar, value in zip(bars, y_data):
63
  height = bar.get_height()
64
+ ax.text(bar.get_x() + bar.get_width()/2., height, f'{value}', ha='center', va='bottom')
65
+
 
66
  elif plot_type == 'line':
67
+ line_labels = labels if labels and len(labels) == len(x_data) else x_data
68
+ ax.plot(line_labels, y_data, marker='o')
69
  ax.set_xlabel(x_label)
70
  ax.set_ylabel(y_label)
71
  ax.set_ylim(bottom=0)
72
  ax.grid(True, alpha=0.3)
73
+
74
  elif plot_type == 'pie':
75
+ pie_labels = labels if labels and len(labels) == len(y_data) else list(data.keys())
76
+ ax.pie(y_data, labels=pie_labels, autopct='%1.1f%%', startangle=90)
77
+ ax.axis('equal')
78
+
 
 
 
 
 
 
 
 
79
  else:
80
+ content = f"Error: Invalid plot_type '{plot_type}'. Choose 'bar', 'line', or 'pie'."
81
+ artifact = {"error": content}
82
+ return content, artifact
83
+
84
+ ax.set_title(title, fontsize=14, fontweight='bold')
85
  plt.tight_layout()
86
+
87
+ # --- In-Memory Image Conversion ---
88
+ buf = io.BytesIO()
89
+ plt.savefig(buf, format='png', dpi=150)
90
+ plt.close(fig)
91
+ buf.seek(0)
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  img_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
94
+
95
+ # --- Return Content and Artifact ---
96
+ content = f"Successfully generated a {plot_type} plot titled '{title}'."
97
+ artifact = {
98
+ "base64_image": img_base64,
99
+ "format": "png"
100
+ }
101
+ return content, artifact
102
+
 
 
 
 
 
 
 
 
 
 
103
  except Exception as e:
104
+ plt.close('all')
105
+ content = f"An unexpected error occurred while generating the plot: {str(e)}"
106
+ artifact = {"error": str(e)}
107
+ return content, artifact