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.
Files changed (156) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +6 -0
  3. cli/commands/approval.py +85 -0
  4. cli/commands/audit.py +127 -0
  5. cli/commands/metrics.py +25 -0
  6. cli/commands/tool.py +389 -0
  7. cli/main.py +32 -0
  8. genxai/__init__.py +81 -0
  9. genxai/api/__init__.py +5 -0
  10. genxai/api/app.py +21 -0
  11. genxai/config/__init__.py +5 -0
  12. genxai/config/settings.py +37 -0
  13. genxai/connectors/__init__.py +19 -0
  14. genxai/connectors/base.py +122 -0
  15. genxai/connectors/kafka.py +92 -0
  16. genxai/connectors/postgres_cdc.py +95 -0
  17. genxai/connectors/registry.py +44 -0
  18. genxai/connectors/sqs.py +94 -0
  19. genxai/connectors/webhook.py +73 -0
  20. genxai/core/__init__.py +37 -0
  21. genxai/core/agent/__init__.py +32 -0
  22. genxai/core/agent/base.py +206 -0
  23. genxai/core/agent/config_io.py +59 -0
  24. genxai/core/agent/registry.py +98 -0
  25. genxai/core/agent/runtime.py +970 -0
  26. genxai/core/communication/__init__.py +6 -0
  27. genxai/core/communication/collaboration.py +44 -0
  28. genxai/core/communication/message_bus.py +192 -0
  29. genxai/core/communication/protocols.py +35 -0
  30. genxai/core/execution/__init__.py +22 -0
  31. genxai/core/execution/metadata.py +181 -0
  32. genxai/core/execution/queue.py +201 -0
  33. genxai/core/graph/__init__.py +30 -0
  34. genxai/core/graph/checkpoints.py +77 -0
  35. genxai/core/graph/edges.py +131 -0
  36. genxai/core/graph/engine.py +813 -0
  37. genxai/core/graph/executor.py +516 -0
  38. genxai/core/graph/nodes.py +161 -0
  39. genxai/core/graph/trigger_runner.py +40 -0
  40. genxai/core/memory/__init__.py +19 -0
  41. genxai/core/memory/base.py +72 -0
  42. genxai/core/memory/embedding.py +327 -0
  43. genxai/core/memory/episodic.py +448 -0
  44. genxai/core/memory/long_term.py +467 -0
  45. genxai/core/memory/manager.py +543 -0
  46. genxai/core/memory/persistence.py +297 -0
  47. genxai/core/memory/procedural.py +461 -0
  48. genxai/core/memory/semantic.py +526 -0
  49. genxai/core/memory/shared.py +62 -0
  50. genxai/core/memory/short_term.py +303 -0
  51. genxai/core/memory/vector_store.py +508 -0
  52. genxai/core/memory/working.py +211 -0
  53. genxai/core/state/__init__.py +6 -0
  54. genxai/core/state/manager.py +293 -0
  55. genxai/core/state/schema.py +115 -0
  56. genxai/llm/__init__.py +14 -0
  57. genxai/llm/base.py +150 -0
  58. genxai/llm/factory.py +329 -0
  59. genxai/llm/providers/__init__.py +1 -0
  60. genxai/llm/providers/anthropic.py +249 -0
  61. genxai/llm/providers/cohere.py +274 -0
  62. genxai/llm/providers/google.py +334 -0
  63. genxai/llm/providers/ollama.py +147 -0
  64. genxai/llm/providers/openai.py +257 -0
  65. genxai/llm/routing.py +83 -0
  66. genxai/observability/__init__.py +6 -0
  67. genxai/observability/logging.py +327 -0
  68. genxai/observability/metrics.py +494 -0
  69. genxai/observability/tracing.py +372 -0
  70. genxai/performance/__init__.py +39 -0
  71. genxai/performance/cache.py +256 -0
  72. genxai/performance/pooling.py +289 -0
  73. genxai/security/audit.py +304 -0
  74. genxai/security/auth.py +315 -0
  75. genxai/security/cost_control.py +528 -0
  76. genxai/security/default_policies.py +44 -0
  77. genxai/security/jwt.py +142 -0
  78. genxai/security/oauth.py +226 -0
  79. genxai/security/pii.py +366 -0
  80. genxai/security/policy_engine.py +82 -0
  81. genxai/security/rate_limit.py +341 -0
  82. genxai/security/rbac.py +247 -0
  83. genxai/security/validation.py +218 -0
  84. genxai/tools/__init__.py +21 -0
  85. genxai/tools/base.py +383 -0
  86. genxai/tools/builtin/__init__.py +131 -0
  87. genxai/tools/builtin/communication/__init__.py +15 -0
  88. genxai/tools/builtin/communication/email_sender.py +159 -0
  89. genxai/tools/builtin/communication/notification_manager.py +167 -0
  90. genxai/tools/builtin/communication/slack_notifier.py +118 -0
  91. genxai/tools/builtin/communication/sms_sender.py +118 -0
  92. genxai/tools/builtin/communication/webhook_caller.py +136 -0
  93. genxai/tools/builtin/computation/__init__.py +15 -0
  94. genxai/tools/builtin/computation/calculator.py +101 -0
  95. genxai/tools/builtin/computation/code_executor.py +183 -0
  96. genxai/tools/builtin/computation/data_validator.py +259 -0
  97. genxai/tools/builtin/computation/hash_generator.py +129 -0
  98. genxai/tools/builtin/computation/regex_matcher.py +201 -0
  99. genxai/tools/builtin/data/__init__.py +15 -0
  100. genxai/tools/builtin/data/csv_processor.py +213 -0
  101. genxai/tools/builtin/data/data_transformer.py +299 -0
  102. genxai/tools/builtin/data/json_processor.py +233 -0
  103. genxai/tools/builtin/data/text_analyzer.py +288 -0
  104. genxai/tools/builtin/data/xml_processor.py +175 -0
  105. genxai/tools/builtin/database/__init__.py +15 -0
  106. genxai/tools/builtin/database/database_inspector.py +157 -0
  107. genxai/tools/builtin/database/mongodb_query.py +196 -0
  108. genxai/tools/builtin/database/redis_cache.py +167 -0
  109. genxai/tools/builtin/database/sql_query.py +145 -0
  110. genxai/tools/builtin/database/vector_search.py +163 -0
  111. genxai/tools/builtin/file/__init__.py +17 -0
  112. genxai/tools/builtin/file/directory_scanner.py +214 -0
  113. genxai/tools/builtin/file/file_compressor.py +237 -0
  114. genxai/tools/builtin/file/file_reader.py +102 -0
  115. genxai/tools/builtin/file/file_writer.py +122 -0
  116. genxai/tools/builtin/file/image_processor.py +186 -0
  117. genxai/tools/builtin/file/pdf_parser.py +144 -0
  118. genxai/tools/builtin/test/__init__.py +15 -0
  119. genxai/tools/builtin/test/async_simulator.py +62 -0
  120. genxai/tools/builtin/test/data_transformer.py +99 -0
  121. genxai/tools/builtin/test/error_generator.py +82 -0
  122. genxai/tools/builtin/test/simple_math.py +94 -0
  123. genxai/tools/builtin/test/string_processor.py +72 -0
  124. genxai/tools/builtin/web/__init__.py +15 -0
  125. genxai/tools/builtin/web/api_caller.py +161 -0
  126. genxai/tools/builtin/web/html_parser.py +330 -0
  127. genxai/tools/builtin/web/http_client.py +187 -0
  128. genxai/tools/builtin/web/url_validator.py +162 -0
  129. genxai/tools/builtin/web/web_scraper.py +170 -0
  130. genxai/tools/custom/my_test_tool_2.py +9 -0
  131. genxai/tools/dynamic.py +105 -0
  132. genxai/tools/mcp_server.py +167 -0
  133. genxai/tools/persistence/__init__.py +6 -0
  134. genxai/tools/persistence/models.py +55 -0
  135. genxai/tools/persistence/service.py +322 -0
  136. genxai/tools/registry.py +227 -0
  137. genxai/tools/security/__init__.py +11 -0
  138. genxai/tools/security/limits.py +214 -0
  139. genxai/tools/security/policy.py +20 -0
  140. genxai/tools/security/sandbox.py +248 -0
  141. genxai/tools/templates.py +435 -0
  142. genxai/triggers/__init__.py +19 -0
  143. genxai/triggers/base.py +104 -0
  144. genxai/triggers/file_watcher.py +75 -0
  145. genxai/triggers/queue.py +68 -0
  146. genxai/triggers/registry.py +82 -0
  147. genxai/triggers/schedule.py +66 -0
  148. genxai/triggers/webhook.py +68 -0
  149. genxai/utils/__init__.py +1 -0
  150. genxai/utils/tokens.py +295 -0
  151. genxai_framework-0.1.0.dist-info/METADATA +495 -0
  152. genxai_framework-0.1.0.dist-info/RECORD +156 -0
  153. genxai_framework-0.1.0.dist-info/WHEEL +5 -0
  154. genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
  155. genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
  156. 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