noesium 0.1.0__py3-none-any.whl → 0.2.0__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 (59) hide show
  1. noesium/agents/askura_agent/__init__.py +22 -0
  2. noesium/agents/askura_agent/askura_agent.py +480 -0
  3. noesium/agents/askura_agent/conversation.py +164 -0
  4. noesium/agents/askura_agent/extractor.py +175 -0
  5. noesium/agents/askura_agent/memory.py +14 -0
  6. noesium/agents/askura_agent/models.py +239 -0
  7. noesium/agents/askura_agent/prompts.py +202 -0
  8. noesium/agents/askura_agent/reflection.py +234 -0
  9. noesium/agents/askura_agent/summarizer.py +30 -0
  10. noesium/agents/askura_agent/utils.py +6 -0
  11. noesium/agents/deep_research/__init__.py +13 -0
  12. noesium/agents/deep_research/agent.py +398 -0
  13. noesium/agents/deep_research/prompts.py +84 -0
  14. noesium/agents/deep_research/schemas.py +42 -0
  15. noesium/agents/deep_research/state.py +54 -0
  16. noesium/agents/search/__init__.py +5 -0
  17. noesium/agents/search/agent.py +474 -0
  18. noesium/agents/search/state.py +28 -0
  19. noesium/core/__init__.py +1 -1
  20. noesium/core/agent/base.py +10 -2
  21. noesium/core/goalith/decomposer/llm_decomposer.py +1 -1
  22. noesium/core/llm/__init__.py +1 -1
  23. noesium/core/llm/base.py +2 -2
  24. noesium/core/llm/litellm.py +42 -21
  25. noesium/core/llm/llamacpp.py +25 -4
  26. noesium/core/llm/ollama.py +43 -22
  27. noesium/core/llm/openai.py +25 -5
  28. noesium/core/llm/openrouter.py +1 -1
  29. noesium/core/toolify/base.py +9 -2
  30. noesium/core/toolify/config.py +2 -2
  31. noesium/core/toolify/registry.py +21 -5
  32. noesium/core/tracing/opik_tracing.py +7 -7
  33. noesium/core/vector_store/__init__.py +2 -2
  34. noesium/core/vector_store/base.py +1 -1
  35. noesium/core/vector_store/pgvector.py +10 -13
  36. noesium/core/vector_store/weaviate.py +2 -1
  37. noesium/toolkits/__init__.py +1 -0
  38. noesium/toolkits/arxiv_toolkit.py +310 -0
  39. noesium/toolkits/audio_aliyun_toolkit.py +441 -0
  40. noesium/toolkits/audio_toolkit.py +370 -0
  41. noesium/toolkits/bash_toolkit.py +332 -0
  42. noesium/toolkits/document_toolkit.py +454 -0
  43. noesium/toolkits/file_edit_toolkit.py +552 -0
  44. noesium/toolkits/github_toolkit.py +395 -0
  45. noesium/toolkits/gmail_toolkit.py +575 -0
  46. noesium/toolkits/image_toolkit.py +425 -0
  47. noesium/toolkits/memory_toolkit.py +398 -0
  48. noesium/toolkits/python_executor_toolkit.py +334 -0
  49. noesium/toolkits/search_toolkit.py +451 -0
  50. noesium/toolkits/serper_toolkit.py +623 -0
  51. noesium/toolkits/tabular_data_toolkit.py +537 -0
  52. noesium/toolkits/user_interaction_toolkit.py +365 -0
  53. noesium/toolkits/video_toolkit.py +168 -0
  54. noesium/toolkits/wikipedia_toolkit.py +420 -0
  55. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/METADATA +56 -48
  56. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/RECORD +59 -23
  57. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/licenses/LICENSE +1 -1
  58. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/WHEEL +0 -0
  59. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,334 @@
