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,102 @@
|
|
|
1
|
+
"""File reader tool for reading file contents."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileReaderTool(Tool):
|
|
13
|
+
"""Tool for reading file contents."""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
"""Initialize file reader tool."""
|
|
17
|
+
metadata = ToolMetadata(
|
|
18
|
+
name="file_reader",
|
|
19
|
+
description="Read contents from a file",
|
|
20
|
+
category=ToolCategory.FILE,
|
|
21
|
+
tags=["file", "read", "io", "text"],
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parameters = [
|
|
25
|
+
ToolParameter(
|
|
26
|
+
name="path",
|
|
27
|
+
type="string",
|
|
28
|
+
description="Path to the file to read",
|
|
29
|
+
),
|
|
30
|
+
ToolParameter(
|
|
31
|
+
name="encoding",
|
|
32
|
+
type="string",
|
|
33
|
+
description="File encoding (default: utf-8)",
|
|
34
|
+
required=False,
|
|
35
|
+
default="utf-8",
|
|
36
|
+
),
|
|
37
|
+
ToolParameter(
|
|
38
|
+
name="max_size",
|
|
39
|
+
type="number",
|
|
40
|
+
description="Maximum file size in bytes (default: 10MB)",
|
|
41
|
+
required=False,
|
|
42
|
+
default=10 * 1024 * 1024, # 10MB
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
super().__init__(metadata, parameters)
|
|
47
|
+
|
|
48
|
+
async def _execute(
|
|
49
|
+
self, path: str, encoding: str = "utf-8", max_size: int = 10 * 1024 * 1024
|
|
50
|
+
) -> Any:
|
|
51
|
+
"""Read file contents.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
path: File path
|
|
55
|
+
encoding: File encoding
|
|
56
|
+
max_size: Maximum file size in bytes
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
File contents and metadata
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
FileNotFoundError: If file doesn't exist
|
|
63
|
+
ValueError: If file is too large
|
|
64
|
+
"""
|
|
65
|
+
file_path = Path(path)
|
|
66
|
+
|
|
67
|
+
# Check if file exists
|
|
68
|
+
if not file_path.exists():
|
|
69
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
70
|
+
|
|
71
|
+
# Check if it's a file
|
|
72
|
+
if not file_path.is_file():
|
|
73
|
+
raise ValueError(f"Path is not a file: {path}")
|
|
74
|
+
|
|
75
|
+
# Check file size
|
|
76
|
+
file_size = file_path.stat().st_size
|
|
77
|
+
if file_size > max_size:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"File too large: {file_size} bytes (max: {max_size} bytes)"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
# Read file contents
|
|
84
|
+
with open(file_path, "r", encoding=encoding) as f:
|
|
85
|
+
content = f.read()
|
|
86
|
+
|
|
87
|
+
logger.info(f"Read file: {path} ({file_size} bytes)")
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
"path": str(file_path),
|
|
91
|
+
"content": content,
|
|
92
|
+
"size": file_size,
|
|
93
|
+
"encoding": encoding,
|
|
94
|
+
"lines": len(content.splitlines()),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
except UnicodeDecodeError as e:
|
|
98
|
+
logger.error(f"Encoding error reading {path}: {e}")
|
|
99
|
+
raise ValueError(f"Failed to decode file with encoding {encoding}: {e}")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.error(f"Error reading file {path}: {e}")
|
|
102
|
+
raise
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""File writer tool for writing content to files."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileWriterTool(Tool):
|
|
13
|
+
"""Write content to files with various encoding options."""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
"""Initialize file writer tool."""
|
|
17
|
+
metadata = ToolMetadata(
|
|
18
|
+
name="file_writer",
|
|
19
|
+
description="Write text content to files with encoding support",
|
|
20
|
+
category=ToolCategory.FILE,
|
|
21
|
+
tags=["file", "write", "io", "save", "output"],
|
|
22
|
+
version="1.0.0",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
parameters = [
|
|
26
|
+
ToolParameter(
|
|
27
|
+
name="path",
|
|
28
|
+
type="string",
|
|
29
|
+
description="Path where file should be written",
|
|
30
|
+
required=True,
|
|
31
|
+
),
|
|
32
|
+
ToolParameter(
|
|
33
|
+
name="content",
|
|
34
|
+
type="string",
|
|
35
|
+
description="Content to write to file",
|
|
36
|
+
required=True,
|
|
37
|
+
),
|
|
38
|
+
ToolParameter(
|
|
39
|
+
name="encoding",
|
|
40
|
+
type="string",
|
|
41
|
+
description="File encoding",
|
|
42
|
+
required=False,
|
|
43
|
+
default="utf-8",
|
|
44
|
+
enum=["utf-8", "ascii", "latin-1", "utf-16"],
|
|
45
|
+
),
|
|
46
|
+
ToolParameter(
|
|
47
|
+
name="mode",
|
|
48
|
+
type="string",
|
|
49
|
+
description="Write mode",
|
|
50
|
+
required=False,
|
|
51
|
+
default="write",
|
|
52
|
+
enum=["write", "append"],
|
|
53
|
+
),
|
|
54
|
+
ToolParameter(
|
|
55
|
+
name="create_dirs",
|
|
56
|
+
type="boolean",
|
|
57
|
+
description="Create parent directories if they don't exist",
|
|
58
|
+
required=False,
|
|
59
|
+
default=True,
|
|
60
|
+
),
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
super().__init__(metadata, parameters)
|
|
64
|
+
|
|
65
|
+
async def _execute(
|
|
66
|
+
self,
|
|
67
|
+
path: str,
|
|
68
|
+
content: str,
|
|
69
|
+
encoding: str = "utf-8",
|
|
70
|
+
mode: str = "write",
|
|
71
|
+
create_dirs: bool = True,
|
|
72
|
+
) -> Dict[str, Any]:
|
|
73
|
+
"""Execute file writing.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
file_path: Path to write file
|
|
77
|
+
content: Content to write
|
|
78
|
+
encoding: File encoding
|
|
79
|
+
mode: Write mode (write or append)
|
|
80
|
+
create_dirs: Create directories flag
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Dictionary containing write results
|
|
84
|
+
"""
|
|
85
|
+
result: Dict[str, Any] = {
|
|
86
|
+
"path": path,
|
|
87
|
+
"success": False,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# Create parent directories if needed
|
|
92
|
+
if create_dirs:
|
|
93
|
+
parent_dir = os.path.dirname(path)
|
|
94
|
+
if parent_dir and not os.path.exists(parent_dir):
|
|
95
|
+
os.makedirs(parent_dir, exist_ok=True)
|
|
96
|
+
result["created_directories"] = True
|
|
97
|
+
|
|
98
|
+
# Determine write mode
|
|
99
|
+
write_mode = "w" if mode == "write" else "a"
|
|
100
|
+
|
|
101
|
+
# Write file
|
|
102
|
+
with open(path, write_mode, encoding=encoding) as f:
|
|
103
|
+
f.write(content)
|
|
104
|
+
|
|
105
|
+
# Get file info
|
|
106
|
+
file_size = os.path.getsize(path)
|
|
107
|
+
|
|
108
|
+
result.update({
|
|
109
|
+
"success": True,
|
|
110
|
+
"bytes_written": len(content.encode(encoding)),
|
|
111
|
+
"file_size": file_size,
|
|
112
|
+
"encoding": encoding,
|
|
113
|
+
"mode": mode,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
except PermissionError:
|
|
117
|
+
result["error"] = f"Permission denied: {path}"
|
|
118
|
+
except Exception as e:
|
|
119
|
+
result["error"] = str(e)
|
|
120
|
+
|
|
121
|
+
logger.info(f"File write completed: success={result['success']}")
|
|
122
|
+
return result
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Image processor tool for analyzing and manipulating images."""
|
|
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 ImageProcessorTool(Tool):
|
|
12
|
+
"""Process and analyze images (resize, format conversion, metadata extraction)."""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
"""Initialize image processor tool."""
|
|
16
|
+
metadata = ToolMetadata(
|
|
17
|
+
name="image_processor",
|
|
18
|
+
description="Analyze image properties, resize, convert formats, and extract metadata",
|
|
19
|
+
category=ToolCategory.FILE,
|
|
20
|
+
tags=["image", "processing", "resize", "convert", "metadata"],
|
|
21
|
+
version="1.0.0",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parameters = [
|
|
25
|
+
ToolParameter(
|
|
26
|
+
name="file_path",
|
|
27
|
+
type="string",
|
|
28
|
+
description="Path to image file",
|
|
29
|
+
required=True,
|
|
30
|
+
),
|
|
31
|
+
ToolParameter(
|
|
32
|
+
name="operation",
|
|
33
|
+
type="string",
|
|
34
|
+
description="Operation to perform",
|
|
35
|
+
required=True,
|
|
36
|
+
enum=["analyze", "resize", "convert", "thumbnail"],
|
|
37
|
+
),
|
|
38
|
+
ToolParameter(
|
|
39
|
+
name="width",
|
|
40
|
+
type="number",
|
|
41
|
+
description="Target width (for resize/thumbnail)",
|
|
42
|
+
required=False,
|
|
43
|
+
),
|
|
44
|
+
ToolParameter(
|
|
45
|
+
name="height",
|
|
46
|
+
type="number",
|
|
47
|
+
description="Target height (for resize/thumbnail)",
|
|
48
|
+
required=False,
|
|
49
|
+
),
|
|
50
|
+
ToolParameter(
|
|
51
|
+
name="output_format",
|
|
52
|
+
type="string",
|
|
53
|
+
description="Output format (for convert)",
|
|
54
|
+
required=False,
|
|
55
|
+
enum=["JPEG", "PNG", "GIF", "BMP", "WEBP"],
|
|
56
|
+
),
|
|
57
|
+
ToolParameter(
|
|
58
|
+
name="output_path",
|
|
59
|
+
type="string",
|
|
60
|
+
description="Output file path (for resize/convert/thumbnail)",
|
|
61
|
+
required=False,
|
|
62
|
+
),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
super().__init__(metadata, parameters)
|
|
66
|
+
|
|
67
|
+
async def _execute(
|
|
68
|
+
self,
|
|
69
|
+
file_path: str,
|
|
70
|
+
operation: str,
|
|
71
|
+
width: Optional[int] = None,
|
|
72
|
+
height: Optional[int] = None,
|
|
73
|
+
output_format: Optional[str] = None,
|
|
74
|
+
output_path: Optional[str] = None,
|
|
75
|
+
) -> Dict[str, Any]:
|
|
76
|
+
"""Execute image processing.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
file_path: Path to image file
|
|
80
|
+
operation: Operation to perform
|
|
81
|
+
width: Target width
|
|
82
|
+
height: Target height
|
|
83
|
+
output_format: Output format
|
|
84
|
+
output_path: Output file path
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Dictionary containing processing results
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
from PIL import Image
|
|
91
|
+
except ImportError:
|
|
92
|
+
raise ImportError(
|
|
93
|
+
"Pillow package not installed. Install with: pip install Pillow"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
result: Dict[str, Any] = {
|
|
97
|
+
"file_path": file_path,
|
|
98
|
+
"operation": operation,
|
|
99
|
+
"success": False,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Open image
|
|
104
|
+
img = Image.open(file_path)
|
|
105
|
+
|
|
106
|
+
if operation == "analyze":
|
|
107
|
+
# Extract image information
|
|
108
|
+
result.update({
|
|
109
|
+
"format": img.format,
|
|
110
|
+
"mode": img.mode,
|
|
111
|
+
"size": {"width": img.width, "height": img.height},
|
|
112
|
+
"aspect_ratio": round(img.width / img.height, 2),
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
# Extract EXIF data if available
|
|
116
|
+
if hasattr(img, "_getexif") and img._getexif():
|
|
117
|
+
exif_data = img._getexif()
|
|
118
|
+
result["exif"] = {k: str(v) for k, v in exif_data.items() if v}
|
|
119
|
+
|
|
120
|
+
result["success"] = True
|
|
121
|
+
|
|
122
|
+
elif operation == "resize":
|
|
123
|
+
if not width or not height:
|
|
124
|
+
raise ValueError("width and height required for resize operation")
|
|
125
|
+
|
|
126
|
+
if not output_path:
|
|
127
|
+
raise ValueError("output_path required for resize operation")
|
|
128
|
+
|
|
129
|
+
# Resize image
|
|
130
|
+
resized_img = img.resize((width, height), Image.Resampling.LANCZOS)
|
|
131
|
+
resized_img.save(output_path)
|
|
132
|
+
|
|
133
|
+
result.update({
|
|
134
|
+
"original_size": {"width": img.width, "height": img.height},
|
|
135
|
+
"new_size": {"width": width, "height": height},
|
|
136
|
+
"output_path": output_path,
|
|
137
|
+
"success": True,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
elif operation == "convert":
|
|
141
|
+
if not output_format:
|
|
142
|
+
raise ValueError("output_format required for convert operation")
|
|
143
|
+
|
|
144
|
+
if not output_path:
|
|
145
|
+
raise ValueError("output_path required for convert operation")
|
|
146
|
+
|
|
147
|
+
# Convert format
|
|
148
|
+
if img.mode == "RGBA" and output_format == "JPEG":
|
|
149
|
+
# Convert RGBA to RGB for JPEG
|
|
150
|
+
img = img.convert("RGB")
|
|
151
|
+
|
|
152
|
+
img.save(output_path, format=output_format)
|
|
153
|
+
|
|
154
|
+
result.update({
|
|
155
|
+
"original_format": img.format,
|
|
156
|
+
"new_format": output_format,
|
|
157
|
+
"output_path": output_path,
|
|
158
|
+
"success": True,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
elif operation == "thumbnail":
|
|
162
|
+
if not width or not height:
|
|
163
|
+
raise ValueError("width and height required for thumbnail operation")
|
|
164
|
+
|
|
165
|
+
if not output_path:
|
|
166
|
+
raise ValueError("output_path required for thumbnail operation")
|
|
167
|
+
|
|
168
|
+
# Create thumbnail (maintains aspect ratio)
|
|
169
|
+
img_copy = img.copy()
|
|
170
|
+
img_copy.thumbnail((width, height), Image.Resampling.LANCZOS)
|
|
171
|
+
img_copy.save(output_path)
|
|
172
|
+
|
|
173
|
+
result.update({
|
|
174
|
+
"original_size": {"width": img.width, "height": img.height},
|
|
175
|
+
"thumbnail_size": {"width": img_copy.width, "height": img_copy.height},
|
|
176
|
+
"output_path": output_path,
|
|
177
|
+
"success": True,
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
except FileNotFoundError:
|
|
181
|
+
result["error"] = f"File not found: {file_path}"
|
|
182
|
+
except Exception as e:
|
|
183
|
+
result["error"] = str(e)
|
|
184
|
+
|
|
185
|
+
logger.info(f"Image {operation} completed: success={result['success']}")
|
|
186
|
+
return result
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""PDF parser tool for extracting text and metadata from PDF files."""
|
|
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 PDFParserTool(Tool):
|
|
12
|
+
"""Extract text, metadata, and structure from PDF documents."""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
"""Initialize PDF parser tool."""
|
|
16
|
+
metadata = ToolMetadata(
|
|
17
|
+
name="pdf_parser",
|
|
18
|
+
description="Extract text, metadata, and structure from PDF files",
|
|
19
|
+
category=ToolCategory.FILE,
|
|
20
|
+
tags=["pdf", "parser", "document", "extraction", "text"],
|
|
21
|
+
version="1.0.0",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parameters = [
|
|
25
|
+
ToolParameter(
|
|
26
|
+
name="file_path",
|
|
27
|
+
type="string",
|
|
28
|
+
description="Path to PDF file",
|
|
29
|
+
required=True,
|
|
30
|
+
),
|
|
31
|
+
ToolParameter(
|
|
32
|
+
name="extract_text",
|
|
33
|
+
type="boolean",
|
|
34
|
+
description="Whether to extract text content",
|
|
35
|
+
required=False,
|
|
36
|
+
default=True,
|
|
37
|
+
),
|
|
38
|
+
ToolParameter(
|
|
39
|
+
name="extract_metadata",
|
|
40
|
+
type="boolean",
|
|
41
|
+
description="Whether to extract PDF metadata",
|
|
42
|
+
required=False,
|
|
43
|
+
default=True,
|
|
44
|
+
),
|
|
45
|
+
ToolParameter(
|
|
46
|
+
name="page_range",
|
|
47
|
+
type="string",
|
|
48
|
+
description="Page range to extract (e.g., '1-5' or 'all')",
|
|
49
|
+
required=False,
|
|
50
|
+
default="all",
|
|
51
|
+
),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
super().__init__(metadata, parameters)
|
|
55
|
+
|
|
56
|
+
async def _execute(
|
|
57
|
+
self,
|
|
58
|
+
file_path: str,
|
|
59
|
+
extract_text: bool = True,
|
|
60
|
+
extract_metadata: bool = True,
|
|
61
|
+
page_range: str = "all",
|
|
62
|
+
) -> Dict[str, Any]:
|
|
63
|
+
"""Execute PDF parsing.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
file_path: Path to PDF file
|
|
67
|
+
extract_text: Extract text flag
|
|
68
|
+
extract_metadata: Extract metadata flag
|
|
69
|
+
page_range: Page range to extract
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Dictionary containing extracted data
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
import PyPDF2
|
|
76
|
+
except ImportError:
|
|
77
|
+
raise ImportError(
|
|
78
|
+
"PyPDF2 package not installed. Install with: pip install PyPDF2"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
result: Dict[str, Any] = {
|
|
82
|
+
"file_path": file_path,
|
|
83
|
+
"success": False,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
with open(file_path, "rb") as file:
|
|
88
|
+
pdf_reader = PyPDF2.PdfReader(file)
|
|
89
|
+
|
|
90
|
+
# Get page count
|
|
91
|
+
num_pages = len(pdf_reader.pages)
|
|
92
|
+
result["page_count"] = num_pages
|
|
93
|
+
|
|
94
|
+
# Extract metadata
|
|
95
|
+
if extract_metadata:
|
|
96
|
+
metadata = pdf_reader.metadata
|
|
97
|
+
if metadata:
|
|
98
|
+
result["metadata"] = {
|
|
99
|
+
"title": metadata.get("/Title", ""),
|
|
100
|
+
"author": metadata.get("/Author", ""),
|
|
101
|
+
"subject": metadata.get("/Subject", ""),
|
|
102
|
+
"creator": metadata.get("/Creator", ""),
|
|
103
|
+
"producer": metadata.get("/Producer", ""),
|
|
104
|
+
"creation_date": str(metadata.get("/CreationDate", "")),
|
|
105
|
+
"modification_date": str(metadata.get("/ModDate", "")),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Determine pages to extract
|
|
109
|
+
if page_range == "all":
|
|
110
|
+
pages_to_extract = range(num_pages)
|
|
111
|
+
else:
|
|
112
|
+
# Parse page range (e.g., "1-5")
|
|
113
|
+
if "-" in page_range:
|
|
114
|
+
start, end = page_range.split("-")
|
|
115
|
+
pages_to_extract = range(int(start) - 1, min(int(end), num_pages))
|
|
116
|
+
else:
|
|
117
|
+
page_num = int(page_range) - 1
|
|
118
|
+
pages_to_extract = [page_num] if 0 <= page_num < num_pages else []
|
|
119
|
+
|
|
120
|
+
# Extract text
|
|
121
|
+
if extract_text:
|
|
122
|
+
pages_text = []
|
|
123
|
+
for page_num in pages_to_extract:
|
|
124
|
+
page = pdf_reader.pages[page_num]
|
|
125
|
+
text = page.extract_text()
|
|
126
|
+
pages_text.append({
|
|
127
|
+
"page_number": page_num + 1,
|
|
128
|
+
"text": text,
|
|
129
|
+
"char_count": len(text),
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
result["pages"] = pages_text
|
|
133
|
+
result["total_text"] = "\n\n".join([p["text"] for p in pages_text])
|
|
134
|
+
result["extracted_pages"] = len(pages_text)
|
|
135
|
+
|
|
136
|
+
result["success"] = True
|
|
137
|
+
|
|
138
|
+
except FileNotFoundError:
|
|
139
|
+
result["error"] = f"File not found: {file_path}"
|
|
140
|
+
except Exception as e:
|
|
141
|
+
result["error"] = str(e)
|
|
142
|
+
|
|
143
|
+
logger.info(f"PDF parsing completed: success={result['success']}")
|
|
144
|
+
return result
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Test tools for playground validation."""
|
|
2
|
+
|
|
3
|
+
from genxai.tools.builtin.test.simple_math import SimpleMathTool
|
|
4
|
+
from genxai.tools.builtin.test.string_processor import StringProcessorTool
|
|
5
|
+
from genxai.tools.builtin.test.data_transformer import DataTransformerTool
|
|
6
|
+
from genxai.tools.builtin.test.async_simulator import AsyncSimulatorTool
|
|
7
|
+
from genxai.tools.builtin.test.error_generator import ErrorGeneratorTool
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"SimpleMathTool",
|
|
11
|
+
"StringProcessorTool",
|
|
12
|
+
"DataTransformerTool",
|
|
13
|
+
"AsyncSimulatorTool",
|
|
14
|
+
"ErrorGeneratorTool",
|
|
15
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Async simulator tool for testing long-running operations and timeouts."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
import asyncio
|
|
5
|
+
from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AsyncSimulatorTool(Tool):
|
|
9
|
+
"""Tool to simulate asynchronous operations and test timeout handling."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Initialize async simulator tool."""
|
|
13
|
+
metadata = ToolMetadata(
|
|
14
|
+
name="async_simulator",
|
|
15
|
+
description="Simulate long-running async operations to test timeout and async handling",
|
|
16
|
+
category=ToolCategory.SYSTEM,
|
|
17
|
+
tags=["async", "timeout", "test", "simulator"],
|
|
18
|
+
version="1.0.0",
|
|
19
|
+
author="GenXAI",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
parameters = [
|
|
23
|
+
ToolParameter(
|
|
24
|
+
name="duration",
|
|
25
|
+
type="number",
|
|
26
|
+
description="Duration to simulate in seconds",
|
|
27
|
+
required=True,
|
|
28
|
+
min_value=0,
|
|
29
|
+
max_value=60,
|
|
30
|
+
),
|
|
31
|
+
ToolParameter(
|
|
32
|
+
name="message",
|
|
33
|
+
type="string",
|
|
34
|
+
description="Message to return after completion",
|
|
35
|
+
required=False,
|
|
36
|
+
default="Operation completed",
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
super().__init__(metadata, parameters)
|
|
41
|
+
|
|
42
|
+
async def _execute(self, **kwargs: Any) -> Any:
|
|
43
|
+
"""Execute the async simulation.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
**kwargs: Tool parameters (duration, message)
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Result after simulated delay
|
|
50
|
+
"""
|
|
51
|
+
duration = kwargs.get("duration", 1)
|
|
52
|
+
message = kwargs.get("message", "Operation completed")
|
|
53
|
+
|
|
54
|
+
# Simulate async work
|
|
55
|
+
await asyncio.sleep(duration)
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
"duration": duration,
|
|
59
|
+
"message": message,
|
|
60
|
+
"status": "completed",
|
|
61
|
+
"simulated": True,
|
|
62
|
+
}
|