plot-agent 0.2.1__tar.gz → 0.2.2__tar.gz
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.
- {plot_agent-0.2.1 → plot_agent-0.2.2}/PKG-INFO +1 -1
- {plot_agent-0.2.1 → plot_agent-0.2.2}/plot_agent/agent.py +30 -15
- {plot_agent-0.2.1 → plot_agent-0.2.2}/plot_agent/execution.py +12 -1
- {plot_agent-0.2.1 → plot_agent-0.2.2}/plot_agent.egg-info/PKG-INFO +1 -1
- {plot_agent-0.2.1 → plot_agent-0.2.2}/plot_agent.egg-info/SOURCES.txt +1 -1
- {plot_agent-0.2.1 → plot_agent-0.2.2}/pyproject.toml +1 -1
- plot_agent-0.2.2/tests/test_plot_agent.py +471 -0
- plot_agent-0.2.1/tests/test_plotly_agent.py +0 -152
- {plot_agent-0.2.1 → plot_agent-0.2.2}/LICENSE +0 -0
- {plot_agent-0.2.1 → plot_agent-0.2.2}/README.md +0 -0
- {plot_agent-0.2.1 → plot_agent-0.2.2}/plot_agent/__init__.py +0 -0
- {plot_agent-0.2.1 → plot_agent-0.2.2}/plot_agent/models.py +0 -0
- {plot_agent-0.2.1 → plot_agent-0.2.2}/plot_agent/prompt.py +0 -0
- {plot_agent-0.2.1 → plot_agent-0.2.2}/plot_agent.egg-info/dependency_links.txt +0 -0
- {plot_agent-0.2.1 → plot_agent-0.2.2}/plot_agent.egg-info/top_level.txt +0 -0
- {plot_agent-0.2.1 → plot_agent-0.2.2}/setup.cfg +0 -0
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
|
-
import numpy as np
|
|
3
|
-
import matplotlib.pyplot as plt
|
|
4
|
-
import plotly.express as px
|
|
5
|
-
import plotly.graph_objects as go
|
|
6
|
-
from plotly.subplots import make_subplots
|
|
7
2
|
from io import StringIO
|
|
8
|
-
import
|
|
9
|
-
import sys
|
|
10
|
-
from typing import Dict, Optional, Any
|
|
3
|
+
from typing import Optional
|
|
11
4
|
|
|
12
5
|
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
13
6
|
from langchain_core.messages import AIMessage, HumanMessage
|
|
@@ -75,6 +68,14 @@ class PlotlyAgent:
|
|
|
75
68
|
Returns:
|
|
76
69
|
None
|
|
77
70
|
"""
|
|
71
|
+
|
|
72
|
+
# Check df
|
|
73
|
+
assert isinstance(df, pd.DataFrame), "The dataframe must be a pandas dataframe."
|
|
74
|
+
assert not df.empty, "The dataframe must not be empty."
|
|
75
|
+
|
|
76
|
+
if sql_query:
|
|
77
|
+
assert isinstance(sql_query, str), "The SQL query must be a string."
|
|
78
|
+
|
|
78
79
|
self.df = df
|
|
79
80
|
|
|
80
81
|
# Capture df.info() output
|
|
@@ -104,18 +105,27 @@ class PlotlyAgent:
|
|
|
104
105
|
Returns:
|
|
105
106
|
str: The result of the execution.
|
|
106
107
|
"""
|
|
108
|
+
assert isinstance(generated_code, str), "The generated code must be a string."
|
|
109
|
+
|
|
107
110
|
if not self.execution_env:
|
|
108
111
|
return "Error: No dataframe has been set. Please set a dataframe first."
|
|
109
112
|
|
|
110
113
|
# Store this as the last generated code
|
|
111
114
|
self.generated_code = generated_code
|
|
112
115
|
|
|
113
|
-
|
|
116
|
+
# Execute the generated code
|
|
117
|
+
code_execution_result = self.execution_env.execute_code(generated_code)
|
|
114
118
|
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
# Extract the results from the code execution
|
|
120
|
+
code_execution_success = code_execution_result.get("success", False)
|
|
121
|
+
code_execution_output = code_execution_result.get("output", "")
|
|
122
|
+
code_execution_error = code_execution_result.get("error", "")
|
|
123
|
+
|
|
124
|
+
# Check if the code executed successfully
|
|
125
|
+
if code_execution_success:
|
|
126
|
+
return f"Code executed successfully! A figure object was created.\n{code_execution_output}"
|
|
117
127
|
else:
|
|
118
|
-
return f"Error: {
|
|
128
|
+
return f"Error: {code_execution_error}\n{code_execution_output}"
|
|
119
129
|
|
|
120
130
|
def does_fig_exist(self, *args, **kwargs) -> str:
|
|
121
131
|
"""
|
|
@@ -144,23 +154,25 @@ class PlotlyAgent:
|
|
|
144
154
|
|
|
145
155
|
def _initialize_agent(self):
|
|
146
156
|
"""Initialize the LangChain agent with the necessary tools and prompt."""
|
|
157
|
+
|
|
158
|
+
# Initialize the tools
|
|
147
159
|
tools = [
|
|
148
160
|
Tool.from_function(
|
|
149
161
|
func=self.execute_plotly_code,
|
|
150
162
|
name="execute_plotly_code",
|
|
151
|
-
description="Execute the provided Plotly code and return the
|
|
163
|
+
description="Execute the provided Plotly code and return a result indicating if the code executed successfully and if a figure object was created.",
|
|
152
164
|
args_schema=GeneratedCodeInput,
|
|
153
165
|
),
|
|
154
166
|
StructuredTool.from_function(
|
|
155
167
|
func=self.does_fig_exist,
|
|
156
168
|
name="does_fig_exist",
|
|
157
|
-
description="Check if a figure exists and is available for display. This tool takes no arguments.",
|
|
169
|
+
description="Check if a figure exists and is available for display. This tool takes no arguments and returns a string indicating if a figure is available for display or not.",
|
|
158
170
|
args_schema=DoesFigExistInput,
|
|
159
171
|
),
|
|
160
172
|
StructuredTool.from_function(
|
|
161
173
|
func=self.view_generated_code,
|
|
162
174
|
name="view_generated_code",
|
|
163
|
-
description="View the generated code.",
|
|
175
|
+
description="View the generated code. This tool takes no arguments and returns the generated code as a string.",
|
|
164
176
|
args_schema=ViewGeneratedCodeInput,
|
|
165
177
|
),
|
|
166
178
|
]
|
|
@@ -198,6 +210,8 @@ class PlotlyAgent:
|
|
|
198
210
|
|
|
199
211
|
def process_message(self, user_message: str) -> str:
|
|
200
212
|
"""Process a user message and return the agent's response."""
|
|
213
|
+
assert isinstance(user_message, str), "The user message must be a string."
|
|
214
|
+
|
|
201
215
|
if not self.agent_executor:
|
|
202
216
|
return "Please set a dataframe first using set_df() method."
|
|
203
217
|
|
|
@@ -238,3 +252,4 @@ class PlotlyAgent:
|
|
|
238
252
|
def reset_conversation(self):
|
|
239
253
|
"""Reset the conversation history."""
|
|
240
254
|
self.chat_history = []
|
|
255
|
+
self.generated_code = None
|
|
@@ -11,9 +11,20 @@ from typing import Dict, Any
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class PlotlyAgentExecutionEnvironment:
|
|
14
|
-
"""
|
|
14
|
+
"""
|
|
15
|
+
Environment to safely execute plotly code and capture the fig object.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
df (pd.DataFrame): The dataframe to use for the execution environment.
|
|
19
|
+
"""
|
|
15
20
|
|
|
16
21
|
def __init__(self, df: pd.DataFrame):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the execution environment with the given dataframe.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
df (pd.DataFrame): The dataframe to use for the execution environment.
|
|
27
|
+
"""
|
|
17
28
|
self.df = df
|
|
18
29
|
self.locals_dict = {
|
|
19
30
|
"df": df,
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
from plot_agent.agent import PlotlyAgent
|
|
5
|
+
from langchain_core.messages import HumanMessage, AIMessage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_plotly_agent_initialization():
|
|
9
|
+
"""Test that PlotlyAgent initializes correctly."""
|
|
10
|
+
agent = PlotlyAgent()
|
|
11
|
+
assert agent.llm is not None
|
|
12
|
+
assert agent.df is None
|
|
13
|
+
assert agent.df_info is None
|
|
14
|
+
assert agent.df_head is None
|
|
15
|
+
assert agent.sql_query is None
|
|
16
|
+
assert agent.execution_env is None
|
|
17
|
+
assert agent.chat_history == []
|
|
18
|
+
assert agent.agent_executor is None
|
|
19
|
+
assert agent.generated_code is None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_set_df():
|
|
23
|
+
"""Test that set_df properly sets up the dataframe and environment."""
|
|
24
|
+
# Create a sample dataframe
|
|
25
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
26
|
+
|
|
27
|
+
agent = PlotlyAgent()
|
|
28
|
+
agent.set_df(df)
|
|
29
|
+
|
|
30
|
+
assert agent.df is not None
|
|
31
|
+
assert agent.df_info is not None
|
|
32
|
+
assert agent.df_head is not None
|
|
33
|
+
assert agent.execution_env is not None
|
|
34
|
+
assert agent.agent_executor is not None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_execute_plotly_code():
|
|
38
|
+
"""Test that execute_plotly_code works with valid code."""
|
|
39
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
40
|
+
|
|
41
|
+
agent = PlotlyAgent()
|
|
42
|
+
agent.set_df(df)
|
|
43
|
+
|
|
44
|
+
# Test with valid plotly code
|
|
45
|
+
valid_code = """import plotly.express as px
|
|
46
|
+
fig = px.scatter(df, x='x', y='y')"""
|
|
47
|
+
|
|
48
|
+
result = agent.execute_plotly_code(valid_code)
|
|
49
|
+
assert "Code executed successfully" in result
|
|
50
|
+
assert agent.execution_env.fig is not None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_execute_plotly_code_with_error():
|
|
54
|
+
"""Test that execute_plotly_code handles errors properly."""
|
|
55
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
56
|
+
|
|
57
|
+
agent = PlotlyAgent()
|
|
58
|
+
agent.set_df(df)
|
|
59
|
+
|
|
60
|
+
# Test with invalid code
|
|
61
|
+
invalid_code = """import plotly.express as px
|
|
62
|
+
fig = px.scatter(df, x='non_existent_column', y='y')"""
|
|
63
|
+
|
|
64
|
+
result = agent.execute_plotly_code(invalid_code)
|
|
65
|
+
assert "Error" in result
|
|
66
|
+
assert agent.execution_env.fig is None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_does_fig_exist():
|
|
70
|
+
"""Test that does_fig_exist correctly reports figure existence."""
|
|
71
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
72
|
+
|
|
73
|
+
agent = PlotlyAgent()
|
|
74
|
+
agent.set_df(df)
|
|
75
|
+
|
|
76
|
+
# Initially no figure should exist
|
|
77
|
+
assert "No figure has been created yet" in agent.does_fig_exist()
|
|
78
|
+
|
|
79
|
+
# Create a figure
|
|
80
|
+
valid_code = """import plotly.express as px
|
|
81
|
+
fig = px.scatter(df, x='x', y='y')"""
|
|
82
|
+
agent.execute_plotly_code(valid_code)
|
|
83
|
+
|
|
84
|
+
# Now a figure should exist
|
|
85
|
+
assert "A figure is available for display" in agent.does_fig_exist()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_reset_conversation():
|
|
89
|
+
"""Test that reset_conversation clears the chat history."""
|
|
90
|
+
agent = PlotlyAgent()
|
|
91
|
+
agent.chat_history = ["message1", "message2"]
|
|
92
|
+
agent.reset_conversation()
|
|
93
|
+
assert agent.chat_history == []
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_view_generated_code():
|
|
97
|
+
"""Test that view_generated_code returns the last generated code."""
|
|
98
|
+
agent = PlotlyAgent()
|
|
99
|
+
test_code = "test code"
|
|
100
|
+
agent.generated_code = test_code
|
|
101
|
+
assert agent.view_generated_code() == test_code
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_get_figure():
|
|
105
|
+
"""Test that get_figure returns the current figure if it exists."""
|
|
106
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
107
|
+
|
|
108
|
+
agent = PlotlyAgent()
|
|
109
|
+
agent.set_df(df)
|
|
110
|
+
|
|
111
|
+
# Initially no figure should exist
|
|
112
|
+
assert agent.get_figure() is None
|
|
113
|
+
|
|
114
|
+
# Create a figure
|
|
115
|
+
valid_code = """import plotly.express as px
|
|
116
|
+
fig = px.scatter(df, x='x', y='y')"""
|
|
117
|
+
agent.execute_plotly_code(valid_code)
|
|
118
|
+
|
|
119
|
+
# Now a figure should exist
|
|
120
|
+
assert agent.get_figure() is not None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_process_message():
|
|
124
|
+
"""Test that process_message updates chat history and handles responses."""
|
|
125
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
126
|
+
|
|
127
|
+
agent = PlotlyAgent()
|
|
128
|
+
agent.set_df(df, sql_query="SELECT x, y FROM df")
|
|
129
|
+
|
|
130
|
+
# Test processing a message
|
|
131
|
+
response = agent.process_message("Create a scatter plot")
|
|
132
|
+
|
|
133
|
+
# Check that chat history was updated
|
|
134
|
+
assert len(agent.chat_history) == 2 # One human message and one AI message
|
|
135
|
+
assert isinstance(agent.chat_history[0], HumanMessage)
|
|
136
|
+
assert isinstance(agent.chat_history[1], AIMessage)
|
|
137
|
+
assert agent.chat_history[0].content == "Create a scatter plot"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_execute_plotly_code_without_df():
|
|
141
|
+
"""Test that execute_plotly_code handles the case when no dataframe is set."""
|
|
142
|
+
agent = PlotlyAgent()
|
|
143
|
+
result = agent.execute_plotly_code("some code")
|
|
144
|
+
assert "Error" in result and "No dataframe has been set" in result
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_set_df_with_sql_query():
|
|
148
|
+
"""Test that set_df properly handles SQL query context."""
|
|
149
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
150
|
+
|
|
151
|
+
sql_query = "SELECT x, y FROM table"
|
|
152
|
+
agent = PlotlyAgent()
|
|
153
|
+
agent.set_df(df, sql_query=sql_query)
|
|
154
|
+
|
|
155
|
+
assert agent.sql_query == sql_query
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_agent_initialization_with_custom_prompt():
|
|
159
|
+
"""Test agent initialization with custom system prompt."""
|
|
160
|
+
custom_prompt = "Custom system prompt for testing"
|
|
161
|
+
agent = PlotlyAgent(system_prompt=custom_prompt)
|
|
162
|
+
assert agent.system_prompt == custom_prompt
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_agent_initialization_with_different_model():
|
|
166
|
+
"""Test agent initialization with different model names."""
|
|
167
|
+
agent = PlotlyAgent(model="gpt-3.5-turbo")
|
|
168
|
+
assert agent.llm.model_name == "gpt-3.5-turbo"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_agent_initialization_with_verbose():
|
|
172
|
+
"""Test agent initialization with verbose settings."""
|
|
173
|
+
agent = PlotlyAgent(verbose=False)
|
|
174
|
+
assert agent.verbose == False
|
|
175
|
+
assert agent.agent_executor is None # Agent executor not initialized yet
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_agent_initialization_with_max_iterations():
|
|
179
|
+
"""Test agent initialization with different max iterations."""
|
|
180
|
+
agent = PlotlyAgent(max_iterations=5)
|
|
181
|
+
assert agent.max_iterations == 5
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_agent_initialization_with_early_stopping():
|
|
185
|
+
"""Test agent initialization with different early stopping methods."""
|
|
186
|
+
agent = PlotlyAgent(early_stopping_method="generate")
|
|
187
|
+
assert agent.early_stopping_method == "generate"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_process_empty_message():
|
|
191
|
+
"""Test processing of empty messages."""
|
|
192
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
193
|
+
|
|
194
|
+
agent = PlotlyAgent()
|
|
195
|
+
agent.set_df(df)
|
|
196
|
+
|
|
197
|
+
response = agent.process_message("")
|
|
198
|
+
assert len(agent.chat_history) == 2 # Should still create chat history entries
|
|
199
|
+
assert isinstance(agent.chat_history[0], HumanMessage)
|
|
200
|
+
assert isinstance(agent.chat_history[1], AIMessage)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_process_message_with_code_blocks():
|
|
204
|
+
"""Test processing messages that contain code blocks."""
|
|
205
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
206
|
+
|
|
207
|
+
agent = PlotlyAgent()
|
|
208
|
+
agent.set_df(df)
|
|
209
|
+
|
|
210
|
+
message = "Here's some code:\n```python\nprint('test')\n```"
|
|
211
|
+
response = agent.process_message(message)
|
|
212
|
+
assert len(agent.chat_history) == 2
|
|
213
|
+
assert "```python" in agent.chat_history[0].content
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_execution_environment_with_different_plot_types():
|
|
217
|
+
"""Test execution environment with different types of plots."""
|
|
218
|
+
df = pd.DataFrame(
|
|
219
|
+
{
|
|
220
|
+
"x": [1, 2, 3, 4, 5],
|
|
221
|
+
"y": [10, 20, 30, 40, 50],
|
|
222
|
+
"category": ["A", "B", "A", "B", "A"],
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
agent = PlotlyAgent()
|
|
227
|
+
agent.set_df(df)
|
|
228
|
+
|
|
229
|
+
# Test scatter plot
|
|
230
|
+
scatter_code = """import plotly.express as px
|
|
231
|
+
fig = px.scatter(df, x='x', y='y')"""
|
|
232
|
+
result = agent.execute_plotly_code(scatter_code)
|
|
233
|
+
assert "Code executed successfully" in result
|
|
234
|
+
assert agent.execution_env.fig is not None
|
|
235
|
+
|
|
236
|
+
# Test bar plot
|
|
237
|
+
bar_code = """import plotly.express as px
|
|
238
|
+
fig = px.bar(df, x='category', y='y')"""
|
|
239
|
+
result = agent.execute_plotly_code(bar_code)
|
|
240
|
+
assert "Code executed successfully" in result
|
|
241
|
+
assert agent.execution_env.fig is not None
|
|
242
|
+
|
|
243
|
+
# Test line plot
|
|
244
|
+
line_code = """import plotly.express as px
|
|
245
|
+
fig = px.line(df, x='x', y='y')"""
|
|
246
|
+
result = agent.execute_plotly_code(line_code)
|
|
247
|
+
assert "Code executed successfully" in result
|
|
248
|
+
assert agent.execution_env.fig is not None
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_execution_environment_with_subplots():
|
|
252
|
+
"""Test execution environment with subplots."""
|
|
253
|
+
df = pd.DataFrame(
|
|
254
|
+
{"x": [1, 2, 3, 4, 5], "y1": [10, 20, 30, 40, 50], "y2": [50, 40, 30, 20, 10]}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
agent = PlotlyAgent()
|
|
258
|
+
agent.set_df(df)
|
|
259
|
+
|
|
260
|
+
subplot_code = """import plotly.subplots as sp
|
|
261
|
+
import plotly.graph_objects as go
|
|
262
|
+
fig = sp.make_subplots(rows=1, cols=2)
|
|
263
|
+
fig.add_trace(go.Scatter(x=df['x'], y=df['y1']), row=1, col=1)
|
|
264
|
+
fig.add_trace(go.Scatter(x=df['x'], y=df['y2']), row=1, col=2)"""
|
|
265
|
+
|
|
266
|
+
result = agent.execute_plotly_code(subplot_code)
|
|
267
|
+
assert "Code executed successfully" in result
|
|
268
|
+
assert agent.execution_env.fig is not None
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def test_execution_environment_with_data_preprocessing():
|
|
272
|
+
"""Test execution environment with data preprocessing steps."""
|
|
273
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
274
|
+
|
|
275
|
+
agent = PlotlyAgent()
|
|
276
|
+
agent.set_df(df)
|
|
277
|
+
|
|
278
|
+
preprocessing_code = """import plotly.express as px
|
|
279
|
+
# Preprocessing steps
|
|
280
|
+
df['y_normalized'] = (df['y'] - df['y'].min()) / (df['y'].max() - df['y'].min())
|
|
281
|
+
fig = px.scatter(df, x='x', y='y_normalized')"""
|
|
282
|
+
|
|
283
|
+
result = agent.execute_plotly_code(preprocessing_code)
|
|
284
|
+
assert "Code executed successfully" in result
|
|
285
|
+
assert agent.execution_env.fig is not None
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def test_handle_syntax_error():
|
|
289
|
+
"""Test handling of syntax errors in generated code."""
|
|
290
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
291
|
+
|
|
292
|
+
agent = PlotlyAgent()
|
|
293
|
+
agent.set_df(df)
|
|
294
|
+
|
|
295
|
+
invalid_code = """import plotly.express as px
|
|
296
|
+
fig = px.scatter(df, x='x', y='y' # Missing closing parenthesis"""
|
|
297
|
+
|
|
298
|
+
result = agent.execute_plotly_code(invalid_code)
|
|
299
|
+
assert "Error" in result
|
|
300
|
+
assert "SyntaxError" in result
|
|
301
|
+
assert agent.execution_env.fig is None
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def test_handle_runtime_error():
|
|
305
|
+
"""Test handling of runtime errors in generated code."""
|
|
306
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
307
|
+
|
|
308
|
+
agent = PlotlyAgent()
|
|
309
|
+
agent.set_df(df)
|
|
310
|
+
|
|
311
|
+
error_code = """import plotly.express as px
|
|
312
|
+
fig = px.scatter(df, x='x', y='y', color='non_existent_column')"""
|
|
313
|
+
|
|
314
|
+
result = agent.execute_plotly_code(error_code)
|
|
315
|
+
assert "Error" in result
|
|
316
|
+
assert agent.execution_env.fig is None
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_tool_interaction():
|
|
320
|
+
"""Test interaction between different tools."""
|
|
321
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
322
|
+
|
|
323
|
+
agent = PlotlyAgent()
|
|
324
|
+
agent.set_df(df)
|
|
325
|
+
|
|
326
|
+
# First check if figure exists (should not)
|
|
327
|
+
assert "No figure has been created yet" in agent.does_fig_exist()
|
|
328
|
+
|
|
329
|
+
# Generate and execute code
|
|
330
|
+
code = """import plotly.express as px
|
|
331
|
+
fig = px.scatter(df, x='x', y='y')"""
|
|
332
|
+
result = agent.execute_plotly_code(code)
|
|
333
|
+
assert "Code executed successfully" in result
|
|
334
|
+
|
|
335
|
+
# Check if figure exists (should now exist)
|
|
336
|
+
assert "A figure is available for display" in agent.does_fig_exist()
|
|
337
|
+
|
|
338
|
+
# View the generated code
|
|
339
|
+
assert code in agent.view_generated_code()
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def test_tool_validation():
|
|
343
|
+
"""Test validation of tool inputs."""
|
|
344
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
345
|
+
|
|
346
|
+
agent = PlotlyAgent()
|
|
347
|
+
agent.set_df(df)
|
|
348
|
+
|
|
349
|
+
# Test with invalid code (empty string)
|
|
350
|
+
result = agent.execute_plotly_code("")
|
|
351
|
+
assert "Error" in result
|
|
352
|
+
|
|
353
|
+
# Test with invalid code (None)
|
|
354
|
+
with pytest.raises(AssertionError):
|
|
355
|
+
agent.execute_plotly_code(None)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def test_tool_response_formatting():
|
|
359
|
+
"""Test formatting of tool responses."""
|
|
360
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
361
|
+
|
|
362
|
+
agent = PlotlyAgent()
|
|
363
|
+
agent.set_df(df)
|
|
364
|
+
|
|
365
|
+
# Test execute_plotly_code response format
|
|
366
|
+
code = """import plotly.express as px
|
|
367
|
+
fig = px.scatter(df, x='x', y='y')"""
|
|
368
|
+
result = agent.execute_plotly_code(code)
|
|
369
|
+
assert isinstance(result, str)
|
|
370
|
+
assert "Code executed successfully" in result
|
|
371
|
+
|
|
372
|
+
# Test does_fig_exist response format
|
|
373
|
+
result = agent.does_fig_exist()
|
|
374
|
+
assert isinstance(result, str)
|
|
375
|
+
assert "figure" in result.lower()
|
|
376
|
+
|
|
377
|
+
# Test view_generated_code response format
|
|
378
|
+
result = agent.view_generated_code()
|
|
379
|
+
assert isinstance(result, str)
|
|
380
|
+
assert code in result
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def test_memory_cleanup():
|
|
384
|
+
"""Test memory cleanup after multiple plot generations."""
|
|
385
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})
|
|
386
|
+
|
|
387
|
+
agent = PlotlyAgent()
|
|
388
|
+
agent.set_df(df)
|
|
389
|
+
|
|
390
|
+
# Generate multiple plots
|
|
391
|
+
for i in range(5):
|
|
392
|
+
code = f"""import plotly.express as px
|
|
393
|
+
fig = px.scatter(df, x='x', y='y', title='Plot {i}')"""
|
|
394
|
+
result = agent.execute_plotly_code(code)
|
|
395
|
+
assert "Code executed successfully" in result
|
|
396
|
+
assert agent.execution_env.fig is not None
|
|
397
|
+
|
|
398
|
+
# Reset conversation and check memory
|
|
399
|
+
agent.reset_conversation()
|
|
400
|
+
assert len(agent.chat_history) == 0
|
|
401
|
+
assert agent.generated_code is None
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def test_large_dataframe_handling():
|
|
405
|
+
"""Test handling of large dataframes."""
|
|
406
|
+
# Create a large dataframe
|
|
407
|
+
df = pd.DataFrame({"x": range(10000), "y": range(10000)})
|
|
408
|
+
|
|
409
|
+
agent = PlotlyAgent()
|
|
410
|
+
agent.set_df(df)
|
|
411
|
+
|
|
412
|
+
# Test plot generation with large dataframe
|
|
413
|
+
code = """import plotly.express as px
|
|
414
|
+
fig = px.scatter(df, x='x', y='y')"""
|
|
415
|
+
result = agent.execute_plotly_code(code)
|
|
416
|
+
assert "Code executed successfully" in result
|
|
417
|
+
assert agent.execution_env.fig is not None
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def test_input_validation():
|
|
421
|
+
"""Test validation of input parameters."""
|
|
422
|
+
# Test invalid dataframe input
|
|
423
|
+
with pytest.raises(AssertionError):
|
|
424
|
+
agent = PlotlyAgent()
|
|
425
|
+
agent.set_df("not a dataframe")
|
|
426
|
+
|
|
427
|
+
# Test invalid SQL query input
|
|
428
|
+
df = pd.DataFrame({"x": [1, 2, 3]})
|
|
429
|
+
agent = PlotlyAgent()
|
|
430
|
+
with pytest.raises(AssertionError):
|
|
431
|
+
agent.set_df(df, sql_query=123) # SQL query should be string
|
|
432
|
+
|
|
433
|
+
# Test invalid message input
|
|
434
|
+
agent.set_df(df)
|
|
435
|
+
with pytest.raises(AssertionError):
|
|
436
|
+
agent.process_message(123) # Message should be string
|
|
437
|
+
|
|
438
|
+
# Test invalid code input
|
|
439
|
+
with pytest.raises(AssertionError):
|
|
440
|
+
agent.execute_plotly_code(123) # Code should be string
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def test_complex_plot_handling():
|
|
444
|
+
"""Test handling of complex plots with multiple traces and layouts."""
|
|
445
|
+
df = pd.DataFrame(
|
|
446
|
+
{
|
|
447
|
+
"x": [1, 2, 3, 4, 5],
|
|
448
|
+
"y1": [10, 20, 30, 40, 50],
|
|
449
|
+
"y2": [50, 40, 30, 20, 10],
|
|
450
|
+
"category": ["A", "B", "A", "B", "A"],
|
|
451
|
+
}
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
agent = PlotlyAgent()
|
|
455
|
+
agent.set_df(df)
|
|
456
|
+
|
|
457
|
+
complex_code = """import plotly.graph_objects as go
|
|
458
|
+
fig = go.Figure()
|
|
459
|
+
fig.add_trace(go.Scatter(x=df['x'], y=df['y1'], name='Trace 1'))
|
|
460
|
+
fig.add_trace(go.Scatter(x=df['x'], y=df['y2'], name='Trace 2'))
|
|
461
|
+
fig.update_layout(
|
|
462
|
+
title='Complex Plot',
|
|
463
|
+
xaxis_title='X Axis',
|
|
464
|
+
yaxis_title='Y Axis',
|
|
465
|
+
showlegend=True,
|
|
466
|
+
template='plotly_white'
|
|
467
|
+
)"""
|
|
468
|
+
|
|
469
|
+
result = agent.execute_plotly_code(complex_code)
|
|
470
|
+
assert "Code executed successfully" in result
|
|
471
|
+
assert agent.execution_env.fig is not None
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
import pandas as pd
|
|
3
|
-
import numpy as np
|
|
4
|
-
from plot_agent.agent import PlotlyAgent
|
|
5
|
-
from langchain_core.messages import HumanMessage, AIMessage
|
|
6
|
-
|
|
7
|
-
def test_plotly_agent_initialization():
|
|
8
|
-
"""Test that PlotlyAgent initializes correctly."""
|
|
9
|
-
agent = PlotlyAgent()
|
|
10
|
-
assert agent.llm is not None
|
|
11
|
-
assert agent.df is None
|
|
12
|
-
assert agent.df_info is None
|
|
13
|
-
assert agent.df_head is None
|
|
14
|
-
assert agent.sql_query is None
|
|
15
|
-
assert agent.execution_env is None
|
|
16
|
-
assert agent.chat_history == []
|
|
17
|
-
assert agent.agent_executor is None
|
|
18
|
-
assert agent.generated_code is None
|
|
19
|
-
|
|
20
|
-
def test_set_df():
|
|
21
|
-
"""Test that set_df properly sets up the dataframe and environment."""
|
|
22
|
-
# Create a sample dataframe
|
|
23
|
-
df = pd.DataFrame({
|
|
24
|
-
'x': [1, 2, 3, 4, 5],
|
|
25
|
-
'y': [10, 20, 30, 40, 50]
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
agent = PlotlyAgent()
|
|
29
|
-
agent.set_df(df)
|
|
30
|
-
|
|
31
|
-
assert agent.df is not None
|
|
32
|
-
assert agent.df_info is not None
|
|
33
|
-
assert agent.df_head is not None
|
|
34
|
-
assert agent.execution_env is not None
|
|
35
|
-
assert agent.agent_executor is not None
|
|
36
|
-
|
|
37
|
-
def test_execute_plotly_code():
|
|
38
|
-
"""Test that execute_plotly_code works with valid code."""
|
|
39
|
-
df = pd.DataFrame({
|
|
40
|
-
'x': [1, 2, 3, 4, 5],
|
|
41
|
-
'y': [10, 20, 30, 40, 50]
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
agent = PlotlyAgent()
|
|
45
|
-
agent.set_df(df)
|
|
46
|
-
|
|
47
|
-
# Test with valid plotly code
|
|
48
|
-
valid_code = """import plotly.express as px
|
|
49
|
-
fig = px.scatter(df, x='x', y='y')"""
|
|
50
|
-
|
|
51
|
-
result = agent.execute_plotly_code(valid_code)
|
|
52
|
-
assert "Code executed successfully" in result
|
|
53
|
-
assert agent.execution_env.fig is not None
|
|
54
|
-
|
|
55
|
-
def test_execute_plotly_code_with_error():
|
|
56
|
-
"""Test that execute_plotly_code handles errors properly."""
|
|
57
|
-
df = pd.DataFrame({
|
|
58
|
-
'x': [1, 2, 3, 4, 5],
|
|
59
|
-
'y': [10, 20, 30, 40, 50]
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
agent = PlotlyAgent()
|
|
63
|
-
agent.set_df(df)
|
|
64
|
-
|
|
65
|
-
# Test with invalid code
|
|
66
|
-
invalid_code = """import plotly.express as px
|
|
67
|
-
fig = px.scatter(df, x='non_existent_column', y='y')"""
|
|
68
|
-
|
|
69
|
-
result = agent.execute_plotly_code(invalid_code)
|
|
70
|
-
assert "Error" in result
|
|
71
|
-
assert agent.execution_env.fig is None
|
|
72
|
-
|
|
73
|
-
def test_does_fig_exist():
|
|
74
|
-
"""Test that does_fig_exist correctly reports figure existence."""
|
|
75
|
-
df = pd.DataFrame({
|
|
76
|
-
'x': [1, 2, 3, 4, 5],
|
|
77
|
-
'y': [10, 20, 30, 40, 50]
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
agent = PlotlyAgent()
|
|
81
|
-
agent.set_df(df)
|
|
82
|
-
|
|
83
|
-
# Initially no figure should exist
|
|
84
|
-
assert "No figure has been created yet" in agent.does_fig_exist()
|
|
85
|
-
|
|
86
|
-
# Create a figure
|
|
87
|
-
valid_code = """import plotly.express as px
|
|
88
|
-
fig = px.scatter(df, x='x', y='y')"""
|
|
89
|
-
agent.execute_plotly_code(valid_code)
|
|
90
|
-
|
|
91
|
-
# Now a figure should exist
|
|
92
|
-
assert "A figure is available for display" in agent.does_fig_exist()
|
|
93
|
-
|
|
94
|
-
def test_reset_conversation():
|
|
95
|
-
"""Test that reset_conversation clears the chat history."""
|
|
96
|
-
agent = PlotlyAgent()
|
|
97
|
-
agent.chat_history = ["message1", "message2"]
|
|
98
|
-
agent.reset_conversation()
|
|
99
|
-
assert agent.chat_history == []
|
|
100
|
-
|
|
101
|
-
def test_view_generated_code():
|
|
102
|
-
"""Test that view_generated_code returns the last generated code."""
|
|
103
|
-
agent = PlotlyAgent()
|
|
104
|
-
test_code = "test code"
|
|
105
|
-
agent.generated_code = test_code
|
|
106
|
-
assert agent.view_generated_code() == test_code
|
|
107
|
-
|
|
108
|
-
def test_get_figure():
|
|
109
|
-
"""Test that get_figure returns the current figure if it exists."""
|
|
110
|
-
df = pd.DataFrame({
|
|
111
|
-
'x': [1, 2, 3, 4, 5],
|
|
112
|
-
'y': [10, 20, 30, 40, 50]
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
agent = PlotlyAgent()
|
|
116
|
-
agent.set_df(df)
|
|
117
|
-
|
|
118
|
-
# Initially no figure should exist
|
|
119
|
-
assert agent.get_figure() is None
|
|
120
|
-
|
|
121
|
-
# Create a figure
|
|
122
|
-
valid_code = """import plotly.express as px
|
|
123
|
-
fig = px.scatter(df, x='x', y='y')"""
|
|
124
|
-
agent.execute_plotly_code(valid_code)
|
|
125
|
-
|
|
126
|
-
# Now a figure should exist
|
|
127
|
-
assert agent.get_figure() is not None
|
|
128
|
-
|
|
129
|
-
def test_process_message():
|
|
130
|
-
"""Test that process_message updates chat history and handles responses."""
|
|
131
|
-
df = pd.DataFrame({
|
|
132
|
-
'x': [1, 2, 3, 4, 5],
|
|
133
|
-
'y': [10, 20, 30, 40, 50]
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
agent = PlotlyAgent()
|
|
137
|
-
agent.set_df(df)
|
|
138
|
-
|
|
139
|
-
# Test processing a message
|
|
140
|
-
response = agent.process_message("Create a scatter plot")
|
|
141
|
-
|
|
142
|
-
# Check that chat history was updated
|
|
143
|
-
assert len(agent.chat_history) == 2 # One human message and one AI message
|
|
144
|
-
assert isinstance(agent.chat_history[0], HumanMessage)
|
|
145
|
-
assert isinstance(agent.chat_history[1], AIMessage)
|
|
146
|
-
assert agent.chat_history[0].content == "Create a scatter plot"
|
|
147
|
-
|
|
148
|
-
def test_execute_plotly_code_without_df():
|
|
149
|
-
"""Test that execute_plotly_code handles the case when no dataframe is set."""
|
|
150
|
-
agent = PlotlyAgent()
|
|
151
|
-
result = agent.execute_plotly_code("some code")
|
|
152
|
-
assert "Error" in result and "No dataframe has been set" in result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|