Charles Azam commited on
Commit
395c3d4
·
1 Parent(s): 1ea134d

feat: add draw image tool

Browse files
data/figure.png CHANGED
pyproject.toml CHANGED
@@ -18,6 +18,11 @@ dependencies = [
18
  "pypdf>=5.7.0",
19
  "pytest-playwright>=0.7.0",
20
  "matplotlib>=3.10.3",
 
 
 
 
 
21
  ]
22
 
23
  [project.scripts]
 
18
  "pypdf>=5.7.0",
19
  "pytest-playwright>=0.7.0",
20
  "matplotlib>=3.10.3",
21
+ "numpy>=2.3.1",
22
+ "pandas>=2.3.1",
23
+ "seaborn>=0.13.2",
24
+ "sympy>=1.14.0",
25
+ "scipy>=1.16.0",
26
  ]
27
 
28
  [project.scripts]
src/deepengineer/deepsearch/draw_agent.py CHANGED
@@ -12,8 +12,7 @@ from PIL import Image
12
 
13
  from smolagents import CodeAgent, LiteLLMModel
14
  from smolagents.agents import ActionStep
15
-
16
- import base64, mimetypes
17
 
18
 
19
  def save_fig(image_path: Path = Path("figure.png")) -> str:
@@ -28,6 +27,34 @@ def save_fig(image_path: Path = Path("figure.png")) -> str:
28
  return f"Figure saved to {image_path}."
29
 
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def _capture_snapshot(
32
  memory_step: ActionStep, agent: CodeAgent, image_path: Path = Path("figure.png")
33
  ) -> None:
