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,15 @@
|
|
|
1
|
+
"""Database tools for GenXAI."""
|
|
2
|
+
|
|
3
|
+
from genxai.tools.builtin.database.sql_query import SQLQueryTool
|
|
4
|
+
from genxai.tools.builtin.database.redis_cache import RedisCacheTool
|
|
5
|
+
from genxai.tools.builtin.database.mongodb_query import MongoDBQueryTool
|
|
6
|
+
from genxai.tools.builtin.database.vector_search import VectorSearchTool
|
|
7
|
+
from genxai.tools.builtin.database.database_inspector import DatabaseInspectorTool
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"SQLQueryTool",
|
|
11
|
+
"RedisCacheTool",
|
|
12
|
+
"MongoDBQueryTool",
|
|
13
|
+
"VectorSearchTool",
|
|
14
|
+
"DatabaseInspectorTool",
|
|
15
|
+
]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Database inspector tool for examining database schemas."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DatabaseInspectorTool(Tool):
|
|
12
|
+
"""Inspect database schemas, tables, and metadata."""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
"""Initialize database inspector tool."""
|
|
16
|
+
metadata = ToolMetadata(
|
|
17
|
+
name="database_inspector",
|
|
18
|
+
description="Inspect database schemas, tables, columns, and indexes",
|
|
19
|
+
category=ToolCategory.DATABASE,
|
|
20
|
+
tags=["database", "schema", "inspect", "metadata", "tables"],
|
|
21
|
+
version="1.0.0",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parameters = [
|
|
25
|
+
ToolParameter(
|
|
26
|
+
name="connection_string",
|
|
27
|
+
type="string",
|
|
28
|
+
description="Database connection string",
|
|
29
|
+
required=True,
|
|
30
|
+
),
|
|
31
|
+
ToolParameter(
|
|
32
|
+
name="operation",
|
|
33
|
+
type="string",
|
|
34
|
+
description="Inspection operation",
|
|
35
|
+
required=True,
|
|
36
|
+
enum=["list_tables", "describe_table", "list_indexes", "database_info"],
|
|
37
|
+
),
|
|
38
|
+
ToolParameter(
|
|
39
|
+
name="table_name",
|
|
40
|
+
type="string",
|
|
41
|
+
description="Table name (for describe_table and list_indexes)",
|
|
42
|
+
required=False,
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
super().__init__(metadata, parameters)
|
|
47
|
+
|
|
48
|
+
async def _execute(
|
|
49
|
+
self,
|
|
50
|
+
connection_string: str,
|
|
51
|
+
operation: str,
|
|
52
|
+
table_name: str = None,
|
|
53
|
+
) -> Dict[str, Any]:
|
|
54
|
+
"""Execute database inspection.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
connection_string: Database connection string
|
|
58
|
+
operation: Inspection operation
|
|
59
|
+
table_name: Table name
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Dictionary containing inspection results
|
|
63
|
+
"""
|
|
64
|
+
engine = None
|
|
65
|
+
try:
|
|
66
|
+
from sqlalchemy import create_engine, inspect
|
|
67
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
68
|
+
except ImportError:
|
|
69
|
+
raise ImportError(
|
|
70
|
+
"sqlalchemy package not installed. Install with: pip install sqlalchemy"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
result: Dict[str, Any] = {
|
|
74
|
+
"operation": operation,
|
|
75
|
+
"success": False,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
# Create engine and inspector
|
|
80
|
+
engine = create_engine(connection_string)
|
|
81
|
+
inspector = inspect(engine)
|
|
82
|
+
|
|
83
|
+
if operation == "list_tables":
|
|
84
|
+
tables = inspector.get_table_names()
|
|
85
|
+
result.update({
|
|
86
|
+
"tables": tables,
|
|
87
|
+
"count": len(tables),
|
|
88
|
+
"success": True,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
elif operation == "describe_table":
|
|
92
|
+
if not table_name:
|
|
93
|
+
raise ValueError("table_name required for describe_table operation")
|
|
94
|
+
|
|
95
|
+
columns = inspector.get_columns(table_name)
|
|
96
|
+
column_info = [
|
|
97
|
+
{
|
|
98
|
+
"name": col["name"],
|
|
99
|
+
"type": str(col["type"]),
|
|
100
|
+
"nullable": col.get("nullable", True),
|
|
101
|
+
"default": str(col.get("default")) if col.get("default") else None,
|
|
102
|
+
"primary_key": col.get("primary_key", False),
|
|
103
|
+
}
|
|
104
|
+
for col in columns
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
result.update({
|
|
108
|
+
"table": table_name,
|
|
109
|
+
"columns": column_info,
|
|
110
|
+
"column_count": len(column_info),
|
|
111
|
+
"success": True,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
elif operation == "list_indexes":
|
|
115
|
+
if not table_name:
|
|
116
|
+
raise ValueError("table_name required for list_indexes operation")
|
|
117
|
+
|
|
118
|
+
indexes = inspector.get_indexes(table_name)
|
|
119
|
+
index_info = [
|
|
120
|
+
{
|
|
121
|
+
"name": idx["name"],
|
|
122
|
+
"columns": idx["column_names"],
|
|
123
|
+
"unique": idx.get("unique", False),
|
|
124
|
+
}
|
|
125
|
+
for idx in indexes
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
result.update({
|
|
129
|
+
"table": table_name,
|
|
130
|
+
"indexes": index_info,
|
|
131
|
+
"index_count": len(index_info),
|
|
132
|
+
"success": True,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
elif operation == "database_info":
|
|
136
|
+
tables = inspector.get_table_names()
|
|
137
|
+
views = inspector.get_view_names()
|
|
138
|
+
|
|
139
|
+
result.update({
|
|
140
|
+
"tables": tables,
|
|
141
|
+
"table_count": len(tables),
|
|
142
|
+
"views": views,
|
|
143
|
+
"view_count": len(views),
|
|
144
|
+
"dialect": engine.dialect.name,
|
|
145
|
+
"success": True,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
except SQLAlchemyError as e:
|
|
149
|
+
result["error"] = f"Database error: {str(e)}"
|
|
150
|
+
except Exception as e:
|
|
151
|
+
result["error"] = str(e)
|
|
152
|
+
finally:
|
|
153
|
+
if engine is not None:
|
|
154
|
+
engine.dispose()
|
|
155
|
+
|
|
156
|
+
logger.info(f"Database inspection ({operation}) completed: success={result['success']}")
|
|
157
|
+
return result
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""MongoDB query tool for NoSQL database operations."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MongoDBQueryTool(Tool):
|
|
12
|
+
"""Query and manipulate MongoDB collections."""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
"""Initialize MongoDB query tool."""
|
|
16
|
+
metadata = ToolMetadata(
|
|
17
|
+
name="mongodb_query",
|
|
18
|
+
description="Query MongoDB collections with find, insert, update, delete operations",
|
|
19
|
+
category=ToolCategory.DATABASE,
|
|
20
|
+
tags=["mongodb", "nosql", "database", "query", "document"],
|
|
21
|
+
version="1.0.0",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parameters = [
|
|
25
|
+
ToolParameter(
|
|
26
|
+
name="operation",
|
|
27
|
+
type="string",
|
|
28
|
+
description="MongoDB operation",
|
|
29
|
+
required=True,
|
|
30
|
+
enum=["find", "find_one", "insert", "update", "delete", "count"],
|
|
31
|
+
),
|
|
32
|
+
ToolParameter(
|
|
33
|
+
name="connection_string",
|
|
34
|
+
type="string",
|
|
35
|
+
description="MongoDB connection string",
|
|
36
|
+
required=True,
|
|
37
|
+
),
|
|
38
|
+
ToolParameter(
|
|
39
|
+
name="database",
|
|
40
|
+
type="string",
|
|
41
|
+
description="Database name",
|
|
42
|
+
required=True,
|
|
43
|
+
),
|
|
44
|
+
ToolParameter(
|
|
45
|
+
name="collection",
|
|
46
|
+
type="string",
|
|
47
|
+
description="Collection name",
|
|
48
|
+
required=True,
|
|
49
|
+
),
|
|
50
|
+
ToolParameter(
|
|
51
|
+
name="filter",
|
|
52
|
+
type="object",
|
|
53
|
+
description="Query filter (for find, update, delete, count)",
|
|
54
|
+
required=False,
|
|
55
|
+
),
|
|
56
|
+
ToolParameter(
|
|
57
|
+
name="document",
|
|
58
|
+
type="object",
|
|
59
|
+
description="Document to insert or update data",
|
|
60
|
+
required=False,
|
|
61
|
+
),
|
|
62
|
+
ToolParameter(
|
|
63
|
+
name="limit",
|
|
64
|
+
type="number",
|
|
65
|
+
description="Maximum number of documents to return",
|
|
66
|
+
required=False,
|
|
67
|
+
default=100,
|
|
68
|
+
),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
super().__init__(metadata, parameters)
|
|
72
|
+
|
|
73
|
+
async def _execute(
|
|
74
|
+
self,
|
|
75
|
+
operation: str,
|
|
76
|
+
connection_string: str,
|
|
77
|
+
database: str,
|
|
78
|
+
collection: str,
|
|
79
|
+
filter: Optional[Dict[str, Any]] = None,
|
|
80
|
+
document: Optional[Dict[str, Any]] = None,
|
|
81
|
+
limit: int = 100,
|
|
82
|
+
) -> Dict[str, Any]:
|
|
83
|
+
"""Execute MongoDB operation.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
operation: Operation to perform
|
|
87
|
+
connection_string: MongoDB connection string
|
|
88
|
+
database: Database name
|
|
89
|
+
collection: Collection name
|
|
90
|
+
filter: Query filter
|
|
91
|
+
document: Document data
|
|
92
|
+
limit: Result limit
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Dictionary containing operation results
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
from pymongo import MongoClient
|
|
99
|
+
from pymongo.errors import PyMongoError
|
|
100
|
+
except ImportError:
|
|
101
|
+
raise ImportError(
|
|
102
|
+
"pymongo package not installed. Install with: pip install pymongo"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
result: Dict[str, Any] = {
|
|
106
|
+
"operation": operation,
|
|
107
|
+
"database": database,
|
|
108
|
+
"collection": collection,
|
|
109
|
+
"success": False,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
# Connect to MongoDB
|
|
114
|
+
client = MongoClient(connection_string)
|
|
115
|
+
db = client[database]
|
|
116
|
+
coll = db[collection]
|
|
117
|
+
|
|
118
|
+
if operation == "find":
|
|
119
|
+
query_filter = filter or {}
|
|
120
|
+
cursor = coll.find(query_filter).limit(limit)
|
|
121
|
+
documents = list(cursor)
|
|
122
|
+
|
|
123
|
+
# Convert ObjectId to string
|
|
124
|
+
for doc in documents:
|
|
125
|
+
if "_id" in doc:
|
|
126
|
+
doc["_id"] = str(doc["_id"])
|
|
127
|
+
|
|
128
|
+
result.update({
|
|
129
|
+
"documents": documents,
|
|
130
|
+
"count": len(documents),
|
|
131
|
+
"success": True,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
elif operation == "find_one":
|
|
135
|
+
query_filter = filter or {}
|
|
136
|
+
doc = coll.find_one(query_filter)
|
|
137
|
+
|
|
138
|
+
if doc and "_id" in doc:
|
|
139
|
+
doc["_id"] = str(doc["_id"])
|
|
140
|
+
|
|
141
|
+
result.update({
|
|
142
|
+
"document": doc,
|
|
143
|
+
"found": doc is not None,
|
|
144
|
+
"success": True,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
elif operation == "insert":
|
|
148
|
+
if not document:
|
|
149
|
+
raise ValueError("document parameter required for insert operation")
|
|
150
|
+
|
|
151
|
+
insert_result = coll.insert_one(document)
|
|
152
|
+
result.update({
|
|
153
|
+
"inserted_id": str(insert_result.inserted_id),
|
|
154
|
+
"success": True,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
elif operation == "update":
|
|
158
|
+
if not filter:
|
|
159
|
+
raise ValueError("filter parameter required for update operation")
|
|
160
|
+
if not document:
|
|
161
|
+
raise ValueError("document parameter required for update operation")
|
|
162
|
+
|
|
163
|
+
update_result = coll.update_many(filter, {"$set": document})
|
|
164
|
+
result.update({
|
|
165
|
+
"matched_count": update_result.matched_count,
|
|
166
|
+
"modified_count": update_result.modified_count,
|
|
167
|
+
"success": True,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
elif operation == "delete":
|
|
171
|
+
if not filter:
|
|
172
|
+
raise ValueError("filter parameter required for delete operation")
|
|
173
|
+
|
|
174
|
+
delete_result = coll.delete_many(filter)
|
|
175
|
+
result.update({
|
|
176
|
+
"deleted_count": delete_result.deleted_count,
|
|
177
|
+
"success": True,
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
elif operation == "count":
|
|
181
|
+
query_filter = filter or {}
|
|
182
|
+
count = coll.count_documents(query_filter)
|
|
183
|
+
result.update({
|
|
184
|
+
"count": count,
|
|
185
|
+
"success": True,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
client.close()
|
|
189
|
+
|
|
190
|
+
except PyMongoError as e:
|
|
191
|
+
result["error"] = f"MongoDB error: {str(e)}"
|
|
192
|
+
except Exception as e:
|
|
193
|
+
result["error"] = str(e)
|
|
194
|
+
|
|
195
|
+
logger.info(f"MongoDB {operation} completed: success={result['success']}")
|
|
196
|
+
return result
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Redis cache tool for caching operations."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RedisCacheTool(Tool):
|
|
12
|
+
"""Interact with Redis cache for get/set/delete operations."""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
"""Initialize Redis cache tool."""
|
|
16
|
+
metadata = ToolMetadata(
|
|
17
|
+
name="redis_cache",
|
|
18
|
+
description="Perform Redis cache operations (get, set, delete, exists)",
|
|
19
|
+
category=ToolCategory.DATABASE,
|
|
20
|
+
tags=["redis", "cache", "key-value", "memory", "storage"],
|
|
21
|
+
version="1.0.0",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parameters = [
|
|
25
|
+
ToolParameter(
|
|
26
|
+
name="operation",
|
|
27
|
+
type="string",
|
|
28
|
+
description="Cache operation to perform",
|
|
29
|
+
required=True,
|
|
30
|
+
enum=["get", "set", "delete", "exists", "keys", "ttl"],
|
|
31
|
+
),
|
|
32
|
+
ToolParameter(
|
|
33
|
+
name="key",
|
|
34
|
+
type="string",
|
|
35
|
+
description="Cache key",
|
|
36
|
+
required=True,
|
|
37
|
+
),
|
|
38
|
+
ToolParameter(
|
|
39
|
+
name="value",
|
|
40
|
+
type="string",
|
|
41
|
+
description="Value to set (for set operation)",
|
|
42
|
+
required=False,
|
|
43
|
+
),
|
|
44
|
+
ToolParameter(
|
|
45
|
+
name="ttl",
|
|
46
|
+
type="number",
|
|
47
|
+
description="Time to live in seconds (for set operation)",
|
|
48
|
+
required=False,
|
|
49
|
+
),
|
|
50
|
+
ToolParameter(
|
|
51
|
+
name="host",
|
|
52
|
+
type="string",
|
|
53
|
+
description="Redis host",
|
|
54
|
+
required=False,
|
|
55
|
+
default="localhost",
|
|
56
|
+
),
|
|
57
|
+
ToolParameter(
|
|
58
|
+
name="port",
|
|
59
|
+
type="number",
|
|
60
|
+
description="Redis port",
|
|
61
|
+
required=False,
|
|
62
|
+
default=6379,
|
|
63
|
+
),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
super().__init__(metadata, parameters)
|
|
67
|
+
|
|
68
|
+
async def _execute(
|
|
69
|
+
self,
|
|
70
|
+
operation: str,
|
|
71
|
+
key: str,
|
|
72
|
+
value: Optional[str] = None,
|
|
73
|
+
ttl: Optional[int] = None,
|
|
74
|
+
host: str = "localhost",
|
|
75
|
+
port: int = 6379,
|
|
76
|
+
) -> Dict[str, Any]:
|
|
77
|
+
"""Execute Redis cache operation.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
operation: Operation to perform
|
|
81
|
+
key: Cache key
|
|
82
|
+
value: Value to set
|
|
83
|
+
ttl: Time to live
|
|
84
|
+
host: Redis host
|
|
85
|
+
port: Redis port
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dictionary containing operation results
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
import redis
|
|
92
|
+
except ImportError:
|
|
93
|
+
raise ImportError(
|
|
94
|
+
"redis package not installed. Install with: pip install redis"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
result: Dict[str, Any] = {
|
|
98
|
+
"operation": operation,
|
|
99
|
+
"key": key,
|
|
100
|
+
"success": False,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
# Connect to Redis
|
|
105
|
+
client = redis.Redis(host=host, port=port, decode_responses=True)
|
|
106
|
+
|
|
107
|
+
if operation == "get":
|
|
108
|
+
cached_value = client.get(key)
|
|
109
|
+
result.update({
|
|
110
|
+
"value": cached_value,
|
|
111
|
+
"exists": cached_value is not None,
|
|
112
|
+
"success": True,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
elif operation == "set":
|
|
116
|
+
if value is None:
|
|
117
|
+
raise ValueError("value parameter required for set operation")
|
|
118
|
+
|
|
119
|
+
if ttl:
|
|
120
|
+
client.setex(key, ttl, value)
|
|
121
|
+
else:
|
|
122
|
+
client.set(key, value)
|
|
123
|
+
|
|
124
|
+
result.update({
|
|
125
|
+
"value": value,
|
|
126
|
+
"ttl": ttl,
|
|
127
|
+
"success": True,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
elif operation == "delete":
|
|
131
|
+
deleted_count = client.delete(key)
|
|
132
|
+
result.update({
|
|
133
|
+
"deleted": deleted_count > 0,
|
|
134
|
+
"success": True,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
elif operation == "exists":
|
|
138
|
+
exists = client.exists(key) > 0
|
|
139
|
+
result.update({
|
|
140
|
+
"exists": exists,
|
|
141
|
+
"success": True,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
elif operation == "keys":
|
|
145
|
+
# Use key as pattern
|
|
146
|
+
matching_keys = client.keys(key)
|
|
147
|
+
result.update({
|
|
148
|
+
"keys": matching_keys,
|
|
149
|
+
"count": len(matching_keys),
|
|
150
|
+
"success": True,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
elif operation == "ttl":
|
|
154
|
+
ttl_value = client.ttl(key)
|
|
155
|
+
result.update({
|
|
156
|
+
"ttl": ttl_value,
|
|
157
|
+
"exists": ttl_value >= 0,
|
|
158
|
+
"success": True,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
except redis.RedisError as e:
|
|
162
|
+
result["error"] = f"Redis error: {str(e)}"
|
|
163
|
+
except Exception as e:
|
|
164
|
+
result["error"] = str(e)
|
|
165
|
+
|
|
166
|
+
logger.info(f"Redis {operation} completed: success={result['success']}")
|
|
167
|
+
return result
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""SQL query tool for executing database queries."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SQLQueryTool(Tool):
|
|
12
|
+
"""Execute SQL queries on databases with safety controls."""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
"""Initialize SQL query tool."""
|
|
16
|
+
metadata = ToolMetadata(
|
|
17
|
+
name="sql_query",
|
|
18
|
+
description="Execute SQL queries on databases (PostgreSQL, MySQL, SQLite)",
|
|
19
|
+
category=ToolCategory.DATABASE,
|
|
20
|
+
tags=["sql", "database", "query", "postgres", "mysql", "sqlite"],
|
|
21
|
+
version="1.0.0",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parameters = [
|
|
25
|
+
ToolParameter(
|
|
26
|
+
name="query",
|
|
27
|
+
type="string",
|
|
28
|
+
description="SQL query to execute",
|
|
29
|
+
required=True,
|
|
30
|
+
),
|
|
31
|
+
ToolParameter(
|
|
32
|
+
name="connection_string",
|
|
33
|
+
type="string",
|
|
34
|
+
description="Database connection string (e.g., postgresql://user:pass@host/db)",
|
|
35
|
+
required=True,
|
|
36
|
+
),
|
|
37
|
+
ToolParameter(
|
|
38
|
+
name="read_only",
|
|
39
|
+
type="boolean",
|
|
40
|
+
description="Restrict to read-only queries (SELECT only)",
|
|
41
|
+
required=False,
|
|
42
|
+
default=True,
|
|
43
|
+
),
|
|
44
|
+
ToolParameter(
|
|
45
|
+
name="max_rows",
|
|
46
|
+
type="number",
|
|
47
|
+
description="Maximum number of rows to return",
|
|
48
|
+
required=False,
|
|
49
|
+
default=1000,
|
|
50
|
+
min_value=1,
|
|
51
|
+
max_value=10000,
|
|
52
|
+
),
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
super().__init__(metadata, parameters)
|
|
56
|
+
|
|
57
|
+
async def _execute(
|
|
58
|
+
self,
|
|
59
|
+
query: str,
|
|
60
|
+
connection_string: str,
|
|
61
|
+
read_only: bool = True,
|
|
62
|
+
max_rows: int = 1000,
|
|
63
|
+
) -> Dict[str, Any]:
|
|
64
|
+
"""Execute SQL query.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
query: SQL query
|
|
68
|
+
connection_string: Database connection string
|
|
69
|
+
read_only: Read-only mode flag
|
|
70
|
+
max_rows: Maximum rows to return
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dictionary containing query results
|
|
74
|
+
"""
|
|
75
|
+
engine = None
|
|
76
|
+
try:
|
|
77
|
+
from sqlalchemy import create_engine, text
|
|
78
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
79
|
+
except ImportError:
|
|
80
|
+
raise ImportError(
|
|
81
|
+
"sqlalchemy package not installed. Install with: pip install sqlalchemy"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
result: Dict[str, Any] = {
|
|
85
|
+
"query": query,
|
|
86
|
+
"read_only": read_only,
|
|
87
|
+
"success": False,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# Validate read-only mode
|
|
92
|
+
if read_only:
|
|
93
|
+
query_upper = query.strip().upper()
|
|
94
|
+
if not query_upper.startswith("SELECT"):
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"Only SELECT queries allowed in read-only mode"
|
|
97
|
+
)
|
|
98
|
+
# Check for dangerous keywords
|
|
99
|
+
dangerous_keywords = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "CREATE"]
|
|
100
|
+
if any(keyword in query_upper for keyword in dangerous_keywords):
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Query contains forbidden keywords in read-only mode"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Create engine
|
|
106
|
+
engine = create_engine(connection_string)
|
|
107
|
+
|
|
108
|
+
# Execute query
|
|
109
|
+
with engine.connect() as connection:
|
|
110
|
+
query_result = connection.execute(text(query))
|
|
111
|
+
|
|
112
|
+
# Fetch results for SELECT queries
|
|
113
|
+
if query_upper.startswith("SELECT"):
|
|
114
|
+
rows = query_result.fetchmany(max_rows)
|
|
115
|
+
columns = list(query_result.keys())
|
|
116
|
+
|
|
117
|
+
# Convert to list of dictionaries
|
|
118
|
+
data = [dict(zip(columns, row)) for row in rows]
|
|
119
|
+
|
|
120
|
+
result.update({
|
|
121
|
+
"data": data,
|
|
122
|
+
"columns": columns,
|
|
123
|
+
"row_count": len(data),
|
|
124
|
+
"truncated": len(data) == max_rows,
|
|
125
|
+
})
|
|
126
|
+
else:
|
|
127
|
+
# For non-SELECT queries
|
|
128
|
+
result.update({
|
|
129
|
+
"rows_affected": query_result.rowcount,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
result["success"] = True
|
|
133
|
+
|
|
134
|
+
except SQLAlchemyError as e:
|
|
135
|
+
result["error"] = f"Database error: {str(e)}"
|
|
136
|
+
except ValueError as e:
|
|
137
|
+
result["error"] = str(e)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
result["error"] = f"Unexpected error: {str(e)}"
|
|
140
|
+
finally:
|
|
141
|
+
if engine is not None:
|
|
142
|
+
engine.dispose()
|
|
143
|
+
|
|
144
|
+
logger.info(f"SQL query executed: success={result['success']}")
|
|
145
|
+
return result
|