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.
- noesium/agents/askura_agent/__init__.py +22 -0
- noesium/agents/askura_agent/askura_agent.py +480 -0
- noesium/agents/askura_agent/conversation.py +164 -0
- noesium/agents/askura_agent/extractor.py +175 -0
- noesium/agents/askura_agent/memory.py +14 -0
- noesium/agents/askura_agent/models.py +239 -0
- noesium/agents/askura_agent/prompts.py +202 -0
- noesium/agents/askura_agent/reflection.py +234 -0
- noesium/agents/askura_agent/summarizer.py +30 -0
- noesium/agents/askura_agent/utils.py +6 -0
- noesium/agents/deep_research/__init__.py +13 -0
- noesium/agents/deep_research/agent.py +398 -0
- noesium/agents/deep_research/prompts.py +84 -0
- noesium/agents/deep_research/schemas.py +42 -0
- noesium/agents/deep_research/state.py +54 -0
- noesium/agents/search/__init__.py +5 -0
- noesium/agents/search/agent.py +474 -0
- noesium/agents/search/state.py +28 -0
- noesium/core/__init__.py +1 -1
- noesium/core/agent/base.py +10 -2
- noesium/core/goalith/decomposer/llm_decomposer.py +1 -1
- noesium/core/llm/__init__.py +1 -1
- noesium/core/llm/base.py +2 -2
- noesium/core/llm/litellm.py +42 -21
- noesium/core/llm/llamacpp.py +25 -4
- noesium/core/llm/ollama.py +43 -22
- noesium/core/llm/openai.py +25 -5
- noesium/core/llm/openrouter.py +1 -1
- noesium/core/toolify/base.py +9 -2
- noesium/core/toolify/config.py +2 -2
- noesium/core/toolify/registry.py +21 -5
- noesium/core/tracing/opik_tracing.py +7 -7
- noesium/core/vector_store/__init__.py +2 -2
- noesium/core/vector_store/base.py +1 -1
- noesium/core/vector_store/pgvector.py +10 -13
- noesium/core/vector_store/weaviate.py +2 -1
- noesium/toolkits/__init__.py +1 -0
- noesium/toolkits/arxiv_toolkit.py +310 -0
- noesium/toolkits/audio_aliyun_toolkit.py +441 -0
- noesium/toolkits/audio_toolkit.py +370 -0
- noesium/toolkits/bash_toolkit.py +332 -0
- noesium/toolkits/document_toolkit.py +454 -0
- noesium/toolkits/file_edit_toolkit.py +552 -0
- noesium/toolkits/github_toolkit.py +395 -0
- noesium/toolkits/gmail_toolkit.py +575 -0
- noesium/toolkits/image_toolkit.py +425 -0
- noesium/toolkits/memory_toolkit.py +398 -0
- noesium/toolkits/python_executor_toolkit.py +334 -0
- noesium/toolkits/search_toolkit.py +451 -0
- noesium/toolkits/serper_toolkit.py +623 -0
- noesium/toolkits/tabular_data_toolkit.py +537 -0
- noesium/toolkits/user_interaction_toolkit.py +365 -0
- noesium/toolkits/video_toolkit.py +168 -0
- noesium/toolkits/wikipedia_toolkit.py +420 -0
- {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/METADATA +56 -48
- {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/RECORD +59 -23
- {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/licenses/LICENSE +1 -1
- {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/WHEEL +0 -0
- {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
|
+
}
|