1
+ """
2
+ Python code execution toolkit.
3
+
4
+ Provides safe execution of Python code in a controlled environment with
5
+ support for matplotlib plots, file operations, and comprehensive error handling.
6
+ """
7
+
8
+ import asyncio
9
+ import base64
10
+ import contextlib
11
+ import glob
12
+ import io
13
+ import os
14
+ import re
15
+ from typing import Any, Callable, Dict
16
+
17
+ try:
18
+ import matplotlib
19
+
20
+ matplotlib.use("Agg") # Use non-interactive backend
21
+ import matplotlib.pyplot as plt
22
+
23
+ MATPLOTLIB_AVAILABLE = True
24
+ except ImportError:
25
+ matplotlib = None
26
+ plt = None
27
+ MATPLOTLIB_AVAILABLE = False
28
+
29
+ from noesium.core.toolify.base import AsyncBaseToolkit
30
+ from noesium.core.toolify.config import ToolkitConfig
31
+ from noesium.core.toolify.registry import register_toolkit
32
+ from noesium.core.utils.logging import get_logger
33
+
34
+ logger = get_logger(__name__)
35
+
36
+ # Regex to clean ANSI escape sequences from output
37
+ ANSI_ESCAPE = re.compile(r"\x1b\[[0-9;]*[a-zA-Z]")
38
+
39
+
40
+ def _execute_python_code_sync(code: str, workdir: str) -> Dict[str, Any]:
41
+ """
42
+ Synchronous execution of Python code in a controlled environment.
43
+
44
+ This function runs in a separate thread to avoid blocking the async event loop.
45
+ It uses IPython for enhanced code execution capabilities.
46
+
47
+ Args:
48
+ code: Python code to execute
49
+ workdir: Working directory for execution
50
+
51
+ Returns:
52
+ Dictionary containing execution results
53
+ """
54
+ try:
55
+ from IPython.core.interactiveshell import InteractiveShell
56
+ from traitlets.config.loader import Config
57
+
58
+ except ImportError:
59
+ pass
60
+
61
+ original_dir = os.getcwd()
62
+
63
+ try:
64
+ # Clean up code format (remove markdown code blocks if present)
65
+ code_clean = code.strip()
66
+ if code_clean.startswith("```python"):
67
+ code_clean = code_clean.split("```python")[1].split("```")[0].strip()
68
+ elif code_clean.startswith("```"):
69
+ # Handle generic code blocks
70
+ lines = code_clean.split("\n")
71
+ if lines[0].strip() == "```":
72
+ code_clean = "\n".join(lines[1:])
73
+ if code_clean.endswith("```"):
74
+ code_clean = code_clean[:-3].strip()
75
+
76
+ # Create and change to working directory
77
+ os.makedirs(workdir, exist_ok=True)
78
+ os.chdir(workdir)
79
+
80
+ # Track files before execution
81
+ files_before = set(glob.glob("*"))
82
+
83
+ # Set up IPython shell with minimal configuration
84
+ from IPython.core.interactiveshell import InteractiveShell
85
+ from traitlets.config.loader import Config
86
+
87
+ # Clear any existing instance
88
+ InteractiveShell.clear_instance()
89
+
90
+ # Configure IPython to minimize overhead
91
+ config = Config()
92
+ config.HistoryManager.enabled = False
93
+ config.HistoryManager.hist_file = ":memory:"
94
+
95
+ shell = InteractiveShell.instance(config=config)
96
+
97
+ # Disable history manager if it exists
98
+ if hasattr(shell, "history_manager"):
99
+ shell.history_manager.enabled = False
100
+
101
+ # Capture output and errors
102
+ output = io.StringIO()
103
+ error_output = io.StringIO()
104
+
105
+ # Execute the code with output redirection
106
+ with contextlib.redirect_stdout(output), contextlib.redirect_stderr(error_output):
107
+ result = shell.run_cell(code_clean)
108
+
109
+ # Handle matplotlib plots
110
+ if plt.get_fignums():
111
+ # Save plot to file and create base64 encoding
112
+ img_buffer = io.BytesIO()
113
+ plt.savefig(img_buffer, format="png", dpi=100, bbox_inches="tight")
114
+ img_base64 = base64.b64encode(img_buffer.getvalue()).decode("utf-8")
115
+ plt.close("all") # Close all figures
116
+
117
+ # Save image file with unique name
118
+ image_name = "output_image.png"
119
+ counter = 1
120
+ while os.path.exists(image_name):
121
+ image_name = f"output_image_{counter}.png"
122
+ counter += 1
123
+
124
+ with open(image_name, "wb") as f:
125
+ f.write(base64.b64decode(img_base64))
126
+
127
+ # Get output and clean ANSI escape sequences
128
+ stdout_result = ANSI_ESCAPE.sub("", output.getvalue())
129
+ stderr_result = ANSI_ESCAPE.sub("", error_output.getvalue())
130
+
131
+ # Track new files created during execution
132
+ files_after = set(glob.glob("*"))
133
+ new_files = list(files_after - files_before)
134
+ new_files = [os.path.join(workdir, f) for f in new_files]
135
+
136
+ # Clean up IPython instance
137
+ try:
138
+ shell.atexit_operations = lambda: None
139
+ if hasattr(shell, "history_manager") and shell.history_manager:
140
+ shell.history_manager.enabled = False
141
+ shell.history_manager.end_session = lambda: None
142
+ InteractiveShell.clear_instance()
143
+ except Exception:
144
+ pass # Ignore cleanup errors
145
+
146
+ # Determine if execution was successful
147
+ has_error = (
148
+ result.error_before_exec is not None
149
+ or result.error_in_exec is not None
150
+ or "Error" in stderr_result
151
+ or ("Error" in stdout_result and "Traceback" in stdout_result)
152
+ )
153
+
154
+ return {
155
+ "success": not has_error,
156
+ "message": (
157
+ f"Code execution completed\nOutput:\n{stdout_result.strip()}"
158
+ if stdout_result.strip()
159
+ else "Code execution completed, no output"
160
+ ),
161
+ "stdout": stdout_result.strip(),
162
+ "stderr": stderr_result.strip(),
163
+ "status": not has_error,
164
+ "files": new_files,
165
+ "error": stderr_result.strip() if stderr_result.strip() else "",
166
+ }
167
+
168
+ except Exception as e:
169
+ return {
170
+ "success": False,
171
+ "message": f"Code execution failed: {str(e)}",
172
+ "stdout": "",
173
+ "stderr": str(e),
174
+ "status": False,
175
+ "files": [],
176
+ "error": str(e),
177
+ }
178
+ finally:
179
+ # Always restore original directory
180
+ os.chdir(original_dir)
181
+
182
+
183
+ @register_toolkit("python_executor")
184
+ class PythonExecutorToolkit(AsyncBaseToolkit):
185
+ """
186
+ Toolkit for executing Python code in a controlled environment.
187
+
188
+ Features:
189
+ - Safe code execution using IPython
190
+ - Automatic matplotlib plot handling
191
+ - File creation tracking
192
+ - Comprehensive error handling
193
+ - Timeout protection
194
+ - ANSI escape sequence cleaning
195
+
196
+ The toolkit executes code in a separate thread to avoid blocking
197
+ the async event loop, making it suitable for use in async applications.
198
+ """
199
+
200
+ def __init__(self, config: ToolkitConfig = None):
201
+ """
202
+ Initialize the Python executor toolkit.
203
+
204
+ Args:
205
+ config: Toolkit configuration
206
+ """
207
+ super().__init__(config)
208
+
209
+ # Get configuration parameters
210
+ self.default_workdir = self.config.config.get("default_workdir", "./run_workdir")
211
+ self.default_timeout = self.config.config.get("default_timeout", 30)
212
+ self.max_timeout = self.config.config.get("max_timeout", 300) # 5 minutes max
213
+
214
+ async def execute_python_code(self, code: str, workdir: str = None, timeout: int = None) -> Dict[str, Any]:
215
+ """
216
+ Execute Python code and return comprehensive results.
217
+
218
+ This tool provides a safe environment for running Python code with
219
+ automatic handling of plots, file operations, and error reporting.
220
+
221
+ Features:
222
+ - Executes code in isolated working directory
223
+ - Captures stdout, stderr, and return values
224
+ - Automatically saves matplotlib plots as PNG files
225
+ - Tracks newly created files
226
+ - Cleans ANSI escape sequences from output
227
+ - Provides timeout protection
228
+
229
+ Args:
230
+ code: Python code to execute (supports markdown code blocks)
231
+ workdir: Working directory for execution (default: "./run_workdir")
232
+ timeout: Execution timeout in seconds (default: 30, max: 300)
233
+
234
+ Returns:
235
+ Dictionary containing:
236
+ - success: Boolean indicating if execution succeeded
237
+ - message: Human-readable execution summary
238
+ - stdout: Standard output from the code
239
+ - stderr: Standard error from the code
240
+ - status: Boolean execution status
241
+ - files: List of newly created files
242
+ - error: Error message if execution failed
243
+
244
+ Example:
245
+ ```python
246
+ result = await execute_python_code('''
247
+ import matplotlib.pyplot as plt
248
+ import numpy as np
249
+
250
+ x = np.linspace(0, 10, 100)
251
+ y = np.sin(x)
252
+
253
+ plt.figure(figsize=(8, 6))
254
+ plt.plot(x, y)
255
+ plt.title("Sine Wave")
256
+ plt.xlabel("x")
257
+ plt.ylabel("sin(x)")
258
+ plt.grid(True)
259
+ plt.show()
260
+
261
+ print("Plot created successfully!")
262
+ ''')
263
+ ```
264
+ """
265
+ # Use defaults if not provided
266
+ if workdir is None:
267
+ workdir = self.default_workdir
268
+ if timeout is None:
269
+ timeout = self.default_timeout
270
+
271
+ # Enforce maximum timeout
272
+ timeout = min(timeout, self.max_timeout)
273
+
274
+ self.logger.info(f"Executing Python code in {workdir} with {timeout}s timeout")
275
+ self.logger.debug(f"Code to execute:\n{code[:200]}{'...' if len(code) > 200 else ''}")
276
+
277
+ try:
278
+ # Run code execution in thread pool to avoid blocking
279
+ loop = asyncio.get_running_loop()
280
+ result = await asyncio.wait_for(
281
+ loop.run_in_executor(
282
+ None, # Use default thread pool executor
283
+ _execute_python_code_sync,
284
+ code,
285
+ workdir,
286
+ ),
287
+ timeout=timeout,
288
+ )
289
+
290
+ # Log execution results
291
+ if result["success"]:
292
+ self.logger.info("Python code executed successfully")
293
+ if result["files"]:
294
+ self.logger.info(f"Created files: {result['files']}")
295
+ else:
296
+ self.logger.warning(f"Python code execution failed: {result['error']}")
297
+
298
+ return result
299
+
300
+ except asyncio.TimeoutError:
301
+ error_msg = f"Code execution timed out after {timeout} seconds"
302
+ self.logger.error(error_msg)
303
+ return {
304
+ "success": False,
305
+ "message": error_msg,
306
+ "stdout": "",
307
+ "stderr": error_msg,
308
+ "status": False,
309
+ "files": [],
310
+ "error": error_msg,
311
+ }
312
+ except Exception as e:
313
+ error_msg = f"Unexpected error during code execution: {str(e)}"
314
+ self.logger.error(error_msg)
315
+ return {
316
+ "success": False,
317
+ "message": error_msg,
318
+ "stdout": "",
319
+ "stderr": str(e),
320
+ "status": False,
321
+ "files": [],
322
+ "error": str(e),
323
+ }
324
+
325
+ async def get_tools_map(self) -> Dict[str, Callable]:
326
+ """
327
+ Get the mapping of tool names to their implementation functions.
328
+
329
+ Returns:
330
+ Dictionary mapping tool names to callable functions
331
+ """
332
+ return {
333
+ "execute_python_code": self.execute_python_code,
334
+ }