genxai-framework 0.1.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.
- cli/__init__.py +3 -0
- cli/commands/__init__.py +6 -0
- cli/commands/approval.py +85 -0
- cli/commands/audit.py +127 -0
- cli/commands/metrics.py +25 -0
- cli/commands/tool.py +389 -0
- cli/main.py +32 -0
- genxai/__init__.py +81 -0
- genxai/api/__init__.py +5 -0
- genxai/api/app.py +21 -0
- genxai/config/__init__.py +5 -0
- genxai/config/settings.py +37 -0
- genxai/connectors/__init__.py +19 -0
- genxai/connectors/base.py +122 -0
- genxai/connectors/kafka.py +92 -0
- genxai/connectors/postgres_cdc.py +95 -0
- genxai/connectors/registry.py +44 -0
- genxai/connectors/sqs.py +94 -0
- genxai/connectors/webhook.py +73 -0
- genxai/core/__init__.py +37 -0
- genxai/core/agent/__init__.py +32 -0
- genxai/core/agent/base.py +206 -0
- genxai/core/agent/config_io.py +59 -0
- genxai/core/agent/registry.py +98 -0
- genxai/core/agent/runtime.py +970 -0
- genxai/core/communication/__init__.py +6 -0
- genxai/core/communication/collaboration.py +44 -0
- genxai/core/communication/message_bus.py +192 -0
- genxai/core/communication/protocols.py +35 -0
- genxai/core/execution/__init__.py +22 -0
- genxai/core/execution/metadata.py +181 -0
- genxai/core/execution/queue.py +201 -0
- genxai/core/graph/__init__.py +30 -0
- genxai/core/graph/checkpoints.py +77 -0
- genxai/core/graph/edges.py +131 -0
- genxai/core/graph/engine.py +813 -0
- genxai/core/graph/executor.py +516 -0
- genxai/core/graph/nodes.py +161 -0
- genxai/core/graph/trigger_runner.py +40 -0
- genxai/core/memory/__init__.py +19 -0
- genxai/core/memory/base.py +72 -0
- genxai/core/memory/embedding.py +327 -0
- genxai/core/memory/episodic.py +448 -0
- genxai/core/memory/long_term.py +467 -0
- genxai/core/memory/manager.py +543 -0
- genxai/core/memory/persistence.py +297 -0
- genxai/core/memory/procedural.py +461 -0
- genxai/core/memory/semantic.py +526 -0
- genxai/core/memory/shared.py +62 -0
- genxai/core/memory/short_term.py +303 -0
- genxai/core/memory/vector_store.py +508 -0
- genxai/core/memory/working.py +211 -0
- genxai/core/state/__init__.py +6 -0
- genxai/core/state/manager.py +293 -0
- genxai/core/state/schema.py +115 -0
- genxai/llm/__init__.py +14 -0
- genxai/llm/base.py +150 -0
- genxai/llm/factory.py +329 -0
- genxai/llm/providers/__init__.py +1 -0
- genxai/llm/providers/anthropic.py +249 -0
- genxai/llm/providers/cohere.py +274 -0
- genxai/llm/providers/google.py +334 -0
- genxai/llm/providers/ollama.py +147 -0
- genxai/llm/providers/openai.py +257 -0
- genxai/llm/routing.py +83 -0
- genxai/observability/__init__.py +6 -0
- genxai/observability/logging.py +327 -0
- genxai/observability/metrics.py +494 -0
- genxai/observability/tracing.py +372 -0
- genxai/performance/__init__.py +39 -0
- genxai/performance/cache.py +256 -0
- genxai/performance/pooling.py +289 -0
- genxai/security/audit.py +304 -0
- genxai/security/auth.py +315 -0
- genxai/security/cost_control.py +528 -0
- genxai/security/default_policies.py +44 -0
- genxai/security/jwt.py +142 -0
- genxai/security/oauth.py +226 -0
- genxai/security/pii.py +366 -0
- genxai/security/policy_engine.py +82 -0
- genxai/security/rate_limit.py +341 -0
- genxai/security/rbac.py +247 -0
- genxai/security/validation.py +218 -0
- genxai/tools/__init__.py +21 -0
- genxai/tools/base.py +383 -0
- genxai/tools/builtin/__init__.py +131 -0
- genxai/tools/builtin/communication/__init__.py +15 -0
- genxai/tools/builtin/communication/email_sender.py +159 -0
- genxai/tools/builtin/communication/notification_manager.py +167 -0
- genxai/tools/builtin/communication/slack_notifier.py +118 -0
- genxai/tools/builtin/communication/sms_sender.py +118 -0
- genxai/tools/builtin/communication/webhook_caller.py +136 -0
- genxai/tools/builtin/computation/__init__.py +15 -0
- genxai/tools/builtin/computation/calculator.py +101 -0
- genxai/tools/builtin/computation/code_executor.py +183 -0
- genxai/tools/builtin/computation/data_validator.py +259 -0
- genxai/tools/builtin/computation/hash_generator.py +129 -0
- genxai/tools/builtin/computation/regex_matcher.py +201 -0
- genxai/tools/builtin/data/__init__.py +15 -0
- genxai/tools/builtin/data/csv_processor.py +213 -0
- genxai/tools/builtin/data/data_transformer.py +299 -0
- genxai/tools/builtin/data/json_processor.py +233 -0
- genxai/tools/builtin/data/text_analyzer.py +288 -0
- genxai/tools/builtin/data/xml_processor.py +175 -0
- genxai/tools/builtin/database/__init__.py +15 -0
- genxai/tools/builtin/database/database_inspector.py +157 -0
- genxai/tools/builtin/database/mongodb_query.py +196 -0
- genxai/tools/builtin/database/redis_cache.py +167 -0
- genxai/tools/builtin/database/sql_query.py +145 -0
- genxai/tools/builtin/database/vector_search.py +163 -0
- genxai/tools/builtin/file/__init__.py +17 -0
- genxai/tools/builtin/file/directory_scanner.py +214 -0
- genxai/tools/builtin/file/file_compressor.py +237 -0
- genxai/tools/builtin/file/file_reader.py +102 -0
- genxai/tools/builtin/file/file_writer.py +122 -0
- genxai/tools/builtin/file/image_processor.py +186 -0
- genxai/tools/builtin/file/pdf_parser.py +144 -0
- genxai/tools/builtin/test/__init__.py +15 -0
- genxai/tools/builtin/test/async_simulator.py +62 -0
- genxai/tools/builtin/test/data_transformer.py +99 -0
- genxai/tools/builtin/test/error_generator.py +82 -0
- genxai/tools/builtin/test/simple_math.py +94 -0
- genxai/tools/builtin/test/string_processor.py +72 -0
- genxai/tools/builtin/web/__init__.py +15 -0
- genxai/tools/builtin/web/api_caller.py +161 -0
- genxai/tools/builtin/web/html_parser.py +330 -0
- genxai/tools/builtin/web/http_client.py +187 -0
- genxai/tools/builtin/web/url_validator.py +162 -0
- genxai/tools/builtin/web/web_scraper.py +170 -0
- genxai/tools/custom/my_test_tool_2.py +9 -0
- genxai/tools/dynamic.py +105 -0
- genxai/tools/mcp_server.py +167 -0
- genxai/tools/persistence/__init__.py +6 -0
- genxai/tools/persistence/models.py +55 -0
- genxai/tools/persistence/service.py +322 -0
- genxai/tools/registry.py +227 -0
- genxai/tools/security/__init__.py +11 -0
- genxai/tools/security/limits.py +214 -0
- genxai/tools/security/policy.py +20 -0
- genxai/tools/security/sandbox.py +248 -0
- genxai/tools/templates.py +435 -0
- genxai/triggers/__init__.py +19 -0
- genxai/triggers/base.py +104 -0
- genxai/triggers/file_watcher.py +75 -0
- genxai/triggers/queue.py +68 -0
- genxai/triggers/registry.py +82 -0
- genxai/triggers/schedule.py +66 -0
- genxai/triggers/webhook.py +68 -0
- genxai/utils/__init__.py +1 -0
- genxai/utils/tokens.py +295 -0
- genxai_framework-0.1.0.dist-info/METADATA +495 -0
- genxai_framework-0.1.0.dist-info/RECORD +156 -0
- genxai_framework-0.1.0.dist-info/WHEEL +5 -0
- genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
- genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
- genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""Service for tool persistence and management."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
from typing import List, Optional, Dict, Any
|
|
6
|
+
from sqlalchemy import create_engine
|
|
7
|
+
from sqlalchemy.orm import sessionmaker, Session
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from genxai.tools.persistence.models import Base, ToolModel
|
|
11
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
12
|
+
from genxai.tools.registry import ToolRegistry
|
|
13
|
+
from genxai.tools.dynamic import DynamicTool
|
|
14
|
+
from genxai.tools.templates import create_tool_from_template
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Database setup - now in genxai/data/
|
|
19
|
+
DB_PATH = Path(__file__).parent.parent.parent / "data" / "tools.db"
|
|
20
|
+
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
DATABASE_URL = f"sqlite:///{DB_PATH}"
|
|
22
|
+
|
|
23
|
+
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
|
24
|
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
25
|
+
|
|
26
|
+
# Create tables
|
|
27
|
+
Base.metadata.create_all(bind=engine)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ToolService:
|
|
31
|
+
"""Service for managing tool persistence."""
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def get_db() -> Session:
|
|
35
|
+
"""Get database session."""
|
|
36
|
+
db = SessionLocal()
|
|
37
|
+
try:
|
|
38
|
+
return db
|
|
39
|
+
finally:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def save_tool(
|
|
44
|
+
name: str,
|
|
45
|
+
description: str,
|
|
46
|
+
category: str,
|
|
47
|
+
tags: List[str],
|
|
48
|
+
version: str,
|
|
49
|
+
author: str,
|
|
50
|
+
tool_type: str,
|
|
51
|
+
code: Optional[str] = None,
|
|
52
|
+
parameters: Optional[List[Dict[str, Any]]] = None,
|
|
53
|
+
template_name: Optional[str] = None,
|
|
54
|
+
template_config: Optional[Dict[str, Any]] = None,
|
|
55
|
+
) -> ToolModel:
|
|
56
|
+
"""Save tool to database.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
name: Tool name
|
|
60
|
+
description: Tool description
|
|
61
|
+
category: Tool category
|
|
62
|
+
tags: Tool tags
|
|
63
|
+
version: Tool version
|
|
64
|
+
author: Tool author
|
|
65
|
+
tool_type: "code_based" or "template_based"
|
|
66
|
+
code: Python code (for code-based tools)
|
|
67
|
+
parameters: Tool parameters (for code-based tools)
|
|
68
|
+
template_name: Template name (for template-based tools)
|
|
69
|
+
template_config: Template configuration (for template-based tools)
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Created ToolModel instance
|
|
73
|
+
"""
|
|
74
|
+
db = SessionLocal()
|
|
75
|
+
try:
|
|
76
|
+
tool_model = ToolModel(
|
|
77
|
+
name=name,
|
|
78
|
+
description=description,
|
|
79
|
+
category=category,
|
|
80
|
+
tags=tags,
|
|
81
|
+
version=version,
|
|
82
|
+
author=author,
|
|
83
|
+
tool_type=tool_type,
|
|
84
|
+
code=code,
|
|
85
|
+
parameters=parameters or [],
|
|
86
|
+
template_name=template_name,
|
|
87
|
+
template_config=template_config,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
db.add(tool_model)
|
|
91
|
+
db.commit()
|
|
92
|
+
db.refresh(tool_model)
|
|
93
|
+
|
|
94
|
+
logger.info(f"Saved tool to database: {name}")
|
|
95
|
+
|
|
96
|
+
# Optionally export to file
|
|
97
|
+
ToolService._export_to_file(tool_model)
|
|
98
|
+
|
|
99
|
+
return tool_model
|
|
100
|
+
except Exception as e:
|
|
101
|
+
db.rollback()
|
|
102
|
+
logger.error(f"Failed to save tool: {e}")
|
|
103
|
+
raise
|
|
104
|
+
finally:
|
|
105
|
+
db.close()
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def get_tool(name: str) -> Optional[ToolModel]:
|
|
109
|
+
"""Get tool from database by name.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
name: Tool name
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
ToolModel instance or None
|
|
116
|
+
"""
|
|
117
|
+
db = SessionLocal()
|
|
118
|
+
try:
|
|
119
|
+
return db.query(ToolModel).filter(ToolModel.name == name).first()
|
|
120
|
+
finally:
|
|
121
|
+
db.close()
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def list_tools() -> List[ToolModel]:
|
|
125
|
+
"""List all tools from database.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
List of ToolModel instances
|
|
129
|
+
"""
|
|
130
|
+
db = SessionLocal()
|
|
131
|
+
try:
|
|
132
|
+
return db.query(ToolModel).all()
|
|
133
|
+
finally:
|
|
134
|
+
db.close()
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def update_tool_code(name: str, code: str) -> bool:
|
|
138
|
+
"""Update tool code in database.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
name: Tool name
|
|
142
|
+
code: New Python code
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
True if updated, False if not found
|
|
146
|
+
"""
|
|
147
|
+
db = SessionLocal()
|
|
148
|
+
try:
|
|
149
|
+
tool = db.query(ToolModel).filter(ToolModel.name == name).first()
|
|
150
|
+
if tool and tool.tool_type == "code_based":
|
|
151
|
+
tool.code = code
|
|
152
|
+
db.commit()
|
|
153
|
+
logger.info(f"Updated tool code in database: {name}")
|
|
154
|
+
|
|
155
|
+
# Update file if exists
|
|
156
|
+
ToolService._export_to_file(tool)
|
|
157
|
+
|
|
158
|
+
return True
|
|
159
|
+
return False
|
|
160
|
+
except Exception as e:
|
|
161
|
+
db.rollback()
|
|
162
|
+
logger.error(f"Failed to update tool code: {e}")
|
|
163
|
+
raise
|
|
164
|
+
finally:
|
|
165
|
+
db.close()
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def delete_tool(name: str) -> bool:
|
|
169
|
+
"""Delete tool from database.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
name: Tool name
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
True if deleted, False if not found
|
|
176
|
+
"""
|
|
177
|
+
db = SessionLocal()
|
|
178
|
+
try:
|
|
179
|
+
tool = db.query(ToolModel).filter(ToolModel.name == name).first()
|
|
180
|
+
if tool:
|
|
181
|
+
db.delete(tool)
|
|
182
|
+
db.commit()
|
|
183
|
+
logger.info(f"Deleted tool from database: {name}")
|
|
184
|
+
|
|
185
|
+
# Delete file if exists
|
|
186
|
+
ToolService._delete_file(name)
|
|
187
|
+
|
|
188
|
+
return True
|
|
189
|
+
return False
|
|
190
|
+
except Exception as e:
|
|
191
|
+
db.rollback()
|
|
192
|
+
logger.error(f"Failed to delete tool: {e}")
|
|
193
|
+
raise
|
|
194
|
+
finally:
|
|
195
|
+
db.close()
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def load_tool_to_registry(tool_model: ToolModel) -> Tool:
|
|
199
|
+
"""Load tool from database model to registry.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
tool_model: ToolModel instance
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Tool instance
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
category = ToolCategory(tool_model.category)
|
|
209
|
+
|
|
210
|
+
if tool_model.tool_type == "code_based":
|
|
211
|
+
# Create code-based tool
|
|
212
|
+
metadata = ToolMetadata(
|
|
213
|
+
name=tool_model.name,
|
|
214
|
+
description=tool_model.description,
|
|
215
|
+
category=category,
|
|
216
|
+
tags=tool_model.tags,
|
|
217
|
+
version=tool_model.version,
|
|
218
|
+
author=tool_model.author,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
parameters = [
|
|
222
|
+
ToolParameter(
|
|
223
|
+
name=p["name"],
|
|
224
|
+
type=p["type"],
|
|
225
|
+
description=p["description"],
|
|
226
|
+
required=p.get("required", True),
|
|
227
|
+
default=p.get("default"),
|
|
228
|
+
enum=p.get("enum"),
|
|
229
|
+
min_value=p.get("min_value"),
|
|
230
|
+
max_value=p.get("max_value"),
|
|
231
|
+
pattern=p.get("pattern"),
|
|
232
|
+
)
|
|
233
|
+
for p in tool_model.parameters
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
tool = DynamicTool(metadata, parameters, tool_model.code)
|
|
237
|
+
|
|
238
|
+
elif tool_model.tool_type == "template_based":
|
|
239
|
+
# Create template-based tool
|
|
240
|
+
tool = create_tool_from_template(
|
|
241
|
+
name=tool_model.name,
|
|
242
|
+
description=tool_model.description,
|
|
243
|
+
category=category,
|
|
244
|
+
tags=tool_model.tags,
|
|
245
|
+
template=tool_model.template_name,
|
|
246
|
+
config=tool_model.template_config or {},
|
|
247
|
+
)
|
|
248
|
+
else:
|
|
249
|
+
raise ValueError(f"Unknown tool type: {tool_model.tool_type}")
|
|
250
|
+
|
|
251
|
+
# Register in registry
|
|
252
|
+
ToolRegistry.register(tool)
|
|
253
|
+
logger.info(f"Loaded tool to registry: {tool_model.name}")
|
|
254
|
+
|
|
255
|
+
return tool
|
|
256
|
+
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logger.error(f"Failed to load tool {tool_model.name}: {e}")
|
|
259
|
+
raise
|
|
260
|
+
|
|
261
|
+
@staticmethod
|
|
262
|
+
def load_all_tools():
|
|
263
|
+
"""Load all tools from database to registry."""
|
|
264
|
+
tools = ToolService.list_tools()
|
|
265
|
+
loaded_count = 0
|
|
266
|
+
|
|
267
|
+
for tool_model in tools:
|
|
268
|
+
try:
|
|
269
|
+
ToolService.load_tool_to_registry(tool_model)
|
|
270
|
+
loaded_count += 1
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.error(f"Failed to load tool {tool_model.name}: {e}")
|
|
273
|
+
|
|
274
|
+
logger.info(f"Loaded {loaded_count}/{len(tools)} tools from database")
|
|
275
|
+
|
|
276
|
+
@staticmethod
|
|
277
|
+
def _export_to_file(tool_model: ToolModel):
|
|
278
|
+
"""Export tool to file system (optional backup).
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
tool_model: ToolModel instance
|
|
282
|
+
"""
|
|
283
|
+
try:
|
|
284
|
+
custom_dir = Path(__file__).parent.parent / "custom"
|
|
285
|
+
custom_dir.mkdir(parents=True, exist_ok=True)
|
|
286
|
+
|
|
287
|
+
file_path = custom_dir / f"{tool_model.name}.py"
|
|
288
|
+
|
|
289
|
+
if tool_model.tool_type == "code_based":
|
|
290
|
+
content = f'''"""
|
|
291
|
+
Auto-generated tool: {tool_model.name}
|
|
292
|
+
Description: {tool_model.description}
|
|
293
|
+
Category: {tool_model.category}
|
|
294
|
+
Created: {tool_model.created_at}
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
# Tool code
|
|
298
|
+
{tool_model.code}
|
|
299
|
+
'''
|
|
300
|
+
file_path.write_text(content)
|
|
301
|
+
logger.info(f"Exported tool to file: {file_path}")
|
|
302
|
+
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.warning(f"Failed to export tool to file: {e}")
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def _delete_file(name: str):
|
|
308
|
+
"""Delete tool file if exists.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
name: Tool name
|
|
312
|
+
"""
|
|
313
|
+
try:
|
|
314
|
+
custom_dir = Path(__file__).parent.parent / "custom"
|
|
315
|
+
file_path = custom_dir / f"{name}.py"
|
|
316
|
+
|
|
317
|
+
if file_path.exists():
|
|
318
|
+
file_path.unlink()
|
|
319
|
+
logger.info(f"Deleted tool file: {file_path}")
|
|
320
|
+
|
|
321
|
+
except Exception as e:
|
|
322
|
+
logger.warning(f"Failed to delete tool file: {e}")
|
genxai/tools/registry.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Tool registry for managing available tools."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from genxai.tools.base import Tool, ToolCategory
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ToolRegistry:
|
|
12
|
+
"""Central registry for all tools."""
|
|
13
|
+
|
|
14
|
+
SCHEMA_VERSION = "1.0"
|
|
15
|
+
_instance: Optional["ToolRegistry"] = None
|
|
16
|
+
_tools: Dict[str, Tool] = {}
|
|
17
|
+
|
|
18
|
+
def __new__(cls) -> "ToolRegistry":
|
|
19
|
+
"""Singleton pattern for tool registry."""
|
|
20
|
+
if cls._instance is None:
|
|
21
|
+
cls._instance = super().__new__(cls)
|
|
22
|
+
return cls._instance
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def register(cls, tool: Tool) -> None:
|
|
26
|
+
"""Register a new tool.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
tool: Tool to register
|
|
30
|
+
"""
|
|
31
|
+
if tool.metadata.name in cls._tools:
|
|
32
|
+
logger.warning(
|
|
33
|
+
f"Tool {tool.metadata.name} already registered, overwriting"
|
|
34
|
+
)
|
|
35
|
+
cls._tools[tool.metadata.name] = tool
|
|
36
|
+
logger.info(f"Registered tool: {tool.metadata.name}")
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def unregister(cls, name: str) -> None:
|
|
40
|
+
"""Unregister a tool.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
name: Tool name to unregister
|
|
44
|
+
"""
|
|
45
|
+
if name in cls._tools:
|
|
46
|
+
del cls._tools[name]
|
|
47
|
+
logger.info(f"Unregistered tool: {name}")
|
|
48
|
+
else:
|
|
49
|
+
logger.warning(f"Tool {name} not found in registry")
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def get(cls, name: str) -> Optional[Tool]:
|
|
53
|
+
"""Get tool by name.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
name: Tool name
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Tool instance or None if not found
|
|
60
|
+
"""
|
|
61
|
+
return cls._tools.get(name)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def list_all(cls) -> List[Tool]:
|
|
65
|
+
"""List all registered tools.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
List of all tools
|
|
69
|
+
"""
|
|
70
|
+
return list(cls._tools.values())
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def search(
|
|
74
|
+
cls, query: str, category: Optional[ToolCategory] = None
|
|
75
|
+
) -> List[Tool]:
|
|
76
|
+
"""Search tools by query and category.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
query: Search query (searches name, description, tags)
|
|
80
|
+
category: Optional category filter
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
List of matching tools
|
|
84
|
+
"""
|
|
85
|
+
results = []
|
|
86
|
+
query_lower = query.lower()
|
|
87
|
+
|
|
88
|
+
for tool in cls._tools.values():
|
|
89
|
+
# Filter by category
|
|
90
|
+
if category and tool.metadata.category != category:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# Search in name, description, and tags
|
|
94
|
+
if (
|
|
95
|
+
query_lower in tool.metadata.name.lower()
|
|
96
|
+
or query_lower in tool.metadata.description.lower()
|
|
97
|
+
or any(query_lower in tag.lower() for tag in tool.metadata.tags)
|
|
98
|
+
):
|
|
99
|
+
results.append(tool)
|
|
100
|
+
|
|
101
|
+
logger.debug(f"Found {len(results)} tools matching '{query}'")
|
|
102
|
+
return results
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def list_categories(cls) -> List[ToolCategory]:
|
|
106
|
+
"""List all tool categories in use.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
List of categories
|
|
110
|
+
"""
|
|
111
|
+
return list(set(tool.metadata.category for tool in cls._tools.values()))
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def get_by_category(cls, category: ToolCategory) -> List[Tool]:
|
|
115
|
+
"""Get all tools in a category.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
category: Tool category
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
List of tools in category
|
|
122
|
+
"""
|
|
123
|
+
return [
|
|
124
|
+
tool for tool in cls._tools.values() if tool.metadata.category == category
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def get_by_tag(cls, tag: str) -> List[Tool]:
|
|
129
|
+
"""Get all tools with a specific tag.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
tag: Tag to search for
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
List of tools with tag
|
|
136
|
+
"""
|
|
137
|
+
return [
|
|
138
|
+
tool
|
|
139
|
+
for tool in cls._tools.values()
|
|
140
|
+
if tag.lower() in [t.lower() for t in tool.metadata.tags]
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def clear(cls) -> None:
|
|
145
|
+
"""Clear all registered tools."""
|
|
146
|
+
cls._tools.clear()
|
|
147
|
+
logger.info("Cleared all tools from registry")
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def export_schema_bundle(cls, category: Optional[ToolCategory] = None) -> Dict[str, Any]:
|
|
151
|
+
"""Export a consolidated schema bundle for all registered tools.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Dictionary containing tool schemas and metadata.
|
|
155
|
+
"""
|
|
156
|
+
tools = [
|
|
157
|
+
tool.get_schema()
|
|
158
|
+
for tool in cls._tools.values()
|
|
159
|
+
if not category or tool.metadata.category == category
|
|
160
|
+
]
|
|
161
|
+
categories = {}
|
|
162
|
+
for tool in cls._tools.values():
|
|
163
|
+
if category and tool.metadata.category != category:
|
|
164
|
+
continue
|
|
165
|
+
category = tool.metadata.category.value
|
|
166
|
+
categories[category] = categories.get(category, 0) + 1
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
"schema_version": cls.SCHEMA_VERSION,
|
|
170
|
+
"tool_count": len(tools),
|
|
171
|
+
"categories": categories,
|
|
172
|
+
"tools": tools,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def export_schema_bundle_to_file(
|
|
177
|
+
cls,
|
|
178
|
+
path: str,
|
|
179
|
+
category: Optional[ToolCategory] = None,
|
|
180
|
+
) -> str:
|
|
181
|
+
"""Export tool schemas to a JSON file.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
path: Output file path
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Absolute path to the exported file
|
|
188
|
+
"""
|
|
189
|
+
from pathlib import Path
|
|
190
|
+
|
|
191
|
+
output_path = Path(path)
|
|
192
|
+
bundle = cls.export_schema_bundle(category=category)
|
|
193
|
+
if output_path.suffix.lower() in {".yaml", ".yml"}:
|
|
194
|
+
try:
|
|
195
|
+
import yaml
|
|
196
|
+
except ImportError as exc:
|
|
197
|
+
raise ImportError(
|
|
198
|
+
"PyYAML is required for YAML output. Install with: pip install PyYAML"
|
|
199
|
+
) from exc
|
|
200
|
+
output_path.write_text(yaml.safe_dump(bundle, sort_keys=False))
|
|
201
|
+
else:
|
|
202
|
+
import json
|
|
203
|
+
|
|
204
|
+
output_path.write_text(json.dumps(bundle, indent=2))
|
|
205
|
+
return str(output_path.resolve())
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def get_stats(cls) -> Dict[str, any]:
|
|
209
|
+
"""Get registry statistics.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Statistics dictionary
|
|
213
|
+
"""
|
|
214
|
+
category_counts = {}
|
|
215
|
+
for tool in cls._tools.values():
|
|
216
|
+
category = tool.metadata.category.value
|
|
217
|
+
category_counts[category] = category_counts.get(category, 0) + 1
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
"total_tools": len(cls._tools),
|
|
221
|
+
"categories": category_counts,
|
|
222
|
+
"tool_names": list(cls._tools.keys()),
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
def __repr__(self) -> str:
|
|
226
|
+
"""String representation."""
|
|
227
|
+
return f"ToolRegistry(tools={len(self._tools)})"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Security utilities for safe tool execution."""
|
|
2
|
+
|
|
3
|
+
from genxai.tools.security.sandbox import SafeExecutor, ExecutionTimeout
|
|
4
|
+
from genxai.tools.security.limits import ResourceLimits, ExecutionLimiter
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"SafeExecutor",
|
|
8
|
+
"ExecutionTimeout",
|
|
9
|
+
"ResourceLimits",
|
|
10
|
+
"ExecutionLimiter",
|
|
11
|
+
]
|