@@ -59,7 +86,7 @@ def _capture_snapshot(
59
 
60
 
61
  matplotlib_instructions_multiple_steps = r"""
62
- You may use the entire **matplotlib** and **numpy** API. Do not worry about saving the image, it is done automatically and you can't access the os library.
63
 
64
  Between each step, the image is provided in memory. From step 2, you can use it to pass additional instructions to the model to improve the image.
65
 
@@ -75,7 +102,7 @@ User instructions:
75
  """
76
 
77
  matplotlib_instructions_single_step = r"""
78
- You may use the entire **matplotlib** and **numpy** API. Do not worry about saving the image, it is done automatically and you can't access the os library.
79
 
80
  Workflow
81
  --------
@@ -99,7 +126,12 @@ def draw_image_agent(
99
  agent = CodeAgent(
100
  tools=[],
101
  model=model,
102
- additional_authorized_imports=["matplotlib.*", "numpy.*"],
 
 
 
 
 
103
  step_callbacks=[
104
  lambda memory_step, agent: _capture_snapshot(memory_step, agent, image_path)
105
  ],
@@ -115,6 +147,40 @@ def draw_image_agent(
115
  return image_path
116
 
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  def multiple_steps_draw_image_agent(
119
  prompt: str,
120
  image_path: str = Path("figure.png"),
 
12
 
13
  from smolagents import CodeAgent, LiteLLMModel
14
  from smolagents.agents import ActionStep
15
+ from smolagents import tool, Tool
 
16
 
17
 
18
  def save_fig(image_path: Path = Path("figure.png")) -> str:
 
27
  return f"Figure saved to {image_path}."
28
 
29
 
30
+ class SaveMatplotlibFigTool(Tool):
31
+ name = "save_matplotlib_fig"
32
+ description = """Save the current matplotlib figure to the current directory. Then plt.close() is called to clear the figure. The image is returned as a markdown string, use this markdown inside the final answer to include the image.
33
+ """
34
+ inputs = {
35
+ "image_name": {
36
+ "type": "string",
37
+ "description": "The name of the image to save.",
38
+ },
39
+ }
40
+ output_type = "string"
41
+
42
+ def __init__(self, output_dir: Path):
43
+ super().__init__()
44
+ self.output_dir: Path = output_dir
45
+
46
+ def forward(self, image_name: str) -> str:
47
+ if not image_name.endswith(".png"):
48
+ image_name = image_name + ".png"
49
+ output_path = self.output_dir / image_name
50
+ output_path.unlink(missing_ok=True)
51
+ save_fig(output_path)
52
+ if output_path.exists():
53
+ return f"![]({image_name})"
54
+ else:
55
+ return f"Error: The image {image_name} was not saved."
56
+
57
+
58
  def _capture_snapshot(
59
  memory_step: ActionStep, agent: CodeAgent, image_path: Path = Path("figure.png")
60
  ) -> None:
 
86
 
87
 
88
  matplotlib_instructions_multiple_steps = r"""
89
+ You may use the entire **matplotlib** and **numpy** and **pandas** and **seaborn** API. Do not worry about saving the image, it is done automatically and you can't access the os library.
90
 
91
  Between each step, the image is provided in memory. From step 2, you can use it to pass additional instructions to the model to improve the image.
92
 
 
102
  """
103
 
104
  matplotlib_instructions_single_step = r"""
105
+ You may use the entire **matplotlib** and **numpy** and **pandas** and **seaborn** API. Do not worry about saving the image, it is done automatically and you can't access the os library.
106
 
107
  Workflow
108
  --------
 
126
  agent = CodeAgent(
127
  tools=[],
128
  model=model,
129
+ additional_authorized_imports=[
130
+ "matplotlib.*",
131
+ "numpy.*",
132
+ "pandas.*",
133
+ "seaborn.*",
134
+ ],
135
  step_callbacks=[
136
  lambda memory_step, agent: _capture_snapshot(memory_step, agent, image_path)
137
  ],
 
147
  return image_path
148
 
149
 
150
+ class DrawImageTool(Tool):
151
+ name = "draw_image"
152
+ description = f"Draw an image based on a prompt. The image is saved in the current directory. The image is returned as a markdown image, use this markdown inside the final answer to include the image. You must be very specific in your prompt."
153
+ inputs = {
154
+ "prompt": {
155
+ "type": "string",
156
+ "description": """
157
+ Draw an image based on a prompt. The image is saved in the current directory. The image is returned as a markdown image, use this markdown inside the final answer to include the image.
158
+
159
+ You must be very specific in your prompt. This tool has access to matplotlib, numpy, pandas, seaborn.
160
+
161
+ """,
162
+ },
163
+ "image_name": {
164
+ "type": "string",
165
+ "description": "The name of the image to save.",
166
+ },
167
+ }
168
+ output_type = "string"
169
+
170
+ def __init__(self, output_dir: Path):
171
+ super().__init__()
172
+ self.output_dir: Path = output_dir
173
+
174
+ def forward(self, prompt: str, image_name: str) -> str:
175
+ if not image_name.endswith(".png"):
176
+ image_name = image_name + ".png"
177
+ output_path = draw_image_agent(prompt, self.output_dir / image_name)
178
+ if output_path.exists():
179
+ return f"![]({image_name})"
180
+ else:
181
+ return f"Error: The image {image_name} was not saved."
182
+
183
+
184
  def multiple_steps_draw_image_agent(
185
  prompt: str,
186
  image_path: str = Path("figure.png"),
tests/deepsearch/test_web_agent.py CHANGED
@@ -11,7 +11,10 @@ def test_run_web_search_agent():
11
  agent = create_web_search_agent()
12
  assert (
13
  agent.run(
14
- "Est il possible de faire un réacteur thermique avec du graphite et du plomb?"
15
  )
16
  is not None
17
  )
 
 
 
 
11
  agent = create_web_search_agent()
12
  assert (
13
  agent.run(
14
+ 'Search a paper called "High Energy Physics Opportunities Using Reactor Antineutrinos" on arXiv, download it and extract the table of contents'
15
  )
16
  is not None
17
  )
18
+
19
+
20
+ test_run_web_search_agent()
tests/webcrawler/test_draw_agent.py CHANGED
@@ -2,9 +2,11 @@ import pytest
2
  from deepengineer.deepsearch.draw_agent import (
3
  draw_image_agent,
4
  multiple_steps_draw_image_agent,
 
5
  )
6
  from deepengineer.common_path import DATA_DIR
7
  from pathlib import Path
 
8
 
9
 
10
  @pytest.mark.expensive
@@ -16,6 +18,32 @@ def test_draw_image_agent():
16
  assert output_path.exists()
17
 
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  @pytest.mark.skip(reason="This function is not working yet")
20
  def test_run_agent_step_by_step():
21
  prompt = """Propose moi un schéma très détaillé d'un réacteur nucléaire hélium graphite."""
 
2
  from deepengineer.deepsearch.draw_agent import (
3
  draw_image_agent,
4
  multiple_steps_draw_image_agent,
5
+ SaveMatplotlibFigTool,
6
  )
7
  from deepengineer.common_path import DATA_DIR
8
  from pathlib import Path
9
+ from smolagents import CodeAgent, LiteLLMModel
10
 
11
 
12
  @pytest.mark.expensive
 
18
  assert output_path.exists()
19
 
20
 
21
+ def test_save_matplotlib_fig_tool():
22
+ model = LiteLLMModel(model_id="mistral/mistral-medium-latest")
23
+ agent = CodeAgent(
24
+ tools=[SaveMatplotlibFigTool(output_dir=Path(DATA_DIR))],
25
+ model=model,
26
+ additional_authorized_imports=[
27
+ "matplotlib.*",
28
+ "numpy.*",
29
+ "pandas.*",
30
+ "seaborn.*",
31
+ ],
32
+ max_steps=20,
33
+ verbosity_level=2,
34
+ )
35
+ agent.run(
36
+ """
37
+ Propose moi un schéma très détaillé d'un réacteur nucléaire hélium graphite.
38
+ Save the figure as figure.png. Return a markdown string where you explain what you did and then include the image.
39
+ """
40
+ )
41
+ assert (Path(DATA_DIR) / "figure.png").exists()
42
+
43
+
44
+ test_save_matplotlib_fig_tool()
45
+
46
+
47
  @pytest.mark.skip(reason="This function is not working yet")
48
  def test_run_agent_step_by_step():
49
  prompt = """Propose moi un schéma très détaillé d'un réacteur nucléaire hélium graphite."""
uv.lock CHANGED
@@ -388,10 +388,15 @@ dependencies = [
388
  { name = "litellm" },
389
  { name = "matplotlib" },
390
  { name = "mistralai" },
 
 
391
  { name = "pypdf" },
392
  { name = "pytest-asyncio" },
393
  { name = "pytest-playwright" },
 
 
394
  { name = "smolagents" },
 
395
  { name = "tavily-python" },
396
  ]
397
 
@@ -410,12 +415,17 @@ requires-dist = [
410
  { name = "litellm" },
411
  { name = "matplotlib", specifier = ">=3.10.3" },
412
  { name = "mistralai", specifier = ">=1.9.1" },
 
 
413
  { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.6.0" },
414
  { name = "pypdf", specifier = ">=5.7.0" },
415
  { name = "pytest-asyncio", specifier = ">=1.0.0" },
416
  { name = "pytest-playwright", specifier = ">=0.7.0" },
417
  { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.2.0" },
 
 
418
  { name = "smolagents", specifier = ">=1.19.0" },
 
419
  { name = "tavily-python", specifier = ">=0.7.9" },
420
  ]
421
  provides-extras = ["dev"]
@@ -961,6 +971,15 @@ wheels = [
961
  { url = "https://files.pythonhosted.org/packages/10/a2/2e177165a24d978f07cf5d5841265ab399c187b0a44077d67502b8129b27/mistralai-1.9.1-py3-none-any.whl", hash = "sha256:250ec26534db6f4a4d5e6292b0801a64da2ab1f0d4c63a20d8ce27e3a427e402", size = 381773 },
962
  ]
963
 
 
 
 
 
 
 
 
 
 
964
  [[package]]
965
  name = "multidict"
966
  version = "6.6.3"
@@ -1097,6 +1116,33 @@ wheels = [
1097
  { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
1098
  ]
1099
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1100
  [[package]]
1101
  name = "pathspec"
1102
  version = "0.12.1"
@@ -1432,6 +1478,15 @@ wheels = [
1432
  { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051 },
1433
  ]
1434
 
 
 
 
 
 
 
 
 
 
1435
  [[package]]
1436
  name = "pyyaml"
1437
  version = "6.0.2"
@@ -1612,6 +1667,49 @@ wheels = [
1612
  { url = "https://files.pythonhosted.org/packages/e2/1f/72d2946e3cc7456bb837e88000eb3437e55f80db339c840c04015a11115d/ruff-0.12.2-py3-none-win_arm64.whl", hash = "sha256:48d6c6bfb4761df68bc05ae630e24f506755e702d4fb08f08460be778c7ccb12", size = 10735334 },
1613
  ]
1614
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1615
  [[package]]
1616
  name = "six"
1617
  version = "1.17.0"
@@ -1665,6 +1763,18 @@ wheels = [
1665
  { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677 },
1666
  ]
1667
 
 
 
 
 
 
 
 
 
 
 
 
 
1668
  [[package]]
1669
  name = "tavily-python"
1670
  version = "0.7.9"
@@ -1777,6 +1887,15 @@ wheels = [
1777
  { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 },
1778
  ]
1779
 
 
 
 
 
 
 
 
 
 
1780
  [[package]]
1781
  name = "urllib3"
1782
  version = "2.5.0"
 
388
  { name = "litellm" },
389
  { name = "matplotlib" },
390
  { name = "mistralai" },
391
+ { name = "numpy" },
392
+ { name = "pandas" },
393
  { name = "pypdf" },
394
  { name = "pytest-asyncio" },
395
  { name = "pytest-playwright" },
396
+ { name = "scipy" },
397
+ { name = "seaborn" },
398
  { name = "smolagents" },
399
+ { name = "sympy" },
400
  { name = "tavily-python" },
401
  ]
402
 
 
415
  { name = "litellm" },
416
  { name = "matplotlib", specifier = ">=3.10.3" },
417
  { name = "mistralai", specifier = ">=1.9.1" },
418
+ { name = "numpy", specifier = ">=2.3.1" },
419
+ { name = "pandas", specifier = ">=2.3.1" },
420
  { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.6.0" },
421
  { name = "pypdf", specifier = ">=5.7.0" },
422
  { name = "pytest-asyncio", specifier = ">=1.0.0" },
423
  { name = "pytest-playwright", specifier = ">=0.7.0" },
424
  { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.2.0" },
425
+ { name = "scipy", specifier = ">=1.16.0" },
426
+ { name = "seaborn", specifier = ">=0.13.2" },
427
  { name = "smolagents", specifier = ">=1.19.0" },
428
+ { name = "sympy", specifier = ">=1.14.0" },
429
  { name = "tavily-python", specifier = ">=0.7.9" },
430
  ]
431
  provides-extras = ["dev"]
 
971
  { url = "https://files.pythonhosted.org/packages/10/a2/2e177165a24d978f07cf5d5841265ab399c187b0a44077d67502b8129b27/mistralai-1.9.1-py3-none-any.whl", hash = "sha256:250ec26534db6f4a4d5e6292b0801a64da2ab1f0d4c63a20d8ce27e3a427e402", size = 381773 },
972
  ]
973
 
974
+ [[package]]
975
+ name = "mpmath"
976
+ version = "1.3.0"
977
+ source = { registry = "https://pypi.org/simple" }
978
+ sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 }
979
+ wheels = [
980
+ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 },
981
+ ]
982
+
983
  [[package]]
984
  name = "multidict"
985
  version = "6.6.3"
 
1116
  { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
1117
  ]
1118
 
1119
+ [[package]]
1120
+ name = "pandas"
1121
+ version = "2.3.1"
1122
+ source = { registry = "https://pypi.org/simple" }
1123
+ dependencies = [
1124
+ { name = "numpy" },
1125
+ { name = "python-dateutil" },
1126
+ { name = "pytz" },
1127
+ { name = "tzdata" },
1128
+ ]
1129
+ sdist = { url = "https://files.pythonhosted.org/packages/d1/6f/75aa71f8a14267117adeeed5d21b204770189c0a0025acbdc03c337b28fc/pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", size = 4487493 }
1130
+ wheels = [
1131
+ { url = "https://files.pythonhosted.org/packages/32/ed/ff0a67a2c5505e1854e6715586ac6693dd860fbf52ef9f81edee200266e7/pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", size = 11531393 },
1132
+ { url = "https://files.pythonhosted.org/packages/c7/db/d8f24a7cc9fb0972adab0cc80b6817e8bef888cfd0024eeb5a21c0bb5c4a/pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", size = 10668750 },
1133
+ { url = "https://files.pythonhosted.org/packages/0f/b0/80f6ec783313f1e2356b28b4fd8d2148c378370045da918c73145e6aab50/pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", size = 11342004 },
1134
+ { url = "https://files.pythonhosted.org/packages/e9/e2/20a317688435470872885e7fc8f95109ae9683dec7c50be29b56911515a5/pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", size = 12050869 },
1135
+ { url = "https://files.pythonhosted.org/packages/55/79/20d746b0a96c67203a5bee5fb4e00ac49c3e8009a39e1f78de264ecc5729/pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", size = 12750218 },
1136
+ { url = "https://files.pythonhosted.org/packages/7c/0f/145c8b41e48dbf03dd18fdd7f24f8ba95b8254a97a3379048378f33e7838/pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", size = 13416763 },
1137
+ { url = "https://files.pythonhosted.org/packages/b2/c0/54415af59db5cdd86a3d3bf79863e8cc3fa9ed265f0745254061ac09d5f2/pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", size = 10987482 },
1138
+ { url = "https://files.pythonhosted.org/packages/48/64/2fd2e400073a1230e13b8cd604c9bc95d9e3b962e5d44088ead2e8f0cfec/pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", size = 12029159 },
1139
+ { url = "https://files.pythonhosted.org/packages/d8/0a/d84fd79b0293b7ef88c760d7dca69828d867c89b6d9bc52d6a27e4d87316/pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", size = 11393287 },
1140
+ { url = "https://files.pythonhosted.org/packages/50/ae/ff885d2b6e88f3c7520bb74ba319268b42f05d7e583b5dded9837da2723f/pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", size = 11309381 },
1141
+ { url = "https://files.pythonhosted.org/packages/85/86/1fa345fc17caf5d7780d2699985c03dbe186c68fee00b526813939062bb0/pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", size = 11883998 },
1142
+ { url = "https://files.pythonhosted.org/packages/81/aa/e58541a49b5e6310d89474333e994ee57fea97c8aaa8fc7f00b873059bbf/pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", size = 12704705 },
1143
+ { url = "https://files.pythonhosted.org/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044 },
1144
+ ]
1145
+
1146
  [[package]]
1147
  name = "pathspec"
1148
  version = "0.12.1"
 
1478
  { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051 },
1479
  ]
1480
 
1481
+ [[package]]
1482
+ name = "pytz"
1483
+ version = "2025.2"
1484
+ source = { registry = "https://pypi.org/simple" }
1485
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 }
1486
+ wheels = [
1487
+ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 },
1488
+ ]
1489
+
1490
  [[package]]
1491
  name = "pyyaml"
1492
  version = "6.0.2"
 
1667
  { url = "https://files.pythonhosted.org/packages/e2/1f/72d2946e3cc7456bb837e88000eb3437e55f80db339c840c04015a11115d/ruff-0.12.2-py3-none-win_arm64.whl", hash = "sha256:48d6c6bfb4761df68bc05ae630e24f506755e702d4fb08f08460be778c7ccb12", size = 10735334 },
1668
  ]
1669
 
1670
+ [[package]]
1671
+ name = "scipy"
1672
+ version = "1.16.0"
1673
+ source = { registry = "https://pypi.org/simple" }
1674
+ dependencies = [
1675
+ { name = "numpy" },
1676
+ ]
1677
+ sdist = { url = "https://files.pythonhosted.org/packages/81/18/b06a83f0c5ee8cddbde5e3f3d0bb9b702abfa5136ef6d4620ff67df7eee5/scipy-1.16.0.tar.gz", hash = "sha256:b5ef54021e832869c8cfb03bc3bf20366cbcd426e02a58e8a58d7584dfbb8f62", size = 30581216 }
1678
+ wheels = [
1679
+ { url = "https://files.pythonhosted.org/packages/46/95/0746417bc24be0c2a7b7563946d61f670a3b491b76adede420e9d173841f/scipy-1.16.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:e9f414cbe9ca289a73e0cc92e33a6a791469b6619c240aa32ee18abdce8ab451", size = 36418162 },
1680
+ { url = "https://files.pythonhosted.org/packages/19/5a/914355a74481b8e4bbccf67259bbde171348a3f160b67b4945fbc5f5c1e5/scipy-1.16.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:bbba55fb97ba3cdef9b1ee973f06b09d518c0c7c66a009c729c7d1592be1935e", size = 28465985 },
1681
+ { url = "https://files.pythonhosted.org/packages/58/46/63477fc1246063855969cbefdcee8c648ba4b17f67370bd542ba56368d0b/scipy-1.16.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:58e0d4354eacb6004e7aa1cd350e5514bd0270acaa8d5b36c0627bb3bb486974", size = 20737961 },
1682
+ { url = "https://files.pythonhosted.org/packages/93/86/0fbb5588b73555e40f9d3d6dde24ee6fac7d8e301a27f6f0cab9d8f66ff2/scipy-1.16.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:75b2094ec975c80efc273567436e16bb794660509c12c6a31eb5c195cbf4b6dc", size = 23377941 },
1683
+ { url = "https://files.pythonhosted.org/packages/ca/80/a561f2bf4c2da89fa631b3cbf31d120e21ea95db71fd9ec00cb0247c7a93/scipy-1.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b65d232157a380fdd11a560e7e21cde34fdb69d65c09cb87f6cc024ee376351", size = 33196703 },
1684
+ { url = "https://files.pythonhosted.org/packages/11/6b/3443abcd0707d52e48eb315e33cc669a95e29fc102229919646f5a501171/scipy-1.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d8747f7736accd39289943f7fe53a8333be7f15a82eea08e4afe47d79568c32", size = 35083410 },
1685
+ { url = "https://files.pythonhosted.org/packages/20/ab/eb0fc00e1e48961f1bd69b7ad7e7266896fe5bad4ead91b5fc6b3561bba4/scipy-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb9f147a1b8529bb7fec2a85cf4cf42bdfadf9e83535c309a11fdae598c88e8b", size = 35387829 },
1686
+ { url = "https://files.pythonhosted.org/packages/57/9e/d6fc64e41fad5d481c029ee5a49eefc17f0b8071d636a02ceee44d4a0de2/scipy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d2b83c37edbfa837a8923d19c749c1935ad3d41cf196006a24ed44dba2ec4358", size = 37841356 },
1687
+ { url = "https://files.pythonhosted.org/packages/7c/a7/4c94bbe91f12126b8bf6709b2471900577b7373a4fd1f431f28ba6f81115/scipy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:79a3c13d43c95aa80b87328a46031cf52508cf5f4df2767602c984ed1d3c6bbe", size = 38403710 },
1688
+ { url = "https://files.pythonhosted.org/packages/47/20/965da8497f6226e8fa90ad3447b82ed0e28d942532e92dd8b91b43f100d4/scipy-1.16.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:f91b87e1689f0370690e8470916fe1b2308e5b2061317ff76977c8f836452a47", size = 36813833 },
1689
+ { url = "https://files.pythonhosted.org/packages/28/f4/197580c3dac2d234e948806e164601c2df6f0078ed9f5ad4a62685b7c331/scipy-1.16.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:88a6ca658fb94640079e7a50b2ad3b67e33ef0f40e70bdb7dc22017dae73ac08", size = 28974431 },
1690
+ { url = "https://files.pythonhosted.org/packages/8a/fc/e18b8550048d9224426e76906694c60028dbdb65d28b1372b5503914b89d/scipy-1.16.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ae902626972f1bd7e4e86f58fd72322d7f4ec7b0cfc17b15d4b7006efc385176", size = 21246454 },
1691
+ { url = "https://files.pythonhosted.org/packages/8c/48/07b97d167e0d6a324bfd7484cd0c209cc27338b67e5deadae578cf48e809/scipy-1.16.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:8cb824c1fc75ef29893bc32b3ddd7b11cf9ab13c1127fe26413a05953b8c32ed", size = 23772979 },
1692
+ { url = "https://files.pythonhosted.org/packages/4c/4f/9efbd3f70baf9582edf271db3002b7882c875ddd37dc97f0f675ad68679f/scipy-1.16.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:de2db7250ff6514366a9709c2cba35cb6d08498e961cba20d7cff98a7ee88938", size = 33341972 },
1693
+ { url = "https://files.pythonhosted.org/packages/3f/dc/9e496a3c5dbe24e76ee24525155ab7f659c20180bab058ef2c5fa7d9119c/scipy-1.16.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e85800274edf4db8dd2e4e93034f92d1b05c9421220e7ded9988b16976f849c1", size = 35185476 },
1694
+ { url = "https://files.pythonhosted.org/packages/ce/b3/21001cff985a122ba434c33f2c9d7d1dc3b669827e94f4fc4e1fe8b9dfd8/scipy-1.16.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4f720300a3024c237ace1cb11f9a84c38beb19616ba7c4cdcd771047a10a1706", size = 35570990 },
1695
+ { url = "https://files.pythonhosted.org/packages/e5/d3/7ba42647d6709251cdf97043d0c107e0317e152fa2f76873b656b509ff55/scipy-1.16.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aad603e9339ddb676409b104c48a027e9916ce0d2838830691f39552b38a352e", size = 37950262 },
1696
+ { url = "https://files.pythonhosted.org/packages/eb/c4/231cac7a8385394ebbbb4f1ca662203e9d8c332825ab4f36ffc3ead09a42/scipy-1.16.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f56296fefca67ba605fd74d12f7bd23636267731a72cb3947963e76b8c0a25db", size = 38515076 },
1697
+ ]
1698
+
1699
+ [[package]]
1700
+ name = "seaborn"
1701
+ version = "0.13.2"
1702
+ source = { registry = "https://pypi.org/simple" }
1703
+ dependencies = [
1704
+ { name = "matplotlib" },
1705
+ { name = "numpy" },
1706
+ { name = "pandas" },
1707
+ ]
1708
+ sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696 }
1709
+ wheels = [
1710
+ { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914 },
1711
+ ]
1712
+
1713
  [[package]]
1714
  name = "six"
1715
  version = "1.17.0"
 
1763
  { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677 },
1764
  ]
1765
 
1766
+ [[package]]
1767
+ name = "sympy"
1768
+ version = "1.14.0"
1769
+ source = { registry = "https://pypi.org/simple" }
1770
+ dependencies = [
1771
+ { name = "mpmath" },
1772
+ ]
1773
+ sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 }
1774
+ wheels = [
1775
+ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 },
1776
+ ]
1777
+
1778
  [[package]]
1779
  name = "tavily-python"
1780
  version = "0.7.9"
 
1887
  { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 },
1888
  ]
1889
 
1890
+ [[package]]
1891
+ name = "tzdata"
1892
+ version = "2025.2"
1893
+ source = { registry = "https://pypi.org/simple" }
1894
+ sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 }
1895
+ wheels = [
1896
+ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 },
1897
+ ]
1898
+
1899
  [[package]]
1900
  name = "urllib3"
1901
  version = "2.5.0"